2734 - Make invoice PDF refactor
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Joan Sanchez 2021-03-15 13:43:22 +01:00
parent 1ed4dd27d6
commit bd081f9fd6
15 changed files with 115 additions and 148 deletions

View File

@ -48,7 +48,7 @@ module.exports = Self => {
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
throw new UserError(`You can't upload images on the test environment`); throw new UserError(`Action not allowed on the test environment`);
// Upload file to temporary path // Upload file to temporary path
const tempContainer = await TempContainer.container(args.collection); const tempContainer = await TempContainer.container(args.collection);

View File

@ -164,7 +164,7 @@
"Amount cannot be zero": "El importe no puede ser cero", "Amount cannot be zero": "El importe no puede ser cero",
"Company has to be official": "Empresa inválida", "Company has to be official": "Empresa inválida",
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria", "You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
"You can't upload images on the test environment": "No puedes subir imágenes en el entorno de pruebas", "Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas",
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta", "The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
"Sorts whole route": "Reordena ruta entera", "Sorts whole route": "Reordena ruta entera",
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día <strong>{{shipped}}</strong>, con una cantidad de <strong>{{quantity}}</strong> y un precio de <strong>{{price}} €</strong>", "New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día <strong>{{shipped}}</strong>, con una cantidad de <strong>{{quantity}}</strong> y un precio de <strong>{{price}} €</strong>",

View File

@ -199,7 +199,7 @@
<span <span
ng-click="ticketDescriptor.show($event, action.sale.ticket.id)" ng-click="ticketDescriptor.show($event, action.sale.ticket.id)"
class="link"> class="link">
{{::action.sale.ticket.id | zeroFill:6}} {{::action.sale.ticket.id}}
</span> </span>
</vn-td> </vn-td>
<vn-td expand>{{::action.claimBeggining.description}}</vn-td> <vn-td expand>{{::action.claimBeggining.description}}</vn-td>

View File

@ -21,10 +21,20 @@ module.exports = Self => {
}); });
Self.book = async ref => { Self.book = async ref => {
let ticketAddress = await Self.app.models.Ticket.findOne({where: {invoiceOut: ref}}); const models = Self.app.models;
let invoiceCompany = await Self.app.models.InvoiceOut.findOne({where: {ref: ref}}); const ticketAddress = await models.Ticket.findOne({
let [taxArea] = await Self.rawSql(`Select vn.addressTaxArea(?, ?) AS code`, [ticketAddress.address, invoiceCompany.company]); where: {invoiceOut: ref}
});
const invoiceCompany = await models.InvoiceOut.findOne({
where: {ref: ref}
});
let query = 'SELECT vn.addressTaxArea(?, ?) AS code';
const [taxArea] = await Self.rawSql(query, [
ticketAddress.address,
invoiceCompany.company
]);
return Self.rawSql(`CALL vn.invoiceOutAgain(?, ?)`, [ref, taxArea.code]); query = 'CALL vn.invoiceOutAgain(?, ?)';
return Self.rawSql(query, [ref, taxArea.code]);
}; };
}; };

View File

@ -5,37 +5,26 @@ const path = require('path');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('createPdf', { Self.remoteMethodCtx('createPdf', {
description: 'Creates an invoice PDF', description: 'Creates an invoice PDF',
accessType: 'READ', accessType: 'WRITE',
accepts: [ accepts: [
{ {
arg: 'id', arg: 'id',
type: 'String', type: 'number',
description: 'The invoice id', description: 'The invoice id',
http: {source: 'path'} http: {source: 'path'}
} }
], ],
returns: [ returns: {
{ type: 'object',
arg: 'body',
type: 'file',
root: true root: true
}, { },
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: { http: {
path: `/:id/createPdf`, path: `/:id/createPdf`,
verb: 'GET' verb: 'POST'
} }
}); });
Self.createPdf = async function(ctx, id, options = {}) { Self.createPdf = async function(ctx, id, options) {
const models = Self.app.models; const models = Self.app.models;
const headers = ctx.req.headers; const headers = ctx.req.headers;
const origin = headers.origin; const origin = headers.origin;
@ -44,10 +33,23 @@ module.exports = Self => {
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`); throw new UserError(`Action not allowed on the test environment`);
const invoiceOut = await Self.findById(id); let tx;
let newOptions = {};
if (typeof options == 'object')
Object.assign(newOptions, options);
if (!newOptions.transaction) {
tx = await Self.beginTransaction({});
newOptions.transaction = tx;
}
let fileSrc;
try {
const invoiceOut = await Self.findById(id, null, newOptions);
await invoiceOut.updateAttributes({ await invoiceOut.updateAttributes({
hasPdf: true hasPdf: true
}); }, newOptions);
const response = got.stream(`${origin}/api/report/invoice`, { const response = got.stream(`${origin}/api/report/invoice`, {
query: { query: {
@ -60,7 +62,7 @@ module.exports = Self => {
const container = await models.InvoiceContainer.container(invoiceYear); const container = await models.InvoiceContainer.container(invoiceYear);
const rootPath = container.client.root; const rootPath = container.client.root;
const fileName = `${invoiceOut.ref}.pdf`; const fileName = `${invoiceOut.ref}.pdf`;
const fileSrc = path.join(rootPath, invoiceYear, fileName); fileSrc = path.join(rootPath, invoiceYear, fileName);
const writeStream = fs.createWriteStream(fileSrc); const writeStream = fs.createWriteStream(fileSrc);
writeStream.on('open', () => { writeStream.on('open', () => {
@ -70,5 +72,15 @@ module.exports = Self => {
writeStream.on('finish', async function() { writeStream.on('finish', async function() {
writeStream.end(); writeStream.end();
}); });
if (tx) await tx.commit();
return invoiceOut;
} catch (e) {
if (tx) await tx.rollback();
if (fs.existsSync(fileSrc))
await fs.unlink(fileSrc);
throw e;
}
}; };
}; };

View File

@ -1,46 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('regenerate', {
description: 'Sends an invoice to a regeneration queue',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceOut id',
http: {source: 'path'}
}],
returns: {
type: 'object',
root: true
},
http: {
path: '/:id/regenerate',
verb: 'POST'
}
});
Self.regenerate = async(ctx, id) => {
const models = Self.app.models;
const tx = await Self.beginTransaction({});
try {
let options = {transaction: tx};
// Remove all invoice references from tickets
const invoiceOut = await models.InvoiceOut.findById(id, null, options);
await invoiceOut.updateAttributes({
hasPdf: false
});
// Create invoice PDF
await models.InvoiceOut.createPdf(ctx, id);
await tx.commit();
return invoiceOut;
} catch (e) {
await tx.rollback();
throw e;
}
};
};

View File

@ -1,36 +0,0 @@
const app = require('vn-loopback/server/server');
describe('invoiceOut regenerate()', () => {
const invoiceReportFk = 30;
const invoiceOutId = 1;
it('should check that the invoice has a PDF and is not in print generation queue', async() => {
const invoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
const [queue] = await app.models.InvoiceOut.rawSql(`
SELECT COUNT(*) AS total
FROM vn.printServerQueue
WHERE reportFk = ?`, [invoiceReportFk]);
expect(invoiceOut.hasPdf).toBeTruthy();
expect(queue.total).toEqual(0);
});
it(`should mark the invoice as doesn't have PDF and add it to a print queue`, async() => {
const ctx = {req: {accessToken: {userId: 5}}};
const invoiceOut = await app.models.InvoiceOut.regenerate(ctx, invoiceOutId);
const [queue] = await app.models.InvoiceOut.rawSql(`
SELECT COUNT(*) AS total
FROM vn.printServerQueue
WHERE reportFk = ?`, [invoiceReportFk]);
expect(invoiceOut.hasPdf).toBeFalsy();
expect(queue.total).toEqual(1);
// restores
const invoiceOutToRestore = await app.models.InvoiceOut.findById(invoiceOutId);
await invoiceOutToRestore.updateAttributes({hasPdf: true});
await app.models.InvoiceOut.rawSql(`
DELETE FROM vn.printServerQueue
WHERE reportFk = ?`, [invoiceReportFk]);
});
});

View File

@ -2,7 +2,6 @@ module.exports = Self => {
require('../methods/invoiceOut/filter')(Self); require('../methods/invoiceOut/filter')(Self);
require('../methods/invoiceOut/summary')(Self); require('../methods/invoiceOut/summary')(Self);
require('../methods/invoiceOut/download')(Self); require('../methods/invoiceOut/download')(Self);
require('../methods/invoiceOut/regenerate')(Self);
require('../methods/invoiceOut/delete')(Self); require('../methods/invoiceOut/delete')(Self);
require('../methods/invoiceOut/book')(Self); require('../methods/invoiceOut/book')(Self);
require('../methods/invoiceOut/createPdf')(Self); require('../methods/invoiceOut/createPdf')(Self);

View File

@ -25,6 +25,14 @@
translate> translate>
Book invoice Book invoice
</vn-item> </vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="regenerateInvoice"
translate>
Regenerate invoice PDF
</vn-item>
</slot-menu> </slot-menu>
<slot-body> <slot-body>
<div class="attributes"> <div class="attributes">
@ -82,3 +90,11 @@
<vn-client-descriptor-popover <vn-client-descriptor-popover
vn-id="clientDescriptor"> vn-id="clientDescriptor">
</vn-client-descriptor-popover> </vn-client-descriptor-popover>
<!-- Create invoice PDF confirmation dialog -->
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createInvoicePdf()"
question="Are you sure you want to regenerate the invoice PDF document?"
message="You are going to regenerate the invoice PDF document">
</vn-confirm>

View File

@ -22,6 +22,16 @@ class Controller extends Descriptor {
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked'))); .then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
} }
createInvoicePdf() {
const invoiceId = this.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage);
});
}
get filter() { get filter() {
if (this.invoiceOut) if (this.invoiceOut)
return JSON.stringify({refFk: this.invoiceOut.ref}); return JSON.stringify({refFk: this.invoiceOut.ref});

View File

@ -9,3 +9,5 @@ Are you sure you want to delete this invoice?: Estas seguro de eliminar esta fac
Book invoice: Asentar factura Book invoice: Asentar factura
InvoiceOut booked: Factura asentada InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura? Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Regenerate invoice PDF: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado

View File

@ -67,7 +67,7 @@ module.exports = function(Self) {
if (serial != 'R' && invoiceId) { if (serial != 'R' && invoiceId) {
await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], options); await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], options);
await models.InvoiceOut.createPdf(ctx, invoiceId); await models.InvoiceOut.createPdf(ctx, invoiceId, options);
} }
await tx.commit(); await tx.commit();

View File

@ -80,13 +80,13 @@
Make invoice Make invoice
</vn-item> </vn-item>
<vn-item <vn-item
ng-click="regenerateInvoiceConfirmation.show()" ng-click="createInvoicePdfConfirmation.show()"
ng-show="$ctrl.isInvoiced" ng-show="$ctrl.isInvoiced"
vn-acl="invoicing" vn-acl="invoicing"
vn-acl-action="remove" vn-acl-action="remove"
name="regenerateInvoice" name="regenerateInvoice"
translate> translate>
Regenerate invoice Regenerate invoice PDF
</vn-item> </vn-item>
<vn-item <vn-item
ng-click="recalculateComponentsConfirmation.show()" ng-click="recalculateComponentsConfirmation.show()"
@ -207,12 +207,12 @@
message="Are you sure you want to invoice this ticket?"> message="Are you sure you want to invoice this ticket?">
</vn-confirm> </vn-confirm>
<!-- Regenerate invoice confirmation dialog --> <!-- Create invoice PDF confirmation dialog -->
<vn-confirm <vn-confirm
vn-id="regenerateInvoiceConfirmation" vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.regenerateInvoice()" on-accept="$ctrl.createInvoicePdf()"
question="You are going to regenerate the invoice" question="Are you sure you want to regenerate the invoice PDF document?"
message="Are you sure you want to regenerate the invoice?"> message="You are going to regenerate the invoice PDF document">
</vn-confirm> </vn-confirm>
<!-- Recalculate components confirmation dialog --> <!-- Recalculate components confirmation dialog -->

View File

@ -219,12 +219,12 @@ class Controller extends Section {
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
} }
regenerateInvoice() { createInvoicePdf() {
const invoiceId = this.ticket.invoiceOut.id; const invoiceId = this.ticket.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/regenerate`) return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => { .then(() => {
const snackbarMessage = this.$t( const snackbarMessage = this.$t(
`Invoice sent for a regeneration, will be available in a few minutes`); `The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage); this.vnApp.showSuccess(snackbarMessage);
}); });
} }

View File

@ -17,12 +17,12 @@ Make a payment: "Verdnatura le comunica:\rSu pedido está pendiente de pago.\rPo
Minimum is needed: "Verdnatura le recuerda:\rEs necesario un importe mínimo de 50€ (Sin IVA) en su pedido {{ticketId}} del día {{created | date: 'dd/MM/yyyy'}} para recibirlo sin portes adicionales." Minimum is needed: "Verdnatura le recuerda:\rEs necesario un importe mínimo de 50€ (Sin IVA) en su pedido {{ticketId}} del día {{created | date: 'dd/MM/yyyy'}} para recibirlo sin portes adicionales."
Ticket invoiced: Ticket facturado Ticket invoiced: Ticket facturado
Make invoice: Crear factura Make invoice: Crear factura
Regenerate invoice: Regenerar factura Regenerate invoice PDF: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
You are going to invoice this ticket: Vas a facturar este ticket You are going to invoice this ticket: Vas a facturar este ticket
Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket? Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket?
You are going to regenerate the invoice: Vas a regenerar la factura You are going to regenerate the invoice PDF document: Vas a regenerar el documento PDF de la factura
Are you sure you want to regenerate the invoice?: ¿Seguro que quieres regenerar la factura? Are you sure you want to regenerate the invoice PDF document?: ¿Seguro que quieres regenerar el documento PDF de la factura?
Invoice sent for a regeneration, will be available in a few minutes: La factura ha sido enviada para ser regenerada, estará disponible en unos minutos
Shipped hour updated: Hora de envio modificada Shipped hour updated: Hora de envio modificada
Deleted ticket: Ticket eliminado Deleted ticket: Ticket eliminado
Recalculate components: Recalcular componentes Recalculate components: Recalcular componentes