diff --git a/front/core/components/menu/menu.js b/front/core/components/menu/menu.js index d0c649004..e0d968e2f 100755 --- a/front/core/components/menu/menu.js +++ b/front/core/components/menu/menu.js @@ -3,8 +3,8 @@ import Popover from '../popover'; import './style.scss'; export default class Menu extends Popover { - show(parent) { - super.show(parent); + show(parent, direction) { + super.show(parent, direction); this.windowEl.addEventListener('click', () => this.hide()); } } diff --git a/front/core/components/menu/style.scss b/front/core/components/menu/style.scss index 92f437243..6125c0e5b 100644 --- a/front/core/components/menu/style.scss +++ b/front/core/components/menu/style.scss @@ -1,7 +1,18 @@ @import "./effects"; +@import "variables"; .vn-menu { vn-item, .vn-item { @extend %clickable; } + + vn-item.dropdown:after, + .vn-item.dropdown:after { + font-family: 'Material Icons'; + content: 'keyboard_arrow_right'; + position: absolute; + color: $color-spacer; + font-size: 1.5em; + right: 0 + } } diff --git a/front/core/components/popover/index.js b/front/core/components/popover/index.js index d78179ab9..446c0697b 100644 --- a/front/core/components/popover/index.js +++ b/front/core/components/popover/index.js @@ -23,12 +23,15 @@ export default class Popover extends Popup { * it is shown in a visible relative position to it. * * @param {HTMLElement|Event} parent Overrides the parent property + * @param {String} direction - Direction [left] */ - show(parent) { + show(parent, direction) { if (parent instanceof Event) parent = event.target; if (parent) this.parent = parent; + if (direction) this.direction = direction; + super.show(); this.content = this.popup.querySelector('.content'); this.$timeout(() => this.relocate(), 10); @@ -89,21 +92,40 @@ export default class Popover extends Popup { let width = clamp(popoverRect.width, parentRect.width, maxWith); let height = popoverRect.height; - let left = parentRect.left + parentRect.width / 2 - width / 2; - left = clamp(left, margin, maxRight - width); + let left; + if (this.direction == 'left') { + left = parentRect.left + parentRect.width; + left = clamp(left, margin, maxRight - width); + } else { + left = parentRect.left + parentRect.width / 2 - width / 2; + left = clamp(left, margin, maxRight - width); + } - let top = parentRect.top + parentRect.height + arrowOffset; + let top; + if (this.direction == 'left') + top = parentRect.top; + else + top = parentRect.top + parentRect.height + arrowOffset; let showTop = top + height > maxBottom; if (showTop) top = parentRect.top - height - arrowOffset; top = Math.max(top, margin); - if (showTop) + if (this.direction == 'left') + arrowStyle.left = `0`; + else if (showTop) arrowStyle.bottom = `0`; else arrowStyle.top = `0`; - let arrowLeft = (parentRect.left - left) + parentRect.width / 2; - arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight); + let arrowLeft; + if (this.direction == 'left') { + arrowLeft = 0; + let arrowTop = arrowOffset; + arrowStyle.top = `${arrowTop}px`; + } else { + arrowLeft = (parentRect.left - left) + parentRect.width / 2; + arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight); + } arrowStyle.left = `${arrowLeft}px`; style.top = `${top}px`; diff --git a/front/core/services/email.js b/front/core/services/email.js index 4385eed5f..633b13a26 100644 --- a/front/core/services/email.js +++ b/front/core/services/email.js @@ -18,6 +18,18 @@ class Email { return this.$http.get(`email/${template}`, {params}) .then(() => this.vnApp.showMessage(this.$t('Notification sent!'))); } + + /** + * Sends an email displaying a notification when it's sent. + * + * @param {String} template The email report name + * @param {Object} params The email parameters + * @return {Promise} Promise resolved when it's sent + */ + sendCsv(template, params) { + return this.$http.get(`csv/${template}/send`, {params}) + .then(() => this.vnApp.showMessage(this.$t('Notification sent!'))); + } } Email.$inject = ['$http', '$translate', 'vnApp']; diff --git a/front/core/services/report.js b/front/core/services/report.js index 32ccb52a3..c58a0ee0e 100644 --- a/front/core/services/report.js +++ b/front/core/services/report.js @@ -20,6 +20,21 @@ class Report { const serializedParams = this.$httpParamSerializer(params); window.open(`api/report/${report}?${serializedParams}`); } + + /** + * Shows a report in another window, automatically adds the authorization + * token to params. + * + * @param {String} report The report name + * @param {Object} params The report parameters + */ + showCsv(report, params) { + params = Object.assign({ + authorization: this.vnToken.token + }, params); + const serializedParams = this.$httpParamSerializer(params); + window.open(`api/csv/${report}/download?${serializedParams}`); + } } Report.$inject = ['$httpParamSerializer', 'vnToken']; diff --git a/modules/invoiceOut/front/descriptor/index.html b/modules/invoiceOut/front/descriptor/index.html index 4d9a4ffd8..e96e99447 100644 --- a/modules/invoiceOut/front/descriptor/index.html +++ b/modules/invoiceOut/front/descriptor/index.html @@ -2,18 +2,49 @@ module="invoiceOut" description="$ctrl.invoiceOut.ref"> - - Show invoice PDF - - + + + Show as PDF + + + Show as CSV + + + + + - Send invoice PDF + Send invoice... + + + + + Send PDF + + + Send CSV + + + - - + + Are you sure you want to send it? + ng-model="sendPdfConfirmation.data.email"> + + + + + + + + + + + + Are you sure you want to send it? + diff --git a/modules/invoiceOut/front/descriptor/index.js b/modules/invoiceOut/front/descriptor/index.js index 0600ffa5b..129fe16d1 100644 --- a/modules/invoiceOut/front/descriptor/index.js +++ b/modules/invoiceOut/front/descriptor/index.js @@ -14,29 +14,6 @@ class Controller extends Descriptor { return this.aclService.hasAny(['invoicing']); } - deleteInvoiceOut() { - return this.$http.post(`InvoiceOuts/${this.id}/delete`) - .then(() => this.$state.go('invoiceOut.index')) - .then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted'))); - } - - bookInvoiceOut() { - return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`) - .then(() => this.$state.reload()) - .then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked'))); - } - - createInvoicePdf() { - const invoiceId = this.invoiceOut.id; - return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`) - .then(() => this.reload()) - .then(() => { - const snackbarMessage = this.$t( - `The invoice PDF document has been regenerated`); - this.vnApp.showSuccess(snackbarMessage); - }); - } - get filter() { if (this.invoiceOut) return JSON.stringify({refFk: this.invoiceOut.ref}); @@ -55,7 +32,7 @@ class Controller extends Descriptor { }, { relation: 'client', scope: { - fields: ['id', 'name'] + fields: ['id', 'name', 'email'] } } ] @@ -76,13 +53,51 @@ class Controller extends Descriptor { // Prevents error when not defined } - sendInvoice($data) { + deleteInvoiceOut() { + return this.$http.post(`InvoiceOuts/${this.id}/delete`) + .then(() => this.$state.go('invoiceOut.index')) + .then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted'))); + } + + bookInvoiceOut() { + return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`) + .then(() => this.$state.reload()) + .then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked'))); + } + + createPdfInvoice() { + const invoiceId = this.invoiceOut.id; + return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`) + .then(() => this.reload()) + .then(() => { + const snackbarMessage = this.$t( + `The invoice PDF document has been regenerated`); + this.vnApp.showSuccess(snackbarMessage); + }); + } + + showCsvInvoice() { + this.vnReport.showCsv('invoice', { + recipientId: this.invoiceOut.client.id, + invoiceId: this.id, + }); + } + + sendPdfInvoice($data) { return this.vnEmail.send('invoice', { recipientId: this.invoiceOut.client.id, recipient: $data.email, invoiceId: this.id }); } + + sendCsvInvoice($data) { + return this.vnEmail.sendCsv('invoice', { + recipientId: this.invoiceOut.client.id, + recipient: $data.email, + invoiceId: this.id + }); + } } ngModule.vnComponent('vnInvoiceOutDescriptor', { diff --git a/modules/invoiceOut/front/descriptor/index.spec.js b/modules/invoiceOut/front/descriptor/index.spec.js index 0a5494b9a..12430d44d 100644 --- a/modules/invoiceOut/front/descriptor/index.spec.js +++ b/modules/invoiceOut/front/descriptor/index.spec.js @@ -3,30 +3,20 @@ import './index'; describe('vnInvoiceOutDescriptor', () => { let controller; let $httpBackend; - const invoiceOut = {id: 1}; + let $httpParamSerializer; + const invoiceOut = { + id: 1, + client: {id: 1101} + }; beforeEach(ngModule('invoiceOut')); - beforeEach(inject(($componentController, _$httpBackend_) => { + beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => { $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; controller = $componentController('vnInvoiceOutDescriptor', {$element: null}); })); - describe('createInvoicePdf()', () => { - it('should make a query and show a success snackbar', () => { - jest.spyOn(controller.vnApp, 'showSuccess'); - - controller.invoiceOut = invoiceOut; - - $httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond(); - $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond(); - controller.createInvoicePdf(); - $httpBackend.flush(); - - expect(controller.vnApp.showSuccess).toHaveBeenCalled(); - }); - }); - describe('loadData()', () => { it(`should perform a get query to store the invoice in data into the controller`, () => { const id = 1; @@ -39,4 +29,81 @@ describe('vnInvoiceOutDescriptor', () => { expect(controller.invoiceOut).toEqual(response); }); }); + + describe('createPdfInvoice()', () => { + it('should make a query to the createPdf() endpoint and show a success snackbar', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.invoiceOut = invoiceOut; + + $httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond(); + $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond(); + controller.createPdfInvoice(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('showCsvInvoice()', () => { + it('should make a query to the csv invoice download endpoint and show a message snackbar', () => { + jest.spyOn(window, 'open').mockReturnThis(); + + controller.invoiceOut = invoiceOut; + + const expectedParams = { + invoiceId: invoiceOut.id, + recipientId: invoiceOut.client.id + }; + const serializedParams = $httpParamSerializer(expectedParams); + const expectedPath = `api/csv/invoice/download?${serializedParams}`; + controller.showCsvInvoice(); + + expect(window.open).toHaveBeenCalledWith(expectedPath); + }); + }); + + describe('sendPdfInvoice()', () => { + it('should make a query to the email invoice endpoint and show a message snackbar', () => { + jest.spyOn(controller.vnApp, 'showMessage'); + + controller.invoiceOut = invoiceOut; + + const $data = {email: 'brucebanner@gothamcity.com'}; + const expectedParams = { + invoiceId: invoiceOut.id, + recipient: $data.email, + recipientId: invoiceOut.client.id + }; + const serializedParams = $httpParamSerializer(expectedParams); + + $httpBackend.expectGET(`email/invoice?${serializedParams}`).respond(); + controller.sendPdfInvoice($data); + $httpBackend.flush(); + + expect(controller.vnApp.showMessage).toHaveBeenCalled(); + }); + }); + + describe('sendCsvInvoice()', () => { + it('should make a query to the csv invoice send endpoint and show a message snackbar', () => { + jest.spyOn(controller.vnApp, 'showMessage'); + + controller.invoiceOut = invoiceOut; + + const $data = {email: 'brucebanner@gothamcity.com'}; + const expectedParams = { + invoiceId: invoiceOut.id, + recipient: $data.email, + recipientId: invoiceOut.client.id + }; + const serializedParams = $httpParamSerializer(expectedParams); + + $httpBackend.expectGET(`csv/invoice/send?${serializedParams}`).respond(); + controller.sendCsvInvoice($data); + $httpBackend.flush(); + + expect(controller.vnApp.showMessage).toHaveBeenCalled(); + }); + }); }); diff --git a/modules/invoiceOut/front/descriptor/locale/es.yml b/modules/invoiceOut/front/descriptor/locale/es.yml index 3430fe4e7..2f377ebdf 100644 --- a/modules/invoiceOut/front/descriptor/locale/es.yml +++ b/modules/invoiceOut/front/descriptor/locale/es.yml @@ -2,8 +2,10 @@ Volume exceded: Volumen excedido Volume: Volumen Client card: Ficha del cliente Invoice ticket list: Listado de tickets de la factura -Show invoice PDF: Ver factura en PDF -Send invoice PDF: Enviar factura en PDF +Show invoice...: Ver factura... +Send invoice...: Enviar factura... +Send PDF invoice: Enviar factura en PDF +Send CSV invoice: Enviar factura en CSV Delete Invoice: Eliminar factura Clone Invoice: Clonar factura InvoiceOut deleted: Factura eliminada diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index 555246f9f..ae5642cf3 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -2,6 +2,7 @@ icon="more_vert" vn-popover="menu"> + Add turn - - Show Delivery Note + Show Delivery Note... + + + + Show as PDF + + + Show as CSV + + + - - Send Delivery Note + Send Delivery Note... + + + + + Send PDF + + + Send CSV + + + @@ -133,13 +163,39 @@ - - - + + + + Are you sure you want to send it? + + + + + + + + + + + + + Are you sure you want to send it? + + + + + + + + diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 96475b7b9..142f44989 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -115,17 +115,32 @@ class Controller extends Section { }); } - showDeliveryNote() { + showPdfDeliveryNote() { this.vnReport.show('delivery-note', { recipientId: this.ticket.client.id, ticketId: this.id, }); } - sendDeliveryNote() { + showCsvDeliveryNote() { + this.vnReport.showCsv('delivery-note', { + recipientId: this.ticket.client.id, + ticketId: this.id, + }); + } + + sendPdfDeliveryNote($data) { return this.vnEmail.send('delivery-note', { recipientId: this.ticket.client.id, - recipient: this.ticket.client.email, + recipient: $data.email, + ticketId: this.id + }); + } + + sendCsvDeliveryNote($data) { + return this.vnEmail.sendCsv('delivery-note', { + recipientId: this.ticket.client.id, + recipient: $data.email, ticketId: this.id }); } @@ -227,7 +242,7 @@ class Controller extends Section { .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); } - createInvoicePdf() { + createPdfInvoice() { const invoiceId = this.ticket.invoiceOut.id; return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`) .then(() => this.reload()) diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index eabb77235..e9486bcd0 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -2,6 +2,7 @@ import './index.js'; describe('Ticket Component vnTicketDescriptorMenu', () => { let $httpBackend; + let $httpParamSerializer; let controller; let $state; @@ -25,8 +26,9 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { beforeEach(ngModule('ticket')); - beforeEach(inject(($componentController, _$httpBackend_, _$state_) => { + beforeEach(inject(($componentController, _$httpBackend_, _$httpParamSerializer_, _$state_) => { $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; $state = _$state_; $state.params.id = 16; $state.getCurrentPath = () => [null, {state: {name: 'ticket'}}]; @@ -104,36 +106,74 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { }); }); - describe('showDeliveryNote()', () => { + describe('showPdfDeliveryNote()', () => { it('should open a new window showing a delivery note PDF document', () => { - jest.spyOn(controller.vnReport, 'show'); + jest.spyOn(window, 'open').mockReturnThis(); - window.open = jasmine.createSpy('open'); - const params = { - recipientId: ticket.client.id, - ticketId: ticket.id + const expectedParams = { + ticketId: ticket.id, + recipientId: ticket.client.id }; - controller.showDeliveryNote(); + const serializedParams = $httpParamSerializer(expectedParams); + const expectedPath = `api/report/delivery-note?${serializedParams}`; + controller.showPdfDeliveryNote(); - expect(controller.vnReport.show).toHaveBeenCalledWith('delivery-note', params); + expect(window.open).toHaveBeenCalledWith(expectedPath); }); }); - describe('sendDeliveryNote()', () => { + describe('sendPdfDeliveryNote()', () => { it('should make a query and call vnApp.showMessage()', () => { jest.spyOn(controller.vnEmail, 'send'); + const $data = {email: 'brucebanner@gothamcity.com'}; const params = { - recipient: ticket.client.email, + recipient: $data.email, recipientId: ticket.client.id, ticketId: ticket.id }; - controller.sendDeliveryNote(); + controller.sendPdfDeliveryNote($data); expect(controller.vnEmail.send).toHaveBeenCalledWith('delivery-note', params); }); }); + describe('showCsvDeliveryNote()', () => { + it('should make a query to the csv delivery-note download endpoint and show a message snackbar', () => { + jest.spyOn(window, 'open').mockReturnThis(); + + const expectedParams = { + ticketId: ticket.id, + recipientId: ticket.client.id + }; + const serializedParams = $httpParamSerializer(expectedParams); + const expectedPath = `api/csv/delivery-note/download?${serializedParams}`; + controller.showCsvDeliveryNote(); + + expect(window.open).toHaveBeenCalledWith(expectedPath); + }); + }); + + describe('sendCsvDeliveryNote()', () => { + it('should make a query to the csv delivery-note send endpoint and show a message snackbar', () => { + jest.spyOn(controller.vnApp, 'showMessage'); + + const $data = {email: 'brucebanner@gothamcity.com'}; + const expectedParams = { + ticketId: ticket.id, + recipient: $data.email, + recipientId: ticket.client.id, + }; + const serializedParams = $httpParamSerializer(expectedParams); + + $httpBackend.expectGET(`csv/delivery-note/send?${serializedParams}`).respond(); + controller.sendCsvDeliveryNote($data); + $httpBackend.flush(); + + expect(controller.vnApp.showMessage).toHaveBeenCalled(); + }); + }); + describe('makeInvoice()', () => { it('should make a query and call $state.reload() method', () => { jest.spyOn(controller, 'reload').mockReturnThis(); @@ -149,13 +189,13 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { }); }); - describe('createInvoicePdf()', () => { + describe('createPdfInvoice()', () => { it('should make a query and show a success snackbar', () => { jest.spyOn(controller.vnApp, 'showSuccess'); $httpBackend.whenGET(`Tickets/16`).respond(); $httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/createPdf`).respond(); - controller.createInvoicePdf(); + controller.createPdfInvoice(); $httpBackend.flush(); expect(controller.vnApp.showSuccess).toHaveBeenCalled(); diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml new file mode 100644 index 000000000..1f4ee710c --- /dev/null +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -0,0 +1,8 @@ +Show Delivery Note...: Ver albarán... +Send Delivery Note...: Enviar albarán... +Show as PDF: Ver como PDF +Show as CSV: Ver como CSV +Send PDF: Enviar PDF +Send CSV: Enviar CSV +Send CSV Delivery Note: Enviar albarán en CSV +Send PDF Delivery Note: Enviar albarán en PDF \ No newline at end of file diff --git a/modules/ticket/front/descriptor/locale/es.yml b/modules/ticket/front/descriptor/locale/es.yml index c72b2e923..f56f29f92 100644 --- a/modules/ticket/front/descriptor/locale/es.yml +++ b/modules/ticket/front/descriptor/locale/es.yml @@ -8,8 +8,6 @@ Add stowaway: Añadir polizón Delete stowaway: Eliminar polizón Are you sure you want to delete this stowaway?: ¿Seguro que quieres eliminar este polizón? Are you sure you want to send it?: ¿Seguro que quieres enviarlo? -Show Delivery Note: Ver albarán -Send Delivery Note: Enviar albarán Show pallet report: Ver hoja de pallet Change shipped hour: Cambiar hora de envío Shipped hour: Hora de envío diff --git a/print/core/email.js b/print/core/email.js index f201be9a8..620c1e083 100644 --- a/print/core/email.js +++ b/print/core/email.js @@ -37,20 +37,30 @@ class Email extends Component { return userTranslations.subject; } - async send() { + /** + * @param {Object} [options] - Additional options + * @param {Boolean} [options.overrideAttachments] - Overrides default PDF attachments + * @param {Array} [options.attachments] - Array containing attachment objects + * @return {Promise} SMTP Promise + */ + async send(options = {}) { const instance = this.build(); const rendered = await this.render(); const attachments = []; const getAttachments = async(componentPath, files) => { for (file of files) { const fileCopy = Object.assign({}, file); + const fileName = fileCopy.filename; + + if (options.overrideAttachments && !fileName.includes('.png')) continue; + if (fileCopy.cid) { const templatePath = `${componentPath}/${file.path}`; const fullFilePath = path.resolve(__dirname, templatePath); fileCopy.path = path.resolve(__dirname, fullFilePath); } else { - const reportName = fileCopy.filename.replace('.pdf', ''); + const reportName = fileName.replace('.pdf', ''); const report = new Report(reportName, this.args); fileCopy.content = await report.toPdfStream(); } @@ -71,9 +81,14 @@ class Email extends Component { if (this.attachments) await getAttachments(this.path, this.attachments); + if (options.attachments) { + for (let attachment of options.attachments) + attachments.push(attachment); + } + const localeSubject = await this.getSubject(); const replyTo = this.args.replyTo || this.args.auth.email; - const options = { + const mailOptions = { to: this.args.recipient, replyTo: replyTo, subject: localeSubject, @@ -81,7 +96,7 @@ class Email extends Component { attachments: attachments }; - return smtp.send(options); + return smtp.send(mailOptions); } } diff --git a/print/core/router.js b/print/core/router.js index feaea214b..c0f20dd9a 100644 --- a/print/core/router.js +++ b/print/core/router.js @@ -8,9 +8,10 @@ module.exports = app => { const methods = []; // Get all methods - methodsDir.forEach(method => { - methods.push(method.replace('.js', '')); - }); + for (let method of methodsDir) { + if (method.includes('.js')) + methods.push(method.replace('.js', '')); + } // Auth middleware const paths = []; diff --git a/print/methods/csv.js b/print/methods/csv.js new file mode 100644 index 000000000..4f4cdf2af --- /dev/null +++ b/print/methods/csv.js @@ -0,0 +1,31 @@ +module.exports = app => { + app.use('/api/csv/delivery-note', require('./csv/delivery-note')(app)); + app.use('/api/csv/invoice', require('./csv/invoice')(app)); + + app.toCSV = function toCSV(rows) { + const [columns] = rows; + let content = Object.keys(columns).join('\t'); + for (let row of rows) { + const values = Object.values(row); + const finalValues = values.map(value => { + if (value instanceof Date) return formatDate(value); + if (value === null) return ''; + return value; + }); + content += '\n'; + content += finalValues.join('\t'); + } + return content; + }; + + function formatDate(date) { + return new Intl.DateTimeFormat('es', { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).format(date); + } +}; diff --git a/print/methods/csv/delivery-note/index.js b/print/methods/csv/delivery-note/index.js new file mode 100644 index 000000000..9ef0e33fa --- /dev/null +++ b/print/methods/csv/delivery-note/index.js @@ -0,0 +1,82 @@ +const express = require('express'); +const router = new express.Router(); +const path = require('path'); +const db = require('../../../core/database'); +const sqlPath = path.join(__dirname, 'sql'); + +module.exports = app => { + router.get('/preview', async function(req, res, next) { + try { + const reqArgs = req.args; + if (!reqArgs.ticketId) + throw new Error('The argument ticketId is required'); + + const ticketId = reqArgs.ticketId; + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]); + const content = app.toCSV(sales); + const fileName = `ticket_${ticketId}.csv`; + + res.setHeader('Content-type', 'application/json; charset=utf-8'); + res.setHeader('Content-Disposition', `inline; filename="${fileName}"`); + res.end(content); + } catch (error) { + next(error); + } + }); + + router.get('/download', async function(req, res, next) { + try { + const reqArgs = req.args; + if (!reqArgs.ticketId) + throw new Error('The argument ticketId is required'); + + const ticketId = reqArgs.ticketId; + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]); + const content = app.toCSV(sales); + const fileName = `ticket_${ticketId}.csv`; + + res.setHeader('Content-type', 'text/csv'); + res.setHeader('Content-Disposition', `inline; filename="${fileName}"`); + res.end(content); + } catch (error) { + next(error); + } + }); + + const Email = require('../../../core/email'); + router.get('/send', async function(req, res, next) { + try { + const reqArgs = req.args; + if (!reqArgs.ticketId) + throw new Error('The argument ticketId is required'); + + const ticketId = reqArgs.ticketId; + const ticket = await db.findOneFromDef(`${sqlPath}/ticket`, [ticketId]); + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]); + + const args = Object.assign({ + ticketId: (String(ticket.id)), + recipientId: ticket.clientFk, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail + }, reqArgs); + + const content = app.toCSV(sales); + const fileName = `ticket_${ticketId}.csv`; + const email = new Email('delivery-note', args); + await email.send({ + overrideAttachments: true, + attachments: [{ + filename: fileName, + content: content + }] + }); + + res.status(200).json({message: 'ok'}); + } catch (error) { + next(error); + } + }); + + return router; +}; diff --git a/print/methods/csv/delivery-note/sql/sales.sql b/print/methods/csv/delivery-note/sql/sales.sql new file mode 100644 index 000000000..e5b419571 --- /dev/null +++ b/print/methods/csv/delivery-note/sql/sales.sql @@ -0,0 +1,35 @@ +SELECT io.ref Invoice, + io.issued InvoiceDate, + s.ticketFk Ticket, + s.itemFk Item, + s.concept Description, + i.size, + i.subName Producer, + s.quantity Quantity, + s.price Price, + s.discount Discount, + s.created Created, + tc.code Taxcode, + tc.description TaxDescription, + i.tag5, + i.value5, + i.tag6, + i.value6, + i.tag7, + i.value7, + i.tag8, + i.value8, + i.tag9, + i.value9, + i.tag10, + i.value10 +FROM vn.sale s + JOIN vn.ticket t ON t.id = s.ticketFk + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.supplier s2 ON s2.id = t.companyFk + JOIN vn.itemTaxCountry itc ON itc.itemFk = i.id + AND itc.countryFk = s2.countryFk + JOIN vn.taxClass tc ON tc.id = itc.taxClassFk + LEFT JOIN vn.invoiceOut io ON io.id = t.refFk +WHERE s.ticketFk = ? +ORDER BY s.ticketFk, s.created \ No newline at end of file diff --git a/print/methods/csv/delivery-note/sql/ticket.sql b/print/methods/csv/delivery-note/sql/ticket.sql new file mode 100644 index 000000000..b80c7c42c --- /dev/null +++ b/print/methods/csv/delivery-note/sql/ticket.sql @@ -0,0 +1,9 @@ +SELECT + t.id, + t.clientFk, + c.email recipient, + eu.email salesPersonEmail +FROM ticket t + JOIN client c ON c.id = t.clientFk + LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk +WHERE t.id = ? \ No newline at end of file diff --git a/print/methods/csv/invoice/index.js b/print/methods/csv/invoice/index.js new file mode 100644 index 000000000..8f325be02 --- /dev/null +++ b/print/methods/csv/invoice/index.js @@ -0,0 +1,82 @@ +const express = require('express'); +const router = new express.Router(); +const path = require('path'); +const db = require('../../../core/database'); +const sqlPath = path.join(__dirname, 'sql'); + +module.exports = app => { + router.get('/preview', async function(req, res, next) { + try { + const reqArgs = req.args; + if (!reqArgs.invoiceId) + throw new Error('The argument invoiceId is required'); + + const invoiceId = reqArgs.invoiceId; + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]); + const content = app.toCSV(sales); + const fileName = `invoice_${invoiceId}.csv`; + + res.setHeader('Content-type', 'application/json; charset=utf-8'); + res.setHeader('Content-Disposition', `inline; filename="${fileName}"`); + res.end(content); + } catch (error) { + next(error); + } + }); + + router.get('/download', async function(req, res, next) { + try { + const reqArgs = req.args; + if (!reqArgs.invoiceId) + throw new Error('The argument invoiceId is required'); + + const invoiceId = reqArgs.invoiceId; + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]); + const content = app.toCSV(sales); + const fileName = `invoice_${invoiceId}.csv`; + + res.setHeader('Content-type', 'text/csv'); + res.setHeader('Content-Disposition', `inline; filename="${fileName}"`); + res.end(content); + } catch (error) { + next(error); + } + }); + + const Email = require('../../../core/email'); + router.get('/send', async function(req, res, next) { + try { + const reqArgs = req.args; + if (!reqArgs.invoiceId) + throw new Error('The argument invoiceId is required'); + + const invoiceId = reqArgs.invoiceId; + const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [invoiceId]); + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]); + + const args = Object.assign({ + invoiceId: (String(invoice.id)), + recipientId: invoice.clientFk, + recipient: invoice.recipient, + replyTo: invoice.salesPersonEmail + }, reqArgs); + + const content = app.toCSV(sales); + const fileName = `invoice_${invoiceId}.csv`; + const email = new Email('invoice', args); + await email.send({ + overrideAttachments: true, + attachments: [{ + filename: fileName, + content: content + }] + }); + + res.status(200).json({message: 'ok'}); + } catch (error) { + next(error); + } + }); + + return router; +}; diff --git a/print/methods/csv/invoice/sql/invoice.sql b/print/methods/csv/invoice/sql/invoice.sql new file mode 100644 index 000000000..853aaddc0 --- /dev/null +++ b/print/methods/csv/invoice/sql/invoice.sql @@ -0,0 +1,9 @@ +SELECT + io.id, + io.clientFk, + c.email recipient, + eu.email salesPersonEmail +FROM invoiceOut io + JOIN client c ON c.id = io.clientFk + LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk +WHERE io.id = ? \ No newline at end of file diff --git a/print/methods/csv/invoice/sql/sales.sql b/print/methods/csv/invoice/sql/sales.sql new file mode 100644 index 000000000..34b5af1f7 --- /dev/null +++ b/print/methods/csv/invoice/sql/sales.sql @@ -0,0 +1,35 @@ +SELECT io.ref Invoice, + io.issued InvoiceDate, + s.ticketFk Ticket, + s.itemFk Item, + s.concept Description, + i.size, + i.subName Producer, + s.quantity Quantity, + s.price Price, + s.discount Discount, + s.created Created, + tc.code Taxcode, + tc.description TaxDescription, + i.tag5, + i.value5, + i.tag6, + i.value6, + i.tag7, + i.value7, + i.tag8, + i.value8, + i.tag9, + i.value9, + i.tag10, + i.value10 +FROM sale s + JOIN ticket t ON t.id = s.ticketFk + JOIN item i ON i.id = s.itemFk + JOIN supplier s2 ON s2.id = t.companyFk + JOIN itemTaxCountry itc ON itc.itemFk = i.id + AND itc.countryFk = s2.countryFk + JOIN taxClass tc ON tc.id = itc.taxClassFk + JOIN invoiceOut io ON io.ref = t.refFk +WHERE io.id = ? +ORDER BY s.ticketFk, s.created \ No newline at end of file diff --git a/print/templates/reports/delivery-note/delivery-note.js b/print/templates/reports/delivery-note/delivery-note.js index 549759651..9b3328d05 100755 --- a/print/templates/reports/delivery-note/delivery-note.js +++ b/print/templates/reports/delivery-note/delivery-note.js @@ -29,6 +29,9 @@ module.exports = { const hash = md5(this.signature.id.toString()).substring(0, 3); const file = `${config.storage.root}/${hash}/${this.signature.id}.png`; + + if (!fs.existsSync(file)) return null; + const src = fs.readFileSync(file); const base64 = Buffer.from(src, 'utf8').toString('base64'); diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index 3e8734306..b56a5533c 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -95,7 +95,6 @@ module.exports = { }, ticketSubtotal(ticket) { let subTotal = 0.00; - console.log(ticket.sales); for (let sale of ticket.sales) subTotal += this.saleImport(sale);