#6889 drop addSaleByCode #2470

Merged
jorgep merged 27 commits from 6889-dropAddSaleByCode into dev 2024-06-03 10:09:07 +00:00
20 changed files with 384 additions and 351 deletions

View File

@ -1,6 +1,16 @@
{ {
"name": "Collection", "name": "Collection",
"base": "VnModel", "base": "VnModel",
"properties": {
"id": {
"id": true,
"type": "number",
"required": true
},
"workerFk": {
"type": "number"
}
},
"options": { "options": {
"mysql": { "mysql": {
"table": "collection" "table": "collection"

View File

@ -762,7 +762,12 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL), (30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL), (31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL), (32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(33, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL, NULL); (33, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL, NULL),
(34, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1103, 'BEJAR', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(35, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Somewhere in Philippines', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(36, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Ant-Man Adventure', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(37, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1110, 'Deadpool swords', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL);
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`) INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES VALUES
(1, 11, 1, 'ready'), (1, 11, 1, 'ready'),
@ -808,7 +813,10 @@ INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `userFk`, `created`)
(21, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)), (21, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)),
(22, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)), (22, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)),
(23, 16, 21, util.VN_NOW()), (23, 16, 21, util.VN_NOW()),
(24, 16, 21, util.VN_NOW()); (24, 16, 21, util.VN_NOW()),
Review

Y 1 línea por ticket.

Y 1 línea por ticket.
(34, 14, 49, util.VN_NOW()),
(35, 14, 18, util.VN_NOW()),
(36, 14, 18, util.VN_NOW());
INSERT INTO `vn`.`deliveryPoint` (`id`, `name`, `ubication`) INSERT INTO `vn`.`deliveryPoint` (`id`, `name`, `ubication`)
VALUES VALUES
@ -1068,7 +1076,10 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
(37, 4, 31, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()), (37, 4, 31, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(36, 4, 30, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()), (36, 4, 30, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(38, 2, 32, 'Melee weapon combat fist 15cm', 30, 7.07, 0, 0, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)), (38, 2, 32, 'Melee weapon combat fist 15cm', 30, 7.07, 0, 0, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
(39, 1, 32, 'Ranged weapon longbow 200cm', 2, 103.49, 0, 0, 0, util.VN_CURDATE()); (39, 1, 32, 'Ranged weapon longbow 200cm', 2, 103.49, 0, 0, 0, util.VN_CURDATE()),
(40, 2, 34, 'Melee weapon combat fist 15cm', 10.00, 3.91, 0, 0, 0, util.VN_CURDATE()),
(41, 2, 35, 'Melee weapon combat fist 15cm', 8.00, 3.01, 0, 0, 0, util.VN_CURDATE()),
(42, 2, 36, 'Melee weapon combat fist 15cm', 6.00, 2.50, 0, 0, 0, util.VN_CURDATE());
INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`) INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
VALUES VALUES
@ -1247,14 +1258,20 @@ INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPacki
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`) INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES VALUES
(1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1), (1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1),
(2, 1106, 14, util.VN_CURDATE(), 1); (2, 1106, 14, util.VN_CURDATE(), 1),
(4, 49, 5, util.VN_CURDATE(), 1),
(5, 18, 5, util.VN_CURDATE(), 1),
(6, 18, 5, util.VN_CURDATE(), 1);
INSERT INTO `vn`.`ticketCollection`(`ticketFk`, `collectionFk`, `level`) INSERT INTO `vn`.`ticketCollection`(`ticketFk`, `collectionFk`, `level`)
VALUES VALUES
(1, 1, 1), (1, 1, 1),
(2, 1, NULL), (2, 1, NULL),
(3, 2, NULL), (3, 2, NULL),
(23, 1, NULL); (23, 1, NULL),
(34, 4, 1),
(35, 5, 1),
(8, 6, 1);
INSERT INTO `vn`.`genus`(`id`, `name`) INSERT INTO `vn`.`genus`(`id`, `name`)
VALUES VALUES
@ -3705,7 +3722,8 @@ INSERT IGNORE INTO vn.saleGroup
SET id = 4, SET id = 4,
userFk = 1, userFk = 1,
parkingFk = 9, parkingFk = 9,
sectorFk = 9992; sectorFk = 9992,
ticketFk = 36;
INSERT IGNORE INTO vn.sectorCollectionSaleGroup INSERT IGNORE INTO vn.sectorCollectionSaleGroup
SET id = 9999, SET id = 9999,
@ -3807,3 +3825,27 @@ INSERT INTO `vn`.`ledgerCompany` SET
INSERT INTO `vn`.`ledgerConfig` SET INSERT INTO `vn`.`ledgerConfig` SET
maxTolerance = 0.01; maxTolerance = 0.01;
INSERT INTO vn.sectorCollection
SET id = 2,
userFk = 18,
sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 8,
sectorCollectionFk = 2,
saleGroupFk = 4;
INSERT INTO vn.saleGroup (userFk, parkingFk, sectorFk, ticketFk)
VALUES
(1, 1, 1, 37);
INSERT INTO vn.sectorCollection
SET id = 3,
userFk = 18,
sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 9,
sectorCollectionFk = 3,
saleGroupFk = 6;

View File

@ -0,0 +1,46 @@
use account;
INSERT INTO role
SET name = 'reviewer',
description = 'Revisor de producción',
hasLogin = TRUE,
created = util.VN_CURDATE(),
modified = util.VN_CURDATE(),
editorFk = NULL;
INSERT INTO roleInherit(
role,
inheritsFrom
)
SELECT r1.id,
r2.id
FROM role r1
JOIN role r2
WHERE r1.name = 'reviewer'
AND r2.name = 'production'
UNION
SELECT ri.role,
r2.id
FROM roleInherit ri
JOIN role r1 ON r1.id = ri.role
JOIN role r2 ON r2.name = 'reviewer'
WHERE r1.name IN ('claimManager', 'productionBoss')
GROUP BY ri.role;
DELETE ri
FROM roleInherit ri
JOIN role r1 ON ri.role = r1.id
JOIN role r2 ON ri.inheritsFrom = r2.id
WHERE r1.name = 'replenisher'
AND r2.name = 'buyer';
UPDATE salix.ACL
SET principalId = 'reviewer'
WHERE property = 'isInPreparing';
UPDATE user u
JOIN vn.workerDepartment wd ON wd.workerFk = u.id
JOIN vn.department d ON wd.departmentFk = d.id
JOIN role r ON r.name = 'reviewer'
SET u.role = r.id
WHERE d.name IN ('REVISION', 'PREVIA');

View File

@ -225,7 +225,7 @@ describe('Ticket Edit sale path', () => {
}); });
it('should show error trying to delete a ticket with a refund', async() => { it('should show error trying to delete a ticket with a refund', async() => {
await page.loginAndModule('production', 'ticket'); await page.loginAndModule('salesPerson', 'ticket');
Review

Production ya no puede borrar lineas.

Production ya no puede borrar lineas.
await page.accessToSearchResult('8'); await page.accessToSearchResult('8');
await page.waitToClick(selectors.ticketDescriptor.moreMenu); await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket); await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);

View File

@ -64,7 +64,7 @@ describe('item getBalance()', () => {
const secondItemBalance = await models.Item.getBalance(ctx, secondFilter, options); const secondItemBalance = await models.Item.getBalance(ctx, secondFilter, options);
expect(firstItemBalance[9].claimFk).toEqual(null); expect(firstItemBalance[9].claimFk).toEqual(null);
expect(secondItemBalance[4].claimFk).toEqual(2); expect(secondItemBalance[7].claimFk).toEqual(2);
Review

Está contando los 3 tickets que yo he añadido. No estaba seleccionando el mismo.

Está contando los 3 tickets que yo he añadido. No estaba seleccionando el mismo.
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -151,7 +151,7 @@ describe('SalesMonitor salesFilter()', () => {
const result = await models.SalesMonitor.salesFilter(ctx, filter, options); const result = await models.SalesMonitor.salesFilter(ctx, filter, options);
const firstRow = result[0]; const firstRow = result[0];
expect(result.length).toEqual(12); expect(result.length).toEqual(15);
Review

Está contando los 3 tickets que yo he añadido.

Está contando los 3 tickets que yo he añadido.
expect(firstRow.alertLevel).not.toEqual(0); expect(firstRow.alertLevel).not.toEqual(0);
await tx.rollback(); await tx.rollback();

View File

@ -11,6 +11,12 @@
"Sector": { "Sector": {
"dataSource": "vn" "dataSource": "vn"
}, },
"SectorCollection": {
"dataSource": "vn"
},
"SectorCollectionSaleGroup": {
"dataSource": "vn"
},
"Train": { "Train": {
"dataSource": "vn" "dataSource": "vn"
} }

View File

@ -0,0 +1,24 @@
{
"name": "SectorCollection",
"base": "VnModel",
"options": {
"mysql": {
"table": "sectorCollection"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"created": {
"type": "date"
},
"userFk": {
"type": "number"
},
"sectorFk": {
"type": "number"
}
}
}

View File

@ -0,0 +1,30 @@
{
"name": "SectorCollectionSaleGroup",
"base": "VnModel",
"options": {
"mysql": {
"table": "sectorCollectionSaleGroup"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"created": {
"type": "date"
}
},
"relations": {
"sectorCollection": {
"type": "belongsTo",
"model": "SectorCollection",
"foreignKey": "sectorCollectionFk"
},
"saleGroup": {
"type": "belongsTo",
"model": "SaleGroup",
"foreignKey": "saleGroupFk"
}
}
}

View File

@ -10,8 +10,8 @@ module.exports = Self => {
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'itemId', arg: 'barcode',
Review

Sergio pasará el idem del item o un barcode.

Sergio pasará el idem del item o un barcode.
type: 'number', type: 'any',
required: true required: true
}, },
{ {
@ -29,7 +29,7 @@ module.exports = Self => {
} }
}); });
Self.addSale = async(ctx, id, itemId, quantity, options) => { Self.addSale = async(ctx, id, barcode, quantity, options) => {
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const models = Self.app.models; const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId}; const myOptions = {userId: ctx.req.accessToken.userId};
@ -46,7 +46,9 @@ module.exports = Self => {
try { try {
await models.Ticket.isEditableOrThrow(ctx, id, myOptions); await models.Ticket.isEditableOrThrow(ctx, id, myOptions);
const itemId = await models.ItemBarcode.toItem(barcode, myOptions);
const item = await models.Item.findById(itemId, null, myOptions); const item = await models.Item.findById(itemId, null, myOptions);
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {
include: { include: {
relation: 'client', relation: 'client',

View File

@ -1,56 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('addSaleByCode', {
description: 'Add a collection',
accessType: 'WRITE',
accepts: [
{
arg: 'barcode',
type: 'string',
required: true
}, {
arg: 'quantity',
type: 'number',
required: true
}, {
arg: 'ticketFk',
type: 'number',
required: true
}, {
arg: 'warehouseFk',
type: 'number',
required: true
},
],
http: {
path: `/addSaleByCode`,
verb: 'POST'
},
});
Self.addSaleByCode = async(ctx, barcode, quantity, ticketFk, warehouseFk, options) => {
const myOptions = {userId: ctx.req.accessToken.userId};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const [[item]] = await Self.rawSql('CALL vn.item_getInfo(?,?)', [barcode, warehouseFk], myOptions);
if (!item?.available) throw new UserError('We do not have availability for the selected item');
await Self.rawSql('CALL vn.collection_addItem(?, ?, ?)', [item.id, quantity, ticketFk], myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -8,18 +8,13 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const state = await models.TicketState.findOne({ const state = await models.TicketState.findOne({where: {ticketFk: id}}, myOptions);
where: {ticketFk: id}
}, myOptions);
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const isProductionReviewer = await models.ACL.checkAccessAcl(ctx, 'Sale', 'isInPreparing', '*');
const canEditWeeklyTicket = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'canEditWeekly', 'WRITE'); const canEditWeeklyTicket = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'canEditWeekly', 'WRITE');
const alertLevel = state ? state.alertLevel : null; const alertLevel = state ? state.alertLevel : null;
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {
fields: ['clientFk'], fields: ['clientFk'], include: {relation: 'client'}
include: {
relation: 'client'
}
}, myOptions); }, myOptions);
const isLocked = await models.Ticket.isLocked(id, myOptions); const isLocked = await models.Ticket.isLocked(id, myOptions);
@ -29,10 +24,24 @@ module.exports = Self => {
const isNormalClient = ticket && ticket.client().typeFk == 'normal'; const isNormalClient = ticket && ticket.client().typeFk == 'normal';
const isEditable = !(alertLevelGreaterThanZero && isNormalClient); const isEditable = !(alertLevelGreaterThanZero && isNormalClient);
const ticketCollection = await models.TicketCollection.findOne({
include: {relation: 'collection'}, where: {ticketFk: id}
}, myOptions);
let isOwner = ticketCollection?.collection()?.workerFk === ctx.req.accessToken.userId;
if (!isOwner) {
const saleGroup = await models.SaleGroup.findOne({fields: ['id'], where: {ticketFk: id}}, myOptions);
const sectorCollectionSaleGroup = saleGroup && await models.SectorCollectionSaleGroup.findOne({
include: {relation: 'sectorCollection'}, where: {saleGroupFk: saleGroup.id}
}, myOptions);
isOwner = sectorCollectionSaleGroup?.sectorCollection()?.userFk === ctx.req.accessToken.userId;
}
jorgep marked this conversation as resolved
Review

aqui hay un caso no contemplado, si la coleccion la tiene otro, pero llega el que tiene el salegroup asociado sí debe poder editarlo, por tanto, no tienen que ser excluyentes esas dos opciones

aqui hay un caso no contemplado, si la coleccion la tiene otro, pero llega el que tiene el salegroup asociado sí debe poder editarlo, por tanto, no tienen que ser excluyentes esas dos opciones
if (!ticket) if (!ticket)
throw new ForbiddenError(`The ticket doesn't exist.`); throw new ForbiddenError(`The ticket doesn't exist.`);
if (!isEditable && !isRoleAdvanced) if (!isEditable && !isRoleAdvanced && !isProductionReviewer && !isOwner)
throw new ForbiddenError(`This ticket is not editable.`); throw new ForbiddenError(`This ticket is not editable.`);
if (isLocked && !isWeekly) if (isLocked && !isWeekly)

View File

@ -1,39 +0,0 @@
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Ticket addSaleByCode()', () => {
const quantity = 3;
const ticketFk = 13;
const warehouseFk = 1;
beforeAll(async() => {
activeCtx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'http://localhost'},
__: value => value
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should add a new sale', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const code = '1111111111';
const salesBefore = await models.Sale.find(null, options);
await models.Ticket.addSaleByCode(activeCtx, code, quantity, ticketFk, warehouseFk, options);
const salesAfter = await models.Sale.find(null, options);
expect(salesAfter.length).toEqual(salesBefore.length + 1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -68,7 +68,7 @@ describe('ticket filter()', () => {
const filter = {}; const filter = {};
const result = await models.Ticket.filter(ctx, filter, options); const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(7); expect(result.length).toEqual(10);
Review

Está contando los 3 tickets que yo he añadido.

Está contando los 3 tickets que yo he añadido.
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -42,7 +42,7 @@ describe('sale priceDifference()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1106}}}; const ctx = {req: {accessToken: {userId: 1105}}};
Review

Es el usuario que tiene el ticket, entonces no falla.

Es el usuario que tiene el ticket, entonces no falla.
ctx.args = { ctx.args = {
id: 1, id: 1,
landed: Date.vnNew(), landed: Date.vnNew(),
@ -84,7 +84,7 @@ describe('sale priceDifference()', () => {
const {items} = await models.Ticket.priceDifference(ctx, options); const {items} = await models.Ticket.priceDifference(ctx, options);
expect(items[0].movable).toEqual(410); expect(items[0].movable).toEqual(386);
Review

Está contando la cantidad exacta de las 3 sales que yo he añadido. Es

Está contando la cantidad exacta de las 3 sales que yo he añadido. Es
expect(items[1].movable).toEqual(1810); expect(items[1].movable).toEqual(1810);
await tx.rollback(); await tx.rollback();

View File

@ -14,6 +14,9 @@
}, },
"parkingFk": { "parkingFk": {
"type": "number" "type": "number"
},
"ticketFk": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -1,361 +1,318 @@
/* eslint max-len: ["error", { "code": 150 }]*/ /* eslint max-len: ["error", { "code": 150 }]*/
const {models} = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('sale model ', () => { describe('sale model ', () => {
const ctx = { const developerId = 9;
req: { const buyerId = 35;
accessToken: {userId: 9}, const employeeId = 1;
headers: {origin: 'localhost:5000'}, const productionId = 49;
__: () => {} const salesPersonId = 18;
} const reviewerId = 130;
};
function getActiveCtx(userId) { const barcode = '4444444444';
return { const ticketCollectionProd = 34;
active: { const ticketCollectionSalesPerson = 35;
accessToken: {userId}, const previaTicketSalesPerson = 36;
http: { const previaTicketProd = 37;
req: { const notEditableError = 'This ticket is not editable.';
headers: {origin: 'http://localhost'}
} const ctx = getCtx(developerId);
} let tx;
} let opts;
};
function getCtx(userId, active = false) {
const accessToken = {userId};
const headers = {origin: 'localhost:5000'};
if (!active) return {req: {accessToken, headers, __: () => {}}};
return {active: {accessToken, http: {req: {headers}}}};
} }
beforeEach(async() => {
tx = await models.Sale.beginTransaction({});
opts = {transaction: tx};
});
afterEach(async() => await tx.rollback());
describe('quantity field ', () => { describe('quantity field ', () => {
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => { it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
const saleId = 17; const saleId = 17;
const buyerId = 35; const ctx = getCtx(buyerId);
const ctx = {
req: { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(buyerId, true));
accessToken: {userId: buyerId}, spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const tx = await models.Sale.beginTransaction({});
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId));
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`; SELECT 100 as available;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
try { const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const options = {transaction: tx};
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); expect(isRoleAdvanced).toEqual(true);
expect(isRoleAdvanced).toEqual(true); const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); expect(originalLine.quantity).toEqual(30);
expect(originalLine.quantity).toEqual(30); const newQuantity = originalLine.quantity + 1;
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const newQuantity = originalLine.quantity + 1; const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); expect(modifiedLine.quantity).toEqual(newQuantity);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should update the quantity of a given sale current line', async() => { it('should update the quantity of a given sale current line', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9)); spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(developerId, true));
const tx = await models.Sale.beginTransaction({});
const saleId = 25; const saleId = 25;
const newQuantity = 4; const newQuantity = 4;
try { const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const options = {transaction: tx};
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); expect(originalLine.quantity).toEqual(20);
expect(originalLine.quantity).toEqual(20); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); expect(modifiedLine.quantity).toEqual(newQuantity);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should throw an error if the quantity is negative and it is not a refund ticket', async() => { it('should throw an error if the quantity is negative and it is not a refund ticket', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const saleId = 17; const saleId = 17;
const newQuantity = -10; const newQuantity = -10;
const tx = await models.Sale.beginTransaction({});
let error;
try { try {
const options = {transaction: tx}; await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); expect(e).toEqual(new Error('You can only add negative amounts in refund tickets'));
error = e;
} }
expect(error).toEqual(new Error('You can only add negative amounts in refund tickets'));
}); });
it('should update a negative quantity when is a ticket refund', async() => { it('should update a negative quantity when is a ticket refund', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9)); spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(developerId, true));
const tx = await models.Sale.beginTransaction({});
const saleId = 32; const saleId = 32;
const newQuantity = -10; const newQuantity = -10;
try { await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); expect(modifiedLine.quantity).toEqual(newQuantity);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should throw an error if the quantity is less than the minimum quantity of the item', async() => { it('should throw an error if the quantity is less than the minimum quantity of the item', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = minQuantity - 1; const newQuantity = minQuantity - 1;
let error;
try { try {
const options = {transaction: tx}; const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
const item = await models.Item.findById(itemId, null, options); spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`; SELECT 100 as available;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); expect(e).toEqual(new Error('The amount cannot be less than the minimum'));
error = e;
} }
expect(error).toEqual(new Error('The amount cannot be less than the minimum'));
}); });
it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => { it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = minQuantity - 1; const newQuantity = minQuantity - 1;
try { const item = await models.Item.findById(itemId, null, opts);
const options = {transaction: tx}; await item.updateAttribute('minQuantity', minQuantity, opts);
const item = await models.Item.findById(itemId, null, options); spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
await item.updateAttribute('minQuantity', minQuantity, options); if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT ${newQuantity} as available;`; SELECT ${newQuantity} as available;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
describe('newPrice', () => { describe('newPrice', () => {
it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => { it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = 31; const newQuantity = 31;
try { const item = await models.Item.findById(itemId, null, opts);
const options = {transaction: tx}; await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
const item = await models.Item.findById(itemId, null, options); if (sqlStatement.includes('catalog_calcFromItem')) {
await item.updateAttribute('minQuantity', minQuantity, options); sqlStatement = `
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should increase quantity when the new price is lower than the previous one', async() => { it('should increase quantity when the new price is lower than the previous one', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = 31; const newQuantity = 31;
try { const item = await models.Item.findById(itemId, null, opts);
const options = {transaction: tx}; await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
const item = await models.Item.findById(itemId, null, options); if (sqlStatement.includes('catalog_calcFromItem')) {
await item.updateAttribute('minQuantity', minQuantity, options); sqlStatement = `
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should throw error when increase quantity and the new price is higher than the previous one', async() => { it('should throw error when increase quantity and the new price is higher than the previous one', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = 31; const newQuantity = 31;
let error;
try { try {
const options = {transaction: tx}; const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
const item = await models.Item.findById(itemId, null, options); spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = ` sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); expect(e).toEqual(new Error('The price of the item changed'));
error = e;
} }
expect(error).toEqual(new Error('The price of the item changed'));
}); });
}); });
}); });
describe('add a sale from a collection or previa ticket', () => {
it('if is allocated to them and alert level higher than 0 as Production role', async() => {
const ctx = getCtx(productionId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(productionId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionProd}, opts);
await models.Ticket.addSale(ctx, ticketCollectionProd, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionProd}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketProd}, opts);
await models.Ticket.addSale(ctx, previaTicketProd, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketProd}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
it('should throw an error if is not allocated to them and alert level higher than 0 as Production role', async() => {
const ctx = getCtx(productionId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(productionId, true));
try {
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
try {
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
});
it('if is allocated to them and alert level higher than 0 as salesPerson role', async() => {
const ctx = getCtx(salesPersonId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(salesPersonId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
it('should throw an error if is not allocated to them and alert level higher than 0 as salesPerson role', async() => {
const ctx = getCtx(salesPersonId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(salesPersonId, true));
try {
await models.Ticket.addSale(ctx, ticketCollectionProd, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
});
it('if is a reviewer', async() => {
const ctx = getCtx(reviewerId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(reviewerId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
});
}); });

View File

@ -46,6 +46,5 @@ module.exports = function(Self) {
require('../methods/ticket/invoiceTicketsAndPdf')(Self); require('../methods/ticket/invoiceTicketsAndPdf')(Self);
require('../methods/ticket/docuwareDownload')(Self); require('../methods/ticket/docuwareDownload')(Self);
require('../methods/ticket/myLastModified')(Self); require('../methods/ticket/myLastModified')(Self);
require('../methods/ticket/addSaleByCode')(Self);
require('../methods/ticket/clone')(Self); require('../methods/ticket/clone')(Self);
}; };

View File

@ -476,7 +476,7 @@ class Controller extends Section {
*/ */
addSale(sale) { addSale(sale) {
const data = { const data = {
itemId: sale.itemFk, barcode: sale.itemFk,
quantity: sale.quantity quantity: sale.quantity
}; };
const query = `tickets/${this.ticket.id}/addSale`; const query = `tickets/${this.ticket.id}/addSale`;

View File

@ -659,7 +659,7 @@ describe('Ticket', () => {
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
const newSale = {itemFk: 4, quantity: 10}; const newSale = {itemFk: 4, quantity: 10};
const expectedParams = {itemId: 4, quantity: 10}; const expectedParams = {barcode: 4, quantity: 10};
const expectedResult = { const expectedResult = {
id: 30, id: 30,
quantity: 10, quantity: 10,