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

This commit is contained in:
Vicent Llopis 2022-10-06 11:50:11 +02:00
commit c8d270499b
271 changed files with 28833 additions and 2436 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

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": {
"dataSource": "vn"
},
"PrintConfig": {
"dataSource": "vn"
}
}

View File

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

View File

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

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

@ -7,13 +7,18 @@ process.on('warning', warning => {
console.log(warning.stack);
});
process.on('exit', async function() {
if (container) await container.rm();
});
let container;
async function test() {
let isCI = false;
if (process.argv[2] === 'ci')
isCI = true;
const container = new Docker();
container = new Docker();
await container.run(isCI);
dataSources = JSON.parse(JSON.stringify(dataSources));
@ -46,6 +51,7 @@ async function test() {
jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
jasmine.exitOnCompletion = true;
}
const backSpecs = [
@ -60,11 +66,10 @@ async function test() {
helpers: [],
});
jasmine.exitOnCompletion = false;
await jasmine.execute();
if (app) await app.disconnect();
if (container) await container.rm();
console.log('app disconnected & container removed');
console.log('App disconnected & container removed');
}
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)
VALUES ('Sector','*','READ','ALLOW','ROLE','employee');
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

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

View File

@ -13,7 +13,11 @@ INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES
('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
('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`)
VALUES
(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),
(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),
(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),
(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),
(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);
(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),
(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),
(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),
(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),
(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),
(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`)
@ -1778,6 +1785,11 @@ INSERT INTO `vn`.`claimEnd`(`id`, `saleFk`, `claimFk`, `workerFk`, `claimDestina
(1, 31, 4, 21, 2),
(2, 32, 3, 21, 3);
INSERT INTO `vn`.`claimConfig`(`id`, `pickupContact`, `maxResponsibility`)
VALUES
(1, 'Contact description', 50),
(2, 'Contact description', 30);
INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`)
VALUES
(1, 'Arkham Bank', 442, 1, 'h12387193H10238'),

View File

@ -835,14 +835,16 @@ export default {
confirmButton: '.vn-confirm.shown button[response="accept"]',
},
routeIndex: {
anyResult: 'vn-table a',
firstRouteCheckbox: 'a:nth-child(1) vn-td:nth-child(1) > vn-check',
anyResult: 'vn-route-index tbody > tr',
firstRouteCheckbox: 'vn-route-index tbody > tr:nth-child(1) > td:nth-child(1) > vn-check',
addNewRouteButton: 'vn-route-index a[ui-sref="route.create"]',
cloneButton: 'vn-route-index button > vn-icon[icon="icon-clone"]',
submitClonationButton: 'tpl-buttons > button[response="accept"]',
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
searchAgencyAutocomlete: 'vn-route-search-panel vn-autocomplete[ng-model="filter.agencyModeFk"]',
advancedSearchButton: 'vn-route-search-panel button[type=submit]',
previewButton: 'vn-route-index tbody > tr:nth-child(7) > td:nth-child(11) > vn-icon-button[icon="preview"]',
},
createRouteView: {
worker: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
@ -862,6 +864,8 @@ export default {
firstTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor',
firstAlias: 'vn-route-summary vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(3) > span',
firstClientDescriptor: '.vn-popover.shown vn-client-descriptor',
goToRouteSummaryButton: 'vn-route-summary > vn-card > h5 > a',
},
routeBasicData: {
worker: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.workerFk"]',

View File

@ -8,7 +8,7 @@ describe('Client Edit web access path', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'client');
await page.loginAndModule('salesPerson', 'client');
await page.accessToSearchResult('max');
await page.accessToSection('client.card.webAccess');
});

View File

@ -9,7 +9,8 @@ describe('Route summary path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'route');
await page.waitToClick('vn-route-index vn-tbody > a:nth-child(7)');
await page.waitToClick(selectors.routeIndex.previewButton);
await page.waitToClick(selectors.routeSummary.goToRouteSummaryButton);
});
afterAll(async() => {
@ -34,6 +35,8 @@ describe('Route summary path', () => {
});
it('should click on the first ticket ID making the descriptor popover visible', async() => {
await page.waitForState('route.card.summary');
await page.waitForTimeout(250);
await page.waitToClick(selectors.routeSummary.firstTicketID);
await page.waitForSelector(selectors.routeSummary.firstTicketDescriptor);
const visible = await page.isVisible(selectors.routeSummary.firstTicketDescriptor);

View File

@ -10,24 +10,12 @@ class Email {
/**
* 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
* @return {Promise} Promise resolved when it's sent
*/
send(template, params) {
return this.$http.get(`email/${template}`, {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})
send(path, params) {
return this.$http.post(path, params)
.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
* token to params.
*
* @param {String} report The report name
* @param {String} path The report name
* @param {Object} params The report parameters
*/
show(report, params) {
show(path, params) {
params = Object.assign({
authorization: this.vnToken.token
access_token: this.vnToken.token
}, params);
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/report/${report}?${serializedParams}`);
}
/**
* 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}`);
const query = serializedParams ? `?${serializedParams}` : '';
window.open(`api/${path}${query}`);
}
}
Report.$inject = ['$httpParamSerializer', 'vnToken'];

View File

@ -132,6 +132,6 @@
"Descanso diario 9h.": "Daily rest 9h.",
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.",
"Password does not meet requirements": "Password does not meet requirements",
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies"
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies",
"Not enough privileges to edit a client": "Not enough privileges to edit a client"
}

View File

@ -232,5 +232,8 @@
"Fichadas impares": "Fichadas impares",
"Descanso diario 12h.": "Descanso diario 12h.",
"Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.",
"Dirección incorrecta": "Dirección incorrecta"
}
"Dirección incorrecta": "Dirección incorrecta",
"Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador",
"Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador",
"Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente"
}

View File

@ -1,3 +1,3 @@
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) {
const [columns] = rows;
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/updateClaimDestination')(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
ng-click="confirmPickupOrder.show()"
vn-acl="salesPerson"
vn-acl-action="remove"
translate>
Send Pickup order
</vn-item>

View File

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

View File

@ -24,12 +24,13 @@ describe('Item Component vnClaimDescriptor', () => {
window.open = jasmine.createSpy('open');
const params = {
recipientId: claim.clientFk,
claimId: claim.id
recipientId: claim.clientFk
};
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 = {
recipient: claim.client.email,
recipientId: claim.clientFk,
claimId: claim.id
recipientId: claim.clientFk
};
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

@ -1,5 +1,6 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('setPassword', {
Self.remoteMethodCtx('setPassword', {
description: 'Sets the password of a non-worker client',
accepts: [
{
@ -20,13 +21,21 @@ module.exports = Self => {
}
});
Self.setPassword = async function(id, newPassword) {
Self.setPassword = async function(ctx, id, newPassword) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const isWorker = await models.Worker.findById(id);
if (isWorker)
throw new Error(`Can't change the password of another worker`);
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson');
await models.Account.setPassword(id, newPassword);
if (!isSalesPerson)
throw new UserError(`Not enough privileges to edit a client`);
const isClient = await models.Client.findById(id, null);
const isUserAccount = await models.UserAccount.findById(id, null);
if (isClient && !isUserAccount)
await models.Account.setPassword(id, newPassword);
else
throw new UserError(`Modifiable password only via recovery or by an administrator`);
};
};

View File

@ -1,23 +1,43 @@
const models = require('vn-loopback/server/server').models;
describe('Client setPassword', () => {
it('should throw an error the setPassword target is not just a client but a worker', async() => {
let error;
const salesPersonId = 19;
const ctx = {
req: {accessToken: {userId: salesPersonId}}
};
it(`should throw an error if you don't have enough permissions`, async() => {
let error;
const employeeId = 1;
const ctx = {
req: {accessToken: {userId: employeeId}}
};
try {
await models.Client.setPassword(1106, 'newPass?');
await models.Client.setPassword(ctx, 1, 't0pl3v3l.p455w0rd!');
} catch (e) {
error = e;
}
expect(error.message).toEqual(`Can't change the password of another worker`);
expect(error.message).toEqual(`Not enough privileges to edit a client`);
});
it('should throw an error the setPassword target is not just a client but a worker', async() => {
let error;
try {
await models.Client.setPassword(ctx, 1, 't0pl3v3l.p455w0rd!');
} catch (e) {
error = e;
}
expect(error.message).toEqual(`Modifiable password only via recovery or by an administrator`);
});
it('should change the password of the client', async() => {
let error;
try {
await models.Client.setPassword(1101, 't0pl3v3l.p455w0rd!');
await models.Client.setPassword(ctx, 1101, 't0pl3v3l.p455w0rd!');
} catch (e) {
error = e;
}

View File

@ -10,8 +10,9 @@ describe('Client updateUser', () => {
}
}
};
const salesPersonId = 19;
const ctx = {
req: {accessToken: {userId: employeeId}},
req: {accessToken: {userId: salesPersonId}},
args: {name: 'test', active: true}
};
@ -21,8 +22,13 @@ describe('Client updateUser', () => {
});
});
it('should throw an error the target user is not just a client but a worker', async() => {
it(`should throw an error if you don't have enough permissions`, async() => {
let error;
const employeeId = 1;
const ctx = {
req: {accessToken: {userId: employeeId}},
args: {name: 'test', active: true}
};
try {
const clientID = 1106;
await models.Client.updateUser(ctx, clientID);
@ -30,7 +36,19 @@ describe('Client updateUser', () => {
error = e;
}
expect(error.message).toEqual(`Can't update the user details of another worker`);
expect(error.message).toEqual(`Not enough privileges to edit a client`);
});
it('should throw an error the target user is not just a client but a worker', async() => {
let error;
try {
const clientID = 1;
await models.Client.updateUser(ctx, clientID);
} catch (e) {
error = e;
}
expect(error.message).toEqual(`Modifiable user details only by an administrator`);
});
it('should update the user data', async() => {

View File

@ -1,3 +1,4 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('updateUser', {
description: 'Updates the user information',
@ -5,8 +6,7 @@ module.exports = Self => {
{
arg: 'id',
type: 'number',
description: 'The user id',
http: {source: 'path'}
description: 'The user id'
},
{
arg: 'name',
@ -15,7 +15,7 @@ module.exports = Self => {
},
{
arg: 'email',
type: 'string',
type: 'any',
description: 'the user email'
},
{
@ -32,6 +32,7 @@ module.exports = Self => {
Self.updateUser = async function(ctx, id, options) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let tx;
const myOptions = {};
@ -44,13 +45,19 @@ module.exports = Self => {
}
try {
const isWorker = await models.Worker.findById(id, null, myOptions);
if (isWorker)
throw new Error(`Can't update the user details of another worker`);
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson', myOptions);
const user = await models.Account.findById(id, null, myOptions);
if (!isSalesPerson)
throw new UserError(`Not enough privileges to edit a client`);
await user.updateAttributes(ctx.args, myOptions);
const isClient = await models.Client.findById(id, null, myOptions);
const isUserAccount = await models.UserAccount.findById(id, null, myOptions);
if (isClient && !isUserAccount) {
const user = await models.Account.findById(id, null, myOptions);
await user.updateAttributes(ctx.args, myOptions);
} else
throw new UserError(`Modifiable user details only by an administrator`);
if (tx) await tx.commit();
} catch (e) {

View File

@ -26,6 +26,9 @@
"ClientCreditLimit": {
"dataSource": "vn"
},
"ClientConsumptionQueue": {
"dataSource": "vn"
},
"ClientLog": {
"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 LoopBackContext = require('loopback-context');
module.exports = Self => {
Self.validatesPresenceOf('typeFk', {
@ -6,10 +7,10 @@ module.exports = Self => {
});
Self.observe('before save', async function(ctx) {
let models = Self.app.models;
let changes = ctx.data || ctx.instance;
const models = Self.app.models;
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)
throw new UserError('Choose a company');
@ -25,11 +26,11 @@ module.exports = Self => {
// Renew mandate
if (mandate) {
let mandateType = await models.MandateType.findOne({
const mandateType = await models.MandateType.findOne({
where: {name: mandate.type}
});
let oldMandate = await models.Mandate.findOne({
const oldMandate = await models.Mandate.findOne({
where: {
clientFk: changes.clientFk,
companyFk: changes.companyFk,
@ -50,10 +51,8 @@ module.exports = Self => {
});
}
// Apply workerFk
let filter = {where: {userFk: ctx.options.accessToken.userId}};
let worker = await Self.app.models.Worker.findOne(filter);
const loopBackContext = LoopBackContext.getCurrentContext();
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 getFinalState = require('vn-loopback/util/hook').getFinalState;
const isMultiple = require('vn-loopback/util/hook').isMultiple;
@ -8,32 +7,7 @@ const LoopBackContext = require('loopback-context');
module.exports = Self => {
// Methods
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('./client-methods')(Self);
// Validations
@ -317,23 +291,22 @@ module.exports = Self => {
const $t = httpRequest.__;
const headers = httpRequest.headers;
const origin = headers.origin;
const authorization = headers.authorization;
const salesPersonId = instance.salesPersonFk;
if (salesPersonId) {
// Send email to client
if (instance.email) {
const {Email} = require('vn-print');
const worker = await models.EmailUser.findById(salesPersonId);
const params = {
authorization: authorization,
id: instance.id,
recipientId: instance.id,
recipient: instance.email,
replyTo: worker.email
};
await got.get(`${origin}/api/email/payment-update`, {
searchParams: params
});
const email = new Email('payment-update', params);
await email.send();
}
const fullUrl = `${origin}/#!/client/${instance.id}/billing-data`;

View File

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

View File

@ -45,11 +45,13 @@ class Controller extends Section {
}
showReport() {
this.vnReport.show('campaign-metrics', this.reportParams);
const path = `Clients/${this.client.id}/campaign-metrics-pdf`;
this.vnReport.show(path, this.reportParams);
}
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) {

View File

@ -34,15 +34,16 @@ describe('Client', () => {
controller.showReport();
const clientId = controller.client.id;
const expectedParams = {
recipientId: 1101,
recipientId: clientId,
from: now,
to: now
};
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,
to: now
};
const expectedParams = {
recipientId: 1101,
from: now,
to: now
};
const clientId = controller.client.id;
const expectedPath = `Clients/${clientId}/campaign-metrics-email`;
const serializedParams = $httpParamSerializer(expectedParams);
const path = `email/campaign-metrics?${serializedParams}`;
$httpBackend.expect('GET', path).respond({});
$httpBackend.expect('POST', expectedPath).respond({});
controller.sendEmail();
$httpBackend.flush();
});

View File

@ -77,11 +77,13 @@ export default class Controller extends Section {
onSendClientConsumption() {
const clientIds = this.checked.map(client => client.id);
const params = Object.assign({
clientIds: clientIds
}, this.campaign);
const params = {
clients: clientIds,
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.vnApp.showSuccess(this.$t('Notifications sent!')));
}

View File

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

View File

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

View File

@ -3,12 +3,13 @@ import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
constructor($element, $, vnEmail) {
super($element, $);
this.clientSample = {
clientFk: this.$params.id,
companyId: this.vnConfig.companyFk
};
this.vnEmail = vnEmail;
}
get client() {
@ -36,61 +37,86 @@ class Controller extends Section {
onSubmit() {
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() {
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) {
validate() {
const sampleType = this.$.sampleType.selection;
const params = {
recipientId: this.$params.id,
recipient: this.clientSample.recipient,
replyTo: this.clientSample.replyTo
};
if (!params.recipient)
return this.vnApp.showError(this.$t('Email cannot be blank'));
if (!this.clientSample.recipient)
return 'Email cannot be blank';
if (!sampleType)
return this.vnApp.showError(this.$t('Choose a sample'));
return 'Choose a sample';
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)
params.companyId = this.clientSample.companyFk;
if (sampleType.datepickerEnabled && !this.clientSample.from)
return this.vnApp.showError(this.$t('Choose a date'));
if (sampleType.datepickerEnabled)
params.from = this.clientSample.from;
}
let query = `email/${sampleType.code}`;
if (isPreview)
query = `email/${sampleType.code}/preview`;
preview() {
const sampleType = this.$.sampleType.selection;
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() {
@ -103,7 +129,7 @@ class Controller extends Section {
}
}
Controller.$inject = ['$element', '$scope'];
Controller.$inject = ['$element', '$scope', 'vnEmail'];
ngModule.vnComponent('vnClientSampleCreate', {
template: require('./index.html'),

View File

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

View File

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

View File

@ -17,13 +17,10 @@ describe('Entry Component vnEntryDescriptor', () => {
jest.spyOn(controller.vnReport, 'show');
window.open = jasmine.createSpy('open');
const params = {
clientId: controller.vnConfig.storage.currentUserWorkerId,
entryId: entry.id
};
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 fs = require('fs-extra');
const path = require('path');
const axios = require('axios');
const print = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('createPdf', {
@ -27,9 +25,7 @@ module.exports = Self => {
Self.createPdf = async function(ctx, id, options) {
const models = Self.app.models;
const headers = ctx.req.headers;
const origin = headers.origin;
const auth = ctx.req.accessToken;
const userId = ctx.req.accessToken.userId;
if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`);
@ -45,10 +41,9 @@ module.exports = Self => {
myOptions.transaction = tx;
}
let fileSrc;
try {
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)
throw new UserError(`You don't have enough privileges`);
@ -57,35 +52,27 @@ module.exports = Self => {
hasPdf: true
}, myOptions);
return axios.get(`${origin}/api/report/invoice`, {
responseType: 'stream',
params: {
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 invoiceReport = new print.Report('invoice', {
reference: invoiceOut.ref,
recipientId: invoiceOut.clientFk
});
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) {
if (tx) await tx.rollback();
throw e;

View File

@ -62,8 +62,14 @@ module.exports = Self => {
name: fileName
};
await fs.access(file.path);
let stream = fs.createReadStream(file.path);
try {
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}"`];
} catch (error) {
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) {
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);
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 = require('vn-print/core/email');
const Report = require('vn-print/core/report');
const storage = require('vn-print/core/storage');
const {Email, Report, storage} = require('vn-print');
module.exports = async function(request, response, next) {
try {
response.status(200).json({
message: 'Success'
});
module.exports = Self => {
Self.remoteMethod('sendQueued', {
description: 'Send all queued invoices',
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
io.id,
io.clientFk,
@ -21,7 +28,7 @@ module.exports = async function(request, response, next) {
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM invoiceOut_queue ioq
FROM invoiceOutQueue ioq
JOIN invoiceOut io ON io.id = ioq.invoiceFk
JOIN client c ON c.id = io.clientFk
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
WHERE status = ''`);
let connection;
let invoiceId;
for (const invoiceOut of invoices) {
try {
invoiceId = invoiceOut.id;
connection = await db.getConnection();
connection.query('START TRANSACTION');
const tx = await Self.beginTransaction({});
const myOptions = {transaction: tx};
const args = Object.assign({
refFk: invoiceOut.ref,
invoiceId = invoiceOut.id;
const args = {
reference: invoiceOut.ref,
recipientId: invoiceOut.clientFk,
recipient: invoiceOut.recipient,
replyTo: invoiceOut.salesPersonEmail
}, response.locals);
};
const invoiceReport = new Report('invoice', args);
const stream = await invoiceReport.toPdfStream();
@ -61,7 +68,11 @@ module.exports = async function(request, response, next) {
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;
@ -94,22 +105,29 @@ module.exports = async function(request, response, next) {
}
// Update queue status
const date = new Date();
sql = `UPDATE invoiceOut_queue
await Self.rawSql(`
UPDATE invoiceOutQueue
SET status = "printed",
printed = ?
WHERE invoiceFk = ?`;
connection.query(sql, [date, invoiceOut.id]);
connection.query('COMMIT');
WHERE invoiceFk = ?`,
[date, invoiceOut.id], myOptions);
await tx.commit();
} catch (error) {
connection.query('ROLLBACK');
connection.release();
sql = `UPDATE invoiceOut_queue
await tx.rollback();
await Self.rawSql(`
UPDATE invoiceOutQueue
SET status = ?
WHERE invoiceFk = ?`;
await db.rawSql(sql, [error.message, invoiceId]);
WHERE invoiceFk = ?`,
[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 LoopBackContext = require('loopback-context');
const fs = require('fs-extra');
const axios = require('axios');
const print = require('vn-print');
describe('InvoiceOut createPdf()', () => {
const userId = 1;
@ -16,22 +15,15 @@ describe('InvoiceOut createPdf()', () => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
const response = {
data: {
pipe: () => {},
on: () => {},
spyOn(print, 'Report').and.returnValue({
toPdfStream: () => {
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 options = {transaction: tx};

View File

@ -8,6 +8,9 @@ describe('InvoiceOut globalInvoicing()', () => {
const invoiceSerial = 'A';
const activeCtx = {
accessToken: {userId: userId},
__: value => {
return value;
}
};
const ctx = {req: activeCtx};
@ -22,7 +25,7 @@ describe('InvoiceOut globalInvoicing()', () => {
invoiceDate: new Date(),
maxShipped: new Date(),
fromClientId: clientId,
toClientId: clientId,
toClientId: 1106,
companyFk: companyFk
};
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/globalInvoicing')(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) {
if (!$data.email)
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,
recipient: $data.email,
refFk: this.invoiceOut.ref
recipient: $data.email
});
}
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)
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,
recipient: $data.email,
refFk: this.invoiceOut.ref
recipient: $data.email
});
}
showExportationLetter() {
this.vnReport.show('exportation', {
this.vnReport.show(`InvoiceOuts/${this.invoiceOut.ref}/exportation-pdf`, {
recipientId: this.invoiceOut.client.id,
refFk: this.invoiceOut.ref
});

View File

@ -41,11 +41,10 @@ describe('vnInvoiceOutDescriptorMenu', () => {
jest.spyOn(window, 'open').mockReturnThis();
const expectedParams = {
recipientId: invoiceOut.client.id,
refFk: invoiceOut.ref
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/invoice/download?${serializedParams}`;
const expectedPath = `api/InvoiceOuts/${invoiceOut.ref}/invoice-csv?${serializedParams}`;
controller.showCsvInvoice();
expect(window.open).toHaveBeenCalledWith(expectedPath);
@ -84,14 +83,8 @@ describe('vnInvoiceOutDescriptorMenu', () => {
jest.spyOn(controller.vnApp, 'showMessage');
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);
$httpBackend.flush();
@ -104,14 +97,8 @@ describe('vnInvoiceOutDescriptorMenu', () => {
jest.spyOn(controller.vnApp, 'showMessage');
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);
$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": {
"dataSource": "vn"
},
"ItemConfig": {
"dataSource": "vn"
},
"ItemFamily": {
"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/createIntrastat')(Self);
require('../methods/item/activeBuyers')(Self);
require('../methods/item/buyerWasteEmail')(Self);
require('../methods/item/labelPdf')(Self);
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});

View File

@ -143,9 +143,6 @@
},
"packingShelve": {
"type": "number"
},
"weightByPiece": {
"type": "number"
}
},
"relations": {

View File

@ -23,10 +23,12 @@ vn-item-product {
}
}
table {
img {
border-radius: 50%;
width: 50px;
height: 50px;
vn-item-index {
table {
img {
border-radius: 50%;
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

@ -49,7 +49,8 @@ module.exports = Self => {
a.city,
am.name AS agencyModeName,
u.nickname AS userNickname,
vn.ticketTotalVolume(t.id) AS volume
vn.ticketTotalVolume(t.id) AS volume,
tob.description
FROM route r
JOIN ticket t ON t.routeFk = r.id
LEFT JOIN ticketState ts ON ts.ticketFk = t.id

View File

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

View File

@ -48,6 +48,9 @@
"description": {
"type": "string"
},
"isOk": {
"type": "boolean"
},
"commissionWorkCenterFk": {
"type": "number"
}

View File

@ -1,6 +1,5 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
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;
}
}

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