Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2176-contextmenu
gitea/salix/pipeline/head This commit has test failures Details

This commit is contained in:
Joan Sanchez 2020-06-08 08:19:18 +02:00
commit 181f2f0148
73 changed files with 3212 additions and 2066 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

@ -1 +1,4 @@
UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213'; 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,8 @@
ALTER TABLE `vn`.`route`
DROP FOREIGN KEY `fk_route_1`;
ALTER TABLE `vn`.`route`
ADD CONSTRAINT `fk_route_1`
FOREIGN KEY (`zoneFk`)
REFERENCES `vn`.`zone` (`id`)
ON DELETE SET NULL
ON UPDATE CASCADE;

View File

@ -0,0 +1,99 @@
DROP procedure IF EXISTS `vn`.`sale_calculateComponent`;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_calculateComponent`(vSale INT, vOption INT)
proc: BEGIN
/**
* Actualiza los componentes
*
* @param vSale Delivery date
* @param vOption indica en que componente pone el descuadre, NULL en casos habituales
*/
DECLARE vShipped DATE;
DECLARE vWarehouseFk SMALLINT;
DECLARE vAgencyModeFk INT;
DECLARE vAddressFk INT;
DECLARE vTicketFk BIGINT;
DECLARE vItemFk BIGINT;
DECLARE vLanded DATE;
DECLARE vIsEditable BOOLEAN;
DECLARE vZoneFk INTEGER;
SELECT t.refFk IS NULL AND (IFNULL(ts.alertLevel, 0) = 0 OR s.price = 0),
s.ticketFk,
s.itemFk ,
t.zoneFk,
t.warehouseFk,
t.shipped,
t.addressFk,
t.agencyModeFk,
t.landed
INTO vIsEditable,
vTicketFk,
vItemFk,
vZoneFk,
vWarehouseFk,
vShipped,
vAddressFk,
vAgencyModeFk,
vLanded
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
WHERE s.id = vSale;
IF vLanded IS NULL OR vZoneFk IS NULL THEN
CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk);
IF (SELECT COUNT(*) FROM tmp.zoneGetLanded LIMIT 1) = 0 THEN
CALL util.throw('There is no zone for these parameters');
END IF;
UPDATE ticket t
SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1)
WHERE t.id = vTicketFk AND t.landed IS NULL;
IF vZoneFk IS NULL THEN
SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1;
UPDATE ticket t
SET t.zoneFk = vZoneFk
WHERE t.id = vTicketFk AND t.zoneFk IS NULL;
END IF;
DROP TEMPORARY TABLE tmp.zoneGetLanded;
END IF;
-- rellena la tabla buyUltimate con la ultima compra
CALL buyUltimate (vWarehouseFk, vShipped);
DELETE FROM tmp.buyUltimate WHERE itemFk != vItemFk;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot
SELECT vWarehouseFk warehouseFk, NULL available, vItemFk itemFk, buyFk, vZoneFk zoneFk
FROM tmp.buyUltimate
WHERE itemFk = vItemFk;
CALL catalog_componentPrepare();
CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped, vWarehouseFk);
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk)) ENGINE = MEMORY
SELECT vSale saleFk,vWarehouseFk warehouseFk;
IF vOption IS NULL THEN
SET vOption = IF(vIsEditable, 1, 6);
END IF;
CALL ticketComponentUpdateSale(vOption);
CALL catalog_componentPurge();
DROP TEMPORARY TABLE tmp.buyUltimate;
DROP TEMPORARY TABLE tmp.sale;
END$$
DELIMITER ;

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -18,15 +18,13 @@ describe('zone zone_getEvents()', () => {
]); ]);
stmts.push(stmt); stmts.push(stmt);
let firstResultIndex = stmts.push(stmt) - 1;
let secondResultIndex = firstResultIndex + 1;
stmts.push('ROLLBACK'); stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';'); let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql); let result = await app.models.Ticket.rawStmt(sql);
let zonesEvents = result[secondResultIndex]; let zonesEvents = result[1];
expect(zonesEvents.length).toBeGreaterThan(0); expect(zonesEvents.length).toBeGreaterThan(0);
}); });

View File

@ -86,7 +86,7 @@ export default {
invoiceByAddressCheckbox: 'vn-client-fiscal-data vn-check[label="Invoice by address"]', invoiceByAddressCheckbox: 'vn-client-fiscal-data vn-check[label="Invoice by address"]',
verifiedDataCheckbox: 'vn-client-fiscal-data vn-check[label="Verified data"]', verifiedDataCheckbox: 'vn-client-fiscal-data vn-check[label="Verified data"]',
hasToInvoiceCheckbox: 'vn-client-fiscal-data vn-check[label="Has to invoice"]', hasToInvoiceCheckbox: 'vn-client-fiscal-data vn-check[label="Has to invoice"]',
invoiceByMailCheckbox: 'vn-client-fiscal-data vn-check[label="Invoice by mail"]', notifyByMailCheckbox: 'vn-client-fiscal-data vn-check[label="Notify by email"]',
viesCheckbox: 'vn-client-fiscal-data vn-check[label="Vies"]', viesCheckbox: 'vn-client-fiscal-data vn-check[label="Vies"]',
saveButton: 'button[type=submit]', saveButton: 'button[type=submit]',
acceptDuplicationButton: '.vn-confirm.shown button[response=accept]', acceptDuplicationButton: '.vn-confirm.shown button[response=accept]',
@ -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',
@ -579,6 +585,7 @@ export default {
claimState: 'vn-claim-basic-data vn-autocomplete[ng-model="$ctrl.claim.claimStateFk"]', claimState: 'vn-claim-basic-data vn-autocomplete[ng-model="$ctrl.claim.claimStateFk"]',
responsabilityInputRange: 'vn-range', responsabilityInputRange: 'vn-range',
observation: 'vn-textarea[ng-model="$ctrl.claim.observation"]', observation: 'vn-textarea[ng-model="$ctrl.claim.observation"]',
hasToPickUpCheckbox: 'vn-claim-basic-data vn-check[ng-model="$ctrl.claim.hasToPickUp"]',
saveButton: `button[type=submit]` saveButton: `button[type=submit]`
}, },
claimDetail: { claimDetail: {
@ -613,8 +620,7 @@ export default {
firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]', firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]', secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]', firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]', isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
hasToPickUpCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.hasToPickUp"]'
}, },
ordersIndex: { ordersIndex: {
searchResult: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr',

View File

@ -72,7 +72,7 @@ describe('Client Edit fiscalData path', () => {
await page.waitToClick(selectors.clientFiscalData.frozenCheckbox); await page.waitToClick(selectors.clientFiscalData.frozenCheckbox);
await page.waitToClick(selectors.clientFiscalData.hasToInvoiceCheckbox); await page.waitToClick(selectors.clientFiscalData.hasToInvoiceCheckbox);
await page.waitToClick(selectors.clientFiscalData.viesCheckbox); await page.waitToClick(selectors.clientFiscalData.viesCheckbox);
await page.waitToClick(selectors.clientFiscalData.invoiceByMailCheckbox); await page.waitToClick(selectors.clientFiscalData.notifyByMailCheckbox);
await page.waitToClick(selectors.clientFiscalData.invoiceByAddressCheckbox); await page.waitToClick(selectors.clientFiscalData.invoiceByAddressCheckbox);
await page.waitToClick(selectors.clientFiscalData.equalizationTaxCheckbox); await page.waitToClick(selectors.clientFiscalData.equalizationTaxCheckbox);
await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox); await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox);
@ -230,8 +230,8 @@ describe('Client Edit fiscalData path', () => {
expect(result).toBe('checked'); expect(result).toBe('checked');
}); });
it('should confirm Invoice by mail checkbox is unchecked', async() => { it('should confirm Notify by email checkbox is unchecked', async() => {
const result = await page.checkboxState(selectors.clientFiscalData.invoiceByMailCheckbox); const result = await page.checkboxState(selectors.clientFiscalData.notifyByMailCheckbox);
expect(result).toBe('unchecked'); expect(result).toBe('unchecked');
}); });

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

@ -34,6 +34,15 @@ describe('Claim edit basic data path', () => {
await page.waitForState('claim.card.detail'); await page.waitForState('claim.card.detail');
}); });
it('should check the "Pick up" checkbox', async() => {
await page.reloadSection('claim.card.basicData');
await page.waitToClick(selectors.claimBasicData.hasToPickUpCheckbox);
await page.waitToClick(selectors.claimBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should confirm the claim state was edited', async() => { it('should confirm the claim state was edited', async() => {
await page.reloadSection('claim.card.basicData'); await page.reloadSection('claim.card.basicData');
await page.wait(selectors.claimBasicData.claimState); await page.wait(selectors.claimBasicData.claimState);
@ -42,6 +51,12 @@ describe('Claim edit basic data path', () => {
expect(result).toEqual('Gestionado'); expect(result).toEqual('Gestionado');
}); });
it('should confirm the "is paid with mana" and "Pick up" checkbox are checked', async() => {
const hasToPickUpCheckbox = await page.checkboxState(selectors.claimBasicData.hasToPickUpCheckbox);
expect(hasToPickUpCheckbox).toBe('checked');
});
it('should confirm the claim observation was edited', async() => { it('should confirm the claim observation was edited', async() => {
const result = await page const result = await page
.waitToGetProperty(selectors.claimBasicData.observation, 'value'); .waitToGetProperty(selectors.claimBasicData.observation, 'value');

View File

@ -72,19 +72,10 @@ describe('Claim action path', () => {
expect(message.type).toBe('success'); expect(message.type).toBe('success');
}); });
it('should check the "Pick up" checkbox', async() => { it('should confirm the "is paid with mana" is checked', async() => {
await page.waitToClick(selectors.claimAction.hasToPickUpCheckbox);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should confirm the "is paid with mana" and "Pick up" checkbox are checked', async() => {
await page.reloadSection('claim.card.action'); await page.reloadSection('claim.card.action');
const isPaidWithManaCheckbox = await page.checkboxState(selectors.claimAction.isPaidWithManaCheckbox); const isPaidWithManaCheckbox = await page.checkboxState(selectors.claimAction.isPaidWithManaCheckbox);
const hasToPickUpCheckbox = await page.checkboxState(selectors.claimAction.hasToPickUpCheckbox);
expect(isPaidWithManaCheckbox).toBe('checked'); expect(isPaidWithManaCheckbox).toBe('checked');
expect(hasToPickUpCheckbox).toBe('checked');
}); });
}); });

View File

@ -13,7 +13,7 @@
</label> </label>
</div> </div>
<div class="icons pre"> <div class="icons pre">
<vn-icon <vn-icon ng-show="::$ctrl.clearDisabled != true"
icon="clear" icon="clear"
translate-attr="{title: 'Clear'}" translate-attr="{title: 'Clear'}"
ng-click="$ctrl.onClear($event)"> ng-click="$ctrl.onClear($event)">

View File

@ -203,6 +203,7 @@ ngModule.vnComponent('vnField', {
type: '@?', type: '@?',
autocomplete: '@?', autocomplete: '@?',
placeholder: '@?', placeholder: '@?',
clearDisabled: '<?',
value: '=?', value: '=?',
info: '@?', info: '@?',
required: '<?', required: '<?',

View File

@ -0,0 +1,24 @@
describe('Component vnField', () => {
let $element;
let controller;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($compile, $rootScope) => {
$element = $compile(`<vn-textfield></vn-textfield>`)($rootScope);
controller = $element.controller('vnTextfield');
}));
afterEach(() => {
$element.remove();
});
// Remove this block
describe('clearDisabled binding', () => {
it(`should enable the show property`, () => {
controller.clearDisabled = true;
expect(controller.clearDisabled).toEqual(true);
});
});
});

View File

@ -78,7 +78,7 @@ export default class Searchbar extends Component {
} }
fetchStateFilter(autoLoad) { fetchStateFilter(autoLoad) {
let filter = null; let filter = this.filter ? this.filter : null;
if (this.$state.is(this.searchState)) { if (this.$state.is(this.searchState)) {
if (this.$params.q) { if (this.$params.q) {

View File

@ -1,5 +1,6 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Field from '../field'; import Field from '../field';
import './style.scss';
export default class Textarea extends Field { export default class Textarea extends Field {
constructor($element, $scope, $compile) { constructor($element, $scope, $compile) {

View File

@ -0,0 +1,8 @@
.vn-textarea {
& > .container {
& > .icons {
display: flex;
align-items: flex-start;
}
}
}

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,18 @@ module.exports = function(Self) {
let action = setActionType(ctx); let action = setActionType(ctx);
removeUnloggableProperties(definition, oldInstance); removeUnloggable(definition, oldInstance);
removeUnloggableProperties(definition, newInstance); removeUnloggable(definition, newInstance);
// Prevent log with no new changes
const hasNewChanges = Object.keys(newInstance).length;
if (!hasNewChanges) return;
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 +275,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 +285,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

@ -88,32 +88,11 @@ module.exports = Self => {
}, options); }, options);
} }
let claim = await Self.findById(claimFk, { let claim = await Self.findById(claimFk, null, options);
include: {
relation: 'client',
scope: {
include: {
relation: 'salesPerson'
}
}
}
}, options);
claim = await claim.updateAttributes({ claim = await claim.updateAttributes({
claimStateFk: resolvedState claimStateFk: resolvedState
}, options); }, options);
// Get sales person from claim client
const salesPerson = claim.client().salesPerson();
if (salesPerson && claim.hasToPickUp) {
const origin = ctx.req.headers.origin;
const message = $t('Claim will be picked', {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
await tx.commit(); await tx.commit();
return claim; return claim;

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('regularizeClaim()', () => { // #2304
xdescribe('regularizeClaim()', () => {
const claimFk = 1; const claimFk = 1;
const pendentState = 1; const pendentState = 1;
const resolvedState = 3; const resolvedState = 3;
@ -103,11 +104,9 @@ describe('regularizeClaim()', () => {
claimEnd.updateAttributes({claimDestinationFk: okDestination}); claimEnd.updateAttributes({claimDestinationFk: okDestination});
}); });
const claim = await app.models.Claim.findById(claimFk);
await claim.updateAttribute('hasToPickUp', true);
await app.models.Claim.regularizeClaim(ctx, claimFk); await app.models.Claim.regularizeClaim(ctx, claimFk);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(5); expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
}); });
}); });

View File

@ -54,6 +54,7 @@ describe('Update Claim', () => {
let data = { let data = {
observation: 'valid observation', observation: 'valid observation',
claimStateFk: correctState, claimStateFk: correctState,
hasToPickUp: false
}; };
let ctx = { let ctx = {
req: { req: {
@ -70,19 +71,25 @@ describe('Update Claim', () => {
}); });
it('should change some sensible fields as salesAssistant', async() => { it('should change some sensible fields as salesAssistant', async() => {
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
const salesAssistantId = 21; const salesAssistantId = 21;
let data = { let data = {
claimStateFk: 3, claimStateFk: 3,
workerFk: 5, workerFk: 5,
observation: 'another valid observation' observation: 'another valid observation',
hasToPickUp: true
}; };
let ctx = { const ctx = {
req: { req: {
accessToken: { accessToken: {userId: salesAssistantId},
userId: salesAssistantId headers: {origin: 'http://localhost'}
}
} }
}; };
ctx.req.__ = (value, params) => {
return params.nickname;
};
await app.models.Claim.updateClaim(ctx, newInstance.id, data); await app.models.Claim.updateClaim(ctx, newInstance.id, data);
let claimUpdated = await app.models.Claim.findById(newInstance.id); let claimUpdated = await app.models.Claim.findById(newInstance.id);
@ -90,5 +97,6 @@ describe('Update Claim', () => {
expect(claimUpdated.observation).toEqual(data.observation); expect(claimUpdated.observation).toEqual(data.observation);
expect(claimUpdated.claimStateFk).toEqual(data.claimStateFk); expect(claimUpdated.claimStateFk).toEqual(data.claimStateFk);
expect(claimUpdated.workerFk).toEqual(data.workerFk); expect(claimUpdated.workerFk).toEqual(data.workerFk);
expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
}); });
}); });

View File

@ -27,16 +27,44 @@ module.exports = Self => {
}); });
Self.updateClaim = async(ctx, id, data) => { Self.updateClaim = async(ctx, id, data) => {
let models = Self.app.models; const models = Self.app.models;
let claim = await models.Claim.findById(id); const userId = ctx.req.accessToken.userId;
let canUpdate = await canChangeState(ctx, claim.claimStateFk); const $t = ctx.req.__; // $translate
let hasRights = await canChangeState(ctx, data.claimStateFk); const claim = await models.Claim.findById(id, {
include: {
relation: 'client',
scope: {
include: {
relation: 'salesPerson'
}
}
}
});
if (!canUpdate || !hasRights) const canUpdate = await canChangeState(ctx, claim.claimStateFk);
const hasRights = await canChangeState(ctx, data.claimStateFk);
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant');
const changedHasToPickUp = claim.hasToPickUp != data.hasToPickUp;
if (!canUpdate || !hasRights || changedHasToPickUp && !isSalesAssistant)
throw new UserError(`You don't have enough privileges to change that field`); throw new UserError(`You don't have enough privileges to change that field`);
return await claim.updateAttributes(data); const updatedClaim = await claim.updateAttributes(data);
// Get sales person from claim client
const salesPerson = claim.client().salesPerson();
if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp) {
const origin = ctx.req.headers.origin;
const message = $t('Claim will be picked', {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
return updatedClaim;
}; };
async function canChangeState(ctx, id) { async function canChangeState(ctx, id) {

View File

@ -17,10 +17,6 @@ module.exports = Self => {
arg: 'isChargedToMana', arg: 'isChargedToMana',
type: 'boolean', type: 'boolean',
required: false required: false
}, {
arg: 'hasToPickUp',
type: 'boolean',
required: false
}], }],
returns: { returns: {
type: 'object', type: 'object',

View File

@ -43,11 +43,6 @@
on-change="$ctrl.save({responsibility: value})"> on-change="$ctrl.save({responsibility: value})">
</vn-range> </vn-range>
</vn-tool-bar> </vn-tool-bar>
<vn-check vn-one class="vn-mr-md"
label="Pick up"
ng-model="$ctrl.claim.hasToPickUp"
on-change="$ctrl.save({hasToPickUp: value})">
</vn-check>
<vn-check vn-one <vn-check vn-one
label="Is paid with mana" label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana" ng-model="$ctrl.claim.isChargedToMana"

View File

@ -9,5 +9,4 @@ Regularize: Regularizar
Do you want to insert greuges?: Desea insertar greuges? Do you want to insert greuges?: Desea insertar greuges?
Insert greuges on client card: Insertar greuges en la ficha del cliente Insert greuges on client card: Insertar greuges en la ficha del cliente
Greuge inserted: Greuge insertado Greuge inserted: Greuge insertado
ClaimGreugeDescription: Reclamación id {{claimId}} ClaimGreugeDescription: Reclamación id {{claimId}}
Pick up: Recoger

View File

@ -53,6 +53,13 @@
rule> rule>
</vn-textarea> </vn-textarea>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-check vn-one class="vn-mr-md"
label="Pick up"
ng-model="$ctrl.claim.hasToPickUp"
vn-acl="salesAssistant">
</vn-check>
</vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit label="Save"></vn-submit> <vn-submit label="Save"></vn-submit>

View File

@ -3,4 +3,5 @@ Claim state: Estado de la reclamación
Is paid with mana: Cargado al maná Is paid with mana: Cargado al maná
Responsability: Responsabilidad Responsability: Responsabilidad
Company: Empresa Company: Empresa
Sales/Client: Comercial/Cliente Sales/Client: Comercial/Cliente
Pick up: Recoger

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

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('client sendSms()', () => { // #2294 - TLS version error
xdescribe('client sendSms()', () => {
let createdLog; let createdLog;
afterAll(async done => { afterAll(async done => {

View File

@ -1,7 +1,8 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const soap = require('soap'); const soap = require('soap');
describe('sms send()', () => { // #2294 - TLS version error
xdescribe('sms send()', () => {
it('should return the expected message and status code', async() => { it('should return the expected message and status code', async() => {
const code = 200; const code = 200;
const smsConfig = await app.models.SmsConfig.findOne(); const smsConfig = await app.models.SmsConfig.findOne();

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

@ -119,7 +119,7 @@
<vn-horizontal> <vn-horizontal>
<vn-check <vn-check
vn-one vn-one
label="Invoice by mail" label="Notify by email"
ng-model="$ctrl.client.isToBeMailed"> ng-model="$ctrl.client.isToBeMailed">
</vn-check> </vn-check>
<vn-check <vn-check

View File

@ -4,7 +4,7 @@ Client: Cliente
client: cliente client: cliente
Comercial Name: Comercial Comercial Name: Comercial
Has to invoice: Factura Has to invoice: Factura
Invoice by mail: Factura via e-mail Notify by email: Notificar vía e-mail
Country: País Country: País
Street: Domicilio fiscal Street: Domicilio fiscal
City: Municipio City: Municipio

View File

@ -81,7 +81,7 @@
disabled="true"> disabled="true">
</vn-check> </vn-check>
<vn-check <vn-check
label="Invoice by mail" label="Notify by email"
ng-model="$ctrl.summary.isToBeMailed" ng-model="$ctrl.summary.isToBeMailed"
disabled="true"> disabled="true">
</vn-check> </vn-check>

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>
@ -83,4 +89,10 @@
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/> <input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<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

@ -60,6 +60,11 @@ 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', {

View File

@ -48,9 +48,8 @@
</span> </span>
</vn-td> </vn-td>
<vn-td number> <vn-td number>
<span class="chip" <span ng-class="::{link: sale.isTicket}"
ng-class="::{link: sale.isTicket}" ng-click="$ctrl.showTicketDescriptor($event, sale)"
vn-click-stop="descriptor.show($event, sale.origin)"
name="origin"> name="origin">
{{::sale.origin | dashIfEmpty}} {{::sale.origin | dashIfEmpty}}
</span> </span>
@ -83,7 +82,7 @@
</vn-card> </vn-card>
</vn-vertical> </vn-vertical>
<vn-ticket-descriptor-popover <vn-ticket-descriptor-popover
vn-id="descriptor"> vn-id="ticket-descriptor">
</vn-ticket-descriptor-popover> </vn-ticket-descriptor-popover>
<vn-client-descriptor-popover <vn-client-descriptor-popover
vn-id="clientDescriptor"> vn-id="clientDescriptor">

View File

@ -57,6 +57,12 @@ class Controller extends Section {
this.$location.hash(hash); this.$location.hash(hash);
this.$anchorScroll(); this.$anchorScroll();
} }
showTicketDescriptor(event, sale) {
if (!sale.isTicket) return;
this.$.ticketDescriptor.show(event.target, sale.origin);
}
} }
Controller.$inject = ['$element', '$scope', '$anchorScroll', '$location']; Controller.$inject = ['$element', '$scope', '$anchorScroll', '$location'];

View File

@ -51,8 +51,11 @@
</vn-date-picker> </vn-date-picker>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-one> <vn-check vn-one
</vn-one> triple-state="true"
label="For me"
ng-model="filter.mine">
</vn-check>
<vn-check <vn-check
vn-one vn-one
triple-state="true" triple-state="true"

View File

@ -1,3 +1,4 @@
Ink: Tinta Ink: Tinta
Origin: Origen Origin: Origen
Producer: Productor Producer: Productor
For me: Para mi

View File

@ -1,28 +1,30 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="TicketRequests/filter" url="TicketRequests/filter"
user-params="::$ctrl.filterParams"
limit="20" limit="20"
data="requests" data="requests"
order="shipped DESC, isOk ASC" order="shippedDate ASC, isOk ASC"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar"> <vn-portal slot="topbar">
<vn-searchbar <vn-searchbar
panel="vn-request-search-panel" panel="vn-request-search-panel"
suggested-filter="$ctrl.filter.where" suggested-filter="$ctrl.filterParams"
info="Search request by id or alias" info="Search request by id or alias"
filter="$ctrl.filterParams"
model="model" model="model"
auto-state="false"> auto-state="false">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-data-viewer model="model"> <vn-data-viewer model="model">
<vn-card> <vn-card>
<vn-table model="model"> <vn-table model="model" auto-load="false">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th field="ticketFk" number>Ticket ID</vn-th> <vn-th field="ticketFk" number>Ticket ID</vn-th>
<vn-th field="shipped">Shipped</vn-th> <vn-th field="shipped" expand>Shipped</vn-th>
<vn-th field="description">Description</vn-th> <vn-th field="description" expand>Description</vn-th>
<vn-th field="quantity" number editable>Requested</vn-th> <vn-th field="quantity" number editable>Requested</vn-th>
<vn-th field="price" number>Price</vn-th> <vn-th field="price" number>Price</vn-th>
<vn-th field="atenderNickname">Atender</vn-th> <vn-th field="atenderNickname">Atender</vn-th>
@ -40,7 +42,7 @@
{{request.ticketFk}} {{request.ticketFk}}
</span> </span>
</vn-td> </vn-td>
<vn-td> <vn-td expand>
<span title="{{::request.shipped | date: 'dd/MM/yyyy'}}" <span title="{{::request.shipped | date: 'dd/MM/yyyy'}}"
class="chip {{$ctrl.compareDate(request.shipped)}}"> class="chip {{$ctrl.compareDate(request.shipped)}}">
{{::request.shipped | date: 'dd/MM/yyyy'}} {{::request.shipped | date: 'dd/MM/yyyy'}}
@ -53,7 +55,7 @@
<span <span
class="link" class="link"
ng-click="workerDescriptor.show($event, request.attenderFk)"> ng-click="workerDescriptor.show($event, request.attenderFk)">
{{::request.atenderNickname}} {{::request.attenderName}}
</span> </span>
</vn-td> </vn-td>
<vn-td-editable disabled="request.isOk != null" number> <vn-td-editable disabled="request.isOk != null" number>

View File

@ -8,19 +8,16 @@ export default class Controller extends Section {
if (!this.$state.q) { if (!this.$state.q) {
const today = new Date(); const today = new Date();
today.setHours(23, 59, 59, 59); today.setHours(0, 0, 0, 0);
const lastWeek = new Date(); const nextWeek = new Date();
lastWeek.setHours(0, 0, 0, 0); nextWeek.setHours(23, 59, 59, 59);
lastWeek.setDate(lastWeek.getDate() - 7); nextWeek.setDate(nextWeek.getDate() + 7);
this.filter = { this.filterParams = {
where: { mine: true,
isOk: false, from: today,
mine: true, to: nextWeek
from: lastWeek,
to: today
}
}; };
} }
} }

View File

@ -31,6 +31,7 @@ module.exports = Self => {
}); });
Self.confirm = async ctx => { Self.confirm = async ctx => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
@ -87,6 +88,23 @@ module.exports = Self => {
}); });
await models.Chat.sendCheckingPresence(ctx, requesterId, message); await models.Chat.sendCheckingPresence(ctx, requesterId, message);
// loguejar
let logRecord = {
originFk: sale.ticketFk,
userFk: userId,
action: 'update',
changedModel: 'ticketRequest',
newInstance: {
destinationFk: sale.ticketFk,
quantity: sale.quantity,
concept: sale.concept,
itemId: sale.itemFk,
ticketId: sale.ticketFk,
}
};
await Self.app.models.TicketLog.create(logRecord);
await tx.commit(); await tx.commit();
return sale; return sale;

View File

@ -115,12 +115,13 @@ module.exports = Self => {
s.itemFk, s.itemFk,
i.name AS itemDescription, i.name AS itemDescription,
t.shipped, t.shipped,
DATE(t.shipped) AS shippedDate,
t.nickname, t.nickname,
t.warehouseFk, t.warehouseFk,
t.clientFk, t.clientFk,
w.name AS warehouse, w.name AS warehouse,
u.nickname AS salesPersonNickname, u.nickname AS salesPersonNickname,
ua.nickname AS atenderNickname, ua.name AS attenderName,
c.salesPersonFk c.salesPersonFk
FROM ticketRequest tr FROM ticketRequest tr
LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk

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

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
let UserError = require('vn-loopback/util/user-error'); let UserError = require('vn-loopback/util/user-error');
describe('ticket new()', () => { describe('ticket new()', () => {
let ticket; let ticket;
let today = new Date(); let today = new Date();
@ -69,7 +68,7 @@ describe('ticket new()', () => {
clientId: 104, clientId: 104,
shipped: today, shipped: today,
landed: today, landed: today,
warehouseId: 1, warehouseId: 2,
companyId: 442, companyId: 442,
addressId: 4, addressId: 4,
agencyModeId: 1 agencyModeId: 1

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('ticket sendSms()', () => { // #2294 - TLS version error
xdescribe('ticket sendSms()', () => {
let logId; let logId;
afterAll(async done => { afterAll(async done => {

View File

@ -1,9 +1,11 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const models = app.models; const models = app.models;
describe('ticket deleted()', () => { // 2301 Failing tests
xdescribe('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 +29,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 +133,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

@ -42,6 +42,9 @@
}, },
"priority": { "priority": {
"type": "Number" "type": "Number"
},
"zoneFk": {
"type": "Number"
} }
}, },
"relations": { "relations": {

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

@ -29,4 +29,8 @@ Product not available: "Verdnatura le comunica:\rPedido {{ticketFk}} día {{crea
Continue anyway?: ¿Continuar de todas formas? 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

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

@ -76,7 +76,7 @@
</vn-check> </vn-check>
</vn-td> </vn-td>
<vn-td shrink>{{entry.id}} </vn-td> <vn-td shrink>{{entry.id}} </vn-td>
<vn-td shrink>{{entry.supplierName}}</vn-td> <vn-td expand>{{entry.supplierName}}</vn-td>
<vn-td shrink>{{entry.ref}}</vn-td> <vn-td shrink>{{entry.ref}}</vn-td>
<vn-td shrink>{{entry.hb}}</vn-td> <vn-td shrink>{{entry.hb}}</vn-td>
<vn-td shrink>{{entry.freightValue | currency: 'EUR': 2}}</vn-td> <vn-td shrink>{{entry.freightValue | currency: 'EUR': 2}}</vn-td>

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

@ -0,0 +1,44 @@
module.exports = Self => {
Self.remoteMethod('deleteZone', {
description: 'Delete a zone',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'Number',
description: 'The zone id',
http: {source: 'path'}
},
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/deleteZone`,
verb: 'POST'
}
});
Self.deleteZone = async id => {
const models = Self.app.models;
const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {where: {zoneFk: id}};
const promises = [];
const ticketList = await models.Ticket.find(filter, options);
ticketList.forEach(ticket => {
promises.push(ticket.updateAttributes({zoneFk: null}, options));
});
await Promise.all(promises);
await models.Zone.destroyById(id, options);
await tx.commit();
return id;
} catch (err) {
await tx.rollback();
throw err;
}
};
};

View File

@ -0,0 +1,39 @@
const app = require('vn-loopback/server/server');
// 2302
describe('zone deletezone()', () => {
let zoneId = 9;
let originalZoneTickets;
let originalZone;
let originalZoneIncluded;
beforeAll(async done => {
originalZone = await app.models.Zone.findById(zoneId);
originalZoneTickets = await app.models.Ticket.find({where: {zoneFk: zoneId}});
originalZoneIncluded = await app.models.ZoneIncluded.find({where: {zoneFk: zoneId}});
done();
});
afterAll(async done => {
await originalZone.save();
originalZoneTickets.forEach(async ticket => {
await ticket.updateAttributes({zoneFk: zoneId});
});
originalZoneIncluded.forEach(async zoneIncluded => {
await zoneIncluded.save();
});
done();
});
it('should delete a zone and update their tickets', async() => {
await app.models.Zone.deleteZone(zoneId);
let updatedZone = await app.models.Zone.findById(zoneId);
let zoneUpdatedTicket = await app.models.Ticket.findById(originalZoneTickets[0].id);
expect(updatedZone).toBeNull();
expect(zoneUpdatedTicket.zoneFk).not.toBe(zoneId);
});
});

View File

@ -4,6 +4,7 @@ module.exports = Self => {
require('../methods/zone/getEvents')(Self); require('../methods/zone/getEvents')(Self);
require('../methods/zone/toggleIsIncluded')(Self); require('../methods/zone/toggleIsIncluded')(Self);
require('../methods/zone/getUpcomingDeliveries')(Self); require('../methods/zone/getUpcomingDeliveries')(Self);
require('../methods/zone/deleteZone')(Self);
Self.validatesPresenceOf('agencyModeFk', { Self.validatesPresenceOf('agencyModeFk', {
message: `Agency cannot be blank` message: `Agency cannot be blank`

View File

@ -3,10 +3,16 @@
description="$ctrl.zone.name"> description="$ctrl.zone.name">
<slot-menu> <slot-menu>
<vn-item class="vn-item" <vn-item class="vn-item"
ng-click="deleteZone.show()" ng-click="$ctrl.onDelete()"
translate> translate>
Delete Delete
</vn-item> </vn-item>
<vn-item
ng-click="clone.show()"
name="cloneZone"
translate>
Clone
</vn-item>
</slot-menu> </slot-menu>
<slot-body> <slot-body>
<div class="attributes"> <div class="attributes">
@ -39,7 +45,12 @@
</vn-descriptor-content> </vn-descriptor-content>
<vn-confirm <vn-confirm
vn-id="deleteZone" vn-id="deleteZone"
on-accept="$ctrl.onDeleteAccept()" on-accept="$ctrl.deleteZone()"
question="Are you sure you want to delete this zone?"
message="This zone will be removed"> message="This zone will be removed">
</vn-confirm> </vn-confirm>
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept()"
question="Do you want to clone this zone?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -10,9 +10,33 @@ class Controller extends Descriptor {
this.entity = value; this.entity = value;
} }
onDeleteAccept() { onDelete() {
return this.$http.delete(`Zones/${this.id}`) const $t = this.$translate.instant;
.then(() => this.$state.go('zone.index')); const today = new Date();
today.setHours(0, 0, 0, 0);
const filter = {where: {zoneFk: this.id, shipped: {gte: today}}};
this.$http.get(`Tickets`, {filter}).then(res => {
const ticketsAmount = res.data.length;
if (ticketsAmount) {
const params = {ticketsAmount};
console.log('ticketsAmount', res.data);
const question = $t('This zone contains tickets', params, null, null, 'sanitizeParameters');
this.$.deleteZone.question = question;
this.$.deleteZone.show();
} else
this.deleteZone();
});
}
deleteZone() {
return this.$http.post(`Zones/${this.id}/deleteZone`).then(() => {
this.$state.go('zone.index');
this.vnApp.showSuccess(this.$t('Zone deleted'));
});
}
onCloneAccept() {
return this.$http.post(`Zones/${this.id}/clone`).
then(res => this.$state.go('zone.card.basicData', {id: res.data.id}));
} }
} }

View File

@ -0,0 +1,4 @@
This zone contains tickets: Esta zona contiene {{ticketsAmount}} tickets. ¿Seguro que quieres eliminar esta zona?
Do you want to clone this zone?: ¿Quieres clonar esta zona?
All it's properties will be copied: Todas sus propiedades serán copiadas
Zone deleted: Zona eliminada

37
package-lock.json generated
View File

@ -11410,9 +11410,9 @@
} }
}, },
"loopback-connector-mysql": { "loopback-connector-mysql": {
"version": "5.4.2", "version": "5.4.3",
"resolved": "https://registry.npmjs.org/loopback-connector-mysql/-/loopback-connector-mysql-5.4.2.tgz", "resolved": "https://registry.npmjs.org/loopback-connector-mysql/-/loopback-connector-mysql-5.4.3.tgz",
"integrity": "sha512-f5iIIcJdfUuBUkScGcK7m4dLZnpjFjl1iFG5OHTk8pFwDq7+Xap/0H99ulueRp2ljfqbULTUvt3Rg1y/W5smtw==", "integrity": "sha512-HQ0Nnscyhhk+4zsDhXyR8dYdkhxIBN8r8N1futX5xznWjCZ4dpkG5svoPOMUjoNaDEtZuLr1I2E4CKb6f5u9Mw==",
"requires": { "requires": {
"async": "^2.6.1", "async": "^2.6.1",
"debug": "^3.1.0", "debug": "^3.1.0",
@ -12231,14 +12231,35 @@
} }
}, },
"mysql": { "mysql": {
"version": "2.17.1", "version": "2.18.1",
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.17.1.tgz", "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
"integrity": "sha512-7vMqHQ673SAk5C8fOzTG2LpPcf3bNt0oL3sFpxPEEFp1mdlDcrLK0On7z8ZYKaaHrHwNcQ/MTUz7/oobZ2OyyA==", "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
"requires": { "requires": {
"bignumber.js": "7.2.1", "bignumber.js": "9.0.0",
"readable-stream": "2.3.6", "readable-stream": "2.3.7",
"safe-buffer": "5.1.2", "safe-buffer": "5.1.2",
"sqlstring": "2.3.1" "sqlstring": "2.3.1"
},
"dependencies": {
"bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
}
} }
}, },
"mysql2": { "mysql2": {

View File

@ -17,7 +17,7 @@
"loopback-boot": "^2.27.1", "loopback-boot": "^2.27.1",
"loopback-component-explorer": "^6.5.0", "loopback-component-explorer": "^6.5.0",
"loopback-component-storage": "^3.6.1", "loopback-component-storage": "^3.6.1",
"loopback-connector-mysql": "^5.4.2", "loopback-connector-mysql": "^5.4.3",
"loopback-connector-remote": "^3.4.1", "loopback-connector-remote": "^3.4.1",
"loopback-context": "^3.4.0", "loopback-context": "^3.4.0",
"md5": "^2.2.1", "md5": "^2.2.1",

View File

@ -72,9 +72,10 @@ class Email extends Component {
await getAttachments(this.path, this.attachments); await getAttachments(this.path, this.attachments);
const localeSubject = await this.getSubject(); const localeSubject = await this.getSubject();
const replyTo = this.args.replyTo || this.args.auth.email;
const options = { const options = {
to: this.args.recipient, to: this.args.recipient,
replyTo: this.args.auth.email, replyTo: replyTo,
subject: localeSubject, subject: localeSubject,
html: rendered, html: rendered,
attachments: attachments attachments: attachments

View File

@ -4,34 +4,64 @@ const smtp = require('../core/smtp');
const config = require('../core/config'); const config = require('../core/config');
module.exports = app => { module.exports = app => {
app.get('/api/closure', async function(request, response) { app.get('/api/closure/by-ticket', async function(req, res) {
});
app.get('/api/closure/all', async function(req, res) {
res.status(200).json({
message: 'Task executed successfully'
});
const failedtickets = []; const failedtickets = [];
const tickets = await db.rawSql(` const tickets = await db.rawSql(`
SELECT SELECT
t.id, t.id,
t.clientFk, t.clientFk,
c.email recipient c.email recipient,
c.isToBeMailed,
c.salesPersonFk,
eu.email salesPersonEmail
FROM expedition e FROM expedition e
JOIN ticket t ON t.id = e.ticketFk JOIN ticket t ON t.id = e.ticketFk
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
JOIN warehouse w ON w.id = t.warehouseFk AND hasComission JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
LEFT JOIN ticketState ts ON ts.ticketFk = t.id JOIN ticketState ts ON ts.ticketFk = t.id
WHERE ts.code = 'PACKED' JOIN alertLevel al ON al.alertLevel = ts.alertLevel
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED'
AND DATE(t.shipped) BETWEEN DATE_ADD(CURDATE(), INTERVAL -2 DAY) AND CURDATE() AND DATE(t.shipped) BETWEEN DATE_ADD(CURDATE(), INTERVAL -2 DAY) AND CURDATE()
AND t.refFk IS NULL AND t.refFk IS NULL
GROUP BY e.ticketFk`); GROUP BY e.ticketFk`);
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 = { if (!ticket.salesPersonFk || !ticket.isToBeMailed) continue;
if (!ticket.recipient) {
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk}</strong> porque no tiene un email especificado.<br/><br/>
Para dejar de recibir esta notificación, asígnale un email o desactiva la notificación por email para este cliente.`;
smtp.send({
to: ticket.salesPersonEmail,
subject: 'No se ha podido enviar el albarán',
html: body
});
continue;
}
const reqArgs = req.args;
const args = Object.assign({
ticketId: ticket.id, ticketId: ticket.id,
recipientId: ticket.clientFk, recipientId: ticket.clientFk,
recipient: ticket.recipient recipient: ticket.recipient,
}; replyTo: ticket.salesPersonEmail
}, reqArgs);
const email = new Email('delivery-note-link', args); const email = new Email('delivery-note-link', args);
await email.send(); await email.send();
} catch (error) { } catch (error) {
@ -45,7 +75,7 @@ module.exports = app => {
// Send email with failed tickets // Send email with failed tickets
if (failedtickets.length > 0) { if (failedtickets.length > 0) {
let body = 'This following tickets has failed:<br/><br/>'; let body = 'This following tickets have failed:<br/><br/>';
for (ticket of failedtickets) { for (ticket of failedtickets) {
body += `Ticket: <strong>${ticket.id}</strong> body += `Ticket: <strong>${ticket.id}</strong>
@ -54,13 +84,9 @@ module.exports = app => {
smtp.send({ smtp.send({
to: config.app.reportEmail, to: config.app.reportEmail,
subject: '[API] Nightly ticket closure has failed', subject: '[API] Nightly ticket closure report',
html: body html: body
}); });
} }
response.status(200).json({
message: 'Closure executed successfully'
});
}); });
}; };

View File

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

View File

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