#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",
"base": "VnModel",
"properties": {
"id": {
"id": true,
"type": "number",
"required": true
},
"workerFk": {
"type": "number"
}
},
"options": {
"mysql": {
"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),
(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),
(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),

Añado 3 tickets libres.

Añado 3 tickets libres.
(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`)
VALUES
(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)),
(22, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)),
(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`)
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()),
(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)),
(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`)
VALUES
@ -1247,14 +1258,20 @@ INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPacki
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES
(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`)
VALUES
(1, 1, 1),
(2, 1, NULL),

Colección para cada ticket

Colección para cada ticket
(3, 2, NULL),
(23, 1, NULL);
(23, 1, NULL),
(34, 4, 1),
(35, 5, 1),
(8, 6, 1);
INSERT INTO `vn`.`genus`(`id`, `name`)
VALUES
@ -3705,7 +3722,8 @@ INSERT IGNORE INTO vn.saleGroup
SET id = 4,
userFk = 1,

salegroup para un ticket. (para hacer los tests)

salegroup para un ticket. (para hacer los tests)
parkingFk = 9,
sectorFk = 9992;
sectorFk = 9992,
ticketFk = 36;
INSERT IGNORE INTO vn.sectorCollectionSaleGroup
SET id = 9999,
@ -3807,3 +3825,27 @@ INSERT INTO `vn`.`ledgerCompany` SET
INSERT INTO `vn`.`ledgerConfig` SET
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() => {
await page.loginAndModule('production', 'ticket');
Review

Production ya no puede borrar lineas.

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

View File

@ -64,7 +64,7 @@ describe('item getBalance()', () => {
const secondItemBalance = await models.Item.getBalance(ctx, secondFilter, options);
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();
} catch (e) {

View File

@ -151,7 +151,7 @@ describe('SalesMonitor salesFilter()', () => {
const result = await models.SalesMonitor.salesFilter(ctx, filter, options);
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);
await tx.rollback();

View File

@ -11,6 +11,12 @@
"Sector": {
"dataSource": "vn"
},
"SectorCollection": {
"dataSource": "vn"
},
"SectorCollectionSaleGroup": {
"dataSource": "vn"
},
"Train": {
"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'}
},
{
arg: 'itemId',
type: 'number',
arg: 'barcode',
Review

Sergio pasará el idem del item o un barcode.

Sergio pasará el idem del item o un barcode.
type: 'any',
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 models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
@ -46,7 +46,9 @@ module.exports = Self => {
try {
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 ticket = await models.Ticket.findById(id, {
include: {
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')
Object.assign(myOptions, options);
const state = await models.TicketState.findOne({
where: {ticketFk: id}
}, myOptions);
const state = await models.TicketState.findOne({where: {ticketFk: id}}, myOptions);
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 alertLevel = state ? state.alertLevel : null;
const ticket = await models.Ticket.findById(id, {
fields: ['clientFk'],
include: {
relation: 'client'
}
fields: ['clientFk'], include: {relation: 'client'}
}, myOptions);
const isLocked = await models.Ticket.isLocked(id, myOptions);
@ -29,10 +24,24 @@ module.exports = Self => {
const isNormalClient = ticket && ticket.client().typeFk == 'normal';
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)
throw new ForbiddenError(`The ticket doesn't exist.`);
if (!isEditable && !isRoleAdvanced)
if (!isEditable && !isRoleAdvanced && !isProductionReviewer && !isOwner)
throw new ForbiddenError(`This ticket is not editable.`);
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 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();
} catch (e) {

View File

@ -42,7 +42,7 @@ describe('sale priceDifference()', () => {
try {
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 = {
id: 1,
landed: Date.vnNew(),
@ -84,7 +84,7 @@ describe('sale priceDifference()', () => {
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);
await tx.rollback();

View File

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

View File

@ -1,361 +1,318 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const models = require('vn-loopback/server/server').models;
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('sale model ', () => {
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
function getActiveCtx(userId) {
return {
active: {
accessToken: {userId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
}
};
const developerId = 9;
const buyerId = 35;
const employeeId = 1;
const productionId = 49;
const salesPersonId = 18;
const reviewerId = 130;
const barcode = '4444444444';
const ticketCollectionProd = 34;
const ticketCollectionSalesPerson = 35;
const previaTicketSalesPerson = 36;
const previaTicketProd = 37;
const notEditableError = 'This ticket is not editable.';
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 ', () => {
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
const saleId = 17;
const buyerId = 35;
const ctx = {
req: {
accessToken: {userId: buyerId},
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) => {
const ctx = getCtx(buyerId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(buyerId, true));
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
return models.Ticket.rawSql(sqlStatement, params, opts);
});
try {
const options = {transaction: tx};
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
expect(isRoleAdvanced).toEqual(true);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
expect(originalLine.quantity).toEqual(30);
const newQuantity = originalLine.quantity + 1;
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
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() => {
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 newQuantity = 4;
try {
const options = {transaction: tx};
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
expect(originalLine.quantity).toEqual(20);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
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() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const saleId = 17;
const newQuantity = -10;
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
} catch (e) {
await tx.rollback();
error = e;
expect(e).toEqual(new Error('You can only add negative amounts in refund tickets'));
}
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() => {
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 newQuantity = -10;
try {
const options = {transaction: tx};
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']}, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
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() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`;
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 tx.rollback();
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
} catch (e) {
await tx.rollback();
error = e;
expect(e).toEqual(new Error('The amount cannot be less than the minimum'));
}
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() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
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);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT ${newQuantity} as available;`;
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 tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
});
describe('newPrice', () => {
it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
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.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`;
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 tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
});
it('should increase quantity when the new price is lower than the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
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.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`;
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 tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
});
it('should throw error when increase quantity and the new price is higher than the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
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.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`;
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 tx.rollback();
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
} catch (e) {
await tx.rollback();
error = e;
expect(e).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);
}
expect(error).toEqual(new Error('The price of the item changed'));
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/docuwareDownload')(Self);
require('../methods/ticket/myLastModified')(Self);
require('../methods/ticket/addSaleByCode')(Self);
require('../methods/ticket/clone')(Self);
};

View File

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

View File

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