Merge branch 'dev' into 6023-changeRol
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Guillermo Bonet 2023-11-28 12:07:50 +01:00
commit 1a5cc67312
144 changed files with 5548 additions and 3762 deletions

View File

@ -5,12 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2348.01] - 2023-11-30 ## [2350.01] - 2023-12-14
### Added ### Added
### Changed ### Changed
### Fixed ### Fixed
## [2348.01] - 2023-11-30
### Added
- (Ticket -> Adelantar) Permite mover lineas sin generar negativos
- (Ticket -> Adelantar) Permite modificar la fecha de los tickets
- (Trabajadores -> Notificaciones) Nueva sección (lilium)
### Changed
### Fixed
- (Ticket -> RocketChat) Arreglada detección de cambios
## [2346.01] - 2023-11-16 ## [2346.01] - 2023-11-16
### Added ### Added

View File

@ -1,4 +1,4 @@
FROM debian:bullseye-slim FROM debian:bookworm-slim
ENV TZ Europe/Madrid ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
@ -25,7 +25,13 @@ RUN apt-get update \
libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \ libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \
libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \ libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \ fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
# Extra dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
samba-common-bin samba-dsdb-modules\
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& npm -g install pm2 && npm -g install pm2

View File

@ -8,7 +8,7 @@ Salix is also the scientific name of a beautifull tree! :)
Required applications. Required applications.
* Node.js >= 16.x LTS * Node.js
* Docker * Docker
* Git * Git
@ -17,20 +17,7 @@ You will need to install globally the following items.
$ sudo npm install -g jest gulp-cli $ sudo npm install -g jest gulp-cli
``` ```
For the usage of jest --watch on macOs. ## Installing dependencies and launching
```
$ brew install watchman
```
* [watchman](https://facebook.github.io/watchman/)
## Linux Only Prerequisites
Your user must be on the docker group to use it so you will need to run this command:
```
$ sudo usermod -a -G docker yourusername
```
## Getting Started // Installing
Pull from repository. Pull from repository.
@ -76,29 +63,6 @@ In Visual Studio Code we use the ESLint extension.
ext install dbaeumer.vscode-eslint ext install dbaeumer.vscode-eslint
``` ```
Gitlens for visualization of code authorship
```
ext install eamodio.gitlens
```
Spanish language pack
```
ext install ms-ceintl.vscode-language-pack-es
```
### Recommended extensions
Material icon Theme
```
ext install pkief.material-icon-theme
```
Material UI Themes
```
ext install equinusocio.vsc-material-theme
```
## Built With ## Built With
* [angularjs](https://angularjs.org/) * [angularjs](https://angularjs.org/)

View File

@ -49,7 +49,7 @@ module.exports = Self => {
ish.packing, ish.packing,
ish.grouping, ish.grouping,
s.isAdded, s.isAdded,
s.originalQuantity, s.originalQuantity,
s.quantity saleQuantity, s.quantity saleQuantity,
iss.quantity reservedQuantity, iss.quantity reservedQuantity,
SUM(iss.quantity) OVER (PARTITION BY s.id ORDER BY ish.id) accumulatedQuantity, SUM(iss.quantity) OVER (PARTITION BY s.id ORDER BY ish.id) accumulatedQuantity,
@ -75,7 +75,7 @@ module.exports = Self => {
LEFT JOIN itemColor ic ON ic.itemFk = s.itemFk LEFT JOIN itemColor ic ON ic.itemFk = s.itemFk
LEFT JOIN origin o ON o.id = i.originFk LEFT JOIN origin o ON o.id = i.originFk
WHERE tc.collectionFk = ? WHERE tc.collectionFk = ?
GROUP BY ish.id, p.code, p2.code GROUP BY s.id, ish.id, p.code, p2.code
ORDER BY pickingOrder;`, [id], myOptions); ORDER BY pickingOrder;`, [id], myOptions);
if (print) if (print)
@ -105,7 +105,7 @@ module.exports = Self => {
LEFT JOIN vn.buy c ON c.itemFk = s.itemFk LEFT JOIN vn.buy c ON c.itemFk = s.itemFk
LEFT JOIN vn.entry e ON e.id = c.entryFk LEFT JOIN vn.entry e ON e.id = c.entryFk
LEFT JOIN vn.travel tr ON tr.id = e.travelFk LEFT JOIN vn.travel tr ON tr.id = e.travelFk
WHERE s.ticketFk = ? WHERE s.ticketFk = ?
AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`, AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`,
[ticketId], myOptions); [ticketId], myOptions);
ticket.sales = []; ticket.sales = [];

View File

@ -1,133 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('newCollection', {
description: 'Make a new collection of tickets',
accessType: 'WRITE',
accepts: [{
arg: 'collectionFk',
type: 'Number',
required: false,
description: 'The collection id'
}, {
arg: 'sectorFk',
type: 'Number',
required: true,
description: 'The sector of worker'
}, {
arg: 'vWagons',
type: 'Number',
required: true,
description: 'The number of wagons'
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/newCollection`,
verb: 'POST'
}
});
Self.newCollection = async(ctx, collectionFk, sectorFk, vWagons) => {
let query = '';
const userId = ctx.req.accessToken.userId;
if (!collectionFk) {
query = `CALL vn.collectionTrain_newBeta(?,?,?)`;
const [result] = await Self.rawSql(query, [sectorFk, vWagons, userId], {userId});
if (result.length == 0)
throw new Error(`No collections for today`);
collectionFk = result[0].vCollectionFk;
}
query = `CALL vn.collectionTicket_get(?)`;
const [tickets] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionSale_get(?)`;
const [sales] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionPlacement_get(?)`;
const [placements] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionSticker_print(?,?)`;
await Self.rawSql(query, [collectionFk, sectorFk], {userId});
return makeCollection(tickets, sales, placements, collectionFk);
};
/**
* Returns a collection json
* @param {*} tickets - Request tickets
* @param {*} sales - Request sales
* @param {*} placements - Request placements
* @param {*} collectionFk - Request placements
* @return {Object} Collection JSON
*/
async function makeCollection(tickets, sales, placements, collectionFk) {
let collection = [];
for (let i = 0; i < tickets.length; i++) {
let ticket = {};
ticket['ticketFk'] = tickets[i]['ticketFk'];
ticket['level'] = tickets[i]['level'];
ticket['agencyName'] = tickets[i]['agencyName'];
ticket['warehouseFk'] = tickets[i]['warehouseFk'];
ticket['salesPersonFk'] = tickets[i]['salesPersonFk'];
let ticketSales = [];
for (let x = 0; x < sales.length; x++) {
if (sales[x]['ticketFk'] == ticket['ticketFk']) {
let sale = {};
sale['collectionFk'] = collectionFk;
sale['ticketFk'] = sales[x]['ticketFk'];
sale['saleFk'] = sales[x]['saleFk'];
sale['itemFk'] = sales[x]['itemFk'];
sale['quantity'] = sales[x]['quantity'];
if (sales[x]['quantityPicked'] != null)
sale['quantityPicked'] = sales[x]['quantityPicked'];
else
sale['quantityPicked'] = 0;
sale['longName'] = sales[x]['longName'];
sale['size'] = sales[x]['size'];
sale['color'] = sales[x]['color'];
sale['discount'] = sales[x]['discount'];
sale['price'] = sales[x]['price'];
sale['stems'] = sales[x]['stems'];
sale['category'] = sales[x]['category'];
sale['origin'] = sales[x]['origin'];
sale['clientFk'] = sales[x]['clientFk'];
sale['productor'] = sales[x]['productor'];
sale['reserved'] = sales[x]['reserved'];
sale['isPreviousPrepared'] = sales[x]['isPreviousPrepared'];
sale['isPrepared'] = sales[x]['isPrepared'];
sale['isControlled'] = sales[x]['isControlled'];
let salePlacements = [];
for (let z = 0; z < placements.length; z++) {
if (placements[z]['saleFk'] == sale['saleFk']) {
let placement = {};
placement['saleFk'] = placements[z]['saleFk'];
placement['itemFk'] = placements[z]['itemFk'];
placement['placement'] = placements[z]['placement'];
placement['shelving'] = placements[z]['shelving'];
placement['created'] = placements[z]['created'];
placement['visible'] = placements[z]['visible'];
placement['order'] = placements[z]['order'];
placement['grouping'] = placements[z]['grouping'];
salePlacements.push(placement);
}
}
sale['placements'] = salePlacements;
ticketSales.push(sale);
}
}
ticket['sales'] = ticketSales;
collection.push(ticket);
}
return collection;
}
};

View File

@ -1,12 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('newCollection()', () => {
it('should return a new collection', async() => {
pending('#3400 analizar que hacer con rutas de back collection');
let ctx = {req: {accessToken: {userId: 1106}}};
let response = await models.Collection.newCollection(ctx, 1, 1, 1);
expect(response.length).toBeGreaterThan(0);
expect(response[0].ticketFk).toEqual(2);
});
});

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<DeleteEnvio xmlns="http://82.223.6.71:82">
<IdCliente><%= viaexpressConfig.client %></IdCliente>
<Usuario><%= viaexpressConfig.user %></Usuario>
<Password><%= viaexpressConfig.password %></Password>
<etiqueta><%= externalId %></etiqueta>
</DeleteEnvio>
</soap12:Body>
</soap12:Envelope>

View File

@ -0,0 +1,45 @@
const axios = require('axios');
const {DOMParser} = require('xmldom');
module.exports = Self => {
Self.remoteMethod('deleteExpedition', {
description: 'Delete a shipment by providing the expedition ID, interacting with Viaexpress API',
accessType: 'WRITE',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/deleteExpedition`,
verb: 'POST'
}
});
Self.deleteExpedition = async expeditionFk => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({
fields: ['url']
});
const renderedXml = await models.ViaexpressConfig.deleteExpeditionRenderer(expeditionFk);
const response = await axios.post(`${viaexpressConfig.url}ServicioVxClientes.asmx`, renderedXml, {
headers: {
'Content-Type': 'application/soap+xml; charset=utf-8'
}
});
const xmlString = response.data;
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
const resultElement = xmlDoc.getElementsByTagName('DeleteEnvioResult')[0];
const result = resultElement.textContent;
return result;
};
};

View File

@ -0,0 +1,44 @@
const fs = require('fs');
const ejs = require('ejs');
module.exports = Self => {
Self.remoteMethod('deleteExpeditionRenderer', {
description: 'Renders the data from an XML',
accessType: 'READ',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/deleteExpeditionRenderer`,
verb: 'GET'
}
});
Self.deleteExpeditionRenderer = async expeditionFk => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({
fields: ['client', 'user', 'password']
});
const expedition = await models.Expedition.findOne({
fields: ['id', 'externalId'],
where: {id: expeditionFk}
});
const data = {
viaexpressConfig,
externalId: expedition.externalId
};
const template = fs.readFileSync(__dirname + '/deleteExpedition.ejs', 'utf-8');
const renderedXml = ejs.render(template, data);
return renderedXml;
};
};

View File

@ -49,8 +49,13 @@ module.exports = Self => {
if (vnUser.twoFactor) if (vnUser.twoFactor)
throw new ForbiddenError(null, 'REQUIRES_2FA'); throw new ForbiddenError(null, 'REQUIRES_2FA');
} }
const validateLogin = await Self.validateLogin(user, password);
return Self.validateLogin(user, password); await Self.app.models.SignInLog.create({
token: validateLogin.token,
userFk: vnUser.id,
ip: ctx.req.ip
});
return validateLogin;
}; };
Self.passExpired = async vnUser => { Self.passExpired = async vnUser => {

View File

@ -12,8 +12,21 @@ describe('VnUser Sign-in()', () => {
}, },
args: {} args: {}
}; };
const {VnUser, AccessToken} = models; const {VnUser, AccessToken, SignInLog} = models;
describe('when credentials are correct', () => { describe('when credentials are correct', () => {
it('should return the token if user uses email', async() => {
let login = await VnUser.signIn(unauthCtx, 'salesAssistant@mydomain.com', 'nightmare');
let accessToken = await AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}};
let signInLog = await SignInLog.find({where: {token: accessToken.id}});
expect(signInLog.length).toEqual(1);
expect(signInLog[0].userFk).toEqual(accessToken.userId);
expect(login.token).toBeDefined();
await VnUser.logout(ctx.req.accessToken.id);
});
it('should return the token', async() => { it('should return the token', async() => {
let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare'); let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare');
let accessToken = await AccessToken.findById(login.token); let accessToken = await AccessToken.findById(login.token);

View File

@ -1,6 +1,5 @@
module.exports = Self => { module.exports = Self => {
require('../methods/collection/getCollection')(Self); require('../methods/collection/getCollection')(Self);
require('../methods/collection/newCollection')(Self);
require('../methods/collection/getSectors')(Self); require('../methods/collection/getSectors')(Self);
require('../methods/collection/setSaleQuantity')(Self); require('../methods/collection/setSaleQuantity')(Self);
require('../methods/collection/previousLabel')(Self); require('../methods/collection/previousLabel')(Self);

View File

@ -1,4 +1,6 @@
module.exports = Self => { module.exports = Self => {
require('../methods/viaexpress-config/internationalExpedition')(Self); require('../methods/viaexpress-config/internationalExpedition')(Self);
require('../methods/viaexpress-config/renderer')(Self); require('../methods/viaexpress-config/renderer')(Self);
require('../methods/viaexpress-config/deleteExpedition')(Self);
require('../methods/viaexpress-config/deleteExpeditionRenderer')(Self);
}; };

View File

@ -2,6 +2,7 @@ const vnModel = require('vn-loopback/common/models/vn-model');
const {Email} = require('vn-print'); const {Email} = require('vn-print');
const ForbiddenError = require('vn-loopback/util/forbiddenError'); const ForbiddenError = require('vn-loopback/util/forbiddenError');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) { module.exports = function(Self) {
vnModel(Self); vnModel(Self);
@ -92,7 +93,11 @@ module.exports = function(Self) {
}; };
Self.on('resetPasswordRequest', async function(info) { Self.on('resetPasswordRequest', async function(info) {
const url = await Self.app.models.Url.getUrl(); const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const headers = httpRequest.headers;
const origin = headers.origin;
const defaultHash = '/reset-password?access_token=$token$'; const defaultHash = '/reset-password?access_token=$token$';
const recoverHashes = { const recoverHashes = {
@ -108,7 +113,7 @@ module.exports = function(Self) {
const params = { const params = {
recipient: info.email, recipient: info.email,
lang: user.lang, lang: user.lang,
url: url.slice(0, -1) + recoverHash url: origin + '/#!' + recoverHash
}; };
const options = Object.assign({}, info.options); const options = Object.assign({}, info.options);
@ -119,12 +124,21 @@ module.exports = function(Self) {
return email.send(); return email.send();
}); });
Self.signInValidate = (user, userToken) => {
const [[key, value]] = Object.entries(Self.userUses(user));
if (userToken[key].toLowerCase().trim() !== value.toLowerCase().trim()) {
console.error('ERROR!!! - Signin with other user', userToken, user);
throw new UserError('Try again');
}
};
Self.validateLogin = async function(user, password) { Self.validateLogin = async function(user, password) {
let loginInfo = Object.assign({password}, Self.userUses(user)); const loginInfo = Object.assign({password}, Self.userUses(user));
token = await Self.login(loginInfo, 'user'); const token = await Self.login(loginInfo, 'user');
const userToken = await token.user.get(); const userToken = await token.user.get();
Self.signInValidate(user, userToken);
try { try {
await Self.app.models.Account.sync(userToken.name, password); await Self.app.models.Account.sync(userToken.name, password);
} catch (err) { } catch (err) {
@ -220,8 +234,11 @@ module.exports = function(Self) {
class Mailer { class Mailer {
async send(verifyOptions, cb) { async send(verifyOptions, cb) {
const url = new URL(verifyOptions.verifyHref);
if (process.env.NODE_ENV) url.port = '';
const params = { const params = {
url: verifyOptions.verifyHref, url: url.href,
recipient: verifyOptions.to recipient: verifyOptions.to
}; };

View File

@ -118,13 +118,13 @@ BEGIN
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef) SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM tmp.ticketToInvoice ti; FROM tmp.ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId); CALL invoiceExpenseMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea); CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
UPDATE invoiceOut io UPDATE invoiceOut io
JOIN ( JOIN (
SELECT SUM(amount) AS total SELECT SUM(amount) AS total
FROM invoiceOutExpence FROM invoiceOutExpense
WHERE invoiceOutFk = vNewInvoiceId WHERE invoiceOutFk = vNewInvoiceId
) base ) base
JOIN ( JOIN (
@ -166,18 +166,18 @@ BEGIN
SET @vTaxableBaseServices := 0.00; SET @vTaxableBaseServices := 0.00;
SET @vTaxCodeGeneral := NULL; SET @vTaxCodeGeneral := NULL;
INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInId, @vTaxableBaseServices, sub.expenceFk, sub.taxTypeSageFk , sub.transactionTypeSageFk SELECT vNewInvoiceInId, @vTaxableBaseServices, sub.expenseFk, sub.taxTypeSageFk , sub.transactionTypeSageFk
FROM ( FROM (
SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk, @vTaxCodeGeneral := i.taxClassCodeFk SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, i.expenseFk, i.taxTypeSageFk , i.transactionTypeSageFk, @vTaxCodeGeneral := i.taxClassCodeFk
FROM tmp.ticketServiceTax tst FROM tmp.ticketServiceTax tst
JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code
WHERE i.isService WHERE i.isService
HAVING taxableBase HAVING taxableBase
) sub; ) sub;
INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInId, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, @vTaxableBaseServices, 0) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk SELECT vNewInvoiceInId, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, @vTaxableBaseServices, 0) taxableBase, i.expenseFk, i.taxTypeSageFk , i.transactionTypeSageFk
FROM tmp.ticketTax tt FROM tmp.ticketTax tt
JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code
WHERE !i.isService WHERE !i.isService

View File

@ -139,13 +139,13 @@ BEGIN
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef) SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM tmp.ticketToInvoice ti; FROM tmp.ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId); CALL invoiceExpenseMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea); CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
UPDATE invoiceOut io UPDATE invoiceOut io
JOIN ( JOIN (
SELECT SUM(amount) total SELECT SUM(amount) total
FROM invoiceOutExpence FROM invoiceOutExpense
WHERE invoiceOutFk = vNewInvoiceId WHERE invoiceOutFk = vNewInvoiceId
) base ) base
JOIN ( JOIN (
@ -182,15 +182,15 @@ BEGIN
SET @vTaxableBaseServices := 0.00; SET @vTaxableBaseServices := 0.00;
SET @vTaxCodeGeneral := NULL; SET @vTaxCodeGeneral := NULL;
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInFk, SELECT vNewInvoiceInFk,
@vTaxableBaseServices, @vTaxableBaseServices,
sub.expenceFk, sub.expenseFk,
sub.taxTypeSageFk, sub.taxTypeSageFk,
sub.transactionTypeSageFk sub.transactionTypeSageFk
FROM ( FROM (
SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase,
i.expenceFk, i.expenseFk,
i.taxTypeSageFk, i.taxTypeSageFk,
i.transactionTypeSageFk, i.transactionTypeSageFk,
@vTaxCodeGeneral := i.taxClassCodeFk @vTaxCodeGeneral := i.taxClassCodeFk
@ -200,11 +200,11 @@ BEGIN
HAVING taxableBase HAVING taxableBase
) sub; ) sub;
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInFk, SELECT vNewInvoiceInFk,
SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral,
@vTaxableBaseServices, 0) taxableBase, @vTaxableBaseServices, 0) taxableBase,
i.expenceFk, i.expenseFk,
i.taxTypeSageFk , i.taxTypeSageFk ,
i.transactionTypeSageFk i.transactionTypeSageFk
FROM tmp.ticketTax tt FROM tmp.ticketTax tt

View File

@ -135,13 +135,13 @@ BEGIN
INSERT INTO ticketTracking(stateFk,ticketFk,workerFk) INSERT INTO ticketTracking(stateFk,ticketFk,workerFk)
SELECT * FROM tmp.updateInter; SELECT * FROM tmp.updateInter;
CALL invoiceExpenceMake(vNewInvoiceId); CALL invoiceExpenseMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea); CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
UPDATE invoiceOut io UPDATE invoiceOut io
JOIN ( JOIN (
SELECT SUM(amount) total SELECT SUM(amount) total
FROM invoiceOutExpence FROM invoiceOutExpense
WHERE invoiceOutFk = vNewInvoiceId WHERE invoiceOutFk = vNewInvoiceId
) base ) base
JOIN ( JOIN (
@ -178,15 +178,15 @@ BEGIN
SET @vTaxableBaseServices := 0.00; SET @vTaxableBaseServices := 0.00;
SET @vTaxCodeGeneral := NULL; SET @vTaxCodeGeneral := NULL;
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInFk, SELECT vNewInvoiceInFk,
@vTaxableBaseServices, @vTaxableBaseServices,
sub.expenceFk, sub.expenseFk,
sub.taxTypeSageFk, sub.taxTypeSageFk,
sub.transactionTypeSageFk sub.transactionTypeSageFk
FROM ( FROM (
SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase,
i.expenceFk, i.expenseFk,
i.taxTypeSageFk, i.taxTypeSageFk,
i.transactionTypeSageFk, i.transactionTypeSageFk,
@vTaxCodeGeneral := i.taxClassCodeFk @vTaxCodeGeneral := i.taxClassCodeFk
@ -196,11 +196,11 @@ BEGIN
HAVING taxableBase HAVING taxableBase
) sub; ) sub;
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInFk, SELECT vNewInvoiceInFk,
SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral,
@vTaxableBaseServices, 0) taxableBase, @vTaxableBaseServices, 0) taxableBase,
i.expenceFk, i.expenseFk,
i.taxTypeSageFk , i.taxTypeSageFk ,
i.transactionTypeSageFk i.transactionTypeSageFk
FROM tmp.ticketTax tt FROM tmp.ticketTax tt

View File

@ -0,0 +1,20 @@
--
-- Table structure for table `signInLog`
-- Description: log to debug cross-login error
--
DROP TABLE IF EXISTS `account`.`signInLog`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `account`.`signInLog` (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`token` varchar(255) NOT NULL ,
`userFk` int(10) unsigned DEFAULT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`ip` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
KEY `userFk` (`userFk`),
CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@ -0,0 +1,2 @@
ALTER TABLE account.sambaConfig
ADD userDn varchar(255) NOT NULL COMMENT 'Base DN for users without domain DN part';

View File

@ -0,0 +1,152 @@
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT)
BEGIN
/**
* Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar.
*
* @param vDateFuture Fecha de los tickets que se quieren adelantar.
* @param vDateToAdvance Fecha a cuando se quiere adelantar.
* @param vWarehouseFk Almacén
*/
DECLARE vDateInventory DATE;
SELECT inventoried INTO vDateInventory FROM config;
CREATE OR REPLACE TEMPORARY TABLE tmp.stock
(itemFk INT PRIMARY KEY,
amount INT)
ENGINE = MEMORY;
INSERT INTO tmp.stock(itemFk, amount)
SELECT itemFk, SUM(quantity) amount FROM
(
SELECT itemFk, quantity
FROM itemTicketOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM itemEntryIn
WHERE landed >= vDateInventory
AND landed <= vDateToAdvance
AND isVirtualStock = FALSE
AND warehouseInFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM itemEntryOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseOutFk = vWarehouseFk
) t
GROUP BY itemFk HAVING amount != 0;
CREATE OR REPLACE TEMPORARY TABLE tmp.filter
(INDEX (id))
SELECT
origin.ticketFk futureId,
dest.ticketFk id,
dest.state,
origin.futureState,
origin.futureIpt,
dest.ipt,
origin.workerFk,
origin.futureLiters,
origin.futureLines,
dest.shipped,
origin.shipped futureShipped,
dest.totalWithVat,
origin.totalWithVat futureTotalWithVat,
dest.agency,
dest.agencyModeFk,
origin.futureAgency,
origin.agencyModeFk futureAgencyModeFk,
dest.lines,
dest.liters,
origin.futureLines - origin.hasStock AS notMovableLines,
(origin.futureLines = origin.hasStock) AS isFullMovable,
dest.zoneFk,
origin.futureZoneFk,
origin.futureZoneName,
origin.classColor futureClassColor,
dest.classColor,
origin.clientFk futureClientFk,
origin.addressFk futureAddressFk,
origin.warehouseFk futureWarehouseFk,
origin.companyFk futureCompanyFk,
IFNULL(dest.nickname, origin.nickname) nickname,
dest.landed
FROM (
SELECT
s.ticketFk,
c.salesPersonFk workerFk,
t.shipped,
t.totalWithVat,
st.name futureState,
am.name futureAgency,
count(s.id) futureLines,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt,
CAST(SUM(litros) AS DECIMAL(10,0)) futureLiters,
SUM((s.quantity <= IFNULL(st.amount,0))) hasStock,
z.id futureZoneFk,
z.name futureZoneName,
st.classColor,
t.clientFk,
t.nickname,
t.addressFk,
t.warehouseFk,
t.companyFk,
t.agencyModeFk
FROM ticket t
JOIN client c ON c.id = t.clientFk
JOIN sale s ON s.ticketFk = t.id
JOIN saleVolume sv ON sv.saleFk = s.id
JOIN item i ON i.id = s.itemFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state st ON st.id = ts.stateFk
JOIN agencyMode am ON t.agencyModeFk = am.id
JOIN zone z ON t.zoneFk = z.id
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
LEFT JOIN tmp.stock st ON st.itemFk = i.id
WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture)
AND t.warehouseFk = vWarehouseFk
GROUP BY t.id
) origin
LEFT JOIN (
SELECT
t.id ticketFk,
st.name state,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt,
t.shipped,
t.totalWithVat,
am.name agency,
CAST(SUM(litros) AS DECIMAL(10,0)) liters,
CAST(COUNT(*) AS DECIMAL(10,0)) `lines`,
st.classColor,
t.clientFk,
t.nickname,
t.addressFk,
t.zoneFk,
t.warehouseFk,
t.companyFk,
t.landed,
t.agencyModeFk
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN saleVolume sv ON sv.saleFk = s.id
JOIN item i ON i.id = s.itemFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state st ON st.id = ts.stateFk
JOIN agencyMode am ON t.agencyModeFk = am.id
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
WHERE t.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance)
AND t.warehouseFk = vWarehouseFk
AND st.order <= 5
GROUP BY t.id
) dest ON dest.addressFk = origin.addressFk
WHERE origin.hasStock;
DROP TEMPORARY TABLE tmp.stock;
END$$
DELIMITER ;

View File

View File

@ -0,0 +1,26 @@
DELETE FROM `salix`.`ACL`
WHERE
model = 'Route'
AND property = '*'
AND accessType = 'READ';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Route', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getRoutesByWorker', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'canViewAllRoute', 'READ', 'ALLOW', 'ROLE', 'deliveryBoss'),
('Route', 'cmr', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'downloadCmrsZip', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'downloadZip', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getByWorker', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getDeliveryPoint', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getExternalCmrs', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getSuggestedTickets', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getTickets', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Route', 'guessPriority', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Route', 'insertTicket', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Route', 'getDeliveryPoint', 'READ', 'ALLOW', 'ROLE', 'deliveryBoss'),
('Route', 'summary', 'READ', 'ALLOW', 'ROLE', 'employee');

File diff suppressed because one or more lines are too long

View File

@ -59,10 +59,6 @@ INSERT IGNORE INTO `vn`.`greugeConfig`(`id`, `freightPickUpPrice`)
VALUES VALUES
('1', '11'); ('1', '11');
INSERT INTO `vn`.`packagingConfig`(`upperGap`)
VALUES
('10');
UPDATE `account`.`role` SET id = 100 WHERE id = 0; UPDATE `account`.`role` SET id = 100 WHERE id = 0;
INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPrefix`, `userHost`, `tplUser`) INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPrefix`, `userHost`, `tplUser`)
@ -73,7 +69,7 @@ CALL `account`.`role_sync`;
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `lang`, `image`, `password`) INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `lang`, `image`, `password`)
SELECT id, name, CONCAT(name, 'Nick'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2' SELECT id, name, CONCAT(name, 'Nick'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2'
FROM `account`.`role` WHERE id <> 20 FROM `account`.`role`
ORDER BY id; ORDER BY id;
INSERT INTO `account`.`account`(`id`) INSERT INTO `account`.`account`(`id`)
@ -87,9 +83,15 @@ INSERT INTO `vn`.`educationLevel` (`id`, `name`)
(1, 'ESTUDIOS PRIMARIOS COMPLETOS'), (1, 'ESTUDIOS PRIMARIOS COMPLETOS'),
(2, 'ENSEÑANZAS DE BACHILLERATO'); (2, 'ENSEÑANZAS DE BACHILLERATO');
INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `bossFk`)
SELECT id,UPPER(LPAD(role, 3, '0')), name, name, NULL
FROM `account`.`user`
WHERE `id` = 9;
INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `bossFk`) INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `bossFk`)
SELECT id,UPPER(LPAD(role, 3, '0')), name, name, 9 SELECT id,UPPER(LPAD(role, 3, '0')), name, name, 9
FROM `account`.`user`; FROM `account`.`user`
WHERE `id` <> 9;
UPDATE `vn`.`worker` SET bossFk = NULL WHERE id = 20; UPDATE `vn`.`worker` SET bossFk = NULL WHERE id = 20;
UPDATE `vn`.`worker` SET bossFk = 20 WHERE id = 1 OR id = 9; UPDATE `vn`.`worker` SET bossFk = 20 WHERE id = 1 OR id = 9;
@ -549,15 +551,6 @@ INSERT INTO `vn`.`supplierActivity`(`code`, `name`)
('flowerPlants', 'Wholesale of flowers and plants'), ('flowerPlants', 'Wholesale of flowers and plants'),
('vegetablesFruits', 'Fruit and vegetable trade'); ('vegetablesFruits', 'Fruit and vegetable trade');
INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `provinceFk`, `postalCode`, `city`, `phone`, `mobile`)
VALUES
(1, 1, 'Ace Chemicals', 'The Midtown', 1, '46000', 'Gotham', '111111111', '222222222'),
(2, 1, 'Arkham Asylum', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(3, 2, 'Wayne Tower', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(4, 2, 'Bank of Gotham', 'Founders Island', 1, '46000', 'Gotham', '111111111', '222222222'),
(5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'),
(6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222');
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`) INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`)
VALUES VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'), (1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, util.VN_CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'),
@ -568,6 +561,15 @@ INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`
(791, 'Bros SL', 'Bros nick', 5115000791, 1, '37718083S', 0, util.VN_CURDATE(), 1, 'supplier address 7', 'ASGARD', 3, 46600, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'), (791, 'Bros SL', 'Bros nick', 5115000791, 1, '37718083S', 0, util.VN_CURDATE(), 1, 'supplier address 7', 'ASGARD', 3, 46600, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'),
(1381, 'Ornamentales', 'Ornamentales', 7185001381, 1, '07972486L', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'); (1381, 'Ornamentales', 'Ornamentales', 7185001381, 1, '07972486L', 0, util.VN_CURDATE(), 1, 'supplier address 4', 'GOTHAM', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V');
INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `provinceFk`, `postalCode`, `city`, `phone`, `mobile`)
VALUES
(1, 1, 'Ace Chemicals', 'The Midtown', 1, '46000', 'Gotham', '111111111', '222222222'),
(2, 1, 'Arkham Asylum', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(3, 2, 'Wayne Tower', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'),
(4, 2, 'Bank of Gotham', 'Founders Island', 1, '46000', 'Gotham', '111111111', '222222222'),
(5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'),
(6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222');
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`) INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES VALUES
(1, 1, 123121212, 654789123, 'supplier1@email.es', 'observation1', 'the boss'), (1, 1, 123121212, 654789123, 'supplier1@email.es', 'observation1', 'the boss'),
@ -628,7 +630,7 @@ INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`)
(4, 8.07, 0.81, 4770000010), (4, 8.07, 0.81, 4770000010),
(5, 8.07, 0.81, 4770000010); (5, 8.07, 0.81, 4770000010);
INSERT INTO `vn`.`expence`(`id`, `name`, `isWithheld`) INSERT INTO `vn`.`expense`(`id`, `name`, `isWithheld`)
VALUES VALUES
(2000000000, 'Inmovilizado pendiente', 0), (2000000000, 'Inmovilizado pendiente', 0),
(2000000001, 'Compra de bienes de inmovilizado', 0), (2000000001, 'Compra de bienes de inmovilizado', 0),
@ -640,7 +642,7 @@ INSERT INTO `vn`.`expence`(`id`, `name`, `isWithheld`)
(7050000000, 'Prestacion de servicios', 1); (7050000000, 'Prestacion de servicios', 1);
INSERT INTO `vn`.`invoiceOutExpence`(`id`, `invoiceOutFk`, `amount`, `expenceFk`, `created`) INSERT INTO `vn`.`invoiceOutExpense`(`id`, `invoiceOutFk`, `amount`, `expenseFk`, `created`)
VALUES VALUES
(1, 1, 813.06, 2000000000, util.VN_CURDATE()), (1, 1, 813.06, 2000000000, util.VN_CURDATE()),
(2, 1, 33.80, 4751000000, util.VN_CURDATE()), (2, 1, 33.80, 4751000000, util.VN_CURDATE()),
@ -920,7 +922,7 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('SER', 'Services'), ('SER', 'Services'),
('VT', 'Sales'); ('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, INSERT INTO `vn`.`item`(`id`, `typeFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenseFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`) `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`)
VALUES VALUES
(1, 2, 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'EMB', 0, NULL, 'V', 0, 15,3), (1, 2, 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'EMB', 0, NULL, 'V', 0, 15,3),
@ -966,6 +968,10 @@ INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPack
('cc', 1640038.00, 56.00, 220.00, 128.00, 1, util.VN_CURDATE(), 15, 90.00), ('cc', 1640038.00, 56.00, 220.00, 128.00, 1, util.VN_CURDATE(), 15, 90.00),
('pallet 100', 2745600.00, 100.00, 220.00, 120.00, 1, util.VN_CURDATE(), 16, 0.00); ('pallet 100', 2745600.00, 100.00, 220.00, 120.00, 1, util.VN_CURDATE(), 16, 0.00);
INSERT INTO `vn`.`packagingConfig`(`upperGap`, `defaultSmallPackageFk`, `defaultBigPackageFk`)
VALUES
('10', 1, 'pallet 100');
INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`) INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`)
VALUES VALUES
(1, 'En reparto', 'ON DELIVERY'), (1, 'En reparto', 'ON DELIVERY'),
@ -1933,7 +1939,7 @@ INSERT INTO `vn`.`ticketRequest`(`id`, `description`, `requesterFk`, `attenderFk
(4, 'Melee weapon combat first 15cm', 18, 35, 15, NULL, 1.30, NULL, NULL, 11, util.VN_CURDATE()), (4, 'Melee weapon combat first 15cm', 18, 35, 15, NULL, 1.30, NULL, NULL, 11, util.VN_CURDATE()),
(5, 'Melee weapon combat first 15cm', 18, 35, 15, 4, 1.30, 0, NULL, 18, util.VN_CURDATE()); (5, 'Melee weapon combat first 15cm', 18, 35, 15, 4, 1.30, 0, NULL, 18, util.VN_CURDATE());
INSERT INTO `vn`.`ticketServiceType`(`id`, `name`, `expenceFk`) INSERT INTO `vn`.`ticketServiceType`(`id`, `name`, `expenseFk`)
VALUES VALUES
(1, 'Porte Agencia', 7001000000), (1, 'Porte Agencia', 7001000000),
(2, 'Portes Retorno', 7001000000), (2, 'Portes Retorno', 7001000000),
@ -2555,7 +2561,7 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`)
(9, 9, 9), (9, 9, 9),
(10, 10, 10); (10, 10, 10);
INSERT INTO `vn`.`invoiceInTax` (`invoiceInFk`, `taxableBase`, `expenceFk`, `foreignValue`, `taxTypeSageFk`, `transactionTypeSageFk`) INSERT INTO `vn`.`invoiceInTax` (`invoiceInFk`, `taxableBase`, `expenseFk`, `foreignValue`, `taxTypeSageFk`, `transactionTypeSageFk`)
VALUES VALUES
(1, 99.99, '2000000000', NULL, NULL, NULL), (1, 99.99, '2000000000', NULL, NULL, NULL),
(2, 999.99, '2000000000', NULL, NULL, NULL), (2, 999.99, '2000000000', NULL, NULL, NULL),
@ -2887,7 +2893,9 @@ INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
INSERT INTO `vn`.`payDemDetail` (`id`, `detail`) INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES VALUES
(1, 1); (1, 1),
(2, 20),
(7, 1);
INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk`, `businessTypeFk`) INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk`, `businessTypeFk`)
VALUES VALUES

File diff suppressed because it is too large Load Diff

View File

@ -45,12 +45,12 @@ TABLES=(
alertLevel alertLevel
bookingPlanner bookingPlanner
businessType businessType
cplusInvoiceType472 siiTypeInvoiceIn
siiTypeInvoiceOut siiTypeInvoiceOut
cplusRectificationType cplusRectificationType
cplusSubjectOp cplusSubjectOp
cplusTaxBreak cplusTaxBreak
cplusTrascendency472 siiTrascendencyInvoiceIn
claimResponsible claimResponsible
claimReason claimReason
claimRedelivery claimRedelivery
@ -68,6 +68,8 @@ TABLES=(
volumeConfig volumeConfig
workCenter workCenter
companyI18n companyI18n
workerTimeControlError
silexACL
) )
dump_tables ${TABLES[@]} dump_tables ${TABLES[@]}

View File

@ -145,6 +145,7 @@ export default {
adController: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adController"]', adController: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adController"]',
adUser: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adUser"]', adUser: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adUser"]',
adPassword: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adPassword"]', adPassword: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.adPassword"]',
userDn: 'vn-account-samba vn-textfield[ng-model="$ctrl.config.userDn"]',
verifyCert: 'vn-account-samba vn-check[ng-model="$ctrl.config.verifyCert"]', verifyCert: 'vn-account-samba vn-check[ng-model="$ctrl.config.verifyCert"]',
save: 'vn-account-samba vn-submit' save: 'vn-account-samba vn-submit'
}, },
@ -722,7 +723,7 @@ export default {
isFullMovable: 'vn-check[ng-model="filter.isFullMovable"]', isFullMovable: 'vn-check[ng-model="filter.isFullMovable"]',
warehouseFk: 'vn-autocomplete[label="Warehouse"]', warehouseFk: 'vn-autocomplete[label="Warehouse"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]', tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Advance tickets"]', moveButton: 'vn-button[vn-tooltip="Advance tickets with negatives"]',
acceptButton: '.vn-confirm.shown button[response="accept"]', acceptButton: '.vn-confirm.shown button[response="accept"]',
firstCheck: 'tbody > tr:nth-child(2) > td > vn-check', firstCheck: 'tbody > tr:nth-child(2) > td > vn-check',
tableId: 'vn-textfield[name="id"]', tableId: 'vn-textfield[name="id"]',

View File

@ -22,7 +22,7 @@ describe('Travel basic data path', () => {
await page.waitForState('travel.card.basicData'); await page.waitForState('travel.card.basicData');
}); });
it('should set a wrong delivery date then receive an error on submit', async() => { it('should throw error if try move a travel with entries', async() => {
const lastMonth = Date.vnNew(); const lastMonth = Date.vnNew();
lastMonth.setMonth(lastMonth.getMonth() - 1); lastMonth.setMonth(lastMonth.getMonth() - 1);
@ -30,6 +30,23 @@ describe('Travel basic data path', () => {
await page.waitToClick(selectors.travelBasicData.save); await page.waitToClick(selectors.travelBasicData.save);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toContain('Cannot past travels with entries');
});
it('should set a wrong delivery date then receive an error on submit', async() => {
await page.loginAndModule('buyer', 'travel');
await page.write(selectors.travelIndex.generalSearchFilter, '4');
await page.keyboard.press('Enter');
await page.accessToSection('travel.card.basicData');
await page.waitForState('travel.card.basicData');
const lastMonth = Date.vnNew();
lastMonth.setMonth(lastMonth.getMonth() - 2);
await page.pickDate(selectors.travelBasicData.deliveryDate, lastMonth);
await page.waitToClick(selectors.travelBasicData.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Landing cannot be lesser than shipment'); expect(message.text).toContain('Landing cannot be lesser than shipment');
}); });
@ -39,7 +56,7 @@ describe('Travel basic data path', () => {
await page.waitToClick(selectors.travelBasicData.undoChanges); await page.waitToClick(selectors.travelBasicData.undoChanges);
const result = await page.waitToGetProperty(selectors.travelBasicData.reference, 'value'); const result = await page.waitToGetProperty(selectors.travelBasicData.reference, 'value');
expect(result).toEqual('third travel'); expect(result).toEqual('fourth travel');
}); });
it('should now edit the whole form then save', async() => { it('should now edit the whole form then save', async() => {

View File

@ -23,18 +23,6 @@ describe('Account Accounts path', () => {
expect(message.text).toContain('Roles synchronized!'); expect(message.text).toContain('Roles synchronized!');
}); });
it('should sync user', async() => {
await page.waitToClick(selectors.accountAccounts.syncUser);
await page.write(selectors.accountAccounts.syncUserName, 'sysadmin');
await page.write(selectors.accountAccounts.syncUserPassword, 'nightmare');
await page.waitToClick(selectors.accountAccounts.buttonAccept);
const message = await page.waitForSnackbar();
expect(message.text).toContain('User synchronized!');
});
it('should relogin', async() => { it('should relogin', async() => {
await page.loginAndModule('sysadmin', 'account'); await page.loginAndModule('sysadmin', 'account');
await page.accessToSection('account.accounts'); await page.accessToSection('account.accounts');

View File

@ -20,8 +20,9 @@ describe('Account Samba path', () => {
await page.waitToClick(selectors.accountSamba.checkEnable); await page.waitToClick(selectors.accountSamba.checkEnable);
await page.write(selectors.accountSamba.adDomain, '1234'); await page.write(selectors.accountSamba.adDomain, '1234');
await page.write(selectors.accountSamba.adController, '1234'); await page.write(selectors.accountSamba.adController, '1234');
await page.write(selectors.accountSamba.adUser, 'nightmare'); await page.write(selectors.accountSamba.adUser, 'sysadmin');
await page.write(selectors.accountSamba.adPassword, 'sysadmin'); await page.write(selectors.accountSamba.adPassword, 'nightmare');
await page.write(selectors.accountSamba.userDn, 'testDn');
await page.waitToClick(selectors.accountSamba.verifyCert); await page.waitToClick(selectors.accountSamba.verifyCert);
await page.waitToClick(selectors.accountSamba.save); await page.waitToClick(selectors.accountSamba.save);

View File

@ -196,6 +196,9 @@
"Negative basis of tickets: 23": "Negative basis of tickets: 23", "Negative basis of tickets: 23": "Negative basis of tickets: 23",
"Booking completed": "Booking complete", "Booking completed": "Booking complete",
"The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation", "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation",
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets" "You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets",
} "Try again": "Try again",
"keepPrice": "keepPrice",
"Cannot past travels with entries": "Cannot past travels with entries",
"It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}"
}

View File

@ -224,7 +224,7 @@
"date in the future": "Fecha en el futuro", "date in the future": "Fecha en el futuro",
"reference duplicated": "Referencia duplicada", "reference duplicated": "Referencia duplicada",
"This ticket is already a refund": "Este ticket ya es un abono", "This ticket is already a refund": "Este ticket ya es un abono",
"isWithoutNegatives": "isWithoutNegatives", "isWithoutNegatives": "Sin negativos",
"routeFk": "routeFk", "routeFk": "routeFk",
"Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador", "Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador",
"No hay un contrato en vigor": "No hay un contrato en vigor", "No hay un contrato en vigor": "No hay un contrato en vigor",
@ -323,8 +323,11 @@
"The response is not a PDF": "La respuesta no es un PDF", "The response is not a PDF": "La respuesta no es un PDF",
"Booking completed": "Reserva completada", "Booking completed": "Reserva completada",
"The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación", "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación",
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mímina",
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mímina",
"Incoterms data for consignee is missing": "Faltan los datos de los Incoterms para el consignatario", "Incoterms data for consignee is missing": "Faltan los datos de los Incoterms para el consignatario",
"The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada" "The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada",
} "User disabled": "Usuario desactivado",
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima",
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima",
"Cannot past travels with entries": "No se pueden pasar envíos con entradas",
"It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}"
}

View File

@ -1,3 +1,4 @@
const NotFoundError = require('vn-loopback/util/not-found-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('test', { Self.remoteMethod('test', {
@ -9,7 +10,8 @@ module.exports = Self => {
}); });
Self.test = async function() { Self.test = async function() {
let connector = await Self.getSynchronizer(); const connector = await Self.getLinker();
if (!connector) throw new NotFoundError('Linker not configured');
await connector.test(); await connector.test();
}; };
}; };

View File

@ -1,3 +1,4 @@
const ForbiddenError = require('vn-loopback/util/forbiddenError');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('sync', { Self.remoteMethod('sync', {
@ -39,9 +40,13 @@ module.exports = Self => {
try { try {
const user = await models.VnUser.findOne({ const user = await models.VnUser.findOne({
fields: ['id'], fields: ['id', 'password'],
where: {name: userName} where: {name: userName}
}, myOptions); }, myOptions);
if (user && password && !await user.hasPassword(password))
throw new ForbiddenError('Wrong password');
const isSync = !await models.UserSync.exists(userName, myOptions); const isSync = !await models.UserSync.exists(userName, myOptions);
if (!force && isSync && user) { if (!force && isSync && user) {

View File

@ -3,14 +3,14 @@ const app = require('vn-loopback/server/server');
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self, options) { module.exports = function(Self, options) {
require('../methods/account-synchronizer/test')(Self); require('../methods/account-linker/test')(Self);
Self.once('attached', function() { Self.once('attached', function() {
app.models.AccountConfig.addSynchronizer(Self); app.models.AccountConfig.addLinker(Self);
}); });
/** /**
* Mixin for user synchronizers. * Mixin for account linkers.
* *
* @property {Array<Model>} $ * @property {Array<Model>} $
* @property {Object} accountConfig * @property {Object} accountConfig
@ -18,12 +18,12 @@ module.exports = function(Self, options) {
*/ */
let Mixin = { let Mixin = {
/** /**
* Initalizes the synchronizer. * Initalizes the linker.
*/ */
async init() {}, async init() {},
/** /**
* Deinitalizes the synchronizer. * Deinitalizes the linker.
*/ */
async deinit() {}, async deinit() {},
@ -57,7 +57,7 @@ module.exports = function(Self, options) {
async syncRoles() {}, async syncRoles() {},
/** /**
* Tests synchronizer configuration. * Tests linker configuration.
*/ */
async test() { async test() {
try { try {

View File

@ -35,6 +35,9 @@
"SambaConfig": { "SambaConfig": {
"dataSource": "vn" "dataSource": "vn"
}, },
"SignInLog": {
"dataSource": "vn"
},
"Sip": { "Sip": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -3,94 +3,85 @@ const models = require('vn-loopback/server/server').models;
module.exports = Self => { module.exports = Self => {
Object.assign(Self, { Object.assign(Self, {
synchronizers: [], linkers: [],
addSynchronizer(synchronizer) { addLinker(linker) {
this.synchronizers.push(synchronizer); this.linkers.push(linker);
}, },
async getInstance() { async initEngine() {
let instance = await Self.findOne({ const accountConfig = await Self.findOne({
fields: ['homedir', 'shell', 'idBase'] fields: ['homedir', 'shell', 'idBase']
}); });
await instance.synchronizerInit(); const mailConfig = await models.MailConfig.findOne({
return instance; fields: ['domain']
});
const linkers = [];
for (const Linker of Self.linkers) {
const linker = await Linker.getLinker();
if (!linker) continue;
Object.assign(linker, {accountConfig});
await linker.init();
linkers.push(linker);
}
Object.assign(accountConfig, {
linkers,
domain: mailConfig.domain
});
return {
accountConfig,
linkers
};
},
async deinitEngine(engine) {
for (const linker of engine.linkers)
await linker.deinit();
},
async syncUser(userName, password) {
const engine = await Self.initEngine();
try {
await Self.syncUserBase(engine, userName, password, true);
} finally {
await Self.deinitEngine(engine);
}
}, },
async syncUsers() { async syncUsers() {
let instance = await Self.getInstance(); const engine = await Self.initEngine();
let usersToSync = new Set();
for (const linker of engine.linkers)
await linker.getUsers(usersToSync);
let usersToSync = await instance.synchronizerGetUsers();
usersToSync = Array.from(usersToSync.values()) usersToSync = Array.from(usersToSync.values())
.sort((a, b) => a.localeCompare(b)); .sort((a, b) => a.localeCompare(b));
for (let userName of usersToSync) { for (let userName of usersToSync) {
try { try {
// eslint-disable-next-line no-console
console.log(`Synchronizing user '${userName}'`); console.log(`Synchronizing user '${userName}'`);
await instance.synchronizerSyncUser(userName);
await Self.syncUserBase(engine, userName);
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' sinchronized`); console.log(` -> User '${userName}' sinchronized`);
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.error(` -> User '${userName}' synchronization error:`, err.message); console.error(` -> User '${userName}' synchronization error:`, err.message);
} }
} }
await instance.synchronizerDeinit(); await Self.deinitEngine(engine);
await Self.syncRoles(); await Self.syncRoles();
}, },
async syncUser(userName, password) { async syncUserBase(engine, userName, password, syncGroups) {
let instance = await Self.getInstance();
try {
await instance.synchronizerSyncUser(userName, password, true);
} finally {
await instance.synchronizerDeinit();
}
},
async syncRoles() {
let instance = await Self.getInstance();
try {
await instance.synchronizerSyncRoles();
} finally {
await instance.synchronizerDeinit();
}
},
async getSynchronizer() {
return await Self.findOne();
}
});
Object.assign(Self.prototype, {
async synchronizerInit() {
let mailConfig = await models.MailConfig.findOne({
fields: ['domain']
});
let synchronizers = [];
for (let Synchronizer of Self.synchronizers) {
let synchronizer = await Synchronizer.getSynchronizer();
if (!synchronizer) continue;
Object.assign(synchronizer, {
accountConfig: this
});
await synchronizer.init();
synchronizers.push(synchronizer);
}
Object.assign(this, {
synchronizers,
domain: mailConfig.domain
});
},
async synchronizerDeinit() {
for (let synchronizer of this.synchronizers)
await synchronizer.deinit();
},
async synchronizerSyncUser(userName, password, syncGroups) {
if (!userName) return; if (!userName) return;
userName = userName.toLowerCase(); userName = userName.toLowerCase();
@ -98,7 +89,7 @@ module.exports = Self => {
if (['administrator', 'root'].indexOf(userName) >= 0) if (['administrator', 'root'].indexOf(userName) >= 0)
return; return;
let user = await models.VnUser.findOne({ const user = await models.VnUser.findOne({
where: {name: userName}, where: {name: userName},
fields: [ fields: [
'id', 'id',
@ -130,27 +121,28 @@ module.exports = Self => {
] ]
}); });
let info = { const info = {
user, user,
hasAccount: false hasAccount: false
}; };
if (user) { if (user) {
let exists = await models.Account.exists(user.id); const exists = await models.Account.exists(user.id);
const {accountConfig} = engine;
Object.assign(info, { Object.assign(info, {
hasAccount: user.active && exists, hasAccount: user.active && exists,
corporateMail: `${userName}@${this.domain}`, corporateMail: `${userName}@${accountConfig.domain}`,
uidNumber: this.idBase + user.id uidNumber: accountConfig.idBase + user.id
}); });
} }
let errs = []; const errs = [];
for (let synchronizer of this.synchronizers) { for (const linker of engine.linkers) {
try { try {
await synchronizer.syncUser(userName, info, password); await linker.syncUser(userName, info, password);
if (syncGroups) if (syncGroups)
await synchronizer.syncUserGroups(userName, info); await linker.syncUserGroups(userName, info);
} catch (err) { } catch (err) {
errs.push(err); errs.push(err);
} }
@ -159,18 +151,16 @@ module.exports = Self => {
if (errs.length) throw errs[0]; if (errs.length) throw errs[0];
}, },
async synchronizerGetUsers() { async syncRoles() {
let usersToSync = new Set(); const engine = await Self.initEngine();
try {
await Self.rawSql(`CALL account.role_sync`);
for (let synchronizer of this.synchronizers) for (const linker of engine.linkers)
await synchronizer.getUsers(usersToSync); await linker.syncRoles();
} finally {
return usersToSync; await Self.deinitEngine(engine);
}, }
async synchronizerSyncRoles() {
for (let synchronizer of this.synchronizers)
await synchronizer.syncRoles();
} }
}); });
}; };

View File

@ -5,9 +5,9 @@ const crypto = require('crypto');
const nthash = require('smbhash').nthash; const nthash = require('smbhash').nthash;
module.exports = Self => { module.exports = Self => {
const shouldSync = process.env.NODE_ENV === 'production'; const shouldSync = process.env.NODE_ENV !== 'test';
Self.getSynchronizer = async function() { Self.getLinker = async function() {
return await Self.findOne({ return await Self.findOne({
fields: [ fields: [
'server', 'server',
@ -24,6 +24,7 @@ module.exports = Self => {
this.client = ldap.createClient({ this.client = ldap.createClient({
url: this.server url: this.server
}); });
this.client.on('error', () => {});
await this.client.bind(this.rdn, this.password); await this.client.bind(this.rdn, this.password);
}, },
@ -140,6 +141,7 @@ module.exports = Self => {
try { try {
if (shouldSync) if (shouldSync)
await client.del(dn); await client.del(dn);
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' removed from LDAP`); console.log(` -> User '${userName}' removed from LDAP`);
} catch (e) { } catch (e) {
if (e.name !== 'NoSuchObjectError') throw e; if (e.name !== 'NoSuchObjectError') throw e;

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {

View File

@ -1,6 +1,6 @@
module.exports = Self => { module.exports = Self => {
Self.getSynchronizer = async function() { Self.getLinker = async function() {
let NODE_ENV = process.env.NODE_ENV; let NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV || NODE_ENV == 'development') if (!NODE_ENV || NODE_ENV == 'development')
return null; return null;
@ -27,8 +27,7 @@ module.exports = Self => {
const [row] = await Self.rawSql( const [row] = await Self.rawSql(
`SELECT COUNT(*) AS nRows `SELECT COUNT(*) AS nRows
FROM mysql.user FROM mysql.user
WHERE User = ? WHERE User = ? AND Host = ?`,
AND Host = ?`,
[mysqlUser, this.userHost] [mysqlUser, this.userHost]
); );
let userExists = row.nRows > 0; let userExists = row.nRows > 0;
@ -38,8 +37,7 @@ module.exports = Self => {
const [row] = await Self.rawSql( const [row] = await Self.rawSql(
`SELECT Priv AS priv `SELECT Priv AS priv
FROM mysql.global_priv FROM mysql.global_priv
WHERE User = ? WHERE User = ? AND Host = ?`,
AND Host = ?`,
[mysqlUser, this.userHost] [mysqlUser, this.userHost]
); );
const priv = row && JSON.parse(row.priv); const priv = row && JSON.parse(row.priv);
@ -47,6 +45,7 @@ module.exports = Self => {
} }
if (!isUpdatable) { if (!isUpdatable) {
// eslint-disable-next-line no-console
console.warn(`RoleConfig.syncUser(): User '${userName}' cannot be updated, not managed by me`); console.warn(`RoleConfig.syncUser(): User '${userName}' cannot be updated, not managed by me`);
return; return;
} }
@ -84,14 +83,23 @@ module.exports = Self => {
[mysqlUser, this.userHost]); [mysqlUser, this.userHost]);
} catch (err) { } catch (err) {
if (err.code == 'ER_REVOKE_GRANTS') if (err.code == 'ER_REVOKE_GRANTS')
// eslint-disable-next-line no-console
console.warn(`${err.code}: ${err.sqlMessage}: ${err.sql}`); console.warn(`${err.code}: ${err.sqlMessage}: ${err.sql}`);
else else
throw err; throw err;
} }
await Self.rawSql('GRANT ? TO ?@?',
[role, mysqlUser, this.userHost]);
if (role) { const [row] = await Self.rawSql(
`SELECT COUNT(*) AS nRows
FROM mysql.user
WHERE User = ? AND Host = ''`,
[role]
);
const roleExists = row.nRows > 0;
if (roleExists) {
await Self.rawSql('GRANT ? TO ?@?',
[role, mysqlUser, this.userHost]);
await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?', await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?',
[role, mysqlUser, this.userHost]); [role, mysqlUser, this.userHost]);
} else { } else {

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {

View File

@ -9,7 +9,7 @@ module.exports = Self => {
Self.observe(hook, async() => { Self.observe(hook, async() => {
try { try {
await Self.rawSql(` await Self.rawSql(`
CREATE EVENT account.role_sync CREATE DEFINER = CURRENT_ROLE EVENT account.role_sync
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 SECOND ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 SECOND
DO CALL role_sync; DO CALL role_sync;
`); `);

View File

@ -1,6 +1,6 @@
const ldap = require('../util/ldapjs-extra'); const ldap = require('../util/ldapjs-extra');
const ssh = require('node-ssh'); const execFile = require('child_process').execFile;
/** /**
* Summary of userAccountControl flags: * Summary of userAccountControl flags:
@ -11,7 +11,9 @@ const UserAccountControlFlags = {
}; };
module.exports = Self => { module.exports = Self => {
Self.getSynchronizer = async function() { const shouldSync = process.env.NODE_ENV !== 'test';
Self.getLinker = async function() {
return await Self.findOne({ return await Self.findOne({
fields: [ fields: [
'host', 'host',
@ -19,6 +21,7 @@ module.exports = Self => {
'adController', 'adController',
'adUser', 'adUser',
'adPassword', 'adPassword',
'userDn',
'verifyCert' 'verifyCert'
] ]
}); });
@ -26,88 +29,124 @@ module.exports = Self => {
Object.assign(Self.prototype, { Object.assign(Self.prototype, {
async init() { async init() {
let sshClient = new ssh.NodeSSH(); const baseDn = this.adDomain
await sshClient.connect({ .split('.')
host: this.adController, .map(part => `dc=${part}`)
username: this.adUser, .join(',');
password: this.adPassword const bindDn = `cn=${this.adUser},cn=Users,${baseDn}`;
});
let adUser = `cn=${this.adUser},${this.usersDn()}`; const adClient = ldap.createClient({
let adClient = ldap.createClient({
url: `ldaps://${this.adController}:636`, url: `ldaps://${this.adController}:636`,
tlsOptions: {rejectUnauthorized: this.verifyCert} tlsOptions: {rejectUnauthorized: this.verifyCert}
}); });
await adClient.bind(adUser, this.adPassword); adClient.on('error', () => {});
await adClient.bind(bindDn, this.adPassword);
Object.assign(this, { Object.assign(this, {
sshClient, adClient,
adClient fullUsersDn: `${this.userDn},${baseDn}`,
bindDn
}); });
}, },
async deinit() { async deinit() {
await this.sshClient.dispose();
await this.adClient.unbind(); await this.adClient.unbind();
}, },
usersDn() { async sambaTool(command, args = []) {
let dnBase = this.adDomain let authArgs = [
.split('.') '--URL', `ldaps://${this.adController}`,
.map(part => `dc=${part}`) '--simple-bind-dn', this.bindDn,
.join(','); '--password', this.adPassword
return `cn=Users,${dnBase}`; ];
if (!this.verifyCert)
authArgs.push('--option', 'tls verify peer = no_check');
const allArgs = [command].concat(
args, authArgs
);
if (!shouldSync) return;
return await new Promise((resolve, reject) => {
execFile('samba-tool', allArgs, (err, stdout, stderr) => {
if (err)
reject(err);
else
resolve({stdout, stderr});
});
});
}, },
async syncUser(userName, info, password) { async getAdUser(userName) {
let {sshClient} = this; const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
let sambaUser = await this.adClient.searchOne(this.usersDn(), {
scope: 'sub', scope: 'sub',
attributes: ['userAccountControl'], attributes: [
'dn',
'userAccountControl',
'uidNumber',
'accountExpires',
'mail'
],
filter: `(&(objectClass=user)(sAMAccountName=${userName}))` filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
}); });
let isEnabled = sambaUser if (sambaUser) {
&& !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE); for (const intProp of ['uidNumber', 'userAccountControl']) {
if (sambaUser[intProp] != null)
if (process.env.NODE_ENV === 'test') sambaUser[intProp] = parseInt(sambaUser[intProp]);
return; }
}
return sambaUser;
},
async syncUser(userName, info, password) {
let sambaUser = await this.getAdUser(userName);
let entry;
if (info.hasAccount) { if (info.hasAccount) {
if (!sambaUser) { if (!sambaUser) {
await sshClient.exec('samba-tool user create', [ await this.sambaTool('user', [
userName, 'create', userName,
'--uid-number', `${info.uidNumber}`, '--userou', this.userDn,
'--mail-address', info.corporateMail,
'--random-password' '--random-password'
]); ]);
await sshClient.exec('samba-tool user setexpiry', [ sambaUser = await this.getAdUser(userName);
userName,
'--noexpiry'
]);
await sshClient.exec('mkhomedir_helper', [
userName,
'0027'
]);
}
if (!isEnabled) {
await sshClient.exec('samba-tool user enable', [
userName
]);
} }
if (password) { if (password) {
await sshClient.exec('samba-tool user setpassword', [ await this.sambaTool('user', [
userName, 'setpassword', userName,
'--newpassword', password '--newpassword', password
]); ]);
} }
} else if (isEnabled) {
await sshClient.exec('samba-tool user disable', [ entry = {
userName userAccountControl: sambaUser.userAccountControl
]); & ~UserAccountControlFlags.ACCOUNTDISABLE,
uidNumber: info.uidNumber,
accountExpires: 0,
mail: info.corporateMail
};
} else if (sambaUser) {
entry = {
userAccountControl: sambaUser.userAccountControl
| UserAccountControlFlags.ACCOUNTDISABLE
};
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' disabled on Samba`); console.log(` -> User '${userName}' disabled on Samba`);
} }
if (sambaUser && entry) {
const changes = [];
for (const prop in entry) {
if (sambaUser[prop] == entry[prop]) continue;
changes.push(new ldap.Change({
operation: 'replace',
modification: {
[prop]: entry[prop]
}
}));
}
if (changes.length && shouldSync)
await this.adClient.modify(sambaUser.dn, changes);
}
}, },
/** /**
@ -117,14 +156,15 @@ module.exports = Self => {
*/ */
async getUsers(usersToSync) { async getUsers(usersToSync) {
const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803'; const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
let filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}:=${UserAccountControlFlags.ACCOUNTDISABLE})`; const filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}`
+ `:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
let opts = { const opts = {
scope: 'sub', scope: 'sub',
attributes: ['sAMAccountName'], attributes: ['sAMAccountName'],
filter: `(&(objectClass=user)(${filter}))` filter: `(&(objectClass=user)(${filter}))`
}; };
await this.adClient.searchForeach(this.usersDn(), opts, await this.adClient.searchForeach(this.fullUsersDn, opts,
o => usersToSync.add(o.sAMAccountName)); o => usersToSync.add(o.sAMAccountName));
} }
}); });

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {
@ -28,6 +28,10 @@
"adPassword": { "adPassword": {
"type": "string" "type": "string"
}, },
"userDn": {
"type": "string",
"required": true
},
"verifyCert": { "verifyCert": {
"type": "boolean" "type": "boolean"
} }

View File

@ -0,0 +1,41 @@
{
"name": "SignInLog",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.signInLog"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"token": {
"required": true,
"type": "string",
"description": "Token's user"
},
"creationDate": {
"type": "date"
},
"userFk": {
"required": true,
"type": "number"
},
"ip": {
"type": "string"
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "VnUser",
"foreignKey": "userFk"
}
},
"scope": {
"order": ["creationDate DESC", "id DESC"]
}
}

View File

@ -2,7 +2,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
module.exports = Self => { module.exports = Self => {
Self.getSynchronizer = async function() { Self.getLinker = async function() {
return await Self.findOne({fields: ['id']}); return await Self.findOne({fields: ['id']});
}; };

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {
@ -16,4 +16,3 @@
} }
} }
} }

View File

@ -12,40 +12,40 @@
<vn-card class="vn-pa-lg" vn-focus> <vn-card class="vn-pa-lg" vn-focus>
<vn-vertical> <vn-vertical>
<vn-textfield <vn-textfield
label="Homedir base" label="Homedir base"
ng-model="$ctrl.config.homedir" ng-model="$ctrl.config.homedir"
rule="AccountConfig"> rule="AccountConfig">
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
label="Shell" label="Shell"
ng-model="$ctrl.config.shell" ng-model="$ctrl.config.shell"
rule="AccountConfig"> rule="AccountConfig">
</vn-textfield> </vn-textfield>
<vn-input-number <vn-input-number
label="User and role base id" label="User and role base id"
ng-model="$ctrl.config.idBase" ng-model="$ctrl.config.idBase"
rule="AccountConfig"> rule="AccountConfig">
</vn-input-number> </vn-input-number>
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number
label="Min" label="Min"
ng-model="$ctrl.config.min" ng-model="$ctrl.config.min"
rule="AccountConfig"> rule="AccountConfig">
</vn-input-number> </vn-input-number>
<vn-input-number <vn-input-number
label="Max" label="Max"
ng-model="$ctrl.config.max" ng-model="$ctrl.config.max"
rule="AccountConfig"> rule="AccountConfig">
</vn-input-number> </vn-input-number>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number
label="Warn" label="Warn"
ng-model="$ctrl.config.warn" ng-model="$ctrl.config.warn"
rule="AccountConfig"> rule="AccountConfig">
</vn-input-number> </vn-input-number>
<vn-input-number <vn-input-number
label="Inact" label="Inact"
ng-model="$ctrl.config.inact" ng-model="$ctrl.config.inact"
rule="AccountConfig"> rule="AccountConfig">
</vn-input-number> </vn-input-number>
@ -61,10 +61,6 @@
label="Synchronize all" label="Synchronize all"
ng-click="$ctrl.onSynchronizeAll()"> ng-click="$ctrl.onSynchronizeAll()">
</vn-button> </vn-button>
<vn-button
label="Synchronize user"
ng-click="syncUser.show()">
</vn-button>
<vn-button <vn-button
label="Synchronize roles" label="Synchronize roles"
ng-click="$ctrl.onSynchronizeRoles()"> ng-click="$ctrl.onSynchronizeRoles()">
@ -77,25 +73,3 @@
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
</form> </form>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onUserSync()"
on-close="$ctrl.onSyncClose()">
<tpl-body>
<vn-textfield
label="Username"
ng-model="$ctrl.syncUser"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
info="If password is not specified, just user attributes are synchronized">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,6 +1,5 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
import UserError from 'core/lib/user-error';
export default class Controller extends Section { export default class Controller extends Section {
onSynchronizeAll() { onSynchronizeAll() {
@ -8,27 +7,10 @@ export default class Controller extends Section {
this.$http.patch(`Accounts/syncAll`); this.$http.patch(`Accounts/syncAll`);
} }
onUserSync() {
if (!this.syncUser)
throw new UserError('Please enter the username');
let params = {
password: this.syncPassword,
force: true
};
return this.$http.patch(`Accounts/${this.syncUser}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
}
onSynchronizeRoles() { onSynchronizeRoles() {
this.$http.patch(`RoleInherits/sync`) this.$http.patch(`RoleInherits/sync`)
.then(() => this.vnApp.showSuccess(this.$t('Roles synchronized!'))); .then(() => this.vnApp.showSuccess(this.$t('Roles synchronized!')));
} }
onSyncClose() {
this.syncUser = '';
this.syncPassword = '';
}
} }
ngModule.component('vnAccountAccounts', { ngModule.component('vnAccountAccounts', {

View File

@ -3,7 +3,6 @@ Homedir base: Directorio base para carpetas de usuario
Shell: Intérprete de línea de comandos Shell: Intérprete de línea de comandos
User and role base id: Id base usuarios y roles User and role base id: Id base usuarios y roles
Synchronize all: Sincronizar todo Synchronize all: Sincronizar todo
Synchronize user: Sincronizar usuario
Synchronize roles: Sincronizar roles Synchronize roles: Sincronizar roles
If password is not specified, just user attributes are synchronized: >- If password is not specified, just user attributes are synchronized: >-
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
@ -12,5 +11,4 @@ Users synchronized!: ¡Usuarios sincronizados!
Username: Nombre de usuario Username: Nombre de usuario
Synchronize: Sincronizar Synchronize: Sincronizar
Please enter the username: Por favor introduce el nombre de usuario Please enter the username: Por favor introduce el nombre de usuario
User synchronized!: ¡Usuario sincronizado!
Roles synchronized!: ¡Roles sincronizados! Roles synchronized!: ¡Roles sincronizados!

View File

@ -67,6 +67,14 @@
translate> translate>
Deactivate user Deactivate user
</vn-item> </vn-item>
<vn-item
ng-click="syncUser.show()"
name="synchronizeUser"
vn-acl="it"
vn-acl-action="remove"
translate>
Synchronize
</vn-item>
</slot-menu> </slot-menu>
<slot-body> <slot-body>
<div class="attributes"> <div class="attributes">
@ -153,6 +161,32 @@
<button response="accept" translate>Change password</button> <button response="accept" translate>Change password</button>
</tpl-buttons> </tpl-buttons>
</vn-dialog> </vn-dialog>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onSync()"
on-close="$ctrl.onSyncClose()">
<tpl-title translate>
Do you want to synchronize user?
</tpl-title>
<tpl-body>
<vn-check
label="Synchronize password"
ng-model="$ctrl.shouldSyncPassword"
info="If password is not specified, just user attributes are synchronized"
vn-focus>
</vn-check>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
ng-if="$ctrl.shouldSyncPassword">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-user-summary user="$ctrl.user"></vn-user-summary> <vn-user-summary user="$ctrl.user"></vn-user-summary>
</vn-popup> </vn-popup>

View File

@ -120,6 +120,20 @@ class Controller extends Descriptor {
this.vnApp.showSuccess(this.$t(message)); this.vnApp.showSuccess(this.$t(message));
}); });
} }
onSync() {
const params = {force: true};
if (this.shouldSyncPassword)
params.password = this.syncPassword;
return this.$http.patch(`Accounts/${this.user.name}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
}
onSyncClose() {
this.shouldSyncPassword = false;
this.syncPassword = undefined;
}
} }
ngModule.component('vnUserDescriptor', { ngModule.component('vnUserDescriptor', {

View File

@ -22,6 +22,10 @@ Old password: Contraseña antigua
New password: Nueva contraseña New password: Nueva contraseña
Repeat password: Repetir contraseña Repeat password: Repetir contraseña
Password changed succesfully!: ¡Contraseña modificada correctamente! Password changed succesfully!: ¡Contraseña modificada correctamente!
Synchronize: Sincronizar
Do you want to synchronize user?: ¿Quieres sincronizar el usuario?
Synchronize password: Sincronizar contraseña
User synchronized!: ¡Usuario sincronizado!
Role changed succesfully!: ¡Rol modificado correctamente! Role changed succesfully!: ¡Rol modificado correctamente!
Password requirements: > Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud, La contraseña debe tener al menos {{ length }} caracteres de longitud,

View File

@ -12,7 +12,7 @@
<vn-card class="vn-pa-lg" vn-focus> <vn-card class="vn-pa-lg" vn-focus>
<vn-vertical> <vn-vertical>
<vn-check <vn-check
label="Enable synchronization" label="Enable synchronization"
ng-model="watcher.hasData"> ng-model="watcher.hasData">
</vn-check> </vn-check>
</vn-vertical> </vn-vertical>
@ -20,28 +20,33 @@
ng-if="watcher.hasData" ng-if="watcher.hasData"
class="vn-mt-md"> class="vn-mt-md">
<vn-textfield <vn-textfield
label="AD domain" label="AD domain"
ng-model="$ctrl.config.adDomain" ng-model="$ctrl.config.adDomain"
rule="SambaConfig"> rule="SambaConfig">
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
label="Domain controller" label="Domain controller"
ng-model="$ctrl.config.adController" ng-model="$ctrl.config.adController"
rule="SambaConfig"> rule="SambaConfig">
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
label="AD user" label="AD user"
ng-model="$ctrl.config.adUser" ng-model="$ctrl.config.adUser"
rule="SambaConfig"> rule="SambaConfig">
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
label="AD password" label="AD password"
ng-model="$ctrl.config.adPassword" ng-model="$ctrl.config.adPassword"
type="password" type="password"
rule="SambaConfig"> rule="SambaConfig">
</vn-textfield> </vn-textfield>
<vn-textfield
label="User DN (without domain part)"
ng-model="$ctrl.config.userDn"
rule="SambaConfig">
</vn-textfield>
<vn-check <vn-check
label="Verify certificate" label="Verify certificate"
ng-model="$ctrl.config.verifyCert"> ng-model="$ctrl.config.verifyCert">
</vn-check> </vn-check>
</vn-vertical> </vn-vertical>
@ -63,4 +68,4 @@
ng-click="watcher.loadOriginalData()"> ng-click="watcher.loadOriginalData()">
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
</form> </form>

View File

@ -3,6 +3,7 @@ Domain controller: Controlador de dominio
AD domain: Dominio AD AD domain: Dominio AD
AD user: Usuario AD AD user: Usuario AD
AD password: Contraseña AD AD password: Contraseña AD
User DN (without domain part): DN usuarios (sin la parte del dominio)
Verify certificate: Verificar certificado Verify certificate: Verificar certificado
Test connection: Probar conexión Test connection: Probar conexión
Samba connection established!: ¡Conexión con Samba establecida! Samba connection established!: ¡Conexión con Samba establecida!

View File

@ -82,12 +82,15 @@ module.exports = Self => {
if (args.zoneFk) { if (args.zoneFk) {
let stmts = []; let stmts = [];
stmts.push(new ParameterizedSQL('CALL vn.zone_getPostalCode(?)', [args.zoneFk])); stmts.push(new ParameterizedSQL('CALL vn.zone_getPostalCode(?)', [args.zoneFk]));
stmts.push(`SELECT name FROM tmp.zoneNodes`); stmts.push(`
SELECT zn.geoFk, zn.name
FROM tmp.zoneNodes zn
JOIN zone z ON z.id = zn.zoneFk`);
stmts.push(`DROP TEMPORARY TABLE tmp.zoneNodes`); stmts.push(`DROP TEMPORARY TABLE tmp.zoneNodes`);
const sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');
const [results] = await conn.executeStmt(sql); const results = await conn.executeStmt(sql);
for (let result of results) for (let result of results[1])
postalCode.push(result.name); postalCode.push(result.name);
} }

View File

@ -16,5 +16,5 @@ columns:
bookEntried: book entried bookEntried: book entried
isVatDeductible: is VAT deductible isVatDeductible: is VAT deductible
withholdingSageFk: withholding withholdingSageFk: withholding
expenceFkDeductible: expence deductible expenseFkDeductible: expense deductible
editorFk: editor editorFk: editor

View File

@ -16,5 +16,5 @@ columns:
bookEntried: fecha asiento bookEntried: fecha asiento
isVatDeductible: impuesto deducible isVatDeductible: impuesto deducible
withholdingSageFk: código de retención withholdingSageFk: código de retención
expenceFkDeductible: gasto deducible expenseFkDeductible: gasto deducible
editorFk: editor editorFk: editor

View File

@ -4,7 +4,7 @@ columns:
invoiceInFk: invoice in invoiceInFk: invoice in
taxCodeFk: tax taxCodeFk: tax
taxableBase: taxable base taxableBase: taxable base
expenceFk: expence expenseFk: expense
foreignValue: foreign amount foreignValue: foreign amount
taxTypeSageFk: tax type taxTypeSageFk: tax type
transactionTypeSageFk: transaction type transactionTypeSageFk: transaction type

View File

@ -4,7 +4,7 @@ columns:
invoiceInFk: factura recibida invoiceInFk: factura recibida
taxCodeFk: código IVA taxCodeFk: código IVA
taxableBase: base imponible taxableBase: base imponible
expenceFk: código gasto expenseFk: código gasto
foreignValue: importe divisa foreignValue: importe divisa
taxTypeSageFk: código impuesto taxTypeSageFk: código impuesto
transactionTypeSageFk: código transacción transactionTypeSageFk: código transacción

View File

@ -0,0 +1,37 @@
module.exports = Self => {
Self.remoteMethodCtx('new', {
description: 'Creates a new invoiceIn due day',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceIn id',
},
http: {
path: '/new',
verb: 'POST'
}
});
Self.new = async(ctx, id, options) => {
let tx;
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
await Self.rawSql(`CALL vn.invoiceInDueDay_calculate(?)`, [id], myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,45 @@
const LoopBackContext = require('loopback-context');
const models = require('vn-loopback/server/server').models;
describe('invoiceInDueDay new()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should correctly create a new due day', async() => {
const userId = 9;
const invoiceInFk = 6;
const ctx = {
req: {
accessToken: {userId: userId},
}
};
const tx = await models.InvoiceIn.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceInDueDay.destroyAll({
invoiceInFk
}, options);
await models.InvoiceInDueDay.new(ctx, invoiceInFk, options);
const result = await models.InvoiceInDueDay.find({where: {invoiceInFk}}, options);
expect(result).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -146,7 +146,7 @@ module.exports = Self => {
ii.docFk AS dmsFk, ii.docFk AS dmsFk,
dm.file, dm.file,
ii.supplierFk, ii.supplierFk,
ii.expenceFkDeductible deductibleExpenseFk, ii.expenseFkDeductible deductibleExpenseFk,
s.name AS supplierName, s.name AS supplierName,
s.account, s.account,
SUM(iid.amount) AS amount, SUM(iid.amount) AS amount,

View File

@ -29,15 +29,18 @@ module.exports = Self => {
SELECT iit.*, SELECT iit.*,
SUM(iidd.amount) totalDueDay SUM(iidd.amount) totalDueDay
FROM vn.invoiceIn ii FROM vn.invoiceIn ii
LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase, LEFT JOIN (
CAST(SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) AS DECIMAL(10,2)) totalVat SELECT SUM(iit.taxableBase) totalTaxableBase,
CAST(
SUM(IFNULL(iit.taxableBase * (1 + (ti.PorcentajeIva / 100)), iit.taxableBase))
AS DECIMAL(10, 2)
) totalVat
FROM vn.invoiceInTax iit FROM vn.invoiceInTax iit
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
WHERE iit.invoiceInFk = ?) iit ON TRUE WHERE iit.invoiceInFk = ?
) iit ON TRUE
LEFT JOIN vn.invoiceInDueDay iidd ON iidd.invoiceInFk = ii.id LEFT JOIN vn.invoiceInDueDay iidd ON iidd.invoiceInFk = ii.id
WHERE WHERE ii.id = ?`, [id, id]);
ii.id = ?`, [id, id]);
return result; return result;
}; };
}; };

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/invoice-in-due-day/new')(Self);
};

View File

@ -19,10 +19,7 @@
"type": "number" "type": "number"
}, },
"expenseFk": { "expenseFk": {
"type": "number", "type": "number"
"mysql": {
"columnName": "expenceFk"
}
}, },
"created": { "created": {
"type": "date" "type": "date"

View File

@ -51,7 +51,7 @@
"deductibleExpenseFk": { "deductibleExpenseFk": {
"type": "number", "type": "number",
"mysql": { "mysql": {
"columnName": "expenceFkDeductible" "columnName": "expenseFkDeductible"
} }
} }
}, },

View File

@ -7,7 +7,7 @@ export default class Controller extends Section {
super($element, $); super($element, $);
this.vnReport = vnReport; this.vnReport = vnReport;
const now = new Date(); const now = Date.vnNew();
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0); const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
this.params = { this.params = {

View File

@ -20,8 +20,7 @@ module.exports = Self => {
}, },
{ {
arg: 'addressFk', arg: 'addressFk',
type: 'Number', type: 'Any',
required: true,
description: 'The address id' description: 'The address id'
}], }],
http: { http: {

View File

@ -3,7 +3,7 @@
"base": "VnModel", "base": "VnModel",
"options": { "options": {
"mysql": { "mysql": {
"table": "expence" "table": "expense"
} }
}, },
"properties": { "properties": {

Some files were not shown because too many files have changed in this diff Show More