3888-ticket.expedition_moveExpedition #1097

Merged
vicent merged 26 commits from 3888-ticket.expedition_moveExpedition into dev 2022-11-02 13:54:11 +00:00
269 changed files with 4765 additions and 3581 deletions
Showing only changes of commit 26d04ce2d2 - Show all commits

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

@ -41,11 +41,14 @@ async function test() {
}
}));
jasmine.exitOnCompletion = false;
if (isCI) {
const JunitReporter = require('jasmine-reporters');
jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
jasmine.exitOnCompletion = true;
}
const backSpecs = [
@ -60,11 +63,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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,8 +19,9 @@ module.exports = class Docker {
* to avoid a bug with OverlayFS driver on MacOS.
*
* @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,9 +60,10 @@ 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;
if (ci) {
this.dbConf.host = netSettings.Networks[networkName].IPAddress;
this.dbConf.port = 3306;
} else
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
}

View File

@ -13,6 +13,10 @@ INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES
('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66);
INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
VALUES
(1, 'it@gotamcity.com', 'incidences@gotamcity.com');
INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
VALUES
('1', '6');
@ -862,25 +866,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`)
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`)
VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL);
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15,3),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10,2),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5,5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL,NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL);
-- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -925,7 +929,10 @@ INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `create
(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);
(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

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

View File

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

View File

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

View File

@ -10,24 +10,12 @@ class Email {
/**
* Sends an email displaying a notification when it's sent.
*
* @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

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

@ -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 => {
validate() {
const sampleType = this.$.sampleType.selection;
if (!this.clientSample.recipient)
return 'Email cannot be blank';
if (!sampleType)
return 'Choose a sample';
if (sampleType.hasCompany && !this.clientSample.companyFk)
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)
params.from = this.clientSample.from;
}
preview() {
const sampleType = this.$.sampleType.selection;
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 = res.data;
body.innerHTML = response.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) {
send() {
const sampleType = this.$.sampleType.selection;
const params = {
recipientId: this.$params.id,
recipientId: this.client.id,
recipient: this.clientSample.recipient,
replyTo: this.clientSample.replyTo
};
if (!params.recipient)
return this.vnApp.showError(this.$t('Email cannot be blank'));
const validationMessage = this.validate();
if (validationMessage)
return this.vnApp.showError(this.$t(validationMessage));
if (!sampleType)
return this.vnApp.showError(this.$t('Choose a sample'));
this.setParams(params);
if (sampleType.hasCompany && !this.clientSample.companyFk)
return this.vnApp.showError(this.$t('Choose a company'));
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`;
this.$http.get(query, {params}).then(cb);
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

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

View File

@ -0,0 +1,54 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('entryOrderPdf', {
description: 'Returns the entry order pdf',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/entry-order-pdf',
verb: 'GET'
}
});
Self.entryOrderPdf = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('entry-order', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

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

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

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

View File

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

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 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 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});
// Store invoice
print.storage.write(stream, {
type: 'invoice',
path: `${year}/${month}/${day}`,
fileName: fileName
});
if (tx) await tx.commit();
response.data.pipe(fs.createWriteStream(fileSrc));
}).catch(async e => {
if (fs.existsSync(fileSrc))
await fs.unlink(fileSrc);
throw e;
});
} catch (e) {
if (tx) await tx.rollback();
throw e;

View File

@ -62,8 +62,14 @@ module.exports = Self => {
name: fileName
};
try {
await fs.access(file.path);
let stream = fs.createReadStream(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

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

View File

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

View File

@ -0,0 +1,72 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('labelPdf', {
description: 'Returns the item label pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'warehouseId',
type: 'number',
description: 'The warehouse id',
required: true
},
{
arg: 'labelNumber',
type: 'number',
required: false
},
{
arg: 'totalLabels',
type: 'number',
required: false
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/label-pdf',
verb: 'GET'
}
});
Self.labelPdf = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('item-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="item-${id}.pdf"`];
};
};

View File

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

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

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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ vn-item-product {
}
}
vn-item-index {
table {
img {
border-radius: 50%;
@ -30,3 +31,4 @@ table {
height: 50px;
}
}
}

View File

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

View File

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

View File

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

View File

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

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