diff --git a/db/changes/232801/00-aclTicket.sql b/db/changes/232801/00-aclTicket.sql
new file mode 100644
index 000000000..0e00751e6
--- /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/en.json b/loopback/locale/en.json
index d33b3b3c3..030afbe9e 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}}"
}
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index 106df379e..1f8987721 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -303,5 +303,6 @@
"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",
+ "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 e6e240931..ca279ef71 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 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')));
}