Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2615-waste_detail

This commit is contained in:
Joan Sanchez 2021-04-01 08:42:02 +02:00
commit 41ee6e70a5
43 changed files with 6619 additions and 4733 deletions

View File

@ -20,17 +20,37 @@ module.exports = Self => {
} }
}); });
Self.removeFile = async(ctx, id) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models; let tx;
const dms = await models.Dms.findById(id); let myOptions = {};
const trashDmsType = await models.DmsType.findOne({
where: {code: 'trash'}
});
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dms.dmsTypeFk); if (typeof options == 'object')
if (!hasWriteRole) Object.assign(myOptions, options);
throw new UserError(`You don't have enough privileges`);
return dms.updateAttribute('dmsTypeFk', trashDmsType.id); if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const models = Self.app.models;
const dms = await models.Dms.findById(id, null, myOptions);
const trashDmsType = await models.DmsType.findOne({
where: {code: 'trash'}
}, myOptions);
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dms.dmsTypeFk, myOptions);
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
await dms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions);
if (tx) await tx.commit();
return dms;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -8,25 +8,23 @@
}, },
"properties": { "properties": {
"id": { "id": {
"type": "Number", "type": "number",
"id": true, "id": true,
"description": "The id" "description": "The id"
}, },
"name": { "name": {
"type": "String", "type": "string",
"required": true "required": true
}, },
"collectionFk": { "collectionFk": {
"type": "String", "type": "string",
"required": true "required": true
}, },
"updated": { "updated": {
"type": "Number" "type": "number"
}, },
"nRefs": { "nRefs": {
"type": "Number", "type": "number"
"required": true,
"default": 1
} }
}, },
"relations": { "relations": {

View File

@ -1,4 +0,0 @@
ALTER TABLE `vn`.`department`
ADD code VARCHAR(45) NULL AFTER id;
UPDATE `vn`.`department` t SET t.code = 'IT', t.chatName = 'informatica-cau' WHERE t.id = 31;

View File

@ -1,2 +0,0 @@
ALTER TABLE `vn`.`itemImageQueue`
ADD attempts INT default 0 NULL AFTER error;

View File

@ -1,5 +0,0 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('SupplierAccount', '*', '*', 'ALLOW', 'ROLE', 'administrative'),
('Entry', '*', '*', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', '*', '*', 'ALLOW', 'ROLE', 'administrative');

View File

@ -1,137 +0,0 @@
DROP PROCEDURE `vn`.`item_getBalance`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`item_getBalance`(IN vItemId INT, IN vWarehouse INT)
BEGIN
DECLARE vDateInventory DATETIME;
DECLARE vCurdate DATE DEFAULT CURDATE();
DECLARE vDayEnd DATETIME DEFAULT util.dayEnd(vCurdate);
SELECT inventoried INTO vDateInventory FROM config;
SET @a = 0;
SET @currentLineFk = 0;
SET @shipped = '';
SELECT DATE(@shipped:= shipped) shipped,
alertLevel,
stateName,
origin,
reference,
clientFk,
name,
`in`,
`out`,
@a := @a + IFNULL(`in`,0) - IFNULL(`out`,0) as balance,
@currentLineFk := IF (@shipped < CURDATE()
OR (@shipped = CURDATE() AND (isPicked OR alertLevel >= 2)),
lineFk,@currentLineFk) lastPreparedLineFk,
isTicket,
lineFk,
isPicked,
clientType
FROM
( SELECT tr.landed AS shipped,
b.quantity AS `in`,
NULL AS `out`,
al.alertLevel AS alertLevel,
st.name AS stateName,
s.name AS name,
e.ref AS reference,
e.id AS origin,
s.id AS clientFk,
IF(al.alertLevel = 3, TRUE, FALSE) isPicked,
FALSE AS isTicket,
b.id lineFk,
NULL `order`,
NULL AS clientType
FROM buy b
JOIN entry e ON e.id = b.entryFk
JOIN travel tr ON tr.id = e.travelFk
JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.alertLevel =
CASE
WHEN tr.shipped < CURDATE() THEN 3
WHEN tr.shipped = CURDATE() AND tr.isReceived = TRUE THEN 3
ELSE 0
END
JOIN state st ON st.code = al.code
WHERE tr.landed >= vDateInventory
AND vWarehouse = tr.warehouseInFk
AND b.itemFk = vItemId
AND e.isInventory = FALSE
AND e.isRaid = FALSE
UNION ALL
SELECT tr.shipped,
NULL as `in`,
b.quantity AS `out`,
al.alertLevel AS alertLevel,
st.name AS stateName,
s.name AS name,
e.ref AS reference,
e.id AS origin,
s.id AS clientFk,
IF(al.alertLevel = 3, TRUE, FALSE) isPicked,
FALSE AS isTicket,
b.id,
NULL `order`,
NULL AS clientType
FROM buy b
JOIN entry e ON e.id = b.entryFk
JOIN travel tr ON tr.id = e.travelFk
JOIN warehouse w ON w.id = tr.warehouseOutFk
JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.alertLevel =
CASE
WHEN tr.shipped < CURDATE() THEN 3
WHEN tr.shipped = CURDATE() AND tr.isReceived = TRUE THEN 3
ELSE 0
END
JOIN state st ON st.code = al.code
WHERE tr.shipped >= vDateInventory
AND vWarehouse =tr.warehouseOutFk
AND s.id <> 4
AND b.itemFk = vItemId
AND e.isInventory = FALSE
AND w.isFeedStock = FALSE
AND e.isRaid = FALSE
UNION ALL
SELECT DATE(t.shipped),
NULL as `in`,
s.quantity AS `out`,
al.alertLevel AS alertLevel,
st.name AS stateName,
t.nickname AS name,
t.refFk AS reference,
t.id AS origin,
t.clientFk,
stk.id AS isPicked,
TRUE AS isTicket,
s.id,
st.`order`,
ct.code AS clientType
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
LEFT JOIN ticketState ts ON ts.ticket = t.id
LEFT JOIN state st ON st.code = ts.code
JOIN client c ON c.id = t.clientFk
JOIN clientType ct ON ct.id = c.clientTypeFk
JOIN alertLevel al ON al.alertLevel =
CASE
WHEN t.shipped < curdate() THEN 3
WHEN t.shipped > util.dayEnd(curdate()) THEN 0
ELSE IFNULL(ts.alertLevel, 0)
END
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
WHERE t.shipped >= vDateInventory
AND s.itemFk = vItemId
AND vWarehouse =t.warehouseFk
ORDER BY shipped, alertLevel DESC, isTicket, `order` DESC, isPicked DESC, `in` DESC, `out` DESC
) AS itemDiary;
END$$
DELIMITER ;

View File

@ -1,2 +0,0 @@
ALTER TABLE `vn`.`supplier` ADD COLUMN `workerFk` INT(11) NULL DEFAULT NULL COMMENT 'Responsible for approving invoices' AFTER `isTrucker`;
ALTER TABLE `vn`.`supplier` ADD CONSTRAINT `supplier_workerFk` FOREIGN KEY (`workerFk`) REFERENCES `vn`.`worker` (`id`) ON UPDATE CASCADE;

View File

@ -1,4 +0,0 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('StarredModule', '*', '*', 'ALLOW', 'ROLE', 'employee'),
('ItemBotanical', '*', 'WRITE', 'ALLOW', 'ROLE', 'logisticBoss');

View File

@ -1,20 +0,0 @@
CREATE TABLE `salix`.`module` (
`code` VARCHAR(45) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `salix`.`module`(`code`)
VALUES
('Items'),
('Orders'),
('Clients'),
('Entries'),
('Travels'),
('Invoices out'),
('Suppliers'),
('Claims'),
('Routes'),
('Tickets'),
('Workers'),
('Users'),
('Zones');

View File

@ -1,10 +0,0 @@
CREATE TABLE `vn`.`starredModule` (
`id` INT(11) unsigned NOT NULL AUTO_INCREMENT,
`workerFk` INT(10) NOT NULL,
`moduleFk` VARCHAR(45) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `starred_workerFk` (`workerFk`),
KEY `starred_moduleFk` (`moduleFk`),
CONSTRAINT `starred_workerFk` FOREIGN KEY (`workerFk`) REFERENCES `vn`.`worker` (`id`) ON UPDATE CASCADE,
CONSTRAINT `starred_moduleFk` FOREIGN KEY (`moduleFk`) REFERENCES `salix`.`module` (`code`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -1,5 +0,0 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Genus', '*', 'WRITE', 'ALLOW', 'ROLE', 'logisticBoss'),
('Specie', '*', 'WRITE', 'ALLOW', 'ROLE', 'logisticBoss'),
('InvoiceOut', 'createPdf', 'WRITE', 'ALLOW', 'ROLE', 'invoicing');

File diff suppressed because one or more lines are too long

View File

@ -294,6 +294,10 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
(103, 0, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)), (103, 0, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(104, -30, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)); (104, -30, DATE_ADD(CURDATE(), INTERVAL -1 MONTH));
INSERT INTO `vn`.`clientConfig`(`riskTolerance`)
VALUES
(200);
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`) INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
VALUES VALUES
(1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 1), (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 1),
@ -711,14 +715,14 @@ INSERT INTO `vn`.`itemCategory`(`id`, `name`, `display`, `color`, `icon`, `code`
(7, 'Accessories', 1, NULL, 'icon-accessory', 'accessory'), (7, 'Accessories', 1, NULL, 'icon-accessory', 'accessory'),
(8, 'Fruit', 1, NULL, 'icon-fruit', 'fruit'); (8, 'Fruit', 1, NULL, 'icon-fruit', 'fruit');
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`,`workerFk`, `isPackaging`) INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `life`,`workerFk`, `isPackaging`)
VALUES VALUES
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0), (1, 'CRI', 'Crisantemo', 2, 1, 31, 35, 0),
(2, 'ITG', 'Anthurium', 1, 31, 35, 0), (2, 'ITG', 'Anthurium', 1, 1, 31, 35, 0),
(3, 'WPN', 'Paniculata', 2, 31, 35, 0), (3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0),
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1), (4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1),
(5, 'CON', 'Container', 3, NULL, 35, 1), (5, 'CON', 'Container', 3, 1, NULL, 35, 1),
(6, 'ALS', 'Alstroemeria', 1, 31, 35, 0); (6, 'ALS', 'Alstroemeria', 1, 1, 31, 35, 0);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`) INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES VALUES
@ -2259,7 +2263,10 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`)
(10, 10, 10); (10, 10, 10);
INSERT INTO `vn`.`ticketRecalc`(`ticketFk`) INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
SELECT `id` FROM `vn`.`ticket`; SELECT `id`
FROM `vn`.`ticket` t
LEFT JOIN vn.ticketRecalc tr ON tr.ticketFk = t.id
WHERE tr.ticketFk IS NULL;
CALL `vn`.`ticket_doRecalc`(); CALL `vn`.`ticket_doRecalc`();

File diff suppressed because it is too large Load Diff

View File

@ -86,6 +86,8 @@ IGNORETABLES=(
--ignore-table=vn.warehouseJoined --ignore-table=vn.warehouseJoined
--ignore-table=vn.workerTeam__ --ignore-table=vn.workerTeam__
--ignore-table=vn.XDiario__ --ignore-table=vn.XDiario__
--ignore-table=sage.movConta
--ignore-table=sage.movContaCopia
) )
mysqldump \ mysqldump \
--defaults-file=config.production.ini \ --defaults-file=config.production.ini \

View File

@ -0,0 +1,31 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('item_getBalance()', () => {
it(`should return the item balance ordered by alert level`, async() => {
let stmts = [];
let params = {
warehouseFk: 1,
itemFk: 1
};
const conn = await app.models.Item.dataSource.connector;
stmts.push(new ParameterizedSQL('CALL vn.item_getBalance(?, ?)', [
params.warehouseFk,
params.itemFk
]));
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
let itemBalance = result[0];
expect(itemBalance[0].alertLevel).toBeGreaterThanOrEqual(itemBalance[1].alertLevel);
expect(itemBalance[1].alertLevel).toBeGreaterThanOrEqual(itemBalance[2].alertLevel);
expect(itemBalance[2].alertLevel).toBeGreaterThanOrEqual(itemBalance[3].alertLevel);
expect(itemBalance[3].alertLevel).toBeGreaterThanOrEqual(itemBalance[4].alertLevel);
expect(itemBalance[4].alertLevel).toBeGreaterThanOrEqual(itemBalance[5].alertLevel);
expect(itemBalance[5].alertLevel).toBeGreaterThanOrEqual(itemBalance[6].alertLevel);
});
});

View File

@ -59,6 +59,6 @@ describe('Account create and basic data path', () => {
await page.accessToSection('account.card.roles'); await page.accessToSection('account.card.roles');
const rolesCount = await page.countElement(selectors.accountRoles.anyResult); const rolesCount = await page.countElement(selectors.accountRoles.anyResult);
expect(rolesCount).toEqual(3); expect(rolesCount).toEqual(4);
}); });
}); });

View File

@ -81,6 +81,6 @@ describe('Account Role create and basic data path', () => {
await page.accessToSection('account.role.card.inherited'); await page.accessToSection('account.role.card.inherited');
const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult); const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult);
expect(rolesCount).toEqual(6); expect(rolesCount).toEqual(7);
}); });
}); });

View File

@ -3,7 +3,7 @@ module.exports = Self => {
description: 'Returns a list of allowed contentTypes', description: 'Returns a list of allowed contentTypes',
accessType: 'READ', accessType: 'READ',
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {

View File

@ -4,12 +4,12 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: { accepts: {
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The document id', description: 'The document id',
http: {source: 'path'} http: {source: 'path'}
}, },
returns: { returns: {
type: 'Object', type: 'object',
root: true root: true
}, },
http: { http: {
@ -18,16 +18,36 @@ module.exports = Self => {
} }
}); });
Self.removeFile = async(ctx, id) => { Self.removeFile = async(ctx, id, options) => {
const models = Self.app.models; let tx;
const targetClaimDms = await models.ClaimDms.findById(id); let myOptions = {};
const targetDms = await models.Dms.findById(targetClaimDms.dmsFk);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}});
await models.Dms.removeFile(ctx, targetClaimDms.dmsFk); if (typeof options == 'object')
await targetClaimDms.destroy(); Object.assign(myOptions, options);
return targetDms.updateAttribute('dmsTypeFk', trashDmsType.id); if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const models = Self.app.models;
const targetClaimDms = await models.ClaimDms.findById(id, null, myOptions);
const targetDms = await models.Dms.findById(targetClaimDms.dmsFk, null, myOptions);
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions);
await models.Dms.removeFile(ctx, targetClaimDms.dmsFk, myOptions);
await targetClaimDms.destroy(myOptions);
await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions);
if (tx) await tx.commit();
return targetDms;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -17,24 +17,45 @@ module.exports = Self => {
} }
}); });
Self.importTicketSales = async(ctx, params) => { Self.importTicketSales = async(ctx, params, options) => {
let models = Self.app.models; let models = Self.app.models;
let userId = ctx.req.accessToken.userId; let userId = ctx.req.accessToken.userId;
let worker = await models.Worker.findOne({where: {userFk: userId}});
let ticketSales = await models.Sale.find({ let tx;
where: {ticketFk: params.ticketFk} let myOptions = {};
});
let claimEnds = []; if (typeof options == 'object')
ticketSales.forEach(sale => { Object.assign(myOptions, options);
claimEnds.push({
saleFk: sale.id, if (!myOptions.transaction) {
claimFk: params.claimFk, tx = await Self.beginTransaction({});
workerFk: worker.id myOptions.transaction = tx;
}
try {
const worker = await models.Worker.findOne({where: {userFk: userId}}, myOptions);
let ticketSales = await models.Sale.find({
where: {ticketFk: params.ticketFk}
}, myOptions);
let claimEnds = [];
ticketSales.forEach(sale => {
claimEnds.push({
saleFk: sale.id,
claimFk: params.claimFk,
workerFk: worker.id
});
}); });
});
return await Self.create(claimEnds); const createdClaimEnds = await Self.create(claimEnds, myOptions);
if (tx) await tx.commit();
return createdClaimEnds;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -1,25 +1,26 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Claim importTicketSales()', () => { describe('Claim importTicketSales()', () => {
let claimEnds;
afterAll(async done => {
claimEnds.forEach(async line => {
await line.destroy();
});
done();
});
it('should import sales to a claim actions from an specific ticket', async() => { it('should import sales to a claim actions from an specific ticket', async() => {
let ctx = {req: {accessToken: {userId: 5}}}; const ctx = {req: {accessToken: {userId: 5}}};
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: 1,
ticketFk: 1
});
expect(claimEnds.length).toEqual(4); const tx = await app.models.Entry.beginTransaction({});
expect(claimEnds[0].saleFk).toEqual(1); try {
expect(claimEnds[2].saleFk).toEqual(3); const options = {transaction: tx};
const claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: 1,
ticketFk: 1
}, options);
expect(claimEnds.length).toEqual(4);
expect(claimEnds[0].saleFk).toEqual(1);
expect(claimEnds[2].saleFk).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -24,15 +24,22 @@ module.exports = Self => {
} }
}); });
Self.createFromSales = async(ctx, ticketId, sales) => { Self.createFromSales = async(ctx, ticketId, sales, options) => {
let tx;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const tx = await Self.beginTransaction({});
try { try {
let options = {transaction: tx}; const ticket = await models.Ticket.findById(ticketId, null, myOptions);
const ticket = await models.Ticket.findById(ticketId, null, options);
if (ticket.isDeleted) if (ticket.isDeleted)
throw new UserError(`You can't create a claim for a removed ticket`); throw new UserError(`You can't create a claim for a removed ticket`);
@ -41,7 +48,7 @@ module.exports = Self => {
clientFk: ticket.clientFk, clientFk: ticket.clientFk,
ticketCreated: ticket.shipped, ticketCreated: ticket.shipped,
workerFk: userId workerFk: userId
}, options); }, myOptions);
const promises = []; const promises = [];
for (const sale of sales) { for (const sale of sales) {
@ -49,17 +56,18 @@ module.exports = Self => {
saleFk: sale.id, saleFk: sale.id,
claimFk: newClaim.id, claimFk: newClaim.id,
quantity: sale.quantity quantity: sale.quantity
}, options); }, myOptions);
promises.push(newClaimBeginning); promises.push(newClaimBeginning);
} }
await Promise.all(promises); await Promise.all(promises);
await tx.commit(); if (tx) await tx.commit();
return newClaim; return newClaim;
} catch (e) { } catch (e) {
await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;
} }
}; };

View File

@ -10,58 +10,67 @@ module.exports = Self => {
accepts: [ accepts: [
{ {
arg: 'filter', arg: 'filter',
type: 'Object', type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'tags', arg: 'tags',
type: ['Object'], type: ['object'],
description: 'List of tags to filter with', description: 'List of tags to filter with',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'search', arg: 'search',
type: 'String', type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by client name`, description: `If it's and integer searchs by id, otherwise it searchs by client name`,
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'client', arg: 'client',
type: 'String', type: 'string',
description: 'The worker name', description: 'The worker name',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'id', arg: 'id',
type: 'Integer', type: 'integer',
description: 'The claim id', description: 'The claim id',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'clientFk', arg: 'clientFk',
type: 'Integer', type: 'integer',
description: 'The client id', description: 'The client id',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'claimStateFk', arg: 'claimStateFk',
type: 'Integer', type: 'integer',
description: 'The claim state id', description: 'The claim state id',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'salesPersonFk', arg: 'salesPersonFk',
type: 'Integer', type: 'integer',
description: 'The salesPerson id', description: 'The salesPerson id',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'attenderFk', arg: 'attenderFk',
type: 'Integer', type: 'integer',
description: 'The attender worker id', description: 'The attender worker id',
http: {source: 'query'} http: {source: 'query'}
}, { },
{
arg: 'created', arg: 'created',
type: 'Date', type: 'date',
description: 'The to date filter', description: 'The to date filter',
http: {source: 'query'} http: {source: 'query'}
} }
], ],
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {

View File

@ -18,32 +18,39 @@ module.exports = Self => {
} }
}); });
Self.regularizeClaim = async(ctx, claimFk) => { Self.regularizeClaim = async(ctx, claimFk, options) => {
const models = Self.app.models; const models = Self.app.models;
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const resolvedState = 3; const resolvedState = 3;
let tx = await Self.beginTransaction({}); let tx;
try { let myOptions = {};
let options = {transaction: tx};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const claimEnds = await models.ClaimEnd.find({ const claimEnds = await models.ClaimEnd.find({
include: { include: {
relation: 'claimDestination', relation: 'claimDestination',
fields: ['addressFk'] fields: ['addressFk']
}, },
where: {claimFk: claimFk} where: {claimFk: claimFk}
}, options); }, myOptions);
for (let i = 0; i < claimEnds.length; i++) { for (let claimEnd of claimEnds) {
const claimEnd = claimEnds[i];
const destination = claimEnd.claimDestination(); const destination = claimEnd.claimDestination();
const sale = await getSale(claimEnd.saleFk, options); const sale = await getSale(claimEnd.saleFk, myOptions);
const addressId = destination && destination.addressFk; const addressId = destination && destination.addressFk;
let address; let address;
if (addressId) if (addressId)
address = await models.Address.findById(addressId, null, options); address = await models.Address.findById(addressId, null, myOptions);
const salesPerson = sale.ticket().client().salesPersonUser(); const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) { if (salesPerson) {
@ -67,7 +74,7 @@ module.exports = Self => {
addressFk: addressId, addressFk: addressId,
companyFk: sale.ticket().companyFk, companyFk: sale.ticket().companyFk,
warehouseFk: sale.ticket().warehouseFk warehouseFk: sale.ticket().warehouseFk
}, options); }, myOptions);
if (!ticketFk) { if (!ticketFk) {
ticketFk = await createTicket(ctx, { ticketFk = await createTicket(ctx, {
@ -75,7 +82,7 @@ module.exports = Self => {
warehouseId: sale.ticket().warehouseFk, warehouseId: sale.ticket().warehouseFk,
companyId: sale.ticket().companyFk, companyId: sale.ticket().companyFk,
addressId: addressId addressId: addressId
}, options); }, myOptions);
} }
await models.Sale.create({ await models.Sale.create({
@ -85,19 +92,19 @@ module.exports = Self => {
quantity: -sale.quantity, quantity: -sale.quantity,
price: sale.price, price: sale.price,
discount: 100 discount: 100
}, options); }, myOptions);
} }
let claim = await Self.findById(claimFk, null, options); let claim = await Self.findById(claimFk, null, myOptions);
claim = await claim.updateAttributes({ claim = await claim.updateAttributes({
claimStateFk: resolvedState claimStateFk: resolvedState
}, options); }, myOptions);
await tx.commit(); if (tx) await tx.commit();
return claim; return claim;
} catch (e) { } catch (e) {
await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;
} }
}; };

View File

@ -10,36 +10,45 @@ describe('Claim createFromSales()', () => {
const ctx = {req: {accessToken: {userId: 1}}}; const ctx = {req: {accessToken: {userId: 1}}};
it('should create a new claim', async() => { it('should create a new claim', async() => {
let claim = await app.models.Claim.createFromSales(ctx, ticketId, newSale); const tx = await app.models.Claim.beginTransaction({});
expect(claim.ticketFk).toEqual(ticketId); try {
const options = {transaction: tx};
let claimBeginning = await app.models.ClaimBeginning.findOne({where: {claimFk: claim.id}}); const claim = await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
expect(claimBeginning.saleFk).toEqual(newSale[0].id); expect(claim.ticketFk).toEqual(ticketId);
expect(claimBeginning.quantity).toEqual(newSale[0].quantity);
const createdClaimId = claim.id; let claimBeginning = await app.models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
// restores expect(claimBeginning.saleFk).toEqual(newSale[0].id);
await app.models.Claim.destroyById(createdClaimId); expect(claimBeginning.quantity).toEqual(newSale[0].quantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should not be able to create a claim if exists that sale', async() => { it('should not be able to create a claim if exists that sale', async() => {
let claim = await app.models.Claim.createFromSales(ctx, ticketId, newSale); const tx = await app.models.Claim.beginTransaction({});
const createdClaimId = claim.id;
let error; let error;
await app.models.Claim.createFromSales(ctx, ticketId, newSale) try {
const options = {transaction: tx};
.catch(e => { await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
error = e;
}); await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.toString()).toContain(`A claim with that sale already exists`); expect(error.toString()).toContain(`A claim with that sale already exists`);
// restores
await app.models.Claim.destroyById(createdClaimId);
}); });
}); });

View File

@ -21,81 +21,94 @@ describe('regularizeClaim()', () => {
let claimEnds = []; let claimEnds = [];
let trashTicket; let trashTicket;
afterEach(async done => { it('should send a chat message with value "Trash" and then change claim state to resolved', async() => {
const tx = await app.models.Claim.beginTransaction({});
try { try {
let claim = await app.models.Claim.findById(claimFk); const options = {transaction: tx};
await claim.updateAttributes({
claimStateFk: pendentState, spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
hasToPickUp: false
}); claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: claimFk,
ticketFk: 1
}, options);
for (claimEnd of claimEnds) for (claimEnd of claimEnds)
await claimEnd.destroy(); await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options);
if (trashTicket) let claimBefore = await app.models.Claim.findById(claimFk, null, options);
await app.models.Ticket.destroyById(trashTicket.id); await app.models.Claim.regularizeClaim(ctx, claimFk, options);
} catch (error) { let claimAfter = await app.models.Claim.findById(claimFk, null, options);
console.error(error);
trashTicket = await app.models.Ticket.findOne({where: {addressFk: 12}}, options);
expect(trashTicket.addressFk).toEqual(trashAddress);
expect(claimBefore.claimStateFk).toEqual(pendentState);
expect(claimAfter.claimStateFk).toEqual(resolvedState);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Trash');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
} }
done();
});
it('should send a chat message with value "Trash" and then change claim state to resolved', async() => {
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: claimFk,
ticketFk: 1
});
for (claimEnd of claimEnds)
await claimEnd.updateAttributes({claimDestinationFk: trashDestination});
let claimBefore = await app.models.Claim.findById(claimFk);
await app.models.Claim.regularizeClaim(ctx, claimFk);
let claimAfter = await app.models.Claim.findById(claimFk);
trashTicket = await app.models.Ticket.findOne({where: {addressFk: 12}});
expect(trashTicket.addressFk).toEqual(trashAddress);
expect(claimBefore.claimStateFk).toEqual(pendentState);
expect(claimAfter.claimStateFk).toEqual(resolvedState);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Trash');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
}); });
it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => { it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => {
spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); const tx = await app.models.Claim.beginTransaction({});
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { try {
claimFk: claimFk, const options = {transaction: tx};
ticketFk: 1
});
for (claimEnd of claimEnds) spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
await claimEnd.updateAttributes({claimDestinationFk: okDestination});
await app.models.Claim.regularizeClaim(ctx, claimFk); claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: claimFk,
ticketFk: 1
}, options);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); for (claimEnd of claimEnds)
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options);
await app.models.Claim.regularizeClaim(ctx, claimFk, options);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should send a chat message to the salesPerson when claim isPickUp is enabled', async() => { it('should send a chat message to the salesPerson when claim isPickUp is enabled', async() => {
spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); const tx = await app.models.Claim.beginTransaction({});
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { try {
claimFk: claimFk, const options = {transaction: tx};
ticketFk: 1
});
for (claimEnd of claimEnds) spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
await claimEnd.updateAttributes({claimDestinationFk: okDestination});
await app.models.Claim.regularizeClaim(ctx, claimFk); claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: claimFk,
ticketFk: 1
}, options);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); for (claimEnd of claimEnds)
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options);
await app.models.Claim.regularizeClaim(ctx, claimFk, options);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -1,7 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Update Claim', () => { describe('Update Claim', () => {
let newDate = new Date(); const newDate = new Date();
const originalData = { const originalData = {
ticketFk: 3, ticketFk: 3,
clientFk: 101, clientFk: 101,
@ -14,89 +14,114 @@ describe('Update Claim', () => {
}; };
it(`should throw an error as the user doesn't have rights`, async() => { it(`should throw an error as the user doesn't have rights`, async() => {
let newClaim = await app.models.Claim.create(originalData); const tx = await app.models.Claim.beginTransaction({});
const forbiddenState = 3;
const salesPersonId = 18; let error;
const ctx = {
req: { try {
accessToken: { const options = {transaction: tx};
userId: salesPersonId
const newClaim = await app.models.Claim.create(originalData, options);
const forbiddenState = 3;
const salesPersonId = 18;
const ctx = {
req: {
accessToken: {
userId: salesPersonId
}
},
args: {
claimStateFk: forbiddenState,
observation: 'valid observation'
} }
}, };
args: { await app.models.Claim.updateClaim(ctx, newClaim.id, options);
claimStateFk: forbiddenState,
observation: 'valid observation' await tx.rollback();
} } catch (e) {
}; error = e;
await app.models.Claim.updateClaim(ctx, newClaim.id) await tx.rollback();
.catch(e => { }
error = e;
});
expect(error.message).toEqual(`You don't have enough privileges to change that field`); expect(error.message).toEqual(`You don't have enough privileges to change that field`);
// restores
await app.models.Claim.destroyById(newClaim.id);
}); });
it(`should success to update the claim within privileges `, async() => { it(`should success to update the claim within privileges `, async() => {
let newClaim = await app.models.Claim.create(originalData); const tx = await app.models.Claim.beginTransaction({});
const canceledState = 4; try {
const claimManagerId = 72; const options = {transaction: tx};
const ctx = {
req: { const newClaim = await app.models.Claim.create(originalData, options);
accessToken: {
userId: claimManagerId const canceledState = 4;
const claimManagerId = 72;
const ctx = {
req: {
accessToken: {
userId: claimManagerId
}
},
args: {
observation: 'valid observation',
claimStateFk: canceledState,
hasToPickUp: false
} }
}, };
args: { await app.models.Claim.updateClaim(ctx, newClaim.id, options);
observation: 'valid observation',
claimStateFk: canceledState,
hasToPickUp: false
}
};
await app.models.Claim.updateClaim(ctx, newClaim.id);
let updatedClaim = await app.models.Claim.findById(newClaim.id); let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
expect(updatedClaim.observation).toEqual(ctx.args.observation); expect(updatedClaim.observation).toEqual(ctx.args.observation);
// restores await tx.rollback();
await app.models.Claim.destroyById(newClaim.id); } catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should change some sensible fields as claimManager', async() => { it('should change some sensible fields as claimManager', async() => {
let newClaim = await app.models.Claim.create(originalData); const tx = await app.models.Claim.beginTransaction({});
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
const claimManagerId = 72; try {
const ctx = { const options = {transaction: tx};
req: {
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
},
args: {
claimStateFk: 3,
workerFk: 5,
observation: 'another valid observation',
hasToPickUp: true
}
};
ctx.req.__ = (value, params) => {
return params.nickname;
};
await app.models.Claim.updateClaim(ctx, newClaim.id);
let updatedClaim = await app.models.Claim.findById(newClaim.id); const newClaim = await app.models.Claim.create(originalData, options);
expect(updatedClaim.observation).toEqual(ctx.args.observation); const chatModel = app.models.Chat;
expect(updatedClaim.claimStateFk).toEqual(ctx.args.claimStateFk); spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
expect(updatedClaim.workerFk).toEqual(ctx.args.workerFk);
expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
// restores const claimManagerId = 72;
await app.models.Claim.destroyById(newClaim.id); const ctx = {
req: {
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
},
args: {
claimStateFk: 3,
workerFk: 5,
observation: 'another valid observation',
hasToPickUp: true
}
};
ctx.req.__ = (value, params) => {
return params.nickname;
};
await app.models.Claim.updateClaim(ctx, newClaim.id, options);
let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
expect(updatedClaim.observation).toEqual(ctx.args.observation);
expect(updatedClaim.claimStateFk).toEqual(ctx.args.claimStateFk);
expect(updatedClaim.workerFk).toEqual(ctx.args.workerFk);
expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -1,9 +1,8 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Update Claim', () => { describe('Update Claim', () => {
let newDate = new Date(); const newDate = new Date();
let newInstance; const original = {
let original = {
ticketFk: 3, ticketFk: 3,
clientFk: 101, clientFk: 101,
ticketCreated: newDate, ticketCreated: newDate,
@ -14,30 +13,43 @@ describe('Update Claim', () => {
observation: 'observation' observation: 'observation'
}; };
beforeAll(async done => {
newInstance = await app.models.Claim.create(original);
done();
});
afterAll(async done => {
await app.models.Claim.destroyById(newInstance.id);
done();
});
it('should update the claim isChargedToMana attribute', async() => { it('should update the claim isChargedToMana attribute', async() => {
const ctx = {args: {isChargedToMana: false}}; const tx = await app.models.Claim.beginTransaction({});
const result = await app.models.Claim.updateClaimAction(ctx, newInstance.id);
expect(result.id).toEqual(newInstance.id); try {
expect(result.isChargedToMana).toBeFalsy(); const options = {transaction: tx};
const ctx = {args: {isChargedToMana: false}};
const newInstance = await app.models.Claim.create(original, options);
const result = await app.models.Claim.updateClaimAction(ctx, newInstance.id, options);
expect(result.id).toEqual(newInstance.id);
expect(result.isChargedToMana).toBeFalsy();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should update the claim responsibility attribute', async() => { it('should update the claim responsibility attribute', async() => {
const ctx = {args: {responsibility: 2}}; const tx = await app.models.Claim.beginTransaction({});
const result = await app.models.Claim.updateClaimAction(ctx, newInstance.id);
expect(result.id).toEqual(newInstance.id); try {
expect(result.responsibility).toEqual(2); const options = {transaction: tx};
const ctx = {args: {responsibility: 2}};
const newInstance = await app.models.Claim.create(original, options);
const result = await app.models.Claim.updateClaimAction(ctx, newInstance.id, options);
expect(result.id).toEqual(newInstance.id);
expect(result.responsibility).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -4,25 +4,26 @@ module.exports = Self => {
description: 'Update a claim with privileges', description: 'Update a claim with privileges',
accepts: [{ accepts: [{
arg: 'ctx', arg: 'ctx',
type: 'Object', type: 'object',
http: {source: 'context'} http: {source: 'context'}
}, { },
{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'Claim id', description: 'Claim id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'workerFk', arg: 'workerFk',
type: 'Number' type: 'number'
}, },
{ {
arg: 'claimStateFk', arg: 'claimStateFk',
type: 'Number' type: 'number'
}, },
{ {
arg: 'observation', arg: 'observation',
type: 'String' type: 'string'
}, },
{ {
arg: 'hasToPickUp', arg: 'hasToPickUp',
@ -38,51 +39,69 @@ module.exports = Self => {
} }
}); });
Self.updateClaim = async(ctx, id) => { Self.updateClaim = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const args = ctx.args; const args = ctx.args;
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const claim = await models.Claim.findById(id, { let tx;
include: { let myOptions = {};
relation: 'client',
scope: { if (typeof options == 'object')
include: { Object.assign(myOptions, options);
relation: 'salesPersonUser'
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const claim = await models.Claim.findById(id, {
include: {
relation: 'client',
scope: {
include: {
relation: 'salesPersonUser'
}
} }
} }
}, myOptions);
let changedHasToPickUp = false;
if (args.hasToPickUp)
changedHasToPickUp = true;
if (args.claimStateFk) {
const canUpdate = await canChangeState(ctx, claim.claimStateFk, myOptions);
const hasRights = await canChangeState(ctx, args.claimStateFk, myOptions);
const isClaimManager = await models.Account.hasRole(userId, 'claimManager', myOptions);
if (!canUpdate || !hasRights || changedHasToPickUp && !isClaimManager)
throw new UserError(`You don't have enough privileges to change that field`);
}
delete args.ctx;
const updatedClaim = await claim.updateAttributes(args, myOptions);
// Get sales person from claim client
const salesPerson = claim.client().salesPersonUser();
if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp) {
const origin = ctx.req.headers.origin;
const message = $t('Claim will be picked', {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
} }
});
let changedHasToPickUp = false;
if (args.hasToPickUp)
changedHasToPickUp = true;
if (args.claimStateFk) { if (tx) await tx.commit();
const canUpdate = await canChangeState(ctx, claim.claimStateFk);
const hasRights = await canChangeState(ctx, args.claimStateFk);
const isClaimManager = await models.Account.hasRole(userId, 'claimManager');
if (!canUpdate || !hasRights || changedHasToPickUp && !isClaimManager) return updatedClaim;
throw new UserError(`You don't have enough privileges to change that field`); } catch (e) {
if (tx) await tx.rollback();
throw e;
} }
delete args.ctx;
const updatedClaim = await claim.updateAttributes(args);
// Get sales person from claim client
const salesPerson = claim.client().salesPersonUser();
if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp) {
const origin = ctx.req.headers.origin;
const message = $t('Claim will be picked', {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
return updatedClaim;
}; };
async function canChangeState(ctx, id) { async function canChangeState(ctx, id, options) {
let models = Self.app.models; let models = Self.app.models;
let userId = ctx.req.accessToken.userId; let userId = ctx.req.accessToken.userId;
@ -90,9 +109,9 @@ module.exports = Self => {
include: { include: {
relation: 'writeRole' relation: 'writeRole'
} }
}); }, options);
let stateRole = state.writeRole().name; let stateRole = state.writeRole().name;
let canUpdate = await models.Account.hasRole(userId, stateRole); let canUpdate = await models.Account.hasRole(userId, stateRole, options);
return canUpdate; return canUpdate;
} }

View File

@ -28,12 +28,32 @@ module.exports = Self => {
} }
}); });
Self.updateClaimAction = async(ctx, id) => { Self.updateClaimAction = async(ctx, id, options) => {
const models = Self.app.models; let tx;
const claim = await models.Claim.findById(id); let myOptions = {};
const args = ctx.args;
delete args.ctx;
return await claim.updateAttributes(args); if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const models = Self.app.models;
const claim = await models.Claim.findById(id, null, myOptions);
const args = ctx.args;
delete args.ctx;
const updatedClaim = await claim.updateAttributes(args, myOptions);
if (tx) await tx.commit();
return updatedClaim;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
}; };
}; };

View File

@ -4,40 +4,46 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The claim id', description: 'The claim id',
http: {source: 'path'} http: {source: 'path'}
}, { },
{
arg: 'warehouseId', arg: 'warehouseId',
type: 'Number', type: 'number',
description: 'The warehouse id', description: 'The warehouse id',
required: true required: true
}, { },
{
arg: 'companyId', arg: 'companyId',
type: 'Number', type: 'number',
description: 'The company id', description: 'The company id',
required: true required: true
}, { },
{
arg: 'dmsTypeId', arg: 'dmsTypeId',
type: 'Number', type: 'number',
description: 'The dms type id', description: 'The dms type id',
required: true required: true
}, { },
{
arg: 'reference', arg: 'reference',
type: 'String', type: 'string',
required: true required: true
}, { },
{
arg: 'description', arg: 'description',
type: 'String', type: 'string',
required: true required: true
}, { },
{
arg: 'hasFile', arg: 'hasFile',
type: 'Boolean', type: 'boolean',
description: 'True if has an attached file', description: 'True if has an attached file',
required: true required: true
}], }],
returns: { returns: {
type: 'Object', type: 'object',
root: true root: true
}, },
http: { http: {
@ -46,31 +52,38 @@ module.exports = Self => {
} }
}); });
Self.uploadFile = async(ctx, id) => { Self.uploadFile = async(ctx, id, options) => {
let tx;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const models = Self.app.models; const models = Self.app.models;
const promises = []; const promises = [];
const tx = await Self.beginTransaction({});
try { try {
const options = {transaction: tx}; const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions);
const uploadedFiles = await models.Dms.uploadFile(ctx, options);
uploadedFiles.forEach(dms => { uploadedFiles.forEach(dms => {
const newClaimDms = models.ClaimDms.create({ const newClaimDms = models.ClaimDms.create({
claimFk: id, claimFk: id,
dmsFk: dms.id dmsFk: dms.id
}, options); }, myOptions);
promises.push(newClaimDms); promises.push(newClaimDms);
}); });
const resolvedPromises = await Promise.all(promises); const resolvedPromises = await Promise.all(promises);
await tx.commit(); if (tx) await tx.commit();
return resolvedPromises; return resolvedPromises;
} catch (err) { } catch (e) {
await tx.rollback(); if (tx) await tx.rollback();
throw err; throw e;
} }
}; };
}; };

View File

@ -10,10 +10,10 @@ module.exports = Self => {
description: 'The routes ids to clone' description: 'The routes ids to clone'
}, },
{ {
arg: 'started', arg: 'created',
type: 'date', type: 'date',
required: true, required: true,
description: 'The started date for all routes' description: 'The created date for all routes'
} }
], ],
returns: { returns: {
@ -26,7 +26,7 @@ module.exports = Self => {
} }
}); });
Self.clone = async(ids, started) => { Self.clone = async(ids, created) => {
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
@ -39,8 +39,7 @@ module.exports = Self => {
throw new Error(`The amount of routes found don't match`); throw new Error(`The amount of routes found don't match`);
const routes = originalRoutes.map(route => { const routes = originalRoutes.map(route => {
route.started = started; route.created = created;
route.created = new Date();
return route; return route;
}); });

View File

@ -38,10 +38,10 @@ module.exports = Self => {
for (let zoneAgencyMode of zoneAgencyModes) for (let zoneAgencyMode of zoneAgencyModes)
zoneIds.push(zoneAgencyMode.zoneFk); zoneIds.push(zoneAgencyMode.zoneFk);
const minDate = new Date(route.finished); const minDate = new Date(route.created);
minDate.setHours(0, 0, 0, 0); minDate.setHours(0, 0, 0, 0);
const maxDate = new Date(route.finished); const maxDate = new Date(route.created);
maxDate.setHours(23, 59, 59, 59); maxDate.setHours(23, 59, 59, 59);
let tickets = await Self.app.models.Ticket.find({ let tickets = await Self.app.models.Ticket.find({
@ -49,7 +49,7 @@ module.exports = Self => {
agencyModeFk: route.agencyModeFk, agencyModeFk: route.agencyModeFk,
zoneFk: {inq: zoneIds}, zoneFk: {inq: zoneIds},
id: {nin: idsToExclude}, id: {nin: idsToExclude},
landed: {between: [minDate, maxDate]} created: {between: [minDate, maxDate]}
}, },
include: [ include: [
{ {

View File

@ -1,14 +1,14 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('route clone()', () => { describe('route clone()', () => {
const startDate = new Date(); const createdDate = new Date();
it('should throw an error if the amount of ids pased to the clone function do no match the database', async() => { it('should throw an error if the amount of ids pased to the clone function do no match the database', async() => {
const ids = [996, 997, 998, 999]; const ids = [996, 997, 998, 999];
let error; let error;
try { try {
await app.models.Route.clone(ids, startDate); await app.models.Route.clone(ids, createdDate);
} catch (e) { } catch (e) {
error = e; error = e;
} }
@ -20,7 +20,7 @@ describe('route clone()', () => {
it('should clone two routes', async() => { it('should clone two routes', async() => {
const ids = [1, 2]; const ids = [1, 2];
const clones = await app.models.Route.clone(ids, startDate); const clones = await app.models.Route.clone(ids, createdDate);
expect(clones.length).toEqual(2); expect(clones.length).toEqual(2);

View File

@ -104,9 +104,8 @@
<tpl-body> <tpl-body>
<vn-horizontal> <vn-horizontal>
<vn-date-picker <vn-date-picker
label="Starting date" label="Starting date"
ng-model="$ctrl.startedDate" ng-model="$ctrl.createdDate">
required="true">
</vn-date-picker> </vn-date-picker>
</vn-horizontal> </vn-horizontal>
</tpl-body> </tpl-body>

View File

@ -41,19 +41,26 @@ export default class Controller extends Section {
} }
openClonationDialog() { openClonationDialog() {
this.startedDate = new Date();
this.$.clonationDialog.show(); this.$.clonationDialog.show();
this.createdDate = new Date();
} }
cloneSelectedRoutes() { cloneSelectedRoutes() {
const routesIds = []; try {
for (let route of this.checked) if (!this.createdDate)
routesIds.push(route.id); throw new Error(`The date can't be empty`);
return this.$http.post('Routes/clone', {ids: routesIds, started: this.startedDate}).then(() => { const routesIds = [];
this.$.model.refresh(); for (let route of this.checked)
this.vnApp.showSuccess(this.$t('Data saved!')); routesIds.push(route.id);
});
return this.$http.post('Routes/clone', {ids: routesIds, created: this.createdDate}).then(() => {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
});
} catch (e) {
this.vnApp.showError(this.$t(e.message));
}
} }
onDrop($event) { onDrop($event) {

View File

@ -62,7 +62,7 @@ describe('Component vnRouteIndex', () => {
describe('cloneSelectedRoutes()', () => { describe('cloneSelectedRoutes()', () => {
it('should perform an http request to Routes/clone', () => { it('should perform an http request to Routes/clone', () => {
controller.startedDate = new Date(); controller.createdDate = new Date();
$httpBackend.expect('POST', 'Routes/clone').respond(); $httpBackend.expect('POST', 'Routes/clone').respond();
controller.cloneSelectedRoutes(); controller.cloneSelectedRoutes();

View File

@ -1,5 +1,5 @@
Vehicle: Vehículo Vehicle: Vehículo
Download selected routes as PDF: Descargar rutas seleccionadas como PDF Download selected routes as PDF: Descargar rutas seleccionadas como PDF
Clone selected routes: Clonar rutas seleccionadas Clone selected routes: Clonar rutas seleccionadas
Select the starting date: Seleccione fecha de inicio The date can't be empty: La fecha no puede estar vacía
Starting date: Fecha de inicio Starting date: Fecha de inicio

View File

@ -10,7 +10,7 @@
data="contacts" data="contacts"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md"> <form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-lg">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<div ng-repeat="contact in contacts" class="contact"> <div ng-repeat="contact in contacts" class="contact">
<vn-vertical> <vn-vertical>

View File

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
// #2868 Excluded until database export describe('ticket filter()', () => {
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'};
@ -24,7 +23,7 @@ xdescribe('ticket filter()', () => {
const filter = {}; const filter = {};
const result = await app.models.Ticket.filter(ctx, filter); const result = await app.models.Ticket.filter(ctx, filter);
expect(result.length).toEqual(4); expect(result.length).toEqual(3);
}); });
it('should return the tickets matching the problems on false', async() => { it('should return the tickets matching the problems on false', async() => {
@ -42,7 +41,7 @@ xdescribe('ticket filter()', () => {
const filter = {}; const filter = {};
const result = await app.models.Ticket.filter(ctx, filter); const result = await app.models.Ticket.filter(ctx, filter);
expect(result.length).toEqual(10); expect(result.length).toEqual(7);
}); });
it('should return the tickets matching the problems on null', async() => { it('should return the tickets matching the problems on null', async() => {

18
package-lock.json generated
View File

@ -9575,13 +9575,13 @@
"dev": true "dev": true
}, },
"jasmine-reporters": { "jasmine-reporters": {
"version": "2.3.2", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz", "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.4.0.tgz",
"integrity": "sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==", "integrity": "sha512-jxONSrBLN1vz/8zCx5YNWQSS8iyDAlXQ5yk1LuqITe4C6iXCDx5u6Q0jfNtkKhL4qLZPe69fL+AWvXFt9/x38w==",
"dev": true, "dev": true,
"requires": { "requires": {
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"xmldom": "^0.1.22" "xmldom": "^0.5.0"
} }
}, },
"jasmine-spec-reporter": { "jasmine-spec-reporter": {
@ -19907,7 +19907,7 @@
}, },
"globby": { "globby": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -20470,9 +20470,9 @@
"integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=" "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8="
}, },
"xmldom": { "xmldom": {
"version": "0.1.31", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz",
"integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", "integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==",
"dev": true "dev": true
}, },
"xpath": { "xpath": {
@ -20568,7 +20568,7 @@
}, },
"os-locale": { "os-locale": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true, "dev": true,
"requires": { "requires": {

View File

@ -76,7 +76,7 @@
"html-webpack-plugin": "^4.0.0-beta.11", "html-webpack-plugin": "^4.0.0-beta.11",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jasmine": "^3.6.3", "jasmine": "^3.6.3",
"jasmine-reporters": "^2.3.2", "jasmine-reporters": "^2.4.0",
"jasmine-spec-reporter": "^6.0.0", "jasmine-spec-reporter": "^6.0.0",
"jest": "^26.0.1", "jest": "^26.0.1",
"jest-junit": "^8.0.0", "jest-junit": "^8.0.0",