Merge pull request '3569-refactor_isEditable-canEdit' (!1082) from 3569-refactor_isEditable-canEdit into dev
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #1082 Reviewed-by: Juan Ferrer <juan@verdnatura.es> Reviewed-by: Vicent Llopis <vicent@verdnatura.es>
This commit is contained in:
commit
b57a64227b
|
@ -1,14 +1,8 @@
|
|||
// Coloque su configuración en este archivo para sobrescribir la configuración predeterminada y de usuario.
|
||||
{
|
||||
// Carácter predeterminado de final de línea.
|
||||
"files.eol": "\n",
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"json"
|
||||
]
|
||||
}
|
||||
// Coloque su configuración en este archivo para sobrescribir la configuración predeterminada y de usuario.
|
||||
{
|
||||
// Carácter predeterminado de final de línea.
|
||||
"files.eol": "\n",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('Sale', 'editTracked', 'WRITE', 'ALLOW', 'ROLE', 'production'),
|
||||
('Sale', 'editFloramondo', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant');
|
|
@ -984,7 +984,7 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
|
|||
(30, 4, 18, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
||||
(31, 2, 23, 'Melee weapon combat fist 15cm', -5, 7.08, 0, 0, 0, util.VN_CURDATE()),
|
||||
(32, 1, 24, 'Ranged weapon longbow 2m', -1, 8.07, 0, 0, 0, util.VN_CURDATE()),
|
||||
(33, 5, 14, 'Ranged weapon pistol 9mm', 50, 1.79, 0, 0, 0, util.VN_CURDATE());
|
||||
(33, 5, 14, 'Ranged weapon pistol 9mm', 50, 1.79, 0, 0, 0, util.VN_CURDATE());
|
||||
|
||||
INSERT INTO `vn`.`saleChecked`(`saleFk`, `isChecked`)
|
||||
VALUES
|
||||
|
@ -1146,10 +1146,11 @@ INSERT INTO `vncontrol`.`accion`(`accion_id`, `accion`)
|
|||
|
||||
INSERT INTO `vn`.`saleTracking`(`saleFk`, `isChecked`, `created`, `originalQuantity`, `workerFk`, `actionFk`, `id`, `stateFk`)
|
||||
VALUES
|
||||
(1, 0, util.VN_CURDATE(), 5, 55, 3, 1, 14),
|
||||
(1, 1, util.VN_CURDATE(), 5, 54, 3, 2, 8),
|
||||
(2, 1, util.VN_CURDATE(), 10, 40, 4, 3, 8),
|
||||
(3, 1, util.VN_CURDATE(), 2, 40, 4, 4, 8);
|
||||
(1, 0, util.VN_CURDATE(), 5, 55, 3, 1, 14),
|
||||
(1, 1, util.VN_CURDATE(), 5, 54, 3, 2, 8),
|
||||
(2, 1, util.VN_CURDATE(), 10, 40, 4, 3, 8),
|
||||
(3, 1, util.VN_CURDATE(), 2, 40, 4, 4, 8),
|
||||
(31, 1, util.VN_CURDATE(), -5, 40, 4, 5, 8);
|
||||
|
||||
INSERT INTO `vn`.`itemBarcode`(`id`, `itemFk`, `code`)
|
||||
VALUES
|
||||
|
@ -1356,7 +1357,8 @@ INSERT INTO `vn`.`ticketWeekly`(`ticketFk`, `weekDay`)
|
|||
(2, 1),
|
||||
(3, 2),
|
||||
(4, 4),
|
||||
(5, 6);
|
||||
(5, 6),
|
||||
(15, 6);
|
||||
|
||||
INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseOutFk`, `agencyModeFk`, `m3`, `kg`,`ref`, `totalEntries`, `cargoSupplierFk`)
|
||||
VALUES
|
||||
|
@ -2712,6 +2714,10 @@ INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `lev
|
|||
VALUES
|
||||
(9, 3, util.VN_NOW(), NULL, 0, NULL, NULL, NULL, NULL);
|
||||
|
||||
INSERT INTO `vn`.`saleCloned` (`saleClonedFk`, `saleOriginalFk`)
|
||||
VALUES
|
||||
(29, 25);
|
||||
|
||||
UPDATE `account`.`user`
|
||||
SET `hasGrant` = 1
|
||||
WHERE `id` = 66;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import selectors from '../../helpers/selectors.js';
|
||||
import getBrowser from '../../helpers/puppeteer';
|
||||
|
||||
describe('Ticket expeditions and log path', () => {
|
||||
fdescribe('Ticket expeditions and log path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('Ticket descriptor path', () => {
|
|||
it('should count the amount of tickets in the turns section', async() => {
|
||||
const result = await page.countElement(selectors.ticketsIndex.weeklyTicket);
|
||||
|
||||
expect(result).toEqual(5);
|
||||
expect(result).toEqual(6);
|
||||
});
|
||||
|
||||
it('should go back to the ticket index then search and access a ticket summary', async() => {
|
||||
|
@ -104,7 +104,7 @@ describe('Ticket descriptor path', () => {
|
|||
await page.doSearch();
|
||||
const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult);
|
||||
|
||||
expect(nResults).toEqual(5);
|
||||
expect(nResults).toEqual(6);
|
||||
});
|
||||
|
||||
it('should update the agency then remove it afterwards', async() => {
|
||||
|
|
|
@ -136,5 +136,6 @@
|
|||
"Not enough privileges to edit a client": "Not enough privileges to edit a client",
|
||||
"Claim pickup order sent": "Claim pickup order sent [({{claimId}})]({{{claimUrl}}}) to client *{{clientName}}*",
|
||||
"You don't have grant privilege": "You don't have grant privilege",
|
||||
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user"
|
||||
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user",
|
||||
"Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
module.exports = function(app) {
|
||||
app.models.ACL.checkAccessAcl = async(ctx, modelId, property, accessType = '*') => {
|
||||
const models = app.models;
|
||||
const context = {
|
||||
accessToken: ctx.req.accessToken,
|
||||
model: models[modelId],
|
||||
property: property,
|
||||
modelId: modelId,
|
||||
accessType: accessType,
|
||||
sharedMethod: {
|
||||
name: property,
|
||||
aliases: [],
|
||||
sharedClass: true
|
||||
}
|
||||
};
|
||||
|
||||
const acl = await models.ACL.checkAccessForContext(context);
|
||||
|
||||
return acl.permission == 'ALLOW';
|
||||
};
|
||||
};
|
|
@ -1,10 +1,12 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('canEdit', {
|
||||
description: 'Check if all the received sales are aditable',
|
||||
accessType: 'READ',
|
||||
accepts: [{
|
||||
arg: 'sales',
|
||||
type: ['object'],
|
||||
type: ['number'],
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
|
@ -12,29 +14,48 @@ module.exports = Self => {
|
|||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/isEditable`,
|
||||
verb: 'get'
|
||||
path: `/canEdit`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.canEdit = async(ctx, sales, options) => {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const idsCollection = sales.map(sale => sale.id);
|
||||
const salesData = await models.Sale.find({
|
||||
fields: ['id', 'itemFk', 'ticketFk'],
|
||||
where: {id: {inq: sales}},
|
||||
include:
|
||||
{
|
||||
relation: 'item',
|
||||
scope: {
|
||||
fields: ['id', 'isFloramondo'],
|
||||
}
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
const saleTracking = await models.SaleTracking.find({where: {saleFk: {inq: idsCollection}}}, myOptions);
|
||||
const ticketId = salesData[0].ticketFk;
|
||||
|
||||
const hasSaleTracking = saleTracking.length;
|
||||
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
|
||||
if (!isTicketEditable)
|
||||
throw new UserError(`The sales of this ticket can't be modified`);
|
||||
|
||||
const isProductionRole = await models.Account.hasRole(userId, 'production', myOptions);
|
||||
const hasSaleTracking = await models.SaleTracking.findOne({where: {saleFk: {inq: sales}}}, myOptions);
|
||||
const hasSaleCloned = await models.SaleCloned.findOne({where: {saleClonedFk: {inq: sales}}}, myOptions);
|
||||
const hasSaleFloramondo = salesData.find(sale => sale.item().isFloramondo);
|
||||
|
||||
const canEdit = (isProductionRole || !hasSaleTracking);
|
||||
const canEditTracked = await models.ACL.checkAccessAcl(ctx, 'Sale', 'editTracked');
|
||||
const canEditCloned = await models.ACL.checkAccessAcl(ctx, 'Sale', 'editCloned');
|
||||
const canEditFloramondo = await models.ACL.checkAccessAcl(ctx, 'Sale', 'editFloramondo');
|
||||
|
||||
return canEdit;
|
||||
const shouldEditTracked = canEditTracked || !hasSaleTracking;
|
||||
const shouldEditCloned = canEditCloned || !hasSaleCloned;
|
||||
const shouldEditFloramondo = canEditFloramondo || !hasSaleFloramondo;
|
||||
|
||||
return shouldEditTracked && shouldEditCloned && shouldEditFloramondo;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -41,7 +41,11 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
try {
|
||||
const canEditSales = await models.Sale.canEdit(ctx, sales, myOptions);
|
||||
const saleIds = sales.map(sale => sale.id);
|
||||
|
||||
const canEditSales = await models.Sale.canEdit(ctx, saleIds, myOptions);
|
||||
if (!canEditSales)
|
||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||
|
||||
const ticket = await models.Ticket.findById(ticketId, {
|
||||
include: {
|
||||
|
@ -57,13 +61,6 @@ module.exports = Self => {
|
|||
}
|
||||
}, myOptions);
|
||||
|
||||
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
|
||||
if (!isTicketEditable)
|
||||
throw new UserError(`The sales of this ticket can't be modified`);
|
||||
|
||||
if (!canEditSales)
|
||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||
|
||||
const promises = [];
|
||||
let deletions = '';
|
||||
for (let sale of sales) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('recalculatePrice', {
|
||||
description: 'Calculates the price of sales and its components',
|
||||
|
@ -34,15 +35,9 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
try {
|
||||
const salesIds = [];
|
||||
for (let sale of sales)
|
||||
salesIds.push(sale.id);
|
||||
const salesIds = sales.map(sale => sale.id);
|
||||
|
||||
const isEditable = await models.Ticket.isEditable(ctx, sales[0].ticketFk, myOptions);
|
||||
if (!isEditable)
|
||||
throw new UserError(`The sales of this ticket can't be modified`);
|
||||
|
||||
const canEditSale = await models.Sale.canEdit(ctx, sales, myOptions);
|
||||
const canEditSale = await models.Sale.canEdit(ctx, salesIds, myOptions);
|
||||
if (!canEditSale)
|
||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||
|
||||
|
|
|
@ -49,12 +49,9 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
try {
|
||||
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
|
||||
if (!isTicketEditable)
|
||||
throw new UserError(`The sales of this ticket can't be modified`);
|
||||
|
||||
const canEditSale = await models.Sale.canEdit(ctx, sales, myOptions);
|
||||
const salesIds = sales.map(sale => sale.id);
|
||||
|
||||
const canEditSale = await models.Sale.canEdit(ctx, salesIds, myOptions);
|
||||
if (!canEditSale)
|
||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||
|
||||
|
|
|
@ -1,69 +1,193 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('sale canEdit()', () => {
|
||||
it('should return true if the role is production regardless of the saleTrackings', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
const employeeId = 1;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
describe('sale editTracked', () => {
|
||||
it('should return true if the role is production regardless of the saleTrackings', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
const productionUserID = 49;
|
||||
const ctx = {req: {accessToken: {userId: productionUserID}}};
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const sales = [{id: 3}];
|
||||
const productionUserID = 49;
|
||||
const ctx = {req: {accessToken: {userId: productionUserID}}};
|
||||
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
const sales = [25];
|
||||
|
||||
expect(result).toEqual(true);
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
expect(result).toEqual(true);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return true if the role is not production and none of the sales has saleTracking', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const salesPersonUserID = 18;
|
||||
const ctx = {req: {accessToken: {userId: salesPersonUserID}}};
|
||||
|
||||
const sales = [10];
|
||||
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return false if any of the sales has a saleTracking record', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const buyerId = 35;
|
||||
const ctx = {req: {accessToken: {userId: buyerId}}};
|
||||
|
||||
const sales = [31];
|
||||
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true if the role is not production and none of the sales has saleTracking', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
describe('sale editCloned', () => {
|
||||
const saleCloned = [29];
|
||||
it('should return false if any of the sales is cloned', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const salesPersonUserID = 18;
|
||||
const ctx = {req: {accessToken: {userId: salesPersonUserID}}};
|
||||
const buyerId = 35;
|
||||
const ctx = {req: {accessToken: {userId: buyerId}}};
|
||||
|
||||
const sales = [{id: 10}];
|
||||
const result = await models.Sale.canEdit(ctx, saleCloned, options);
|
||||
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
expect(result).toEqual(false);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
it('should return true if any of the sales is cloned and has the correct role', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
const roleEnabled = await models.ACL.findOne({
|
||||
where: {
|
||||
model: 'Sale',
|
||||
property: 'editCloned',
|
||||
permission: 'ALLOW'
|
||||
}
|
||||
});
|
||||
if (!roleEnabled || !roleEnabled.principalId) return await tx.rollback();
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const role = await models.Role.findOne({
|
||||
where: {
|
||||
name: roleEnabled.principalId
|
||||
}
|
||||
});
|
||||
const ctx = {req: {accessToken: {userId: role.id}}};
|
||||
|
||||
const result = await models.Sale.canEdit(ctx, saleCloned, options);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false if any of the sales has a saleTracking record', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
describe('sale editFloramondo', () => {
|
||||
it('should return false if any of the sales isFloramondo', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
const sales = [26];
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const salesPersonUserID = 18;
|
||||
const ctx = {req: {accessToken: {userId: salesPersonUserID}}};
|
||||
const ctx = {req: {accessToken: {userId: employeeId}}};
|
||||
|
||||
const sales = [{id: 3}];
|
||||
// For test
|
||||
const saleToEdit = await models.Sale.findById(sales[0], null, options);
|
||||
await saleToEdit.updateAttribute('itemFk', 9, options);
|
||||
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
expect(result).toEqual(false);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return true if any of the sales is of isFloramondo and has the correct role', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
const sales = [26];
|
||||
|
||||
const roleEnabled = await models.ACL.findOne({
|
||||
where: {
|
||||
model: 'Sale',
|
||||
property: 'editFloramondo',
|
||||
permission: 'ALLOW'
|
||||
}
|
||||
});
|
||||
|
||||
if (!roleEnabled || !roleEnabled.principalId) return await tx.rollback();
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const role = await models.Role.findOne({
|
||||
where: {
|
||||
name: roleEnabled.principalId
|
||||
}
|
||||
});
|
||||
const ctx = {req: {accessToken: {userId: role.id}}};
|
||||
|
||||
// For test
|
||||
const saleToEdit = await models.Sale.findById(sales[0], null, options);
|
||||
await saleToEdit.updateAttribute('itemFk', 9, options);
|
||||
|
||||
const result = await models.Sale.canEdit(ctx, sales, options);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ const models = require('vn-loopback/server/server').models;
|
|||
describe('sale reserve()', () => {
|
||||
const ctx = {
|
||||
req: {
|
||||
accessToken: {userId: 9},
|
||||
accessToken: {userId: 1},
|
||||
headers: {origin: 'localhost:5000'},
|
||||
__: () => {}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models;
|
|||
|
||||
describe('sale updateConcept()', () => {
|
||||
const ctx = {req: {accessToken: {userId: 9}}};
|
||||
const saleId = 1;
|
||||
const saleId = 25;
|
||||
|
||||
it('should throw if ID was undefined', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
|
|
@ -9,32 +9,21 @@ describe('sale updateQuantity()', () => {
|
|||
}
|
||||
};
|
||||
|
||||
it('should throw an error if the quantity is not a number', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Sale.updateQuantity(ctx, 1, 'wrong quantity!', options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toEqual(new Error('The value should be a number'));
|
||||
});
|
||||
|
||||
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'},
|
||||
__: () => {}
|
||||
}
|
||||
};
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Sale.updateQuantity(ctx, 1, 99, options);
|
||||
await models.Sale.updateQuantity(ctx, 17, 99, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -45,21 +34,60 @@ describe('sale updateQuantity()', () => {
|
|||
expect(error).toEqual(new Error('The new quantity should be smaller than the old one'));
|
||||
});
|
||||
|
||||
it('should update the quantity of a given sale current line', async() => {
|
||||
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
|
||||
const tx = await models.Sale.beginTransaction({});
|
||||
const saleId = 17;
|
||||
const buyerId = 35;
|
||||
const ctx = {
|
||||
req: {
|
||||
accessToken: {userId: buyerId},
|
||||
headers: {origin: 'localhost:5000'},
|
||||
__: () => {}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const originalLine = await models.Sale.findOne({where: {id: 1}, fields: ['quantity']}, options);
|
||||
const isRoleAdvanced = await models.Ticket.isRoleAdvanced(ctx, options);
|
||||
|
||||
expect(originalLine.quantity).toEqual(5);
|
||||
expect(isRoleAdvanced).toEqual(true);
|
||||
|
||||
await models.Sale.updateQuantity(ctx, 1, 4, options);
|
||||
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
|
||||
|
||||
const modifiedLine = await models.Sale.findOne({where: {id: 1}, fields: ['quantity']}, options);
|
||||
expect(originalLine.quantity).toEqual(30);
|
||||
|
||||
expect(modifiedLine.quantity).toEqual(4);
|
||||
const newQuantity = originalLine.quantity + 1;
|
||||
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);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should update the quantity of a given sale current line', async() => {
|
||||
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);
|
||||
|
||||
expect(originalLine.quantity).toEqual(20);
|
||||
|
||||
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);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
|
@ -66,12 +66,7 @@ module.exports = Self => {
|
|||
|
||||
const sale = await models.Sale.findById(id, filter, myOptions);
|
||||
|
||||
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions);
|
||||
if (!isEditable)
|
||||
throw new UserError(`The sales of this ticket can't be modified`);
|
||||
|
||||
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
|
||||
|
||||
if (!canEditSale)
|
||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||
|
||||
|
|
|
@ -42,13 +42,9 @@ module.exports = Self => {
|
|||
|
||||
try {
|
||||
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions);
|
||||
|
||||
if (!canEditSale)
|
||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||
|
||||
if (isNaN(newQuantity))
|
||||
throw new UserError(`The value should be a number`);
|
||||
|
||||
const filter = {
|
||||
include: {
|
||||
relation: 'ticket',
|
||||
|
@ -70,7 +66,8 @@ module.exports = Self => {
|
|||
|
||||
const sale = await models.Sale.findById(id, filter, myOptions);
|
||||
|
||||
if (newQuantity > sale.quantity)
|
||||
const isRoleAdvanced = await models.Ticket.isRoleAdvanced(ctx, myOptions);
|
||||
if (newQuantity > sale.quantity && !isRoleAdvanced)
|
||||
throw new UserError('The new quantity should be smaller than the old one');
|
||||
|
||||
const oldQuantity = sale.quantity;
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('ticket-weekly filter()', () => {
|
|||
const firstRow = result[0];
|
||||
|
||||
expect(firstRow.ticketFk).toEqual(1);
|
||||
expect(result.length).toEqual(5);
|
||||
expect(result.length).toEqual(6);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
|
@ -20,24 +20,20 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.isEditable = async(ctx, id, options) => {
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
let state = await Self.app.models.TicketState.findOne({
|
||||
const state = await models.TicketState.findOne({
|
||||
where: {ticketFk: id}
|
||||
}, myOptions);
|
||||
|
||||
const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant', myOptions);
|
||||
const isDeliveryBoss = await Self.app.models.Account.hasRole(userId, 'deliveryBoss', myOptions);
|
||||
const isBuyer = await Self.app.models.Account.hasRole(userId, 'buyer', myOptions);
|
||||
const isRoleAdvanced = await models.Ticket.isRoleAdvanced(ctx, myOptions);
|
||||
|
||||
const isValidRole = isSalesAssistant || isDeliveryBoss || isBuyer;
|
||||
|
||||
let alertLevel = state ? state.alertLevel : null;
|
||||
let ticket = await Self.app.models.Ticket.findById(id, {
|
||||
const alertLevel = state ? state.alertLevel : null;
|
||||
const ticket = await models.Ticket.findById(id, {
|
||||
fields: ['clientFk'],
|
||||
include: [{
|
||||
relation: 'client',
|
||||
|
@ -48,15 +44,17 @@ module.exports = Self => {
|
|||
}
|
||||
}]
|
||||
}, myOptions);
|
||||
const isLocked = await Self.app.models.Ticket.isLocked(id, myOptions);
|
||||
|
||||
const isLocked = await models.Ticket.isLocked(id, myOptions);
|
||||
const isWeekly = await models.TicketWeekly.findOne({where: {ticketFk: id}}, myOptions);
|
||||
|
||||
const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0);
|
||||
const isNormalClient = ticket && ticket.client().type().code == 'normal';
|
||||
const validAlertAndRoleNormalClient = (alertLevelGreaterThanZero && isNormalClient && !isValidRole);
|
||||
const isEditable = !(alertLevelGreaterThanZero && isNormalClient);
|
||||
|
||||
if (!ticket || validAlertAndRoleNormalClient || isLocked)
|
||||
return false;
|
||||
if (ticket && (isEditable || isRoleAdvanced) && !isLocked && !isWeekly)
|
||||
return true;
|
||||
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('isRoleAdvanced', {
|
||||
description: 'Check if a ticket is editable',
|
||||
accessType: 'READ',
|
||||
returns: {
|
||||
type: 'boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/isRoleAdvanced`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.isRoleAdvanced = async(ctx, options) => {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
|
||||
const isDeliveryBoss = await models.Account.hasRole(userId, 'deliveryBoss', myOptions);
|
||||
const isBuyer = await models.Account.hasRole(userId, 'buyer', myOptions);
|
||||
const isClaimManager = await models.Account.hasRole(userId, 'claimManager', myOptions);
|
||||
|
||||
const isRoleAdvanced = isSalesAssistant || isDeliveryBoss || isBuyer || isClaimManager;
|
||||
|
||||
return isRoleAdvanced;
|
||||
};
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('ticket componentUpdate()', () => {
|
||||
const userID = 1101;
|
||||
|
@ -178,10 +179,15 @@ describe('ticket componentUpdate()', () => {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: ctx.req
|
||||
});
|
||||
const oldTicket = await models.Ticket.findById(ticketID, null, options);
|
||||
|
||||
await models.Ticket.componentUpdate(ctx, options);
|
||||
|
||||
const [newTicketID] = await models.Ticket.rawSql('SELECT MAX(id) as id FROM ticket', null, options);
|
||||
const oldTicket = await models.Ticket.findById(ticketID, null, options);
|
||||
const newTicket = await models.Ticket.findById(newTicketID.id, null, options);
|
||||
const newTicketSale = await models.Sale.findOne({where: {ticketFk: args.id}}, options);
|
||||
|
||||
|
|
|
@ -134,4 +134,23 @@ describe('ticket isEditable()', () => {
|
|||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not be able to edit if is a ticket weekly', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 1}}};
|
||||
|
||||
const result = await models.Ticket.isEditable(ctx, 15, options);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
"SaleChecked": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"SaleCloned": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"SaleComponent": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "SaleCloned",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "saleCloned"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"saleClonedFk": {
|
||||
"id": true
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"saleOriginal": {
|
||||
"type": "belongsTo",
|
||||
"model": "Sale",
|
||||
"foreignKey": "saleOriginalFk"
|
||||
},
|
||||
"saleCloned": {
|
||||
"type": "belongsTo",
|
||||
"model": "Sale",
|
||||
"foreignKey": "saleClonedFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,5 +33,6 @@ module.exports = function(Self) {
|
|||
require('../methods/ticket/closeByTicket')(Self);
|
||||
require('../methods/ticket/closeByAgency')(Self);
|
||||
require('../methods/ticket/closeByRoute')(Self);
|
||||
require('../methods/ticket/isRoleAdvanced')(Self);
|
||||
require('../methods/ticket/collectionLabel')(Self);
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
"jsdom": "^16.7.0",
|
||||
"jszip": "^3.10.0",
|
||||
"ldapjs": "^2.2.0",
|
||||
"loopback": "^3.26.0",
|
||||
"loopback": "^3.28.0",
|
||||
"loopback-boot": "3.3.1",
|
||||
"loopback-component-explorer": "^6.5.0",
|
||||
"loopback-component-storage": "3.6.1",
|
||||
|
|
Loading…
Reference in New Issue