Merge pull request '4804-ticket.descriptor_sms' (!1217) from 4804-ticket.descriptor_sms into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #1217
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
This commit is contained in:
Vicent Llopis 2022-12-21 11:29:59 +00:00
commit 7731ca143e
20 changed files with 247 additions and 33 deletions

View File

@ -26,11 +26,30 @@ module.exports = Self => {
Self.setSaleQuantity = async(saleId, quantity) => { Self.setSaleQuantity = async(saleId, quantity) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {};
let tx;
const sale = await models.Sale.findById(saleId); if (typeof options == 'object')
return await sale.updateAttributes({ Object.assign(myOptions, options);
originalQuantity: sale.quantity,
quantity: quantity 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;
}
}; };
}; };

View File

@ -2,15 +2,26 @@ const models = require('vn-loopback/server/server').models;
describe('setSaleQuantity()', () => { describe('setSaleQuantity()', () => {
it('should change quantity sale', async() => { it('should change quantity sale', async() => {
const saleId = 30; const tx = await models.Ticket.beginTransaction({});
const newQuantity = 10;
const originalSale = await models.Sale.findById(saleId); try {
const options = {transaction: tx};
await models.Collection.setSaleQuantity(saleId, newQuantity); const saleId = 30;
const updateSale = await models.Sale.findById(saleId); const newQuantity = 10;
expect(updateSale.originalQuantity).toEqual(originalSale.quantity); const originalSale = await models.Sale.findById(saleId, null, options);
expect(updateSale.quantity).toEqual(newQuantity);
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;
}
}); });
}); });

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('TicketLog', 'getChanges', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -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

View File

@ -2733,6 +2733,14 @@ UPDATE `account`.`user`
SET `hasGrant` = 1 SET `hasGrant` = 1
WHERE `id` = 66; 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`) INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`)
VALUES 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'); (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');

View File

@ -66,9 +66,10 @@
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*", "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}}})", "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}}})", "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", "Change quantity": "{{concept}} change of {{oldQuantity}} to {{newQuantity}}",
"Claim state has changed to incomplete": "The state of the claim [{{claimId}}]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*", "Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked",
"Claim state has changed to canceled": "The state of the claim [{{claimId}}]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*", "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", "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", "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}}", "Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}",

View File

@ -134,9 +134,10 @@
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*", "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}}})", "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}}})", "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}}*", "Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}",
"Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*", "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*",
"Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*", "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}}", "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", "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",

View File

@ -104,6 +104,9 @@
"SageTransactionType": { "SageTransactionType": {
"dataSource": "vn" "dataSource": "vn"
}, },
"TicketSms": {
"dataSource": "vn"
},
"TpvError": { "TpvError": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -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"
}
}
}

View File

@ -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');
};
};

View File

@ -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`);
});
});

View File

@ -31,6 +31,7 @@ module.exports = Self => {
}); });
Self.sendSms = async(ctx, id, destination, message, options) => { Self.sendSms = async(ctx, id, destination, message, options) => {
const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -45,7 +46,14 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
try { 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 = { const logRecord = {
originFk: id, originFk: id,
userFk: userId, 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; sms.logId = ticketLog.id;

View File

@ -15,9 +15,16 @@ describe('ticket sendSms()', () => {
const sms = await models.Ticket.sendSms(ctx, id, destination, message, options); const sms = await models.Ticket.sendSms(ctx, id, destination, message, options);
const createdLog = await models.TicketLog.findById(sms.logId, null, 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)); const json = JSON.parse(JSON.stringify(createdLog.newInstance));
expect(json.message).toEqual(message); expect(json.message).toEqual(message);
expect(ticketSms.ticketFk).toEqual(createdLog.originFk);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -0,0 +1,3 @@
module.exports = function(Self) {
require('../methods/ticket-log/getChanges')(Self);
};

View File

@ -43,7 +43,7 @@
ng-if="!$ctrl.hasDocuwareFile" ng-if="!$ctrl.hasDocuwareFile"
ng-click="$ctrl.showPdfDeliveryNote('withoutPrices')" ng-click="$ctrl.showPdfDeliveryNote('withoutPrices')"
translate> translate>
as PDF without prices as PDF without prices
</vn-item> </vn-item>
<vn-item <vn-item
ng-click="$ctrl.showCsvDeliveryNote()" ng-click="$ctrl.showCsvDeliveryNote()"
@ -110,6 +110,12 @@
translate> translate>
SMS Minimum import SMS Minimum import
</vn-item> </vn-item>
<vn-item
ng-click="$ctrl.sendChangesSms()"
name="sendChangesSms"
translate>
SMS Notify changes
</vn-item>
<vn-item <vn-item
ng-click="makeInvoiceConfirmation.show()" ng-click="makeInvoiceConfirmation.show()"
ng-show="$ctrl.isEditable" ng-show="$ctrl.isEditable"
@ -150,31 +156,31 @@
</h5> </h5>
<vn-tool-bar class="vn-mt-md"> <vn-tool-bar class="vn-mt-md">
<vn-button <vn-button
label="Monday" label="Monday"
ng-click="$ctrl.addTurn(0)"> ng-click="$ctrl.addTurn(0)">
</vn-button> </vn-button>
<vn-button <vn-button
label="Tuesday" label="Tuesday"
ng-click="$ctrl.addTurn(1)"> ng-click="$ctrl.addTurn(1)">
</vn-button> </vn-button>
<vn-button <vn-button
label="Wednesday" label="Wednesday"
ng-click="$ctrl.addTurn(2)"> ng-click="$ctrl.addTurn(2)">
</vn-button> </vn-button>
<vn-button <vn-button
label="Thursday" label="Thursday"
ng-click="$ctrl.addTurn(3)"> ng-click="$ctrl.addTurn(3)">
</vn-button> </vn-button>
<vn-button <vn-button
label="Friday" label="Friday"
ng-click="$ctrl.addTurn(4)"> ng-click="$ctrl.addTurn(4)">
</vn-button> </vn-button>
<vn-button <vn-button
label="Saturday" label="Saturday"
ng-click="$ctrl.addTurn(5)"> ng-click="$ctrl.addTurn(5)">
</vn-button> </vn-button>
<vn-button <vn-button
label="Sunday" label="Sunday"
ng-click="$ctrl.addTurn(6)"> ng-click="$ctrl.addTurn(6)">
</vn-button> </vn-button>
</vn-tool-bar> </vn-tool-bar>
@ -251,13 +257,13 @@
<!-- Transfer Client popup --> <!-- Transfer Client popup -->
<vn-dialog <vn-dialog
vn-id="transferClient" vn-id="transferClient"
title="transferClient" title="transferClient"
size="sm" size="sm"
on-accept="$ctrl.transferClient($client)"> on-accept="$ctrl.transferClient($client)">
<tpl-body> <tpl-body>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
vn-id="client" vn-id="client"
required="true" required="true"
@ -316,4 +322,4 @@
on-accept="$ctrl.refund()" on-accept="$ctrl.refund()"
question="Are you sure you want to refund all?" question="Are you sure you want to refund all?"
message="Refund all"> message="Refund all">
</vn-confirm> </vn-confirm>

View File

@ -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) { showSMSDialog(params) {
const address = this.ticket.address; const address = this.ticket.address;
const client = this.ticket.client; const client = this.ticket.client;

View File

@ -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()', () => { describe('showSMSDialog()', () => {
it('should set the destionationFk and destination properties and then call the sms open() method', () => { it('should set the destionationFk and destination properties and then call the sms open() method', () => {
controller.$.sms = {open: () => {}}; controller.$.sms = {open: () => {}};

View File

@ -11,4 +11,5 @@ Show Proforma: Ver proforma
Refund all: Abonar todo Refund all: Abonar todo
Invoice sent: Factura enviada Invoice sent: Factura enviada
The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}"
Transfer client: Transferir cliente Transfer client: Transferir cliente
SMS Notify changes: SMS Notificar cambios

View File

@ -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." 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." 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}}"

View File

@ -23,3 +23,4 @@ Restore ticket: Restaurar ticket
You are going to restore this ticket: Vas a restaurar este 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 restore this ticket?: ¿Seguro que quieres restaurar el ticket?
Are you sure you want to refund all?: ¿Seguro que quieres abonar todo? 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}}"