diff --git a/back/methods/collection/setSaleQuantity.js b/back/methods/collection/setSaleQuantity.js index 644c44a60..b6c56ddc4 100644 --- a/back/methods/collection/setSaleQuantity.js +++ b/back/methods/collection/setSaleQuantity.js @@ -26,11 +26,30 @@ module.exports = Self => { Self.setSaleQuantity = async(saleId, quantity) => { const models = Self.app.models; + const myOptions = {}; + let tx; - const sale = await models.Sale.findById(saleId); - return await sale.updateAttributes({ - originalQuantity: sale.quantity, - quantity: quantity - }); + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const sale = await models.Sale.findById(saleId, null, myOptions); + const saleUpdated = await sale.updateAttributes({ + originalQuantity: sale.quantity, + quantity: quantity + }, myOptions); + + if (tx) await tx.commit(); + + return saleUpdated; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; }; diff --git a/back/methods/collection/spec/setSaleQuantity.spec.js b/back/methods/collection/spec/setSaleQuantity.spec.js index 5d06a4383..63dc3bd2d 100644 --- a/back/methods/collection/spec/setSaleQuantity.spec.js +++ b/back/methods/collection/spec/setSaleQuantity.spec.js @@ -2,15 +2,26 @@ const models = require('vn-loopback/server/server').models; describe('setSaleQuantity()', () => { it('should change quantity sale', async() => { - const saleId = 30; - const newQuantity = 10; + const tx = await models.Ticket.beginTransaction({}); - const originalSale = await models.Sale.findById(saleId); + try { + const options = {transaction: tx}; - await models.Collection.setSaleQuantity(saleId, newQuantity); - const updateSale = await models.Sale.findById(saleId); + const saleId = 30; + const newQuantity = 10; - expect(updateSale.originalQuantity).toEqual(originalSale.quantity); - expect(updateSale.quantity).toEqual(newQuantity); + const originalSale = await models.Sale.findById(saleId, null, options); + + await models.Collection.setSaleQuantity(saleId, newQuantity, options); + const updateSale = await models.Sale.findById(saleId, null, options); + + expect(updateSale.originalQuantity).toEqual(originalSale.quantity); + expect(updateSale.quantity).toEqual(newQuantity); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/db/changes/225201/00-aclTicketLog.sql b/db/changes/225201/00-aclTicketLog.sql new file mode 100644 index 000000000..edba17ab4 --- /dev/null +++ b/db/changes/225201/00-aclTicketLog.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('TicketLog', 'getChanges', 'READ', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/225201/00-ticketSms.sql b/db/changes/225201/00-ticketSms.sql new file mode 100644 index 000000000..f454f99b1 --- /dev/null +++ b/db/changes/225201/00-ticketSms.sql @@ -0,0 +1,8 @@ +CREATE TABLE `vn`.`ticketSms` ( + `smsFk` mediumint(8) unsigned NOT NULL, + `ticketFk` int(11) DEFAULT NULL, + PRIMARY KEY (`smsFk`), + KEY `ticketSms_FK_1` (`ticketFk`), + CONSTRAINT `ticketSms_FK` FOREIGN KEY (`smsFk`) REFERENCES `sms` (`id`) ON UPDATE CASCADE, + CONSTRAINT `ticketSms_FK_1` FOREIGN KEY (`ticketFk`) REFERENCES `ticket` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 114a12bff..c4ce78658 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2733,6 +2733,14 @@ UPDATE `account`.`user` SET `hasGrant` = 1 WHERE `id` = 66; +INSERT INTO `vn`.`ticketLog` (`originFk`, userFk, `action`, changedModel, oldInstance, newInstance, changedModelId, `description`) + VALUES + (7, 18, 'update', 'Sale', '{"quantity":1}', '{"quantity":10}', 1, NULL), + (7, 18, 'update', 'Ticket', '{"quantity":1,"concept":"Chest ammo box"}', '{"quantity":10,"concept":"Chest ammo box"}', 1, NULL), + (7, 18, 'update', 'Sale', '{"price":3}', '{"price":5}', 1, NULL), + (7, 18, 'update', NULL, NULL, NULL, NULL, "Cambio cantidad Melee weapon heavy shield 1x0.5m de '5' a '10'"); + + INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`) VALUES (0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all'); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 3c6f6188f..4885e34ec 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -66,9 +66,10 @@ "MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*", "Changed client paymethod": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})", "Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})", - "Claim will be picked": "The product from the claim [{{claimId}}]({{{claimUrl}}}) from the client *{{clientName}}* will be picked", - "Claim state has changed to incomplete": "The state of the claim [{{claimId}}]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*", - "Claim state has changed to canceled": "The state of the claim [{{claimId}}]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*", + "Change quantity": "{{concept}} change of {{oldQuantity}} to {{newQuantity}}", + "Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked", + "Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*", + "Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*", "Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", "Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member", "Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}", diff --git a/loopback/locale/es.json b/loopback/locale/es.json index c12d6bbe5..a500ff550 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -134,9 +134,10 @@ "MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*", "Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})", "Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})", - "Claim will be picked": "Se recogerá el género de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}*", - "Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*", - "Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*", + "Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}", + "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*", + "Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*", + "Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", diff --git a/modules/client/back/model-config.json b/modules/client/back/model-config.json index 4ef34ca3a..b466aa5a1 100644 --- a/modules/client/back/model-config.json +++ b/modules/client/back/model-config.json @@ -104,6 +104,9 @@ "SageTransactionType": { "dataSource": "vn" }, + "TicketSms": { + "dataSource": "vn" + }, "TpvError": { "dataSource": "vn" }, diff --git a/modules/client/back/models/ticket-sms.json b/modules/client/back/models/ticket-sms.json new file mode 100644 index 000000000..03f592f51 --- /dev/null +++ b/modules/client/back/models/ticket-sms.json @@ -0,0 +1,28 @@ +{ + "name": "TicketSms", + "base": "VnModel", + "options": { + "mysql": { + "table": "ticketSms" + } + }, + "properties": { + "smsFk": { + "type": "number", + "id": true, + "description": "Identifier" + } + }, + "relations": { + "ticket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "ticketFk" + }, + "sms": { + "type": "belongsTo", + "model": "Sms", + "foreignKey": "smsFk" + } + } +} diff --git a/modules/ticket/back/methods/ticket-log/getChanges.js b/modules/ticket/back/methods/ticket-log/getChanges.js new file mode 100644 index 000000000..d020a0957 --- /dev/null +++ b/modules/ticket/back/methods/ticket-log/getChanges.js @@ -0,0 +1,64 @@ +module.exports = Self => { + Self.remoteMethodCtx('getChanges', { + description: 'Get changues in the sales of a ticket', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'the ticket id', + http: {source: 'path'} + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/:id/getChanges`, + verb: 'get' + } + }); + + Self.getChanges = async(ctx, id, options) => { + const models = Self.app.models; + const myOptions = {}; + const $t = ctx.req.__; // $translate + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const ticketLogs = await models.TicketLog.find( + { + where: { + and: [ + {originFk: id}, + {action: 'update'}, + {changedModel: 'Sale'} + ] + }, + fields: [ + 'oldInstance', + 'newInstance', + 'changedModelId' + ], + }, myOptions); + + const changes = []; + for (const ticketLog of ticketLogs) { + const oldQuantity = ticketLog.oldInstance.quantity; + const newQuantity = ticketLog.newInstance.quantity; + + if (oldQuantity || newQuantity) { + const sale = await models.Sale.findById(ticketLog.changedModelId, null, myOptions); + const message = $t('Change quantity', { + concept: sale.concept, + oldQuantity: oldQuantity || 0, + newQuantity: newQuantity || 0, + }); + + changes.push(message); + } + } + return changes.join('\n'); + }; +}; diff --git a/modules/ticket/back/methods/ticket-log/specs/getChanges.spec.js b/modules/ticket/back/methods/ticket-log/specs/getChanges.spec.js new file mode 100644 index 000000000..c0f7dde0e --- /dev/null +++ b/modules/ticket/back/methods/ticket-log/specs/getChanges.spec.js @@ -0,0 +1,16 @@ +const models = require('vn-loopback/server/server').models; + +describe('ticketLog getChanges()', () => { + const ctx = {req: {}}; + + ctx.req.__ = value => { + return value; + }; + it('should return the changes in the sales of a ticket', async() => { + const ticketId = 7; + + const changues = await models.TicketLog.getChanges(ctx, ticketId); + + expect(changues).toContain(`Change quantity`); + }); +}); diff --git a/modules/ticket/back/methods/ticket/sendSms.js b/modules/ticket/back/methods/ticket/sendSms.js index a0adcae07..2336ae859 100644 --- a/modules/ticket/back/methods/ticket/sendSms.js +++ b/modules/ticket/back/methods/ticket/sendSms.js @@ -31,6 +31,7 @@ module.exports = Self => { }); Self.sendSms = async(ctx, id, destination, message, options) => { + const models = Self.app.models; const myOptions = {}; let tx; @@ -45,7 +46,14 @@ module.exports = Self => { const userId = ctx.req.accessToken.userId; try { - const sms = await Self.app.models.Sms.send(ctx, destination, message); + const sms = await models.Sms.send(ctx, destination, message); + + const newTicketSms = { + ticketFk: id, + smsFk: sms.id + }; + await models.TicketSms.create(newTicketSms); + const logRecord = { originFk: id, userFk: userId, @@ -60,7 +68,7 @@ module.exports = Self => { } }; - const ticketLog = await Self.app.models.TicketLog.create(logRecord, myOptions); + const ticketLog = await models.TicketLog.create(logRecord, myOptions); sms.logId = ticketLog.id; diff --git a/modules/ticket/back/methods/ticket/specs/sendSms.spec.js b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js index f50253b10..f94b8be2a 100644 --- a/modules/ticket/back/methods/ticket/specs/sendSms.spec.js +++ b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js @@ -15,9 +15,16 @@ describe('ticket sendSms()', () => { const sms = await models.Ticket.sendSms(ctx, id, destination, message, options); const createdLog = await models.TicketLog.findById(sms.logId, null, options); + + const filter = { + ticketFk: createdLog.originFk + }; + const ticketSms = await models.TicketSms.findOne(filter, options); + const json = JSON.parse(JSON.stringify(createdLog.newInstance)); expect(json.message).toEqual(message); + expect(ticketSms.ticketFk).toEqual(createdLog.originFk); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/models/ticket-log.js b/modules/ticket/back/models/ticket-log.js new file mode 100644 index 000000000..81855ada7 --- /dev/null +++ b/modules/ticket/back/models/ticket-log.js @@ -0,0 +1,3 @@ +module.exports = function(Self) { + require('../methods/ticket-log/getChanges')(Self); +}; diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index 1f0ae8362..805e0b391 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -43,7 +43,7 @@ ng-if="!$ctrl.hasDocuwareFile" ng-click="$ctrl.showPdfDeliveryNote('withoutPrices')" translate> - as PDF without prices + as PDF without prices SMS Minimum import + + SMS Notify changes + @@ -251,13 +257,13 @@ - - - \ No newline at end of file + diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 8ed30e9da..168002d07 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -225,6 +225,18 @@ class Controller extends Section { }); } + sendChangesSms() { + return this.$http.get(`TicketLogs/${this.id}/getChanges`) + .then(res => { + const params = { + ticketId: this.id, + created: this.ticket.updated, + changes: res.data + }; + this.showSMSDialog({message: this.$t('Send changes', params)}); + }); + } + showSMSDialog(params) { const address = this.ticket.address; const client = this.ticket.client; diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index f3d8ea3b3..48b64f4a0 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -258,6 +258,19 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { }); }); + describe('sendChangesSms()', () => { + it('should make a query and open the sms dialog', () => { + controller.$.sms = {open: () => {}}; + jest.spyOn(controller.$.sms, 'open'); + + $httpBackend.expectGET(`TicketLogs/${ticket.id}/getChanges`).respond(); + controller.sendChangesSms(); + $httpBackend.flush(); + + expect(controller.$.sms.open).toHaveBeenCalledWith(); + }); + }); + describe('showSMSDialog()', () => { it('should set the destionationFk and destination properties and then call the sms open() method', () => { controller.$.sms = {open: () => {}}; diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index a09a32131..a2725f485 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -11,4 +11,5 @@ Show Proforma: Ver proforma Refund all: Abonar todo Invoice sent: Factura enviada The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" -Transfer client: Transferir cliente \ No newline at end of file +Transfer client: Transferir cliente +SMS Notify changes: SMS Notificar cambios diff --git a/modules/ticket/front/descriptor/locale/en.yml b/modules/ticket/front/descriptor/locale/en.yml index 64075c7ef..8eed2265d 100644 --- a/modules/ticket/front/descriptor/locale/en.yml +++ b/modules/ticket/front/descriptor/locale/en.yml @@ -1,2 +1,3 @@ Make a payment: "Verdnatura communicates:\rYour order is pending of payment.\rPlease, enter the web page and make the payment with card.\rThank you." Minimum is needed: "Verdnatura communicates:\rA minimum import of 50€ (Without BAT) is needed for your order {{ticketId}} from date {{created | date: 'dd/MM/yyyy'}} to receive it with no extra fees." +Send changes: "Verdnatura communicates:\rOrder {{ticketId}} date {{created | date: 'dd/MM/yyyy'}}\r{{changes}}" diff --git a/modules/ticket/front/descriptor/locale/es.yml b/modules/ticket/front/descriptor/locale/es.yml index bce9e62d7..d921b5dc2 100644 --- a/modules/ticket/front/descriptor/locale/es.yml +++ b/modules/ticket/front/descriptor/locale/es.yml @@ -23,3 +23,4 @@ Restore ticket: Restaurar ticket You are going to restore this ticket: Vas a restaurar este ticket Are you sure you want to restore this ticket?: ¿Seguro que quieres restaurar el ticket? Are you sure you want to refund all?: ¿Seguro que quieres abonar todo? +Send changes: "Verdnatura le recuerda:\rPedido {{ticketId}} día {{created | date: 'dd/MM/yyyy'}}\r{{changes}}"