From 7030be3efa7f0df9dd0fc9f0680a7147f17820ba Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Jul 2023 09:30:04 +0200 Subject: [PATCH 01/15] fix --- front/core/services/token.js | 1 - modules/worker/back/models/worker.json | 8 -------- modules/worker/front/calendar/index.html | 2 +- modules/worker/front/card/index.js | 11 ++++++----- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/front/core/services/token.js b/front/core/services/token.js index c4b644a89..c16cc3c4f 100644 --- a/front/core/services/token.js +++ b/front/core/services/token.js @@ -83,7 +83,6 @@ export default class Token { this.renewPeriod = data.renewPeriod; this.stopRenewer(); this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000); - this.checkValidity(); }); } diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json index 978d613e9..6d23c1b66 100644 --- a/modules/worker/back/models/worker.json +++ b/modules/worker/back/models/worker.json @@ -43,9 +43,6 @@ "SSN": { "type" : "string" }, - "labelerFk": { - "type" : "number" - }, "mobileExtension": { "type" : "number" }, @@ -86,11 +83,6 @@ "type": "hasMany", "model": "WorkerTeamCollegues", "foreignKey": "workerFk" - }, - "sector": { - "type": "belongsTo", - "model": "Sector", - "foreignKey": "sectorFk" } } } diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index ace2f4e27..877add57b 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -31,7 +31,7 @@
-
{{'Contract' | translate}} #{{$ctrl.card.worker.hasWorkCenter}}
+
{{'Contract' | translate}} #{{$ctrl.businessId}}
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}} {{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}} diff --git a/modules/worker/front/card/index.js b/modules/worker/front/card/index.js index e1eb97327..35f331764 100644 --- a/modules/worker/front/card/index.js +++ b/modules/worker/front/card/index.js @@ -33,11 +33,12 @@ class Controller extends ModuleCard { }; this.$http.get(`Workers/${this.$params.id}`, {filter}) - .then(res => this.worker = res.data); - this.$http.get(`Workers/${this.$params.id}/activeContract`) - .then(res => { - if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk; - }); + .then(res => this.worker = res.data) + .then(() => + this.$http.get(`Workers/${this.$params.id}/activeContract`) + .then(res => { + if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk; + })); } } From 6fa6c21cb1c086ce7c23e0a9a5bae7fb3c0a3af1 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Jul 2023 09:31:26 +0200 Subject: [PATCH 02/15] remove fixtures --- db/dump/fixtures.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 475a4992f..3de5f15ab 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -188,13 +188,13 @@ INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAd UPDATE `vn`.`sector` SET mainPrinterFk = 1 WHERE id = 1; -INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`) +INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`) VALUES - (1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106, NULL, NULL), - (1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107, NULL, NULL), - (1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108, 1, NULL), - (1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109, 1, NULL), - (1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110, 2, NULL); + (1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106), + (1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107), + (1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108), + (1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109), + (1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110); INSERT INTO `vn`.`parking` (`id`, `column`, `row`, `sectorFk`, `code`, `pickingOrder`) VALUES From 9f2ef5c2c0835237e621e28e4fba908bd2d6516c Mon Sep 17 00:00:00 2001 From: carlossa Date: Mon, 10 Jul 2023 07:22:23 +0200 Subject: [PATCH 03/15] change ref master --- modules/travel/front/extra-community/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index c888f97da..8132bddb1 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -163,7 +163,7 @@ {{::entry.invoiceAmount | currency: 'EUR': 2}} - {{::entry.invoiceNumber}} + {{::entry.reference}} {{::entry.stickers}} {{::entry.loadedkg}} From 1bcd3039cdf58af87eff7d0c2f8004db9be4cb95 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 11 Jul 2023 07:41:05 +0200 Subject: [PATCH 04/15] refs #5475 redesign 2FA email --- .../email/auth-code/assets/css/style.css | 5 +- .../templates/email/auth-code/auth-code.html | 68 ++++++++++++------- print/templates/email/auth-code/auth-code.js | 4 +- print/templates/email/auth-code/locale/en.yml | 3 +- print/templates/email/auth-code/locale/es.yml | 3 +- print/templates/email/auth-code/locale/fr.yml | 3 +- print/templates/email/auth-code/locale/pt.yml | 3 +- 7 files changed, 54 insertions(+), 35 deletions(-) diff --git a/print/templates/email/auth-code/assets/css/style.css b/print/templates/email/auth-code/assets/css/style.css index d3bfa2aea..7fbccdc3f 100644 --- a/print/templates/email/auth-code/assets/css/style.css +++ b/print/templates/email/auth-code/assets/css/style.css @@ -1,5 +1,6 @@ .code { border: 2px dashed #8dba25; border-radius: 3px; - text-align: center -} \ No newline at end of file + text-align: center; + font-size: 24px; +} diff --git a/print/templates/email/auth-code/auth-code.html b/print/templates/email/auth-code/auth-code.html index 0d303682a..ae9aa6478 100644 --- a/print/templates/email/auth-code/auth-code.html +++ b/print/templates/email/auth-code/auth-code.html @@ -1,23 +1,45 @@ - -
-
-

{{ $t('title') }}

-

{{ $t('description') }}

-

- {{ $t('device') }}: {{ device }} -

-

- {{$t('ip')}}: {{ ip }} -

-
-
-
-
-

{{ $t('Enter the following code to continue to your account') }}

-
- {{ code }} -
-

{{ $t('It expires in 5 minutes') }}

-
-
-
+ + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+

{{ $t('title') }}

+

{{ $t('description') }}

+

+ {{ $t('device') }}: {{ device }} +

+

+ {{$t('ip')}}: {{ ip }} +

+
+
+
+
+

{{ $t('Enter the following code to continue to your account. It expires in 5 minutes.') }}

+
+ {{ code }} +
+
+
+
+ + diff --git a/print/templates/email/auth-code/auth-code.js b/print/templates/email/auth-code/auth-code.js index 362b307d8..b16833036 100755 --- a/print/templates/email/auth-code/auth-code.js +++ b/print/templates/email/auth-code/auth-code.js @@ -1,10 +1,10 @@ const Component = require(`vn-print/core/component`); -const emailBody = new Component('email-body'); +const emailHeader = new Component('email-header'); module.exports = { name: 'auth-code', components: { - 'email-body': emailBody.build(), + 'email-header': emailHeader.build(), }, props: { code: { diff --git a/print/templates/email/auth-code/locale/en.yml b/print/templates/email/auth-code/locale/en.yml index 27b493e20..74ea87e32 100644 --- a/print/templates/email/auth-code/locale/en.yml +++ b/print/templates/email/auth-code/locale/en.yml @@ -3,5 +3,4 @@ title: Verification code description: Somebody did request a verification code for login. If you didn't request it, please ignore this email. device: 'Device' ip: 'IP' -Enter the following code to continue to your account: Enter the following code to continue to your account -It expires in 5 minutes: It expires in 5 minutes +Enter the following code to continue to your account. It expires in 5 minutes.: Enter the following code to continue to your account. It expires in 5 minutes. diff --git a/print/templates/email/auth-code/locale/es.yml b/print/templates/email/auth-code/locale/es.yml index 463ea95fa..0de99f5be 100644 --- a/print/templates/email/auth-code/locale/es.yml +++ b/print/templates/email/auth-code/locale/es.yml @@ -3,5 +3,4 @@ title: Código de verificación description: Alguien ha solicitado un código de verificación para poder iniciar sesión. Si no lo has solicitado tu, ignora este email. device: 'Dispositivo' ip: 'IP' -Enter the following code to continue to your account: Introduce el siguiente código para poder continuar con tu cuenta -It expires in 5 minutes: Expira en 5 minutos +Enter the following code to continue to your account. It expires in 5 minutes: Introduce el siguiente código para poder continuar con tu cuenta. Expira en 5 minutos. diff --git a/print/templates/email/auth-code/locale/fr.yml b/print/templates/email/auth-code/locale/fr.yml index 454bb80ae..0b71d4228 100644 --- a/print/templates/email/auth-code/locale/fr.yml +++ b/print/templates/email/auth-code/locale/fr.yml @@ -3,5 +3,4 @@ title: Code de vérification description: Quelqu'un a demandé un code de vérification pour se connecter. Si ce n'était pas toi, ignore cet email. device: 'Appareil' ip: 'IP' -Enter the following code to continue to your account: Entrez le code suivant pour continuer avec votre compte -It expires in 5 minutes: Il expire dans 5 minutes. +Enter the following code to continue to your account. It expires in 5 minutes.: Entrez le code suivant pour continuer avec votre compte. Il expire dans 5 minutes. diff --git a/print/templates/email/auth-code/locale/pt.yml b/print/templates/email/auth-code/locale/pt.yml index 7f9eb85f5..cea27dce7 100644 --- a/print/templates/email/auth-code/locale/pt.yml +++ b/print/templates/email/auth-code/locale/pt.yml @@ -3,5 +3,4 @@ title: Código de verificação description: Alguém solicitou um código de verificação para entrar. Se você não fez essa solicitação, ignore este e-mail. device: 'Dispositivo' ip: 'IP' -Enter the following code to continue to your account: Insira o seguinte código para continuar com sua conta. -It expires in 5 minutes: Expira em 5 minutos. +Enter the following code to continue to your account. It expires in 5 minutes: Insira o seguinte código para continuar com sua conta. Expira em 5 minutos. From 6978339fd3f2d3f13c7161f14c74769cd1117c77 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 11 Jul 2023 08:03:36 +0200 Subject: [PATCH 05/15] refs #5475 fix email translations --- print/templates/email/auth-code/locale/es.yml | 2 +- print/templates/email/auth-code/locale/pt.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/print/templates/email/auth-code/locale/es.yml b/print/templates/email/auth-code/locale/es.yml index 0de99f5be..92150c4ed 100644 --- a/print/templates/email/auth-code/locale/es.yml +++ b/print/templates/email/auth-code/locale/es.yml @@ -3,4 +3,4 @@ title: Código de verificación description: Alguien ha solicitado un código de verificación para poder iniciar sesión. Si no lo has solicitado tu, ignora este email. device: 'Dispositivo' ip: 'IP' -Enter the following code to continue to your account. It expires in 5 minutes: Introduce el siguiente código para poder continuar con tu cuenta. Expira en 5 minutos. +Enter the following code to continue to your account. It expires in 5 minutes.: Introduce el siguiente código para poder continuar con tu cuenta. Expira en 5 minutos. diff --git a/print/templates/email/auth-code/locale/pt.yml b/print/templates/email/auth-code/locale/pt.yml index cea27dce7..1cf5d23be 100644 --- a/print/templates/email/auth-code/locale/pt.yml +++ b/print/templates/email/auth-code/locale/pt.yml @@ -3,4 +3,4 @@ title: Código de verificação description: Alguém solicitou um código de verificação para entrar. Se você não fez essa solicitação, ignore este e-mail. device: 'Dispositivo' ip: 'IP' -Enter the following code to continue to your account. It expires in 5 minutes: Insira o seguinte código para continuar com sua conta. Expira em 5 minutos. +Enter the following code to continue to your account. It expires in 5 minutes.: Insira o seguinte código para continuar com sua conta. Expira em 5 minutos. From 93e70e699a4821e1a91b6f2adc5fa5910c6ab820 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 09:19:15 +0200 Subject: [PATCH 06/15] =?UTF-8?q?refs=20#5874=20feat:=20refactorizado=20pr?= =?UTF-8?q?oceso=20facturaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- loopback/locale/en.json | 5 +- loopback/locale/es.json | 3 +- modules/client/back/models/client.js | 6 +- modules/client/front/summary/index.html | 5 +- .../back/methods/invoiceOut/invoiceClient.js | 56 ++------- .../methods/invoiceOut/makePdfAndNotify.js | 5 +- .../back/methods/ticket/canBeInvoiced.js | 47 ++++--- .../back/methods/ticket/invoiceTickets.js | 105 ++++++++++++++++ .../ticket/back/methods/ticket/makeInvoice.js | 91 ++++++-------- .../ticket/specs/canBeInvoiced.spec.js | 76 +++++++++--- .../ticket/specs/invoiceTickets.spec.js | 115 ++++++++++++++++++ .../methods/ticket/specs/makeInvoice.spec.js | 92 +++----------- modules/ticket/back/models/ticket-methods.js | 1 + modules/ticket/front/descriptor-menu/index.js | 2 +- .../front/descriptor-menu/index.spec.js | 2 +- modules/ticket/front/index/index.js | 2 +- 16 files changed, 390 insertions(+), 223 deletions(-) create mode 100644 modules/ticket/back/methods/ticket/invoiceTickets.js create mode 100644 modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 9b1b04600..d7d31a3dc 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -177,5 +177,6 @@ "Invalid quantity": "Invalid quantity", "Failed to upload delivery note": "Error to upload delivery note {{id}}", "Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address", - "The renew period has not been exceeded": "The renew period has not been exceeded" -} \ No newline at end of file + "The renew period has not been exceeded": "The renew period has not been exceeded", + "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}" +} diff --git a/loopback/locale/es.json b/loopback/locale/es.json index ed036331f..20a9557e9 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -297,5 +297,6 @@ "Error while generating PDF": "Error al generar PDF", "Error when sending mail to client": "Error al enviar el correo al cliente", "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", - "The renew period has not been exceeded": "El periodo de renovación no ha sido superado" + "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", + "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}" } diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 51157e15d..046138725 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -14,7 +14,7 @@ module.exports = Self => { Self.validatesPresenceOf('street', { message: 'Street cannot be empty' }); - + Self.validatesPresenceOf('city', { message: 'City cannot be empty' }); @@ -89,7 +89,7 @@ module.exports = Self => { }; const country = await Self.app.models.Country.findOne(filter); const code = country ? country.code.toLowerCase() : null; - const countryCode = this.fi.toLowerCase().substring(0, 2); + const countryCode = this.fi?.toLowerCase().substring(0, 2); if (!this.fi || !validateTin(this.fi, code) || (this.isVies && countryCode == code)) err(); @@ -401,7 +401,7 @@ module.exports = Self => { Self.changeCredit = async function changeCredit(ctx, finalState, changes) { const models = Self.app.models; const userId = ctx.options.accessToken.userId; - const accessToken = {req: {accessToken: ctx.options.accessToken} }; + const accessToken = {req: {accessToken: ctx.options.accessToken}}; const canEditCredit = await models.ACL.checkAccessAcl(accessToken, 'Client', 'editCredit', 'WRITE'); if (!canEditCredit) { diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index a0d4b918a..0a15efcd1 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -64,7 +64,7 @@ - @@ -296,6 +296,9 @@ value="{{$ctrl.summary.rating}}" info="Value from 1 to 20. The higher the better value"> + + diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index ddd008942..fa22dab1e 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -1,5 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { Self.remoteMethodCtx('invoiceClient', { description: 'Make a invoice of a client', @@ -56,7 +54,6 @@ module.exports = Self => { const minShipped = Date.vnNew(); minShipped.setFullYear(args.maxShipped.getFullYear() - 1); - let invoiceId; try { const client = await models.Client.findById(args.clientId, { fields: ['id', 'hasToInvoiceByAddress'] @@ -77,56 +74,21 @@ module.exports = Self => { ], options); } - // Check negative bases - - let query = - `SELECT COUNT(*) isSpanishCompany - FROM supplier s - JOIN country c ON c.id = s.countryFk - AND c.code = 'ES' - WHERE s.id = ?`; - const [supplierCompany] = await Self.rawSql(query, [ - args.companyFk - ], options); - - const isSpanishCompany = supplierCompany?.isSpanishCompany; - - query = 'SELECT hasAnyNegativeBase() AS base'; - const [result] = await Self.rawSql(query, null, options); - - const hasAnyNegativeBase = result?.base; - if (hasAnyNegativeBase && isSpanishCompany) - throw new UserError('Negative basis'); - - // Invoicing - - query = `SELECT invoiceSerial(?, ?, ?) AS serial`; - const [invoiceSerial] = await Self.rawSql(query, [ - client.id, + const invoiceType = 'G'; + const invoiceId = await models.Ticket.makeInvoice( + ctx, + invoiceType, args.companyFk, - 'G' - ], options); - const serialLetter = invoiceSerial.serial; - - query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`; - await Self.rawSql(query, [ - serialLetter, - args.invoiceDate - ], options); - - const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, options); - if (!newInvoice) - throw new UserError('No tickets to invoice', 'notInvoiced'); - - await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], options); - invoiceId = newInvoice.id; + args.invoiceDate, + options + ); if (tx) await tx.commit(); + + return invoiceId; } catch (e) { if (tx) await tx.rollback(); throw e; } - - return invoiceId; }; }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js index a999437c0..a48664b30 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js +++ b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js @@ -14,8 +14,7 @@ module.exports = Self => { }, { arg: 'printerFk', type: 'number', - description: 'The printer to print', - required: true + description: 'The printer to print' } ], http: { @@ -51,7 +50,7 @@ module.exports = Self => { const ref = invoiceOut.ref; const client = invoiceOut.client(); - if (client.isToBeMailed) { + if (client.isToBeMailed || !printerFk) { try { ctx.args = { reference: ref, diff --git a/modules/ticket/back/methods/ticket/canBeInvoiced.js b/modules/ticket/back/methods/ticket/canBeInvoiced.js index 6b8f9e71a..0f6cb476b 100644 --- a/modules/ticket/back/methods/ticket/canBeInvoiced.js +++ b/modules/ticket/back/methods/ticket/canBeInvoiced.js @@ -1,3 +1,5 @@ +const UserError = require('vn-loopback/util/user-error'); + module.exports = function(Self) { Self.remoteMethodCtx('canBeInvoiced', { description: 'Whether the ticket can or not be invoiced', @@ -21,8 +23,9 @@ module.exports = function(Self) { } }); - Self.canBeInvoiced = async(ticketsIds, options) => { + Self.canBeInvoiced = async(ctx, ticketsIds, options) => { const myOptions = {}; + const $t = ctx.req.__; // $translate if (typeof options == 'object') Object.assign(myOptions, options); @@ -31,29 +34,43 @@ module.exports = function(Self) { where: { id: {inq: ticketsIds} }, - fields: ['id', 'refFk', 'shipped', 'totalWithVat'] + fields: ['id', 'refFk', 'shipped', 'totalWithVat', 'companyFk'] }, myOptions); + const [firstTicket] = tickets; + const companyFk = firstTicket.companyFk; - const query = ` - SELECT vn.hasSomeNegativeBase(t.id) AS hasSomeNegativeBase - FROM ticket t - WHERE id IN(?)`; - const ticketBases = await Self.rawSql(query, [ticketsIds], myOptions); - const hasSomeNegativeBase = ticketBases.some( - ticketBases => ticketBases.hasSomeNegativeBase - ); + const query = + `SELECT COUNT(*) isSpanishCompany + FROM supplier s + JOIN country c ON c.id = s.countryFk + AND c.code = 'ES' + WHERE s.id = ?`; + const [supplierCompany] = await Self.rawSql(query, [companyFk], options); + + const isSpanishCompany = supplierCompany?.isSpanishCompany; + + const [result] = await Self.rawSql('SELECT hasAnyNegativeBase() AS base', null, options); + const hasAnyNegativeBase = result?.base && isSpanishCompany; + if (hasAnyNegativeBase) + throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds})); const today = Date.vnNew(); - const invalidTickets = tickets.some(ticket => { + tickets.some(ticket => { const shipped = new Date(ticket.shipped); const shippingInFuture = shipped.getTime() > today.getTime(); - const isInvoiced = ticket.refFk; - const priceZero = ticket.totalWithVat == 0; + if (shippingInFuture) + throw new UserError(`Can't invoice to future`); - return isInvoiced || priceZero || shippingInFuture; + const isInvoiced = ticket.refFk; + if (isInvoiced) + throw new UserError(`This ticket is already invoiced`); + + const priceZero = ticket.totalWithVat == 0; + if (priceZero) + throw new UserError(`A ticket with an amount of zero can't be invoiced`); }); - return !(invalidTickets || hasSomeNegativeBase); + return true; }; }; diff --git a/modules/ticket/back/methods/ticket/invoiceTickets.js b/modules/ticket/back/methods/ticket/invoiceTickets.js new file mode 100644 index 000000000..780e9eb57 --- /dev/null +++ b/modules/ticket/back/methods/ticket/invoiceTickets.js @@ -0,0 +1,105 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = function(Self) { + Self.remoteMethodCtx('invoiceTickets', { + description: 'Make out an invoice from one or more tickets', + accessType: 'WRITE', + accepts: [ + { + arg: 'ticketsIds', + description: 'The tickets id', + type: ['number'], + required: true + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/invoiceTickets`, + verb: 'POST' + } + }); + + Self.invoiceTickets = async(ctx, ticketsIds, options) => { + const models = Self.app.models; + const date = Date.vnNew(); + date.setHours(0, 0, 0, 0); + + const myOptions = {userId: ctx.req.accessToken.userId}; + let tx; + + if (typeof options === 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + let invoicesIds = []; + try { + const tickets = await models.Ticket.find({ + where: { + id: {inq: ticketsIds} + }, + fields: ['id', 'clientFk', 'companyFk'] + }, myOptions); + + const [firstTicket] = tickets; + const clientId = firstTicket.clientFk; + const companyId = firstTicket.companyFk; + + const isSameClient = tickets.every(ticket => ticket.clientFk === clientId); + if (!isSameClient) + throw new UserError(`You can't invoice tickets from multiple clients`); + + const client = await models.Client.findById(clientId, { + fields: ['id', 'hasToInvoiceByAddress'] + }, myOptions); + + if (client.hasToInvoiceByAddress) { + const query = ` + SELECT DISTINCT addressFk + FROM ticket t + WHERE id IN (?)`; + const result = await Self.rawSql(query, [ticketsIds], myOptions); + + const addressIds = result.map(address => address.addressFk); + for (const address of addressIds) + await createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions); + } else + await createInvoice(ctx, companyId, ticketsIds, null, invoicesIds, myOptions); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + + for (const invoiceId of invoicesIds) + await models.InvoiceOut.makePdfAndNotify(ctx, invoiceId, null); + + return invoicesIds; + }; + + async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions) { + const models = Self.app.models; + + await models.Ticket.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; + CREATE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + ${address ? `AND addressFk = ${address}` : ''} + `, [ticketsIds], myOptions); + + const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), myOptions); + invoicesIds.push(invoiceId); + } +}; + diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index ee82b874f..22fe7b3f5 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -6,15 +6,26 @@ module.exports = function(Self) { accessType: 'WRITE', accepts: [ { - arg: 'ticketsIds', - description: 'The tickets id', - type: ['number'], + arg: 'invoiceType', + description: 'The invoice type', + type: 'string', + required: true + }, + { + arg: 'companyFk', + description: 'The company id', + type: 'string', + required: true + }, + { + arg: 'invoiceDate', + description: 'The invoice date', + type: 'date', required: true } ], returns: { - arg: 'data', - type: 'boolean', + type: ['object'], root: true }, http: { @@ -23,10 +34,9 @@ module.exports = function(Self) { } }); - Self.makeInvoice = async(ctx, ticketsIds, options) => { + Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, options) => { const models = Self.app.models; - const date = Date.vnNew(); - date.setHours(0, 0, 0, 0); + invoiceDate.setHours(0, 0, 0, 0); const myOptions = {userId: ctx.req.accessToken.userId}; let tx; @@ -40,81 +50,50 @@ module.exports = function(Self) { } let serial; - let invoiceId; - let invoiceOut; try { + const ticketToInvoice = await Self.rawSql(` + SELECT id + FROM tmp.ticketToInvoice`, null, myOptions); + + const ticketsIds = ticketToInvoice.map(ticket => ticket.id); const tickets = await models.Ticket.find({ where: { id: {inq: ticketsIds} }, - fields: ['id', 'clientFk', 'companyFk'] + fields: ['id', 'clientFk'] }, myOptions); + await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions); + const [firstTicket] = tickets; const clientId = firstTicket.clientFk; - const companyId = firstTicket.companyFk; - - const isSameClient = tickets.every(ticket => ticket.clientFk == clientId); - if (!isSameClient) - throw new UserError(`You can't invoice tickets from multiple clients`); - const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, myOptions); if (!clientCanBeInvoiced) throw new UserError(`This client can't be invoiced`); - const ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ticketsIds, myOptions); - if (!ticketCanBeInvoiced) - throw new UserError(`Some of the selected tickets are not billable`); - const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`; const [result] = await Self.rawSql(query, [ clientId, - companyId, - 'R' + companyFk, + invoiceType, ], myOptions); serial = result.serial; - await Self.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; - CREATE TEMPORARY TABLE tmp.ticketToInvoice - (PRIMARY KEY (id)) - ENGINE = MEMORY - SELECT id FROM vn.ticket - WHERE id IN(?) AND refFk IS NULL - `, [ticketsIds], myOptions); - - await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, date], myOptions); + await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions); const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions); + if (!resultInvoice) + throw new UserError('No tickets to invoice', 'notInvoiced'); - invoiceId = resultInvoice.id; + if (serial != 'R' && resultInvoice.id) + await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions); - if (serial != 'R' && invoiceId) - await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], myOptions); - - invoiceOut = await models.InvoiceOut.findById(invoiceId, { - include: { - relation: 'client' - } - }, myOptions); if (tx) await tx.commit(); + + return resultInvoice.id; } catch (e) { if (tx) await tx.rollback(); throw e; } - - if (serial != 'R' && invoiceId) - await models.InvoiceOut.createPdf(ctx, invoiceId); - - if (invoiceId) { - ctx.args = { - reference: invoiceOut.ref, - recipientId: invoiceOut.clientFk, - recipient: invoiceOut.client().email - }; - await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); - } - - return {invoiceFk: invoiceId, serial: serial}; }; }; diff --git a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js index 806f80227..42f9b5a9e 100644 --- a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js +++ b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js @@ -1,61 +1,83 @@ const models = require('vn-loopback/server/server').models; -const LoopBackContext = require('loopback-context'); describe('ticket canBeInvoiced()', () => { const userId = 19; const ticketId = 11; - const activeCtx = { - accessToken: {userId: userId} + const ctx = {req: {accessToken: {userId: userId}}}; + ctx.req.__ = value => { + return value; }; - beforeAll(async() => { - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - }); - it('should return falsy for an already invoiced ticket', async() => { const tx = await models.Ticket.beginTransaction({}); + let error; try { const options = {transaction: tx}; const ticket = await models.Ticket.findById(ticketId, null, options); await ticket.updateAttribute('refFk', 'T1111111', options); - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; + CREATE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(false); await tx.rollback(); } catch (e) { + error = e; await tx.rollback(); - throw e; } + + expect(error.message).toEqual(`This ticket is already invoiced`); }); it('should return falsy for a ticket with a price of zero', async() => { const tx = await models.Ticket.beginTransaction({}); + let error; try { const options = {transaction: tx}; const ticket = await models.Ticket.findById(ticketId, null, options); await ticket.updateAttribute('totalWithVat', 0, options); - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; + CREATE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(false); await tx.rollback(); } catch (e) { + error = e; await tx.rollback(); - throw e; } + + expect(error.message).toEqual(`A ticket with an amount of zero can't be invoiced`); }); it('should return falsy for a ticket shipping in future', async() => { const tx = await models.Ticket.beginTransaction({}); + + let error; try { const options = {transaction: tx}; @@ -66,15 +88,27 @@ describe('ticket canBeInvoiced()', () => { await ticket.updateAttribute('shipped', shipped, options); - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; + CREATE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(false); await tx.rollback(); } catch (e) { + error = e; await tx.rollback(); - throw e; } + + expect(error.message).toEqual(`Can't invoice to future`); }); it('should return truthy for an invoiceable ticket', async() => { @@ -83,7 +117,17 @@ describe('ticket canBeInvoiced()', () => { try { const options = {transaction: tx}; - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; + CREATE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(true); diff --git a/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js b/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js new file mode 100644 index 000000000..8971fb24a --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js @@ -0,0 +1,115 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('ticket invoiceTickets()', () => { + const userId = 19; + const clientId = 1102; + const activeCtx = { + getLocale: () => { + return 'en'; + }, + accessToken: {userId: userId}, + headers: {origin: 'http://localhost:5000'}, + }; + const ctx = {req: activeCtx}; + + beforeAll(async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should throw an error when invoicing tickets from multiple clients', async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + let error; + + try { + const options = {transaction: tx}; + + const ticketsIds = [11, 16]; + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`You can't invoice tickets from multiple clients`); + }); + + it(`should throw an error when invoicing a client without tax data checked`, async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + let error; + + try { + const options = {transaction: tx}; + + const client = await models.Client.findById(clientId, null, options); + await client.updateAttribute('isTaxDataChecked', false, options); + + const ticketsIds = [11]; + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This client can't be invoiced`); + }); + + it('should invoice a ticket, then try again to fail', async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + let error; + + try { + const options = {transaction: tx}; + + const ticketsIds = [11]; + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This ticket is already invoiced`); + }); + + it('should success to invoice a ticket', async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ticketsIds = [11]; + const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + expect(invoicesIds.length).toBeGreaterThan(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js index 270ba5c93..9af6ad557 100644 --- a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js +++ b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js @@ -3,8 +3,9 @@ const LoopBackContext = require('loopback-context'); describe('ticket makeInvoice()', () => { const userId = 19; - const ticketId = 11; - const clientId = 1102; + const invoiceType = 'R'; + const companyFk = 442; + const invoiceDate = Date.vnNew(); const activeCtx = { getLocale: () => { return 'en'; @@ -20,77 +21,6 @@ describe('ticket makeInvoice()', () => { }); }); - it('should throw an error when invoicing tickets from multiple clients', async() => { - const invoiceOutModel = models.InvoiceOut; - spyOn(invoiceOutModel, 'createPdf'); - - const tx = await models.Ticket.beginTransaction({}); - - let error; - - try { - const options = {transaction: tx}; - const otherClientTicketId = 16; - await models.Ticket.makeInvoice(ctx, [ticketId, otherClientTicketId], options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error.message).toEqual(`You can't invoice tickets from multiple clients`); - }); - - it(`should throw an error when invoicing a client without tax data checked`, async() => { - const invoiceOutModel = models.InvoiceOut; - spyOn(invoiceOutModel, 'createPdf'); - - const tx = await models.Ticket.beginTransaction({}); - - let error; - - try { - const options = {transaction: tx}; - - const client = await models.Client.findById(clientId, null, options); - await client.updateAttribute('isTaxDataChecked', false, options); - - await models.Ticket.makeInvoice(ctx, [ticketId], options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error.message).toEqual(`This client can't be invoiced`); - }); - - it('should invoice a ticket, then try again to fail', async() => { - const invoiceOutModel = models.InvoiceOut; - spyOn(invoiceOutModel, 'createPdf'); - spyOn(invoiceOutModel, 'invoiceEmail'); - - const tx = await models.Ticket.beginTransaction({}); - - let error; - - try { - const options = {transaction: tx}; - - await models.Ticket.makeInvoice(ctx, [ticketId], options); - await models.Ticket.makeInvoice(ctx, [ticketId], options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error.message).toEqual(`Some of the selected tickets are not billable`); - }); - it('should success to invoice a ticket', async() => { const invoiceOutModel = models.InvoiceOut; spyOn(invoiceOutModel, 'createPdf'); @@ -101,10 +31,20 @@ describe('ticket makeInvoice()', () => { try { const options = {transaction: tx}; - const invoice = await models.Ticket.makeInvoice(ctx, [ticketId], options); + const ticketsIds = [11, 16]; + await models.Ticket.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; + CREATE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketsIds], options); - expect(invoice.invoiceFk).toBeDefined(); - expect(invoice.serial).toEqual('T'); + const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options); + + expect(invoiceId).toBeDefined(); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index e5a8e8d94..f0c85ecc4 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -39,4 +39,5 @@ module.exports = function(Self) { require('../methods/ticket/collectionLabel')(Self); require('../methods/ticket/expeditionPalletLabel')(Self); require('../methods/ticket/saveSign')(Self); + require('../methods/ticket/invoiceTickets')(Self); }; diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 17a34ad59..dd518671b 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -275,7 +275,7 @@ class Controller extends Section { }); } - return this.$http.post(`Tickets/makeInvoice`, params) + return this.$http.post(`Tickets/invoiceTickets`, params) .then(() => this.reload()) .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); } diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 0aef956db..fbe67d21a 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -191,7 +191,7 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { jest.spyOn(controller.vnApp, 'showSuccess'); const expectedParams = {ticketsIds: [ticket.id]}; - $httpBackend.expectPOST(`Tickets/makeInvoice`, expectedParams).respond(); + $httpBackend.expectPOST(`Tickets/invoiceTickets`, expectedParams).respond(); controller.makeInvoice(); $httpBackend.flush(); diff --git a/modules/ticket/front/index/index.js b/modules/ticket/front/index/index.js index 55229eabb..b3afc838c 100644 --- a/modules/ticket/front/index/index.js +++ b/modules/ticket/front/index/index.js @@ -163,7 +163,7 @@ export default class Controller extends Section { makeInvoice() { const ticketsIds = this.checked.map(ticket => ticket.id); - return this.$http.post(`Tickets/makeInvoice`, {ticketsIds}) + return this.$http.post(`Tickets/invoiceTickets`, {ticketsIds}) .then(() => this.$.model.refresh()) .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); } From 56247b50127df6d878d61bf081a2ef1b709c1019 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 09:33:45 +0200 Subject: [PATCH 07/15] add acl --- db/changes/232601/00-aclInvoiceTickets.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 db/changes/232601/00-aclInvoiceTickets.sql diff --git a/db/changes/232601/00-aclInvoiceTickets.sql b/db/changes/232601/00-aclInvoiceTickets.sql new file mode 100644 index 000000000..2c221950e --- /dev/null +++ b/db/changes/232601/00-aclInvoiceTickets.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES('Ticket', 'invoiceTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee'); From da13f0811cf793b115bdd7bc3180162592391f79 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 09:59:11 +0200 Subject: [PATCH 08/15] refs 5887 cambiado drop y create por create or replace --- modules/client/front/credit-management/index.html | 2 +- modules/client/front/summary/index.html | 4 ++-- modules/ticket/back/methods/ticket/invoiceTickets.js | 3 +-- .../back/methods/ticket/specs/canBeInvoiced.spec.js | 12 ++++-------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/modules/client/front/credit-management/index.html b/modules/client/front/credit-management/index.html index b9064ff69..f66b988f5 100644 --- a/modules/client/front/credit-management/index.html +++ b/modules/client/front/credit-management/index.html @@ -68,7 +68,7 @@ {{::clientInforma.rating}} - {{::clientInforma.recommendedCredit | currency: 'EUR': 2}} + {{::clientInforma.recommendedCredit | currency: 'EUR'}} diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index 0a15efcd1..15a55ec8c 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -270,7 +270,7 @@ info="Invoices minus payments plus orders not yet invoiced"> @@ -297,7 +297,7 @@ info="Value from 1 to 20. The higher the better value"> + value="{{$ctrl.summary.recommendedCredit | currency: 'EUR'}}"> diff --git a/modules/ticket/back/methods/ticket/invoiceTickets.js b/modules/ticket/back/methods/ticket/invoiceTickets.js index 780e9eb57..ca1bf15fb 100644 --- a/modules/ticket/back/methods/ticket/invoiceTickets.js +++ b/modules/ticket/back/methods/ticket/invoiceTickets.js @@ -88,8 +88,7 @@ module.exports = function(Self) { const models = Self.app.models; await models.Ticket.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; - CREATE TEMPORARY TABLE tmp.ticketToInvoice + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice (PRIMARY KEY (id)) ENGINE = MEMORY SELECT id diff --git a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js index 42f9b5a9e..538dbc49f 100644 --- a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js +++ b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js @@ -19,8 +19,7 @@ describe('ticket canBeInvoiced()', () => { await ticket.updateAttribute('refFk', 'T1111111', options); await models.Ticket.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; - CREATE TEMPORARY TABLE tmp.ticketToInvoice + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice (PRIMARY KEY (id)) ENGINE = MEMORY SELECT id @@ -52,8 +51,7 @@ describe('ticket canBeInvoiced()', () => { await ticket.updateAttribute('totalWithVat', 0, options); await models.Ticket.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; - CREATE TEMPORARY TABLE tmp.ticketToInvoice + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice (PRIMARY KEY (id)) ENGINE = MEMORY SELECT id @@ -89,8 +87,7 @@ describe('ticket canBeInvoiced()', () => { await ticket.updateAttribute('shipped', shipped, options); await models.Ticket.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; - CREATE TEMPORARY TABLE tmp.ticketToInvoice + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice (PRIMARY KEY (id)) ENGINE = MEMORY SELECT id @@ -118,8 +115,7 @@ describe('ticket canBeInvoiced()', () => { const options = {transaction: tx}; await models.Ticket.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; - CREATE TEMPORARY TABLE tmp.ticketToInvoice + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice (PRIMARY KEY (id)) ENGINE = MEMORY SELECT id From d4a4e04f2590d7da33da070f1476a19484e20e22 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 12:33:34 +0200 Subject: [PATCH 09/15] =?UTF-8?q?refs=20#5887=20feat:=20cambiados=20permis?= =?UTF-8?q?os=20para=20a=C3=B1adir/quitar=20alias=20de=20correo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/methods/vn-user/addAlias.js | 68 ++++++++++++++++++++++++++++ back/methods/vn-user/removeAlias.js | 55 ++++++++++++++++++++++ db/changes/232601/00-aclAddAlias.sql | 11 +++++ 3 files changed, 134 insertions(+) create mode 100644 back/methods/vn-user/addAlias.js create mode 100644 back/methods/vn-user/removeAlias.js create mode 100644 db/changes/232601/00-aclAddAlias.sql diff --git a/back/methods/vn-user/addAlias.js b/back/methods/vn-user/addAlias.js new file mode 100644 index 000000000..a9a5dcb85 --- /dev/null +++ b/back/methods/vn-user/addAlias.js @@ -0,0 +1,68 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('addAlias', { + description: 'Add alias if user has grant', + accessType: 'WRITE', + accepts: [ + { + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + }, + { + arg: 'id', + type: 'number', + required: true, + description: 'The user id', + http: {source: 'path'} + }, + { + arg: 'mailAlias', + type: 'number', + description: 'The new alias for user', + required: true + } + ], + http: { + path: `/:id/addAlias`, + verb: 'POST' + } + }); + + Self.addAlias = async function(ctx, id, mailAlias, options) { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions); + + if (!user.hasGrant) + throw new UserError(`You don't have grant privilege`); + + const account = await models.Account.findById(userId, { + fields: ['id'], + include: { + relation: 'aliases', + scope: { + fields: ['mailAlias'] + } + } + }, myOptions); + + const aliases = account.aliases().map(alias => alias.mailAlias); + + const hasAlias = aliases.includes(mailAlias); + if (!hasAlias) + throw new UserError(`You don't have the alias assigned and you can't assign it to another user`); + + return models.MailAliasAccount.create({ + mailAlias: mailAlias, + account: id + }, myOptions); + }; +}; diff --git a/back/methods/vn-user/removeAlias.js b/back/methods/vn-user/removeAlias.js new file mode 100644 index 000000000..4c402cc54 --- /dev/null +++ b/back/methods/vn-user/removeAlias.js @@ -0,0 +1,55 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('removeAlias', { + description: 'Add alias if user has grant', + accessType: 'WRITE', + accepts: [ + { + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + }, + { + arg: 'id', + type: 'number', + required: true, + description: 'The user id', + http: {source: 'path'} + }, + { + arg: 'mailAlias', + type: 'number', + description: 'The alias to delete', + required: true + } + ], + http: { + path: `/:id/removeAlias`, + verb: 'POST' + } + }); + + Self.removeAlias = async function(ctx, id, mailAlias, options) { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const canRemoveAlias = await models.ACL.checkAccessAcl(ctx, 'VnUser', 'canRemoveAlias', 'WRITE'); + + if (userId != id && !canRemoveAlias) throw new UserError(`You don't have grant privilege`); + + const mailAliasAccount = await models.MailAliasAccount.findOne({ + where: { + mailAlias: mailAlias, + account: id + } + }, myOptions); + + await mailAliasAccount.destroy(myOptions); + }; +}; diff --git a/db/changes/232601/00-aclAddAlias.sql b/db/changes/232601/00-aclAddAlias.sql new file mode 100644 index 000000000..cc96f5ad8 --- /dev/null +++ b/db/changes/232601/00-aclAddAlias.sql @@ -0,0 +1,11 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', 'addAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', 'removeAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', 'canRemoveAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement'); From e1f12bea020fc367cd8002241f5e950680770fe4 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 12:33:44 +0200 Subject: [PATCH 10/15] a --- back/models/vn-user.js | 2 ++ loopback/locale/es.json | 5 +++-- modules/account/front/aliases/index.html | 8 ++------ modules/account/front/aliases/index.js | 16 +++++++++------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/back/models/vn-user.js b/back/models/vn-user.js index b58395acc..12aab585c 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -11,6 +11,8 @@ module.exports = function(Self) { require('../methods/vn-user/validate-token')(Self); require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/renew-token')(Self); + require('../methods/vn-user/addAlias')(Self); + require('../methods/vn-user/removeAlias')(Self); Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 20a9557e9..63b20995d 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -296,7 +296,8 @@ "Fecha fuera de rango": "Fecha fuera de rango", "Error while generating PDF": "Error al generar PDF", "Error when sending mail to client": "Error al enviar el correo al cliente", - "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", + "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", - "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}" + "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", + "You don't have the alias assigned and you can't assign it to another user": "No tienes el alias asignado y no puedes asignarlo a otro usuario" } diff --git a/modules/account/front/aliases/index.html b/modules/account/front/aliases/index.html index 11d546afb..4a73ec873 100644 --- a/modules/account/front/aliases/index.html +++ b/modules/account/front/aliases/index.html @@ -17,9 +17,7 @@ + ng-click="removeConfirm.show(row)"> @@ -32,9 +30,7 @@ translate-attr="{title: 'Add'}" vn-bind="+" ng-click="$ctrl.onAddClick()" - fixed-bottom-right - vn-acl="itManagement" - vn-acl-action="remove"> + fixed-bottom-right> this.refresh()) .then(() => this.vnApp.showSuccess( this.$t('Subscribed to alias!')) @@ -34,11 +33,14 @@ export default class Controller extends Section { } onRemove(row) { - return this.$http.delete(`MailAliasAccounts/${row.id}`) - .then(() => { - this.$.data.splice(this.$.data.indexOf(row), 1); - this.vnApp.showSuccess(this.$t('Unsubscribed from alias!')); - }); + const params = { + mailAlias: row.mailAlias + }; + return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params) + .then(() => this.refresh()) + .then(() => this.vnApp.showSuccess( + this.$t('Subscribed to alias!')) + ); } } From 5a5a1ed20bebc4bee8be651903980fe5f4e3c778 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 13:22:54 +0200 Subject: [PATCH 11/15] refs #5887 feat: addback test --- back/methods/vn-user/specs/addAlias.spec.js | 68 +++++++++++++++++++++ modules/account/front/aliases/index.js | 4 +- modules/account/front/aliases/index.spec.js | 9 ++- 3 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 back/methods/vn-user/specs/addAlias.spec.js diff --git a/back/methods/vn-user/specs/addAlias.spec.js b/back/methods/vn-user/specs/addAlias.spec.js new file mode 100644 index 000000000..239d09c94 --- /dev/null +++ b/back/methods/vn-user/specs/addAlias.spec.js @@ -0,0 +1,68 @@ +const {models} = require('vn-loopback/server/server'); + +describe('VnUser addAlias()', () => { + const employeeId = 1; + const sysadminId = 66; + const developerId = 9; + const customerId = 2; + const mailAlias = 1; + it('should throw an error when user not has privileges', async() => { + const ctx = {req: {accessToken: {userId: employeeId}}}; + const tx = await models.VnUser.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + + await models.VnUser.addAlias(ctx, employeeId, mailAlias, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toContain(`You don't have grant privilege`); + }); + + it('should throw an error when user has privileges but not has the role from user', async() => { + const ctx = {req: {accessToken: {userId: sysadminId}}}; + const tx = await models.VnUser.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + + await models.VnUser.addAlias(ctx, employeeId, mailAlias, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toContain(`You don't have the alias assigned and you can't assign it to another user`); + }); + + it('should add an alias', async() => { + const ctx = {req: {accessToken: {userId: developerId}}}; + const tx = await models.VnUser.beginTransaction({}); + + let result; + try { + const options = {transaction: tx}; + + const user = await models.VnUser.findById(developerId, null, options); + await user.updateAttribute('hasGrant', true, options); + + result = await models.VnUser.addAlias(ctx, customerId, mailAlias, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + + expect(result.mailAlias).toBe(mailAlias); + expect(result.account).toBe(customerId); + }); +}); diff --git a/modules/account/front/aliases/index.js b/modules/account/front/aliases/index.js index 91d0c6e7c..e0c738ee4 100644 --- a/modules/account/front/aliases/index.js +++ b/modules/account/front/aliases/index.js @@ -38,9 +38,7 @@ export default class Controller extends Section { }; return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params) .then(() => this.refresh()) - .then(() => this.vnApp.showSuccess( - this.$t('Subscribed to alias!')) - ); + .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); } } diff --git a/modules/account/front/aliases/index.spec.js b/modules/account/front/aliases/index.spec.js index 466f1e1e9..61f71949c 100644 --- a/modules/account/front/aliases/index.spec.js +++ b/modules/account/front/aliases/index.spec.js @@ -25,8 +25,9 @@ describe('component vnUserAliases', () => { describe('onAddSave()', () => { it('should add the new row', () => { controller.addData = {account: 1}; + controller.$params = {id: 1}; - $httpBackend.expectPOST('MailAliasAccounts').respond(); + $httpBackend.expectPOST('VnUsers/1/addAlias').respond(); $httpBackend.expectGET('MailAliasAccounts').respond('foo'); controller.onAddSave(); $httpBackend.flush(); @@ -41,12 +42,14 @@ describe('component vnUserAliases', () => { {id: 1, alias: 'foo'}, {id: 2, alias: 'bar'} ]; + controller.$params = {id: 1}; - $httpBackend.expectDELETE('MailAliasAccounts/1').respond(); + $httpBackend.expectPOST('VnUsers/1/removeAlias').respond(); + $httpBackend.expectGET('MailAliasAccounts').respond(controller.$.data[1]); controller.onRemove(controller.$.data[0]); $httpBackend.flush(); - expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]); + expect(controller.$.data).toEqual({id: 2, alias: 'bar'}); expect(controller.vnApp.showSuccess).toHaveBeenCalled(); }); }); From d4217ff8d3c812fc4f42ee04c96aef494843b052 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 13:38:48 +0200 Subject: [PATCH 12/15] refs #5887 correct translations --- back/methods/vn-user/addAlias.js | 4 ++-- loopback/locale/es.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/back/methods/vn-user/addAlias.js b/back/methods/vn-user/addAlias.js index a9a5dcb85..9fe43e713 100644 --- a/back/methods/vn-user/addAlias.js +++ b/back/methods/vn-user/addAlias.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethod('addAlias', { - description: 'Add alias if user has grant', + description: 'Add an alias if the user has the grant', accessType: 'WRITE', accepts: [ { @@ -58,7 +58,7 @@ module.exports = Self => { const hasAlias = aliases.includes(mailAlias); if (!hasAlias) - throw new UserError(`You don't have the alias assigned and you can't assign it to another user`); + throw new UserError(`You cannot assign an alias that you are not assigned to`); return models.MailAliasAccount.create({ mailAlias: mailAlias, diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 63b20995d..809ed5874 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -299,5 +299,5 @@ "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", - "You don't have the alias assigned and you can't assign it to another user": "No tienes el alias asignado y no puedes asignarlo a otro usuario" + "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado" } From a215797390f0b2601a51b4e5a21dd30c456ce70f Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 13:39:22 +0200 Subject: [PATCH 13/15] tranalstion --- back/methods/vn-user/removeAlias.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/vn-user/removeAlias.js b/back/methods/vn-user/removeAlias.js index 4c402cc54..0424c3e96 100644 --- a/back/methods/vn-user/removeAlias.js +++ b/back/methods/vn-user/removeAlias.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethod('removeAlias', { - description: 'Add alias if user has grant', + description: 'Remove alias if the user has the grant', accessType: 'WRITE', accepts: [ { From 808a9976abcb5ef86115902641a1c8d6337189c7 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 13:53:25 +0200 Subject: [PATCH 14/15] refs #5887 fix: back etst --- back/methods/vn-user/specs/addAlias.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/vn-user/specs/addAlias.spec.js b/back/methods/vn-user/specs/addAlias.spec.js index 239d09c94..ef657a3a8 100644 --- a/back/methods/vn-user/specs/addAlias.spec.js +++ b/back/methods/vn-user/specs/addAlias.spec.js @@ -41,7 +41,7 @@ describe('VnUser addAlias()', () => { await tx.rollback(); } - expect(error.message).toContain(`You don't have the alias assigned and you can't assign it to another user`); + expect(error.message).toContain(`You cannot assign an alias that you are not assigned to`); }); it('should add an alias', async() => { From d7ac20dc6a7df1fe92db107a463e399baf812cd7 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 12 Jul 2023 13:02:27 +0200 Subject: [PATCH 15/15] hotFix(supplier): fix accessToken in before save --- modules/supplier/back/models/supplier.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/supplier/back/models/supplier.js b/modules/supplier/back/models/supplier.js index 9c78e8590..e88fadc0b 100644 --- a/modules/supplier/back/models/supplier.js +++ b/modules/supplier/back/models/supplier.js @@ -103,7 +103,7 @@ module.exports = Self => { const changes = ctx.data || ctx.instance; const orgData = ctx.currentInstance; const loopBackContext = LoopBackContext.getCurrentContext(); - const accessToken = {req: loopBackContext.active.accessToken}; + const accessToken = {req: loopBackContext.active}; const editPayMethodCheck = await Self.app.models.ACL.checkAccessAcl(accessToken, 'Supplier', 'editPayMethodCheck', 'WRITE');