diff --git a/db/changes/233201/00-updatePrice.sql b/db/changes/233201/00-updatePrice.sql new file mode 100644 index 000000000..959943d6f --- /dev/null +++ b/db/changes/233201/00-updatePrice.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`) + VALUES ('Ticket','canEditWeekly','WRITE','ALLOW','ROLE','buyer'); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 030afbe9e..135bb2be9 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -178,5 +178,9 @@ "The renew period has not been exceeded": "The renew period has not been exceeded", "You can not use the same password": "You can not use the same password", "Valid priorities": "Valid priorities: %d", - "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}" + "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}", + "You don't have enough privileges.": "You don't have enough privileges.", + "This ticket is locked.": "This ticket is locked.", + "This ticket is not editable.": "This ticket is not editable.", + "The ticket doesn't exist.": "The ticket doesn't exist." } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index d95e8d8a4..ac62d62e1 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -307,5 +307,9 @@ "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", - "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado" -} + "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", + "You don't have enough privileges.": "No tienes suficientes permisos.", + "This ticket is locked.": "Este ticket está bloqueado.", + "This ticket is not editable.": "Este ticket no es editable.", + "The ticket doesn't exist.": "No existe el ticket." +} \ No newline at end of file diff --git a/modules/ticket/back/methods/ticket/addSale.js b/modules/ticket/back/methods/ticket/addSale.js index 2cd02a8a4..cbf884273 100644 --- a/modules/ticket/back/methods/ticket/addSale.js +++ b/modules/ticket/back/methods/ticket/addSale.js @@ -1,4 +1,3 @@ - const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { @@ -47,9 +46,7 @@ module.exports = Self => { } try { - const isEditable = await models.Ticket.isEditable(ctx, id, myOptions); - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await models.Ticket.isEditableOrThrow(ctx, id, myOptions); const item = await models.Item.findById(itemId, null, myOptions); const ticket = await models.Ticket.findById(id, { diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index 48aee36d4..b5ff50d59 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -1,4 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); const loggable = require('vn-loopback/util/log'); module.exports = Self => { @@ -116,10 +115,7 @@ module.exports = Self => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; const $t = ctx.req.__; // $translate - const isEditable = await models.Ticket.isEditable(ctx, args.id, myOptions); - - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await models.Ticket.isEditableOrThrow(ctx, args.id, myOptions); const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE'); if (!editZone) { diff --git a/modules/ticket/back/methods/ticket/isEditable.js b/modules/ticket/back/methods/ticket/isEditable.js index 13bd4d57f..f657cd5e3 100644 --- a/modules/ticket/back/methods/ticket/isEditable.js +++ b/modules/ticket/back/methods/ticket/isEditable.js @@ -20,41 +20,12 @@ module.exports = Self => { }); Self.isEditable = async(ctx, id, options) => { - const models = Self.app.models; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - const state = await models.TicketState.findOne({ - where: {ticketFk: id} - }, myOptions); - - const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); - - const alertLevel = state ? state.alertLevel : null; - const ticket = await models.Ticket.findById(id, { - fields: ['clientFk'], - include: [{ - relation: 'client', - scope: { - include: { - relation: 'type' - } - } - }] - }, 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 isEditable = !(alertLevelGreaterThanZero && isNormalClient); - - if (ticket && (isEditable || isRoleAdvanced) && !isLocked && !isWeekly) - return true; - - return false; + try { + await Self.isEditableOrThrow(ctx, id, options); + } catch (e) { + if (e.name === 'ForbiddenError') return false; + throw e; + } + return true; }; }; diff --git a/modules/ticket/back/methods/ticket/isEditableOrThrow.js b/modules/ticket/back/methods/ticket/isEditableOrThrow.js new file mode 100644 index 000000000..6a8bafa2c --- /dev/null +++ b/modules/ticket/back/methods/ticket/isEditableOrThrow.js @@ -0,0 +1,49 @@ +const ForbiddenError = require('vn-loopback/util/forbiddenError'); + +module.exports = Self => { + Self.isEditableOrThrow = async(ctx, id, options) => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const state = await models.TicketState.findOne({ + where: {ticketFk: id} + }, myOptions); + + const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); + 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', + scope: { + include: { + relation: 'type' + } + } + }] + }, 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 isEditable = !(alertLevelGreaterThanZero && isNormalClient); + + if (!ticket) + throw new ForbiddenError(`The ticket doesn't exist.`); + + if (!isEditable && !isRoleAdvanced) + throw new ForbiddenError(`This ticket is not editable.`); + + if (isLocked) + throw new ForbiddenError(`This ticket is locked.`); + + if (isWeekly && !canEditWeeklyTicket) + throw new ForbiddenError(`You don't have enough privileges.`); + }; +}; diff --git a/modules/ticket/back/methods/ticket/priceDifference.js b/modules/ticket/back/methods/ticket/priceDifference.js index 42a71f1c8..495e9e1aa 100644 --- a/modules/ticket/back/methods/ticket/priceDifference.js +++ b/modules/ticket/back/methods/ticket/priceDifference.js @@ -1,5 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { Self.remoteMethodCtx('priceDifference', { description: 'Returns sales with price difference if the ticket is editable', @@ -72,10 +70,7 @@ module.exports = Self => { } try { - const isEditable = await Self.isEditable(ctx, args.id, myOptions); - - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await Self.isEditableOrThrow(ctx, args.id, myOptions); const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE'); if (!editZone) { diff --git a/modules/ticket/back/methods/ticket/recalculateComponents.js b/modules/ticket/back/methods/ticket/recalculateComponents.js index eb9c8a72e..bdf74f8b6 100644 --- a/modules/ticket/back/methods/ticket/recalculateComponents.js +++ b/modules/ticket/back/methods/ticket/recalculateComponents.js @@ -1,4 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('recalculateComponents', { description: 'Calculates the price of a sale and its components', @@ -33,10 +32,7 @@ module.exports = Self => { } try { - const isEditable = await Self.isEditable(ctx, id, myOptions); - - if (!isEditable) - throw new UserError(`The current ticket can't be modified`); + await Self.isEditableOrThrow(ctx, id, myOptions); const recalculation = await Self.rawSql('CALL vn.ticket_recalcComponents(?, NULL)', [id], myOptions); diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index 878cce056..2f8c402da 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -39,10 +39,7 @@ module.exports = Self => { const ticketToDelete = await models.Ticket.findById(id, {fields: ['isDeleted']}, myOptions); if (ticketToDelete.isDeleted) return false; - const isEditable = await Self.isEditable(ctx, id, myOptions); - - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await Self.isEditableOrThrow(ctx, id, myOptions); // Check if ticket has refunds const ticketRefunds = await models.TicketRefund.find({ diff --git a/modules/ticket/back/methods/ticket/specs/addSale.spec.js b/modules/ticket/back/methods/ticket/specs/addSale.spec.js index 2e568716a..b1ecb133b 100644 --- a/modules/ticket/back/methods/ticket/specs/addSale.spec.js +++ b/modules/ticket/back/methods/ticket/specs/addSale.spec.js @@ -97,6 +97,6 @@ describe('ticket addSale()', () => { error = e; } - expect(error.message).toEqual(`The sales of this ticket can't be modified`); + expect(error.message).toEqual(`This ticket is locked.`); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/isEditable.spec.js b/modules/ticket/back/methods/ticket/specs/isEditable.spec.js index 7337017d6..301745ed3 100644 --- a/modules/ticket/back/methods/ticket/specs/isEditable.spec.js +++ b/modules/ticket/back/methods/ticket/specs/isEditable.spec.js @@ -1,156 +1,37 @@ const models = require('vn-loopback/server/server').models; -describe('ticket isEditable()', () => { - it('should return false if the given ticket does not exist', async() => { +describe('isEditable()', () => { + it('should return false if It is able to edit', async() => { const tx = await models.Ticket.beginTransaction({}); let result; try { const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 9}} - }; - - result = await models.Ticket.isEditable(ctx, 9999, options); - + const ctx = {req: {accessToken: {userId: 35}}}; + result = await models.Ticket.isEditable(ctx, 5, options); await tx.rollback(); - } catch (e) { + } catch (error) { await tx.rollback(); throw e; } - expect(result).toEqual(false); + expect(result).toBeFalse(); }); - it(`should return false if the given ticket isn't invoiced but isDeleted`, async() => { + it('should return true if It is able to edit', async() => { const tx = await models.Ticket.beginTransaction({}); let result; try { const options = {transaction: tx}; - const deletedTicket = await models.Ticket.findOne({ - where: { - invoiceOut: null, - isDeleted: true - }, - fields: ['id'] - }); - - const ctx = { - req: {accessToken: {userId: 9}} - }; - - result = await models.Ticket.isEditable(ctx, deletedTicket.id, options); - + const ctx = {req: {accessToken: {userId: 35}}}; + result = await models.Ticket.isEditable(ctx, 15, options); await tx.rollback(); - } catch (e) { + } catch (error) { await tx.rollback(); throw e; } - expect(result).toEqual(false); - }); - - it('should return true if the given ticket is editable', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 9}} - }; - - result = await models.Ticket.isEditable(ctx, 16, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(result).toEqual(true); - }); - - it('should not be able to edit a deleted or invoiced ticket even for salesAssistant', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 21}} - }; - - result = await models.Ticket.isEditable(ctx, 19, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(result).toEqual(false); - }); - - it('should not be able to edit a deleted or invoiced ticket even for productionBoss', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 50}} - }; - - result = await models.Ticket.isEditable(ctx, 19, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(result).toEqual(false); - }); - - it('should not be able to edit a deleted or invoiced ticket even for salesPerson', async() => { - const tx = await models.Ticket.beginTransaction({}); - let result; - - try { - const options = {transaction: tx}; - const ctx = { - req: {accessToken: {userId: 18}} - }; - - result = await models.Ticket.isEditable(ctx, 19, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - 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; - } + expect(result).toBeTrue(); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js b/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js new file mode 100644 index 000000000..6c89bac26 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js @@ -0,0 +1,96 @@ +const models = require('vn-loopback/server/server').models; + +describe('ticket isEditableOrThrow()', () => { + it('should throw an error as the ticket does not exist', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + try { + const options = {transaction: tx}; + const ctx = { + req: {accessToken: {userId: 9}} + }; + + await models.Ticket.isEditableOrThrow(ctx, 9999, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toEqual(`The ticket doesn't exist.`); + }); + + it('should throw an error as this ticket is not editable', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + + try { + const options = {transaction: tx}; + const ctx = { + req: {accessToken: {userId: 1}} + }; + + await models.Ticket.isEditableOrThrow(ctx, 8, options); + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This ticket is not editable.`); + }); + + it('should throw an error as this ticket is locked.', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + try { + const options = {transaction: tx}; + const ctx = { + req: {accessToken: {userId: 18}} + }; + + await models.Ticket.isEditableOrThrow(ctx, 19, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This ticket is locked.`); + }); + + it('should throw an error as you do not have enough privileges.', async() => { + const tx = await models.Ticket.beginTransaction({}); + let error; + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: 1}}}; + + await models.Ticket.isEditableOrThrow(ctx, 15, options); + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`You don't have enough privileges.`); + }); + + it('should return undefined if It can be edited', async() => { + const tx = await models.Ticket.beginTransaction({}); + let result; + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: 35}}}; + + result = await models.Ticket.isEditableOrThrow(ctx, 15, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(result).toBeUndefined(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js index cc54353ef..d01f0c1bb 100644 --- a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js +++ b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js @@ -1,5 +1,6 @@ const models = require('vn-loopback/server/server').models; const UserError = require('vn-loopback/util/user-error'); +const ForbiddenError = require('vn-loopback/util/forbiddenError'); describe('sale priceDifference()', () => { it('should return ticket price differences', async() => { @@ -59,7 +60,7 @@ describe('sale priceDifference()', () => { await tx.rollback(); } - expect(error).toEqual(new UserError(`The sales of this ticket can't be modified`)); + expect(error).toEqual(new ForbiddenError(`This ticket is not editable.`)); }); it('should return ticket movable', async() => { diff --git a/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js index ed10d114f..383c2c6d5 100644 --- a/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js +++ b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js @@ -1,4 +1,5 @@ const models = require('vn-loopback/server/server').models; +const ForbiddenError = require('vn-loopback/util/forbiddenError'); describe('ticket recalculateComponents()', () => { const ticketId = 11; @@ -38,6 +39,6 @@ describe('ticket recalculateComponents()', () => { error = e; } - expect(error).toEqual(new Error(`The current ticket can't be modified`)); + expect(error).toEqual(new ForbiddenError(`This ticket is locked.`)); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/transferClient.spec.js b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js index ed10e5159..c09c20083 100644 --- a/modules/ticket/back/methods/ticket/specs/transferClient.spec.js +++ b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js @@ -23,7 +23,7 @@ describe('Ticket transferClient()', () => { error = e; } - expect(error.message).toEqual(`The current ticket can't be modified`); + expect(error.message).toEqual(`This ticket is locked.`); }); it('should be assigned a different clientFk', async() => { diff --git a/modules/ticket/back/methods/ticket/specs/transferSales.spec.js b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js index 562688917..ef92e88c0 100644 --- a/modules/ticket/back/methods/ticket/specs/transferSales.spec.js +++ b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js @@ -33,7 +33,7 @@ describe('sale transferSales()', () => { error = e; } - expect(error.message).toEqual(`The sales of this ticket can't be modified`); + expect(error.message).toEqual(`This ticket is not editable.`); }); it('should throw an error if the receiving ticket is not editable', async() => { diff --git a/modules/ticket/back/methods/ticket/transferClient.js b/modules/ticket/back/methods/ticket/transferClient.js index 04c153bd2..60e70d710 100644 --- a/modules/ticket/back/methods/ticket/transferClient.js +++ b/modules/ticket/back/methods/ticket/transferClient.js @@ -1,4 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('transferClient', { description: 'Transfering ticket to another client', @@ -29,10 +28,7 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - const isEditable = await Self.isEditable(ctx, id, myOptions); - - if (!isEditable) - throw new UserError(`The current ticket can't be modified`); + await Self.isEditableOrThrow(ctx, id, myOptions); const ticket = await models.Ticket.findById( id, diff --git a/modules/ticket/back/methods/ticket/transferSales.js b/modules/ticket/back/methods/ticket/transferSales.js index 0eee50d5f..a48c5683c 100644 --- a/modules/ticket/back/methods/ticket/transferSales.js +++ b/modules/ticket/back/methods/ticket/transferSales.js @@ -1,4 +1,4 @@ -let UserError = require('vn-loopback/util/user-error'); +const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('transferSales', { @@ -48,9 +48,7 @@ module.exports = Self => { } try { - const isEditable = await models.Ticket.isEditable(ctx, id, myOptions); - if (!isEditable) - throw new UserError(`The sales of this ticket can't be modified`); + await models.Ticket.isEditableOrThrow(ctx, id, myOptions); if (ticketId) { const isReceiverEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions); diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index b432c9f6b..c37337253 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -6,6 +6,7 @@ module.exports = function(Self) { require('../methods/ticket/componentUpdate')(Self); require('../methods/ticket/new')(Self); require('../methods/ticket/isEditable')(Self); + require('../methods/ticket/isEditableOrThrow')(Self); require('../methods/ticket/setDeleted')(Self); require('../methods/ticket/restore')(Self); require('../methods/ticket/getSales')(Self);