diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb85e4ffe..d4a1e147f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,45 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2330.01] - 2023-07-27
+
+### Added
+
+### Changed
+
+### Fixed
+
+
+## [2330.01] - 2023-07-27
+
+### Added
+- (Artículos -> Vista Previa) Añadido campo "Plástico reciclado"
+- (Rutas -> Troncales) Nueva sección
+- (Tickets -> Opciones) Opción establecer peso
+- (Clientes -> SMS) Nueva sección
+
+### Changed
+- (General -> Iconos) Añadidos nuevos iconos
+- (Clientes -> Razón social) Nuevas restricciones por pais
+
+
+### Fixed
+
+
+## [2328.01] - 2023-07-13
+
+### Added
+- (Clientes -> Morosos) Añadida columna "es trabajador"
+- (Trabajadores -> Departamentos) Nueva sección
+- (Trabajadores -> Departamentos) Añadido listado de Trabajadores por departamento
+- (Trabajadores -> Departamentos) Añadido características de departamento e información
+
+### Changed
+
+### Fixed
+- (Trabajadores -> Departamentos) Arreglado búscador
+
+
## [2326.01] - 2023-06-29
### Added
diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js
index c0a4e8ef3..19224057c 100644
--- a/back/methods/docuware/checkFile.js
+++ b/back/methods/docuware/checkFile.js
@@ -1,7 +1,5 @@
-const axios = require('axios');
-
module.exports = Self => {
- Self.remoteMethodCtx('checkFile', {
+ Self.remoteMethod('checkFile', {
description: 'Check if exist docuware file',
accessType: 'READ',
accepts: [
@@ -17,12 +15,16 @@ module.exports = Self => {
required: true,
description: 'The fileCabinet name'
},
+ {
+ arg: 'filter',
+ type: 'object',
+ description: 'The filter'
+ },
{
arg: 'signed',
type: 'boolean',
- required: true,
description: 'If pdf is necessary to be signed'
- }
+ },
],
returns: {
type: 'object',
@@ -34,7 +36,7 @@ module.exports = Self => {
}
});
- Self.checkFile = async function(ctx, id, fileCabinet, signed) {
+ Self.checkFile = async function(id, fileCabinet, filter, signed) {
const models = Self.app.models;
const action = 'find';
@@ -45,39 +47,34 @@ module.exports = Self => {
}
});
- const searchFilter = {
- condition: [
- {
- DBName: docuwareInfo.findById,
-
- Value: [id]
- }
- ],
- sortOrder: [
- {
- Field: 'FILENAME',
- Direction: 'Desc'
- }
- ]
- };
+ if (!filter) {
+ filter = {
+ condition: [
+ {
+ DBName: docuwareInfo.findById,
+ Value: [id]
+ }
+ ],
+ sortOrder: [
+ {
+ Field: 'FILENAME',
+ Direction: 'Desc'
+ }
+ ]
+ };
+ }
+ if (signed) {
+ filter.condition.push({
+ DBName: 'ESTADO',
+ Value: ['Firmado']
+ });
+ }
try {
- const options = await Self.getOptions();
-
- const fileCabinetId = await Self.getFileCabinet(fileCabinet);
- const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
-
- const response = await axios.post(
- `${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
- searchFilter,
- options.headers
- );
- const [documents] = response.data.Items;
+ const response = await Self.get(fileCabinet, filter);
+ const [documents] = response.Items;
if (!documents) return false;
- const state = documents.Fields.find(field => field.FieldName == 'ESTADO');
- if (signed && state.Item != 'Firmado') return false;
-
return {id: documents.Id};
} catch (error) {
return false;
diff --git a/back/methods/docuware/core.js b/back/methods/docuware/core.js
index 2053ddf85..74d922236 100644
--- a/back/methods/docuware/core.js
+++ b/back/methods/docuware/core.js
@@ -1,59 +1,6 @@
const axios = require('axios');
module.exports = Self => {
- /**
- * Returns the dialog id
- *
- * @param {string} code - The fileCabinet name
- * @param {string} action - The fileCabinet name
- * @param {string} fileCabinetId - Optional The fileCabinet name
- * @return {number} - The fileCabinet id
- */
- Self.getDialog = async(code, action, fileCabinetId) => {
- const docuwareInfo = await Self.app.models.Docuware.findOne({
- where: {
- code: code,
- action: action
- }
- });
- if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
-
- const options = await Self.getOptions();
-
- if (!process.env.NODE_ENV)
- return Math.round();
-
- const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
- const dialogs = response.data.Dialog;
- const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
-
- return dialogId;
- };
-
- /**
- * Returns the fileCabinetId
- *
- * @param {string} code - The fileCabinet code
- * @return {number} - The fileCabinet id
- */
- Self.getFileCabinet = async code => {
- const options = await Self.getOptions();
- const docuwareInfo = await Self.app.models.Docuware.findOne({
- where: {
- code: code
- }
- });
-
- if (!process.env.NODE_ENV)
- return Math.round();
-
- const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
- const fileCabinets = fileCabinetResponse.data.FileCabinet;
- const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
-
- return fileCabinetId;
- };
-
/**
* Returns basic headers
*
@@ -75,4 +22,139 @@ module.exports = Self => {
headers
};
};
+
+ /**
+ * Returns the dialog id
+ *
+ * @param {string} code - The fileCabinet name
+ * @param {string} action - The fileCabinet name
+ * @param {string} fileCabinetId - Optional The fileCabinet name
+ * @return {number} - The fileCabinet id
+ */
+ Self.getDialog = async(code, action, fileCabinetId) => {
+ if (!process.env.NODE_ENV)
+ return Math.floor(Math.random() + 100);
+
+ const docuwareInfo = await Self.app.models.Docuware.findOne({
+ where: {
+ code,
+ action
+ }
+ });
+ if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
+
+ const options = await Self.getOptions();
+
+ const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
+ const dialogs = response.data.Dialog;
+ const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
+
+ return dialogId;
+ };
+
+ /**
+ * Returns the fileCabinetId
+ *
+ * @param {string} code - The fileCabinet code
+ * @return {number} - The fileCabinet id
+ */
+ Self.getFileCabinet = async code => {
+ if (!process.env.NODE_ENV)
+ return Math.floor(Math.random() + 100);
+
+ const options = await Self.getOptions();
+ const docuwareInfo = await Self.app.models.Docuware.findOne({
+ where: {
+ code
+ }
+ });
+
+ const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
+ const fileCabinets = fileCabinetResponse.data.FileCabinet;
+ const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
+
+ return fileCabinetId;
+ };
+
+ /**
+ * Returns docuware data
+ *
+ * @param {string} code - The fileCabinet code
+ * @param {object} filter - The filter for docuware
+ * @param {object} parse - The fields parsed
+ * @return {object} - The data
+ */
+ Self.get = async(code, filter, parse) => {
+ if (!process.env.NODE_ENV) return;
+
+ const options = await Self.getOptions();
+ const fileCabinetId = await Self.getFileCabinet(code);
+ const dialogId = await Self.getDialog(code, 'find', fileCabinetId);
+
+ const data = await axios.post(
+ `${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
+ filter,
+ options.headers
+ );
+ return parser(data.data, parse);
+ };
+
+ /**
+ * Returns docuware data
+ *
+ * @param {string} code - The fileCabinet code
+ * @param {any} id - The id of docuware
+ * @param {object} parse - The fields parsed
+ * @return {object} - The data
+ */
+ Self.getById = async(code, id, parse) => {
+ if (!process.env.NODE_ENV) return;
+
+ const docuwareInfo = await Self.app.models.Docuware.findOne({
+ fields: ['findById'],
+ where: {
+ code,
+ action: 'find'
+ }
+ });
+ const filter = {
+ condition: [
+ {
+ DBName: docuwareInfo.findById,
+ Value: [id]
+ }
+ ]
+ };
+
+ return Self.get(code, filter, parse);
+ };
+
+ /**
+ * Returns docuware data filtered
+ *
+ * @param {array} data - The data
+ * @param {object} parse - The fields parsed
+ * @return {object} - The data parsed
+ */
+ function parser(data, parse) {
+ if (!(data && data.Items)) return data;
+
+ const parsed = [];
+ for (item of data.Items) {
+ const itemParsed = {};
+ item.Fields.map(field => {
+ if (field.ItemElementName.includes('Date')) field.Item = toDate(field.Item);
+ if (!parse) return itemParsed[field.FieldLabel] = field.Item;
+ if (parse[field.FieldLabel])
+ itemParsed[parse[field.FieldLabel]] = field.Item;
+ });
+ parsed.push(itemParsed);
+ }
+ return parsed;
+ }
+
+ function toDate(value) {
+ if (!value) return;
+ return new Date(Number(value.substring(6, 19)));
+ }
};
diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js
index 56d006ee7..a0d72ce01 100644
--- a/back/methods/docuware/download.js
+++ b/back/methods/docuware/download.js
@@ -3,7 +3,7 @@ const axios = require('axios');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
- Self.remoteMethodCtx('download', {
+ Self.remoteMethod('download', {
description: 'Download an docuware PDF',
accessType: 'READ',
accepts: [
@@ -16,8 +16,12 @@ module.exports = Self => {
{
arg: 'fileCabinet',
type: 'string',
- description: 'The file cabinet',
- http: {source: 'path'}
+ description: 'The file cabinet'
+ },
+ {
+ arg: 'filter',
+ type: 'object',
+ description: 'The filter'
}
],
returns: [
@@ -36,14 +40,15 @@ module.exports = Self => {
}
],
http: {
- path: `/:id/download/:fileCabinet`,
+ path: `/:id/download`,
verb: 'GET'
}
});
- Self.download = async function(ctx, id, fileCabinet) {
+ Self.download = async function(id, fileCabinet, filter) {
const models = Self.app.models;
- const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true);
+
+ const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, filter);
if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists');
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
diff --git a/back/methods/docuware/specs/checkFile.spec.js b/back/methods/docuware/specs/checkFile.spec.js
index dd11951cc..8460bb561 100644
--- a/back/methods/docuware/specs/checkFile.spec.js
+++ b/back/methods/docuware/specs/checkFile.spec.js
@@ -1,57 +1,15 @@
const models = require('vn-loopback/server/server').models;
-const axios = require('axios');
describe('docuware download()', () => {
const ticketId = 1;
- const userId = 9;
- const ctx = {
- req: {
-
- accessToken: {userId: userId},
- headers: {origin: 'http://localhost:5000'},
- }
- };
const docuwareModel = models.Docuware;
const fileCabinetName = 'deliveryNote';
- beforeAll(() => {
- spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
- spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
- });
-
it('should return false if there are no documents', async() => {
- const response = {
- data: {
- Items: []
- }
- };
- spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
+ spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve({Items: []}))));
- const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
-
- expect(result).toEqual(false);
- });
-
- it('should return false if the document is unsigned', async() => {
- const response = {
- data: {
- Items: [
- {
- Id: 1,
- Fields: [
- {
- FieldName: 'ESTADO',
- Item: 'Unsigned'
- }
- ]
- }
- ]
- }
- };
- spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
-
- const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
+ const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
expect(result).toEqual(false);
});
@@ -59,23 +17,21 @@ describe('docuware download()', () => {
it('should return the document data', async() => {
const docuwareId = 1;
const response = {
- data: {
- Items: [
- {
- Id: docuwareId,
- Fields: [
- {
- FieldName: 'ESTADO',
- Item: 'Firmado'
- }
- ]
- }
- ]
- }
+ Items: [
+ {
+ Id: docuwareId,
+ Fields: [
+ {
+ FieldName: 'ESTADO',
+ Item: 'Firmado'
+ }
+ ]
+ }
+ ]
};
- spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
+ spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
- const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
+ const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
expect(result.id).toEqual(docuwareId);
});
diff --git a/back/methods/docuware/specs/core.spec.js b/back/methods/docuware/specs/core.spec.js
new file mode 100644
index 000000000..cdf8a3b62
--- /dev/null
+++ b/back/methods/docuware/specs/core.spec.js
@@ -0,0 +1,135 @@
+const axios = require('axios');
+const models = require('vn-loopback/server/server').models;
+
+describe('Docuware core', () => {
+ beforeAll(() => {
+ process.env.NODE_ENV = 'testing';
+ });
+
+ afterAll(() => {
+ delete process.env.NODE_ENV;
+ });
+
+ describe('getOptions()', () => {
+ it('should return url and headers', async() => {
+ const result = await models.Docuware.getOptions();
+
+ expect(result.url).toBeDefined();
+ expect(result.headers).toBeDefined();
+ });
+ });
+
+ describe('getDialog()', () => {
+ it('should return dialogId', async() => {
+ const dialogs = {
+ data: {
+ Dialog: [
+ {
+ DisplayName: 'find',
+ Id: 'getDialogTest'
+ }
+ ]
+ }
+ };
+ spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
+ const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId');
+
+ expect(result).toEqual('getDialogTest');
+ });
+ });
+
+ describe('getFileCabinet()', () => {
+ it('should return fileCabinetId', async() => {
+ const code = 'deliveryNote';
+ const docuwareInfo = await models.Docuware.findOne({
+ where: {
+ code
+ }
+ });
+ const dialogs = {
+ data: {
+ FileCabinet: [
+ {
+ Name: docuwareInfo.fileCabinetName,
+ Id: 'getFileCabinetTest'
+ }
+ ]
+ }
+ };
+ spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
+ const result = await models.Docuware.getFileCabinet(code);
+
+ expect(result).toEqual('getFileCabinetTest');
+ });
+ });
+
+ describe('get()', () => {
+ it('should return data without parse', async() => {
+ spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ const data = {
+ data: {
+ id: 1
+ }
+ };
+ spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
+ const result = await models.Docuware.get('deliveryNote');
+
+ expect(result.id).toEqual(1);
+ });
+
+ it('should return data with parse', async() => {
+ spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ const data = {
+ data: {
+ Items: [{
+ Fields: [
+ {
+ ItemElementName: 'integer',
+ FieldLabel: 'firstRequiredField',
+ Item: 1
+ },
+ {
+ ItemElementName: 'string',
+ FieldLabel: 'secondRequiredField',
+ Item: 'myName'
+ },
+ {
+ ItemElementName: 'integer',
+ FieldLabel: 'notRequiredField',
+ Item: 2
+ }
+ ]
+ }]
+ }
+ };
+ const parse = {
+ 'firstRequiredField': 'id',
+ 'secondRequiredField': 'name',
+ };
+ spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
+ const [result] = await models.Docuware.get('deliveryNote', null, parse);
+
+ expect(result.id).toEqual(1);
+ expect(result.name).toEqual('myName');
+ expect(result.notRequiredField).not.toBeDefined();
+ });
+ });
+
+ describe('getById()', () => {
+ it('should return data', async() => {
+ spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ const data = {
+ data: {
+ id: 1
+ }
+ };
+ spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
+ const result = await models.Docuware.getById('deliveryNote', 1);
+
+ expect(result.id).toEqual(1);
+ });
+ });
+});
diff --git a/back/methods/docuware/specs/download.spec.js b/back/methods/docuware/specs/download.spec.js
index fcc1671a6..bc580a079 100644
--- a/back/methods/docuware/specs/download.spec.js
+++ b/back/methods/docuware/specs/download.spec.js
@@ -39,7 +39,7 @@ describe('docuware download()', () => {
spyOn(docuwareModel, 'checkFile').and.returnValue({});
spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
- const result = await models.Docuware.download(ctx, ticketId, fileCabinetName);
+ const result = await models.Docuware.download(ticketId, fileCabinetName);
expect(result[1]).toEqual('application/pdf');
expect(result[2]).toEqual(`filename="${ticketId}.pdf"`);
diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js
new file mode 100644
index 000000000..73cc705de
--- /dev/null
+++ b/back/methods/vn-user/sign-in.js
@@ -0,0 +1,102 @@
+const ForbiddenError = require('vn-loopback/util/forbiddenError');
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('signIn', {
+ description: 'Login a user with username/email and password',
+ accepts: [
+ {
+ arg: 'user',
+ type: 'String',
+ description: 'The user name or email',
+ required: true
+ }, {
+ arg: 'password',
+ type: 'String',
+ description: 'The password'
+ }
+ ],
+ returns: {
+ type: 'object',
+ root: true
+ },
+ http: {
+ path: `/sign-in`,
+ verb: 'POST'
+ }
+ });
+
+ Self.signIn = async function(ctx, user, password, options) {
+ const myOptions = {};
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ const where = Self.userUses(user);
+ const vnUser = await Self.findOne({
+ fields: ['id', 'name', 'password', 'active', 'email', 'passExpired', 'twoFactor'],
+ where
+ }, myOptions);
+
+ const validCredentials = vnUser
+ && await vnUser.hasPassword(password);
+
+ if (validCredentials) {
+ if (!vnUser.active)
+ throw new UserError('User disabled');
+ await Self.sendTwoFactor(ctx, vnUser, myOptions);
+ await Self.passExpired(vnUser, myOptions);
+
+ if (vnUser.twoFactor)
+ throw new ForbiddenError(null, 'REQUIRES_2FA');
+ }
+
+ return Self.validateLogin(user, password);
+ };
+
+ Self.passExpired = async(vnUser, myOptions) => {
+ const today = Date.vnNew();
+ today.setHours(0, 0, 0, 0);
+
+ if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) {
+ const $ = Self.app.models;
+ const changePasswordToken = await $.AccessToken.create({
+ scopes: ['changePassword'],
+ userId: vnUser.id
+ }, myOptions);
+ const err = new UserError('Pass expired', 'passExpired');
+ changePasswordToken.twoFactor = vnUser.twoFactor ? true : false;
+ err.details = {token: changePasswordToken};
+ throw err;
+ }
+ };
+
+ Self.sendTwoFactor = async(ctx, vnUser, myOptions) => {
+ if (vnUser.twoFactor === 'email') {
+ const $ = Self.app.models;
+
+ const code = String(Math.floor(Math.random() * 999999));
+ const maxTTL = ((60 * 1000) * 5); // 5 min
+ await $.AuthCode.upsertWithWhere({userFk: vnUser.id}, {
+ userFk: vnUser.id,
+ code: code,
+ expires: Date.vnNow() + maxTTL
+ }, myOptions);
+
+ const headers = ctx.req.headers;
+ const platform = headers['sec-ch-ua-platform']?.replace(/['"=]+/g, '');
+ const browser = headers['sec-ch-ua']?.replace(/['"=]+/g, '');
+ const params = {
+ args: {
+ recipientId: vnUser.id,
+ recipient: vnUser.email,
+ code: code,
+ ip: ctx.req?.connection?.remoteAddress,
+ device: platform && browser ? platform + ', ' + browser : headers['user-agent'],
+ },
+ req: {getLocale: ctx.req.getLocale},
+ };
+
+ await Self.sendTemplate(params, 'auth-code', true);
+ }
+ };
+};
diff --git a/back/methods/vn-user/signIn.js b/back/methods/vn-user/signIn.js
deleted file mode 100644
index e52d68df5..000000000
--- a/back/methods/vn-user/signIn.js
+++ /dev/null
@@ -1,81 +0,0 @@
-const UserError = require('vn-loopback/util/user-error');
-
-module.exports = Self => {
- Self.remoteMethod('signIn', {
- description: 'Login a user with username/email and password',
- accepts: [
- {
- arg: 'user',
- type: 'String',
- description: 'The user name or email',
- http: {source: 'form'},
- required: true
- }, {
- arg: 'password',
- type: 'String',
- description: 'The password'
- }
- ],
- returns: {
- type: 'object',
- root: true
- },
- http: {
- path: `/signIn`,
- verb: 'POST'
- }
- });
-
- Self.signIn = async function(user, password) {
- const models = Self.app.models;
- const usesEmail = user.indexOf('@') !== -1;
- let token;
-
- const userInfo = usesEmail
- ? {email: user}
- : {username: user};
- const instance = await Self.findOne({
- fields: ['username', 'password'],
- where: userInfo
- });
-
- const where = usesEmail
- ? {email: user}
- : {name: user};
- const vnUser = await Self.findOne({
- fields: ['id', 'active', 'passExpired'],
- where
- });
-
- const today = Date.vnNew();
- today.setHours(0, 0, 0, 0);
-
- const validCredentials = instance
- && await instance.hasPassword(password);
-
- if (validCredentials) {
- if (!vnUser.active)
- throw new UserError('User disabled');
-
- if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) {
- const changePasswordToken = await models.AccessToken.create({
- scopes: ['change-password'],
- userId: vnUser.id
- });
- const err = new UserError('Pass expired', 'passExpired');
- err.details = {token: changePasswordToken};
- throw err;
- }
-
- try {
- await models.Account.sync(instance.username, password);
- } catch (err) {
- console.warn(err);
- }
- }
-
- let loginInfo = Object.assign({password}, userInfo);
- token = await Self.login(loginInfo, 'user');
- return {token: token.id, ttl: token.ttl};
- };
-};
diff --git a/back/methods/vn-user/specs/sign-in.spec.js b/back/methods/vn-user/specs/sign-in.spec.js
new file mode 100644
index 000000000..f4cad88b9
--- /dev/null
+++ b/back/methods/vn-user/specs/sign-in.spec.js
@@ -0,0 +1,101 @@
+const {models} = require('vn-loopback/server/server');
+
+describe('VnUser Sign-in()', () => {
+ const employeeId = 1;
+ const unauthCtx = {
+ req: {
+ headers: {},
+ connection: {
+ remoteAddress: '127.0.0.1'
+ },
+ getLocale: () => 'en'
+ },
+ args: {}
+ };
+ const {VnUser, AccessToken} = models;
+ describe('when credentials are correct', () => {
+ it('should return the token', async() => {
+ let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare');
+ let accessToken = await AccessToken.findById(login.token);
+ let ctx = {req: {accessToken: accessToken}};
+
+ expect(login.token).toBeDefined();
+
+ await VnUser.logout(ctx.req.accessToken.id);
+ });
+
+ it('should return the token if the user doesnt exist but the client does', async() => {
+ let login = await VnUser.signIn(unauthCtx, 'PetterParker', 'nightmare');
+ let accessToken = await AccessToken.findById(login.token);
+ let ctx = {req: {accessToken: accessToken}};
+
+ expect(login.token).toBeDefined();
+
+ await VnUser.logout(ctx.req.accessToken.id);
+ });
+ });
+
+ describe('when credentials are incorrect', () => {
+ it('should throw a 401 error', async() => {
+ let error;
+
+ try {
+ await VnUser.signIn(unauthCtx, 'IDontExist', 'TotallyWrongPassword');
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error).toBeDefined();
+ expect(error.statusCode).toBe(401);
+ expect(error.code).toBe('LOGIN_FAILED');
+ });
+ });
+
+ describe('when two-factor auth is required', () => {
+ it('should throw a 403 error', async() => {
+ const employee = await VnUser.findById(employeeId);
+ const tx = await VnUser.beginTransaction({});
+
+ let error;
+ try {
+ const options = {transaction: tx};
+ await employee.updateAttribute('twoFactor', 'email', options);
+
+ await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options);
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ error = e;
+ }
+
+ expect(error).toBeDefined();
+ expect(error.statusCode).toBe(403);
+ expect(error.code).toBe('REQUIRES_2FA');
+ });
+ });
+
+ describe('when passExpired', () => {
+ it('should throw a passExpired error', async() => {
+ const tx = await VnUser.beginTransaction({});
+ const employee = await VnUser.findById(employeeId);
+ const yesterday = Date.vnNew();
+ yesterday.setDate(yesterday.getDate() - 1);
+
+ let error;
+ try {
+ const options = {transaction: tx};
+ await employee.updateAttribute('passExpired', yesterday, options);
+
+ await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options);
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ error = e;
+ }
+
+ expect(error).toBeDefined();
+ expect(error.statusCode).toBe(400);
+ expect(error.message).toBe('Pass expired');
+ });
+ });
+});
diff --git a/back/methods/vn-user/specs/signIn.spec.js b/back/methods/vn-user/specs/signIn.spec.js
deleted file mode 100644
index c3f4630c6..000000000
--- a/back/methods/vn-user/specs/signIn.spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const {models} = require('vn-loopback/server/server');
-
-describe('VnUser signIn()', () => {
- describe('when credentials are correct', () => {
- it('should return the token', async() => {
- let login = await models.VnUser.signIn('salesAssistant', 'nightmare');
- let accessToken = await models.AccessToken.findById(login.token);
- let ctx = {req: {accessToken: accessToken}};
-
- expect(login.token).toBeDefined();
-
- await models.VnUser.logout(ctx.req.accessToken.id);
- });
-
- it('should return the token if the user doesnt exist but the client does', async() => {
- let login = await models.VnUser.signIn('PetterParker', 'nightmare');
- let accessToken = await models.AccessToken.findById(login.token);
- let ctx = {req: {accessToken: accessToken}};
-
- expect(login.token).toBeDefined();
-
- await models.VnUser.logout(ctx.req.accessToken.id);
- });
- });
-
- describe('when credentials are incorrect', () => {
- it('should throw a 401 error', async() => {
- let error;
-
- try {
- await models.VnUser.signIn('IDontExist', 'TotallyWrongPassword');
- } catch (e) {
- error = e;
- }
-
- expect(error).toBeDefined();
- expect(error.statusCode).toBe(401);
- expect(error.code).toBe('LOGIN_FAILED');
- });
- });
-});
diff --git a/back/methods/vn-user/specs/validate-auth.spec.js b/back/methods/vn-user/specs/validate-auth.spec.js
new file mode 100644
index 000000000..8018bd3e1
--- /dev/null
+++ b/back/methods/vn-user/specs/validate-auth.spec.js
@@ -0,0 +1,52 @@
+const {models} = require('vn-loopback/server/server');
+
+describe('VnUser validate-auth()', () => {
+ describe('validateAuth', () => {
+ it('should signin if data is correct', async() => {
+ await models.AuthCode.create({
+ userFk: 9,
+ code: '555555',
+ expires: Date.vnNow() + (60 * 1000)
+ });
+ const token = await models.VnUser.validateAuth('developer', 'nightmare', '555555');
+
+ expect(token.token).toBeDefined();
+ });
+ });
+
+ describe('validateCode', () => {
+ it('should throw an error for a non existent code', async() => {
+ let error;
+ try {
+ await models.VnUser.validateCode('developer', '123456');
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error).toBeDefined();
+ expect(error.statusCode).toBe(400);
+ expect(error.message).toEqual('Invalid or expired verification code');
+ });
+
+ it('should throw an error when a code doesn`t match the login username', async() => {
+ let error;
+ let authCode;
+ try {
+ authCode = await models.AuthCode.create({
+ userFk: 1,
+ code: '555555',
+ expires: Date.vnNow() + (60 * 1000)
+ });
+
+ await models.VnUser.validateCode('developer', '555555');
+ } catch (e) {
+ authCode && await authCode.destroy();
+ error = e;
+ }
+
+ expect(error).toBeDefined();
+ expect(error.statusCode).toBe(400);
+ expect(error.message).toEqual('Authentication failed');
+ });
+ });
+});
diff --git a/back/methods/vn-user/validate-auth.js b/back/methods/vn-user/validate-auth.js
new file mode 100644
index 000000000..beab43417
--- /dev/null
+++ b/back/methods/vn-user/validate-auth.js
@@ -0,0 +1,66 @@
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethod('validateAuth', {
+ description: 'Login a user with username/email and password',
+ accepts: [
+ {
+ arg: 'user',
+ type: 'String',
+ description: 'The user name or email',
+ required: true
+ },
+ {
+ arg: 'password',
+ type: 'String',
+ description: 'The password'
+ },
+ {
+ arg: 'code',
+ type: 'String',
+ description: 'The auth code'
+ }
+ ],
+ returns: {
+ type: 'object',
+ root: true
+ },
+ http: {
+ path: `/validate-auth`,
+ verb: 'POST'
+ }
+ });
+
+ Self.validateAuth = async(username, password, code, options) => {
+ const myOptions = {};
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ const token = Self.validateLogin(username, password);
+ await Self.validateCode(username, code, myOptions);
+ return token;
+ };
+
+ Self.validateCode = async(username, code, myOptions) => {
+ const {AuthCode} = Self.app.models;
+
+ const authCode = await AuthCode.findOne({
+ where: {
+ code: code
+ }
+ }, myOptions);
+
+ const expired = authCode && Date.vnNow() > authCode.expires;
+ if (!authCode || expired)
+ throw new UserError('Invalid or expired verification code');
+
+ const user = await Self.findById(authCode.userFk, {
+ fields: ['name', 'twoFactor']
+ }, myOptions);
+
+ if (user.name !== username)
+ throw new UserError('Authentication failed');
+
+ await authCode.destroy(myOptions);
+ };
+};
diff --git a/back/model-config.json b/back/model-config.json
index d945f3250..0e37bf527 100644
--- a/back/model-config.json
+++ b/back/model-config.json
@@ -1,7 +1,4 @@
{
- "AccountingType": {
- "dataSource": "vn"
- },
"AccessTokenConfig": {
"dataSource": "vn",
"options": {
@@ -10,6 +7,12 @@
}
}
},
+ "AccountingType": {
+ "dataSource": "vn"
+ },
+ "AuthCode": {
+ "dataSource": "vn"
+ },
"Bank": {
"dataSource": "vn"
},
diff --git a/back/models/auth-code.json b/back/models/auth-code.json
new file mode 100644
index 000000000..b6a89115f
--- /dev/null
+++ b/back/models/auth-code.json
@@ -0,0 +1,31 @@
+{
+ "name": "AuthCode",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "salix.authCode"
+ }
+ },
+ "properties": {
+ "userFk": {
+ "type": "number",
+ "required": true,
+ "id": true
+ },
+ "code": {
+ "type": "string",
+ "required": true
+ },
+ "expires": {
+ "type": "number",
+ "required": true
+ }
+ },
+ "relations": {
+ "user": {
+ "type": "belongsTo",
+ "model": "Account",
+ "foreignKey": "userFk"
+ }
+ }
+}
diff --git a/back/models/company.json b/back/models/company.json
index f16c5762f..f8b5641ac 100644
--- a/back/models/company.json
+++ b/back/models/company.json
@@ -18,6 +18,9 @@
},
"expired": {
"type": "date"
+ },
+ "supplierAccountFk": {
+ "type": "number"
}
},
"scope": {
diff --git a/back/models/country.json b/back/models/country.json
index 8fa25b88e..fd540d819 100644
--- a/back/models/country.json
+++ b/back/models/country.json
@@ -22,6 +22,9 @@
},
"isUeeMember": {
"type": "boolean"
+ },
+ "isSocialNameUnique": {
+ "type": "boolean"
}
},
"relations": {
@@ -39,4 +42,4 @@
"permission": "ALLOW"
}
]
-}
\ No newline at end of file
+}
diff --git a/back/models/docuware.json b/back/models/docuware.json
index dec20eede..b1a6a8bce 100644
--- a/back/models/docuware.json
+++ b/back/models/docuware.json
@@ -28,5 +28,12 @@
"findById": {
"type": "string"
}
+ },
+ "relations": {
+ "dmsType": {
+ "type": "belongsTo",
+ "model": "DmsType",
+ "foreignKey": "dmsTypeFk"
+ }
}
}
diff --git a/back/models/vn-user.js b/back/models/vn-user.js
index b58395acc..a7ce12073 100644
--- a/back/models/vn-user.js
+++ b/back/models/vn-user.js
@@ -5,11 +5,12 @@ const {Email} = require('vn-print');
module.exports = function(Self) {
vnModel(Self);
- require('../methods/vn-user/signIn')(Self);
+ require('../methods/vn-user/sign-in')(Self);
require('../methods/vn-user/acl')(Self);
require('../methods/vn-user/recover-password')(Self);
require('../methods/vn-user/validate-token')(Self);
require('../methods/vn-user/privileges')(Self);
+ require('../methods/vn-user/validate-auth')(Self);
require('../methods/vn-user/renew-token')(Self);
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
@@ -111,6 +112,18 @@ module.exports = function(Self) {
return email.send();
});
+ Self.validateLogin = async function(user, password) {
+ let loginInfo = Object.assign({password}, Self.userUses(user));
+ token = await Self.login(loginInfo, 'user');
+ return {token: token.id, ttl: token.ttl};
+ };
+
+ Self.userUses = function(user) {
+ return user.indexOf('@') !== -1
+ ? {email: user}
+ : {username: user};
+ };
+
const _setPassword = Self.prototype.setPassword;
Self.prototype.setPassword = async function(newPassword, options, cb) {
if (cb === undefined && typeof options === 'function') {
@@ -143,8 +156,9 @@ module.exports = function(Self) {
}
};
- Self.sharedClass._methods.find(method => method.name == 'changePassword')
- .accessScopes = ['change-password'];
+ Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls =
+ Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
+ .filter(acl => acl.property != 'changePassword');
// FIXME: https://redmine.verdnatura.es/issues/5761
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
diff --git a/back/models/vn-user.json b/back/models/vn-user.json
index 61e42f77a..9131c9134 100644
--- a/back/models/vn-user.json
+++ b/back/models/vn-user.json
@@ -59,7 +59,10 @@
},
"passExpired": {
"type": "date"
- }
+ },
+ "twoFactor": {
+ "type": "string"
+ }
},
"relations": {
"role": {
@@ -111,6 +114,13 @@
"principalId": "$authenticated",
"permission": "ALLOW"
},
+ {
+ "property": "validateAuth",
+ "accessType": "EXECUTE",
+ "principalType": "ROLE",
+ "principalId": "$everyone",
+ "permission": "ALLOW"
+ },
{
"property": "privileges",
"accessType": "*",
diff --git a/db/changes/232601/00-aclInvoiceTickets.sql b/db/changes/232601/00-aclInvoiceTickets.sql
new file mode 100644
index 000000000..2c221950e
--- /dev/null
+++ b/db/changes/232601/00-aclInvoiceTickets.sql
@@ -0,0 +1,2 @@
+INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
+ VALUES('Ticket', 'invoiceTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee');
diff --git a/db/changes/232602/01-aclAddAlias.sql b/db/changes/232602/01-aclAddAlias.sql
new file mode 100644
index 000000000..d4df3cd44
--- /dev/null
+++ b/db/changes/232602/01-aclAddAlias.sql
@@ -0,0 +1,8 @@
+DELETE FROM `salix`.`ACL` WHERE model = 'MailAliasAccount';
+
+INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
+ VALUES
+ ('MailAliasAccount', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
+ ('MailAliasAccount', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
+ ('MailAliasAccount', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
+ ('MailAliasAccount', 'canEditAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');
diff --git a/db/changes/232801/00-authCode.sql b/db/changes/232801/00-authCode.sql
new file mode 100644
index 000000000..a256db43f
--- /dev/null
+++ b/db/changes/232801/00-authCode.sql
@@ -0,0 +1,13 @@
+create table `salix`.`authCode`
+(
+ userFk int UNSIGNED not null,
+ code int not null,
+ expires bigint not null,
+ constraint authCode_pk
+ primary key (userFk),
+ constraint authCode_unique
+ unique (code),
+ constraint authCode_user_id_fk
+ foreign key (userFk) references `account`.`user` (id)
+ on update cascade on delete cascade
+);
diff --git a/db/changes/232801/00-client_create.sql b/db/changes/232801/00-client_create.sql
new file mode 100644
index 000000000..d21094dad
--- /dev/null
+++ b/db/changes/232801/00-client_create.sql
@@ -0,0 +1,89 @@
+DROP PROCEDURE IF EXISTS `vn`.`clientCreate`;
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`(
+ vFirstname VARCHAR(50),
+ vSurnames VARCHAR(50),
+ vFi VARCHAR(9),
+ vAddress TEXT,
+ vPostcode CHAR(5),
+ vCity VARCHAR(25),
+ vProvinceFk SMALLINT(5),
+ vCompanyFk SMALLINT(5),
+ vPhone VARCHAR(11),
+ vEmail VARCHAR(255),
+ vUserFk INT
+)
+BEGIN
+/**
+ * Create new client
+ *
+ * @params vFirstname firstName
+ * @params vSurnames surnames
+ * @params vFi company code from accounting transactions
+ * @params vAddress address
+ * @params vPostcode postCode
+ * @params vCity city
+ * @params vProvinceFk province
+ * @params vCompanyFk company in which he has become a client
+ * @params vPhone telephone number
+ * @params vEmail email address
+ * @params vUserFk user id
+ */
+ DECLARE vPayMethodFk INT;
+ DECLARE vDueDay INT;
+ DECLARE vDefaultCredit DECIMAL(10, 2);
+ DECLARE vIsTaxDataChecked TINYINT(1);
+ DECLARE vHasCoreVnl BOOLEAN;
+ DECLARE vMandateTypeFk INT;
+
+ SELECT defaultPayMethodFk,
+ defaultDueDay,
+ defaultCredit,
+ defaultIsTaxDataChecked,
+ defaultHasCoreVnl,
+ defaultMandateTypeFk
+ INTO vPayMethodFk,
+ vDueDay,
+ vDefaultCredit,
+ vIsTaxDataChecked,
+ vHasCoreVnl,
+ vMandateTypeFk
+ FROM clientConfig;
+
+ INSERT INTO `client`
+ SET id = vUserFk,
+ name = CONCAT(vFirstname, ' ', vSurnames),
+ street = vAddress,
+ fi = TRIM(vFi),
+ phone = vPhone,
+ email = vEmail,
+ provinceFk = vProvinceFk,
+ city = vCity,
+ postcode = vPostcode,
+ socialName = CONCAT(vSurnames, ' ', vFirstname),
+ payMethodFk = vPayMethodFk,
+ dueDay = vDueDay,
+ credit = vDefaultCredit,
+ isTaxDataChecked = vIsTaxDataChecked,
+ hasCoreVnl = vHasCoreVnl,
+ isEqualizated = FALSE
+ ON duplicate KEY UPDATE
+ payMethodFk = vPayMethodFk,
+ dueDay = vDueDay,
+ credit = vDefaultCredit,
+ isTaxDataChecked = vIsTaxDataChecked,
+ hasCoreVnl = vHasCoreVnl,
+ isActive = TRUE;
+
+ INSERT INTO mandate (clientFk, companyFk, mandateTypeFk)
+ SELECT vUserFk, vCompanyFk, vMandateTypeFk
+ WHERE NOT EXISTS (
+ SELECT id
+ FROM mandate
+ WHERE clientFk = vUserFk
+ AND companyFk = vCompanyFk
+ AND mandateTypeFk = vMandateTypeFk
+ );
+END$$
+DELIMITER ;
diff --git a/db/changes/232801/00-client_create2.sql b/db/changes/232801/00-client_create2.sql
new file mode 100644
index 000000000..f2e660351
--- /dev/null
+++ b/db/changes/232801/00-client_create2.sql
@@ -0,0 +1,17 @@
+ALTER TABLE `vn`.`clientConfig` ADD defaultPayMethodFk tinyint(3) unsigned NULL;
+ALTER TABLE `vn`.`clientConfig` ADD defaultDueDay int unsigned NULL;
+ALTER TABLE `vn`.`clientConfig` ADD defaultCredit decimal(10, 2) NULL;
+ALTER TABLE `vn`.`clientConfig` ADD defaultIsTaxDataChecked tinyint(1) NULL;
+ALTER TABLE `vn`.`clientConfig` ADD defaultHasCoreVnl boolean NULL;
+ALTER TABLE `vn`.`clientConfig` ADD defaultMandateTypeFk smallint(5) NULL;
+ALTER TABLE `vn`.`clientConfig` ADD CONSTRAINT clientNewConfigPayMethod_FK FOREIGN KEY (defaultPayMethodFk) REFERENCES vn.payMethod(id);
+ALTER TABLE `vn`.`clientConfig` ADD CONSTRAINT clientNewConfigMandateType_FK FOREIGN KEY (defaultMandateTypeFk) REFERENCES vn.mandateType(id);
+
+UPDATE `vn`.`clientConfig`
+ SET defaultPayMethodFk = 4,
+ defaultDueDay = 5,
+ defaultCredit = 300.0,
+ defaultIsTaxDataChecked = 1,
+ defaultHasCoreVnl = 1,
+ defaultMandateTypeFk = 2
+ WHERE id = 1;
diff --git a/db/changes/232801/00-department.sql b/db/changes/232801/00-department.sql
new file mode 100644
index 000000000..3dcb8501d
--- /dev/null
+++ b/db/changes/232801/00-department.sql
@@ -0,0 +1,24 @@
+alter table `vn`.`department`
+ add `twoFactor` ENUM ('email') null comment 'Default user two-factor auth type';
+
+drop trigger `vn`.`department_afterUpdate`;
+
+DELIMITER $$
+$$
+create definer = root@localhost trigger `vn`.`department_afterUpdate`
+ after update
+ on department
+ for each row
+BEGIN
+ IF !(OLD.parentFk <=> NEW.parentFk) THEN
+ UPDATE vn.department_recalc SET isChanged = TRUE;
+ END IF;
+
+ IF !(OLD.twoFactor <=> NEW.twoFactor) THEN
+ UPDATE account.user u
+ JOIN vn.workerDepartment wd ON wd.workerFk = u.id
+ SET u.twoFactor = NEW.twoFactor
+ WHERE wd.departmentFk = NEW.id;
+ END IF;
+END;$$
+DELIMITER ;
diff --git a/db/changes/232801/00-user.sql b/db/changes/232801/00-user.sql
new file mode 100644
index 000000000..376b3dbb1
--- /dev/null
+++ b/db/changes/232801/00-user.sql
@@ -0,0 +1,5 @@
+alter table `account`.`user`
+ add `twoFactor` ENUM ('email') null comment 'Two-factor auth type';
+
+DELETE FROM `salix`.`ACL`
+ WHERE model = 'VnUser' AND property = 'changePassword';
diff --git a/db/changes/232802/01-aclWorkerDisable.sql b/db/changes/232802/01-aclWorkerDisable.sql
new file mode 100644
index 000000000..149dd6f15
--- /dev/null
+++ b/db/changes/232802/01-aclWorkerDisable.sql
@@ -0,0 +1,4 @@
+INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
+ VALUES
+ ('WorkerDisableExcluded', '*', 'READ', 'ALLOW', 'ROLE', 'itManagement'),
+ ('WorkerDisableExcluded', '*', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');
diff --git a/db/changes/233001/.gitkeep b/db/changes/233001/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/db/changes/233001/00-clientSms.sql b/db/changes/233001/00-clientSms.sql
new file mode 100644
index 000000000..e1e34f6b2
--- /dev/null
+++ b/db/changes/233001/00-clientSms.sql
@@ -0,0 +1,15 @@
+CREATE TABLE `vn`.`clientSms` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clientFk` int(11) NOT NULL,
+ `smsFk` mediumint(8) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `clientSms_FK` (`clientFk`),
+ KEY `clientSms_FK_1` (`smsFk`),
+ CONSTRAINT `clientSms_FK` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON UPDATE CASCADE,
+ CONSTRAINT `clientSms_FK_1` FOREIGN KEY (`smsFk`) REFERENCES `sms` (`id`) ON UPDATE CASCADE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
+
+INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
+ VALUES
+ ('ClientSms', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
+ ('ClientSms', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee');
diff --git a/db/changes/233001/00-company.sql b/db/changes/233001/00-company.sql
new file mode 100644
index 000000000..a3b61b9cc
--- /dev/null
+++ b/db/changes/233001/00-company.sql
@@ -0,0 +1 @@
+ALTER TABLE `vn`.`company` MODIFY COLUMN sage200Company int(2) DEFAULT 10 NOT NULL;
diff --git a/db/changes/233001/00-fixACLVehicle.sql b/db/changes/233001/00-fixACLVehicle.sql
new file mode 100644
index 000000000..6625f0d5c
--- /dev/null
+++ b/db/changes/233001/00-fixACLVehicle.sql
@@ -0,0 +1,3 @@
+INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
+ VALUES
+ ('Vehicle','sorted','WRITE','ALLOW','employee');
\ No newline at end of file
diff --git a/db/changes/233001/00-itemRecycle.sql b/db/changes/233001/00-itemRecycle.sql
new file mode 100644
index 000000000..c191e5d1c
--- /dev/null
+++ b/db/changes/233001/00-itemRecycle.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `vn`.`item` ADD recycledPlastic INT NULL;
+ALTER TABLE `vn`.`item` ADD nonRecycledPlastic INT NULL;
diff --git a/db/changes/233001/00-itemShelving_inventory.sql b/db/changes/233001/00-itemShelving_inventory.sql
new file mode 100644
index 000000000..b0b080ef3
--- /dev/null
+++ b/db/changes/233001/00-itemShelving_inventory.sql
@@ -0,0 +1,64 @@
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelving_inventory`(vParkingFromFk VARCHAR(8), vParkingToFk VARCHAR(8))
+BEGIN
+/**
+ * Devuelve un listado de ubicaciones a revisar
+ *
+ * @param vParkingFromFk Parking de partida, identificador de parking
+ * @param vParkingToFk Parking de llegada, identificador de parking
+*/
+
+ DECLARE vSectorFk INT;
+ DECLARE vPickingOrderFrom INT;
+ DECLARE vPickingOrderTo INT;
+
+ SELECT p.sectorFk, p.pickingOrder INTO vSectorFk, vPickingOrderFrom
+ FROM parking p
+ WHERE p.code = vParkingFromFk COLLATE 'utf8mb3_general_ci';
+
+ SELECT p.pickingOrder INTO vPickingOrderTo
+ FROM parking p
+ WHERE p.code = vParkingToFk COLLATE 'utf8mb3_general_ci';
+
+ CALL visible_getMisfit(vSectorFk);
+
+ SELECT ish.id,
+ p.pickingOrder,
+ p.code parking,
+ ish.shelvingFk,
+ ish.itemFk,
+ i.longName,
+ ish.visible,
+ p.sectorFk,
+ it.workerFk buyer,
+ CONCAT('http:',ic.url, '/catalog/1600x900/',i.image) urlImage,
+ ish.isChecked,
+ CASE
+ WHEN s.notPrepared > sm.parked THEN 0
+ WHEN sm.visible > sm.parked THEN 1
+ ELSE 2
+ END priority
+ FROM itemShelving ish
+ JOIN item i ON i.id = ish.itemFk
+ JOIN itemType it ON it.id = i.typeFk
+ JOIN tmp.stockMisfit sm ON sm.itemFk = ish.itemFk
+ JOIN shelving sh ON sh.code = ish.shelvingFk
+ JOIN parking p ON p.id = sh.parkingFk
+ JOIN (SELECT s.itemFk, sum(s.quantity) notPrepared
+ FROM sale s
+ JOIN ticket t ON t.id = s.ticketFk
+ JOIN warehouse w ON w.id = t.warehouseFk
+ JOIN config c ON c.mainWarehouseFk = w.id
+ WHERE t.shipped BETWEEN util.VN_CURDATE()
+ AND util.dayEnd(util.VN_CURDATE())
+ AND s.isPicked = FALSE
+ GROUP BY s.itemFk) s ON s.itemFk = i.id
+ JOIN hedera.imageConfig ic
+ WHERE p.pickingOrder BETWEEN vPickingOrderFrom AND vPickingOrderTo
+ AND p.sectorFk = vSectorFk
+ ORDER BY p.pickingOrder;
+
+END$$
+DELIMITER ;
+
diff --git a/db/changes/233001/00-noUniqueSocialName.sql b/db/changes/233001/00-noUniqueSocialName.sql
new file mode 100644
index 000000000..0dc4c832f
--- /dev/null
+++ b/db/changes/233001/00-noUniqueSocialName.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `vn`.`country`
+ADD COLUMN `isSocialNameUnique` tinyint(1) NOT NULL DEFAULT 1;
diff --git a/db/changes/233001/00-roadmap.sql b/db/changes/233001/00-roadmap.sql
new file mode 100644
index 000000000..9b5db54eb
--- /dev/null
+++ b/db/changes/233001/00-roadmap.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `vn`.`roadmap` COMMENT='Troncales diarios que se contratan';
+ALTER TABLE `vn`.`roadmap` ADD price decimal(10,2) NULL;
+ALTER TABLE `vn`.`roadmap` ADD driverName varchar(45) NULL;
+ALTER TABLE `vn`.`roadmap` ADD name varchar(45) NOT NULL;
+ALTER TABLE `vn`.`roadmap` CHANGE name name varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL AFTER id;
+ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL;
+
+ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales';
diff --git a/db/changes/233001/00-roadmapACL.sql b/db/changes/233001/00-roadmapACL.sql
new file mode 100644
index 000000000..4fc116f86
--- /dev/null
+++ b/db/changes/233001/00-roadmapACL.sql
@@ -0,0 +1,6 @@
+INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
+ VALUES
+ ('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'),
+ ('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'productionBoss'),
+ ('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'),
+ ('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'productionBoss');
diff --git a/db/changes/233001/00-ticketWeight.sql b/db/changes/233001/00-ticketWeight.sql
new file mode 100644
index 000000000..0b727b434
--- /dev/null
+++ b/db/changes/233001/00-ticketWeight.sql
@@ -0,0 +1,7 @@
+UPDATE `vn`.`ticket` t
+ JOIN `vn`.`ticketObservation` o ON o.ticketFk = t.id
+ SET t.weight = cast(REPLACE(o.description, ',', '.') as decimal(10,2))
+ WHERE o.observationTypeFk = 6;
+
+DELETE FROM `vn`.`ticketObservation` WHERE observationTypeFk = 6;
+DELETE FROM `vn`.`observationType` WHERE id = 6;
diff --git a/db/changes/233201/.gitkeep b/db/changes/233201/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/db/changes/233201/00-transferClient.sql b/db/changes/233201/00-transferClient.sql
new file mode 100644
index 000000000..8a7ce0543
--- /dev/null
+++ b/db/changes/233201/00-transferClient.sql
@@ -0,0 +1,2 @@
+INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
+ VALUES ('Ticket','transferClient','WRITE','ALLOW','ROLE','administrative');
\ No newline at end of file
diff --git a/db/changes/233201/00-updatePrice.sql b/db/changes/233201/00-updatePrice.sql
new file mode 100644
index 000000000..959943d6f
--- /dev/null
+++ b/db/changes/233201/00-updatePrice.sql
@@ -0,0 +1,2 @@
+INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
+ VALUES ('Ticket','canEditWeekly','WRITE','ALLOW','ROLE','buyer');
diff --git a/db/changes/233201/00-workerDocuware.sql b/db/changes/233201/00-workerDocuware.sql
new file mode 100644
index 000000000..2f2c4a1cd
--- /dev/null
+++ b/db/changes/233201/00-workerDocuware.sql
@@ -0,0 +1,10 @@
+ALTER TABLE `vn`.`docuware` ADD dmsTypeFk INT(11) DEFAULT NULL NULL;
+ALTER TABLE `vn`.`docuware` ADD CONSTRAINT docuware_FK FOREIGN KEY (dmsTypeFk) REFERENCES `vn`.`dmsType`(id) ON DELETE RESTRICT ON UPDATE CASCADE;
+INSERT INTO `vn`.`docuware` (code, fileCabinetName, `action`, dialogName, findById, dmsTypeFk)
+ VALUES
+ ('hr', 'RRHH', 'find', 'Búsqueda', 'N__DOCUMENTO', NULL); -- set dmsTypeFk 3 when deploy in production
+
+INSERT INTO `salix`.`url` (appName, environment, url)
+ VALUES
+ ('docuware', 'production', 'https://verdnatura.docuware.cloud/DocuWare/Platform/');
+
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index 208eb2c21..eaa00a3de 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -37,7 +37,7 @@ ALTER TABLE `vn`.`ticket` AUTO_INCREMENT = 1;
INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES
- ('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66);
+ ('DEFAULT_TOKEN', '1209600', CURDATE(), 66);
INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
VALUES
@@ -77,7 +77,10 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `
ORDER BY id;
INSERT INTO `account`.`account`(`id`)
- SELECT id FROM `account`.`user`;
+ SELECT `u`.`id`
+ FROM `account`.`user` `u`
+ JOIN `account`.`role` `r` ON `u`.`role` = `r`.`id`
+ WHERE `r`.`name` <> 'customer';
INSERT INTO `vn`.`educationLevel` (`id`, `name`)
VALUES
@@ -185,13 +188,13 @@ INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAd
UPDATE `vn`.`sector` SET mainPrinterFk = 1 WHERE id = 1;
-INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`)
+INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`)
VALUES
- (1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106, NULL, NULL),
- (1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107, NULL, NULL),
- (1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108, 1, NULL),
- (1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109, 1, NULL),
- (1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110, 2, NULL);
+ (1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106),
+ (1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107),
+ (1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108),
+ (1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109),
+ (1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110);
INSERT INTO `vn`.`parking` (`id`, `column`, `row`, `sectorFk`, `code`, `pickingOrder`)
VALUES
@@ -382,9 +385,16 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
(1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(1104, -30, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH));
-INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`)
+INSERT INTO `vn`.`mandateType`(`id`, `name`)
VALUES
- (200, 10);
+ (1, 'B2B'),
+ (2, 'CORE'),
+ (3, 'LCR');
+
+INSERT INTO `vn`.`clientConfig`(`id`, `riskTolerance`, `maxCreditRows`, `maxPriceIncreasingRatio`, `riskScope`, `defaultPayMethodFk`, `defaultDueDay`, `defaultCredit`, `defaultIsTaxDataChecked`, `defaultHasCoreVnl`, `defaultMandateTypeFk`)
+ VALUES
+ (1, 200, 10, 0.25, 2, 4, 5, 300.00, 1, 1, 2);
+
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
VALUES
@@ -700,40 +710,40 @@ INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agen
(6, NULL, 57, util.VN_CURDATE(), 5, 7, 'sixth route', 1.7, 60, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 3),
(7, NULL, 57, util.VN_CURDATE(), 6, 8, 'seventh route', 0, 70, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 5);
-INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`)
+INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`, `weight`)
VALUES
- (1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
- (2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
- (3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)),
- (4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH)),
- (5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH)),
- (6 , 1, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
- (7 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (8 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Bat cave', 121, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (9 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (10, 1, 1, 5, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (11, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (12, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (13, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (14, 1, 2, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, util.VN_CURDATE()),
- (15, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (16, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (17, 1, 7, 2, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE()),
- (18, 1, 4, 4, 4, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1108, 'Cerebro', 128, NULL, 0, 12, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +12 HOUR)),
- (19, 1, 5, 5, NULL, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 1, NULL, 5, 1, util.VN_CURDATE()),
- (20, 1, 5, 5, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
- (21, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Holland', 102, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
- (22, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Japan', 103, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
- (23, NULL, 8, 1, 7, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'address 21', 121, NULL, 0, 5, 5, 1, util.VN_CURDATE()),
- (24 ,NULL, 8, 1, 7, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 5, 5, 1, util.VN_CURDATE()),
- (25 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (26 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'An incredibly long alias for testing purposes', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
- (32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE());
+ (1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1),
+ (2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2),
+ (3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL),
+ (4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL),
+ (5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL),
+ (6 , 1, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL),
+ (7 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (8 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'Bat cave', 121, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (9 , NULL, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (10, 1, 1, 5, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (11, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (12, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (13, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (14, 1, 2, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, util.VN_CURDATE(), NULL),
+ (15, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (16, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (17, 1, 7, 2, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1106, 'Many Places', 126, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL),
+ (18, 1, 4, 4, 4, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1108, 'Cerebro', 128, NULL, 0, 12, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +12 HOUR), NULL),
+ (19, 1, 5, 5, NULL, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 1, NULL, 5, 1, util.VN_CURDATE(), NULL),
+ (20, 1, 5, 5, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Thailand', 129, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), NULL),
+ (21, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Holland', 102, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), NULL),
+ (22, NULL, 5, 5, 5, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 1109, 'Somewhere in Japan', 103, NULL, 0, 13, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH), NULL),
+ (23, NULL, 8, 1, 7, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1101, 'address 21', 121, NULL, 0, 5, 5, 1, util.VN_CURDATE(), NULL),
+ (24 ,NULL, 8, 1, 7, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 5, 5, 1, util.VN_CURDATE(), NULL),
+ (25 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (26 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'An incredibly long alias for testing purposes', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL),
+ (32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL);
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES
@@ -824,12 +834,6 @@ INSERT INTO `vn`.`greuge`(`id`, `clientFk`, `description`, `amount`, `shipped`,
(11, 1101, 'some heritage charges', -15.99, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 5, 1),
(12, 1101, 'some miscellaneous charges', 58.00, DATE_ADD(util.VN_CURDATE(), INTERVAL 1 MONTH), util.VN_CURDATE(), 6, 1);
-INSERT INTO `vn`.`mandateType`(`id`, `name`)
- VALUES
- (1, 'B2B'),
- (2, 'CORE'),
- (3, 'LCR');
-
INSERT INTO `vn`.`mandate`(`id`, `clientFk`, `companyFk`, `code`, `created`, `mandateTypeFk`)
VALUES
(1, 1102, 442, '1-1', util.VN_CURDATE(), 2);
@@ -2593,7 +2597,7 @@ UPDATE `vn`.`ticket`
UPDATE `vn`.`ticket`
SET refFk = 'A1111111'
- WHERE id = 6;
+ WHERE id = 6;
INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
VALUES
@@ -2602,9 +2606,18 @@ INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
(3, 6, 5),
(4, 7, 1);
-INSERT INTO `vn`.`expeditionTruck` (`id`, `eta`, `description`)
+INSERT INTO `vn`.`roadmap` (`id`, `name`, `tractorPlate`, `trailerPlate`, `phone`, `supplierFk`, `etd`, `observations`, `userFk`, `price`, `driverName`)
VALUES
- (1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet');
+ (1, 'val-algemesi', 'RE-001', 'PO-001', '111111111', 1, util.VN_NOW(), 'this is test observation', 1, 15, 'Batman'),
+ (2, 'alg-valencia', 'RE-002', 'PO-002', '111111111', 1, util.VN_NOW(), 'test observation', 1, 20, 'Robin'),
+ (3, 'alz-algemesi', 'RE-003', 'PO-003', '222222222', 2, DATE_ADD(util.VN_NOW(), INTERVAL 2 DAY), 'observations...', 2, 25, 'Driverman');
+
+INSERT INTO `vn`.`expeditionTruck` (`id`, `roadmapFk`, `warehouseFk`, `eta`, `description`, `userFk`)
+ VALUES
+ (1, 1, 1, DATE_ADD(util.VN_NOW(), INTERVAL 1 DAY), 'Best truck in fleet', 1),
+ (2, 1, 2, DATE_ADD(util.VN_NOW(), INTERVAL '1 2' DAY_HOUR), 'Second truck in fleet', 1),
+ (3, 1, 3, DATE_ADD(util.VN_NOW(), INTERVAL '1 4' DAY_HOUR), 'Third truck in fleet', 1),
+ (4, 2, 1, DATE_ADD(util.VN_NOW(), INTERVAL 3 DAY), 'Truck red', 1);
INSERT INTO `vn`.`expeditionPallet` (`id`, `truckFk`, `built`, `position`, `isPrint`)
VALUES
@@ -2848,8 +2861,8 @@ INSERT INTO `vn`.`profileType` (`id`, `name`)
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
- ('lilium', 'dev', 'http://localhost:9000/#/'),
- ('salix', 'dev', 'http://localhost:5000/#!/');
+ ('lilium', 'development', 'http://localhost:9000/#/'),
+ ('salix', 'development', 'http://localhost:5000/#!/');
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
VALUES
@@ -2940,3 +2953,8 @@ INSERT INTO `vn`.`invoiceInSerial` (`code`, `description`, `cplusTerIdNifFk`, `t
('E', 'Midgard', 1, 'CEE'),
('R', 'Jotunheim', 1, 'NATIONAL'),
('W', 'Vanaheim', 1, 'WORLD');
+
+
+INSERT INTO `hedera`.`imageConfig` (`id`, `maxSize`, `useXsendfile`, `url`)
+ VALUES
+ (1, 0, 0, 'marvel.com');
diff --git a/db/dump/structure.sql b/db/dump/structure.sql
index ee5fb40a1..4e7127310 100644
--- a/db/dump/structure.sql
+++ b/db/dump/structure.sql
@@ -77831,7 +77831,7 @@ BEGIN
LEAVE cur1Loop;
END IF;
- CALL zone_getLeaves2(vZoneFk, NULL, NULL);
+ CALL zone_getLeaves(vZoneFk, NULL, NULL, TRUE);
myLoop: LOOP
SET vGeoFk = NULL;
@@ -77844,7 +77844,7 @@ BEGIN
LEAVE myLoop;
END IF;
- CALL zone_getLeaves2(vZoneFk, vGeoFk, NULL);
+ CALL zone_getLeaves(vZoneFk, vGeoFk, NULL, TRUE);
UPDATE tmp.zoneNodes
SET isChecked = TRUE
WHERE geoFk = vGeoFk;
@@ -78130,55 +78130,58 @@ DELIMITER ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
-CREATE DEFINER=`root`@`localhost` PROCEDURE `zone_getLeaves`(vSelf INT, vParentFk INT, vSearch VARCHAR(255))
-BEGIN
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getLeaves`(
+ vSelf INT,
+ vParentFk INT,
+ vSearch VARCHAR(255),
+ vHasInsert BOOL
+)
+BEGIN
/**
* Devuelve las ubicaciones incluidas en la ruta y que sean hijos de parentFk.
* @param vSelf Id de la zona
* @param vParentFk Id del geo a calcular
- * @param vSearch cadena a buscar
+ * @param vSearch Cadena a buscar
+ * @param vHasInsert Indica si inserta en tmp.zoneNodes
+ * Optional @table tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
*/
DECLARE vIsNumber BOOL;
- DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != '';
+ DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch <> '';
- DROP TEMPORARY TABLE IF EXISTS tNodes;
- CREATE TEMPORARY TABLE tNodes
+ CREATE OR REPLACE TEMPORARY TABLE tNodes
(UNIQUE (id))
ENGINE = MEMORY
- SELECT id
- FROM zoneGeo
+ SELECT id
+ FROM zoneGeo
LIMIT 0;
IF vIsSearch THEN
SET vIsNumber = vSearch REGEXP '^[0-9]+$';
-
+
INSERT INTO tNodes
- SELECT id
+ SELECT id
FROM zoneGeo
WHERE (vIsNumber AND `name` = vSearch)
OR (!vIsNumber AND `name` LIKE CONCAT('%', vSearch, '%'))
LIMIT 1000;
-
+
ELSEIF vParentFk IS NULL THEN
INSERT INTO tNodes
- SELECT geoFk
+ SELECT geoFk
FROM zoneIncluded
WHERE zoneFk = vSelf;
END IF;
IF vParentFk IS NULL THEN
- DROP TEMPORARY TABLE IF EXISTS tChilds;
- CREATE TEMPORARY TABLE tChilds
+ CREATE OR REPLACE TEMPORARY TABLE tChilds
+ (INDEX(id))
ENGINE = MEMORY
- SELECT id
- FROM tNodes;
+ SELECT id FROM tNodes;
- DROP TEMPORARY TABLE IF EXISTS tParents;
- CREATE TEMPORARY TABLE tParents
+ CREATE OR REPLACE TEMPORARY TABLE tParents
+ (INDEX(id))
ENGINE = MEMORY
- SELECT id
- FROM zoneGeo
- LIMIT 0;
+ SELECT id FROM zoneGeo LIMIT 0;
myLoop: LOOP
DELETE FROM tParents;
@@ -78186,43 +78189,67 @@ BEGIN
SELECT parentFk id
FROM zoneGeo g
JOIN tChilds c ON c.id = g.id
- WHERE g.parentFk IS NOT NULL;
-
+ WHERE g.parentFk IS NOT NULL;
+
INSERT IGNORE INTO tNodes
- SELECT id
- FROM tParents;
-
- IF ROW_COUNT() = 0 THEN
+ SELECT id FROM tParents;
+
+ IF NOT ROW_COUNT() THEN
LEAVE myLoop;
END IF;
-
+
DELETE FROM tChilds;
INSERT INTO tChilds
- SELECT id
- FROM tParents;
+ SELECT id FROM tParents;
END LOOP;
-
+
DROP TEMPORARY TABLE tChilds, tParents;
END IF;
- IF !vIsSearch THEN
+ IF NOT vIsSearch THEN
INSERT IGNORE INTO tNodes
- SELECT id
+ SELECT id
FROM zoneGeo
WHERE parentFk <=> vParentFk;
END IF;
- SELECT g.id,
- g.name,
- g.parentFk,
- g.sons,
- isIncluded selected
- FROM zoneGeo g
- JOIN tNodes n ON n.id = g.id
- LEFT JOIN zoneIncluded i ON i.geoFk = g.id AND i.zoneFk = vSelf
- ORDER BY `depth`, selected DESC, name;
+ CREATE OR REPLACE TEMPORARY TABLE tZones
+ SELECT g.id,
+ g.name,
+ g.parentFk,
+ g.sons,
+ NOT g.sons OR `type` = 'country' isChecked,
+ i.isIncluded selected,
+ g.`depth`,
+ vSelf
+ FROM zoneGeo g
+ JOIN tNodes n ON n.id = g.id
+ LEFT JOIN zoneIncluded i ON i.geoFk = g.id
+ AND i.zoneFk = vSelf
+ ORDER BY g.`depth`, selected DESC, g.name;
- DROP TEMPORARY TABLE tNodes;
+ IF vHasInsert THEN
+ INSERT IGNORE INTO tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
+ SELECT id,
+ name,
+ parentFk,
+ sons,
+ isChecked,
+ vSelf
+ FROM tZones
+ WHERE selected
+ OR (selected IS NULL AND vParentFk IS NOT NULL);
+ ELSE
+ SELECT id,
+ name,
+ parentFk,
+ sons,
+ selected
+ FROM tZones
+ ORDER BY `depth`, selected DESC, name;
+ END IF;
+
+ DROP TEMPORARY TABLE tNodes, tZones;
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
@@ -78540,7 +78567,7 @@ BEGIN
INDEX(geoFk))
ENGINE = MEMORY;
- CALL zone_getLeaves2(vSelf, NULL , NULL);
+ CALL zone_getLeaves(vSelf, NULL , NULL, TRUE);
UPDATE tmp.zoneNodes zn
SET isChecked = 0
@@ -78553,7 +78580,7 @@ BEGIN
WHERE NOT isChecked
LIMIT 1;
- CALL zone_getLeaves2(vSelf, vGeoFk, NULL);
+ CALL zone_getLeaves(vSelf, vGeoFk, NULL, TRUE);
UPDATE tmp.zoneNodes
SET isChecked = TRUE
WHERE geoFk = vGeoFk;
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index b51a6cf7a..478635598 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -547,6 +547,7 @@ export default {
moreMenuMakeInvoice: '.vn-menu [name="makeInvoice"]',
moreMenuRegenerateInvoice: '.vn-menu [name="regenerateInvoice"]',
moreMenuChangeShippedHour: '.vn-menu [name="changeShipped"]',
+ moreMenuSMSOptions: '.vn-menu [name="smsOptions"]',
moreMenuPaymentSMS: '.vn-menu [name="sendPaymentSms"]',
moreMenuSendImportSms: '.vn-menu [name="sendImportSms"]',
SMStext: 'textarea[name="message"]',
@@ -874,7 +875,7 @@ export default {
},
routeTickets: {
- firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-input-number[ng-model="ticket.priority"]',
+ firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-td-editable',
firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check',
buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]',
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',
@@ -894,6 +895,18 @@ export default {
extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span',
},
+ department: {
+ firstDepartment: 'vn-worker-department-index vn-card > vn-treeview vn-treeview-childs vn-treeview-childs vn-treeview-childs a'
+ },
+ departmentSummary: {
+ header: 'vn-worker-department-summary h5',
+ name: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(1) > section > span',
+ code: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(2) > section > span',
+ chat: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(3) > section > span',
+ bossDepartment: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(4) > section > span',
+ email: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(5) > section > span',
+ clientFk: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(6) > section > span',
+ },
workerBasicData: {
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
@@ -901,6 +914,13 @@ export default {
locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
saveButton: 'vn-worker-basic-data button[type=submit]'
},
+ departmentBasicData: {
+ Name: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.name"]',
+ Code: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.code"]',
+ Chat: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.chat"]',
+ Email: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.notificationEmail"]',
+ saveButton: 'vn-worker-department-basic-data button[type=submit]'
+ },
workerNotes: {
addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]',
note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]',
diff --git a/e2e/paths/01-salix/05_changePassword.spec.js b/e2e/paths/01-salix/05_changePassword.spec.js
index 6e4cfb7f3..950f773dd 100644
--- a/e2e/paths/01-salix/05_changePassword.spec.js
+++ b/e2e/paths/01-salix/05_changePassword.spec.js
@@ -16,6 +16,7 @@ describe('ChangePassword path', async() => {
await browser.close();
});
+ const badPassword = 'badpass';
const oldPassword = 'nightmare';
const newPassword = 'newPass.1234';
describe('Bad login', async() => {
@@ -37,13 +38,22 @@ describe('ChangePassword path', async() => {
expect(message.text).toContain('Invalid current password');
// Bad attempt: password not meet requirements
+ message = await page.sendForm($.form, {
+ oldPassword: oldPassword,
+ newPassword: badPassword,
+ repeatPassword: badPassword
+ });
+
+ expect(message.text).toContain('Password does not meet requirements');
+
+ // Bad attempt: same password
message = await page.sendForm($.form, {
oldPassword: oldPassword,
newPassword: oldPassword,
repeatPassword: oldPassword
});
- expect(message.text).toContain('Password does not meet requirements');
+ expect(message.text).toContain('You can not use the same password');
// Correct attempt: change password
message = await page.sendForm($.form, {
diff --git a/e2e/paths/03-worker/01-department/01_summary.spec.js b/e2e/paths/03-worker/01-department/01_summary.spec.js
new file mode 100644
index 000000000..e4bf8fc2d
--- /dev/null
+++ b/e2e/paths/03-worker/01-department/01_summary.spec.js
@@ -0,0 +1,29 @@
+import selectors from '../../../helpers/selectors.js';
+import getBrowser from '../../../helpers/puppeteer';
+
+describe('department summary path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('hr', 'worker');
+ await page.accessToSection('worker.department');
+ await page.doSearch('INFORMATICA');
+ await page.click(selectors.department.firstDepartment);
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should reach the employee summary section and check all properties', async() => {
+ expect(await page.waitToGetProperty(selectors.departmentSummary.header, 'innerText')).toEqual('INFORMATICA');
+ expect(await page.getProperty(selectors.departmentSummary.name, 'innerText')).toEqual('INFORMATICA');
+ expect(await page.getProperty(selectors.departmentSummary.code, 'innerText')).toEqual('it');
+ expect(await page.getProperty(selectors.departmentSummary.chat, 'innerText')).toEqual('informatica-cau');
+ expect(await page.getProperty(selectors.departmentSummary.bossDepartment, 'innerText')).toEqual('');
+ expect(await page.getProperty(selectors.departmentSummary.email, 'innerText')).toEqual('-');
+ expect(await page.getProperty(selectors.departmentSummary.clientFk, 'innerText')).toEqual('-');
+ });
+});
diff --git a/e2e/paths/03-worker/01-department/02-basicData.spec.js b/e2e/paths/03-worker/01-department/02-basicData.spec.js
new file mode 100644
index 000000000..219d1426c
--- /dev/null
+++ b/e2e/paths/03-worker/01-department/02-basicData.spec.js
@@ -0,0 +1,43 @@
+import getBrowser from '../../../helpers/puppeteer';
+import selectors from '../../../helpers/selectors.js';
+
+const $ = {
+ form: 'vn-worker-department-basic-data form',
+};
+
+describe('department summary path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('hr', 'worker');
+ await page.accessToSection('worker.department');
+ await page.doSearch('INFORMATICA');
+ await page.click(selectors.department.firstDepartment);
+ });
+
+ beforeEach(async() => {
+ await page.accessToSection('worker.department.card.basicData');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should edit the department basic data and confirm the department data was edited`, async() => {
+ const values = {
+ Name: 'Informatica',
+ Code: 'IT',
+ Chat: 'informatica-cau',
+ Email: 'it@verdnatura.es',
+ };
+
+ await page.fillForm($.form, values);
+ const formValues = await page.fetchForm($.form, Object.keys(values));
+ const message = await page.sendForm($.form, values);
+
+ expect(message.isSuccess).toBeTrue();
+ expect(formValues).toEqual(values);
+ });
+});
diff --git a/e2e/paths/05-ticket/12_descriptor.spec.js b/e2e/paths/05-ticket/12_descriptor.spec.js
index ca6fb8290..95a114c45 100644
--- a/e2e/paths/05-ticket/12_descriptor.spec.js
+++ b/e2e/paths/05-ticket/12_descriptor.spec.js
@@ -124,6 +124,7 @@ describe('Ticket descriptor path', () => {
describe('SMS', () => {
it('should send the payment SMS using the descriptor menu', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS);
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 128);
@@ -134,8 +135,7 @@ describe('Ticket descriptor path', () => {
});
it('should send the import SMS using the descriptor menu', async() => {
- await page.waitToClick(selectors.ticketDescriptor.moreMenu);
- await page.waitForContentLoaded();
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms);
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 144);
diff --git a/e2e/paths/08-route/04_tickets.spec.js b/e2e/paths/08-route/04_tickets.spec.js
index ccd5562c2..c890162a1 100644
--- a/e2e/paths/08-route/04_tickets.spec.js
+++ b/e2e/paths/08-route/04_tickets.spec.js
@@ -18,8 +18,7 @@ describe('Route tickets path', () => {
});
it('should modify the first ticket priority', async() => {
- await page.clearInput(selectors.routeTickets.firstTicketPriority);
- await page.type(selectors.routeTickets.firstTicketPriority, '9');
+ await page.writeOnEditableTD(selectors.routeTickets.firstTicketPriority, '9');
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
diff --git a/front/core/services/app.js b/front/core/services/app.js
index fb0a08777..6c80fafcc 100644
--- a/front/core/services/app.js
+++ b/front/core/services/app.js
@@ -66,7 +66,11 @@ export default class App {
return this.logger.$http.get('Urls/findOne', {filter})
.then(res => {
- return res.data.url + route;
+ if (res && res.data)
+ return res.data.url + route;
+ })
+ .catch(() => {
+ this.showError('Direction not found');
});
}
}
diff --git a/front/core/services/auth.js b/front/core/services/auth.js
index 92ff4b061..844a5145d 100644
--- a/front/core/services/auth.js
+++ b/front/core/services/auth.js
@@ -24,7 +24,7 @@ export default class Auth {
initialize() {
let criteria = {
to: state => {
- const outLayout = ['login', 'recover-password', 'reset-password', 'change-password'];
+ const outLayout = ['login', 'recover-password', 'reset-password', 'change-password', 'validate-email'];
return !outLayout.some(ol => ol == state.name);
}
};
@@ -60,7 +60,25 @@ export default class Auth {
};
const now = new Date();
- return this.$http.post('VnUsers/signIn', params)
+ return this.$http.post('VnUsers/sign-in', params).then(
+ json => this.onLoginOk(json, now, remember));
+ }
+
+ validateCode(user, password, code, remember) {
+ if (!user) {
+ let err = new UserError('Please enter your username');
+ err.code = 'EmptyLogin';
+ return this.$q.reject(err);
+ }
+
+ let params = {
+ user: user,
+ password: password || undefined,
+ code: code
+ };
+
+ const now = new Date();
+ return this.$http.post('VnUsers/validate-auth', params)
.then(json => this.onLoginOk(json, now, remember));
}
diff --git a/front/core/services/locale/es.yml b/front/core/services/locale/es.yml
index cf8801b52..e9811e38f 100644
--- a/front/core/services/locale/es.yml
+++ b/front/core/services/locale/es.yml
@@ -3,4 +3,5 @@ Could not contact the server: No se ha podido contactar con el servidor, asegura
Please enter your username: Por favor introduce tu nombre de usuario
It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo
Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión
-Access denied: Acción no permitida
\ No newline at end of file
+Access denied: Acción no permitida
+Direction not found: Dirección no encontrada
diff --git a/front/core/services/token.js b/front/core/services/token.js
index 8f9f80e5c..c16cc3c4f 100644
--- a/front/core/services/token.js
+++ b/front/core/services/token.js
@@ -34,7 +34,6 @@ export default class Token {
remember
});
this.vnInterceptor.setToken(token);
-
try {
if (remember)
this.setStorage(localStorage, token, created, ttl);
@@ -84,7 +83,6 @@ export default class Token {
this.renewPeriod = data.renewPeriod;
this.stopRenewer();
this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000);
- this.checkValidity();
});
}
diff --git a/front/core/styles/icons/salixfont.css b/front/core/styles/icons/salixfont.css
index 6f9b1fe15..37f489fe2 100644
--- a/front/core/styles/icons/salixfont.css
+++ b/front/core/styles/icons/salixfont.css
@@ -1,402 +1,411 @@
@font-face {
- font-family: 'salixfont';
- src:
- url('./salixfont.ttf?wtrl3') format('truetype'),
- url('./salixfont.woff?wtrl3') format('woff'),
- url('./salixfont.svg?wtrl3#salixfont') format('svg');
- font-weight: normal;
- font-style: normal;
-}
+ font-family: 'salixfont';
+ src:
+ url('./salixfont.ttf?wtrl3') format('truetype'),
+ url('./salixfont.woff?wtrl3') format('woff'),
+ url('./salixfont.svg?wtrl3#salixfont') format('svg');
+ font-weight: normal;
+ font-style: normal;
+ }
-[class^="icon-"], [class*=" icon-"] {
- /* use !important to prevent issues with browser extensions that change fonts */
- font-family: 'salixfont' !important;
- speak: none;
- font-style: normal;
- font-weight: normal;
- font-variant: normal;
- text-transform: none;
- line-height: 1;
+ [class^="icon-"], [class*=" icon-"] {
+ /* use !important to prevent issues with browser extensions that change fonts */
+ font-family: 'salixfont' !important;
+ speak: none;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
- /* Better Font Rendering =========== */
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
+ /* Better Font Rendering =========== */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
-.icon-agency-term:before {
- content: "\e950";
-}
-.icon-defaulter:before {
- content: "\e94b";
-}
-.icon-100:before {
- content: "\e95a";
-}
-.icon-clientUnpaid:before {
- content: "\e95b";
-}
-.icon-history:before {
- content: "\e968";
-}
-.icon-Person:before {
- content: "\e901";
-}
-.icon-accessory:before {
- content: "\e90a";
-}
-.icon-account:before {
- content: "\e92a";
-}
-.icon-actions:before {
- content: "\e960";
-}
-.icon-addperson:before {
- content: "\e90e";
-}
-.icon-agency:before {
- content: "\e938";
-}
-.icon-albaran:before {
- content: "\e94d";
-}
-.icon-anonymous:before {
- content: "\e930";
-}
-.icon-apps:before {
- content: "\e951";
-}
-.icon-artificial:before {
- content: "\e90b";
-}
-.icon-attach:before {
- content: "\e92e";
-}
-.icon-barcode:before {
- content: "\e971";
-}
-.icon-basket:before {
- content: "\e914";
-}
-.icon-basketadd:before {
- content: "\e913";
-}
-.icon-bin:before {
- content: "\e96f";
-}
-.icon-botanical:before {
- content: "\e972";
-}
-.icon-bucket:before {
- content: "\e97a";
-}
-.icon-buscaman:before {
- content: "\e93b";
-}
-.icon-buyrequest:before {
- content: "\e932";
-}
-.icon-calc_volum .path1:before {
- content: "\e915";
-}
-.icon-calc_volum .path2:before {
- content: "\e916";
- margin-left: -1em;
-}
-.icon-calc_volum .path3:before {
- content: "\e917";
- margin-left: -1em;
-}
-.icon-calc_volum .path4:before {
- content: "\e918";
- margin-left: -1em;
-}
-.icon-calc_volum .path5:before {
- content: "\e919";
- margin-left: -1em;
-}
-.icon-calc_volum .path6:before {
- content: "\e91a";
- margin-left: -1em;
-}
-.icon-calendar:before {
- content: "\e93d";
-}
-.icon-catalog:before {
- content: "\e937";
-}
-.icon-claims:before {
- content: "\e963";
-}
-.icon-client:before {
- content: "\e928";
-}
-.icon-clone:before {
- content: "\e973";
-}
-.icon-columnadd:before {
- content: "\e954";
-}
-.icon-columndelete:before {
- content: "\e953";
-}
-.icon-components:before {
- content: "\e946";
-}
-.icon-consignatarios:before {
- content: "\e93f";
-}
-.icon-control:before {
- content: "\e949";
-}
-.icon-credit:before {
- content: "\e927";
-}
-.icon-deletedTicket:before {
- content: "\e935";
-}
-.icon-deleteline:before {
- content: "\e955";
-}
-.icon-delivery:before {
- content: "\e939";
-}
-.icon-deliveryprices:before {
- content: "\e91c";
-}
-.icon-details:before {
- content: "\e961";
-}
-.icon-dfiscales:before {
- content: "\e984";
-}
-.icon-disabled:before {
- content: "\e921";
-}
-.icon-doc:before {
- content: "\e977";
-}
-.icon-entry:before {
- content: "\e934";
-}
-.icon-exit:before {
- content: "\e92f";
-}
-.icon-eye:before {
- content: "\e976";
-}
-.icon-fixedPrice:before {
- content: "\e90d";
-}
-.icon-flower:before {
- content: "\e90c";
-}
-.icon-frozen:before {
- content: "\e900";
-}
-.icon-fruit:before {
- content: "\e903";
-}
-.icon-funeral:before {
- content: "\e904";
-}
-.icon-greenery:before {
- content: "\e907";
-}
-.icon-greuge:before {
- content: "\e944";
-}
-.icon-grid:before {
- content: "\e980";
-}
-.icon-handmade:before {
- content: "\e909";
-}
-.icon-handmadeArtificial:before {
- content: "\e902";
-}
-.icon-headercol:before {
- content: "\e958";
-}
-.icon-info:before {
- content: "\e952";
-}
-.icon-inventory:before {
- content: "\e92b";
-}
-.icon-invoice:before {
- content: "\e923";
-}
-.icon-invoice-in:before {
- content: "\e911";
-}
-.icon-invoice-in-create:before {
- content: "\e912";
-}
-.icon-invoice-out:before {
- content: "\e910";
-}
-.icon-isTooLittle:before {
- content: "\e91b";
-}
-.icon-item:before {
- content: "\e956";
-}
-.icon-languaje:before {
- content: "\e926";
-}
-.icon-lines:before {
- content: "\e942";
-}
-.icon-linesprepaired:before {
- content: "\e948";
-}
-.icon-logout:before {
- content: "\e936";
-}
-.icon-mana:before {
- content: "\e96a";
-}
-.icon-mandatory:before {
- content: "\e97b";
-}
-.icon-net:before {
- content: "\e931";
-}
-.icon-niche:before {
- content: "\e96c";
-}
-.icon-no036:before {
- content: "\e920";
-}
-.icon-noPayMethod:before {
- content: "\e905";
-}
-.icon-notes:before {
- content: "\e941";
-}
-.icon-noweb:before {
- content: "\e91f";
-}
-.icon-onlinepayment:before {
- content: "\e91d";
-}
-.icon-package:before {
- content: "\e978";
-}
-.icon-payment:before {
- content: "\e97e";
-}
-.icon-pbx:before {
- content: "\e93c";
-}
-.icon-pets:before {
- content: "\e947";
-}
-.icon-photo:before {
- content: "\e924";
-}
-.icon-plant:before {
- content: "\e908";
-}
-.icon-polizon:before {
- content: "\e95e";
-}
-.icon-preserved:before {
- content: "\e906";
-}
-.icon-recovery:before {
- content: "\e97c";
-}
-.icon-regentry:before {
- content: "\e964";
-}
-.icon-reserva:before {
- content: "\e959";
-}
-.icon-revision:before {
- content: "\e94a";
-}
-.icon-risk:before {
- content: "\e91e";
-}
-.icon-services:before {
- content: "\e94c";
-}
-.icon-settings:before {
- content: "\e979";
-}
-.icon-shipment-01:before {
- content: "\e929";
-}
-.icon-sign:before {
- content: "\e95d";
-}
-.icon-sms:before {
- content: "\e975";
-}
-.icon-solclaim:before {
- content: "\e95f";
-}
-.icon-solunion:before {
- content: "\e94e";
-}
-.icon-stowaway:before {
- content: "\e94f";
-}
-.icon-splitline:before {
- content: "\e93e";
-}
-.icon-splur:before {
- content: "\e970";
-}
-.icon-supplier:before {
- content: "\e925";
-}
-.icon-supplierfalse:before {
- content: "\e90f";
-}
-.icon-tags:before {
- content: "\e96d";
-}
-.icon-tax:before {
- content: "\e940";
-}
-.icon-thermometer:before {
- content: "\e933";
-}
-.icon-ticket:before {
- content: "\e96b";
-}
-.icon-ticketAdd:before {
- content: "\e945";
-}
-.icon-traceability:before {
- content: "\e962";
-}
-.icon-transaction:before {
- content: "\e966";
-}
-.icon-treatments:before {
- content: "\e922";
-}
-.icon-unavailable:before {
- content: "\e92c";
-}
-.icon-volume:before {
- content: "\e96e";
-}
-.icon-wand:before {
- content: "\e93a";
-}
-.icon-web:before {
- content: "\e982";
-}
-.icon-wiki:before {
- content: "\e92d";
-}
-.icon-worker:before {
- content: "\e957";
-}
-.icon-zone:before {
- content: "\e943";
-}
+ .icon-trailer:before {
+ content: "\e967";
+ }
+ .icon-grafana:before {
+ content: "\e965";
+ }
+ .icon-trolley:before {
+ content: "\e95c";
+ }
+ .icon-agency-term:before {
+ content: "\e950";
+ }
+ .icon-defaulter:before {
+ content: "\e94b";
+ }
+ .icon-100:before {
+ content: "\e95a";
+ }
+ .icon-clientUnpaid:before {
+ content: "\e95b";
+ }
+ .icon-history:before {
+ content: "\e968";
+ }
+ .icon-Person:before {
+ content: "\e901";
+ }
+ .icon-accessory:before {
+ content: "\e90a";
+ }
+ .icon-account:before {
+ content: "\e92a";
+ }
+ .icon-actions:before {
+ content: "\e960";
+ }
+ .icon-addperson:before {
+ content: "\e90e";
+ }
+ .icon-agency:before {
+ content: "\e938";
+ }
+ .icon-albaran:before {
+ content: "\e94d";
+ }
+ .icon-anonymous:before {
+ content: "\e930";
+ }
+ .icon-apps:before {
+ content: "\e951";
+ }
+ .icon-artificial:before {
+ content: "\e90b";
+ }
+ .icon-attach:before {
+ content: "\e92e";
+ }
+ .icon-barcode:before {
+ content: "\e971";
+ }
+ .icon-basket:before {
+ content: "\e914";
+ }
+ .icon-basketadd:before {
+ content: "\e913";
+ }
+ .icon-bin:before {
+ content: "\e96f";
+ }
+ .icon-botanical:before {
+ content: "\e972";
+ }
+ .icon-bucket:before {
+ content: "\e97a";
+ }
+ .icon-buscaman:before {
+ content: "\e93b";
+ }
+ .icon-buyrequest:before {
+ content: "\e932";
+ }
+ .icon-calc_volum .path1:before {
+ content: "\e915";
+ }
+ .icon-calc_volum .path2:before {
+ content: "\e916";
+ margin-left: -1em;
+ }
+ .icon-calc_volum .path3:before {
+ content: "\e917";
+ margin-left: -1em;
+ }
+ .icon-calc_volum .path4:before {
+ content: "\e918";
+ margin-left: -1em;
+ }
+ .icon-calc_volum .path5:before {
+ content: "\e919";
+ margin-left: -1em;
+ }
+ .icon-calc_volum .path6:before {
+ content: "\e91a";
+ margin-left: -1em;
+ }
+ .icon-calendar:before {
+ content: "\e93d";
+ }
+ .icon-catalog:before {
+ content: "\e937";
+ }
+ .icon-claims:before {
+ content: "\e963";
+ }
+ .icon-client:before {
+ content: "\e928";
+ }
+ .icon-clone:before {
+ content: "\e973";
+ }
+ .icon-columnadd:before {
+ content: "\e954";
+ }
+ .icon-columndelete:before {
+ content: "\e953";
+ }
+ .icon-components:before {
+ content: "\e946";
+ }
+ .icon-consignatarios:before {
+ content: "\e93f";
+ }
+ .icon-control:before {
+ content: "\e949";
+ }
+ .icon-credit:before {
+ content: "\e927";
+ }
+ .icon-deletedTicket:before {
+ content: "\e935";
+ }
+ .icon-deleteline:before {
+ content: "\e955";
+ }
+ .icon-delivery:before {
+ content: "\e939";
+ }
+ .icon-deliveryprices:before {
+ content: "\e91c";
+ }
+ .icon-details:before {
+ content: "\e961";
+ }
+ .icon-dfiscales:before {
+ content: "\e984";
+ }
+ .icon-disabled:before {
+ content: "\e921";
+ }
+ .icon-doc:before {
+ content: "\e977";
+ }
+ .icon-entry:before {
+ content: "\e934";
+ }
+ .icon-exit:before {
+ content: "\e92f";
+ }
+ .icon-eye:before {
+ content: "\e976";
+ }
+ .icon-fixedPrice:before {
+ content: "\e90d";
+ }
+ .icon-flower:before {
+ content: "\e90c";
+ }
+ .icon-frozen:before {
+ content: "\e900";
+ }
+ .icon-fruit:before {
+ content: "\e903";
+ }
+ .icon-funeral:before {
+ content: "\e904";
+ }
+ .icon-greenery:before {
+ content: "\e907";
+ }
+ .icon-greuge:before {
+ content: "\e944";
+ }
+ .icon-grid:before {
+ content: "\e980";
+ }
+ .icon-handmade:before {
+ content: "\e909";
+ }
+ .icon-handmadeArtificial:before {
+ content: "\e902";
+ }
+ .icon-headercol:before {
+ content: "\e958";
+ }
+ .icon-info:before {
+ content: "\e952";
+ }
+ .icon-inventory:before {
+ content: "\e92b";
+ }
+ .icon-invoice:before {
+ content: "\e923";
+ }
+ .icon-invoice-in:before {
+ content: "\e911";
+ }
+ .icon-invoice-in-create:before {
+ content: "\e912";
+ }
+ .icon-invoice-out:before {
+ content: "\e910";
+ }
+ .icon-isTooLittle:before {
+ content: "\e91b";
+ }
+ .icon-item:before {
+ content: "\e956";
+ }
+ .icon-languaje:before {
+ content: "\e926";
+ }
+ .icon-lines:before {
+ content: "\e942";
+ }
+ .icon-linesprepaired:before {
+ content: "\e948";
+ }
+ .icon-logout:before {
+ content: "\e936";
+ }
+ .icon-mana:before {
+ content: "\e96a";
+ }
+ .icon-mandatory:before {
+ content: "\e97b";
+ }
+ .icon-net:before {
+ content: "\e931";
+ }
+ .icon-niche:before {
+ content: "\e96c";
+ }
+ .icon-no036:before {
+ content: "\e920";
+ }
+ .icon-noPayMethod:before {
+ content: "\e905";
+ }
+ .icon-notes:before {
+ content: "\e941";
+ }
+ .icon-noweb:before {
+ content: "\e91f";
+ }
+ .icon-onlinepayment:before {
+ content: "\e91d";
+ }
+ .icon-package:before {
+ content: "\e978";
+ }
+ .icon-payment:before {
+ content: "\e97e";
+ }
+ .icon-pbx:before {
+ content: "\e93c";
+ }
+ .icon-pets:before {
+ content: "\e947";
+ }
+ .icon-photo:before {
+ content: "\e924";
+ }
+ .icon-plant:before {
+ content: "\e908";
+ }
+ .icon-polizon:before {
+ content: "\e95e";
+ }
+ .icon-preserved:before {
+ content: "\e906";
+ }
+ .icon-recovery:before {
+ content: "\e97c";
+ }
+ .icon-regentry:before {
+ content: "\e964";
+ }
+ .icon-reserva:before {
+ content: "\e959";
+ }
+ .icon-revision:before {
+ content: "\e94a";
+ }
+ .icon-risk:before {
+ content: "\e91e";
+ }
+ .icon-services:before {
+ content: "\e94c";
+ }
+ .icon-settings:before {
+ content: "\e979";
+ }
+ .icon-shipment-01:before {
+ content: "\e929";
+ }
+ .icon-sign:before {
+ content: "\e95d";
+ }
+ .icon-sms:before {
+ content: "\e975";
+ }
+ .icon-solclaim:before {
+ content: "\e95f";
+ }
+ .icon-solunion:before {
+ content: "\e94e";
+ }
+ .icon-stowaway:before {
+ content: "\e94f";
+ }
+ .icon-splitline:before {
+ content: "\e93e";
+ }
+ .icon-splur:before {
+ content: "\e970";
+ }
+ .icon-supplier:before {
+ content: "\e925";
+ }
+ .icon-supplierfalse:before {
+ content: "\e90f";
+ }
+ .icon-tags:before {
+ content: "\e96d";
+ }
+ .icon-tax:before {
+ content: "\e940";
+ }
+ .icon-thermometer:before {
+ content: "\e933";
+ }
+ .icon-ticket:before {
+ content: "\e96b";
+ }
+ .icon-ticketAdd:before {
+ content: "\e945";
+ }
+ .icon-traceability:before {
+ content: "\e962";
+ }
+ .icon-transaction:before {
+ content: "\e966";
+ }
+ .icon-treatments:before {
+ content: "\e922";
+ }
+ .icon-unavailable:before {
+ content: "\e92c";
+ }
+ .icon-volume:before {
+ content: "\e96e";
+ }
+ .icon-wand:before {
+ content: "\e93a";
+ }
+ .icon-web:before {
+ content: "\e982";
+ }
+ .icon-wiki:before {
+ content: "\e92d";
+ }
+ .icon-worker:before {
+ content: "\e957";
+ }
+ .icon-zone:before {
+ content: "\e943";
+ }
diff --git a/front/core/styles/icons/salixfont.eot b/front/core/styles/icons/salixfont.eot
index 61a3be8b7..6a158c806 100644
Binary files a/front/core/styles/icons/salixfont.eot and b/front/core/styles/icons/salixfont.eot differ
diff --git a/front/core/styles/icons/salixfont.svg b/front/core/styles/icons/salixfont.svg
index d6bfd5d74..da5404842 100644
--- a/front/core/styles/icons/salixfont.svg
+++ b/front/core/styles/icons/salixfont.svg
@@ -98,7 +98,8 @@