From 8d682f4e03e1d9a700e9c9f9f27eb906a172d568 Mon Sep 17 00:00:00 2001 From: carlosjr Date: Thu, 29 Jul 2021 17:20:57 +0200 Subject: [PATCH] chat notifications for many sales modifications --- loopback/locale/en.json | 7 ++ loopback/locale/es.json | 12 ++- .../back/methods/claim/createFromSales.js | 58 ++++++++++--- .../ticket/back/methods/sale/deleteSales.js | 36 +++++++- modules/ticket/back/methods/sale/reserve.js | 83 ++++++++++++++----- .../ticket/back/methods/sale/updatePrice.js | 58 +++++++++---- .../back/methods/sale/updateQuantity.js | 49 +++++++++-- modules/ticket/back/methods/ticket/addSale.js | 29 ++++++- .../back/methods/ticket/componentUpdate.js | 2 +- .../back/methods/ticket/updateDiscount.js | 38 ++++++++- 10 files changed, 308 insertions(+), 64 deletions(-) diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 75804ba218..fb1310d958 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -57,6 +57,13 @@ "The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one", "Can't create stowaway for this ticket": "Can't create stowaway for this ticket", "Swift / BIC can't be empty": "Swift / BIC can't be empty", + "Deleted sales from ticket": "I have deleted the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}", + "Added sale to ticket": "I have added the following line to the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}", + "Changed sale discount": "I have changed the following lines discounts from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", + "Created claim": "I have created a claim [{{claimId}}]({{{claimUrl}}}) for the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", + "Changed sale price": "I have changed the price of [{{itemId}}]({{{itemUrl}}}) {{concept}} ({{quantity}}) from {{oldPrice}}€ ➔ *{{newPrice}}€* of the ticket [{{ticketId}}]({{{ticketUrl}}})", + "Changed sale quantity": "I have changed the quantity of [{{itemId}}]({{{itemUrl}}}) {{concept}} from {{oldQuantity}} ➔ *{{newQuantity}}* of the ticket [{{ticketId}}]({{{ticketUrl}}})", + "Changed sale reserved state": "I have changed the following lines reserved state from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Bought units from buy request": "Bought {{quantity}} units of {{concept}} [{{itemId}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})", "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}}})", diff --git a/loopback/locale/es.json b/loopback/locale/es.json index ff30a61ff4..daac7d5dc7 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -54,7 +54,7 @@ "This address doesn't exist": "Este consignatario no existe", "You can't create an order for a inactive client": "You can't create an order for a inactive client", "You can't create an order for a client that doesn't has tax data verified": "You can't create an order for a client that doesn't has tax data verified", - "You must delete the claim id %d first": "Antes debes borrar la reclamacion %d", + "You must delete the claim id %d first": "Antes debes borrar la reclamación %d", "You don't have enough privileges": "No tienes suficientes permisos", "Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF", "You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos basicos de una orden con artículos", @@ -122,6 +122,16 @@ "Swift / BIC can't be empty": "Swift / BIC no puede estar vacío", "Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios", "Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios", + "Deleted sales from ticket": "He eliminado las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}", + "Added sale to ticket": "He añadido la siguiente linea al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}", + "Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", + "Created claim": "He creado una reclamación [{{claimId}}]({{{claimUrl}}}) de las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", + "Changed sale price": "He cambiado el precio de [{{itemId}}]({{{itemUrl}}}) {{concept}} ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* del ticket [{{ticketId}}]({{{ticketUrl}}})", + "Changed sale quantity": "He cambiado la cantidad de [{{itemId}}]({{{itemUrl}}}) {{concept}} de {{oldQuantity}} ➔ *{{newQuantity}}* del ticket [{{ticketId}}]({{{ticketUrl}}})", + "State": "Estado", + "regular": "normal", + "reserved": "reservado", + "Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Bought units from buy request": "Se ha comprado {{quantity}} unidades de {{concept}} [{{itemId}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})", "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}}})", diff --git a/modules/claim/back/methods/claim/createFromSales.js b/modules/claim/back/methods/claim/createFromSales.js index 2dd1b75c24..f22aabbf36 100644 --- a/modules/claim/back/methods/claim/createFromSales.js +++ b/modules/claim/back/methods/claim/createFromSales.js @@ -3,17 +3,20 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('createFromSales', { description: 'Create a claim', - accepts: [{ - arg: 'ticketId', - type: 'Number', - required: true, - description: 'The origin ticket id' - }, { - arg: 'sales', - type: ['Object'], - required: true, - description: 'The claimed sales' - }], + accepts: [ + { + arg: 'ticketId', + type: 'number', + required: true, + description: 'The origin ticket id' + }, + { + arg: 'sales', + type: ['object'], + required: true, + description: 'The claimed sales' + } + ], returns: { type: 'object', root: true @@ -25,6 +28,7 @@ module.exports = Self => { }); Self.createFromSales = async(ctx, ticketId, sales, options) => { + const $t = ctx.req.__; // $translate const models = Self.app.models; let tx; const myOptions = {}; @@ -39,7 +43,20 @@ module.exports = Self => { const userId = ctx.req.accessToken.userId; try { - const ticket = await models.Ticket.findById(ticketId, null, myOptions); + const ticket = await models.Ticket.findById(ticketId, { + include: { + relation: 'client', + scope: { + include: { + relation: 'salesPersonUser', + scope: { + fields: ['id', 'name'] + } + } + } + } + }, myOptions); + if (ticket.isDeleted) throw new UserError(`You can't create a claim for a removed ticket`); @@ -49,6 +66,8 @@ module.exports = Self => { ticketCreated: ticket.shipped, workerFk: userId }, myOptions); + + let changesMade = ''; const promises = []; for (const sale of sales) { @@ -59,10 +78,25 @@ module.exports = Self => { }, myOptions); promises.push(newClaimBeginning); + changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`; } await Promise.all(promises); + const salesPerson = ticket.client().salesPersonUser(); + if (salesPerson) { + const origin = ctx.req.headers.origin; + + const message = $t('Created claim', { + claimId: newClaim.id, + ticketId: ticketId, + ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, + claimUrl: `${origin}/#!/claim/${newClaim.id}/summary`, + changes: changesMade + }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); + } + if (tx) await tx.commit(); return newClaim; diff --git a/modules/ticket/back/methods/sale/deleteSales.js b/modules/ticket/back/methods/sale/deleteSales.js index 31899a501b..a604da8582 100644 --- a/modules/ticket/back/methods/sale/deleteSales.js +++ b/modules/ticket/back/methods/sale/deleteSales.js @@ -6,18 +6,18 @@ module.exports = Self => { accessType: 'WRITE', accepts: [{ arg: 'sales', - type: ['Object'], + type: ['object'], required: true, description: 'The sales to remove' }, { arg: 'ticketId', - type: 'Number', + type: 'number', required: true, description: 'The ticket id' }], returns: { - type: ['Object'], + type: ['object'], root: true }, http: { @@ -27,10 +27,25 @@ module.exports = Self => { }); Self.deleteSales = async(ctx, sales, ticketId) => { + const $t = ctx.req.__; // $translate const models = Self.app.models; const canEditSales = await models.Sale.canEdit(ctx, sales); + const ticket = await models.Ticket.findById(ticketId, { + include: { + relation: 'client', + scope: { + include: { + relation: 'salesPersonUser', + scope: { + fields: ['id', 'name'] + } + } + } + } + }); + const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId); if (!isTicketEditable) throw new UserError(`The sales of this ticket can't be modified`); @@ -39,11 +54,26 @@ module.exports = Self => { throw new UserError(`Sale(s) blocked, please contact production`); const promises = []; + let deletions = ''; for (let sale of sales) { const deletedSale = models.Sale.destroyById(sale.id); + deletions += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`; + promises.push(deletedSale); } + const salesPerson = ticket.client().salesPersonUser(); + if (salesPerson) { + const origin = ctx.req.headers.origin; + + const message = $t('Deleted sales from ticket', { + ticketId: ticketId, + ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, + deletions: deletions + }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); + } + return Promise.all(promises); }; }; diff --git a/modules/ticket/back/methods/sale/reserve.js b/modules/ticket/back/methods/sale/reserve.js index 96c794e7ca..639827fa7d 100644 --- a/modules/ticket/back/methods/sale/reserve.js +++ b/modules/ticket/back/methods/sale/reserve.js @@ -5,24 +5,27 @@ module.exports = Self => { Self.remoteMethodCtx('reserve', { description: 'Change the state of a ticket', accessType: 'WRITE', - accepts: [{ - arg: 'ticketId', - type: 'Number', - required: true, - description: 'The ticket id' - }, { - arg: 'sales', - type: ['Object'], - required: true, - description: 'The sale to reserve' - }, - { - arg: 'reserved', - type: 'Boolean', - required: true - }], + accepts: [ + { + arg: 'ticketId', + type: 'number', + required: true, + description: 'The ticket id' + }, + { + arg: 'sales', + type: ['object'], + required: true, + description: 'The sale to reserve' + }, + { + arg: 'reserved', + type: 'boolean', + required: true + } + ], returns: { - type: ['Object'], + type: ['object'], root: true }, http: { @@ -32,7 +35,9 @@ module.exports = Self => { }); Self.reserve = async(ctx, ticketId, sales, reserved) => { + const $t = ctx.req.__; // $translate const models = Self.app.models; + const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId); if (!isTicketEditable) throw new UserError(`The sales of this ticket can't be modified`); @@ -42,12 +47,50 @@ module.exports = Self => { if (!canEditSale) throw new UserError(`Sale(s) blocked, please contact production`); + let changesMade = ''; const promises = []; + for (let sale of sales) { - const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved}); - promises.push(reservedSale); + if (sale.reserved != reserved) { + const oldState = sale.reserved ? 'reserved' : 'regular'; + const newState = reserved ? 'reserved' : 'regular'; + + const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved}); + + promises.push(reservedSale); + + changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`; + } } - return Promise.all(promises); + const result = await Promise.all(promises); + + const ticket = await models.Ticket.findById(ticketId, { + include: { + relation: 'client', + scope: { + include: { + relation: 'salesPersonUser', + scope: { + fields: ['id', 'name'] + } + } + } + } + }); + + const salesPerson = ticket.client().salesPersonUser(); + if (salesPerson) { + const origin = ctx.req.headers.origin; + + const message = $t('Changed sale reserved state', { + ticketId: ticketId, + ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, + changes: changesMade + }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); + } + + return result; }; }; diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index 2195c2b7b9..cb86296c93 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -19,7 +19,7 @@ module.exports = Self => { } ], returns: { - type: 'Number', + type: 'number', root: true }, http: { @@ -29,29 +29,37 @@ module.exports = Self => { }); Self.updatePrice = async(ctx, id, newPrice) => { - let models = Self.app.models; - let tx = await Self.beginTransaction({}); + const $t = ctx.req.__; // $translate + const models = Self.app.models; + const tx = await Self.beginTransaction({}); try { - let options = {transaction: tx}; + const options = {transaction: tx}; - let filter = { + const filter = { include: { relation: 'ticket', scope: { include: { relation: 'client', scope: { - fields: ['salesPersonFk'] + fields: ['salesPersonFk'], + include: { + relation: 'salesPersonUser', + scope: { + fields: ['id', 'name'] + } + } } }, fields: ['id', 'clientFk'] } } }; - let sale = await models.Sale.findById(id, filter, options); - let isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk); + const sale = await models.Sale.findById(id, filter, options); + + const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk); if (!isEditable) throw new UserError(`The sales of this ticket can't be modified`); @@ -60,21 +68,19 @@ module.exports = Self => { if (!canEditSale) throw new UserError(`Sale(s) blocked, please contact production`); + const oldPrice = sale.price; const userId = ctx.req.accessToken.userId; + const usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options); + const componentCode = usesMana ? 'mana' : 'buyerDiscount'; + const discount = await models.Component.findOne({where: {code: componentCode}}, options); + const componentId = discount.id; + const componentValue = newPrice - sale.price; - let usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options); - - let componentCode = usesMana ? 'mana' : 'buyerDiscount'; - - let discount = await models.Component.findOne({where: {code: componentCode}}, options); - let componentId = discount.id; - let componentValue = newPrice - sale.price; - - let where = { + const where = { componentFk: componentId, saleFk: id }; - let saleComponent = await models.SaleComponent.findOne({where}, options); + const saleComponent = await models.SaleComponent.findOne({where}, options); if (saleComponent) { await models.SaleComponent.updateAll(where, { @@ -92,6 +98,22 @@ module.exports = Self => { query = `CALL vn.manaSpellersRequery(?)`; await Self.rawSql(query, [userId], options); + const salesPerson = sale.ticket().client().salesPersonUser(); + if (salesPerson) { + const origin = ctx.req.headers.origin; + const message = $t('Changed sale price', { + ticketId: sale.ticket().id, + itemId: sale.itemFk, + concept: sale.concept, + quantity: sale.quantity, + oldPrice: oldPrice, + newPrice: newPrice, + ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`, + itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` + }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); + } + await tx.commit(); return sale; diff --git a/modules/ticket/back/methods/sale/updateQuantity.js b/modules/ticket/back/methods/sale/updateQuantity.js index 00df49b9f7..62e09f1f58 100644 --- a/modules/ticket/back/methods/sale/updateQuantity.js +++ b/modules/ticket/back/methods/sale/updateQuantity.js @@ -26,7 +26,8 @@ module.exports = Self => { } }); - Self.updateQuantity = async(ctx, id, quantity) => { + Self.updateQuantity = async(ctx, id, newQuantity) => { + const $t = ctx.req.__; // $translate const models = Self.app.models; const canEditSale = await models.Sale.canEdit(ctx, [id]); @@ -34,13 +35,51 @@ module.exports = Self => { if (!canEditSale) throw new UserError(`Sale(s) blocked, please contact production`); - if (isNaN(quantity)) + if (isNaN(newQuantity)) throw new UserError(`The value should be a number`); - let currentLine = await models.Sale.findOne({where: {id: id}}); - if (quantity > currentLine.quantity) + const filter = { + include: { + relation: 'ticket', + scope: { + include: { + relation: 'client', + scope: { + include: { + relation: 'salesPersonUser', + scope: { + fields: ['id', 'name'] + } + } + } + } + } + } + }; + + const sale = await models.Sale.findById(id, filter); + + if (newQuantity > sale.quantity) throw new UserError('The new quantity should be smaller than the old one'); - return await currentLine.updateAttributes({quantity: quantity}); + const oldQuantity = sale.quantity; + const result = await sale.updateAttributes({quantity: newQuantity}); + + const salesPerson = sale.ticket().client().salesPersonUser(); + if (salesPerson) { + const origin = ctx.req.headers.origin; + const message = $t('Changed sale quantity', { + ticketId: sale.ticket().id, + itemId: sale.itemFk, + concept: sale.concept, + oldQuantity: oldQuantity, + newQuantity: newQuantity, + ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`, + itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` + }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); + } + + return result; }; }; diff --git a/modules/ticket/back/methods/ticket/addSale.js b/modules/ticket/back/methods/ticket/addSale.js index 365355df2c..dc45e5de9c 100644 --- a/modules/ticket/back/methods/ticket/addSale.js +++ b/modules/ticket/back/methods/ticket/addSale.js @@ -33,6 +33,7 @@ module.exports = Self => { }); Self.addSale = async(ctx, id, itemId, quantity) => { + const $t = ctx.req.__; // $translate const models = Self.app.models; const isEditable = await models.Ticket.isEditable(ctx, id); @@ -40,7 +41,19 @@ module.exports = Self => { throw new UserError(`The sales of this ticket can't be modified`); const item = await models.Item.findById(itemId); - const ticket = await models.Ticket.findById(id); + const ticket = await models.Ticket.findById(id, { + include: { + relation: 'client', + scope: { + include: { + relation: 'salesPersonUser', + scope: { + fields: ['id', 'name'] + } + } + } + } + }); const res = await models.Item.getVisibleAvailable(itemId, ticket.warehouseFk, ticket.shipped); @@ -63,6 +76,20 @@ module.exports = Self => { } }); + const addition = `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`; + + const salesPerson = ticket.client().salesPersonUser(); + if (salesPerson) { + const origin = ctx.req.headers.origin; + + const message = $t('Added sale to ticket', { + ticketId: id, + ticketUrl: `${origin}/#!/ticket/${id}/sale`, + addition: addition + }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); + } + return sale; }; }; diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index b66179eb8e..9ebd51bf42 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -230,7 +230,7 @@ module.exports = Self => { ticketUrl: `${origin}/#!/ticket/${args.id}/sale`, changes: changesMade }); - await models.Chat.sendCheckingPresence(ctx, salesPersonId, message, myOptions); + await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); } if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index 1bebaae8dc..7494f8ecee 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -17,7 +17,8 @@ module.exports = Self => { description: 'The sales id', type: ['number'], required: true, - }, { + }, + { arg: 'newDiscount', description: 'The new discount', type: 'number', @@ -35,6 +36,7 @@ module.exports = Self => { }); Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => { + const $t = ctx.req.__; // $translate const models = Self.app.models; const tx = await Self.beginTransaction({}); @@ -59,7 +61,7 @@ module.exports = Self => { } }; - let sales = await models.Sale.find(filter, options); + const sales = await models.Sale.find(filter, options); if (sales.length === 0) throw new UserError('Please select at least one sale'); @@ -94,8 +96,11 @@ module.exports = Self => { where: {code: componentCode}}, options); const componentId = discountComponent.id; - let promises = []; + const promises = []; + let changesMade = ''; + for (let sale of sales) { + const oldDiscount = sale.discount; const value = ((-sale.price * newDiscount) / 100); const newComponent = models.SaleComponent.upsert({ saleFk: sale.id, @@ -105,6 +110,7 @@ module.exports = Self => { const updatedSale = sale.updateAttribute('discount', newDiscount, options); promises.push(newComponent, updatedSale); + changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${oldDiscount}% ➔ *${newDiscount}%*`; } await Promise.all(promises); @@ -112,6 +118,32 @@ module.exports = Self => { const query = `call vn.manaSpellersRequery(?)`; await Self.rawSql(query, [userId], options); + const ticket = await models.Ticket.findById(id, { + include: { + relation: 'client', + scope: { + include: { + relation: 'salesPersonUser', + scope: { + fields: ['id', 'name'] + } + } + } + } + }); + + const salesPerson = ticket.client().salesPersonUser(); + if (salesPerson) { + const origin = ctx.req.headers.origin; + + const message = $t('Changed sale discount', { + ticketId: id, + ticketUrl: `${origin}/#!/ticket/${id}/sale`, + changes: changesMade + }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); + } + await tx.commit(); } catch (error) { await tx.rollback();