Merge pull request 'refs #6199 feat(sale_updateQuantity): can upload' (!1804) from 6199-saleUploadQuantity into test
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #1804
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
This commit is contained in:
Alex Moreno 2023-10-19 12:10:52 +00:00
commit 2b3fb4f89e
6 changed files with 153 additions and 56 deletions

View File

@ -18,7 +18,14 @@ describe('setSaleQuantity()', () => {
it('should change quantity sale', async() => { it('should change quantity sale', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100})))); 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 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -134,14 +134,6 @@ describe('Ticket Edit sale path', () => {
await page.accessToSection('ticket.card.sale'); await page.accessToSection('ticket.card.sale');
}); });
it('should try to add a higher quantity value and then receive an error', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell);
await page.type(selectors.ticketSales.firstSaleQuantity, '11\u000d');
const message = await page.waitForSnackbar();
expect(message.text).toContain('The new quantity should be smaller than the old one');
});
it('should remove 1 from the first sale quantity', async() => { it('should remove 1 from the first sale quantity', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell); await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell);
await page.waitForSelector(selectors.ticketSales.firstSaleQuantity); await page.waitForSelector(selectors.ticketSales.firstSaleQuantity);

View File

@ -14,7 +14,7 @@
"The default consignee can not be unchecked": "The default consignee can not be unchecked", "The default consignee can not be unchecked": "The default consignee can not be unchecked",
"Enter an integer different to zero": "Enter an integer different to zero", "Enter an integer different to zero": "Enter an integer different to zero",
"Package cannot be blank": "Package cannot be blank", "Package cannot be blank": "Package cannot be blank",
"The new quantity should be smaller than the old one": "The new quantity should be smaller than the old one", "The price of the item changed": "The price of the item changed",
"The sales of this ticket can't be modified": "The sales of this ticket can't be modified", "The sales of this ticket can't be modified": "The sales of this ticket can't be modified",
"Cannot check Equalization Tax in this NIF/CIF": "Cannot check Equalization Tax in this NIF/CIF", "Cannot check Equalization Tax in this NIF/CIF": "Cannot check Equalization Tax in this NIF/CIF",
"You can't create an order for a frozen client": "You can't create an order for a frozen client", "You can't create an order for a frozen client": "You can't create an order for a frozen client",
@ -191,4 +191,4 @@
"Booking completed": "Booking complete", "Booking completed": "Booking complete",
"The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation", "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation",
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets" "You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets"
} }

View File

@ -35,7 +35,7 @@
"The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero", "The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero",
"Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco", "Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco",
"Description cannot be blank": "Se debe rellenar el campo de texto", "Description cannot be blank": "Se debe rellenar el campo de texto",
"The new quantity should be smaller than the old one": "La nueva cantidad debe de ser menor que la anterior", "The price of the item changed": "El precio del artículo cambió",
"The value should not be greater than 100%": "El valor no debe de ser mayor de 100%", "The value should not be greater than 100%": "El valor no debe de ser mayor de 100%",
"The value should be a number": "El valor debe ser un numero", "The value should be a number": "El valor debe ser un numero",
"This order is not editable": "Esta orden no se puede modificar", "This order is not editable": "Esta orden no se puede modificar",

View File

@ -24,34 +24,6 @@ describe('sale updateQuantity()', () => {
}; };
} }
it('should throw an error if the quantity is greater than it should be', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100}))));
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, 17, 31, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The new quantity should be smaller than the old one'));
});
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 buyerId = 35;
@ -64,7 +36,14 @@ describe('sale updateQuantity()', () => {
}; };
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId)); spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId));
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: 100})))); 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 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
@ -193,6 +172,14 @@ describe('sale updateQuantity()', () => {
const item = await models.Item.findById(itemId, null, options); const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options); await item.updateAttribute('minQuantity', minQuantity, options);
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 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
@ -226,7 +213,15 @@ describe('sale updateQuantity()', () => {
const item = await models.Item.findById(itemId, null, options); const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options); await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Item, 'getVisibleAvailable').and.returnValue((new Promise(resolve => resolve({available: newQuantity}))));
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;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
@ -236,4 +231,87 @@ describe('sale updateQuantity()', () => {
throw e; throw e;
} }
}); });
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));
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) => {
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, 8 as price;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw error when increase quantity and 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));
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) => {
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);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toEqual(new Error('The price of the item changed'));
});
}); });

View File

@ -31,11 +31,13 @@ module.exports = Self => {
const ticketId = changes?.ticketFk || instance?.ticketFk; const ticketId = changes?.ticketFk || instance?.ticketFk;
const itemId = changes?.itemFk || instance?.itemFk; const itemId = changes?.itemFk || instance?.itemFk;
const oldQuantity = instance?.quantity ?? null;
const quantityAdded = newQuantity - oldQuantity;
const ticket = await models.Ticket.findById( const ticket = await models.Ticket.findById(
ticketId, ticketId,
{ {
fields: ['id', 'clientFk', 'warehouseFk', 'shipped'], fields: ['id', 'clientFk', 'warehouseFk', 'addressFk', 'agencyModeFk', 'shipped', 'landed'],
include: { include: {
relation: 'client', relation: 'client',
scope: { scope: {
@ -65,28 +67,46 @@ module.exports = Self => {
fields: ['family', 'minQuantity'], fields: ['family', 'minQuantity'],
where: {id: itemId}, where: {id: itemId},
}, ctx.options); }, ctx.options);
if (item.family == 'EMB') return; if (item.family == 'EMB') return;
const itemInfo = await models.Item.getVisibleAvailable( await models.Sale.rawSql(`CALL catalog_calcFromItem(?,?,?,?)`, [
itemId, ticket.landed,
ticket.warehouseFk, ticket.addressFk,
ticket.shipped, ticket.agencyModeFk,
ctx.options itemId
); ],
ctx.options);
const oldQuantity = instance?.quantity ?? null; const [itemInfo] = await models.Sale.rawSql(`SELECT available FROM tmp.ticketCalculateItem`, null, ctx.options);
const quantityAdded = newQuantity - oldQuantity;
if (itemInfo.available < quantityAdded) if (!itemInfo?.available || itemInfo.available < quantityAdded)
throw new UserError(`This item is not available`); throw new UserError(`This item is not available`);
if (await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*')) return; if (await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*')) return;
if (newQuantity < item.minQuantity && itemInfo.available != newQuantity) if (newQuantity < item.minQuantity && newQuantity != itemInfo?.available)
throw new UserError('The amount cannot be less than the minimum'); throw new UserError('The amount cannot be less than the minimum');
if (!ctx.isNewInstance && newQuantity > oldQuantity) if (ctx.isNewInstance || newQuantity <= oldQuantity) return;
throw new UserError('The new quantity should be smaller than the old one');
const [saleGrouping] = await models.Sale.rawSql(`
SELECT MAX(t.grouping), t.price newPrice
FROM tmp.ticketComponentPrice t
WHERE t.grouping <= ?`,
[quantityAdded],
ctx.options);
await models.Sale.rawSql(`
DROP TEMPORARY TABLE IF EXISTS
tmp.ticketCalculateItem,
tmp.ticketComponentPrice,
tmp.ticketComponent,
tmp.ticketLot,
tmp.zoneGetShipped;
`, null, ctx.options);
if (!saleGrouping?.newPrice || saleGrouping.newPrice < instance.price)
throw new UserError('The price of the item changed');
}); });
}; };