diff --git a/.gitignore b/.gitignore index 04a977352c..b741a2d440 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,11 @@ coverage node_modules dist storage +.idea npm-debug.log .eslintcache datasources.*.json print.*.json db.json junit.xml -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json index 54dc05c7ff..c27d01a765 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,13 @@ { // Carácter predeterminado de final de línea. "files.eol": "\n", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - } + "editor.bracketPairColorization.enabled": true, + "editor.guides.bracketPairs": true, + "editor.formatOnSave": true, + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "editor.codeActionsOnSave": ["source.fixAll.eslint"], + "eslint.validate": [ + "javascript", + "json" + ] } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 378a87f84e..a59725f770 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,43 @@ -FROM debian:stretch-slim +FROM debian:bullseye-slim ENV TZ Europe/Madrid ARG DEBIAN_FRONTEND=noninteractive + +# NodeJs + RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl \ ca-certificates \ gnupg2 \ - libfontconfig lftp \ - && apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \ - libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \ - libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \ - libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \ - libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \ - && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && npm install -g npm@8.19.2 + +# Puppeteer + +RUN apt-get update \ && apt-get install -y --no-install-recommends \ - nodejs \ - && apt-get purge -y --auto-remove \ - gnupg2 \ + libfontconfig lftp xvfb gconf-service libasound2 libatk1.0-0 libc6 \ + libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 \ + libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 \ + libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \ + libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \ + libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \ + fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \ && rm -rf /var/lib/apt/lists/* \ && npm -g install pm2 +# Salix + WORKDIR /salix + +COPY print/package.json print/package-lock.json print/ +RUN npm --prefix ./print install --omit=dev ./print + COPY package.json package-lock.json ./ COPY loopback/package.json loopback/ -COPY print/package.json print/ -RUN npm install --only=prod -RUN npm --prefix ./print install --only=prod ./print +RUN npm install --omit=dev COPY loopback loopback COPY back back diff --git a/Jenkinsfile b/Jenkinsfile index 5a8ff39c5d..b1706d8028 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,4 @@ #!/usr/bin/env groovy - pipeline { agent any options { @@ -62,13 +61,13 @@ pipeline { } } } - // stage('Backend') { - // steps { - // nodejs('node-v14') { - // sh 'npm run test:back:ci' - // } - // } - // } + stage('Backend') { + steps { + nodejs('node-v14') { + sh 'npm run test:back:ci' + } + } + } } } stage('Build') { diff --git a/back/methods/dms/deleteTrashFiles.js b/back/methods/dms/deleteTrashFiles.js index 828f9658c6..63d7021c5f 100644 --- a/back/methods/dms/deleteTrashFiles.js +++ b/back/methods/dms/deleteTrashFiles.js @@ -47,20 +47,22 @@ module.exports = Self => { for (let dms of dmsToDelete) { const pathHash = DmsContainer.getHash(dms.id); const dmsContainer = await DmsContainer.container(pathHash); - const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file); try { + const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file); await fs.unlink(dstFile); } catch (err) { - continue; + if (err.code != 'ENOENT') + throw err; } + + await dms.destroy(myOptions); + const dstFolder = path.join(dmsContainer.client.root, pathHash); try { await fs.rmdir(dstFolder); } catch (err) { continue; } - - await dms.destroy(myOptions); } }; }; diff --git a/back/methods/osticket/closeTicket.js b/back/methods/osticket/closeTicket.js index 87d54d3b86..33fe5958b0 100644 --- a/back/methods/osticket/closeTicket.js +++ b/back/methods/osticket/closeTicket.js @@ -1,12 +1,13 @@ const jsdom = require('jsdom'); const mysql = require('mysql'); +const FormData = require('form-data'); module.exports = Self => { Self.remoteMethodCtx('closeTicket', { description: 'Close tickets without response from the user', accessType: 'READ', returns: { - type: 'Object', + type: 'object', root: true }, http: { @@ -54,9 +55,9 @@ module.exports = Self => { }); }); - await requestToken(); + await getRequestToken(); - async function requestToken() { + async function getRequestToken() { const response = await fetch(ostUri); const result = response.headers.get('set-cookie'); @@ -93,24 +94,45 @@ module.exports = Self => { await close(token, secondCookie); } + async function getLockCode(token, secondCookie, ticketId) { + const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`; + const params = { + method: 'POST', + headers: { + 'X-CSRFToken': token, + 'Cookie': secondCookie + } + }; + const response = await fetch(ostUri, params); + const body = await response.text(); + const json = JSON.parse(body); + + return json.code; + } + async function close(token, secondCookie) { for (const ticketId of ticketsId) { - const ostUri = `${config.host}/ajax.php/tickets/${ticketId}/status`; - const data = { - status_id: config.newStatusId, - comments: config.comment, - undefined: config.action - }; + const lockCode = await getLockCode(token, secondCookie, ticketId); + let form = new FormData(); + form.append('__CSRFToken__', token); + form.append('id', ticketId); + form.append('a', config.responseType); + form.append('lockCode', lockCode); + form.append('from_email_id', config.fromEmailId); + form.append('reply-to', config.replyTo); + form.append('cannedResp', 0); + form.append('response', config.comment); + form.append('signature', 'none'); + form.append('reply_status_id', config.newStatusId); + + const ostUri = `${config.host}/tickets.php?id=${ticketId}`; const params = { method: 'POST', - body: new URLSearchParams(data), + body: form, headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'X-CSRFToken': token, 'Cookie': secondCookie } }; - return fetch(ostUri, params); } } diff --git a/back/models/osticket-config.json b/back/models/osticket-config.json index d42632c6a5..5a863e7bc7 100644 --- a/back/models/osticket-config.json +++ b/back/models/osticket-config.json @@ -27,9 +27,6 @@ "newStatusId": { "type": "number" }, - "action": { - "type": "string" - }, "day": { "type": "number" }, @@ -47,6 +44,15 @@ }, "portDb": { "type": "number" + }, + "responseType": { + "type": "string" + }, + "fromEmailId": { + "type": "number" + }, + "replyTo": { + "type": "string" } } } \ No newline at end of file diff --git a/db/changes/10480-june/00-itemType.sql b/db/changes/10480-june/00-itemType.sql deleted file mode 100644 index d201acf370..0000000000 --- a/db/changes/10480-june/00-itemType.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE `vn`.`itemType` CHANGE `transaction` transaction__ tinyint(4) DEFAULT 0 NOT NULL; -ALTER TABLE `vn`.`itemType` CHANGE location location__ varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL; -ALTER TABLE `vn`.`itemType` CHANGE hasComponents hasComponents__ tinyint(1) DEFAULT 1 NOT NULL; -ALTER TABLE `vn`.`itemType` CHANGE warehouseFk warehouseFk__ smallint(6) unsigned DEFAULT 60 NOT NULL; -ALTER TABLE `vn`.`itemType` CHANGE compression compression__ decimal(5,2) DEFAULT 1.00 NULL; \ No newline at end of file diff --git a/db/changes/10481-june/00-osTicketConfig.sql b/db/changes/10481-june/00-osTicketConfig.sql index ad66627153..8727c816dd 100644 --- a/db/changes/10481-june/00-osTicketConfig.sql +++ b/db/changes/10481-june/00-osTicketConfig.sql @@ -13,8 +13,4 @@ CREATE TABLE `vn`.`osTicketConfig` ( `passwordDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, `portDb` int(11) DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; - -INSERT INTO `vn`.`osTicketConfig`(`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `action`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`) - VALUES - (0, 'https://cau.verdnatura.es/scp', NULL, NULL, 'open', 3, 'Cerrar', 60, 'Este CAU se ha cerrado automáticamente', NULL, NULL, NULL, NULL); \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; \ No newline at end of file diff --git a/db/changes/10491-august/00-ACL_workerDisableExcluded.sql b/db/changes/10491-august/00-ACL_workerDisableExcluded.sql new file mode 100644 index 0000000000..2fd9e8b121 --- /dev/null +++ b/db/changes/10491-august/00-ACL_workerDisableExcluded.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalId) + VALUES ('WorkerDisableExcluded','*','*','ALLOW','hr'); \ No newline at end of file diff --git a/db/changes/10491-august/00-aclBusiness.sql b/db/changes/10491-august/00-aclBusiness.sql new file mode 100644 index 0000000000..8ea2c6d83b --- /dev/null +++ b/db/changes/10491-august/00-aclBusiness.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Business', '*', '*', 'ALLOW', 'ROLE', 'hr'); \ No newline at end of file diff --git a/db/changes/10491-august/00-aclUsesMana.sql b/db/changes/10491-august/00-aclUsesMana.sql new file mode 100644 index 0000000000..5bb7178dd4 --- /dev/null +++ b/db/changes/10491-august/00-aclUsesMana.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Sale', 'usesMana', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10491-august/00-invoiceInPdf.sql b/db/changes/10491-august/00-invoiceInPdf.sql new file mode 100644 index 0000000000..bce256c056 --- /dev/null +++ b/db/changes/10491-august/00-invoiceInPdf.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('InvoiceIn', 'invoiceInPdf', 'READ', 'ALLOW', 'ROLE', 'administrative'), + ('InvoiceIn', 'invoiceInEmail', 'WRITE', 'ALLOW', 'ROLE', 'administrative'); diff --git a/db/changes/10491-august/00-osTicketConfig.sql b/db/changes/10491-august/00-osTicketConfig.sql new file mode 100644 index 0000000000..10a58b6c82 --- /dev/null +++ b/db/changes/10491-august/00-osTicketConfig.sql @@ -0,0 +1,8 @@ +ALTER TABLE `vn`.`osTicketConfig` DROP COLUMN `action`; +ALTER TABLE `vn`.`osTicketConfig` ADD responseType varchar(100) NULL; +ALTER TABLE `vn`.`osTicketConfig` ADD fromEmailId INT NULL; +ALTER TABLE `vn`.`osTicketConfig` ADD replyTo varchar(100) NULL; + +UPDATE `vn`.`osTicketConfig` + SET responseType='reply', fromEmailId=5, replyTo='all' +WHERE id=0; \ No newline at end of file diff --git a/db/changes/10491-august/00-zipConfig.sql b/db/changes/10491-august/00-zipConfig.sql new file mode 100644 index 0000000000..134ce77706 --- /dev/null +++ b/db/changes/10491-august/00-zipConfig.sql @@ -0,0 +1,9 @@ +CREATE TABLE `vn`.`zipConfig` ( + `id` double(10,2) NOT NULL, + `maxSize` int(11) DEFAULT NULL COMMENT 'in MegaBytes', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('ZipConfig', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10490-goldenSummer/00-aclNotification.sql b/db/changes/10501-november/00-aclNotification.sql similarity index 100% rename from db/changes/10490-goldenSummer/00-aclNotification.sql rename to db/changes/10501-november/00-aclNotification.sql diff --git a/db/changes/10490-august/00-packingSiteConfig.sql b/db/changes/10501-november/00-packingSiteConfig.sql similarity index 100% rename from db/changes/10490-august/00-packingSiteConfig.sql rename to db/changes/10501-november/00-packingSiteConfig.sql diff --git a/db/changes/10490-august/00-packingSiteUpdate.sql b/db/changes/10501-november/00-packingSiteUpdate.sql similarity index 100% rename from db/changes/10490-august/00-packingSiteUpdate.sql rename to db/changes/10501-november/00-packingSiteUpdate.sql diff --git a/db/changes/10490-august/00-salix_url.sql b/db/changes/10501-november/00-salix_url.sql similarity index 100% rename from db/changes/10490-august/00-salix_url.sql rename to db/changes/10501-november/00-salix_url.sql diff --git a/db/changes/10502-november/00-deletePickupContact.sql b/db/changes/10502-november/00-deletePickupContact.sql new file mode 100644 index 0000000000..6bfa662c5e --- /dev/null +++ b/db/changes/10502-november/00-deletePickupContact.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`claimConfig` DROP COLUMN `pickupContact`; diff --git a/db/changes/10502-november/00-itemShelvingACL.sql b/db/changes/10502-november/00-itemShelvingACL.sql new file mode 100644 index 0000000000..fc32fe1edc --- /dev/null +++ b/db/changes/10502-november/00-itemShelvingACL.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) +VALUES + ('ItemShelving', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('ItemShelving', '*', 'WRITE', 'ALLOW', 'ROLE', 'production'); \ No newline at end of file diff --git a/db/changes/10502-november/00-itemShelvingPlacementSupplyStockACL.sql b/db/changes/10502-november/00-itemShelvingPlacementSupplyStockACL.sql new file mode 100644 index 0000000000..25eac8d518 --- /dev/null +++ b/db/changes/10502-november/00-itemShelvingPlacementSupplyStockACL.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) +VALUES + ('ItemShelvingPlacementSupplyStock', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); + diff --git a/db/changes/10502-november/00-workerTimeControlMail.sql b/db/changes/10502-november/00-workerTimeControlMail.sql new file mode 100644 index 0000000000..e3d169a837 --- /dev/null +++ b/db/changes/10502-november/00-workerTimeControlMail.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`workerTimeControlMail` CHANGE emailResponse reason text CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL; \ No newline at end of file diff --git a/db/changes/10502-november/00-zone_getPostalCode.sql b/db/changes/10502-november/00-zone_getPostalCode.sql new file mode 100644 index 0000000000..58a281cb2d --- /dev/null +++ b/db/changes/10502-november/00-zone_getPostalCode.sql @@ -0,0 +1,54 @@ +DROP PROCEDURE IF EXISTS `vn`.`zone_getPostalCode`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getPostalCode`(vSelf INT) +BEGIN +/** + * Devuelve los códigos postales incluidos en una zona + */ + DECLARE vGeoFk INT DEFAULT NULL; + + DROP TEMPORARY TABLE IF EXISTS tmp.zoneNodes; + CREATE TEMPORARY TABLE tmp.zoneNodes ( + geoFk INT, + name VARCHAR(100), + parentFk INT, + sons INT, + isChecked BOOL DEFAULT 0, + zoneFk INT, + PRIMARY KEY zoneNodesPk (zoneFk, geoFk), + INDEX(geoFk)) + ENGINE = MEMORY; + + CALL zone_getLeaves2(vSelf, NULL , NULL); + + UPDATE tmp.zoneNodes zn + SET isChecked = 0 + WHERE parentFk IS NULL; + + myLoop: LOOP + SET vGeoFk = NULL; + SELECT geoFk INTO vGeoFk + FROM tmp.zoneNodes zn + WHERE NOT isChecked + LIMIT 1; + + CALL zone_getLeaves2(vSelf, vGeoFk, NULL); + UPDATE tmp.zoneNodes + SET isChecked = TRUE + WHERE geoFk = vGeoFk; + + IF vGeoFk IS NULL THEN + LEAVE myLoop; + END IF; + END LOOP; + + DELETE FROM tmp.zoneNodes + WHERE sons > 0; + + SELECT zn.geoFk, zn.name + FROM tmp.zoneNodes zn + JOIN zone z ON z.id = zn.zoneFk; +END$$ +DELIMITER ; diff --git a/db/changes/10503-november/delete.keep b/db/changes/10503-november/delete.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/docker.js b/db/docker.js index 3eb262a822..05217d659a 100644 --- a/db/docker.js +++ b/db/docker.js @@ -47,12 +47,8 @@ module.exports = class Docker { if (ci) network = `--network="${networkName}"`; log('Starting container...'); - const container = await this.execP(` - docker run \ - ${network} \ - --env RUN_CHOWN=${runChown} \ - -d ${dockerArgs} salix-db - `); + const container = await this.execP( + `docker run ${network} --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`); this.id = container.stdout.trim(); try { diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 5cadaf77c2..6624e99f44 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -353,48 +353,48 @@ INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`) INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`) VALUES - (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 1), - (2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 1), - (3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 1), - (4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 1), - (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1), - (6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1), - (7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1), - (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1), - (9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1), - (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1), - (11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1), - (12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1), - (101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), - (103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), - (104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), - (105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0), - (122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0), - (123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 0), - (124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0), - (125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0), - (126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0), - (127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0), - (128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0), - (129, 'Luke Cages Bar', 'address 29', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0), - (130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0); + (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 1), + (2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 1), + (3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 1), + (4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1 , 1104, 2, NULL, NULL, 0, 1), + (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1), + (6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1), + (7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1), + (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1), + (9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1), + (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1), + (11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1), + (12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1), + (101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), + (103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), + (104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), + (105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0), + (122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0), + (123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 0), + (124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0), + (125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0), + (126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0), + (127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0), + (128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0), + (129, 'Luke Cages Bar', 'address 29', 'Gotham', 'EC170150', 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0), + (130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0); INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`) SELECT name, CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, 1, id, 2, 1 @@ -918,7 +918,7 @@ INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`) (3, 'Perdida', 'LOST'); -INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`, `hostFk`) +INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `freightItemFk`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`, `hostFk`) VALUES (1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1, 'pc1'), (2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1, NULL), @@ -1778,10 +1778,10 @@ INSERT INTO `vn`.`claimEnd`(`id`, `saleFk`, `claimFk`, `workerFk`, `claimDestina (1, 31, 4, 21, 2), (2, 32, 3, 21, 3); -INSERT INTO `vn`.`claimConfig`(`id`, `pickupContact`, `maxResponsibility`) +INSERT INTO `vn`.`claimConfig`(`id`, `maxResponsibility`) VALUES - (1, 'Contact description', 50), - (2, 'Contact description', 30); + (1, 50), + (2, 30); INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`) VALUES @@ -1791,7 +1791,7 @@ INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRa (1104, 2500, 150.00, 0.02, 0.10, 1.00); INSERT INTO vn.claimRma (`id`, `code`, `created`, `workerFk`) -VALUES + VALUES (1, '02676A049183', DEFAULT, 1106), (2, '02676A049183', DEFAULT, 1106), (3, '02676A049183', DEFAULT, 1107), @@ -2047,6 +2047,7 @@ INSERT INTO `vn`.`zoneIncluded` (`zoneFk`, `geoFk`, `isIncluded`) (8, 4, 0), (8, 5, 0), (8, 1, 1), + (9, 7, 1), (10, 14, 1); INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `dated`) @@ -2266,12 +2267,16 @@ INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `started`, `ended`) VALUES (9, 'range', DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR)); -INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`) +INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`, `isSendMail`) VALUES - (1106, CONCAT(util.VN_CURDATE(), ' 07:00'), TRUE, 'in'), - (1106, CONCAT(util.VN_CURDATE(), ' 10:00'), TRUE, 'middle'), - (1106, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle'), - (1106, CONCAT(util.VN_CURDATE(), ' 14:50'), TRUE, 'out'); + (1106, CONCAT(util.VN_CURDATE(), ' 07:00'), TRUE, 'in', 0), + (1106, CONCAT(util.VN_CURDATE(), ' 10:00'), TRUE, 'middle', 0), + (1106, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle', 0), + (1106, CONCAT(util.VN_CURDATE(), ' 14:50'), TRUE, 'out', 0), + (1107, CONCAT(util.VN_CURDATE(), ' 07:00'), TRUE, 'in', 1), + (1107, CONCAT(util.VN_CURDATE(), ' 10:00'), TRUE, 'middle', 1), + (1107, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle', 1), + (1107, CONCAT(util.VN_CURDATE(), ' 14:50'), TRUE, 'out', 1); INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `code`) VALUES @@ -2710,3 +2715,7 @@ INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `lev UPDATE `account`.`user` SET `hasGrant` = 1 WHERE `id` = 66; + +INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`) + VALUES + (0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all'); diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 402c8e695b..9f2370832b 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -27518,7 +27518,7 @@ CREATE TABLE `expedition` ( `id` int(11) NOT NULL AUTO_INCREMENT, `agencyModeFk` int(11) NOT NULL, `ticketFk` int(10) NOT NULL, - `isBox` int(11) DEFAULT 1 COMMENT 'Este campo realmente en un campo itemFk, haciendo referencia al artículo que nos va a facturar el proveedor de transporte.\nSe debería llamar freightItemFk', + `freightItemFk` int(11) DEFAULT 1 COMMENT 'Este campo realmente en un campo itemFk, haciendo referencia al artículo que nos va a facturar el proveedor de transporte.\nSe debería llamar freightItemFk', `created` timestamp NULL DEFAULT current_timestamp(), `isRefund__` bit(1) DEFAULT b'0' COMMENT 'Deprecado 01/06/2022', `isPickUp__` bit(1) DEFAULT b'0' COMMENT 'Deprecado 01/06/2022', @@ -27534,7 +27534,7 @@ CREATE TABLE `expedition` ( `hasNewRoute` bit(1) NOT NULL DEFAULT b'0', PRIMARY KEY (`id`), KEY `index1` (`agencyModeFk`), - KEY `index2` (`isBox`), + KEY `index2` (`freightItemFk`), KEY `index3` (`created`), KEY `index4` (`ticketFk`), KEY `expedition_fk3_idx` (`packagingFk`), @@ -27567,7 +27567,7 @@ BEGIN DECLARE vShipFk INT; - IF NEW.isBox > 0 THEN + IF NEW.freightItemFk > 0 THEN UPDATE ticket SET packages = nz(packages) + 1 WHERE id = NEW.ticketFk; @@ -27638,7 +27638,7 @@ DELIMITER ;; BEGIN UPDATE ticket t SET packages = (SELECT COUNT(counter)-1 - FROM expedition e WHERE e.ticketFk = OLD.ticketFk and e.isBox) + FROM expedition e WHERE e.ticketFk = OLD.ticketFk and e.freightItemFk) WHERE t.id = OLD.ticketFk; END */;; @@ -36287,7 +36287,7 @@ CREATE TABLE `sorter` ( `created` datetime NOT NULL, `routeFk` int(10) unsigned NOT NULL, `ticketFk` int(10) NOT NULL, - `isBox` int(11) DEFAULT 1, + `freightItemFk` int(11) DEFAULT 1, `itemFk` int(11) DEFAULT NULL, `width` decimal(10,2) DEFAULT 0.00, `depth` decimal(10,2) DEFAULT 0.00, @@ -44956,7 +44956,7 @@ BEGIN SELECT SUM((t.zonePrice - t.zoneBonus) * ebv.ratio) INTO deliveryPrice FROM vn.ticket t LEFT JOIN expedition e ON e.ticketFk = t.id - JOIN expeditionBoxVol ebv ON ebv.boxFk = e.isBox + JOIN expeditionBoxVol ebv ON ebv.boxFk = e.freightItemFk WHERE t.id = vTicketFk; END IF; @@ -46492,7 +46492,7 @@ BEGIN LEFT JOIN item i ON i.id = b.itemFk LEFT JOIN itemType it ON it.id = i.typeFk LEFT JOIN itemCategory ic ON ic.id = it.categoryFk - LEFT JOIN packaging p ON p.id = b.packageFk AND NOT p.isBox + LEFT JOIN packaging p ON p.id = b.packageFk AND NOT p.freightItemFk JOIN volumeConfig vc ON TRUE WHERE b.id = vSelf; @@ -53229,7 +53229,7 @@ BEGIN INNER JOIN vn.ticketState ts ON ts.ticketFk = exp.ticketFk LEFT JOIN vn.address a ON t.addressFk = a.id LEFT JOIN vn.warehouse w ON t.warehouseFk = w.id - WHERE t.routeFk = vRouteFk AND exp.isBox > 0; + WHERE t.routeFk = vRouteFk AND exp.freightItemFk > 0; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; @@ -53760,7 +53760,7 @@ BEGIN GROUP BY sub.ticketFk ) sub2 ON sub2.ticketFk = t.id LEFT JOIN expeditionStateType est ON est.id = e.stateTypeFk - WHERE t.routeFk = vRouteFk AND e.isBox <> FALSE + WHERE t.routeFk = vRouteFk AND e.freightItemFk <> FALSE ORDER BY r.created, t.priority DESC; END ;; DELIMITER ; diff --git a/db/tests/vn/orderConfirmWithUser.spec.js b/db/tests/vn/orderConfirmWithUser.spec.js deleted file mode 100644 index f2a3d0c4e3..0000000000 --- a/db/tests/vn/orderConfirmWithUser.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -const app = require('vn-loopback/server/server'); -const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; - -// #1885 -xdescribe('order_confirmWithUser()', () => { - it('should confirm an order', async() => { - let stmts = []; - let stmt; - - stmts.push('START TRANSACTION'); - - let params = { - orderFk: 10, - userId: 9 - }; - // problema: la funcion order_confirmWithUser tiene una transacción, por tanto esta nunca hace rollback - stmt = new ParameterizedSQL('CALL hedera.order_confirmWithUser(?, ?)', [ - params.orderFk, - params.userId - ]); - stmts.push(stmt); - - stmt = new ParameterizedSQL('SELECT confirmed FROM hedera.order WHERE id = ?', [ - params.orderFk - ]); - let orderIndex = stmts.push(stmt) - 1; - - stmts.push('ROLLBACK'); - - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await app.models.Ticket.rawStmt(sql); - - savedDescription = result[orderIndex][0].confirmed; - - expect(savedDescription).toBeTruthy(); - }); -}); diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index cd6d39795b..f0d726ed67 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -394,11 +394,18 @@ export default { intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]', originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]', buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]', + densityCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Density"]', + openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]', + advancedSearchItemType: 'vn-item-search-panel vn-autocomplete[ng-model="filter.typeFk"]', + advancedSearchButton: 'vn-item-search-panel button[type=submit]', + advancedSmartTableButton: 'vn-item-index vn-button[icon="search"]', + advancedSmartTableGrouping: 'vn-item-index vn-textfield[name=grouping]', weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]', saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button' }, itemFixedPrice: { add: 'vn-fixed-price vn-icon-button[icon="add_circle"]', + firstItemID: 'vn-fixed-price tr:nth-child(2) vn-autocomplete[ng-model="price.itemFk"]', fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)', fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]', fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]', @@ -408,7 +415,8 @@ export default { fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]', fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]', fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]', - fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]' + fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]', + orderColumnId: 'vn-fixed-price th[field="itemFk"]' }, itemCreateView: { temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]', @@ -596,7 +604,14 @@ export default { submitNotesButton: 'button[type=submit]' }, ticketExpedition: { - thirdExpeditionRemoveButton: 'vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(3) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]', + firstSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(1) vn-check[ng-model="expedition.checked"]', + thirdSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(3) vn-check[ng-model="expedition.checked"]', + deleteExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="delete"]', + moveExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="keyboard_arrow_down"]', + moreMenuWithoutRoute: 'vn-item[name="withoutRoute"]', + moreMenuWithRoute: 'vn-item[name="withRoute"]', + newRouteId: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.newRoute"]', + saveButton: '.vn-dialog.shown [response="accept"]', expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr' }, ticketPackages: { @@ -1100,7 +1115,8 @@ export default { anyBuyLine: 'vn-entry-summary tr.dark-row' }, entryBasicData: { - reference: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.ref"]', + reference: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.reference"]', + invoiceNumber: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.invoiceNumber"]', notes: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.notes"]', observations: 'vn-entry-basic-data vn-textarea[ng-model="$ctrl.entry.observation"]', supplier: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]', diff --git a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js new file mode 100644 index 0000000000..4fc2802098 --- /dev/null +++ b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js @@ -0,0 +1,77 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('SmartTable SearchBar integration', () => { + let browser; + let page; + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('salesPerson', 'item'); + await page.waitToClick(selectors.globalItems.searchButton); + }); + + afterAll(async() => { + await browser.close(); + }); + + describe('as filters', () => { + it('should search by type in searchBar', async() => { + await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton); + await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium'); + await page.waitToClick(selectors.itemsIndex.advancedSearchButton); + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); + }); + + it('should reload page and have same results', async() => { + await page.reload({ + waitUntil: 'networkidle2' + }); + + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); + }); + + it('should search by grouping in smartTable', async() => { + await page.waitToClick(selectors.itemsIndex.advancedSmartTableButton); + await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1'); + await page.keyboard.press('Enter'); + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); + }); + + it('should now reload page and have same results', async() => { + await page.reload({ + waitUntil: 'networkidle2' + }); + + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); + }); + }); + + describe('as orders', () => { + it('should order by first id', async() => { + await page.loginAndModule('developer', 'item'); + await page.accessToSection('item.fixedPrice'); + await page.doSearch(); + + const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); + + expect(result).toEqual('1'); + }); + + it('should order by last id', async() => { + await page.waitToClick(selectors.itemFixedPrice.orderColumnId); + const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); + + expect(result).toEqual('13'); + }); + + it('should reload page and have same order', async() => { + await page.reload({ + waitUntil: 'networkidle2' + }); + const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); + + expect(result).toEqual('13'); + }); + }); +}); diff --git a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js index dd2525f439..f970247e51 100644 --- a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js +++ b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js @@ -18,7 +18,8 @@ describe('Ticket expeditions and log path', () => { }); it(`should delete a former expedition and confirm the remaining expedition are the expected ones`, async() => { - await page.waitToClick(selectors.ticketExpedition.thirdExpeditionRemoveButton); + await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox); + await page.waitToClick(selectors.ticketExpedition.deleteExpeditionButton); await page.waitToClick(selectors.globalItems.acceptButton); await page.reloadSection('ticket.card.expedition'); diff --git a/e2e/paths/05-ticket/20_moveExpedition.spec.js b/e2e/paths/05-ticket/20_moveExpedition.spec.js new file mode 100644 index 0000000000..cf1c5ded32 --- /dev/null +++ b/e2e/paths/05-ticket/20_moveExpedition.spec.js @@ -0,0 +1,50 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Ticket expeditions', () => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('production', 'ticket'); + await page.accessToSearchResult('1'); + await page.accessToSection('ticket.card.expedition'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it(`should move one expedition to new ticket withoute route`, async() => { + await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox); + await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton); + await page.waitToClick(selectors.ticketExpedition.moreMenuWithoutRoute); + await page.waitToClick(selectors.ticketExpedition.saveButton); + await page.waitForState('ticket.card.summary'); + await page.accessToSection('ticket.card.expedition'); + + await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {}); + const result = await page + .countElement(selectors.ticketExpedition.expeditionRow); + + expect(result).toEqual(1); + }); + + it(`should move one expedition to new ticket with route`, async() => { + await page.waitToClick(selectors.ticketExpedition.firstSaleCheckbox); + await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton); + await page.waitToClick(selectors.ticketExpedition.moreMenuWithRoute); + await page.write(selectors.ticketExpedition.newRouteId, '1'); + await page.waitToClick(selectors.ticketExpedition.saveButton); + await page.waitForState('ticket.card.summary'); + await page.accessToSection('ticket.card.expedition'); + + await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {}); + const result = await page + .countElement(selectors.ticketExpedition.expeditionRow); + + expect(result).toEqual(1); + }); +}); diff --git a/e2e/paths/12-entry/05_basicData.spec.js b/e2e/paths/12-entry/05_basicData.spec.js index c1aa140192..3b5f40c357 100644 --- a/e2e/paths/12-entry/05_basicData.spec.js +++ b/e2e/paths/12-entry/05_basicData.spec.js @@ -19,6 +19,7 @@ describe('Entry basic data path', () => { it('should edit the basic data', async() => { await page.write(selectors.entryBasicData.reference, 'new movement 8'); + await page.write(selectors.entryBasicData.invoiceNumber, 'new movement 8'); await page.write(selectors.entryBasicData.notes, 'new notes'); await page.write(selectors.entryBasicData.observations, ' edited'); await page.autocompleteSearch(selectors.entryBasicData.supplier, 'Plants nick'); @@ -45,6 +46,13 @@ describe('Entry basic data path', () => { expect(result).toEqual('new movement 8'); }); + it('should confirm the invoiceNumber was edited', async() => { + await page.reloadSection('entry.card.basicData'); + const result = await page.waitToGetProperty(selectors.entryBasicData.invoiceNumber, 'value'); + + expect(result).toEqual('new movement 8'); + }); + it('should confirm the note was edited', async() => { const result = await page.waitToGetProperty(selectors.entryBasicData.notes, 'value'); diff --git a/front/core/components/crud-model/crud-model.js b/front/core/components/crud-model/crud-model.js index 4994e15475..1095985dc1 100644 --- a/front/core/components/crud-model/crud-model.js +++ b/front/core/components/crud-model/crud-model.js @@ -99,6 +99,18 @@ export default class CrudModel extends ModelProxy { return this.refresh(); } + /** + * Applies a new filter to the model. + * + * @param {Object} params Custom parameters + * @return {Promise} The request promise + */ + + applyParams(params) { + this.userParams = params; + return this.refresh(); + } + removeFilter() { return this.applyFilter(null, null); } diff --git a/front/core/components/input-file/index.html b/front/core/components/input-file/index.html index 5ec7e1da49..14d138cfe6 100644 --- a/front/core/components/input-file/index.html +++ b/front/core/components/input-file/index.html @@ -3,7 +3,7 @@ ng-transclude="prepend" class="prepend"> -
+
+ accept="{{$ctrl.accept}}">
diff --git a/front/core/components/input-file/style.scss b/front/core/components/input-file/style.scss index 44d11f7744..72470c4fe3 100644 --- a/front/core/components/input-file/style.scss +++ b/front/core/components/input-file/style.scss @@ -4,4 +4,13 @@ .value { cursor: pointer; } + .control { + & > input[type=file] { + opacity: 0; + } + & > section { + position: absolute; + bottom: 0; + } + } } \ No newline at end of file diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index 89b5d7df69..10ec1f6085 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -139,8 +139,12 @@ export default class Searchbar extends Component { } removeParam(index) { + const field = this.params[index].key; + this.filterSanitizer(field); + this.params.splice(index, 1); - this.doSearch(this.fromBar(), 'bar'); + this.toRemove = field; + this.doSearch(this.fromBar(), 'removeBar'); } fromBar() { @@ -163,7 +167,7 @@ export default class Searchbar extends Component { let keys = Object.keys(filter); keys.forEach(key => { - if (key == 'search') return; + if (key == 'search' || key == 'tableQ' || key == 'tableOrder') return; let value = filter[key]; let chip; @@ -198,6 +202,7 @@ export default class Searchbar extends Component { let promise = this.onSearch({$params: filter}); promise = promise || this.$q.resolve(); promise.then(data => this.onFilter(filter, source, data)); + this.toBar(filter); } onFilter(filter, source, data) { @@ -238,8 +243,11 @@ export default class Searchbar extends Component { } else { state = this.searchState; - if (filter) + if (filter) { + if (this.tableQ) + filter.tableQ = this.tableQ; params = {q: JSON.stringify(filter)}; + } if (this.$state.is(state)) opts = {location: 'replace'}; } @@ -247,6 +255,12 @@ export default class Searchbar extends Component { this.filter = filter; + if (source == 'removeBar') { + delete params[this.toRemove]; + delete this.model.userParams[this.toRemove]; + this.model.refresh(); + } + if (!filter && this.model) this.model.clear(); if (source != 'state') @@ -269,9 +283,14 @@ export default class Searchbar extends Component { this.model.clear(); return; } + if (Object.keys(filter).length === 0) { + this.filterSanitizer('search'); + if (this.model.userParams) + delete this.model.userParams['search']; + } let where = null; - let params = null; + let params = {}; if (this.exprBuilder) { where = buildFilter(filter, @@ -283,9 +302,89 @@ export default class Searchbar extends Component { params = this.fetchParams({$params: params}); } + this.tableQ = null; + + const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length; + if (hasParams) { + const stateFilter = JSON.parse(this.$params.q); + for (let param in stateFilter) { + if (param != 'tableQ' && param != 'orderQ') + this.filterSanitizer(param); + } + + for (let param in this.suggestedFilter) { + this.filterSanitizer(param); + delete stateFilter[param]; + } + + this.tableQ = stateFilter.tableQ; + for (let param in stateFilter.tableQ) + params[param] = stateFilter.tableQ[param]; + + Object.assign(stateFilter, params); + return this.model.applyParams(params) + .then(() => this.model.data); + } + return this.model.applyFilter(where ? {where} : null, params) .then(() => this.model.data); } + + filterSanitizer(field) { + if (!field) return; + const userFilter = this.model.userFilter; + const userParams = this.model.userParams; + const where = userFilter && userFilter.where; + + if (this.model.userParams) + delete this.model.userParams[field]; + + if (this.exprBuilder) { + const param = this.exprBuilder({ + param: field, + value: null + }); + if (param) [field] = Object.keys(param); + } + + if (!where) return; + + const whereKeys = Object.keys(where); + for (let key of whereKeys) { + removeProp(where, field, key); + + if (Object.keys(where).length == 0) + delete userFilter.where; + } + + function removeProp(obj, targetProp, prop) { + if (prop == targetProp) + delete obj[prop]; + + if (prop === 'and' || prop === 'or' && obj[prop]) { + const arrayCopy = obj[prop].slice(); + for (let param of arrayCopy) { + const [key] = Object.keys(param); + const index = obj[prop].findIndex(param => { + return Object.keys(param)[0] == key; + }); + if (key == targetProp) + obj[prop].splice(index, 1); + + if (param[key] instanceof Array) + removeProp(param, field, key); + + if (Object.keys(param).length == 0) + obj[prop].splice(index, 1); + } + + if (obj[prop].length == 0) + delete obj[prop]; + } + } + + return {userFilter, userParams}; + } } ngModule.vnComponent('vnSearchbar', { diff --git a/front/core/components/searchbar/searchbar.spec.js b/front/core/components/searchbar/searchbar.spec.js index e4f58d294c..ed8fd9d074 100644 --- a/front/core/components/searchbar/searchbar.spec.js +++ b/front/core/components/searchbar/searchbar.spec.js @@ -6,7 +6,7 @@ describe('Component vnSearchbar', () => { let $state; let $params; let $scope; - let filter = {id: 1, search: 'needle'}; + const filter = {id: 1, search: 'needle'}; beforeEach(ngModule('vnCore', $stateProvider => { $stateProvider @@ -70,8 +70,8 @@ describe('Component vnSearchbar', () => { describe('filter() setter', () => { it(`should update the bar params and search`, () => { - let withoutHours = new Date(2000, 1, 1); - let withHours = new Date(withoutHours.getTime()); + const withoutHours = new Date(2000, 1, 1); + const withHours = new Date(withoutHours.getTime()); withHours.setHours(12, 30, 15, 10); controller.filter = { @@ -83,8 +83,8 @@ describe('Component vnSearchbar', () => { myObjectProp: {myProp: 1} }; - let chips = {}; - for (let param of controller.params || []) + const chips = {}; + for (const param of controller.params || []) chips[param.key] = param.chip; expect(controller.searchString).toBe('needle'); @@ -172,13 +172,22 @@ describe('Component vnSearchbar', () => { describe('removeParam()', () => { it(`should remove the parameter from the filter`, () => { jest.spyOn(controller, 'doSearch'); + controller.model = { + refresh: jest.fn(), + userParams: { + id: 1 + } + }; + + controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve()); + jest.spyOn(controller.model, 'applyParams'); controller.filter = filter; controller.removeParam(0); expect(controller.doSearch).toHaveBeenCalledWith({ search: 'needle' - }, 'bar'); + }, 'removeBar'); }); }); @@ -199,7 +208,7 @@ describe('Component vnSearchbar', () => { it(`should go to the summary state when one result`, () => { jest.spyOn($state, 'go'); - let data = [{id: 1}]; + const data = [{id: 1}]; controller.baseState = 'foo'; controller.onFilter(filter, 'any', data); @@ -214,7 +223,7 @@ describe('Component vnSearchbar', () => { $scope.$apply(); jest.spyOn($state, 'go'); - let data = [{id: 1}]; + const data = [{id: 1}]; controller.baseState = 'foo'; controller.onFilter(filter, 'any', data); @@ -229,7 +238,7 @@ describe('Component vnSearchbar', () => { $scope.$apply(); jest.spyOn($state, 'go'); - let data = [{id: 1}]; + const data = [{id: 1}]; controller.baseState = 'foo'; controller.onFilter(filter, 'any', data); @@ -247,7 +256,7 @@ describe('Component vnSearchbar', () => { controller.onFilter(filter, 'any'); $scope.$apply(); - let queryParams = {q: JSON.stringify(filter)}; + const queryParams = {q: JSON.stringify(filter)}; expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined); expect(controller.filter).toEqual(filter); diff --git a/front/core/components/smart-table/index.html b/front/core/components/smart-table/index.html index f26a6b4a2d..752019313c 100644 --- a/front/core/components/smart-table/index.html +++ b/front/core/components/smart-table/index.html @@ -103,3 +103,4 @@
+ diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index 9e6e7009c9..8d2c3c1531 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -15,9 +15,17 @@ export default class SmartTable extends Component { this.$inputsScope; this.columns = []; this.autoSave = false; + this.autoState = true; this.transclude(); } + $onChanges() { + if (this.model) { + this.defaultFilter(); + this.defaultOrder(); + } + } + $onDestroy() { const styleElement = document.querySelector('style[id="smart-table"]'); if (this.$.css && styleElement) @@ -47,10 +55,8 @@ export default class SmartTable extends Component { set model(value) { this._model = value; - if (value) { + if (value) this.$.model = value; - this.defaultOrder(); - } } getDefaultViewConfig() { @@ -160,8 +166,36 @@ export default class SmartTable extends Component { } } + defaultFilter() { + if (this.disabledTableFilter || !this.$params.q) return; + + const stateFilter = JSON.parse(this.$params.q).tableQ; + if (!stateFilter || !this.exprBuilder) return; + + const columns = this.columns.map(column => column.field); + + this.displaySearch(); + if (!this.$inputsScope.searchProps) + this.$inputsScope.searchProps = {}; + + for (let param in stateFilter) { + if (columns.includes(param)) { + const whereParams = {[param]: stateFilter[param]}; + Object.assign(this.$inputsScope.searchProps, whereParams); + this.addFilter(param, stateFilter[param]); + } + } + } + defaultOrder() { - const order = this.model.order; + if (this.disabledTableOrder) return; + + let stateOrder; + if (this.$params.q) + stateOrder = JSON.parse(this.$params.q).tableOrder; + + const order = stateOrder ? stateOrder : this.model.order; + if (!order) return; const orderFields = order.split(', '); @@ -195,6 +229,9 @@ export default class SmartTable extends Component { this.setPriority(column.element, priority); } } + + this.model.order = order; + this.refresh(); } registerColumns() { @@ -395,28 +432,54 @@ export default class SmartTable extends Component { } searchByColumn(field) { - const searchCriteria = this.$inputsScope.searchProps[field]; - const emptySearch = searchCriteria === '' || searchCriteria == null; - const filters = this.filterSanitizer(field); if (filters && filters.userFilter) this.model.userFilter = filters.userFilter; - if (!emptySearch) - this.addFilter(field, this.$inputsScope.searchProps[field]); - else this.model.refresh(); + this.addFilter(field, this.$inputsScope.searchProps[field]); + } + + searchPropsSanitizer() { + if (!this.$inputsScope || !this.$inputsScope.searchProps) return null; + let searchProps = this.$inputsScope.searchProps; + const searchPropsArray = Object.entries(searchProps); + searchProps = searchPropsArray.filter( + ([key, value]) => value && value != '' + ); + + return Object.fromEntries(searchProps); } addFilter(field, value) { - let where = {[field]: value}; + if (value == '') value = null; - if (this.exprBuilder) { - where = buildFilter(where, (param, value) => - this.exprBuilder({param, value}) - ); + let stateFilter = {tableQ: {}}; + if (this.$params.q) { + stateFilter = JSON.parse(this.$params.q); + if (!stateFilter.tableQ) + stateFilter.tableQ = {}; + delete stateFilter.tableQ[field]; } - this.model.addFilter({where}); + const whereParams = {[field]: value}; + if (value) { + let where = {[field]: value}; + if (this.exprBuilder) { + where = buildFilter(whereParams, (param, value) => + this.exprBuilder({param, value}) + ); + } + this.model.addFilter({where}); + } + + const searchProps = this.searchPropsSanitizer(); + + Object.assign(stateFilter.tableQ, searchProps); + + const params = {q: JSON.stringify(stateFilter)}; + + this.$state.go(this.$state.current.name, params, {location: 'replace'}); + this.refresh(); } applySort() { @@ -426,7 +489,18 @@ export default class SmartTable extends Component { if (order) this.model.order = order; - this.model.refresh(); + let stateFilter = {tableOrder: {}}; + if (this.$params.q) { + stateFilter = JSON.parse(this.$params.q); + if (!stateFilter.tableOrder) + stateFilter.tableOrder = {}; + } + + stateFilter.tableOrder = order; + + const params = {q: JSON.stringify(stateFilter)}; + this.$state.go(this.$state.current.name, params, {location: 'replace'}); + this.refresh(); } filterSanitizer(field) { @@ -535,6 +609,8 @@ ngModule.vnComponent('smartTable', { autoSave: ' { $httpBackend = _$httpBackend_; $element = $compile(``)($rootScope); controller = $element.controller('smartTable'); + controller.model = { + refresh: jest.fn().mockReturnValue(new Promise(resolve => resolve())), + addFilter: jest.fn(), + userParams: {} + }; })); afterEach(() => { @@ -83,7 +88,7 @@ describe('Component smartTable', () => { describe('defaultOrder', () => { it('should insert a new object to the controller sortCriteria with a sortType value of "ASC"', () => { const element = document.createElement('div'); - controller.model = {order: 'id'}; + controller.model.order = 'id'; controller.columns = [ {field: 'id', element: element}, {field: 'test1', element: element}, @@ -101,7 +106,8 @@ describe('Component smartTable', () => { 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.model.order = 'test1, id DESC'; + controller.columns = [ {field: 'id', element: element}, {field: 'test1', element: element}, @@ -125,8 +131,6 @@ describe('Component smartTable', () => { describe('addFilter()', () => { it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => { - controller.model = {addFilter: jest.fn()}; - controller.addFilter('myField', 'myValue'); const expectedFilter = { @@ -140,7 +144,6 @@ describe('Component smartTable', () => { it('should call the model addFilter() with a built where filter resultant of exprBuilder()', () => { controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'}); - controller.model = {addFilter: jest.fn()}; controller.addFilter('myField', 'myValue'); @@ -155,35 +158,48 @@ describe('Component smartTable', () => { }); describe('applySort()', () => { - it('should call the model refresh() without making changes on the model order', () => { - controller.model = {refresh: jest.fn()}; + it('should call the $state go and model refresh without making changes on the model order', () => { + controller.$state = { + go: jest.fn(), + current: { + name: 'section' + } + }; + jest.spyOn(controller, 'refresh'); controller.applySort(); expect(controller.model.order).toBeUndefined(); - expect(controller.model.refresh).toHaveBeenCalled(); + expect(controller.$state.go).toHaveBeenCalled(); + expect(controller.refresh).toHaveBeenCalled(); }); - it('should call the model.refresh() after setting model order according to the controller sortCriteria', () => { - controller.model = {refresh: jest.fn()}; + it('should call the $state go and model refresh after setting model order according to the controller sortCriteria', () => { const orderBy = {field: 'myField', sortType: 'ASC'}; + controller.$state = { + go: jest.fn(), + current: { + name: 'section' + } + }; + jest.spyOn(controller, 'refresh'); + controller.sortCriteria = [orderBy]; controller.applySort(); expect(controller.model.order).toEqual(`${orderBy.field} ${orderBy.sortType}`); - expect(controller.model.refresh).toHaveBeenCalled(); + expect(controller.$state.go).toHaveBeenCalled(); + expect(controller.refresh).toHaveBeenCalled(); }); }); describe('filterSanitizer()', () => { it('should remove the where filter after leaving no fields in it', () => { - controller.model = { - userFilter: { - where: {fieldToRemove: 'valueToRemove'} - }, - userParams: {} + controller.model.userFilter = { + where: {fieldToRemove: 'valueToRemove'} }; + controller.model.userParams = {}; const result = controller.filterSanitizer('fieldToRemove'); @@ -193,23 +209,21 @@ describe('Component smartTable', () => { }); it('should remove the where filter after leaving no fields and "empty ands/ors" in it', () => { - controller.model = { - userFilter: { - where: { - and: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - { - or: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - ] - } - ] - } - }, - userParams: {} - }; + controller.model.userFilter = { + where: { + and: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + { + or: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + ] + } + ] + } + }, + controller.model.userParams = {}; const result = controller.filterSanitizer('aFieldToRemove'); @@ -219,24 +233,22 @@ describe('Component smartTable', () => { }); it('should not remove the where filter after leaving no empty "ands/ors" in it', () => { - controller.model = { - userFilter: { - where: { - and: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - { - or: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - ] - } - ], - or: [{dontKillMe: 'thanks'}] - } - }, - userParams: {} + controller.model.userFilter = { + where: { + and: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + { + or: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + ] + } + ], + or: [{dontKillMe: 'thanks'}] + } }; + controller.model.userParams = {}; const result = controller.filterSanitizer('aFieldToRemove'); @@ -249,7 +261,7 @@ describe('Component smartTable', () => { describe('saveAll()', () => { it('should throw an error if there are no changes to save in the model', () => { jest.spyOn(controller.vnApp, 'showError'); - controller.model = {isChanged: false}; + controller.model.isChanged = false; controller.saveAll(); expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save'); @@ -258,10 +270,8 @@ describe('Component smartTable', () => { it('should call the showSuccess() if there are changes to save in the model', done => { jest.spyOn(controller.vnApp, 'showSuccess'); - controller.model = { - save: jest.fn().mockReturnValue(Promise.resolve()), - isChanged: true - }; + controller.model.save = jest.fn().mockReturnValue(Promise.resolve()); + controller.model.isChanged = true; controller.saveAll().then(() => { expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); @@ -269,4 +279,43 @@ describe('Component smartTable', () => { }).catch(done.fail); }); }); + + describe('defaultFilter()', () => { + it('should call model refresh and model addFilter with filter', () => { + controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'}); + + controller.$params = { + q: '{"tableQ": {"fieldName":"value"}}' + }; + controller.columns = [ + {field: 'fieldName'} + ]; + controller.$inputsScope = { + searchProps: {} + }; + jest.spyOn(controller, 'refresh'); + + controller.defaultFilter(); + + expect(controller.model.addFilter).toHaveBeenCalled(); + expect(controller.refresh).toHaveBeenCalled(); + }); + }); + + describe('searchPropsSanitizer()', () => { + it('should searchProps sanitize', () => { + controller.$inputsScope = { + searchProps: { + filterOne: '1', + filterTwo: '' + } + }; + const searchPropsExpected = { + filterOne: '1' + }; + const newSearchProps = controller.searchPropsSanitizer(); + + expect(newSearchProps).toEqual(searchPropsExpected); + }); + }); }); diff --git a/front/package-lock.json b/front/package-lock.json index 10e4a4df34..6922cae539 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -3,24 +3,190 @@ "version": "1.0.0", "lockfileVersion": 1, "requires": true, + "packages": { + "": { + "name": "salix-front", + "version": "1.0.0", + "license": "GPL-3.0", + "dependencies": { + "@uirouter/angularjs": "^1.0.20", + "angular": "^1.7.5", + "angular-animate": "^1.7.8", + "angular-moment": "^1.3.0", + "angular-translate": "^2.18.1", + "angular-translate-loader-partial": "^2.18.1", + "croppie": "^2.6.5", + "js-yaml": "^3.13.1", + "mg-crud": "^1.1.2", + "oclazyload": "^0.6.3", + "require-yaml": "0.0.1", + "validator": "^6.3.0" + } + }, + "node_modules/@uirouter/angularjs": { + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz", + "integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==", + "dependencies": { + "@uirouter/core": "6.0.8" + }, + "engines": { + "node": ">=4.0.0" + }, + "peerDependencies": { + "angular": ">=1.2.0" + } + }, + "node_modules/@uirouter/core": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz", + "integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/angular": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz", + "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==", + "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward." + }, + "node_modules/angular-animate": { + "version": "1.8.2", + "license": "MIT" + }, + "node_modules/angular-moment": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "moment": ">=2.8.0 <3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/angular-translate": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz", + "integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==", + "dependencies": { + "angular": "^1.8.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/angular-translate-loader-partial": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz", + "integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==", + "dependencies": { + "angular-translate": "~2.19.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/croppie": { + "version": "2.6.5", + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mg-crud": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "angular": "^1.6.1" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/oclazyload": { + "version": "0.6.3", + "license": "MIT" + }, + "node_modules/require-yaml": { + "version": "0.0.1", + "license": "BSD", + "dependencies": { + "js-yaml": "" + } + }, + "node_modules/require-yaml/node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/require-yaml/node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "license": "BSD-3-Clause" + }, + "node_modules/validator": { + "version": "6.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + } + }, "dependencies": { "@uirouter/angularjs": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.29.tgz", - "integrity": "sha512-RImWnBarNixkMto0o8stEaGwZmvhv5cnuOLXyMU2pY8MP2rgEF74ZNJTLeJCW14LR7XDUxVH8Mk8bPI6lxedmQ==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz", + "integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==", "requires": { - "@uirouter/core": "6.0.7" + "@uirouter/core": "6.0.8" } }, "@uirouter/core": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.7.tgz", - "integrity": "sha512-KUTJxL+6q0PiBnFx4/Z+Hsyg0pSGiaW5yZQeJmUxknecjpTbnXkLU8H2EqRn9N2B+qDRa7Jg8RcgeNDPY72O1w==" + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz", + "integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw==" }, "angular": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.2.tgz", - "integrity": "sha512-IauMOej2xEe7/7Ennahkbb5qd/HFADiNuLSESz9Q27inmi32zB0lnAsFeLEWcox3Gd1F6YhNd1CP7/9IukJ0Gw==" + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz", + "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==" }, "angular-animate": { "version": "1.8.2", @@ -36,19 +202,19 @@ } }, "angular-translate": { - "version": "2.18.4", - "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.4.tgz", - "integrity": "sha512-KohNrkH6J9PK+VW0L/nsRTcg5Fw70Ajwwe3Jbfm54Pf9u9Fd+wuingoKv+h45mKf38eT+Ouu51FPua8VmZNoCw==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz", + "integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==", "requires": { "angular": "^1.8.0" } }, "angular-translate-loader-partial": { - "version": "2.18.4", - "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.4.tgz", - "integrity": "sha512-bsjR+FbB0sdA2528E/ugwKdlPPQhA1looxLxI3otayBTFXBpED33besfSZhYAISLgNMSL038vSssfRUen9qD8w==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz", + "integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==", "requires": { - "angular-translate": "~2.18.4" + "angular-translate": "~2.19.0" } }, "argparse": { @@ -87,9 +253,9 @@ } }, "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "oclazyload": { "version": "0.6.3", diff --git a/front/salix/locale/es.yml b/front/salix/locale/es.yml index e5dc82b164..d92c32b337 100644 --- a/front/salix/locale/es.yml +++ b/front/salix/locale/es.yml @@ -51,6 +51,7 @@ Entries: Entradas Users: Usuarios Suppliers: Proveedores Monitors: Monitores +Shelvings: Carros # Common diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 1e151294fd..f03441c0f5 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -134,6 +134,7 @@ "Password does not meet requirements": "Password does not meet requirements", "You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies", "Not enough privileges to edit a client": "Not enough privileges to edit a client", + "Claim pickup order sent": "Claim pickup order sent [({{claimId}})]({{{claimUrl}}}) to client *{{clientName}}*", "You don't have grant privilege": "You don't have grant privilege", "You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index a41315dd1d..b85edb5fe2 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -153,6 +153,7 @@ "Email already exists": "Email already exists", "User already exists": "User already exists", "Absence change notification on the labour calendar": "Notificacion de cambio de ausencia en el calendario laboral", + "Record of hours week": "Registro de horas semana {{week}} año {{year}} ", "Created absence": "El empleado {{author}} ha añadido una ausencia de tipo '{{absenceType}}' a {{employee}} para el día {{dated}}.", "Deleted absence": "El empleado {{author}} ha eliminado una ausencia de tipo '{{absenceType}}' a {{employee}} del día {{dated}}.", "I have deleted the ticket id": "He eliminado el ticket id [{{id}}]({{{url}}})", @@ -236,6 +237,11 @@ "Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador", "Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador", "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente", - "You don't have grant privilege": "No tienes privilegios para dar privilegios", - "You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario" + "This route does not exists": "Esta ruta no existe", + "Claim pickup order sent": "Reclamación Orden de recogida enviada [({{claimId}})]({{{claimUrl}}}) al cliente *{{clientName}}*", + "You don't have grant privilege": "No tienes privilegios para dar privilegios", + "You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario", + "Already has this status": "Ya tiene este estado", + "There aren't records for this week": "No existen registros para esta semana", + "Empty data source": "Origen de datos vacio" } diff --git a/modules/account/back/models/ldap-config.js b/modules/account/back/models/ldap-config.js index 819659066d..a2a2684a9a 100644 --- a/modules/account/back/models/ldap-config.js +++ b/modules/account/back/models/ldap-config.js @@ -5,6 +5,8 @@ const crypto = require('crypto'); const nthash = require('smbhash').nthash; module.exports = Self => { + const shouldSync = process.env.NODE_ENV !== 'test'; + Self.getSynchronizer = async function() { return await Self.findOne({ fields: [ @@ -30,6 +32,7 @@ module.exports = Self => { }, async syncUser(userName, info, password) { + let { client, accountConfig @@ -130,13 +133,14 @@ module.exports = Self => { })); } - if (changes.length) + if (shouldSync && changes.length) await client.modify(dn, changes); - } else + } else if (shouldSync) await client.add(dn, newEntry); } else { try { - await client.del(dn); + if (shouldSync) + await client.del(dn); console.log(` -> User '${userName}' removed from LDAP`); } catch (e) { if (e.name !== 'NoSuchObjectError') throw e; @@ -196,17 +200,19 @@ module.exports = Self => { for (let group of groups) { try { let dn = `cn=${group},${groupDn}`; - await client.modify(dn, new ldap.Change({ - operation, - modification: {memberUid: userName} - })); + if (shouldSync) { + await client.modify(dn, new ldap.Change({ + operation, + modification: {memberUid: userName} + })); + } } catch (err) { if (err.name !== 'NoSuchObjectError') throw err; } } } - + await applyOperations(deleteGroups, 'delete'); await applyOperations(addGroups, 'add'); }, @@ -266,8 +272,10 @@ module.exports = Self => { filter: 'objectClass=posixGroup' }; let reqs = []; - await client.searchForeach(this.groupDn, opts, - o => reqs.push(client.del(o.dn))); + await client.searchForeach(this.groupDn, opts, object => { + if (shouldSync) + reqs.push(client.del(object.dn)); + }); await Promise.all(reqs); // Recreate roles @@ -291,7 +299,8 @@ module.exports = Self => { } let dn = `cn=${role.name},${this.groupDn}`; - reqs.push(client.add(dn, newEntry)); + if (shouldSync) + reqs.push(client.add(dn, newEntry)); } await Promise.all(reqs); } diff --git a/modules/account/back/models/samba-config.js b/modules/account/back/models/samba-config.js index 5fd62a68b1..168b5ffb47 100644 --- a/modules/account/back/models/samba-config.js +++ b/modules/account/back/models/samba-config.js @@ -60,16 +60,19 @@ module.exports = Self => { return `cn=Users,${dnBase}`; }, - async syncUser(userName, info, password) { + async syncUser(userName, info, password) { let {sshClient} = this; - + let sambaUser = await this.adClient.searchOne(this.usersDn(), { scope: 'sub', attributes: ['userAccountControl'], filter: `(&(objectClass=user)(sAMAccountName=${userName}))` }); let isEnabled = sambaUser - && !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE); + && !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE); + + if (process.env.NODE_ENV === 'test') + return; if (info.hasAccount) { if (!sambaUser) { diff --git a/modules/claim/back/methods/claim/claimPickupEmail.js b/modules/claim/back/methods/claim/claimPickupEmail.js index 4d64cc66ea..c688d6ded2 100644 --- a/modules/claim/back/methods/claim/claimPickupEmail.js +++ b/modules/claim/back/methods/claim/claimPickupEmail.js @@ -9,7 +9,7 @@ module.exports = Self => { arg: 'id', type: 'number', required: true, - description: 'The client id', + description: 'The claim id', http: {source: 'path'} }, { @@ -42,6 +42,11 @@ module.exports = Self => { }); Self.claimPickupEmail = async ctx => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const $t = ctx.req.__; // $translate + const origin = ctx.req.headers.origin; + const args = Object.assign({}, ctx.args); const params = { recipient: args.recipient, @@ -52,6 +57,34 @@ module.exports = Self => { for (const param in args) params[param] = args[param]; + const claim = await models.Claim.findById(args.id, { + fields: ['id', 'clientFk'], + include: { + relation: 'client', + scope: { + fields: ['name', 'salesPersonFk'] + } + } + }); + + const message = $t('Claim pickup order sent', { + claimId: args.id, + clientName: claim.client().name, + claimUrl: `${origin}/#!/claim/${args.id}/summary`, + }); + + const salesPersonId = claim.client().salesPersonFk; + if (salesPersonId) + await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); + + await models.ClaimLog.create({ + originFk: args.id, + userFk: userId, + action: 'insert', + description: 'Claim-pickup-order sent', + changedModel: 'Mail' + }); + const email = new Email('claim-pickup-order', params); return email.send(); diff --git a/modules/claim/front/basic-data/index.html b/modules/claim/front/basic-data/index.html index 7a91e180a4..16e134c60a 100644 --- a/modules/claim/front/basic-data/index.html +++ b/modules/claim/front/basic-data/index.html @@ -19,7 +19,7 @@ readonly="true"> @@ -56,7 +56,7 @@ label="Pick up" ng-model="$ctrl.claim.hasToPickUp" vn-acl="claimManager" - info="When checked will notify to the salesPerson"> + title="{{'When checked will notify to the salesPerson' | translate}}"> diff --git a/modules/claim/front/basic-data/locale/es.yml b/modules/claim/front/basic-data/locale/es.yml index c51afee3fb..5250d266c6 100644 --- a/modules/claim/front/basic-data/locale/es.yml +++ b/modules/claim/front/basic-data/locale/es.yml @@ -5,5 +5,5 @@ Responsability: Responsabilidad Company: Empresa Sales/Client: Comercial/Cliente Pick up: Recoger -When checked will notify a pickup to the salesPerson: Cuando se marque enviará una notificación de recogida al comercial -Packages received: Bultos recibidos \ No newline at end of file +When checked will notify to the salesPerson: Cuando se marque enviará una notificación de recogida al comercial +Packages received: Bultos recibidos diff --git a/modules/claim/front/routes.json b/modules/claim/front/routes.json index d02ea6f6c8..a8b958c5f2 100644 --- a/modules/claim/front/routes.json +++ b/modules/claim/front/routes.json @@ -73,7 +73,7 @@ { "url": "/note", "state": "claim.card.note", - "component": "ui-view", + "component": "ui-view", "abstract": true, "acl": ["salesPerson"] }, @@ -105,7 +105,7 @@ "acl": ["claimManager"] }, { - "url": "/action", + "url": "/action?q", "state": "claim.card.action", "component": "vn-claim-action", "description": "Action", @@ -131,4 +131,4 @@ "acl": ["claimManager"] } ] -} \ No newline at end of file +} diff --git a/modules/claim/front/summary/index.html b/modules/claim/front/summary/index.html index 0c12aa2e64..6adbfd6840 100644 --- a/modules/claim/front/summary/index.html +++ b/modules/claim/front/summary/index.html @@ -25,16 +25,23 @@ - - - - - - +

+ + Basic data + +

+ + + + + @@ -42,16 +49,23 @@ label="Attended by" value="{{$ctrl.summary.claim.worker.user.nickname}}">
-
+ + +

- Observations

-

Observations @@ -70,13 +84,13 @@

- Detail

-

Detail @@ -98,7 +112,7 @@ - {{::saleClaimed.sale.itemFk | zeroFill:6}} @@ -111,7 +125,7 @@ {{::saleClaimed.sale.price | currency: 'EUR':2}} {{::saleClaimed.sale.discount}} % - {{saleClaimed.sale.quantity * saleClaimed.sale.price * + {{saleClaimed.sale.quantity * saleClaimed.sale.price * ((100 - saleClaimed.sale.discount) / 100) | currency: 'EUR':2}} @@ -123,7 +137,7 @@

Photos

-
@@ -137,13 +151,13 @@

- Development

-

Development @@ -165,8 +179,8 @@ {{::development.claimResult.description}} {{::development.claimResponsible.description}} - {{::development.worker.user.nickname}} @@ -179,21 +193,21 @@

- Action

Action

- {{::action.sale.itemFk | zeroFill:6}} - {{::action.sale.ticket.id}} @@ -258,9 +272,9 @@ vn-id="item-descriptor" warehouse-fk="$ctrl.vnConfig.warehouseFk"> - - - \ No newline at end of file + diff --git a/modules/client/back/methods/client/filter.js b/modules/client/back/methods/client/filter.js new file mode 100644 index 0000000000..3e1ea43bb8 --- /dev/null +++ b/modules/client/back/methods/client/filter.js @@ -0,0 +1,147 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('filter', { + description: 'Find all clients matched by the filter', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + }, + { + arg: 'search', + type: 'string', + description: `If it's and integer searchs by id, otherwise it searchs by name`, + }, + { + arg: 'name', + type: 'string', + description: 'The client name', + }, + { + arg: 'salesPersonFk', + type: 'number', + }, + { + arg: 'fi', + type: 'string', + description: 'The client fiscal id', + }, + { + arg: 'socialName', + type: 'string', + }, + { + arg: 'city', + type: 'string', + }, + { + arg: 'postcode', + type: 'string', + }, + { + arg: 'provinceFk', + type: 'number', + }, + { + arg: 'email', + type: 'string', + }, + { + arg: 'phone', + type: 'string', + }, + { + arg: 'zoneFk', + type: 'number', + }, + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/filter`, + verb: 'GET' + } + }); + + Self.filter = async(ctx, filter, options) => { + const conn = Self.dataSource.connector; + const myOptions = {}; + const postalCode = []; + const args = ctx.args; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (args.zoneFk) { + query = `CALL vn.zone_getPostalCode(?)`; + const [geos] = await Self.rawSql(query, [args.zoneFk]); + for (let geo of geos) + postalCode.push(geo.name); + } + + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {'c.id': {inq: value}} + : {'c.name': {like: `%${value}%`}}; + case 'name': + case 'salesPersonFk': + case 'fi': + case 'socialName': + case 'city': + case 'postcode': + case 'provinceFk': + case 'email': + case 'phone': + param = `c.${param}`; + return {[param]: value}; + case 'zoneFk': + param = 'a.postalCode'; + return {[param]: {inq: postalCode}}; + } + }); + + filter = mergeFilters(filter, {where}); + + const stmts = []; + const stmt = new ParameterizedSQL( + `SELECT + DISTINCT c.id, + c.name, + c.fi, + c.socialName, + c.phone, + c.city, + c.postcode, + c.email, + c.isActive, + c.isFreezed, + p.id AS provinceFk, + p.name AS province, + u.id AS salesPersonFk, + u.name AS salesPerson + FROM client c + LEFT JOIN account.user u ON u.id = c.salesPersonFk + LEFT JOIN province p ON p.id = c.provinceFk + JOIN vn.address a ON a.clientFk = c.id + ` + ); + + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge(conn.makePagination(filter)); + + const clientsIndex = stmts.push(stmt) - 1; + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + + return clientsIndex === 0 ? result : result[clientsIndex]; + }; +}; diff --git a/modules/client/back/methods/client/specs/filter.spec.js b/modules/client/back/methods/client/specs/filter.spec.js new file mode 100644 index 0000000000..6795850508 --- /dev/null +++ b/modules/client/back/methods/client/specs/filter.spec.js @@ -0,0 +1,199 @@ +const {models} = require('vn-loopback/server/server'); + +describe('client filter()', () => { + it('should return the clients matching the filter with a limit of 20 rows', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {}}; + const filter = {limit: '8'}; + const result = await models.Client.filter(ctx, filter, options); + + expect(result.length).toEqual(8); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the client "Bruce Wayne" matching the search argument with his name', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {search: 'Bruce Wayne'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstResult = result[0]; + + expect(result.length).toEqual(1); + expect(firstResult.name).toEqual('Bruce Wayne'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the client "Bruce Wayne" matching the search argument with his id', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {search: '1101'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstResult = result[0]; + + expect(result.length).toEqual(1); + expect(firstResult.name).toEqual('Bruce Wayne'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the client "Bruce Wayne" matching the name argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {name: 'Bruce Wayne'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstResult = result[0]; + + expect(result.length).toEqual(1); + expect(firstResult.name).toEqual('Bruce Wayne'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "salesPersonFk" argument', async() => { + const tx = await models.Client.beginTransaction({}); + const salesPersonId = 18; + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {salesPersonFk: salesPersonId}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const randomIndex = Math.floor(Math.random() * result.length); + const randomResultClient = result[randomIndex]; + + expect(result.length).toBeGreaterThanOrEqual(5); + expect(randomResultClient.salesPersonFk).toEqual(salesPersonId); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "fi" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {fi: '251628698'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstClient = result[0]; + + expect(result.length).toEqual(1); + expect(firstClient.name).toEqual('Max Eisenhardt'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "city" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {city: 'Gotham'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const randomIndex = Math.floor(Math.random() * result.length); + const randomResultClient = result[randomIndex]; + + expect(result.length).toBeGreaterThanOrEqual(20); + expect(randomResultClient.city.toLowerCase()).toEqual('gotham'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "postcode" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {postcode: '46460'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const randomIndex = Math.floor(Math.random() * result.length); + const randomResultClient = result[randomIndex]; + + expect(result.length).toBeGreaterThanOrEqual(20); + expect(randomResultClient.postcode).toEqual('46460'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "zoneFk" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {zoneFk: 9}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + expect(result.length).toBe(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/client/back/methods/receipt/balanceCompensationEmail.js b/modules/client/back/methods/receipt/balanceCompensationEmail.js new file mode 100644 index 0000000000..e9ded147de --- /dev/null +++ b/modules/client/back/methods/receipt/balanceCompensationEmail.js @@ -0,0 +1,40 @@ +const {Email} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('balanceCompensationEmail', { + description: 'Sends the debit balances compensation email with an attached PDF', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'Number', + required: true, + description: 'The receipt id', + http: { source: 'path' } + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: '/:id/balance-compensation-email', + verb: 'POST' + } + }); + + Self.balanceCompensationEmail = async (ctx, id) => { + + const models = Self.app.models; + const receipt = await models.Receipt.findById(id, {fields: ['clientFk']}); + const client = await models.Client.findById(receipt.clientFk, {fields:['email']}); + + const email = new Email('balance-compensation', { + lang: ctx.req.getLocale(), + recipient: client.email+',administracion@verdnatura.es', + id + }); + + return email.send(); + }; +}; diff --git a/modules/client/back/methods/receipt/balanceCompensationPdf.js b/modules/client/back/methods/receipt/balanceCompensationPdf.js new file mode 100644 index 0000000000..ff8713253f --- /dev/null +++ b/modules/client/back/methods/receipt/balanceCompensationPdf.js @@ -0,0 +1,50 @@ +const { Report } = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('balanceCompensationPdf', { + description: 'Returns the the debit balances compensation pdf', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The receipt id', + http: { source: 'path' } + } + ], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, { + arg: 'Content-Type', + type: 'String', + http: {target: 'header'} + }, { + arg: 'Content-Disposition', + type: 'String', + http: {target: 'header'} + } + ], + http: { + path: '/:id/balance-compensation-pdf', + verb: 'GET' + } + }); + + Self.balanceCompensationPdf = async(ctx, id) => { + const args = Object.assign({}, ctx.args); + const params = {lang: ctx.req.getLocale()}; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const report = new Report('balance-compensation', params); + const stream = await report.toPdfStream(); + + return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; + }; +}; diff --git a/modules/client/back/model-config.json b/modules/client/back/model-config.json index b2e600610e..4ef34ca3a6 100644 --- a/modules/client/back/model-config.json +++ b/modules/client/back/model-config.json @@ -8,6 +8,9 @@ "BankEntity": { "dataSource": "vn" }, + "Business": { + "dataSource": "vn" + }, "BusinessType": { "dataSource": "vn" }, diff --git a/modules/client/back/models/business.json b/modules/client/back/models/business.json new file mode 100644 index 0000000000..7ad2d307ff --- /dev/null +++ b/modules/client/back/models/business.json @@ -0,0 +1,27 @@ +{ + "name": "Business", + "base": "VnModel", + "options": { + "mysql": { + "table": "business" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + } + }, + "relations": { + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "workerFk" + }, + "department": { + "type": "belongsTo", + "model": "Department", + "foreignKey": "departmentFk" + } + } +} \ No newline at end of file diff --git a/modules/client/back/models/client-methods.js b/modules/client/back/models/client-methods.js index 04d10413a8..5134e39422 100644 --- a/modules/client/back/models/client-methods.js +++ b/modules/client/back/models/client-methods.js @@ -47,4 +47,5 @@ module.exports = Self => { require('../methods/client/incotermsAuthorizationHtml')(Self); require('../methods/client/incotermsAuthorizationEmail')(Self); require('../methods/client/consumptionSendQueued')(Self); + require('../methods/client/filter')(Self); }; diff --git a/modules/client/back/models/receipt.js b/modules/client/back/models/receipt.js index b79102e6b8..3118cc2390 100644 --- a/modules/client/back/models/receipt.js +++ b/modules/client/back/models/receipt.js @@ -2,6 +2,8 @@ const LoopBackContext = require('loopback-context'); module.exports = function(Self) { require('../methods/receipt/filter')(Self); + require('../methods/receipt/balanceCompensationEmail')(Self); + require('../methods/receipt/balanceCompensationPdf')(Self); require('../methods/receipt/receiptPdf')(Self); Self.validateBinded('amountPaid', isNotZero, { diff --git a/modules/client/front/balance/index/index.html b/modules/client/front/balance/index/index.html index 3c61d8543d..1e07161128 100644 --- a/modules/client/front/balance/index/index.html +++ b/modules/client/front/balance/index/index.html @@ -121,9 +121,22 @@ - - - + + + + + + + + +

diff --git a/modules/client/front/balance/index/index.js b/modules/client/front/balance/index/index.js index 7e09e018cd..b2529924f9 100644 --- a/modules/client/front/balance/index/index.js +++ b/modules/client/front/balance/index/index.js @@ -2,8 +2,9 @@ import ngModule from '../../module'; import Section from 'salix/components/section'; class Controller extends Section { - constructor($element, $) { + constructor($element, $, vnEmail) { super($element, $); + this.vnEmail = vnEmail; this.filter = { include: { relation: 'company', @@ -54,45 +55,49 @@ class Controller extends Section { } })).then(() => this.getBalances()); } - + getCurrentBalance() { const clientRisks = this.$.riskModel.data; const selectedCompany = this.companyId; const currentBalance = clientRisks.find(balance => { return balance.companyFk === selectedCompany; }); - + return currentBalance && currentBalance.amount; } - + getBalances() { const balances = this.$.model.data; balances.forEach((balance, index) => { if (index === 0) - balance.balance = this.getCurrentBalance(); + balance.balance = this.getCurrentBalance(); if (index > 0) { let previousBalance = balances[index - 1]; balance.balance = previousBalance.balance - (previousBalance.debit - previousBalance.credit); } }); } - + showInvoiceOutDescriptor(event, balance) { if (!balance.isInvoice) return; if (event.defaultPrevented) return; - + this.$.invoiceOutDescriptor.show(event.target, balance.id); } - + changeDescription(balance) { const params = {description: balance.description}; const endpoint = `Receipts/${balance.id}`; this.$http.patch(endpoint, params) - .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); + .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); + } + + sendEmail(balance) { + return this.vnEmail.send(`Receipts/${balance.id}/balance-compensation-email`); } } -Controller.$inject = ['$element', '$scope']; +Controller.$inject = ['$element', '$scope', 'vnEmail']; ngModule.vnComponent('vnClientBalanceIndex', { template: require('./index.html'), diff --git a/modules/client/front/balance/index/locale/es.yml b/modules/client/front/balance/index/locale/es.yml index 05ef7070af..de710869e7 100644 --- a/modules/client/front/balance/index/locale/es.yml +++ b/modules/client/front/balance/index/locale/es.yml @@ -8,4 +8,6 @@ Havings: Haber Balance: Balance Total by company: Total por empresa Download PDF: Descargar PDF -BILL: N/FRA {{ref}} \ No newline at end of file +Send compensation: Enviar compensación +BILL: N/FRA {{ref}} +Notify compensation: ¿Desea informar de la compensación al cliente por correo? \ No newline at end of file diff --git a/modules/client/front/main/index.html b/modules/client/front/main/index.html index e8bc4b9884..0787858ae1 100644 --- a/modules/client/front/main/index.html +++ b/modules/client/front/main/index.html @@ -1,6 +1,6 @@ @@ -10,8 +10,7 @@ vn-focus panel="vn-client-search-panel" info="Search client by id or name" - model="model" - expr-builder="$ctrl.exprBuilder(param, value)"> + model="model"> diff --git a/modules/client/front/main/index.js b/modules/client/front/main/index.js index 1069d3487e..346880f4c5 100644 --- a/modules/client/front/main/index.js +++ b/modules/client/front/main/index.js @@ -2,32 +2,6 @@ import ngModule from '../module'; import ModuleMain from 'salix/components/module-main'; export default class Client extends ModuleMain { - exprBuilder(param, value) { - switch (param) { - case 'search': - return /^\d+$/.test(value) - ? {id: value} - : {or: [{name: {like: `%${value}%`}}, {socialName: {like: `%${value}%`}}]}; - case 'phone': - return { - or: [ - {phone: value}, - {mobile: value} - ] - }; - case 'name': - case 'socialName': - case 'city': - case 'email': - return {[param]: {like: `%${value}%`}}; - case 'id': - case 'fi': - case 'postcode': - case 'provinceFk': - case 'salesPersonFk': - return {[param]: value}; - } - } } ngModule.vnComponent('vnClient', { diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json index c7462e46c1..406ca07d7e 100644 --- a/modules/client/front/routes.json +++ b/modules/client/front/routes.json @@ -20,7 +20,7 @@ {"state": "client.card.credit.index", "icon": "credit_card"}, {"state": "client.card.greuge.index", "icon": "work"}, {"state": "client.card.balance.index", "icon": "icon-invoice"}, - {"state": "client.card.recovery.index", "icon": "icon-recovery"}, + {"state": "client.card.recovery.index", "icon": "icon-recovery"}, {"state": "client.card.webAccess", "icon": "cloud"}, {"state": "client.card.log", "icon": "history"}, { @@ -37,7 +37,7 @@ {"state": "client.card.unpaid", "icon": "icon-defaulter"} ] } - ] + ] }, "keybindings": [ {"key": "c", "state": "client.index"} @@ -147,7 +147,7 @@ { "url": "/note", "state": "client.card.note", - "component": "ui-view", + "component": "ui-view", "abstract": true }, { @@ -236,7 +236,7 @@ "client": "$ctrl.client" } }, - { + { "url": "/create?payed&companyFk&bankFk&payedAmount", "state": "client.card.balance.create", "component": "vn-client-balance-create", @@ -406,13 +406,13 @@ } }, { - "url": "/defaulter", + "url": "/defaulter?q", "state": "client.defaulter", "component": "vn-client-defaulter", "description": "Defaulter" }, { - "url" : "/notification", + "url" : "/notification?q", "state": "client.notification", "component": "vn-client-notification", "description": "Notifications" @@ -424,7 +424,7 @@ "description": "Unpaid" }, { - "url": "/extended-list", + "url": "/extended-list?q", "state": "client.extendedList", "component": "vn-client-extended-list", "description": "Extended list" diff --git a/modules/client/front/search-panel/index.html b/modules/client/front/search-panel/index.html index 234cb6f53e..a02f93882f 100644 --- a/modules/client/front/search-panel/index.html +++ b/modules/client/front/search-panel/index.html @@ -58,6 +58,13 @@ value-field="id" label="Province"> + + { e.id, e.supplierFk, e.dated, - e.ref, + e.ref reference, + e.ref invoiceNumber, e.isBooked, e.isExcludedFromAvailable, e.notes, diff --git a/modules/entry/back/methods/entry/importBuys.js b/modules/entry/back/methods/entry/importBuys.js index fb2f5f4529..fdc6b058e5 100644 --- a/modules/entry/back/methods/entry/importBuys.js +++ b/modules/entry/back/methods/entry/importBuys.js @@ -12,10 +12,15 @@ module.exports = Self => { http: {source: 'path'} }, { - arg: 'ref', + arg: 'reference', type: 'string', description: 'The buyed boxes ids', }, + { + arg: 'invoiceNumber', + type: 'string', + description: 'The registered invoice number', + }, { arg: 'observation', type: 'string', @@ -63,7 +68,8 @@ module.exports = Self => { await entry.updateAttributes({ observation: args.observation, - ref: args.ref + reference: args.reference, + invoiceNumber: args.invoiceNumber }, myOptions); const travel = entry.travel(); diff --git a/modules/entry/back/methods/entry/specs/importBuys.spec.js b/modules/entry/back/methods/entry/specs/importBuys.spec.js index 9cf6f43008..4f9204c6a3 100644 --- a/modules/entry/back/methods/entry/specs/importBuys.spec.js +++ b/modules/entry/back/methods/entry/specs/importBuys.spec.js @@ -15,13 +15,15 @@ describe('entry import()', () => { }); it('should import the buy rows', async() => { - const expectedRef = '1, 2'; + const expectedReference = '1, 2'; + const expectedInvoiceNumber = '1, 2'; const expectedObservation = '123456'; const ctx = { req: activeCtx, args: { observation: expectedObservation, - ref: expectedRef, + reference: expectedReference, + invoiceNumber: expectedInvoiceNumber, buys: [ { itemFk: 1, @@ -58,7 +60,8 @@ describe('entry import()', () => { }, options); expect(updatedEntry.observation).toEqual(expectedObservation); - expect(updatedEntry.ref).toEqual(expectedRef); + expect(updatedEntry.reference).toEqual(expectedReference); + expect(updatedEntry.invoiceNumber).toEqual(expectedInvoiceNumber); expect(entryBuys.length).toEqual(4); await tx.rollback(); diff --git a/modules/entry/back/models/entry.json b/modules/entry/back/models/entry.json index c456859a58..d3c802ad21 100644 --- a/modules/entry/back/models/entry.json +++ b/modules/entry/back/models/entry.json @@ -18,8 +18,17 @@ "dated": { "type": "date" }, - "ref": { - "type": "string" + "reference": { + "type": "string", + "mysql": { + "columnName": "ref" + } + }, + "invoiceNumber": { + "type": "string", + "mysql": { + "columnName": "ref" + } }, "isBooked": { "type": "boolean" diff --git a/modules/entry/front/basic-data/index.html b/modules/entry/front/basic-data/index.html index 423e9d70d2..68a65e8903 100644 --- a/modules/entry/front/basic-data/index.html +++ b/modules/entry/front/basic-data/index.html @@ -48,7 +48,7 @@ @@ -61,17 +61,25 @@ - - + label="Invoice number" + ng-model="$ctrl.entry.invoiceNumber" + rule + vn-focus> + + + - - + + + + diff --git a/modules/entry/front/index/index.html b/modules/entry/front/index/index.html index 3e27a6a555..42177be210 100644 --- a/modules/entry/front/index/index.html +++ b/modules/entry/front/index/index.html @@ -12,6 +12,7 @@ Id Landed Reference + Invoice number Supplier Booked Confirmed @@ -45,7 +46,8 @@ {{::entry.landed | date:'dd/MM/yyyy'}} - {{::entry.ref}} + {{::entry.reference}} + {{::entry.invoiceNumber}} {{::entry.supplierName}} diff --git a/modules/entry/front/index/locale/es.yml b/modules/entry/front/index/locale/es.yml index 519f8e39aa..4f12fc7bb3 100644 --- a/modules/entry/front/index/locale/es.yml +++ b/modules/entry/front/index/locale/es.yml @@ -14,4 +14,5 @@ Booked: Contabilizada Is inventory: Inventario Notes: Notas Status: Estado -Selection: Selección \ No newline at end of file +Selection: Selección +Invoice number: Núm. factura \ No newline at end of file diff --git a/modules/entry/front/search-panel/index.html b/modules/entry/front/search-panel/index.html index 38acdf77d6..adcb9d6d4a 100644 --- a/modules/entry/front/search-panel/index.html +++ b/modules/entry/front/search-panel/index.html @@ -13,9 +13,16 @@ + ng-model="filter.reference"> + + + + diff --git a/modules/entry/front/search-panel/locale/es.yml b/modules/entry/front/search-panel/locale/es.yml index 88f1641451..05b71da99a 100644 --- a/modules/entry/front/search-panel/locale/es.yml +++ b/modules/entry/front/search-panel/locale/es.yml @@ -5,4 +5,5 @@ From: Desde To: Hasta Agency: Agencia Warehouse: Almacén -Search entry by id or a suppliers by name or alias: Buscar entrada por id o proveedores por nombre y alias \ No newline at end of file +Search entry by id or a suppliers by name or alias: Buscar entrada por id o proveedores por nombre y alias +Invoice number: Núm. factura \ No newline at end of file diff --git a/modules/entry/front/summary/index.html b/modules/entry/front/summary/index.html index ffd8aafab5..04844ab99d 100644 --- a/modules/entry/front/summary/index.html +++ b/modules/entry/front/summary/index.html @@ -27,7 +27,10 @@ value="{{$ctrl.entryData.company.code}}"> + value="{{$ctrl.entryData.reference}}"> + + diff --git a/modules/entry/front/summary/locale/es.yml b/modules/entry/front/summary/locale/es.yml index a141ce56ca..1761561edd 100644 --- a/modules/entry/front/summary/locale/es.yml +++ b/modules/entry/front/summary/locale/es.yml @@ -8,4 +8,4 @@ Minimum price: Precio mínimo Buys: Compras Travel: Envio Go to the entry: Ir a la entrada - +Invoice number: Núm. factura diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js new file mode 100644 index 0000000000..0768541a85 --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js @@ -0,0 +1,53 @@ +const {Email} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('invoiceInEmail', { + description: 'Sends the invoice in email with an attached PDF', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The invoice id', + http: {source: 'path'} + }, + { + arg: 'recipient', + type: 'string', + description: 'The recipient email', + required: true, + }, + { + arg: 'recipientId', + type: 'number', + description: 'The recipient id to send to the recipient preferred language', + required: false + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: '/:id/invoice-in-email', + verb: 'POST' + } + }); + + Self.invoiceInEmail = async ctx => { + const args = Object.assign({}, ctx.args); + const params = { + recipient: args.recipient, + lang: ctx.req.getLocale() + }; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const email = new Email('invoiceIn', params); + + return email.send(); + }; +}; diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js new file mode 100644 index 0000000000..e7962c93f9 --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js @@ -0,0 +1,50 @@ +const {Report} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('invoiceInPdf', { + description: 'Returns the invoiceIn pdf', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The invoiceIn id', + http: {source: 'path'} + } + ], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, { + arg: 'Content-Type', + type: 'String', + http: {target: 'header'} + }, { + arg: 'Content-Disposition', + type: 'String', + http: {target: 'header'} + } + ], + http: { + path: '/:id/invoice-in-pdf', + verb: 'GET' + } + }); + + Self.invoiceInPdf = async(ctx, id) => { + const args = Object.assign({}, ctx.args); + const params = {lang: ctx.req.getLocale()}; + delete args.ctx; + + for (const param in args) + params[param] = args[param]; + + const report = new Report('invoiceIn', params); + const stream = await report.toPdfStream(); + + return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; + }; +}; diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js index 3b5aa65d9f..95ccc7b205 100644 --- a/modules/invoiceIn/back/models/invoice-in.js +++ b/modules/invoiceIn/back/models/invoice-in.js @@ -4,4 +4,6 @@ module.exports = Self => { require('../methods/invoice-in/clone')(Self); require('../methods/invoice-in/toBook')(Self); require('../methods/invoice-in/getTotals')(Self); + require('../methods/invoice-in/invoiceInPdf')(Self); + require('../methods/invoice-in/invoiceInEmail')(Self); }; diff --git a/modules/invoiceIn/back/models/invoice-in.json b/modules/invoiceIn/back/models/invoice-in.json index c6a736b06a..fa8a1d8f84 100644 --- a/modules/invoiceIn/back/models/invoice-in.json +++ b/modules/invoiceIn/back/models/invoice-in.json @@ -94,6 +94,11 @@ "model": "Supplier", "foreignKey": "supplierFk" }, + "supplierContact": { + "type": "hasMany", + "model": "SupplierContact", + "foreignKey": "supplierFk" + }, "currency": { "type": "belongsTo", "model": "Currency", diff --git a/modules/invoiceIn/front/card/index.js b/modules/invoiceIn/front/card/index.js index 582c2abb8c..c7ac08cc7e 100644 --- a/modules/invoiceIn/front/card/index.js +++ b/modules/invoiceIn/front/card/index.js @@ -8,6 +8,14 @@ class Controller extends ModuleCard { { relation: 'supplier' }, + { + relation: 'supplierContact', + scope: { + where: { + email: {neq: null} + } + } + }, { relation: 'invoiceInDueDay' }, diff --git a/modules/invoiceIn/front/descriptor/index.html b/modules/invoiceIn/front/descriptor/index.html index 33f9ee8c65..819615c20b 100644 --- a/modules/invoiceIn/front/descriptor/index.html +++ b/modules/invoiceIn/front/descriptor/index.html @@ -1,5 +1,5 @@ - @@ -10,7 +10,6 @@ translate> To book - Clone Invoice + + Show agricultural invoice as PDF + + + Send agricultural invoice as PDF +
@@ -37,7 +46,7 @@ - {{$ctrl.invoiceIn.supplier.nickname}} + {{$ctrl.invoiceIn.supplier.nickname}}
@@ -57,9 +66,9 @@ icon="icon-invoice-in"> - + - +
- - \ No newline at end of file + + + + + + Are you sure you want to send it? + + + + + + + + diff --git a/modules/invoiceIn/front/descriptor/index.js b/modules/invoiceIn/front/descriptor/index.js index cde3242963..5cd00d7434 100644 --- a/modules/invoiceIn/front/descriptor/index.js +++ b/modules/invoiceIn/front/descriptor/index.js @@ -96,6 +96,20 @@ class Controller extends Descriptor { .then(() => this.$state.reload()) .then(() => this.vnApp.showSuccess(this.$t('InvoiceIn booked'))); } + + showPdfInvoice() { + this.vnReport.show(`InvoiceIns/${this.id}/invoice-in-pdf`); + } + + sendPdfInvoice($data) { + if (!$data.email) + return this.vnApp.showError(this.$t(`The email can't be empty`)); + + return this.vnEmail.send(`InvoiceIns/${this.entity.id}/invoice-in-email`, { + recipient: $data.email, + recipientId: this.entity.supplier.id + }); + } } ngModule.vnComponent('vnInvoiceInDescriptor', { diff --git a/modules/invoiceIn/front/locale/es.yml b/modules/invoiceIn/front/locale/es.yml index 4f36b33fa8..1deff32d32 100644 --- a/modules/invoiceIn/front/locale/es.yml +++ b/modules/invoiceIn/front/locale/es.yml @@ -19,3 +19,5 @@ To book: Contabilizar Total amount: Total importe Total net: Total neto Total stems: Total tallos +Show agricultural invoice as PDF: Ver factura agrícola como PDF +Send agricultural invoice as PDF: Enviar factura agrícola como PDF diff --git a/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js b/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js new file mode 100644 index 0000000000..72a00b7641 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js @@ -0,0 +1,55 @@ +const JSZip = require('jszip'); +const fs = require('fs-extra'); +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('downloadZip', { + description: 'Download a zip file with multiple invoices pdfs', + accessType: 'READ', + accepts: [ + { + arg: 'ids', + type: ['number'], + description: 'The invoice ids' + } + ], + returns: { + arg: 'base64', + type: 'string', + root: true + }, + http: { + path: '/downloadZip', + verb: 'POST' + } + }); + + Self.downloadZip = async function(ctx, ids, options) { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const zip = new JSZip(); + let totalSize = 0; + const zipConfig = await models.ZipConfig.findOne(null, myOptions); + for (let id of ids) { + if (zipConfig && totalSize > zipConfig.maxSize) throw new UserError('Files are too large'); + const invoiceOutPdf = await models.InvoiceOut.download(ctx, id, myOptions); + const fileName = extractFileName(invoiceOutPdf[2]); + const body = invoiceOutPdf[0]; + const sizeInBytes = (await fs.promises.stat(body.path)).size; + const sizeInMegabytes = sizeInBytes / (1024 * 1024); + totalSize += sizeInMegabytes; + zip.file(fileName, body); + } + const base64 = await zip.generateAsync({type: 'base64'}); + return base64; + }; + + function extractFileName(str) { + const matches = str.match(/"(.*?)"/); + return matches ? matches[1] : str; + } +}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js new file mode 100644 index 0000000000..7a9e184ea2 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js @@ -0,0 +1,53 @@ +const models = require('vn-loopback/server/server').models; +const UserError = require('vn-loopback/util/user-error'); + +describe('InvoiceOut downloadZip()', () => { + const userId = 9; + const invoiceIds = [1, 2]; + const ctx = { + req: { + + accessToken: {userId: userId}, + headers: {origin: 'http://localhost:5000'}, + } + }; + + it('should return part of link to dowloand the zip', async() => { + const tx = await models.Order.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const result = await models.InvoiceOut.downloadZip(ctx, invoiceIds, options); + + expect(result).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return an error if the size of the files is too large', async() => { + const tx = await models.Order.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + const zipConfig = { + maxSize: 0 + }; + await models.ZipConfig.create(zipConfig, options); + + await models.InvoiceOut.downloadZip(ctx, invoiceIds, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toEqual(new UserError(`Files are too large`)); + }); +}); diff --git a/modules/invoiceOut/back/model-config.json b/modules/invoiceOut/back/model-config.json index d52f794775..04933c4f02 100644 --- a/modules/invoiceOut/back/model-config.json +++ b/modules/invoiceOut/back/model-config.json @@ -22,5 +22,8 @@ }, "TaxType": { "dataSource": "vn" + }, + "ZipConfig": { + "dataSource": "vn" } } diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 5af64de2b1..9533ad484f 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -3,6 +3,7 @@ module.exports = Self => { require('../methods/invoiceOut/summary')(Self); require('../methods/invoiceOut/getTickets')(Self); require('../methods/invoiceOut/download')(Self); + require('../methods/invoiceOut/downloadZip')(Self); require('../methods/invoiceOut/delete')(Self); require('../methods/invoiceOut/book')(Self); require('../methods/invoiceOut/createPdf')(Self); diff --git a/modules/invoiceOut/back/models/zip-config.json b/modules/invoiceOut/back/models/zip-config.json new file mode 100644 index 0000000000..17fe8a1faf --- /dev/null +++ b/modules/invoiceOut/back/models/zip-config.json @@ -0,0 +1,25 @@ +{ + "name": "ZipConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "zipConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "maxSize": { + "type": "number" + } + }, + "acls": [{ + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }] +} \ No newline at end of file diff --git a/modules/invoiceOut/front/index/index.html b/modules/invoiceOut/front/index/index.html index d970bd15ac..632dbf90bf 100644 --- a/modules/invoiceOut/front/index/index.html +++ b/modules/invoiceOut/front/index/index.html @@ -4,10 +4,24 @@ + + + + + + + + Reference Issued Amount @@ -23,6 +37,12 @@ + + + + {{::invoiceOut.ref | dashIfEmpty}} {{::invoiceOut.issued | date:'dd/MM/yyyy' | dashIfEmpty}} {{::invoiceOut.amount | currency: 'EUR': 2 | dashIfEmpty}} @@ -36,15 +56,6 @@ {{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}} {{::invoiceOut.companyCode | dashIfEmpty}} {{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}} - - - - { + location.href = 'data:application/zip;base64,' + res.data; + }); + } } } diff --git a/modules/invoiceOut/front/index/locale/es.yml b/modules/invoiceOut/front/index/locale/es.yml index 1460b90d8a..74572da493 100644 --- a/modules/invoiceOut/front/index/locale/es.yml +++ b/modules/invoiceOut/front/index/locale/es.yml @@ -6,3 +6,4 @@ Minimum: Minimo Maximum: Máximo Global invoicing: Facturación global Manual invoicing: Facturación manual +Files are too large: Los archivos son demasiado grandes \ No newline at end of file diff --git a/modules/item/back/methods/item-shelving/deleteItemShelvings.js b/modules/item/back/methods/item-shelving/deleteItemShelvings.js new file mode 100644 index 0000000000..f534b4e9a2 --- /dev/null +++ b/modules/item/back/methods/item-shelving/deleteItemShelvings.js @@ -0,0 +1,51 @@ +module.exports = Self => { + Self.remoteMethod('deleteItemShelvings', { + description: 'Deletes the selected item shelvings', + accessType: 'WRITE', + accepts: [{ + arg: 'itemShelvingIds', + type: ['number'], + required: true, + description: 'The itemShelving ids to delete' + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/deleteItemShelvings`, + verb: 'POST' + } + }); + + Self.deleteItemShelvings = async(itemShelvingIds, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const promises = []; + for (let itemShelvingId of itemShelvingIds) { + const itemShelvingToDelete = models.ItemShelving.destroyById(itemShelvingId, myOptions); + promises.push(itemShelvingToDelete); + } + + const deletedItemShelvings = await Promise.all(promises); + + if (tx) await tx.commit(); + + return deletedItemShelvings; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js b/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js new file mode 100644 index 0000000000..b4113d7cf5 --- /dev/null +++ b/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js @@ -0,0 +1,21 @@ +const models = require('vn-loopback/server/server').models; + +describe('ItemShelving deleteItemShelvings()', () => { + it('should return the deleted itemShelvings', async() => { + const tx = await models.Order.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const itemShelvingIds = [1, 2]; + const result = await models.ItemShelving.deleteItemShelvings(itemShelvingIds, options); + + expect(result.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/item/back/model-config.json b/modules/item/back/model-config.json index 9737d26fc0..40d73f1a61 100644 --- a/modules/item/back/model-config.json +++ b/modules/item/back/model-config.json @@ -53,6 +53,9 @@ "ItemShelvingSale": { "dataSource": "vn" }, + "ItemShelvingPlacementSupplyStock": { + "dataSource": "vn" + }, "ItemImageQueue": { "dataSource": "vn" }, diff --git a/modules/item/back/models/item-shelving-placement-supply.json b/modules/item/back/models/item-shelving-placement-supply.json new file mode 100644 index 0000000000..a54013e05b --- /dev/null +++ b/modules/item/back/models/item-shelving-placement-supply.json @@ -0,0 +1,36 @@ +{ + "name": "ItemShelvingPlacementSupplyStock", + "base": "VnModel", + "options": { + "mysql": { + "table": "itemShelvingPlacementSupplyStock" + } + }, + "properties": { + "itemShelvingFk": { + "type": "number", + "id": true + }, + "created": { + "type": "date" + }, + "itemFk": { + "type": "number" + }, + "longName": { + "type": "string" + }, + "parking": { + "type": "string" + }, + "shelving": { + "type": "string" + }, + "packing": { + "type": "number" + }, + "stock": { + "type": "number" + } + } +} \ No newline at end of file diff --git a/modules/item/back/models/item-shelving.js b/modules/item/back/models/item-shelving.js new file mode 100644 index 0000000000..5f372a3be4 --- /dev/null +++ b/modules/item/back/models/item-shelving.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/item-shelving/deleteItemShelvings')(Self); +}; diff --git a/modules/item/front/fixed-price/index.html b/modules/item/front/fixed-price/index.html index 8127a76dc1..9498bf96f9 100644 --- a/modules/item/front/fixed-price/index.html +++ b/modules/item/front/fixed-price/index.html @@ -23,9 +23,9 @@
- @@ -34,18 +34,18 @@ - - -
Item ID + Description Warehouse P.P.U. P.P.P. @@ -170,7 +170,7 @@ - + - \ No newline at end of file + diff --git a/modules/item/front/fixed-price/index.js b/modules/item/front/fixed-price/index.js index b84c2cc2da..89ce0b1728 100644 --- a/modules/item/front/fixed-price/index.js +++ b/modules/item/front/fixed-price/index.js @@ -12,14 +12,6 @@ export default class Controller extends Section { }, defaultSearch: true, columns: [ - { - field: 'itemName', - autocomplete: { - url: 'Items', - showField: 'name', - valueField: 'id' - } - }, { field: 'warehouseFk', autocomplete: { @@ -105,8 +97,8 @@ export default class Controller extends Section { exprBuilder(param, value) { switch (param) { - case 'itemName': - return {'i.id': value}; + case 'name': + return {'i.name': {like: `%${value}%`}}; case 'itemFk': case 'warehouseFk': case 'rate2': diff --git a/modules/item/front/index.js b/modules/item/front/index.js index 6a8d1b3b78..d2ffcc8fb1 100644 --- a/modules/item/front/index.js +++ b/modules/item/front/index.js @@ -24,3 +24,5 @@ import './waste/detail'; import './fixed-price'; import './fixed-price-search-panel'; import './item-type'; +import './item-shelving'; + diff --git a/modules/item/front/item-shelving/index.html b/modules/item/front/item-shelving/index.html new file mode 100644 index 0000000000..fa7a705440 --- /dev/null +++ b/modules/item/front/item-shelving/index.html @@ -0,0 +1,118 @@ + + + + + +
+
+
Total
+ + +
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Created + + Item + + Concept + + Parking + + Shelving + + Etiqueta + + Packing +
+ + + {{::itemShelvingPlacementSupplyStock.created | date: 'dd/MM/yyyy'}} + {{::itemShelvingPlacementSupplyStock.itemFk}} + + + {{itemShelvingPlacementSupplyStock.longName}} + + + {{::itemShelvingPlacementSupplyStock.parking}} + + {{::itemShelvingPlacementSupplyStock.shelving}} + + {{(itemShelvingPlacementSupplyStock.stock / itemShelvingPlacementSupplyStock.packing).toFixed(2)}} + + {{::itemShelvingPlacementSupplyStock.packing}} +
+
+
+
+ + + + + \ No newline at end of file diff --git a/modules/item/front/item-shelving/index.js b/modules/item/front/item-shelving/index.js new file mode 100644 index 0000000000..b8584039b8 --- /dev/null +++ b/modules/item/front/item-shelving/index.js @@ -0,0 +1,89 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + + this.smartTableOptions = { + activeButtons: { + search: true + }, + columns: [ + { + field: 'parking', + autocomplete: { + url: 'Parkings', + showField: 'code', + valueField: 'code' + } + }, + { + field: 'shelving', + autocomplete: { + url: 'Shelvings', + showField: 'code', + valueField: 'code' + } + }, + { + field: 'created', + searchable: false + }, + { + field: 'itemFk', + searchable: false + }, + { + field: 'longName', + searchable: false + } + ] + }; + } + + get checked() { + const itemShelvings = this.$.model.data || []; + const checkedLines = []; + for (let itemShelving of itemShelvings) { + if (itemShelving.checked) + checkedLines.push(itemShelving.itemShelvingFk); + } + + return checkedLines; + } + + calculateTotals() { + this.labelTotal = 0; + const itemShelvings = this.$.model.data || []; + itemShelvings.forEach(itemShelving => { + const label = itemShelving.stock / itemShelving.packing; + this.labelTotal += label; + }); + } + + onRemove() { + const params = {itemShelvingIds: this.checked}; + const query = `ItemShelvings/deleteItemShelvings`; + this.$http.post(query, params) + .then(() => { + this.vnApp.showSuccess(this.$t('ItemShelvings removed')); + this.$.model.refresh(); + }); + } + + exprBuilder(param, value) { + switch (param) { + case 'parking': + case 'shelving': + case 'label': + case 'packing': + return {[param]: value}; + } + } +} + +ngModule.vnComponent('vnItemShelving', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/item/front/item-shelving/index.spec.js b/modules/item/front/item-shelving/index.spec.js new file mode 100644 index 0000000000..55df1c27dd --- /dev/null +++ b/modules/item/front/item-shelving/index.spec.js @@ -0,0 +1,81 @@ +import './index'; +import crudModel from 'core/mocks/crud-model'; + +describe('item shelving', () => { + describe('Component vnItemShelving', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('item')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + const $element = angular.element(''); + controller = $componentController('vnItemShelving', {$element}); + controller.$.model = crudModel; + controller.$.model.data = [ + {itemShelvingFk: 1, packing: 10, stock: 1}, + {itemShelvingFk: 2, packing: 12, stock: 5}, + {itemShelvingFk: 4, packing: 20, stock: 10} + ]; + const modelData = controller.$.model.data; + modelData[0].checked = true; + modelData[1].checked = true; + })); + + describe('checked() getter', () => { + it('should return a the selected rows', () => { + const result = controller.checked; + + expect(result).toEqual(expect.arrayContaining([1, 2])); + }); + }); + + describe('calculateTotals()', () => { + it('should calculate the total of labels', () => { + controller.calculateTotals(); + + expect(controller.labelTotal).toEqual(1.0166666666666666); + }); + }); + + describe('onRemove()', () => { + it('shoud remove the selected lines', () => { + jest.spyOn(controller.$.model, 'refresh'); + const expectedParams = {itemShelvingIds: [1, 2]}; + + $httpBackend.expectPOST('ItemShelvings/deleteItemShelvings', expectedParams).respond(200); + controller.onRemove(); + $httpBackend.flush(); + + expect(controller.$.model.refresh).toHaveBeenCalled(); + }); + }); + + describe('exprBuilder()', () => { + it('should search by parking', () => { + const expr = controller.exprBuilder('parking', '700-01'); + + expect(expr).toEqual({'parking': '700-01'}); + }); + + it('should search by shelving', () => { + const expr = controller.exprBuilder('shelving', 'AAA'); + + expect(expr).toEqual({'shelving': 'AAA'}); + }); + + it('should search by label', () => { + const expr = controller.exprBuilder('label', 0.17); + + expect(expr).toEqual({'label': 0.17}); + }); + + it('should search by packing', () => { + const expr = controller.exprBuilder('packing', 10); + + expect(expr).toEqual({'packing': 10}); + }); + }); + }); +}); diff --git a/modules/item/front/item-shelving/locale/es.yml b/modules/item/front/item-shelving/locale/es.yml new file mode 100644 index 0000000000..006363cfa1 --- /dev/null +++ b/modules/item/front/item-shelving/locale/es.yml @@ -0,0 +1,5 @@ +Shelving: Matrícula +Remove selected lines: Eliminar líneas seleccionadas +Selected lines will be deleted: Las líneas seleccionadas serán eliminadas +ItemShelvings removed: Carros eliminados +Total labels: Total etiquetas \ No newline at end of file diff --git a/modules/item/front/locale/es.yml b/modules/item/front/locale/es.yml index 1b75e38021..88ab031e14 100644 --- a/modules/item/front/locale/es.yml +++ b/modules/item/front/locale/es.yml @@ -54,6 +54,7 @@ Basic data: Datos básicos Tax: IVA History: Historial Botanical: Botánico +Shelvings: Carros Barcodes: Códigos de barras Diary: Histórico Item diary: Registro de compra-venta diff --git a/modules/item/front/routes.json b/modules/item/front/routes.json index 5743d0ce7f..3dea69ba1b 100644 --- a/modules/item/front/routes.json +++ b/modules/item/front/routes.json @@ -15,11 +15,12 @@ "card": [ {"state": "item.card.basicData", "icon": "settings"}, {"state": "item.card.tags", "icon": "icon-tags"}, + {"state": "item.card.last-entries", "icon": "icon-regentry"}, {"state": "item.card.tax", "icon": "icon-tax"}, - {"state": "item.card.botanical", "icon": "local_florist"}, + {"state": "item.card.botanical", "icon": "local_florist"}, + {"state": "item.card.shelving", "icon": "icon-inventory"}, {"state": "item.card.itemBarcode", "icon": "icon-barcode"}, {"state": "item.card.diary", "icon": "icon-transaction"}, - {"state": "item.card.last-entries", "icon": "icon-regentry"}, {"state": "item.card.log", "icon": "history"} ], "itemType": [ @@ -92,6 +93,16 @@ }, "acl": ["buyer"] }, + { + "url" : "/shelving", + "state": "item.card.shelving", + "component": "vn-item-shelving", + "description": "Shelvings", + "params": { + "item": "$ctrl.item" + }, + "acl": ["employee"] + }, { "url" : "/barcode", "state": "item.card.itemBarcode", diff --git a/modules/monitor/front/index/clients/index.html b/modules/monitor/front/index/clients/index.html index eafc2256ee..381c0e1ae5 100644 --- a/modules/monitor/front/index/clients/index.html +++ b/modules/monitor/front/index/clients/index.html @@ -19,22 +19,24 @@ - @@ -100,9 +102,9 @@ - - \ No newline at end of file + diff --git a/modules/route/front/routes.json b/modules/route/front/routes.json index f5e7d9ae85..75e1fdc576 100644 --- a/modules/route/front/routes.json +++ b/modules/route/front/routes.json @@ -39,7 +39,7 @@ "abstract": true, "component": "vn-route-card" }, { - "url": "/agency-term", + "url": "/agency-term?q", "abstract": true, "state": "route.agencyTerm", "component": "ui-view" @@ -49,12 +49,12 @@ "component": "vn-agency-term-index", "description": "Autonomous", "acl": ["administrative"] - },{ + },{ "url": "/createInvoiceIn?q", "state": "route.agencyTerm.createInvoiceIn", "component": "vn-agency-term-create-invoice-in", "description": "File management", - "params": { + "params": { "route": "$ctrl.route" }, "acl": ["administrative"] @@ -92,4 +92,4 @@ "acl": ["delivery"] } ] -} \ No newline at end of file +} diff --git a/modules/shelving/front/routes.json b/modules/shelving/front/routes.json index b99ca4caca..09a8e389ba 100644 --- a/modules/shelving/front/routes.json +++ b/modules/shelving/front/routes.json @@ -13,9 +13,6 @@ {"state": "shelving.card.log", "icon": "history"} ] }, - "keybindings": [ - {"key": "s", "state": "shelving.index"} - ], "routes": [ { "url": "/shelving", diff --git a/modules/supplier/back/models/supplier.json b/modules/supplier/back/models/supplier.json index b27073ca5c..3cd6386a87 100644 --- a/modules/supplier/back/models/supplier.json +++ b/modules/supplier/back/models/supplier.json @@ -51,6 +51,9 @@ "isSerious": { "type": "boolean" }, + "isTrucker": { + "type": "boolean" + }, "note": { "type": "string" }, diff --git a/modules/supplier/front/descriptor/index.js b/modules/supplier/front/descriptor/index.js index df9fe2155c..a26d9c5106 100644 --- a/modules/supplier/front/descriptor/index.js +++ b/modules/supplier/front/descriptor/index.js @@ -41,6 +41,7 @@ class Controller extends Descriptor { 'payDay', 'isActive', 'isSerious', + 'isTrucker', 'account' ], include: [ diff --git a/modules/supplier/front/descriptor/index.spec.js b/modules/supplier/front/descriptor/index.spec.js index 8926fa4d1a..4d16c51833 100644 --- a/modules/supplier/front/descriptor/index.spec.js +++ b/modules/supplier/front/descriptor/index.spec.js @@ -27,6 +27,7 @@ describe('Supplier Component vnSupplierDescriptor', () => { 'payDay', 'isActive', 'isSerious', + 'isTrucker', 'account' ], include: [ diff --git a/modules/supplier/front/fiscal-data/index.html b/modules/supplier/front/fiscal-data/index.html index 4ae07c81a5..a3ede2058d 100644 --- a/modules/supplier/front/fiscal-data/index.html +++ b/modules/supplier/front/fiscal-data/index.html @@ -118,8 +118,6 @@ rule vn-focus> - - + + - - {{name}} ({{country.country}}) + + + + diff --git a/modules/supplier/front/fiscal-data/locale/es.yml b/modules/supplier/front/fiscal-data/locale/es.yml index 4cb537198a..5232dd95d5 100644 --- a/modules/supplier/front/fiscal-data/locale/es.yml +++ b/modules/supplier/front/fiscal-data/locale/es.yml @@ -3,3 +3,4 @@ Sage transaction type: Tipo de transacción Sage Sage withholding: Retención Sage Supplier activity: Actividad proveedor Healt register: Pasaporte sanitario +Trucker: Transportista \ No newline at end of file diff --git a/modules/ticket/back/methods/expedition/deleteExpeditions.js b/modules/ticket/back/methods/expedition/deleteExpeditions.js new file mode 100644 index 0000000000..2419d3a5e8 --- /dev/null +++ b/modules/ticket/back/methods/expedition/deleteExpeditions.js @@ -0,0 +1,52 @@ + +module.exports = Self => { + Self.remoteMethod('deleteExpeditions', { + description: 'Delete the selected expeditions', + accessType: 'WRITE', + accepts: [{ + arg: 'expeditionIds', + type: ['number'], + required: true, + description: 'The expeditions ids to delete' + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/deleteExpeditions`, + verb: 'POST' + } + }); + + Self.deleteExpeditions = async(expeditionIds, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const promises = []; + for (let expeditionId of expeditionIds) { + const deletedExpedition = models.Expedition.destroyById(expeditionId, myOptions); + promises.push(deletedExpedition); + } + + const deletedExpeditions = await Promise.all(promises); + + if (tx) await tx.commit(); + + return deletedExpeditions; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/expedition/filter.js b/modules/ticket/back/methods/expedition/filter.js index 723d7c8443..65f840d80f 100644 --- a/modules/ticket/back/methods/expedition/filter.js +++ b/modules/ticket/back/methods/expedition/filter.js @@ -32,7 +32,7 @@ module.exports = Self => { `SELECT e.id, e.ticketFk, - e.isBox, + e.freightItemFk, e.workerFk, i1.name packageItemName, e.counter, @@ -51,7 +51,7 @@ module.exports = Self => { FROM vn.expedition e LEFT JOIN vn.expeditionStateType est ON est.id = e.stateTypeFk LEFT JOIN vn.item i2 ON i2.id = e.itemFk - INNER JOIN vn.item i1 ON i1.id = e.isBox + INNER JOIN vn.item i1 ON i1.id = e.freightItemFk LEFT JOIN vn.packaging p ON p.id = e.packagingFk LEFT JOIN vn.item i3 ON i3.id = p.itemFk LEFT JOIN account.user u ON u.id = e.workerFk diff --git a/modules/ticket/back/methods/expedition/moveExpeditions.js b/modules/ticket/back/methods/expedition/moveExpeditions.js new file mode 100644 index 0000000000..cef35ab867 --- /dev/null +++ b/modules/ticket/back/methods/expedition/moveExpeditions.js @@ -0,0 +1,93 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('moveExpeditions', { + description: 'Move the selected expeditions to another ticket', + accessType: 'WRITE', + accepts: [{ + arg: 'clientId', + type: 'number', + description: `The client id`, + required: true + }, + { + arg: 'landed', + type: 'date', + description: `The landing date` + }, + { + arg: 'warehouseId', + type: 'number', + description: `The warehouse id`, + required: true + }, + { + arg: 'addressId', + type: 'number', + description: `The address id`, + required: true + }, + { + arg: 'agencyModeId', + type: 'any', + description: `The agencyMode id` + }, + { + arg: 'routeId', + type: 'any', + description: `The route id` + }, + { + arg: 'expeditionIds', + type: ['number'], + required: true, + description: 'The expeditions ids to move' + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/moveExpeditions`, + verb: 'POST' + } + }); + + Self.moveExpeditions = async(ctx, options) => { + const models = Self.app.models; + const args = ctx.args; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + if (args.routeId) { + const route = await models.Route.findById(args.routeId, null, myOptions); + if (!route) throw new UserError('This route does not exists'); + } + const ticket = await models.Ticket.new(ctx, myOptions); + const promises = []; + for (let expeditionsId of args.expeditionIds) { + const expeditionToUpdate = await models.Expedition.findById(expeditionsId, null, myOptions); + const expeditionUpdated = expeditionToUpdate.updateAttribute('ticketFk', ticket.id, myOptions); + promises.push(expeditionUpdated); + } + + await Promise.all(promises); + + if (tx) await tx.commit(); + + return ticket; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js new file mode 100644 index 0000000000..14bdf7aea6 --- /dev/null +++ b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js @@ -0,0 +1,22 @@ +const models = require('vn-loopback/server/server').models; + +describe('ticket deleteExpeditions()', () => { + it('should delete the selected expeditions', async() => { + const tx = await models.Expedition.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const expeditionIds = [12, 13]; + const result = await models.Expedition.deleteExpeditions(expeditionIds, options); + + expect(result.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); + diff --git a/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js new file mode 100644 index 0000000000..67919e76c0 --- /dev/null +++ b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js @@ -0,0 +1,39 @@ +const models = require('vn-loopback/server/server').models; + +describe('ticket moveExpeditions()', () => { + it('should move the selected expeditions to new ticket', async() => { + const tx = await models.Expedition.beginTransaction({}); + const ctx = { + req: {accessToken: {userId: 9}}, + args: {}, + params: {} + }; + const myCtx = Object.assign({}, ctx); + + try { + const options = {transaction: tx}; + myCtx.args = { + clientId: 1101, + landed: new Date(), + warehouseId: 1, + addressId: 121, + agencyModeId: 1, + routeId: null, + expeditionIds: [1, 2] + + }; + + const ticket = await models.Expedition.moveExpeditions(myCtx, options); + + const newestTicketIdInFixtures = 27; + + expect(ticket.id).toBeGreaterThan(newestTicketIdInFixtures); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); + diff --git a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js index e76511421e..51cd2403f1 100644 --- a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js @@ -79,10 +79,18 @@ describe('sale updatePrice()', () => { const price = 5.4; const originalSalesPersonMana = await models.WorkerMana.findById(18, null, options); const manaComponent = await models.Component.findOne({where: {code: 'mana'}}, options); + const teamOne = 96; + const userId = ctx.req.accessToken.userId; + + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamOne, options); await models.Sale.updatePrice(ctx, saleId, price, options); const updatedSale = await models.Sale.findById(saleId, null, options); - createdSaleComponent = await models.SaleComponent.findOne({where: {saleFk: saleId, componentFk: manaComponent.id}}, options); + const createdSaleComponent = await models.SaleComponent.findOne({ + where: { + saleFk: saleId, componentFk: manaComponent.id + }}, options); expect(updatedSale.price).toBe(price); expect(createdSaleComponent.value).toEqual(-2.04); diff --git a/modules/ticket/back/methods/sale/specs/usesMana.spec.js b/modules/ticket/back/methods/sale/specs/usesMana.spec.js new file mode 100644 index 0000000000..777bdc8f0a --- /dev/null +++ b/modules/ticket/back/methods/sale/specs/usesMana.spec.js @@ -0,0 +1,48 @@ +const models = require('vn-loopback/server/server').models; + +describe('sale usesMana()', () => { + const ctx = { + req: { + accessToken: {userId: 18} + } + }; + + it('should return that the worker uses mana', async() => { + const tx = await models.Sale.beginTransaction({}); + + try { + const options = {transaction: tx}; + const teamOne = 96; + const userId = ctx.req.accessToken.userId; + + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamOne, options); + + const usesMana = await models.Sale.usesMana(ctx, options); + + expect(usesMana).toBe(true); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return that the worker not uses mana', async() => { + const tx = await models.Sale.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const usesMana = await models.Sale.usesMana(ctx, options); + + expect(usesMana).toBe(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index bbd9d154db..302522cfbc 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -77,7 +77,8 @@ module.exports = Self => { const oldPrice = sale.price; const userId = ctx.req.accessToken.userId; - const usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, myOptions); + + const usesMana = await models.Sale.usesMana(ctx, myOptions); const componentCode = usesMana ? 'mana' : 'buyerDiscount'; const discount = await models.Component.findOne({where: {code: componentCode}}, myOptions); const componentId = discount.id; @@ -88,7 +89,6 @@ module.exports = Self => { saleFk: id }; const saleComponent = await models.SaleComponent.findOne({where}, myOptions); - if (saleComponent) { await models.SaleComponent.updateAll(where, { value: saleComponent.value + componentValue diff --git a/modules/ticket/back/methods/sale/usesMana.js b/modules/ticket/back/methods/sale/usesMana.js new file mode 100644 index 0000000000..093057dca4 --- /dev/null +++ b/modules/ticket/back/methods/sale/usesMana.js @@ -0,0 +1,31 @@ +module.exports = Self => { + Self.remoteMethodCtx('usesMana', { + description: 'Returns if the worker uses mana', + accessType: 'READ', + accepts: [], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/usesMana`, + verb: 'GET' + } + }); + + Self.usesMana = async(ctx, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); + const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); + const workerDepartment = await models.WorkerDepartment.findById(userId, null, myOptions); + const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); + + return usesMana ? true : false; + }; +}; diff --git a/modules/ticket/back/methods/ticket/collectionLabel.js b/modules/ticket/back/methods/ticket/collectionLabel.js new file mode 100644 index 0000000000..064ca0210b --- /dev/null +++ b/modules/ticket/back/methods/ticket/collectionLabel.js @@ -0,0 +1,50 @@ +const {Report} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('collectionLabel', { + description: 'Returns the collection label', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The ticket id', + http: {source: 'path'} + }, + ], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, { + arg: 'Content-Type', + type: 'String', + http: {target: 'header'} + }, { + arg: 'Content-Disposition', + type: 'String', + http: {target: 'header'} + } + ], + http: { + path: '/:id/collection-label', + verb: 'GET' + } + }); + + Self.collectionLabel = async(ctx, id) => { + const args = Object.assign({}, ctx.args); + const params = {lang: ctx.req.getLocale()}; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const report = new Report('collection-label', params); + const stream = await report.toPdfStream(); + + return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; + }; +}; diff --git a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js index 2aa2a07c4c..b3291c25a4 100644 --- a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js +++ b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js @@ -18,9 +18,12 @@ describe('ticket componentUpdate()', () => { beforeAll(async() => { const deliveryComponenet = await models.Component.findOne({where: {code: 'delivery'}}); deliveryComponentId = deliveryComponenet.id; - componentOfSaleSeven = `SELECT value FROM vn.saleComponent WHERE saleFk = 7 AND componentFk = ${deliveryComponentId}`; - componentOfSaleEight = `SELECT value FROM vn.saleComponent WHERE saleFk = 8 AND componentFk = ${deliveryComponentId}`; - + componentOfSaleSeven = `SELECT value + FROM vn.saleComponent + WHERE saleFk = 7 AND componentFk = ${deliveryComponentId}`; + componentOfSaleEight = `SELECT value + FROM vn.saleComponent + WHERE saleFk = 8 AND componentFk = ${deliveryComponentId}`; [componentValue] = await models.SaleComponent.rawSql(componentOfSaleSeven); firstvalueBeforeChange = componentValue.value; diff --git a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js index 1873207aad..1f6712087d 100644 --- a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js +++ b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js @@ -110,6 +110,11 @@ describe('sale updateDiscount()', () => { const componentId = manaDiscount.id; const manaCode = 'mana'; + const teamOne = 96; + const userId = ctx.req.accessToken.userId; + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamOne, options); + await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, manaCode, options); const updatedSale = await models.Sale.findById(originalSaleId, null, options); @@ -150,6 +155,11 @@ describe('sale updateDiscount()', () => { const componentId = manaDiscount.id; const manaCode = 'manaClaim'; + const teamOne = 96; + const userId = ctx.req.accessToken.userId; + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamOne, options); + await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, manaCode, options); const updatedSale = await models.Sale.findById(originalSaleId, null, options); diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index b1291a45bf..4dd3461615 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -98,12 +98,7 @@ module.exports = Self => { if (isLocked || (!hasAllowedRoles && alertLevel > 0)) throw new UserError(`The sales of this ticket can't be modified`); - const usesMana = await models.WorkerMana.findOne({ - where: { - workerFk: userId - }, - fields: 'amount'}, myOptions); - + const usesMana = await models.Sale.usesMana(ctx, myOptions); const componentCode = usesMana ? manaCode : 'buyerDiscount'; const discountComponent = await models.Component.findOne({ where: {code: componentCode}}, myOptions); @@ -115,14 +110,38 @@ module.exports = Self => { for (let sale of sales) { const oldDiscount = sale.discount; const value = ((-sale.price * newDiscount) / 100); - const newComponent = models.SaleComponent.upsert({ - saleFk: sale.id, - value: value, - componentFk: componentId}, myOptions); + + const manaComponent = await models.Component.findOne({ + where: {code: 'mana'} + }, myOptions); + + const manaClaimComponent = await models.Component.findOne({ + where: {code: 'manaClaim'} + }, myOptions); + + const [oldComponent] = await models.SaleComponent.find({ + where: { + and: [ + {saleFk: sale.id}, + {componentFk: {inq: [manaComponent.id, manaClaimComponent.id]}} + ] + } + }, myOptions); + + let deletedComponent; + if (oldComponent) { + const filter = { + saleFk: sale.id, + componentFk: oldComponent.componentFk + }; + deletedComponent = await models.SaleComponent.destroyAll(filter, myOptions); + } + + const newComponent = await createSaleComponent(sale.id, value, componentId, myOptions); const updatedSale = sale.updateAttribute('discount', newDiscount, myOptions); - promises.push(newComponent, updatedSale); + promises.push(newComponent, updatedSale, deletedComponent); const change = `${oldDiscount}% ➔ *${newDiscount}%*`; changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${change}`; @@ -165,4 +184,14 @@ module.exports = Self => { throw e; } }; + + async function createSaleComponent(saleId, value, componentId, myOptions) { + const models = Self.app.models; + + return models.SaleComponent.upsert({ + saleFk: saleId, + value: value, + componentFk: componentId + }, myOptions); + } }; diff --git a/modules/ticket/back/models/expedition.js b/modules/ticket/back/models/expedition.js index 9d65643737..46cde68902 100644 --- a/modules/ticket/back/models/expedition.js +++ b/modules/ticket/back/models/expedition.js @@ -1,3 +1,5 @@ module.exports = function(Self) { require('../methods/expedition/filter')(Self); + require('../methods/expedition/deleteExpeditions')(Self); + require('../methods/expedition/moveExpeditions')(Self); }; diff --git a/modules/ticket/back/models/expedition.json b/modules/ticket/back/models/expedition.json index fc94f185df..324ad46092 100644 --- a/modules/ticket/back/models/expedition.json +++ b/modules/ticket/back/models/expedition.json @@ -16,7 +16,7 @@ "type": "number", "description": "Identifier" }, - "isBox": { + "freightItemFk": { "type": "number" }, "created": { @@ -55,7 +55,7 @@ "freightItem": { "type": "belongsTo", "model": "Item", - "foreignKey": "isBox" + "foreignKey": "freightItemFk" }, "packaging": { "type": "belongsTo", diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index 2a4457263d..ae247fc242 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -8,6 +8,7 @@ module.exports = Self => { require('../methods/sale/recalculatePrice')(Self); require('../methods/sale/refund')(Self); require('../methods/sale/canEdit')(Self); + require('../methods/sale/usesMana')(Self); Self.validatesPresenceOf('concept', { message: `Concept cannot be blank` diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index 9255e52e62..f265709e73 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -33,4 +33,5 @@ module.exports = function(Self) { require('../methods/ticket/closeByTicket')(Self); require('../methods/ticket/closeByAgency')(Self); require('../methods/ticket/closeByRoute')(Self); + require('../methods/ticket/collectionLabel')(Self); }; diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index ea84743bcd..0c04b42fb9 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -5,6 +5,13 @@ + + Transfer client + + + + + + + + #{{id}} - {{::name}} + + + + + + + + { + const ticket = this.ticket; + + const params = + { + clientFk: client.data.id, + addressFk: client.data.defaultAddressFk, + }; + + this.$http.patch(`Tickets/${ticket.id}`, params).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + this.reload(); + }); + }); + } + isTicketEditable() { if (!this.ticket) return; diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 091b9a2cfd..1716e36f6f 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -281,4 +281,17 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { $httpBackend.flush(); }); }); + + describe('transferClient()', () => { + it(`should perform two queries, a get to obtain the clientData and a patch to update the ticket`, () => { + const client = + { + clientFk: 1101, + addressFk: 1, + }; + $httpBackend.expect('GET', `Clients/${ticket.client.id}`).respond(client); + $httpBackend.expect('PATCH', `Tickets/${ticket.id}`).respond(); + controller.transferClient(); + }); + }); }); diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index b65159a3c5..968c61f841 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -9,4 +9,5 @@ Send CSV Delivery Note: Enviar albarán en CSV Send PDF Delivery Note: Enviar albarán en PDF Show Proforma: Ver proforma Refund all: Abonar todo -The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" \ No newline at end of file +The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" +Transfer client: Transferir cliente \ No newline at end of file diff --git a/modules/ticket/front/descriptor/locale/es.yml b/modules/ticket/front/descriptor/locale/es.yml index 8ab88ce09e..bce9e62d7c 100644 --- a/modules/ticket/front/descriptor/locale/es.yml +++ b/modules/ticket/front/descriptor/locale/es.yml @@ -22,4 +22,4 @@ SMS Pending payment: 'SMS Pago pendiente' Restore ticket: Restaurar ticket You are going to restore this ticket: Vas a restaurar este ticket Are you sure you want to restore this ticket?: ¿Seguro que quieres restaurar el ticket? -Are you sure you want to refund all?: ¿Seguro que quieres abonar todo? \ No newline at end of file +Are you sure you want to refund all?: ¿Seguro que quieres abonar todo? diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index a41d368f6b..ec6dc7ee2b 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -8,54 +8,77 @@ auto-load="true"> - - - - - - Expedition - Item - Name - Package type - Counter - externalId - Created - State - - - - - - - - - - {{expedition.id | zeroFill:6}} - - - {{expedition.packagingFk}} - - - {{::expedition.packageItemName}} - {{::expedition.freightItemName}} - {{::expedition.counter}} - {{::expedition.externalId}} - {{::expedition.created | date:'dd/MM/yyyy HH:mm'}} - {{::expedition.state}} - - - - - - - + + + + + + + + + +

Subtotal {{$ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}

+

VAT {{$ctrl.ticket.totalWithVat - $ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}

+

Total {{$ctrl.ticket.totalWithVat | currency: 'EUR':2}}

+
+
+ + + + + + + + Expedition + Item + Name + Package type + Counter + externalId + Created + State + + + + + + + + + + {{expedition.id | zeroFill:6}} + + + {{expedition.packagingFk}} + + + {{::expedition.packageItemName}} + {{::expedition.freightItemName}} + {{::expedition.counter}} + {{::expedition.externalId}} + {{::expedition.created | date:'dd/MM/yyyy HH:mm'}} + {{::expedition.state}} + + + + + + +
- + on-accept="$ctrl.onRemove()"> - + - + State @@ -111,4 +134,37 @@ - \ No newline at end of file + + + + + New ticket without route + + + New ticket with route + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index 120d89bb29..7ffe2fe5e6 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -2,6 +2,27 @@ import ngModule from '../module'; import Section from 'salix/components/section'; class Controller extends Section { + constructor($element, $scope) { + super($element, $scope); + this.landed = new Date(); + this.newRoute = null; + } + + get checked() { + const rows = this.$.model.data || []; + const checkedRows = []; + for (let row of rows) { + if (row.checked) + checkedRows.push(row.id); + } + + return checkedRows; + } + + get totalChecked() { + return this.checked.length; + } + onDialogAccept(id) { return this.$http.delete(`Expeditions/${id}`) .then(() => this.$.model.refresh()); @@ -11,6 +32,33 @@ class Controller extends Section { this.expedition = expedition; this.$.statusLog.show(); } + + onRemove() { + const params = {expeditionIds: this.checked}; + const query = `Expeditions/deleteExpeditions`; + this.$http.post(query, params) + .then(() => { + this.vnApp.showSuccess(this.$t('Expedition removed')); + this.$.model.refresh(); + }); + } + + createTicket(landed, routeFk) { + const params = { + clientId: this.ticket.clientFk, + landed: landed, + warehouseId: this.ticket.warehouseFk, + addressId: this.ticket.addressFk, + agencyModeId: this.ticket.agencyModeFk, + routeId: routeFk, + expeditionIds: this.checked + }; + const query = `Expeditions/moveExpeditions`; + this.$http.post(query, params).then(res => { + this.vnApp.showSuccess(this.$t('Data saved!')); + this.$state.go('ticket.card.summary', {id: res.data.id}); + }); + } } ngModule.vnComponent('vnTicketExpedition', { diff --git a/modules/ticket/front/expedition/index.spec.js b/modules/ticket/front/expedition/index.spec.js index 586ef21096..b95d64fa34 100644 --- a/modules/ticket/front/expedition/index.spec.js +++ b/modules/ticket/front/expedition/index.spec.js @@ -17,6 +17,14 @@ describe('Ticket', () => { refresh: () => {} }; controller = $componentController('vnTicketExpedition', {$element: null, $scope}); + controller.$.model.data = [ + {id: 1}, + {id: 2}, + {id: 3} + ]; + const modelData = controller.$.model.data; + modelData[0].checked = true; + modelData[1].checked = true; })); describe('onDialogAccept()', () => { @@ -50,5 +58,51 @@ describe('Ticket', () => { expect(controller.$.statusLog.show).toHaveBeenCalledWith(); }); }); + + describe('onRemove()', () => { + it('should make a query and then call to the model refresh() method', () => { + jest.spyOn($scope.model, 'refresh'); + + const expectedParams = {expeditionIds: [1, 2]}; + $httpBackend.expect('POST', 'Expeditions/deleteExpeditions', expectedParams).respond(200); + controller.onRemove(); + $httpBackend.flush(); + + expect($scope.model.refresh).toHaveBeenCalledWith(); + }); + }); + + describe('createTicket()', () => { + it('should make a query and then call to the $state go() method', () => { + jest.spyOn(controller.$state, 'go').mockReturnThis(); + + const ticket = { + clientFk: 1101, + landed: new Date(), + addressFk: 121, + agencyModeFk: 1, + warehouseFk: 1 + }; + const routeId = null; + controller.ticket = ticket; + + const ticketToTransfer = {id: 28}; + + const expectedParams = { + clientId: 1101, + landed: new Date(), + warehouseId: 1, + addressId: 121, + agencyModeId: 1, + routeId: null, + expeditionIds: [1, 2] + }; + $httpBackend.expect('POST', 'Expeditions/moveExpeditions', expectedParams).respond(ticketToTransfer); + controller.createTicket(ticket.landed, routeId); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.summary', {id: ticketToTransfer.id}); + }); + }); }); }); diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml index d23cf25af3..278dcc8f2a 100644 --- a/modules/ticket/front/expedition/locale/es.yml +++ b/modules/ticket/front/expedition/locale/es.yml @@ -1 +1,6 @@ -Status log: Hitorial de estados \ No newline at end of file +Status log: Hitorial de estados +Expedition removed: Expedición eliminada +Move: Mover +New ticket without route: Nuevo ticket sin ruta +New ticket with route: Nuevo ticket con ruta +Route id: Id ruta \ No newline at end of file diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 42eb10cb06..c624b1a955 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -244,7 +244,7 @@
-
MANÁ: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
+
Mana: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
- - - - - -
- - -
-
-
Mana: {{::$ctrl.edit.mana | currency: 'EUR':0}}
-
-
- - - - - - - - -
-

New price

-

- {{$ctrl.getNewPrice() | currency: 'EUR': 2}} -

-
-
-
- - - + + +
+
+
Mana: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
+
- + + + + + + +
+

New price

+

+ {{$ctrl.getNewPrice() | currency: 'EUR': 2}} +

+
-
- Mana: {{::$ctrl.edit.mana | currency: 'EUR': 0}} -
- - + + + + + + +
+
@@ -490,7 +474,7 @@ + ng-click="$ctrl.showEditDiscountPopover($event, sale)"> Update discount { + this.useMana = res.data; + }); + } + /** * Returns checked instances * @@ -243,26 +251,21 @@ class Controller extends Section { showEditDiscountPopover(event, sale) { if (this.isLocked) return; - - this.edit = { - discount: sale.discount, - sale: sale - }; + if (sale) { + this.edit = { + discount: sale.discount, + sale: sale + }; + } else { + this.edit = { + discount: null, + sales: this.selectedValidSales() + }; + } this.$.editDiscount.show(event); } - showEditDiscountDialog(event) { - if (this.isLocked) return; - - this.edit = { - discount: null, - sales: this.selectedValidSales() - }; - - this.$.editDiscountDialog.show(event); - } - changeDiscount() { const sale = this.edit.sale; const newDiscount = this.edit.discount; @@ -278,11 +281,10 @@ class Controller extends Section { const hasChanges = sales.some(sale => { return sale.discount != newDiscount; }); - if (newDiscount != null && hasChanges) this.updateDiscount(sales); - this.$.editDiscountDialog.hide(); + this.$.editDiscount.hide(); } updateDiscount(sales) { @@ -303,7 +305,7 @@ class Controller extends Section { } getNewPrice() { - if (this.edit) { + if (this.edit.sale) { const sale = this.edit.sale; let newDiscount = sale.discount; let newPrice = this.edit.price || sale.price; @@ -375,6 +377,7 @@ class Controller extends Section { const params = { ticketFk: this.ticket.id, created: this.ticket.updated, + landed: this.ticket.landed, notAvailables }; this.newSMS = { @@ -505,7 +508,8 @@ class Controller extends Section { } save() { - this.changeDiscount(); + if (this.edit.sale) this.changeDiscount(); + if (this.edit.sales) this.changeMultipleDiscount(); } cancel() { diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index 28d8749328..fbee966fd4 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -115,6 +115,7 @@ describe('Ticket', () => { const expectedAmount = 250; $httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount); + $httpBackend.expect('GET', 'Sales/usesMana').respond(200); $httpBackend.expect('GET', 'WorkerManas/getCurrentWorkerMana').respond(200, expectedAmount); controller.getMana(); $httpBackend.flush(); @@ -393,7 +394,7 @@ describe('Ticket', () => { secondSelectedSale.checked = true; const expectedSales = [firstSelectedSale, secondSelectedSale]; - controller.$.editDiscountDialog = {hide: jest.fn()}; + controller.$.editDiscount = {hide: jest.fn()}; controller.edit = { discount: 10, sales: expectedSales @@ -402,7 +403,7 @@ describe('Ticket', () => { controller.changeMultipleDiscount(); expect(controller.updateDiscount).toHaveBeenCalledWith(expectedSales); - expect(controller.$.editDiscountDialog.hide).toHaveBeenCalledWith(); + expect(controller.$.editDiscount.hide).toHaveBeenCalledWith(); }); it('should not call to the updateDiscount() method and then to the editDiscountDialog hide() method', () => { @@ -417,7 +418,7 @@ describe('Ticket', () => { secondSelectedSale.discount = 10; const expectedSales = [firstSelectedSale, secondSelectedSale]; - controller.$.editDiscountDialog = {hide: jest.fn()}; + controller.$.editDiscount = {hide: jest.fn()}; controller.edit = { discount: 10, sales: expectedSales @@ -426,7 +427,7 @@ describe('Ticket', () => { controller.changeMultipleDiscount(); expect(controller.updateDiscount).not.toHaveBeenCalledWith(expectedSales); - expect(controller.$.editDiscountDialog.hide).toHaveBeenCalledWith(); + expect(controller.$.editDiscount.hide).toHaveBeenCalledWith(); }); }); diff --git a/modules/ticket/front/sale/locale/en.yml b/modules/ticket/front/sale/locale/en.yml index ec7f541609..b418c086cb 100644 --- a/modules/ticket/front/sale/locale/en.yml +++ b/modules/ticket/front/sale/locale/en.yml @@ -1,3 +1,3 @@ Product not available: >- - Verdnatura communicates: Your order {{ticketFk}} created on {{created | date: "dd/MM/yyyy"}}. + Verdnatura communicates: Your order {{ticketFk}} with reception date on {{landed | date: "dd/MM/yyyy"}}. {{notAvailables}} not available. Sorry for the inconvenience. \ No newline at end of file diff --git a/modules/ticket/front/sale/locale/es.yml b/modules/ticket/front/sale/locale/es.yml index aab8ff493d..072e57534d 100644 --- a/modules/ticket/front/sale/locale/es.yml +++ b/modules/ticket/front/sale/locale/es.yml @@ -26,7 +26,7 @@ Destination ticket: Ticket destinatario Change ticket state to 'Ok': Cambiar estado del ticket a 'Ok' Reserved: Reservado Send shortage SMS: Enviar SMS faltas -Product not available: "Verdnatura le comunica:\rPedido {{ticketFk}} día {{created | date: 'dd/MM/yyyy'}}.\r{{notAvailables}} no disponible/s.\rDisculpe las molestias." +Product not available: "Verdnatura le comunica:\rPedido {{ticketFk}} con fecha recepción {{landed | date: 'dd/MM/yyyy'}}.\r{{notAvailables}} no disponible/s.\rDisculpe las molestias." Continue anyway?: ¿Continuar de todas formas? This ticket is now empty: El ticket ha quedado vacio Do you want to delete it?: ¿Quieres eliminarlo? diff --git a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js deleted file mode 100644 index 3e64a985ab..0000000000 --- a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js +++ /dev/null @@ -1,182 +0,0 @@ -const Imap = require('imap'); -module.exports = Self => { - Self.remoteMethod('checkInbox', { - description: 'Check an email inbox and process it', - accessType: 'READ', - returns: - { - arg: 'body', - type: 'file', - root: true - }, - http: { - path: `/checkInbox`, - verb: 'POST' - } - }); - - Self.checkInbox = async() => { - let imapConfig = await Self.app.models.WorkerTimeControlParams.findOne(); - let imap = new Imap({ - user: imapConfig.mailUser, - password: imapConfig.mailPass, - host: imapConfig.mailHost, - port: 993, - tls: true - }); - let isEmailOk; - let uid; - let emailBody; - - function openInbox(cb) { - imap.openBox('INBOX', true, cb); - } - - imap.once('ready', function() { - openInbox(function(err, box) { - if (err) throw err; - const totalMessages = box.messages.total; - if (totalMessages == 0) - imap.end(); - - let f = imap.seq.fetch('1:*', { - bodies: ['HEADER.FIELDS (FROM SUBJECT)', '1'], - struct: true - }); - f.on('message', function(msg, seqno) { - isEmailOk = false; - msg.on('body', function(stream, info) { - let buffer = ''; - let bufferCopy = ''; - stream.on('data', function(chunk) { - buffer = chunk.toString('utf8'); - if (info.which === '1' && bufferCopy.length == 0) - bufferCopy = buffer.replace(/\s/g, ' '); - }); - stream.on('end', function() { - if (bufferCopy.length > 0) { - emailBody = bufferCopy.toUpperCase().trim(); - - const bodyPositionOK = emailBody.match(/\bOK\b/i); - const bodyPositionIndex = (bodyPositionOK.index == 0 || bodyPositionOK.index == 122); - if (bodyPositionOK != null && bodyPositionIndex) - isEmailOk = true; - else - isEmailOk = false; - } - }); - msg.once('attributes', function(attrs) { - uid = attrs.uid; - }); - msg.once('end', function() { - if (info.which === 'HEADER.FIELDS (FROM SUBJECT)') { - if (isEmailOk) { - imap.move(uid, 'exito', function(err) { - }); - emailConfirm(buffer); - } else { - imap.move(uid, 'error', function(err) { - }); - emailReply(buffer, emailBody); - } - } - }); - }); - }); - f.once('end', function() { - imap.end(); - }); - }); - }); - - imap.connect(); - return 'Leer emails de gestion horaria'; - }; - - async function emailConfirm(buffer) { - const now = new Date(); - const from = JSON.stringify(Imap.parseHeader(buffer).from); - const subject = JSON.stringify(Imap.parseHeader(buffer).subject); - - const timeControlDate = await getEmailDate(subject); - const week = timeControlDate[0]; - const year = timeControlDate[1]; - const user = await getUser(from); - let workerMail; - - if (user.id != null) { - workerMail = await Self.app.models.WorkerTimeControlMail.findOne({ - where: { - week: week, - year: year, - workerFk: user.id - } - }); - } - if (workerMail != null) { - await workerMail.updateAttributes({ - updated: now, - state: 'CONFIRMED' - }); - } - } - - async function emailReply(buffer, emailBody) { - const now = new Date(); - const from = JSON.stringify(Imap.parseHeader(buffer).from); - const subject = JSON.stringify(Imap.parseHeader(buffer).subject); - - const timeControlDate = await getEmailDate(subject); - const week = timeControlDate[0]; - const year = timeControlDate[1]; - const user = await getUser(from); - let workerMail; - - if (user.id != null) { - workerMail = await Self.app.models.WorkerTimeControlMail.findOne({ - where: { - week: week, - year: year, - workerFk: user.id - } - }); - - if (workerMail != null) { - await workerMail.updateAttributes({ - updated: now, - state: 'REVISE', - emailResponse: emailBody - }); - } else - await sendMail(user, subject, emailBody); - } - } - - async function getUser(workerEmail) { - const userEmail = workerEmail.match(/(?<=<)(.*?)(?=>)/); - - let [user] = await Self.rawSql(`SELECT u.id,u.name FROM account.user u - LEFT JOIN account.mailForward m on m.account = u.id - WHERE forwardTo =? OR - CONCAT(u.name,'@verdnatura.es') = ?`, - [userEmail[0], userEmail[0]]); - - return user; - } - - async function getEmailDate(subject) { - const date = subject.match(/\d+/g); - return date; - } - - async function sendMail(user, subject, emailBody) { - const sendTo = 'rrhh@verdnatura.es'; - const emailSubject = subject + ' ' + user.name; - - await Self.app.models.Mail.create({ - receiver: sendTo, - subject: emailSubject, - body: emailBody - }); - } -}; diff --git a/modules/worker/back/methods/worker-time-control/sendMail.js b/modules/worker/back/methods/worker-time-control/sendMail.js new file mode 100644 index 0000000000..2f9559b3a1 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/sendMail.js @@ -0,0 +1,377 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; + +module.exports = Self => { + Self.remoteMethodCtx('sendMail', { + description: `Send an email with the hours booked to the employees who telecommuting. + It also inserts booked hours in cases where the employee is telecommuting`, + accessType: 'WRITE', + accepts: [{ + arg: 'workerId', + type: 'number', + description: 'The worker id' + }, + { + arg: 'week', + type: 'number' + }, + { + arg: 'year', + type: 'number' + }], + returns: [{ + type: 'Object', + root: true + }], + http: { + path: `/sendMail`, + verb: 'POST' + } + }); + + Self.sendMail = async(ctx, options) => { + const models = Self.app.models; + const conn = Self.dataSource.connector; + const args = ctx.args; + const $t = ctx.req.__; // $translate + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + const stmts = []; + let stmt; + + try { + if (!args.week || !args.year) { + const from = new Date(); + const to = new Date(); + + const time = await models.Time.findOne({ + where: { + dated: {between: [from.setDate(from.getDate() - 10), to.setDate(to.getDate() - 4)]} + }, + order: 'week ASC' + }, myOptions); + + args.week = time.week; + args.year = time.year; + } + + const started = getStartDateOfWeekNumber(args.week, args.year); + started.setHours(0, 0, 0, 0); + + const ended = new Date(started); + ended.setDate(started.getDate() + 6); + ended.setHours(23, 59, 59, 999); + + stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate'); + stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeBusinessCalculate'); + + if (args.workerId) { + await models.WorkerTimeControl.destroyAll({ + userFk: args.workerId, + timed: {between: [started, ended]}, + isSendMail: true + }, myOptions); + + const where = { + workerFk: args.workerId, + year: args.year, + week: args.week + }; + await models.WorkerTimeControlMail.updateAll(where, { + updated: new Date(), state: 'SENDED' + }, myOptions); + + stmt = new ParameterizedSQL( + `CALL vn.timeControl_calculateByUser(?, ?, ?) + `, [args.workerId, started, ended]); + stmts.push(stmt); + + stmt = new ParameterizedSQL( + `CALL vn.timeBusiness_calculateByUser(?, ?, ?) + `, [args.workerId, started, ended]); + stmts.push(stmt); + } else { + await models.WorkerTimeControl.destroyAll({ + timed: {between: [started, ended]}, + isSendMail: true + }, myOptions); + + const where = { + year: args.year, + week: args.week + }; + await models.WorkerTimeControlMail.updateAll(where, { + updated: new Date(), state: 'SENDED' + }, myOptions); + + stmt = new ParameterizedSQL(`CALL vn.timeControl_calculateAll(?, ?)`, [started, ended]); + stmts.push(stmt); + + stmt = new ParameterizedSQL(`CALL vn.timeBusiness_calculateAll(?, ?)`, [started, ended]); + stmts.push(stmt); + } + + stmt = new ParameterizedSQL(` + SELECT CONCAT(u.name, '@verdnatura.es') receiver, + u.id workerFk, + tb.dated, + tb.timeWorkDecimal, + tb.timeWorkSexagesimal timeWorkSexagesimal, + tb.timeTable, + tc.timeWorkDecimal timeWorkedDecimal, + tc.timeWorkSexagesimal timeWorkedSexagesimal, + tb.type, + tb.businessFk, + tb.permissionRate, + d.isTeleworking + FROM tmp.timeBusinessCalculate tb + JOIN user u ON u.id = tb.userFk + JOIN department d ON d.id = tb.departmentFk + JOIN business b ON b.id = tb.businessFk + LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk AND tc.dated = tb.dated + LEFT JOIN worker w ON w.id = u.id + JOIN (SELECT tb.userFk, + SUM(IF(tb.type IS NULL, + IF(tc.timeWorkDecimal > 0, FALSE, IF(tb.timeWorkDecimal > 0, TRUE, FALSE)), + TRUE))isTeleworkingWeek + FROM tmp.timeBusinessCalculate tb + LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk + AND tc.dated = tb.dated + GROUP BY tb.userFk + HAVING isTeleworkingWeek > 0 + )sub ON sub.userFk = u.id + WHERE d.hasToRefill + AND IFNULL(?, u.id) = u.id + AND b.companyCodeFk = 'VNL' + AND w.businessFk + ORDER BY u.id, tb.dated + `, [args.workerId]); + const index = stmts.push(stmt) - 1; + + const sql = ParameterizedSQL.join(stmts, ';'); + const days = await conn.executeStmt(sql, myOptions); + + let previousWorkerFk = days[index][0].workerFk; + let previousReceiver = days[index][0].receiver; + + const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions); + + for (let day of days[index]) { + workerFk = day.workerFk; + if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null + && (day.permissionRate ? day.permissionRate : true)) { + if (day.timeTable == null) { + const timed = new Date(day.dated); + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(8), + manual: true, + direction: 'in', + isSendMail: true + }, myOptions); + + if (day.timeWorkDecimal >= workerTimeControlConfig.timeToBreakTime / 3600) { + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(9), + manual: true, + direction: 'middle', + isSendMail: true + }, myOptions); + + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(9, 20), + manual: true, + direction: 'middle', + isSendMail: true + }, myOptions); + } + + const [hoursWork, minutesWork, secondsWork] = getTime(day.timeWorkSexagesimal); + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(8 + hoursWork, minutesWork, secondsWork), + manual: true, + direction: 'out', + isSendMail: true + }, myOptions); + } else { + const weekDay = day.dated.getDay(); + const journeys = await models.Journey.find({ + where: { + business_id: day.businessFk, + day_id: weekDay + } + }, myOptions); + + let timeTableDecimalInSeconds = 0; + for (let journey of journeys) { + const start = new Date(); + const [startHours, startMinutes, startSeconds] = getTime(journey.start); + start.setHours(startHours, startMinutes, startSeconds, 0); + + const end = new Date(); + const [endHours, endMinutes, endSeconds] = getTime(journey.end); + end.setHours(endHours, endMinutes, endSeconds, 0); + + const result = (end - start) / 1000; + timeTableDecimalInSeconds += result; + } + + for (let journey of journeys) { + const timeTableDecimal = timeTableDecimalInSeconds / 3600; + if (day.timeWorkDecimal == timeTableDecimal) { + const timed = new Date(day.dated); + const [startHours, startMinutes, startSeconds] = getTime(journey.start); + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(startHours, startMinutes, startSeconds), + manual: true, + isSendMail: true + }, myOptions); + + const [endHours, endMinutes, endSeconds] = getTime(journey.end); + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(endHours, endMinutes, endSeconds), + manual: true, + isSendMail: true + }, myOptions); + } else { + const minStart = journeys.reduce(function(prev, curr) { + return curr.start < prev.start ? curr : prev; + }); + if (journey == minStart) { + const timed = new Date(day.dated); + const [startHours, startMinutes, startSeconds] = getTime(journey.start); + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(startHours, startMinutes, startSeconds), + manual: true, + isSendMail: true + }, myOptions); + + const [hoursWork, minutesWork, secondsWork] = getTime(day.timeWorkSexagesimal); + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours( + startHours + hoursWork, + startMinutes + minutesWork, + startSeconds + secondsWork + ), + manual: true, + isSendMail: true + }, myOptions); + } + } + + if (day.timeWorkDecimal >= workerTimeControlConfig.timeToBreakTime / 3600) { + const minStart = journeys.reduce(function(prev, curr) { + return curr.start < prev.start ? curr : prev; + }); + if (journey == minStart) { + const timed = new Date(day.dated); + const [startHours, startMinutes, startSeconds] = getTime(journey.start); + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(startHours + 1, startMinutes, startSeconds), + manual: true, + isSendMail: true + }, myOptions); + + await models.WorkerTimeControl.create({ + userFk: day.workerFk, + timed: timed.setHours(startHours + 1, startMinutes + 20, startSeconds), + manual: true, + isSendMail: true + }, myOptions); + } + } + } + const timed = new Date(day.dated); + const firstWorkerTimeControl = await models.WorkerTimeControl.findOne({ + where: { + userFk: day.workerFk, + timed: {between: [timed.setHours(0, 0, 0, 0), timed.setHours(23, 59, 59, 999)]} + }, + order: 'timed ASC' + }, myOptions); + + if (firstWorkerTimeControl) + firstWorkerTimeControl.updateAttribute('direction', 'in', myOptions); + + const lastWorkerTimeControl = await models.WorkerTimeControl.findOne({ + where: { + userFk: day.workerFk, + timed: {between: [timed.setHours(0, 0, 0, 0), timed.setHours(23, 59, 59, 999)]} + }, + order: 'timed DESC' + }, myOptions); + + if (lastWorkerTimeControl) + lastWorkerTimeControl.updateAttribute('direction', 'out', myOptions); + } + } + + const lastDay = days[index][days[index].length - 1]; + if (day.workerFk != previousWorkerFk || day == lastDay) { + const salix = await models.Url.findOne({ + where: { + appName: 'salix', + environment: process.env.NODE_ENV || 'dev' + } + }, myOptions); + + const timestamp = started.getTime() / 1000; + await models.Mail.create({ + receiver: previousReceiver, + subject: $t('Record of hours week', { + week: args.week, + year: args.year + }), + body: `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}` + }, myOptions); + + query = `INSERT IGNORE INTO workerTimeControlMail (workerFk, year, week) + VALUES (?, ?, ?);`; + await Self.rawSql(query, [previousWorkerFk, args.year, args.week], myOptions); + + previousWorkerFk = day.workerFk; + previousReceiver = day.receiver; + } + } + + if (tx) await tx.commit(); + + return true; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; + + function getStartDateOfWeekNumber(week, year) { + const simple = new Date(year, 0, 1 + (week - 1) * 7); + const dow = simple.getDay(); + const weekStart = simple; + if (dow <= 4) + weekStart.setDate(simple.getDate() - simple.getDay() + 1); + else + weekStart.setDate(simple.getDate() + 8 - simple.getDay()); + return weekStart; + } + + function getTime(timeString) { + const [hours, minutes, seconds] = timeString.split(':'); + return [parseInt(hours), parseInt(minutes), parseInt(seconds)]; + } +}; diff --git a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js new file mode 100644 index 0000000000..d0afd45b9b --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js @@ -0,0 +1,132 @@ +const models = require('vn-loopback/server/server').models; + +describe('workerTimeControl sendMail()', () => { + const workerId = 18; + const ctx = { + req: { + __: value => { + return value; + } + }, + args: {} + + }; + + beforeAll(function() { + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; + }); + + it('should fill time control of a worker without records in Journey and with rest', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + + try { + const options = {transaction: tx}; + + await models.WorkerTimeControl.sendMail(ctx, options); + + const workerTimeControl = await models.WorkerTimeControl.find({ + where: {userFk: workerId} + }, options); + + expect(workerTimeControl[0].timed.getHours()).toEqual(8); + expect(workerTimeControl[1].timed.getHours()).toEqual(9); + expect(`${workerTimeControl[2].timed.getHours()}:${workerTimeControl[2].timed.getMinutes()}`).toEqual('9:20'); + expect(workerTimeControl[3].timed.getHours()).toEqual(16); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should fill time control of a worker without records in Journey and without rest', async() => { + const workdayOf20Hours = 3; + const tx = await models.WorkerTimeControl.beginTransaction({}); + + try { + const options = {transaction: tx}; + query = `UPDATE business b + SET b.calendarTypeFk = ? + WHERE b.workerFk = ?; `; + await models.WorkerTimeControl.rawSql(query, [workdayOf20Hours, workerId], options); + + await models.WorkerTimeControl.sendMail(ctx, options); + + const workerTimeControl = await models.WorkerTimeControl.find({ + where: {userFk: workerId} + }, options); + + expect(workerTimeControl[0].timed.getHours()).toEqual(8); + expect(workerTimeControl[1].timed.getHours()).toEqual(12); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should fill time control of a worker with records in Journey and with rest', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + + try { + const options = {transaction: tx}; + query = `INSERT INTO postgresql.journey(journey_id, day_id, start, end, business_id) + VALUES + (1, 1, '09:00:00', '13:00:00', ?), + (2, 1, '14:00:00', '19:00:00', ?);`; + await models.WorkerTimeControl.rawSql(query, [workerId, workerId, workerId], options); + + await models.WorkerTimeControl.sendMail(ctx, options); + + const workerTimeControl = await models.WorkerTimeControl.find({ + where: {userFk: workerId} + }, options); + + expect(workerTimeControl[0].timed.getHours()).toEqual(9); + expect(workerTimeControl[2].timed.getHours()).toEqual(10); + expect(`${workerTimeControl[3].timed.getHours()}:${workerTimeControl[3].timed.getMinutes()}`).toEqual('10:20'); + expect(workerTimeControl[1].timed.getHours()).toEqual(13); + expect(workerTimeControl[4].timed.getHours()).toEqual(14); + expect(workerTimeControl[5].timed.getHours()).toEqual(19); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should fill time control of a worker with records in Journey and without rest', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + + try { + const options = {transaction: tx}; + query = `INSERT INTO postgresql.journey(journey_id, day_id, start, end, business_id) + VALUES + (1, 1, '12:30:00', '14:00:00', ?);`; + await models.WorkerTimeControl.rawSql(query, [workerId, workerId, workerId], options); + + await models.WorkerTimeControl.sendMail(ctx, options); + + const workerTimeControl = await models.WorkerTimeControl.find({ + where: {userFk: workerId} + }, options); + + expect(`${workerTimeControl[0].timed.getHours()}:${workerTimeControl[0].timed.getMinutes()}`).toEqual('12:30'); + expect(workerTimeControl[1].timed.getHours()).toEqual(14); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + afterAll(function() { + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; + }); +}); + diff --git a/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js b/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js new file mode 100644 index 0000000000..a8dc14bb1e --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js @@ -0,0 +1,87 @@ +const UserError = require('vn-loopback/util/user-error'); +module.exports = Self => { + Self.remoteMethodCtx('updateWorkerTimeControlMail', { + description: 'Updates the state of WorkerTimeControlMail', + accessType: 'WRITE', + accepts: [{ + arg: 'workerId', + type: 'number', + required: true + }, + { + arg: 'year', + type: 'number', + required: true + }, + { + arg: 'week', + type: 'number', + required: true + }, + { + arg: 'state', + type: 'string', + required: true + }, + { + arg: 'reason', + type: 'string' + }], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/updateWorkerTimeControlMail`, + verb: 'POST' + } + }); + + Self.updateWorkerTimeControlMail = async(ctx, options) => { + const models = Self.app.models; + const args = ctx.args; + const userId = ctx.req.accessToken.userId; + + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({ + where: { + workerFk: args.workerId, + year: args.year, + week: args.week + } + }, myOptions); + + if (!workerTimeControlMail) throw new UserError(`There aren't records for this week`); + + const oldState = workerTimeControlMail.state; + const oldReason = workerTimeControlMail.reason; + + if (oldState == args.state) throw new UserError('Already has this status'); + + await workerTimeControlMail.updateAttributes({ + state: args.state, + reason: args.reason || null + }, myOptions); + + const logRecord = { + originFk: args.workerId, + userFk: userId, + action: 'update', + changedModel: 'WorkerTimeControlMail', + oldInstance: { + state: oldState, + reason: oldReason + }, + newInstance: { + state: args.state, + reason: args.reason + } + }; + + return models.WorkerLog.create(logRecord, myOptions); + }; +}; diff --git a/modules/worker/back/model-config.json b/modules/worker/back/model-config.json index c155e331de..3f34165040 100644 --- a/modules/worker/back/model-config.json +++ b/modules/worker/back/model-config.json @@ -20,6 +20,12 @@ "EducationLevel": { "dataSource": "vn" }, + "Journey": { + "dataSource": "vn" + }, + "Time": { + "dataSource": "vn" + }, "WorkCenter": { "dataSource": "vn" }, @@ -59,11 +65,17 @@ "WorkerLog": { "dataSource": "vn" }, + "WorkerTimeControlConfig": { + "dataSource": "vn" + }, "WorkerTimeControlParams": { "dataSource": "vn" }, "WorkerTimeControlMail": { "dataSource": "vn" + }, + "WorkerDisableExcluded": { + "dataSource": "vn" } } diff --git a/modules/worker/back/models/journey.json b/modules/worker/back/models/journey.json new file mode 100644 index 0000000000..b7d5f28176 --- /dev/null +++ b/modules/worker/back/models/journey.json @@ -0,0 +1,27 @@ +{ + "name": "Journey", + "base": "VnModel", + "options": { + "mysql": { + "table": "postgresql.journey" + } + }, + "properties": { + "journey_id": { + "id": true, + "type": "number" + }, + "day_id": { + "type": "number" + }, + "start": { + "type": "date" + }, + "end": { + "type": "date" + }, + "business_id": { + "type": "number" + } + } +} diff --git a/modules/worker/back/models/time.json b/modules/worker/back/models/time.json new file mode 100644 index 0000000000..df92575405 --- /dev/null +++ b/modules/worker/back/models/time.json @@ -0,0 +1,21 @@ +{ + "name": "Time", + "base": "VnModel", + "options": { + "mysql": { + "table": "time" + } + }, + "properties": { + "dated": { + "id": true, + "type": "date" + }, + "year": { + "type": "number" + }, + "week": { + "type": "number" + } + } +} diff --git a/modules/worker/back/models/worker-time-control-config.json b/modules/worker/back/models/worker-time-control-config.json new file mode 100644 index 0000000000..4c12ce5d73 --- /dev/null +++ b/modules/worker/back/models/worker-time-control-config.json @@ -0,0 +1,18 @@ +{ + "name": "WorkerTimeControlConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "workerTimeControlConfig" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + }, + "timeToBreakTime": { + "type": "number" + } + } +} \ No newline at end of file diff --git a/modules/worker/back/models/worker-time-control-mail.js b/modules/worker/back/models/worker-time-control-mail.js deleted file mode 100644 index 36f3851b6d..0000000000 --- a/modules/worker/back/models/worker-time-control-mail.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = Self => { - require('../methods/worker-time-control-mail/checkInbox')(Self); -}; diff --git a/modules/worker/back/models/worker-time-control-mail.json b/modules/worker/back/models/worker-time-control-mail.json index daf3d5155e..78b99881dc 100644 --- a/modules/worker/back/models/worker-time-control-mail.json +++ b/modules/worker/back/models/worker-time-control-mail.json @@ -9,8 +9,7 @@ "properties": { "id": { "id": true, - "type": "number", - "required": true + "type": "number" }, "workerFk": { "type": "number" @@ -27,7 +26,7 @@ "updated": { "type": "date" }, - "emailResponse": { + "reason": { "type": "string" } }, diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index 45f4e2194e..9f802511aa 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -5,6 +5,8 @@ module.exports = Self => { require('../methods/worker-time-control/addTimeEntry')(Self); require('../methods/worker-time-control/deleteTimeEntry')(Self); require('../methods/worker-time-control/updateTimeEntry')(Self); + require('../methods/worker-time-control/sendMail')(Self); + require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/worker/back/models/worker-time-control.json b/modules/worker/back/models/worker-time-control.json index ab07802ca1..bc3e535016 100644 --- a/modules/worker/back/models/worker-time-control.json +++ b/modules/worker/back/models/worker-time-control.json @@ -22,6 +22,9 @@ }, "direction": { "type": "string" + }, + "isSendMail": { + "type": "boolean" } }, "relations": { diff --git a/modules/worker/back/models/workerDisableExcluded.json b/modules/worker/back/models/workerDisableExcluded.json new file mode 100644 index 0000000000..48083748d2 --- /dev/null +++ b/modules/worker/back/models/workerDisableExcluded.json @@ -0,0 +1,26 @@ +{ + "name": "WorkerDisableExcluded", + "base": "VnModel", + "options": { + "mysql": { + "table": "workerDisableExcluded" + } + }, + "properties": { + "workerFk": { + "id": true, + "type": "number" + }, + "dated": { + "type": "date" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index 01681ebb80..58ac3d9e6e 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -14,6 +14,20 @@
+ + + Click to exclude the user from getting disabled + + + Click to allow the user to be disabled + +
+
+ + +
+ \ No newline at end of file diff --git a/print/templates/email/payment-update/payment-update.js b/print/templates/email/payment-update/payment-update.js index a9d99d4ef6..c03cf76ca0 100755 --- a/print/templates/email/payment-update/payment-update.js +++ b/print/templates/email/payment-update/payment-update.js @@ -1,6 +1,5 @@ const Component = require(`vn-print/core/component`); -const emailHeader = new Component('email-header'); -const emailFooter = new Component('email-footer'); +const emailBody = new Component('email-body'); module.exports = { name: 'payment-update', @@ -21,12 +20,11 @@ module.exports = { } }, components: { - 'email-header': emailHeader.build(), - 'email-footer': emailFooter.build() + 'email-body': emailBody.build(), }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The client id' } diff --git a/print/templates/email/printer-setup/printer-setup.html b/print/templates/email/printer-setup/printer-setup.html index fff21eb90b..4320ae50dd 100644 --- a/print/templates/email/printer-setup/printer-setup.html +++ b/print/templates/email/printer-setup/printer-setup.html @@ -1,90 +1,49 @@ - - - - - - {{ $t('subject') }} - - - - - - - - -
- -
-
-
- -
-
- -
-
- -
-
-

{{ $t('title') }}

-

{{$t('description.dear')}},

-

{{$t('description.instructions')}}

-

-

-

+ +
+
+

{{ $t('title') }}

+

{{$t('description.dear')}},

+

{{$t('description.instructions')}}

+

+

+

-

{{$t('sections.QLabel.title')}}

-

{{$t('sections.QLabel.description')}}:

-
    -
  1. - -
  2. -
-
-
- -
-
-

{{$t('sections.help.title')}}

-

{{$t('sections.help.description')}}

-

-
-
- -
-
-
- {{$t('salesPersonName')}}: {{client.salesPersonName}} -
-
- {{$t('salesPersonPhone')}}: {{client.salesPersonPhone}} -
-
- {{$t('salesPersonEmail')}}: - {{client.salesPersonEmail}} -
-
-
- -
-
- - -
-
- -
-
- -
-
- -
-
-
-
- - \ No newline at end of file +

{{$t('sections.QLabel.title')}}

+

{{$t('sections.QLabel.description')}}:

+
    +
  1. + +
  2. +
+ + +
+
+

{{$t('sections.help.title')}}

+

{{$t('sections.help.description')}}

+

+
+
+
+
+
+ {{$t('salesPersonName')}}: {{client.salesPersonName}} +
+
+ {{$t('salesPersonPhone')}}: {{client.salesPersonPhone}} +
+
+ {{$t('salesPersonEmail')}}: + {{client.salesPersonEmail}} +
+
+
+
+
+ + +
+
+ \ No newline at end of file diff --git a/print/templates/email/printer-setup/printer-setup.js b/print/templates/email/printer-setup/printer-setup.js index a7d3c40bf9..8e892737a9 100755 --- a/print/templates/email/printer-setup/printer-setup.js +++ b/print/templates/email/printer-setup/printer-setup.js @@ -1,6 +1,5 @@ const Component = require(`vn-print/core/component`); -const emailHeader = new Component('email-header'); -const emailFooter = new Component('email-footer'); +const emailBody = new Component('email-body'); const attachment = new Component('attachment'); const attachments = require('./attachments.json'); @@ -18,13 +17,12 @@ module.exports = { } }, components: { - 'email-header': emailHeader.build(), - 'email-footer': emailFooter.build(), + 'email-body': emailBody.build(), 'attachment': attachment.build() }, props: { id: { - type: [Number, String], + type: Number, required: true } } diff --git a/print/templates/email/sepa-core/sepa-core.html b/print/templates/email/sepa-core/sepa-core.html index 88acdbf86e..d584605dd8 100644 --- a/print/templates/email/sepa-core/sepa-core.html +++ b/print/templates/email/sepa-core/sepa-core.html @@ -1,57 +1,21 @@ - - - - - - {{ $t('subject') }} - - - - - - - - -
- -
-
-
- -
-
- -
-
- -
-
-

{{ $t('title') }}

-

{{$t('description.dear')}},

-
-

{{$t('description.conclusion')}}

-
-
- -
-
- - -
-
- -
-
- -
-
- -
-
-
-
- - \ No newline at end of file + +
+
+

{{ $t('title') }}

+

{{$t('description.dear')}},

+
+

{{$t('description.conclusion')}}

+
+
+
+
+ + +
+
+
diff --git a/print/templates/email/sepa-core/sepa-core.js b/print/templates/email/sepa-core/sepa-core.js index 00cc527dc6..15a61e1b1b 100755 --- a/print/templates/email/sepa-core/sepa-core.js +++ b/print/templates/email/sepa-core/sepa-core.js @@ -1,7 +1,5 @@ const Component = require(`vn-print/core/component`); -const emailHeader = new Component('email-header'); -const emailFooter = new Component('email-footer'); -const attachment = new Component('attachment'); +const emailBody = new Component('email-body'); const attachments = require('./attachments.json'); module.exports = { @@ -10,17 +8,15 @@ module.exports = { return {attachments}; }, components: { - 'email-header': emailHeader.build(), - 'email-footer': emailFooter.build(), - 'attachment': attachment.build() + 'email-body': emailBody.build() }, props: { id: { - type: [Number, String], + type: Number, required: true }, companyId: { - type: [Number, String], + type: Number, required: true } } diff --git a/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.html b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.html index ff8c661ee7..79d8566320 100644 --- a/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.html +++ b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.html @@ -1,46 +1,9 @@ - - - - - - {{ $t('subject') }} - - - - - - - - -
- -
-
-
- -
-
- -
-
- -
-
-

{{ $t('title') }}

-

{{$t('dear')}},

-

-
-
- -
-
- -
-
- -
-
-
-
- - \ No newline at end of file + +
+
+

{{ $t('title') }}

+

{{$t('dear')}},

+

+
+
+
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 9cb9210ef7..3a673d2e4f 100755 --- a/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js +++ b/print/templates/email/supplier-campaign-metrics/supplier-campaign-metrics.js @@ -1,6 +1,5 @@ const Component = require(`vn-print/core/component`); -const emailHeader = new Component('email-header'); -const emailFooter = new Component('email-footer'); +const emailBody = new Component('email-body'); module.exports = { name: 'supplier-campaign-metrics', @@ -16,12 +15,11 @@ module.exports = { } }, components: { - 'email-header': emailHeader.build(), - 'email-footer': emailFooter.build() + 'email-body': emailBody.build() }, props: { id: { - type: [Number, String], + type: Number, required: true }, from: { diff --git a/print/templates/reports/balance-compensation/balance-compensation.html b/print/templates/reports/balance-compensation/balance-compensation.html new file mode 100644 index 0000000000..59af1bd111 --- /dev/null +++ b/print/templates/reports/balance-compensation/balance-compensation.html @@ -0,0 +1,31 @@ + +
+
+
+
+

{{$t('Place')}} {{currentDate()}}

+

{{$t('Compensation') | uppercase}}

+

{{$t('In one hand')}}:

+

+ {{company.name}} {{$t('CIF')}} {{company.nif}} {{$t('Home')}} {{company.street}}, + {{company.city}}. +

+

{{$t('In other hand')}}:

+

+ {{$t('Sr')}} {{client.name}} {{$t('NIF')}} {{client.fi}} {{$t('Home')}} {{client.street}}, + {{client.city}}. +

+

{{$t('Agree') | uppercase}}

+

+ {{$t('Date')}} {{client.payed | date('%d-%m-%Y')}} {{$t('Compensate')}} {{client.amountPaid}} € + {{$t('From client')}} {{client.name}} {{$t('Toclient')}} {{company.name}}. +

+

+ {{$t('Reception')}} administracion@verdnatura.es +

+
{{$t('Greetings')}}
+
+
+
+
+
diff --git a/print/templates/reports/balance-compensation/balance-compensation.js b/print/templates/reports/balance-compensation/balance-compensation.js new file mode 100644 index 0000000000..98a9cf577f --- /dev/null +++ b/print/templates/reports/balance-compensation/balance-compensation.js @@ -0,0 +1,34 @@ +const Component = require(`vn-print/core/component`); +const reportBody = new Component('report-body'); + +module.exports = { + name: 'balance-compensation', + async serverPrefetch() { + this.client = await this.fetchClient(this.id); + this.company = await this.fetchCompany(this.id); + }, + methods: { + fetchClient(id) { + return this.findOneFromDef('client', [id]); + }, + fetchCompany(id) { + return this.findOneFromDef('company', [id]); + }, + + currentDate() { + const current = new Date(); + const date = `${current.getDate()}/${current.getMonth() + 1}/${current.getFullYear()}`; + return date; + } + }, + components: { + 'report-body': reportBody.build() + }, + props: { + id: { + type: Number, + required: true, + description: 'The receipt id' + } + } +}; diff --git a/print/templates/reports/balance-compensation/css/import.js b/print/templates/reports/balance-compensation/css/import.js new file mode 100644 index 0000000000..37a98dfddb --- /dev/null +++ b/print/templates/reports/balance-compensation/css/import.js @@ -0,0 +1,12 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/report.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/templates/reports/balance-compensation/locale/es.yml b/print/templates/reports/balance-compensation/locale/es.yml new file mode 100644 index 0000000000..546e55f060 --- /dev/null +++ b/print/templates/reports/balance-compensation/locale/es.yml @@ -0,0 +1,16 @@ +reportName: compensacion-saldo +Place: Algemesí, a +Compensation: Compensación de saldos deudores y acreedores +In one hand: De una parte +CIF: con CIF +NIF: con NIF +Home: y domicilio sito en +In other hand: De la otra +Sr: Don/Doña +Agree: Acuerdan +Date: En fecha de +Compensate: se ha compensado el saldo de +From client: del cliente/proveedor +To client: con el cliente/proveedor +Reception: Por favor, rogamos confirmen la recepción de esta compensación al email +Greetings: Saludos cordiales, \ No newline at end of file diff --git a/print/templates/reports/balance-compensation/sql/client.sql b/print/templates/reports/balance-compensation/sql/client.sql new file mode 100644 index 0000000000..92e6f6cabe --- /dev/null +++ b/print/templates/reports/balance-compensation/sql/client.sql @@ -0,0 +1,12 @@ +SELECT + c.name, + c.socialName, + c.street, + c.fi, + c.city, + r.amountPaid, + r.payed + FROM client c + JOIN receipt r ON r.clientFk = c.id + JOIN supplier s ON c.fi = s.nif + WHERE r.id = ? \ No newline at end of file diff --git a/print/templates/reports/balance-compensation/sql/company.sql b/print/templates/reports/balance-compensation/sql/company.sql new file mode 100644 index 0000000000..e61228a105 --- /dev/null +++ b/print/templates/reports/balance-compensation/sql/company.sql @@ -0,0 +1,8 @@ +SELECT + s.name, + s.nif, + s.street, + s.city + FROM supplier s + JOIN receipt r ON r.companyFk = s.id + WHERE r.id = ?; \ No newline at end of file diff --git a/print/templates/reports/campaign-metrics/campaign-metrics.html b/print/templates/reports/campaign-metrics/campaign-metrics.html index 725c03e4de..e86d4cd3bf 100644 --- a/print/templates/reports/campaign-metrics/campaign-metrics.html +++ b/print/templates/reports/campaign-metrics/campaign-metrics.html @@ -1,95 +1,72 @@ - - - - - - - - - -
- - - -
-
-
-
-

{{$t('title')}}

-
- - - - - - - - - - - - - - - -
{{$t('Client')}}{{client.id}}
{{$t('From')}}{{from | date('%d-%m-%Y')}}
{{$t('To')}}{{to | date('%d-%m-%Y')}}
-
-
-
-
-
{{$t('clientData')}}
-
-

{{client.socialName}}

-
- {{client.street}} -
-
- {{client.postcode}}, {{client.city}} ({{client.province}}) -
-
- {{client.country}} -
-
-
-
-
- - - - - - - - - - - - - - - - - - -
{{$t('Code')}}{{$t('Quantity')}}{{$t('Concept')}}
{{sale.itemFk | zerofill('000000')}}{{Math.trunc(sale.subtotal)}}{{sale.concept}}
- - {{sale.tag5}} {{sale.value5}} - - - {{sale.tag6}} {{sale.value6}} - - - {{sale.tag7}} {{sale.value7}} - -
-
+ +
+
+
+
+

{{$t('title')}}

+
+ + + + + + + + + + + + + + + +
{{$t('Client')}}{{client.id}}
{{$t('From')}}{{from | date('%d-%m-%Y')}}
{{$t('To')}}{{to | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('clientData')}}
+
+

{{client.socialName}}

+
{{client.street}}
+
{{client.postcode}}, {{client.city}} ({{client.province}})
+
{{client.country}}
- - - -
- - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + +
{{$t('Code')}}{{$t('Quantity')}}{{$t('Concept')}}
{{sale.itemFk | zerofill('000000')}}{{Math.trunc(sale.subtotal)}}{{sale.concept}}
+ {{sale.tag5}} {{sale.value5}} + {{sale.tag6}} {{sale.value6}} + {{sale.tag7}} {{sale.value7}} +
+ + + + diff --git a/print/templates/reports/campaign-metrics/campaign-metrics.js b/print/templates/reports/campaign-metrics/campaign-metrics.js index b60a2b7eb6..14a4b6a9b6 100755 --- a/print/templates/reports/campaign-metrics/campaign-metrics.js +++ b/print/templates/reports/campaign-metrics/campaign-metrics.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -20,12 +20,12 @@ module.exports = { }, }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The client id' }, diff --git a/print/templates/reports/claim-pickup-order/claim-pickup-order.html b/print/templates/reports/claim-pickup-order/claim-pickup-order.html index 6764344b5f..b14f5410c4 100644 --- a/print/templates/reports/claim-pickup-order/claim-pickup-order.html +++ b/print/templates/reports/claim-pickup-order/claim-pickup-order.html @@ -1,97 +1,77 @@ - - - - - - - - - -
- - - -
-
-
-
-

{{$t('title')}}

- - - - - - - - - - - - - - - - - - - -
{{$t('claimId')}}{{id}}
{{$t('clientId')}}{{client.id}}
{{$t('phone')}}{{client.phone}}
{{$t('date')}}{{dated}}
-
-
-
-
{{$t('clientData')}}
-
-

{{client.nickname}}

-
- {{client.street}} -
-
- {{client.postalCode}}, {{client.city}} ({{client.province}}) -
-
- {{client.country}} -
-
-
-
-
- - - - - - - - - - - - - - - - - - -
{{$t('reference')}}{{$t('quantity')}}{{$t('claims')}}{{$t('concept')}}
{{sale.id}}{{sale.quantity}}{{sale.claimQuantity}}{{sale.concept}}
- -
-
{{$t('clientSignature')}}
-
-

{{client.name}}

-
-
- -

-

{{claimConfig.pickupContact}}

-
+ +
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + + + + + + + + + +
{{$t('claimId')}}{{id}}
{{$t('clientId')}}{{client.id}}
{{$t('phone')}}{{client.phone}}
{{$t('date')}}{{dated}}
+
+
+
+
{{$t('clientData')}}
+
+

{{client.nickname}}

+
{{client.street}}
+
{{client.postalCode}}, {{client.city}} ({{client.province}})
+
{{client.country}}
- - - -
- - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + +
{{$t('reference')}}{{$t('quantity')}}{{$t('claims')}}{{$t('concept')}}
{{sale.id}}{{sale.quantity}}{{sale.claimQuantity}}{{sale.concept}}
+ +
+
{{$t('clientSignature')}}
+
+

{{client.name}}

+
+
+ + + + 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 f7d21a2d39..5c7002e476 100755 --- a/print/templates/reports/claim-pickup-order/claim-pickup-order.js +++ b/print/templates/reports/claim-pickup-order/claim-pickup-order.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -7,7 +7,6 @@ module.exports = { async serverPrefetch() { this.client = await this.fetchClient(this.id); this.sales = await this.fetchSales(this.id); - this.claimConfig = await this.fetchClaimConfig(); if (!this.client) throw new Error('Something went wrong'); @@ -25,18 +24,15 @@ module.exports = { }, fetchSales(id) { return this.rawSqlFromDef('sales', [id]); - }, - fetchClaimConfig() { - return this.findOneFromDef('claimConfig'); - }, + } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The claim id' } diff --git a/print/templates/reports/claim-pickup-order/locale/es.yml b/print/templates/reports/claim-pickup-order/locale/es.yml index 388c1f1a69..5ee5ecda76 100644 --- a/print/templates/reports/claim-pickup-order/locale/es.yml +++ b/print/templates/reports/claim-pickup-order/locale/es.yml @@ -11,7 +11,3 @@ concept: Concepto clientSignature: Firma del cliente claim: Reclamación {0} phone: Teléfono -sections: - agency: - description: 'Para agilizar su recogida, por favor, póngase en contacto con la oficina - de Logista Parcel.' diff --git a/print/templates/reports/claim-pickup-order/sql/claimConfig.sql b/print/templates/reports/claim-pickup-order/sql/claimConfig.sql deleted file mode 100644 index 9d744ca6d6..0000000000 --- a/print/templates/reports/claim-pickup-order/sql/claimConfig.sql +++ /dev/null @@ -1,2 +0,0 @@ -SELECT pickupContact - FROM claimConfig; \ No newline at end of file diff --git a/print/templates/reports/client-debt-statement/client-debt-statement.html b/print/templates/reports/client-debt-statement/client-debt-statement.html index 88bf15bdbd..962af021db 100644 --- a/print/templates/reports/client-debt-statement/client-debt-statement.html +++ b/print/templates/reports/client-debt-statement/client-debt-statement.html @@ -1,95 +1,78 @@ - - - - - - - - - -
- - - -
-
-
-
-
-

{{$t('title')}}

- - - - - - - - - - - -
{{$t('clientId')}}{{client.id}}
{{$t('date')}}{{dated}}
-
-
-
-
-
{{$t('clientData')}}
-
-

{{client.socialName}}

-
- {{client.street}} -
-
- {{client.postcode}}, {{client.city}} ({{client.province}}) -
-
- {{client.country}} -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('date')}}{{$t('concept')}}{{$t('invoiced')}}{{$t('payed')}}{{$t('balance')}}
{{sale.issued | date('%d-%m-%Y')}}{{sale.ref}}{{sale.debtOut}}{{sale.debtIn}}{{getBalance(sale)}}
- Total - {{getTotalDebtOut() | currency('EUR', $i18n.locale)}} - {{getTotalDebtIn() | currency('EUR', $i18n.locale)}}{{totalBalance | currency('EUR', $i18n.locale)}}
-
+ +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t('date')}}{{dated}}
+
+
+
+
+
{{$t('clientData')}}
+
+

{{client.socialName}}

+
{{client.street}}
+
{{client.postcode}}, {{client.city}} ({{client.province}})
+
{{client.country}}
- - - -
- - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('date')}}{{$t('concept')}}{{$t('invoiced')}}{{$t('payed')}}{{$t('balance')}}
{{sale.issued | date('%d-%m-%Y')}}{{sale.ref}}{{sale.debtOut}}{{sale.debtIn}}{{getBalance(sale)}}
+ Total + {{getTotalDebtOut() | currency('EUR', $i18n.locale)}} + {{getTotalDebtIn() | currency('EUR', $i18n.locale)}}{{totalBalance | currency('EUR', $i18n.locale)}}
+ + + + 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 80c83b4947..45b97518f6 100755 --- a/print/templates/reports/client-debt-statement/client-debt-statement.js +++ b/print/templates/reports/client-debt-statement/client-debt-statement.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -64,12 +64,12 @@ module.exports = { }, }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The client id' }, diff --git a/print/templates/reports/collection-label/assets/css/import.js b/print/templates/reports/collection-label/assets/css/import.js new file mode 100644 index 0000000000..37a98dfddb --- /dev/null +++ b/print/templates/reports/collection-label/assets/css/import.js @@ -0,0 +1,12 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/report.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/templates/reports/collection-label/assets/css/style.css b/print/templates/reports/collection-label/assets/css/style.css new file mode 100644 index 0000000000..fe1975445d --- /dev/null +++ b/print/templates/reports/collection-label/assets/css/style.css @@ -0,0 +1,37 @@ +html { + font-family: "Roboto"; + margin-top: -7px; +} +* { + box-sizing: border-box; + text-align: center; + font-size: 26px; +} +#vertical { + writing-mode: vertical-rl; + height: 226px; + margin-left: -13px; +} +.outline { + border: 1px solid black; + padding: 5px; +} +#nickname { + font-size: 22px; +} +#agencyDescripton { + font-size: 32px; + font-weight: bold; +} +#bold { + font-weight: bold; +} +#barcode{ + width: 390px; +} +#shipped { + font-weight: bold; +} +#ticketFk, #vertical { + font-size: 34px; +} \ No newline at end of file diff --git a/print/templates/reports/collection-label/collection-label.html b/print/templates/reports/collection-label/collection-label.html new file mode 100644 index 0000000000..35c0786b6c --- /dev/null +++ b/print/templates/reports/collection-label/collection-label.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{labelData.levelV}}{{labelData.ticketFk}} ⬸ {{labelData.clientFk}}{{labelData.shipped}}
{{labelData.workerCode}}
{{labelData.labelCount}}
{{labelData.size}}
{{labelData.agencyDescription}}
{{labelData.lineCount}}
{{labelData.nickName}}{{labelData.agencyHour}}
+ +
diff --git a/print/templates/reports/collection-label/collection-label.js b/print/templates/reports/collection-label/collection-label.js new file mode 100644 index 0000000000..7bc25ad790 --- /dev/null +++ b/print/templates/reports/collection-label/collection-label.js @@ -0,0 +1,51 @@ +const Component = require(`vn-print/core/component`); +const reportBody = new Component('report-body'); +const jsBarcode = require('jsbarcode'); +const {DOMImplementation, XMLSerializer} = require('xmldom'); +const UserError = require('vn-loopback/util/user-error'); + +module.exports = { + name: 'collection-label', + props: { + id: { + type: Number, + required: true, + description: 'The ticket or collection id' + } + }, + async serverPrefetch() { + let ticketIds; + const res = await this.rawSqlFromDef('tickets', [this.id]); + + if (res.length) { + ticketIds = []; + for (const row of res) + ticketIds.push(row.ticketFk); + } else + ticketIds = [this.id]; + + this.labelsData = await this.rawSqlFromDef('labelsData', [ticketIds]); + + if (!this.labelsData.length) + throw new UserError('Empty data source'); + }, + methods: { + getBarcode(id) { + const xmlSerializer = new XMLSerializer(); + const document = new DOMImplementation().createDocument('http://www.w3.org/1999/xhtml', 'html', null); + const svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + + jsBarcode(svgNode, id, { + xmlDocument: document, + format: 'code128', + displayValue: false, + width: 3.8, + height: 110, + }); + return xmlSerializer.serializeToString(svgNode); + }, + }, + components: { + 'report-body': reportBody.build() + }, +}; diff --git a/print/templates/reports/collection-label/locale/es.yml b/print/templates/reports/collection-label/locale/es.yml new file mode 100644 index 0000000000..a54d3bad8b --- /dev/null +++ b/print/templates/reports/collection-label/locale/es.yml @@ -0,0 +1 @@ +reportName: labelCollection \ No newline at end of file diff --git a/print/templates/reports/collection-label/options.json b/print/templates/reports/collection-label/options.json new file mode 100644 index 0000000000..175b3c1db8 --- /dev/null +++ b/print/templates/reports/collection-label/options.json @@ -0,0 +1,11 @@ +{ + "width": "10.4cm", + "height": "4.8cm", + "margin": { + "top": "0cm", + "right": "0.5cm", + "bottom": "0cm", + "left": "0cm" + }, + "printBackground": true +} \ No newline at end of file diff --git a/print/templates/reports/collection-label/sql/labelsData.sql b/print/templates/reports/collection-label/sql/labelsData.sql new file mode 100644 index 0000000000..6f5b47a54d --- /dev/null +++ b/print/templates/reports/collection-label/sql/labelsData.sql @@ -0,0 +1,35 @@ +SELECT c.itemPackingTypeFk, + CONCAT(tc.collectionFk, ' ', LEFT(cc.code, 4)) color, + CONCAT(tc.collectionFk, ' ', SUBSTRING('ABCDEFGH',tc.wagon, 1), '-', tc.`level`) levelV, + tc.ticketFk, + LEFT(COALESCE(et.description, zo.name, am.name),12) agencyDescription, + am.name, + t.clientFk, + CONCAT(CAST(SUM(sv.volume) AS DECIMAL(5, 2)), 'm³') m3 , + CAST(IF(ic.code = 'plant', CONCAT(MAX(i.`size`),' cm'), COUNT(*)) AS CHAR) size, + w.code workerCode, + tt.labelCount, + IF(HOUR(t.shipped), TIME_FORMAT(t.shipped, '%H:%i'), TIME_FORMAT(zo.`hour`, '%H:%i')) agencyHour, + DATE_FORMAT(t.shipped, '%d/%m/%y') shipped, + COUNT(*) lineCount, + t.nickName + FROM vn.ticket t + JOIN vn.ticketCollection tc ON tc.ticketFk = t.id + JOIN vn.collection c ON c.id = tc.collectionFk + LEFT JOIN vn.collectionColors cc ON cc.shelve = tc.`level` + AND cc.wagon = tc.wagon + AND cc.trainFk = c.trainFk + JOIN vn.sale s ON s.ticketFk = t.id + LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.itemType it ON it.id = i.typeFk + JOIN vn.itemCategory ic ON ic.id = it.categoryFk + JOIN vn.worker w ON w.id = c.workerFk + JOIN vn.agencyMode am ON am.id = t.agencyModeFk + LEFT JOIN vn.ticketTrolley tt ON tt.ticket = t.id + LEFT JOIN vn.`zone` zo ON t.zoneFk = zo.id + LEFT JOIN vn.routesMonitor rm ON rm.routeFk = t.routeFk + LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk + WHERE tc.ticketFk IN (?) + GROUP BY t.id + ORDER BY cc.`code`; \ No newline at end of file diff --git a/print/templates/reports/collection-label/sql/tickets.sql b/print/templates/reports/collection-label/sql/tickets.sql new file mode 100644 index 0000000000..05deeba83a --- /dev/null +++ b/print/templates/reports/collection-label/sql/tickets.sql @@ -0,0 +1,3 @@ +SELECT ticketFk + FROM ticketCollection + WHERE collectionFk = ? \ No newline at end of file diff --git a/print/templates/reports/credit-request/credit-request.html b/print/templates/reports/credit-request/credit-request.html index 975115eef8..d5e3368094 100644 --- a/print/templates/reports/credit-request/credit-request.html +++ b/print/templates/reports/credit-request/credit-request.html @@ -1,190 +1,164 @@ - - - - - - - + +
- - - - -
-
-

{{$t('fields.title')}}

-
-
- - - - - - - - - -
{{$t('fields.date')}}: -
- -
-
- - -
-
-
-
-

{{$t('fields.companyInfo')}}

+ +
+
+

{{$t('fields.title')}}

+
+
+ + + + + + + +
{{$t('fields.date')}}: +
+ +
+
+
+
+
+
+

{{$t('fields.companyInfo')}}

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('fields.companyName')}}: +
+
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('fields.companyName')}}: -
- -
-
{{$t('fields.businessType')}}: -
- -
-
{{$t('fields.antiquity')}}: -
- -
-
{{$t('fields.surface')}}: -
- -
-
{{$t('fields.numberOfEmployees')}}: -
- -
-
{{$t('fields.owner')}}: -
- -
-
{{$t('fields.phone')}}: -
- -
-
{{$t('fields.payer')}}: -
- -
-
{{$t('fields.phone')}}: -
- -
-
+
{{$t('fields.businessType')}}: +
+
+
{{$t('fields.antiquity')}}: +
+ +
+
{{$t('fields.surface')}}: +
+ +
+
{{$t('fields.numberOfEmployees')}}: +
+ +
+
{{$t('fields.owner')}}: +
+ +
+
{{$t('fields.phone')}}: +
+ +
+
{{$t('fields.payer')}}: +
+ +
+
{{$t('fields.phone')}}: +
+ +
+
+
+
+
+
+
+
+
+
+

{{$t('fields.economicInfo')}}

+
+
+ + + + + + + + + + + + + + + +
{{$t('fields.previousSalesVolume')}}: +
+ +
+
{{$t('fields.forecastedSalesVolume')}}: +
+ +
+
{{$t('fields.forecastedPurchases')}}: +
+ +
+
+
+
+
+
+
+
+
+ + + + + + + - -
{{$t('fields.personFilling')}}: +
+
- - - - -
-
-
-
-

{{$t('fields.economicInfo')}}

-
-
- - - - - - - - - - - - - - - -
{{$t('fields.previousSalesVolume')}}: -
- -
-
{{$t('fields.forecastedSalesVolume')}}: -
- -
-
{{$t('fields.forecastedPurchases')}}: -
- -
-
-
+
{{$t('fields.phone')}}: +
+
- - - - -
-
-
- - - - - - - - - -
{{$t('fields.personFilling')}}: -
- -
-
{{$t('fields.phone')}}: -
- -
-
-
-
-
- - - - -
- - \ No newline at end of file + +
+ + + + + diff --git a/print/templates/reports/credit-request/credit-request.js b/print/templates/reports/credit-request/credit-request.js index 7de1060caf..c1aa74e1bb 100755 --- a/print/templates/reports/credit-request/credit-request.js +++ b/print/templates/reports/credit-request/credit-request.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body') const reportFooter = new Component('report-footer'); const rptCreditRequest = { @@ -12,7 +12,7 @@ const rptCreditRequest = { } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() } }; diff --git a/print/templates/reports/delivery-note/assets/css/style.css b/print/templates/reports/delivery-note/assets/css/style.css index f99c385fab..8405ae78d9 100644 --- a/print/templates/reports/delivery-note/assets/css/style.css +++ b/print/templates/reports/delivery-note/assets/css/style.css @@ -37,4 +37,9 @@ h2 { .phytosanitary-info { margin-top: 10px +} + +.observations{ + text-align: justify; + text-justify: inter-word; } \ No newline at end of file diff --git a/print/templates/reports/delivery-note/delivery-note.html b/print/templates/reports/delivery-note/delivery-note.html index d166f3307f..06653c9492 100644 --- a/print/templates/reports/delivery-note/delivery-note.html +++ b/print/templates/reports/delivery-note/delivery-note.html @@ -1,293 +1,257 @@ - - - - - - - - - -
- - - - -
-
-
-
-
-

{{$t(deliverNoteType)}}

- - - - - - - - - - - - - - - - - - - -
{{$t('clientId')}}{{client.id}}
{{$t(deliverNoteType)}}{{ticket.id}}
{{$t('date')}}{{ticket.shipped | date('%d-%m-%Y')}}
{{$t('packages')}}{{ticket.packages}}
-
-
-
-
-
{{$t('deliveryAddress')}}
-
-

{{address.nickname}}

-
- {{address.street}} -
-
- {{address.postalCode}}, {{address.city}} ({{address.province}}) -
-
-
- -
-
{{$t('fiscalData')}}
-
-
- {{client.socialName}} -
-
- {{client.street}} -
-
- {{client.fi}} -
-
-
-
-
- - -

{{$t('saleLines')}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}}{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', $i18n.locale)}}
- - {{sale.tag5}} {{sale.value5}} - - - {{sale.tag6}} {{sale.value6}} - - - {{sale.tag7}} {{sale.value7}} - -
- {{$t('subtotal')}} - {{getSubTotal() | currency('EUR', $i18n.locale)}}
- - -
- -
-

{{$t('services.title')}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('services.theader.quantity')}}{{$t('services.theader.concept')}}{{$t('services.theader.price')}}{{$t('services.theader.vat')}}{{$t('services.theader.amount')}}
{{service.quantity}}{{service.description}}{{service.price | currency('EUR', $i18n.locale)}}{{service.taxDescription}}{{service.price | currency('EUR', $i18n.locale)}}
- {{$t('services.tfoot.subtotal')}} - {{serviceTotal | currency('EUR', $i18n.locale)}}
- * {{ $t('services.warning') }} -
- -
-
- -
-

{{$t('packagings.title')}}

- - - - - - - - - - - - - - - -
{{$t('packagings.theader.reference')}}{{$t('packagings.theader.quantity')}}{{$t('packagings.theader.concept')}}
{{packaging.itemFk | zerofill('000000')}}{{packaging.quantity}}{{packaging.name}}
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('taxes.title')}}
{{$t('taxes.theader.type')}}{{$t('taxes.theader.taxBase')}}{{$t('taxes.theader.tax')}}{{$t('taxes.theader.fee')}}
{{tax.name}} - {{tax.Base | currency('EUR', $i18n.locale)}} - {{tax.vatPercent | percentage}}{{tax.tax | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} - {{getTotalBase() | currency('EUR', $i18n.locale)}} - {{getTotalTax()| currency('EUR', $i18n.locale)}}
{{$t('total')}}{{getTotal() | currency('EUR', $i18n.locale)}}
-
- + + +
+
+
+
+
+

{{$t(deliverNoteType)}}

+ + + + + + + + + + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t(deliverNoteType)}}{{ticket.id}}
{{$t('date')}}{{ticket.shipped | date('%d-%m-%Y')}}
{{$t('packages')}}{{ticket.packages}}
+
+
+
+
+
{{$t('deliveryAddress')}}
+
+

{{address.nickname}}

+
{{address.street}}
+
{{address.postalCode}}, {{address.city}} ({{address.province}})
+
+
- -
-
-
-
-
-
- -
-
- {{$t('plantPassport')}}
-
-
-
-
-
- A - {{getBotanical()}} -
-
- B - ES17462130 -
-
- C - {{ticket.id}} -
-
- D - ES -
-
-
-
-
- -
+
+
{{$t('fiscalData')}}
+
+
{{client.socialName}}
+
{{client.street}}
+
{{client.fi}}
+
+
+
+
+ +

{{$t('saleLines')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}} + {{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', $i18n.locale)}} +
+ {{sale.tag5}} {{sale.value5}} + {{sale.tag6}} {{sale.value6}} + {{sale.tag7}} {{sale.value7}} +
+ {{$t('subtotal')}} + {{getSubTotal() | currency('EUR', $i18n.locale)}}
+ +
+
+

{{$t('services.title')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('services.theader.quantity')}}{{$t('services.theader.concept')}}{{$t('services.theader.price')}}{{$t('services.theader.vat')}}{{$t('services.theader.amount')}}
{{service.quantity}}{{service.description}}{{service.price | currency('EUR', $i18n.locale)}}{{service.taxDescription}}{{service.price | currency('EUR', $i18n.locale)}}
+ {{$t('services.tfoot.subtotal')}} + {{serviceTotal | currency('EUR', $i18n.locale)}}
+ * {{ $t('services.warning') }} +
+
+
+
+

{{$t('packagings.title')}}

+ + + + + + + + + + + + + + + +
{{$t('packagings.theader.reference')}}{{$t('packagings.theader.quantity')}}{{$t('packagings.theader.concept')}}
{{packaging.itemFk | zerofill('000000')}}{{packaging.quantity}}{{packaging.name}}
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('taxes.title')}}
{{$t('taxes.theader.type')}}{{$t('taxes.theader.taxBase')}}{{$t('taxes.theader.tax')}}{{$t('taxes.theader.fee')}}
{{tax.name}}{{tax.Base | currency('EUR', $i18n.locale)}}{{tax.vatPercent | percentage}}{{tax.tax | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}}{{getTotalBase() | currency('EUR', $i18n.locale)}}{{getTotalTax()| currency('EUR', $i18n.locale)}}
{{$t('total')}}{{getTotal() | currency('EUR', $i18n.locale)}}
+
+ +
+
+
+
- -
-
-
{{$t('digitalSignature')}}
-
- -
{{signature.created | date('%d-%m-%Y')}}
-
-
+
+
- +
{{$t('plantPassport')}}
+
+
+
+
+ A + {{getBotanical()}} +
+
+ B + ES17462130 +
+
+ C + {{ticket.id}} +
+
+ D + ES
- - - -
- - \ No newline at end of file + + + +
+
+
+
{{$t('digitalSignature')}}
+
+ +
{{signature.created | date('%d-%m-%Y')}}
+
+
+
+
+
+
+

{{$t('observations')}}

+

{{ticket.description}}

+
+
+ + + + diff --git a/print/templates/reports/delivery-note/delivery-note.js b/print/templates/reports/delivery-note/delivery-note.js index 1037e51296..1a1a57c590 100755 --- a/print/templates/reports/delivery-note/delivery-note.js +++ b/print/templates/reports/delivery-note/delivery-note.js @@ -1,5 +1,6 @@ const config = require(`vn-print/core/config`); const Component = require(`vn-print/core/component`); +const reportBody = new Component('report-body'); const reportHeader = new Component('report-header'); const reportFooter = new Component('report-footer'); const md5 = require('md5'); @@ -54,6 +55,9 @@ module.exports = { footerType() { const translatedType = this.$t(this.deliverNoteType); return `${translatedType} ${this.id}`; + }, + hasObservations() { + return this.ticket.description !== null; } }, methods: { @@ -122,12 +126,13 @@ module.exports = { } }, components: { + 'report-body': reportBody.build(), 'report-header': reportHeader.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The ticket id' }, diff --git a/print/templates/reports/delivery-note/locale/en.yml b/print/templates/reports/delivery-note/locale/en.yml index c74b875204..5902da8b39 100644 --- a/print/templates/reports/delivery-note/locale/en.yml +++ b/print/templates/reports/delivery-note/locale/en.yml @@ -46,4 +46,5 @@ taxes: fee: Fee tfoot: subtotal: Subtotal - total: Total \ No newline at end of file + total: Total +observations: Observations \ No newline at end of file diff --git a/print/templates/reports/delivery-note/locale/es.yml b/print/templates/reports/delivery-note/locale/es.yml index 5c5a6af4d3..d87198f45a 100644 --- a/print/templates/reports/delivery-note/locale/es.yml +++ b/print/templates/reports/delivery-note/locale/es.yml @@ -47,4 +47,5 @@ taxes: fee: Cuota tfoot: subtotal: Subtotal - total: Total \ No newline at end of file + total: Total +observations: Observaciones \ No newline at end of file diff --git a/print/templates/reports/delivery-note/locale/fr.yml b/print/templates/reports/delivery-note/locale/fr.yml index df7ca053b7..603623a475 100644 --- a/print/templates/reports/delivery-note/locale/fr.yml +++ b/print/templates/reports/delivery-note/locale/fr.yml @@ -47,4 +47,5 @@ taxes: fee: Quote tfoot: subtotal: Total partiel - total: Total \ No newline at end of file + total: Total +observations: Observations \ No newline at end of file diff --git a/print/templates/reports/delivery-note/locale/pt.yml b/print/templates/reports/delivery-note/locale/pt.yml index 1f418b31f5..fb49d230b4 100644 --- a/print/templates/reports/delivery-note/locale/pt.yml +++ b/print/templates/reports/delivery-note/locale/pt.yml @@ -47,4 +47,5 @@ taxes: fee: Compartilhado tfoot: subtotal: Subtotal - total: Total \ No newline at end of file + total: Total +observations: Observações \ No newline at end of file diff --git a/print/templates/reports/delivery-note/sql/ticket.sql b/print/templates/reports/delivery-note/sql/ticket.sql index f78c725444..9eac2d322b 100644 --- a/print/templates/reports/delivery-note/sql/ticket.sql +++ b/print/templates/reports/delivery-note/sql/ticket.sql @@ -2,7 +2,11 @@ SELECT t.id, t.shipped, c.code companyCode, - t.packages + t.packages, + tto.description FROM ticket t JOIN company c ON c.id = t.companyFk + LEFT JOIN ticketObservation tto + ON tto.ticketFk = t.id + AND tto.observationTypeFk = (SELECT id FROM observationType WHERE code = 'deliveryNote') WHERE t.id = ? \ No newline at end of file diff --git a/print/templates/reports/driver-route/driver-route.html b/print/templates/reports/driver-route/driver-route.html index 96fb6e957f..48489de6f8 100644 --- a/print/templates/reports/driver-route/driver-route.html +++ b/print/templates/reports/driver-route/driver-route.html @@ -1,161 +1,141 @@ - - - - - - - - - -
- - - -
-
-

{{$t('route')}} {{route.id}}

-
-
{{$t('information')}}
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('route')}}{{route.id}}{{$t('driver')}}{{route.userNickName}}
{{$t('date')}}{{route.created | date('%d-%m-%Y')}}{{$t('vehicle')}}{{route.vehicleTradeMark}} {{route.vehicleModel}}
{{$t('time')}}{{route.time | date('%H:%M')}}{{route.plateNumber}}
{{$t('volume')}}{{route.m3}}{{$t('agency')}}{{route.agencyName}}
-
- - - - - - - - - - - - - - - -
-

Hora inicio

-
-

Hora fin

-
-

Km inicio

-
-

Km fin

-
-
- -
-
-
- -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - -
{{$t('order')}}{{$t('ticket')}}{{$t('client')}}{{$t('address')}}{{$t('packages')}}{{$t('packagingType')}}
{{ticket.priority}}{{ticket.id}}{{ticket.clientFk}} {{ticket.addressName}} - {{ticket.addressFk.toString().substr(0, - ticket.addressFk.toString().length - 3)}} - - {{ticket.addressFk.toString().substr(-3, 3)}} - - {{ticket.packages}}{{ticket.itemPackingTypes}}
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('street')}}{{ticket.street}}{{$t('postcode')}}{{ticket.postalCode}}
{{$t('city')}}{{ticket.city}}{{$t('agency')}}{{ticket.ticketAgency}}
{{$t('mobile')}}{{ticket.mobile}}{{$t('phone')}}{{ticket.phone}}
{{$t('warehouse')}}{{ticket.warehouseName}}{{$t('salesPerson')}}{{ticket.salesPersonName}}
-
-

{{ticket.description}}

-
-
-
+ +
+
+

{{$t('route')}} {{route.id}}

+
+
{{$t('information')}}
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('route')}}{{route.id}}{{$t('driver')}}{{route.userNickName}}
{{$t('date')}}{{route.created | date('%d-%m-%Y')}}{{$t('vehicle')}}{{route.vehicleTradeMark}} {{route.vehicleModel}}
{{$t('time')}}{{route.time | date('%H:%M')}}{{route.plateNumber}}
{{$t('volume')}}{{route.m3}}{{$t('agency')}}{{route.agencyName}}
+
+ + + + + + + + + + + + + + + +
+

Hora inicio

+
+

Hora fin

+
+

Km inicio

+
+

Km fin

+
+
+ +
+
+
+ +
+
- - - -
- - \ No newline at end of file + + +
+
+ + + + + + + + + + + + + + + + + + + + + +
{{$t('order')}}{{$t('ticket')}}{{$t('client')}}{{$t('address')}}{{$t('packages')}}{{$t('packagingType')}}
{{ticket.priority}}{{ticket.id}}{{ticket.clientFk}} {{ticket.addressName}} + {{ticket.addressFk.toString().substr(0, ticket.addressFk.toString().length - 3)}} + {{ticket.addressFk.toString().substr(-3, 3)}} + {{ticket.packages}}{{ticket.itemPackingTypes}}
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('street')}}{{ticket.street}}{{$t('postcode')}}{{ticket.postalCode}}
{{$t('city')}}{{ticket.city}}{{$t('agency')}}{{ticket.ticketAgency}}
{{$t('mobile')}}{{ticket.mobile}}{{$t('phone')}}{{ticket.phone}}
{{$t('warehouse')}}{{ticket.warehouseName}}{{$t('salesPerson')}}{{ticket.salesPersonName}}
+
+

{{ticket.description}}

+
+
+
+ + + + diff --git a/print/templates/reports/driver-route/driver-route.js b/print/templates/reports/driver-route/driver-route.js index 2de3d51924..1b2e8871a2 100755 --- a/print/templates/reports/driver-route/driver-route.js +++ b/print/templates/reports/driver-route/driver-route.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -39,12 +39,12 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The route id' } diff --git a/print/templates/reports/entry-order/entry-order.html b/print/templates/reports/entry-order/entry-order.html index 956d4e2f6f..bdc4b07595 100644 --- a/print/templates/reports/entry-order/entry-order.html +++ b/print/templates/reports/entry-order/entry-order.html @@ -1,127 +1,102 @@ - - - - - - - - - -
- - - - -
-
-
-
-
-

{{$t('title')}}

- - - - - - - - - - - - - - - -
{{$t('entryId')}}{{entry.id}}
{{$t('date')}}{{entry.landed | date('%d-%m-%Y')}}
{{$t('ref')}}{{entry.ref}}
-
-
-
-
-
{{$t('supplierData')}}
-
-

{{supplier.name}}

-
- {{supplier.street}} -
-
- {{supplier.postCode}}, {{supplier.city}}, ({{supplier.province}}) -
-
- {{supplier.nif}} -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('boxes')}}{{$t('packing')}}{{$t('concept')}}{{$t('quantity')}}{{$t('price')}}{{$t('amount')}}
{{buy.box}}{{buy.packing}}{{buy.itemName}}{{buy.quantity | number($i18n.locale)}}{{buy.buyingValue | currency('EUR', $i18n.locale)}}{{buy.buyingValue * buy.quantity | currency('EUR', $i18n.locale)}}
- - {{buy.tag5}} {{buy.value5}} - - - {{buy.tag6}} {{buy.value6}} - - - {{buy.tag7}} {{buy.value7}} - -
- {{$t('total')}} - {{getTotal() | currency('EUR', $i18n.locale)}}
- -
-
-
-
-

{{$t('notes')}}

-
- {{entry.notes}} -
-
-
-
-
-
+ + +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + + + + + +
{{$t('entryId')}}{{entry.id}}
{{$t('date')}}{{entry.landed | date('%d-%m-%Y')}}
{{$t('ref')}}{{entry.ref}}
+
+
+
+
+
{{$t('supplierData')}}
+
+

{{supplier.name}}

+
{{supplier.street}}
+
{{supplier.postCode}}, {{supplier.city}}, ({{supplier.province}})
+
{{supplier.nif}}
- - - -
- - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('boxes')}}{{$t('packing')}}{{$t('concept')}}{{$t('quantity')}}{{$t('price')}}{{$t('amount')}}
{{buy.box}}{{buy.packing}}{{buy.itemName}}{{buy.quantity | number($i18n.locale)}}{{buy.buyingValue | currency('EUR', $i18n.locale)}} + {{buy.buyingValue * buy.quantity | currency('EUR', $i18n.locale)}} +
+ {{buy.tag5}} {{buy.value5}} + {{buy.tag6}} {{buy.value6}} + {{buy.tag7}} {{buy.value7}} +
+ {{$t('total')}} + {{getTotal() | currency('EUR', $i18n.locale)}}
+
+
+
+
+

{{$t('notes')}}

+
{{entry.notes}}
+
+
+
+
+ + + + diff --git a/print/templates/reports/entry-order/entry-order.js b/print/templates/reports/entry-order/entry-order.js index ff4a65e0cb..22c5b55fe6 100755 --- a/print/templates/reports/entry-order/entry-order.js +++ b/print/templates/reports/entry-order/entry-order.js @@ -1,4 +1,5 @@ const Component = require(`vn-print/core/component`); +const reportBody = new Component('report-body'); const reportHeader = new Component('report-header'); const reportFooter = new Component('report-footer'); @@ -35,12 +36,13 @@ module.exports = { } }, components: { + 'report-body': reportBody.build(), 'report-header': reportHeader.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The entry id' } diff --git a/print/templates/reports/exportation/exportation.html b/print/templates/reports/exportation/exportation.html index 2529994675..920e32a48e 100644 --- a/print/templates/reports/exportation/exportation.html +++ b/print/templates/reports/exportation/exportation.html @@ -1,51 +1,35 @@ - - - - - - - - - -
- - - -
-
-

{{$t('title')}}

-

{{$t('toAttention')}}

-

-

-

    -
  • - {{responsibility}} -
  • -
-

-
-

{{$t('issued', [ - 'Algemesí', - invoice.issued.getDate(), - $t('months')[invoice.issued.getMonth()], - invoice.issued.getFullYear()]) - }} -

-

({{$t('signature')}})

- -

-

{{$t('signer.name')}}: {{company.manager}}
-
{{$t('signer.ID')}}: {{company.managerFi}}
-
{{$t('signer.position')}}: {{$t('manager')}}
-

-
-
-
- - - -
- - \ No newline at end of file + +
+
+

{{$t('title')}}

+

{{$t('toAttention')}}

+

+

+

    +
  • + {{responsibility}} +
  • +
+

+
+

{{$t('issued', [ + 'Algemesí', + invoice.issued.getDate(), + $t('months')[invoice.issued.getMonth()], + invoice.issued.getFullYear()]) + }} +

+

({{$t('signature')}})

+ +

+

{{$t('signer.name')}}: {{company.manager}}
+
{{$t('signer.ID')}}: {{company.managerFi}}
+
{{$t('signer.position')}}: {{$t('manager')}}
+

+
+
+
+ +
\ No newline at end of file diff --git a/print/templates/reports/exportation/exportation.js b/print/templates/reports/exportation/exportation.js index 630baf421b..9f39aa1a11 100755 --- a/print/templates/reports/exportation/exportation.js +++ b/print/templates/reports/exportation/exportation.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -25,12 +25,12 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { reference: { - type: [Number, String], + type: Number, required: true, description: 'The invoice ref' } diff --git a/print/templates/reports/extra-community/extra-community.html b/print/templates/reports/extra-community/extra-community.html index 86baf858bc..42afedc761 100644 --- a/print/templates/reports/extra-community/extra-community.html +++ b/print/templates/reports/extra-community/extra-community.html @@ -1,84 +1,67 @@ - - - - - - - - - -
- - -
-
-

{{$t('title')}}

-
-
+ +
+
+

{{$t('title')}}

+
+
+
+
+
+
{{$t('information')}}
+
+
+ + + + + + + + + + + + + + + + + + + + + +
{{$t('reference')}}{{travel.ref}}{{$t('blockedKg')}}{{travel.kg | number($i18n.locale)}}
{{$t('packages')}}{{travel.stickers}}{{$t('volumeKg')}}{{travel.volumeKg | number($i18n.locale)}}
{{$t('shipped')}}{{travel.shipped | date('%d-%m-%Y')}}{{$t('physicalKg')}}{{travel.loadedKg | number($i18n.locale)}}
+
+
+
- -
-
-
-
{{$t('information')}}
-
-
- - - - - - - - - - - - - - - - - - - - - -
{{$t('reference')}}{{travel.ref}}{{$t('blockedKg')}}{{travel.kg | number($i18n.locale)}}
{{$t('packages')}}{{travel.stickers}}{{$t('volumeKg')}}{{travel.volumeKg | number($i18n.locale)}}
{{$t('shipped')}}{{travel.shipped | date('%d-%m-%Y')}}{{$t('physicalKg')}}{{travel.loadedKg | number($i18n.locale)}}
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
{{$t('supplier')}}{{$t('reference')}}{{$t('volKg')}}{{$t('phyKg')}}{{$t('packages')}}
{{entry.supplierName}}{{entry.ref}}{{entry.volumeKg | number($i18n.locale)}}{{entry.loadedKg | number($i18n.locale)}}{{entry.stickers}}
{{$t('noRows')}}
-
-
- - - -
- - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + +
{{$t('supplier')}}{{$t('reference')}}{{$t('volKg')}}{{$t('phyKg')}}{{$t('packages')}}
{{entry.supplierName}}{{entry.ref}}{{entry.volumeKg | number($i18n.locale)}}{{entry.loadedKg | number($i18n.locale)}}{{entry.stickers}}
{{$t('noRows')}}
+ + + + diff --git a/print/templates/reports/extra-community/extra-community.js b/print/templates/reports/extra-community/extra-community.js index dec89648a8..4dfe6e2784 100755 --- a/print/templates/reports/extra-community/extra-community.js +++ b/print/templates/reports/extra-community/extra-community.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); const db = require(`vn-print/core/database`); @@ -100,7 +100,7 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: [ diff --git a/print/templates/reports/incoterms-authorization/incoterms-authorization.html b/print/templates/reports/incoterms-authorization/incoterms-authorization.html index 27cad97b16..99a5e9caa7 100644 --- a/print/templates/reports/incoterms-authorization/incoterms-authorization.html +++ b/print/templates/reports/incoterms-authorization/incoterms-authorization.html @@ -1,84 +1,61 @@ - - - - - - - - - -
- - - -
-
-

-

-

-

+ +
+
+

+

+

-
-
-

{{client.name}}

-
-

- - - - - - - - - - - - - - - -
{{$t('signer.representative')}}:
{{$t('signer.representativeRole')}}:
{{$t('signer.signed')}}:
-

-
-
-

{{ company.name }}

- -

-

{{company.manager}}
-
{{$t('manager')}}
-

{{$t('issued', [ - company.city, - issued.getDate(), - $t('months')[issued.getMonth()], - issued.getFullYear()]) - }} -

-

-
-
-
-
- - - -
- - \ No newline at end of file +
+
+

{{client.name}}

+
+ + + + + + + + + + + + + + + +
{{$t('signer.representative')}}:
{{$t('signer.representativeRole')}}:
{{$t('signer.signed')}}:
+
+
+

{{ company.name }}

+ +
{{company.manager}}
+
{{$t('manager')}}
+
+
+ + + + diff --git a/print/templates/reports/incoterms-authorization/incoterms-authorization.js b/print/templates/reports/incoterms-authorization/incoterms-authorization.js index 26637b8c21..bfe675985c 100755 --- a/print/templates/reports/incoterms-authorization/incoterms-authorization.js +++ b/print/templates/reports/incoterms-authorization/incoterms-authorization.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -16,17 +16,17 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The client id' }, companyId: { - type: [Number, String], + type: Number, required: true } } diff --git a/print/templates/reports/invoice-incoterms/invoice-incoterms.html b/print/templates/reports/invoice-incoterms/invoice-incoterms.html index f306670424..20c9ad4404 100644 --- a/print/templates/reports/invoice-incoterms/invoice-incoterms.html +++ b/print/templates/reports/invoice-incoterms/invoice-incoterms.html @@ -1,123 +1,94 @@ - - - - - - - - - -
- - - - -
-
-
-
-
-

{{$t('title')}}

- - - - - - - - - - - - - - - -
{{$t('clientId')}}{{client.id}}
{{$t('invoice')}}{{invoice.ref}}
{{$t('date')}}{{invoice.issued | date('%d-%m-%Y')}}
-
-
-
-
-
{{$t('invoiceData')}}
-
-

{{client.socialName}}

-
- {{client.postalAddress}} -
-
- {{client.postcodeCity}} -
-
- {{$t('fiscalId')}}: {{client.fi}} -
-
-
-
-
- -
-
{{$t('incotermsTitle')}}
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{$t('incoterms')}} - {{incoterms.incotermsFk}} - {{incoterms.incotermsName}}
- {{$t('productDescription')}} - {{incoterms.intrastat}}
{{$t('expeditionDescription')}}
{{$t('packageNumber')}}{{incoterms.packages}}
{{$t('packageGrossWeight')}}{{incoterms.weight}} KG
{{$t('packageCubing')}}{{incoterms.volume}} m3
- -

-

- {{$t('customsInfo')}} - {{incoterms.customsAgentName}} -
-
- ( - {{incoterms.customsAgentNif}} - {{incoterms.customsAgentStreet}} - - ☎ {{incoterms.customsAgentPhone}} - - - ✉ {{incoterms.customsAgentEmail}} - - ) -
-

-

- {{$t('productDisclaimer')}} -

-
-
-
+ + +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t('invoice')}}{{invoice.ref}}
{{$t('date')}}{{invoice.issued | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('invoiceData')}}
+
+

{{client.socialName}}

+
{{client.postalAddress}}
+
{{client.postcodeCity}}
+
{{$t('fiscalId')}}: {{client.fi}}
-
- - \ No newline at end of file + + + + +
+
{{$t('incotermsTitle')}}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('incoterms')}}{{incoterms.incotermsFk}} - {{incoterms.incotermsName}}
{{$t('productDescription')}}{{incoterms.intrastat}}
{{$t('expeditionDescription')}}
{{$t('packageNumber')}}{{incoterms.packages}}
{{$t('packageGrossWeight')}}{{incoterms.weight}} KG
{{$t('packageCubing')}}{{incoterms.volume}} m3
+ +
+ {{$t('customsInfo')}} + {{incoterms.customsAgentName}} +
+
+ ( + {{incoterms.customsAgentNif}} + {{incoterms.customsAgentStreet}} + ☎ {{incoterms.customsAgentPhone}} + ✉ {{incoterms.customsAgentEmail}} + ) +
+

+ {{$t('productDisclaimer')}} +

+
+
+ + + + diff --git a/print/templates/reports/invoice-incoterms/invoice-incoterms.js b/print/templates/reports/invoice-incoterms/invoice-incoterms.js index 3dbe76ac32..3afc50376b 100755 --- a/print/templates/reports/invoice-incoterms/invoice-incoterms.js +++ b/print/templates/reports/invoice-incoterms/invoice-incoterms.js @@ -1,6 +1,6 @@ const Component = require(`vn-print/core/component`); +const reportBody = new Component('report-body'); const reportHeader = new Component('report-header'); -const reportFooter = new Component('report-footer'); module.exports = { name: 'invoice-incoterms', @@ -27,12 +27,12 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), - 'report-footer': reportFooter.build() + 'report-body': reportBody.build(), + 'report-header': reportHeader.build() }, props: { reference: { - type: [Number, String], + type: Number, required: true, description: 'The invoice ref' } diff --git a/print/templates/reports/invoice/invoice.html b/print/templates/reports/invoice/invoice.html index 1d646a0db9..d23bba1e90 100644 --- a/print/templates/reports/invoice/invoice.html +++ b/print/templates/reports/invoice/invoice.html @@ -1,319 +1,266 @@ - - - - - - - - - -
+ + + +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t('invoice')}}{{invoice.ref}}
{{$t('date')}}{{invoice.issued | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('invoiceData')}}
+
+

{{client.socialName}}

+
{{client.postalAddress}}
+
{{client.postcodeCity}}
+
{{$t('fiscalId')}}: {{client.fi}}
+
+
+
+
+
+

{{$t('rectifiedInvoices')}}

+ + + + + + + + + + + + + + + + + +
{{$t('invoice')}}{{$t('issued')}}{{$t('amount')}}{{$t('description')}}
{{row.ref}}{{row.issued | date}}{{row.amount | currency('EUR', $i18n.locale)}}{{row.description}}
+
+
+
+
+

{{$t('deliveryNote')}}

+
+
+
+ {{ticket.id}} +
+
+
+

{{$t('shipped')}}

+
+
+
+ {{ticket.shipped | date}} +
+
+ +

{{ticket.nickname}}

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}}{{saleImport(sale) | currency('EUR', $i18n.locale)}}
+ {{sale.tag5}} {{sale.value5}} + {{sale.tag6}} {{sale.value6}} + {{sale.tag7}} {{sale.value7}} +
+ {{$t('subtotal')}} + {{ticketSubtotal(ticket) | currency('EUR', $i18n.locale)}}
+
- - - +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('taxBreakdown')}}
{{$t('type')}}{{$t('taxBase')}}{{$t('tax')}}{{$t('fee')}}
{{tax.name}}{{tax.base | currency('EUR', $i18n.locale)}}{{tax.vatPercent | percentage}}{{tax.vat | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} + {{sumTotal(taxes, 'base') | currency('EUR', $i18n.locale)}} + {{sumTotal(taxes, 'vat') | currency('EUR', $i18n.locale)}}
{{$t('total')}}{{taxTotal | currency('EUR', $i18n.locale)}}
- - - - -
-
-
-
-
-

{{$t('title')}}

- - - - - - - - - - - - - - - -
{{$t('clientId')}}{{client.id}}
{{$t('invoice')}}{{invoice.ref}}
{{$t('date')}}{{invoice.issued | date('%d-%m-%Y')}}
-
-
-
-
-
{{$t('invoiceData')}}
-
-

{{client.socialName}}

-
- {{client.postalAddress}} -
-
- {{client.postcodeCity}} -
-
- {{$t('fiscalId')}}: {{client.fi}} -
-
-
+
+
{{$t('notes')}}
+
+ {{invoice.footNotes}} +
+
+
+
+
+
+
+
+
+
+
{{$t('plantPassport')}}
- - -
-

{{$t('rectifiedInvoices')}}

- - - - - - - - - - - - - - - - - -
{{$t('invoice')}}{{$t('issued')}}{{$t('amount')}}{{$t('description')}}
{{row.ref}}{{row.issued | date}}{{row.amount | currency('EUR', $i18n.locale)}}{{row.description}}
+
+
+
+ A + {{botanical}}
- - - -
-
-
-

{{$t('deliveryNote')}} -

-
-
- {{ticket.id}} -
-
-
-

{{$t('shipped')}}

-
-
-
- {{ticket.shipped | date}} -
-
- -

{{ticket.nickname}}

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}}{{saleImport(sale) | currency('EUR', $i18n.locale)}}
- - {{sale.tag5}} {{sale.value5}} - - - {{sale.tag6}} {{sale.value6}} - - - {{sale.tag7}} {{sale.value7}} - -
- {{$t('subtotal')}} - {{ticketSubtotal(ticket) | currency('EUR', $i18n.locale)}}
+
+ B + ES17462130
- - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('taxBreakdown')}}
{{$t('type')}} - {{$t('taxBase')}} - {{$t('tax')}}{{$t('fee')}}
{{tax.name}} - {{tax.base | currency('EUR', $i18n.locale)}} - {{tax.vatPercent | percentage}}{{tax.vat | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} - {{sumTotal(taxes, 'base') | currency('EUR', $i18n.locale)}} - {{sumTotal(taxes, 'vat') | currency('EUR', $i18n.locale)}}
{{$t('total')}}{{taxTotal | currency('EUR', $i18n.locale)}}
- -
-
{{$t('notes')}}
-
- {{invoice.footNotes}} -
-
-
- - - -
-
-
-
-
-
- -
-
- {{$t('plantPassport')}}
-
-
-
-
-
- A - {{botanical}} -
-
- B - ES17462130 -
-
- C - {{ticketsId}} -
-
- D - ES -
-
-
-
-
- +
+ C + {{ticketsId}}
- - - -
-

{{$t('intrastat')}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('code')}}{{$t('description')}}{{$t('stems')}}{{$t('netKg')}}{{$t('amount')}}
{{row.code}}{{row.description}}{{row.stems | number($i18n.locale)}}{{row.netKg | number($i18n.locale)}}{{row.subtotal | currency('EUR', $i18n.locale)}}
- {{sumTotal(intrastat, 'stems') | number($i18n.locale)}} - - {{sumTotal(intrastat, 'netKg') | number($i18n.locale)}} - - {{sumTotal(intrastat, 'subtotal') | currency('EUR', $i18n.locale)}} -
+
+ D + ES
- - - -
-
-
-
{{$t('observations')}}
-
-
{{$t('wireTransfer')}}
-
{{$t('accountNumber', [invoice.iban])}}
-
-
-
-
- -
- - - -
- - \ No newline at end of file + + + +
+

{{$t('intrastat')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('code')}}{{$t('description')}}{{$t('stems')}}{{$t('netKg')}}{{$t('amount')}}
{{row.code}}{{row.description || $t('services') }}{{row.stems | number($i18n.locale)}}{{row.netKg | number($i18n.locale)}}{{row.subtotal | currency('EUR', $i18n.locale)}}
+ {{sumTotal(intrastat, 'stems') | number($i18n.locale)}} + + {{sumTotal(intrastat, 'netKg') | number($i18n.locale)}} + + {{sumTotal(intrastat, 'subtotal') | currency('EUR', $i18n.locale)}} +
+
+
+
+
+
{{$t('observations')}}
+
+
{{$t('wireTransfer')}}
+
{{$t('accountNumber', [invoice.iban])}}
+
+
+
+
+ + + + diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index db94a7a12e..eebbde8ef1 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -1,5 +1,6 @@ const Component = require(`vn-print/core/component`); const Report = require(`vn-print/core/report`); +const reportBody = new Component('report-body'); const reportHeader = new Component('report-header'); const reportFooter = new Component('report-footer'); const invoiceIncoterms = new Report('invoice-incoterms'); @@ -81,7 +82,7 @@ module.exports = { return this.rawSqlFromDef(`taxes`, [reference]); }, fetchIntrastat(reference) { - return this.rawSqlFromDef(`intrastat`, [reference, reference, reference]); + return this.rawSqlFromDef(`intrastat`, [reference, reference, reference, reference]); }, fetchRectified(reference) { return this.rawSqlFromDef(`rectified`, [reference]); @@ -110,6 +111,7 @@ module.exports = { } }, components: { + 'report-body': reportBody.build(), 'report-header': reportHeader.build(), 'report-footer': reportFooter.build(), 'invoice-incoterms': invoiceIncoterms.build() diff --git a/print/templates/reports/invoice/locale/en.yml b/print/templates/reports/invoice/locale/en.yml index 4e4688b554..336592f0c8 100644 --- a/print/templates/reports/invoice/locale/en.yml +++ b/print/templates/reports/invoice/locale/en.yml @@ -33,4 +33,5 @@ issued: Issued plantPassport: Plant passport observations: Observations wireTransfer: "Pay method: Transferencia" -accountNumber: "Account number: {0}" \ No newline at end of file +accountNumber: "Account number: {0}" +services: Services \ 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 d37e77943f..32f6fc7080 100644 --- a/print/templates/reports/invoice/locale/es.yml +++ b/print/templates/reports/invoice/locale/es.yml @@ -33,4 +33,5 @@ issued: F. emisión plantPassport: Pasaporte fitosanitario observations: Observaciones wireTransfer: "Forma de pago: Transferencia" -accountNumber: "Número de cuenta: {0}" \ No newline at end of file +accountNumber: "Número de cuenta: {0}" +services: Servicios \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/intrastat.sql b/print/templates/reports/invoice/sql/intrastat.sql index e2ee476678..5cc3ebd7f1 100644 --- a/print/templates/reports/invoice/sql/intrastat.sql +++ b/print/templates/reports/invoice/sql/intrastat.sql @@ -1,4 +1,4 @@ -SELECT +(SELECT ir.id code, ir.description description, CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, @@ -19,4 +19,14 @@ SELECT WHERE t.refFk = ? AND i.intrastatFk GROUP BY i.intrastatFk - ORDER BY i.intrastatFk; \ No newline at end of file + ORDER BY i.intrastatFk) +UNION ALL +(SELECT + NULL AS code, + NULL AS description, + 0 AS stems, + 0 AS netKg, + CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)) AS subtotal + FROM vn.ticketService ts + JOIN vn.ticket t ON ts.ticketFk = t.id + WHERE t.refFk = ?); \ No newline at end of file diff --git a/print/templates/reports/invoiceIn/assets/css/import.js b/print/templates/reports/invoiceIn/assets/css/import.js new file mode 100644 index 0000000000..37a98dfddb --- /dev/null +++ b/print/templates/reports/invoiceIn/assets/css/import.js @@ -0,0 +1,12 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/report.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/templates/reports/invoiceIn/assets/css/style.css b/print/templates/reports/invoiceIn/assets/css/style.css new file mode 100644 index 0000000000..9fda2a6138 --- /dev/null +++ b/print/templates/reports/invoiceIn/assets/css/style.css @@ -0,0 +1,42 @@ +h2 { + font-weight: 100; + color: #555 +} + +.table-title { + margin-bottom: 15px; + font-size: .8rem +} + +.table-title h2 { + margin: 0 15px 0 0 +} + +.ticket-info { + font-size: 22px +} + + +#nickname h2 { + max-width: 400px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +#phytosanitary { + padding-right: 10px +} + +#phytosanitary .flag img { + width: 100% +} + +#phytosanitary .flag .flag-text { + padding-left: 10px; + box-sizing: border-box; +} + +.phytosanitary-info { + margin-top: 10px +} \ No newline at end of file diff --git a/print/templates/reports/invoiceIn/assets/images/europe.png b/print/templates/reports/invoiceIn/assets/images/europe.png new file mode 100644 index 0000000000..673be92ae2 Binary files /dev/null and b/print/templates/reports/invoiceIn/assets/images/europe.png differ diff --git a/print/templates/reports/invoiceIn/invoiceIn.html b/print/templates/reports/invoiceIn/invoiceIn.html new file mode 100644 index 0000000000..69ce3d0f2f --- /dev/null +++ b/print/templates/reports/invoiceIn/invoiceIn.html @@ -0,0 +1,177 @@ + + +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + + + + + +
{{$t('supplierId')}}{{invoice.supplierId}}
{{$t('invoiceId')}}{{invoice.id}}
{{$t('date')}}{{invoice.created | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('invoiceData')}}
+
+

{{invoice.name}}

+
{{invoice.postalAddress}}
+
{{invoice.postcodeCity}}
+
{{$t('fiscalId')}}: {{invoice.nif}}
+
{{$t('phone')}}: {{invoice.phone}}
+
+
+
+
+ +
+
+
+

{{$t('invoiceId')}}

+
+
+
+ {{entry.id}} +
+
+
+

{{$t('date')}}

+
+
+
+ {{entry.landed | date}} +
+
+ +
+

{{$t('reference')}}

+
+
+
+ {{entry.ref}} +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('item')}}{{$t('quantity')}}{{$t('buyingValue')}}{{$t('amount')}}
{{buy.name}}{{buy.quantity}}{{buy.buyingValue}}{{buyImport(buy) | currency('EUR', $i18n.locale)}}
+ {{buy.tag5}} {{buy.value5}} + {{buy.tag6}} {{buy.value6}} + {{buy.tag7}} {{buy.value7}} +
+ {{$t('subtotal')}} + {{entrySubtotal(entry) | currency('EUR', $i18n.locale)}}
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('taxBreakdown')}}
{{$t('type')}}{{$t('taxBase')}}{{$t('tax')}}{{$t('fee')}}
{{tax.name}}{{tax.taxableBase | currency('EUR', $i18n.locale)}}{{tax.rate | percentage}}{{tax.vat | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} + {{sumTotal(taxes, 'taxableBase') | currency('EUR', $i18n.locale)}} + {{sumTotal(taxes, 'vat') | currency('EUR', $i18n.locale)}}
{{$t('total')}}{{taxTotal() | currency('EUR', $i18n.locale)}}
+ +
+
{{$t('notes')}}
+
+ {{invoice.footNotes}} +
+
+
+
+
+
+
{{$t('observations')}}
+
+
{{$t('payMethod')}}
+
{{invoice.payMethod}}
+
+
+
+
+
+
+
+ +
diff --git a/print/templates/reports/invoiceIn/invoiceIn.js b/print/templates/reports/invoiceIn/invoiceIn.js new file mode 100755 index 0000000000..40dd25a5b8 --- /dev/null +++ b/print/templates/reports/invoiceIn/invoiceIn.js @@ -0,0 +1,96 @@ +const Component = require(`vn-print/core/component`); +const reportBody = new Component('report-body'); +const reportHeader = new Component('report-header'); +const reportFooter = new Component('report-footer'); + +module.exports = { + name: 'invoiceIn', + async serverPrefetch() { + this.invoice = await this.fetchInvoice(this.id); + this.taxes = await this.fetchTaxes(this.id); + + if (!this.invoice) + throw new Error('Something went wrong'); + + const entries = await this.fetchEntry(this.id); + const buys = await this.fetchBuy(this.id); + + const map = new Map(); + + for (let entry of entries) { + entry.buys = []; + + map.set(entry.id, entry); + } + + for (let buy of buys) { + const entry = map.get(buy.entryFk); + + if (entry) entry.buys.push(buy); + } + + this.entries = entries; + }, + computed: { + }, + methods: { + fetchInvoice(id) { + return this.findOneFromDef('invoice', [id]); + }, + fetchEntry(id) { + return this.rawSqlFromDef('entry', [id]); + }, + fetchBuy(id) { + return this.rawSqlFromDef('buy', [id]); + }, + async fetchTaxes(id) { + const taxes = await this.rawSqlFromDef(`taxes`, [id]); + return this.taxVat(taxes); + }, + buyImport(buy) { + return buy.quantity * buy.buyingValue; + }, + entrySubtotal(entry) { + let subTotal = 0.00; + for (let buy of entry.buys) + subTotal += this.buyImport(buy); + + return subTotal; + }, + sumTotal(rows, prop) { + let total = 0.00; + for (let row of rows) + total += parseFloat(row[prop]); + + return total; + }, + taxVat(taxes) { + for (tax of taxes) { + let vat = 0; + if (tax.rate && tax.taxableBase) + vat = (tax.rate / 100) * tax.taxableBase; + + tax.vat = vat; + } + + return taxes; + }, + taxTotal() { + const base = this.sumTotal(this.taxes, 'taxableBase'); + const vat = this.sumTotal(this.taxes, 'vat'); + + return base + vat; + } + }, + components: { + 'report-body': reportBody.build(), + 'report-header': reportHeader.build(), + 'report-footer': reportFooter.build(), + }, + props: { + id: { + type: Number, + description: 'The invoice id' + } + } +}; diff --git a/print/templates/reports/invoiceIn/locale/en.yml b/print/templates/reports/invoiceIn/locale/en.yml new file mode 100644 index 0000000000..92d3b0c2dd --- /dev/null +++ b/print/templates/reports/invoiceIn/locale/en.yml @@ -0,0 +1,25 @@ +reportName: invoice +title: Agricultural invoice +invoiceId: Agricultural invoice +supplierId: Proveedor +invoiceData: Invoice data +reference: Reference +fiscalId: FI / NIF +phone: Phone +date: Date +item: Item +quantity: Qty. +concept: Concept +buyingValue: PSP/u +discount: Disc. +vat: VAT +amount: Amount +type: Type +taxBase: Tax base +tax: Tax +fee: Fee +total: Total +subtotal: Subtotal +taxBreakdown: Tax breakdown +observations: Observations +payMethod: Pay method diff --git a/print/templates/reports/invoiceIn/locale/es.yml b/print/templates/reports/invoiceIn/locale/es.yml new file mode 100644 index 0000000000..f2fb28e644 --- /dev/null +++ b/print/templates/reports/invoiceIn/locale/es.yml @@ -0,0 +1,25 @@ +reportName: factura +title: Factura Agrícola +invoiceId: Factura Agrícola +supplierId: Proveedor +invoiceData: Datos de facturación +reference: Referencia +fiscalId: CIF / NIF +phone: Tlf +date: Fecha +item: Artículo +quantity: Cant. +concept: Concepto +buyingValue: PVP/u +discount: Dto. +vat: IVA +amount: Importe +type: Tipo +taxBase: Base imp. +tax: Tasa +fee: Cuota +total: Total +subtotal: Subtotal +taxBreakdown: Desglose impositivo +observations: Observaciones +payMethod: Método de pago diff --git a/print/templates/reports/invoiceIn/sql/buy.sql b/print/templates/reports/invoiceIn/sql/buy.sql new file mode 100644 index 0000000000..28a80ff9cf --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/buy.sql @@ -0,0 +1,18 @@ +SELECT + b.id, + e.id entryFk, + it.name, + b.quantity, + b.buyingValue, + (b.quantity * b.buyingValue) total, + it.tag5, + it.value5, + it.tag6, + it.value6, + it.tag7, + it.value7 + FROM entry e + JOIN invoiceIn i ON i.id = e.invoiceInFk + JOIN buy b ON b.entryFk = e.id + JOIN item it ON it.id = b.itemFk + WHERE i.id = ? diff --git a/print/templates/reports/invoiceIn/sql/entry.sql b/print/templates/reports/invoiceIn/sql/entry.sql new file mode 100644 index 0000000000..0b29cd81cd --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/entry.sql @@ -0,0 +1,8 @@ +SELECT + e.id, + t.landed, + e.ref + FROM entry e + JOIN invoiceIn i ON i.id = e.invoiceInFk + JOIN travel t ON t.id = e.travelFk + WHERE i.id = ? diff --git a/print/templates/reports/invoiceIn/sql/invoice.sql b/print/templates/reports/invoiceIn/sql/invoice.sql new file mode 100644 index 0000000000..fe9ef1e9e0 --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/invoice.sql @@ -0,0 +1,14 @@ +SELECT + i.id, + s.id supplierId, + i.created, + s.name, + s.street AS postalAddress, + s.nif, + s.phone, + p.name payMethod + FROM invoiceIn i + JOIN supplier s ON s.id = i.supplierFk + JOIN company c ON c.id = i.companyFk + JOIN payMethod p ON p.id = s.payMethodFk + WHERE i.id = ? diff --git a/print/templates/reports/invoiceIn/sql/taxes.sql b/print/templates/reports/invoiceIn/sql/taxes.sql new file mode 100644 index 0000000000..20df33f83a --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/taxes.sql @@ -0,0 +1,8 @@ +SELECT + ti.iva as name, + ti.PorcentajeIva as rate, + iit.taxableBase + FROM invoiceIn ii + JOIN invoiceInTax iit ON ii.id = iit.invoiceInFk + JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk + WHERE ii.id = ?; diff --git a/print/templates/reports/item-label/item-label.html b/print/templates/reports/item-label/item-label.html index 3f2d7ce690..49593f7ddd 100644 --- a/print/templates/reports/item-label/item-label.html +++ b/print/templates/reports/item-label/item-label.html @@ -1,35 +1,29 @@ - - - - - - - - - -
-
-
-

{{item.id}}

-
- -
-
-
-
{{item.name}}
-
{{tags.color}}
-
{{tags.producer}}
-
-
-
{{packing()}}
-
{{dated}}
-
{{labelPage}}
-
-
{{item.size}}
-
-
-
-
- - - + + +
+
+

{{item.id}}

+
+ +
+
+
+
{{item.name}}
+
{{tags.color}}
+
{{tags.producer}}
+
+
+
{{packing()}}
+
{{dated}}
+
{{labelPage}}
+
+
{{item.size}}
+
+
+
+ +
diff --git a/print/templates/reports/item-label/item-label.js b/print/templates/reports/item-label/item-label.js index d03d03cfa4..6341bd11aa 100755 --- a/print/templates/reports/item-label/item-label.js +++ b/print/templates/reports/item-label/item-label.js @@ -1,6 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); -const reportFooter = new Component('report-footer'); +const reportBody = new Component('report-body'); const qrcode = require('qrcode'); module.exports = { @@ -50,8 +49,7 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), - 'report-footer': reportFooter.build() + 'report-body': reportBody.build() }, props: { id: { diff --git a/print/templates/reports/letter-debtor/letter-debtor.html b/print/templates/reports/letter-debtor/letter-debtor.html index 88bf15bdbd..962af021db 100644 --- a/print/templates/reports/letter-debtor/letter-debtor.html +++ b/print/templates/reports/letter-debtor/letter-debtor.html @@ -1,95 +1,78 @@ - - - - - - - - - -
- - - -
-
-
-
-
-

{{$t('title')}}

- - - - - - - - - - - -
{{$t('clientId')}}{{client.id}}
{{$t('date')}}{{dated}}
-
-
-
-
-
{{$t('clientData')}}
-
-

{{client.socialName}}

-
- {{client.street}} -
-
- {{client.postcode}}, {{client.city}} ({{client.province}}) -
-
- {{client.country}} -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('date')}}{{$t('concept')}}{{$t('invoiced')}}{{$t('payed')}}{{$t('balance')}}
{{sale.issued | date('%d-%m-%Y')}}{{sale.ref}}{{sale.debtOut}}{{sale.debtIn}}{{getBalance(sale)}}
- Total - {{getTotalDebtOut() | currency('EUR', $i18n.locale)}} - {{getTotalDebtIn() | currency('EUR', $i18n.locale)}}{{totalBalance | currency('EUR', $i18n.locale)}}
-
+ +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t('date')}}{{dated}}
+
+
+
+
+
{{$t('clientData')}}
+
+

{{client.socialName}}

+
{{client.street}}
+
{{client.postcode}}, {{client.city}} ({{client.province}})
+
{{client.country}}
- - - -
- - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('date')}}{{$t('concept')}}{{$t('invoiced')}}{{$t('payed')}}{{$t('balance')}}
{{sale.issued | date('%d-%m-%Y')}}{{sale.ref}}{{sale.debtOut}}{{sale.debtIn}}{{getBalance(sale)}}
+ Total + {{getTotalDebtOut() | currency('EUR', $i18n.locale)}} + {{getTotalDebtIn() | currency('EUR', $i18n.locale)}}{{totalBalance | currency('EUR', $i18n.locale)}}
+ + + + diff --git a/print/templates/reports/letter-debtor/letter-debtor.js b/print/templates/reports/letter-debtor/letter-debtor.js index 80d4cba9b6..749fde4edf 100755 --- a/print/templates/reports/letter-debtor/letter-debtor.js +++ b/print/templates/reports/letter-debtor/letter-debtor.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -58,17 +58,17 @@ module.exports = { }, }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The client id' }, companyId: { - type: [Number, String], + type: Number, required: true } } diff --git a/print/templates/reports/receipt/receipt.html b/print/templates/reports/receipt/receipt.html index 5dc1846f7e..e0bab5ecf9 100644 --- a/print/templates/reports/receipt/receipt.html +++ b/print/templates/reports/receipt/receipt.html @@ -1,45 +1,30 @@ - - - - - - - - - -
- - - -
-
-
-

{{$t('title')}}

-

- Recibo de {{client.socialName}}, - la cantidad de {{receipt.amountPaid}} € en concepto de 'entrega a cuenta', - quedando pendiente en la cuenta del cliente - un saldo de {{receipt.amountUnpaid}} €. -

-
- -

{{$t('payed', [ - 'Algemesí', - receipt.payed.getDate(), - $t('months')[receipt.payed.getMonth()], - receipt.payed.getFullYear()]) - }} -

-
-
-
-
- - - -
- - \ No newline at end of file + +
+
+
+

{{$t('title')}}

+

+ Recibo de {{client.socialName}}, la cantidad de + {{receipt.amountPaid}} € en concepto de 'entrega a cuenta', quedando pendiente en + la cuenta del cliente un saldo de {{receipt.amountUnpaid}} €. +

+
+ +

+ {{$t('payed', [ 'Algemesí', receipt.payed.getDate(), $t('months')[receipt.payed.getMonth()], + receipt.payed.getFullYear()]) }} +

+
+
+
+
+ +
diff --git a/print/templates/reports/receipt/receipt.js b/print/templates/reports/receipt/receipt.js index 401aa1ef36..9a9ccd4525 100755 --- a/print/templates/reports/receipt/receipt.js +++ b/print/templates/reports/receipt/receipt.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -20,12 +20,12 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'Receipt id' } diff --git a/print/templates/reports/sepa-core/sepa-core.html b/print/templates/reports/sepa-core/sepa-core.html index c935bc2a8d..f2326e43cf 100644 --- a/print/templates/reports/sepa-core/sepa-core.html +++ b/print/templates/reports/sepa-core/sepa-core.html @@ -1,201 +1,174 @@ - - - - - - - + + + + + + + + + + + + + + + + + + +
- - - -
-
-
-

{{$t('instructions.title')}}

-

- 1. {{$t('instructions.accountFields')}} -

-

- 2. {{$t('instructions.signDocument')}} -

-

- {{$t('instructions.thanks')}} -

-
-
-
+ +
+
+
+

{{$t('instructions.title')}}

+

1. {{$t('instructions.accountFields')}}

+

2. {{$t('instructions.signDocument')}}

+

{{$t('instructions.thanks')}}

+
+
+
- - - -
-
-

{{$t('title')}}

-
-
-
- {{$t('supplier.toCompleteBySupplier')}} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('supplier.orderReference')}}{{supplier.mandateCode}}
{{$t('supplier.identifier')}} -
ES89000B97367486
-
B97367486-000
-
{{$t('supplier.name')}}{{supplier.name}}
{{$t('supplier.street')}}{{supplier.street}}
{{$t('supplier.location')}}{{supplier.postCode}}, {{supplier.city}} ({{supplier.province}})
{{$t('supplier.country')}}{{supplier.country}}
+ +
+
+

{{$t('title')}}

+
+
+
{{$t('supplier.toCompleteBySupplier')}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('supplier.orderReference')}}{{supplier.mandateCode}}
{{$t('supplier.identifier')}} +
ES89000B97367486
+
B97367486-000
+
{{$t('supplier.name')}}{{supplier.name}}
{{$t('supplier.street')}}{{supplier.street}}
{{$t('supplier.location')}}{{supplier.postCode}}, {{supplier.city}} ({{supplier.province}})
{{$t('supplier.country')}}{{supplier.country}}
+
+
+ +

{{$t('description')}}

+

+ {{$t('documentCopy')}} +

+ +
+
+
{{$t('client.toCompleteByClient')}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - -
+ {{$t('client.name')}} +
{{$t('client.accountHolder')}}
+
{{client.socialName}}
{{$t('client.fiscalId')}}{{client.fi}}
{{$t('client.street')}}{{client.street}}
{{$t('client.location')}}{{client.postcode}}, {{client.city}} ({{client.province}})
{{$t('client.country')}}{{client.country}}
{{$t('client.swift')}} +
+
- - -

{{$t('description')}}

-

- {{$t('documentCopy')}} -

- -
-
-
- {{$t('client.toCompleteByClient')}} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{$t('client.name')}} -
{{$t('client.accountHolder')}}
-
{{client.socialName}}
- {{$t('client.fiscalId')}} - {{client.fi}}
{{$t('client.street')}}{{client.street}}
{{$t('client.location')}}{{client.postcode}}, {{client.city}} ({{client.province}})
{{$t('client.country')}}{{client.country}}
{{$t('client.swift')}} -
- -
-
{{$t('client.accountNumber')}}
-
- {{client.countryCode.substr(0, 1)}} - {{client.countryCode.substr(1, 1)}} - -
-
-
- {{client.countryCode.substr(0, 1)}} - {{client.countryCode.substr(1, 1)}} - -
-
-
- {{$t('client.accountNumberFormat', [ - $t(`${client.country}`), - client.ibanLength, - client.countryCode - ])}} - -
-
{{$t('client.paymentType')}} - - - - - - - - -
-
- X -
-
{{$t('client.recurrent')}}O -
- -
-
{{$t('client.unique')}}
-
{{$t('client.signLocation')}}{{dated}}, {{client.province}}
{{$t('client.sign')}}
+
{{$t('client.accountNumber')}}
+
+ {{client.countryCode.substr(0, 1)}} + {{client.countryCode.substr(1, 1)}} +
- - -

{{$t('mandatoryFields')}}

-

{{$t('sendOrder')}}

- - - - - -
- - \ No newline at end of file +
+
+ {{client.countryCode.substr(0, 1)}} + {{client.countryCode.substr(1, 1)}} + +
+
+
+ {{$t('client.accountNumberFormat', [ $t(`${client.country}`), + client.ibanLength, client.countryCode ])}} + +
+
{{$t('client.paymentType')}} + + + + + + + + +
+
+ X +
+
{{$t('client.recurrent')}}O +
+ +
+
{{$t('client.unique')}}
+
{{$t('client.signLocation')}}{{dated}}, {{client.province}}
{{$t('client.sign')}}
+ + + +

{{$t('mandatoryFields')}}

+

{{$t('sendOrder')}}

+ + + + diff --git a/print/templates/reports/sepa-core/sepa-core.js b/print/templates/reports/sepa-core/sepa-core.js index 73e0beaaa3..ee8a64842f 100755 --- a/print/templates/reports/sepa-core/sepa-core.js +++ b/print/templates/reports/sepa-core/sepa-core.js @@ -1,4 +1,5 @@ const Component = require(`vn-print/core/component`); +const reportBody = new Component('report-body'); const reportHeader = new Component('report-header'); const reportFooter = new Component('report-footer'); @@ -35,17 +36,18 @@ const rptSepaCore = { } }, components: { + 'report-body': reportBody.build(), 'report-header': reportHeader.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The client id' }, companyId: { - type: [Number, String], + type: Number, required: true } } diff --git a/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.html b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.html index 1303f22663..fd6ee57254 100644 --- a/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.html +++ b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.html @@ -1,105 +1,81 @@ - - - - - - - - - -
- - - -
-
-
-
-

{{$t('title')}}

-
- - - - - - - - - - - - - - - -
{{$t('Supplier')}}{{supplier.id}}
{{$t('From')}}{{from | date('%d-%m-%Y')}}
{{$t('To')}}{{to | date('%d-%m-%Y')}}
-
-
-
-
-
{{$t('supplierData')}}
-
-

{{supplier.supplierName}}

-
- {{supplier.street}} -
-
- {{supplier.postcode}}, {{supplier.city}} ({{supplier.province}}) -
-
- {{supplier.country}} -
-
-
-
-
-
-

- {{$t('entry')}} {{entry.id}} - {{$t('dated')}} {{entry.shipped | date('%d-%m-%Y')}} - {{$t('reference')}} {{entry.ref}} -

- - - - - - - - - - - - - - - - - - - - -
{{$t('itemName')}}{{$t('Quantity')}}{{$t('price')}}{{$t('total')}}
{{buy.itemName}}{{buy.quantity}}{{buy.price | currency('EUR', $i18n.locale)}}{{buy.quantity * buy.price | currency('EUR', $i18n.locale)}}
- - {{buy.tag5}} {{buy.value5}} - - - {{buy.tag6}} {{buy.value6}} - - - {{buy.tag7}} {{buy.value7}} - -
- -
-
+ +
+
+
+
+

{{$t('title')}}

+
+ + + + + + + + + + + + + + + +
{{$t('Supplier')}}{{supplier.id}}
{{$t('From')}}{{from | date('%d-%m-%Y')}}
{{$t('To')}}{{to | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('supplierData')}}
+
+

{{supplier.supplierName}}

+
{{supplier.street}}
+
{{supplier.postcode}}, {{supplier.city}} ({{supplier.province}})
+
{{supplier.country}}
- - - -
- - \ No newline at end of file + + + +
+

+ {{$t('entry')}} {{entry.id}} + {{$t('dated')}} {{entry.shipped | date('%d-%m-%Y')}} + {{$t('reference')}} {{entry.ref}} +

+ + + + + + + + + + + + + + + + + + + + +
{{$t('itemName')}}{{$t('Quantity')}}{{$t('price')}}{{$t('total')}}
{{buy.itemName}}{{buy.quantity}}{{buy.price | currency('EUR', $i18n.locale)}}{{buy.quantity * buy.price | currency('EUR', $i18n.locale)}}
+ {{buy.tag5}} {{buy.value5}} + {{buy.tag6}} {{buy.value6}} + {{buy.tag7}} {{buy.value7}} +
+
+ + + + 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 6a58cbd0e3..f6fb4bd4e7 100755 --- a/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js +++ b/print/templates/reports/supplier-campaign-metrics/supplier-campaign-metrics.js @@ -1,5 +1,5 @@ const Component = require(`vn-print/core/component`); -const reportHeader = new Component('report-header'); +const reportBody = new Component('report-body'); const reportFooter = new Component('report-footer'); module.exports = { @@ -44,12 +44,12 @@ module.exports = { } }, components: { - 'report-header': reportHeader.build(), + 'report-body': reportBody.build(), 'report-footer': reportFooter.build() }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The supplier id' }, diff --git a/print/templates/reports/vehicle-event-expired/vehicle-event-expired.html b/print/templates/reports/vehicle-event-expired/vehicle-event-expired.html index 2045f2adbe..65776851d0 100644 --- a/print/templates/reports/vehicle-event-expired/vehicle-event-expired.html +++ b/print/templates/reports/vehicle-event-expired/vehicle-event-expired.html @@ -1,39 +1,25 @@ - - - - - - - - - -
- - - -
-
-
-

{{$t('title')}}

-
- - - - - - - - - - - - - - - -
{{$t('Plate')}}{{$t('Concept')}}{{$t('expirationDate')}}
{{vehicleEvent.numberPlate}}{{vehicleEvent.description}}{{vehicleEvent.finished | date('%d-%m-%Y')}}
-
-
-
- - \ No newline at end of file + +
+
+
+

{{$t('title')}}

+
+ + + + + + + + + + + + + + + +
{{$t('Plate')}}{{$t('Concept')}}{{$t('expirationDate')}}
{{vehicleEvent.numberPlate}}{{vehicleEvent.description}}{{vehicleEvent.finished | date('%d-%m-%Y')}}
+
+
+
diff --git a/print/templates/reports/vehicle-event-expired/vehicle-event-expired.js b/print/templates/reports/vehicle-event-expired/vehicle-event-expired.js index 33e60eab71..278caeabfd 100755 --- a/print/templates/reports/vehicle-event-expired/vehicle-event-expired.js +++ b/print/templates/reports/vehicle-event-expired/vehicle-event-expired.js @@ -1,6 +1,5 @@ const Component = require(`${appPath}/core/component`); -const reportHeader = new Component('report-header'); -const reportFooter = new Component('report-footer'); +const reportBody = new Component('report-body'); module.exports = { name: 'vehicle-event-expired', @@ -16,8 +15,7 @@ module.exports = { }, }, components: { - 'report-header': reportHeader.build(), - 'report-footer': reportFooter.build() + 'report-body': reportBody.build() }, props: { eventIds: { diff --git a/print/templates/reports/zone/zone.html b/print/templates/reports/zone/zone.html index c1e8d1ec14..54c55e1686 100644 --- a/print/templates/reports/zone/zone.html +++ b/print/templates/reports/zone/zone.html @@ -1,16 +1,5 @@ - - - - - - - - - -
-
{{zone.agencyName}}
-
{{zone.id}}
-
{{zone.plateNumber}} {{zone.time | date('%H:%M')}}
-
- - \ No newline at end of file + +
{{zone.agencyName}}
+
{{zone.id}}
+
{{zone.plateNumber}} {{zone.time | date('%H:%M')}}
+
diff --git a/print/templates/reports/zone/zone.js b/print/templates/reports/zone/zone.js index bbce9df36a..463c28acfc 100755 --- a/print/templates/reports/zone/zone.js +++ b/print/templates/reports/zone/zone.js @@ -1,3 +1,6 @@ +const Component = require(`${appPath}/core/component`); +const reportBody = new Component('report-body'); + module.exports = { name: 'zone', async serverPrefetch() { @@ -11,9 +14,12 @@ module.exports = { return this.findOneFromDef('zone', [id]); } }, + components: { + 'report-body': reportBody.build() + }, props: { id: { - type: [Number, String], + type: Number, required: true, description: 'The zone id' }