diff --git a/db/routines/vn/procedures/ticket_canbePostponed.sql b/db/routines/vn/procedures/ticket_canbePostponed.sql index 871cafddc..1f3c43057 100644 --- a/db/routines/vn/procedures/ticket_canbePostponed.sql +++ b/db/routines/vn/procedures/ticket_canbePostponed.sql @@ -21,6 +21,7 @@ BEGIN t.clientFk, t.warehouseFk, ts.alertLevel, + sub2.alertLevel futureAlertLevel, t.shipped, t.totalWithVat, sub2.shipped futureShipped, @@ -47,6 +48,7 @@ BEGIN t.addressFk, t.id, t.shipped, + ts.alertLevel, st.name state, st.code, st.classColor, diff --git a/db/versions/11178-yellowCamellia/00-aclSetWeight.sql b/db/versions/11178-yellowCamellia/00-aclSetWeight.sql new file mode 100644 index 000000000..d70287738 --- /dev/null +++ b/db/versions/11178-yellowCamellia/00-aclSetWeight.sql @@ -0,0 +1,9 @@ +-- Place your SQL code here +INSERT INTO salix.ACL + SET model = 'Ticket', + property = 'setWeight', + accessType = 'WRITE', + permission = 'ALLOW', + principalType = 'ROLE', + principalId = 'salesPerson'; + \ No newline at end of file diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 097c6e1ab..0a3892c86 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -687,8 +687,8 @@ export default { ticketFuture: { searchResult: 'vn-ticket-future tbody tr', openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]', - originDated: 'vn-date-picker[label="Origin date"]', - futureDated: 'vn-date-picker[label="Destination date"]', + originScopeDays: 'vn-date-picker[label="Origin date"]', + futureScopeDays: 'vn-date-picker[label="Destination date"]', linesMax: 'vn-textfield[label="Max Lines"]', litersMax: 'vn-textfield[label="Max Liters"]', ipt: 'vn-autocomplete[label="Origin IPT"]', diff --git a/e2e/paths/05-ticket/21_future.spec.js b/e2e/paths/05-ticket/21_future.spec.js index 7b7d478f7..60bb9c38d 100644 --- a/e2e/paths/05-ticket/21_future.spec.js +++ b/e2e/paths/05-ticket/21_future.spec.js @@ -30,18 +30,18 @@ describe('Ticket Future path', () => { expect(message.text).toContain('warehouseFk is a required argument'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.clearInput(selectors.ticketFuture.futureDated); + await page.clearInput(selectors.ticketFuture.futureScopeDays); await page.waitToClick(selectors.ticketFuture.submit); message = await page.waitForSnackbar(); - expect(message.text).toContain('futureDated is a required argument'); + expect(message.text).toContain('futureScopeDays is a required argument'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.clearInput(selectors.ticketFuture.originDated); + await page.clearInput(selectors.ticketFuture.originScopeDays); await page.waitToClick(selectors.ticketFuture.submit); message = await page.waitForSnackbar(); - expect(message.text).toContain('originDated is a required argument'); + expect(message.text).toContain('originScopeDays is a required argument'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.waitToClick(selectors.ticketFuture.submit); @@ -71,7 +71,7 @@ describe('Ticket Future path', () => { await page.autocompleteSearch(selectors.ticketFuture.state, 'Free'); await page.waitToClick(selectors.ticketFuture.submit); - expect(httpRequest).toContain('state=FREE'); + expect(httpRequest).toContain('state=0'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); @@ -80,7 +80,7 @@ describe('Ticket Future path', () => { await page.autocompleteSearch(selectors.ticketFuture.futureState, 'Free'); await page.waitToClick(selectors.ticketFuture.submit); - expect(httpRequest).toContain('futureState=FREE'); + expect(httpRequest).toContain('futureState=0'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.state); diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 8b443d96b..064ea4a95 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -372,5 +372,7 @@ "The entry not have stickers": "La entrada no tiene etiquetas", "Too many records": "Demasiados registros", "Original invoice not found": "Factura original no encontrada", - "The entry has no lines or does not exist": "La entrada no tiene lineas o no existe" + "The entry has no lines or does not exist": "La entrada no tiene lineas o no existe", + "Weight already set": "El peso ya está establecido", + "This ticket is not allocated to your department": "Este ticket no está asignado a tu departamento" } \ No newline at end of file diff --git a/modules/ticket/back/methods/ticket/getTicketsFuture.js b/modules/ticket/back/methods/ticket/getTicketsFuture.js index abd269e5e..247924591 100644 --- a/modules/ticket/back/methods/ticket/getTicketsFuture.js +++ b/modules/ticket/back/methods/ticket/getTicketsFuture.js @@ -9,13 +9,13 @@ module.exports = Self => { accessType: 'READ', accepts: [ { - arg: 'originDated', + arg: 'originScopeDays', type: 'date', description: 'The date in question', required: true }, { - arg: 'futureDated', + arg: 'futureScopeDays', type: 'date', description: 'The date to probe', required: true @@ -129,9 +129,9 @@ module.exports = Self => { ] }; case 'state': - return {'f.stateCode': {like: `%${value}%`}}; + return {'f.alertLevel': value}; case 'futureState': - return {'f.futureStateCode': {like: `%${value}%`}}; + return {'f.futureAlertLevel': value}; } }); @@ -141,7 +141,7 @@ module.exports = Self => { stmt = new ParameterizedSQL( `CALL vn.ticket_canbePostponed(?,?,?)`, - [args.originDated, args.futureDated, args.warehouseFk]); + [args.originScopeDays, args.futureScopeDays, args.warehouseFk]); stmts.push(stmt); @@ -170,7 +170,7 @@ module.exports = Self => { LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = f.id `); - if (args.problems != undefined && (!args.originDated && !args.futureDated)) + if (args.problems != undefined && (!args.originScopeDays && !args.futureScopeDays)) throw new UserError('Choose a date range or days forward'); let condition; diff --git a/modules/ticket/back/methods/ticket/setWeight.js b/modules/ticket/back/methods/ticket/setWeight.js new file mode 100644 index 000000000..b3b505832 --- /dev/null +++ b/modules/ticket/back/methods/ticket/setWeight.js @@ -0,0 +1,86 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('setWeight', { + description: 'Sets weight of a ticket', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The ticket id', + http: {source: 'path'} + }, { + arg: 'weight', + type: 'number', + required: true, + description: 'The weight value', + }], + returns: { + type: 'Array', + root: true + }, + http: { + path: `/:id/setWeight`, + verb: 'POST' + } + }); + + Self.setWeight = async(ctx, ticketId, weight, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {userId}; + let tx; + + if (typeof options == 'object') Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const ticket = await Self.findById(ticketId, null, myOptions); + if (ticket.weight) throw new UserError('Weight already set'); + + const canEdit = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'updateAttributes'); + const client = await models.Client.findById(ticket.clientFk, { + include: {relation: 'salesPersonUser'}}, + myOptions); + + if (!canEdit) { + const salesPersonUser = client.salesPersonUser(); + const workerDepartments = await models.WorkerDepartment.find({ + include: {relation: 'department'}, + where: {workerFk: {inq: [userId, salesPersonUser.id]}} + }, myOptions); + + if ( + workerDepartments.length == 2 && + workerDepartments[0].departmentFk != workerDepartments[1].departmentFk + ) + throw new UserError('This ticket is not allocated to your department'); + } + + await ticket.updateAttribute('weight', weight, myOptions); + + const packedState = await models.State.findOne({where: {code: 'PACKED'}}, myOptions); + const ticketState = await models.TicketState.findOne({where: {ticketFk: ticketId}}, myOptions); + + const [{taxArea}] = await Self.rawSql('SELECT clientTaxArea(?,?) taxArea', + [ticket.clientFk, ticket.warehouseFk], myOptions); + + const isInvoiceable = ticketState.alertLevel >= packedState.alertLevel && + taxArea == 'WORLD' && client.hasDailyInvoice; + + if (tx) await tx.commit(); + let invoiceIds = []; + if (isInvoiceable) invoiceIds = [...await Self.invoiceTicketsAndPdf(ctx, [ticketId])]; + + return invoiceIds; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js b/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js index 4b28f34ea..e1e9a0ed2 100644 --- a/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js +++ b/modules/ticket/back/methods/ticket/specs/getTicketsFuture.spec.js @@ -12,8 +12,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, }; @@ -35,8 +35,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, problems: true }; @@ -60,8 +60,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, problems: false }; @@ -85,8 +85,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, problems: null }; @@ -110,8 +110,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, state: 'OK' }; @@ -135,8 +135,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, futureState: 'OK' }; @@ -160,8 +160,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, ipt: null }; @@ -185,8 +185,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, ipt: 'H' }; @@ -210,8 +210,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, futureIpt: null }; @@ -235,8 +235,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, futureIpt: 'H' }; @@ -260,8 +260,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, id: 13 }; @@ -285,8 +285,8 @@ describe('ticket getTicketsFuture()', () => { const options = {transaction: tx}; const args = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: 1, futureId: 12 }; diff --git a/modules/ticket/back/methods/ticket/specs/setWeight.spec.js b/modules/ticket/back/methods/ticket/specs/setWeight.spec.js new file mode 100644 index 000000000..f6ef1f8e7 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/setWeight.spec.js @@ -0,0 +1,70 @@ +const {models} = require('vn-loopback/server/server'); + +describe('ticket setWeight()', () => { + const ctx = beforeAll.getCtx(); + beforeAll.mockLoopBackContext(); + let opts; + let tx; + const employeeId = 1; + const administrativeId = 5; + + beforeEach(async() => { + opts = {transaction: tx}; + tx = await models.Ticket.beginTransaction({}); + opts.transaction = tx; + ctx.req.accessToken.userId = administrativeId; + }); + + afterEach(async() => await tx.rollback()); + + it('should throw an error if the weight is already set', async() => { + try { + const ticketId = 1; + const weight = 10; + + await models.Ticket.setWeight(ctx, ticketId, weight, opts); + } catch (e) { + expect(e.message).toEqual('Weight already set'); + } + }); + + it('should set the weight of a ticket', async() => { + const ticketId = 31; + const weight = 15; + + await models.Ticket.setWeight(ctx, ticketId, weight, opts); + + const ticket = await models.Ticket.findById(ticketId, null, opts); + + expect(ticket.weight).toEqual(weight); + }); + + it('should throw an error if the user is not allocated to the same department', async() => { + ctx.req.accessToken.userId = employeeId; + try { + const ticketId = 10; + const weight = 20; + + await models.Ticket.setWeight(ctx, ticketId, weight, opts); + } catch (e) { + expect(e.message).toEqual('This ticket is not allocated to your department'); + } + }); + + it('should call invoiceTicketsAndPdf if the ticket is invoiceable', async() => { + const ticketId = 10; + const weight = 25; + + spyOn(models.Client, 'findById').and.returnValue({ + hasDailyInvoice: true, + salesPersonUser: () => ({id: 1}) + }); + spyOn(models.TicketState, 'findOne').and.returnValue({alertLevel: 3}); + spyOn(models.Ticket, 'rawSql').and.returnValue([{taxArea: 'WORLD'}]); + spyOn(models.Ticket, 'invoiceTicketsAndPdf').and.returnValue([10]); + + const invoiceIds = await models.Ticket.setWeight(ctx, ticketId, weight, opts); + + expect(invoiceIds.length).toBeGreaterThan(0); + }); +}); diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index 462862cb3..12161d5f5 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -46,4 +46,5 @@ module.exports = function(Self) { require('../methods/ticket/invoiceTicketsAndPdf')(Self); require('../methods/ticket/docuwareDownload')(Self); require('../methods/ticket/myLastModified')(Self); + require('../methods/ticket/setWeight')(Self); }; diff --git a/modules/ticket/front/future-search-panel/index.html b/modules/ticket/front/future-search-panel/index.html index d873fbc37..bcf6e5fe3 100644 --- a/modules/ticket/front/future-search-panel/index.html +++ b/modules/ticket/front/future-search-panel/index.html @@ -1,7 +1,7 @@
@@ -9,13 +9,13 @@ @@ -59,7 +59,7 @@ @@ -69,7 +69,7 @@ diff --git a/modules/ticket/front/future/index.js b/modules/ticket/front/future/index.js index f18dfa17e..a7d579681 100644 --- a/modules/ticket/front/future/index.js +++ b/modules/ticket/front/future/index.js @@ -65,8 +65,8 @@ export default class Controller extends Section { this.$http.get(`UserConfigs/getUserConfig`) .then(res => { this.filterParams = { - originDated: today, - futureDated: today, + originScopeDays: today, + futureScopeDays: today, warehouseFk: res.data.warehouseFk }; this.$.model.applyFilter(null, this.filterParams); diff --git a/modules/ticket/front/summary/locale/es.yml b/modules/ticket/front/summary/locale/es.yml index d1e6dba58..9de7ccbf4 100644 --- a/modules/ticket/front/summary/locale/es.yml +++ b/modules/ticket/front/summary/locale/es.yml @@ -3,4 +3,5 @@ Address mobile: Móv. consignatario Client phone: Tel. cliente Client mobile: Móv. cliente Go to the ticket: Ir al ticket -Change state: Cambiar estado \ No newline at end of file +Change state: Cambiar estado +Weight: Peso \ No newline at end of file