Merge branch 'dev' into 4159-tableQ_filter
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2022-10-05 14:36:00 +00:00
commit ccc2375a5e
243 changed files with 4591 additions and 3479 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

@ -1,3 +1,48 @@
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) INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES ('Sector','*','READ','ALLOW','ROLE','employee'); VALUES ('Sector','*','READ','ALLOW','ROLE','employee');
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId) INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)

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

@ -42,8 +42,16 @@ module.exports = class Docker {
let runChown = process.platform != 'linux'; let runChown = process.platform != 'linux';
let network = '';
if (ci) network = '--network="jenkins"';
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 +59,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.jenkins.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');
@ -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

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

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

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

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

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

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

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

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

View File

@ -0,0 +1,55 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('driverRoutePdf', {
description: 'Returns the driver route pdf',
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/driver-route-pdf',
verb: 'GET'
}
});
Self.driverRoutePdf = 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('driver-route', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -10,6 +10,8 @@ module.exports = Self => {
require('../methods/route/getSuggestedTickets')(Self); require('../methods/route/getSuggestedTickets')(Self);
require('../methods/route/unlink')(Self); require('../methods/route/unlink')(Self);
require('../methods/route/updateWorkCenter')(Self); require('../methods/route/updateWorkCenter')(Self);
require('../methods/route/driverRoutePdf')(Self);
require('../methods/route/driverRouteEmail')(Self);
Self.validate('kmStart', validateDistance, { Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 1000' message: 'Distance must be lesser than 1000'

View File

@ -1,6 +1,5 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section { class Controller extends Section {
constructor($element, $) { constructor($element, $) {

View File

@ -1,32 +0,0 @@
@import "variables";
vn-item-product {
display: block;
.id {
background-color: $color-main;
color: $color-font-dark;
margin-bottom: 0;
}
.image {
height: 112px;
width: 112px;
& > img {
max-height: 100%;
max-width: 100%;
border-radius: 3px;
}
}
vn-label-value:first-of-type section{
margin-top: 9px;
}
}
table {
img {
border-radius: 50%;
width: 50px;
height: 50px;
}
}

View File

@ -11,16 +11,16 @@ class Controller extends Descriptor {
} }
showRouteReport() { showRouteReport() {
this.vnReport.show('driver-route', { this.vnReport.show(`Routes/${this.id}/driver-route-pdf`, {
routeId: this.id id: this.id
}); });
} }
sendRouteReport() { sendRouteReport() {
const workerUser = this.route.worker.user; const workerUser = this.route.worker.user;
this.vnEmail.send('driver-route', { this.vnEmail.send(`Routes/${this.id}/driver-route-email`, {
recipient: workerUser.emailUser.email, recipient: workerUser.emailUser.email,
routeId: this.id id: this.id
}); });
} }

View File

@ -0,0 +1,68 @@
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,
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('supplier-campaign-metrics', params);
return email.send();
};
};

View File

@ -0,0 +1,65 @@
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,
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('supplier-campaign-metrics', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -8,6 +8,8 @@ module.exports = Self => {
require('../methods/supplier/updateFiscalData')(Self); require('../methods/supplier/updateFiscalData')(Self);
require('../methods/supplier/consumption')(Self); require('../methods/supplier/consumption')(Self);
require('../methods/supplier/freeAgencies')(Self); require('../methods/supplier/freeAgencies')(Self);
require('../methods/supplier/campaignMetricsPdf')(Self);
require('../methods/supplier/campaignMetricsEmail')(Self);
Self.validatesPresenceOf('name', { Self.validatesPresenceOf('name', {
message: 'The social name cannot be empty' message: 'The social name cannot be empty'

View File

@ -33,7 +33,8 @@ class Controller extends Section {
} }
showReport() { showReport() {
this.vnReport.show('supplier-campaign-metrics', this.reportParams); const path = `Suppliers/${this.supplier.id}/campaign-metrics-pdf`;
this.vnReport.show(path, this.reportParams);
} }
sendEmail() { sendEmail() {
@ -52,7 +53,9 @@ class Controller extends Section {
const params = Object.assign({ const params = Object.assign({
recipient: contact.email recipient: contact.email
}, this.reportParams); }, this.reportParams);
this.vnEmail.send('supplier-campaign-metrics', params);
const path = `Suppliers/${this.supplier.id}/campaign-metrics-email`;
this.vnEmail.send(path, params);
} else { } else {
const message = this.$t(`This supplier doesn't have a contact with an email address`); const message = this.$t(`This supplier doesn't have a contact with an email address`);
this.vnApp.showError(message); this.vnApp.showError(message);

View File

@ -42,7 +42,7 @@ describe('Supplier', () => {
to: now to: now
}; };
const serializedParams = $httpParamSerializer(expectedParams); const serializedParams = $httpParamSerializer(expectedParams);
const path = `api/report/supplier-campaign-metrics?${serializedParams}`; const path = `api/Suppliers/${supplierId}/campaign-metrics-pdf?${serializedParams}`;
expect(window.open).toHaveBeenCalledWith(path); expect(window.open).toHaveBeenCalledWith(path);
}); });
@ -66,7 +66,8 @@ describe('Supplier', () => {
controller.sendEmail(); controller.sendEmail();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showError).toHaveBeenCalledWith(`This supplier doesn't have a contact with an email address`); expect(controller.vnApp.showError)
.toHaveBeenCalledWith(`This supplier doesn't have a contact with an email address`);
}); });
it('should make a GET query sending the report', () => { it('should make a GET query sending the report', () => {
@ -91,16 +92,15 @@ describe('Supplier', () => {
to: now to: now
}; };
const expectedParams = { const expectedParams = {
recipientId: 2,
recipient: 'batman@gothamcity.com', recipient: 'batman@gothamcity.com',
from: now, from: now,
to: now to: now
}; };
serializedParams = $httpParamSerializer(expectedParams); serializedParams = $httpParamSerializer(expectedParams);
const path = `email/supplier-campaign-metrics?${serializedParams}`; const path = `Suppliers/${supplierId}/campaign-metrics-email`;
$httpBackend.expect('GET', path).respond({}); $httpBackend.expect('POST', path).respond({});
controller.sendEmail(); controller.sendEmail();
$httpBackend.flush(); $httpBackend.flush();
}); });

View File

@ -10,7 +10,7 @@ describe('expedition filter()', () => {
const filter = {where: {packagingFk: 1}}; const filter = {where: {packagingFk: 1}};
const response = await models.Expedition.filter(filter, options); const response = await models.Expedition.filter(filter, options);
expect(response.length).toEqual(10); expect(response.length).toBeGreaterThan(1);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -1,28 +1,36 @@
const db = require('vn-print/core/database'); const UserError = require('vn-loopback/util/user-error');
const closure = require('./closure'); const closure = require('./closure');
module.exports = async function(request, response, next) { module.exports = Self => {
try { Self.remoteMethod('closeAll', {
const reqArgs = request.body; description: 'Makes the closure process from all warehouses',
accessType: 'WRITE',
accepts: [],
returns: {
type: 'object',
root: true
},
http: {
path: `/close-all`,
verb: 'POST'
}
});
let toDate = new Date(); Self.closeAll = async() => {
const toDate = new Date();
toDate.setDate(toDate.getDate() - 1); toDate.setDate(toDate.getDate() - 1);
if (reqArgs.to) toDate = reqArgs.to;
const todayMinDate = new Date(); const todayMinDate = new Date();
todayMinDate.setHours(0, 0, 0, 0); todayMinDate.setHours(0, 0, 0, 0);
const todayMaxDate = new Date(); const todayMaxDate = new Date();
todayMinDate.setHours(23, 59, 59, 59); todayMaxDate.setHours(23, 59, 59, 59);
// Prevent closure for current day // Prevent closure for current day
if (toDate >= todayMinDate && toDate <= todayMaxDate) if (toDate >= todayMinDate && toDate <= todayMaxDate)
throw new Error('You cannot close tickets for today'); throw new UserError('You cannot close tickets for today');
console.log(`Making closure up to ${toDate}...`); const tickets = await Self.rawSql(`
const tickets = await db.rawSql(`
SELECT SELECT
t.id, t.id,
t.clientFk, t.clientFk,
@ -47,11 +55,12 @@ module.exports = async function(request, response, next) {
AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?) AND util.dayEnd(?)
AND t.refFk IS NULL AND t.refFk IS NULL
GROUP BY t.id`, [toDate, toDate]); GROUP BY t.id
`, [toDate, toDate]);
await closure.start(tickets, response.locals); await closure(Self, tickets);
await db.rawSql(` await Self.rawSql(`
UPDATE ticket t UPDATE ticket t
JOIN ticketState ts ON t.id = ts.ticketFk JOIN ticketState ts ON t.id = ts.ticketFk
JOIN alertLevel al ON al.id = ts.alertLevel JOIN alertLevel al ON al.id = ts.alertLevel
@ -65,10 +74,8 @@ module.exports = async function(request, response, next) {
AND t.routeFk AND t.routeFk
AND z.name LIKE '%MADRID%'`, [toDate, toDate]); AND z.name LIKE '%MADRID%'`, [toDate, toDate]);
response.status(200).json({ return {
message: 'Success' message: 'Success'
}); };
} catch (error) { };
next(error);
}
}; };

View File

@ -0,0 +1,79 @@
const closure = require('./closure');
module.exports = Self => {
Self.remoteMethodCtx('closeByAgency', {
description: 'Makes the closure process by agency mode',
accessType: 'WRITE',
accepts: [
{
arg: 'agencyModeFk',
type: ['number'],
required: true,
description: 'The agencies mode ids',
},
{
arg: 'warehouseFk',
type: 'number',
description: 'The ticket warehouse id',
required: true
},
{
arg: 'to',
type: 'date',
description: 'Max closure date',
required: true
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/close-by-agency`,
verb: 'POST'
}
});
Self.closeByAgency = async ctx => {
const args = ctx.args;
const tickets = await Self.rawSql(`
SELECT
t.id,
t.clientFk,
t.companyFk,
c.name clientName,
c.email recipient,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED'
AND t.agencyModeFk IN(?)
AND t.warehouseFk = ?
AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [
args.agencyModeFk,
args.warehouseFk,
args.to,
args.to
]);
await closure(Self, tickets);
return {
message: 'Success'
};
};
};

View File

@ -1,15 +1,32 @@
const db = require('vn-print/core/database');
const Email = require('vn-print/core/email');
const closure = require('./closure'); const closure = require('./closure');
const {Email} = require('vn-print');
module.exports = async function(request, response, next) { module.exports = Self => {
try { Self.remoteMethodCtx('closeByRoute', {
const reqArgs = request.body; description: 'Makes the closure process by route',
accessType: 'WRITE',
accepts: [
{
arg: 'routeFk',
type: 'number',
required: true,
description: 'The routes ids',
},
],
returns: {
type: 'object',
root: true
},
http: {
path: `/close-by-route`,
verb: 'POST'
}
});
if (!reqArgs.routeId) Self.closeByRoute = async ctx => {
throw new Error('The argument routeId is required'); const args = ctx.args;
const tickets = await db.rawSql(` const tickets = await Self.rawSql(`
SELECT SELECT
t.id, t.id,
t.clientFk, t.clientFk,
@ -32,31 +49,27 @@ module.exports = async function(request, response, next) {
WHERE al.code = 'PACKED' WHERE al.code = 'PACKED'
AND t.routeFk = ? AND t.routeFk = ?
AND t.refFk IS NULL AND t.refFk IS NULL
GROUP BY e.ticketFk`, [reqArgs.routeId]); GROUP BY e.ticketFk`, [args.routeFk]);
await closure.start(tickets, response.locals); await closure(Self, tickets);
// Send route report to the agency // Send route report to the agency
const agencyMail = await db.findValue(` const [agencyMail] = await Self.rawSql(`
SELECT am.reportMail SELECT am.reportMail
FROM route r FROM route r
JOIN agencyMode am ON am.id = r.agencyModeFk JOIN agencyMode am ON am.id = r.agencyModeFk
WHERE r.id = ?`, [reqArgs.routeId]); WHERE r.id = ?`, [args.routeFk]);
if (agencyMail) { if (agencyMail) {
const args = Object.assign({ const email = new Email('driver-route', {
routeId: Number.parseInt(reqArgs.routeId), id: args.routeFk,
recipient: agencyMail recipient: agencyMail
}, response.locals); });
const email = new Email('driver-route', args);
await email.send(); await email.send();
} }
response.status(200).json({ return {
message: 'Success' message: 'Success'
}); };
} catch (error) { };
next(error);
}
}; };

View File

@ -1,14 +1,32 @@
const db = require('vn-print/core/database');
const closure = require('./closure'); const closure = require('./closure');
module.exports = async function(request, response, next) { module.exports = Self => {
try { Self.remoteMethodCtx('closeByTicket', {
const reqArgs = request.body; description: 'Makes the closure process by ticket',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/close-by-ticket`,
verb: 'POST'
}
});
if (!reqArgs.ticketId) Self.closeByTicket = async ctx => {
throw new Error('The argument ticketId is required'); const args = ctx.args;
const tickets = await db.rawSql(` const tickets = await Self.rawSql(`
SELECT SELECT
t.id, t.id,
t.clientFk, t.clientFk,
@ -31,14 +49,12 @@ module.exports = async function(request, response, next) {
WHERE al.code = 'PACKED' WHERE al.code = 'PACKED'
AND t.id = ? AND t.id = ?
AND t.refFk IS NULL AND t.refFk IS NULL
GROUP BY e.ticketFk`, [reqArgs.ticketId]); GROUP BY e.ticketFk`, [args.id]);
await closure.start(tickets, response.locals); await closure(Self, tickets);
response.status(200).json({ return {
message: 'Success' message: 'Success'
}); };
} catch (error) { };
next(error);
}
}; };

View File

@ -0,0 +1,179 @@
const Report = require('vn-print/core/report');
const Email = require('vn-print/core/email');
const smtp = require('vn-print/core/smtp');
const config = require('vn-print/core/config');
const storage = require('vn-print/core/storage');
module.exports = async function(Self, tickets, reqArgs = {}) {
if (tickets.length == 0) return;
const failedtickets = [];
for (const ticket of tickets) {
try {
await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id]);
const [invoiceOut] = await Self.rawSql(`
SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued
FROM ticket t
JOIN invoiceOut io ON io.ref = t.refFk
JOIN company cny ON cny.id = io.companyFk
WHERE t.id = ?
`, [ticket.id]);
const mailOptions = {
overrideAttachments: true,
attachments: []
};
const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed;
if (invoiceOut) {
const args = {
reference: invoiceOut.ref,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
};
const invoiceReport = new Report('invoice', args);
const stream = await invoiceReport.toPdfStream();
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString();
const fileName = `${year}${invoiceOut.ref}.pdf`;
// Store invoice
storage.write(stream, {
type: 'invoice',
path: `${year}/${month}/${day}`,
fileName: fileName
});
await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id]);
if (isToBeMailed) {
const invoiceAttachment = {
filename: fileName,
content: stream
};
if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') {
const exportation = new Report('exportation', args);
const stream = await exportation.toPdfStream();
const fileName = `CITES-${invoiceOut.ref}.pdf`;
mailOptions.attachments.push({
filename: fileName,
content: stream
});
}
mailOptions.attachments.push(invoiceAttachment);
const email = new Email('invoice', args);
await email.send(mailOptions);
}
} else if (isToBeMailed) {
const args = {
id: ticket.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
};
const email = new Email('delivery-note-link', args);
await email.send();
}
// Incoterms authorization
const [{firstOrder}] = await Self.rawSql(`
SELECT COUNT(*) as firstOrder
FROM ticket t
JOIN client c ON c.id = t.clientFk
WHERE t.clientFk = ?
AND NOT t.isDeleted
AND c.isVies
`, [ticket.clientFk]);
if (firstOrder == 1) {
const args = {
id: ticket.clientFk,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
};
const email = new Email('incoterms-authorization', args);
await email.send();
const sample = await Self.rawSql(
`SELECT id
FROM sample
WHERE code = 'incoterms-authorization'
`);
await Self.rawSql(`
INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?)
`, [ticket.clientFk, sample.id, ticket.companyFk]);
}
} catch (error) {
// Domain not found
if (error.responseCode == 450)
return invalidEmail(ticket);
// Save tickets on a list of failed ids
failedtickets.push({
id: ticket.id,
stacktrace: error
});
}
}
// Send email with failed tickets
if (failedtickets.length > 0) {
let body = 'This following tickets have failed:<br/><br/>';
for (const ticket of failedtickets) {
body += `Ticket: <strong>${ticket.id}</strong>
<br/> <strong>${ticket.stacktrace}</strong><br/><br/>`;
}
smtp.send({
to: config.app.reportEmail,
subject: '[API] Nightly ticket closure report',
html: body
});
}
async function invalidEmail(ticket) {
await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
ticket.clientFk
]);
const oldInstance = `{"email": "${ticket.recipient}"}`;
const newInstance = `{"email": ""}`;
await Self.rawSql(`
INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
ticket.clientFk,
oldInstance,
newInstance
]);
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta
o no está disponible.<br/><br/>
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
Actualiza la dirección de email con una correcta.`;
smtp.send({
to: ticket.salesPersonEmail,
subject: 'No se ha podido enviar el albarán',
html: body
});
}
};

View File

@ -0,0 +1,84 @@
const {toCSV} = require('vn-loopback/util/csv');
module.exports = Self => {
Self.remoteMethod('deliveryNoteCsv', {
description: 'Returns the delivery note csv',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
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: '/:id/delivery-note-csv',
verb: 'GET'
}
});
Self.deliveryNoteCsv = async id => {
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 vn.sale s
JOIN vn.ticket t ON t.id = s.ticketFk
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.supplier s2 ON s2.id = t.companyFk
JOIN vn.itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = s2.countryFk
JOIN vn.taxClass tc ON tc.id = itc.taxClassFk
LEFT JOIN vn.invoiceOut io ON io.id = t.refFk
WHERE s.ticketFk = ?
ORDER BY s.ticketFk, s.created`, [id]);
const content = toCSV(sales);
return [content, 'text/csv', `inline; filename="doc-${id}.pdf"`];
};
};

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