Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2265-item_request_filters
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2020-06-02 10:56:40 +02:00
commit a0767a6ea2
113 changed files with 1158 additions and 342 deletions

View File

@ -8,7 +8,7 @@ RUN apt-get update \
ca-certificates \ ca-certificates \
gnupg2 \ gnupg2 \
libfontconfig \ libfontconfig \
&& curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && curl -sL https://deb.nodesource.com/setup_12.x | bash - \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
nodejs \ nodejs \
&& apt-get purge -y --auto-remove \ && apt-get purge -y --auto-remove \

View File

@ -9,7 +9,7 @@ Salix is also the scientific name of a beautifull tree! :)
Required applications. Required applications.
* Visual Studio Code * Visual Studio Code
* Node.js = 10.15.3 LTS * Node.js = 12.17.0 LTS
* Docker * Docker
In Visual Studio Code we use the ESLint extension. Open Visual Studio Code, press Ctrl+P and paste the following command. In Visual Studio Code we use the ESLint extension. Open Visual Studio Code, press Ctrl+P and paste the following command.

View File

@ -0,0 +1 @@
INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('WorkerLog', '*', 'READ', 'ALLOW', 'ROLE', 'hr');

View File

@ -0,0 +1,4 @@
UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213';
INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('CustomsAgent', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,42 @@
DROP procedure IF EXISTS `vn`.`itemLastEntries`;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`itemLastEntries`(IN `vItem` INT, IN `vDays` DATE)
BEGIN
SELECT
w.id AS warehouseFk,
w.name AS warehouse,
tr.landed,
b.entryFk,
b.isIgnored,
b.price2,
b.price3,
b.stickers,
b.packing,
b.`grouping`,
b.groupingMode,
b.weight,
i.stems,
b.quantity,
b.buyingValue,
b.packageFk ,
s.id AS supplierFk,
s.name AS supplier
FROM itemType it
RIGHT JOIN (entry e
LEFT JOIN supplier s ON s.id = e.supplierFk
RIGHT JOIN buy b ON b.entryFk = e.id
LEFT JOIN item i ON i.id = b.itemFk
LEFT JOIN ink ON ink.id = i.inkFk
LEFT JOIN travel tr ON tr.id = e.travelFk
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
LEFT JOIN origin o ON o.id = i.originFk
) ON it.id = i.typeFk
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id
WHERE b.itemFk = vItem And tr.shipped BETWEEN vDays AND DATE_ADD(CURDATE(), INTERVAl + 10 DAY)
ORDER BY tr.landed DESC , b.id DESC;
END$$
DELIMITER ;

View File

@ -1216,23 +1216,23 @@ INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `saleTotal`, `saleWa
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', '186', '0', '0.0'), ('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', '186', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', '277', '0', '0.0'); ('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', '277', '0', '0.0');
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`, `created`) INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
VALUES VALUES
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)), (1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)), (2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()), (3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, CURDATE()),
(4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, CURDATE()), (4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, 0.00, NULL, 0, 1, 0, CURDATE()), (5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()), (6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, 0.00, NULL, 0, 1, 0, CURDATE()), (7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()), (8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()), (9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()), (10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()), (11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()), (12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()), (13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, CURDATE()), (14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()); (15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE());
INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_id`, `agency_id`, `address_id`, `company_id`, `note`, `source_app`, `confirmed`, `date_make`, `first_row_stamp`, `confirm_date`) INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_id`, `agency_id`, `address_id`, `company_id`, `note`, `source_app`, `confirmed`, `date_make`, `first_row_stamp`, `confirm_date`)
VALUES VALUES

View File

@ -0,0 +1,123 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
// 2277 solucionar problema al testear procedimiento con start transaction / rollback
xdescribe('ticket_componentMakeUpdate()', () => {
it('should recalculate the ticket components without make modifications', async() => {
let stmts = [];
let stmt;
let params = {
ticketId: 11,
clientId: 102,
agencyModeId: 2,
addressId: 122,
zoneId: 3,
warehouseId: 1,
companyId: 442,
isDeleted: 0,
hasToBeUnrouted: 0,
componentOption: 1
};
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let originalTicketIndex = stmts.push(stmt) - 1;
stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [
params.ticketId,
params.clientId,
params.agencyModeId,
params.addressId,
params.zoneId,
params.warehouseId,
params.companyId,
params.isDeleted,
params.hasToBeUnrouted,
params.componentOption
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let updatedTicketIndex = stmts.push(stmt) - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let originalTicketData = result[originalTicketIndex];
let updatedTicketData = result[updatedTicketIndex];
expect(originalTicketData[0].isDeleted).toEqual(updatedTicketData[0].isDeleted);
expect(originalTicketData[0].routeFk).toEqual(updatedTicketData[0].routeFk);
});
it('should delete and unroute a ticket and recalculate the components', async() => {
let stmts = [];
let stmt;
let params = {
ticketId: 11,
clientId: 102,
agencyModeId: 2,
addressId: 122,
zoneId: 3,
warehouseId: 1,
companyId: 442,
isDeleted: 1,
hasToBeUnrouted: 1,
componentOption: 1
};
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let originalTicketIndex = stmts.push(stmt) - 1;
stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [
params.ticketId,
params.clientId,
params.agencyModeId,
params.addressId,
params.zoneId,
params.warehouseId,
params.companyId,
params.isDeleted,
params.hasToBeUnrouted,
params.componentOption
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let updatedTicketIndex = stmts.push(stmt) - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let originalTicketData = result[originalTicketIndex];
let updatedTicketData = result[updatedTicketIndex];
expect(originalTicketData[0].isDeleted).not.toEqual(updatedTicketData[0].isDeleted);
expect(originalTicketData[0].routeFk).not.toEqual(updatedTicketData[0].routeFk);
});
});

View File

@ -0,0 +1,33 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('zone zone_getEvents()', () => {
it(`should return data for a agencyMode with deliveryMethod pickup`, async() => {
let stmts = [];
let stmt;
stmts.push('START TRANSACTION');
let params = {
zoneGeoFk: 1,
agencyModeFk: 1};
stmt = new ParameterizedSQL('CALL zone_getEvents(?, ?)', [
params.zoneGeoFk,
params.agencyModeFk,
]);
stmts.push(stmt);
let firstResultIndex = stmts.push(stmt) - 1;
let secondResultIndex = firstResultIndex + 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let zonesEvents = result[secondResultIndex];
expect(zonesEvents.length).toBeGreaterThan(0);
});
});

View File

@ -122,6 +122,12 @@ export default {
mobileInput: 'vn-textfield[ng-model="$ctrl.address.mobile"]', mobileInput: 'vn-textfield[ng-model="$ctrl.address.mobile"]',
defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]', defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]',
incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsId"]', incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsId"]',
addNewCustomsAgent: 'vn-client-address-create vn-autocomplete[ng-model="$ctrl.address.customsAgentId"] vn-icon-button[icon="add_circle"]',
newCustomsAgentFiscalID: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.nif"]',
newCustomsAgentFiscalName: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.fiscalName"]',
newCustomsAgentStreet: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.street"]',
newCustomsAgentPhone: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.phone"]',
saveNewCustomsAgentButton: 'button[response="accept"]',
customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentId"]', customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentId"]',
secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]', secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]',
firstEditAddress: 'vn-client-address-index div:nth-child(1) > a', firstEditAddress: 'vn-client-address-index div:nth-child(1) > a',
@ -828,6 +834,12 @@ export default {
}, },
travelThermograph: { travelThermograph: {
add: 'vn-travel-thermograph-index vn-float-button[icon="add"]', add: 'vn-travel-thermograph-index vn-float-button[icon="add"]',
addThermographIcon: 'vn-travel-thermograph-create vn-autocomplete vn-icon[icon="add_circle"]',
newThermographId: 'vn-textfield[ng-model="$ctrl.newThermograph.thermographId"]',
newThermographModel: 'vn-autocomplete[ng-model="$ctrl.newThermograph.model"]',
newThermographWarehouse: 'vn-autocomplete[ng-model="$ctrl.newThermograph.warehouseId"]',
newThermographTemperature: 'vn-autocomplete[ng-model="$ctrl.newThermograph.temperature"]',
createThermographButton: 'form button[response="accept"]',
thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]', thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]',
uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="icon-attach"]', uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="icon-attach"]',
createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr', createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr',

View File

@ -61,12 +61,18 @@ describe('Client Add address path', () => {
expect(message.text).toBe('Customs agent is required for a non UEE member'); expect(message.text).toBe('Customs agent is required for a non UEE member');
}); });
it(`should create a new address with all it's data`, async() => { it(`should create a new custom agent and then save the address`, async() => {
await page.autocompleteSearch(selectors.clientAddresses.customsAgent, 'Agent one'); await page.waitToClick(selectors.clientAddresses.addNewCustomsAgent);
await page.write(selectors.clientAddresses.newCustomsAgentFiscalID, 'ID');
await page.write(selectors.clientAddresses.newCustomsAgentFiscalName, 'name');
await page.write(selectors.clientAddresses.newCustomsAgentStreet, 'street');
await page.write(selectors.clientAddresses.newCustomsAgentPhone, '555555555');
await page.waitToClick(selectors.clientAddresses.saveNewCustomsAgentButton);
await page.waitToClick(selectors.clientAddresses.saveButton); await page.waitToClick(selectors.clientAddresses.saveButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.type).toBe('success'); expect(message.text).toBe('Data saved!');
}); });
it(`should navigate back to the addresses index`, async() => { it(`should navigate back to the addresses index`, async() => {

View File

@ -2,6 +2,7 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Travel thermograph path', () => { describe('Travel thermograph path', () => {
const thermographName = '7H3-37H3RN4L-FL4M3';
let browser; let browser;
let page; let page;
@ -26,10 +27,18 @@ describe('Travel thermograph path', () => {
await page.waitForState('travel.card.thermograph.create'); await page.waitForState('travel.card.thermograph.create');
}); });
it('should select the thermograph and then the file to upload', async() => { it('should click on the add thermograph icon of the thermograph autocomplete', async() => {
await page.waitToClick(selectors.travelThermograph.addThermographIcon);
await page.write(selectors.travelThermograph.newThermographId, thermographName);
await page.autocompleteSearch(selectors.travelThermograph.newThermographModel, 'TEMPMATE');
await page.autocompleteSearch(selectors.travelThermograph.newThermographWarehouse, 'Warehouse Two');
await page.autocompleteSearch(selectors.travelThermograph.newThermographTemperature, 'WARM');
await page.waitToClick(selectors.travelThermograph.createThermographButton);
});
it('should select the file to upload', async() => {
let currentDir = process.cwd(); let currentDir = process.cwd();
let filePath = `${currentDir}/e2e/dms/ecc/3.jpeg`; let filePath = `${currentDir}/e2e/dms/ecc/3.jpeg`;
await page.autocompleteSearch(selectors.travelThermograph.thermographID, '138350-0');
const [fileChooser] = await Promise.all([ const [fileChooser] = await Promise.all([
page.waitForFileChooser(), page.waitForFileChooser(),
@ -38,11 +47,17 @@ describe('Travel thermograph path', () => {
await fileChooser.accept([filePath]); await fileChooser.accept([filePath]);
await page.waitToClick(selectors.travelThermograph.upload); await page.waitToClick(selectors.travelThermograph.upload);
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.type).toBe('success');
expect(state).toBe('travel.card.thermograph.index');
}); });
it('should reload the section and check everything was saved', async() => { it('should check everything was saved correctly', async() => {
let createdThermograph = await page.waitToGetProperty(selectors.travelThermograph.createdThermograph, 'innerText'); const result = await page.waitToGetProperty(selectors.travelThermograph.createdThermograph, 'innerText');
expect(createdThermograph).toContain('138350-0'); expect(result).toContain(thermographName);
}); });
}); });

View File

@ -0,0 +1,55 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
/**
* Returns a set of allowed values defined on table scheme
* @param {String} column - Model or Table column name
* @return {Array} - Array of set values
*/
Self.getEnumValues = async function(column) {
let model = this.app.models[this.modelName].definition;
let properties = model.properties;
let tableName = this.modelName;
let schema = null;
if (model.settings && model.settings.mysql) {
let tableSplit = model.settings.mysql.table.split('.');
tableName = tableSplit.pop();
schema = tableSplit.pop() || null;
}
let property = properties[column];
if (!property)
throw new UserError(`Column does not exist`);
let columnName = property.mysql
? property.mysql.columnName
: column;
let columnInfo = await this.rawSql(
`SELECT column_type columnType
FROM information_schema.columns
WHERE table_name = ?
AND table_schema = IFNULL(?, DATABASE())
AND column_name = ?`,
[tableName, schema, columnName]
);
if (!columnInfo || !columnInfo[0])
throw new UserError(`Cannot fetch column values`);
let setValues;
setValues = columnInfo[0].columnType
.replace(/^enum\((.*)\)$/i, '$1')
.replace(/'/g, '')
.match(new RegExp(/(\w+)+/, 'ig'));
let values = [];
setValues.forEach(setValue => {
values.push({value: setValue});
});
return values;
};
};

View File

@ -0,0 +1,18 @@
const app = require('vn-loopback/server/server');
describe('Model getEnumValues()', () => {
it('should extend getEnumValues properties to any model passed', () => {
let exampleModel = app.models.TravelThermograph;
expect(exampleModel.getEnumValues).toBeDefined();
});
it('should return an array of enum values from a given column', async() => {
let result = await app.models.TravelThermograph.getSetValues('temperature');
expect(result.length).toEqual(3);
expect(result[0].value).toEqual('enum');
expect(result[1].value).toEqual('COOL');
expect(result[2].value).toEqual('WARM');
});
});

View File

@ -12,23 +12,34 @@ module.exports = function(Self) {
}); });
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
let options = {}; const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
const options = {};
// Check for transactions
if (ctx.options && ctx.options.transaction) if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction; options.transaction = ctx.options.transaction;
let oldInstance; let oldInstance;
let oldInstanceFk;
let newInstance; let newInstance;
if (ctx.data) { if (ctx.data) {
oldInstanceFk = pick(ctx.currentInstance, Object.keys(ctx.data)); const changes = pick(ctx.currentInstance, Object.keys(ctx.data));
newInstance = await fkToValue(ctx.data, ctx); newInstance = await fkToValue(ctx.data, ctx);
oldInstance = await fkToValue(oldInstanceFk, ctx); oldInstance = await fkToValue(changes, ctx);
if (ctx.where && !ctx.currentInstance) { if (ctx.where && !ctx.currentInstance) {
let fields = Object.keys(ctx.data); const fields = Object.keys(ctx.data);
ctx.oldInstances = await ctx.Model.app.models[ctx.Model.definition.name].find({where: ctx.where, fields: fields}, options); const modelName = definition.name;
ctx.oldInstances = await appModels[modelName].find({
where: ctx.where,
fields: fields
}, options);
} }
} }
// Get changes from created instance
if (ctx.isNewInstance) if (ctx.isNewInstance)
newInstance = await fkToValue(ctx.instance.__data, ctx); newInstance = await fkToValue(ctx.instance.__data, ctx);
@ -37,18 +48,24 @@ module.exports = function(Self) {
}); });
Self.observe('before delete', async function(ctx) { Self.observe('before delete', async function(ctx) {
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
const relations = ctx.Model.relations;
let options = {}; let options = {};
if (ctx.options && ctx.options.transaction) if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction; options.transaction = ctx.options.transaction;
if (ctx.where) { if (ctx.where) {
let affectedModel = ctx.Model.definition.name; let affectedModel = definition.name;
let definition = ctx.Model.definition; let deletedInstances = await appModels[affectedModel].find({
let deletedInstances = await ctx.Model.app.models[affectedModel].find({where: ctx.where}, options); where: ctx.where
}, options);
let relation = definition.settings.log.relation; let relation = definition.settings.log.relation;
if (relation) { if (relation) {
let primaryKey = ctx.Model.relations[relation].keyFrom; let primaryKey = relations[relation].keyFrom;
let arrangedDeletedInstances = []; let arrangedDeletedInstances = [];
for (let i = 0; i < deletedInstances.length; i++) { for (let i = 0; i < deletedInstances.length; i++) {
@ -69,6 +86,8 @@ module.exports = function(Self) {
}); });
async function logDeletedInstances(ctx, loopBackContext) { async function logDeletedInstances(ctx, loopBackContext) {
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
let options = {}; let options = {};
if (ctx.options && ctx.options.transaction) if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction; options.transaction = ctx.options.transaction;
@ -78,14 +97,12 @@ module.exports = function(Self) {
if (loopBackContext) if (loopBackContext)
userFk = loopBackContext.active.accessToken.userId; userFk = loopBackContext.active.accessToken.userId;
let definition = ctx.Model.definition;
let changedModelValue = definition.settings.log.changedModelValue; let changedModelValue = definition.settings.log.changedModelValue;
let logRecord = { let logRecord = {
originFk: instance.originFk, originFk: instance.originFk,
userFk: userFk, userFk: userFk,
action: 'delete', action: 'delete',
changedModel: ctx.Model.definition.name, changedModel: definition.name,
changedModelId: instance.id, changedModelId: instance.id,
changedModelValue: instance[changedModelValue], changedModelValue: instance[changedModelValue],
oldInstance: instance, oldInstance: instance,
@ -95,26 +112,44 @@ module.exports = function(Self) {
delete instance.originFk; delete instance.originFk;
let logModel = definition.settings.log.model; let logModel = definition.settings.log.model;
await ctx.Model.app.models[logModel].create(logRecord, options); await appModels[logModel].create(logRecord, options);
}); });
} }
// Get log values from a foreign key
async function fkToValue(instance, ctx) { async function fkToValue(instance, ctx) {
const appModels = ctx.Model.app.models;
const relations = ctx.Model.relations;
let options = {}; let options = {};
// Check for transactions
if (ctx.options && ctx.options.transaction) if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction; options.transaction = ctx.options.transaction;
let cleanInstance = JSON.parse(JSON.stringify(instance)); const instanceCopy = JSON.parse(JSON.stringify(instance));
let result = {}; const result = {};
for (let key in cleanInstance) { for (const key in instanceCopy) {
let val = cleanInstance[key]; let value = instanceCopy[key];
if (val === undefined || val === null) continue;
for (let key1 in ctx.Model.relations) { if (value instanceof Object)
let val1 = ctx.Model.relations[key1]; continue;
if (val1.keyFrom == key && key != 'id') {
let recordSet = await ctx.Model.app.models[val1.modelTo.modelName].findById(val, null, options); if (value === undefined || value === null) continue;
for (let relationName in relations) {
const relation = relations[relationName];
if (relation.keyFrom == key && key != 'id') {
const model = relation.modelTo;
const modelName = relation.modelTo.modelName;
const properties = model && model.definition.properties;
const settings = model && model.definition.settings;
const recordSet = await appModels[modelName].findById(value, null, options);
const hasShowField = settings.log && settings.log.showField;
let showField = hasShowField && recordSet
&& recordSet[settings.log.showField];
let showField = val1.modelTo && val1.modelTo.definition.settings.log && val1.modelTo.definition.settings.log.showField && recordSet && recordSet[val1.modelTo.definition.settings.log.showField];
if (!showField) { if (!showField) {
const showFieldNames = [ const showFieldNames = [
'name', 'name',
@ -122,7 +157,10 @@ module.exports = function(Self) {
'code' 'code'
]; ];
for (field of showFieldNames) { for (field of showFieldNames) {
if (val1.modelTo.definition.properties && val1.modelTo.definition.properties[field] && recordSet && recordSet[field]) { const propField = properties && properties[field];
const recordField = recordSet && recordSet[field];
if (propField && recordField) {
showField = field; showField = field;
break; break;
} }
@ -130,25 +168,29 @@ module.exports = function(Self) {
} }
if (showField && recordSet && recordSet[showField]) { if (showField && recordSet && recordSet[showField]) {
val = recordSet[showField]; value = recordSet[showField];
break; break;
} }
val = recordSet && recordSet.id || val; value = recordSet && recordSet.id || value;
break; break;
} }
} }
result[key] = val; result[key] = value;
} }
return result; return result;
} }
async function logInModel(ctx, loopBackContext) { async function logInModel(ctx, loopBackContext) {
let options = {}; const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
const defSettings = ctx.Model.definition.settings;
const relations = ctx.Model.relations;
const options = {};
if (ctx.options && ctx.options.transaction) if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction; options.transaction = ctx.options.transaction;
let definition = ctx.Model.definition;
let primaryKey; let primaryKey;
for (let property in definition.properties) { for (let property in definition.properties) {
if (definition.properties[property].id) { if (definition.properties[property].id) {
@ -163,11 +205,11 @@ module.exports = function(Self) {
// RELATIONS LOG // RELATIONS LOG
let changedModelId; let changedModelId;
if (ctx.instance && !definition.settings.log.relation) { if (ctx.instance && !defSettings.log.relation) {
originId = ctx.instance.id; originId = ctx.instance.id;
changedModelId = ctx.instance.id; changedModelId = ctx.instance.id;
} else if (definition.settings.log.relation) { } else if (defSettings.log.relation) {
primaryKey = ctx.Model.relations[definition.settings.log.relation].keyFrom; primaryKey = relations[defSettings.log.relation].keyFrom;
if (ctx.where && ctx.where[primaryKey]) if (ctx.where && ctx.where[primaryKey])
originId = ctx.where[primaryKey]; originId = ctx.where[primaryKey];
@ -181,12 +223,16 @@ module.exports = function(Self) {
} }
// Sets the changedModelValue to save and the instances changed in case its an updateAll // Sets the changedModelValue to save and the instances changed in case its an updateAll
let showField = definition.settings.log.showField; let showField = defSettings.log.showField;
let where; let where;
if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) { if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) {
changedModelId = []; changedModelId = [];
where = []; where = [];
let changedInstances = await ctx.Model.app.models[definition.name].find({where: ctx.where, fields: ['id', showField, primaryKey]}, options); let changedInstances = await appModels[definition.name].find({
where: ctx.where,
fields: ['id', showField, primaryKey]
}, options);
changedInstances.forEach(element => { changedInstances.forEach(element => {
where.push(element[showField]); where.push(element[showField]);
changedModelId.push(element.id); changedModelId.push(element.id);
@ -195,7 +241,6 @@ module.exports = function(Self) {
} else if (ctx.hookState.oldInstance) } else if (ctx.hookState.oldInstance)
where = ctx.instance[showField]; where = ctx.instance[showField];
// Set oldInstance, newInstance, userFk and action // Set oldInstance, newInstance, userFk and action
let oldInstance = {}; let oldInstance = {};
if (ctx.hookState.oldInstance) if (ctx.hookState.oldInstance)
@ -211,14 +256,14 @@ module.exports = function(Self) {
let action = setActionType(ctx); let action = setActionType(ctx);
removeUnloggableProperties(definition, oldInstance); removeUnloggable(definition, oldInstance);
removeUnloggableProperties(definition, newInstance); removeUnloggable(definition, newInstance);
let logRecord = { let logRecord = {
originFk: originId, originFk: originId,
userFk: userFk, userFk: userFk,
action: action, action: action,
changedModel: ctx.Model.definition.name, changedModel: definition.name,
changedModelId: changedModelId, // Model property with an different data type will throw a NaN error changedModelId: changedModelId, // Model property with an different data type will throw a NaN error
changedModelValue: where, changedModelValue: where,
oldInstance: oldInstance, oldInstance: oldInstance,
@ -226,9 +271,9 @@ module.exports = function(Self) {
}; };
let logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx); let logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx);
let logModel = definition.settings.log.model; let logModel = defSettings.log.model;
await ctx.Model.app.models[logModel].create(logsToSave, options); await appModels[logModel].create(logsToSave, options);
} }
/** /**
@ -236,7 +281,7 @@ module.exports = function(Self) {
* @param {*} definition Model definition * @param {*} definition Model definition
* @param {*} properties Modified object properties * @param {*} properties Modified object properties
*/ */
function removeUnloggableProperties(definition, properties) { function removeUnloggable(definition, properties) {
const propList = Object.keys(properties); const propList = Object.keys(properties);
const propDefs = new Map(); const propDefs = new Map();

View File

@ -6,6 +6,7 @@ module.exports = function(Self) {
Self.ParameterizedSQL = ParameterizedSQL; Self.ParameterizedSQL = ParameterizedSQL;
require('../methods/vn-model/getSetValues')(Self); require('../methods/vn-model/getSetValues')(Self);
require('../methods/vn-model/getEnumValues')(Self);
Object.assign(Self, { Object.assign(Self, {
setup() { setup() {

View File

@ -132,5 +132,5 @@
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
"This ticket is deleted": "Este ticket está eliminado", "This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos", "A travel with this data already exists": "Ya existe un travel con estos datos",
"AMOUNT_NOT_MATCH_GROUPING": "AMOUNT_NOT_MATCH_GROUPING" "This thermograph id already exists": "La id del termógrafo ya existe"
} }

View File

@ -2,11 +2,11 @@
module="claim" module="claim"
description="$ctrl.claim.client.name"> description="$ctrl.claim.client.name">
<slot-menu> <slot-menu>
<a class="vn-item" <vn-item
ui-sref="ticket.create({clientFk: $ctrl.client.id})" ng-click="$ctrl.showPickupOrder()"
translate> translate>
Show Pickup order Show Pickup order
</a> </vn-item>
<vn-item <vn-item
ng-click="confirmPickupOrder.show()" ng-click="confirmPickupOrder.show()"
translate> translate>

View File

@ -12,7 +12,7 @@ class Controller extends Descriptor {
showPickupOrder() { showPickupOrder() {
this.showReport('claim-pickup-order', { this.showReport('claim-pickup-order', {
clientId: this.claim.clientFk, recipientId: this.claim.clientFk,
claimId: this.claim.id claimId: this.claim.id
}); });
} }
@ -20,7 +20,7 @@ class Controller extends Descriptor {
sendPickupOrder() { sendPickupOrder() {
return this.sendEmail('claim-pickup-order', { return this.sendEmail('claim-pickup-order', {
recipient: this.claim.client.email, recipient: this.claim.client.email,
clientId: this.claim.clientFk, recipientId: this.claim.clientFk,
claimId: this.claim.id claimId: this.claim.id
}); });
} }

View File

@ -23,7 +23,7 @@ describe('Item Component vnClaimDescriptor', () => {
controller.showReport = jest.fn(); controller.showReport = jest.fn();
const params = { const params = {
clientId: claim.clientFk, recipientId: claim.clientFk,
claimId: claim.id claimId: claim.id
}; };
controller.showPickupOrder(); controller.showPickupOrder();
@ -38,7 +38,7 @@ describe('Item Component vnClaimDescriptor', () => {
const params = { const params = {
recipient: claim.client.email, recipient: claim.client.email,
clientId: claim.clientFk, recipientId: claim.clientFk,
claimId: claim.id claimId: claim.id
}; };
controller.sendPickupOrder(); controller.sendPickupOrder();

View File

@ -26,11 +26,12 @@ module.exports = Self => {
Self.lastActiveTickets = async(id, ticketId) => { Self.lastActiveTickets = async(id, ticketId) => {
const ticket = await Self.app.models.Ticket.findById(ticketId); const ticket = await Self.app.models.Ticket.findById(ticketId);
const query = ` const query = `
SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName, ad.city AS address
FROM vn.ticket t FROM vn.ticket t
JOIN vn.ticketState ts ON t.id = ts.ticketFk JOIN vn.ticketState ts ON t.id = ts.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.id JOIN vn.agencyMode a ON t.agencyModeFk = a.id
JOIN vn.warehouse w ON t.warehouseFk = w.id JOIN vn.warehouse w ON t.warehouseFk = w.id
JOIN vn.address ad ON t.addressFk = ad.id
WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0
AND t.id <> ? AND t.warehouseFk = ? AND t.id <> ? AND t.warehouseFk = ?
ORDER BY t.shipped ORDER BY t.shipped

View File

@ -257,7 +257,7 @@ module.exports = Self => {
if (!instance.email) return; if (!instance.email) return;
const params = { const params = {
authorization: authorization, authorization: authorization,
clientId: instance.id, recipientId: instance.id,
recipient: instance.email recipient: instance.email
}; };
await request.get(`${origin}/api/email/payment-update`, { await request.get(`${origin}/api/email/payment-update`, {

View File

@ -29,7 +29,7 @@ export default class Controller extends Section {
onCustomAgentAccept() { onCustomAgentAccept() {
return this.$http.post(`CustomsAgents`, this.newCustomsAgent) return this.$http.post(`CustomsAgents`, this.newCustomsAgent)
.then(res => this.address.customsAgentFk = res.data.id); .then(res => this.address.customsAgentId = res.data.id);
} }
get town() { get town() {

View File

@ -123,7 +123,7 @@ describe('Client', () => {
controller.onCustomAgentAccept(); controller.onCustomAgentAccept();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.address.customsAgentFk).toEqual(1); expect(controller.address.customsAgentId).toEqual(1);
}); });
}); });
}); });

View File

@ -64,7 +64,7 @@ describe('Client', () => {
}); });
describe('onCustomAgentAccept()', () => { describe('onCustomAgentAccept()', () => {
it(`should create a new customs agent and then set the customsAgentFk property on the address`, () => { it(`should now create a new customs agent and then set the customsAgentFk property on the address`, () => {
const expectedResult = {id: 1, fiscalName: 'Customs agent one'}; const expectedResult = {id: 1, fiscalName: 'Customs agent one'};
$httpBackend.when('POST', 'CustomsAgents').respond(200, expectedResult); $httpBackend.when('POST', 'CustomsAgents').respond(200, expectedResult);
controller.onCustomAgentAccept(); controller.onCustomAgentAccept();

View File

@ -42,7 +42,7 @@ class Controller extends Descriptor {
onConsumerReportAccept() { onConsumerReportAccept() {
this.showReport('campaign-metrics', { this.showReport('campaign-metrics', {
clientId: this.id, recipientId: this.id,
from: this.from, from: this.from,
to: this.to, to: this.to,
}); });

View File

@ -61,7 +61,7 @@ class Controller extends Section {
send(isPreview, cb) { send(isPreview, cb) {
const sampleType = this.$.sampleType.selection; const sampleType = this.$.sampleType.selection;
const params = { const params = {
clientId: this.$params.id, recipientId: this.$params.id,
recipient: this.clientSample.recipient recipient: this.clientSample.recipient
}; };

View File

@ -68,7 +68,7 @@ describe('Client', () => {
code: 'MyReport' code: 'MyReport'
}; };
controller.clientSample = { controller.clientSample = {
clientId: 101 recipientId: 101
}; };
controller.send(false, () => {}); controller.send(false, () => {});
@ -81,7 +81,7 @@ describe('Client', () => {
controller.$.sampleType.selection = null; controller.$.sampleType.selection = null;
controller.clientSample = { controller.clientSample = {
clientId: 101, recipientId: 101,
recipient: 'client@email.com' recipient: 'client@email.com'
}; };
@ -98,7 +98,7 @@ describe('Client', () => {
code: 'MyReport' code: 'MyReport'
}; };
controller.clientSample = { controller.clientSample = {
clientId: 101, recipientId: 101,
recipient: 'client@email.com' recipient: 'client@email.com'
}; };
@ -113,11 +113,11 @@ describe('Client', () => {
code: 'MyReport' code: 'MyReport'
}; };
controller.clientSample = { controller.clientSample = {
clientId: 101, recipientId: 101,
recipient: 'client@email.com' recipient: 'client@email.com'
}; };
const expectedParams = { const expectedParams = {
clientId: 101, recipientId: 101,
recipient: 'client@email.com' recipient: 'client@email.com'
}; };
const serializedParams = $httpParamSerializer(expectedParams); const serializedParams = $httpParamSerializer(expectedParams);
@ -133,12 +133,12 @@ describe('Client', () => {
code: 'MyReport' code: 'MyReport'
}; };
controller.clientSample = { controller.clientSample = {
clientId: 101, recipientId: 101,
recipient: 'client@email.com', recipient: 'client@email.com',
companyFk: 442 companyFk: 442
}; };
const expectedParams = { const expectedParams = {
clientId: 101, recipientId: 101,
recipient: 'client@email.com', recipient: 'client@email.com',
companyId: 442 companyId: 442
}; };

View File

@ -37,7 +37,6 @@ class Controller extends Descriptor {
showEntryReport() { showEntryReport() {
this.showReport('entry-order', { this.showReport('entry-order', {
clientId: this.vnConfig.storage.currentUserWorkerId,
entryId: this.entry.id entryId: this.entry.id
}); });
} }

View File

@ -22,6 +22,7 @@ module.exports = Self => {
Self.getSummary = async id => { Self.getSummary = async id => {
let promises = []; let promises = [];
let summary = {}; let summary = {};
const models = Self.app.models;
// Item basic data and taxes // Item basic data and taxes
let filter = { let filter = {
@ -66,7 +67,7 @@ module.exports = Self => {
} }
] ]
}; };
promises.push(Self.app.models.Item.find(filter)); promises.push(models.Item.find(filter));
// Tags // Tags
filter = { filter = {
@ -78,21 +79,21 @@ module.exports = Self => {
relation: 'tag' relation: 'tag'
} }
}; };
promises.push(Self.app.models.ItemTag.find(filter)); promises.push(models.ItemTag.find(filter));
// Botanical // Botanical
filter = { filter = {
where: {itemFk: id}, where: {itemFk: id},
include: [{relation: 'genus'}, {relation: 'specie'}] include: [{relation: 'genus'}, {relation: 'specie'}]
}; };
promises.push(Self.app.models.ItemBotanical.find(filter)); promises.push(models.ItemBotanical.find(filter));
// Niches // Niches
filter = { filter = {
where: {itemFk: id}, where: {itemFk: id},
include: {relation: 'warehouse'} include: {relation: 'warehouse'}
}; };
promises.push(Self.app.models.ItemNiche.find(filter)); promises.push(models.ItemNiche.find(filter));
let res = await Promise.all(promises); let res = await Promise.all(promises);
@ -101,15 +102,10 @@ module.exports = Self => {
[summary.botanical] = res[2]; [summary.botanical] = res[2];
summary.niches = res[3]; summary.niches = res[3];
// Visible Avaible res = await models.Item.getVisibleAvailable(summary.item.id, summary.item.itemType().warehouseFk);
let query = `
CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`;
let options = [summary.item.id, summary.item.itemType().warehouseFk, false]; summary.available = res.available;
[res] = await Self.rawSql(query, options); summary.visible = res.visible;
summary.available = res[0].available ? res[0].available : '-';
summary.visible = res[0].visible ? res[0].visible : '-';
return summary; return summary;
}; };
}; };

View File

@ -1,3 +1,4 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('getVisibleAvailable', { Self.remoteMethod('getVisibleAvailable', {
description: 'Returns visible and available for params', description: 'Returns visible and available for params',
@ -11,6 +12,11 @@ module.exports = Self => {
arg: 'warehouseFk', arg: 'warehouseFk',
type: 'Number', type: 'Number',
required: true, required: true,
},
{
arg: 'dated',
type: 'Date',
required: false,
}], }],
returns: { returns: {
type: ['object'], type: ['object'],
@ -22,15 +28,35 @@ module.exports = Self => {
} }
}); });
Self.getVisibleAvailable = async(id, warehouseFk) => { Self.getVisibleAvailable = async(id, warehouseFk, dated = new Date()) => {
let query = ` let stmts = [];
CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`;
let options = [id, warehouseFk, false]; stmts.push(new ParameterizedSQL(
[res] = await Self.rawSql(query, options); 'CALL cache.available_refresh(@availableCalc, FALSE, ?, ?)', [
warehouseFk,
dated
]
));
stmts.push(new ParameterizedSQL(
'CALL cache.visible_refresh(@visibleCalc, FALSE,?)', [
warehouseFk
]
));
const visibleIndex = stmts.push(new ParameterizedSQL(
'SELECT visible FROM cache.visible WHERE calc_id = @visibleCalc AND item_id = ?', [
id
]
)) - 1;
const availableIndex = stmts.push(new ParameterizedSQL(
'SELECT available FROM cache.available WHERE calc_id = @availableCalc AND item_id = ?', [
id
]
)) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
let res = await Self.rawStmt(sql);
return { return {
available: res[0].available, available: res[availableIndex][0] ? res[availableIndex][0].available : 0,
visible: res[0].visible}; visible: res[visibleIndex][0] ? res[visibleIndex][0].visible : 0};
}; };
}; };

View File

@ -61,13 +61,9 @@ module.exports = Self => {
}, options); }, options);
} }
let query = ` res = await models.Item.getVisibleAvailable(itemFk, warehouseFk);
CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`;
let params = [itemFk, warehouseFk, true]; let newQuantity = res.visible - quantity;
let [res] = await Self.rawSql(query, params, options);
let newQuantity = res[0].visible - quantity;
await models.Sale.create({ await models.Sale.create({
ticketFk: ticketFk, ticketFk: ticketFk,

View File

@ -0,0 +1,33 @@
const app = require('vn-loopback/server/server');
describe('item getVisibleAvailable()', () => {
it('should check available visible for today', async() => {
const itemFk = 1;
const warehouseFk = 1;
const dated = new Date();
let result = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk, dated);
expect(result.available).toEqual(187);
expect(result.visible).toEqual(92);
});
it('should check available visible for no dated', async() => {
const itemFk = 1;
const warehouseFk = 1;
let result = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk);
expect(result.available).toEqual(187);
expect(result.visible).toEqual(92);
});
it('should check available visible for yesterday', async() => {
const itemFk = 1;
const warehouseFk = 1;
let dated = new Date();
dated.setDate(dated.getDate() - 1);
let result = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk, dated);
expect(result.available).toEqual(0);
expect(result.visible).toEqual(92);
});
});

View File

@ -14,13 +14,8 @@ describe('regularize()', () => {
it('should create a new ticket and add a line', async() => { it('should create a new ticket and add a line', async() => {
let ctx = {req: {accessToken: {userId: 18}}}; let ctx = {req: {accessToken: {userId: 18}}};
let res = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk);
let query = `CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`; let visible = res.visible;
let options = [itemFk, warehouseFk, true];
let [res] = await app.models.Item.rawSql(query, options);
let visible = res[0].visible;
let saleQuantity = visible - 11; let saleQuantity = visible - 11;
let ticketFk = await app.models.Item.regularize(ctx, itemFk, 11, warehouseFk); let ticketFk = await app.models.Item.regularize(ctx, itemFk, 11, warehouseFk);

View File

@ -1,5 +1,5 @@
<slot-descriptor> <slot-descriptor>
<vn-item-descriptor warehouse-fk="$ctrl.warehouseFk"> <vn-item-descriptor warehouse-fk="$ctrl.warehouseFk" dated="$ctrl.dated">
<btn-three> <btn-three>
<vn-quick-link <vn-quick-link
tooltip="Item diary" tooltip="Item diary"

View File

@ -2,14 +2,16 @@ import ngModule from '../module';
import DescriptorPopover from 'salix/components/descriptor-popover'; import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover { class Controller extends DescriptorPopover {
show(parent, id, lineFk) { show(parent, id, lineFk, dated) {
super.show(parent, id); super.show(parent, id);
this.lineFk = lineFk; this.lineFk = lineFk;
this.dated = dated;
} }
hide() { hide() {
super.hide(); super.hide();
this.lineFk = null; this.lineFk = null;
this.dated = null;
} }
} }
@ -18,6 +20,7 @@ ngModule.vnComponent('vnItemDescriptorPopover', {
controller: Controller, controller: Controller,
bindings: { bindings: {
warehouseFk: '<?', warehouseFk: '<?',
lineFk: '<?' lineFk: '<?',
dated: '<?'
} }
}); });

View File

@ -8,6 +8,12 @@
translate> translate>
Regularize stock Regularize stock
</vn-item> </vn-item>
<vn-item
ng-click="clone.show()"
name="cloneItem"
translate>
Clone
</vn-item>
</slot-menu> </slot-menu>
<slot-before> <slot-before>
<div style="position: relative" text-center> <div style="position: relative" text-center>
@ -84,3 +90,9 @@
<button response="accept" translate>Save</button> <button response="accept" translate>Save</button>
</tpl-buttons> </tpl-buttons>
</vn-dialog> </vn-dialog>
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept()"
question="Do you want to clone this item?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -31,7 +31,8 @@ class Controller extends Descriptor {
if (!this.item) return; if (!this.item) return;
const params = { const params = {
warehouseFk: this.item.itemType.warehouseFk warehouseFk: this.item.itemType.warehouseFk,
dated: this.dated
}; };
return this.$http.get(`Items/${this.id}/getVisibleAvailable`, {params}) return this.$http.get(`Items/${this.id}/getVisibleAvailable`, {params})
@ -59,12 +60,18 @@ class Controller extends Descriptor {
this.warehouseFk = null; this.warehouseFk = null;
this.quantity = null; this.quantity = null;
} }
onCloneAccept() {
this.$http.post(`Items/${this.item.id}/clone`)
.then(res => this.$state.go('item.card.tags', {id: res.data.id}));
}
} }
ngModule.vnComponent('vnItemDescriptor', { ngModule.vnComponent('vnItemDescriptor', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
item: '<' item: '<',
dated: '<'
} }
}); });

View File

@ -33,4 +33,14 @@ describe('vnItemDescriptor', () => {
expect(controller.item).toEqual(item); expect(controller.item).toEqual(item);
}); });
}); });
describe('updateStock()', () => {
it(`should perform a get query to store the item data into the controller`, () => {
$httpBackend.expectGET(`Items/${item.id}/getCard`).respond(item);
controller.id = item.id;
$httpBackend.flush();
expect(controller.item).toEqual(item);
});
});
}); });

View File

@ -31,6 +31,7 @@
<vn-th number class="expendable">Stems</vn-th> <vn-th number class="expendable">Stems</vn-th>
<vn-th number>Quantity</vn-th> <vn-th number>Quantity</vn-th>
<vn-th number class="expendable">Cost</vn-th> <vn-th number class="expendable">Cost</vn-th>
<vn-th number>Kg.</vn-th>
<vn-th number>Cube</vn-th> <vn-th number>Cube</vn-th>
<vn-th class="expendable">Provider</vn-th> <vn-th class="expendable">Provider</vn-th>
</vn-tr> </vn-tr>
@ -62,6 +63,7 @@
<vn-td number class="expendable">{{::entry.stems | dashIfEmpty}}</vn-td> <vn-td number class="expendable">{{::entry.stems | dashIfEmpty}}</vn-td>
<vn-td number>{{::entry.quantity}}</vn-td> <vn-td number>{{::entry.quantity}}</vn-td>
<vn-td number class="expendable">{{::entry.buyingValue | dashIfEmpty}}</vn-td> <vn-td number class="expendable">{{::entry.buyingValue | dashIfEmpty}}</vn-td>
<vn-td number>{{::entry.weight | dashIfEmpty}}</vn-td>
<vn-td number>{{::entry.packageFk | dashIfEmpty}}</vn-td> <vn-td number>{{::entry.packageFk | dashIfEmpty}}</vn-td>
<vn-td class="expendable">{{::entry.supplier | dashIfEmpty}}</vn-td> <vn-td class="expendable">{{::entry.supplier | dashIfEmpty}}</vn-td>
</vn-tr> </vn-tr>

View File

@ -12,7 +12,7 @@ module.exports = Self => {
} }
}); });
Self.getSourceValues = async () => { Self.getSourceValues = async() => {
return Self.getSetValues('sourceApp'); return Self.getSetValues('sourceApp');
}; };
}; };

View File

@ -11,7 +11,7 @@
data="$ctrl.items"> data="$ctrl.items">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar"> <vn-portal slot="topbar">
<vn-searchbar <vn-searchbar vn-id="searchbar"
auto-state="false" auto-state="false"
info="Search by item id or name" info="Search by item id or name"
on-search="$ctrl.onSearch($params)"> on-search="$ctrl.onSearch($params)">

View File

@ -206,7 +206,7 @@ class Controller extends Section {
removeItemId() { removeItemId() {
this.itemId = null; this.itemId = null;
this.applyFilters(); this.$.searchbar.doSearch({}, 'bar');
} }
removeItemName() { removeItemName() {

View File

@ -12,19 +12,16 @@ class Controller extends Descriptor {
showRouteReport() { showRouteReport() {
this.showReport('driver-route', { this.showReport('driver-route', {
clientId: this.vnConfig.storage.currentUserWorkerId,
routeId: this.id routeId: this.id
}); });
} }
sendRouteReport() { sendRouteReport() {
const params = { const workerUser = this.route.worker.user;
recipient: user.emailUser.email, this.sendEmail('driver-route', {
clientId: this.vnConfig.storage.currentUserWorkerId, recipient: workerUser.emailUser.email,
routeId: this.id routeId: this.id
}; });
return this.$http.get(`email/driver-route`, {params})
.then(() => this.vnApp.showSuccess(this.$t('Report sent')));
} }
updateVolume() { updateVolume() {

View File

@ -47,14 +47,9 @@ module.exports = Self => {
include: {relation: 'ticket'} include: {relation: 'ticket'}
}, options); }, options);
let [[stock]] = await Self.rawSql(`CALL vn.item_getVisibleAvailable(?,?,?,?)`, [ const res = await models.Item.getVisibleAvailable(ctx.args.itemFk, request.ticket().warehouseFk, request.ticket().shipped);
ctx.args.itemFk,
request.ticket().shipped,
request.ticket().warehouseFk,
false
], options);
if (stock.available < 0) if (res.available < 0)
throw new UserError(`This item is not available`); throw new UserError(`This item is not available`);
if (request.saleFk) { if (request.saleFk) {

View File

@ -42,15 +42,9 @@ module.exports = Self => {
const item = await models.Item.findById(itemId); const item = await models.Item.findById(itemId);
const ticket = await models.Ticket.findById(id); const ticket = await models.Ticket.findById(id);
const shouldRefresh = false; const res = await models.Item.getVisibleAvailable(itemId, ticket.warehouseFk, ticket.shipped);
const [[stock]] = await Self.rawSql(`CALL vn.item_getVisibleAvailable(?, ?, ?, ?)`, [
itemId,
ticket.shipped,
ticket.warehouseFk,
shouldRefresh
]);
if (stock.available < quantity) if (res.available < quantity)
throw new UserError(`This item is not available`); throw new UserError(`This item is not available`);
const newSale = await models.Sale.create({ const newSale = await models.Sale.create({

View File

@ -83,7 +83,7 @@ module.exports = Self => {
} }
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {
include: { include: [{
relation: 'client', relation: 'client',
scope: { scope: {
fields: ['id', 'salesPersonFk'], fields: ['id', 'salesPersonFk'],
@ -97,9 +97,27 @@ module.exports = Self => {
} }
} }
} }
} }, {
relation: 'ship'
}, {
relation: 'stowaway'
}]
}); });
// Change state to "fixing" if contains an stowaway
let otherTicketId;
if (ticket.stowaway())
otherTicketId = ticket.stowaway().shipFk;
else if (ticket.ship())
otherTicketId = ticket.ship().id;
if (otherTicketId) {
await models.TicketTracking.changeState(ctx, {
ticketFk: otherTicketId,
code: 'FIXING'
});
}
// Send notification to salesPerson // Send notification to salesPerson
const salesPerson = ticket.client().salesPerson(); const salesPerson = ticket.client().salesPerson();
if (salesPerson) { if (salesPerson) {

View File

@ -4,6 +4,7 @@ const models = app.models;
describe('ticket deleted()', () => { describe('ticket deleted()', () => {
let ticket; let ticket;
let sale; let sale;
let deletedClaim;
beforeAll(async done => { beforeAll(async done => {
let originalTicket = await models.Ticket.findOne({where: {id: 16}}); let originalTicket = await models.Ticket.findOne({where: {id: 16}});
@ -27,8 +28,36 @@ describe('ticket deleted()', () => {
}); });
afterAll(async done => { afterAll(async done => {
const ticketId = 16;
const stowawayTicketId = 17;
const ctx = {
req: {
accessToken: {userId: 106},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
await models.Ticket.destroyById(ticket.id); await models.Ticket.destroyById(ticket.id);
const stowaway = await models.Stowaway.findOne({
where: {
id: stowawayTicketId,
shipFk: ticketId
}
});
await stowaway.destroy();
await models.Claim.create(deletedClaim);
await models.TicketTracking.changeState(ctx, {
ticketFk: ticketId,
code: 'OK'
});
await models.TicketTracking.changeState(ctx, {
ticketFk: stowawayTicketId,
code: 'OK'
});
const orgTicket = await models.Ticket.findById(ticketId);
await orgTicket.updateAttribute('isDeleted', false);
done(); done();
}); });
@ -103,4 +132,35 @@ describe('ticket deleted()', () => {
expect(error.translateArgs[0]).toEqual(2); expect(error.translateArgs[0]).toEqual(2);
expect(error.message).toEqual('You must delete the claim id %d first'); expect(error.message).toEqual('You must delete the claim id %d first');
}); });
it('should delete the ticket and change the state to "FIXING" to the stowaway ticket', async() => {
const ticketId = 16;
const claimIdToRemove = 2;
const stowawayTicketId = 17;
const ctx = {
req: {
accessToken: {userId: 106},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
await app.models.Stowaway.rawSql(`
INSERT INTO vn.stowaway(id, shipFk)
VALUES (?, ?)`, [stowawayTicketId, ticketId]);
deletedClaim = await app.models.Claim.findById(claimIdToRemove);
await app.models.Claim.destroyById(claimIdToRemove);
await app.models.Ticket.setDeleted(ctx, ticketId);
const stowawayTicket = await app.models.TicketState.findOne({
where: {
ticketFk: stowawayTicketId
}
});
expect(stowawayTicket.code).toEqual('FIXING');
});
}); });

View File

@ -17,6 +17,9 @@
}, },
"alertLevel": { "alertLevel": {
"type": "Number" "type": "Number"
},
"code": {
"type": "string"
} }
}, },
"relations": { "relations": {

View File

@ -102,15 +102,15 @@ class Controller extends Descriptor {
showDeliveryNote() { showDeliveryNote() {
this.showReport('delivery-note', { this.showReport('delivery-note', {
clientId: this.ticket.client.id, recipientId: this.ticket.client.id,
ticketId: this.id, ticketId: this.id,
}); });
} }
sendDeliveryNote() { sendDeliveryNote() {
return this.sendEmail('delivery-note', { return this.sendEmail('delivery-note', {
recipientId: this.ticket.client.id,
recipient: this.ticket.client.email, recipient: this.ticket.client.email,
clientId: this.ticket.client.id,
ticketId: this.id ticketId: this.id
}); });
} }

View File

@ -306,9 +306,10 @@
<thead> <thead>
<tr> <tr>
<th number>Id</th> <th number>Id</th>
<th number>F. envio</th> <th number>Shipped</th>
<th number>Agencia</th> <th number>Agency</th>
<th number>Almacen</th> <th number>Warehouse</th>
<th number>Address</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -323,6 +324,7 @@
<td number>{{::ticket.shipped | date: 'dd/MM/yyyy'}}</td> <td number>{{::ticket.shipped | date: 'dd/MM/yyyy'}}</td>
<td number>{{::ticket.agencyName}}</td> <td number>{{::ticket.agencyName}}</td>
<td number>{{::ticket.warehouseName}}</td> <td number>{{::ticket.warehouseName}}</td>
<td number>{{::ticket.address}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -30,3 +30,7 @@ Continue anyway?: ¿Continuar de todas formas?
This ticket is now empty: El ticket ha quedado vacio This ticket is now empty: El ticket ha quedado vacio
Do you want to delete it?: ¿Quieres eliminarlo? Do you want to delete it?: ¿Quieres eliminarlo?
Recalculate price: Recalcular precio Recalculate price: Recalcular precio
Address: Dirección
Warehouse: Almacen
Agency: Agencia
Shipped: F. envio

View File

@ -121,7 +121,7 @@
</vn-td> </vn-td>
<vn-td number shrink> <vn-td number shrink>
<span <span
ng-click="descriptor.show($event, sale.itemFk, sale.id)" ng-click="descriptor.show($event, sale.itemFk, sale.id, $ctrl.ticket.shipped)"
class="link"> class="link">
{{sale.itemFk | zeroFill:6}} {{sale.itemFk | zeroFill:6}}
</span> </span>

View File

@ -37,6 +37,11 @@ class Controller extends Section {
return true; return true;
} }
showInvoiceOutDescriptor(event, refFk) {
if (!refFk) return;
this.$.invoiceOutDescriptor.show(event.target, this.summary.invoiceOut.id);
}
setOkState() { setOkState() {
let params = {}; let params = {};

View File

@ -0,0 +1,60 @@
module.exports = Self => {
Self.remoteMethod('createThermograph', {
description: 'Creates a new thermograph',
accessType: 'WRITE',
accepts: [{
arg: 'thermographId',
type: 'String',
description: 'The thermograph id',
required: true
}, {
arg: 'model',
type: 'String',
description: 'The thermograph model',
required: true
}, {
arg: 'temperature',
type: 'String',
description: 'The thermograph temperature',
required: true
}, {
arg: 'warehouseId',
type: 'Number',
description: 'The warehouse id',
required: true
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/createThermograph`,
verb: 'POST'
}
});
Self.createThermograph = async(thermographId, model, temperature, warehouseId) => {
const models = Self.app.models;
const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx};
const thermograph = await models.Thermograph.create({
id: thermographId,
model: model
}, options);
await Self.rawSql(`
INSERT INTO travelThermograph(thermographFk, warehouseFk, temperature, created)
VALUES (?, ?,?, NOW())
`, [thermograph.id, warehouseId, temperature], options);
await tx.commit();
return thermograph;
} catch (err) {
await tx.rollback();
throw err;
}
};
};

View File

@ -0,0 +1,18 @@
module.exports = Self => {
Self.remoteMethod('getThermographModels', {
description: 'Gets the thermograph models',
accessType: 'READ',
returns: {
type: ['String'],
root: true
},
http: {
path: `/getThermographModels`,
verb: 'GET'
}
});
Self.getThermographModels = async() => {
return Self.getEnumValues('model');
};
};

View File

@ -0,0 +1,49 @@
const app = require('vn-loopback/server/server');
describe('Termograph createThermograph()', () => {
const models = app.models;
const thermographId = '99999-1';
const model = 'DISPOSABLE';
const temperature = 'COOL';
const warehouseId = 1;
let createdThermograph;
afterAll(async done => {
let travelThermograpToDelete = await models.TravelThermograph.findOne({where: {thermographFk: createdThermograph.id}});
let thermograpToDelete = await models.Thermograph.findById(createdThermograph.id);
await travelThermograpToDelete.destroy();
await thermograpToDelete.destroy();
done();
});
it(`should create a thermograph which is saved in both thermograph and travelThermograph`, async() => {
let createdTravelThermograpth = await models.TravelThermograph.findOne({where: {thermographFk: thermographId}});
expect(createdTravelThermograpth).toBeNull();
createdThermograph = await models.Thermograph.createThermograph(thermographId, model, temperature, warehouseId);
expect(createdThermograph.id).toEqual(thermographId);
expect(createdThermograph.model).toEqual(model);
createdTravelThermograpth = await models.TravelThermograph.findOne({where: {thermographFk: thermographId}});
expect(createdTravelThermograpth.warehouseFk).toEqual(warehouseId);
expect(createdTravelThermograpth.temperature).toEqual(temperature);
});
it(`should not be able to created duplicated entries`, async() => {
let error;
try {
await models.Thermograph.createThermograph(thermographId, model, temperature, warehouseId);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe('This thermograph id already exists');
});
});

View File

@ -0,0 +1,18 @@
module.exports = Self => {
Self.remoteMethod('getThermographTemperatures', {
description: 'Gets the thermograph temperatures',
accessType: 'READ',
returns: {
type: ['String'],
root: true
},
http: {
path: `/getThermographTemperatures`,
verb: 'GET'
}
});
Self.getThermographTemperatures = async() => {
return Self.getEnumValues('temperature');
};
};

View File

@ -0,0 +1,12 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/thermograph/createThermograph')(Self);
require('../methods/thermograph/getThermographModels')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`This thermograph id already exists`);
return err;
});
};

View File

@ -10,10 +10,12 @@
"id": { "id": {
"type": "String", "type": "String",
"id": true, "id": true,
"description": "Identifier" "description": "Identifier",
"required": true
}, },
"model": { "model": {
"type": "String" "type": "String",
"required": true
} }
} }
} }

View File

@ -1,4 +1,5 @@
module.exports = Self => { module.exports = Self => {
require('../methods/travel-thermograph/allowedContentTypes')(Self); require('../methods/travel-thermograph/allowedContentTypes')(Self);
require('../methods/travel-thermograph/getThermographTemperatures')(Self);
}; };

View File

@ -21,10 +21,15 @@
"type": "Date" "type": "Date"
}, },
"temperature": { "temperature": {
"type": "String" "type": "String",
"required": true
}, },
"result": { "result": {
"type": "String" "type": "String"
},
"warehouseFk": {
"type": "Number",
"required": true
} }
}, },
"relations": { "relations": {

View File

@ -17,6 +17,18 @@
where="{travelFk: null}" where="{travelFk: null}"
show-field="thermographFk" show-field="thermographFk"
value-field="thermographFk"> value-field="thermographFk">
<tpl-item>
{{thermographFk}}
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New thermograph"
ng-click="$ctrl.onAddThermographClick($event)"
vn-acl="buyer"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-autocomplete> </vn-autocomplete>
<vn-textfield vn-one <vn-textfield vn-one
label="State" label="State"
@ -84,3 +96,63 @@
</vn-button-bar> </vn-button-bar>
</div> </div>
</form> </form>
<!-- Create thermograph dialog -->
<vn-crud-model
vn-id="modelsModel"
url="Thermographs/getThermographModels"
data="thermographModels">
</vn-crud-model>
<vn-crud-model
vn-id="temperaturesModel"
url="TravelThermographs/getThermographTemperatures"
data="thermographTemperatures">
</vn-crud-model>
<vn-dialog class="edit"
vn-id="newThermographDialog"
on-accept="$ctrl.onNewThermographAccept()"
message="New thermograph">
<tpl-body>
<vn-horizontal>
<vn-textfield
vn-one
required="true"
label="Identifier"
ng-model="$ctrl.newThermograph.thermographId"
vn-focus>
</vn-textfield>
<vn-autocomplete
vn-one
required="true"
label="Model"
ng-model="$ctrl.newThermograph.model"
data="thermographModels"
show-field="value"
value-field="value">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
required="true"
label="Warehouse"
ng-model="$ctrl.newThermograph.warehouseId"
url="Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
vn-one
required="true"
label="Temperature"
ng-model="$ctrl.newThermograph.temperature"
data="thermographTemperatures"
show-field="value"
value-field="value">
</vn-autocomplete>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Create</button>
</tpl-buttons>
</vn-dialog>

View File

@ -55,6 +55,28 @@ class Controller extends Section {
}); });
} }
onAddThermographClick(event) {
const defaultTemperature = 'COOL';
const defaultModel = 'DISPOSABLE';
event.preventDefault();
this.newThermograph = {
thermographId: this.thermographId,
warehouseId: this.warehouseId,
temperature: defaultTemperature,
model: defaultModel
};
this.$.modelsModel.refresh();
this.$.temperaturesModel.refresh();
this.$.newThermographDialog.show();
}
onNewThermographAccept() {
return this.$http.post(`Thermographs/createThermograph`, this.newThermograph)
.then(res => this.dms.thermographId = res.data.id);
}
onSubmit() { onSubmit() {
const query = `Travels/${this.travel.id}/createThermograph`; const query = `Travels/${this.travel.id}/createThermograph`;
const options = { const options = {

View File

@ -63,5 +63,32 @@ describe('Ticket', () => {
expect(controller.allowedContentTypes).toEqual('application/pdf, image/png, image/jpg'); expect(controller.allowedContentTypes).toEqual('application/pdf, image/png, image/jpg');
}); });
}); });
describe('onAddThermographClick()', () => {
it('should call the show() function of the create thermograph dialog', () => {
controller.$.newThermographDialog = {show: jest.fn()};
controller.$.modelsModel = {refresh: jest.fn()};
controller.$.temperaturesModel = {refresh: jest.fn()};
const event = new Event('click');
jest.spyOn(event, 'preventDefault');
controller.onAddThermographClick(event);
expect(event.preventDefault).toHaveBeenCalledTimes(1);
expect(controller.$.newThermographDialog.show).toHaveBeenCalledTimes(1);
});
});
describe('onNewThermographAccept()', () => {
it('should set the created thermograph id on to the controller for the autocomplete to use it', () => {
const response = {id: 'the created id'};
$httpBackend.when('POST', `Thermographs/createThermograph`).respond(response);
controller.onNewThermographAccept();
$httpBackend.flush();
expect(controller.dms.thermographId).toEqual(response.id);
});
});
}); });
}); });

View File

@ -15,4 +15,6 @@ Add thermograph: Añadir termógrafo
Edit thermograph: Editar termógrafo Edit thermograph: Editar termógrafo
Thermograph deleted: Termógrafo eliminado Thermograph deleted: Termógrafo eliminado
Thermograph: Termógrafo Thermograph: Termógrafo
New thermograph: Nuevo termógrafo
Are you sure you want to remove the thermograph?: ¿Seguro que quieres quitar el termógrafo? Are you sure you want to remove the thermograph?: ¿Seguro que quieres quitar el termógrafo?
Identifier: Identificador

View File

@ -1,3 +1,4 @@
Date: Fecha
Model: Modelo Model: Modelo
Action: Acción Action: Acción
Author: Autor Author: Autor

View File

@ -62,7 +62,8 @@
"url" : "/log", "url" : "/log",
"state": "worker.card.workerLog", "state": "worker.card.workerLog",
"component": "vn-worker-log", "component": "vn-worker-log",
"description": "Log" "description": "Log",
"acl": ["hr"]
}, { }, {
"url": "/pbx", "url": "/pbx",
"state": "worker.card.pbx", "state": "worker.card.pbx",

View File

@ -61,8 +61,8 @@ describe('Zone Component vnZoneDeliveryDays', () => {
expect(controller.$.data).toEqual(expectedData); expect(controller.$.data).toEqual(expectedData);
}); });
}); });
// Petición #2259 cread
xdescribe('onSelection()', () => { describe('onSelection()', () => {
it('should not call the show popover method if events array is empty', () => { it('should not call the show popover method if events array is empty', () => {
jest.spyOn(controller.$.zoneEvents, 'show'); jest.spyOn(controller.$.zoneEvents, 'show');

38
package-lock.json generated
View File

@ -3244,7 +3244,7 @@
}, },
"util": { "util": {
"version": "0.10.3", "version": "0.10.3",
"resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -4060,7 +4060,7 @@
"base": { "base": {
"version": "0.11.2", "version": "0.11.2",
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=",
"dev": true, "dev": true,
"requires": { "requires": {
"cache-base": "^1.0.1", "cache-base": "^1.0.1",
@ -4577,7 +4577,7 @@
"cache-base": { "cache-base": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=",
"dev": true, "dev": true,
"requires": { "requires": {
"collection-visit": "^1.0.0", "collection-visit": "^1.0.0",
@ -4754,7 +4754,7 @@
"class-utils": { "class-utils": {
"version": "0.3.6", "version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
"integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=",
"dev": true, "dev": true,
"requires": { "requires": {
"arr-union": "^3.1.0", "arr-union": "^3.1.0",
@ -5816,7 +5816,7 @@
"dot-prop": { "dot-prop": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=",
"requires": { "requires": {
"is-obj": "^1.0.0" "is-obj": "^1.0.0"
} }
@ -6751,7 +6751,7 @@
}, },
"file-loader": { "file-loader": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "http://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz",
"integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -7918,7 +7918,7 @@
"global-modules": { "global-modules": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
"integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=",
"dev": true, "dev": true,
"requires": { "requires": {
"global-prefix": "^1.0.1", "global-prefix": "^1.0.1",
@ -8500,7 +8500,7 @@
"dependencies": { "dependencies": {
"es6-promise": { "es6-promise": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
"dev": true "dev": true
}, },
@ -9579,7 +9579,7 @@
"is-plain-object": { "is-plain-object": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=",
"dev": true, "dev": true,
"requires": { "requires": {
"isobject": "^3.0.1" "isobject": "^3.0.1"
@ -9935,7 +9935,7 @@
"jasmine-spec-reporter": { "jasmine-spec-reporter": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
"integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", "integrity": "sha1-HWMq7ANBZwrTJPkrqEtLMrNeniI=",
"dev": true, "dev": true,
"requires": { "requires": {
"colors": "1.1.2" "colors": "1.1.2"
@ -11932,7 +11932,7 @@
}, },
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}, },
"mississippi": { "mississippi": {
@ -12972,7 +12972,7 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "0.0.10", "version": "0.0.10",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
"dev": true "dev": true
}, },
@ -14180,7 +14180,7 @@
"dependencies": { "dependencies": {
"jsesc": { "jsesc": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true "dev": true
} }
@ -14558,7 +14558,7 @@
}, },
"safe-regex": { "safe-regex": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -14648,7 +14648,7 @@
"dependencies": { "dependencies": {
"source-map": { "source-map": {
"version": "0.4.4", "version": "0.4.4",
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -15006,7 +15006,7 @@
"snapdragon-node": { "snapdragon-node": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
"integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=",
"dev": true, "dev": true,
"requires": { "requires": {
"define-property": "^1.0.0", "define-property": "^1.0.0",
@ -15057,7 +15057,7 @@
"snapdragon-util": { "snapdragon-util": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
"integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=",
"dev": true, "dev": true,
"requires": { "requires": {
"kind-of": "^3.2.0" "kind-of": "^3.2.0"
@ -15332,7 +15332,7 @@
"split-string": { "split-string": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
"integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=",
"dev": true, "dev": true,
"requires": { "requires": {
"extend-shallow": "^3.0.0" "extend-shallow": "^3.0.0"
@ -16409,7 +16409,7 @@
"touch": { "touch": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=",
"dev": true, "dev": true,
"requires": { "requires": {
"nopt": "~1.0.10" "nopt": "~1.0.10"

View File

@ -87,17 +87,29 @@ class Component {
return component; return component;
} }
async render() { component() {
if (this._component)
return this._component;
const component = this.build(); const component = this.build();
const i18n = new VueI18n(config.i18n); const i18n = new VueI18n(config.i18n);
const app = new Vue({ this._component = new Vue({
i18n: i18n, i18n: i18n,
render: h => h(component, { render: h => h(component, {
props: this.args props: this.args
}) })
}); });
return renderer.renderToString(app); return this._component;
}
/**
* @return {Promise} Rendered component
*/
async render() {
return renderer.renderToString(
this.component()
);
} }
} }

View File

@ -15,10 +15,11 @@ module.exports = {
const props = this.args; const props = this.args;
let query = ''; let query = '';
for (let param in props) { for (let param in props) {
if (query != '') if (!(props[param] instanceof Object)) {
query += '&'; if (query != '') query += '&';
query += `${param}=${props[param]}`; query += `${param}=${props[param]}`;
} }
}
return query; return query;
} }

View File

@ -1,9 +1,8 @@
const path = require('path'); const path = require('path');
const smtp = require('./smtp'); const smtp = require('./smtp');
const config = require('./config');
const Component = require('./component'); const Component = require('./component');
const Report = require('./report'); const Report = require('./report');
const db = require('./database');
const config = require('./config');
if (!process.env.OPENSSL_CONF) if (!process.env.OPENSSL_CONF)
process.env.OPENSSL_CONF = '/etc/ssl/'; process.env.OPENSSL_CONF = '/etc/ssl/';
@ -20,27 +19,22 @@ class Email extends Component {
} }
async getSubject() { async getSubject() {
if (!this.lang) await this.getLang(); const component = await this.component();
const locale = this.locale.messages; let locale = this.args.auth.locale;
const userLocale = locale[this.lang];
if (!userLocale) { if (this.args.recipientId)
locale = await component.getLocale(this.args.recipientId);
const messages = this.locale.messages;
const userTranslations = messages[locale];
if (!userTranslations) {
const fallbackLocale = config.i18n.fallbackLocale; const fallbackLocale = config.i18n.fallbackLocale;
return locale[fallbackLocale].subject; return messages[fallbackLocale].subject;
} }
return userLocale.subject; return userTranslations.subject;
}
async getLang() {
const clientId = this.args.clientId;
const lang = await db.findOne(`
SELECT lang FROM account.user
WHERE id = ?`, [clientId]).then(rows => {
return rows.lang;
});
this.lang = lang;
} }
async send() { async send() {
@ -80,6 +74,7 @@ class Email extends Component {
const localeSubject = await this.getSubject(); const localeSubject = await this.getSubject();
const options = { const options = {
to: this.args.recipient, to: this.args.recipient,
replyTo: this.args.auth.email,
subject: localeSubject, subject: localeSubject,
html: rendered, html: rendered,
attachments: attachments attachments: attachments

View File

@ -19,8 +19,7 @@ const validator = {
throw new Error(`Required properties not found [${required}]`); throw new Error(`Required properties not found [${required}]`);
} }
}, },
props: ['isPreview'] props: ['isPreview', 'authorization']
}; };
Vue.mixin(validator); Vue.mixin(validator);

View File

@ -4,28 +4,27 @@ const config = require('../config');
const fallbackLocale = config.i18n.fallbackLocale; const fallbackLocale = config.i18n.fallbackLocale;
const userLocale = { const userLocale = {
async serverPrefetch() { async serverPrefetch() {
if (this.clientId) if (this.auth)
this.locale = await this.getLocale(this.clientId); this.$i18n.locale = this.auth.locale;
if (this.locale) if (this.recipientId)
this.$i18n.locale = this.locale; this.$i18n.locale = await this.getLocale(this.recipientId);
}, },
methods: { methods: {
getLocale(clientId) { getLocale(recipientId) {
return db.findOne(` return db.findOne(`
SELECT IF(u.lang IS NOT NULL, u.lang, LOWER(ct.code)) lang SELECT IF(u.lang IS NOT NULL, u.lang, LOWER(ct.code)) lang
FROM client c FROM client c
JOIN country ct ON ct.id = c.countryFk JOIN country ct ON ct.id = c.countryFk
JOIN account.user u ON u.id = c.id JOIN account.user u ON u.id = c.id
WHERE c.id = ?`, [clientId]).then(rows => { WHERE c.id = ?`, [recipientId]).then(rows => {
if (rows) if (rows)
return rows.lang; return rows.lang;
else return fallbackLocale; else return fallbackLocale;
}); });
} }
}, },
props: ['clientId'] props: ['auth', 'recipientId']
}; };
Vue.mixin(userLocale); Vue.mixin(userLocale);

View File

@ -17,17 +17,32 @@ module.exports = app => {
for (let method of methods) for (let method of methods)
paths.push(`/api/${method}/*`); paths.push(`/api/${method}/*`);
app.use(paths, async function(request, response, next) { app.use(paths, async function(req, res, next) {
const authorization = getToken(request); const token = getToken(req);
const query = `SELECT userId, ttl, created const query = `SELECT at.id, at.userId, eu.email, u.lang, at.ttl, at.created
FROM salix.AccessToken WHERE id = ?`; FROM salix.AccessToken at
JOIN account.user u ON u.id = at.userid
JOIN account.emailUser eu ON eu.userFk = u.id
WHERE at.id = ?`;
try { try {
const authToken = await db.findOne(query, [authorization]); const auth = await db.findOne(query, [token]);
if (!authToken || isTokenExpired(authToken.created, authToken.ttl)) if (!auth || isTokenExpired(auth.created, auth.ttl))
throw new Error('Invalid authorization token'); throw new Error('Invalid authorization token');
const args = Object.assign({}, req.query);
const props = Object.assign(args, req.body);
props.authorization = auth.id;
req.args = props;
req.args.auth = {
userId: auth.userId,
token: auth.id,
email: auth.email,
locale: auth.lang
};
next(); next();
} catch (error) { } catch (error) {
next(error); next(error);
@ -36,12 +51,9 @@ module.exports = app => {
function getToken(request) { function getToken(request) {
const headers = request.headers; const headers = request.headers;
const params = request.query; const queryParams = request.query;
if (headers.authorization) return headers.authorization || queryParams.authorization;
params.authorization = headers.authorization;
return headers.authorization || params.authorization;
} }
function isTokenExpired(created, ttl) { function isTokenExpired(created, ttl) {

View File

@ -27,7 +27,7 @@ module.exports = {
await db.rawSql(` await db.rawSql(`
INSERT INTO vn.mail (sender, replyTo, sent, subject, body, status) INSERT INTO vn.mail (sender, replyTo, sent, subject, body, status)
VALUES (:recipient, :sender, 1, :subject, :body, :status)`, { VALUES (:recipient, :sender, 1, :subject, :body, :status)`, {
sender: config.app.senderEmail, sender: options.replyTo,
recipient: options.to, recipient: options.to,
subject: options.subject, subject: options.subject,
body: options.text || options.html, body: options.text || options.html,

View File

@ -23,13 +23,13 @@ module.exports = app => {
for (const ticket of tickets) { for (const ticket of tickets) {
try { try {
await db.rawSql(`CALL vn.ticketClosureTicket(:ticketId)`, { await db.rawSql(`CALL vn.ticket_closeByTicket(:ticketId)`, {
ticketId: ticket.id ticketId: ticket.id
}); });
const args = { const args = {
ticketId: ticket.id, ticketId: ticket.id,
clientId: ticket.clientFk, recipientId: ticket.clientFk,
recipient: ticket.recipient recipient: ticket.recipient
}; };
const email = new Email('delivery-note-link', args); const email = new Email('delivery-note-link', args);

View File

@ -2,19 +2,11 @@ const Email = require('../core/email');
module.exports = app => { module.exports = app => {
app.get(`/api/email/:name`, async(req, res, next) => { app.get(`/api/email/:name`, async(req, res, next) => {
const args = req.query;
const requiredArgs = ['clientId', 'recipient'];
const argList = requiredArgs.join(',');
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
try { try {
if (!hasRequiredArgs) const reportName = req.params.name;
throw new Error(`Required properties not found [${argList}]`); const email = new Email(reportName, req.args);
const email = new Email(req.params.name, args); if (req.args.isPreview === 'true') {
if (args.isPreview === 'true') {
const rendered = await email.render(); const rendered = await email.render();
res.send(rendered); res.send(rendered);

View File

@ -2,20 +2,10 @@ const Report = require('../core/report');
module.exports = app => { module.exports = app => {
app.get(`/api/report/:name`, async(req, res, next) => { app.get(`/api/report/:name`, async(req, res, next) => {
const args = req.query;
const requiredArgs = ['clientId'];
const argList = requiredArgs.join(',');
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
try { try {
if (!hasRequiredArgs)
throw new Error(`Required properties not found [${argList}]`);
const reportName = req.params.name; const reportName = req.params.name;
const fileName = getFileName(reportName, args); const fileName = getFileName(reportName, req.args);
const report = new Report(reportName, args); const report = new Report(reportName, req.args);
const stream = await report.toPdfStream(); const stream = await report.toPdfStream();
res.setHeader('Content-type', 'application/pdf'); res.setHeader('Content-type', 'application/pdf');
@ -38,8 +28,7 @@ module.exports = app => {
const keys = Object.keys(args); const keys = Object.keys(args);
for (let arg of keys) { for (let arg of keys) {
// FIXME: #2197 - Remove clientId as a required param if (arg.endsWith('Id'))
if (arg != 'clientId' && arg.endsWith('Id'))
identifiers.push(arg); identifiers.push(arg);
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
@ -43,9 +43,9 @@
<tbody> <tbody>
<tr v-for="waste in wastes" v-bind:key="waste.buyer"> <tr v-for="waste in wastes" v-bind:key="waste.buyer">
<td class="font gray">{{waste.buyer}}</td> <td class="font gray">{{waste.buyer}}</td>
<td class="number">{{(waste.percentage / 100) | percentage(2, 2, locale)}}</td> <td class="number">{{(waste.percentage / 100) | percentage(2, 2, $i18n.locale)}}</td>
<td class="number">{{waste.dwindle | currency('EUR', locale)}}</td> <td class="number">{{waste.dwindle | currency('EUR', $i18n.locale)}}</td>
<td class="number">{{waste.total | currency('EUR', locale)}}</td> <td class="number">{{waste.total | currency('EUR', $i18n.locale)}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -10,7 +10,7 @@ module.exports = {
'email-footer': emailFooter.build() 'email-footer': emailFooter.build()
}, },
props: { props: {
clientId: { recipientId: {
required: true required: true
}, },
from: { from: {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -6,7 +6,7 @@ const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'client-welcome', name: 'client-welcome',
async serverPrefetch() { async serverPrefetch() {
this.client = await this.fetchClient(this.clientId); this.client = await this.fetchClient(this.recipientId);
}, },
methods: { methods: {
fetchClient(clientId) { fetchClient(clientId) {
@ -29,7 +29,7 @@ module.exports = {
'email-footer': emailFooter.build() 'email-footer': emailFooter.build()
}, },
props: { props: {
clientId: { recipientId: {
required: true required: true
} }
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -0,0 +1,10 @@
subject: Your delivery note
title: "Here is your delivery note!"
dear: Dear client
description: The delivery note from the order <strong>{0}</strong> is now available. <br/>
You can download it by clicking <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">this link</a>.
copyLink: 'As an alternative, you can copy the following link in your browser:'
poll: If you wish, you can answer our satisfaction survey to
   help us provide better service. Your opinion is very important for us!
help: Any questions that arise, do not hesitate to consult it, <strong>we are here to assist you!</strong>
conclusion: Thanks for your attention!

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -0,0 +1,9 @@
subject: Your delivery note
title: "Here is your delivery note!"
dear: Dear client
description: The delivery note from the order <strong>{0}</strong> is now available. <br/>
You can download it by clicking on the attachment of this email.
poll: If you wish, you can answer our satisfaction survey to
   help us provide better service. Your opinion is very important for us!
help: Any questions that arise, do not hesitate to consult it, <strong>we are here to assist you!</strong>
conclusion: Thanks for your attention!

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -8,7 +8,7 @@ const attachments = require('./attachments.json');
module.exports = { module.exports = {
name: 'letter-debtor-nd', name: 'letter-debtor-nd',
async serverPrefetch() { async serverPrefetch() {
this.debtor = await this.fetchDebtor(this.clientId, this.companyId); this.debtor = await this.fetchDebtor(this.recipientId, this.companyId);
if (!this.debtor) if (!this.debtor)
throw new Error('Something went wrong'); throw new Error('Something went wrong');
@ -40,7 +40,7 @@ module.exports = {
authorization: { authorization: {
required: true required: true
}, },
clientId: { recipientId: {
required: true required: true
}, },
companyId: { companyId: {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
@ -23,7 +23,7 @@
<!-- Block --> <!-- Block -->
<div class="grid-row"> <div class="grid-row">
<div class="grid-block vn-pa-lg"> <div class="grid-block vn-pa-lg">
<h1>{{ $t('title') }}</h1> <h1>{{ $t('title') }} {{$i18n.locale}}</h1>
<p>{{ $t('sections.introduction.title') }},</p> <p>{{ $t('sections.introduction.title') }},</p>
<p>{{ $t('sections.introduction.description') }}</p> <p>{{ $t('sections.introduction.description') }}</p>

View File

@ -8,7 +8,7 @@ const attachments = require('./attachments.json');
module.exports = { module.exports = {
name: 'letter-debtor-st', name: 'letter-debtor-st',
async serverPrefetch() { async serverPrefetch() {
this.debtor = await this.fetchDebtor(this.clientId, this.companyId); this.debtor = await this.fetchDebtor(this.recipientId, this.companyId);
if (!this.debtor) if (!this.debtor)
throw new Error('Something went wrong'); throw new Error('Something went wrong');
@ -37,10 +37,7 @@ module.exports = {
'attachment': attachment.build() 'attachment': attachment.build()
}, },
props: { props: {
authorization: { recipientId: {
required: true
},
clientId: {
required: true required: true
}, },
companyId: { companyId: {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -6,7 +6,7 @@ const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'payment-update', name: 'payment-update',
async serverPrefetch() { async serverPrefetch() {
this.payMethod = await this.fetchPayMethod(this.clientId); this.payMethod = await this.fetchPayMethod(this.recipientId);
if (!this.payMethod) if (!this.payMethod)
throw new Error('Something went wrong'); throw new Error('Something went wrong');
@ -34,7 +34,7 @@ module.exports = {
'email-footer': emailFooter.build() 'email-footer': emailFooter.build()
}, },
props: { props: {
clientId: { recipientId: {
required: true required: true
} }
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -8,7 +8,7 @@ const attachments = require('./attachments.json');
module.exports = { module.exports = {
name: 'printer-setup', name: 'printer-setup',
async serverPrefetch() { async serverPrefetch() {
this.client = await this.fetchClient(this.clientId); this.client = await this.fetchClient(this.recipientId);
}, },
data() { data() {
return {attachments}; return {attachments};
@ -37,7 +37,7 @@ module.exports = {
'attachment': attachment.build() 'attachment': attachment.build()
}, },
props: { props: {
clientId: { recipientId: {
required: true required: true
} }
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="locale"> <html v-bind:lang="$i18n.locale">
<head> <head>
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@ -15,7 +15,7 @@ module.exports = {
'attachment': attachment.build() 'attachment': attachment.build()
}, },
props: { props: {
clientId: { recipientId: {
required: true required: true
}, },
companyId: { companyId: {

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