Merge branch 'dev' into 2785-getBalance-tests
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Javi Gallego 2021-03-29 15:42:22 +02:00
commit 3cb0859f85
49 changed files with 573 additions and 400 deletions

View File

@ -1 +0,0 @@
Delete me

View File

@ -0,0 +1 @@
Delete this

View File

@ -564,13 +564,13 @@ INSERT INTO `vn`.`zoneConfig` (`scope`) VALUES ('1');
INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agencyModeFk`, `description`, `m3`, `cost`, `started`, `finished`, `zoneFk`) INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agencyModeFk`, `description`, `m3`, `cost`, `started`, `finished`, `zoneFk`)
VALUES VALUES
(1, '1899-12-30 12:15:00', 56, CURDATE(), 1, 1, 'first route', 1.8, 10, CURDATE(), CURDATE(), 1), (1, '1899-12-30 12:15:00', 56, CURDATE(), 1, 1, 'first route', 1.8, 10, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1),
(2, '1899-12-30 13:20:00', 56, CURDATE(), 1, 2, 'second route', 0.2, 20, CURDATE(), CURDATE(), 9), (2, '1899-12-30 13:20:00', 56, CURDATE(), 1, 2, 'second route', 0.2, 20, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 9),
(3, '1899-12-30 14:30:00', 56, CURDATE(), 2, 3, 'third route', 0.5, 30, CURDATE(), CURDATE(), 10), (3, '1899-12-30 14:30:00', 56, CURDATE(), 2, 3, 'third route', 0.5, 30, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 10),
(4, '1899-12-30 15:45:00', 56, CURDATE(), 3, 4, 'fourth route', 0, 40, CURDATE(), CURDATE(), 12), (4, '1899-12-30 15:45:00', 56, CURDATE(), 3, 4, 'fourth route', 0, 40, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 12),
(5, '1899-12-30 16:00:00', 56, CURDATE(), 4, 5, 'fifth route', 0.1, 50, CURDATE(), CURDATE(), 13), (5, '1899-12-30 16:00:00', 56, CURDATE(), 4, 5, 'fifth route', 0.1, 50, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 13),
(6, NULL, 57, CURDATE(), 5, 7, 'sixth route', 1.7, 60, CURDATE(), CURDATE(), 3), (6, NULL, 57, CURDATE(), 5, 7, 'sixth route', 1.7, 60, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 3),
(7, NULL, 57, CURDATE(), 6, 8, 'seventh route', 0, 70, CURDATE(), CURDATE(), 5); (7, NULL, 57, CURDATE(), 6, 8, 'seventh route', 0, 70, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 5);
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`) INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`)
VALUES VALUES
@ -2257,3 +2257,10 @@ INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
SELECT `id` FROM `vn`.`ticket`; SELECT `id` FROM `vn`.`ticket`;
CALL `vn`.`ticket_doRecalc`(); CALL `vn`.`ticket_doRecalc`();
INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`)
VALUES
(1, 1, 1),
(2, 1, 2),
(3, 6, 5),
(4, 7, 1);

View File

@ -790,10 +790,11 @@ export default {
saveButton: 'vn-route-basic-data button[type=submit]' saveButton: 'vn-route-basic-data button[type=submit]'
}, },
routeTickets: { routeTickets: {
firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-textfield[ng-model="ticket.priority"]', firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-input-number[ng-model="ticket.priority"]',
firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check', firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check',
buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]', buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]',
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]', firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',
anyTicket: 'vn-route-tickets vn-tbody > vn-tr',
confirmButton: '.vn-confirm.shown button[response="accept"]' confirmButton: '.vn-confirm.shown button[response="accept"]'
}, },
workerSummary: { workerSummary: {

View File

@ -376,7 +376,8 @@ describe('Ticket Edit sale path', () => {
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
it('should update all sales discount', async() => { // tickets no longer update their totals instantly, a task performed ever 5-10 mins does it. disabled this test until it changes.
xit('should update all sales discount', async() => {
await page.closePopup(); await page.closePopup();
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuUpdateDiscount); await page.waitToClick(selectors.ticketSales.moreMenuUpdateDiscount);

View File

@ -1,8 +1,7 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
// #1528 e2e claim/detail describe('Route tickets path', () => {
xdescribe('Route basic Data path', () => {
let browser; let browser;
let page; let page;
@ -10,7 +9,7 @@ xdescribe('Route basic Data path', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('delivery', 'route'); await page.loginAndModule('delivery', 'route');
await page.accessToSearchResult('3'); await page.accessToSearchResult('2');
await page.accessToSection('route.card.tickets'); await page.accessToSection('route.card.tickets');
}); });
@ -19,40 +18,32 @@ xdescribe('Route basic Data path', () => {
}); });
it('should modify the first ticket priority', async() => { it('should modify the first ticket priority', async() => {
await page.write(selectors.routeTickets.firstTicketPriority, '2'); await page.clearInput(selectors.routeTickets.firstTicketPriority);
await page.type(selectors.routeTickets.firstTicketPriority, '9');
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
}); });
it('should confirm the buscamanButton is disabled', async() => { it('should confirm the buscaman button is disabled', async() => {
const result = await page.evaluate(selector => { await page.waitForSelector(`${selectors.routeTickets.buscamanButton}.disabled`);
return document.querySelector(selector);
}, `${selectors.routeTickets.buscamanButton} :disabled`);
expect(result).toBeTruthy();
}); });
it('should check the first ticket checkbox and confirm the buscamanButton button is no longer disabled', async() => { it('should check the first ticket checkbox and confirm the buscamanButton button is no longer disabled', async() => {
await page.waitToClick(selectors.routeTickets.firstTicketCheckbox); await page.waitForSelector(`${selectors.routeTickets.buscamanButton}.disabled`, {visible: false});
const result = await page.evaluate(selector => {
return document.querySelector(selector);
}, `${selectors.routeTickets.buscamanButton} :disabled`);
expect(result).toBeFalsy();
}); });
it('should check the route volume on the descriptor', async() => { it('should check the route volume on the descriptor', async() => {
const result = await page.waitToGetProperty(selectors.routeDescriptor.volume, 'innerText'); const result = await page.waitToGetProperty(selectors.routeDescriptor.volume, 'innerText');
expect(result).toEqual('1.1 / 18 m³'); expect(result).toEqual('0.2 / 50 m³');
}); });
it('should count how many tickets are in route', async() => { it('should count how many tickets are in route', async() => {
const result = await page.countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]'); const result = await page.countElement(selectors.routeTickets.anyTicket);
expect(result).toEqual(11); expect(result).toEqual(1);
}); });
it('should delete the first ticket in route', async() => { it('should delete the first ticket in route', async() => {
@ -63,23 +54,14 @@ xdescribe('Route basic Data path', () => {
expect(message.text).toContain('Ticket removed from route'); expect(message.text).toContain('Ticket removed from route');
}); });
it('should again delete the first ticket in route', async() => {
await page.waitToClick(selectors.routeTickets.firstTicketDeleteButton);
await page.waitToClick(selectors.routeTickets.confirmButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Ticket removed from route');
});
it('should now count how many tickets are in route to find one less', async() => { it('should now count how many tickets are in route to find one less', async() => {
const result = await page.countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]'); await page.waitForNumberOfElements(selectors.routeTickets.anyTicket, 0);
expect(result).toEqual(9);
}); });
it('should confirm the route volume on the descriptor has been updated by the changes made', async() => { // #2862 updateVolume() route descriptor no actualiza volumen
xit('should confirm the route volume on the descriptor has been updated by the changes made', async() => {
const result = await page.waitToGetProperty(selectors.routeDescriptor.volume, 'innerText'); const result = await page.waitToGetProperty(selectors.routeDescriptor.volume, 'innerText');
expect(result).toEqual('0.9 / 18 m³'); expect(result).toEqual('0 / 50 m³');
}); });
}); });

View File

@ -1,8 +1,7 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
// #2833 Refactor account.basicData describe('Account create and basic data path', () => {
xdescribe('Account create and basic data path', () => {
let browser; let browser;
let page; let page;
@ -37,40 +36,23 @@ xdescribe('Account create and basic data path', () => {
await page.waitForState('account.card.basicData'); await page.waitForState('account.card.basicData');
}); });
it('should edit the basic data', async() => { it('should reload the section and check the name is as expected', async() => {
await page.overwrite(selectors.accountBasicData.name, 'Anna');
await page.overwrite(selectors.accountBasicData.nickname, 'Rogue');
await page.overwrite(selectors.accountBasicData.email, 'AnnaMarieLeBeau@verdnatura.es');
await page.autocompleteSearch(selectors.accountBasicData.language, 'english');
await page.waitToClick(selectors.accountBasicData.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the section and check the name was edited successfully', async() => {
await page.reloadSection('account.card.basicData'); await page.reloadSection('account.card.basicData');
const result = await page.waitToGetProperty(selectors.accountBasicData.name, 'value'); const result = await page.waitToGetProperty(selectors.accountBasicData.name, 'value');
expect(result).toEqual('Anna'); expect(result).toEqual('Remy');
}); });
it('should check the nickname was edited successfully', async() => { it('should check the nickname is as expected', async() => {
const result = await page.waitToGetProperty(selectors.accountBasicData.nickname, 'value'); const result = await page.waitToGetProperty(selectors.accountBasicData.nickname, 'value');
expect(result).toEqual('Rogue'); expect(result).toEqual('Gambit');
}); });
it('should check the email was edited successfully', async() => { it('should check the email is as expected', async() => {
const result = await page.waitToGetProperty(selectors.accountBasicData.email, 'value'); const result = await page.waitToGetProperty(selectors.accountBasicData.email, 'value');
expect(result).toEqual('AnnaMarieLeBeau@verdnatura.es'); expect(result).toEqual('RemyEtienneLeBeau@verdnatura.es');
});
it('should check the language was edited successfully', async() => {
const result = await page.waitToGetProperty(selectors.accountBasicData.language, 'value');
expect(result).toEqual('English');
}); });
it('should navigate to the roles section to check the roles are correct', async() => { it('should navigate to the roles section to check the roles are correct', async() => {

View File

@ -32,7 +32,7 @@
<div ng-transclude="before"></div> <div ng-transclude="before"></div>
<div class="body"> <div class="body">
<div class="top"> <div class="top">
<h5>{{$ctrl.description}}</h5> <h5 title="{{$ctrl.description}}">{{$ctrl.description}}</h5>
<div> <div>
{{$ctrl.descriptor.id | id}} {{$ctrl.descriptor.id | id}}
</div> </div>

View File

@ -42,7 +42,7 @@ Travels: Envíos
Workers: Trabajadores Workers: Trabajadores
Routes: Rutas Routes: Rutas
Locator: Localizador Locator: Localizador
Invoices out: Facturas emitidas Invoices out: Fact. emitidas
Invoices in: Fact. recibidas Invoices in: Fact. recibidas
Entries: Entradas Entries: Entradas
Users: Usuarios Users: Usuarios

View File

@ -25,8 +25,8 @@ module.exports = function(Self) {
if (ctx.data) { if (ctx.data) {
const changes = pick(ctx.currentInstance, Object.keys(ctx.data)); const changes = pick(ctx.currentInstance, Object.keys(ctx.data));
newInstance = await fkToValue(ctx.data, ctx); newInstance = ctx.data;
oldInstance = await fkToValue(changes, ctx); oldInstance = changes;
if (ctx.where && !ctx.currentInstance) { if (ctx.where && !ctx.currentInstance) {
const fields = Object.keys(ctx.data); const fields = Object.keys(ctx.data);
@ -41,7 +41,7 @@ module.exports = function(Self) {
// Get changes from created instance // Get changes from created instance
if (ctx.isNewInstance) if (ctx.isNewInstance)
newInstance = await fkToValue(ctx.instance.__data, ctx); newInstance = ctx.instance.__data;
ctx.hookState.oldInstance = oldInstance; ctx.hookState.oldInstance = oldInstance;
ctx.hookState.newInstance = newInstance; ctx.hookState.newInstance = newInstance;
@ -134,47 +134,49 @@ module.exports = function(Self) {
if (value instanceof Object) if (value instanceof Object)
continue; continue;
if (value === undefined || value === null) continue; if (value === undefined) continue;
for (let relationName in relations) { if (value) {
const relation = relations[relationName]; for (let relationName in relations) {
if (relation.keyFrom == key && key != 'id') { const relation = relations[relationName];
const model = relation.modelTo; if (relation.keyFrom == key && key != 'id') {
const modelName = relation.modelTo.modelName; const model = relation.modelTo;
const properties = model && model.definition.properties; const modelName = relation.modelTo.modelName;
const settings = model && model.definition.settings; const properties = model && model.definition.properties;
const settings = model && model.definition.settings;
const recordSet = await appModels[modelName].findById(value, null, options); const recordSet = await appModels[modelName].findById(value, null, options);
const hasShowField = settings.log && settings.log.showField; const hasShowField = settings.log && settings.log.showField;
let showField = hasShowField && recordSet let showField = hasShowField && recordSet
&& recordSet[settings.log.showField]; && recordSet[settings.log.showField];
if (!showField) { if (!showField) {
const showFieldNames = [ const showFieldNames = [
'name', 'name',
'description', 'description',
'code', 'code',
'nickname' 'nickname'
]; ];
for (field of showFieldNames) { for (field of showFieldNames) {
const propField = properties && properties[field]; const propField = properties && properties[field];
const recordField = recordSet && recordSet[field]; const recordField = recordSet && recordSet[field];
if (propField && recordField) { if (propField && recordField) {
showField = field; showField = field;
break; break;
}
} }
} }
}
if (showField && recordSet && recordSet[showField]) { if (showField && recordSet && recordSet[showField]) {
value = recordSet[showField]; value = recordSet[showField];
break;
}
value = recordSet && recordSet.id || value;
break; break;
} }
value = recordSet && recordSet.id || value;
break;
} }
} }
result[key] = value; result[key] = value;
@ -259,6 +261,9 @@ module.exports = function(Self) {
removeUnloggable(definition, oldInstance); removeUnloggable(definition, oldInstance);
removeUnloggable(definition, newInstance); removeUnloggable(definition, newInstance);
oldInstance = await fkToValue(oldInstance, ctx);
newInstance = await fkToValue(newInstance, ctx);
// Prevent log with no new changes // Prevent log with no new changes
const hasNewChanges = Object.keys(newInstance).length; const hasNewChanges = Object.keys(newInstance).length;
if (!hasNewChanges) return; if (!hasNewChanges) return;

View File

@ -93,5 +93,7 @@
"New ticket request has been created": "New ticket request has been created *'{{description}}'* for day *{{shipped}}*, with a quantity of *{{quantity}}*", "New ticket request has been created": "New ticket request has been created *'{{description}}'* for day *{{shipped}}*, with a quantity of *{{quantity}}*",
"There's a new urgent ticket": "There's a new urgent ticket: [{{title}}](https://cau.verdnatura.es/WorkOrder.do?woMode=viewWO&woID={{issueId}})", "There's a new urgent ticket": "There's a new urgent ticket: [{{title}}](https://cau.verdnatura.es/WorkOrder.do?woMode=viewWO&woID={{issueId}})",
"Swift / BIC cannot be empty": "Swift / BIC cannot be empty", "Swift / BIC cannot be empty": "Swift / BIC cannot be empty",
"Role name must be written in camelCase": "Role name must be written in camelCase" "Role name must be written in camelCase": "Role name must be written in camelCase",
"Client assignment has changed": "I did change the salesperson ~*\"<{{previousWorkerName}}>\"*~ by *\"<{{currentWorkerName}}>\"* from the client [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "None"
} }

View File

@ -167,8 +167,8 @@
"Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas", "Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas",
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta", "The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
"Sorts whole route": "Reordena ruta entera", "Sorts whole route": "Reordena ruta entera",
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día <strong>{{shipped}}</strong>, con una cantidad de <strong>{{quantity}}</strong> y un precio de <strong>{{price}} €</strong>", "New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*",
"New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día <strong>{{shipped}}</strong>, con una cantidad de <strong>{{quantity}}</strong>", "New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*",
"Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío", "Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío",
"This BIC already exist.": "Este BIC ya existe.", "This BIC already exist.": "Este BIC ya existe.",
"That item doesn't exists": "Ese artículo no existe", "That item doesn't exists": "Ese artículo no existe",
@ -176,5 +176,7 @@
"Invalid account": "Cuenta inválida", "Invalid account": "Cuenta inválida",
"Compensation account is empty": "La cuenta para compensar está vacia", "Compensation account is empty": "La cuenta para compensar está vacia",
"This genus already exist": "Este genus ya existe", "This genus already exist": "Este genus ya existe",
"This specie already exist": "Esta especie ya existe" "This specie already exist": "Esta especie ya existe",
"Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "Ninguno"
} }

View File

@ -3,7 +3,7 @@
* @param {Object} instance - The model or context instance * @param {Object} instance - The model or context instance
* @param {Object} changes - Object containing changes * @param {Object} changes - Object containing changes
*/ */
exports.translateValues = async(instance, changes) => { exports.translateValues = async(instance, changes, options = {}) => {
const models = instance.app.models; const models = instance.app.models;
function getRelation(instance, property) { function getRelation(instance, property) {
const relations = instance.definition.settings.relations; const relations = instance.definition.settings.relations;
@ -38,12 +38,20 @@ exports.translateValues = async(instance, changes) => {
const properties = Object.assign({}, changes); const properties = Object.assign({}, changes);
for (let property in properties) { for (let property in properties) {
const firstChar = property.substring(0, 1);
const isPrivate = firstChar == '$';
if (isPrivate) {
delete properties[property];
continue;
}
const relation = getRelation(instance, property); const relation = getRelation(instance, property);
const value = properties[property]; const value = properties[property];
let finalValue = value; const hasValue = value != null && value != undefined;
if (relation) { let finalValue = value;
let fieldsToShow = ['alias', 'name', 'code', 'description']; if (relation && hasValue) {
let fieldsToShow = ['nickname', 'name', 'code', 'description'];
const modelName = relation.model; const modelName = relation.model;
const model = models[modelName]; const model = models[modelName];
const log = model.definition.settings.log; const log = model.definition.settings.log;
@ -53,7 +61,7 @@ exports.translateValues = async(instance, changes) => {
const row = await model.findById(value, { const row = await model.findById(value, {
fields: fieldsToShow fields: fieldsToShow
}); }, options);
const newValue = getValue(row); const newValue = getValue(row);
if (newValue) finalValue = newValue; if (newValue) finalValue = newValue;
} }
@ -76,7 +84,12 @@ exports.translateValues = async(instance, changes) => {
exports.getChanges = (original, changes) => { exports.getChanges = (original, changes) => {
const oldChanges = {}; const oldChanges = {};
const newChanges = {}; const newChanges = {};
for (let property in changes) { for (let property in changes) {
const firstChar = property.substring(0, 1);
const isPrivate = firstChar == '$';
if (isPrivate) return;
if (changes[property] != original[property]) { if (changes[property] != original[property]) {
newChanges[property] = changes[property]; newChanges[property] = changes[property];

View File

@ -19,11 +19,10 @@ module.exports = Self => {
} }
}); });
Self.importToNewRefundTicket = async(ctx, id) => { Self.importToNewRefundTicket = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
const token = ctx.req.accessToken; const token = ctx.req.accessToken;
const userId = token.userId; const userId = token.userId;
const tx = await Self.beginTransaction({});
const filter = { const filter = {
where: {id: id}, where: {id: id},
include: [ include: [
@ -63,29 +62,39 @@ module.exports = Self => {
] ]
}; };
let tx;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try { try {
let options = {transaction: tx};
const worker = await models.Worker.findOne({ const worker = await models.Worker.findOne({
where: {userFk: userId} where: {userFk: userId}
}, options); }, myOptions);
const obsevationType = await models.ObservationType.findOne({ const obsevationType = await models.ObservationType.findOne({
where: {description: 'comercial'} where: {description: 'comercial'}
}, options); }, myOptions);
const agencyMode = await models.AgencyMode.findOne({ const agencyMode = await models.AgencyMode.findOne({
where: {code: 'refund'} where: {code: 'refund'}
}, options); }, myOptions);
const state = await models.State.findOne({ const state = await models.State.findOne({
where: {code: 'DELIVERED'} where: {code: 'DELIVERED'}
}, options); }, myOptions);
const zone = await models.Zone.findOne({ const zone = await models.Zone.findOne({
where: {agencyModeFk: agencyMode.id} where: {agencyModeFk: agencyMode.id}
}, options); }, myOptions);
const claim = await models.Claim.findOne(filter, options); const claim = await models.Claim.findOne(filter, myOptions);
const today = new Date(); const today = new Date();
const newRefundTicket = await models.Ticket.create({ const newRefundTicket = await models.Ticket.create({
@ -98,33 +107,33 @@ module.exports = Self => {
addressFk: claim.ticket().addressFk, addressFk: claim.ticket().addressFk,
agencyModeFk: agencyMode.id, agencyModeFk: agencyMode.id,
zoneFk: zone.id zoneFk: zone.id
}, options); }, myOptions);
await saveObservation({ await saveObservation({
description: `Reclama ticket: ${claim.ticketFk}`, description: `Reclama ticket: ${claim.ticketFk}`,
ticketFk: newRefundTicket.id, ticketFk: newRefundTicket.id,
observationTypeFk: obsevationType.id observationTypeFk: obsevationType.id
}, options); }, myOptions);
await models.TicketTracking.create({ await models.TicketTracking.create({
ticketFk: newRefundTicket.id, ticketFk: newRefundTicket.id,
stateFk: state.id, stateFk: state.id,
workerFk: worker.id workerFk: worker.id
}, options); }, myOptions);
const salesToRefund = await models.ClaimBeginning.find(salesFilter, options); const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions);
const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, options); const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, myOptions);
await insertIntoClaimEnd(createdSales, id, worker.id, options); await insertIntoClaimEnd(createdSales, id, worker.id, myOptions);
await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [ await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [
newRefundTicket.id, claim.ticketFk newRefundTicket.id, claim.ticketFk
], options); ], myOptions);
await tx.commit(); if (tx) await tx.commit();
return newRefundTicket; return newRefundTicket;
} catch (e) { } catch (e) {
await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;
} }
}; };

View File

@ -1,42 +1,43 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
const models = app.models;
describe('claimBeginning', () => { describe('claimBeginning', () => {
const claimManagerId = 72; const claimManagerId = 72;
let ticket;
let refundTicketSales;
let salesInsertedInClaimEnd;
const activeCtx = { const activeCtx = {
accessToken: {userId: claimManagerId}, accessToken: {userId: claimManagerId},
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
afterAll(async done => {
try {
await app.models.Ticket.destroyById(ticket.id);
await app.models.Ticket.rawSql(`DELETE FROM vn.orderTicket WHERE ticketFk ='${ticket.id}';`);
} catch (error) {
console.error(error);
}
done();
});
describe('importToNewRefundTicket()', () => { describe('importToNewRefundTicket()', () => {
it('should create a new ticket with negative sales and insert the negative sales into claimEnd', async() => { it('should create a new ticket with negative sales and insert the negative sales into claimEnd', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
let claimId = 1; let claimId = 1;
ticket = await app.models.ClaimBeginning.importToNewRefundTicket(ctx, claimId);
refundTicketSales = await app.models.Sale.find({where: {ticketFk: ticket.id}}); const tx = await models.Entry.beginTransaction({});
salesInsertedInClaimEnd = await app.models.ClaimEnd.find({where: {claimFk: claimId}}); try {
const options = {transaction: tx};
expect(refundTicketSales.length).toEqual(1); const ticket = await models.ClaimBeginning.importToNewRefundTicket(ctx, claimId, options);
expect(refundTicketSales[0].quantity).toEqual(-5);
expect(salesInsertedInClaimEnd[0].saleFk).toEqual(refundTicketSales[0].id); const refundTicketSales = await models.Sale.find({
where: {ticketFk: ticket.id}
}, options);
const salesInsertedInClaimEnd = await models.ClaimEnd.find({
where: {claimFk: claimId}
}, options);
expect(refundTicketSales.length).toEqual(1);
expect(refundTicketSales[0].quantity).toEqual(-5);
expect(salesInsertedInClaimEnd[0].saleFk).toEqual(refundTicketSales[0].id);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });
}); });

View File

@ -227,36 +227,6 @@ module.exports = Self => {
await Self.app.models.ClientCredit.create(newCredit); await Self.app.models.ClientCredit.create(newCredit);
} }
}); });
const app = require('vn-loopback/server/server');
app.on('started', function() {
let account = app.models.Account;
account.observe('before save', async ctx => {
if (ctx.isNewInstance) return;
ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance));
});
account.observe('after save', async ctx => {
let changes = ctx.data || ctx.instance;
if (!ctx.isNewInstance && changes) {
let oldData = ctx.hookState.oldInstance;
let hasChanges = oldData.name != changes.name || oldData.active != changes.active;
if (!hasChanges) return;
let userId = ctx.options.accessToken.userId;
let logRecord = {
originFk: oldData.id,
userFk: userId,
action: 'update',
changedModel: 'Account',
oldInstance: {name: oldData.name, active: oldData.active},
newInstance: {name: changes.name, active: changes.active}
};
await Self.app.models.ClientLog.create(logRecord);
}
});
});
Self.observe('after save', async ctx => { Self.observe('after save', async ctx => {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) return;
@ -303,8 +273,58 @@ module.exports = Self => {
query: params query: params
}); });
} }
const workerIdBefore = oldInstance.salesPersonFk;
const workerIdAfter = newInstance.salesPersonFk;
const assignmentChanged = workerIdBefore != workerIdAfter;
if (assignmentChanged)
await Self.notifyAssignment(instance, workerIdBefore, workerIdAfter);
}); });
// Send notification on client worker assignment
Self.notifyAssignment = async function notifyAssignment(client, previousWorkerId, currentWorkerId) {
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__;
const headers = httpRequest.headers;
const origin = headers.origin;
const models = Self.app.models;
let previousWorker = {name: $t('None')};
let currentWorker = {name: $t('None')};
if (previousWorkerId) {
const worker = await models.Worker.findById(previousWorkerId, {
include: {relation: 'user'}
});
previousWorker.user = worker && worker.user().name;
previousWorker.name = worker && worker.user().nickname;
}
if (currentWorkerId) {
const worker = await models.Worker.findById(currentWorkerId, {
include: {relation: 'user'}
});
currentWorker.user = worker && worker.user().name;
currentWorker.name = worker && worker.user().nickname;
}
const fullUrl = `${origin}/#!/client/${client.id}/basic-data`;
const message = $t('Client assignment has changed', {
clientId: client.id,
clientName: client.name,
url: fullUrl,
previousWorkerName: previousWorker.name,
currentWorkerName: currentWorker.name
});
if (previousWorkerId)
await models.Chat.send(httpCtx, `@${previousWorker.user}`, message);
if (currentWorkerId)
await models.Chat.send(httpCtx, `@${currentWorker.user}`, message);
};
async function validateCreditChange(ctx, finalState) { async function validateCreditChange(ctx, finalState) {
let models = Self.app.models; let models = Self.app.models;
let userId = ctx.options.accessToken.userId; let userId = ctx.options.accessToken.userId;
@ -341,4 +361,34 @@ module.exports = Self => {
if (count <= 0) if (count <= 0)
throw new UserError('The role cannot set this credit amount'); throw new UserError('The role cannot set this credit amount');
} }
const app = require('vn-loopback/server/server');
app.on('started', function() {
let account = app.models.Account;
account.observe('before save', async ctx => {
if (ctx.isNewInstance) return;
ctx.hookState.oldInstance = JSON.parse(JSON.stringify(ctx.currentInstance));
});
account.observe('after save', async ctx => {
let changes = ctx.data || ctx.instance;
if (!ctx.isNewInstance && changes) {
let oldData = ctx.hookState.oldInstance;
let hasChanges = oldData.name != changes.name || oldData.active != changes.active;
if (!hasChanges) return;
let userId = ctx.options.accessToken.userId;
let logRecord = {
originFk: oldData.id,
userFk: userId,
action: 'update',
changedModel: 'Account',
oldInstance: {name: oldData.name, active: oldData.active},
newInstance: {name: changes.name, active: changes.active}
};
await Self.app.models.ClientLog.create(logRecord);
}
});
});
}; };

View File

@ -0,0 +1,54 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Client Model', () => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'},
[`__`]: value => {
return value;
}
}
}
};
const ctx = {req: activeCtx};
const chatModel = app.models.Chat;
const client = {id: 101, name: 'Bruce Banner'};
const previousWorkerId = 106; // DavidCharlesHaller
const currentWorkerId = 107; // HankPym
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
describe('notifyAssignment()', () => {
it('should call to the Chat send() method for both workers', async() => {
spyOn(chatModel, 'send').and.callThrough();
await app.models.Client.notifyAssignment(client, previousWorkerId, currentWorkerId);
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@DavidCharlesHaller', `Client assignment has changed`);
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@HankPym', `Client assignment has changed`);
});
it('should call to the Chat send() method for the previous worker', async() => {
spyOn(chatModel, 'send').and.callThrough();
await app.models.Client.notifyAssignment(client, null, currentWorkerId);
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@HankPym', `Client assignment has changed`);
});
it('should call to the Chat send() method for the current worker', async() => {
spyOn(chatModel, 'send').and.callThrough();
await app.models.Client.notifyAssignment(client, previousWorkerId, null);
expect(chatModel.send).toHaveBeenCalledWith(ctx, '@DavidCharlesHaller', `Client assignment has changed`);
});
});
});

View File

@ -2,7 +2,6 @@ const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('entry import()', () => { describe('entry import()', () => {
let newEntry;
const buyerId = 35; const buyerId = 35;
const companyId = 442; const companyId = 442;
const travelId = 1; const travelId = 1;
@ -52,29 +51,32 @@ describe('entry import()', () => {
} }
}; };
const tx = await app.models.Entry.beginTransaction({}); const tx = await app.models.Entry.beginTransaction({});
const options = {transaction: tx}; try {
const options = {transaction: tx};
const newEntry = await app.models.Entry.create({
dated: new Date(),
supplierFk: supplierId,
travelFk: travelId,
companyFk: companyId,
observation: 'The entry',
ref: 'Entry ref'
}, options);
newEntry = await app.models.Entry.create({ await app.models.Entry.importBuys(ctx, newEntry.id, options);
dated: new Date(),
supplierFk: supplierId,
travelFk: travelId,
companyFk: companyId,
observation: 'The entry',
ref: 'Entry ref'
}, options);
await app.models.Entry.importBuys(ctx, newEntry.id, options); const updatedEntry = await app.models.Entry.findById(newEntry.id, null, options);
const entryBuys = await app.models.Buy.find({
where: {entryFk: newEntry.id}
}, options);
const updatedEntry = await app.models.Entry.findById(newEntry.id, null, options); expect(updatedEntry.observation).toEqual(expectedObservation);
const entryBuys = await app.models.Buy.find({ expect(updatedEntry.ref).toEqual(expectedRef);
where: {entryFk: newEntry.id} expect(entryBuys.length).toEqual(2);
}, options);
expect(updatedEntry.observation).toEqual(expectedObservation); await tx.rollback();
expect(updatedEntry.ref).toEqual(expectedRef); } catch (e) {
expect(entryBuys.length).toEqual(2); await tx.rollback();
throw e;
// Restores }
await tx.rollback();
}); });
}); });

View File

@ -2,4 +2,5 @@ reference: Referencia
Observation: Observación Observation: Observación
Box: Embalaje Box: Embalaje
Import buys: Importar compras Import buys: Importar compras
Some of the imported buys doesn't have an item: Algunas de las compras importadas no tienen un artículo Some of the imported buys doesn't have an item: Algunas de las compras importadas no tienen un artículo
JSON files only: Solo ficheros JSON

View File

@ -165,9 +165,13 @@
<vn-dialog class="edit" <vn-dialog class="edit"
vn-id="edit" vn-id="edit"
on-accept="$ctrl.onEditAccept()" on-accept="$ctrl.onEditAccept()"
on-close="$ctrl.editedColumn = null" on-close="$ctrl.editedColumn = null">
message="Edit buy(s)">
<tpl-body> <tpl-body>
<span translate>Edit</span>
<span class="countLines">
{{::$ctrl.totalChecked}}
</span>
<span translate>buy(s)</span>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete
vn-two vn-two

View File

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

View File

@ -12,3 +12,5 @@ Weight: Peso
Minimun amount: Cantidad mínima de compra Minimun amount: Cantidad mínima de compra
Field to edit: Campo a editar Field to edit: Campo a editar
PackageName: Cubo PackageName: Cubo
Edit: Editar
buy(s): compra(s)

View File

@ -0,0 +1,7 @@
.countLines {
flex: 0.15;
font-size: 24px;
color: orangered;
font-weight: bold;
max-width: 30px;
}

View File

@ -12,7 +12,7 @@ module.exports = Self => {
description: 'itemFk, id' description: 'itemFk, id'
}], }],
returns: { returns: {
type: 'Array', type: ['Object'],
root: true root: true
}, },
http: { http: {
@ -40,12 +40,19 @@ module.exports = Self => {
b.weight, b.weight,
i.stems, i.stems,
b.quantity, b.quantity,
b.buyingValue +
b.freightValue +
b.comissionValue +
b.packageValue AS cost,
b.buyingValue, b.buyingValue,
b.freightValue,
b.comissionValue,
b.packageValue,
b.packageFk , b.packageFk ,
s.id AS supplierFk, s.id AS supplierFk,
s.name AS supplier s.name AS supplier
FROM itemType it FROM itemType it
RIGHT JOIN (entry e RIGHT JOIN (entry e
LEFT JOIN supplier s ON s.id = e.supplierFk LEFT JOIN supplier s ON s.id = e.supplierFk
RIGHT JOIN buy b ON b.entryFk = e.id RIGHT JOIN buy b ON b.entryFk = e.id
LEFT JOIN item i ON i.id = b.itemFk LEFT JOIN item i ON i.id = b.itemFk

View File

@ -72,7 +72,17 @@
</vn-td> </vn-td>
<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">
<span
vn-tooltip="
{{::$ctrl.$t('Cost')}}: {{::entry.buyingValue| dashIfEmpty}}<br>
{{::$ctrl.$t('Package')}}: {{::entry.packageValue| dashIfEmpty}}<br>
{{::$ctrl.$t('Freight')}}: {{::entry.freightValue| dashIfEmpty}}<br>
{{::$ctrl.$t('Comission')}}: {{::entry.comissionValue| dashIfEmpty}}">
{{::entry.cost | dashIfEmpty}}
</span>
</vn-td>
<vn-td number>{{::entry.weight | 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" title="{{::entry.supplier | dashIfEmpty}}">{{::entry.supplier | dashIfEmpty}}</vn-td> <vn-td class="expendable" title="{{::entry.supplier | dashIfEmpty}}">{{::entry.supplier | dashIfEmpty}}</vn-td>

View File

@ -9,4 +9,7 @@ Ignored: Ignorado
Provider: Proveedor Provider: Proveedor
Cube: Cubo Cube: Cubo
Price Per Unit: Precio Por Unidad Price Per Unit: Precio Por Unidad
Price Per Package: Precio Por Paquete Price Per Package: Precio Por Paquete
Freight: Porte
Package: Embalaje
Comission: Comision

View File

@ -0,0 +1,72 @@
module.exports = Self => {
Self.remoteMethod('getSuggestedTickets', {
description: 'Returns an array of suggested tickets for the given route',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The route id',
http: {source: 'path'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/getSuggestedTickets`,
verb: 'GET'
}
});
Self.getSuggestedTickets = async id => {
const ticketsInRoute = await Self.app.models.Ticket.find({
where: {routeFk: id},
fields: ['id']
});
const idsToExclude = ticketsInRoute.map(ticket => ticket.id);
const route = await Self.app.models.Route.findById(id);
const zoneAgencyModes = await Self.app.models.ZoneAgencyMode.find({
where: {
agencyModeFk: route.agencyModeFk
}
});
const zoneIds = [];
for (let zoneAgencyMode of zoneAgencyModes)
zoneIds.push(zoneAgencyMode.zoneFk);
const minDate = new Date(route.finished);
minDate.setHours(0, 0, 0, 0);
const maxDate = new Date(route.finished);
maxDate.setHours(23, 59, 59, 59);
let tickets = await Self.app.models.Ticket.find({
where: {
agencyModeFk: route.agencyModeFk,
zoneFk: {inq: zoneIds},
id: {nin: idsToExclude},
landed: {between: [minDate, maxDate]}
},
include: [
{
relation: 'warehouse',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'address',
scope: {
fields: ['id', 'street', 'postalCode', 'city'],
}
},
]
});
return tickets;
};
};

View File

@ -0,0 +1,30 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('route getSuggestedTickets()', () => {
it('should return an array of suggested tickets', async() => {
const activeCtx = {
accessToken: {userId: 19},
headers: {origin: 'http://localhost'}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
const routeID = 1;
const ticketInRoute = await app.models.Ticket.findById(12);
await ticketInRoute.updateAttribute('routeFk', null);
const result = await app.models.Route.getSuggestedTickets(routeID);
const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
expect(result.length).toEqual(1);
expect(anyResult.zoneFk).toEqual(1);
expect(anyResult.agencyModeFk).toEqual(1);
await ticketInRoute.updateAttribute('routeFk', routeID);
});
});

View File

@ -4,7 +4,7 @@ const LoopBackContext = require('loopback-context');
describe('route insertTicket()', () => { describe('route insertTicket()', () => {
const deliveryId = 56; const deliveryId = 56;
let originalTicket; let originalTicket;
const routeId = 2; const routeId = 1;
const activeCtx = { const activeCtx = {
accessToken: {userId: deliveryId}, accessToken: {userId: deliveryId},
}; };
@ -17,26 +17,18 @@ describe('route insertTicket()', () => {
done(); done();
}); });
afterAll(async done => {
try {
await originalTicket.updateAttribute('routeFk', null);
} catch (error) {
console.error(error);
}
done();
});
it('should add the ticket to a route', async() => { it('should add the ticket to a route', async() => {
originalTicket = await app.models.Ticket.findById(14); const ticketId = 12;
originalTicket = await app.models.Ticket.findById(ticketId);
await originalTicket.updateAttribute('routeFk', null);
const ticketId = 14;
const result = await app.models.Route.insertTicket(routeId, ticketId); const result = await app.models.Route.insertTicket(routeId, ticketId);
expect(result.routeFk).toEqual(2); expect(result.routeFk).toEqual(routeId);
}); });
it('should throw and error if the ticket is not suitable for the route', async() => { it('should throw and error if the ticket is not suitable for the route', async() => {
const ticketId = 23; const ticketId = 2;
let error; let error;
try { try {

View File

@ -7,6 +7,7 @@ module.exports = Self => {
require('../methods/route/getDeliveryPoint')(Self); require('../methods/route/getDeliveryPoint')(Self);
require('../methods/route/insertTicket')(Self); require('../methods/route/insertTicket')(Self);
require('../methods/route/clone')(Self); require('../methods/route/clone')(Self);
require('../methods/route/getSuggestedTickets')(Self);
Self.validate('kmStart', validateDistance, { Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 1000' message: 'Distance must be lesser than 1000'

View File

@ -80,7 +80,7 @@
<vn-td shrink> <vn-td shrink>
<vn-icon <vn-icon
ng-if="ticket.notes.length" ng-if="ticket.notes.length"
title="::{{ticket.notes[0].description}}" title="{{ticket.notes[0].description}}"
icon="insert_drive_file" icon="insert_drive_file"
class="bright"> class="bright">
</vn-icon> </vn-icon>
@ -110,10 +110,9 @@
question="Delete ticket from route?" question="Delete ticket from route?"
on-accept="$ctrl.removeTicketFromRoute()"> on-accept="$ctrl.removeTicketFromRoute()">
</vn-confirm> </vn-confirm>
<vn-crud-model <vn-crud-model
vn-id="possibleTicketsModel" vn-id="possibleTicketsModel"
url="Tickets" url="Routes/{{$ctrl.$params.id}}/getSuggestedTickets"
filter="$ctrl.possibleTicketsFilter"
data="$ctrl.possibleTickets"> data="$ctrl.possibleTickets">
</vn-crud-model> </vn-crud-model>
<vn-dialog <vn-dialog

View File

@ -9,8 +9,6 @@ class Controller extends Section {
set route(value) { set route(value) {
this._route = value; this._route = value;
if (value)
this.buildPossibleTicketsFilter();
} }
get isChecked() { get isChecked() {
@ -22,32 +20,6 @@ class Controller extends Section {
return false; return false;
} }
buildPossibleTicketsFilter() {
let minDate = new Date(this.route.finished);
minDate.setHours(0, 0, 0, 0);
let maxDate = new Date(this.route.finished);
maxDate.setHours(23, 59, 59, 59);
this.possibleTicketsFilter = {
where: {
zoneFk: this.route.zoneFk,
routeFk: null,
landed: {between: [minDate, maxDate]},
},
include: [
{
relation: 'warehouse',
scope: {
fields: ['name']
},
}, {
relation: 'address'
}
]
};
}
getHighestPriority() { getHighestPriority() {
let highestPriority = Math.max(...this.$.model.data.map(tag => { let highestPriority = Math.max(...this.$.model.data.map(tag => {
return tag.priority; return tag.priority;
@ -134,14 +106,26 @@ class Controller extends Section {
setTicketsRoute() { setTicketsRoute() {
let tickets = this.getSelectedItems(this.possibleTickets); let tickets = this.getSelectedItems(this.possibleTickets);
if (tickets.length === 0) return; if (tickets.length === 0) return;
for (let i = 0; i < tickets.length; i++) {
delete tickets[i].checked; const updates = [];
tickets[i].routeFk = this.route.id;
for (let ticket of tickets) {
delete ticket.checked;
const update = {
where: {id: ticket.id},
data: {routeFk: this.route.id}
};
updates.push(update);
} }
return this.$.possibleTicketsModel.save().then(() => { const data = {creates: [], updates: updates, deletes: []};
this.$.model.data = this.$.model.data.concat(tickets);
}); return this.$http.post(`Tickets/crud`, data)
.then(() => {
this.$.model.data = this.$.model.data.concat(tickets);
this.vnApp.showSuccess(this.$t('Data saved!'));
});
} }
onDrop($event) { onDrop($event) {

View File

@ -37,42 +37,6 @@ describe('Route', () => {
}); });
}); });
describe('buildPossibleTicketsFilter()', () => {
it('should build the possible tickets filter', () => {
let expectedFilter = {
include: [
{
relation: 'warehouse',
scope: {
fields: ['name']
}
}, {
relation: 'address'
}
],
where: {
landed: {
between: [
jasmine.any(Date),
jasmine.any(Date)
]
},
routeFk: null,
zoneFk: 67
}
};
controller.route = {
finished: new Date(),
routeFk: null,
zoneFk: 67
};
controller.buildPossibleTicketsFilter();
expect(controller.possibleTicketsFilter).toEqual(expectedFilter);
});
});
describe('getHighestPriority()', () => { describe('getHighestPriority()', () => {
it('should return the highest value found in priorities plus 1', () => { it('should return the highest value found in priorities plus 1', () => {
controller.$.model = {data: [ controller.$.model = {data: [
@ -228,13 +192,13 @@ describe('Route', () => {
}); });
describe('setTicketsRoute()', () => { describe('setTicketsRoute()', () => {
it('should perform a POST query to add tickets to the route', done => { it('should perform a POST query to add tickets to the route', () => {
controller.$.possibleTicketsModel = {save: () => {}};
jest.spyOn(controller.$.possibleTicketsModel, 'save').mockReturnValue(Promise.resolve());
controller.$.model = {data: [ controller.$.model = {data: [
{id: 1, checked: false} {id: 1, checked: false}
]}; ]};
const existingTicket = controller.$.model.data[0];
controller.route = {id: 111}; controller.route = {id: 111};
controller.possibleTickets = [ controller.possibleTickets = [
@ -245,15 +209,16 @@ describe('Route', () => {
]; ];
let expectedResult = [ let expectedResult = [
{checked: false, id: 1}, existingTicket,
{id: 3, routeFk: 111}, {id: 3},
{id: 5, routeFk: 111} {id: 5}
]; ];
controller.setTicketsRoute().then(() => { $httpBackend.expectPOST(`Tickets/crud`).respond();
expect(controller.$.model.data).toEqual(expectedResult); controller.setTicketsRoute();
done(); $httpBackend.flush();
}).catch(done.fail);
expect(controller.$.model.data).toEqual(expectedResult);
}); });
}); });

View File

@ -0,0 +1 @@
Accounts: Cuentas

View File

@ -9,10 +9,10 @@
{"state": "supplier.index", "icon": "icon-supplier"} {"state": "supplier.index", "icon": "icon-supplier"}
], ],
"card": [ "card": [
{"state": "supplier.card.account", "icon": "contact_support"},
{"state": "supplier.card.basicData", "icon": "settings"}, {"state": "supplier.card.basicData", "icon": "settings"},
{"state": "supplier.card.fiscalData", "icon": "account_balance"}, {"state": "supplier.card.fiscalData", "icon": "account_balance"},
{"state": "supplier.card.billingData", "icon": "icon-payment"}, {"state": "supplier.card.billingData", "icon": "icon-payment"},
{"state": "supplier.card.account", "icon": "contact_support"},
{"state": "supplier.card.contact", "icon": "contact_phone"}, {"state": "supplier.card.contact", "icon": "contact_phone"},
{"state": "supplier.card.log", "icon": "history"}, {"state": "supplier.card.log", "icon": "history"},
{"state": "supplier.card.consumption", "icon": "show_chart"} {"state": "supplier.card.consumption", "icon": "show_chart"}
@ -104,7 +104,7 @@
"url": "/account", "url": "/account",
"state": "supplier.card.account", "state": "supplier.card.account",
"component": "vn-supplier-account", "component": "vn-supplier-account",
"description": "Account", "description": "Accounts",
"params": { "params": {
"supplier": "$ctrl.supplier" "supplier": "$ctrl.supplier"
}, },

View File

@ -259,7 +259,7 @@ module.exports = Self => {
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL) WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)
AND f.shipped >= CURDATE()`); AND f.shipped >= CURDATE()`);
stmts.push('CALL ticketGetProblems()'); stmts.push('CALL ticketGetProblems(FALSE)');
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`
SELECT SELECT

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('ticket filter()', () => { // #2868 Excluded until database export
xdescribe('ticket filter()', () => {
it('should return the tickets matching the filter', async() => { it('should return the tickets matching the filter', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {}}; const ctx = {req: {accessToken: {userId: 9}}, args: {}};
const filter = {order: 'id DESC'}; const filter = {order: 'id DESC'};

View File

@ -2,7 +2,8 @@
"name": "Ticket", "name": "Ticket",
"base": "Loggable", "base": "Loggable",
"log": { "log": {
"model":"TicketLog" "model":"TicketLog",
"showField": "id"
}, },
"options": { "options": {
"mysql": { "mysql": {

View File

@ -43,9 +43,9 @@
</vn-button> </vn-button>
</vn-tool-bar> </vn-tool-bar>
<vn-one class="taxes" ng-if="$ctrl.sales.length > 0"> <vn-one class="taxes" ng-if="$ctrl.sales.length > 0">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.subtotal | currency: 'EUR': 2}}</p> <p><vn-label translate>Subtotal</vn-label> {{$ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.VAT | currency: 'EUR': 2}}</p> <p><vn-label translate>VAT</vn-label> {{$ctrl.ticket.totalWithVat - $ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.total | currency: 'EUR': 2}}</strong></p> <p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.ticket.totalWithVat | currency: 'EUR':2}}</strong></p>
</vn-one> </vn-one>
</vn-horizontal> </vn-horizontal>
<vn-table model="model"> <vn-table model="model">

View File

@ -24,7 +24,6 @@ class Controller extends Section {
set sales(value) { set sales(value) {
this._sales = value; this._sales = value;
this.refreshTotal();
} }
get ticketState() { get ticketState() {
@ -33,17 +32,6 @@ class Controller extends Section {
return this.ticket.ticketState.state.code; return this.ticket.ticketState.state.code;
} }
get total() {
return this.subtotal + this.VAT;
}
getSubTotal() {
if (!this.$params.id || !this.sales) return;
this.$http.get(`Tickets/${this.$params.id}`).then(res => {
this.subtotal = res.data.totalWithoutVat || 0.0;
});
}
getSaleTotal(sale) { getSaleTotal(sale) {
if (sale.quantity == null || sale.price == null) if (sale.quantity == null || sale.price == null)
return null; return null;
@ -59,19 +47,6 @@ class Controller extends Section {
.then(res => this.edit.mana = res.data); .then(res => this.edit.mana = res.data);
} }
getVat() {
this.VAT = 0.0;
if (!this.$params.id || !this.sales) return;
this.$http.get(`Tickets/${this.$params.id}`).then(res => {
this.VAT = res.data.totalWithVat - res.data.totalWithoutVat || 0.0;
});
}
refreshTotal() {
this.getSubTotal();
this.getVat();
}
/** /**
* Returns checked instances * Returns checked instances
* *
@ -158,8 +133,6 @@ class Controller extends Section {
const index = this.sales.indexOf(sale); const index = this.sales.indexOf(sale);
this.sales.splice(index, 1); this.sales.splice(index, 1);
}); });
this.refreshTotal();
} }
createClaim() { createClaim() {
@ -221,7 +194,6 @@ class Controller extends Section {
this.$http.post(query, {newPrice}).then(res => { this.$http.post(query, {newPrice}).then(res => {
sale.price = res.data.price; sale.price = res.data.price;
this.edit = null; this.edit = null;
this.refreshTotal();
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}).finally(() => this.resetChanges()); }).finally(() => this.resetChanges());
} }
@ -287,7 +259,6 @@ class Controller extends Section {
sale.discount = this.edit.discount; sale.discount = this.edit.discount;
this.edit = null; this.edit = null;
this.refreshTotal();
}).finally(() => this.resetChanges()); }).finally(() => this.resetChanges());
} }
@ -401,7 +372,6 @@ class Controller extends Section {
updateQuantity(sale) { updateQuantity(sale) {
const data = {quantity: sale.quantity}; const data = {quantity: sale.quantity};
this.$http.post(`Sales/${sale.id}/updateQuantity`, data).then(() => { this.$http.post(`Sales/${sale.id}/updateQuantity`, data).then(() => {
this.refreshTotal();
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}).catch(e => { }).catch(e => {
this.$.model.refresh(); this.$.model.refresh();
@ -444,7 +414,6 @@ class Controller extends Section {
sale.price = newSale.price; sale.price = newSale.price;
sale.item = newSale.item; sale.item = newSale.item;
this.refreshTotal();
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}).finally(() => this.resetChanges()); }).finally(() => this.resetChanges());
} }
@ -466,7 +435,6 @@ class Controller extends Section {
this.$http.post(query).then(() => { this.$http.post(query).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.model.refresh(); this.$.model.refresh();
this.refreshTotal();
}); });
} }

View File

@ -72,28 +72,6 @@ describe('Ticket', () => {
}); });
}); });
describe('sales() setter', () => {
it('should set the sales data an then call the refreshTotal() method', () => {
jest.spyOn(controller, 'refreshTotal').mockReturnThis();
controller.sales = [{id: 1}];
expect(controller.refreshTotal).toHaveBeenCalledWith();
});
});
describe('getSubTotal()', () => {
it('should make an HTTP GET query and then set the subtotal property', () => {
const expectedResponse = {totalWithoutVat: 128};
$httpBackend.expect('GET', 'Tickets/1').respond(200, expectedResponse);
controller.getSubTotal();
$httpBackend.flush();
expect(controller.subtotal).toEqual(expectedResponse.totalWithoutVat);
});
});
describe('getSaleTotal()', () => { describe('getSaleTotal()', () => {
it('should return the sale total amount', () => { it('should return the sale total amount', () => {
const sale = { const sale = {
@ -122,21 +100,6 @@ describe('Ticket', () => {
}); });
}); });
describe('getVat()', () => {
it('should make an HTTP GET query and return the ticket VAT', () => {
controller.edit = {};
const expectedResponse = {totalWithVat: 1000, totalWithoutVat: 999};
const expectedVAT = expectedResponse.totalWithVat - expectedResponse.totalWithoutVat;
$httpBackend.expect('GET', 'Tickets/1').respond(200, expectedResponse);
controller.getVat();
$httpBackend.flush();
expect(controller.VAT).toEqual(expectedVAT);
});
});
describe('selectedSales()', () => { describe('selectedSales()', () => {
it('should return a list of selected sales', () => { it('should return a list of selected sales', () => {
controller.sales[1].checked = true; controller.sales[1].checked = true;
@ -276,7 +239,6 @@ describe('Ticket', () => {
describe('removeSelectedSales()', () => { describe('removeSelectedSales()', () => {
it('should remove the selected sales from the controller sale property', () => { it('should remove the selected sales from the controller sale property', () => {
jest.spyOn(controller, 'refreshTotal').mockReturnThis();
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
const firstSale = controller.sales[0]; const firstSale = controller.sales[0];
@ -288,7 +250,6 @@ describe('Ticket', () => {
expect(controller.sales.length).toEqual(1); expect(controller.sales.length).toEqual(1);
expect(lastSale.id).toEqual(4); expect(lastSale.id).toEqual(4);
expect(controller.refreshTotal).toHaveBeenCalledWith();
}); });
}); });
@ -355,7 +316,6 @@ describe('Ticket', () => {
describe('updatePrice()', () => { describe('updatePrice()', () => {
it('should make an HTTP POST query, update the sale price and then call to the resetChanges() method', () => { it('should make an HTTP POST query, update the sale price and then call to the resetChanges() method', () => {
jest.spyOn(controller, 'refreshTotal').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
@ -372,7 +332,6 @@ describe('Ticket', () => {
$httpBackend.flush(); $httpBackend.flush();
expect(selectedSale.price).toEqual(2); expect(selectedSale.price).toEqual(2);
expect(controller.refreshTotal).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalled(); expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$.editPricePopover.hide).toHaveBeenCalledWith(); expect(controller.$.editPricePopover.hide).toHaveBeenCalledWith();
expect(controller.resetChanges).toHaveBeenCalledWith(); expect(controller.resetChanges).toHaveBeenCalledWith();
@ -451,7 +410,6 @@ describe('Ticket', () => {
it('should make an HTTP POST query, update the sales discount and then call to the resetChanges() method', () => { it('should make an HTTP POST query, update the sales discount and then call to the resetChanges() method', () => {
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
jest.spyOn(controller, 'refreshTotal').mockReturnThis();
const expectedDiscount = 10; const expectedDiscount = 10;
const firstSelectedSale = controller.sales[0]; const firstSelectedSale = controller.sales[0];
@ -473,7 +431,6 @@ describe('Ticket', () => {
expect(firstSelectedSale.discount).toEqual(expectedDiscount); expect(firstSelectedSale.discount).toEqual(expectedDiscount);
expect(secondSelectedSale.discount).toEqual(expectedDiscount); expect(secondSelectedSale.discount).toEqual(expectedDiscount);
expect(controller.refreshTotal).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalled(); expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.resetChanges).toHaveBeenCalledWith(); expect(controller.resetChanges).toHaveBeenCalledWith();
}); });
@ -622,7 +579,6 @@ describe('Ticket', () => {
describe('updateQuantity()', () => { describe('updateQuantity()', () => {
it('should make a POST query saving sale quantity', () => { it('should make a POST query saving sale quantity', () => {
jest.spyOn(controller, 'refreshTotal').mockReturnThis();
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
const selectedSale = controller.sales[0]; const selectedSale = controller.sales[0];
@ -634,7 +590,6 @@ describe('Ticket', () => {
controller.updateQuantity(selectedSale); controller.updateQuantity(selectedSale);
$httpBackend.flush(); $httpBackend.flush();
expect(controller.refreshTotal).toHaveBeenCalledWith();
expect(controller.resetChanges).toHaveBeenCalledWith(); expect(controller.resetChanges).toHaveBeenCalledWith();
}); });
}); });
@ -659,7 +614,6 @@ describe('Ticket', () => {
describe('addSale()', () => { describe('addSale()', () => {
it('should make a POST query adding a new sale', () => { it('should make a POST query adding a new sale', () => {
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
jest.spyOn(controller, 'refreshTotal').mockReturnThis();
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
const newSale = {itemFk: 4, quantity: 10}; const newSale = {itemFk: 4, quantity: 10};
@ -681,7 +635,6 @@ describe('Ticket', () => {
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled(); expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.refreshTotal).toHaveBeenCalledWith();
expect(controller.resetChanges).toHaveBeenCalledWith(); expect(controller.resetChanges).toHaveBeenCalledWith();
}); });
}); });
@ -712,7 +665,6 @@ describe('Ticket', () => {
it('should make an HTTP post query ', () => { it('should make an HTTP post query ', () => {
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
jest.spyOn(controller.$.model, 'refresh').mockReturnThis(); jest.spyOn(controller.$.model, 'refresh').mockReturnThis();
jest.spyOn(controller, 'refreshTotal').mockReturnThis();
const selectedSale = controller.sales[0]; const selectedSale = controller.sales[0];
selectedSale.checked = true; selectedSale.checked = true;
@ -723,7 +675,6 @@ describe('Ticket', () => {
expect(controller.vnApp.showSuccess).toHaveBeenCalled(); expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$.model.refresh).toHaveBeenCalledWith(); expect(controller.$.model.refresh).toHaveBeenCalledWith();
expect(controller.refreshTotal).toHaveBeenCalledWith();
}); });
}); });
}); });

View File

@ -17,7 +17,7 @@ You have to allow pop-ups in your web browser to use this functionality:
Debes permitir los pop-pups en tu navegador para que esta herramienta funcione correctamente Debes permitir los pop-pups en tu navegador para que esta herramienta funcione correctamente
Disc: Dto Disc: Dto
Available: Disponible Available: Disponible
What is the day of receipt of the ticket?: ¿Cual es del día de recepción del pedido? What is the day of receipt of the ticket?: ¿Cual es el día de preparación del pedido?
Add claim: Crear reclamación Add claim: Crear reclamación
Claim: Reclamación Claim: Reclamación
Transfer lines: Transferir líneas Transfer lines: Transferir líneas

View File

@ -11,6 +11,9 @@
"Zone": { "Zone": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ZoneAgencyMode": {
"dataSource": "vn"
},
"ZoneClosure": { "ZoneClosure": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,29 @@
{
"name": "ZoneAgencyMode",
"base": "VnModel",
"options": {
"mysql": {
"table": "zoneAgencyMode"
}
},
"properties": {
"id": {
"id": true,
"type": "number"
},
"agencyModeFk": {
"type": "number"
},
"zoneFk": {
"type": "number"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1,5 +1,5 @@
<div class="vn-mx-xs" v-if="attachment.component"> <div class="vn-mx-xs" v-if="attachment.component">
<a target="_blank" class="vn-py-sm vn-px-md" v-bind:href="path"> <a target="_blank" class="vn-py-sm vn-px-md" v-bind:href="attachmentPath">
<div class="text">{{attachment.filename}}</div> <div class="text">{{attachment.filename}}</div>
<div class="icon vn-pl-md">&#x25BC;</div> <div class="icon vn-pl-md">&#x25BC;</div>
</a> </a>

View File

@ -1,7 +1,7 @@
module.exports = { module.exports = {
name: 'attachment', name: 'attachment',
computed: { computed: {
path() { attachmentPath() {
const filename = this.attachment.filename; const filename = this.attachment.filename;
const component = this.attachment.component; const component = this.attachment.component;
if (this.attachment.cid) if (this.attachment.cid)

View File

@ -0,0 +1,20 @@
subject: Avis initial de solde débiteur
title: Avis initial de solde débiteur
sections:
introduction:
title: Madame, Monsieur,
description: Sauf erreur ou omission de notre part, nous constatons que votre
compte client présente à ce jour un solde débiteur.
checkExtract: Ce montant correspond à nos factures restées impayées, ci-joint en annexe.
Notre service administratif se fera un plaisir de clarifier toutes les questions que vous
pourriez avoir, et vous fournira également tout document que vous nous demandez.
checkValidData: Si lors de la vérification des données fournies, elles sont correctes et
léchéance étant dépassée, nous vous demandons de bien vouloir régulariser cette situation.
payMethod: Si vous ne souhaitez pas vous rendre personnellement à nos bureaux, vous
pouvez effectuer le paiement par virement bancaire sur le compte qui apparaît en
bas du relevé, en indiquant votre numéro de client, ou vous pouvez effectuer le
paiement en ligne avec une carte bleue sur notre site Internet.
conclusion: Dans le cas où votre règlement aurait été adressé entre temps,
nous vous prions de ne pas tenir compte de la présente.
Nous vous remercions par avance de votre aimable coopération.
transferAccount: Coordonées pour virement bancaire

View File

@ -12,4 +12,4 @@ claim: Reclamación {0}
sections: sections:
agency: agency:
description: 'Para agilizar su recogida, por favor, póngase en contacto con la oficina description: 'Para agilizar su recogida, por favor, póngase en contacto con la oficina
de integrados. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) <em>(agomezf@integra2.es)</em>' de integrados. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) <em>(agomez@integra2.com)</em>'

View File

@ -0,0 +1,10 @@
title: Relevé de compte
claimId: Réclamation
clientId: Client
clientData: Données client
date: Date
concept: Objet
invoiced: Facturé
payed: Payé
balance: Solde
client: Client {0}