diff --git a/.eslintrc.yml b/.eslintrc.yml
index 247319137..163ff22a7 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -31,3 +31,4 @@ rules:
curly: [error, multi-or-nest]
indent: [error, 4]
arrow-parens: [error, as-needed]
+ jasmine/no-focused-tests: 0
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index 41246bd13..d163c809d 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -330,7 +330,7 @@ export default {
moreMenuDeleteStowawayButton: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li:nth-child(5)',
moreMenuAddToTurn: `vn-ticket-descriptor vn-drop-down > vn-popover ul > li:nth-child(2)`,
moreMenuDeleteTicket: `vn-ticket-descriptor vn-drop-down > vn-popover ul > li:nth-child(3)`,
- moreMenuMakeInvoice: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li:nth-child(5)',
+ moreMenuMakeInvoice: 'vn-ticket-descriptor vn-drop-down > vn-popover ul > li[name="Make invoice"]',
addStowawayDialogSecondTicket: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog vn-table vn-tr:nth-child(2)',
shipSelectButton: 'vn-ticket-descriptor > div > div.body > div.quicklinks > vn-button-menu[icon="icon-stowaway"]',
shipButton: 'vn-ticket-descriptor > div > div.body > div.quicklinks vn-icon[icon="icon-stowaway"]',
@@ -339,7 +339,7 @@ export default {
saturdayButton: `vn-ticket-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-tool-bar > vn-button:nth-child(6)`,
closeStowawayDialog: 'vn-ticket-descriptor > vn-add-stowaway > vn-dialog > div > button[class="close"]',
acceptDeleteButton: 'vn-ticket-descriptor button[response="ACCEPT"]',
- acceptInvoiceOutButton: 'vn-ticket-descriptor vn-confirm[vn-id="invoiceMakeConfirmation"] button[response="ACCEPT"]',
+ acceptInvoiceOutButton: 'vn-ticket-descriptor vn-confirm[vn-id="makeInvoiceConfirmation"] button[response="ACCEPT"]',
acceptDeleteStowawayButton: 'vn-ticket-descriptor > vn-remove-stowaway button[response="ACCEPT"]'
},
ticketNotes: {
diff --git a/front/core/components/drop-down/drop-down.js b/front/core/components/drop-down/drop-down.js
index 123df1994..af9fc0bfa 100755
--- a/front/core/components/drop-down/drop-down.js
+++ b/front/core/components/drop-down/drop-down.js
@@ -285,6 +285,8 @@ export default class DropDown extends Component {
}
let li = this.document.createElement('li');
+
+ li.setAttribute('name', option[this.showField]);
fragment.appendChild(li);
if (this.multiple) {
diff --git a/modules/invoiceOut/back/methods/invoiceOut/regenerate.js b/modules/invoiceOut/back/methods/invoiceOut/regenerate.js
new file mode 100644
index 000000000..cf95d5af9
--- /dev/null
+++ b/modules/invoiceOut/back/methods/invoiceOut/regenerate.js
@@ -0,0 +1,48 @@
+module.exports = Self => {
+ Self.remoteMethodCtx('regenerate', {
+ description: 'Sends an invoice to a regeneration queue',
+ 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 userId = ctx.req.accessToken.userId;
+ const models = Self.app.models;
+ const invoiceReportFk = 30; // FIXME - Should be deprecated
+ const worker = await models.Worker.findOne({where: {userFk: userId}});
+ const transaction = await Self.beginTransaction({});
+
+ try {
+ // Remove all invoice references from tickets
+ const invoiceOut = await models.InvoiceOut.findById(id, {transaction});
+ await invoiceOut.updateAttributes({
+ hasPdf: false
+ });
+
+ // Send to print queue
+ await Self.rawSql(`
+ INSERT INTO vn.printServerQueue (reportFk, param1, workerFk)
+ VALUES (?, ?, ?)`, [invoiceReportFk, id, worker.id], {transaction});
+
+ await transaction.commit();
+
+ return invoiceOut;
+ } catch (e) {
+ await transaction.rollback();
+ throw e;
+ }
+ };
+};
diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/regenerate.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/regenerate.spec.js
new file mode 100644
index 000000000..39e5f5171
--- /dev/null
+++ b/modules/invoiceOut/back/methods/invoiceOut/specs/regenerate.spec.js
@@ -0,0 +1,39 @@
+const app = require('vn-loopback/server/server');
+
+describe('invoiceOut regenerate()', () => {
+ const invoiceReportFk = 30; // FIXME - Should be deprecated
+ const invoiceOutId = 1;
+
+ afterAll(async done => {
+ const invoiceOut = await app.models.InvoiceOut.findById(invoiceOutId);
+ await invoiceOut.updateAttributes({hasPdf: true});
+ await app.models.InvoiceOut.rawSql(`
+ DELETE FROM vn.printServerQueue
+ WHERE reportFk = ?`, [invoiceReportFk]);
+
+ done();
+ });
+
+ 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);
+ });
+});
diff --git a/modules/invoiceOut/back/models/invoiceOut.js b/modules/invoiceOut/back/models/invoiceOut.js
index 2d7c691fc..726f26540 100644
--- a/modules/invoiceOut/back/models/invoiceOut.js
+++ b/modules/invoiceOut/back/models/invoiceOut.js
@@ -2,4 +2,5 @@ module.exports = Self => {
require('../methods/invoiceOut/filter')(Self);
require('../methods/invoiceOut/summary')(Self);
require('../methods/invoiceOut/download')(Self);
+ require('../methods/invoiceOut/regenerate')(Self);
};
diff --git a/modules/invoiceOut/back/models/invoiceOut.json b/modules/invoiceOut/back/models/invoiceOut.json
index 7990949e9..0d8881de2 100644
--- a/modules/invoiceOut/back/models/invoiceOut.json
+++ b/modules/invoiceOut/back/models/invoiceOut.json
@@ -35,7 +35,7 @@
"type": "date"
},
"hasPdf": {
- "type": "Number",
+ "type": "Boolean",
"mysql": {
"columnName": "pdf"
}
diff --git a/modules/ticket/front/card/index.js b/modules/ticket/front/card/index.js
index 9c1e45e78..2386b1bb6 100644
--- a/modules/ticket/front/card/index.js
+++ b/modules/ticket/front/card/index.js
@@ -7,6 +7,7 @@ class Controller {
this.filter = {
include: [
{relation: 'warehouse', scope: {fields: ['name']}},
+ {relation: 'invoiceOut', scope: {fields: ['id']}},
{relation: 'address'},
{relation: 'ship'},
{relation: 'agencyMode', scope: {fields: ['name']}},
diff --git a/modules/ticket/front/descriptor/index.html b/modules/ticket/front/descriptor/index.html
index 326a72695..6e77365c1 100644
--- a/modules/ticket/front/descriptor/index.html
+++ b/modules/ticket/front/descriptor/index.html
@@ -180,13 +180,22 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/ticket/front/descriptor/index.js b/modules/ticket/front/descriptor/index.js
index 5d1c6253d..9578b7128 100644
--- a/modules/ticket/front/descriptor/index.js
+++ b/modules/ticket/front/descriptor/index.js
@@ -9,15 +9,34 @@ class Controller {
this.$translate = $translate;
this.aclService = aclService;
this.moreOptions = [
- {callback: this.showAddTurnDialog, name: 'Add turn'},
- {callback: this.showAddStowaway, name: 'Add stowaway', show: () => this.isTicketModule()},
- {callback: this.showRemoveStowaway, name: 'Remove stowaway', show: () => this.shouldShowRemoveStowaway()},
- {callback: this.showInvoiceOutMakeDialog, name: 'Make invoice', acl: 'invoicing'},
- {callback: this.showDeliveryNote, name: 'Show Delivery Note'},
- {callback: this.showDeleteTicketDialog, name: 'Delete ticket'},
- {callback: this.showChangeShipped, name: 'Change shipped hour'},
- {callback: this.showSMSDialog, name: 'Send SMS'},
- {callback: this.openRptRoute, name: 'Show pallet report'}
+ {name: 'Add turn', callback: this.showAddTurnDialog},
+ {name: 'Show Delivery Note', callback: this.showDeliveryNote},
+ {name: 'Delete ticket', callback: this.showDeleteTicketDialog},
+ {name: 'Change shipped hour', callback: this.showChangeShipped},
+ {name: 'Send SMS', callback: this.showSMSDialog},
+ {name: 'Show pallet report', callback: this.openRptRoute},
+ {
+ name: 'Add stowaway',
+ callback: this.showAddStowaway,
+ show: () => this.isTicketModule()
+ },
+ {
+ name: 'Remove stowaway',
+ callback: this.showRemoveStowaway,
+ show: () => this.shouldShowRemoveStowaway()
+ },
+ {
+ name: 'Make invoice',
+ acl: 'invoicing',
+ callback: this.showMakeInvoiceDialog,
+ show: () => !this.hasInvoice()
+ },
+ {
+ name: 'Regenerate invoice',
+ acl: 'invoicing',
+ callback: this.showRegenerateInvoiceDialog,
+ show: () => this.hasInvoice()
+ },
];
}
@@ -189,8 +208,8 @@ class Controller {
/**
* Shows an invoice confirmation
*/
- showInvoiceOutMakeDialog() {
- this.$scope.invoiceMakeConfirmation.show();
+ showMakeInvoiceDialog() {
+ this.$scope.makeInvoiceConfirmation.show();
}
/**
@@ -199,7 +218,7 @@ class Controller {
*
* @param {String} response - Response result
*/
- makeInvoiceOut(response) {
+ makeInvoice(response) {
if (response === 'ACCEPT') {
const query = `/ticket/api/Tickets/${this.ticket.id}/makeInvoice`;
this.$http.post(query).then(() => {
@@ -208,6 +227,40 @@ class Controller {
});
}
}
+
+ /**
+ * Shows an invoice confirmation
+ */
+ showRegenerateInvoiceDialog() {
+ this.$scope.regenerateInvoiceConfirmation.show();
+ }
+
+ /**
+ * Sends an invoice to a regeneration queue
+ * for the current ticket
+ *
+ * @param {String} response - Response result
+ */
+ regenerateInvoice(response) {
+ if (response === 'ACCEPT') {
+ const invoiceId = this.ticket.invoiceOut.id;
+ const query = `/invoiceOut/api/InvoiceOuts/${invoiceId}/regenerate`;
+ this.$http.post(query).then(() => {
+ const snackbarMessage = this.$translate.instant(
+ `Invoice sent for a regeneration, will be available in a few minutes`);
+ this.vnApp.showSuccess(snackbarMessage);
+ });
+ }
+ }
+
+ /**
+ * Returns if the current ticket
+ * is already invoiced
+ * @return {Boolean} - True if invoiced
+ */
+ hasInvoice() {
+ return this.ticket.refFk !== null;
+ }
}
Controller.$inject = ['$state', '$scope', '$http', 'vnApp', '$translate', 'aclService'];
diff --git a/modules/ticket/front/descriptor/index.spec.js b/modules/ticket/front/descriptor/index.spec.js
index 164ad8009..c799b16ec 100644
--- a/modules/ticket/front/descriptor/index.spec.js
+++ b/modules/ticket/front/descriptor/index.spec.js
@@ -9,7 +9,7 @@ describe('Ticket Component vnTicketDescriptor', () => {
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnTicketDescriptor');
- controller.ticket = {id: 2};
+ controller.ticket = {id: 2, invoiceOut: {id: 1}};
}));
describe('showAddTurnDialog()', () => {
@@ -78,19 +78,32 @@ describe('Ticket Component vnTicketDescriptor', () => {
});
});
- describe('invoiceMakeOut(response)', () => {
+ describe('makeInvoice(response)', () => {
it('should make a query and call $state.reload() method if the response is ACCEPT', () => {
spyOn(controller.$state, 'reload');
spyOn(controller.vnApp, 'showSuccess');
$httpBackend.when('POST', `/ticket/api/Tickets/2/makeInvoice`).respond();
$httpBackend.expect('POST', `/ticket/api/Tickets/2/makeInvoice`).respond();
- controller.makeInvoiceOut('ACCEPT');
+ controller.makeInvoice('ACCEPT');
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Ticket invoiced');
expect(controller.$state.reload).toHaveBeenCalledWith();
});
});
+
+ describe('regenerateInvoice(response)', () => {
+ it('should make a query and show a success snackbar if the response is ACCEPT', () => {
+ spyOn(controller.vnApp, 'showSuccess');
+
+ $httpBackend.when('POST', `/invoiceOut/api/InvoiceOuts/1/regenerate`).respond();
+ $httpBackend.expect('POST', `/invoiceOut/api/InvoiceOuts/1/regenerate`).respond();
+ controller.regenerateInvoice('ACCEPT');
+ $httpBackend.flush();
+
+ expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Invoice sent for a regeneration, will be available in a few minutes');
+ });
+ });
});
diff --git a/modules/ticket/front/descriptor/locale/es.yml b/modules/ticket/front/descriptor/locale/es.yml
index a661c239f..1b6baed2a 100644
--- a/modules/ticket/front/descriptor/locale/es.yml
+++ b/modules/ticket/front/descriptor/locale/es.yml
@@ -15,6 +15,10 @@ SMSPayment: >-
Verdnatura le comunica: Su pedido está pendiente de pago.
Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.
Ticket invoiced: Ticket facturado
-Make invoice: Facturar
+Make invoice: Crear factura
+Regenerate invoice: Regenerar factura
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?
\ No newline at end of file
+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
+Are you sure you want to regenerate the invoice?: ¿Seguro que quieres regenerar 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
\ No newline at end of file