diff --git a/back/methods/account/aclFunc.js b/back/methods/account/aclFunc.js new file mode 100644 index 000000000..47dcd6f7a --- /dev/null +++ b/back/methods/account/aclFunc.js @@ -0,0 +1,33 @@ +module.exports = Self => { + Self.remoteMethodCtx('aclFunc', { + description: 'Get the user information and permissions', + accepts: [ + { + arg: 'property', + type: 'String', + description: 'The user name or email', + required: true + } + ], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/aclFunc`, + verb: 'GET' + } + }); + + Self.aclFunc = async function(ctx, property) { + const userId = ctx.req.accessToken.userId; + const models = Self.app.models; + + const [acl] = await Self.rawSql( + `SELECT a.principalId + FROM salix.ACL a + WHERE a.property = ?`, [property]); + + return await models.Account.hasRole(userId, acl.principalId); + }; +}; diff --git a/back/models/account.js b/back/models/account.js index ba703c68d..5b101eef7 100644 --- a/back/models/account.js +++ b/back/models/account.js @@ -7,6 +7,7 @@ module.exports = Self => { require('../methods/account/change-password')(Self); require('../methods/account/set-password')(Self); require('../methods/account/validate-token')(Self); + require('../methods/account/aclFunc')(Self); // Validations @@ -77,7 +78,7 @@ module.exports = Self => { `SELECT r.name FROM account.user u JOIN account.roleRole rr ON rr.role = u.role - JOIN account.role r ON r.id = rr.inheritsFrom + JOIN account.role r ON r.id = rr.inheritsFrom WHERE u.id = ?`, [userId], options); let roles = []; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 1f66a53cf..fd78a5401 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2651,3 +2651,9 @@ INSERT INTO `vn`.`collection` (`id`, `created`, `workerFk`, `stateFk`, `itemPack INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `level`, `wagon`, `smartTagFk`, `usedShelves`, `itemCount`, `liters`) VALUES (9, 3, util.VN_NOW(), NULL, 0, NULL, NULL, NULL, NULL); + +INSERT INTO salix.ACL +(model, property, accessType, permission, principalType, principalId) +VALUES +('Sale', 'editTracked', 'WRITE', 'ALLOW', 'ROLE', 'production'), +('Sale', 'editCloned', 'WRITE', 'ALLOW', 'ROLE', 'production'); diff --git a/modules/ticket/back/methods/sale/canEdit.js b/modules/ticket/back/methods/sale/canEdit.js index 4e0fc5f8b..f359b19e4 100644 --- a/modules/ticket/back/methods/sale/canEdit.js +++ b/modules/ticket/back/methods/sale/canEdit.js @@ -1,3 +1,5 @@ +const UserError = require('vn-loopback/util/user-error'); + module.exports = Self => { Self.remoteMethodCtx('canEdit', { description: 'Check if all the received sales are aditable', @@ -25,16 +27,26 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - const idsCollection = sales.map(sale => sale.id); - - const saleTracking = await models.SaleTracking.find({where: {saleFk: {inq: idsCollection}}}, myOptions); + /* const firstSale = await models.Sale.findById(sales[0], null, myOptions); + const isTicketEditable = await models.Ticket.isEditable(ctx, firstSale.ticketFk, myOptions); + if (!isTicketEditable) + throw new UserError(`The sales of this ticket can't be modified`);*/ + const saleTracking = await models.SaleTracking.find({where: {saleFk: {inq: sales}}}, myOptions); const hasSaleTracking = saleTracking.length; - const isProductionRole = await models.Account.hasRole(userId, 'production', myOptions); + const saleCloned = await models.SaleCloned.find({where: {saleClonedFk: {inq: sales}}}, myOptions); + const hasSaleCloned = saleCloned.length; - const canEdit = (isProductionRole || !hasSaleTracking); + /* const isProductionRole = await models.Account.hasRole(userId, 'production', myOptions); + const canEdit = (isProductionRole || !hasSaleTracking);// && (isRole || !hasSaleCloned); - return canEdit; + const isRole = await models.Account.hasRole(userId, 'developer', myOptions);*/ + const editTracked = await models.Account.aclFunc(ctx, 'editTracked'); + const editCloned = await models.Account.aclFunc(ctx, 'editCloned'); + console.log(editTracked); + const canEdit = (editTracked || !hasSaleTracking) && (editCloned || !hasSaleCloned); + + return canEdit;// && isTicketEditable; }; }; diff --git a/modules/ticket/back/methods/sale/deleteSales.js b/modules/ticket/back/methods/sale/deleteSales.js index c1359569d..7036821e9 100644 --- a/modules/ticket/back/methods/sale/deleteSales.js +++ b/modules/ticket/back/methods/sale/deleteSales.js @@ -41,7 +41,8 @@ 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); const ticket = await models.Ticket.findById(ticketId, { include: { diff --git a/modules/ticket/back/methods/sale/recalculatePrice.js b/modules/ticket/back/methods/sale/recalculatePrice.js index 59c7d3535..cbe59cad2 100644 --- a/modules/ticket/back/methods/sale/recalculatePrice.js +++ b/modules/ticket/back/methods/sale/recalculatePrice.js @@ -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,13 @@ 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`); diff --git a/modules/ticket/back/methods/sale/reserve.js b/modules/ticket/back/methods/sale/reserve.js index b368f6fc3..c3e9ac30f 100644 --- a/modules/ticket/back/methods/sale/reserve.js +++ b/modules/ticket/back/methods/sale/reserve.js @@ -53,7 +53,8 @@ module.exports = Self => { 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`); diff --git a/modules/ticket/back/methods/sale/specs/canEdit.spec.js b/modules/ticket/back/methods/sale/specs/canEdit.spec.js index 4f6747257..667ce98da 100644 --- a/modules/ticket/back/methods/sale/specs/canEdit.spec.js +++ b/modules/ticket/back/methods/sale/specs/canEdit.spec.js @@ -10,7 +10,7 @@ describe('sale canEdit()', () => { const productionUserID = 49; const ctx = {req: {accessToken: {userId: productionUserID}}}; - const sales = [{id: 3}]; + const sales = [3]; const result = await models.Sale.canEdit(ctx, sales, options); @@ -32,7 +32,7 @@ describe('sale canEdit()', () => { const salesPersonUserID = 18; const ctx = {req: {accessToken: {userId: salesPersonUserID}}}; - const sales = [{id: 10}]; + const sales = [10]; const result = await models.Sale.canEdit(ctx, sales, options); @@ -54,7 +54,7 @@ describe('sale canEdit()', () => { const salesPersonUserID = 18; const ctx = {req: {accessToken: {userId: salesPersonUserID}}}; - const sales = [{id: 3}]; + const sales = [3]; const result = await models.Sale.canEdit(ctx, sales, options); diff --git a/modules/ticket/back/methods/ticket/isEditable.js b/modules/ticket/back/methods/ticket/isEditable.js index 5b9a397a1..bdd18d7de 100644 --- a/modules/ticket/back/methods/ticket/isEditable.js +++ b/modules/ticket/back/methods/ticket/isEditable.js @@ -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,15 @@ module.exports = Self => { } }] }, myOptions); - const isLocked = await Self.app.models.Ticket.isLocked(id, myOptions); + const isLocked = await models.Ticket.isLocked(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) + return true; - return true; + return false; }; }; diff --git a/modules/ticket/back/methods/ticket/isRoleAdvanced.js b/modules/ticket/back/methods/ticket/isRoleAdvanced.js new file mode 100644 index 000000000..7c5c8ed86 --- /dev/null +++ b/modules/ticket/back/methods/ticket/isRoleAdvanced.js @@ -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; + }; +}; diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 8a6ac0c00..032cc8894 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -29,6 +29,9 @@ "SaleChecked": { "dataSource": "vn" }, + "SaleCloned": { + "dataSource": "vn" + }, "SaleComponent": { "dataSource": "vn" }, diff --git a/modules/ticket/back/models/sale-cloned.json b/modules/ticket/back/models/sale-cloned.json new file mode 100644 index 000000000..eb0641684 --- /dev/null +++ b/modules/ticket/back/models/sale-cloned.json @@ -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" + } + } +} diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index 47d105824..7f5984f50 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -28,6 +28,7 @@ module.exports = Self => { require('../methods/ticket/freightCost')(Self); require('../methods/ticket/getComponentsSum')(Self); require('../methods/ticket/refund')(Self); + require('../methods/ticket/isRoleAdvanced')(Self); Self.observe('before save', async function(ctx) { const loopBackContext = LoopBackContext.getCurrentContext();