diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 8f667ca32..efe7906ce 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -8,7 +8,7 @@ ALTER TABLE `vn`.`ticket` AUTO_INCREMENT = 1; INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`) VALUES - ('TOTALLY_SECURE_TOKEN', '1209600', CURDATE(), 66); + ('DEFAULT_TOKEN', '1209600', CURDATE(), 66); INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) @@ -104,17 +104,17 @@ INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`) (3, 'GBP', 'Libra', 1), (4, 'JPY', 'Yen Japones', 1); -INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`) +INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`) VALUES - (1, 'España', 1, 'ES', 1, 24, 4), - (2, 'Italia', 1, 'IT', 1, 27, 4), - (3, 'Alemania', 1, 'DE', 1, 22, 4), - (4, 'Rumania', 1, 'RO', 1, 24, 4), - (5, 'Holanda', 1, 'NL', 1, 18, 4), - (8, 'Portugal', 1, 'PT', 1, 27, 4), - (13,'Ecuador', 0, 'EC', 1, 24, 2), - (19,'Francia', 1, 'FR', 1, 27, 4), - (30,'Canarias', 1, 'IC', 1, 24, 4); + (1, 'España', 1, 'ES', 1, 24, 4, 0, 1), + (2, 'Italia', 1, 'IT', 1, 27, 4, 0, 1), + (3, 'Alemania', 1, 'DE', 1, 22, 4, 0, 1), + (4, 'Rumania', 1, 'RO', 1, 24, 4, 0, 1), + (5, 'Holanda', 1, 'NL', 1, 18, 4, 0, 1), + (8, 'Portugal', 1, 'PT', 1, 27, 4, 0, 1), + (13,'Ecuador', 0, 'EC', 1, 24, 2, 1, 2), + (19,'Francia', 1, 'FR', 1, 27, 4, 0, 1), + (30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2); INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`) VALUES @@ -243,7 +243,7 @@ INSERT INTO `vn`.`province`(`id`, `name`, `countryFk`, `autonomyFk`, `warehouseF VALUES (1, 'Province one', 1, 1, NULL), (2, 'Province two', 1, 1, NULL), - (3, 'Province three', 1, 2, NULL), + (3, 'Province three', 30, 2, NULL), (4, 'Province four', 2, 3, NULL), (5, 'Province five', 13, 4, NULL); @@ -455,7 +455,8 @@ INSERT INTO `vn`.`creditInsurance`(`id`, `creditClassification`, `credit`, `crea INSERT INTO `vn`.`companyGroup`(`id`, `code`) VALUES - (1, 'Wayne Industries'); + (1, 'wayneIndustries'), + (2, 'Verdnatura'); INSERT INTO `vn`.`bankEntity`(`id`, `countryFk`, `name`, `bic`) VALUES @@ -466,13 +467,13 @@ INSERT INTO `vn`.`supplierAccount`(`id`, `supplierFk`, `iban`, `bankEntityFk`) VALUES (241, 442, 'ES111122333344111122221111', 128); -INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `phytosanitary`) +INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary`) VALUES - (69 , 'CCs', NULL, 30, NULL, 0, NULL, NULL), - (442 , 'VNL', 241, 30, 2 , 1, NULL, 'VNL Company - Plant passport'), - (567 , 'VNH', NULL, 30, NULL, 4, NULL, 'VNH Company - Plant passport'), - (791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', NULL), - (1381, 'ORN', NULL, 30, NULL, 7, NULL, 'ORN Company - Plant passport'); + (69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL), + (442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport'), + (567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport'), + (791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL), + (1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport'); INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`) VALUES @@ -486,7 +487,9 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF ('A', 'Global nacional', 1, 'NATIONAL', 0), ('T', 'Española rapida', 1, 'NATIONAL', 0), ('V', 'Intracomunitaria global', 0, 'CEE', 1), - ('M', 'Múltiple nacional', 1, 'NATIONAL', 0); + ('M', 'Múltiple nacional', 1, 'NATIONAL', 0), + ('E', 'Exportación rápida', 0, 'WORLD', 0); +; INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`) VALUES diff --git a/front/core/components/check/style.scss b/front/core/components/check/style.scss index b84b61ce5..add593570 100644 --- a/front/core/components/check/style.scss +++ b/front/core/components/check/style.scss @@ -43,4 +43,10 @@ &.disabled.checked > .btn { background-color: $color-font-secondary; } + + &[triple-state]:not(.indeterminate):not(.checked) { + .btn { + background-color: lighten($color-alert, 5%); + } + } } diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index cb0304b34..b0b4d90dc 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -171,9 +171,10 @@ export default class SmartTable extends Component { if (field.length === 2) sortType = field[1]; + const priority = this.sortCriteria.length + 1; const column = this.columns.find(column => column.field == fieldName); if (column) { - this.sortCriteria.push({field: fieldName, sortType: sortType}); + this.sortCriteria.push({field: fieldName, sortType: sortType, priority: priority}); const isASC = sortType == 'ASC'; const isDESC = sortType == 'DESC'; @@ -187,6 +188,8 @@ export default class SmartTable extends Component { column.element.classList.remove('desc'); column.element.classList.add('asc'); } + + this.setPriority(column.element, priority); } } } @@ -241,9 +244,13 @@ export default class SmartTable extends Component { const isDESC = existingCriteria && existingCriteria.sortType == 'DESC'; if (!existingCriteria) { - this.sortCriteria.push({field: field, sortType: 'ASC'}); + const priority = this.sortCriteria.length + 1; + + this.sortCriteria.push({field: field, sortType: 'ASC', priority: priority}); element.classList.remove('desc'); element.classList.add('asc'); + + this.setPriority(element, priority); } if (isDESC) { @@ -252,6 +259,8 @@ export default class SmartTable extends Component { }), 1); element.classList.remove('desc'); element.classList.remove('asc'); + + element.querySelector('sort-priority').remove(); } if (isASC) { @@ -260,9 +269,29 @@ export default class SmartTable extends Component { element.classList.add('desc'); } + let priority = 0; + for (const criteria of this.sortCriteria) { + const column = this.columns.find(column => column.field == criteria.field); + if (column) { + criteria.priority = priority; + priority++; + + column.element.querySelector('sort-priority').remove(); + + this.setPriority(column.element, priority); + } + } + this.applySort(); } + setPriority(column, priority) { + const sortPriority = document.createElement('sort-priority'); + sortPriority.setAttribute('class', 'sort-priority'); + sortPriority.innerHTML = priority; + column.appendChild(sortPriority); + } + displaySearch() { const header = this.element.querySelector('thead > tr'); if (!header) return; diff --git a/front/core/components/smart-table/index.spec.js b/front/core/components/smart-table/index.spec.js index 94edd45bb..720e24c7e 100644 --- a/front/core/components/smart-table/index.spec.js +++ b/front/core/components/smart-table/index.spec.js @@ -96,9 +96,10 @@ describe('Component smartTable', () => { expect(firstSortCriteria.field).toEqual('id'); expect(firstSortCriteria.sortType).toEqual('ASC'); + expect(firstSortCriteria.priority).toEqual(1); }); - it('should insert two new objects to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => { + it('should add new entries to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => { const element = document.createElement('div'); controller.model = {order: 'test1, id DESC'}; controller.columns = [ @@ -114,8 +115,11 @@ describe('Component smartTable', () => { expect(firstSortCriteria.field).toEqual('test1'); expect(firstSortCriteria.sortType).toEqual('ASC'); + expect(firstSortCriteria.priority).toEqual(1); + expect(secondSortCriteria.field).toEqual('id'); expect(secondSortCriteria.sortType).toEqual('DESC'); + expect(secondSortCriteria.priority).toEqual(2); }); }); diff --git a/front/core/components/smart-table/style.scss b/front/core/components/smart-table/style.scss index 1e882f679..bf1c14082 100644 --- a/front/core/components/smart-table/style.scss +++ b/front/core/components/smart-table/style.scss @@ -9,7 +9,7 @@ smart-table { } th[field][number] { - & > :before { + & > span:before { vertical-align: middle; font-family: 'Material Icons'; content: 'arrow_downward'; @@ -19,26 +19,26 @@ smart-table { } - &.asc > :before, &.desc > :before { + &.asc > span:before, &.desc > span:before { color: $color-font; opacity: 1; } - &.asc > :before { + &.asc > span:before { content: 'arrow_upward'; } - &.desc > :before { + &.desc > span:before { content: 'arrow_downward'; } - &:hover > :before { + &:hover > span:before { opacity: 1; } } th[field]:not([number]) { - & > :after { + & > span:after { vertical-align: middle; font-family: 'Material Icons'; content: 'arrow_downward'; @@ -48,20 +48,20 @@ smart-table { } - &.asc > :after, &.desc > :after { + &.asc > span:after, &.desc > span:after { color: $color-font; opacity: 1; } - &.asc > :after { + &.asc > span:after { content: 'arrow_upward'; } - &.desc > :after { + &.desc > span:after { content: 'arrow_downward'; } - &:hover > :after { + &:hover > span:after { opacity: 1; } } @@ -143,4 +143,16 @@ smart-table { flex: initial; width: 33% } +} +.sort-priority { + background-color: $color-font-bg-marginal; + border-radius: 50%; + padding: 2px 5px; + display: inline-block; + text-align: center; + width: 7px; + height: 13px; + + font-size: 10px; + color: $color-font-bg } \ No newline at end of file diff --git a/modules/entry/front/basic-data/index.html b/modules/entry/front/basic-data/index.html index 4b7661a8f..f75834045 100644 --- a/modules/entry/front/basic-data/index.html +++ b/modules/entry/front/basic-data/index.html @@ -35,6 +35,13 @@ {{::agencyModeName}} - {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}}) → {{::warehouseOutName}} ({{::landed | date: 'dd/MM/yyyy'}}) + + + + @@ -121,4 +128,94 @@ ng-click="watcher.loadOriginalData()"> - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + ID + Agency + Warehouse Out + Warehouse In + Shipped + Landed + + + + + + + {{::travel.id}} + + + {{::travel.agency.name}} + {{::travel.warehouseOut.name}} + {{::travel.warehouseIn.name}} + {{::travel.shipped | date: 'dd/MM/yyyy'}} + {{::travel.landed | date: 'dd/MM/yyyy'}} + + + + + + + + \ No newline at end of file diff --git a/modules/entry/front/basic-data/index.js b/modules/entry/front/basic-data/index.js index 141a365fa..80870c3f3 100644 --- a/modules/entry/front/basic-data/index.js +++ b/modules/entry/front/basic-data/index.js @@ -1,10 +1,68 @@ import ngModule from '../module'; import Section from 'salix/components/section'; +import './style.scss'; +class Controller extends Section { + showFilterDialog(travel) { + this.activeTravel = travel; + this.travelFilterParams = {}; + this.travelFilter = { + include: [ + { + relation: 'agency', + scope: { + fields: ['name'] + } + }, + { + relation: 'warehouseIn', + scope: { + fields: ['name'] + } + }, + { + relation: 'warehouseOut', + scope: { + fields: ['name'] + } + } + ] + }; + + this.$.filterDialog.show(); + } + + selectTravel(id) { + this.entry.travelFk = id; + this.$.filterDialog.hide(); + } + + filter() { + const filter = this.travelFilter; + const params = this.travelFilterParams; + const where = {}; + for (let key in params) { + const value = params[key]; + if (!value) continue; + + switch (key) { + case 'agencyFk': + case 'warehouseInFk': + case 'warehouseOutFk': + case 'shipped': + case 'landed': + where[key] = value; + } + } + + filter.where = where; + this.$.travelsModel.applyFilter(filter); + } +} ngModule.vnComponent('vnEntryBasicData', { template: require('./index.html'), - controller: Section, bindings: { entry: '<' - } + }, + controller: Controller }); diff --git a/modules/entry/front/basic-data/style.scss b/modules/entry/front/basic-data/style.scss new file mode 100644 index 000000000..508aa9091 --- /dev/null +++ b/modules/entry/front/basic-data/style.scss @@ -0,0 +1,3 @@ +.travelFilter{ + width: 950px; +} diff --git a/modules/invoiceIn/front/search-panel/index.html b/modules/invoiceIn/front/search-panel/index.html index d26bc063e..609fa56d8 100644 --- a/modules/invoiceIn/front/search-panel/index.html +++ b/modules/invoiceIn/front/search-panel/index.html @@ -21,6 +21,20 @@ ng-model="filter.fi"> + + + + {{::id}} - {{::nickname}} + + + { Self.remoteMethodCtx('createPdf', { @@ -57,39 +57,37 @@ module.exports = Self => { hasPdf: true }, myOptions); - const response = got.stream(`${origin}/api/report/invoice`, { - searchParams: { + return axios.get(`${origin}/api/report/invoice`, { + responseType: 'stream', + params: { authorization: auth.id, invoiceId: id } - }); + }).then(async response => { + const issued = invoiceOut.issued; + const year = issued.getFullYear().toString(); + const month = (issued.getMonth() + 1).toString(); + const day = issued.getDate().toString(); - const issued = invoiceOut.issued; - const year = issued.getFullYear().toString(); - const month = (issued.getMonth() + 1).toString(); - const day = issued.getDate().toString(); + const container = await models.InvoiceContainer.container(year); + const rootPath = container.client.root; + const fileName = `${year}${invoiceOut.ref}.pdf`; + const src = path.join(rootPath, year, month, day); + fileSrc = path.join(src, fileName); - const container = await models.InvoiceContainer.container(year); - const rootPath = container.client.root; - const fileName = `${year}${invoiceOut.ref}.pdf`; - const src = path.join(rootPath, year, month, day); - fileSrc = path.join(src, fileName); + await fs.mkdir(src, {recursive: true}); - await fs.mkdir(src, {recursive: true}); + if (tx) await tx.commit(); - if (tx) await tx.commit(); + response.data.pipe(fs.createWriteStream(fileSrc)); + }).catch(async e => { + if (fs.existsSync(fileSrc)) + await fs.unlink(fileSrc); - const writeStream = fs.createWriteStream(fileSrc); - writeStream.on('open', () => response.pipe(writeStream)); - writeStream.on('finish', () => writeStream.end()); - - return new Promise(resolve => { - writeStream.on('close', () => resolve(invoiceOut)); + throw e; }); } catch (e) { if (tx) await tx.rollback(); - if (fs.existsSync(fileSrc)) - await fs.unlink(fileSrc); throw e; } }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js index 2f503d11c..7600f065f 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js @@ -1,24 +1,28 @@ const models = require('vn-loopback/server/server').models; -const got = require('got'); +const LoopBackContext = require('loopback-context'); const fs = require('fs-extra'); +const axios = require('axios'); describe('InvoiceOut createPdf()', () => { const userId = 1; - const ctx = { - req: { - - accessToken: {userId: userId}, - headers: {origin: 'http://localhost:5000'}, - } + const activeCtx = { + accessToken: {userId: userId, id: 'DEFAULT_TOKEN'}, + headers: {origin: 'http://localhost:5000'} }; + const ctx = {req: activeCtx}; it('should create a new PDF file and set true the hasPdf property', async() => { const invoiceId = 1; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); const response = { - pipe: () => {}, - on: () => {}, + data: { + pipe: () => {}, + on: () => {}, + } }; - spyOn(got, 'stream').and.returnValue(response); + spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(response))); spyOn(models.InvoiceContainer, 'container').and.returnValue({ client: {root: '/path'} }); @@ -32,9 +36,10 @@ describe('InvoiceOut createPdf()', () => { const options = {transaction: tx}; try { - const result = await models.InvoiceOut.createPdf(ctx, invoiceId, options); + await models.InvoiceOut.createPdf(ctx, invoiceId, options); + const invoiceOut = await models.InvoiceOut.findById(invoiceId, null, options); - expect(result.hasPdf).toBe(true); + expect(invoiceOut.hasPdf).toBe(true); await tx.rollback(); } catch (e) { diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js index b6e7e0261..7738845f9 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.js +++ b/modules/invoiceOut/front/descriptor-menu/index.js @@ -19,6 +19,10 @@ class Controller extends Section { this.id = value.id; } + get hasInvoicing() { + return this.aclService.hasAny(['invoicing']); + } + loadData() { const filter = { include: [ @@ -51,8 +55,13 @@ class Controller extends Section { deleteInvoiceOut() { return this.$http.post(`InvoiceOuts/${this.invoiceOut.id}/delete`) - .then(() => this.$state.go('invoiceOut.index')) - .then(() => this.$state.reload()) + .then(() => { + const isInsideInvoiceOut = this.$state.current.name.startsWith('invoiceOut'); + if (isInsideInvoiceOut) + this.$state.go('invoiceOut.index'); + else + this.$state.reload(); + }) .then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted'))); } diff --git a/modules/invoiceOut/front/descriptor-menu/index.spec.js b/modules/invoiceOut/front/descriptor-menu/index.spec.js index 19822f094..fced12e0d 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.spec.js +++ b/modules/invoiceOut/front/descriptor-menu/index.spec.js @@ -50,6 +50,35 @@ describe('vnInvoiceOutDescriptorMenu', () => { }); }); + describe('deleteInvoiceOut()', () => { + it(`should make a query and call showSuccess()`, () => { + controller.invoiceOut = invoiceOut; + controller.$state.reload = jest.fn(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/delete`).respond(); + controller.deleteInvoiceOut(); + $httpBackend.flush(); + + expect(controller.$state.reload).toHaveBeenCalled(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + + it(`should make a query and call showSuccess() after state.go if the state wasn't in invoiceOut module`, () => { + controller.invoiceOut = invoiceOut; + jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); + jest.spyOn(controller.vnApp, 'showSuccess'); + controller.$state.current.name = 'invoiceOut.card.something'; + + $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/delete`).respond(); + controller.deleteInvoiceOut(); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('invoiceOut.index'); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + describe('sendPdfInvoice()', () => { it('should make a query to the email invoice endpoint and show a message snackbar', () => { jest.spyOn(controller.vnApp, 'showMessage'); diff --git a/modules/monitor/front/index/tickets/index.html b/modules/monitor/front/index/tickets/index.html index 065a591c3..23253259a 100644 --- a/modules/monitor/front/index/tickets/index.html +++ b/modules/monitor/front/index/tickets/index.html @@ -1,8 +1,8 @@ + auto-load="false" + limit="20"> this.reload()) .then(() => { - this.$state.go('ticket.index'); + const isInsideTicket = this.$state.current.name.startsWith('ticket'); + if (isInsideTicket) + this.$state.go('ticket.index'); + this.vnApp.showSuccess(this.$t('Ticket deleted. You can undo this action within the first hour')); }); } diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index e9486bcd0..288c7508b 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -78,9 +78,22 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { describe('deleteTicket()', () => { it('should make a query and call vnApp.showSuccess()', () => { + jest.spyOn(controller, 'reload').mockReturnThis(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.expectPOST(`Tickets/${ticket.id}/setDeleted`).respond(); + controller.deleteTicket(); + $httpBackend.flush(); + + expect(controller.reload).toHaveBeenCalled(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + + it(`should make a query and call showSuccess() after state.go if the state wasn't inside ticket module`, () => { jest.spyOn(controller, 'reload').mockReturnThis(); jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); jest.spyOn(controller.vnApp, 'showSuccess'); + controller.$state.current.name = 'ticket.card.something'; $httpBackend.expectPOST(`Tickets/${ticket.id}/setDeleted`).respond(); controller.deleteTicket(); diff --git a/package.json b/package.json index dc132131a..e5b817e20 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "node": ">=14" }, "dependencies": { + "axios": "^0.25.0", "bmp-js": "^0.1.0", "compression": "^1.7.3", "fs-extra": "^5.0.0", @@ -42,6 +43,7 @@ "strong-error-handler": "^2.3.2", "uuid": "^3.3.3", "vn-loopback": "file:./loopback", + "vn-print": "file:./print", "xml2js": "^0.4.23" }, "devDependencies": { diff --git a/print/boot.js b/print/boot.js index 8cd0eaad7..d5c06264c 100644 --- a/print/boot.js +++ b/print/boot.js @@ -1,11 +1,9 @@ const express = require('express'); const path = require('path'); const fs = require('fs'); -const puppeteer = require('puppeteer'); const templatesPath = path.resolve(__dirname, './templates'); const componentsPath = path.resolve(__dirname, './core/components'); -const config = require('./core/config'); module.exports = async app => { global.appPath = __dirname; @@ -53,21 +51,4 @@ module.exports = async app => { app.use(`/api/${templateName}/assets`, express.static(assetsDir)); }); }); - - // Instantiate Puppeteer browser - async function launchBrowser() { - config.browser = await puppeteer.launch({ - headless: true, - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--single-process', - '--no-zygote' - ] - }); - - config.browser.on('disconnected', launchBrowser); - } - - launchBrowser(); }; diff --git a/print/common/css/layout.css b/print/common/css/layout.css index 4f521bea4..c1af4807d 100644 --- a/print/common/css/layout.css +++ b/print/common/css/layout.css @@ -6,6 +6,8 @@ .grid { font-family: Arial, Helvetica, sans-serif; font-size: 16px !important; + max-width: 1200px; + margin: 0 auto; width: 100% } diff --git a/print/config/print.json b/print/config/print.json index ceb7cbb28..d288c585d 100755 --- a/print/config/print.json +++ b/print/config/print.json @@ -48,6 +48,12 @@ "pool": true }, "storage": { - "root": "./storage/dms" + "root": "./storage/dms", + "invoice": { + "root": "./storage/pdfs/invoice" + }, + "signature": { + "root": "./storage/signatures" + } } } \ No newline at end of file diff --git a/print/core/component.js b/print/core/component.js index 12474566e..37656c240 100644 --- a/print/core/component.js +++ b/print/core/component.js @@ -27,29 +27,50 @@ class Component { get locale() { if (!this._locale) - this.getLocale(); + this._locale = this.getLocales(); return this._locale; } - getLocale() { - const mergedLocale = {messages: {}}; + getLocales() { + const mergedLocales = {messages: {}}; const localePath = path.resolve(__dirname, `${this.path}/locale`); if (!fs.existsSync(localePath)) - return mergedLocale; + return mergedLocales; const localeDir = fs.readdirSync(localePath); - localeDir.forEach(locale => { + for (const locale of localeDir) { const fullPath = path.join(localePath, '/', locale); const yamlLocale = fs.readFileSync(fullPath, 'utf8'); const jsonLocale = yaml.safeLoad(yamlLocale); const localeName = locale.replace('.yml', ''); - mergedLocale.messages[localeName] = jsonLocale; - }); + mergedLocales.messages[localeName] = jsonLocale; + } - this._locale = mergedLocale; + return mergedLocales; + } + + async getUserLocale() { + let locale = this.args.auth.locale; + + // Fetches user locale from mixing method getLocale() + if (this.args.recipientId) { + const component = await this.component(); + locale = await component.getLocale(this.args.recipientId); + } + + const messages = this.locale.messages; + const userTranslations = messages[locale]; + + if (!userTranslations) { + const fallbackLocale = config.i18n.fallbackLocale; + + return messages[fallbackLocale]; + } + + return userTranslations; } get stylesheet() { @@ -75,7 +96,7 @@ class Component { build() { const fullPath = path.resolve(__dirname, this.path); if (!fs.existsSync(fullPath)) - throw new Error(`Sample "${this.name}" not found`); + throw new Error(`Template "${this.name}" not found`); const component = require(`${this.path}/${this.name}`); component.i18n = this.locale; diff --git a/print/core/components/report-header/report-header.html b/print/core/components/report-header/report-header.html index 2667f14ed..0479e5caf 100644 --- a/print/core/components/report-header/report-header.html +++ b/print/core/components/report-header/report-header.html @@ -1,5 +1,9 @@
- Verdnatura + Verdnatura
{{companyName}}. {{company.street}}. {{company.postCode}} {{company.city}}. diff --git a/print/core/components/report-header/report-header.js b/print/core/components/report-header/report-header.js index 58c06bc1a..50c3a1337 100755 --- a/print/core/components/report-header/report-header.js +++ b/print/core/components/report-header/report-header.js @@ -10,9 +10,16 @@ module.exports = { }, computed: { companyName() { - if (!this.company.name) return; + if (this.company.name) + return this.company.name.toUpperCase(); - return this.company.name.toUpperCase(); + return; + }, + companyGroup() { + if (this.company.groupName) + return this.company.groupName.toLowerCase(); + + return; }, companyPhone() { if (!this.company.phone) return; @@ -30,8 +37,15 @@ module.exports = { methods: { getCompany(code) { return db.findOne(` - SELECT s.name, s.street, s.postCode, s.city, s.phone + SELECT + s.name, + s.street, + s.postCode, + s.city, + s.phone, + cg.code AS groupName FROM company c + JOIN companyGroup cg ON cg.id = c.companyGroupFk JOIN supplier s ON s.id = c.id WHERE c.code = ?`, [code]); }, diff --git a/print/core/email.js b/print/core/email.js index 620c1e083..bc8345cab 100644 --- a/print/core/email.js +++ b/print/core/email.js @@ -19,22 +19,7 @@ class Email extends Component { } async getSubject() { - const component = await this.component(); - let locale = this.args.auth.locale; - - if (this.args.recipientId) - locale = await component.getLocale(this.args.recipientId); - - const messages = this.locale.messages; - const userTranslations = messages[locale]; - - if (!userTranslations) { - const fallbackLocale = config.i18n.fallbackLocale; - - return messages[fallbackLocale].subject; - } - - return userTranslations.subject; + return (await this.getUserLocale())['subject']; } /** @@ -63,6 +48,7 @@ class Email extends Component { const reportName = fileName.replace('.pdf', ''); const report = new Report(reportName, this.args); fileCopy.content = await report.toPdfStream(); + fileCopy.filename = await report.getFileName(); } attachments.push(fileCopy); diff --git a/print/core/report.js b/print/core/report.js index b20b8e5df..093f5e99e 100644 --- a/print/core/report.js +++ b/print/core/report.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); const config = require('./config'); const Component = require('./component'); +const puppeteer = require('puppeteer'); if (!process.env.OPENSSL_CONF) process.env.OPENSSL_CONF = '/etc/ssl/'; @@ -17,6 +18,10 @@ class Report extends Component { return `../templates/reports/${this.name}`; } + async getName() { + return (await this.getUserLocale())['reportName']; + } + async toPdfStream() { const template = await this.render(); const defaultOptions = Object.assign({}, config.pdf); @@ -27,7 +32,17 @@ class Report extends Component { if (fs.existsSync(fullPath)) options = require(optionsPath); - const page = (await config.browser.pages())[0]; + const browser = await puppeteer.launch({ + headless: true, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--single-process', + '--no-zygote' + ] + }); + + const page = (await browser.pages())[0]; await page.emulateMedia('screen'); await page.setContent(template); @@ -47,8 +62,43 @@ class Report extends Component { const buffer = await page.pdf(options); + await browser.close(); + return buffer; } + + /** + * Returns all the params that ends with id + * + * @return {array} List of identifiers + */ + getIdentifiers() { + const identifiers = []; + const args = this.args; + const keys = Object.keys(args); + + for (let arg of keys) { + if (arg.endsWith('Id')) + identifiers.push(arg); + } + + return identifiers; + } + + async getFileName() { + const args = this.args; + const identifiers = this.getIdentifiers(args); + const name = await this.getName(); + const params = []; + params.push(name); + + for (let id of identifiers) + params.push(args[id]); + + const fileName = params.join('_'); + + return `${fileName}.pdf`; + } } module.exports = Report; diff --git a/print/core/router.js b/print/core/router.js index c0f20dd9a..cd64ba07e 100644 --- a/print/core/router.js +++ b/print/core/router.js @@ -1,43 +1,30 @@ -const path = require('path'); -const fs = require('fs'); const db = require('./database'); module.exports = app => { - const methodsPath = path.resolve(__dirname, '../methods'); - const methodsDir = fs.readdirSync(methodsPath); - const methods = []; + const routes = require('../methods/routes'); - // Get all methods - for (let method of methodsDir) { - if (method.includes('.js')) - methods.push(method.replace('.js', '')); - } - - // Auth middleware - const paths = []; - for (let method of methods) - paths.push(`/api/${method}/*`); - - app.use(paths, async function(req, res, next) { - const token = getToken(req); - const query = `SELECT at.id, at.userId, eu.email, u.lang, at.ttl, at.created - FROM salix.AccessToken at - JOIN account.user u ON u.id = at.userid - JOIN account.emailUser eu ON eu.userFk = u.id - WHERE at.id = ?`; + const paths = routes.map(route => route.url); + app.use(paths, async function(request, response, next) { try { + const token = getToken(request); + const query = `SELECT at.id, at.userId, eu.email, u.lang, at.ttl, at.created + FROM salix.AccessToken at + JOIN account.user u ON u.id = at.userid + JOIN account.emailUser eu ON eu.userFk = u.id + WHERE at.id = ?`; + const auth = await db.findOne(query, [token]); if (!auth || isTokenExpired(auth.created, auth.ttl)) throw new Error('Invalid authorization token'); - const args = Object.assign({}, req.query); - const props = Object.assign(args, req.body); + const args = Object.assign({}, request.query); + const props = Object.assign(args, request.body); props.authorization = auth.id; - req.args = props; - req.args.auth = { + response.locals = props; + response.locals.auth = { userId: auth.userId, token: auth.id, email: auth.email, @@ -50,6 +37,10 @@ module.exports = app => { } }); + // Register routes + for (let route of routes) + app.use(route.url, route.cb); + function getToken(request) { const headers = request.headers; const queryParams = request.query; @@ -68,8 +59,4 @@ module.exports = app => { return false; } - - // Mount methods - for (let method of methods) - require(`../methods/${method}`)(app); }; diff --git a/print/core/smtp.js b/print/core/smtp.js index 36a76dbaf..50a413673 100644 --- a/print/core/smtp.js +++ b/print/core/smtp.js @@ -25,14 +25,17 @@ module.exports = { throw err; }).finally(async() => { const attachments = []; - for (let attachment of options.attachments) { - const fileName = attachment.filename; - const filePath = attachment.path; - if (fileName.includes('.png')) return; + if (options.attachments) { + for (let attachment of options.attachments) { + const fileName = attachment.filename; + const filePath = attachment.path; + if (fileName.includes('.png')) return; - if (fileName || filePath) - attachments.push(filePath ? filePath : fileName); + if (fileName || filePath) + attachments.push(filePath ? filePath : fileName); + } } + const fileNames = attachments.join(',\n'); await db.rawSql(` INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status) diff --git a/print/core/storage.js b/print/core/storage.js new file mode 100644 index 000000000..063a2fbec --- /dev/null +++ b/print/core/storage.js @@ -0,0 +1,28 @@ +const config = require('./config.js'); +const path = require('path'); +const fs = require('fs-extra'); + +module.exports = { + async write(stream, options) { + const storage = config.storage[options.type]; + + if (!storage) return; + + const src = path.join(storage.root, options.path); + const fileSrc = path.join(src, options.fileName); + + await fs.mkdir(src, {recursive: true}); + + const writeStream = fs.createWriteStream(fileSrc); + writeStream.on('open', () => writeStream.write(stream)); + writeStream.on('finish', () => writeStream.end()); + + return new Promise(resolve => { + writeStream.on('close', () => resolve()); + }); + }, + + load(type, data) { + + } +}; diff --git a/print/core/stylesheet.js b/print/core/stylesheet.js index ffa141968..42a44fb57 100644 --- a/print/core/stylesheet.js +++ b/print/core/stylesheet.js @@ -7,9 +7,8 @@ class Stylesheet { } mergeStyles() { - this.files.forEach(file => { + for (const file of this.files) this.css.push(fs.readFileSync(file)); - }); return this.css.join('\n'); } diff --git a/print/methods/closure.js b/print/methods/closure.js deleted file mode 100644 index 07bd1768d..000000000 --- a/print/methods/closure.js +++ /dev/null @@ -1,311 +0,0 @@ -const db = require('../core/database'); -const Email = require('../core/email'); -const Report = require('../core/report'); -const smtp = require('../core/smtp'); -const config = require('../core/config'); - -module.exports = app => { - app.get('/api/closure/all', async function(req, res, next) { - try { - const reqArgs = req.args; - if (!reqArgs.to) - throw new Error('The argument to is required'); - - res.status(200).json({ - message: 'Task executed successfully' - }); - - const tickets = await db.rawSql(` - SELECT - t.id - FROM expedition e - JOIN ticket t ON t.id = e.ticketFk - JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission - JOIN ticketState ts ON ts.ticketFk = t.id - JOIN alertLevel al ON al.id = ts.alertLevel - WHERE al.code = 'PACKED' - AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) - AND util.dayEnd(?) - AND t.refFk IS NULL - GROUP BY e.ticketFk`, [reqArgs.to, reqArgs.to]); - const ticketIds = tickets.map(ticket => ticket.id); - - await closeAll(ticketIds, req.args); - await db.rawSql(` - UPDATE ticket t - JOIN ticketState ts ON t.id = ts.ticketFk - JOIN alertLevel al ON al.id = ts.alertLevel - JOIN agencyMode am ON am.id = t.agencyModeFk - JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk - JOIN zone z ON z.id = t.zoneFk - SET t.routeFk = NULL - WHERE DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) - AND util.dayEnd(?) - AND al.code NOT IN('DELIVERED','PACKED') - AND t.routeFk - AND z.name LIKE '%MADRID%'`, [reqArgs.to, reqArgs.to]); - } catch (error) { - next(error); - } - }); - - app.get('/api/closure/by-ticket', async function(req, res, next) { - try { - const reqArgs = req.args; - if (!reqArgs.ticketId) - throw new Error('The argument ticketId is required'); - - res.status(200).json({ - message: 'Task executed successfully' - }); - - const tickets = await db.rawSql(` - SELECT - t.id - FROM expedition e - JOIN ticket t ON t.id = e.ticketFk - JOIN ticketState ts ON ts.ticketFk = t.id - JOIN alertLevel al ON al.id = ts.alertLevel - WHERE al.code = 'PACKED' - AND t.id = ? - AND t.refFk IS NULL - GROUP BY e.ticketFk`, [reqArgs.ticketId]); - const ticketIds = tickets.map(ticket => ticket.id); - - await closeAll(ticketIds, reqArgs); - } catch (error) { - next(error); - } - }); - - app.get('/api/closure/by-agency', async function(req, res, next) { - try { - const reqArgs = req.args; - if (!reqArgs.agencyModeId) - throw new Error('The argument agencyModeId is required'); - - if (!reqArgs.warehouseId) - throw new Error('The argument warehouseId is required'); - - if (!reqArgs.to) - throw new Error('The argument to is required'); - - res.status(200).json({ - message: 'Task executed successfully' - }); - - const agenciesId = reqArgs.agencyModeId.split(','); - const tickets = await db.rawSql(` - SELECT - t.id - FROM expedition e - JOIN ticket t ON t.id = e.ticketFk - JOIN ticketState ts ON ts.ticketFk = t.id - JOIN alertLevel al ON al.id = ts.alertLevel - WHERE al.code = 'PACKED' - AND t.agencyModeFk IN(?) - AND t.warehouseFk = ? - AND DATE(t.shipped) BETWEEN DATE_ADD(:to, INTERVAL -2 DAY) - AND util.dayEnd(?) - AND t.refFk IS NULL - GROUP BY e.ticketFk`, [ - agenciesId, - reqArgs.warehouseId, - reqArgs.to, - reqArgs.to - ]); - const ticketIds = tickets.map(ticket => ticket.id); - - await closeAll(ticketIds, reqArgs); - } catch (error) { - next(error); - } - }); - - app.get('/api/closure/by-route', async function(req, res, next) { - try { - const reqArgs = req.args; - if (!reqArgs.routeId) - throw new Error('The argument routeId is required'); - - res.status(200).json({ - message: 'Task executed successfully' - }); - - const tickets = await db.rawSql(` - SELECT - t.id - FROM expedition e - JOIN ticket t ON t.id = e.ticketFk - JOIN ticketState ts ON ts.ticketFk = t.id - JOIN alertLevel al ON al.id = ts.alertLevel - WHERE al.code = 'PACKED' - AND t.routeFk = ? - AND t.refFk IS NULL - GROUP BY e.ticketFk`, [reqArgs.routeId]); - const ticketIds = tickets.map(ticket => ticket.id); - await closeAll(ticketIds, reqArgs); - - // Send route report to the agency - const agencyMail = await db.findValue(` - SELECT am.reportMail - FROM route r - JOIN agencyMode am ON am.id = r.agencyModeFk - WHERE r.id = ?`, [reqArgs.routeId]); - - if (agencyMail) { - const args = Object.assign({ - routeId: reqArgs.routeId, - recipient: agencyMail - }, reqArgs); - - const email = new Email('driver-route', args); - await email.send(); - } - } catch (error) { - next(error); - } - }); - - async function closeAll(ticketIds, reqArgs) { - const failedtickets = []; - const tickets = await db.rawSql(` - SELECT - t.id, - t.clientFk, - c.name clientName, - c.email recipient, - c.salesPersonFk, - c.isToBeMailed, - c.hasToInvoice, - co.hasDailyInvoice, - eu.email salesPersonEmail - FROM ticket t - JOIN client c ON c.id = t.clientFk - JOIN province p ON p.id = c.provinceFk - JOIN country co ON co.id = p.countryFk - LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk - WHERE t.id IN(?)`, [ticketIds]); - - for (const ticket of tickets) { - try { - await db.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id]); - - if (!ticket.salesPersonFk || !ticket.isToBeMailed) continue; - - if (!ticket.recipient) { - const body = `No se ha podido enviar el albarán ${ticket.id} - al cliente ${ticket.clientFk} porque no tiene un email especificado.

- Para dejar de recibir esta notificación, asígnale un email o desactiva - la notificación por email para este cliente.`; - smtp.send({ - to: ticket.salesPersonEmail, - subject: 'No se ha podido enviar el albarán', - html: body - }); - - continue; - } - - const hasToInvoice = ticket.hasToInvoice && ticket.hasDailyInvoice; - if (hasToInvoice) { - const invoice = await db.findOne(` - SELECT io.id, io.ref, io.serial, cny.code companyCode - FROM ticket t - JOIN invoiceOut io ON io.ref = t.refFk - JOIN company cny ON cny.id = io.companyFk - WHERE t.id = ? - `, [ticket.id]); - - const args = Object.assign({ - invoiceId: invoice.id, - recipientId: ticket.clientFk, - recipient: ticket.recipient, - replyTo: ticket.salesPersonEmail - }, reqArgs); - - let mailOptions = {}; - if (invoice.serial == 'E' && invoice.companyCode == 'VNL') { - const exportation = new Report('exportation', args); - const stream = await exportation.toPdfStream(); - const fileName = `exportation-${invoice.ref}.pdf`; - mailOptions = { - overrideAttachments: false, - attachments: [{ - filename: fileName, - content: stream - }] - }; - } - - const email = new Email('invoice', args); - await email.send(mailOptions); - } else { - const args = Object.assign({ - ticketId: ticket.id, - recipientId: ticket.clientFk, - recipient: ticket.recipient, - replyTo: ticket.salesPersonEmail - }, reqArgs); - - const email = new Email('delivery-note-link', args); - await email.send(); - } - } catch (error) { - // Domain not found - if (error.responseCode == 450) - return invalidEmail(ticket); - - // Save tickets on a list of failed ids - failedtickets.push({ - id: ticket.id, - stacktrace: error - }); - } - } - - // Send email with failed tickets - if (failedtickets.length > 0) { - let body = 'This following tickets have failed:

'; - - for (ticket of failedtickets) { - body += `Ticket: ${ticket.id} -
${ticket.stacktrace}

`; - } - - smtp.send({ - to: config.app.reportEmail, - subject: '[API] Nightly ticket closure report', - html: body - }); - } - } - - async function invalidEmail(ticket) { - await db.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [ - ticket.clientFk - ]); - - const oldInstance = `{"email": "${ticket.recipient}"}`; - const newInstance = `{"email": ""}`; - await db.rawSql(` - INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance) - VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [ - ticket.clientFk, - oldInstance, - newInstance - ]); - - const body = `No se ha podido enviar el albarán ${ticket.id} - al cliente ${ticket.clientFk} - ${ticket.clientName} - porque la dirección de email "${ticket.recipient}" no es correcta o no está disponible.

- Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente. - Actualiza la dirección de email con una correcta.`; - - smtp.send({ - to: ticket.salesPersonEmail, - subject: 'No se ha podido enviar el albarán', - html: body - }); - } -}; diff --git a/print/methods/closure/closeAll.js b/print/methods/closure/closeAll.js new file mode 100644 index 000000000..7af3676f2 --- /dev/null +++ b/print/methods/closure/closeAll.js @@ -0,0 +1,58 @@ +const db = require('vn-print/core/database'); +const closure = require('./closure'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + if (!reqArgs.to) + throw new Error('The argument to is required'); + + response.status(200).json({ + message: 'Success' + }); + + const tickets = await db.rawSql(` + SELECT + t.id, + t.clientFk, + c.name clientName, + c.email recipient, + c.salesPersonFk, + c.isToBeMailed, + c.hasToInvoice, + co.hasDailyInvoice, + eu.email salesPersonEmail + FROM ticket t + JOIN agencyMode am ON am.id = t.agencyModeFk + JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN alertLevel al ON al.id = ts.alertLevel + JOIN client c ON c.id = t.clientFk + JOIN province p ON p.id = c.provinceFk + JOIN country co ON co.id = p.countryFk + LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk + WHERE al.code = 'PACKED' + AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) + AND util.dayEnd(?) + AND t.refFk IS NULL + GROUP BY t.id`, [reqArgs.to, reqArgs.to]); + + await closure.start(tickets, response.locals); + + await db.rawSql(` + UPDATE ticket t + JOIN ticketState ts ON t.id = ts.ticketFk + JOIN alertLevel al ON al.id = ts.alertLevel + JOIN agencyMode am ON am.id = t.agencyModeFk + JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk + JOIN zone z ON z.id = t.zoneFk + SET t.routeFk = NULL + WHERE DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) + AND util.dayEnd(?) + AND al.code NOT IN('DELIVERED','PACKED') + AND t.routeFk + AND z.name LIKE '%MADRID%'`, [reqArgs.to, reqArgs.to]); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/closure/closeByAgency.js b/print/methods/closure/closeByAgency.js new file mode 100644 index 000000000..7807de23a --- /dev/null +++ b/print/methods/closure/closeByAgency.js @@ -0,0 +1,58 @@ +const db = require('vn-print/core/database'); +const closure = require('./closure'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + + if (!reqArgs.agencyModeId) + throw new Error('The argument agencyModeId is required'); + + if (!reqArgs.warehouseId) + throw new Error('The argument warehouseId is required'); + + if (!reqArgs.to) + throw new Error('The argument to is required'); + + response.status(200).json({ + message: 'Success' + }); + + const agencyIds = reqArgs.agencyModeId.split(','); + const tickets = await db.rawSql(` + SELECT + t.id, + t.clientFk, + c.name clientName, + c.email recipient, + c.salesPersonFk, + c.isToBeMailed, + c.hasToInvoice, + co.hasDailyInvoice, + eu.email salesPersonEmail + FROM expedition e + JOIN ticket t ON t.id = e.ticketFk + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN alertLevel al ON al.id = ts.alertLevel + JOIN client c ON c.id = t.clientFk + JOIN province p ON p.id = c.provinceFk + JOIN country co ON co.id = p.countryFk + LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk + WHERE al.code = 'PACKED' + AND t.agencyModeFk IN(?) + AND t.warehouseFk = ? + AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) + AND util.dayEnd(?) + AND t.refFk IS NULL + GROUP BY e.ticketFk`, [ + agencyIds, + reqArgs.warehouseId, + reqArgs.to, + reqArgs.to + ]); + + await closure.start(tickets, response.locals); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/closure/closeByRoute.js b/print/methods/closure/closeByRoute.js new file mode 100644 index 000000000..2c0bfd1eb --- /dev/null +++ b/print/methods/closure/closeByRoute.js @@ -0,0 +1,61 @@ +const db = require('vn-print/core/database'); +const Email = require('vn-print/core/email'); +const closure = require('./closure'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + + if (!reqArgs.routeId) + throw new Error('The argument routeId is required'); + + response.status(200).json({ + message: 'Success' + }); + + const tickets = await db.rawSql(` + SELECT + t.id, + t.clientFk, + c.name clientName, + c.email recipient, + c.salesPersonFk, + c.isToBeMailed, + c.hasToInvoice, + co.hasDailyInvoice, + eu.email salesPersonEmail + FROM expedition e + JOIN ticket t ON t.id = e.ticketFk + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN alertLevel al ON al.id = ts.alertLevel + JOIN client c ON c.id = t.clientFk + JOIN province p ON p.id = c.provinceFk + JOIN country co ON co.id = p.countryFk + LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk + WHERE al.code = 'PACKED' + AND t.routeFk = ? + AND t.refFk IS NULL + GROUP BY e.ticketFk`, [reqArgs.routeId]); + + await closure.start(tickets, response.locals); + + // Send route report to the agency + const agencyMail = await db.findValue(` + SELECT am.reportMail + FROM route r + JOIN agencyMode am ON am.id = r.agencyModeFk + WHERE r.id = ?`, [reqArgs.routeId]); + + if (agencyMail) { + const args = Object.assign({ + routeId: Number.parseInt(reqArgs.routeId), + recipient: agencyMail + }, response.locals); + + const email = new Email('driver-route', args); + await email.send(); + } + } catch (error) { + next(error); + } +}; diff --git a/print/methods/closure/closeByTicket.js b/print/methods/closure/closeByTicket.js new file mode 100644 index 000000000..c71b3ecd0 --- /dev/null +++ b/print/methods/closure/closeByTicket.js @@ -0,0 +1,43 @@ +const db = require('vn-print/core/database'); +const closure = require('./closure'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + + if (!reqArgs.ticketId) + throw new Error('The argument ticketId is required'); + + response.status(200).json({ + message: 'Success' + }); + + const tickets = await db.rawSql(` + SELECT + t.id, + t.clientFk, + c.name clientName, + c.email recipient, + c.salesPersonFk, + c.isToBeMailed, + c.hasToInvoice, + co.hasDailyInvoice, + eu.email salesPersonEmail + FROM expedition e + JOIN ticket t ON t.id = e.ticketFk + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN alertLevel al ON al.id = ts.alertLevel + JOIN client c ON c.id = t.clientFk + JOIN province p ON p.id = c.provinceFk + JOIN country co ON co.id = p.countryFk + LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk + WHERE al.code = 'PACKED' + AND t.id = ? + AND t.refFk IS NULL + GROUP BY e.ticketFk`, [reqArgs.ticketId]); + + await closure.start(tickets, response.locals); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/closure/closure.js b/print/methods/closure/closure.js new file mode 100644 index 000000000..8cce8237c --- /dev/null +++ b/print/methods/closure/closure.js @@ -0,0 +1,153 @@ +const db = require('vn-print/core/database'); +const Report = require('vn-print/core/report'); +const Email = require('vn-print/core/email'); +const smtp = require('vn-print/core/smtp'); +const config = require('vn-print/core/config'); +const storage = require('vn-print/core/storage'); + +module.exports = { + async start(tickets, reqArgs) { + if (tickets.length == 0) return; + + const failedtickets = []; + for (const ticket of tickets) { + try { + await db.rawSql('START TRANSACTION'); + + await db.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id]); + + const invoiceOut = await db.findOne(` + SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued + FROM ticket t + JOIN invoiceOut io ON io.ref = t.refFk + JOIN company cny ON cny.id = io.companyFk + WHERE t.id = ? + `, [ticket.id]); + + const mailOptions = { + overrideAttachments: true, + attachments: [] + }; + + const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed; + + if (invoiceOut) { + const args = Object.assign({ + invoiceId: invoiceOut.id, + recipientId: ticket.clientFk, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail + }, reqArgs); + + const invoiceReport = new Report('invoice', args); + const stream = await invoiceReport.toPdfStream(); + + const issued = invoiceOut.issued; + const year = issued.getFullYear().toString(); + const month = (issued.getMonth() + 1).toString(); + const day = issued.getDate().toString(); + + const fileName = `${year}${invoiceOut.ref}.pdf`; + + // Store invoice + storage.write(stream, { + type: 'invoice', + path: `${year}/${month}/${day}`, + fileName: fileName + }); + + await db.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id]); + + if (isToBeMailed) { + const invoiceAttachment = { + filename: fileName, + content: stream + }; + + if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') { + const exportation = new Report('exportation', args); + const stream = await exportation.toPdfStream(); + const fileName = `CITES-${invoiceOut.ref}.pdf`; + + mailOptions.attachments.push({ + filename: fileName, + content: stream + }); + } + + mailOptions.attachments.push(invoiceAttachment); + + const email = new Email('invoice', args); + await email.send(mailOptions); + } + } else if (isToBeMailed) { + const args = Object.assign({ + ticketId: ticket.id, + recipientId: ticket.clientFk, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail + }, reqArgs); + + const email = new Email('delivery-note-link', args); + await email.send(); + } + await db.rawSql('COMMIT'); + } catch (error) { + await db.rawSql('ROLLBACK'); + // Domain not found + if (error.responseCode == 450) + return invalidEmail(ticket); + + // Save tickets on a list of failed ids + failedtickets.push({ + id: ticket.id, + stacktrace: error + }); + } + } + + // Send email with failed tickets + if (failedtickets.length > 0) { + let body = 'This following tickets have failed:

'; + + for (const ticket of failedtickets) { + body += `Ticket: ${ticket.id} +
${ticket.stacktrace}

`; + } + + smtp.send({ + to: config.app.reportEmail, + subject: '[API] Nightly ticket closure report', + html: body + }); + } + }, + + async invalidEmail(ticket) { + await db.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [ + ticket.clientFk + ]); + + const oldInstance = `{"email": "${ticket.recipient}"}`; + const newInstance = `{"email": ""}`; + await db.rawSql(` + INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance) + VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [ + ticket.clientFk, + oldInstance, + newInstance + ]); + + const body = `No se ha podido enviar el albarán ${ticket.id} + al cliente ${ticket.clientFk} - ${ticket.clientName} + porque la dirección de email "${ticket.recipient}" no es correcta o no está disponible.

+ Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente. + Actualiza la dirección de email con una correcta.`; + + smtp.send({ + to: ticket.salesPersonEmail, + subject: 'No se ha podido enviar el albarán', + html: body + }); + } +}; diff --git a/print/methods/closure/index.js b/print/methods/closure/index.js new file mode 100644 index 000000000..fcca76f71 --- /dev/null +++ b/print/methods/closure/index.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = new express.Router(); + +router.get('/all', require('./closeAll')); +router.get('/by-ticket', require('./closeByTicket')); +router.get('/by-agency', require('./closeByAgency')); +router.get('/by-route', require('./closeByRoute')); + +module.exports = router; diff --git a/print/methods/csv.js b/print/methods/csv.js deleted file mode 100644 index 4f4cdf2af..000000000 --- a/print/methods/csv.js +++ /dev/null @@ -1,31 +0,0 @@ -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/csv.js b/print/methods/csv/csv.js new file mode 100644 index 000000000..d8725582d --- /dev/null +++ b/print/methods/csv/csv.js @@ -0,0 +1,31 @@ +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); +} + +module.exports = { + toCSV, + formatDate +}; diff --git a/print/methods/csv/delivery-note/download.js b/print/methods/csv/delivery-note/download.js new file mode 100644 index 000000000..d369d5f4a --- /dev/null +++ b/print/methods/csv/delivery-note/download.js @@ -0,0 +1,24 @@ +const path = require('path'); +const db = require('vn-print/core/database'); + +const {toCSV} = require('../csv'); +const sqlPath = path.join(__dirname, 'sql'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + 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 = toCSV(sales); + const fileName = `ticket_${ticketId}.csv`; + + response.setHeader('Content-type', 'text/csv'); + response.setHeader('Content-Disposition', `inline; filename="${fileName}"`); + response.end(content); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/csv/delivery-note/index.js b/print/methods/csv/delivery-note/index.js deleted file mode 100644 index 9ef0e33fa..000000000 --- a/print/methods/csv/delivery-note/index.js +++ /dev/null @@ -1,82 +0,0 @@ -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/send.js b/print/methods/csv/delivery-note/send.js new file mode 100644 index 000000000..478f20f57 --- /dev/null +++ b/print/methods/csv/delivery-note/send.js @@ -0,0 +1,40 @@ +const path = require('path'); +const db = require('vn-print/core/database'); +const Email = require('vn-print/core/email'); + +const {toCSV} = require('../csv'); +const sqlPath = path.join(__dirname, 'sql'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + 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 + }, response.locals); + + const content = toCSV(sales); + const fileName = `ticket_${ticketId}.csv`; + const email = new Email('delivery-note', args); + await email.send({ + overrideAttachments: true, + attachments: [{ + filename: fileName, + content: content + }] + }); + + response.status(200).json({message: 'Success'}); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/csv/index.js b/print/methods/csv/index.js new file mode 100644 index 000000000..6bdd1b60d --- /dev/null +++ b/print/methods/csv/index.js @@ -0,0 +1,9 @@ +const express = require('express'); +const router = new express.Router(); + +router.get('/delivery-note/download', require('./delivery-note/download')); +router.get('/delivery-note/send', require('./delivery-note/send')); +router.get('/invoice/download', require('./invoice/download')); +router.get('/invoice/send', require('./invoice/send')); + +module.exports = router; diff --git a/print/methods/csv/invoice/download.js b/print/methods/csv/invoice/download.js new file mode 100644 index 000000000..593d2d8d0 --- /dev/null +++ b/print/methods/csv/invoice/download.js @@ -0,0 +1,24 @@ +const path = require('path'); +const db = require('vn-print/core/database'); + +const {toCSV} = require('../csv'); +const sqlPath = path.join(__dirname, 'sql'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + 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 = toCSV(sales); + const fileName = `invoice_${invoiceId}.csv`; + + response.setHeader('Content-type', 'text/csv'); + response.setHeader('Content-Disposition', `inline; filename="${fileName}"`); + response.end(content); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/csv/invoice/index.js b/print/methods/csv/invoice/index.js deleted file mode 100644 index 8f325be02..000000000 --- a/print/methods/csv/invoice/index.js +++ /dev/null @@ -1,82 +0,0 @@ -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/send.js b/print/methods/csv/invoice/send.js new file mode 100644 index 000000000..919d7aeb1 --- /dev/null +++ b/print/methods/csv/invoice/send.js @@ -0,0 +1,40 @@ +const path = require('path'); +const db = require('vn-print/core/database'); +const Email = require('vn-print/core/email'); + +const {toCSV} = require('../csv'); +const sqlPath = path.join(__dirname, 'sql'); + +module.exports = async function(request, response, next) { + try { + const reqArgs = request.query; + 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 + }, response.locals); + + const content = toCSV(sales); + const fileName = `invoice_${invoiceId}.csv`; + const email = new Email('invoice', args); + await email.send({ + overrideAttachments: true, + attachments: [{ + filename: fileName, + content: content + }] + }); + + response.status(200).json({message: 'Success'}); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/email.js b/print/methods/email.js deleted file mode 100644 index cc4590e5d..000000000 --- a/print/methods/email.js +++ /dev/null @@ -1,33 +0,0 @@ -const Email = require('../core/email'); - -module.exports = app => { - app.get(`/api/email/:name`, async(req, res, next) => { - try { - const reportName = req.params.name; - const email = new Email(reportName, req.args); - - await email.send(); - - res.status(200).json({ - message: 'Sent' - }); - } catch (e) { - next(e); - } - }); - - app.get(`/api/email/:name/preview`, async(req, res, next) => { - try { - const reportName = req.params.name; - const args = req.args; - args.isPreview = true; - - const email = new Email(reportName, args); - const rendered = await email.render(); - - res.send(rendered); - } catch (e) { - next(e); - } - }); -}; diff --git a/print/methods/email/email.js b/print/methods/email/email.js new file mode 100644 index 000000000..5d6882f7d --- /dev/null +++ b/print/methods/email/email.js @@ -0,0 +1,16 @@ +const Email = require('vn-print/core/email'); + +module.exports = async function(request, response, next) { + try { + const templateName = request.params.name; + const args = response.locals; + const email = new Email(templateName, args); + await email.send(); + + response.status(200).json({ + message: 'Sent' + }); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/email/index.js b/print/methods/email/index.js new file mode 100644 index 000000000..10c2d2325 --- /dev/null +++ b/print/methods/email/index.js @@ -0,0 +1,7 @@ +const express = require('express'); +const router = new express.Router(); + +router.get('/:name', require('./email')); +router.get('/:name/preview', require('./preview')); + +module.exports = router; diff --git a/print/methods/email/preview.js b/print/methods/email/preview.js new file mode 100644 index 000000000..e6a1aaf35 --- /dev/null +++ b/print/methods/email/preview.js @@ -0,0 +1,14 @@ +const Email = require('vn-print/core/email'); + +module.exports = async function(request, response, next) { + try { + const templateName = request.params.name; + const args = Object.assign({isPreview: true}, response.locals); + const email = new Email(templateName, args); + const template = await email.render(); + + response.send(template); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/report.js b/print/methods/report.js deleted file mode 100644 index 750fec4c8..000000000 --- a/print/methods/report.js +++ /dev/null @@ -1,54 +0,0 @@ -const Report = require('../core/report'); - -module.exports = app => { - app.get(`/api/report/:name`, async(req, res, next) => { - try { - const reportName = req.params.name; - const fileName = getFileName(reportName, req.args); - const report = new Report(reportName, req.args); - if (req.args.preview) { - const template = await report.render(); - res.send(template); - } else { - const stream = await report.toPdfStream(); - - res.setHeader('Content-type', 'application/pdf'); - res.setHeader('Content-Disposition', `inline; filename="${fileName}"`); - res.end(stream); - } - } catch (error) { - next(error); - } - }); - - /** - * Returns all the params that ends with id - * @param {object} args - Params object - * - * @return {array} List of identifiers - */ - function getIdentifiers(args) { - const identifiers = []; - const keys = Object.keys(args); - - for (let arg of keys) { - if (arg.endsWith('Id')) - identifiers.push(arg); - } - - return identifiers; - } - - function getFileName(name, args) { - const identifiers = getIdentifiers(args); - const params = []; - params.push(name); - - for (let id of identifiers) - params.push(args[id]); - - const fileName = params.join('_'); - - return `${fileName}.pdf`; - } -}; diff --git a/print/methods/report/document.js b/print/methods/report/document.js new file mode 100644 index 000000000..b24abf4ac --- /dev/null +++ b/print/methods/report/document.js @@ -0,0 +1,17 @@ +const Report = require('vn-print/core/report'); + +module.exports = async function(request, response, next) { + try { + const reportName = request.params.name; + const args = response.locals; + const report = new Report(reportName, args); + const stream = await report.toPdfStream(); + const fileName = await report.getFileName(); + + response.setHeader('Content-type', 'application/pdf'); + response.setHeader('Content-Disposition', `inline; filename="${fileName}"`); + response.end(stream); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/report/index.js b/print/methods/report/index.js new file mode 100644 index 000000000..c422c76df --- /dev/null +++ b/print/methods/report/index.js @@ -0,0 +1,7 @@ +const express = require('express'); +const router = new express.Router(); + +router.get('/:name', require('./document')); +router.get('/:name/preview', require('./preview')); + +module.exports = router; diff --git a/print/methods/report/preview.js b/print/methods/report/preview.js new file mode 100644 index 000000000..0d6ad6f43 --- /dev/null +++ b/print/methods/report/preview.js @@ -0,0 +1,13 @@ +const Report = require('vn-print/core/report'); + +module.exports = async function(request, response, next) { + try { + const reportName = request.params.name; + const report = new Report(reportName, request.query); + const template = await report.render(); + + response.send(template); + } catch (error) { + next(error); + } +}; diff --git a/print/methods/routes.js b/print/methods/routes.js new file mode 100644 index 000000000..0c452028e --- /dev/null +++ b/print/methods/routes.js @@ -0,0 +1,18 @@ +module.exports = [ + { + url: '/api/report', + cb: require('./report') + }, + { + url: '/api/email', + cb: require('./email') + }, + { + url: '/api/csv', + cb: require('./csv') + }, + { + url: '/api/closure', + cb: require('./closure') + }, +]; diff --git a/print/templates/email/campaign-metrics/campaign-metrics.js b/print/templates/email/campaign-metrics/campaign-metrics.js index 0ace0fc25..2bd93b725 100755 --- a/print/templates/email/campaign-metrics/campaign-metrics.js +++ b/print/templates/email/campaign-metrics/campaign-metrics.js @@ -21,6 +21,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, from: { diff --git a/print/templates/email/claim-pickup-order/claim-pickup-order.js b/print/templates/email/claim-pickup-order/claim-pickup-order.js index 4396b144a..cf4ba7d12 100755 --- a/print/templates/email/claim-pickup-order/claim-pickup-order.js +++ b/print/templates/email/claim-pickup-order/claim-pickup-order.js @@ -10,6 +10,7 @@ module.exports = { }, props: { claimId: { + type: [Number, String], required: true } } diff --git a/print/templates/email/client-debt-statement/client-debt-statement.js b/print/templates/email/client-debt-statement/client-debt-statement.js index c32e68943..f32f9e239 100755 --- a/print/templates/email/client-debt-statement/client-debt-statement.js +++ b/print/templates/email/client-debt-statement/client-debt-statement.js @@ -16,6 +16,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, from: { diff --git a/print/templates/email/client-welcome/client-welcome.js b/print/templates/email/client-welcome/client-welcome.js index f562339cc..eeb11bb78 100755 --- a/print/templates/email/client-welcome/client-welcome.js +++ b/print/templates/email/client-welcome/client-welcome.js @@ -18,6 +18,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true } } diff --git a/print/templates/email/delivery-note-link/delivery-note-link.js b/print/templates/email/delivery-note-link/delivery-note-link.js index 009fe7b5b..471b370d9 100755 --- a/print/templates/email/delivery-note-link/delivery-note-link.js +++ b/print/templates/email/delivery-note-link/delivery-note-link.js @@ -10,6 +10,7 @@ module.exports = { }, props: { ticketId: { + type: [Number, String], required: true } } diff --git a/print/templates/email/delivery-note/delivery-note.js b/print/templates/email/delivery-note/delivery-note.js index 64839b8e0..ffd2fe202 100755 --- a/print/templates/email/delivery-note/delivery-note.js +++ b/print/templates/email/delivery-note/delivery-note.js @@ -10,7 +10,7 @@ module.exports = { }, props: { ticketId: { - type: String, + type: [Number, String], required: true } } diff --git a/print/templates/email/driver-route/driver-route.js b/print/templates/email/driver-route/driver-route.js index de1dd9c39..378cd82ce 100755 --- a/print/templates/email/driver-route/driver-route.js +++ b/print/templates/email/driver-route/driver-route.js @@ -10,7 +10,7 @@ module.exports = { }, props: { routeId: { - type: String, + type: [Number, String], required: true } } diff --git a/print/templates/email/invoice/invoice.js b/print/templates/email/invoice/invoice.js index b8d3b8282..d92b65cb3 100755 --- a/print/templates/email/invoice/invoice.js +++ b/print/templates/email/invoice/invoice.js @@ -18,7 +18,7 @@ module.exports = { }, props: { invoiceId: { - type: String, + type: [Number, String], required: true } } diff --git a/print/templates/email/letter-debtor-nd/letter-debtor-nd.js b/print/templates/email/letter-debtor-nd/letter-debtor-nd.js index ba9f7957d..5e010d1ba 100755 --- a/print/templates/email/letter-debtor-nd/letter-debtor-nd.js +++ b/print/templates/email/letter-debtor-nd/letter-debtor-nd.js @@ -30,9 +30,11 @@ module.exports = { required: true }, recipientId: { + type: [Number, String], required: true }, companyId: { + type: [Number, String], required: true } } diff --git a/print/templates/email/letter-debtor-st/letter-debtor-st.js b/print/templates/email/letter-debtor-st/letter-debtor-st.js index 56fc7c8a8..a514097cf 100755 --- a/print/templates/email/letter-debtor-st/letter-debtor-st.js +++ b/print/templates/email/letter-debtor-st/letter-debtor-st.js @@ -1,5 +1,4 @@ const Component = require(`${appPath}/core/component`); -const db = require(`${appPath}/core/database`); const emailHeader = new Component('email-header'); const emailFooter = new Component('email-footer'); const attachment = new Component('attachment'); @@ -28,9 +27,11 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, companyId: { + type: [Number, String], required: true }, } diff --git a/print/templates/email/payment-update/payment-update.js b/print/templates/email/payment-update/payment-update.js index eb6690c02..2b92976a3 100755 --- a/print/templates/email/payment-update/payment-update.js +++ b/print/templates/email/payment-update/payment-update.js @@ -26,6 +26,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true } } diff --git a/print/templates/email/printer-setup/printer-setup.js b/print/templates/email/printer-setup/printer-setup.js index f6f168163..95dff8ebb 100755 --- a/print/templates/email/printer-setup/printer-setup.js +++ b/print/templates/email/printer-setup/printer-setup.js @@ -24,6 +24,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true } } diff --git a/print/templates/email/sepa-core/sepa-core.js b/print/templates/email/sepa-core/sepa-core.js index 76f8d842f..743c6719c 100755 --- a/print/templates/email/sepa-core/sepa-core.js +++ b/print/templates/email/sepa-core/sepa-core.js @@ -16,9 +16,11 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, companyId: { + type: [Number, String], required: true } } diff --git a/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js index 20113d8ea..3cf290e4d 100755 --- a/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js +++ b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js @@ -21,6 +21,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, from: { diff --git a/print/templates/reports/campaign-metrics/campaign-metrics.js b/print/templates/reports/campaign-metrics/campaign-metrics.js index 07d261a61..6669ce067 100755 --- a/print/templates/reports/campaign-metrics/campaign-metrics.js +++ b/print/templates/reports/campaign-metrics/campaign-metrics.js @@ -25,6 +25,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, from: { diff --git a/print/templates/reports/campaign-metrics/locale/es.yml b/print/templates/reports/campaign-metrics/locale/es.yml index 8a4cc4637..c455be5a8 100644 --- a/print/templates/reports/campaign-metrics/locale/es.yml +++ b/print/templates/reports/campaign-metrics/locale/es.yml @@ -1,3 +1,4 @@ +reportName: consumo-cliente title: Consumo Client: Cliente clientData: Datos del cliente diff --git a/print/templates/reports/claim-pickup-order/claim-pickup-order.js b/print/templates/reports/claim-pickup-order/claim-pickup-order.js index 0d1228a4e..fa2124057 100755 --- a/print/templates/reports/claim-pickup-order/claim-pickup-order.js +++ b/print/templates/reports/claim-pickup-order/claim-pickup-order.js @@ -32,6 +32,7 @@ module.exports = { }, props: { claimId: { + type: [Number, String], required: true } } diff --git a/print/templates/reports/claim-pickup-order/locale/es.yml b/print/templates/reports/claim-pickup-order/locale/es.yml index 9faf9ac06..faa6eac33 100644 --- a/print/templates/reports/claim-pickup-order/locale/es.yml +++ b/print/templates/reports/claim-pickup-order/locale/es.yml @@ -1,3 +1,4 @@ +reportName: orden-de-recogida title: Ord. recogida claimId: Reclamación clientId: Cliente diff --git a/print/templates/reports/client-debt-statement/client-debt-statement.js b/print/templates/reports/client-debt-statement/client-debt-statement.js index 09b99590b..f006b0a92 100755 --- a/print/templates/reports/client-debt-statement/client-debt-statement.js +++ b/print/templates/reports/client-debt-statement/client-debt-statement.js @@ -69,6 +69,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, from: { diff --git a/print/templates/reports/client-debt-statement/locale/es.yml b/print/templates/reports/client-debt-statement/locale/es.yml index ccdce7b5b..2c8e18ee1 100644 --- a/print/templates/reports/client-debt-statement/locale/es.yml +++ b/print/templates/reports/client-debt-statement/locale/es.yml @@ -1,3 +1,4 @@ +reportName: extracto-cliente title: Extracto clientId: Cliente clientData: Datos del cliente diff --git a/print/templates/reports/client-debt-statement/locale/fr.yml b/print/templates/reports/client-debt-statement/locale/fr.yml index 12534f9ff..4edb29d8a 100644 --- a/print/templates/reports/client-debt-statement/locale/fr.yml +++ b/print/templates/reports/client-debt-statement/locale/fr.yml @@ -1,3 +1,4 @@ +reportName: releve-de-compte title: Relevé de compte clientId: Client clientData: Données client diff --git a/print/templates/reports/cmr-authorization/cmr-authorization.js b/print/templates/reports/cmr-authorization/cmr-authorization.js index da08b6ec8..1adc75fa6 100755 --- a/print/templates/reports/cmr-authorization/cmr-authorization.js +++ b/print/templates/reports/cmr-authorization/cmr-authorization.js @@ -8,9 +8,8 @@ module.exports = { this.ticket = await this.findOneFromDef('ticket', [this.ticketId]); if (!this.ticket) throw new Error('Something went wrong'); - - this.client = await this.findOneFromDef('client', [this.ticket.clientFk]); + this.client = await this.findOneFromDef('client', [this.ticket.clientFk]); }, computed: { issued: function() { @@ -23,6 +22,7 @@ module.exports = { }, props: { ticketId: { + type: [Number, String], required: true } } diff --git a/print/templates/reports/cmr-authorization/locale/es.yml b/print/templates/reports/cmr-authorization/locale/es.yml index 779dc0c8f..37e40202d 100644 --- a/print/templates/reports/cmr-authorization/locale/es.yml +++ b/print/templates/reports/cmr-authorization/locale/es.yml @@ -1,3 +1,4 @@ +reportName: autorizacion-cmr description: '{socialName} una sociedad debidamente constituida con responsabilidad limitada y registrada conforme al derecho de sociedades de {country} y aquí representada por ___________________. {socialName}, con domicilio en {address}, diff --git a/print/templates/reports/credit-request/locale/es.yml b/print/templates/reports/credit-request/locale/es.yml index e4e9739a5..cd6f92dc5 100644 --- a/print/templates/reports/credit-request/locale/es.yml +++ b/print/templates/reports/credit-request/locale/es.yml @@ -1,3 +1,4 @@ +reportName: solicitud-de-credito fields: title: Solicitud de crédito date: Fecha diff --git a/print/templates/reports/delivery-note/delivery-note.js b/print/templates/reports/delivery-note/delivery-note.js index 9b3328d05..0ee7c8c91 100755 --- a/print/templates/reports/delivery-note/delivery-note.js +++ b/print/templates/reports/delivery-note/delivery-note.js @@ -117,7 +117,7 @@ module.exports = { }, props: { ticketId: { - type: String, + type: [Number, String], required: true } } diff --git a/print/templates/reports/delivery-note/locale/en.yml b/print/templates/reports/delivery-note/locale/en.yml index 8a3ff834b..16d0954e2 100644 --- a/print/templates/reports/delivery-note/locale/en.yml +++ b/print/templates/reports/delivery-note/locale/en.yml @@ -1,3 +1,4 @@ +reportName: delivery-note title: Delivery note ticketId: Delivery note clientId: Client diff --git a/print/templates/reports/delivery-note/locale/es.yml b/print/templates/reports/delivery-note/locale/es.yml index f9c2e02f3..ca670ad59 100644 --- a/print/templates/reports/delivery-note/locale/es.yml +++ b/print/templates/reports/delivery-note/locale/es.yml @@ -1,3 +1,4 @@ +reportName: albaran title: Albarán ticketId: Albarán clientId: Cliente diff --git a/print/templates/reports/delivery-note/locale/fr.yml b/print/templates/reports/delivery-note/locale/fr.yml index 72ca771e1..6b3779a5b 100644 --- a/print/templates/reports/delivery-note/locale/fr.yml +++ b/print/templates/reports/delivery-note/locale/fr.yml @@ -1,3 +1,4 @@ +reportName: bon-de-livraison title: Bon de livraison ticketId: BL clientId: Client diff --git a/print/templates/reports/delivery-note/locale/pt.yml b/print/templates/reports/delivery-note/locale/pt.yml index e83087142..1a9c1fbd1 100644 --- a/print/templates/reports/delivery-note/locale/pt.yml +++ b/print/templates/reports/delivery-note/locale/pt.yml @@ -1,3 +1,4 @@ +reportName: nota-de-entrega title: Nota de Entrega ticketId: Nota de Entrega clientId: Cliente diff --git a/print/templates/reports/driver-route/driver-route.js b/print/templates/reports/driver-route/driver-route.js index 0b2638239..c34de37cc 100755 --- a/print/templates/reports/driver-route/driver-route.js +++ b/print/templates/reports/driver-route/driver-route.js @@ -39,6 +39,7 @@ module.exports = { }, props: { routeId: { + type: [Number, String], required: true } } diff --git a/print/templates/reports/driver-route/locale/es.yml b/print/templates/reports/driver-route/locale/es.yml index 4f0f3ac3c..4c922ba64 100644 --- a/print/templates/reports/driver-route/locale/es.yml +++ b/print/templates/reports/driver-route/locale/es.yml @@ -1,3 +1,4 @@ +reportName: hoja-de-ruta title: Hoja de ruta information: Información date: Fecha diff --git a/print/templates/reports/entry-order/entry-order.js b/print/templates/reports/entry-order/entry-order.js index de396df2c..52a56bf03 100755 --- a/print/templates/reports/entry-order/entry-order.js +++ b/print/templates/reports/entry-order/entry-order.js @@ -40,7 +40,7 @@ module.exports = { }, props: { entryId: { - type: String, + type: [Number, String], required: true } } diff --git a/print/templates/reports/entry-order/locale/es.yml b/print/templates/reports/entry-order/locale/es.yml index 3c29d6401..5c633aeaa 100644 --- a/print/templates/reports/entry-order/locale/es.yml +++ b/print/templates/reports/entry-order/locale/es.yml @@ -1,3 +1,4 @@ +reportName: pedido-de-entrada title: Pedido supplierName: Proveedor supplierStreet: Dirección diff --git a/print/templates/reports/exportation/exportation.js b/print/templates/reports/exportation/exportation.js index f63d17930..fbf663249 100755 --- a/print/templates/reports/exportation/exportation.js +++ b/print/templates/reports/exportation/exportation.js @@ -28,6 +28,7 @@ module.exports = { }, props: { invoiceId: { + type: [Number, String], required: true } } diff --git a/print/templates/reports/exportation/locale/es.yml b/print/templates/reports/exportation/locale/es.yml index d5fb78b4c..a689e245b 100644 --- a/print/templates/reports/exportation/locale/es.yml +++ b/print/templates/reports/exportation/locale/es.yml @@ -1,3 +1,4 @@ +reportName: carta-CITES title: 'Carta CITES' toAttention: 'A la atención del Sr. Administrador de la Aduana de la Farga de Moles.' declaration: 'Por la presente DECLARO, bajo mi responsabilidad, que las mercancías detalladas en la factura diff --git a/print/templates/reports/extra-community/locale/es.yml b/print/templates/reports/extra-community/locale/es.yml index 1112b0fe8..36201400f 100644 --- a/print/templates/reports/extra-community/locale/es.yml +++ b/print/templates/reports/extra-community/locale/es.yml @@ -1,3 +1,4 @@ +reportName: orden-de-carga title: Orden de carga reference: Referencia information: Información diff --git a/print/templates/reports/invoice-incoterms/invoice-incoterms.js b/print/templates/reports/invoice-incoterms/invoice-incoterms.js index 95bf1f397..99e23e15f 100755 --- a/print/templates/reports/invoice-incoterms/invoice-incoterms.js +++ b/print/templates/reports/invoice-incoterms/invoice-incoterms.js @@ -32,7 +32,7 @@ module.exports = { }, props: { invoiceId: { - type: String, + type: [Number, String], required: true } } diff --git a/print/templates/reports/invoice-incoterms/locale/es.yml b/print/templates/reports/invoice-incoterms/locale/es.yml index 9828564d7..a69805935 100644 --- a/print/templates/reports/invoice-incoterms/locale/es.yml +++ b/print/templates/reports/invoice-incoterms/locale/es.yml @@ -1,3 +1,4 @@ +reportName: factura title: Factura invoice: Factura clientId: Cliente diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index b56a5533c..bd85a812c 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -115,7 +115,7 @@ module.exports = { }, props: { invoiceId: { - type: String, + type: [Number, String], required: true } } diff --git a/print/templates/reports/invoice/locale/en.yml b/print/templates/reports/invoice/locale/en.yml new file mode 100644 index 000000000..4e4688b55 --- /dev/null +++ b/print/templates/reports/invoice/locale/en.yml @@ -0,0 +1,36 @@ +reportName: invoice +title: Invoice +invoice: Invoice +clientId: Client +invoiceData: Invoice data +fiscalId: FI / NIF +invoiceRef: Invoice {0} +deliveryNote: Delivery note +shipped: Shipped +date: Date +reference: Ref. +quantity: Qty. +concept: Concept +price: PSP/u +discount: Disc. +vat: VAT +amount: Amount +type: Type +taxBase: Tax base +tax: Tax +fee: Fee +total: Total +subtotal: Subtotal +taxBreakdown: Tax breakdown +notes: Notes +intrastat: Intrastat +code: Code +description: Description +stems: Stems +netKg: Net kg +rectifiedInvoices: Rectified invoices +issued: Issued +plantPassport: Plant passport +observations: Observations +wireTransfer: "Pay method: Transferencia" +accountNumber: "Account number: {0}" \ No newline at end of file diff --git a/print/templates/reports/invoice/locale/es.yml b/print/templates/reports/invoice/locale/es.yml index 6fdfc8a14..d37e77943 100644 --- a/print/templates/reports/invoice/locale/es.yml +++ b/print/templates/reports/invoice/locale/es.yml @@ -1,3 +1,4 @@ +reportName: factura title: Factura invoice: Factura clientId: Cliente diff --git a/print/templates/reports/letter-debtor/letter-debtor.js b/print/templates/reports/letter-debtor/letter-debtor.js index bdb3a504a..354b1d8d8 100755 --- a/print/templates/reports/letter-debtor/letter-debtor.js +++ b/print/templates/reports/letter-debtor/letter-debtor.js @@ -63,9 +63,11 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, companyId: { + type: [Number, String], required: true } } diff --git a/print/templates/reports/letter-debtor/locale/es.yml b/print/templates/reports/letter-debtor/locale/es.yml index a9bd8c796..6c66f0717 100644 --- a/print/templates/reports/letter-debtor/locale/es.yml +++ b/print/templates/reports/letter-debtor/locale/es.yml @@ -1,3 +1,4 @@ +reportName: extracto-de-cuenta title: Extracto claimId: Reclamación clientId: Cliente diff --git a/print/templates/reports/letter-debtor/locale/fr.yml b/print/templates/reports/letter-debtor/locale/fr.yml index eff39d783..277c1162f 100644 --- a/print/templates/reports/letter-debtor/locale/fr.yml +++ b/print/templates/reports/letter-debtor/locale/fr.yml @@ -1,3 +1,4 @@ +reportName: releve-de-compte title: Relevé de compte claimId: Réclamation clientId: Client diff --git a/print/templates/reports/receipt/locale/es.yml b/print/templates/reports/receipt/locale/es.yml index 67b9a948f..41be106df 100644 --- a/print/templates/reports/receipt/locale/es.yml +++ b/print/templates/reports/receipt/locale/es.yml @@ -1,3 +1,4 @@ +reportName: receipt title: 'Recibo' date: 'Fecha' payed: 'En {0}, a {1} de {2} de {3}' diff --git a/print/templates/reports/receipt/receipt.js b/print/templates/reports/receipt/receipt.js index d34735bb7..d7f4dd6da 100755 --- a/print/templates/reports/receipt/receipt.js +++ b/print/templates/reports/receipt/receipt.js @@ -25,6 +25,7 @@ module.exports = { }, props: { receiptId: { + type: [Number, String], required: true } } diff --git a/print/templates/reports/sepa-core/locale/es.yml b/print/templates/reports/sepa-core/locale/es.yml index bb9cc4e49..5f3f08fc3 100644 --- a/print/templates/reports/sepa-core/locale/es.yml +++ b/print/templates/reports/sepa-core/locale/es.yml @@ -1,3 +1,4 @@ +reportName: orden-de-domiciliacion title: Orden de domiciliación de adeudo SEPA CORE description: Mediante la firma de esta orden de domiciliación, el deudor autoriza (A) al acreedor a enviar instrucciones a la entidad del deudor para adeudar su cuenta diff --git a/print/templates/reports/sepa-core/locale/fr.yml b/print/templates/reports/sepa-core/locale/fr.yml index 45a9039ea..354c06114 100644 --- a/print/templates/reports/sepa-core/locale/fr.yml +++ b/print/templates/reports/sepa-core/locale/fr.yml @@ -1,3 +1,4 @@ +reportName: direct-debit title: Direct Debit description: En signant ce formulaire de mandat, vous autorisez VERDNATURA LEVANTE SL à envoyer des instructions à votre banque pour débiter votre compte, et (B) votre banque diff --git a/print/templates/reports/sepa-core/locale/pt.yml b/print/templates/reports/sepa-core/locale/pt.yml index e7127d06b..5459779ec 100644 --- a/print/templates/reports/sepa-core/locale/pt.yml +++ b/print/templates/reports/sepa-core/locale/pt.yml @@ -1,3 +1,4 @@ +reportName: autorizacao-de-debito title: Autorização de débito directo SEPA CORE description: Ao subscrever esta autorização, está a autorizar a (A) Verdnatura Levante S.L. a enviar instruções ao seu banco para debitar a sua conta e (B) seu banco a diff --git a/print/templates/reports/sepa-core/sepa-core.js b/print/templates/reports/sepa-core/sepa-core.js index 55487d829..7e3dd3566 100755 --- a/print/templates/reports/sepa-core/sepa-core.js +++ b/print/templates/reports/sepa-core/sepa-core.js @@ -40,9 +40,11 @@ const rptSepaCore = { }, props: { recipientId: { + type: [Number, String], required: true }, companyId: { + type: [Number, String], required: true } } diff --git a/print/templates/reports/supplier-campaign-metrics/locale/es.yml b/print/templates/reports/supplier-campaign-metrics/locale/es.yml index 31c1e17dd..1a38541fa 100644 --- a/print/templates/reports/supplier-campaign-metrics/locale/es.yml +++ b/print/templates/reports/supplier-campaign-metrics/locale/es.yml @@ -1,3 +1,4 @@ +reportName: consumo-proveedor title: Consumo Supplier: Proveedor supplierData: Datos del proveedor diff --git a/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js index c37155556..1a460daa9 100755 --- a/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js +++ b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js @@ -49,6 +49,7 @@ module.exports = { }, props: { recipientId: { + type: [Number, String], required: true }, from: { diff --git a/print/templates/reports/zone/zone.js b/print/templates/reports/zone/zone.js index 61c6cddfe..d611e1e53 100755 --- a/print/templates/reports/zone/zone.js +++ b/print/templates/reports/zone/zone.js @@ -13,6 +13,7 @@ module.exports = { }, props: { routeId: { + type: [Number, String], required: true } } diff --git a/storage/pdfs/invoice/.keep b/storage/pdfs/invoice/.keep deleted file mode 100644 index e69de29bb..000000000