Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3888-ticket.expedition_moveExpedition
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Vicent Llopis 2022-10-06 10:08:29 +02:00
commit 26d04ce2d2
269 changed files with 4765 additions and 3581 deletions

View File

@ -0,0 +1,29 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('osTicketReportEmail', {
description: 'Sends the buyer waste email',
accessType: 'WRITE',
accepts: [],
returns: {
type: ['object'],
root: true
},
http: {
path: '/osticket-report-email',
verb: 'POST'
}
});
Self.osTicketReportEmail = async ctx => {
const models = Self.app.models;
const printConfig = await models.PrintConfig.findOne();
const email = new Email('osticket-report', {
recipient: printConfig.itRecipient,
lang: ctx.req.getLocale()
});
return email.send();
};
};

View File

@ -121,6 +121,9 @@
}, },
"Edi": { "Edi": {
"dataSource": "vn" "dataSource": "vn"
},
"PrintConfig": {
"dataSource": "vn"
} }
} }

View File

@ -1,3 +1,4 @@
module.exports = Self => { module.exports = Self => {
require('../methods/osticket/osTicketReportEmail')(Self);
require('../methods/osticket/closeTicket')(Self); require('../methods/osticket/closeTicket')(Self);
}; };

View File

@ -1,12 +1,5 @@
{ {
"name": "OsTicket", "name": "OsTicket",
"base": "VnModel", "base": "VnModel"
"acls": [{
"property": "validations",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
} }

View File

@ -0,0 +1,29 @@
{
"name": "PrintConfig",
"description": "Print config",
"base": "VnModel",
"options": {
"mysql": {
"table": "salix.printConfig"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"itRecipient": {
"type": "string"
},
"incidencesEmail": {
"type": "string"
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -41,11 +41,14 @@ async function test() {
} }
})); }));
jasmine.exitOnCompletion = false;
if (isCI) { if (isCI) {
const JunitReporter = require('jasmine-reporters'); const JunitReporter = require('jasmine-reporters');
jasmine.addReporter(new JunitReporter.JUnitXmlReporter()); jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
jasmine.exitOnCompletion = true;
} }
const backSpecs = [ const backSpecs = [
@ -60,11 +63,10 @@ async function test() {
helpers: [], helpers: [],
}); });
jasmine.exitOnCompletion = false;
await jasmine.execute(); await jasmine.execute();
if (app) await app.disconnect(); if (app) await app.disconnect();
if (container) await container.rm(); if (container) await container.rm();
console.log('app disconnected & container removed'); console.log('App disconnected & container removed');
} }
test(); test();

View File

@ -0,0 +1,49 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('ClientConsumptionQueue', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'deliveryNotePdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'deliveryNoteEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'deliveryNoteCsvPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'deliveryNoteCsvEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientWelcomeHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientWelcomeEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'creditRequestEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'printerSetupHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'printerSetupEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'sepaCoreEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorStHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorStEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorNdHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'letterDebtorNdEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'clientDebtStatementEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Client', 'incotermsAuthorizationEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Client', 'consumptionSendQueued', 'WRITE', 'ALLOW', 'ROLE', 'system'),
('InvoiceOut', 'invoiceEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('InvoiceOut', 'exportationPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('InvoiceOut', 'sendQueued', 'WRITE', 'ALLOW', 'ROLE', 'system'),
('Ticket', 'invoiceCsvPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'invoiceCsvEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Supplier', 'campaignMetricsPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Supplier', 'campaignMetricsEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Travel', 'extraCommunityPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Travel', 'extraCommunityEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Entry', 'entryOrderPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('OsTicket', 'osTicketReportEmail', 'WRITE', 'ALLOW', 'ROLE', 'system'),
('Item', 'buyerWasteEmail', 'WRITE', 'ALLOW', 'ROLE', 'system'),
('Claim', 'claimPickupPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Claim', 'claimPickupEmail', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Item', 'labelPdf', 'READ', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES ('Sector','*','READ','ALLOW','ROLE','employee');
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES ('Sector','*','WRITE','ALLOW','ROLE','employee');

View File

@ -0,0 +1,9 @@
create table `vn`.`clientConsumptionQueue`
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
params json not null,
queued datetime default current_timestamp() not null,
printed datetime null,
status varchar(50) default '' null
)
comment 'Queue for client consumption PDF mailing';

View File

@ -0,0 +1 @@
rename table `vn`.`invoiceOut_queue` to `vn`.`invoiceOutQueue`;

View File

@ -0,0 +1,5 @@
ALTER TABLE `vn`.`itemConfig`
ADD id int null PRIMARY KEY first;
ALTER TABLE `vn`.`itemConfig`
ADD wasteRecipients VARCHAR(50) NOT NULL comment 'Weekly waste report schedule recipients';

View File

@ -0,0 +1,10 @@
create table `salix`.`printConfig`
(
id int auto_increment,
itRecipient varchar(50) null comment 'IT recipients for report mailing',
incidencesEmail varchar(50) null comment 'CAU destinatary email',
constraint printConfig_pk
primary key (id)
)
comment 'Print service config';

View File

@ -0,0 +1,6 @@
alter table `vn`.`sample`
add model VARCHAR(25) null comment 'Model name in plural';
UPDATE vn.sample t
SET t.model = 'Clients'
WHERE t.id IN(12, 13, 14, 15, 16, 18, 19, 20);

View File

@ -19,8 +19,9 @@ module.exports = class Docker {
* to avoid a bug with OverlayFS driver on MacOS. * to avoid a bug with OverlayFS driver on MacOS.
* *
* @param {Boolean} ci continuous integration environment argument * @param {Boolean} ci continuous integration environment argument
* @param {String} networkName Name of the container network
*/ */
async run(ci) { async run(ci, networkName = 'jenkins') {
let d = new Date(); let d = new Date();
let pad = v => v < 10 ? '0' + v : v; let pad = v => v < 10 ? '0' + v : v;
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
@ -42,8 +43,16 @@ module.exports = class Docker {
let runChown = process.platform != 'linux'; let runChown = process.platform != 'linux';
let network = '';
if (ci) network = `--network="${networkName}"`;
log('Starting container...'); log('Starting container...');
const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`); const container = await this.execP(`
docker run \
${network} \
--env RUN_CHOWN=${runChown} \
-d ${dockerArgs} salix-db
`);
this.id = container.stdout.trim(); this.id = container.stdout.trim();
try { try {
@ -51,10 +60,11 @@ module.exports = class Docker {
let inspect = await this.execP(`docker inspect -f "{{json .NetworkSettings}}" ${this.id}`); let inspect = await this.execP(`docker inspect -f "{{json .NetworkSettings}}" ${this.id}`);
let netSettings = JSON.parse(inspect.stdout); let netSettings = JSON.parse(inspect.stdout);
if (ci) if (ci) {
this.dbConf.host = netSettings.Gateway; this.dbConf.host = netSettings.Networks[networkName].IPAddress;
this.dbConf.port = 3306;
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort']; } else
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
} }
await this.wait(); await this.wait();

View File

@ -13,7 +13,11 @@ INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES VALUES
('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66); ('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66);
INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
VALUES
(1, 'it@gotamcity.com', 'incidences@gotamcity.com');
INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
VALUES VALUES
('1', '6'); ('1', '6');
@ -862,25 +866,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('VT', 'Sales'); ('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`) `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`)
VALUES VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15), (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15,3),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10), (2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10,2),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5), (3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5,5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL), (4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL), (5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL), (6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL), (7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL), (8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL), (9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL), (10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL), (11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL), (12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL), (13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL,NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL), (14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL), (15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL), (16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL); (71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL);
-- Update the taxClass after insert of the items -- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2 UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -916,16 +920,19 @@ INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`)
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`) INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`)
VALUES VALUES
(1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1), (1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1),
(2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1), (2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1),
(3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2), (3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2),
(4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2), (4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2),
(5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3), (5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3),
(6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3), (6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3),
(7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL), (7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL),
(8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1), (8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1),
(9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2), (9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2),
(10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3); (10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3),
(11, 7, 8, 71, NOW(), NULL, 1, 18, NULL, 94, 3),
(12, 7, 9, 71, NOW(), NULL, 1, 18, NULL, 94, 3),
(13, 1, 10, 71, NOW(), NULL, 1, 18, NULL, 94, 3);
INSERT INTO `vn`.`expeditionState`(`id`, `created`, `expeditionFk`, `typeFk`, `userFk`) INSERT INTO `vn`.`expeditionState`(`id`, `created`, `expeditionFk`, `typeFk`, `userFk`)
@ -1778,6 +1785,11 @@ INSERT INTO `vn`.`claimEnd`(`id`, `saleFk`, `claimFk`, `workerFk`, `claimDestina
(1, 31, 4, 21, 2), (1, 31, 4, 21, 2),
(2, 32, 3, 21, 3); (2, 32, 3, 21, 3);
INSERT INTO `vn`.`claimConfig`(`id`, `pickupContact`, `maxResponsibility`)
VALUES
(1, 'Contact description', 50),
(2, 'Contact description', 30);
INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`) INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`)
VALUES VALUES
(1, 'Arkham Bank', 442, 1, 'h12387193H10238'), (1, 'Arkham Bank', 442, 1, 'h12387193H10238'),

View File

@ -391,7 +391,7 @@ export default {
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]', intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]', originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]', buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]',
densityCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Density"]', weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button' saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
}, },
itemFixedPrice: { itemFixedPrice: {

View File

@ -31,7 +31,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox); await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox); await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox); await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox); await page.waitToClick(selectors.itemsIndex.weightByPieceCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton); await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
@ -64,7 +64,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox); await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox); await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox); await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox); await page.waitToClick(selectors.itemsIndex.weightByPieceCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton); await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();

View File

@ -23,12 +23,15 @@ export default class InputTime extends Field {
let date = null; let date = null;
let value = this.input.value; let value = this.input.value;
if (this.field && !this.modelDate)
this.modelDate = this.field;
if (value) { if (value) {
let split = value.split(':').map(i => parseInt(i) || null); let split = value.split(':').map(i => parseInt(i) || null);
date = this.field instanceof Date date = this.modelDate
? this.field ? new Date(this.modelDate)
: new Date(this.field || null); : new Date();
date.setHours(split[0], split[1], 0, 0); date.setHours(split[0], split[1], 0, 0);
} }

View File

@ -10,24 +10,12 @@ class Email {
/** /**
* Sends an email displaying a notification when it's sent. * Sends an email displaying a notification when it's sent.
* *
* @param {String} template The email report name * @param {String} path The email report name
* @param {Object} params The email parameters * @param {Object} params The email parameters
* @return {Promise} Promise resolved when it's sent * @return {Promise} Promise resolved when it's sent
*/ */
send(template, params) { send(path, params) {
return this.$http.get(`email/${template}`, {params}) return this.$http.post(path, params)
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
/**
* Sends an email displaying a notification when it's sent.
*
* @param {String} template The email report name
* @param {Object} params The email parameters
* @return {Promise} Promise resolved when it's sent
*/
sendCsv(template, params) {
return this.$http.get(`csv/${template}/send`, {params})
.then(() => this.vnApp.showMessage(this.$t('Notification sent!'))); .then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
} }
} }

View File

@ -10,30 +10,16 @@ class Report {
* Shows a report in another window, automatically adds the authorization * Shows a report in another window, automatically adds the authorization
* token to params. * token to params.
* *
* @param {String} report The report name * @param {String} path The report name
* @param {Object} params The report parameters * @param {Object} params The report parameters
*/ */
show(report, params) { show(path, params) {
params = Object.assign({ params = Object.assign({
authorization: this.vnToken.token access_token: this.vnToken.token
}, params); }, params);
const serializedParams = this.$httpParamSerializer(params); const serializedParams = this.$httpParamSerializer(params);
window.open(`api/report/${report}?${serializedParams}`); const query = serializedParams ? `?${serializedParams}` : '';
} window.open(`api/${path}${query}`);
/**
* Shows a report in another window, automatically adds the authorization
* token to params.
*
* @param {String} report The report name
* @param {Object} params The report parameters
*/
showCsv(report, params) {
params = Object.assign({
authorization: this.vnToken.token
}, params);
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/csv/${report}/download?${serializedParams}`);
} }
} }
Report.$inject = ['$httpParamSerializer', 'vnToken']; Report.$inject = ['$httpParamSerializer', 'vnToken'];

View File

@ -1,3 +1,3 @@
module.exports = function(app) { module.exports = function(app) {
require('../../../print/boot.js')(app); require('vn-print').boot(app);
}; };

View File

@ -1,3 +1,9 @@
/**
* Transforms an object to a raw data CSV file.
*
* @param {Object} rows Data
* @return {String} Formatted CSV data
*/
function toCSV(rows) { function toCSV(rows) {
const [columns] = rows; const [columns] = rows;
let content = Object.keys(columns).join('\t'); let content = Object.keys(columns).join('\t');

View File

@ -0,0 +1,58 @@
const {Report, Email, smtp} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('claimPickupEmail', {
description: 'Sends the the claim pickup order email with an attached PDF',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/claim-pickup-email',
verb: 'POST'
}
});
Self.claimPickupEmail = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('claim-pickup-order', params);
return email.send();
};
};

View File

@ -0,0 +1,55 @@
const { Report } = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('claimPickupPdf', {
description: 'Returns the claim pickup order pdf',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The claim id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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/claim-pickup-pdf',
verb: 'GET'
}
});
Self.claimPickupPdf = 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('claim-pickup-order', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -9,4 +9,6 @@ module.exports = Self => {
require('../methods/claim/isEditable')(Self); require('../methods/claim/isEditable')(Self);
require('../methods/claim/updateClaimDestination')(Self); require('../methods/claim/updateClaimDestination')(Self);
require('../methods/claim/downloadFile')(Self); require('../methods/claim/downloadFile')(Self);
require('../methods/claim/claimPickupPdf')(Self);
require('../methods/claim/claimPickupEmail')(Self);
}; };

View File

@ -10,6 +10,8 @@
</vn-item> </vn-item>
<vn-item <vn-item
ng-click="confirmPickupOrder.show()" ng-click="confirmPickupOrder.show()"
vn-acl="salesPerson"
vn-acl-action="remove"
translate> translate>
Send Pickup order Send Pickup order
</vn-item> </vn-item>

View File

@ -11,17 +11,15 @@ class Controller extends Descriptor {
} }
showPickupOrder() { showPickupOrder() {
this.vnReport.show('claim-pickup-order', { this.vnReport.show(`Claims/${this.claim.id}/claim-pickup-pdf`, {
recipientId: this.claim.clientFk, recipientId: this.claim.clientFk
claimId: this.claim.id
}); });
} }
sendPickupOrder() { sendPickupOrder() {
return this.vnEmail.send('claim-pickup-order', { return this.vnEmail.send(`Claims/${this.claim.id}/claim-pickup-email`, {
recipient: this.claim.client.email, recipient: this.claim.client.email,
recipientId: this.claim.clientFk, recipientId: this.claim.clientFk
claimId: this.claim.id
}); });
} }

View File

@ -24,12 +24,13 @@ describe('Item Component vnClaimDescriptor', () => {
window.open = jasmine.createSpy('open'); window.open = jasmine.createSpy('open');
const params = { const params = {
recipientId: claim.clientFk, recipientId: claim.clientFk
claimId: claim.id
}; };
controller.showPickupOrder(); controller.showPickupOrder();
expect(controller.vnReport.show).toHaveBeenCalledWith('claim-pickup-order', params); const expectedPath = `Claims/${claim.id}/claim-pickup-pdf`;
expect(controller.vnReport.show).toHaveBeenCalledWith(expectedPath, params);
}); });
}); });
@ -39,12 +40,13 @@ describe('Item Component vnClaimDescriptor', () => {
const params = { const params = {
recipient: claim.client.email, recipient: claim.client.email,
recipientId: claim.clientFk, recipientId: claim.clientFk
claimId: claim.id
}; };
controller.sendPickupOrder(); controller.sendPickupOrder();
expect(controller.vnEmail.send).toHaveBeenCalledWith('claim-pickup-order', params); const expectedPath = `Claims/${claim.id}/claim-pickup-email`;
expect(controller.vnEmail.send).toHaveBeenCalledWith(expectedPath, params);
}); });
}); });

View File

@ -0,0 +1,69 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('campaignMetricsEmail', {
description: 'Sends the campaign metrics email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
},
{
arg: 'from',
type: 'string',
required: true
},
{
arg: 'to',
type: 'string',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/campaign-metrics-email',
verb: 'POST'
}
});
Self.campaignMetricsEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('campaign-metrics', params);
return email.send();
};
};

View File

@ -0,0 +1,66 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('campaignMetricsPdf', {
description: 'Returns the campaign metrics pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'from',
type: 'string',
required: true
},
{
arg: 'to',
type: 'string',
required: true
}
],
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/campaign-metrics-pdf',
verb: 'GET'
}
});
Self.campaignMetricsPdf = 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('campaign-metrics', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -0,0 +1,64 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('clientDebtStatementEmail', {
description: 'Sends the client debt statement email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
},
{
arg: 'from',
type: 'string',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/client-debt-statement-email',
verb: 'POST'
}
});
Self.clientDebtStatementEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('client-debt-statement', params);
return email.send();
};
};

View File

@ -0,0 +1,65 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('clientDebtStatementHtml', {
description: 'Returns the client debt statement email preview',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'from',
type: 'string',
required: true
}
],
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/client-debt-statement-html',
verb: 'GET'
}
});
Self.clientDebtStatementHtml = async(ctx, id) => {
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('client-debt-statement', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
};

View File

@ -0,0 +1,61 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('clientDebtStatementPdf', {
description: 'Returns the client debt statement pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'from',
type: 'string',
required: true
}
],
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/client-debt-statement-pdf',
verb: 'GET'
}
});
Self.clientDebtStatementPdf = 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('client-debt-statement', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -0,0 +1,59 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('clientWelcomeEmail', {
description: 'Sends the client welcome email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/client-welcome-email',
verb: 'POST'
}
});
Self.clientWelcomeEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('client-welcome', params);
return email.send();
};
};

View File

@ -0,0 +1,58 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('clientWelcomeHtml', {
description: 'Returns the client welcome email preview',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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/client-welcome-html',
verb: 'GET'
}
});
Self.clientWelcomeHtml = 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];
params.isPreview = true;
const report = new Email('client-welcome', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
};

View File

@ -0,0 +1,80 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethod('consumptionSendQueued', {
description: 'Send all queued invoices',
accessType: 'WRITE',
accepts: [],
returns: {
type: 'object',
root: true
},
http: {
path: '/consumption-send-queued',
verb: 'POST'
}
});
Self.consumptionSendQueued = async() => {
const queues = await Self.rawSql(`
SELECT
ccq.id,
c.id AS clientFk,
c.email AS clientEmail,
eu.email salesPersonEmail,
REPLACE(json_extract(params, '$.from'), '"', '') AS fromDate,
REPLACE(json_extract(params, '$.to'), '"', '') AS toDate
FROM clientConsumptionQueue ccq
JOIN client c ON (
JSON_SEARCH(
JSON_ARRAY(
json_extract(params, '$.clients')
)
, 'all', c.id) IS NOT NULL)
JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
JOIN ticket t ON t.clientFk = c.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
JOIN itemType it ON it.id = i.typeFk
WHERE status = ''
AND it.isPackaging = FALSE
AND DATE(t.shipped) BETWEEN
REPLACE(json_extract(params, '$.from'), '"', '') AND
REPLACE(json_extract(params, '$.to'), '"', '')
GROUP BY c.id`);
for (const queue of queues) {
try {
const args = {
id: queue.clientFk,
recipient: queue.clientEmail,
replyTo: queue.salesPersonEmail,
from: queue.fromDate,
to: queue.toDate
};
const email = new Email('campaign-metrics', args);
await email.send();
await Self.rawSql(`
UPDATE clientConsumptionQueue
SET status = 'printed',
printed = ?
WHERE id = ?`,
[new Date(), queue.id]);
} catch (error) {
await Self.rawSql(`
UPDATE clientConsumptionQueue
SET status = ?
WHERE id = ?`,
[error.message, queue.id]);
throw e;
}
}
return {
message: 'Success'
};
};
};

View File

@ -0,0 +1,59 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('clientCreditEmail', {
description: 'Sends the credit request email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/credit-request-email',
verb: 'POST'
}
});
Self.clientCreditEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('credit-request', params);
return email.send();
};
};

View File

@ -0,0 +1,60 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('creditRequestHtml', {
description: 'Returns the credit request email preview',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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/credit-request-html',
verb: 'GET'
}
});
Self.creditRequestHtml = async(ctx, id) => {
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('credit-request', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
};

View File

@ -0,0 +1,56 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('creditRequestPdf', {
description: 'Returns the credit request pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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/credit-request-pdf',
verb: 'GET'
}
});
Self.creditRequestPdf = 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('credit-request', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -0,0 +1,65 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('incotermsAuthorizationEmail', {
description: 'Sends the incoterms authorization email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/incoterms-authorization-email',
verb: 'POST'
}
});
Self.incotermsAuthorizationEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('incoterms-authorization', params);
return email.send();
};
};

View File

@ -0,0 +1,66 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('incotermsAuthorizationHtml', {
description: 'Returns the incoterms authorization email preview',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
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/incoterms-authorization-html',
verb: 'GET'
}
});
Self.incotermsAuthorizationHtml = async(ctx, id) => {
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('incoterms-authorization', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
};

View File

@ -0,0 +1,62 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('incotermsAuthorizationPdf', {
description: 'Returns the incoterms authorization pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
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/incoterms-authorization-pdf',
verb: 'GET'
}
});
Self.incotermsAuthorizationPdf = 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('incoterms-authorization', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -0,0 +1,65 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('letterDebtorNdEmail', {
description: 'Sends the second debtor letter email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/letter-debtor-nd-email',
verb: 'POST'
}
});
Self.letterDebtorNdEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('letter-debtor-nd', params);
return email.send();
};
};

View File

@ -0,0 +1,66 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('letterDebtorNdHtml', {
description: 'Returns the second letter debtor email preview',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
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/letter-debtor-nd-html',
verb: 'GET'
}
});
Self.letterDebtorNdHtml = async(ctx, id) => {
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('letter-debtor-nd', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
};

View File

@ -0,0 +1,62 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('letterDebtorPdf', {
description: 'Returns the letter debtor pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
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/letter-debtor-pdf',
verb: 'GET'
}
});
Self.letterDebtorPdf = 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('letter-debtor', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -0,0 +1,65 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('letterDebtorStEmail', {
description: 'Sends the printer setup email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/letter-debtor-st-email',
verb: 'POST'
}
});
Self.letterDebtorStEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('letter-debtor-st', params);
return email.send();
};
};

View File

@ -0,0 +1,66 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('letterDebtorStHtml', {
description: 'Returns the letter debtor email preview',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
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/letter-debtor-st-html',
verb: 'GET'
}
});
Self.letterDebtorStHtml = async(ctx, id) => {
const {accessToken} = ctx.req;
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
params.isPreview = true;
params.access_token = accessToken.id;
const report = new Email('letter-debtor-st', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
};

View File

@ -0,0 +1,59 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('printerSetupEmail', {
description: 'Sends the printer setup email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/printer-setup-email',
verb: 'POST'
}
});
Self.printerSetupEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('printer-setup', params);
return email.send();
};
};

View File

@ -0,0 +1,58 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('printerSetupHtml', {
description: 'Returns the printer setup email preview',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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/printer-setup-html',
verb: 'GET'
}
});
Self.printerSetupHtml = 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];
params.isPreview = true;
const report = new Email('printer-setup', params);
const html = await report.render();
return [html, 'text/html', `filename="mail-${id}.pdf"`];
};
};

View File

@ -0,0 +1,65 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('sepaCoreEmail', {
description: 'Sends the campaign metrics email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
},
{
arg: 'companyId',
type: 'number',
description: 'The company id',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/sepa-core-email',
verb: 'POST'
}
});
Self.sepaCoreEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('sepa-core', params);
return email.send();
};
};

View File

@ -26,6 +26,9 @@
"ClientCreditLimit": { "ClientCreditLimit": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ClientConsumptionQueue": {
"dataSource": "vn"
},
"ClientLog": { "ClientLog": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,30 @@
{
"name": "ClientConsumptionQueue",
"base": "VnModel",
"options": {
"mysql": {
"table": "clientConsumptionQueue"
}
},
"properties": {
"params": {
"type": "string"
},
"queued": {
"type": "date"
},
"printed": {
"type": "date"
},
"status": {
"type": "string"
}
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
}
}
}

View File

@ -0,0 +1,50 @@
module.exports = Self => {
require('../methods/client/addressesPropagateRe')(Self);
require('../methods/client/canBeInvoiced')(Self);
require('../methods/client/canCreateTicket')(Self);
require('../methods/client/checkDuplicated')(Self);
require('../methods/client/confirmTransaction')(Self);
require('../methods/client/consumption')(Self);
require('../methods/client/createAddress')(Self);
require('../methods/client/createReceipt')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/extendedListFilter')(Self);
require('../methods/client/getAverageInvoiced')(Self);
require('../methods/client/getCard')(Self);
require('../methods/client/getDebt')(Self);
require('../methods/client/getMana')(Self);
require('../methods/client/getTransactions')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/isValidClient')(Self);
require('../methods/client/lastActiveTickets')(Self);
require('../methods/client/sendSms')(Self);
require('../methods/client/setPassword')(Self);
require('../methods/client/summary')(Self);
require('../methods/client/updateAddress')(Self);
require('../methods/client/updateFiscalData')(Self);
require('../methods/client/updatePortfolio')(Self);
require('../methods/client/updateUser')(Self);
require('../methods/client/uploadFile')(Self);
require('../methods/client/campaignMetricsPdf')(Self);
require('../methods/client/campaignMetricsEmail')(Self);
require('../methods/client/clientWelcomeHtml')(Self);
require('../methods/client/clientWelcomeEmail')(Self);
require('../methods/client/printerSetupHtml')(Self);
require('../methods/client/printerSetupEmail')(Self);
require('../methods/client/sepaCoreEmail')(Self);
require('../methods/client/letterDebtorPdf')(Self);
require('../methods/client/letterDebtorStHtml')(Self);
require('../methods/client/letterDebtorStEmail')(Self);
require('../methods/client/letterDebtorNdHtml')(Self);
require('../methods/client/letterDebtorNdEmail')(Self);
require('../methods/client/clientDebtStatementPdf')(Self);
require('../methods/client/clientDebtStatementHtml')(Self);
require('../methods/client/clientDebtStatementEmail')(Self);
require('../methods/client/creditRequestPdf')(Self);
require('../methods/client/creditRequestHtml')(Self);
require('../methods/client/creditRequestEmail')(Self);
require('../methods/client/incotermsAuthorizationPdf')(Self);
require('../methods/client/incotermsAuthorizationHtml')(Self);
require('../methods/client/incotermsAuthorizationEmail')(Self);
require('../methods/client/consumptionSendQueued')(Self);
};

View File

@ -1,4 +1,5 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const LoopBackContext = require('loopback-context');
module.exports = Self => { module.exports = Self => {
Self.validatesPresenceOf('typeFk', { Self.validatesPresenceOf('typeFk', {
@ -6,10 +7,10 @@ module.exports = Self => {
}); });
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
let models = Self.app.models; const models = Self.app.models;
let changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
let sample = await models.Sample.findById(changes.typeFk); const sample = await models.Sample.findById(changes.typeFk);
if (sample.hasCompany && !changes.companyFk) if (sample.hasCompany && !changes.companyFk)
throw new UserError('Choose a company'); throw new UserError('Choose a company');
@ -25,11 +26,11 @@ module.exports = Self => {
// Renew mandate // Renew mandate
if (mandate) { if (mandate) {
let mandateType = await models.MandateType.findOne({ const mandateType = await models.MandateType.findOne({
where: {name: mandate.type} where: {name: mandate.type}
}); });
let oldMandate = await models.Mandate.findOne({ const oldMandate = await models.Mandate.findOne({
where: { where: {
clientFk: changes.clientFk, clientFk: changes.clientFk,
companyFk: changes.companyFk, companyFk: changes.companyFk,
@ -50,10 +51,8 @@ module.exports = Self => {
}); });
} }
// Apply workerFk const loopBackContext = LoopBackContext.getCurrentContext();
let filter = {where: {userFk: ctx.options.accessToken.userId}};
let worker = await Self.app.models.Worker.findOne(filter);
changes.workerFk = worker.id; changes.userFk = loopBackContext.active.accessToken.userId;
}); });
}; };

View File

@ -1,4 +1,3 @@
const got = require('got');
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const getFinalState = require('vn-loopback/util/hook').getFinalState; const getFinalState = require('vn-loopback/util/hook').getFinalState;
const isMultiple = require('vn-loopback/util/hook').isMultiple; const isMultiple = require('vn-loopback/util/hook').isMultiple;
@ -8,32 +7,7 @@ const LoopBackContext = require('loopback-context');
module.exports = Self => { module.exports = Self => {
// Methods // Methods
require('../methods/client/addressesPropagateRe')(Self); require('./client-methods')(Self);
require('../methods/client/canBeInvoiced')(Self);
require('../methods/client/canCreateTicket')(Self);
require('../methods/client/checkDuplicated')(Self);
require('../methods/client/confirmTransaction')(Self);
require('../methods/client/consumption')(Self);
require('../methods/client/createAddress')(Self);
require('../methods/client/createReceipt')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/extendedListFilter')(Self);
require('../methods/client/getAverageInvoiced')(Self);
require('../methods/client/getCard')(Self);
require('../methods/client/getDebt')(Self);
require('../methods/client/getMana')(Self);
require('../methods/client/getTransactions')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/isValidClient')(Self);
require('../methods/client/lastActiveTickets')(Self);
require('../methods/client/sendSms')(Self);
require('../methods/client/setPassword')(Self);
require('../methods/client/summary')(Self);
require('../methods/client/updateAddress')(Self);
require('../methods/client/updateFiscalData')(Self);
require('../methods/client/updatePortfolio')(Self);
require('../methods/client/updateUser')(Self);
require('../methods/client/uploadFile')(Self);
// Validations // Validations
@ -317,23 +291,22 @@ module.exports = Self => {
const $t = httpRequest.__; const $t = httpRequest.__;
const headers = httpRequest.headers; const headers = httpRequest.headers;
const origin = headers.origin; const origin = headers.origin;
const authorization = headers.authorization;
const salesPersonId = instance.salesPersonFk; const salesPersonId = instance.salesPersonFk;
if (salesPersonId) { if (salesPersonId) {
// Send email to client // Send email to client
if (instance.email) { if (instance.email) {
const {Email} = require('vn-print');
const worker = await models.EmailUser.findById(salesPersonId); const worker = await models.EmailUser.findById(salesPersonId);
const params = { const params = {
authorization: authorization, id: instance.id,
recipientId: instance.id, recipientId: instance.id,
recipient: instance.email, recipient: instance.email,
replyTo: worker.email replyTo: worker.email
}; };
await got.get(`${origin}/api/email/payment-update`, { const email = new Email('payment-update', params);
searchParams: params await email.send();
});
} }
const fullUrl = `${origin}/#!/client/${instance.id}/billing-data`; const fullUrl = `${origin}/#!/client/${instance.id}/billing-data`;

View File

@ -29,6 +29,9 @@
}, },
"datepickerEnabled": { "datepickerEnabled": {
"type": "boolean" "type": "boolean"
},
"model": {
"type": "string"
} }
}, },
"scopes": { "scopes": {

View File

@ -45,11 +45,13 @@ class Controller extends Section {
} }
showReport() { showReport() {
this.vnReport.show('campaign-metrics', this.reportParams); const path = `Clients/${this.client.id}/campaign-metrics-pdf`;
this.vnReport.show(path, this.reportParams);
} }
sendEmail() { sendEmail() {
this.vnEmail.send('campaign-metrics', this.reportParams); const path = `Clients/${this.client.id}/campaign-metrics-email`;
this.vnEmail.send(path, this.reportParams);
} }
changeGrouped(value) { changeGrouped(value) {

View File

@ -34,15 +34,16 @@ describe('Client', () => {
controller.showReport(); controller.showReport();
const clientId = controller.client.id;
const expectedParams = { const expectedParams = {
recipientId: 1101, recipientId: clientId,
from: now, from: now,
to: now to: now
}; };
const serializedParams = $httpParamSerializer(expectedParams); const serializedParams = $httpParamSerializer(expectedParams);
const path = `api/report/campaign-metrics?${serializedParams}`; const expectedPath = `api/Clients/${clientId}/campaign-metrics-pdf?${serializedParams}`;
expect(window.open).toHaveBeenCalledWith(path); expect(window.open).toHaveBeenCalledWith(expectedPath);
}); });
}); });
@ -53,16 +54,10 @@ describe('Client', () => {
from: now, from: now,
to: now to: now
}; };
const expectedParams = { const clientId = controller.client.id;
recipientId: 1101, const expectedPath = `Clients/${clientId}/campaign-metrics-email`;
from: now,
to: now
};
const serializedParams = $httpParamSerializer(expectedParams); $httpBackend.expect('POST', expectedPath).respond({});
const path = `email/campaign-metrics?${serializedParams}`;
$httpBackend.expect('GET', path).respond({});
controller.sendEmail(); controller.sendEmail();
$httpBackend.flush(); $httpBackend.flush();
}); });

View File

@ -77,11 +77,13 @@ export default class Controller extends Section {
onSendClientConsumption() { onSendClientConsumption() {
const clientIds = this.checked.map(client => client.id); const clientIds = this.checked.map(client => client.id);
const params = Object.assign({ const params = {
clientIds: clientIds clients: clientIds,
}, this.campaign); from: this.campaign.from,
to: this.campaign.to
};
this.$http.post('schedule/consumption', params) this.$http.post('ClientConsumptionQueues', {params})
.then(() => this.$.filters.hide()) .then(() => this.$.filters.hide())
.then(() => this.vnApp.showSuccess(this.$t('Notifications sent!'))); .then(() => this.vnApp.showSuccess(this.$t('Notifications sent!')));
} }

View File

@ -61,7 +61,6 @@ describe('Client notification', () => {
controller.$.filters = {hide: () => {}}; controller.$.filters = {hide: () => {}};
controller.campaign = { controller.campaign = {
id: 1,
from: new Date(), from: new Date(),
to: new Date() to: new Date()
}; };
@ -71,10 +70,10 @@ describe('Client notification', () => {
data[1].$checked = true; data[1].$checked = true;
const params = Object.assign({ const params = Object.assign({
clientIds: [1101, 1102] clients: [1101, 1102]
}, controller.campaign); }, controller.campaign);
$httpBackend.expect('POST', `schedule/consumption`, params).respond(200, params); $httpBackend.expect('POST', `ClientConsumptionQueues`, {params}).respond(200, params);
controller.onSendClientConsumption(); controller.onSendClientConsumption();
$httpBackend.flush(); $httpBackend.flush();

View File

@ -7,6 +7,15 @@
<vn-crud-model <vn-crud-model
auto-load="true" auto-load="true"
url="Samples/visible" url="Samples/visible"
fields="[
'id',
'code',
'description',
'model',
'hasCompany',
'hasPreview',
'datepickerEnabled'
]"
data="samplesVisible" data="samplesVisible"
order="description"> order="description">
</vn-crud-model> </vn-crud-model>
@ -77,7 +86,7 @@
<vn-button <vn-button
disabled="!sampleType.selection.hasPreview" disabled="!sampleType.selection.hasPreview"
label="Preview" label="Preview"
ng-click="$ctrl.showPreview()"> ng-click="$ctrl.preview()">
</vn-button> </vn-button>
<vn-button <vn-button
class="cancel" class="cancel"

View File

@ -3,12 +3,13 @@ import Section from 'salix/components/section';
import './style.scss'; import './style.scss';
class Controller extends Section { class Controller extends Section {
constructor($element, $) { constructor($element, $, vnEmail) {
super($element, $); super($element, $);
this.clientSample = { this.clientSample = {
clientFk: this.$params.id, clientFk: this.$params.id,
companyId: this.vnConfig.companyFk companyId: this.vnConfig.companyFk
}; };
this.vnEmail = vnEmail;
} }
get client() { get client() {
@ -36,61 +37,86 @@ class Controller extends Section {
onSubmit() { onSubmit() {
this.$.watcher.check(); this.$.watcher.check();
this.$.watcher.realSubmit().then(() =>
this.sendSample() const validationMessage = this.validate();
); if (validationMessage)
return this.vnApp.showError(this.$t(validationMessage));
this.$.watcher.realSubmit().then(() => this.send());
} }
showPreview() { validate() {
this.send(true, res => {
this.$.showPreview.show();
const dialog = document.body.querySelector('div.vn-dialog');
const body = dialog.querySelector('tpl-body');
const scroll = dialog.querySelector('div:first-child');
body.innerHTML = res.data;
scroll.scrollTop = 0;
});
}
sendSample() {
this.send(false, () => {
this.vnApp.showSuccess(this.$t('Notification sent!'));
this.$state.go('client.card.sample.index');
});
}
send(isPreview, cb) {
const sampleType = this.$.sampleType.selection; const sampleType = this.$.sampleType.selection;
const params = {
recipientId: this.$params.id,
recipient: this.clientSample.recipient,
replyTo: this.clientSample.replyTo
};
if (!params.recipient) if (!this.clientSample.recipient)
return this.vnApp.showError(this.$t('Email cannot be blank')); return 'Email cannot be blank';
if (!sampleType) if (!sampleType)
return this.vnApp.showError(this.$t('Choose a sample')); return 'Choose a sample';
if (sampleType.hasCompany && !this.clientSample.companyFk) if (sampleType.hasCompany && !this.clientSample.companyFk)
return this.vnApp.showError(this.$t('Choose a company')); return 'Choose a company';
if (sampleType.datepickerEnabled && !this.clientSample.from)
return 'Choose a date';
return;
}
setParams(params) {
const sampleType = this.$.sampleType.selection;
if (sampleType.hasCompany) if (sampleType.hasCompany)
params.companyId = this.clientSample.companyFk; params.companyId = this.clientSample.companyFk;
if (sampleType.datepickerEnabled && !this.clientSample.from)
return this.vnApp.showError(this.$t('Choose a date'));
if (sampleType.datepickerEnabled) if (sampleType.datepickerEnabled)
params.from = this.clientSample.from; params.from = this.clientSample.from;
}
let query = `email/${sampleType.code}`; preview() {
if (isPreview) const sampleType = this.$.sampleType.selection;
query = `email/${sampleType.code}/preview`;
this.$http.get(query, {params}).then(cb); const params = {
recipientId: this.$params.id
};
const validationMessage = this.validate();
if (validationMessage)
return this.vnApp.showError(this.$t(validationMessage));
this.setParams(params);
const path = `${sampleType.model}/${this.$params.id}/${sampleType.code}-html`;
this.$http.get(path, {params})
.then(response => {
this.$.showPreview.show();
const dialog = document.body.querySelector('div.vn-dialog');
const body = dialog.querySelector('tpl-body');
const scroll = dialog.querySelector('div:first-child');
body.innerHTML = response.data;
scroll.scrollTop = 0;
});
}
send() {
const sampleType = this.$.sampleType.selection;
const params = {
recipientId: this.client.id,
recipient: this.clientSample.recipient,
replyTo: this.clientSample.replyTo
};
const validationMessage = this.validate();
if (validationMessage)
return this.vnApp.showError(this.$t(validationMessage));
this.setParams(params);
const path = `${sampleType.model}/${this.$params.id}/${sampleType.code}-email`;
this.vnEmail.send(path, params)
.then(() => this.$state.go('client.card.sample.index'));
} }
getWorkerEmail() { getWorkerEmail() {
@ -103,7 +129,7 @@ class Controller extends Section {
} }
} }
Controller.$inject = ['$element', '$scope']; Controller.$inject = ['$element', '$scope', 'vnEmail'];
ngModule.vnComponent('vnClientSampleCreate', { ngModule.vnComponent('vnClientSampleCreate', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -40,6 +40,7 @@ describe('Client', () => {
$httpParamSerializer = _$httpParamSerializer_; $httpParamSerializer = _$httpParamSerializer_;
$element = angular.element('<vn-client-sample-create></vn-client-sample-create>'); $element = angular.element('<vn-client-sample-create></vn-client-sample-create>');
controller = $componentController('vnClientSampleCreate', {$element, $scope}); controller = $componentController('vnClientSampleCreate', {$element, $scope});
controller._client = {id: 1101};
const element = document.createElement('div'); const element = document.createElement('div');
document.body.querySelector = () => { document.body.querySelector = () => {
return { return {
@ -48,14 +49,26 @@ describe('Client', () => {
} }
}; };
}; };
// $httpBackend.expectGET('EmailUsers?filter=%7B%22where%22:%7B%7D%7D').respond();
})); }));
describe('onSubmit()', () => { describe('onSubmit()', () => {
it(`should call sendSample() method`, () => { it(`should call send() method`, () => {
jest.spyOn(controller, 'sendSample'); controller.send = jest.fn();
controller.$.sampleType.selection = {
hasCompany: false,
code: 'MyReport',
model: 'Clients'
};
controller.clientSample = {
recipient: 'email@email'
};
controller.onSubmit(); controller.onSubmit();
expect(controller.sendSample).toHaveBeenCalledWith(); expect(controller.send).toHaveBeenCalledWith();
}); });
}); });
@ -65,13 +78,14 @@ describe('Client', () => {
controller.$.sampleType.selection = { controller.$.sampleType.selection = {
hasCompany: false, hasCompany: false,
code: 'MyReport' code: 'MyReport',
model: 'Clients'
}; };
controller.clientSample = { controller.clientSample = {
recipientId: 1101 recipientId: 1101
}; };
controller.send(false, () => {}); controller.send();
expect(controller.$http.get).not.toHaveBeenCalled(); expect(controller.$http.get).not.toHaveBeenCalled();
}); });
@ -85,7 +99,7 @@ describe('Client', () => {
recipient: 'client@email.com' recipient: 'client@email.com'
}; };
controller.send(false, () => {}); controller.send();
expect(controller.$http.get).not.toHaveBeenCalled(); expect(controller.$http.get).not.toHaveBeenCalled();
}); });
@ -102,84 +116,81 @@ describe('Client', () => {
recipient: 'client@email.com' recipient: 'client@email.com'
}; };
controller.send(false, () => {}); controller.send();
expect(controller.$http.get).not.toHaveBeenCalled(); expect(controller.$http.get).not.toHaveBeenCalled();
}); });
it(`should perform an HTTP query without passing companyFk param`, () => { it(`should perform an HTTP query without passing companyFk param`, () => {
$state.go = jest.fn();
controller.$.sampleType.selection = { controller.$.sampleType.selection = {
hasCompany: false, hasCompany: false,
code: 'MyReport' code: 'my-report',
model: 'Clients'
}; };
controller.clientSample = { controller.clientSample = {
recipientId: 1101, recipientId: 1101,
recipient: 'client@email.com' recipient: 'client@email.com'
}; };
const expectedParams = {
recipientId: 1101,
recipient: 'client@email.com'
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`).respond(true); const expectedPath = `Clients/${controller.client.id}/my-report-email`;
controller.send(false, () => {}); $httpBackend.expect('POST', expectedPath).respond(true);
controller.send();
$httpBackend.flush(); $httpBackend.flush();
}); });
it(`should perform an HTTP query passing companyFk param`, () => { it(`should perform an HTTP query passing companyFk param`, () => {
$state.go = jest.fn();
controller.$.sampleType.selection = { controller.$.sampleType.selection = {
hasCompany: true, hasCompany: true,
code: 'MyReport' code: 'my-report',
model: 'Clients'
}; };
controller.clientSample = { controller.clientSample = {
recipientId: 1101, recipientId: 1101,
recipient: 'client@email.com', recipient: 'client@email.com',
companyFk: 442 companyFk: 442
}; };
const expectedParams = {
recipientId: 1101,
recipient: 'client@email.com',
companyId: 442
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`).respond(true); const expectedPath = `Clients/${controller.client.id}/my-report-email`;
controller.send(false, () => {}); $httpBackend.expect('POST', expectedPath).respond(true);
controller.send();
$httpBackend.flush(); $httpBackend.flush();
}); });
}); });
describe('showPreview()', () => { describe('preview()', () => {
it(`should open a sample preview`, () => { it(`should open a sample preview`, () => {
jest.spyOn(controller.$.showPreview, 'show'); jest.spyOn(controller.$.showPreview, 'show');
controller.send = (isPreview, cb) => { controller.$.sampleType.selection = {
cb({ hasCompany: true,
data: '<div></div>' code: 'my-report',
}); model: 'Clients'
}; };
controller.showPreview(); controller.clientSample = {
recipientId: 1101,
recipient: 'client@email.com',
companyFk: 442
};
const expectedParams = {
companyId: 442,
recipientId: 1101
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `Clients/${controller.client.id}/my-report-html?${serializedParams}`;
$httpBackend.expect('GET', expectedPath).respond(true);
controller.preview();
$httpBackend.flush();
expect(controller.$.showPreview.show).toHaveBeenCalledWith(); expect(controller.$.showPreview.show).toHaveBeenCalledWith();
}); });
}); });
describe('sendSample()', () => {
it(`should perform a query (GET) and call go() method`, () => {
jest.spyOn(controller.$state, 'go');
controller.send = (isPreview, cb) => {
cb({
data: true
});
};
controller.sendSample();
expect(controller.$state.go).toHaveBeenCalledWith('client.card.sample.index');
});
});
describe('getWorkerEmail()', () => { describe('getWorkerEmail()', () => {
it(`should perform a query and then set the replyTo property to the clientSample object`, () => { it(`should perform a query and then set the replyTo property to the clientSample object`, () => {
const expectedEmail = 'batman@arkhamcity.com'; const expectedEmail = 'batman@arkhamcity.com';

View File

@ -52,7 +52,7 @@ module.exports = Self => {
switch (field) { switch (field) {
case 'size': case 'size':
case 'density': case 'weightByPiece':
case 'description': case 'description':
case 'packingOut': case 'packingOut':
modelName = 'Item'; modelName = 'Item';

View File

@ -0,0 +1,54 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('entryOrderPdf', {
description: 'Returns the entry order pdf',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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/entry-order-pdf',
verb: 'GET'
}
});
Self.entryOrderPdf = 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('entry-order', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -157,7 +157,7 @@ module.exports = Self => {
i.image, i.image,
i.id AS itemFk, i.id AS itemFk,
i.size, i.size,
i.density, i.weightByPiece,
it.code, it.code,
i.typeFk, i.typeFk,
i.family, i.family,

View File

@ -6,4 +6,5 @@ module.exports = Self => {
require('../methods/entry/importBuys')(Self); require('../methods/entry/importBuys')(Self);
require('../methods/entry/importBuysPreview')(Self); require('../methods/entry/importBuysPreview')(Self);
require('../methods/entry/lastItemBuys')(Self); require('../methods/entry/lastItemBuys')(Self);
require('../methods/entry/entryOrderPdf')(Self);
}; };

View File

@ -86,9 +86,7 @@ class Controller extends Descriptor {
} }
showEntryReport() { showEntryReport() {
this.vnReport.show('entry-order', { this.vnReport.show(`Entries/${this.id}/entry-order-pdf`);
entryId: this.entry.id
});
} }
} }

View File

@ -17,13 +17,10 @@ describe('Entry Component vnEntryDescriptor', () => {
jest.spyOn(controller.vnReport, 'show'); jest.spyOn(controller.vnReport, 'show');
window.open = jasmine.createSpy('open'); window.open = jasmine.createSpy('open');
const params = {
clientId: controller.vnConfig.storage.currentUserWorkerId,
entryId: entry.id
};
controller.showEntryReport(); controller.showEntryReport();
const expectedPath = `Entries/${entry.id}/entry-order-pdf`;
expect(controller.vnReport.show).toHaveBeenCalledWith('entry-order', params); expect(controller.vnReport.show).toHaveBeenCalledWith(expectedPath);
}); });
}); });

View File

@ -68,8 +68,8 @@
<th field="origin"> <th field="origin">
<span translate>Origin</span> <span translate>Origin</span>
</th> </th>
<th field="density"> <th field="weightByPiece">
<span translate>Density</span> <span translate>Weight/Piece</span>
</th> </th>
<th field="isActive"> <th field="isActive">
<span translate>Active</span> <span translate>Active</span>
@ -183,7 +183,7 @@
{{::buy.intrastat}} {{::buy.intrastat}}
</td> </td>
<td>{{::buy.origin}}</td> <td>{{::buy.origin}}</td>
<td>{{::buy.density}}</td> <td>{{::buy.weightByPiece}}</td>
<td> <td>
<vn-check <vn-check
disabled="true" disabled="true"

View File

@ -80,7 +80,7 @@ export default class Controller extends Section {
{field: 'weight', displayName: this.$t('Weight')}, {field: 'weight', displayName: this.$t('Weight')},
{field: 'description', displayName: this.$t('Description')}, {field: 'description', displayName: this.$t('Description')},
{field: 'size', displayName: this.$t('Size')}, {field: 'size', displayName: this.$t('Size')},
{field: 'density', displayName: this.$t('Density')}, {field: 'weightByPiece', displayName: this.$t('weight/Piece')},
{field: 'packingOut', displayName: this.$t('PackingOut')}, {field: 'packingOut', displayName: this.$t('PackingOut')},
{field: 'landing', displayName: this.$t('Landing')} {field: 'landing', displayName: this.$t('Landing')}
]; ];
@ -103,7 +103,7 @@ export default class Controller extends Section {
switch (param) { switch (param) {
case 'id': case 'id':
case 'size': case 'size':
case 'density': case 'weightByPiece':
case 'isActive': case 'isActive':
case 'family': case 'family':
case 'minPrice': case 'minPrice':

View File

@ -1,7 +1,5 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const print = require('vn-print');
const path = require('path');
const axios = require('axios');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('createPdf', { Self.remoteMethodCtx('createPdf', {
@ -27,9 +25,7 @@ module.exports = Self => {
Self.createPdf = async function(ctx, id, options) { Self.createPdf = async function(ctx, id, options) {
const models = Self.app.models; const models = Self.app.models;
const headers = ctx.req.headers; const userId = ctx.req.accessToken.userId;
const origin = headers.origin;
const auth = ctx.req.accessToken;
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`); throw new UserError(`Action not allowed on the test environment`);
@ -45,10 +41,9 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
let fileSrc;
try { try {
const invoiceOut = await Self.findById(id, null, myOptions); const invoiceOut = await Self.findById(id, null, myOptions);
const hasInvoicing = await models.Account.hasRole(auth.userId, 'invoicing', myOptions); const hasInvoicing = await models.Account.hasRole(userId, 'invoicing', myOptions);
if (invoiceOut.hasPdf && !hasInvoicing) if (invoiceOut.hasPdf && !hasInvoicing)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
@ -57,35 +52,27 @@ module.exports = Self => {
hasPdf: true hasPdf: true
}, myOptions); }, myOptions);
return axios.get(`${origin}/api/report/invoice`, { const invoiceReport = new print.Report('invoice', {
responseType: 'stream', reference: invoiceOut.ref,
params: { recipientId: invoiceOut.clientFk
authorization: auth.id,
refFk: invoiceOut.ref
}
}).then(async response => {
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString();
const container = await models.InvoiceContainer.container(year);
const rootPath = container.client.root;
const fileName = `${year}${invoiceOut.ref}.pdf`;
const src = path.join(rootPath, year, month, day);
fileSrc = path.join(src, fileName);
await fs.mkdir(src, {recursive: true});
if (tx) await tx.commit();
response.data.pipe(fs.createWriteStream(fileSrc));
}).catch(async e => {
if (fs.existsSync(fileSrc))
await fs.unlink(fileSrc);
throw e;
}); });
const stream = await invoiceReport.toPdfStream();
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString();
const fileName = `${year}${invoiceOut.ref}.pdf`;
// Store invoice
print.storage.write(stream, {
type: 'invoice',
path: `${year}/${month}/${day}`,
fileName: fileName
});
if (tx) await tx.commit();
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;

View File

@ -62,8 +62,14 @@ module.exports = Self => {
name: fileName name: fileName
}; };
await fs.access(file.path); try {
let stream = fs.createReadStream(file.path); await fs.access(file.path);
} catch (error) {
await Self.createPdf(ctx, id);
}
const stream = fs.createReadStream(file.path);
return [stream, file.contentType, `filename="${file.name}"`]; return [stream, file.contentType, `filename="${file.name}"`];
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') if (error.code === 'ENOENT')

View File

@ -0,0 +1,55 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('exportationPdf', {
description: 'Returns the exportation pdf',
accessType: 'READ',
accepts: [
{
arg: 'reference',
type: 'string',
required: true,
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
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: '/:reference/exportation-pdf',
verb: 'GET'
}
});
Self.exportationPdf = async(ctx, reference) => {
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('exportation', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${reference}.pdf"`];
};
};

View File

@ -138,7 +138,7 @@ module.exports = Self => {
if (newInvoice.id) { if (newInvoice.id) {
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions); await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
query = `INSERT IGNORE INTO invoiceOut_queue(invoiceFk) VALUES(?)`; query = `INSERT IGNORE INTO invoiceOutQueue(invoiceFk) VALUES(?)`;
await Self.rawSql(query, [newInvoice.id], myOptions); await Self.rawSql(query, [newInvoice.id], myOptions);
invoicesIds.push(newInvoice.id); invoicesIds.push(newInvoice.id);

View File

@ -0,0 +1,85 @@
const {toCSV} = require('vn-loopback/util/csv');
module.exports = Self => {
Self.remoteMethod('invoiceCsv', {
description: 'Returns the delivery note csv',
accessType: 'READ',
accepts: [
{
arg: 'reference',
type: 'string',
required: true,
description: 'The invoice reference',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The client id',
required: false
}
],
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: '/:reference/invoice-csv',
verb: 'GET'
}
});
Self.invoiceCsv = async reference => {
const sales = await Self.rawSql(`
SELECT io.ref Invoice,
io.issued InvoiceDate,
s.ticketFk Ticket,
s.itemFk Item,
s.concept Description,
i.size,
i.subName Producer,
s.quantity Quantity,
s.price Price,
s.discount Discount,
s.created Created,
tc.code Taxcode,
tc.description TaxDescription,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7,
i.tag8,
i.value8,
i.tag9,
i.value9,
i.tag10,
i.value10
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
JOIN item i ON i.id = s.itemFk
JOIN supplier s2 ON s2.id = t.companyFk
JOIN itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = s2.countryFk
JOIN taxClass tc ON tc.id = itc.taxClassFk
JOIN invoiceOut io ON io.ref = t.refFk
WHERE t.refFk = ?
ORDER BY s.ticketFk, s.created`, [reference]);
const content = toCSV(sales);
return [content, 'text/csv', `inline; filename="doc-${reference}.pdf"`];
};
};

View File

@ -0,0 +1,117 @@
const {Email} = require('vn-print');
const {toCSV} = require('vn-loopback/util/csv');
module.exports = Self => {
Self.remoteMethodCtx('invoiceCsvEmail', {
description: 'Returns the delivery note csv',
accessType: 'READ',
accepts: [
{
arg: 'reference',
type: 'string',
required: true,
description: 'The invoice reference',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The client id',
required: false
}
],
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: '/:reference/invoice-csv-email',
verb: 'POST'
}
});
Self.invoiceCsvEmail = async(ctx, reference) => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const sales = await Self.rawSql(`
SELECT io.ref Invoice,
io.issued InvoiceDate,
s.ticketFk Ticket,
s.itemFk Item,
s.concept Description,
i.size,
i.subName Producer,
s.quantity Quantity,
s.price Price,
s.discount Discount,
s.created Created,
tc.code Taxcode,
tc.description TaxDescription,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7,
i.tag8,
i.value8,
i.tag9,
i.value9,
i.tag10,
i.value10
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
JOIN item i ON i.id = s.itemFk
JOIN supplier s2 ON s2.id = t.companyFk
JOIN itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = s2.countryFk
JOIN taxClass tc ON tc.id = itc.taxClassFk
JOIN invoiceOut io ON io.ref = t.refFk
WHERE t.refFk = ?
ORDER BY s.ticketFk, s.created`, [reference]);
const content = toCSV(sales);
const fileName = `invoice_${reference}.csv`;
const email = new Email('invoice', params);
return email.send({
overrideAttachments: true,
attachments: [{
filename: fileName,
content: content
}]
});
};
};

View File

@ -0,0 +1,58 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('invoiceEmail', {
description: 'Sends the invoice email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'reference',
type: 'string',
required: true,
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:reference/invoice-email',
verb: 'POST'
}
});
Self.invoiceEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('invoice', params);
return email.send();
};
};

View File

@ -1,15 +1,22 @@
const db = require('vn-print/core/database'); const {Email, Report, storage} = require('vn-print');
const Email = require('vn-print/core/email');
const Report = require('vn-print/core/report');
const storage = require('vn-print/core/storage');
module.exports = async function(request, response, next) { module.exports = Self => {
try { Self.remoteMethod('sendQueued', {
response.status(200).json({ description: 'Send all queued invoices',
message: 'Success' accessType: 'WRITE',
}); accepts: [],
returns: {
type: 'object',
root: true
},
http: {
path: '/send-queued',
verb: 'POST'
}
});
const invoices = await db.rawSql(` Self.sendQueued = async() => {
const invoices = await Self.rawSql(`
SELECT SELECT
io.id, io.id,
io.clientFk, io.clientFk,
@ -21,7 +28,7 @@ module.exports = async function(request, response, next) {
c.hasToInvoice, c.hasToInvoice,
co.hasDailyInvoice, co.hasDailyInvoice,
eu.email salesPersonEmail eu.email salesPersonEmail
FROM invoiceOut_queue ioq FROM invoiceOutQueue ioq
JOIN invoiceOut io ON io.id = ioq.invoiceFk JOIN invoiceOut io ON io.id = ioq.invoiceFk
JOIN client c ON c.id = io.clientFk JOIN client c ON c.id = io.clientFk
JOIN province p ON p.id = c.provinceFk JOIN province p ON p.id = c.provinceFk
@ -29,20 +36,20 @@ module.exports = async function(request, response, next) {
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE status = ''`); WHERE status = ''`);
let connection;
let invoiceId; let invoiceId;
for (const invoiceOut of invoices) { for (const invoiceOut of invoices) {
try { try {
invoiceId = invoiceOut.id; const tx = await Self.beginTransaction({});
connection = await db.getConnection(); const myOptions = {transaction: tx};
connection.query('START TRANSACTION');
const args = Object.assign({ invoiceId = invoiceOut.id;
refFk: invoiceOut.ref,
const args = {
reference: invoiceOut.ref,
recipientId: invoiceOut.clientFk, recipientId: invoiceOut.clientFk,
recipient: invoiceOut.recipient, recipient: invoiceOut.recipient,
replyTo: invoiceOut.salesPersonEmail replyTo: invoiceOut.salesPersonEmail
}, response.locals); };
const invoiceReport = new Report('invoice', args); const invoiceReport = new Report('invoice', args);
const stream = await invoiceReport.toPdfStream(); const stream = await invoiceReport.toPdfStream();
@ -61,7 +68,11 @@ module.exports = async function(request, response, next) {
fileName: fileName fileName: fileName
}); });
connection.query('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id]); await Self.rawSql(`
UPDATE invoiceOut
SET hasPdf = true
WHERE id = ?`,
[invoiceOut.id], myOptions);
const isToBeMailed = invoiceOut.recipient && invoiceOut.salesPersonFk && invoiceOut.isToBeMailed; const isToBeMailed = invoiceOut.recipient && invoiceOut.salesPersonFk && invoiceOut.isToBeMailed;
@ -94,22 +105,29 @@ module.exports = async function(request, response, next) {
} }
// Update queue status // Update queue status
const date = new Date(); const date = new Date();
sql = `UPDATE invoiceOut_queue await Self.rawSql(`
UPDATE invoiceOutQueue
SET status = "printed", SET status = "printed",
printed = ? printed = ?
WHERE invoiceFk = ?`; WHERE invoiceFk = ?`,
connection.query(sql, [date, invoiceOut.id]); [date, invoiceOut.id], myOptions);
connection.query('COMMIT');
await tx.commit();
} catch (error) { } catch (error) {
connection.query('ROLLBACK'); await tx.rollback();
connection.release();
sql = `UPDATE invoiceOut_queue await Self.rawSql(`
UPDATE invoiceOutQueue
SET status = ? SET status = ?
WHERE invoiceFk = ?`; WHERE invoiceFk = ?`,
await db.rawSql(sql, [error.message, invoiceId]); [error.message, invoiceId]);
throw e;
} }
} }
} catch (error) {
next(error); return {
} message: 'Success'
};
};
}; };

View File

@ -1,7 +1,6 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
const fs = require('fs-extra'); const print = require('vn-print');
const axios = require('axios');
describe('InvoiceOut createPdf()', () => { describe('InvoiceOut createPdf()', () => {
const userId = 1; const userId = 1;
@ -16,22 +15,15 @@ describe('InvoiceOut createPdf()', () => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
const response = {
data: { spyOn(print, 'Report').and.returnValue({
pipe: () => {}, toPdfStream: () => {
on: () => {}, return '';
} }
};
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(response)));
spyOn(models.InvoiceContainer, 'container').and.returnValue({
client: {root: '/path'}
});
spyOn(fs, 'mkdir').and.returnValue(true);
spyOn(fs, 'createWriteStream').and.returnValue({
on: (event, cb) => cb(),
end: () => {}
}); });
spyOn(print.storage, 'write').and.returnValue(true);
const tx = await models.InvoiceOut.beginTransaction({}); const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -8,6 +8,9 @@ describe('InvoiceOut globalInvoicing()', () => {
const invoiceSerial = 'A'; const invoiceSerial = 'A';
const activeCtx = { const activeCtx = {
accessToken: {userId: userId}, accessToken: {userId: userId},
__: value => {
return value;
}
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
@ -22,7 +25,7 @@ describe('InvoiceOut globalInvoicing()', () => {
invoiceDate: new Date(), invoiceDate: new Date(),
maxShipped: new Date(), maxShipped: new Date(),
fromClientId: clientId, fromClientId: clientId,
toClientId: clientId, toClientId: 1106,
companyFk: companyFk companyFk: companyFk
}; };
const result = await models.InvoiceOut.globalInvoicing(ctx, options); const result = await models.InvoiceOut.globalInvoicing(ctx, options);

View File

@ -9,4 +9,9 @@ module.exports = Self => {
require('../methods/invoiceOut/createManualInvoice')(Self); require('../methods/invoiceOut/createManualInvoice')(Self);
require('../methods/invoiceOut/globalInvoicing')(Self); require('../methods/invoiceOut/globalInvoicing')(Self);
require('../methods/invoiceOut/refund')(Self); require('../methods/invoiceOut/refund')(Self);
require('../methods/invoiceOut/invoiceEmail')(Self);
require('../methods/invoiceOut/exportationPdf')(Self);
require('../methods/invoiceOut/sendQueued')(Self);
require('../methods/invoiceOut/invoiceCsv')(Self);
require('../methods/invoiceOut/invoiceCsvEmail')(Self);
}; };

View File

@ -81,21 +81,19 @@ class Controller extends Section {
}); });
} }
showCsvInvoice() {
this.vnReport.showCsv('invoice', {
recipientId: this.invoiceOut.client.id,
refFk: this.invoiceOut.ref
});
}
sendPdfInvoice($data) { sendPdfInvoice($data) {
if (!$data.email) if (!$data.email)
return this.vnApp.showError(this.$t(`The email can't be empty`)); return this.vnApp.showError(this.$t(`The email can't be empty`));
return this.vnEmail.send('invoice', { return this.vnEmail.send(`InvoiceOuts/${this.invoiceOut.ref}/invoice-email`, {
recipientId: this.invoiceOut.client.id, recipientId: this.invoiceOut.client.id,
recipient: $data.email, recipient: $data.email
refFk: this.invoiceOut.ref });
}
showCsvInvoice() {
this.vnReport.show(`InvoiceOuts/${this.invoiceOut.ref}/invoice-csv`, {
recipientId: this.invoiceOut.client.id
}); });
} }
@ -103,15 +101,14 @@ class Controller extends Section {
if (!$data.email) if (!$data.email)
return this.vnApp.showError(this.$t(`The email can't be empty`)); return this.vnApp.showError(this.$t(`The email can't be empty`));
return this.vnEmail.sendCsv('invoice', { return this.vnEmail.send(`InvoiceOuts/${this.invoiceOut.ref}/invoice-csv-email`, {
recipientId: this.invoiceOut.client.id, recipientId: this.invoiceOut.client.id,
recipient: $data.email, recipient: $data.email
refFk: this.invoiceOut.ref
}); });
} }
showExportationLetter() { showExportationLetter() {
this.vnReport.show('exportation', { this.vnReport.show(`InvoiceOuts/${this.invoiceOut.ref}/exportation-pdf`, {
recipientId: this.invoiceOut.client.id, recipientId: this.invoiceOut.client.id,
refFk: this.invoiceOut.ref refFk: this.invoiceOut.ref
}); });

View File

@ -41,11 +41,10 @@ describe('vnInvoiceOutDescriptorMenu', () => {
jest.spyOn(window, 'open').mockReturnThis(); jest.spyOn(window, 'open').mockReturnThis();
const expectedParams = { const expectedParams = {
recipientId: invoiceOut.client.id, recipientId: invoiceOut.client.id
refFk: invoiceOut.ref
}; };
const serializedParams = $httpParamSerializer(expectedParams); const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/invoice/download?${serializedParams}`; const expectedPath = `api/InvoiceOuts/${invoiceOut.ref}/invoice-csv?${serializedParams}`;
controller.showCsvInvoice(); controller.showCsvInvoice();
expect(window.open).toHaveBeenCalledWith(expectedPath); expect(window.open).toHaveBeenCalledWith(expectedPath);
@ -84,14 +83,8 @@ describe('vnInvoiceOutDescriptorMenu', () => {
jest.spyOn(controller.vnApp, 'showMessage'); jest.spyOn(controller.vnApp, 'showMessage');
const $data = {email: 'brucebanner@gothamcity.com'}; const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
recipient: $data.email,
recipientId: invoiceOut.client.id,
refFk: invoiceOut.ref
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`email/invoice?${serializedParams}`).respond(); $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.ref}/invoice-email`).respond();
controller.sendPdfInvoice($data); controller.sendPdfInvoice($data);
$httpBackend.flush(); $httpBackend.flush();
@ -104,14 +97,8 @@ describe('vnInvoiceOutDescriptorMenu', () => {
jest.spyOn(controller.vnApp, 'showMessage'); jest.spyOn(controller.vnApp, 'showMessage');
const $data = {email: 'brucebanner@gothamcity.com'}; const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
recipient: $data.email,
recipientId: invoiceOut.client.id,
refFk: invoiceOut.ref
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`csv/invoice/send?${serializedParams}`).respond(); $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.ref}/invoice-csv-email`).respond();
controller.sendCsvInvoice($data); controller.sendCsvInvoice($data);
$httpBackend.flush(); $httpBackend.flush();

View File

@ -0,0 +1,29 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('buyerWasteEmail', {
description: 'Sends the buyer waste email',
accessType: 'WRITE',
accepts: [],
returns: {
type: ['object'],
root: true
},
http: {
path: '/buyer-waste-email',
verb: 'POST'
}
});
Self.buyerWasteEmail = async ctx => {
const models = Self.app.models;
const itemConfig = await models.ItemConfig.findOne();
const email = new Email('buyer-week-waste', {
recipient: itemConfig.wasteRecipients,
lang: ctx.req.getLocale()
});
return email.send();
};
};

View File

@ -42,7 +42,6 @@ module.exports = Self => {
origin.itemTag = undefined; origin.itemTag = undefined;
origin.description = undefined; origin.description = undefined;
origin.image = undefined;
origin.comment = undefined; origin.comment = undefined;
origin.size = undefined; origin.size = undefined;

View File

@ -160,7 +160,7 @@ module.exports = Self => {
i.subName, i.subName,
i.isActive, i.isActive,
i.stems, i.stems,
i.density, i.weightByPiece,
i.stemMultiplier, i.stemMultiplier,
i.typeFk, i.typeFk,
i.isFloramondo, i.isFloramondo,

View File

@ -0,0 +1,72 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('labelPdf', {
description: 'Returns the item label pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'warehouseId',
type: 'number',
description: 'The warehouse id',
required: true
},
{
arg: 'labelNumber',
type: 'number',
required: false
},
{
arg: 'totalLabels',
type: 'number',
required: false
}
],
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/label-pdf',
verb: 'GET'
}
});
Self.labelPdf = 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('item-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="item-${id}.pdf"`];
};
};

View File

@ -21,7 +21,7 @@ describe('item clone()', () => {
const result = await models.Item.clone(itemFk, options); const result = await models.Item.clone(itemFk, options);
expect(result.id).toEqual(nextItemId); expect(result.id).toEqual(nextItemId);
expect(result.image).toBeUndefined(); expect(result.image).toBeDefined();
expect(result.itemTag).toBeUndefined(); expect(result.itemTag).toBeUndefined();
expect(result.comment).toBeUndefined(); expect(result.comment).toBeUndefined();
expect(result.description).toBeUndefined(); expect(result.description).toBeUndefined();

View File

@ -23,6 +23,9 @@
"ItemCategory": { "ItemCategory": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ItemConfig": {
"dataSource": "vn"
},
"ItemFamily": { "ItemFamily": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,21 @@
{
"name": "ItemConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemConfig"
}
},
"properties": {
"isItemTagTriggerDisabled": {
"type": "boolean"
},
"monthToDeactivate": {
"type": "boolean"
},
"wasteRecipients": {
"type": "string",
"description": "Buyers waste report recipients"
}
}
}

View File

@ -15,6 +15,8 @@ module.exports = Self => {
require('../methods/item/getWasteByItem')(Self); require('../methods/item/getWasteByItem')(Self);
require('../methods/item/createIntrastat')(Self); require('../methods/item/createIntrastat')(Self);
require('../methods/item/activeBuyers')(Self); require('../methods/item/activeBuyers')(Self);
require('../methods/item/buyerWasteEmail')(Self);
require('../methods/item/labelPdf')(Self);
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'}); Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});

View File

@ -53,9 +53,9 @@
"type": "number", "type": "number",
"description": "Relevancy" "description": "Relevancy"
}, },
"density": { "weightByPiece": {
"type": "number", "type": "number",
"description": "Density" "description": "WeightByPiece"
}, },
"stemMultiplier": { "stemMultiplier": {
"type": "number", "type": "number",
@ -143,6 +143,9 @@
}, },
"packingShelve": { "packingShelve": {
"type": "number" "type": "number"
},
"weightByPiece": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -124,9 +124,8 @@
<vn-input-number <vn-input-number
vn-one vn-one
min="0" min="0"
step="0.01" label="Weight/Piece"
label="Density" ng-model="$ctrl.item.weightByPiece"
ng-model="$ctrl.item.density"
rule> rule>
</vn-input-number> </vn-input-number>
<vn-autocomplete <vn-autocomplete

View File

@ -45,8 +45,8 @@
<th field="buyerFk"> <th field="buyerFk">
<span translate>Buyer</span> <span translate>Buyer</span>
</th> </th>
<th field="density"> <th field="weightByPiece">
<span translate>Density</span> <span translate>Weight/Piece</span>
</th> </th>
<th field="stemMultiplier"> <th field="stemMultiplier">
<span translate>Multiplier</span> <span translate>Multiplier</span>
@ -117,7 +117,7 @@
{{::item.userName}} {{::item.userName}}
</span> </span>
</td> </td>
<td>{{::item.density}}</td> <td>{{::item.weightByPiece}}</td>
<td>{{::item.stemMultiplier}}</td> <td>{{::item.stemMultiplier}}</td>
<td> <td>
<vn-check <vn-check

View File

@ -87,7 +87,7 @@ class Controller extends Section {
case 'size': case 'size':
case 'subname': case 'subname':
case 'isActive': case 'isActive':
case 'density': case 'weightByPiece':
case 'stemMultiplier': case 'stemMultiplier':
case 'stems': case 'stems':
return {[`i.${param}`]: value}; return {[`i.${param}`]: value};

View File

@ -23,10 +23,12 @@ vn-item-product {
} }
} }
table { vn-item-index {
img { table {
border-radius: 50%; img {
width: 50px; border-radius: 50%;
height: 50px; width: 50px;
height: 50px;
}
} }
} }

View File

@ -40,7 +40,7 @@ Create: Crear
Client card: Ficha del cliente Client card: Ficha del cliente
Shipped: F. envío Shipped: F. envío
stems: Tallos stems: Tallos
Density: Densidad Weight/Piece: Peso/tallo
Search items by id, name or barcode: Buscar articulos por identificador, nombre o codigo de barras Search items by id, name or barcode: Buscar articulos por identificador, nombre o codigo de barras
SalesPerson: Comercial SalesPerson: Comercial
Concept: Concepto Concept: Concepto

View File

@ -91,8 +91,8 @@
<vn-label-value label="Relevancy" <vn-label-value label="Relevancy"
value="{{$ctrl.summary.item.relevancy}}"> value="{{$ctrl.summary.item.relevancy}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Density" <vn-label-value label="Weight/Piece"
value="{{$ctrl.summary.item.density}}"> value="{{$ctrl.summary.item.weightByPiece}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Expense" <vn-label-value label="Expense"
value="{{$ctrl.summary.item.expense.name}}"> value="{{$ctrl.summary.item.expense.name}}">

View File

@ -210,6 +210,9 @@
</slot-table> </slot-table>
</smart-table> </smart-table>
</vn-card> </vn-card>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
vn-id="workerDescriptor"> vn-id="workerDescriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>

View File

@ -0,0 +1,58 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('driverRouteEmail', {
description: 'Sends the driver route email with an attached PDF',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/driver-route-email',
verb: 'POST'
}
});
Self.driverRouteEmail = async ctx => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('driver-route', params);
return email.send();
};
};

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