From 0941b2423ede6d7aa02fec98e368a1b280db1611 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 24 Jun 2021 13:33:51 +0200 Subject: [PATCH 1/5] 2983 - Invoice tickets --- modules/ticket/front/index/index.html | 41 +++++++++++++++++++++++- modules/ticket/front/index/index.js | 8 +++++ modules/ticket/front/index/locale/es.yml | 3 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/modules/ticket/front/index/index.html b/modules/ticket/front/index/index.html index 79774c647..00bddc750 100644 --- a/modules/ticket/front/index/index.html +++ b/modules/ticket/front/index/index.html @@ -166,6 +166,15 @@ vn-tooltip="Payment on account..." tooltip-position="left"> + + + + + + Quick invoice + + + Multiple invoices + + + Rectificative invoice + + @@ -224,4 +255,12 @@ Copy value - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/modules/ticket/front/index/index.js b/modules/ticket/front/index/index.js index 9dc3d81b8..668030bb1 100644 --- a/modules/ticket/front/index/index.js +++ b/modules/ticket/front/index/index.js @@ -8,6 +8,7 @@ export default class Controller extends Section { super($element, $); this.vnReport = vnReport; } + setDelivered() { const checkedTickets = this.checked; let ids = []; @@ -159,6 +160,13 @@ export default class Controller extends Section { } return {}; } + + makeInvoice() { + const [ticket] = this.checked; + return this.$http.post(`Tickets/${ticket.id}/makeInvoice`) + .then(() => this.$.model.refresh()) + .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); + } } Controller.$inject = ['$element', '$scope', 'vnReport']; diff --git a/modules/ticket/front/index/locale/es.yml b/modules/ticket/front/index/locale/es.yml index 9ff8d1568..c82aa2ef1 100644 --- a/modules/ticket/front/index/locale/es.yml +++ b/modules/ticket/front/index/locale/es.yml @@ -11,4 +11,5 @@ Remove filter: Quitar filtro por selección Remove all filters: Eliminar todos los filtros Copy value: Copiar valor No verified data: Sin datos comprobados -Component lack: Faltan componentes \ No newline at end of file +Component lack: Faltan componentes +Quick invoice: Factura rápida \ No newline at end of file -- 2.40.1 From 7e836e570f50b02a40dfdeff1e5d1cd87d2e9052 Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 30 Jun 2021 16:10:26 +0200 Subject: [PATCH 2/5] 2983 - Invoice tickets --- loopback/locale/es.json | 3 +- .../back/methods/client/canBeInvoiced.js | 16 ++- .../client/specs/canBeInvoiced.spec.js | 63 +++++++++ .../back/methods/ticket/canBeInvoiced.js | 55 +++++--- .../ticket/back/methods/ticket/makeInvoice.js | 102 +++++++++----- .../ticket/specs/canBeInvoiced.spec.js | 86 ++++++++++++ .../methods/ticket/specs/makeInvoice.spec.js | 127 ++++++++++++------ modules/ticket/front/index/index.html | 13 +- modules/ticket/front/index/index.js | 12 +- modules/ticket/front/index/locale/en.yml | 1 + modules/ticket/front/index/locale/es.yml | 6 +- 11 files changed, 375 insertions(+), 109 deletions(-) create mode 100644 modules/client/back/methods/client/specs/canBeInvoiced.spec.js create mode 100644 modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js create mode 100644 modules/ticket/front/index/locale/en.yml diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 872d45ae0..6d465c57b 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -182,5 +182,6 @@ "Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})", "None": "Ninguno", "The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada", - "This document already exists on this ticket": "Este documento ya existe en el ticket" + "This document already exists on this ticket": "Este documento ya existe en el ticket", + "Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables" } \ No newline at end of file diff --git a/modules/client/back/methods/client/canBeInvoiced.js b/modules/client/back/methods/client/canBeInvoiced.js index 1f695aba8..d8a126ed2 100644 --- a/modules/client/back/methods/client/canBeInvoiced.js +++ b/modules/client/back/methods/client/canBeInvoiced.js @@ -5,7 +5,7 @@ module.exports = function(Self) { accepts: [ { arg: 'id', - type: 'string', + type: 'number', required: true, description: 'Client id', http: {source: 'path'} @@ -22,8 +22,18 @@ module.exports = function(Self) { } }); - Self.canBeInvoiced = async id => { - let client = await Self.app.models.Client.findById(id, {fields: ['id', 'isTaxDataChecked', 'hasToInvoice']}); + Self.canBeInvoiced = async(id, options) => { + const models = Self.app.models; + + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const client = await models.Client.findById(id, { + fields: ['id', 'isTaxDataChecked', 'hasToInvoice'] + }, myOptions); + if (client.isTaxDataChecked && client.hasToInvoice) return true; diff --git a/modules/client/back/methods/client/specs/canBeInvoiced.spec.js b/modules/client/back/methods/client/specs/canBeInvoiced.spec.js new file mode 100644 index 000000000..0ac1a3930 --- /dev/null +++ b/modules/client/back/methods/client/specs/canBeInvoiced.spec.js @@ -0,0 +1,63 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); + +describe('client canBeInvoiced()', () => { + const userId = 19; + const clientId = 1101; + const activeCtx = { + accessToken: {userId: userId} + }; + const models = app.models; + + beforeAll(async done => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + + done(); + }); + + it('should return falsy for a client without the data checked', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const client = await models.Client.findById(clientId, null, options); + await client.updateAttribute('isTaxDataChecked', false, options); + + const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); + + expect(canBeInvoiced).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); + + it('should return falsy for a client with invoicing disabled', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const client = await models.Client.findById(clientId, null, options); + await client.updateAttribute('hasToInvoice', false, options); + + const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); + + expect(canBeInvoiced).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); + + it('should return truthy for an invoiceable client', async() => { + const canBeInvoiced = await models.Client.canBeInvoiced(clientId); + + expect(canBeInvoiced).toEqual(true); + }); +}); diff --git a/modules/ticket/back/methods/ticket/canBeInvoiced.js b/modules/ticket/back/methods/ticket/canBeInvoiced.js index 8300ae110..facb7b945 100644 --- a/modules/ticket/back/methods/ticket/canBeInvoiced.js +++ b/modules/ticket/back/methods/ticket/canBeInvoiced.js @@ -4,11 +4,10 @@ module.exports = function(Self) { accessType: 'READ', accepts: [ { - arg: 'id', - type: 'number', - required: true, - description: 'The ticket id', - http: {source: 'path'} + arg: 'ticketsIds', + description: 'The tickets id', + type: ['number'], + required: true } ], returns: { @@ -17,26 +16,44 @@ module.exports = function(Self) { root: true }, http: { - path: `/:id/canBeInvoiced`, + path: `/canBeInvoiced`, verb: 'get' } }); - Self.canBeInvoiced = async id => { - let ticket = await Self.app.models.Ticket.findById(id, { + Self.canBeInvoiced = async(ticketsIds, options) => { + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const tickets = await Self.find({ + where: { + id: {inq: ticketsIds} + }, fields: ['id', 'refFk', 'shipped', 'totalWithVat'] + }, myOptions); + + 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 today = new Date(); + + const invalidTickets = tickets.some(ticket => { + const shipped = new Date(ticket.shipped); + const shippingInFuture = shipped.getTime() > today.getTime(); + const isInvoiced = ticket.refFk; + const priceZero = ticket.totalWithVat == 0; + + return isInvoiced || priceZero || shippingInFuture; }); - let query = `SELECT vn.hasSomeNegativeBase(?) AS hasSomeNegativeBase`; - let [result] = await Self.rawSql(query, [id]); - let hasSomeNegativeBase = result.hasSomeNegativeBase; - - let today = new Date(); - let shipped = new Date(ticket.shipped); - - if (ticket.refFk || ticket.totalWithVat == 0 || shipped.getTime() > today.getTime() || hasSomeNegativeBase) - return false; - - return true; + return !(invalidTickets || hasSomeNegativeBase); }; }; diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index a44c41e16..d8c2dc5c9 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -2,15 +2,14 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = function(Self) { Self.remoteMethodCtx('makeInvoice', { - description: 'Make out an invoice from a ticket id', + description: 'Make out an invoice from one or more tickets', accessType: 'WRITE', accepts: [ { - arg: 'id', - type: 'string', - required: true, - description: 'Ticket id', - http: {source: 'path'} + arg: 'ticketsIds', + description: 'The tickets id', + type: ['number'], + required: true } ], returns: { @@ -19,61 +18,98 @@ module.exports = function(Self) { root: true }, http: { - path: `/:id/makeInvoice`, + path: `/makeInvoice`, verb: 'POST' } }); - Self.makeInvoice = async(ctx, id) => { + Self.makeInvoice = async(ctx, ticketsIds, options) => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; - const tx = await Self.beginTransaction({}); + + let tx; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } try { - const options = {transaction: tx}; + const tickets = await models.Ticket.find({ + where: { + id: {inq: ticketsIds} + }, + fields: ['id', 'clientFk', 'companyFk'] + }, myOptions); - const filter = {fields: ['id', 'clientFk', 'companyFk']}; - const ticket = await models.Ticket.findById(id, filter, options); + const [firstTicket] = tickets; + const clientId = firstTicket.clientFk; + const companyId = firstTicket.companyFk; - const clientCanBeInvoiced = await models.Client.canBeInvoiced(ticket.clientFk); + 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(ticket.id); + const ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ticketsIds, myOptions); if (!ticketCanBeInvoiced) - throw new UserError(`This ticket can't be invoiced`); + throw new UserError(`Some of the selected tickets are not billable`); const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`; - const [result] = await Self.rawSql(query, [ticket.clientFk, ticket.companyFk, 'R'], options); + const [result] = await Self.rawSql(query, [ + clientId, + companyId, + 'R' + ], myOptions); const serial = result.serial; - await Self.rawSql('CALL invoiceFromTicket(?)', [id], options); - await Self.rawSql('CALL invoiceOut_new(?, CURDATE(), null, @invoiceId)', [serial], options); + await Self.rawSql(` + DROP TEMPORARY TABLE IF EXISTS ticketToInvoice; + CREATE TEMPORARY TABLE ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id FROM vn.ticket + WHERE id IN(?) AND refFk IS NULL + `, [ticketsIds], myOptions); - const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], options); + await Self.rawSql('CALL invoiceOut_new(?, CURDATE(), null, @invoiceId)', [serial], myOptions); + + const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions); const invoiceId = resultInvoice.id; - const ticketInvoice = await models.Ticket.findById(id, {fields: ['refFk']}, options); + for (let ticket of tickets) { + const ticketInvoice = await models.Ticket.findById(ticket.id, { + fields: ['refFk'] + }, myOptions); - await models.TicketLog.create({ - originFk: ticket.id, - userFk: userId, - action: 'insert', - changedModel: 'Ticket', - changedModelId: ticket.id, - newInstance: ticketInvoice - }, options); + await models.TicketLog.create({ + originFk: ticket.id, + userFk: userId, + action: 'insert', + changedModel: 'Ticket', + changedModelId: ticket.id, + newInstance: ticketInvoice + }, myOptions); + } if (serial != 'R' && invoiceId) { - await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], options); - await models.InvoiceOut.createPdf(ctx, invoiceId, options); + await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], myOptions); + await models.InvoiceOut.createPdf(ctx, invoiceId, myOptions); } - await tx.commit(); - return {invoiceFk: invoiceId, serial}; + if (tx) await tx.commit(); + + return {invoiceFk: invoiceId, serial: serial}; } catch (e) { - await tx.rollback(); + if (tx) await tx.rollback(); throw e; } }; diff --git a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js new file mode 100644 index 000000000..77da98b26 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js @@ -0,0 +1,86 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); +const models = app.models; + +describe('ticket canBeInvoiced()', () => { + const userId = 19; + const ticketId = 11; + const activeCtx = { + accessToken: {userId: userId} + }; + + beforeAll(async done => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + + done(); + }); + + it('should return falsy for an already invoiced ticket', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ticket = await models.Ticket.findById(ticketId, null, options); + await ticket.updateAttribute('refFk', 'T1234567', options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + + expect(canBeInvoiced).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); + + it('should return falsy for a ticket with a price of zero', async() => { + const tx = await models.Ticket.beginTransaction({}); + + 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); + + expect(canBeInvoiced).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); + + it('should return falsy for a ticket shipping in future', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ticket = await models.Ticket.findById(ticketId, null, options); + + const shipped = new Date(); + shipped.setDate(shipped.getDate() + 1); + + await ticket.updateAttribute('shipped', shipped, options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + + expect(canBeInvoiced).toEqual(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); + + it('should return truthy for an invoiceable ticket', async() => { + const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId]); + + expect(canBeInvoiced).toEqual(true); + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js index 32c769ca4..3011d49a6 100644 --- a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js +++ b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js @@ -1,18 +1,17 @@ const app = require('vn-loopback/server/server'); const LoopBackContext = require('loopback-context'); +const models = app.models; -describe('ticket makeInvoice()', () => { +fdescribe('ticket makeInvoice()', () => { const userId = 19; + const ticketId = 11; + const clientId = 1102; const activeCtx = { accessToken: {userId: userId}, headers: {origin: 'http://localhost:5000'}, }; const ctx = {req: activeCtx}; - let invoice; - let ticketId = 11; - const okState = 3; - beforeAll(async done => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx @@ -21,47 +20,93 @@ describe('ticket makeInvoice()', () => { done(); }); - afterAll(async done => { - try { - let ticket = await app.models.Ticket.findById(11); - await ticket.updateAttributes({refFk: null}); - - let ticketTrackings = await app.models.TicketTracking.find({ - where: { - ticketFk: ticketId, - stateFk: {neq: okState} - }, - order: 'id DESC' - }); - - for (let state of ticketTrackings) - await state.destroy(); - - let invoiceOut = await app.models.InvoiceOut.findById(invoice.invoiceFk); - await invoiceOut.destroy(); - } catch (error) { - console.error(error); - } - done(); - }); - - it('should invoice a ticket, then try again to fail', async() => { - const invoiceOutModel = app.models.InvoiceOut; + it('should throw an error when invoicing tickets from multiple clients', async() => { + const invoiceOutModel = models.InvoiceOut; spyOn(invoiceOutModel, 'createPdf'); - invoice = await app.models.Ticket.makeInvoice(ctx, ticketId); - - expect(invoice.invoiceFk).toBeDefined(); - expect(invoice.serial).toEqual('T'); + const tx = await models.Ticket.beginTransaction({}); let error; - await app.models.Ticket.makeInvoice(ctx, ticketId).catch(e => { - error = e; - }).finally(() => { - expect(error.message).toEqual(`This ticket can't be invoiced`); - }); + try { + const options = {transaction: tx}; + const otherClientTicketId = 16; + await models.Ticket.makeInvoice(ctx, [ticketId, otherClientTicketId], options); - expect(error).toBeDefined(); + 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'); + + 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(`One or more tickets are not billable`); + }); + + it('should success to invoice a ticket', async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'createPdf'); + + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const invoice = await models.Ticket.makeInvoice(ctx, [ticketId], options); + + expect(invoice.invoiceFk).toBeDefined(); + expect(invoice.serial).toEqual('T'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } }); }); diff --git a/modules/ticket/front/index/index.html b/modules/ticket/front/index/index.html index 00bddc750..0762714d4 100644 --- a/modules/ticket/front/index/index.html +++ b/modules/ticket/front/index/index.html @@ -193,15 +193,10 @@ - Multiple invoices - - - Rectificative invoice + Multiple invoice @@ -261,6 +256,6 @@ + question="{{$ctrl.confirmationMessage}}" + message="Invoice selected tickets"> \ No newline at end of file diff --git a/modules/ticket/front/index/index.js b/modules/ticket/front/index/index.js index 668030bb1..2df4de0a5 100644 --- a/modules/ticket/front/index/index.js +++ b/modules/ticket/front/index/index.js @@ -75,6 +75,14 @@ export default class Controller extends Section { return this.checked.length; } + get confirmationMessage() { + if (!this.$.model) return 0; + + return this.$t(`Are you sure to invoice tickets`, { + ticketsAmount: this.totalChecked + }); + } + onMoreOpen() { let options = this.moreOptions.filter(o => o.always || this.isChecked); this.$.moreButton.data = options; @@ -162,8 +170,8 @@ export default class Controller extends Section { } makeInvoice() { - const [ticket] = this.checked; - return this.$http.post(`Tickets/${ticket.id}/makeInvoice`) + const ticketsIds = this.checked.map(ticket => ticket.id); + return this.$http.post(`Tickets/makeInvoice`, {ticketsIds}) .then(() => this.$.model.refresh()) .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); } diff --git a/modules/ticket/front/index/locale/en.yml b/modules/ticket/front/index/locale/en.yml new file mode 100644 index 000000000..6b411b492 --- /dev/null +++ b/modules/ticket/front/index/locale/en.yml @@ -0,0 +1 @@ +Are you sure to invoice tickets: Are you sure to invoice {{ticketsAmount}} tickets? \ No newline at end of file diff --git a/modules/ticket/front/index/locale/es.yml b/modules/ticket/front/index/locale/es.yml index c82aa2ef1..eac0084f6 100644 --- a/modules/ticket/front/index/locale/es.yml +++ b/modules/ticket/front/index/locale/es.yml @@ -12,4 +12,8 @@ Remove all filters: Eliminar todos los filtros Copy value: Copiar valor No verified data: Sin datos comprobados Component lack: Faltan componentes -Quick invoice: Factura rápida \ No newline at end of file +Quick invoice: Factura rápida +Multiple invoice: Factura múltiple +Make invoice...: Crear factura... +Invoice selected tickets: Facturar tickets seleccionados +Are you sure to invoice tickets: ¿Seguro que quieres facturar {{ticketsAmount}} tickets? \ No newline at end of file -- 2.40.1 From 92b6318c7996e88fc7b8c4004199b763be349c65 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 1 Jul 2021 13:31:04 +0200 Subject: [PATCH 3/5] Ammends --- loopback/locale/es.json | 3 ++- modules/ticket/front/index/index.html | 19 +------------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 6d465c57b..ff30a61ff 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -183,5 +183,6 @@ "None": "Ninguno", "The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada", "This document already exists on this ticket": "Este documento ya existe en el ticket", - "Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables" + "Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables", + "You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes" } \ No newline at end of file diff --git a/modules/ticket/front/index/index.html b/modules/ticket/front/index/index.html index 0762714d4..63b0b049d 100644 --- a/modules/ticket/front/index/index.html +++ b/modules/ticket/front/index/index.html @@ -168,7 +168,7 @@ - - - - Quick invoice - - - Multiple invoice - - -- 2.40.1 From 319a168b893e518a333e24ad6648bf6f4054a28f Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 1 Jul 2021 13:43:13 +0200 Subject: [PATCH 4/5] Removed focus --- modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js index 3011d49a6..7f75c7d31 100644 --- a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js +++ b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js @@ -2,7 +2,7 @@ const app = require('vn-loopback/server/server'); const LoopBackContext = require('loopback-context'); const models = app.models; -fdescribe('ticket makeInvoice()', () => { +describe('ticket makeInvoice()', () => { const userId = 19; const ticketId = 11; const clientId = 1102; -- 2.40.1 From 324610157440450d731a412073f445e949b43119 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 1 Jul 2021 14:03:28 +0200 Subject: [PATCH 5/5] Updated unit tests --- modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js | 2 +- modules/ticket/front/descriptor-menu/index.js | 3 ++- modules/ticket/front/descriptor-menu/index.spec.js | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js index 7f75c7d31..55c5bccd7 100644 --- a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js +++ b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js @@ -87,7 +87,7 @@ describe('ticket makeInvoice()', () => { await tx.rollback(); } - expect(error.message).toEqual(`One or more tickets are not billable`); + expect(error.message).toEqual(`Some of the selected tickets are not billable`); }); it('should success to invoice a ticket', async() => { diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 17ed36ab5..5da9544e2 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -217,7 +217,8 @@ class Controller extends Section { } makeInvoice() { - return this.$http.post(`Tickets/${this.id}/makeInvoice`) + const params = {ticketsIds: [this.id]}; + return this.$http.post(`Tickets/makeInvoice`, 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 931f0be0d..b102b1f44 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -139,7 +139,8 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { jest.spyOn(controller, 'reload').mockReturnThis(); jest.spyOn(controller.vnApp, 'showSuccess'); - $httpBackend.expectPOST(`Tickets/${ticket.id}/makeInvoice`).respond(); + const expectedParams = {ticketsIds: [ticket.id]}; + $httpBackend.expectPOST(`Tickets/makeInvoice`, expectedParams).respond(); controller.makeInvoice(); $httpBackend.flush(); -- 2.40.1