diff --git a/back/methods/collection/getTickets.js b/back/methods/collection/getTickets.js new file mode 100644 index 0000000000..445fc070d3 --- /dev/null +++ b/back/methods/collection/getTickets.js @@ -0,0 +1,146 @@ + +module.exports = Self => { + Self.remoteMethodCtx('getTickets', { + description: 'Make a new collection of tickets', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'number', + description: 'The collection id', + required: true, + http: {source: 'path'} + }, { + arg: 'print', + type: 'boolean', + description: 'True if you want to print' + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:id/getTickets`, + verb: 'POST' + } + }); + + Self.getTickets = async(ctx, id, print, options) => { + const userId = ctx.req.accessToken.userId; + const origin = ctx.req.headers.origin; + const $t = ctx.req.__; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + myOptions.userId = userId; + + const promises = []; + + const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions); + const sales = await Self.rawSql(` + SELECT s.ticketFk, + sgd.saleGroupFk, + s.id saleFk, + s.itemFk, + i.longName, + i.size, + ic.color, + o.code origin, + ish.packing, + ish.grouping, + s.isAdded, + s.originalQuantity, + s.quantity saleQuantity, + iss.quantity reservedQuantity, + SUM(iss.quantity) OVER (PARTITION BY s.id ORDER BY ish.id) accumulatedQuantity, + ROW_NUMBER () OVER (PARTITION BY s.id ORDER BY pickingOrder) currentItemShelving, + COUNT(*) OVER (PARTITION BY s.id ORDER BY s.id) totalItemShelving, + sh.code, + IFNULL(p2.code, p.code) parkingCode, + IFNULL(p2.pickingOrder, p.pickingOrder) pickingOrder, + iss.id itemShelvingSaleFk, + iss.isPicked + FROM ticketCollection tc + LEFT JOIN collection c ON c.id = tc.collectionFk + JOIN ticket t ON t.id = tc.ticketFk + JOIN sale s ON s.ticketFk = t.id + LEFT JOIN saleGroupDetail sgd ON sgd.saleFk = s.id + LEFT JOIN saleGroup sg ON sg.id = sgd.saleGroupFk + LEFT JOIN parking p2 ON p2.id = sg.parkingFk + JOIN item i ON i.id = s.itemFk + LEFT JOIN itemShelvingSale iss ON iss.saleFk = s.id + LEFT JOIN itemShelving ish ON ish.id = iss.itemShelvingFk + LEFT JOIN shelving sh ON sh.code = ish.shelvingFk + LEFT JOIN parking p ON p.id = sh.parkingFk + LEFT JOIN itemColor ic ON ic.itemFk = s.itemFk + LEFT JOIN origin o ON o.id = i.originFk + WHERE tc.collectionFk = ? + GROUP BY ish.id, p.code, p2.code + ORDER BY pickingOrder;`, [id], myOptions); + + if (print) + await Self.rawSql(`CALL vn.collection_printSticker(?, ?)`, [id, null], myOptions); + + const collection = {collectionFk: id, tickets: []}; + if (tickets && tickets.length) { + for (const ticket of tickets) { + const ticketId = ticket.ticketFk; + + // SEND ROCKET + if (ticket.observaciones != '') { + for (observation of ticket.observaciones.split(' ')) { + if (['#', '@'].includes(observation.charAt(0))) { + promises.push(Self.app.models.Chat.send(ctx, observation, + $t('The ticket is in preparation', { + ticketId: ticketId, + ticketUrl: `${origin}/#!/ticket/${ticketId}/summary`, + salesPersonId: ticket.salesPersonFk + }))); + } + } + } + + // SET COLLECTION + if (sales && sales.length) { + // GET BARCODES + const barcodes = await Self.rawSql(` + SELECT s.id saleFk, b.code, c.id + FROM vn.sale s + LEFT JOIN vn.itemBarcode b ON b.itemFk = s.itemFk + LEFT JOIN vn.buy c ON c.itemFk = s.itemFk + LEFT JOIN vn.entry e ON e.id = c.entryFk + LEFT JOIN vn.travel tr ON tr.id = e.travelFk + WHERE s.ticketFk = ? + AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`, + [ticketId], myOptions); + + // BINDINGS + ticket.sales = []; + for (const sale of sales) { + if (sale.ticketFk === ticketId) { + sale.Barcodes = []; + + if (barcodes && barcodes.length) { + for (const barcode of barcodes) { + if (barcode.saleFk === sale.saleFk) { + for (const prop in barcode) { + if (['id', 'code'].includes(prop) && barcode[prop]) + sale.Barcodes.push(barcode[prop].toString(), '0' + barcode[prop]); + } + } + } + } + + ticket.sales.push(sale); + } + } + } + collection.tickets.push(ticket); + } + } + await Promise.all(promises); + + return collection; + }; +}; diff --git a/back/methods/collection/spec/getTickets.spec.js b/back/methods/collection/spec/getTickets.spec.js new file mode 100644 index 0000000000..e6b9e6a17f --- /dev/null +++ b/back/methods/collection/spec/getTickets.spec.js @@ -0,0 +1,39 @@ +const models = require('vn-loopback/server/server').models; + +describe('collection getTickets()', () => { + let ctx; + beforeAll(async() => { + ctx = { + req: { + accessToken: {userId: 9}, + headers: {origin: 'http://localhost'} + } + }; + }); + + it('should get tickets, sales and barcodes from collection', async() => { + const tx = await models.Collection.beginTransaction({}); + + try { + const options = {transaction: tx}; + const collectionId = 1; + + const collectionTickets = await models.Collection.getTickets(ctx, collectionId, null, options); + + expect(collectionTickets.collectionFk).toEqual(collectionId); + expect(collectionTickets.tickets.length).toEqual(3); + expect(collectionTickets.tickets[0].ticketFk).toEqual(1); + expect(collectionTickets.tickets[1].ticketFk).toEqual(2); + expect(collectionTickets.tickets[2].ticketFk).toEqual(23); + expect(collectionTickets.tickets[0].sales[0].ticketFk).toEqual(1); + expect(collectionTickets.tickets[0].sales[1].ticketFk).toEqual(1); + expect(collectionTickets.tickets[0].sales[2].ticketFk).toEqual(1); + expect(collectionTickets.tickets[0].sales[0].Barcodes.length).toBeTruthy(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/back/models/collection.js b/back/models/collection.js index a41742ee77..bfa906af60 100644 --- a/back/models/collection.js +++ b/back/models/collection.js @@ -4,4 +4,5 @@ module.exports = Self => { require('../methods/collection/getSectors')(Self); require('../methods/collection/setSaleQuantity')(Self); require('../methods/collection/previousLabel')(Self); + require('../methods/collection/getTickets')(Self); }; diff --git a/db/changes/233802/.gitkeep b/db/changes/233802/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/changes/233802/00-collectionGetTicketsACL.sql b/db/changes/233802/00-collectionGetTicketsACL.sql new file mode 100644 index 0000000000..06b584386f --- /dev/null +++ b/db/changes/233802/00-collectionGetTicketsACL.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL`(model, property, accessType, permission, principalType, principalId) + VALUES + ('Collection', 'getTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index eb28c4a01b..ab76155916 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1409,10 +1409,8 @@ INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_ INSERT INTO `vn`.`ticketWeekly`(`ticketFk`, `weekDay`) VALUES - (1, 0), (2, 1), (3, 2), - (4, 4), (5, 6), (15, 6); diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 1b18eb8668..6f78d7c4e5 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -520,8 +520,7 @@ export default { searchResultDate: 'vn-ticket-summary [label=Landed] span', topbarSearch: 'vn-searchbar', moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]', - fourthWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(5)', - fiveWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(6)', + thirdWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(4)', weeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table table tbody tr', firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(2) vn-icon-button[icon="delete"]', firstWeeklyTicketAgency: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(2) [ng-model="weekly.agencyModeFk"]', diff --git a/e2e/paths/05-ticket/09_weekly.spec.js b/e2e/paths/05-ticket/09_weekly.spec.js index a9cce2ead1..74febfd014 100644 --- a/e2e/paths/05-ticket/09_weekly.spec.js +++ b/e2e/paths/05-ticket/09_weekly.spec.js @@ -19,7 +19,7 @@ describe('Ticket descriptor path', () => { it('should count the amount of tickets in the turns section', async() => { const result = await page.countElement(selectors.ticketsIndex.weeklyTicket); - expect(result).toEqual(7); + expect(result).toEqual(5); }); it('should go back to the ticket index then search and access a ticket summary', async() => { @@ -45,7 +45,7 @@ describe('Ticket descriptor path', () => { it('should confirm the ticket 11 was added to thursday', async() => { await page.accessToSection('ticket.weekly.index'); - const result = await page.waitToGetProperty(selectors.ticketsIndex.fourthWeeklyTicket, 'value'); + const result = await page.waitToGetProperty(selectors.ticketsIndex.thirdWeeklyTicket, 'value'); expect(result).toEqual('Thursday'); }); @@ -80,7 +80,9 @@ describe('Ticket descriptor path', () => { it('should confirm the ticket 11 was added on saturday', async() => { await page.accessToSection('ticket.weekly.index'); - const result = await page.waitToGetProperty(selectors.ticketsIndex.fiveWeeklyTicket, 'value'); + await page.waitForTimeout(5000); + + const result = await page.waitToGetProperty(selectors.ticketsIndex.thirdWeeklyTicket, 'value'); expect(result).toEqual('Saturday'); }); @@ -104,7 +106,7 @@ describe('Ticket descriptor path', () => { await page.doSearch(); const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult); - expect(nResults).toEqual(7); + expect(nResults).toEqual(5); }); it('should update the agency then remove it afterwards', async() => { diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 7e194451f3..645a874e89 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -188,5 +188,6 @@ "The ticket doesn't exist.": "The ticket doesn't exist.", "The sales do not exists": "The sales do not exists", "Ticket without Route": "Ticket without route", - "Booking completed": "Booking completed" + "Booking completed": "Booking complete", + "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 034f267d0f..8c50cd9d8d 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -318,5 +318,6 @@ "Street should be uppercase": "La dirección fiscal debe ir en mayúscula", "The response is not a PDF": "La respuesta no es un PDF", "Ticket without Route": "Ticket sin ruta", - "Booking completed": "Reserva completada" -} \ No newline at end of file + "Booking completed": "Reserva completada", + "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación" +} diff --git a/modules/client/back/methods/client/creditRequestEmail.js b/modules/client/back/methods/client/creditRequestEmail.js index 0255949e0d..60047de771 100644 --- a/modules/client/back/methods/client/creditRequestEmail.js +++ b/modules/client/back/methods/client/creditRequestEmail.js @@ -1,5 +1,5 @@ module.exports = Self => { - Self.remoteMethodCtx('clientCreditEmail', { + Self.remoteMethodCtx('creditRequestEmail', { description: 'Sends the credit request email with an attached PDF', accessType: 'WRITE', accepts: [ @@ -40,5 +40,5 @@ module.exports = Self => { }, }); - Self.clientCreditEmail = ctx => Self.sendTemplate(ctx, 'credit-request'); + Self.creditRequestEmail = ctx => Self.sendTemplate(ctx, 'credit-request'); }; diff --git a/modules/ticket/back/methods/ticket-request/specs/filter.spec.js b/modules/ticket/back/methods/ticket-request/specs/filter.spec.js index ae004a024a..9ab89f0099 100644 --- a/modules/ticket/back/methods/ticket-request/specs/filter.spec.js +++ b/modules/ticket/back/methods/ticket-request/specs/filter.spec.js @@ -14,7 +14,7 @@ describe('ticket-request filter()', () => { const result = await models.TicketRequest.filter(ctx, options); - expect(result.length).toEqual(3); + expect(result.length).toEqual(5); await tx.rollback(); } catch (e) { @@ -93,7 +93,7 @@ describe('ticket-request filter()', () => { const result = await models.TicketRequest.filter(ctx, options); const requestId = result[0].id; - expect(requestId).toEqual(3); + expect(requestId).toEqual(1); await tx.rollback(); } catch (e) { @@ -113,7 +113,7 @@ describe('ticket-request filter()', () => { const result = await models.TicketRequest.filter(ctx, options); const requestId = result[0].id; - expect(requestId).toEqual(3); + expect(requestId).toEqual(1); await tx.rollback(); } catch (e) { @@ -153,7 +153,7 @@ describe('ticket-request filter()', () => { const result = await models.TicketRequest.filter(ctx, {order: 'id'}, options); const requestId = result[0].id; - expect(requestId).toEqual(3); + expect(requestId).toEqual(1); await tx.rollback(); } catch (e) { @@ -173,7 +173,7 @@ describe('ticket-request filter()', () => { const result = await models.TicketRequest.filter(ctx, options); const requestId = result[0].id; - expect(requestId).toEqual(3); + expect(requestId).toEqual(1); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/methods/ticket-weekly/specs/filter.spec.js b/modules/ticket/back/methods/ticket-weekly/specs/filter.spec.js index 2587b66576..453b7924f1 100644 --- a/modules/ticket/back/methods/ticket-weekly/specs/filter.spec.js +++ b/modules/ticket/back/methods/ticket-weekly/specs/filter.spec.js @@ -16,8 +16,8 @@ describe('ticket-weekly filter()', () => { const firstRow = result[0]; - expect(firstRow.ticketFk).toEqual(1); - expect(result.length).toEqual(6); + expect(firstRow.ticketFk).toEqual(2); + expect(result.length).toEqual(4); await tx.rollback(); } catch (e) { @@ -52,12 +52,12 @@ describe('ticket-weekly filter()', () => { try { const options = {transaction: tx}; - const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}}; + const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'max'}}; const result = await models.TicketWeekly.filter(ctx, null, options); const firstRow = result[0]; - expect(firstRow.clientName).toEqual('Bruce Wayne'); + expect(firstRow.clientName).toEqual('Max Eisenhardt'); await tx.rollback(); } catch (e) { @@ -72,13 +72,13 @@ describe('ticket-weekly filter()', () => { try { const options = {transaction: tx}; - const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}}; + const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1105}}; const result = await models.TicketWeekly.filter(ctx, null, options); const firstRow = result[0]; - expect(firstRow.clientFk).toEqual(1101); - expect(firstRow.clientName).toEqual('Bruce Wayne'); + expect(firstRow.clientFk).toEqual(1105); + expect(firstRow.clientName).toEqual('Max Eisenhardt'); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/methods/ticket/isEditableOrThrow.js b/modules/ticket/back/methods/ticket/isEditableOrThrow.js index 6a8bafa2cb..f8285cecd8 100644 --- a/modules/ticket/back/methods/ticket/isEditableOrThrow.js +++ b/modules/ticket/back/methods/ticket/isEditableOrThrow.js @@ -40,7 +40,7 @@ module.exports = Self => { if (!isEditable && !isRoleAdvanced) throw new ForbiddenError(`This ticket is not editable.`); - if (isLocked) + if (isLocked && !isWeekly) throw new ForbiddenError(`This ticket is locked.`); if (isWeekly && !canEditWeeklyTicket) diff --git a/modules/ticket/back/methods/ticket/specs/isEditable.spec.js b/modules/ticket/back/methods/ticket/specs/isEditable.spec.js index 301745ed34..9dd1f85442 100644 --- a/modules/ticket/back/methods/ticket/specs/isEditable.spec.js +++ b/modules/ticket/back/methods/ticket/specs/isEditable.spec.js @@ -8,7 +8,7 @@ describe('isEditable()', () => { try { const options = {transaction: tx}; const ctx = {req: {accessToken: {userId: 35}}}; - result = await models.Ticket.isEditable(ctx, 5, options); + result = await models.Ticket.isEditable(ctx, 19, options); await tx.rollback(); } catch (error) { await tx.rollback();