diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f241b91f..64ddda720 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- [General](Inicio) Permite recuperar la contraseña
+- [Ticket](Opciones) Subir albarán a Docuware
+- [Ticket](Opciones) Enviar correo con PDF de Docuware
- [Artículo](Datos Básicos) Añadido campo Unidades/Caja
### Changed
diff --git a/back/methods/docuware/checkFile.js b/back/methods/docuware/checkFile.js
index c6712bb65..c0a4e8ef3 100644
--- a/back/methods/docuware/checkFile.js
+++ b/back/methods/docuware/checkFile.js
@@ -1,4 +1,4 @@
-const got = require('got');
+const axios = require('axios');
module.exports = Self => {
Self.remoteMethodCtx('checkFile', {
@@ -8,7 +8,7 @@ module.exports = Self => {
{
arg: 'id',
type: 'number',
- description: 'The id',
+ description: 'The id',
http: {source: 'path'}
},
{
@@ -18,14 +18,14 @@ module.exports = Self => {
description: 'The fileCabinet name'
},
{
- arg: 'dialog',
- type: 'string',
+ arg: 'signed',
+ type: 'boolean',
required: true,
- description: 'The dialog name'
+ description: 'If pdf is necessary to be signed'
}
],
returns: {
- type: 'boolean',
+ type: 'object',
root: true
},
http: {
@@ -34,58 +34,51 @@ module.exports = Self => {
}
});
- Self.checkFile = async function(ctx, id, fileCabinet, dialog) {
- const myUserId = ctx.req.accessToken.userId;
- if (!myUserId)
- return false;
-
+ Self.checkFile = async function(ctx, id, fileCabinet, signed) {
const models = Self.app.models;
- const docuwareConfig = await models.DocuwareConfig.findOne();
+ const action = 'find';
+
const docuwareInfo = await models.Docuware.findOne({
where: {
code: fileCabinet,
- dialogName: dialog
+ action: action
}
});
- const docuwareUrl = docuwareConfig.url;
- const cookie = docuwareConfig.token;
- const fileCabinetName = docuwareInfo.fileCabinetName;
- const find = docuwareInfo.find;
- const options = {
- 'headers': {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Cookie': cookie
- }
- };
const searchFilter = {
condition: [
{
- DBName: find,
+ DBName: docuwareInfo.findById,
+
Value: [id]
}
+ ],
+ sortOrder: [
+ {
+ Field: 'FILENAME',
+ Direction: 'Desc'
+ }
]
};
try {
- // get fileCabinetId
- const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options);
- const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet;
- const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id;
+ const options = await Self.getOptions();
- // get dialog
- const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options);
- const dialogJson = JSON.parse(dialogResponse.body).Dialog;
- const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id;
+ const fileCabinetId = await Self.getFileCabinet(fileCabinet);
+ const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
- // get docuwareID
- Object.assign(options, {'body': JSON.stringify(searchFilter)});
- const response = await got.post(
- `${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options);
- JSON.parse(response.body).Items[0].Id;
+ const response = await axios.post(
+ `${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
+ searchFilter,
+ options.headers
+ );
+ const [documents] = response.data.Items;
+ if (!documents) return false;
- return true;
+ 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
new file mode 100644
index 000000000..2053ddf85
--- /dev/null
+++ b/back/methods/docuware/core.js
@@ -0,0 +1,78 @@
+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
+ *
+ * @param {string} cookie - The docuware cookie
+ * @return {object} - The headers
+ */
+ Self.getOptions = async() => {
+ const docuwareConfig = await Self.app.models.DocuwareConfig.findOne();
+ const headers = {
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Cookie': docuwareConfig.cookie
+ }
+ };
+
+ return {
+ url: docuwareConfig.url,
+ headers
+ };
+ };
+};
diff --git a/back/methods/docuware/deliveryNoteEmail.js b/back/methods/docuware/deliveryNoteEmail.js
new file mode 100644
index 000000000..1f9d7556f
--- /dev/null
+++ b/back/methods/docuware/deliveryNoteEmail.js
@@ -0,0 +1,72 @@
+const {Email} = require('vn-print');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('deliveryNoteEmail', {
+ description: 'Sends the delivery note email with an docuware attached PDF',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'id',
+ type: 'string',
+ required: true,
+ description: 'The ticket id',
+ http: {source: 'path'}
+ },
+ {
+ arg: 'recipient',
+ type: 'string',
+ description: 'The recipient email',
+ required: true,
+ },
+ {
+ arg: 'recipientId',
+ type: 'number',
+ description: 'The client id',
+ required: false
+ }
+ ],
+ returns: [
+ {
+ arg: 'body',
+ type: 'file',
+ root: true
+ }, {
+ arg: 'Content-Type',
+ type: 'String',
+ http: {target: 'header'}
+ }, {
+ arg: 'Content-Disposition',
+ type: 'String',
+ http: {target: 'header'}
+ }
+ ],
+ http: {
+ path: '/:id/delivery-note-email',
+ verb: 'POST'
+ }
+ });
+
+ Self.deliveryNoteEmail = async(ctx, id) => {
+ const args = Object.assign({}, ctx.args);
+ const params = {
+ recipient: args.recipient,
+ lang: ctx.req.getLocale()
+ };
+
+ delete args.ctx;
+ for (const param in args)
+ params[param] = args[param];
+
+ const email = new Email('delivery-note', params);
+
+ const docuwareFile = await Self.app.models.Docuware.download(ctx, id, 'deliveryNote');
+
+ return email.send({
+ overrideAttachments: true,
+ attachments: [{
+ filename: `${id}.pdf`,
+ content: docuwareFile[0]
+ }]
+ });
+ };
+};
diff --git a/back/methods/docuware/download.js b/back/methods/docuware/download.js
index 489a07e34..56d006ee7 100644
--- a/back/methods/docuware/download.js
+++ b/back/methods/docuware/download.js
@@ -1,5 +1,5 @@
/* eslint max-len: ["error", { "code": 180 }]*/
-const got = require('got');
+const axios = require('axios');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
@@ -10,19 +10,13 @@ module.exports = Self => {
{
arg: 'id',
type: 'number',
- description: 'The id',
+ description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'fileCabinet',
type: 'string',
- description: 'The id',
- http: {source: 'path'}
- },
- {
- arg: 'dialog',
- type: 'string',
- description: 'The id',
+ description: 'The file cabinet',
http: {source: 'path'}
}
],
@@ -42,79 +36,26 @@ module.exports = Self => {
}
],
http: {
- path: `/:id/download/:fileCabinet/:dialog`,
+ path: `/:id/download/:fileCabinet`,
verb: 'GET'
}
});
- Self.download = async function(ctx, id, fileCabinet, dialog) {
- const myUserId = ctx.req.accessToken.userId;
- if (!myUserId)
- throw new UserError(`You don't have enough privileges`);
-
+ Self.download = async function(ctx, id, fileCabinet) {
const models = Self.app.models;
- const docuwareConfig = await models.DocuwareConfig.findOne();
- const docuwareInfo = await models.Docuware.findOne({
- where: {
- code: fileCabinet,
- dialogName: dialog
- }
- });
+ const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true);
+ if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists');
- const docuwareUrl = docuwareConfig.url;
- const cookie = docuwareConfig.token;
- const fileCabinetName = docuwareInfo.fileCabinetName;
- const find = docuwareInfo.find;
- const options = {
- 'headers': {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Cookie': cookie
- }
- };
- const searchFilter = {
- condition: [
- {
- DBName: find,
- Value: [id]
- }
- ]
- };
+ const fileCabinetId = await Self.getFileCabinet(fileCabinet);
+ const options = await Self.getOptions();
+ options.headers.responseType = 'stream';
- try {
- // get fileCabinetId
- const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options);
- const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet;
- const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id;
+ const fileName = `filename="${id}.pdf"`;
+ const contentType = 'application/pdf';
+ const downloadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
- // get dialog
- const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options);
- const dialogJson = JSON.parse(dialogResponse.body).Dialog;
- const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id;
+ const stream = await axios.get(downloadUri, options.headers);
- // get docuwareID
- Object.assign(options, {'body': JSON.stringify(searchFilter)});
- const response = await got.post(`${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options);
- const docuwareId = JSON.parse(response.body).Items[0].Id;
-
- // download & save file
- const fileName = `filename="${id}.pdf"`;
- const contentType = 'application/pdf';
- const downloadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents/${docuwareId}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
- const downloadOptions = {
- 'headers': {
- 'Cookie': cookie
- }
- };
-
- const stream = got.stream(downloadUri, downloadOptions);
-
- return [stream, contentType, fileName];
- } catch (error) {
- if (error.code === 'ENOENT')
- throw new UserError('The DOCUWARE PDF document does not exists');
-
- throw error;
- }
+ return [stream.data, contentType, fileName];
};
};
diff --git a/back/methods/docuware/specs/checkFile.spec.js b/back/methods/docuware/specs/checkFile.spec.js
index 0d0e4d71a..dd11951cc 100644
--- a/back/methods/docuware/specs/checkFile.spec.js
+++ b/back/methods/docuware/specs/checkFile.spec.js
@@ -1,5 +1,5 @@
const models = require('vn-loopback/server/server').models;
-const got = require('got');
+const axios = require('axios');
describe('docuware download()', () => {
const ticketId = 1;
@@ -12,53 +12,71 @@ describe('docuware download()', () => {
}
};
- const fileCabinetName = 'deliveryClient';
- const dialogDisplayName = 'find';
- const dialogName = 'findTicket';
+ const docuwareModel = models.Docuware;
+ const fileCabinetName = 'deliveryNote';
- const gotGetResponse = {
- body: JSON.stringify(
- {
- FileCabinet: [
- {Id: 12, Name: fileCabinetName}
- ],
- Dialog: [
- {Id: 34, DisplayName: dialogDisplayName}
- ]
- })
- };
-
- it('should return exist file in docuware', async() => {
- const gotPostResponse = {
- body: JSON.stringify(
- {
- Items: [
- {Id: 56}
- ],
- })
- };
-
- spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse)));
- spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
-
- const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName);
-
- expect(result).toEqual(true);
+ 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 not exist file in docuware', async() => {
- const gotPostResponse = {
- body: JSON.stringify(
- {
- Items: [],
- })
+ 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(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse)));
- spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
-
- const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName);
+ 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);
+
+ expect(result).toEqual(false);
+ });
+
+ it('should return the document data', async() => {
+ const docuwareId = 1;
+ const response = {
+ data: {
+ Items: [
+ {
+ Id: docuwareId,
+ Fields: [
+ {
+ FieldName: 'ESTADO',
+ Item: 'Firmado'
+ }
+ ]
+ }
+ ]
+ }
+ };
+ spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
+
+ const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
+
+ expect(result.id).toEqual(docuwareId);
+ });
});
diff --git a/back/methods/docuware/specs/download.spec.js b/back/methods/docuware/specs/download.spec.js
index dc80c67d8..fcc1671a6 100644
--- a/back/methods/docuware/specs/download.spec.js
+++ b/back/methods/docuware/specs/download.spec.js
@@ -1,5 +1,5 @@
const models = require('vn-loopback/server/server').models;
-const got = require('got');
+const axios = require('axios');
const stream = require('stream');
describe('docuware download()', () => {
@@ -13,36 +13,33 @@ describe('docuware download()', () => {
}
};
- it('should return the downloaded file name', async() => {
- const fileCabinetName = 'deliveryClient';
- const dialogDisplayName = 'find';
- const dialogName = 'findTicket';
- const gotGetResponse = {
- body: JSON.stringify(
- {
- FileCabinet: [
- {Id: 12, Name: fileCabinetName}
- ],
- Dialog: [
- {Id: 34, DisplayName: dialogDisplayName}
- ]
- })
- };
+ const docuwareModel = models.Docuware;
+ const fileCabinetName = 'deliveryNote';
- const gotPostResponse = {
- body: JSON.stringify(
- {
- Items: [
- {Id: 56}
- ],
- })
- };
+ beforeAll(() => {
+ spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
+ });
- spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse)));
- spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
- spyOn(got, 'stream').and.returnValue(new stream.PassThrough({objectMode: true}));
+ it('should return error if file not exist', async() => {
+ spyOn(docuwareModel, 'checkFile').and.returnValue(false);
+ spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
- const result = await models.Docuware.download(ctx, ticketId, fileCabinetName, dialogName);
+ let error;
+ try {
+ await models.Docuware.download(ctx, ticketId, fileCabinetName);
+ } catch (e) {
+ error = e.message;
+ }
+
+ expect(error).toEqual('The DOCUWARE PDF document does not exists');
+ });
+
+ it('should return the downloaded file if exist file ', async() => {
+ spyOn(docuwareModel, 'checkFile').and.returnValue({});
+ spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
+
+ const result = await models.Docuware.download(ctx, ticketId, fileCabinetName);
expect(result[1]).toEqual('application/pdf');
expect(result[2]).toEqual(`filename="${ticketId}.pdf"`);
diff --git a/back/methods/docuware/specs/upload.spec.js b/back/methods/docuware/specs/upload.spec.js
new file mode 100644
index 000000000..7ac873e95
--- /dev/null
+++ b/back/methods/docuware/specs/upload.spec.js
@@ -0,0 +1,37 @@
+const models = require('vn-loopback/server/server').models;
+
+describe('docuware upload()', () => {
+ const userId = 9;
+ const ticketId = 10;
+ const ctx = {
+ req: {
+ getLocale: () => {
+ return 'en';
+ },
+ accessToken: {userId: userId},
+ headers: {origin: 'http://localhost:5000'},
+ }
+ };
+
+ const docuwareModel = models.Docuware;
+ const ticketModel = models.Ticket;
+ 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 try upload file', async() => {
+ spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({})));
+
+ let error;
+ try {
+ await models.Docuware.upload(ctx, ticketId, fileCabinetName);
+ } catch (e) {
+ error = e.message;
+ }
+
+ expect(error).toEqual('Action not allowed on the test environment');
+ });
+});
diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js
new file mode 100644
index 000000000..b5ee3d18f
--- /dev/null
+++ b/back/methods/docuware/upload.js
@@ -0,0 +1,141 @@
+const UserError = require('vn-loopback/util/user-error');
+const axios = require('axios');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('upload', {
+ description: 'Upload an docuware PDF',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'id',
+ type: 'number',
+ description: 'The ticket id',
+ http: {source: 'path'}
+ },
+ {
+ arg: 'fileCabinet',
+ type: 'string',
+ description: 'The file cabinet'
+ },
+ {
+ arg: 'dialog',
+ type: 'string',
+ description: 'The dialog'
+ }
+ ],
+ returns: [],
+ http: {
+ path: `/:id/upload`,
+ verb: 'POST'
+ }
+ });
+
+ Self.upload = async function(ctx, id, fileCabinet) {
+ const models = Self.app.models;
+ const action = 'store';
+
+ const options = await Self.getOptions();
+ const fileCabinetId = await Self.getFileCabinet(fileCabinet);
+ const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
+
+ // get delivery note
+ const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, {
+ id,
+ type: 'deliveryNote'
+ });
+
+ // get ticket data
+ const ticket = await models.Ticket.findById(id, {
+ include: [{
+ relation: 'client',
+ scope: {
+ fields: ['id', 'socialName', 'fi']
+ }
+ }]
+ });
+
+ // upload file
+ const templateJson = {
+ 'Fields': [
+ {
+ 'FieldName': 'N__ALBAR_N',
+ 'ItemElementName': 'string',
+ 'Item': id,
+ },
+ {
+ 'FieldName': 'CIF_PROVEEDOR',
+ 'ItemElementName': 'string',
+ 'Item': ticket.client().fi,
+ },
+ {
+ 'FieldName': 'CODIGO_PROVEEDOR',
+ 'ItemElementName': 'string',
+ 'Item': ticket.client().id,
+ },
+ {
+ 'FieldName': 'NOMBRE_PROVEEDOR',
+ 'ItemElementName': 'string',
+ 'Item': ticket.client().socialName,
+ },
+ {
+ 'FieldName': 'FECHA_FACTURA',
+ 'ItemElementName': 'date',
+ 'Item': ticket.shipped,
+ },
+ {
+ 'FieldName': 'TOTAL_FACTURA',
+ 'ItemElementName': 'Decimal',
+ 'Item': ticket.totalWithVat,
+ },
+ {
+ 'FieldName': 'ESTADO',
+ 'ItemElementName': 'string',
+ 'Item': 'Pendiente procesar',
+ },
+ {
+ 'FieldName': 'FIRMA_',
+ 'ItemElementName': 'string',
+ 'Item': 'Si',
+ },
+ {
+ 'FieldName': 'FILTRO_TABLET',
+ 'ItemElementName': 'string',
+ 'Item': 'Tablet1',
+ }
+ ]
+ };
+
+ if (process.env.NODE_ENV != 'production')
+ throw new UserError('Action not allowed on the test environment');
+
+ // delete old
+ const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false);
+ if (docuwareFile) {
+ const deleteJson = {
+ 'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
+ };
+ const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`;
+ await axios.put(deleteUri, deleteJson, options.headers);
+ }
+
+ const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`;
+ const FormData = require('form-data');
+ const data = new FormData();
+
+ data.append('document', JSON.stringify(templateJson), 'schema.json');
+ data.append('file[]', deliveryNote[0], 'file.pdf');
+ const uploadOptions = {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ 'X-File-ModifiedDate': new Date(),
+ 'Cookie': options.headers.headers.Cookie,
+ ...data.getHeaders()
+ },
+ };
+
+ return await axios.post(uploadUri, data, uploadOptions)
+ .catch(() => {
+ throw new UserError('Failed to upload file');
+ });
+ };
+};
diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js
index 33fe5958b..178b09601 100644
--- a/back/methods/osticket/closeTicket.js
+++ b/back/methods/osticket/closeTicket.js
@@ -25,36 +25,36 @@ module.exports = Self => {
return false;
const con = mysql.createConnection({
- host: `${config.hostDb}`,
- user: `${config.userDb}`,
- password: `${config.passwordDb}`,
- port: `${config.portDb}`
+ host: config.hostDb,
+ user: config.userDb,
+ password: config.passwordDb,
+ port: config.portDb
});
const sql = `SELECT ot.ticket_id, ot.number
FROM osticket.ost_ticket ot
- JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id
+ JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id
JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T'
JOIN (
SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated
FROM osticket.ost_thread_entry ote
- WHERE ote.staff_id != 0 AND ote.type = 'R'
+ WHERE ote.staff_id AND ote.type = 'R'
GROUP BY ote.thread_id
- ) sub ON sub.thread_id = ot2.id
- WHERE ot.isanswered = 1
- AND ots.state = '${config.oldStatus}'
- AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`;
+ ) sub ON sub.thread_id = ot2.id
+ WHERE ot.isanswered
+ AND ots.state = ?
+ AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`;
- let ticketsId = [];
+ const ticketsId = [];
con.connect(err => {
if (err) throw err;
- con.query(sql, (err, results) => {
- if (err) throw err;
- for (const result of results)
- ticketsId.push(result.ticket_id);
- });
+ con.query(sql, [config.oldStatus, config.day],
+ (err, results) => {
+ if (err) throw err;
+ for (const result of results)
+ ticketsId.push(result.ticket_id);
+ });
});
-
await getRequestToken();
async function getRequestToken() {
@@ -94,6 +94,44 @@ module.exports = Self => {
await close(token, secondCookie);
}
+ async function close(token, secondCookie) {
+ for (const ticketId of ticketsId) {
+ try {
+ const lock = await getLockCode(token, secondCookie, ticketId);
+ if (!lock.code) {
+ let error = `Can't get lock code`;
+ if (lock.msg) error += `: ${lock.msg}`;
+ throw new Error(error);
+ }
+ let form = new FormData();
+ form.append('__CSRFToken__', token);
+ form.append('id', ticketId);
+ form.append('a', config.responseType);
+ form.append('lockCode', lock.code);
+ form.append('from_email_id', config.fromEmailId);
+ form.append('reply-to', config.replyTo);
+ form.append('cannedResp', 0);
+ form.append('response', config.comment);
+ form.append('signature', 'none');
+ form.append('reply_status_id', config.newStatusId);
+
+ const ostUri = `${config.host}/tickets.php?id=${ticketId}`;
+ const params = {
+ method: 'POST',
+ body: form,
+ headers: {
+ 'Cookie': secondCookie
+ }
+ };
+ await fetch(ostUri, params);
+ } catch (e) {
+ const err = new Error(`${ticketId} Ticket close failed: ${e.message}`);
+ err.stack += e.stack;
+ console.error(err);
+ }
+ }
+ }
+
async function getLockCode(token, secondCookie, ticketId) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = {
@@ -107,34 +145,7 @@ module.exports = Self => {
const body = await response.text();
const json = JSON.parse(body);
- return json.code;
- }
-
- async function close(token, secondCookie) {
- for (const ticketId of ticketsId) {
- const lockCode = await getLockCode(token, secondCookie, ticketId);
- let form = new FormData();
- form.append('__CSRFToken__', token);
- form.append('id', ticketId);
- form.append('a', config.responseType);
- form.append('lockCode', lockCode);
- form.append('from_email_id', config.fromEmailId);
- form.append('reply-to', config.replyTo);
- form.append('cannedResp', 0);
- form.append('response', config.comment);
- form.append('signature', 'none');
- form.append('reply_status_id', config.newStatusId);
-
- const ostUri = `${config.host}/tickets.php?id=${ticketId}`;
- const params = {
- method: 'POST',
- body: form,
- headers: {
- 'Cookie': secondCookie
- }
- };
- return fetch(ostUri, params);
- }
+ return json;
}
};
};
diff --git a/back/models/docuware-config.json b/back/models/docuware-config.json
index 8ca76d8ba..9d06c4874 100644
--- a/back/models/docuware-config.json
+++ b/back/models/docuware-config.json
@@ -16,7 +16,7 @@
"url": {
"type": "string"
},
- "token": {
+ "cookie": {
"type": "string"
}
},
@@ -29,4 +29,4 @@
"permission": "ALLOW"
}
]
-}
\ No newline at end of file
+}
diff --git a/back/models/docuware.js b/back/models/docuware.js
index 8fd8065ed..b983f7bb4 100644
--- a/back/models/docuware.js
+++ b/back/models/docuware.js
@@ -1,4 +1,7 @@
module.exports = Self => {
require('../methods/docuware/download')(Self);
+ require('../methods/docuware/upload')(Self);
require('../methods/docuware/checkFile')(Self);
+ require('../methods/docuware/deliveryNoteEmail')(Self);
+ require('../methods/docuware/core')(Self);
};
diff --git a/back/models/docuware.json b/back/models/docuware.json
index fb2ed919e..dec20eede 100644
--- a/back/models/docuware.json
+++ b/back/models/docuware.json
@@ -19,20 +19,14 @@
"fileCabinetName": {
"type": "string"
},
+ "action": {
+ "type": "string"
+ },
"dialogName": {
"type": "string"
},
- "find": {
+ "findById": {
"type": "string"
}
- },
- "acls": [
- {
- "property": "*",
- "accessType": "*",
- "principalType": "ROLE",
- "principalId": "$everyone",
- "permission": "ALLOW"
- }
- ]
-}
\ No newline at end of file
+ }
+}
diff --git a/db/changes/224903/00-insert_notification_invoiceE.sql b/db/changes/224903/00-insert_notification_invoiceE.sql
deleted file mode 100644
index 1d416c196..000000000
--- a/db/changes/224903/00-insert_notification_invoiceE.sql
+++ /dev/null
@@ -1 +0,0 @@
-insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoiceElectronic', 'A electronic invoice has been generated');
\ No newline at end of file
diff --git a/db/changes/225001/.gitkeep b/db/changes/225001/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/db/changes/225201/00-invoiceOut_new.sql b/db/changes/225201/00-invoiceOut_new.sql
new file mode 100644
index 000000000..10a42d40d
--- /dev/null
+++ b/db/changes/225201/00-invoiceOut_new.sql
@@ -0,0 +1,225 @@
+DROP PROCEDURE IF EXISTS `vn`.`invoiceOut_new`;
+DELIMITER $$
+CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`(
+ vSerial VARCHAR(255),
+ vInvoiceDate DATETIME,
+ vTaxArea VARCHAR(25),
+ OUT vNewInvoiceId INT)
+BEGIN
+/**
+ * Creación de facturas emitidas.
+ * requiere previamente tabla ticketToInvoice(id).
+ *
+ * @param vSerial serie a la cual se hace la factura
+ * @param vInvoiceDate fecha de la factura
+ * @param vTaxArea tipo de iva en relacion a la empresa y al cliente
+ * @param vNewInvoiceId id de la factura que se acaba de generar
+ * @return vNewInvoiceId
+ */
+ DECLARE vSpainCountryCode INT DEFAULT 1;
+ DECLARE vIsAnySaleToInvoice BOOL;
+ DECLARE vIsAnyServiceToInvoice BOOL;
+ DECLARE vNewRef VARCHAR(255);
+ DECLARE vWorker INT DEFAULT account.myUser_getId();
+ DECLARE vCompany INT;
+ DECLARE vSupplier INT;
+ DECLARE vClient INT;
+ DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1;
+ DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6;
+ DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2;
+ DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R';
+ DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S';
+ DECLARE vNewInvoiceInId INT;
+ DECLARE vIsInterCompany BOOL;
+
+ SET vInvoiceDate = IFNULL(vInvoiceDate,CURDATE());
+
+ SELECT t.clientFk, t.companyFk
+ INTO vClient, vCompany
+ FROM ticketToInvoice tt
+ JOIN ticket t ON t.id = tt.id
+ LIMIT 1;
+
+ -- Eliminem de ticketToInvoice els tickets que no han de ser facturats
+ DELETE ti.*
+ FROM ticketToInvoice ti
+ JOIN ticket t ON t.id = ti.id
+ JOIN sale s ON s.ticketFk = t.id
+ JOIN item i ON i.id = s.itemFk
+ JOIN supplier su ON su.id = t.companyFk
+ JOIN client c ON c.id = t.clientFk
+ LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk
+ WHERE YEAR(t.shipped) < 2001
+ OR c.isTaxDataChecked = FALSE
+ OR t.isDeleted
+ OR c.hasToInvoice = FALSE
+ OR itc.id IS NULL;
+
+ SELECT SUM(s.quantity * s.price * (100 - s.discount)/100), ts.id
+ INTO vIsAnySaleToInvoice, vIsAnyServiceToInvoice
+ FROM ticketToInvoice t
+ LEFT JOIN sale s ON s.ticketFk = t.id
+ LEFT JOIN ticketService ts ON ts.ticketFk = t.id;
+
+ IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice)
+ AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase())
+ THEN
+
+ -- el trigger añade el siguiente Id_Factura correspondiente a la vSerial
+ INSERT INTO invoiceOut
+ (
+ ref,
+ serial,
+ issued,
+ clientFk,
+ dued,
+ companyFk,
+ cplusInvoiceType477Fk
+ )
+ SELECT
+ 1,
+ vSerial,
+ vInvoiceDate,
+ vClient,
+ getDueDate(vInvoiceDate, dueDay),
+ vCompany,
+ IF(vSerial = vCorrectingSerial,
+ vCplusCorrectingInvoiceTypeFk,
+ IF(vSerial = vSimplifiedSerial,
+ vCplusSimplifiedInvoiceTypeFk,
+ vCplusStandardInvoiceTypeFk))
+ FROM client
+ WHERE id = vClient;
+
+
+ SET vNewInvoiceId = LAST_INSERT_ID();
+
+ SELECT `ref`
+ INTO vNewRef
+ FROM invoiceOut
+ WHERE id = vNewInvoiceId;
+
+ UPDATE ticket t
+ JOIN ticketToInvoice ti ON ti.id = t.id
+ SET t.refFk = vNewRef;
+
+ DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
+ CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
+ SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
+ FROM ticketToInvoice ti
+ LEFT JOIN ticketState ts ON ti.id = ts.ticket
+ JOIN state s
+ WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
+
+ INSERT INTO vncontrol.inter(state_id,Id_Ticket,Id_Trabajador)
+ SELECT * FROM tmp.updateInter;
+
+ INSERT INTO ticketLog (action, userFk, originFk, description)
+ SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
+ FROM ticketToInvoice ti;
+
+ CALL invoiceExpenceMake(vNewInvoiceId);
+ CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
+
+ UPDATE invoiceOut io
+ JOIN (
+ SELECT SUM(amount) AS total
+ FROM invoiceOutExpence
+ WHERE invoiceOutFk = vNewInvoiceId
+ ) base
+ JOIN (
+ SELECT SUM(vat) AS total
+ FROM invoiceOutTax
+ WHERE invoiceOutFk = vNewInvoiceId
+ ) vat
+ SET io.amount = base.total + vat.total
+ WHERE io.id = vNewInvoiceId;
+
+ DROP TEMPORARY TABLE tmp.updateInter;
+
+ SELECT ios.isCEE INTO vIsInterCompany
+ FROM vn.ticket t
+ JOIN vn.invoiceOut io ON io.`ref` = t.refFk
+ JOIN vn.invoiceOutSerial ios ON ios.code = io.serial
+ WHERE t.refFk = vNewRef
+ LIMIT 1;
+
+ IF (vIsInterCompany) THEN
+
+ SELECT vCompany INTO vSupplier;
+ SELECT id INTO vCompany FROM company WHERE clientFk = vClient;
+
+ INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk)
+ SELECT vSupplier, vNewRef, vInvoiceDate, vCompany;
+
+ SET vNewInvoiceInId = LAST_INSERT_ID();
+
+ DROP TEMPORARY TABLE IF EXISTS tmp.ticket;
+ CREATE TEMPORARY TABLE tmp.ticket
+ (KEY (ticketFk))
+ ENGINE = MEMORY
+ SELECT id ticketFk
+ FROM ticketToInvoice;
+
+ CALL `ticket_getTax`('NATIONAL');
+
+ SET @vTaxableBaseServices := 0.00;
+ SET @vTaxCodeGeneral := NULL;
+
+ INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
+ SELECT vNewInvoiceInId, @vTaxableBaseServices, sub.expenceFk, sub.taxTypeSageFk , sub.transactionTypeSageFk
+ FROM (
+ SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk, @vTaxCodeGeneral := i.taxClassCodeFk
+ FROM tmp.ticketServiceTax tst
+ JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code
+ WHERE i.isService
+ HAVING taxableBase
+ ) sub;
+
+ INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
+ SELECT vNewInvoiceInId, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, @vTaxableBaseServices, 0) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk
+ FROM tmp.ticketTax tt
+ JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code
+ WHERE !i.isService
+ GROUP BY tt.pgcFk
+ HAVING taxableBase
+ ORDER BY tt.priority;
+
+ CALL invoiceInDueDay_calculate(vNewInvoiceInId);
+
+ INSERT INTO invoiceInIntrastat (
+ invoiceInFk,
+ intrastatFk,
+ amount,
+ stems,
+ countryFk,
+ net)
+ SELECT
+ vNewInvoiceInId invoiceInFk,
+ i.intrastatFk,
+ CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal,
+ CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems,
+ su.countryFk,
+ CAST(SUM(IFNULL(i.stems, 1)
+ * s.quantity
+ * IF(ic.grams, ic.grams, i.weightByPiece) / 1000) AS DECIMAL(10,2)) netKg
+ FROM sale s
+ JOIN ticket t ON s.ticketFk = t.id
+ JOIN supplier su ON su.id = t.companyFk
+ JOIN item i ON i.id = s.itemFk
+ JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk
+ JOIN intrastat ir ON ir.id = i.intrastatFk
+ WHERE t.refFk = vNewRef;
+
+ DROP TEMPORARY TABLE tmp.ticket;
+ DROP TEMPORARY TABLE tmp.ticketAmount;
+ DROP TEMPORARY TABLE tmp.ticketTax;
+ DROP TEMPORARY TABLE tmp.ticketServiceTax;
+
+ END IF;
+
+ END IF;
+
+ DROP TEMPORARY TABLE `ticketToInvoice`;
+END$$
+DELIMITER ;
diff --git a/db/changes/225201/01-modules.sql b/db/changes/225201/01-modules.sql
index 82861a5e2..243d2d016 100644
--- a/db/changes/225201/01-modules.sql
+++ b/db/changes/225201/01-modules.sql
@@ -43,7 +43,7 @@ SET t.code = 'claim'
WHERE t.code LIKE 'Claims' ESCAPE '#';
UPDATE salix.module t
-SET t.code = 'user'
+SET t.code = 'account'
WHERE t.code LIKE 'Users' ESCAPE '#';
UPDATE salix.module t
diff --git a/db/changes/225202/00-mdbApp.sql b/db/changes/225202/00-mdbApp.sql
new file mode 100644
index 000000000..50c595d71
--- /dev/null
+++ b/db/changes/225202/00-mdbApp.sql
@@ -0,0 +1,28 @@
+ALTER TABLE `vn`.`mdbApp` DROP PRIMARY KEY;
+ALTER TABLE `vn`.`mdbApp` ADD CONSTRAINT mdbApp_PK PRIMARY KEY (app,baselineBranchFk);
+
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('com','master');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('enc','master');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('ent','master');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('eti','master');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('lab','master');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('tpv','master');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('com','dev');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('enc','dev');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('ent','dev');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('eti','dev');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('lab','dev');
+INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
+ VALUES ('tpv','dev');
+
diff --git a/db/changes/230201/00-docuwareStore.sql b/db/changes/230201/00-docuwareStore.sql
new file mode 100644
index 000000000..b20c2554f
--- /dev/null
+++ b/db/changes/230201/00-docuwareStore.sql
@@ -0,0 +1,23 @@
+CREATE OR REPLACE TABLE `vn`.`docuware` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `code` varchar(50) COLLATE utf8mb3_unicode_ci NOT NULL,
+ `fileCabinetName` varchar(50) COLLATE utf8mb3_unicode_ci NOT NULL,
+ `action` varchar(255) COLLATE utf8mb3_unicode_ci NOT NULL,
+ `dialogName` varchar(100) COLLATE utf8mb3_unicode_ci NOT NULL,
+ `findById` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
+
+INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `action`, `dialogName`, `findById`)
+ VALUES
+ ('deliveryNote', 'Albaranes cliente', 'find', 'find', 'N__ALBAR_N'),
+ ('deliveryNote', 'Albaranes cliente', 'store', 'Archivar', 'N__ALBAR_N');
+
+INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
+ VALUES
+ ('Docuware','checkFile','READ','ALLOW','employee'),
+ ('Docuware','download','READ','ALLOW','salesPerson'),
+ ('Docuware','upload','WRITE','ALLOW','productionAssi'),
+ ('Docuware','deliveryNoteEmail','WRITE','ALLOW','salesPerson');
+
+ALTER TABLE `vn`.`docuwareConfig` CHANGE token cookie varchar(1000) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL;
diff --git a/db/changes/230201/00-priceFixed_getRate2.sql b/db/changes/230201/00-priceFixed_getRate2.sql
new file mode 100644
index 000000000..cf36efb57
--- /dev/null
+++ b/db/changes/230201/00-priceFixed_getRate2.sql
@@ -0,0 +1,23 @@
+DROP FUNCTION IF EXISTS `vn`.`priceFixed_getRate2`;
+
+DELIMITER $$
+$$
+CREATE FUNCTION `vn`.`priceFixed_getRate2`(vFixedPriceFk INT, vRate3 DOUBLE)
+RETURNS DOUBLE
+BEGIN
+
+ DECLARE vWarehouse INT;
+ DECLARE vRate2 DOUBLE;
+
+ SELECT round(vRate3 * (1 + ((r.rate2 - r.rate3)/100)), 2) INTO vRate2
+ FROM vn.rate r
+ JOIN vn.priceFixed p ON p.id = vFixedPriceFk
+ WHERE r.dated <= p.started
+ AND r.warehouseFk = p.warehouseFk
+ ORDER BY r.dated DESC
+ LIMIT 1;
+
+ RETURN vRate2;
+
+END$$
+DELIMITER ;
diff --git a/db/changes/230201/00-triggersXDiario.sql b/db/changes/230201/00-triggersXDiario.sql
new file mode 100644
index 000000000..5cf0b6253
--- /dev/null
+++ b/db/changes/230201/00-triggersXDiario.sql
@@ -0,0 +1,73 @@
+DROP TRIGGER IF EXISTS vn.XDiario_beforeUpdate;
+USE vn;
+
+DELIMITER $$
+$$
+CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeUpdate`
+ BEFORE UPDATE ON `XDiario`
+ FOR EACH ROW
+BEGIN
+ IF NOT NEW.SUBCTA <=> OLD.SUBCTA THEN
+ IF NEW.SUBCTA <=> '' THEN
+ SET NEW.SUBCTA = NULL;
+ END IF;
+ IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN
+ CALL util.throw('INVALID_STRING_LENGTH');
+ END IF;
+ END IF;
+ IF NOT NEW.CONTRA <=> OLD.CONTRA THEN
+ IF NEW.CONTRA <=> '' THEN
+ SET NEW.CONTRA = NULL;
+ END IF;
+ IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN
+ CALL util.throw('INVALID_STRING_LENGTH');
+ END IF;
+ END IF;
+ IF NOT NEW.FECHA <=> OLD.FECHA THEN
+ CALL XDiario_checkDate(NEW.FECHA);
+ END IF;
+ IF NOT NEW.FECHA_EX <=> OLD.FECHA_EX THEN
+ CALL XDiario_checkDate(NEW.FECHA_EX);
+ END IF;
+ IF NOT NEW.FECHA_OP <=> OLD.FECHA_OP THEN
+ CALL XDiario_checkDate(NEW.FECHA_OP);
+ END IF;
+ IF NOT NEW.FECHA_RT <=> OLD.FECHA_RT THEN
+ CALL XDiario_checkDate(NEW.FECHA_RT);
+ END IF;
+ IF NOT NEW.FECREGCON <=> OLD.FECREGCON THEN
+ CALL XDiario_checkDate(NEW.FECREGCON);
+ END IF;
+END$$
+DELIMITER ;
+
+
+DROP TRIGGER IF EXISTS vn.XDiario_beforeInsert;
+USE vn;
+
+DELIMITER $$
+$$
+CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeInsert`
+ BEFORE INSERT ON `XDiario`
+ FOR EACH ROW
+BEGIN
+ IF NEW.SUBCTA <=> '' THEN
+ SET NEW.SUBCTA = NULL;
+ END IF;
+ IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN
+ CALL util.throw('INVALID_STRING_LENGTH');
+ END IF;
+ IF NEW.CONTRA <=> '' THEN
+ SET NEW.CONTRA = NULL;
+ END IF;
+ IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN
+ CALL util.throw('INVALID_STRING_LENGTH');
+ END IF;
+ CALL XDiario_checkDate(NEW.FECHA);
+ CALL XDiario_checkDate(NEW.FECHA_EX);
+ CALL XDiario_checkDate(NEW.FECHA_OP);
+ CALL XDiario_checkDate(NEW.FECHA_RT);
+ CALL XDiario_checkDate(NEW.FECREGCON);
+END$$
+DELIMITER ;
+
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index 9c8163414..93569adc6 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -2581,13 +2581,9 @@ INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`)
(4, 33.8, util.VN_CURDATE(), 1, 1101),
(30, 34.4, util.VN_CURDATE(), 1, 1108);
-INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName` , `find`)
- VALUES
- ('deliveryClient', 'deliveryClient', 'findTicket', 'word');
-
INSERT INTO `vn`.`docuwareConfig` (`url`)
VALUES
- ('https://verdnatura.docuware.cloud/docuware/platform');
+ ('http://docuware.url/');
INSERT INTO `vn`.`calendarHolidaysName` (`id`, `name`)
VALUES
@@ -2693,6 +2689,7 @@ INSERT INTO `util`.`notificationConfig`
INSERT INTO `util`.`notification` (`id`, `name`, `description`)
VALUES
(1, 'print-email', 'notification fixture one'),
+ (2, 'invoice-electronic', 'A electronic invoice has been generated'),
(4, 'supplier-pay-method-update', 'A supplier pay method has been updated');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
diff --git a/db/dump/structure.sql b/db/dump/structure.sql
index ab8b8969c..1d743bce8 100644
--- a/db/dump/structure.sql
+++ b/db/dump/structure.sql
@@ -80203,3 +80203,4 @@ USE `vncontrol`;
-- Dump completed on 2022-11-21 7:57:28
+
diff --git a/db/export-data.sh b/db/export-data.sh
index 8bff538a7..bdf8049e0 100755
--- a/db/export-data.sh
+++ b/db/export-data.sh
@@ -59,6 +59,7 @@ TABLES=(
componentType
continent
department
+ docuware
itemPackingType
pgc
sample
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index 052d8a480..c0f10a506 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -417,8 +417,8 @@ export default {
fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)',
fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]',
fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]',
- fourthPPU: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)',
- fourthPPP: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)',
+ fourthGroupingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)',
+ fourthPackingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)',
fourthHasMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-check[ng-model="price.hasMinPrice"]',
fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]',
fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]',
@@ -679,7 +679,10 @@ export default {
moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]',
moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]',
stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]',
- moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield'
+ moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield',
+ firstSaleHistoryButton: 'vn-ticket-sale vn-tr:nth-child(1) vn-icon-button[icon="history"]',
+ firstSaleHistory: 'form vn-table div > vn-tbody > vn-tr',
+ closeHistory: 'div.window vn-button[icon="clear"]'
},
ticketTracking: {
createStateButton: 'vn-float-button'
diff --git a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js
index 4fc280209..ad558ace2 100644
--- a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js
+++ b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js
@@ -15,7 +15,7 @@ describe('SmartTable SearchBar integration', () => {
await browser.close();
});
- describe('as filters', () => {
+ describe('as filters in smart-table section', () => {
it('should search by type in searchBar', async() => {
await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
@@ -47,6 +47,34 @@ describe('SmartTable SearchBar integration', () => {
});
});
+ describe('as filters in section without smart-table', () => {
+ it('go to zone section', async() => {
+ await page.loginAndModule('salesPerson', 'zone');
+ await page.waitToClick(selectors.globalItems.searchButton);
+ });
+
+ it('should search in searchBar first time', async() => {
+ await page.doSearch('A');
+ const count = await page.countElement(selectors.zoneIndex.searchResult);
+
+ expect(count).toEqual(7);
+ });
+
+ it('should search in searchBar second time', async() => {
+ await page.doSearch('A');
+ const count = await page.countElement(selectors.zoneIndex.searchResult);
+
+ expect(count).toEqual(7);
+ });
+
+ it('should search in searchBar third time', async() => {
+ await page.doSearch('A');
+ const count = await page.countElement(selectors.zoneIndex.searchResult);
+
+ expect(count).toEqual(7);
+ });
+ });
+
describe('as orders', () => {
it('should order by first id', async() => {
await page.loginAndModule('developer', 'item');
diff --git a/e2e/paths/03-worker/01_summary.spec.js b/e2e/paths/03-worker/01_summary.spec.js
index 4ea87481a..4e5b0cfa9 100644
--- a/e2e/paths/03-worker/01_summary.spec.js
+++ b/e2e/paths/03-worker/01_summary.spec.js
@@ -2,68 +2,32 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker summary path', () => {
+ const workerId = 3;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'worker');
+ const httpDataResponse = page.waitForResponse(response => {
+ return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
+ });
await page.accessToSearchResult('agencyNick');
+ await httpDataResponse;
});
afterAll(async() => {
await browser.close();
});
- it('should reach the employee summary section', async() => {
- await page.waitForState('worker.card.summary');
- });
-
- it('should check the summary contains the name and userName on the header', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.header, 'innerText');
-
- expect(result).toEqual('agency agency');
- });
-
- it('should check the summary contains the basic data id', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.id, 'innerText');
-
- expect(result).toEqual('3');
- });
-
- it('should check the summary contains the basic data email', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.email, 'innerText');
-
- expect(result).toEqual('agency@verdnatura.es');
- });
-
- it('should check the summary contains the basic data department', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.department, 'innerText');
-
- expect(result).toEqual('CAMARA');
- });
-
- it('should check the summary contains the user data id', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.userId, 'innerText');
-
- expect(result).toEqual('3');
- });
-
- it('should check the summary contains the user data name', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.userName, 'innerText');
-
- expect(result).toEqual('agency');
- });
-
- it('should check the summary contains the user data role', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.role, 'innerText');
-
- expect(result).toEqual('agency');
- });
-
- it('should check the summary contains the user data extension', async() => {
- const result = await page.waitToGetProperty(selectors.workerSummary.extension, 'innerText');
-
- expect(result).toEqual('1101');
+ it('should reach the employee summary section and check all properties', async() => {
+ expect(await page.getProperty(selectors.workerSummary.header, 'innerText')).toEqual('agency agency');
+ expect(await page.getProperty(selectors.workerSummary.id, 'innerText')).toEqual('3');
+ expect(await page.getProperty(selectors.workerSummary.email, 'innerText')).toEqual('agency@verdnatura.es');
+ expect(await page.getProperty(selectors.workerSummary.department, 'innerText')).toEqual('CAMARA');
+ expect(await page.getProperty(selectors.workerSummary.userId, 'innerText')).toEqual('3');
+ expect(await page.getProperty(selectors.workerSummary.userName, 'innerText')).toEqual('agency');
+ expect(await page.getProperty(selectors.workerSummary.role, 'innerText')).toEqual('agency');
+ expect(await page.getProperty(selectors.workerSummary.extension, 'innerText')).toEqual('1101');
});
});
diff --git a/e2e/paths/03-worker/02_basicData.spec.js b/e2e/paths/03-worker/02_basicData.spec.js
index c367c8706..66a597dd1 100644
--- a/e2e/paths/03-worker/02_basicData.spec.js
+++ b/e2e/paths/03-worker/02_basicData.spec.js
@@ -2,13 +2,18 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker basic data path', () => {
+ const workerId = 1106;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
+ const httpDataResponse = page.waitForResponse(response => {
+ return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
+ });
await page.accessToSearchResult('David Charles Haller');
+ await httpDataResponse;
await page.accessToSection('worker.card.basicData');
});
@@ -16,35 +21,20 @@ describe('Worker basic data path', () => {
await browser.close();
});
- it('should edit the form', async() => {
- await page.clearInput(selectors.workerBasicData.name);
- await page.write(selectors.workerBasicData.name, 'David C.');
- await page.clearInput(selectors.workerBasicData.surname);
- await page.write(selectors.workerBasicData.surname, 'H.');
- await page.clearInput(selectors.workerBasicData.phone);
- await page.write(selectors.workerBasicData.phone, '444332211');
- await page.waitToClick(selectors.workerBasicData.saveButton);
+ it('should edit the form and then reload the section and check the data was edited', async() => {
+ await page.overwrite(selectors.workerBasicData.name, 'David C.');
+ await page.overwrite(selectors.workerBasicData.surname, 'H.');
+ await page.overwrite(selectors.workerBasicData.phone, '444332211');
+ await page.click(selectors.workerBasicData.saveButton);
+
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
- });
- it('should reload the section then check the name was edited', async() => {
await page.reloadSection('worker.card.basicData');
- const result = await page.waitToGetProperty(selectors.workerBasicData.name, 'value');
- expect(result).toEqual('David C.');
- });
-
- it('should the surname was edited', async() => {
- const result = await page.waitToGetProperty(selectors.workerBasicData.surname, 'value');
-
- expect(result).toEqual('H.');
- });
-
- it('should the phone was edited', async() => {
- const result = await page.waitToGetProperty(selectors.workerBasicData.phone, 'value');
-
- expect(result).toEqual('444332211');
+ expect(await page.waitToGetProperty(selectors.workerBasicData.name, 'value')).toEqual('David C.');
+ expect(await page.waitToGetProperty(selectors.workerBasicData.surname, 'value')).toEqual('H.');
+ expect(await page.waitToGetProperty(selectors.workerBasicData.phone, 'value')).toEqual('444332211');
});
});
diff --git a/e2e/paths/03-worker/03_pbx.spec.js b/e2e/paths/03-worker/03_pbx.spec.js
index f5d2711d1..0e8003c47 100644
--- a/e2e/paths/03-worker/03_pbx.spec.js
+++ b/e2e/paths/03-worker/03_pbx.spec.js
@@ -16,19 +16,16 @@ describe('Worker pbx path', () => {
await browser.close();
});
- it('should receive an error when the extension exceeds 4 characters', async() => {
+ it('should receive an error when the extension exceeds 4 characters and then sucessfully save the changes', async() => {
await page.write(selectors.workerPbx.extension, '55555');
- await page.waitToClick(selectors.workerPbx.saveButton);
- const message = await page.waitForSnackbar();
+ await page.click(selectors.workerPbx.saveButton);
+ let message = await page.waitForSnackbar();
expect(message.text).toContain('Extension format is invalid');
- });
- it('should sucessfully save the changes', async() => {
- await page.clearInput(selectors.workerPbx.extension);
- await page.write(selectors.workerPbx.extension, '4444');
- await page.waitToClick(selectors.workerPbx.saveButton);
- const message = await page.waitForSnackbar();
+ await page.overwrite(selectors.workerPbx.extension, '4444');
+ await page.click(selectors.workerPbx.saveButton);
+ message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved! User must access web');
});
diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js
index 4236ae0e4..bba7ced89 100644
--- a/e2e/paths/03-worker/04_time_control.spec.js
+++ b/e2e/paths/03-worker/04_time_control.spec.js
@@ -21,38 +21,34 @@ describe('Worker time control path', () => {
const fourPm = '16:00';
const hankPymId = 1107;
- it('should go to the next month', async() => {
- const date = new Date();
+ it('should go to the next month, go to current month and go 1 month in the past', async() => {
+ let date = new Date();
date.setMonth(date.getMonth() + 1);
- const month = date.toLocaleString('default', {month: 'long'});
+ let month = date.toLocaleString('default', {month: 'long'});
- await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
- const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
+ await page.click(selectors.workerTimeControl.nextMonthButton);
+ let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
- });
- it('should go to current month', async() => {
- const date = new Date();
- const month = date.toLocaleString('default', {month: 'long'});
+ date = new Date();
+ month = date.toLocaleString('default', {month: 'long'});
- await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
- const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
+ await page.click(selectors.workerTimeControl.previousMonthButton);
+ result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
- });
- it('should go 1 month in the past', async() => {
- const date = new Date();
+ date = new Date();
date.setMonth(date.getMonth() - 1);
const timestamp = Math.round(date.getTime() / 1000);
- const month = date.toLocaleString('default', {month: 'long'});
+ month = date.toLocaleString('default', {month: 'long'});
await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
- await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
+ await page.click(selectors.workerTimeControl.secondWeekDay);
- const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
+ result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
});
@@ -115,7 +111,9 @@ describe('Worker time control path', () => {
});
it('should change week of month', async() => {
- await page.waitToClick(selectors.workerTimeControl.thrirdWeekDay);
- await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '00:00 h.');
+ await page.click(selectors.workerTimeControl.thrirdWeekDay);
+ const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText');
+
+ expect(result).toEqual('00:00 h.');
});
});
diff --git a/e2e/paths/03-worker/05_calendar.spec.js b/e2e/paths/03-worker/05_calendar.spec.js
index e97b7fe7c..c310baf5a 100644
--- a/e2e/paths/03-worker/05_calendar.spec.js
+++ b/e2e/paths/03-worker/05_calendar.spec.js
@@ -1,16 +1,25 @@
+/* eslint-disable max-len */
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker calendar path', () => {
- let reasonableTimeBetweenClicks = 400;
+ const reasonableTimeBetweenClicks = 300;
+ const date = new Date();
+ const lastYear = (date.getFullYear() - 1).toString();
+
let browser;
let page;
+
+ async function accessAs(user) {
+ await page.loginAndModule(user, 'worker');
+ await page.accessToSearchResult('Charles Xavier');
+ await page.accessToSection('worker.card.calendar');
+ }
+
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
- await page.loginAndModule('hr', 'worker');
- await page.accessToSearchResult('Charles Xavier');
- await page.accessToSection('worker.card.calendar');
+ accessAs('hr');
});
afterAll(async() => {
@@ -21,48 +30,40 @@ describe('Worker calendar path', () => {
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
+ await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.absence);
+ await page.click(selectors.workerCalendar.absence);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch);
+ await page.click(selectors.workerCalendar.lastMondayOfMarch);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.halfHoliday);
+ await page.click(selectors.workerCalendar.halfHoliday);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.fistMondayOfMay);
+ await page.click(selectors.workerCalendar.fistMondayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.furlough);
+ await page.click(selectors.workerCalendar.furlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay);
+ await page.click(selectors.workerCalendar.secondTuesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay);
+ await page.click(selectors.workerCalendar.secondWednesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay);
+ await page.click(selectors.workerCalendar.secondThursdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.halfFurlough);
- await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
+ await page.click(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
+ await page.click(selectors.workerCalendar.secondFridayOfJun);
- const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
-
- expect(result).toContain(' 1.5 ');
+ expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 1.5 ');
});
});
describe(`as salesBoss`, () => {
- it(`should log in and get to Charles Xavier's calendar`, async() => {
- await page.loginAndModule('salesBoss', 'worker');
- await page.accessToSearchResult('Charles Xavier');
- await page.accessToSection('worker.card.calendar');
- });
+ it(`should log in, get to Charles Xavier's calendar, undo what was done here, and check the total holidays used are back to what it was`, async() => {
+ accessAs('salesBoss');
- it('should undo what was done here', async() => {
- await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
@@ -90,45 +91,24 @@ describe('Worker calendar path', () => {
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
- });
- it('should check the total holidays used are back to what it was', async() => {
- const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
-
- expect(result).toContain(' 0 ');
+ expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
describe(`as Charles Xavier`, () => {
- it(`should log in and get to his calendar`, async() => {
- await page.loginAndModule('CharlesXavier', 'worker');
- await page.accessToSearchResult('Charles Xavier');
- await page.accessToSection('worker.card.calendar');
- });
-
- it('should make a futile attempt to add holidays', async() => {
- await page.waitForTimeout(reasonableTimeBetweenClicks);
+ it('should log in and get to his calendar, make a futile attempt to add holidays, check the total holidays used are now the initial ones and use the year selector to go to the previous year', async() => {
+ accessAs('CharlesXavier');
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
- await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
- });
- it('should check the total holidays used are now the initial ones', async() => {
- const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
+ await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
- expect(result).toContain(' 0 ');
- });
-
- it('should use the year selector to go to the previous year', async() => {
- const date = new Date();
- const lastYear = (date.getFullYear() - 1).toString();
+ expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
await page.autocompleteSearch(selectors.workerCalendar.year, lastYear);
- await page.waitForTimeout(reasonableTimeBetweenClicks);
- const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
-
- expect(result).toContain(' 0 ');
+ expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
});
diff --git a/e2e/paths/04-item/13_fixedPrice.spec.js b/e2e/paths/04-item/13_fixedPrice.spec.js
index fc7aac3d0..40ccc009e 100644
--- a/e2e/paths/04-item/13_fixedPrice.spec.js
+++ b/e2e/paths/04-item/13_fixedPrice.spec.js
@@ -24,8 +24,8 @@ describe('Item fixed prices path', () => {
it('should fill the fixed price data', async() => {
const now = new Date();
await page.autocompleteSearch(selectors.itemFixedPrice.fourthWarehouse, 'Warehouse one');
- await page.write(selectors.itemFixedPrice.fourthPPU, '1');
- await page.write(selectors.itemFixedPrice.fourthPPP, '1');
+ await page.writeOnEditableTD(selectors.itemFixedPrice.fourthGroupingPrice, '1');
+ await page.writeOnEditableTD(selectors.itemFixedPrice.fourthPackingPrice, '1');
await page.write(selectors.itemFixedPrice.fourthMinPrice, '1');
await page.pickDate(selectors.itemFixedPrice.fourthStarted, now);
await page.pickDate(selectors.itemFixedPrice.fourthEnded, now);
diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js
index 67dfd83bf..9d6fddbe6 100644
--- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js
+++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js
@@ -196,6 +196,15 @@ describe('Ticket Edit sale path', () => {
expect(result).toContain('22.50');
});
+ it('should check in the history that logs has been added', async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton);
+ await page.waitForSelector(selectors.ticketSales.firstSaleHistory);
+ const result = await page.countElement(selectors.ticketSales.firstSaleHistory);
+
+ expect(result).toBeGreaterThan(0);
+ await page.waitToClick(selectors.ticketSales.closeHistory);
+ });
+
it('should recalculate price of sales', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
diff --git a/e2e/paths/05-ticket/21_future.spec.js b/e2e/paths/05-ticket/21_future.spec.js
index 45c39de86..34ae3d688 100644
--- a/e2e/paths/05-ticket/21_future.spec.js
+++ b/e2e/paths/05-ticket/21_future.spec.js
@@ -55,7 +55,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit);
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
+ await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search with the destination IPT', async() => {
@@ -68,7 +68,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit);
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
+ await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search with the origin grouped state', async() => {
@@ -152,50 +152,6 @@ describe('Ticket Future path', () => {
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
- it('should search in smart-table with especified Lines', async() => {
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.write(selectors.ticketFuture.tableLines, '0');
- await page.keyboard.press('Enter');
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
-
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
- await page.waitToClick(selectors.ticketFuture.submit);
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
-
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.write(selectors.ticketFuture.tableLines, '1');
- await page.keyboard.press('Enter');
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
-
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
- await page.waitToClick(selectors.ticketFuture.submit);
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
- });
-
- it('should search in smart-table with especified Liters', async() => {
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.write(selectors.ticketFuture.tableLiters, '0');
- await page.keyboard.press('Enter');
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
-
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
- await page.waitToClick(selectors.ticketFuture.submit);
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
-
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.write(selectors.ticketFuture.tableLiters, '28');
- await page.keyboard.press('Enter');
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
-
- await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
- await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
- await page.waitToClick(selectors.ticketFuture.submit);
- await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
- });
-
it('should check the three last tickets and move to the future', async() => {
await page.waitToClick(selectors.ticketFuture.multiCheck);
await page.waitToClick(selectors.ticketFuture.firstCheck);
diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js
index f2855d711..aefa89b5b 100644
--- a/front/core/components/searchbar/searchbar.js
+++ b/front/core/components/searchbar/searchbar.js
@@ -308,7 +308,7 @@ export default class Searchbar extends Component {
this.tableQ = null;
- const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length;
+ const hasParams = this.$params.q && JSON.parse(this.$params.q).tableQ;
if (hasParams) {
const stateFilter = JSON.parse(this.$params.q);
for (let param in stateFilter) {
@@ -325,8 +325,8 @@ export default class Searchbar extends Component {
for (let param in stateFilter.tableQ)
params[param] = stateFilter.tableQ[param];
- Object.assign(stateFilter, params);
- return this.model.applyParams(params)
+ const newParams = Object.assign(stateFilter, params);
+ return this.model.applyParams(newParams)
.then(() => this.model.data);
}
diff --git a/front/core/components/searchbar/searchbar.spec.js b/front/core/components/searchbar/searchbar.spec.js
index ed8fd9d07..9998e7a7c 100644
--- a/front/core/components/searchbar/searchbar.spec.js
+++ b/front/core/components/searchbar/searchbar.spec.js
@@ -174,14 +174,12 @@ describe('Component vnSearchbar', () => {
jest.spyOn(controller, 'doSearch');
controller.model = {
refresh: jest.fn(),
+ applyFilter: jest.fn().mockReturnValue(Promise.resolve()),
userParams: {
id: 1
}
};
- controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve());
- jest.spyOn(controller.model, 'applyParams');
-
controller.filter = filter;
controller.removeParam(0);
diff --git a/front/salix/components/index.js b/front/salix/components/index.js
index f6727fadf..8f5724862 100644
--- a/front/salix/components/index.js
+++ b/front/salix/components/index.js
@@ -19,4 +19,5 @@ import './user-popover';
import './upload-photo';
import './bank-entity';
import './log';
+import './instance-log';
import './sendSms';
diff --git a/front/salix/components/instance-log/index.html b/front/salix/components/instance-log/index.html
new file mode 100644
index 000000000..354e81080
--- /dev/null
+++ b/front/salix/components/instance-log/index.html
@@ -0,0 +1,12 @@
+
+
{{ $t('clientMail') }} {{email}}
+{{ $t('ticketId') }} {{ticketId}} + + diff --git a/print/templates/email/invoice-electronic/invoice-electronic.js b/print/templates/email/invoice-electronic/invoice-electronic.js new file mode 100644 index 000000000..2e1e739ac --- /dev/null +++ b/print/templates/email/invoice-electronic/invoice-electronic.js @@ -0,0 +1,21 @@ +module.exports = { + name: 'invoice-electronic', + props: { + name: { + type: [String], + required: true + }, + email: { + type: [String], + required: true + }, + ticketId: { + type: [Number], + required: true + }, + url: { + type: [String], + required: true + } + }, +}; diff --git a/print/templates/email/invoice-electronic/locale/en.yml b/print/templates/email/invoice-electronic/locale/en.yml new file mode 100644 index 000000000..5523a2fa3 --- /dev/null +++ b/print/templates/email/invoice-electronic/locale/en.yml @@ -0,0 +1,4 @@ +subject: A electronic invoice has been created +title: A new electronic invoice has been created for the client +clientMail: The client's email is +ticketId: The invoice's ticket is \ No newline at end of file diff --git a/print/templates/email/invoice-electronic/locale/es.yml b/print/templates/email/invoice-electronic/locale/es.yml new file mode 100644 index 000000000..2cbcfbb36 --- /dev/null +++ b/print/templates/email/invoice-electronic/locale/es.yml @@ -0,0 +1,4 @@ +subject: Se ha creado una factura electrónica +title: Se ha creado una nueva factura electrónica para el cliente +clientMail: El correo del cliente es +ticketId: El ticket de la factura es \ No newline at end of file diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index 48848c079..f7011ad81 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -82,7 +82,7 @@ module.exports = { return this.rawSqlFromDef(`taxes`, [reference]); }, fetchIntrastat(reference) { - return this.rawSqlFromDef(`intrastat`, [reference, reference, reference, reference, reference]); + return this.rawSqlFromDef(`intrastat`, [reference, reference, reference]); }, fetchRectified(reference) { return this.rawSqlFromDef(`rectified`, [reference]); diff --git a/print/templates/reports/invoice/sql/intrastat.sql b/print/templates/reports/invoice/sql/intrastat.sql index 7f5fbdf39..f986a9564 100644 --- a/print/templates/reports/invoice/sql/intrastat.sql +++ b/print/templates/reports/invoice/sql/intrastat.sql @@ -1,39 +1,26 @@ SELECT * FROM invoiceOut io JOIN invoiceOutSerial ios ON io.serial = ios.code - JOIN - (SELECT - t.refFk, - ir.id code, - ir.description description, - CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, - CAST(SUM(CAST(IFNULL(i.stems, 1) * s.quantity * IF(ic.grams, ic.grams, i.density * ic.cm3delivery / 1000) / 1000 AS DECIMAL(10,2)) * - IF(sub.weight, sub.weight / vn.invoiceOut_getWeight(?), 1)) AS DECIMAL(10,2)) netKg, - CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal - FROM vn.ticket t - JOIN vn.sale s ON s.ticketFk = t.id - JOIN vn.item i ON i.id = s.itemFk - JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk - JOIN vn.intrastat ir ON ir.id = i.intrastatFk - LEFT JOIN ( - SELECT t2.weight - FROM vn.ticket t2 - WHERE refFk = ? AND weight - LIMIT 1 - ) sub ON TRUE - WHERE t.refFk = ? - AND i.intrastatFk - GROUP BY i.intrastatFk - UNION ALL - SELECT - NULL AS refFk, - NULL AS code, - NULL AS description, - 0 AS stems, - 0 AS netKg, - IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) AS subtotal - FROM vn.ticketService ts - JOIN vn.ticket t ON ts.ticketFk = t.id - WHERE t.refFk = ?) sub - WHERE io.`ref` = ? AND ios.isCEE - ORDER BY sub.code; + JOIN( + SELECT ir.id code, + ir.description, + iii.stems, + iii.net netKg, + iii.amount subtotal + FROM vn.invoiceInIntrastat iii + LEFT JOIN vn.invoiceIn ii ON ii.id = iii.invoiceInFk + LEFT JOIN vn.invoiceOut io ON io.ref = ii.supplierRef + LEFT JOIN vn.intrastat ir ON ir.id = iii.intrastatFk + WHERE io.`ref` = ? + UNION ALL + SELECT NULL code, + 'Servicios' description, + 0 stems, + 0 netKg, + IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) subtotal + FROM vn.ticketService ts + JOIN vn.ticket t ON ts.ticketFk = t.id + WHERE t.refFk = ? + ) sub + WHERE io.ref = ? AND ios.isCEE + ORDER BY sub.code;