ticket.basicData Select zone for productionBoss #1604
gitea/salix/dev This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-07-26 11:48:01 +02:00
parent ab2a47d363
commit 64a4e5f43d
33 changed files with 819 additions and 333 deletions

View File

@ -0,0 +1,38 @@
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`ticket_componentMakeUpdate`(
vTicketFk INT,
vClientFk INT,
vAgencyModeFk INT,
vAddressFk INT,
vZoneFk INT,
vWarehouseFk TINYINT,
vCompanyFk SMALLINT,
vShipped DATETIME,
vLanded DATE,
vIsDeleted BOOLEAN,
vHasToBeUnrouted BOOLEAN,
vOption INT)
BEGIN
CALL vn.ticket_componentPreview (vTicketFk, vLanded, vAddressFk, vZoneFk, vWarehouseFk);
CALL vn.ticket_componentUpdate (
vTicketFk,
vClientFk,
vAgencyModeFk,
vAddressFk,
vZoneFk,
vWarehouseFk,
vCompanyFk,
vShipped,
vLanded,
vIsDeleted,
vHasToBeUnrouted,
vOption
);
DROP TEMPORARY TABLE
tmp.ticketComponent,
tmp.ticketComponentPrice;
END$$
DELIMITER ;

View File

@ -0,0 +1,111 @@
USE `vn`;
DROP procedure IF EXISTS `ticket_componentPreview`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticket_componentPreview`(
vTicketFk INT,
vLanded DATE,
vAddressFk INT,
vZoneFk INT,
vWarehouseFk SMALLINT)
BEGIN
/**
* Calcula los componentes de los articulos de un ticket
*
* @param vTicketFk id del ticket
* @param vLanded nueva fecha de entrega
* @param vAddressFk nuevo consignatario
* @param vZoneFk nueva zona
* @param vWarehouseFk nuevo warehouse
*
* @return tmp.ticketComponent (warehouseFk, itemFk, componentFk, cost)
*/
DECLARE vShipped DATE;
DECLARE vBuyOrderItem INT DEFAULT 100;
DECLARE vHasDataChanged BOOL DEFAULT FALSE;
DECLARE vHasAddressChanged BOOL;
DECLARE vHasZoneChanged BOOL DEFAULT FALSE;
DECLARE vHasWarehouseChanged BOOL DEFAULT FALSE;
DECLARE vAddressTypeRateFk INT DEFAULT NULL;
DECLARE vAgencyModeTypeRateFk INT DEFAULT NULL;
DECLARE vHasChangeAll BOOL DEFAULT FALSE;
SELECT DATE(landed) <> vLanded,
addressFk <> vAddressFk,
zoneFk <> vZoneFk,
warehouseFk <> vWarehouseFk
INTO
vHasDataChanged,
vHasAddressChanged,
vHasZoneChanged,
vHasWarehouseChanged
FROM vn.ticket t
WHERE t.id = vTicketFk;
IF vHasDataChanged OR vHasWarehouseChanged THEN
SET vHasChangeAll = TRUE;
END IF;
IF vHasAddressChanged THEN
SET vAddressTypeRateFk = 5;
END IF;
IF vHasZoneChanged THEN
SET vAgencyModeTypeRateFk = 6;
END IF;
SELECT TIMESTAMPADD(DAY, -travelingDays, vLanded) INTO vShipped
FROM zone
WHERE id = vZoneFk;
CALL buyUltimate(vWarehouseFk, vShipped);
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot ENGINE = MEMORY (
SELECT
vWarehouseFk AS warehouseFk,
NULL AS available,
s.itemFk,
bu.buyFk
FROM sale s
LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk
WHERE s.ticketFk = vTicketFk
AND s.itemFk != vBuyOrderItem
GROUP BY bu.warehouseFk, bu.itemFk);
CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped);
REPLACE INTO tmp.ticketComponent (warehouseFk, itemFk, componentFk, cost)
SELECT t.warehouseFk, s.itemFk, sc.componentFk, sc.value
FROM saleComponent sc
JOIN sale s ON s.id = sc.saleFk
JOIN ticket t ON t.id = s.ticketFk
JOIN componentRate cr ON cr.id = sc.componentFk
WHERE s.ticketFk = vTicketFk
AND (cr.isRenewable = FALSE
OR
(NOT vHasChangeAll
AND (NOT (cr.componentTypeRate <=> vAddressTypeRateFk
OR cr.componentTypeRate <=> vAgencyModeTypeRateFk))));
SET @shipped = vShipped;
DROP TEMPORARY TABLE
tmp.buyUltimate,
tmp.ticketLot;
IF vShipped IS NULL THEN
CALL util.throw('NO_ZONE_AVAILABLE');
END IF;
IF vShipped < CURDATE() THEN
CALL util.throw('ERROR_PAST_SHIPMENT');
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,52 @@
DROP procedure IF EXISTS `vn`.`ticket_priceDifference`;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`ticket_priceDifference`(
vTicketFk INT,
vLanded DATE,
vAddressFk INT,
vZoneFk INT,
vWarehouseFk INT)
BEGIN
/**
* Devuelve las diferencias de precio
* de los movimientos de un ticket.
*
* @param vTicketFk Id del ticket
* @param vLanded Fecha de recepcion
* @param vAddressFk Id del consignatario
* @param vZoneFk Id de la zona
* @param vWarehouseFk Id del almacén
*/
CALL vn.ticket_componentPreview(vTicketFk, vLanded, vAddressFk, vZoneFk, vWarehouseFk);
SELECT s.itemFk,
i.name,
i.size,
i.category,
IFNULL(s.quantity, 0) AS quantity,
IFNULL(s.price, 0) AS price,
ROUND(SUM(tc.cost), 2) AS newPrice,
s.quantity * (s.price - ROUND(SUM(tc.cost), 2)) difference,
s.id AS saleFk
FROM sale s
JOIN item i ON i.id = s.itemFk
JOIN ticket t ON t.id = s.ticketFk
LEFT JOIN tmp.ticketComponent tc ON tc.itemFk = s.itemFk
AND tc.warehouseFk = t.warehouseFk
LEFT JOIN saleComponent sc ON sc.saleFk = s.id
AND sc.componentFk = tc.componentFk
LEFT JOIN componentRate cr ON cr.id = tc.componentFk
WHERE
t.id = vTicketFk
AND IF(sc.componentFk IS NULL
AND cr.classRate IS NOT NULL, FALSE, TRUE)
GROUP BY s.id ORDER BY s.id;
DROP TEMPORARY TABLE
tmp.ticketComponent,
tmp.ticketComponentPrice;
END$$
DELIMITER ;

View File

@ -1448,6 +1448,14 @@ INSERT INTO `vn`.`ticketRequest`(`id`, `description`, `requesterFk`, `atenderFk`
(3, 'Melee weapon heavy shield 1x0.5m', 18, 35, 20, 4, 3.06, 0, NULL, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
(4, 'Melee weapon combat first 15cm', 18, 35, 15, NULL, 1.30, NULL, NULL, 11, CURDATE());
INSERT INTO `vn`.`ticketServiceType`(`id`, `name`)
VALUES
(1, 'Porte Agencia'),
(2, 'Portes Retorno'),
(3, 'Porte Carry'),
(4, 'Cargo FITOSANITARIO'),
(5, 'Documentos');
INSERT INTO `vn`.`ticketService`(`id`, `description`, `quantity`, `price`, `taxClassFk`, `ticketFk`)
VALUES
(1, 'Documentos', 1, 2.00, 1, 1),

View File

@ -441,7 +441,8 @@ export default {
basicDataButton: 'vn-left-menu a[ui-sref="ticket.card.basicData.stepOne"]',
clientAutocomplete: 'vn-autocomplete[field="$ctrl.clientFk"]',
addressAutocomplete: 'vn-autocomplete[field="$ctrl.ticket.addressFk"]',
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.ticket.agencyModeFk"]',
agencyAutocomplete: 'vn-autocomplete[field="$ctrl.agencyModeId"]',
zoneAutocomplete: 'vn-autocomplete[field="$ctrl.zoneId"]',
nextStepButton: 'vn-step-control > section > section.buttons > section:nth-child(2) > vn-button',
finalizeButton: 'vn-step-control > section > section.buttons > section:nth-child(2) > vn-submit',
stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > form > vn-card > div > vn-horizontal > table > tfoot > tr > td:nth-child(4)',

View File

@ -14,6 +14,7 @@ describe('Ticket Edit basic data path', () => {
it(`should edit the ticket agency then click next`, async() => {
let url = await nightmare
.autocompleteSearch(selectors.ticketBasicData.agencyAutocomplete, 'Silla247Expensive')
.waitToGetProperty(`${selectors.ticketBasicData.zoneAutocomplete} input`, 'value')
.waitToClick(selectors.ticketBasicData.nextStepButton)
.waitForURL('data/step-two')
.parsedUrl();

View File

@ -92,8 +92,6 @@ export default class Autocomplete extends Input {
set field(value) {
this._field = value;
if (!value) return;
this.refreshSelection();
this.emit('change', {value});
}

View File

@ -198,7 +198,9 @@ async function backendStatus() {
timer = setInterval(() => {
const url = `${e2eConfig.url}/api/Applications/status`;
request.get(url, (err, res) => {
if (res.body == 'true') {
if (err || attempts > 100) // 250ms * 100 => 25s timeout
throw new Error('Could not connect to backend');
else if (res && res.body == 'true') {
clearInterval(timer);
resolve(attempts);
} else

View File

@ -21,7 +21,6 @@
"Cannot check Equalization Tax in this NIF/CIF": "Cannot check Equalization Tax in this NIF/CIF",
"You can't create an order for a frozen client": "You can't create an order for a frozen client",
"This address doesn't exist": "This address doesn't exist",
"NO_AGENCY_AVAILABLE": "NO_AGENCY_AVAILABLE",
"Warehouse cannot be blank": "Warehouse cannot be blank",
"Agency cannot be blank": "Agency cannot be blank",
"The IBAN does not have the correct format": "The IBAN does not have the correct format",

View File

@ -82,7 +82,8 @@
"INFINITE_LOOP": "Existe una dependencia entre dos Jefes",
"The sales of the current ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas",
"The sales of the receiver ticket can't be modified": "Las lineas del ticket al que envias no pueden ser modificadas",
"NO_AGENCY_AVAILABLE": "No hay agencias disponibles",
"NO_AGENCY_AVAILABLE": "No hay una zona de reparto disponible con estos parámetros",
"ERROR_PAST_SHIPMENT": "No puedes seleccionar una fecha de envío en pasado",
"The current ticket can't be modified": "El ticket actual no puede ser modificado",
"The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas",
"Please select at least one sale": "Por favor selecciona al menos una linea",
@ -95,5 +96,6 @@
"This item is not available": "Este artículo no está disponible",
"This postcode already exists": "Este código postal ya existe",
"Concept cannot be blank": "El concepto no puede quedar en blanco",
"File doesn't exists": "El archivo no existe"
"File doesn't exists": "El archivo no existe",
"You don't have privileges to change the zone": "No tienes permisos para cambiar la zona"
}

View File

@ -1,14 +1,10 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethodCtx('getLanded', {
Self.remoteMethod('getLanded', {
description: 'Returns the first shipped and landed possible for params',
accessType: 'READ',
accepts: [{
arg: 'params',
type: 'Object',
description: `shipped, addressFk, agencyModeFk, warehouseFk`
}, {
arg: 'shipped',
type: 'date',
required: true
@ -38,15 +34,14 @@ module.exports = Self => {
}
});
Self.getLanded = async(ctx, params) => {
Self.getLanded = async(shipped, addressFk, agencyModeFk, warehouseFk) => {
let stmts = [];
params = params || ctx.args;
stmts.push(new ParameterizedSQL(
`CALL vn.zoneGetLanded(?, ?, ?, ?)`, [
params.shipped,
params.addressFk,
params.agencyModeFk,
params.warehouseFk
shipped,
addressFk,
agencyModeFk,
warehouseFk
]
));

View File

@ -1,12 +1,8 @@
module.exports = Self => {
Self.remoteMethodCtx('getShipped', {
Self.remoteMethod('getShipped', {
description: 'Returns the first shipped possible for params',
accessType: 'READ',
accepts: [{
arg: 'params',
type: 'Object',
description: `landed, addressFk, agencyModeFk, warehouseFk`
}, {
arg: 'landed',
type: 'date',
required: true
@ -36,15 +32,14 @@ module.exports = Self => {
}
});
Self.getShipped = async(ctx, params)=> {
params = params || ctx.args;
Self.getShipped = async(landed, addressFk, agencyModeFk, warehouseFk)=> {
let query = `CALL vn.zoneGetShipped(?, ?, ?, ?)`;
let [response] = await Self.rawSql(query, [
params.landed,
params.addressFk,
params.agencyModeFk,
params.warehouseFk
let [[response]] = await Self.rawSql(query, [
landed,
addressFk,
agencyModeFk,
warehouseFk
]);
return (response[0] && response[0].shipped && response[0].shipped.toJSON()) || null;
return response;
};
};

View File

@ -2,13 +2,11 @@ const app = require('vn-loopback/server/server');
describe('agency getLanded()', () => {
it('should return a landing date', async() => {
let data = {
shipped: new Date(),
addressFk: 121,
agencyModeFk: 7,
warehouseFk: 1
};
let result = await app.models.Agency.getLanded({}, data);
const shipped = new Date();
const addressFk = 121;
const agencyModeFk = 7;
const warehouseFk = 1;
let result = await app.models.Agency.getLanded(shipped, addressFk, agencyModeFk, warehouseFk);
expect(result.landed).toBeDefined();
});

View File

@ -2,13 +2,12 @@ const app = require('vn-loopback/server/server');
describe('agency getShipped()', () => {
it('should return a shipment date', async() => {
let data = {
landed: new Date(),
addressFk: 121,
agencyModeFk: 7,
warehouseFk: 1
};
let result = await app.models.Agency.getShipped({}, data);
const landed = new Date();
const addressFk = 121;
const agencyModeFk = 7;
const warehouseFk = 1;
let result = await app.models.Agency.getShipped(landed, addressFk, agencyModeFk, warehouseFk);
expect(result).toBeDefined();
});
@ -17,14 +16,13 @@ describe('agency getShipped()', () => {
let newDate = new Date();
newDate.setMonth(newDate.getMonth() - 1);
let data = {
landed: newDate,
addressFk: 121,
agencyModeFk: 7,
warehouseFk: 1
};
let result = await app.models.Agency.getShipped({}, data);
const landed = newDate;
const addressFk = 121;
const agencyModeFk = 7;
const warehouseFk = 1;
expect(result).toBeNull();
let result = await app.models.Agency.getShipped(landed, addressFk, agencyModeFk, warehouseFk);
expect(result).toBeUndefined();
});
});

View File

@ -43,7 +43,7 @@ module.exports = Self => {
r.bankFk,
u.nickname userNickname,
r.clientFk,
FALSE pdf,
FALSE hasPdf,
FALSE isInvoice
FROM vn.receipt r
LEFT JOIN vn.worker w ON w.id = r.workerFk
@ -64,7 +64,7 @@ module.exports = Self => {
NULL,
NULL,
i.clientFk,
i.pdf,
i.hasPdf,
TRUE isInvoice
FROM vn.invoiceOut i
JOIN vn.company c ON c.id = i.companyFk

View File

@ -83,7 +83,7 @@
</vn-check>
</vn-td>
<vn-td center>
<a ng-show="balance.pdf"
<a ng-show="balance.hasPdf"
target="_blank"
href="api/InvoiceOuts/{{::balance.id}}/download?access_token={{::$ctrl.accessToken}}">
<vn-icon-button

View File

@ -83,7 +83,7 @@ module.exports = Self => {
case 'max':
return {amount: {lte: value}};
case 'hasPdf':
return {'i.pdf': value};
return {'i.hasPdf': value};
case 'created':
return {'i.created': value};
case 'amount':
@ -109,7 +109,7 @@ module.exports = Self => {
i.created,
i.dued,
i.clientFk,
i.pdf AS hasPdf,
i.hasPdf,
c.socialName AS clientSocialName,
co.code AS companyCode
FROM invoiceOut i

View File

@ -1,75 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('priceDifference', {
description: 'Returns sales with price difference if the ticket is editable',
accessType: 'READ',
accepts: [{
arg: 'ticketFk',
type: 'number',
required: true,
description: 'ticket id',
http: {source: 'path'}
}, {
arg: 'data',
type: 'Object',
required: true,
description: 'landed, addressFk, agencyModeFk',
http: {source: 'body'}
}],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/:ticketFk/priceDifference`,
verb: 'post'
}
});
Self.priceDifference = async(ctx, ticketFk, data) => {
let thisTicketIsEditable = await Self.app.models.Ticket.isEditable(ctx, ticketFk);
if (!thisTicketIsEditable)
throw new UserError(`The sales of this ticket can't be modified`);
let filter = {
where: {
ticketFk: ticketFk
},
order: 'concept ASC',
include: [{
relation: 'item'
}]
};
let salesObj = {};
salesObj.items = await Self.find(filter);
salesObj.totalUnitPrice = 0.00;
salesObj.totalNewPrice = 0.00;
salesObj.totalDifference = 0.00;
let query = `CALL vn.ticketComponentPriceDifference(?, ?, ?, ?, ?)`;
let [differences] = await Self.rawSql(query, [
ticketFk,
data.landed,
data.addressFk,
data.agencyModeFk,
data.warehouseFk
]);
salesObj.items.forEach(sale => {
differences.forEach(difference => {
if (sale.id == difference.saleFk)
sale.component = difference;
});
salesObj.totalUnitPrice += sale.price;
salesObj.totalNewPrice += sale.component.newPrice;
salesObj.totalDifference += sale.component.difference;
salesObj.totalUnitPrice = Math.round(salesObj.totalUnitPrice * 100) / 100;
salesObj.totalNewPrice = Math.round(salesObj.totalNewPrice * 100) / 100;
salesObj.totalDifference = Math.round(salesObj.totalDifference * 100) / 100;
});
return salesObj;
};
};

View File

@ -1,56 +1,116 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('componentUpdate', {
Self.remoteMethodCtx('componentUpdate', {
description: 'Save ticket sale components',
accessType: 'WRITE',
accepts: [{
arg: 'ticketFk',
type: 'number',
arg: 'id',
type: 'Number',
required: true,
description: 'ticket id',
description: 'The ticket id',
http: {source: 'path'}
}, {
arg: 'data',
type: 'Object',
required: true,
description: 'landed, addressFk, agencyModeFk, warehouseFk',
http: {source: 'body'}
arg: 'clientId',
type: 'Number',
description: 'The client id',
required: true
}, {
arg: 'context',
type: 'object',
http: function(ctx) {
return ctx;
}
arg: 'agencyModeId',
type: 'Number',
description: 'The agencyMode id',
required: true
}, {
arg: 'addressId',
type: 'Number',
description: 'The address id',
required: true
}, {
arg: 'zoneId',
type: 'Number',
description: 'The zone id',
required: true
}, {
arg: 'warehouseId',
type: 'Number',
description: 'The warehouse id',
required: true
}, {
arg: 'companyId',
type: 'Number',
description: 'The company id',
required: true
}, {
arg: 'shipped',
type: 'Date',
description: 'The shipped date',
required: true
}, {
arg: 'landed',
type: 'Date',
description: 'The landing date',
required: true
}, {
arg: 'isDeleted',
type: 'Boolean',
description: 'Ticket is deleted',
required: true
}, {
arg: 'hasToBeUnrouted',
type: 'Boolean',
description: 'Ticket should be removed from ticket',
required: true
}, {
arg: 'option',
type: 'Number',
description: 'Action id',
required: true
}],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/:ticketFk/componentUpdate`,
path: `/:id/componentUpdate`,
verb: 'post'
}
});
Self.componentUpdate = async(ticketFk, data, ctx) => {
let userId = ctx.req.accessToken.userId;
let hasDeliveryRole = await Self.app.models.Account.hasRole(userId, 'delivery');
Self.componentUpdate = async(ctx, id, clientId, agencyModeId, addressId, zoneId, warehouseId,
companyId, shipped, landed, isDeleted, hasToBeUnrouted, option) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const isEditable = await models.Ticket.isEditable(ctx, id);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
const hasDeliveryRole = await models.Account.hasRole(userId, 'delivery');
if (!hasDeliveryRole)
data.hasToBeUnrouted = true;
hasToBeUnrouted = true;
let query = 'CALL vn.ticketComponentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss');
if (!isProductionBoss) {
const zone = await models.Agency.getShipped(landed, addressId, agencyModeId, warehouseId);
if (zone.id != zoneId)
throw new UserError(`You don't have privileges to change the zone`);
}
let query = 'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
let res = await Self.rawSql(query, [
ticketFk,
data.clientFk,
data.agencyModeFk,
data.addressFk,
data.warehouseFk,
data.companyFk,
data.shipped,
data.landed,
data.isDeleted,
data.hasToBeUnrouted,
data.option
id,
clientId,
agencyModeId,
addressId,
zoneId,
warehouseId,
companyId,
shipped,
landed,
isDeleted,
hasToBeUnrouted,
option
]);
return res;
};

View File

@ -20,7 +20,7 @@ module.exports = Self => {
});
Self.isEditable = async(ctx, ticketFk) => {
const accessToken = ctx.active && ctx.active.accessToken || ctx.req && ctx.req.accessToken;
const accessToken = ctx.req.accessToken;
const userId = accessToken.userId;
let state = await Self.app.models.TicketState.findOne({
where: {ticketFk: ticketFk}

View File

@ -61,21 +61,14 @@ module.exports = Self => {
try {
if (!params.shipped && params.landed) {
params.shipped = await models.Agency.getShipped(ctx, {
landed: params.landed,
addressFk: address.id,
agencyModeFk: params.agencyModeFk,
warehouseFk: params.warehouseFk
});
const shippedResult = await models.Agency.getShipped(params.landed,
address.id, params.agencyModeFk, params.warehouseFk);
params.shipped = shippedResult.shipped;
}
if (params.shipped && !params.landed) {
const landedResult = await models.Agency.getLanded(ctx, {
shipped: params.shipped,
addressFk: address.id,
agencyModeFk: params.agencyModeFk,
warehouseFk: params.warehouseFk
});
const landedResult = await models.Agency.getLanded(params.shipped,
address.id, params.agencyModeFk, params.warehouseFk);
params.landed = landedResult.landed;
}

View File

@ -0,0 +1,110 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('priceDifference', {
description: 'Returns sales with price difference if the ticket is editable',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'Number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'landed',
type: 'Date',
description: 'The landing date',
required: true
},
{
arg: 'addressId',
type: 'Number',
description: 'The address id',
required: true
},
{
arg: 'agencyModeId',
type: 'Number',
description: 'The agencyMode id',
required: true
},
{
arg: 'zoneId',
type: 'Number',
description: 'The zone id',
required: true
},
{
arg: 'warehouseId',
type: 'Number',
description: 'The warehouse id',
required: true
}],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/:id/priceDifference`,
verb: 'POST'
}
});
Self.priceDifference = async(ctx, id, landed, addressId, agencyModeId, zoneId, warehouseId) => {
const models = Self.app.models;
const isEditable = await models.Ticket.isEditable(ctx, id);
const userId = ctx.req.accessToken.userId;
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss');
if (!isProductionBoss) {
const zone = await models.Agency.getShipped(landed, addressId, agencyModeId, warehouseId);
if (zone.id != zoneId)
throw new UserError(`You don't have privileges to change the zone`);
}
let salesObj = {};
salesObj.items = await models.Sale.find({
where: {
ticketFk: id
},
order: 'concept ASC',
include: [{
relation: 'item'
}]
});
salesObj.totalUnitPrice = 0.00;
salesObj.totalNewPrice = 0.00;
salesObj.totalDifference = 0.00;
const query = `CALL vn.ticket_priceDifference(?, ?, ?, ?, ?)`;
const args = [id, landed, addressId, zoneId, warehouseId];
const [difComponents] = await Self.rawSql(query, args);
const map = new Map();
difComponents.forEach(difComponent => {
map.set(difComponent.saleFk, difComponent);
});
salesObj.items.forEach(sale => {
const difComponent = map.get(sale.id);
if (difComponent)
sale.component = difComponent;
salesObj.totalUnitPrice += sale.price;
salesObj.totalNewPrice += sale.component.newPrice;
salesObj.totalDifference += sale.component.difference;
salesObj.totalUnitPrice = Math.round(salesObj.totalUnitPrice * 100) / 100;
salesObj.totalNewPrice = Math.round(salesObj.totalNewPrice * 100) / 100;
salesObj.totalDifference = Math.round(salesObj.totalDifference * 100) / 100;
});
return salesObj;
};
};

View File

@ -28,22 +28,22 @@ describe('ticket componentUpdate()', () => {
});
it('should change the agencyMode to modify the sale components value', async() => {
let data = {
clientFk: 102,
agencyModeFk: 8,
addressFk: 122,
warehouseFk: 1,
companyFk: 442,
shipped: today,
landed: tomorrow,
isDeleted: false,
hasToBeUnrouted: false,
option: 1
};
const clientId = 102;
const addressId = 122;
const agencyModeId = 8;
const warehouseId = 1;
const zoneId = 5;
const shipped = today;
const companyId = 442;
const isDeleted = false;
const landed = tomorrow;
const hasToBeUnrouted = false;
const option = 1;
let ctx = {req: {accessToken: {userId: 101}}};
await app.models.Ticket.componentUpdate(ticketId, data, ctx);
await app.models.Ticket.componentUpdate(ctx, ticketId, clientId, agencyModeId, addressId,
zoneId, warehouseId, companyId, shipped, landed, isDeleted, hasToBeUnrouted, option);
[componentValue] = await app.models.SaleComponent.rawSql(componentOfSaleSeven);
let firstvalueAfterChange = componentValue.value;
@ -56,21 +56,21 @@ describe('ticket componentUpdate()', () => {
});
it('should change the agencyMode to go back to the originals sale components value', async() => {
let data = {
clientFk: 102,
agencyModeFk: 7,
addressFk: 122,
warehouseFk: 1,
companyFk: 442,
shipped: today,
landed: tomorrow,
isDeleted: false,
hasToBeUnrouted: false,
option: 1
};
const clientId = 102;
const addressId = 122;
const agencyModeId = 7;
const warehouseId = 1;
const zoneId = 3;
const shipped = today;
const companyId = 442;
const isDeleted = false;
const landed = tomorrow;
const hasToBeUnrouted = false;
const option = 1;
let ctx = {req: {accessToken: {userId: 101}}};
await app.models.Ticket.componentUpdate(ticketId, data, ctx);
await app.models.Ticket.componentUpdate(ctx, ticketId, clientId, agencyModeId, addressId,
zoneId, warehouseId, companyId, shipped, landed, isDeleted, hasToBeUnrouted, option);
[componentValue] = await app.models.SaleComponent.rawSql(componentOfSaleSeven);
let firstvalueAfterChange = componentValue.value;

View File

@ -3,16 +3,19 @@ let UserError = require('vn-loopback/util/user-error');
describe('sale priceDifference()', () => {
it('should return ticket price differences', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
let data = {
landed: tomorrow,
addressFk: 126,
agencyModeFk: 7,
warehouseFk: 1
};
let result = await app.models.Sale.priceDifference(ctx, 16, data);
const ticketId = 16;
const landed = tomorrow;
const addressId = 126;
const agencyModeId = 7;
const zoneId = 3;
const warehouseId = 1;
const httpCtx = {req: {accessToken: {userId: 106}}};
let result = await app.models.Ticket.priceDifference(httpCtx, ticketId, landed,
addressId, agencyModeId, zoneId, warehouseId);
expect(result.totalUnitPrice).toEqual(215.77);
expect(result.totalNewPrice).toEqual(215.77);
@ -20,15 +23,14 @@ describe('sale priceDifference()', () => {
});
it('should return an error if the ticket is not editable', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
const ticketId = 1;
const landed = new Date();
const addressId = 121;
const zoneId = 3;
const warehouseId = 1;
let error;
let data = {
landed: new Date(),
addressFk: 121,
agencyModeFk: 1,
warehouseFk: 1
};
await app.models.Sale.priceDifference(ctx, 1, data)
const httpCtx = {req: {accessToken: {userId: 106}}};
await app.models.Ticket.priceDifference(httpCtx, ticketId, landed, addressId, zoneId, warehouseId)
.catch(e => {
error = e;
});

View File

@ -1,6 +1,5 @@
module.exports = Self => {
require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/priceDifference')(Self);
require('../methods/sale/moveToTicket')(Self);
require('../methods/sale/reserve')(Self);
require('../methods/sale/removes')(Self);

View File

@ -4,10 +4,12 @@ const LoopBackContext = require('loopback-context');
module.exports = Self => {
Self.observe('before save', async ctx => {
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const models = Self.app.models;
let changes = ctx.currentInstance || ctx.instance;
if (changes) {
let ticketId = changes.ticketFk;
let isEditable = await Self.app.models.Ticket.isEditable(loopBackContext, ticketId);
let isEditable = await models.Ticket.isEditable(httpCtx, ticketId);
if (!isEditable)
throw new UserError(`The current ticket can't be modified`);
}
@ -15,9 +17,10 @@ module.exports = Self => {
Self.observe('before delete', async ctx => {
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const models = Self.app.models;
const service = await models.TicketService.findById(ctx.where.id);
const isEditable = await Self.app.models.Ticket.isEditable(loopBackContext, service.ticketFk);
const isEditable = await models.Ticket.isEditable(httpCtx, service.ticketFk);
if (!isEditable)
throw new UserError(`The current ticket can't be modified`);

View File

@ -7,6 +7,7 @@ module.exports = Self => {
require('../methods/ticket/getTotal')(Self);
require('../methods/ticket/getTaxes')(Self);
require('../methods/ticket/subtotal')(Self);
require('../methods/ticket/priceDifference')(Self);
require('../methods/ticket/componentUpdate')(Self);
require('../methods/ticket/new')(Self);
require('../methods/ticket/isEditable')(Self);

View File

@ -1,3 +1,11 @@
<vn-crud-model
url="/api/Zones"
include="{relation: 'warehouse'}"
data="zones"
order="name"
auto-load="true">
</vn-crud-model>
<form name="form">
<vn-card pad-large>
<vn-horizontal>
@ -7,8 +15,8 @@
label="Client"
show-field="name"
value-field="id"
field="$ctrl.clientFk"
initial-data="$ctrl.clientFk"
field="$ctrl.clientId"
initial-data="$ctrl.clientId"
order="id">
</vn-autocomplete>
<vn-autocomplete vn-one
@ -24,12 +32,12 @@
</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-one
url="/api/AgencyModes/isActive"
label="Agency"
url="/api/Warehouses"
label="Warehouse"
show-field="name"
value-field="id"
field="$ctrl.ticket.agencyModeFk"
initial-data="$ctrl.ticket.agencyModeFk">
field="$ctrl.ticket.warehouseFk"
initial-data="$ctrl.ticket.warehouseFk">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
@ -53,16 +61,29 @@
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/api/Warehouses"
label="Warehouse"
url="/api/AgencyModes/isActive"
label="Agency"
show-field="name"
value-field="id"
field="$ctrl.ticket.warehouseFk"
initial-data="$ctrl.ticket.warehouseFk">
field="$ctrl.agencyModeId">
</vn-autocomplete>
<vn-one>
<vn-check label="Deleted" field="$ctrl.ticket.isDeleted"></vn-check>
</vn-one>
<vn-autocomplete vn-one
data="zones"
label="Zone"
show-field="name"
value-field="id"
field="$ctrl.zoneId"
vn-acl="productionBoss">
<tpl-item>
<span>{{::name}} - {{::warehouse.name}} - Max. {{::hour | dateTime: 'HH:mm'}} h.</span>
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-check vn-one
label="Deleted"
field="$ctrl.ticket.isDeleted">
</vn-check>
</vn-horizontal>
</vn-card>
</form>

View File

@ -10,7 +10,11 @@ class Controller {
}
$onInit() {
this.data.registerChild(this);
this.basicData.registerChild(this);
}
get ticket() {
return this._ticket;
}
set ticket(value) {
@ -18,27 +22,23 @@ class Controller {
if (!value || !value.id) return;
this.onChangeAddress(value.clientFk);
this.onChangeClient(value.clientFk);
}
get ticket() {
return this._ticket;
}
set clientFk(value) {
this.ticket.clientFk = value;
this.ticket.addressFk = null;
this.onChangeAddress(value);
}
get clientFk() {
get clientId() {
if (this.ticket)
return this.ticket.clientFk;
return null;
}
set clientId(value) {
this.ticket.clientFk = value;
this.ticket.addressFk = null;
this.onChangeClient(value);
}
set shipped(value) {
this.ticket.shipped = value;
this.onChangeShipped(value);
@ -51,11 +51,6 @@ class Controller {
return null;
}
set landed(value) {
this.ticket.landed = value;
this.onChangeLanded(value);
}
get landed() {
if (this.ticket)
return this.ticket.landed;
@ -63,47 +58,43 @@ class Controller {
return null;
}
onChangeShipped(value) {
let params = {
shipped: value,
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk
};
let query = `/api/Agencies/getLanded`;
this.$http.get(query, {params}).then(res => {
if (res.data && res.data.landed)
this.ticket.landed = res.data.landed;
else {
return this.vnApp.showError(
this.$translate.instant(`There's no available agency for this shipping date`)
);
}
});
set landed(value) {
this.ticket.landed = value;
this.onChangeLanded(value);
}
onChangeLanded(value) {
let params = {
landed: value,
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk
};
get agencyModeId() {
if (this.ticket)
return this.ticket.agencyModeFk;
let query = `/api/Agencies/getShipped`;
this.$http.get(query, {params}).then(res => {
if (res.data)
this.ticket.shipped = res.data;
else {
return this.vnApp.showError(
this.$translate.instant(`There's no available agency for this landing date`)
);
}
});
return null;
}
onChangeAddress(value) {
set agencyModeId(value) {
this.ticket.agencyModeFk = value;
if (value)
this.onChangeAgencyMode(value);
}
get zoneId() {
if (this.ticket)
return this.ticket.zoneFk;
return null;
}
set zoneId(value) {
this.ticket.zoneFk = value;
if (value)
this.onChangeZone(value);
}
/*
* Autocompletes address on client change
*/
onChangeClient(value) {
let filter = {
include: [
{
@ -129,6 +120,96 @@ class Controller {
});
}
/*
* Returns a landing date
*/
getLanded(params) {
let query = `/api/Agencies/getLanded`;
return this.$http.get(query, {params});
}
/*
* Returns a shipment date
*/
getShipped(params) {
let query = `/api/Agencies/getShipped`;
return this.$http.get(query, {params});
}
onChangeShipped(shipped) {
let params = {
shipped: shipped,
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk
};
let query = `/api/Agencies/getLanded`;
this.$http.get(query, {params}).then(res => {
if (res.data && res.data.landed)
this.ticket.landed = res.data.landed;
else {
return this.vnApp.showError(
this.$translate.instant(`No delivery zone available for this shipping date`)
);
}
});
}
onChangeLanded(landed) {
let params = {
landed: landed,
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk
};
let query = `/api/Agencies/getShipped`;
this.$http.get(query, {params}).then(res => {
if (res.data)
this.ticket.shipped = res.data.shipped;
else {
return this.vnApp.showError(
this.$translate.instant(`No delivery zone available for this landing date`)
);
}
});
}
/*
* Gets an agency from an specified zone
*/
onChangeZone(zoneId) {
const query = `/api/Zones/${zoneId}`;
this.$http.get(query).then(res => {
if (res.data)
this.ticket.agencyModeFk = res.data.agencyModeFk;
});
}
/*
* Gets a zone from an agency
*/
onChangeAgencyMode(agencyModeId) {
let params = {
landed: this.ticket.landed,
addressFk: this.ticket.addressFk,
agencyModeFk: agencyModeId,
warehouseFk: this.ticket.warehouseFk
};
this.ticket.zoneFk = null;
let query = `/api/Agencies/getShipped`;
this.$http.get(query, {params}).then(res => {
if (res.data)
this.ticket.zoneFk = res.data.id;
else {
return this.vnApp.showMessage(
this.$translate.instant('No delivery zone available for this parameters')
);
}
});
}
async onStepChange() {
if (this.isFormInvalid()) {
return this.vnApp.showError(
@ -136,35 +217,31 @@ class Controller {
);
}
let query = `/ticket/api/sales/${this.ticket.id}/priceDifference`;
let data = {
let query = `/api/tickets/${this.ticket.id}/priceDifference`;
let params = {
landed: this.ticket.landed,
addressFk: this.ticket.addressFk,
agencyModeFk: this.ticket.agencyModeFk,
warehouseFk: this.ticket.warehouseFk
addressId: this.ticket.addressFk,
agencyModeId: this.ticket.agencyModeFk,
zoneId: this.ticket.zoneFk,
warehouseId: this.ticket.warehouseFk
};
return this.$http.post(query, data).then(res => {
return this.$http.post(query, params).then(res => {
if (res.data)
this.ticket.sale = res.data;
return true;
}, err => {
if (err.data.error.message === 'NO_AGENCY_AVAILABLE') {
this.vnApp.showMessage(
this.$translate.instant(`There's no available agency for this landing date`)
);
} else {
this.vnApp.showMessage(
this.$translate.instant(err.data.error.message)
);
}
this.vnApp.showError(
this.$translate.instant(err.data.error.message)
);
});
}
isFormInvalid() {
return !this.ticket.clientFk || !this.ticket.addressFk || !this.ticket.agencyModeFk
|| !this.ticket.companyFk || !this.ticket.shipped || !this.ticket.landed;
|| !this.ticket.companyFk || !this.ticket.shipped || !this.ticket.landed
|| !this.ticket.zoneFk;
}
}
@ -177,6 +254,6 @@ ngModule.component('vnTicketBasicDataStepOne', {
ticket: '<'
},
require: {
data: '^vnTicketBasicData'
basicData: '^vnTicketBasicData'
}
});

View File

@ -18,21 +18,21 @@ describe('Ticket', () => {
}));
describe('ticket() setter', () => {
it('should set ticket property and call onChangeAddress() method', () => {
spyOn(controller, 'onChangeAddress');
it('should set ticket property and call onChangeClient() method', () => {
spyOn(controller, 'onChangeClient');
controller.ticket = {id: 1, clientFk: 101};
expect(controller.onChangeAddress).toHaveBeenCalledWith(101);
expect(controller.onChangeClient).toHaveBeenCalledWith(101);
});
});
describe('clientFk() setter', () => {
it('should set clientFk property and call onChangeAddress() method ', () => {
spyOn(controller, 'onChangeAddress');
controller.ticket = {id: 1, clientFk: 101};
controller.clientFk = 102;
describe('clientId() setter', () => {
it('should set clientId property and call onChangeClient() method ', () => {
spyOn(controller, 'onChangeClient');
controller.ticket = {id: 1, clientId: 101};
controller.clientId = 102;
expect(controller.onChangeAddress).toHaveBeenCalledWith(102);
expect(controller.onChangeClient).toHaveBeenCalledWith(102);
});
});
@ -58,6 +58,57 @@ describe('Ticket', () => {
});
});
describe('agencyModeId() setter', () => {
it('should set agencyModeId property and call onChangeAgencyMode() method ', () => {
const agencyModeId = 8;
spyOn(controller, 'onChangeAgencyMode');
controller.ticket = {id: 1};
controller.agencyModeId = 8;
expect(controller.onChangeAgencyMode).toHaveBeenCalledWith(agencyModeId);
});
});
describe('zoneId() setter', () => {
it('should set zoneId property and call onChangeZone() method ', () => {
const zoneId = 5;
spyOn(controller, 'onChangeZone');
controller.ticket = {id: 1};
controller.zoneId = 5;
expect(controller.onChangeZone).toHaveBeenCalledWith(zoneId);
});
});
describe('onChangeClient()', () => {
it('should return a list of addresses from choosed client', async() => {
const clientId = 102;
let filter = {
include: [
{
relation: 'province',
scope: {
fields: ['name']
}
},
{
relation: 'agencyMode',
scope: {
fields: ['name']
}
}
]
};
filter = encodeURIComponent(JSON.stringify(filter));
$httpBackend.when('GET', `/api/Clients/${clientId}/addresses?filter=${filter}`).respond(200);
$httpBackend.expect('GET', `/api/Clients/${clientId}/addresses?filter=${filter}`);
controller.onChangeClient(clientId);
$httpBackend.flush();
});
});
describe('onChangeShipped()', () => {
it('should return an available landing date', async() => {
let shipped = new Date();
@ -111,11 +162,51 @@ describe('Ticket', () => {
});
});
describe('onChangeZone()', () => {
it('should return an available zone', async() => {
const zoneId = 5;
$httpBackend.when('GET', `/api/Zones/${zoneId}`).respond(200);
$httpBackend.expect('GET', `/api/Zones/${zoneId}`);
controller.onChangeZone(zoneId);
$httpBackend.flush();
});
});
describe('onChangeAgencyMode()', () => {
it('should return an available agency', async() => {
const landed = new Date();
const agencyModeId = 7;
controller._ticket = {
id: 1,
landed: landed,
addressFk: 121,
agencyModeFk: agencyModeId,
warehouseFk: 1
};
let params = {
landed: landed,
addressFk: 121,
agencyModeFk: agencyModeId,
warehouseFk: 1
};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `/api/Agencies/getShipped?${serializedParams}`).respond(200);
$httpBackend.expect('GET', `/api/Agencies/getShipped?${serializedParams}`);
controller.onChangeAgencyMode(agencyModeId);
$httpBackend.flush();
});
});
describe('isFormInvalid()', () => {
it('should check if all form fields are valid', () => {
controller.ticket = {
clientFk: 1,
addressFk: 121,
zoneFk: 3,
agencyModeFk: 1,
companyFk: 442,
warehouseFk: 1,
@ -136,6 +227,7 @@ describe('Ticket', () => {
id: 1,
clientFk: 1,
addressFk: 121,
zoneFk: 3,
agencyModeFk: 1,
companyFk: 442,
warehouseFk: 1,
@ -145,8 +237,8 @@ describe('Ticket', () => {
let response = {error: new Error('NO_AGENCY_AVAILABLE')};
$httpBackend.whenPOST(`/ticket/api/sales/1/priceDifference`).respond(400, response);
$httpBackend.expectPOST(`/ticket/api/sales/1/priceDifference`);
$httpBackend.whenPOST(`/api/tickets/1/priceDifference`).respond(400, response);
$httpBackend.expectPOST(`/api/tickets/1/priceDifference`);
controller.onStepChange();
$httpBackend.flush();
});

View File

@ -1,3 +1,5 @@
There's no available agency for this landing date: No hay ninguna agencia disponible para la fecha de envío seleccionada
No delivery zone available for this landing date: No hay una zona de reparto disponible para la fecha de envío seleccionada
No delivery zone available for this shipping date: No hay una zona de reparto disponible para la fecha de preparación seleccionada
No delivery zone available for this parameters: No hay una zona de reparto disponible con estos parámetros
Deleted: Eliminado
There's no available agency for this shipping date: No hay ninguna agencia disponible para la fecha de preparación seleccionada
Zone: Zona

View File

@ -29,13 +29,14 @@ class Controller {
);
}
let query = `/ticket/api/tickets/${this.ticket.id}/componentUpdate`;
let data = {
clientFk: this.ticket.clientFk,
agencyModeFk: this.ticket.agencyModeFk,
addressFk: this.ticket.addressFk,
warehouseFk: this.ticket.warehouseFk,
companyFk: this.ticket.companyFk,
let query = `/api/tickets/${this.ticket.id}/componentUpdate`;
let params = {
clientId: this.ticket.clientFk,
agencyModeId: this.ticket.agencyModeFk,
addressId: this.ticket.addressFk,
zoneId: this.ticket.zoneFk,
warehouseId: this.ticket.warehouseFk,
companyId: this.ticket.companyFk,
shipped: this.ticket.shipped,
landed: this.ticket.landed,
isDeleted: this.ticket.isDeleted,
@ -43,7 +44,7 @@ class Controller {
option: this.ticket.option
};
this.$http.post(query, data).then(res => {
this.$http.post(query, params).then(res => {
if (res.data) {
this.$state.go('ticket.card.summary', {id: this.$state.params.id});
this.card.reload();

View File

@ -36,6 +36,7 @@ describe('ticket', () => {
id: 1,
agencyModeFk: 1,
addressFk: 121,
zoneFk: 3,
warehouseFk: 1,
shipped: now,
landed: now,
@ -43,16 +44,17 @@ describe('ticket', () => {
};
let data = {
agencyModeFk: 1,
addressFk: 121,
warehouseFk: 1,
agencyModeId: 1,
addressId: 121,
zoneId: 3,
warehouseId: 1,
shipped: now,
landed: now,
option: 1
};
$httpBackend.whenPOST(`/ticket/api/tickets/1/componentUpdate`, data).respond('ok');
$httpBackend.expectPOST(`/ticket/api/tickets/1/componentUpdate`, data);
$httpBackend.whenPOST(`/api/tickets/1/componentUpdate`, data).respond('ok');
$httpBackend.expectPOST(`/api/tickets/1/componentUpdate`, data);
controller.onSubmit();
$httpBackend.flush();