From e1cbf781e12c8fe3a3495531b4f33de092f6f21f Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Thu, 25 Jul 2019 09:55:09 +0200 Subject: [PATCH] #1608 item.request --- modules/item/front/request/index.html | 41 +++--- modules/item/front/request/index.js | 9 +- modules/item/front/request/index.spec.js | 129 ++++++++++++++++++ modules/item/front/request/locale/es.yml | 4 +- modules/item/front/request/style.scss | 6 + .../back/methods/ticket-request/confirm.js | 29 ++-- .../back/methods/ticket-request/deny.js | 4 +- .../back/methods/ticket-request/filter.js | 66 +++++---- .../ticket-request/specs/confirm.spec.js | 110 +++++++++++++++ .../methods/ticket-request/specs/deny.spec.js | 25 ++++ .../ticket-request/specs/filter.spec.js | 83 +++++++++++ 11 files changed, 440 insertions(+), 66 deletions(-) create mode 100644 modules/item/front/request/index.spec.js create mode 100644 modules/ticket/back/methods/ticket-request/specs/confirm.spec.js create mode 100644 modules/ticket/back/methods/ticket-request/specs/deny.spec.js create mode 100644 modules/ticket/back/methods/ticket-request/specs/filter.spec.js diff --git a/modules/item/front/request/index.html b/modules/item/front/request/index.html index c35246809..e0936f3b7 100644 --- a/modules/item/front/request/index.html +++ b/modules/item/front/request/index.html @@ -3,26 +3,29 @@ url="/ticket/api/TicketRequests/filter" limit="20" data="requests" + order="isOk ASC" auto-load="false">
- - + Ticket ID - Shipped + Shipped Warehouse SalesPerson Description @@ -31,8 +34,8 @@ Atender Item Concept - Quantity - State + Sale quantity + State @@ -56,14 +59,8 @@ ng-click="$ctrl.showWorkerDescriptor($event, request.salesPersonFk)"> {{::request.salesPersonNickname}} - - - - {{::request.description}} - + {{::request.description}} {{::request.quantity}} {{::request.price | currency: 'EUR':2}} @@ -73,7 +70,7 @@ {{::request.atenderNickname}} - + {{request.itemFk}} - {{::request.itemDescription}} - + + + {{::request.itemDescription}} + + + {{request.saleQuantity}} {{::$ctrl.getState(request.isOk)}} + + { this.vnApp.showSuccess(this._.instant('Data saved!')); + this.$.model.refresh(); }).catch( e => { this.$.model.refresh(); throw e; @@ -59,7 +60,8 @@ export default class Controller { this.$.model.refresh(); throw e; }); - } + } else + this.confirmRequest(request); } compareDate(date) { @@ -87,6 +89,7 @@ export default class Controller { this.denyRequestId = requestId; this.$.denyReason.parent = event.target; this.$.denyReason.show(); + document.querySelector('vn-item-request vn-textarea textArea').focus(); } clear() { diff --git a/modules/item/front/request/index.spec.js b/modules/item/front/request/index.spec.js new file mode 100644 index 000000000..7c984f73f --- /dev/null +++ b/modules/item/front/request/index.spec.js @@ -0,0 +1,129 @@ +import './index.js'; +import crudModel from 'core/mocks/crud-model'; + +describe('Item', () => { + describe('Component vnItemRequest', () => { + let $scope; + let controller; + let $httpBackend; + + beforeEach(ngModule('item')); + + beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + $scope = $rootScope.$new(); + $scope.model = crudModel; + $scope.denyReason = {hide: () => {}}; + controller = $componentController('vnItemRequest', {$scope: $scope}); + })); + + describe('getState()', () => { + it(`should return an string depending to the isOK value`, () => { + let isOk = null; + let result = controller.getState(isOk); + + expect(result).toEqual('Nueva'); + + isOk = 1; + result = controller.getState(isOk); + + expect(result).toEqual('Aceptada'); + + isOk = 0; + result = controller.getState(isOk); + + expect(result).toEqual('Denegada'); + }); + }); + + describe('confirmRequest()', () => { + it(`should do nothing if the request does't have itemFk or saleQuantity`, () => { + let request = {}; + spyOn(controller.vnApp, 'showSuccess'); + + controller.confirmRequest(request); + + expect(controller.vnApp.showSuccess).not.toHaveBeenCalledWith(); + }); + + it('should perform a query and call vnApp.showSuccess() and refresh if the conditions are met', () => { + spyOn(controller.vnApp, 'showSuccess'); + let model = controller.$.model; + spyOn(model, 'refresh'); + + let request = {itemFk: 1, saleQuantity: 1, id: 1}; + + $httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond(); + $httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond(); + controller.confirmRequest(request); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect($scope.model.refresh).toHaveBeenCalledWith(); + }); + }); + + describe('changeQuantity()', () => { + it(`should call confirmRequest() if there's no sale id in the request`, () => { + let request = {}; + spyOn(controller, 'confirmRequest'); + + controller.changeQuantity(request); + + expect(controller.confirmRequest).toHaveBeenCalledWith(jasmine.any(Object)); + }); + + it(`should perform a query and call vnApp.showSuccess() if the conditions are met`, () => { + let request = {saleFk: 1, saleQuantity: 1}; + spyOn(controller.vnApp, 'showSuccess'); + + + $httpBackend.when('PATCH', `/api/Sales/${request.saleFk}/`).respond(); + $httpBackend.expect('PATCH', `/api/Sales/${request.saleFk}/`).respond(); + controller.changeQuantity(request); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + }); + }); + + describe('compareDate()', () => { + it(`should return "success" if receives a future date`, () => { + let date = '3019-02-18T11:00:00.000Z'; + + let result = controller.compareDate(date); + + expect(result).toEqual('success'); + }); + + it(`should return "warning" if date is today`, () => { + let date = new Date(); + + let result = controller.compareDate(date); + + expect(result).toEqual('warning'); + }); + }); + + describe('denyRequest()', () => { + it(`should perform a query and call vnApp.showSuccess(), refresh(), hide() and set denyObservation to null in the controller`, () => { + spyOn(controller.vnApp, 'showSuccess'); + let model = controller.$.model; + spyOn(model, 'refresh'); + spyOn(controller.$.denyReason, 'hide'); + + controller.denyRequestId = 1; + + $httpBackend.when('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond(); + $httpBackend.expect('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond(); + controller.denyRequest(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect($scope.model.refresh).toHaveBeenCalledWith(); + expect($scope.denyReason.hide).toHaveBeenCalledWith(); + }); + }); + }); +}); + diff --git a/modules/item/front/request/locale/es.yml b/modules/item/front/request/locale/es.yml index f4801da7c..760f30bd3 100644 --- a/modules/item/front/request/locale/es.yml +++ b/modules/item/front/request/locale/es.yml @@ -1,3 +1,5 @@ Discard: Descartar Indicate the reasons to deny this request: Indique las razones para descartar esta peticion -Buy requests: Peticiones de compra \ No newline at end of file +Buy requests: Peticiones de compra +Search request by id or alias: Buscar peticiones por identificador o alias +Sale quantity: C. conseguida \ No newline at end of file diff --git a/modules/item/front/request/style.scss b/modules/item/front/request/style.scss index 37e2a10a5..48c3bae99 100644 --- a/modules/item/front/request/style.scss +++ b/modules/item/front/request/style.scss @@ -1,3 +1,5 @@ +@import "variables"; + vn-dialog[vn-id="denyReason"] { button.close { display: none @@ -9,4 +11,8 @@ vn-dialog[vn-id="denyReason"] { vn-textarea { width: 100% } +} + +vn-icon[icon=insert_drive_file]{ + color: $color-font-secondary; } \ No newline at end of file diff --git a/modules/ticket/back/methods/ticket-request/confirm.js b/modules/ticket/back/methods/ticket-request/confirm.js index 79551796d..02d1a33c5 100644 --- a/modules/ticket/back/methods/ticket-request/confirm.js +++ b/modules/ticket/back/methods/ticket-request/confirm.js @@ -13,12 +13,12 @@ module.exports = Self => { arg: 'itemFk', type: 'Integer', required: true, - description: 'The request observation', + description: 'The requested item ID', }, { arg: 'quantity', type: 'Integer', required: true, - description: 'The request observation', + description: 'The requested item quantity', }], returns: { type: 'Object', @@ -32,6 +32,7 @@ module.exports = Self => { Self.confirm = async ctx => { const models = Self.app.models; + let sale; let tx = await Self.beginTransaction({}); try { @@ -52,32 +53,32 @@ module.exports = Self => { false ]); - if (stock.available < quantity) + if (stock.available < ctx.args.quantity) throw new UserError(`This item is not available`); + if (request.saleFk) { - let sale = await models.Sale.findById(request.saleFk); + sale = await models.Sale.findById(request.saleFk); sale.updateAttributes({ itemFk: ctx.args.itemFk, quantity: ctx.args.quantity, - description: item.description + concept: item.name, }, options); } else { - params = { + sale = await models.Sale.create({ ticketFk: request.ticketFk, itemFk: ctx.args.itemFk, - quantity: ctx.args.quantity - }; - sale = await models.Sale.create(params, options); - request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk}, options); + quantity: ctx.args.quantity, + concept: item.name + }, options); + request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk, isOk: true}, options); } query = `CALL vn.ticketCalculateSale(?)`; - params = [sale.id]; - await Self.rawSql(query, params, options); + await Self.rawSql(query, [sale.id], options); - const message = `Se ha comprado ${params.quantity} unidades de "${item.description}" (#${params.itemFk}) ` - + `para el ticket #${params.ticketFk}`; + const message = `Se ha comprado ${sale.quantity} unidades de "${sale.concept}" (#${sale.itemFk}) ` + + `para el ticket #${sale.ticketFk}`; await models.Message.send(ctx, { recipientFk: request.requesterFk, diff --git a/modules/ticket/back/methods/ticket-request/deny.js b/modules/ticket/back/methods/ticket-request/deny.js index cc648bb72..817c90782 100644 --- a/modules/ticket/back/methods/ticket-request/deny.js +++ b/modules/ticket/back/methods/ticket-request/deny.js @@ -1,6 +1,6 @@ module.exports = Self => { Self.remoteMethodCtx('deny', { - description: 'Create a newticket and returns the new ID', + description: 'sets a ticket request to denied and returns the changes', accessType: 'WRITE', accepts: [{ arg: 'id', @@ -30,7 +30,7 @@ module.exports = Self => { let params = { isOk: false, atenderFk: worker.id, - observation: ctx.args.observation, + response: ctx.args.observation, }; let request = await Self.app.models.TicketRequest.findById(ctx.args.id); diff --git a/modules/ticket/back/methods/ticket-request/filter.js b/modules/ticket/back/methods/ticket-request/filter.js index 4e0db40f8..40c1f7652 100644 --- a/modules/ticket/back/methods/ticket-request/filter.js +++ b/modules/ticket/back/methods/ticket-request/filter.js @@ -71,7 +71,7 @@ module.exports = Self => { switch (param) { case 'search': return /^\d+$/.test(value) - ? {'t.ticketFk': {inq: value}} + ? {'tr.ticketFk': {inq: value}} : {'t.nickname': {like: `%${value}%`}}; case 'ticketFk': return {'t.id': value}; @@ -92,41 +92,47 @@ module.exports = Self => { } }); + if (!where) + where = {}; + where['tw.ticketFk'] = null; + filter = mergeFilters(filter, {where}); let stmt; stmt = new ParameterizedSQL( `SELECT - tr.id, - tr.ticketFk, - tr.quantity, - tr.price, - tr.atenderFk, - tr.description, - tr.itemFk, - tr.saleFk, - tr.isOk, - s.quantity AS saleQuantity, - i.description AS itemDescription, - t.shipped, - t.nickname, - t.warehouseFk, - t.clientFk, - w.name AS warehouse, - u.nickname AS salesPersonNickname, - ua.nickname AS atenderNickname, - c.salesPersonFk - FROM ticketRequest tr - LEFT JOIN ticket t ON t.id = tr.ticketFk - LEFT JOIN warehouse w ON w.id = t.warehouseFk - LEFT JOIN client c ON c.id = t.clientFk - LEFT JOIN item i ON i.id = tr.itemFk - LEFT JOIN sale s ON s.id = tr.saleFk - LEFT JOIN worker wk ON wk.id = c.salesPersonFk - LEFT JOIN account.user u ON u.id = wk.userFk - LEFT JOIN worker wka ON wka.id = tr.atenderFk - LEFT JOIN account.user ua ON ua.id = wka.userFk`); + tr.id, + tr.ticketFk, + tr.quantity, + tr.price, + tr.atenderFk, + tr.description, + tr.response, + tr.saleFk, + tr.isOk, + s.quantity AS saleQuantity, + s.itemFK, + i.name AS itemDescription, + t.shipped, + t.nickname, + t.warehouseFk, + t.clientFk, + w.name AS warehouse, + u.nickname AS salesPersonNickname, + ua.nickname AS atenderNickname, + c.salesPersonFk + FROM ticketRequest tr + LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk + LEFT JOIN ticket t ON t.id = tr.ticketFk + LEFT JOIN warehouse w ON w.id = t.warehouseFk + LEFT JOIN client c ON c.id = t.clientFk + LEFT JOIN item i ON i.id = tr.itemFk + LEFT JOIN sale s ON s.id = tr.saleFk + LEFT JOIN worker wk ON wk.id = c.salesPersonFk + LEFT JOIN account.user u ON u.id = wk.userFk + LEFT JOIN worker wka ON wka.id = tr.atenderFk + LEFT JOIN account.user ua ON ua.id = wka.userFk`); stmt.merge(conn.makeSuffix(filter)); let result = await conn.executeStmt(stmt); diff --git a/modules/ticket/back/methods/ticket-request/specs/confirm.spec.js b/modules/ticket/back/methods/ticket-request/specs/confirm.spec.js new file mode 100644 index 000000000..cab7e8b8b --- /dev/null +++ b/modules/ticket/back/methods/ticket-request/specs/confirm.spec.js @@ -0,0 +1,110 @@ +const app = require('vn-loopback/server/server'); + +describe('ticket-request confirm()', () => { + let request; + let sale; + let createdSaleId; + + afterAll(async done => { + const paramsForRequest = { + saleFk: request.saleFk, + isOk: request.isOk, + itemFk: request.itemFk, + ticketFk: request.ticketFk + }; + + const paramsForSale = { + itemFk: sale.itemFk, + quantity: sale.quantity, + concept: sale.concept, + }; + + await request.updateAttributes(paramsForRequest); + await sale.updateAttributes(paramsForSale); + app.models.Sale.destroyById(createdSaleId); + done(); + }); + + it(`should throw an error if the item doesn't exist`, async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {itemFk: 999}}; + let error; + + try { + await app.models.TicketRequest.confirm(ctx); + } catch (err) { + error = err; + } + + expect(error.message).toEqual(`That item doesn't exists`); + }); + + it(`should throw an error if the item is not available`, async() => { + const requestId = 4; + const itemId = 1; + const quantity = 99999; + + let ctx = {req: {accessToken: {userId: 9}}, args: { + itemFk: itemId, + id: requestId, + quantity: quantity + }}; + let error; + + try { + await app.models.TicketRequest.confirm(ctx); + } catch (err) { + error = err; + } + + expect(error.message).toEqual(`This item is not available`); + }); + + it(`should update the sale details if the request already contains a sale id`, async() => { + const requestId = 4; + const saleId = 11; + const itemId = 1; + const quantity = 10; + + request = await app.models.TicketRequest.findById(requestId); + sale = await app.models.Sale.findById(saleId); + + request.updateAttributes({saleFk: saleId}); + + let ctx = {req: {accessToken: {userId: 9}}, args: { + itemFk: itemId, + id: requestId, + quantity: quantity + }}; + + await app.models.TicketRequest.confirm(ctx); + + let updatedSale = await app.models.Sale.findById(saleId); + + expect(updatedSale.itemFk).toEqual(itemId); + expect(updatedSale.quantity).toEqual(quantity); + }); + + it(`should create a new sale for the the request if there's no sale id`, async() => { + const requestId = 4; + const itemId = 1; + const quantity = 10; + + request.updateAttributes({saleFk: null}); + + let ctx = {req: {accessToken: {userId: 9}}, args: { + itemFk: itemId, + id: requestId, + quantity: quantity, + ticketFk: 1 + }}; + + await app.models.TicketRequest.confirm(ctx); + + let updatedRequest = await app.models.TicketRequest.findById(requestId); + createdSaleId = updatedRequest.saleFk; + + expect(updatedRequest.saleFk).toEqual(createdSaleId); + expect(updatedRequest.isOk).toEqual(true); + expect(updatedRequest.itemFk).toEqual(itemId); + }); +}); diff --git a/modules/ticket/back/methods/ticket-request/specs/deny.spec.js b/modules/ticket/back/methods/ticket-request/specs/deny.spec.js new file mode 100644 index 000000000..cac63586e --- /dev/null +++ b/modules/ticket/back/methods/ticket-request/specs/deny.spec.js @@ -0,0 +1,25 @@ +const app = require('vn-loopback/server/server'); + +describe('ticket-request deny()', () => { + let request; + afterAll(async done => { + let params = { + isOk: null, + atenderFk: request.atenderFk, + response: null, + }; + + await request.updateAttributes(params); + done(); + }); + + it('should return all ticket requests', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {id: 4, observation: 'my observation'}}; + + request = await app.models.TicketRequest.findById(ctx.args.id); + + let result = await app.models.TicketRequest.deny(ctx); + + expect(result.id).toEqual(4); + }); +}); diff --git a/modules/ticket/back/methods/ticket-request/specs/filter.spec.js b/modules/ticket/back/methods/ticket-request/specs/filter.spec.js new file mode 100644 index 000000000..ae73eb1da --- /dev/null +++ b/modules/ticket/back/methods/ticket-request/specs/filter.spec.js @@ -0,0 +1,83 @@ +const app = require('vn-loopback/server/server'); + +describe('ticket-request filter()', () => { + it('should return all ticket requests', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {}}; + + let result = await app.models.TicketRequest.filter(ctx); + + expect(result.length).toEqual(1); + }); + + it('should return the ticket request matching a generic search value which is the ticket ID', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {search: 11}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); + + it('should return the ticket request matching a generic search value which is the client address alias', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {search: 'NY roofs'}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); + + it('should return the ticket request matching the ticket ID', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {ticketFk: 11}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); + + it('should return the ticket request matching the atender ID', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {atenderFk: 35}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); + + it('should return the ticket request matching the isOk triple-state', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {isOk: null}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); + + it('should return the ticket request matching the client ID', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {clientFk: 102}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); + + it('should return the ticket request matching the warehouse ID', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {warehouse: 1}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); + + it('should return the ticket request matching the salesPerson ID', async() => { + let ctx = {req: {accessToken: {userId: 9}}, args: {salesPersonFk: 18}}; + + let result = await app.models.TicketRequest.filter(ctx); + let requestId = result[0].id; + + expect(requestId).toEqual(4); + }); +});