From af4de1895b8264476d851b17ce695238f0ff4229 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 6 Jul 2023 15:33:01 +0200 Subject: [PATCH 1/6] =?UTF-8?q?refs=20#5875=20feat:=20refatorizado=20el=20?= =?UTF-8?q?proceso=20de=20facturaci=C3=B3n=20y=20arreglados=20errores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/changes/232801/00-aclTicket.sql | 2 + loopback/locale/es.json | 7 +- .../back/methods/invoiceOut/invoiceClient.js | 51 +-------- .../methods/invoiceOut/makePdfAndNotify.js | 5 +- .../back/methods/ticket/canBeInvoiced.js | 33 ++++-- .../back/methods/ticket/invoiceTickets.js | 105 ++++++++++++++++++ .../ticket/back/methods/ticket/makeInvoice.js | 79 +++++-------- modules/ticket/back/models/ticket-methods.js | 1 + modules/ticket/front/descriptor-menu/index.js | 2 +- modules/ticket/front/index/index.js | 2 +- 10 files changed, 170 insertions(+), 117 deletions(-) create mode 100644 db/changes/232801/00-aclTicket.sql create mode 100644 modules/ticket/back/methods/ticket/invoiceTickets.js diff --git a/db/changes/232801/00-aclTicket.sql b/db/changes/232801/00-aclTicket.sql new file mode 100644 index 0000000000..0e00751e68 --- /dev/null +++ b/db/changes/232801/00-aclTicket.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) +VALUES('Ticket', 'invoiceTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 106df379eb..97d995c8d7 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -303,5 +303,8 @@ "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", - "Valid priorities": "Prioridades válidas: %d" -} + "Valid priorities": "Prioridades válidas: %d", + "There are no tickets to bill": "There are no tickets to bill", + "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", + "Base negativa para los tickets: 10": "Base negativa para los tickets: 10" +} \ No newline at end of file diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index ddd008942e..f74fdc8080 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,14 @@ 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, - 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; + const invoiceId = await models.Ticket.makeInvoice(ctx, 'G', args.companyFk, 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 a999437c06..a48664b302 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 6b8f9e71a4..b297ad8c41 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,17 +34,25 @@ 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(); @@ -54,6 +65,6 @@ module.exports = function(Self) { return isInvoiced || priceZero || shippingInFuture; }); - return !(invalidTickets || hasSomeNegativeBase); + return !(invalidTickets || hasAnyNegativeBase); }; }; diff --git a/modules/ticket/back/methods/ticket/invoiceTickets.js b/modules/ticket/back/methods/ticket/invoiceTickets.js new file mode 100644 index 0000000000..262ee6c5af --- /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, myOptions); + invoicesIds.push(invoiceId); + } +}; + diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index ee82b874fc..955b84c2bb 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -6,15 +6,20 @@ module.exports = function(Self) { accessType: 'WRITE', accepts: [ { - arg: 'ticketsIds', - description: 'The tickets id', - type: ['number'], + arg: 'type', + description: 'The invoice type', + type: 'string', + required: true + }, + { + arg: 'companyFk', + description: 'The company id', + type: 'string', required: true } ], returns: { - arg: 'data', - type: 'boolean', + type: ['object'], root: true }, http: { @@ -23,7 +28,7 @@ module.exports = function(Self) { } }); - Self.makeInvoice = async(ctx, ticketsIds, options) => { + Self.makeInvoice = async(ctx, type, companyFk, options) => { const models = Self.app.models; const date = Date.vnNew(); date.setHours(0, 0, 0, 0); @@ -41,80 +46,52 @@ 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); + const ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions); + if (!ticketCanBeInvoiced || !ticketToInvoice.length) + throw new UserError(`Some of the selected tickets are not billable`); + 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, + type, ], 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); 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' && invoiceId) + if (serial != 'R' && resultInvoice.id) 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/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index e5a8e8d942..f0c85ecc45 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 17a34ad597..dd518671b7 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/index/index.js b/modules/ticket/front/index/index.js index 55229eabbb..b3afc838cc 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'))); } -- 2.40.1 From aa666ee08d17926a14a716a5098a60a01597bd83 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Jul 2023 08:27:02 +0200 Subject: [PATCH 2/6] refs #5874 feat: refactor code --- loopback/locale/es.json | 6 ++---- .../back/methods/invoiceOut/invoiceClient.js | 3 ++- .../ticket/back/methods/ticket/canBeInvoiced.js | 16 +++++++++++----- .../ticket/back/methods/ticket/makeInvoice.js | 4 +--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 97d995c8d7..1f8987721e 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -304,7 +304,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", "Valid priorities": "Prioridades válidas: %d", - "There are no tickets to bill": "There are no tickets to bill", - "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", - "Base negativa para los tickets: 10": "Base negativa para los tickets: 10" -} \ No newline at end of file + "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}" +} diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index f74fdc8080..38e49120ac 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -74,7 +74,8 @@ module.exports = Self => { ], options); } - const invoiceId = await models.Ticket.makeInvoice(ctx, 'G', args.companyFk, options); + const invoiceType = 'G'; + const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, args.companyFk, options); if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/ticket/canBeInvoiced.js b/modules/ticket/back/methods/ticket/canBeInvoiced.js index b297ad8c41..0f6cb476be 100644 --- a/modules/ticket/back/methods/ticket/canBeInvoiced.js +++ b/modules/ticket/back/methods/ticket/canBeInvoiced.js @@ -56,15 +56,21 @@ module.exports = function(Self) { 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 || hasAnyNegativeBase); + return true; }; }; diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index 955b84c2bb..c4eef30c85 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -59,9 +59,7 @@ module.exports = function(Self) { fields: ['id', 'clientFk'] }, myOptions); - const ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions); - if (!ticketCanBeInvoiced || !ticketToInvoice.length) - throw new UserError(`Some of the selected tickets are not billable`); + await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions); const [firstTicket] = tickets; const clientId = firstTicket.clientFk; -- 2.40.1 From 97ef30404d5d41db6e1fc6d403f6b8c92f7d16cd Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Jul 2023 08:29:40 +0200 Subject: [PATCH 3/6] add translation --- loopback/locale/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/loopback/locale/en.json b/loopback/locale/en.json index d33b3b3c39..030afbe9e3 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -177,5 +177,6 @@ "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", "You can not use the same password": "You can not use the same password", - "Valid priorities": "Valid priorities: %d" + "Valid priorities": "Valid priorities: %d", + "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}" } -- 2.40.1 From 8c18cd82170dfaea18a0ae9dab4d0d23a9aaf7b2 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Jul 2023 08:54:55 +0200 Subject: [PATCH 4/6] refs #5874 fix: global invoicing --- .../back/methods/invoiceOut/invoiceClient.js | 8 +++++++- .../back/methods/ticket/invoiceTickets.js | 2 +- .../ticket/back/methods/ticket/makeInvoice.js | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index 38e49120ac..fa22dab1e0 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -75,7 +75,13 @@ module.exports = Self => { } const invoiceType = 'G'; - const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, args.companyFk, options); + const invoiceId = await models.Ticket.makeInvoice( + ctx, + invoiceType, + args.companyFk, + args.invoiceDate, + options + ); if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/ticket/invoiceTickets.js b/modules/ticket/back/methods/ticket/invoiceTickets.js index 262ee6c5af..f53bcd80d8 100644 --- a/modules/ticket/back/methods/ticket/invoiceTickets.js +++ b/modules/ticket/back/methods/ticket/invoiceTickets.js @@ -98,7 +98,7 @@ module.exports = function(Self) { ${address ? `AND addressFk = ${address}` : ''} `, [ticketsIds], myOptions); - const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, myOptions); + const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, null, myOptions); invoicesIds.push(invoiceId); } }; diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index c4eef30c85..16d7ffc5f1 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -6,7 +6,7 @@ module.exports = function(Self) { accessType: 'WRITE', accepts: [ { - arg: 'type', + arg: 'invoiceType', description: 'The invoice type', type: 'string', required: true @@ -16,6 +16,11 @@ module.exports = function(Self) { description: 'The company id', type: 'string', required: true + }, + { + arg: 'invoiceDate', + description: 'The invoice date', + type: 'date' } ], returns: { @@ -28,10 +33,9 @@ module.exports = function(Self) { } }); - Self.makeInvoice = async(ctx, type, companyFk, options) => { + Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate = Date.vnNew(), 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; @@ -45,7 +49,6 @@ module.exports = function(Self) { } let serial; - let invoiceId; try { const ticketToInvoice = await Self.rawSql(` SELECT id @@ -71,18 +74,18 @@ module.exports = function(Self) { const [result] = await Self.rawSql(query, [ clientId, companyFk, - type, + invoiceType, ], myOptions); serial = result.serial; - 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'); if (serial != 'R' && resultInvoice.id) - await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], myOptions); + await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions); if (tx) await tx.commit(); -- 2.40.1 From a6c084fc530b77512847a8fde698e98ed9da5651 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Jul 2023 12:18:02 +0200 Subject: [PATCH 5/6] =?UTF-8?q?refs=20#5874=20fix:=20solucionado=20Cannot?= =?UTF-8?q?=20read=20property=20'toLowerCase'=20y=20a=C3=B1adido=20recomme?= =?UTF-8?q?ndedCredit=20al=20summary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/client/back/models/client.js | 2 +- modules/client/front/summary/index.html | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index e6e240931a..ca279ef718 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -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(); diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index a0d4b918a0..0a15efcd1c 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"> + + -- 2.40.1 From bed34c1750c73ed3ece1a82d49a4c0280c10fdbb Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Jul 2023 09:10:23 +0200 Subject: [PATCH 6/6] refs #5874 fix: test back --- .../back/methods/ticket/invoiceTickets.js | 2 +- .../ticket/back/methods/ticket/makeInvoice.js | 5 +- .../ticket/specs/canBeInvoiced.spec.js | 76 +++++++++--- .../ticket/specs/invoiceTickets.spec.js | 115 ++++++++++++++++++ .../methods/ticket/specs/makeInvoice.spec.js | 92 +++----------- .../front/descriptor-menu/index.spec.js | 2 +- 6 files changed, 196 insertions(+), 96 deletions(-) create mode 100644 modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js diff --git a/modules/ticket/back/methods/ticket/invoiceTickets.js b/modules/ticket/back/methods/ticket/invoiceTickets.js index f53bcd80d8..780e9eb578 100644 --- a/modules/ticket/back/methods/ticket/invoiceTickets.js +++ b/modules/ticket/back/methods/ticket/invoiceTickets.js @@ -98,7 +98,7 @@ module.exports = function(Self) { ${address ? `AND addressFk = ${address}` : ''} `, [ticketsIds], myOptions); - const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, null, 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 16d7ffc5f1..22fe7b3f5d 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -20,7 +20,8 @@ module.exports = function(Self) { { arg: 'invoiceDate', description: 'The invoice date', - type: 'date' + type: 'date', + required: true } ], returns: { @@ -33,7 +34,7 @@ module.exports = function(Self) { } }); - Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate = Date.vnNew(), options) => { + Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, options) => { const models = Self.app.models; invoiceDate.setHours(0, 0, 0, 0); diff --git a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js index 806f80227b..42f9b5a9e7 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 0000000000..8971fb24a7 --- /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 270ba5c930..9af6ad557e 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/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 0aef956dbb..fbe67d21a9 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(); -- 2.40.1