diff --git a/db/changes/10071-coffee/00-ACL.sql b/db/changes/10071-coffee/00-ACL.sql new file mode 100644 index 000000000..eab2c83f5 --- /dev/null +++ b/db/changes/10071-coffee/00-ACL.sql @@ -0,0 +1,6 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Dms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('ClaimDms', 'removeFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('ClaimDms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('Claim', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index cddff8123..e4c504aed 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1838,7 +1838,7 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c (17, 'cmr', 'cmr', NULL, NULL, 'cmr'), (18, 'dua', 'dua', NULL, NULL, 'dua'), (19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'), - (20, 'Reclamación', 'reclamacion', NULL, NULL, 'claim'); + (20, 'Reclamación', 'reclamacion', 1, 1, 'claim'); INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`) VALUES diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index cbbae3dd6..2e9bc37e8 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -422,7 +422,7 @@ export default { thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[field="sale.checked"] md-checkbox', deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]', transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]', - moveToTicketInput: 'vn-ticket-sale vn-popover.transfer vn-textfield[model="$ctrl.receiverTicketId"] input', + moveToTicketInput: 'vn-ticket-sale vn-popover.transfer vn-textfield[model="$ctrl.transfer.ticketId"] input', moveToTicketInputClearButton: 'vn-popover.shown i[title="Clear"]', moveToTicketButton: 'vn-ticket-sale vn-popover.transfer vn-icon[icon="arrow_forward_ios"]', moveToNewTicketButton: 'vn-ticket-sale vn-popover.transfer vn-button[label="New ticket"]', diff --git a/modules/ticket/back/methods/ticket/threeLastActive.js b/modules/client/back/methods/client/lastActiveTickets.js similarity index 59% rename from modules/ticket/back/methods/ticket/threeLastActive.js rename to modules/client/back/methods/client/lastActiveTickets.js index 839fcc465..4dc1e2c56 100644 --- a/modules/ticket/back/methods/ticket/threeLastActive.js +++ b/modules/client/back/methods/client/lastActiveTickets.js @@ -1,35 +1,39 @@ module.exports = Self => { - Self.remoteMethod('threeLastActive', { + Self.remoteMethod('lastActiveTickets', { description: 'Returns the last three tickets of a client that have the alertLevel at 0 and the shiped day is gt today', accessType: 'READ', accepts: [{ - arg: 'filter', - type: 'object', + arg: 'id', + type: 'Number', required: true, - description: 'client id, ticketFk' + description: 'Client id', + http: {source: 'path'} + }, { + arg: 'ticketId', + type: 'Number', + required: true }], returns: { - type: [this.modelName], + type: ['Object'], root: true }, http: { - path: `/threeLastActive`, + path: `/:id/lastActiveTickets`, verb: 'GET' } }); - Self.threeLastActive = async params => { - let query = ` + Self.lastActiveTickets = async(id, ticketId) => { + const query = ` SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName FROM vn.ticket t JOIN vn.ticketState ts ON t.id = ts.ticketFk JOIN vn.agencyMode a ON t.agencyModeFk = a.id JOIN vn.warehouse w ON t.warehouseFk = w.id - WHERE t.shipped >= CURDATE() AND t.clientFk = ? - AND ts.alertLevel = 0 AND t.id <> ? + WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 AND t.id <> ? ORDER BY t.shipped - LIMIT 5`; - let tickets = await Self.rawSql(query, [params.clientFk, params.ticketFk]); - return tickets; + LIMIT 3`; + + return Self.rawSql(query, [id, ticketId]); }; }; diff --git a/modules/client/back/methods/client/specs/threeLastActive.spec.js b/modules/client/back/methods/client/specs/threeLastActive.spec.js new file mode 100644 index 000000000..cd76a0b5b --- /dev/null +++ b/modules/client/back/methods/client/specs/threeLastActive.spec.js @@ -0,0 +1,12 @@ +const app = require('vn-loopback/server/server'); + +describe('client lastActiveTickets()', () => { + it('should return the last three active tickets', async() => { + const clientId = 109; + const ticketId = 19; + + let result = await app.models.Client.lastActiveTickets(clientId, ticketId); + + expect(result.length).toEqual(3); + }); +}); diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index dc60d3111..25b9d7d35 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -21,6 +21,7 @@ module.exports = Self => { require('../methods/client/confirmTransaction')(Self); require('../methods/client/canBeInvoiced')(Self); require('../methods/client/uploadFile')(Self); + require('../methods/client/lastActiveTickets')(Self); // Validations diff --git a/modules/ticket/back/methods/sale/moveToTicket.js b/modules/ticket/back/methods/sale/moveToTicket.js deleted file mode 100644 index aaa30534c..000000000 --- a/modules/ticket/back/methods/sale/moveToTicket.js +++ /dev/null @@ -1,90 +0,0 @@ -let UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethodCtx('moveToTicket', { - description: 'Moves lines to a new or a given ticket', - accepts: [{ - arg: 'params', - type: 'object', - required: true, - description: 'currentTicket, receiverTicket, [sales IDs], removeEmptyTicket', - http: {source: 'body'} - }], - returns: { - type: 'string', - root: true - }, - http: { - path: `/moveToTicket`, - verb: 'post' - } - }); - - Self.moveToTicket = async(ctx, params) => { - const models = Self.app.models; - const userId = ctx.req.accessToken.userId; - let currentTicket = await models.Ticket.findById(params.currentTicket.currentTicketId); - let newTicketData = {}; - let receiverTicket = params.receiverTicket; - - let isCurrentTicketEditable = await models.Ticket.isEditable(ctx, params.currentTicket.currentTicketId); - if (!isCurrentTicketEditable) - throw new UserError(`The sales of the current ticket can't be modified`); - - if (params.receiverTicket.id) { - let isReceiverTicketEditable = await models.Ticket.isEditable(ctx, params.receiverTicket.id); - if (!isReceiverTicketEditable) - throw new UserError(`The sales of the receiver ticket can't be modified`); - } - - if (!params.receiverTicket.id) { - let travelDates = await models.Agency.getFirstShipped(params.currentTicket); - - if (!travelDates) - throw new UserError(`Invalid parameters to create a new ticket`); - let shipped = new Date(travelDates.shipped); - let landed = new Date(travelDates.landed); - - newTicketData = { - clientFk: params.currentTicket.clientFk, - addressFk: params.currentTicket.addressFk, - agencyModeFk: params.currentTicket.agencyModeFk, - warehouseFk: params.currentTicket.warehouseFk, - shipped: shipped, - landed: landed, - userId: userId - }; - } - - let tx = await Self.beginTransaction({}); - - try { - let options = {transaction: tx}; - - if (!params.receiverTicket.id) - receiverTicket = await models.Ticket.new(ctx, newTicketData, options); - - let promises = []; - for (let sale of params.sales) { - promises.push( - models.Sale.update( - {id: sale.id}, - {ticketFk: receiverTicket.id}, - options - ) - ); - } - - if (params.removeEmptyTicket) - promises.push(currentTicket.updateAttributes({isDeleted: true}, options)); - - await Promise.all(promises); - await tx.commit(); - - return receiverTicket; - } catch (error) { - await tx.rollback(); - throw error; - } - }; -}; diff --git a/modules/ticket/back/methods/ticket/checkEmptiness.js b/modules/ticket/back/methods/ticket/checkEmptiness.js deleted file mode 100644 index 2cc70deda..000000000 --- a/modules/ticket/back/methods/ticket/checkEmptiness.js +++ /dev/null @@ -1,37 +0,0 @@ -module.exports = function(Self) { - Self.remoteMethod('checkEmptiness', { - description: 'Checks if the ticket has no packages, componenets and purchase requests', - accessType: 'READ', - accepts: [ - { - arg: 'id', - type: 'number', - required: true, - description: 'Ticket id', - http: {source: 'path'} - } - ], - returns: { - arg: 'data', - type: 'boolean', - root: true - }, - http: { - path: `/:id/checkEmptiness`, - verb: 'get' - } - }); - - Self.checkEmptiness = async id => { - const packages = await Self.app.models.TicketPackaging.find({where: {ticketFk: id}}); - const services = await Self.app.models.TicketService.find({where: {ticketFk: id}}); - const purchaseRequests = await Self.app.models.TicketRequest.find({where: {ticketFk: id}}); - - emptyTicket = !packages.length && !services.length && !purchaseRequests.length; - - if (emptyTicket) - return true; - - return false; - }; -}; diff --git a/modules/ticket/back/methods/ticket/isEmpty.js b/modules/ticket/back/methods/ticket/isEmpty.js new file mode 100644 index 000000000..7f9de98b1 --- /dev/null +++ b/modules/ticket/back/methods/ticket/isEmpty.js @@ -0,0 +1,51 @@ +module.exports = function(Self) { + Self.remoteMethod('isEmpty', { + description: 'Checks if the ticket has no packages, componenets and purchase requests', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'Ticket id', + http: {source: 'path'} + } + ], + returns: { + type: 'Boolean', + root: true + }, + http: { + path: `/:id/isEmpty`, + verb: 'GET' + } + }); + + Self.isEmpty = async(id, options) => { + const models = Self.app.models; + + if ((typeof options) != 'object') + options = {}; + + const hasSales = await models.Sale.count({ + ticketFk: id + }, options); + + const hasPackages = await models.TicketPackaging.count({ + ticketFk: id + }, options); + + const hasServices = await models.TicketService.count({ + ticketFk: id + }, options); + + const hasPurchaseRequests = await models.TicketRequest.count({ + ticketFk: id + }, options); + + const isEmpty = !hasSales && !hasPackages && + !hasServices && !hasPurchaseRequests; + + return isEmpty; + }; +}; diff --git a/modules/ticket/back/methods/ticket/specs/checkEmptiness.spec.js b/modules/ticket/back/methods/ticket/specs/checkEmptiness.spec.js deleted file mode 100644 index 1d67f4c98..000000000 --- a/modules/ticket/back/methods/ticket/specs/checkEmptiness.spec.js +++ /dev/null @@ -1,27 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('ticket checkEmptiness()', () => { - it('should return false if the ticket contains any packages', async() => { - let result = await app.models.Ticket.checkEmptiness(3); - - expect(result).toBeFalsy(); - }); - - it('should return false if the ticket contains any services', async() => { - let result = await app.models.Ticket.checkEmptiness(8); - - expect(result).toBeFalsy(); - }); - - it('should return false if the ticket contains any purchase request', async() => { - let result = await app.models.Ticket.checkEmptiness(11); - - expect(result).toBeFalsy(); - }); - - it('should return true if the ticket does not contain any packages, services or purchase request', async() => { - let result = await app.models.Ticket.checkEmptiness(4); - - expect(result).toBeTruthy(); - }); -}); diff --git a/modules/ticket/back/methods/ticket/specs/isEmpty.spec.js b/modules/ticket/back/methods/ticket/specs/isEmpty.spec.js new file mode 100644 index 000000000..df8818261 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/isEmpty.spec.js @@ -0,0 +1,27 @@ +const app = require('vn-loopback/server/server'); + +describe('ticket isEmpty()', () => { + it('should return false if the ticket contains any packages', async() => { + let result = await app.models.Ticket.isEmpty(3); + + expect(result).toBeFalsy(); + }); + + it('should return false if the ticket contains any services', async() => { + let result = await app.models.Ticket.isEmpty(8); + + expect(result).toBeFalsy(); + }); + + it('should return false if the ticket contains any purchase request', async() => { + let result = await app.models.Ticket.isEmpty(11); + + expect(result).toBeFalsy(); + }); + + it('should return false if the ticket contains any sale', async() => { + let result = await app.models.Ticket.isEmpty(4); + + expect(result).toBeFalsy(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/threeLastActive.spec.js b/modules/ticket/back/methods/ticket/specs/threeLastActive.spec.js deleted file mode 100644 index d3cd9ba93..000000000 --- a/modules/ticket/back/methods/ticket/specs/threeLastActive.spec.js +++ /dev/null @@ -1,10 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('ticket threeLastActive()', () => { - it('should return the last three active tickets', async() => { - let params = {clientFk: 109, ticketFk: 19}; - let result = await app.models.Ticket.threeLastActive(params); - - expect(result.length).toEqual(3); - }); -}); diff --git a/modules/ticket/back/methods/sale/specs/moveToTicket.spec.js b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js similarity index 65% rename from modules/ticket/back/methods/sale/specs/moveToTicket.spec.js rename to modules/ticket/back/methods/ticket/specs/transferSales.spec.js index 8380ebb13..6e8496f43 100644 --- a/modules/ticket/back/methods/sale/specs/moveToTicket.spec.js +++ b/modules/ticket/back/methods/ticket/specs/transferSales.spec.js @@ -1,6 +1,6 @@ const app = require('vn-loopback/server/server'); -describe('sale moveToTicket()', () => { +describe('sale transferSales()', () => { let createdTicketId; afterAll(async done => { @@ -12,11 +12,13 @@ describe('sale moveToTicket()', () => { const ctx = {req: {accessToken: {userId: 101}}}; let error; - const params = {currentTicket: {currentTicketId: 10}}; + const currentTicketId = 10; + const receiverTicketId = undefined; + const sales = []; - await app.models.Sale.moveToTicket(ctx, params) + await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales) .catch(response => { - expect(response.message).toEqual(`The sales of the current ticket can't be modified`); + expect(response.message).toEqual(`The sales of this ticket can't be modified`); error = response; }); @@ -27,9 +29,11 @@ describe('sale moveToTicket()', () => { const ctx = {req: {accessToken: {userId: 101}}}; let error; - const params = {currentTicket: {currentTicketId: 16}, receiverTicket: {id: 1}}; + const currentTicketId = 16; + const receiverTicketId = 1; + const sales = []; - await app.models.Sale.moveToTicket(ctx, params) + await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales) .catch(response => { expect(response.message).toEqual(`The sales of the receiver ticket can't be modified`); error = response; @@ -42,9 +46,11 @@ describe('sale moveToTicket()', () => { const ctx = {req: {accessToken: {userId: 101}}}; let error; - const params = {currentTicket: {currentTicketId: 18}, receiverTicket: {id: undefined}}; + const currentTicketId = 18; + const receiverTicketId = undefined; + const sales = []; - await app.models.Sale.moveToTicket(ctx, params) + await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales) .catch(response => { expect(response.message).toEqual(`Invalid parameters to create a new ticket`); error = response; @@ -56,26 +62,19 @@ describe('sale moveToTicket()', () => { it('should transfer the sales from one ticket to a new one', async() => { const ctx = {req: {accessToken: {userId: 101}}}; let currentTicket = await app.models.Ticket.findById(11); - currentTicket.currentTicketId = currentTicket.id; - currentTicket.id = undefined; - - let currentTicketSales = await app.models.Ticket.getSales(currentTicket.currentTicketId); + let currentTicketSales = await app.models.Ticket.getSales(currentTicket.id); expect(currentTicketSales.length).toEqual(2); - let params = { - currentTicket: currentTicket, - receiverTicket: {id: undefined}, - sales: [ - {id: currentTicketSales[0].id}, - {id: currentTicketSales[1].id} - ] - }; + const currentTicketId = currentTicket.id; + const receiverTicketId = undefined; + const sales = currentTicketSales; - let createdTicket = await app.models.Sale.moveToTicket(ctx, params); + let createdTicket = await app.models.Ticket.transferSales( + ctx, currentTicketId, receiverTicketId, sales); createdTicketId = createdTicket.id; - currentTicketSales = await app.models.Ticket.getSales(currentTicket.currentTicketId); + currentTicketSales = await app.models.Ticket.getSales(currentTicket.id); receiverTicketSales = await app.models.Ticket.getSales(createdTicket.id); expect(currentTicketSales.length).toEqual(0); @@ -84,33 +83,24 @@ describe('sale moveToTicket()', () => { it('should transfer back the sales and set the created ticket as deleted', async() => { const ctx = {req: {accessToken: {userId: 101}}}; - let receiverTicketId = 11; - let currentTicket = await app.models.Ticket.findById(createdTicketId); - currentTicket.currentTicketId = createdTicketId; - currentTicket.id = undefined; + const currentTicket = await app.models.Ticket.findById(createdTicketId); + const receiverTicketId = 11; let createdTicket = await app.models.Ticket.findById(createdTicketId); let createdTicketSales = await app.models.Ticket.getSales(createdTicketId); let receiverTicketSales = await app.models.Ticket.getSales(receiverTicketId); + const currentTicketId = currentTicket.id; + const sales = createdTicketSales; + expect(createdTicket.isDeleted).toBeFalsy(); expect(createdTicketSales.length).toEqual(2); expect(receiverTicketSales.length).toEqual(0); - let params = { - removeEmptyTicket: true, - currentTicket: currentTicket, - receiverTicket: {id: receiverTicketId}, - sales: [ - {id: createdTicketSales[0].id}, - {id: createdTicketSales[1].id} - ] - }; - - await app.models.Sale.moveToTicket(ctx, params); + await app.models.Ticket.transferSales( + ctx, currentTicketId, receiverTicketId, sales); createdTicket = await app.models.Ticket.findById(createdTicketId); - createdTicketSales = await app.models.Ticket.getSales(createdTicketId); receiverTicketSales = await app.models.Ticket.getSales(receiverTicketId); diff --git a/modules/ticket/back/methods/ticket/transferSales.js b/modules/ticket/back/methods/ticket/transferSales.js new file mode 100644 index 000000000..eeea8e745 --- /dev/null +++ b/modules/ticket/back/methods/ticket/transferSales.js @@ -0,0 +1,158 @@ +let UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('transferSales', { + description: 'Transfer sales to a new or a given ticket', + accepts: [{ + arg: 'id', + type: 'Number', + required: true, + description: 'Origin ticket id', + http: {source: 'path'} + }, + { + arg: 'ticketId', + type: 'Number', + description: 'Destination ticket id', + required: false + }, + { + arg: 'sales', + type: ['Object'], + description: 'The sales to transfer', + required: true + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/transferSales`, + verb: 'POST' + } + }); + + Self.transferSales = async(ctx, id, ticketId, sales) => { + const models = Self.app.models; + + const isEditable = await models.Ticket.isEditable(ctx, id); + if (!isEditable) + throw new UserError(`The sales of this ticket can't be modified`); + + if (ticketId) { + const isReceiverEditable = await models.Ticket.isEditable(ctx, ticketId); + if (!isReceiverEditable) + throw new UserError(`The sales of the receiver ticket can't be modified`); + } + + let tx = await Self.beginTransaction({}); + + try { + const options = {transaction: tx}; + const originalTicket = await models.Ticket.findById(id, null, options); + const originalSales = await models.Sale.find({ + where: {ticketFk: id} + }, options); + + if (!ticketId) + ticketId = await cloneTicket(ctx, originalTicket, options); + + const map = new Map(); + for (const sale of originalSales) + map.set(sale.id, sale); + + const promises = []; + for (const sale of sales) { + const originalSale = map.get(sale.id); + + if (sale.quantity == originalSale.quantity) { + const updatedSale = models.Sale.updateAll({ + id: sale.id + }, {ticketFk: ticketId}, options); + + promises.push(updatedSale); + } else if (sale.quantity < originalSale.quantity) { + const transferedSale = await transferPartialSale( + ticketId, originalSale, sale, options); + + promises.push(transferedSale); + } + } + + const isTicketEmpty = await models.Ticket.isEmpty(id, options); + if (isTicketEmpty) { + originalTicket.updateAttributes({ + isDeleted: true + }, options); + } + + await Promise.all(promises); + await tx.commit(); + + return {id: ticketId}; + } catch (error) { + await tx.rollback(); + throw error; + } + }; + + async function cloneTicket(ctx, ticket, options) { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + + const travelDates = await models.Agency.getFirstShipped({ + agencyModeFk: ticket.agencyModeFk, + addressFk: ticket.addressFk, + warehouseFk: ticket.warehouseFk + }); + + if (!travelDates) + throw new UserError(`Invalid parameters to create a new ticket`); + + let shipped = new Date(travelDates.shipped); + let landed = new Date(travelDates.landed); + + const newTicket = await models.Ticket.new(ctx, { + clientFk: ticket.clientFk, + addressFk: ticket.addressFk, + agencyModeFk: ticket.agencyModeFk, + warehouseFk: ticket.warehouseFk, + shipped: shipped, + landed: landed, + userId: userId + }, options); + + return newTicket.id; + } + + async function transferPartialSale(ticketId, originalSale, sale, options) { + const models = Self.app.models; + // Update original sale + const rest = originalSale.quantity - sale.quantity; + const updatedSale = models.Sale.updateAll({ + id: sale.id + }, {quantity: rest}, options); + + // Clone sale with new quantity + const newSale = originalSale; + newSale.id = undefined; + newSale.ticketFk = ticketId; + newSale.quantity = sale.quantity; + + const createdSale = await models.Sale.create(newSale, options); + + // Clone sale components + const saleComponents = await models.SaleComponent.find({ + where: {saleFk: sale.id} + }, options); + const newComponents = saleComponents.map(component => { + component.saleFk = createdSale.id; + + return component; + }); + const createdComponents = models.SaleComponent + .create(newComponents, options); + + return [updatedSale, createdComponents]; + } +}; diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index fc1281911..e8b885ef4 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -1,6 +1,5 @@ module.exports = Self => { require('../methods/sale/getClaimableFromTicket')(Self); - require('../methods/sale/moveToTicket')(Self); require('../methods/sale/reserve')(Self); require('../methods/sale/removes')(Self); require('../methods/sale/updatePrice')(Self); diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index 91abb58cb..67b8d9bc7 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -11,7 +11,6 @@ module.exports = Self => { require('../methods/ticket/componentUpdate')(Self); require('../methods/ticket/new')(Self); require('../methods/ticket/isEditable')(Self); - require('../methods/ticket/threeLastActive')(Self); require('../methods/ticket/delete')(Self); require('../methods/ticket/getVAT')(Self); require('../methods/ticket/getSales')(Self); @@ -21,10 +20,11 @@ module.exports = Self => { require('../methods/ticket/canBeInvoiced')(Self); require('../methods/ticket/makeInvoice')(Self); require('../methods/ticket/updateEditableTicket')(Self); - require('../methods/ticket/checkEmptiness')(Self); + require('../methods/ticket/isEmpty')(Self); require('../methods/ticket/updateDiscount')(Self); require('../methods/ticket/uploadFile')(Self); require('../methods/ticket/addSale')(Self); + require('../methods/ticket/transferSales')(Self); Self.observe('before save', async function(ctx) { if (ctx.isNewInstance) return; diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 936cfe1bd..4253be612 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -115,7 +115,11 @@ field="sale.itemFk" show-field="name" value-field="id" - search-function="{or: [{id: $search}, {name: {like: '%' + $search + '%'}}]}"> + search-function="{or: [{id: $search}, {name: {like: '%' + $search + '%'}}]}" + order="id DESC"> + + {{id}} - {{name}} + @@ -258,51 +262,87 @@
- - - - - - - - - - - - - - - - - - - - -
IDF. envioAgenciaAlmacen
No results
{{::ticket.id}}{{::ticket.shipped | dateTime: 'dd/MM/yyyy HH:mm'}}{{::ticket.agencyName}}{{::ticket.warehouseName}}
- - - - - - - - - - + +

Sales to transfer

+ + + + Id + Item + Quantity + + + + + {{::sale.itemFk | zeroFill:6}} + + {{::sale.concept}} + + + {{sale.quantity}} + + + + + + + + +
+ + +

Destination ticket

+ + +
+ + + + + + + + + + + + + + + + + + + + +
IDF. envioAgenciaAlmacen
No results
{{::ticket.id}}{{::ticket.shipped | dateTime: 'dd/MM/yyyy'}}{{::ticket.agencyName}}{{::ticket.warehouseName}}
+ + + + + + + + + + + +
@@ -321,7 +361,7 @@ vn-id="delete-ticket" question="Do you want to delete it?" message="This ticket is now empty" - on-response="$ctrl.moveLines(response)"> + on-response="$ctrl.transferSales($ctrl.transfer.ticketId, response)"> { @@ -121,13 +122,14 @@ class Controller { }); } + /** * Returns an array of indexes * from checked instances * * @return {Array} Indexes of checked instances */ - getCheckedLinesIndex() { + checkedLinesIndex() { if (!this.sales) return; let indexes = []; @@ -138,8 +140,25 @@ class Controller { return indexes; } + firstCheckedLine() { + const checkedLines = this.checkedLines(); + if (checkedLines) + return checkedLines[0]; + } + + /** + * Returns the total of checked instances + * + * @return {Number} Total checked instances + */ + totalCheckedLines() { + const checkedLines = this.checkedLines(); + if (checkedLines) + return checkedLines.length; + } + removeCheckedLines() { - const sales = this.getCheckedLines(); + const sales = this.checkedLines(); sales.forEach(sale => { const index = this.sales.indexOf(sale); @@ -167,7 +186,7 @@ class Controller { onRemoveLinesClick(response) { if (response === 'ACCEPT') { - let sales = this.getCheckedLines(); + let sales = this.checkedLines(); // Remove unsaved instances sales.forEach((sale, index) => { @@ -188,60 +207,36 @@ class Controller { } showTransferPopover(event) { - let filter = {clientFk: this.ticket.clientFk, ticketFk: this.ticket.id}; - let json = encodeURIComponent(JSON.stringify(filter)); - let query = `/api/Tickets/threeLastActive?filter=${json}`; - this.$http.get(query).then(res => { - this.lastThreeTickets = res.data; - }); + this.setTransferParams(); this.$scope.transfer.parent = event.target; this.$scope.transfer.show(); } + setTransferParams() { + const checkedSales = JSON.stringify(this.checkedLines()); + const sales = JSON.parse(checkedSales); + this.transfer = { + lastActiveTickets: [], + sales: sales + }; - checkEmptiness(receiverTicketId) { - let sales = this.getCheckedLines(); - let areAllSalesSelected = sales.length === this.$scope.model.data.length; - this.receiverTicketId = receiverTicketId; - - - if (areAllSalesSelected) { - let query = `/api/Tickets/${this.ticket.id}/checkEmptiness`; - this.$http.get(query).then(res => { - if (res.data) - this.$scope.deleteTicket.show(); - if (!res.data) - this.moveLines(false); - }); - } - - if (!areAllSalesSelected) - this.moveLines(false); + const params = {ticketId: this.ticket.id}; + const query = `/api/clients/${this.ticket.clientFk}/lastActiveTickets`; + this.$http.get(query, {params}).then(res => { + this.transfer.lastActiveTickets = res.data; + }); } - moveLines(removeEmptyTicket) { - let sales = this.getCheckedLines(); - - let currentTicketData = { - currentTicketId: this.ticket.id, - clientFk: this.ticket.clientFk, - addressFk: this.ticket.addressFk, - agencyModeFk: this.ticket.agencyModeFk, - warehouseFk: this.ticket.warehouseFk + transferSales(ticketId) { + const params = { + ticketId: ticketId, + sales: this.transfer.sales }; - let params = { - currentTicket: currentTicketData, - receiverTicket: this.receiverTicketId ? {id: this.receiverTicketId} : currentTicketData, - sales: sales, - removeEmptyTicket: removeEmptyTicket - }; - - this.$http.post(`/api/Sales/moveToTicket`, params).then(res => { - if (res.data) { - this.receiverTicketId = null; + const query = `/api/tickets/${this.ticket.id}/transferSales`; + this.$http.post(query, params).then(res => { + if (res.data) this.goToTicket(res.data.id); - } }); } @@ -251,14 +246,14 @@ class Controller { clientFk: this.ticket.clientFk, ticketCreated: this.ticket.shipped }; - const sales = this.getCheckedLines(); + const sales = this.checkedLines(); this.$http.post(`/api/Claims/createFromSales`, {claim: claim, sales: sales}).then(res => { this.$state.go('claim.card.basicData', {id: res.data.id}); }); } - goToTicket(ticketID) { - this.$state.go('ticket.card.sale', {id: ticketID}); + goToTicket(ticketId) { + this.$state.go('ticket.card.sale', {id: ticketId}); } // Slesperson Mana @@ -336,7 +331,7 @@ class Controller { } showEditDialog() { - this.edit = this.getCheckedLines(); + this.edit = this.checkedLines(); this.$scope.editDialog.show(); } @@ -365,7 +360,7 @@ class Controller { } setReserved(reserved) { - let selectedSales = this.getCheckedLines(); + let selectedSales = this.checkedLines(); let params = {sales: selectedSales, ticketFk: this.ticket.id, reserved: reserved}; let reservedSales = new Map(); @@ -393,7 +388,7 @@ class Controller { showSMSDialog() { const address = this.ticket.address; - const sales = this.getCheckedLines(); + const sales = this.checkedLines(); const items = sales.map(sale => { return `${sale.quantity} ${sale.concept}`; }); diff --git a/modules/ticket/front/sale/locale/es.yml b/modules/ticket/front/sale/locale/es.yml index b8966232f..753323c41 100644 --- a/modules/ticket/front/sale/locale/es.yml +++ b/modules/ticket/front/sale/locale/es.yml @@ -7,7 +7,7 @@ Unmark as reserved: Desmarcar como reservado Update discount: Actualizar descuento There is no changes to save: No hay cambios que guardar Edit discount: Editar descuento -Move to ticket: Mover a ticket +Transfer to ticket: Transferir a ticket New ticket: Nuevo ticket Edit price: Editar precio You are going to delete lines of the ticket: Vas a borrar lineas del ticket @@ -19,7 +19,8 @@ Available: Disponible In which day you want to add the ticket?: ¿A que dia quieres añadir el ticket? Add claim: Crear reclamación Claim: Reclamación -Transfer lines: Transferir líneas +Sales to transfer: Líneas a transferir +Destination ticket: Ticket destinatario Change ticket state to 'Ok': Cambiar estado del ticket a 'Ok' Reserved: Reservado SMSAvailability: >- diff --git a/modules/ticket/front/sale/specs/index.spec.js b/modules/ticket/front/sale/specs/index.spec.js index 4e97bee56..7d669d343 100644 --- a/modules/ticket/front/sale/specs/index.spec.js +++ b/modules/ticket/front/sale/specs/index.spec.js @@ -9,7 +9,7 @@ describe('Ticket', () => { let ticket = { id: 1, - clientFk: 1, + clientFk: 101, shipped: 1, created: new Date(), client: {salesPersonFk: 1}, @@ -92,13 +92,13 @@ describe('Ticket', () => { }); }); - describe('getCheckedLines()', () => { + describe('checkedLines()', () => { it('should make an array of the instances with the property checked true()', () => { let sale = controller.sales[1]; sale.checked = true; let expectedResult = [sale]; - expect(controller.getCheckedLines()).toEqual(expectedResult); + expect(controller.checkedLines()).toEqual(expectedResult); }); }); @@ -235,5 +235,46 @@ describe('Ticket', () => { $httpBackend.flush(); }); }); + + describe('transferSales()', () => { + it('should transfer sales to a ticket', () => { + spyOn(controller, 'goToTicket'); + controller.transfer = { + sales: [{id: 1, itemFk: 1}, {id: 2, itemFk: 4}] + }; + + const expectedResponse = {id: 13}; + const params = { + ticketId: 13, + sales: controller.transfer.sales + }; + + $httpBackend.when('POST', `/api/tickets/1/transferSales`, params).respond(expectedResponse); + $httpBackend.expect('POST', `/api/tickets/1/transferSales`, params).respond(expectedResponse); + controller.transferSales(13); + $httpBackend.flush(); + + expect(controller.goToTicket).toHaveBeenCalledWith(13); + }); + }); + + describe('setTransferParams()', () => { + it('should define the transfer object on the controller and its default properties', () => { + let sale = controller.sales[1]; + sale.checked = true; + const expectedResponse = [sale]; + + $httpBackend.when('GET', `/api/clients/101/lastActiveTickets?ticketId=1`).respond(expectedResponse); + $httpBackend.expect('GET', `/api/clients/101/lastActiveTickets?ticketId=1`).respond(expectedResponse); + controller.setTransferParams(); + $httpBackend.flush(); + + const lastActiveTickets = controller.transfer.lastActiveTickets; + + expect(controller.transfer).toBeDefined(); + expect(lastActiveTickets).toBeDefined(); + expect(lastActiveTickets[0].id).toEqual(4); + }); + }); }); }); diff --git a/modules/ticket/front/sale/style.scss b/modules/ticket/front/sale/style.scss index 2831631ff..bcaaa6fbc 100644 --- a/modules/ticket/front/sale/style.scss +++ b/modules/ticket/front/sale/style.scss @@ -61,13 +61,30 @@ vn-ticket-sale { } vn-popover.transfer{ - vn-table { - min-width: 650px; + vn-textfield { + margin: 0 + } + + vn-horizontal { + & > vn-one:nth-child(1){ + border-right: 1px solid $color-bg; + padding-right: 1em; + } + + & > vn-one:nth-child(2){ + margin-left: 1em + } + } + vn-table, table { margin-bottom: 10px; } - vn-icon:nth-child(1) { - padding-top: 0.2em; - font-size: 1.7em; + + vn-table { + width: 20em + } + + table { + width: 25em } }