Merge branch 'dev' into 4157-send-sms-to-routes
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Pau 2022-11-15 06:09:40 +00:00
commit e6e70703a6
151 changed files with 44092 additions and 4148 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@ coverage
node_modules node_modules
dist dist
storage storage
.idea
npm-debug.log npm-debug.log
.eslintcache .eslintcache
datasources.*.json datasources.*.json

12
.vscode/settings.json vendored
View File

@ -2,7 +2,13 @@
{ {
// Carácter predeterminado de final de línea. // Carácter predeterminado de final de línea.
"files.eol": "\n", "files.eol": "\n",
"editor.codeActionsOnSave": { "editor.bracketPairColorization.enabled": true,
"source.fixAll.eslint": true "editor.guides.bracketPairs": true,
} "editor.formatOnSave": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": [
"javascript",
"json"
]
} }

View File

@ -1,32 +1,43 @@
FROM debian:stretch-slim FROM debian:bullseye-slim
ENV TZ Europe/Madrid ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
# NodeJs
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
curl \ curl \
ca-certificates \ ca-certificates \
gnupg2 \ gnupg2 \
libfontconfig lftp \ && curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \ && apt-get install -y --no-install-recommends nodejs \
libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \ && npm install -g npm@8.19.2
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 \ # Puppeteer
libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \
&& curl -sL https://deb.nodesource.com/setup_14.x | bash - \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
nodejs \ libfontconfig lftp xvfb gconf-service libasound2 libatk1.0-0 libc6 \
&& apt-get purge -y --auto-remove \ libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 \
gnupg2 \ 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/* \ && rm -rf /var/lib/apt/lists/* \
&& npm -g install pm2 && npm -g install pm2
# Salix
WORKDIR /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 package.json package-lock.json ./
COPY loopback/package.json loopback/ COPY loopback/package.json loopback/
COPY print/package.json print/ RUN npm install --omit=dev
RUN npm install --only=prod
RUN npm --prefix ./print install --only=prod ./print
COPY loopback loopback COPY loopback loopback
COPY back back COPY back back

15
Jenkinsfile vendored
View File

@ -1,5 +1,4 @@
#!/usr/bin/env groovy #!/usr/bin/env groovy
pipeline { pipeline {
agent any agent any
options { options {
@ -62,13 +61,13 @@ pipeline {
} }
} }
} }
// stage('Backend') { stage('Backend') {
// steps { steps {
// nodejs('node-v14') { nodejs('node-v14') {
// sh 'npm run test:back:ci' sh 'npm run test:back:ci'
// } }
// } }
// } }
} }
} }
stage('Build') { stage('Build') {

View File

@ -47,20 +47,22 @@ module.exports = Self => {
for (let dms of dmsToDelete) { for (let dms of dmsToDelete) {
const pathHash = DmsContainer.getHash(dms.id); const pathHash = DmsContainer.getHash(dms.id);
const dmsContainer = await DmsContainer.container(pathHash); const dmsContainer = await DmsContainer.container(pathHash);
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
try { try {
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
await fs.unlink(dstFile); await fs.unlink(dstFile);
} catch (err) { } catch (err) {
continue; if (err.code != 'ENOENT')
throw err;
} }
await dms.destroy(myOptions);
const dstFolder = path.join(dmsContainer.client.root, pathHash); const dstFolder = path.join(dmsContainer.client.root, pathHash);
try { try {
await fs.rmdir(dstFolder); await fs.rmdir(dstFolder);
} catch (err) { } catch (err) {
continue; continue;
} }
await dms.destroy(myOptions);
} }
}; };
}; };

View File

@ -1,4 +1,4 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES VALUES
('InvoiceIn', 'invoiceInPdf', 'READ', 'ALLOW', 'ROLE', 'administrative'), ('InvoiceIn', 'invoiceInPdf', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'invoiceInEmail', 'WRITE', 'ALLOW', 'ROLE', 'administrative'), ('InvoiceIn', 'invoiceInEmail', 'WRITE', 'ALLOW', 'ROLE', 'administrative');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`workerTimeControlMail` CHANGE emailResponse reason text CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL;

View File

View File

@ -47,12 +47,8 @@ module.exports = class Docker {
if (ci) network = `--network="${networkName}"`; if (ci) network = `--network="${networkName}"`;
log('Starting container...'); log('Starting container...');
const container = await this.execP(` const container = await this.execP(
docker run \ `docker run ${network} --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
${network} \
--env RUN_CHOWN=${runChown} \
-d ${dockerArgs} salix-db
`);
this.id = container.stdout.trim(); this.id = container.stdout.trim();
try { try {

View File

@ -918,7 +918,7 @@ INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`)
(3, 'Perdida', 'LOST'); (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 VALUES
(1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1, 'pc1'), (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), (2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1, NULL),
@ -2267,12 +2267,16 @@ INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `started`, `ended`)
VALUES VALUES
(9, 'range', DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR)); (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 VALUES
(1106, CONCAT(util.VN_CURDATE(), ' 07:00'), TRUE, 'in'), (1106, CONCAT(util.VN_CURDATE(), ' 07:00'), TRUE, 'in', 0),
(1106, CONCAT(util.VN_CURDATE(), ' 10:00'), TRUE, 'middle'), (1106, CONCAT(util.VN_CURDATE(), ' 10:00'), TRUE, 'middle', 0),
(1106, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle'), (1106, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle', 0),
(1106, CONCAT(util.VN_CURDATE(), ' 14:50'), TRUE, 'out'); (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`) INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `code`)
VALUES VALUES

View File

@ -27518,7 +27518,7 @@ CREATE TABLE `expedition` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`agencyModeFk` int(11) NOT NULL, `agencyModeFk` int(11) NOT NULL,
`ticketFk` int(10) 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(), `created` timestamp NULL DEFAULT current_timestamp(),
`isRefund__` bit(1) DEFAULT b'0' COMMENT 'Deprecado 01/06/2022', `isRefund__` bit(1) DEFAULT b'0' COMMENT 'Deprecado 01/06/2022',
`isPickUp__` 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', `hasNewRoute` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `index1` (`agencyModeFk`), KEY `index1` (`agencyModeFk`),
KEY `index2` (`isBox`), KEY `index2` (`freightItemFk`),
KEY `index3` (`created`), KEY `index3` (`created`),
KEY `index4` (`ticketFk`), KEY `index4` (`ticketFk`),
KEY `expedition_fk3_idx` (`packagingFk`), KEY `expedition_fk3_idx` (`packagingFk`),
@ -27567,7 +27567,7 @@ BEGIN
DECLARE vShipFk INT; DECLARE vShipFk INT;
IF NEW.isBox > 0 THEN IF NEW.freightItemFk > 0 THEN
UPDATE ticket SET packages = nz(packages) + 1 WHERE id = NEW.ticketFk; UPDATE ticket SET packages = nz(packages) + 1 WHERE id = NEW.ticketFk;
@ -27638,7 +27638,7 @@ DELIMITER ;;
BEGIN BEGIN
UPDATE ticket t UPDATE ticket t
SET packages = (SELECT COUNT(counter)-1 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; WHERE t.id = OLD.ticketFk;
END */;; END */;;
@ -36287,7 +36287,7 @@ CREATE TABLE `sorter` (
`created` datetime NOT NULL, `created` datetime NOT NULL,
`routeFk` int(10) unsigned NOT NULL, `routeFk` int(10) unsigned NOT NULL,
`ticketFk` int(10) NOT NULL, `ticketFk` int(10) NOT NULL,
`isBox` int(11) DEFAULT 1, `freightItemFk` int(11) DEFAULT 1,
`itemFk` int(11) DEFAULT NULL, `itemFk` int(11) DEFAULT NULL,
`width` decimal(10,2) DEFAULT 0.00, `width` decimal(10,2) DEFAULT 0.00,
`depth` 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 SELECT SUM((t.zonePrice - t.zoneBonus) * ebv.ratio) INTO deliveryPrice
FROM vn.ticket t FROM vn.ticket t
LEFT JOIN expedition e ON e.ticketFk = t.id 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; WHERE t.id = vTicketFk;
END IF; END IF;
@ -46492,7 +46492,7 @@ BEGIN
LEFT JOIN item i ON i.id = b.itemFk LEFT JOIN item i ON i.id = b.itemFk
LEFT JOIN itemType it ON it.id = i.typeFk LEFT JOIN itemType it ON it.id = i.typeFk
LEFT JOIN itemCategory ic ON ic.id = it.categoryFk 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 JOIN volumeConfig vc ON TRUE
WHERE b.id = vSelf; WHERE b.id = vSelf;
@ -53229,7 +53229,7 @@ BEGIN
INNER JOIN vn.ticketState ts ON ts.ticketFk = exp.ticketFk INNER JOIN vn.ticketState ts ON ts.ticketFk = exp.ticketFk
LEFT JOIN vn.address a ON t.addressFk = a.id LEFT JOIN vn.address a ON t.addressFk = a.id
LEFT JOIN vn.warehouse w ON t.warehouseFk = w.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 ;; END ;;
DELIMITER ; DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET sql_mode = @saved_sql_mode */ ;
@ -53760,7 +53760,7 @@ BEGIN
GROUP BY sub.ticketFk GROUP BY sub.ticketFk
) sub2 ON sub2.ticketFk = t.id ) sub2 ON sub2.ticketFk = t.id
LEFT JOIN expeditionStateType est ON est.id = e.stateTypeFk 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; ORDER BY r.created, t.priority DESC;
END ;; END ;;
DELIMITER ; DELIMITER ;

View File

@ -3,7 +3,7 @@
ng-transclude="prepend" ng-transclude="prepend"
class="prepend"> class="prepend">
</div> </div>
<div class="infix"> <div class="infix" >
<div class="fix prefix"></div> <div class="fix prefix"></div>
<div class="control"> <div class="control">
<section <section
@ -13,8 +13,7 @@
</section> </section>
<input <input
type="file" type="file"
accept="{{$ctrl.accept}}" accept="{{$ctrl.accept}}">
style="display: none;">
</input> </input>
</div> </div>
<div class="fix suffix"></div> <div class="fix suffix"></div>

View File

@ -4,4 +4,13 @@
.value { .value {
cursor: pointer; cursor: pointer;
} }
.control {
& > input[type=file] {
opacity: 0;
}
& > section {
position: absolute;
bottom: 0;
}
}
} }

View File

@ -24,10 +24,11 @@
} }
}, },
"node_modules/@uirouter/angularjs": { "node_modules/@uirouter/angularjs": {
"version": "1.0.29", "version": "1.0.30",
"license": "MIT", "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz",
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
"dependencies": { "dependencies": {
"@uirouter/core": "6.0.7" "@uirouter/core": "6.0.8"
}, },
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
@ -37,15 +38,18 @@
} }
}, },
"node_modules/@uirouter/core": { "node_modules/@uirouter/core": {
"version": "6.0.7", "version": "6.0.8",
"license": "MIT", "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz",
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw==",
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/angular": { "node_modules/angular": {
"version": "1.8.2", "version": "1.8.3",
"license": "MIT" "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": { "node_modules/angular-animate": {
"version": "1.8.2", "version": "1.8.2",
@ -62,8 +66,9 @@
} }
}, },
"node_modules/angular-translate": { "node_modules/angular-translate": {
"version": "2.18.4", "version": "2.19.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz",
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
"dependencies": { "dependencies": {
"angular": "^1.8.0" "angular": "^1.8.0"
}, },
@ -72,10 +77,11 @@
} }
}, },
"node_modules/angular-translate-loader-partial": { "node_modules/angular-translate-loader-partial": {
"version": "2.18.4", "version": "2.19.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz",
"integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==",
"dependencies": { "dependencies": {
"angular-translate": "~2.18.4" "angular-translate": "~2.19.0"
} }
}, },
"node_modules/argparse": { "node_modules/argparse": {
@ -119,8 +125,9 @@
} }
}, },
"node_modules/moment": { "node_modules/moment": {
"version": "2.29.1", "version": "2.29.4",
"license": "MIT", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": { "engines": {
"node": "*" "node": "*"
} }
@ -164,16 +171,22 @@
}, },
"dependencies": { "dependencies": {
"@uirouter/angularjs": { "@uirouter/angularjs": {
"version": "1.0.29", "version": "1.0.30",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz",
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
"requires": { "requires": {
"@uirouter/core": "6.0.7" "@uirouter/core": "6.0.8"
} }
}, },
"@uirouter/core": { "@uirouter/core": {
"version": "6.0.7" "version": "6.0.8",
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz",
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw=="
}, },
"angular": { "angular": {
"version": "1.8.2" "version": "1.8.3",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
}, },
"angular-animate": { "angular-animate": {
"version": "1.8.2" "version": "1.8.2"
@ -185,15 +198,19 @@
} }
}, },
"angular-translate": { "angular-translate": {
"version": "2.18.4", "version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz",
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
"requires": { "requires": {
"angular": "^1.8.0" "angular": "^1.8.0"
} }
}, },
"angular-translate-loader-partial": { "angular-translate-loader-partial": {
"version": "2.18.4", "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": { "requires": {
"angular-translate": "~2.18.4" "angular-translate": "~2.19.0"
} }
}, },
"argparse": { "argparse": {
@ -222,7 +239,9 @@
} }
}, },
"moment": { "moment": {
"version": "2.29.1" "version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
}, },
"oclazyload": { "oclazyload": {
"version": "0.6.3" "version": "0.6.3"

View File

@ -153,6 +153,7 @@
"Email already exists": "Email already exists", "Email already exists": "Email already exists",
"User already exists": "User already exists", "User already exists": "User already exists",
"Absence change notification on the labour calendar": "Notificacion de cambio de ausencia en el calendario laboral", "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 <strong>{{author}}</strong> ha añadido una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> para el día {{dated}}.", "Created absence": "El empleado <strong>{{author}}</strong> ha añadido una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> para el día {{dated}}.",
"Deleted absence": "El empleado <strong>{{author}}</strong> ha eliminado una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> del día {{dated}}.", "Deleted absence": "El empleado <strong>{{author}}</strong> ha eliminado una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> del día {{dated}}.",
"I have deleted the ticket id": "He eliminado el ticket id [{{id}}]({{{url}}})", "I have deleted the ticket id": "He eliminado el ticket id [{{id}}]({{{url}}})",
@ -237,7 +238,10 @@
"Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o 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", "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente",
"This route does not exists": "Esta ruta no existe", "This route does not exists": "Esta ruta no existe",
"Claim pickup order sent": "Reclamación Orden de recogida enviada [({{claimId}})]({{{claimUrl}}}) al cliente *{{clientName}}*", "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 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" "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"
} }

View File

@ -91,7 +91,7 @@
url="Packagings" url="Packagings"
show-field="id" show-field="id"
value-field="id" value-field="id"
where="{isBox: true}" where="{freightItemFk: true}"
ng-model="buy.packageFk" ng-model="buy.packageFk"
on-change="$ctrl.saveBuy(buy)"> on-change="$ctrl.saveBuy(buy)">
</vn-autocomplete> </vn-autocomplete>

View File

@ -32,7 +32,7 @@ module.exports = Self => {
`SELECT `SELECT
e.id, e.id,
e.ticketFk, e.ticketFk,
e.isBox, e.freightItemFk,
e.workerFk, e.workerFk,
i1.name packageItemName, i1.name packageItemName,
e.counter, e.counter,
@ -51,7 +51,7 @@ module.exports = Self => {
FROM vn.expedition e FROM vn.expedition e
LEFT JOIN vn.expeditionStateType est ON est.id = e.stateTypeFk LEFT JOIN vn.expeditionStateType est ON est.id = e.stateTypeFk
LEFT JOIN vn.item i2 ON i2.id = e.itemFk 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.packaging p ON p.id = e.packagingFk
LEFT JOIN vn.item i3 ON i3.id = p.itemFk LEFT JOIN vn.item i3 ON i3.id = p.itemFk
LEFT JOIN account.user u ON u.id = e.workerFk LEFT JOIN account.user u ON u.id = e.workerFk

View File

@ -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"`];
};
};

View File

@ -16,7 +16,7 @@
"type": "number", "type": "number",
"description": "Identifier" "description": "Identifier"
}, },
"isBox": { "freightItemFk": {
"type": "number" "type": "number"
}, },
"created": { "created": {
@ -55,7 +55,7 @@
"freightItem": { "freightItem": {
"type": "belongsTo", "type": "belongsTo",
"model": "Item", "model": "Item",
"foreignKey": "isBox" "foreignKey": "freightItemFk"
}, },
"packaging": { "packaging": {
"type": "belongsTo", "type": "belongsTo",

View File

@ -33,4 +33,5 @@ module.exports = function(Self) {
require('../methods/ticket/closeByTicket')(Self); require('../methods/ticket/closeByTicket')(Self);
require('../methods/ticket/closeByAgency')(Self); require('../methods/ticket/closeByAgency')(Self);
require('../methods/ticket/closeByRoute')(Self); require('../methods/ticket/closeByRoute')(Self);
require('../methods/ticket/collectionLabel')(Self);
}; };

View File

@ -1,181 +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);
if (bodyPositionOK != null && (bodyPositionOK.index == 0 || bodyPositionOK.index == 122))
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
});
}
};

View File

@ -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)];
}
};

View File

@ -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;
});
});

View File

@ -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);
};
};

View File

@ -20,6 +20,12 @@
"EducationLevel": { "EducationLevel": {
"dataSource": "vn" "dataSource": "vn"
}, },
"Journey": {
"dataSource": "vn"
},
"Time": {
"dataSource": "vn"
},
"WorkCenter": { "WorkCenter": {
"dataSource": "vn" "dataSource": "vn"
}, },
@ -59,6 +65,9 @@
"WorkerLog": { "WorkerLog": {
"dataSource": "vn" "dataSource": "vn"
}, },
"WorkerTimeControlConfig": {
"dataSource": "vn"
},
"WorkerTimeControlParams": { "WorkerTimeControlParams": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -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"
}
}
}

View File

@ -0,0 +1,21 @@
{
"name": "Time",
"base": "VnModel",
"options": {
"mysql": {
"table": "time"
}
},
"properties": {
"dated": {
"id": true,
"type": "date"
},
"year": {
"type": "number"
},
"week": {
"type": "number"
}
}
}

View File

@ -0,0 +1,18 @@
{
"name": "WorkerTimeControlConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "workerTimeControlConfig"
}
},
"properties": {
"id": {
"id": true,
"type": "number"
},
"timeToBreakTime": {
"type": "number"
}
}
}

View File

@ -1,3 +0,0 @@
module.exports = Self => {
require('../methods/worker-time-control-mail/checkInbox')(Self);
};

View File

@ -9,8 +9,7 @@
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,
"type": "number", "type": "number"
"required": true
}, },
"workerFk": { "workerFk": {
"type": "number" "type": "number"
@ -27,7 +26,7 @@
"updated": { "updated": {
"type": "date" "type": "date"
}, },
"emailResponse": { "reason": {
"type": "string" "type": "string"
} }
}, },

View File

@ -5,6 +5,8 @@ module.exports = Self => {
require('../methods/worker-time-control/addTimeEntry')(Self); require('../methods/worker-time-control/addTimeEntry')(Self);
require('../methods/worker-time-control/deleteTimeEntry')(Self); require('../methods/worker-time-control/deleteTimeEntry')(Self);
require('../methods/worker-time-control/updateTimeEntry')(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) { Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY') if (err.code === 'ER_DUP_ENTRY')

View File

@ -22,6 +22,9 @@
}, },
"direction": { "direction": {
"type": "string" "type": "string"
},
"isSendMail": {
"type": "boolean"
} }
}, },
"relations": { "relations": {

View File

@ -77,6 +77,18 @@
</vn-tfoot> </vn-tfoot>
</vn-table> </vn-table>
</vn-card> </vn-card>
<vn-button-bar class="vn-pa-xs vn-w-lg">
<vn-button
label="Satisfied"
ng-click="$ctrl.isSatisfied()">
</vn-button>
<vn-button
label="Not satisfied"
ng-click="reason.show()">
</vn-button>
</vn-button-bar>
<vn-side-menu side="right"> <vn-side-menu side="right">
<div class="vn-pa-md"> <div class="vn-pa-md">
<div class="totalBox" style="text-align: center;"> <div class="totalBox" style="text-align: center;">
@ -149,3 +161,20 @@
</vn-icon-button> </vn-icon-button>
</vn-horizontal> </vn-horizontal>
</vn-popover> </vn-popover>
<vn-dialog
vn-id="reason"
on-accept="$ctrl.isUnsatisfied()">
<tpl-body>
<vn-textarea
label="Reason"
ng-model="$ctrl.reason"
required="true"
rows="3">
</vn-textarea>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>

View File

@ -294,6 +294,42 @@ class Controller extends Section {
this.$.editEntry.show($event); this.$.editEntry.show($event);
} }
getWeekNumber(currentDate) {
const startDate = new Date(currentDate.getFullYear(), 0, 1);
let days = Math.floor((currentDate - startDate) /
(24 * 60 * 60 * 1000));
return Math.ceil(days / 7);
}
isSatisfied() {
const weekNumber = this.getWeekNumber(this.date);
const params = {
workerId: this.worker.id,
year: this.date.getFullYear(),
week: weekNumber,
state: 'CONFIRMED'
};
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
this.$http.post(query, params).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
isUnsatisfied() {
const weekNumber = this.getWeekNumber(this.date);
const params = {
workerId: this.worker.id,
year: this.date.getFullYear(),
week: weekNumber,
state: 'REVISE',
reason: this.reason
};
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
this.$http.post(query, params).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
save() { save() {
try { try {
const entry = this.selectedRow; const entry = this.selectedRow;

View File

@ -11,3 +11,6 @@ Are you sure you want to delete this entry?: ¿Seguro que quieres eliminarla?
Finish at: Termina a las Finish at: Termina a las
Entry removed: Fichada borrada Entry removed: Fichada borrada
The entry type can't be empty: El tipo de fichada no puede quedar vacía The entry type can't be empty: El tipo de fichada no puede quedar vacía
Satisfied: Conforme
Not satisfied: No conforme
Reason: Motivo

40261
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,7 @@
"puppeteer": "^18.0.5", "puppeteer": "^18.0.5",
"read-chunk": "^3.2.0", "read-chunk": "^3.2.0",
"require-yaml": "0.0.1", "require-yaml": "0.0.1",
"sharp": "^0.31.0", "sharp": "^0.31.2",
"smbhash": "0.0.1", "smbhash": "0.0.1",
"strong-error-handler": "^2.3.2", "strong-error-handler": "^2.3.2",
"uuid": "^3.3.3", "uuid": "^3.3.3",
@ -80,7 +80,7 @@
"html-loader-jest": "^0.2.1", "html-loader-jest": "^0.2.1",
"html-webpack-plugin": "^4.0.0-beta.11", "html-webpack-plugin": "^4.0.0-beta.11",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jasmine": "^4.1.0", "jasmine": "^4.5.0",
"jasmine-reporters": "^2.4.0", "jasmine-reporters": "^2.4.0",
"jasmine-spec-reporter": "^7.0.0", "jasmine-spec-reporter": "^7.0.0",
"jest": "^26.0.1", "jest": "^26.0.1",

View File

@ -0,0 +1,11 @@
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/email.css`])
.mergeStyles();

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html v-bind:lang="$i18n.locale">
<head>
<meta name="viewport" content="width=device-width" />
<meta name="format-detection" content="telephone=no" />
</head>
<body>
<table class="grid">
<tbody>
<tr>
<td>
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<slot name="header">
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
</slot>
<slot></slot>
<slot name="footer">
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
</slot>
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,12 @@
const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
module.exports = {
components: {
'email-header': emailHeader.build(),
'email-footer': emailFooter.build()
},
name: 'email-body',
};

View File

@ -3,7 +3,7 @@
<div class="buttons"> <div class="buttons">
<div class="columns"> <div class="columns">
<div class="size50"> <div class="size50">
<a href="https://www.verdnatura.es" target="_blank"> <a href="https://verdnatura.es" target="_blank">
<div class="btn"> <div class="btn">
<!-- <span class="icon vn-pa-sm"><img v-bind:src="getEmailSrc('action.png')"/></span> --> <!-- <span class="icon vn-pa-sm"><img v-bind:src="getEmailSrc('action.png')"/></span> -->
<span class="text vn-pa-sm">{{ $t('buttons.webAcccess')}}</span> <span class="text vn-pa-sm">{{ $t('buttons.webAcccess')}}</span>

View File

@ -2,7 +2,7 @@ buttons:
webAcccess: Visit our website webAcccess: Visit our website
info: Help us to improve info: Help us to improve
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
· www.verdnatura.es · clientes@verdnatura.es · verdnatura.es · clientes@verdnatura.es
disclaimer: '- NOTICE - This message is private and confidential, and should be used disclaimer: '- NOTICE - This message is private and confidential, and should be used
exclusively by the person receiving it. If you have received this message by mistake, exclusively by the person receiving it. If you have received this message by mistake,
please notify the sender and delete that message and any attached documents that it may contain. please notify the sender and delete that message and any attached documents that it may contain.

View File

@ -2,7 +2,7 @@ buttons:
webAcccess: Visita nuestra Web webAcccess: Visita nuestra Web
info: Ayúdanos a mejorar info: Ayúdanos a mejorar
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
· www.verdnatura.es · clientes@verdnatura.es · verdnatura.es · clientes@verdnatura.es
disclaimer: '- AVISO - Este mensaje es privado y confidencial, y debe ser utilizado disclaimer: '- AVISO - Este mensaje es privado y confidencial, y debe ser utilizado
exclusivamente por la persona destinataria del mismo. Si has recibido este mensaje exclusivamente por la persona destinataria del mismo. Si has recibido este mensaje
por error, te rogamos lo comuniques al remitente y borres dicho mensaje y cualquier por error, te rogamos lo comuniques al remitente y borres dicho mensaje y cualquier

View File

@ -2,7 +2,7 @@ buttons:
webAcccess: Visitez notre site web webAcccess: Visitez notre site web
info: Aidez-nous à améliorer info: Aidez-nous à améliorer
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
· www.verdnatura.es · clientes@verdnatura.es · verdnatura.es · clientes@verdnatura.es
disclaimer: "- AVIS - Ce message est privé et confidentiel et doit être utilisé disclaimer: "- AVIS - Ce message est privé et confidentiel et doit être utilisé
exclusivement par le destinataire. Si vous avez reçu ce message par erreur, exclusivement par le destinataire. Si vous avez reçu ce message par erreur,
veuillez en informer l'expéditeur et supprimer ce message ainsi que tous les veuillez en informer l'expéditeur et supprimer ce message ainsi que tous les

View File

@ -2,7 +2,7 @@ buttons:
webAcccess: Visite o nosso site webAcccess: Visite o nosso site
info: Ajude-nos a melhorar info: Ajude-nos a melhorar
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
· www.verdnatura.es · clientes@verdnatura.es · verdnatura.es · clientes@verdnatura.es
disclaimer: '- AVISO - Esta mensagem é privada e confidencial e deve ser usada exclusivamente disclaimer: '- AVISO - Esta mensagem é privada e confidencial e deve ser usada exclusivamente
pela pessoa que a recebe. Se você recebeu esta mensagem por engano, notifique o remetente e pela pessoa que a recebe. Se você recebeu esta mensagem por engano, notifique o remetente e
exclua essa mensagem e todos os documentos anexos que ela possa conter. exclua essa mensagem e todos os documentos anexos que ela possa conter.

View File

@ -1,7 +1,7 @@
<header> <header>
<div class="logo"> <div class="logo">
<a href="https://www.verdnatura.es" target="_blank"> <a href="https://verdnatura.es" target="_blank">
<img v-bind:src="getEmailSrc('logo-black.png')" alt="VerdNatura"/> <img v-bind:src="getEmailSrc('logo-black.png')" alt="VerdNatura" />
</a> </a>
</div> </div>
<div class="topbar"></div> <div class="topbar"></div>

View File

@ -0,0 +1,10 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/report.css`,
`${vnPrintPath}/common/css/misc.css`])
.mergeStyles();

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html v-bind:lang="$i18n.locale">
<body>
<table class="grid">
<tbody>
<tr>
<td>
<slot name="header">
<report-header v-bind="$props"></report-header>
</slot>
<slot></slot>
<slot name="footer">
<report-footer id="pageFooter" v-bind="$props"></report-footer>
</slot>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,12 @@
const Component = require(`vn-print/core/component`);
const reportHeader = new Component('report-header');
const reportFooter = new Component('report-footer');
module.exports = {
name: 'report-body',
components: {
'report-header': reportHeader.build(),
'report-footer': reportFooter.build()
},
};

View File

@ -1,2 +1,2 @@
company: company:
contactData: www.verdnatura.es - clientes@verdnatura.es contactData: verdnatura.es - clientes@verdnatura.es

View File

@ -1,2 +1,2 @@
company: company:
contactData: www.verdnatura.es - clientes@verdnatura.es contactData: verdnatura.es - clientes@verdnatura.es

View File

@ -1,2 +1,2 @@
company: company:
contactData: www.verdnatura.es - clientes@verdnatura.es contactData: verdnatura.es - clientes@verdnatura.es

View File

@ -1,2 +1,2 @@
company: company:
contactData: · www.verdnatura.es · clientes@verdnatura.es contactData: · verdnatura.es · clientes@verdnatura.es

View File

@ -32,7 +32,7 @@ class Email extends Component {
const rendered = await this.render(); const rendered = await this.render();
const attachments = []; const attachments = [];
const getAttachments = async(componentPath, files) => { const getAttachments = async(componentPath, files) => {
for (file of files) { for (const file of files) {
const fileCopy = Object.assign({}, file); const fileCopy = Object.assign({}, file);
const fileName = fileCopy.filename; const fileName = fileCopy.filename;
@ -54,15 +54,22 @@ class Email extends Component {
} }
}; };
if (instance.components) { async function getSubcomponentAttachments(instance) {
const components = instance.components; if (instance.components) {
for (let componentName in components) { const components = instance.components;
const component = components[componentName]; for (let componentName in components) {
const componentPath = `./components/${componentName}`; const component = components[componentName];
await getAttachments(componentPath, component.attachments); const componentPath = `./components/${componentName}`;
await getAttachments(componentPath, component.attachments);
if (component.components)
await getSubcomponentAttachments(component)
}
} }
} }
await getSubcomponentAttachments(instance)
if (this.attachments) if (this.attachments)
await getAttachments(this.path, this.attachments); await getAttachments(this.path, this.attachments);

View File

@ -1,57 +1,17 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <p>{{$t('description.instructions')}} {{client.name}}</p>
<meta name="format-detection" content="telephone=no"> <p>{{$t('description.attached')}}</p>
<title v-html="$t('subject')"></title> <p>{{$t('description.response')}}</p>
</head> <p>{{$t('description.regards')}}</p>
<body> </div>
<table class="grid"> </div>
<tbody> <div class="grid-row" v-if="isPreview">
<tr> <div class="grid-block vn-pa-ml">
<td> <attachment v-for="attachment in attachments" v-bind:key="attachment.filename"
<!-- Empty block --> v-bind:attachment="attachment" v-bind:args="$props">
<div class="grid-row"> </attachment>
<div class="grid-block empty"></div> </div>
</div> </div>
<!-- Header block --> </email-body>
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<p>{{$t('description.instructions')}} {{client.name}}</p>
<p>{{$t('description.attached')}}</p>
<p>{{$t('description.response')}}</p>
<p>{{$t('description.regards')}}</p>
</div>
</div>
<!-- Preview block -->
<div class="grid-row" v-if="isPreview">
<div class="grid-block vn-pa-ml">
<attachment v-for="attachment in attachments"
v-bind:key="attachment.filename"
v-bind:attachment="attachment"
v-bind:args="$props">
</attachment>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,9 +1,7 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment'); const attachment = new Component('attachment');
module.exports = { module.exports = {
name: 'balance-compensation', name: 'balance-compensation',
async serverPrefetch() { async serverPrefetch() {
@ -15,8 +13,7 @@ module.exports = {
}, },
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build(),
'attachment': attachment.build() 'attachment': attachment.build()
}, },
data() { data() {

View File

@ -1,76 +1,37 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description', [dated])"></p>
</head> </div>
<body> </div>
<table class="grid"> <div class="grid-row">
<tbody> <div class="grid-block vn-pa-ml">
<tr> <table class="column-oriented">
<td> <thead>
<!-- Empty block --> <tr>
<div class="grid-row"> <th>{{$t('buyer')}}</th>
<div class="grid-block empty"></div> <th class="number">{{$t('percentage')}}</th>
</div> <th class="number">{{$t('dwindle')}}</th>
<!-- Header block --> <th class="number">{{$t('total')}}</th>
<div class="grid-row"> </tr>
<div class="grid-block"> </thead>
<email-header v-bind="$props"></email-header> <tbody>
</div> <tr v-for="waste in wastes" v-bind:key="waste.buyer">
</div> <td class="font gray">{{waste.buyer}}</td>
<!-- Block --> <td class="number">{{(waste.percentage / 100) | percentage(2, 2, $i18n.locale)}}</td>
<div class="grid-row"> <td class="number">{{waste.dwindle | currency('EUR', $i18n.locale)}}</td>
<div class="grid-block vn-pa-ml"> <td class="number">{{waste.total | currency('EUR', $i18n.locale)}}</td>
<h1>{{ $t('title') }}</h1> </tr>
<p>{{$t('dear')}},</p> </tbody>
<p v-html="$t('description', [dated])"></p> </table>
</div> <p v-html="$t('wasteDetailLink')"></p>
</div> <div class="external-link vn-pa-sm vn-m-md">
<!-- Block --> <a href="https://salix.verdnatura.es/#!/item/waste/index" target="_blank">
<div class="grid-row"> https://salix.verdnatura.es/#!/item/waste/index
<div class="grid-block vn-pa-ml"> </a>
<table class="column-oriented"> </div>
<thead> </div>
<tr> </div>
<th>{{$t('buyer')}}</th> </email-body>
<th class="number">{{$t('percentage')}}</th>
<th class="number">{{$t('dwindle')}}</th>
<th class="number">{{$t('total')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="waste in wastes" v-bind:key="waste.buyer">
<td class="font gray">{{waste.buyer}}</td>
<td class="number">{{(waste.percentage / 100) | percentage(2, 2, $i18n.locale)}}</td>
<td class="number">{{waste.dwindle | currency('EUR', $i18n.locale)}}</td>
<td class="number">{{waste.total | currency('EUR', $i18n.locale)}}</td>
</tr>
</tbody>
</table>
<p v-html="$t('wasteDetailLink')"></p>
<div class="external-link vn-pa-sm vn-m-md">
<a href="https://salix.verdnatura.es/#!/item/waste/index" target="_blank">
https://salix.verdnatura.es/#!/item/waste/index
</a>
</div>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'buyer-week-waste', name: 'buyer-week-waste',
@ -23,8 +22,7 @@ module.exports = {
} }
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build()
'email-footer': emailFooter.build()
}, },
props: {} props: {}
}; };

View File

@ -1,46 +1,9 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description', [minDate, maxDate])"></p>
</head> </div>
<body> </div>
<table class="grid"> </email-body>
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}},</p>
<p v-html="$t('description', [minDate, maxDate])"></p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'campaign-metrics', name: 'campaign-metrics',
@ -16,8 +15,7 @@ module.exports = {
} }
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: { props: {
id: { id: {

View File

@ -1,47 +1,10 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title', [id]) }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{ $t('description.dear') }},</p>
<title>{{ $t('subject') }}</title> <p v-html="instructions"></p>
</head> <p>{{ $t('description.conclusion') }}</p>
<body> </div>
<table class="grid"> </div>
<tbody> </email-body>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title', [id]) }}</h1>
<p>{{ $t('description.dear') }},</p>
<p v-html="instructions"></p>
<p>{{ $t('description.conclusion') }}</p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,12 +1,10 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'claim-pickup-order', name: 'claim-pickup-order',
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
async serverPrefetch() { async serverPrefetch() {
this.ticket = await this.fetchTicket(this.id); this.ticket = await this.fetchTicket(this.id);

View File

@ -0,0 +1,6 @@
[
{
"filename": "client-debt-statement.pdf",
"component": "client-debt-statement"
}
]

View File

@ -1,55 +1,15 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('description.instructions')}}</p>
<title>{{ $t('subject') }}</title> </div>
</head> </div>
<body> <div class="grid-row" v-if="isPreview">
<table class="grid"> <div class="grid-block vn-pa-ml">
<tbody> <attachment v-for="attachment in attachments" v-bind:key="attachment.filename"
<tr> v-bind:attachment="attachment" v-bind:args="$props">
<td> </attachment>
<!-- Empty block --> </div>
<div class="grid-row"> </div>
<div class="grid-block empty"></div> </email-body>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('description.instructions')}}</p>
</div>
</div>
<!-- Preview block -->
<div class="grid-row" v-if="isPreview">
<div class="grid-block vn-pa-ml">
<attachment v-for="attachment in attachments"
v-bind:key="attachment.filename"
v-bind:attachment="attachment"
v-bind:args="$props">
</attachment>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,13 +1,11 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment'); const attachment = new Component('attachment');
module.exports = { module.exports = {
name: 'client-debt-statement', name: 'client-debt-statement',
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build(),
'attachment': attachment.build() 'attachment': attachment.build()
}, },
data() { data() {

View File

@ -1,92 +1,56 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dearClient')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('clientData')"></p>
</head>
<body>
<table class="grid">
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dearClient')}},</p>
<p v-html="$t('clientData')"></p>
<p> <p>
<div>{{$t('clientId')}}: <strong>{{client.id}}</strong></div> <div>{{$t('clientId')}}: <strong>{{client.id}}</strong></div>
<div>{{$t('user')}}: <strong>{{client.userName}}</strong></div> <div>{{$t('user')}}: <strong>{{client.userName}}</strong></div>
<div>{{$t('password')}}: <strong>********</strong> <div>{{$t('password')}}: <strong>********</strong>
(<a href="https://verdnatura.es">{{$t('passwordResetText')}}</a>) (<a href="https://verdnatura.es">{{$t('passwordResetText')}}</a>)
</div> </div>
</p> </p>
<h1>{{$t('sections.howToBuy.title')}}</h1> <h1>{{$t('sections.howToBuy.title')}}</h1>
<p>{{$t('sections.howToBuy.description')}}</p> <p>{{$t('sections.howToBuy.description')}}</p>
<ol> <ol>
<li v-for="requeriment in $t('sections.howToBuy.requeriments')"> <li v-for="requeriment in $t('sections.howToBuy.requeriments')">
<span v-html="requeriment"></span> <span v-html="requeriment"></span>
</li> </li>
</ol> </ol>
<p>{{$t('sections.howToBuy.stock')}}</p> <p>{{$t('sections.howToBuy.stock')}}</p>
<p>{{$t('sections.howToBuy.delivery')}}</p> <p>{{$t('sections.howToBuy.delivery')}}</p>
<h1>{{$t('sections.howToPay.title')}}</h1> <h1>{{$t('sections.howToPay.title')}}</h1>
<p>{{$t('sections.howToPay.description')}}</p> <p>{{$t('sections.howToPay.description')}}</p>
<ul> <ul>
<li v-for="option in $t('sections.howToPay.options')"> <li v-for="option in $t('sections.howToPay.options')">
<span v-html="option"></span> <span v-html="option"></span>
</li> </li>
</ul> </ul>
<h1>{{$t('sections.toConsider.title')}}</h1> <h1>{{$t('sections.toConsider.title')}}</h1>
<p>{{$t('sections.toConsider.description')}}</p> <p>{{$t('sections.toConsider.description')}}</p>
<h4>{{$t('sections.claimsPolicy.title')}}</h4> <h4>{{$t('sections.claimsPolicy.title')}}</h4>
<p>{{$t('sections.claimsPolicy.description')}}</p> <p>{{$t('sections.claimsPolicy.description')}}</p>
<p v-html="$t('help')"></p> <p v-html="$t('help')"></p>
<p> <p>
<section v-if="client.salesPersonName"> <section v-if="client.salesPersonName">
{{$t('salesPersonName')}}: <strong>{{client.salesPersonName}}</strong> {{$t('salesPersonName')}}: <strong>{{client.salesPersonName}}</strong>
</section> </section>
<section v-if="client.salesPersonPhone"> <section v-if="client.salesPersonPhone">
{{$t('salesPersonPhone')}}: <strong>{{client.salesPersonPhone}}</strong> {{$t('salesPersonPhone')}}: <strong>{{client.salesPersonPhone}}</strong>
</section> </section>
<section v-if="client.salesPersonEmail"> <section v-if="client.salesPersonEmail">
{{$t('salesPersonEmail')}}: {{$t('salesPersonEmail')}}:
<strong><a v-bind:href="`mailto: ${client.salesPersonEmail}`" target="_blank">{{client.salesPersonEmail}}</strong> <strong><a v-bind:href="`mailto: ${client.salesPersonEmail}`"
</section> target="_blank">{{client.salesPersonEmail}}</strong>
</p> </section>
</div> </p>
</div> </div>
<!-- Footer block --> </div>
<div class="grid-row"> </email-body>
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'client-welcome', name: 'client-welcome',
@ -13,8 +12,7 @@ module.exports = {
}, },
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: { props: {
id: { id: {

View File

@ -1,8 +1,8 @@
subject: Bienvenido a Verdnatura subject: Bienvenido a Verdnatura
title: "¡Te damos la bienvenida!" title: "¡Te damos la bienvenida!"
dearClient: Estimado cliente dearClient: Estimado cliente
clientData: 'Tus datos para poder comprar en la web de Verdnatura (<a href="https://www.verdnatura.es" clientData: 'Tus datos para poder comprar en la web de Verdnatura (<a href="https://verdnatura.es"
title="Visitar Verdnatura" target="_blank" style="color: #8dba25">https://www.verdnatura.es</a>) title="Visitar Verdnatura" target="_blank" style="color: #8dba25">https://verdnatura.es</a>)
o en nuestras aplicaciones para <a href="https://goo.gl/3hC2mG" title="App Store" o en nuestras aplicaciones para <a href="https://goo.gl/3hC2mG" title="App Store"
target="_blank" style="color: #8dba25">iOS</a> y <a href="https://goo.gl/8obvLc" target="_blank" style="color: #8dba25">iOS</a> y <a href="https://goo.gl/8obvLc"
title="Google Play" target="_blank" style="color: #8dba25">Android</a>, son' title="Google Play" target="_blank" style="color: #8dba25">Android</a>, son'

View File

@ -1,55 +1,15 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('description.instructions')}}</p>
<title>{{ $t('subject') }}</title> </div>
</head> </div>
<body> <div class="grid-row" v-if="isPreview">
<table class="grid"> <div class="grid-block vn-pa-ml">
<tbody> <attachment v-for="attachment in attachments" v-bind:key="attachment.filename"
<tr> v-bind:attachment="attachment" v-bind:args="$props">
<td> </attachment>
<!-- Empty block --> </div>
<div class="grid-row"> </div>
<div class="grid-block empty"></div> </email-body>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('description.instructions')}}</p>
</div>
</div>
<!-- Preview block -->
<div class="grid-row" v-if="isPreview">
<div class="grid-block vn-pa-ml">
<attachment v-for="attachment in attachments"
v-bind:key="attachment.filename"
v-bind:attachment="attachment"
v-bind:args="$props">
</attachment>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,13 +1,11 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment'); const attachment = new Component('attachment');
module.exports = { module.exports = {
name: 'credit-request', name: 'credit-request',
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build(),
'attachment': attachment.build() 'attachment': attachment.build()
}, },
data() { data() {

View File

@ -3,3 +3,7 @@
border-radius: 3px; border-radius: 3px;
text-align: center text-align: center
} }
a {
color: #8dba25
}

View File

@ -1,69 +1,24 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}}</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description', [id])"></p>
<style type="text/css"> </div>
a { </div>
color: #8dba25 <div class="grid-row">
} <div class="grid-block vn-px-ml">
</style> <p>{{$t('copyLink')}}</p>
</head> <div class="external-link vn-pa-sm vn-m-md">
<body> https://shop.verdnatura.es/#!form=ecomerce/ticket&ticket={{id}}
<table class="grid"> </div>
<tbody> </div>
<tr> </div>
<td> <div class="grid-row">
<!-- Empty block --> <div class="grid-block vn-pa-ml">
<div class="grid-row"> <p v-html="$t('poll')"></p>
<div class="grid-block empty"></div> <p v-html="$t('help')"></p>
</div> <p v-html="$t('conclusion')"></p>
<!-- Header block --> </div>
<div class="grid-row"> </div>
<div class="grid-block"> </email-body>
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}}</p>
<p v-html="$t('description', [id])"></p>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-px-ml">
<p>{{$t('copyLink')}}</p>
<div class="external-link vn-pa-sm vn-m-md">
https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={{id}}
</div>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<p v-html="$t('poll')"></p>
<p v-html="$t('help')"></p>
<p v-html="$t('conclusion')"></p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,12 +1,10 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'delivery-note-link', name: 'delivery-note-link',
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: { props: {
id: { id: {

View File

@ -2,7 +2,7 @@ subject: Your delivery note
title: Your delivery note title: Your delivery note
dear: Dear client dear: Dear client
description: The delivery note from the order <strong>{0}</strong> is now available. <br/> description: The delivery note from the order <strong>{0}</strong> is now available. <br/>
You can download it by clicking <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">this link</a>. You can download it by clicking <a href="https://shop.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">this link</a>.
copyLink: 'As an alternative, you can copy the following link in your browser:' copyLink: 'As an alternative, you can copy the following link in your browser:'
poll: If you wish, you can answer our satisfaction survey to poll: If you wish, you can answer our satisfaction survey to
help us provide better service. Your opinion is very important for us! help us provide better service. Your opinion is very important for us!

View File

@ -2,7 +2,7 @@ subject: Tu albarán
title: Tu albarán title: Tu albarán
dear: Estimado cliente dear: Estimado cliente
description: Ya está disponible el albarán correspondiente al pedido <strong>{0}</strong>. <br/> description: Ya está disponible el albarán correspondiente al pedido <strong>{0}</strong>. <br/>
Puedes verlo haciendo clic <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">en este enlace</a>. Puedes verlo haciendo clic <a href="https://shop.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">en este enlace</a>.
copyLink: 'Como alternativa, puedes copiar el siguiente enlace en tu navegador:' copyLink: 'Como alternativa, puedes copiar el siguiente enlace en tu navegador:'
poll: Si lo deseas, puedes responder a nuestra encuesta de satisfacción para poll: Si lo deseas, puedes responder a nuestra encuesta de satisfacción para
ayudarnos a prestar un mejor servicio. ¡Tu opinión es muy importante para nosotros! ayudarnos a prestar un mejor servicio. ¡Tu opinión es muy importante para nosotros!

View File

@ -2,7 +2,7 @@ subject: Votre bon de livraison
title: Votre bon de livraison title: Votre bon de livraison
dear: Cher client, dear: Cher client,
description: Le bon de livraison correspondant à la commande <strong>{0}</strong> est maintenant disponible.<br/> description: Le bon de livraison correspondant à la commande <strong>{0}</strong> est maintenant disponible.<br/>
Vous pouvez le voir en cliquant <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}" target="_blank">sur ce lien</a>. Vous pouvez le voir en cliquant <a href="https://shop.verdnatura.es/#!form=ecomerce/ticket&ticket={0}" target="_blank">sur ce lien</a>.
copyLink: 'Vous pouvez également copier le lien suivant dans votre navigateur:' copyLink: 'Vous pouvez également copier le lien suivant dans votre navigateur:'
poll: Si vous le souhaitez, vous pouvez répondre à notre questionaire de satisfaction poll: Si vous le souhaitez, vous pouvez répondre à notre questionaire de satisfaction
pour nous aider à améliorer notre service. Votre avis est très important pour nous! pour nous aider à améliorer notre service. Votre avis est très important pour nous!

View File

@ -2,7 +2,7 @@ subject: Sua nota de entrega
title: Sua nota de entrega title: Sua nota de entrega
dear: Estimado cliente dear: Estimado cliente
description: Já está disponível sua nota de entrega correspondente a encomenda numero <strong>{0}</strong>. <br/> description: Já está disponível sua nota de entrega correspondente a encomenda numero <strong>{0}</strong>. <br/>
Para ver-lo faça um clique <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">neste link</a>. Para ver-lo faça um clique <a href="https://shop.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">neste link</a>.
copyLink: 'Como alternativa, podes copiar o siguinte link no teu navegador:' copyLink: 'Como alternativa, podes copiar o siguinte link no teu navegador:'
poll: Si o deseja, podes responder nosso questionário de satiscação para ajudar-nos a prestar-vos um melhor serviço. Tua opinião é muito importante para nós! poll: Si o deseja, podes responder nosso questionário de satiscação para ajudar-nos a prestar-vos um melhor serviço. Tua opinião é muito importante para nós!
help: Cualquer dúvida que surja, no hesites em consultar-la, <strong>Estamos aqui para help: Cualquer dúvida que surja, no hesites em consultar-la, <strong>Estamos aqui para

View File

@ -1,49 +1,12 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description', [id])"></p>
</head> <p v-html="$t('poll')"></p>
<body> <p v-html="$t('help')"></p>
<table class="grid"> <p v-html="$t('conclusion')"></p>
<tbody> </div>
<tr> </div>
<td> </email-body>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}},</p>
<p v-html="$t('description', [id])"></p>
<p v-html="$t('poll')"></p>
<p v-html="$t('help')"></p>
<p v-html="$t('conclusion')"></p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,12 +1,10 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'delivery-note', name: 'delivery-note',
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: { props: {
id: { id: {

View File

@ -1,45 +1,8 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('description.instructions')}}</p>
<title>{{ $t('subject') }}</title> </div>
</head> </div>
<body> </email-body>
<table class="grid">
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('description.instructions')}}</p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,12 +1,10 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'driver-route', name: 'driver-route',
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: { props: {
id: { id: {

View File

@ -1,57 +1,17 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('description.dear')}},</p>
<title>{{ $t('subject') }}</title> <p>{{$t('description.instructions')}}</p>
</head> <p>{{$t('description.conclusion')}}</p>
<body> </div>
<table class="grid"> </div>
<tbody> <div class="grid-row" v-if="isPreview">
<tr> <div class="grid-block vn-pa-ml">
<td> <attachment v-for="attachment in attachments" v-bind:key="attachment.filename"
<!-- Empty block --> v-bind:attachment="attachment" v-bind:args="$props">
<div class="grid-row"> </attachment>
<div class="grid-block empty"></div> </div>
</div> </div>
<!-- Header block --> </email-body>
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('description.dear')}},</p>
<p>{{$t('description.instructions')}}</p>
<p>{{$t('description.conclusion')}}</p>
</div>
</div>
<!-- Attachments block -->
<div class="grid-row" v-if="isPreview">
<div class="grid-block vn-pa-ml">
<attachment v-for="attachment in attachments"
v-bind:key="attachment.filename"
v-bind:attachment="attachment"
v-bind:args="$props">
</attachment>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment'); const attachment = new Component('attachment');
module.exports = { module.exports = {
@ -17,8 +16,7 @@ module.exports = {
}; };
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build(),
'attachment': attachment.build() 'attachment': attachment.build()
}, },
props: { props: {

View File

@ -1,49 +1,12 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description', [invoice.ref, invoice.ticketFk])"></p>
</head> <p v-html="$t('poll')"></p>
<body> <p v-html="$t('help')"></p>
<table class="grid"> <p v-html="$t('conclusion')"></p>
<tbody> </div>
<tr> </div>
<td> </email-body>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}},</p>
<p v-html="$t('description', [invoice.ref, invoice.ticketFk])"></p>
<p v-html="$t('poll')"></p>
<p v-html="$t('help')"></p>
<p v-html="$t('conclusion')"></p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,7 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'invoice', name: 'invoice',
async serverPrefetch() { async serverPrefetch() {
@ -13,8 +11,7 @@ module.exports = {
}, },
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: { props: {
reference: { reference: {

View File

@ -1,47 +1,10 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description')"></p>
</head> <p v-html="$t('conclusion')"></p>
<body> </div>
<table class="grid"> </div>
<tbody> </email-body>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}},</p>
<p v-html="$t('description')"></p>
<p v-html="$t('conclusion')"></p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,11 +1,8 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'invoiceIn', name: 'invoiceIn',
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
} }
}; };

View File

@ -1,88 +1,48 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{ $t('sections.introduction.title') }},</p>
<title>{{ $t('subject') }}</title> <p>{{ $t('sections.introduction.description') }}</p>
</head> <p>{{ $t('sections.introduction.terms') }}</p>
<body>
<table class="grid">
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{ $t('sections.introduction.title') }},</p>
<p>{{ $t('sections.introduction.description') }}</p>
<p>{{ $t('sections.introduction.terms') }}</p>
<p> <p>
{{ $t('sections.payMethod.description') }}: {{ $t('sections.payMethod.description') }}:
<ol> <ol>
<li v-for="option in $t('sections.payMethod.options')"> <li v-for="option in $t('sections.payMethod.options')">
{{ option }} {{ option }}
</li> </li>
</ol> </ol>
</p> </p>
<p> <p>
{{ $t('sections.legalAction.description') }}: {{ $t('sections.legalAction.description') }}:
<ol type="a"> <ol type="a">
<li v-for="option in $t('sections.legalAction.options')"> <li v-for="option in $t('sections.legalAction.options')">
{{ option }} {{ option }}
</li> </li>
</ol> </ol>
</p> </p>
<p v-html="$t('contactPhone')"></p> <p v-html="$t('contactPhone')"></p>
<p v-html="$t('conclusion')"></p> <p v-html="$t('conclusion')"></p>
<p> <p>
<div class="row"> <div class="row">
<div class="text">{{debtor.bankName}}</div> <div class="text">{{debtor.bankName}}</div>
<div class="control">{{debtor.iban}}</div> <div class="control">{{debtor.iban}}</div>
<div class="description"> <div class="description">
<div class="line"><span>{{$t('transferAccount') }}</span></div> <div class="line"><span>{{$t('transferAccount') }}</span></div>
</div> </div>
</div> </div>
</p> </p>
</div> </div>
</div> </div>
<!-- Block --> <div class="grid-row" v-if="isPreview">
<div class="grid-row" v-if="isPreview"> <div class="grid-block vn-pa-ml">
<div class="grid-block vn-pa-ml"> <attachment v-for="attachment in attachments" v-bind:key="attachment.filename"
<attachment v-for="attachment in attachments" v-bind:attachment="attachment" v-bind:args="$props">
v-bind:key="attachment.filename" </attachment>
v-bind:attachment="attachment" </div>
v-bind:args="$props"> </div>
</attachment> </email-body>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment'); const attachment = new Component('attachment');
module.exports = { module.exports = {
@ -28,8 +27,7 @@ module.exports = {
} }
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build(),
'attachment': attachment.build() 'attachment': attachment.build()
}, },
props: { props: {

View File

@ -1,71 +1,31 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{ $t('sections.introduction.title') }},</p>
<title>{{ $t('subject') }}</title> <p>{{ $t('sections.introduction.description') }}</p>
</head>
<body>
<table class="grid">
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{ $t('sections.introduction.title') }},</p>
<p>{{ $t('sections.introduction.description') }}</p>
<p>{{ $t('checkExtract') }}</p> <p>{{ $t('checkExtract') }}</p>
<p>{{ $t('checkValidData') }}</p> <p>{{ $t('checkValidData') }}</p>
<p>{{ $t('payMethod') }}</p> <p>{{ $t('payMethod') }}</p>
<p>{{ $t('conclusion') }}</p> <p>{{ $t('conclusion') }}</p>
<p> <p>
<div class="row"> <div class="row">
<div class="text">{{debtor.bankName}}</div> <div class="text">{{debtor.bankName}}</div>
<div class="control">{{debtor.iban}}</div> <div class="control">{{debtor.iban}}</div>
<div class="description"> <div class="description">
<div class="line"><span>{{$t('transferAccount') }}</span></div> <div class="line"><span>{{$t('transferAccount') }}</span></div>
</div> </div>
</div> </div>
</p> </p>
</div> </div>
</div> </div>
<!-- Block --> <div class="grid-row" v-if="isPreview">
<div class="grid-row" v-if="isPreview"> <div class="grid-block vn-pa-ml">
<div class="grid-block vn-pa-ml"> <attachment v-for="attachment in attachments" v-bind:key="attachment.filename"
<attachment v-for="attachment in attachments" v-bind:attachment="attachment" v-bind:args="$props">
v-bind:key="attachment.filename" </attachment>
v-bind:attachment="attachment" </div>
v-bind:args="$props"> </div>
</attachment> </email-body>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment'); const attachment = new Component('attachment');
module.exports = { module.exports = {
@ -28,8 +27,7 @@ module.exports = {
} }
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build(),
'attachment': attachment.build() 'attachment': attachment.build()
}, },
props: { props: {

View File

@ -1,99 +1,59 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description', [started, ended])"></p>
</head> <p v-html="$t('totalResolved', [resolvedTickets])"></p>
<body> <p v-html="$t('grafanaLink')"></p>
<table class="grid"> <div class="external-link vn-pa-sm vn-m-md">
<tbody> <a
<tr> v-bind:href="'https://grafana.verdnatura.es/d/2kaHDi9Mk/osticket?orgId=1&from=' + startedTime + '&to=' + endedTime"
<td> target="_blank"
<!-- Empty block --> >
<div class="grid-row"> https://grafana.verdnatura.es/d/2kaHDi9Mk/osticket?orgId=1&from={{startedTime}}&to={{endedTime}}
<div class="grid-block empty"></div> </a>
</div> </div>
<!-- Header block --> <p v-html="$t('redmineLink')"></p>
<div class="grid-row"> <div class="external-link vn-pa-sm vn-m-md">
<div class="grid-block"> <a href="https://redmine.verdnatura.es/projects/desarrollo/issues?query_id=112" target="_blank">
<email-header v-bind="$props"></email-header> https://redmine.verdnatura.es/projects/desarrollo/issues?query_id=112
</div> </a>
</div> </div>
<!-- Block --> </div>
<div class="grid-row"> </div>
<div class="grid-block vn-pa-ml"> <div class="grid-row">
<h1>{{ $t('title') }}</h1> <div class="grid-block vn-pa-ml" v-for="technician in technicians">
<p>{{$t('dear')}},</p> <div class="table-title clearfix">
<p v-html="$t('description', [started, ended])"></p> <h2>{{technician.name}} (<strong>{{technician.tickets.length}}</strong>)</h2>
<p v-html="$t('totalResolved', [resolvedTickets])"></p> </div>
<p v-html="$t('grafanaLink')"></p> <table class="column-oriented">
<div class="external-link vn-pa-sm vn-m-md"> <thead>
<a v-bind:href="'https://grafana.verdnatura.es/d/2kaHDi9Mk/osticket?orgId=1&from=' + startedTime + '&to=' + endedTime" target="_blank"> <tr>
https://grafana.verdnatura.es/d/2kaHDi9Mk/osticket?orgId=1&from={{startedTime}}&to={{endedTime}} <th width="5%">{{$t('author')}}</th>
</a> <th width="15%">{{$t('dated')}}</th>
</div> <th width="25%">{{$t('ticketSubject')}}</th>
<p v-html="$t('redmineLink')"></p> <th width="30%">{{$t('ticketDescription')}}</th>
<div class="external-link vn-pa-sm vn-m-md"> <th width="20%">{{$t('resolution')}}</th>
<a href="https://redmine.verdnatura.es/projects/desarrollo/issues?query_id=112" target="_blank"> </tr>
https://redmine.verdnatura.es/projects/desarrollo/issues?query_id=112 </thead>
</a> <tbody v-for="ticket in technician.tickets">
</div> <tr>
</div> <td>{{ticket.author}}</td>
</div> <td class="font light-gray">
<!-- Block --> <div v-bind:title="$t('opened')">&#128275; {{ticket.created | date('%d-%m-%Y %H:%M')}}</div>
<div class="grid-row"> <div v-bind:title="$t('closed')">&#128274; {{ticket.closed | date('%d-%m-%Y %H:%M')}}</div>
<div class="grid-block vn-pa-ml" v-for="technician in technicians"> </td>
<div class="table-title clearfix"> <td>
<h2>{{technician.name}} (<strong>{{technician.tickets.length}}</strong>)</h2> <a v-bind:href="'https://cau.verdnatura.es/scp/tickets.php?id=' + ticket.ticket_id">
</div> {{ticket.number}} - {{ticket.subject}}
<table class="column-oriented"> </a>
<thead> </td>
<tr> <td class="message" v-html="ticket.description"></td>
<th width="5%">{{$t('author')}}</th> <td class="message" v-html="ticket.resolution"></td>
<th width="15%">{{$t('dated')}}</th> </tr>
<th width="25%">{{$t('ticketSubject')}}</th> </tbody>
<th width="30%">{{$t('ticketDescription')}}</th> </table>
<th width="20%">{{$t('resolution')}}</th> </div>
</tr> </div>
</thead> </email-body>
<tbody v-for="ticket in technician.tickets">
<tr>
<td>{{ticket.author}}</td>
<td class="font light-gray">
<div v-bind:title="$t('opened')">
&#128275; {{ticket.created | date('%d-%m-%Y %H:%M')}}
</div>
<div v-bind:title="$t('closed')">
&#128274; {{ticket.closed | date('%d-%m-%Y %H:%M')}}
</div>
</td>
<td>
<a v-bind:href="'https://cau.verdnatura.es/scp/tickets.php?id=' + ticket.ticket_id">
{{ticket.number}} - {{ticket.subject}}
</a>
</td>
<td class="message" v-html="ticket.description"></td>
<td class="message" v-html="ticket.resolution"></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'osticket-report', name: 'osticket-report',
@ -61,8 +60,7 @@ module.exports = {
} }
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: {} props: {}
}; };

View File

@ -1,66 +1,28 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{ $t('sections.introduction.title') }},</p>
<title>{{ $t('subject') }}</title> <p v-html="`${$t('sections.introduction.description')}:`"></p>
</head>
<body>
<table class="grid">
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{ $t('sections.introduction.title') }},</p>
<p v-html="`${$t('sections.introduction.description')}:`"></p>
<p> <p>
<section> <section>
<span>{{ $t('sections.pay.method') }}:</span> <span>{{ $t('sections.pay.method') }}:</span>
<strong>{{payMethod.name}}</strong> <strong>{{payMethod.name}}</strong>
</section> </section>
<section v-if="payMethod.code != 'card'"> <section v-if="payMethod.code != 'card'">
<span>{{ $t('sections.pay.day') }}:</span> <span>{{ $t('sections.pay.day') }}:</span>
<strong>{{ $t('sections.pay.dueDay', [payMethod.dueDay]) }}</strong> <strong>{{ $t('sections.pay.dueDay', [payMethod.dueDay]) }}</strong>
</section> </section>
</p> </p>
<p v-if="payMethod.code == 'bankDraft'" <p v-if="payMethod.code == 'bankDraft'" v-html="$t('sections.pay.accountImplicates', [accountAddress])">
v-html="$t('sections.pay.accountImplicates', [accountAddress])"> </p>
</p> <p v-else-if="payMethod.code == 'card'">
<p v-else-if="payMethod.code == 'card'"> {{ $t('sections.pay.cardImplicates') }}
{{ $t('sections.pay.cardImplicates') }} </p>
</p>
<p>{{ $t('notifyAnError') }}</p> <p>{{ $t('notifyAnError') }}</p>
</div> </div>
</div> </div>
<!-- Footer block --> </email-body>
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'payment-update', name: 'payment-update',
@ -21,8 +20,7 @@ module.exports = {
} }
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build()
}, },
props: { props: {
id: { id: {

View File

@ -1,90 +1,49 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('description.dear')}},</p>
<title>{{ $t('subject') }}</title> <p>{{$t('description.instructions')}}</p>
</head> <p v-html="$t('description.followGuide')"></p>
<body> <p v-html="$t('description.downloadFrom')"></p>
<table class="grid"> <p v-html="$t('description.downloadDriver')"></p>
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('description.dear')}},</p>
<p>{{$t('description.instructions')}}</p>
<p v-html="$t('description.followGuide')"></p>
<p v-html="$t('description.downloadFrom')"></p>
<p v-html="$t('description.downloadDriver')"></p>
<h1>{{$t('sections.QLabel.title')}}</h1> <h1>{{$t('sections.QLabel.title')}}</h1>
<p>{{$t('sections.QLabel.description')}}:</p> <p>{{$t('sections.QLabel.description')}}:</p>
<ol> <ol>
<li v-for="step in $t('sections.QLabel.steps')"> <li v-for="step in $t('sections.QLabel.steps')">
<span v-html="step"></span> <span v-html="step"></span>
</li> </li>
</ol> </ol>
</div> </div>
</div> </div>
<!-- Block --> <div class="grid-row">
<div class="grid-row"> <div class="grid-block vn-pa-ml">
<div class="grid-block vn-pa-ml"> <h1>{{$t('sections.help.title')}}</h1>
<h1>{{$t('sections.help.title')}}</h1> <p>{{$t('sections.help.description')}}</p>
<p>{{$t('sections.help.description')}}</p> <p v-html="$t('sections.help.remoteSupport')"></p>
<p v-html="$t('sections.help.remoteSupport')"></p> </div>
</div> </div>
</div> <div class="grid-row">
<!-- Block --> <div class="grid-block vn-pa-ml">
<div class="grid-row"> <div v-if="client.salesPersonName">
<div class="grid-block vn-pa-ml"> {{$t('salesPersonName')}}: <strong>{{client.salesPersonName}}</strong>
<div v-if="client.salesPersonName"> </div>
{{$t('salesPersonName')}}: <strong>{{client.salesPersonName}}</strong> <div v-if="client.salesPersonPhone">
</div> {{$t('salesPersonPhone')}}: <strong>{{client.salesPersonPhone}}</strong>
<div v-if="client.salesPersonPhone"> </div>
{{$t('salesPersonPhone')}}: <strong>{{client.salesPersonPhone}}</strong> <div v-if="client.salesPersonEmail">
</div> {{$t('salesPersonEmail')}}:
<div v-if="client.salesPersonEmail"> <strong><a v-bind:href="`mailto:${client.salesPersonEmail}`"
{{$t('salesPersonEmail')}}: target="_blank">{{client.salesPersonEmail}}</strong>
<strong><a v-bind:href="`mailto:${client.salesPersonEmail}`" target="_blank">{{client.salesPersonEmail}}</strong> </div>
</div> </div>
</div> </div>
</div> <div class="grid-row" v-if="isPreview">
<!-- Block --> <div class="grid-block vn-pa-ml">
<div class="grid-row" v-if="isPreview"> <attachment v-for="attachment in attachments" v-bind:key="attachment.filename"
<div class="grid-block vn-pa-ml"> v-bind:attachment="attachment" v-bind:args="$props">
<attachment v-for="attachment in attachments" </attachment>
v-bind:key="attachment.filename" </div>
v-bind:attachment="attachment" </div>
v-bind:args="$props"> </email-body>
</attachment>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,6 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment'); const attachment = new Component('attachment');
const attachments = require('./attachments.json'); const attachments = require('./attachments.json');
@ -18,8 +17,7 @@ module.exports = {
} }
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build(),
'email-footer': emailFooter.build(),
'attachment': attachment.build() 'attachment': attachment.build()
}, },
props: { props: {

View File

@ -1,57 +1,21 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('description.dear')}},</p>
<title>{{ $t('subject') }}</title> <div v-html="$t('description.instructions')"></div>
</head> <p>{{$t('description.conclusion')}}</p>
<body> </div>
<table class="grid"> </div>
<tbody> <div class="grid-row" v-if="isPreview">
<tr> <div class="grid-block vn-pa-ml">
<td> <attachment
<!-- Empty block --> v-for="attachment in attachments"
<div class="grid-row"> v-bind:key="attachment.filename"
<div class="grid-block empty"></div> v-bind:attachment="attachment"
</div> v-bind:args="$props"
<!-- Header block --> >
<div class="grid-row"> </attachment>
<div class="grid-block"> </div>
<email-header v-bind="$props"></email-header> </div>
</div> </email-body>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('description.dear')}},</p>
<div v-html="$t('description.instructions')"></div>
<p>{{$t('description.conclusion')}}</p>
</div>
</div>
<!-- Attachments block -->
<div class="grid-row" v-if="isPreview">
<div class="grid-block vn-pa-ml">
<attachment v-for="attachment in attachments"
v-bind:key="attachment.filename"
v-bind:attachment="attachment"
v-bind:args="$props">
</attachment>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -1,7 +1,5 @@
const Component = require(`vn-print/core/component`); const Component = require(`vn-print/core/component`);
const emailHeader = new Component('email-header'); const emailBody = new Component('email-body');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment');
const attachments = require('./attachments.json'); const attachments = require('./attachments.json');
module.exports = { module.exports = {
@ -10,9 +8,7 @@ module.exports = {
return {attachments}; return {attachments};
}, },
components: { components: {
'email-header': emailHeader.build(), 'email-body': emailBody.build()
'email-footer': emailFooter.build(),
'attachment': attachment.build()
}, },
props: { props: {
id: { id: {

View File

@ -1,46 +1,9 @@
<!DOCTYPE html> <email-body v-bind="$props">
<html v-bind:lang="$i18n.locale"> <div class="grid-row">
<head> <div class="grid-block vn-pa-ml">
<meta name="viewport" content="width=device-width"> <h1>{{ $t('title') }}</h1>
<meta name="format-detection" content="telephone=no"> <p>{{$t('dear')}},</p>
<title>{{ $t('subject') }}</title> <p v-html="$t('description', [minDate, maxDate])"></p>
</head> </div>
<body> </div>
<table class="grid"> </email-body>
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header v-bind="$props"></email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}},</p>
<p v-html="$t('description', [minDate, maxDate])"></p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer v-bind="$props"></email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More