From 896a7eb6f05a5764d0a0a4cd2fc685c31287f1a3 Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 7 Nov 2022 15:55:17 +0100 Subject: [PATCH 1/6] refs #4700 arreglar filtro y traducciones --- .../10501-november/00-ticket_canMerge.sql | 9 + .../00-ticket_canbePostposed.sql | 75 ++++++++ loopback/locale/es.json | 3 +- .../methods/ticket-future/getTicketsFuture.js | 179 ++++++++++++++++++ .../ticket-future/moveTicketsFuture.js | 68 +++++++ modules/ticket/back/model-config.json | 3 + modules/ticket/back/models/ticket-future.json | 12 ++ modules/ticket/back/models/ticket.js | 2 + .../front/future-search-panel/index.html | 86 +++++++++ .../ticket/front/future-search-panel/index.js | 30 +++ .../front/future-search-panel/locale/en.yml | 1 + .../front/future-search-panel/locale/es.yml | 13 ++ modules/ticket/front/future/index.html | 164 ++++++++++++++++ modules/ticket/front/future/index.js | 107 +++++++++++ modules/ticket/front/future/locale/es.yml | 15 ++ modules/ticket/front/future/style.scss | 0 modules/ticket/front/index.js | 2 + modules/ticket/front/routes.json | 9 +- 18 files changed, 776 insertions(+), 2 deletions(-) create mode 100644 db/changes/10501-november/00-ticket_canMerge.sql create mode 100644 db/changes/10501-november/00-ticket_canbePostposed.sql create mode 100644 modules/ticket/back/methods/ticket-future/getTicketsFuture.js create mode 100644 modules/ticket/back/methods/ticket-future/moveTicketsFuture.js create mode 100644 modules/ticket/back/models/ticket-future.json create mode 100644 modules/ticket/front/future-search-panel/index.html create mode 100644 modules/ticket/front/future-search-panel/index.js create mode 100644 modules/ticket/front/future-search-panel/locale/en.yml create mode 100644 modules/ticket/front/future-search-panel/locale/es.yml create mode 100644 modules/ticket/front/future/index.html create mode 100644 modules/ticket/front/future/index.js create mode 100644 modules/ticket/front/future/locale/es.yml create mode 100644 modules/ticket/front/future/style.scss diff --git a/db/changes/10501-november/00-ticket_canMerge.sql b/db/changes/10501-november/00-ticket_canMerge.sql new file mode 100644 index 000000000..6db3637ac --- /dev/null +++ b/db/changes/10501-november/00-ticket_canMerge.sql @@ -0,0 +1,9 @@ +DROP PROCEDURE IF EXISTS vn.ticket_canMerge; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canMerge`(vDated DATE, vScopeDays INT, vLitersMax INT, vLinesMax INT, vWarehouseFk INT) +BEGIN + CALL vn.ticket_canbePostponed(vDated,TIMESTAMPADD(DAY, vScopeDays, vDated),vLitersMax,vLinesMax,vWarehouseFk); +END $$ +DELIMITER ; diff --git a/db/changes/10501-november/00-ticket_canbePostposed.sql b/db/changes/10501-november/00-ticket_canbePostposed.sql new file mode 100644 index 000000000..6ad9df154 --- /dev/null +++ b/db/changes/10501-november/00-ticket_canbePostposed.sql @@ -0,0 +1,75 @@ +DROP PROCEDURE IF EXISTS vn.ticket_canbePostponed; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canbePostponed`(vOriginDated DATE, vFutureDated DATE, vLitersMax INT, vLinesMax INT, vWarehouseFk INT) +BEGIN +/** + * Devuelve un listado de tickets susceptibles de fusionarse con otros tickets en el futuro + * + * @param vOriginDated Fecha en cuestión + * @param vFutureDated Fecha en el futuro a sondear + * @param vLitersMax Volumen máximo de los tickets a catapultar + * @param vLinesMax Número máximo de lineas de los tickets a catapultar + * @param vWarehouseFk Identificador de vn.warehouse + */ + DROP TEMPORARY TABLE IF EXISTS tmp.filter; + CREATE TEMPORARY TABLE tmp.filter + (INDEX (id)) + SELECT sv.ticketFk id, + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt, + CAST(sum(litros) AS DECIMAL(10,0)) liters, + CAST(count(*) AS DECIMAL(10,0)) `lines`, + st.name state, + sub2.id ticketFuture, + t.landed originETD, + sub2.landed destETD, + sub2.iptd tfIpt, + sub2.state tfState, + t.clientFk, + t.warehouseFk, + ts.alertLevel, + t.shipped, + sub2.shipped tfShipped, + t.workerFk + FROM vn.saleVolume sv + JOIN vn.sale s ON s.id = sv.saleFk + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.ticket t ON t.id = sv.ticketFk + JOIN vn.address a ON a.id = t.addressFk + JOIN vn.province p ON p.id = a.provinceFk + JOIN vn.country c ON c.id = p.countryFk + JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.alertLevel al ON al.id = ts.alertLevel + LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id + LEFT JOIN ( + SELECT * + FROM ( + SELECT + t.addressFk , + t.id, + t.landed, + t.shipped, + st.name state, + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) iptd + FROM vn.ticket t + JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk + WHERE t.shipped BETWEEN vFutureDated + AND util.dayend(vFutureDated) + AND t.warehouseFk = vWarehouseFk + GROUP BY t.id + ) sub + GROUP BY sub.addressFk + ) sub2 ON sub2.addressFk = t.addressFk AND t.id != sub2.id + WHERE t.shipped BETWEEN vOriginDated AND util.dayend(vOriginDated) + AND t.warehouseFk = vWarehouseFk + AND al.code = 'FREE' + AND tp.ticketFk IS NULL + GROUP BY sv.ticketFk + HAVING liters <= IFNULL(vLitersMax, 9999) AND `lines` <= IFNULL(vLinesMax, 9999) AND ticketFuture; +END$$ +DELIMITER ; diff --git a/loopback/locale/es.json b/loopback/locale/es.json index a41315dd1..a12cf1c09 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -237,5 +237,6 @@ "Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador", "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente", "You don't have grant privilege": "No tienes privilegios para dar privilegios", - "You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario" + "You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario", + "MOVE_TICKET_CONFIRMATION": "Ticket {{id}} ({{originDated}}) fusionado con {{tfId}} ({{futureDated}}) [{{fullPath}}]" } diff --git a/modules/ticket/back/methods/ticket-future/getTicketsFuture.js b/modules/ticket/back/methods/ticket-future/getTicketsFuture.js new file mode 100644 index 000000000..5b83e50be --- /dev/null +++ b/modules/ticket/back/methods/ticket-future/getTicketsFuture.js @@ -0,0 +1,179 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('getTicketsFuture', { + description: 'Find all instances of the model matched by filter from the data source.', + accessType: 'READ', + accepts: [ + { + arg: 'originDated', + type: 'date', + description: 'The date in question', + required: true + }, + { + arg: 'futureDated', + type: 'date', + description: 'The date to probe', + required: true + }, + { + arg: 'litersMax', + type: 'number', + description: 'Maximum volume of tickets to catapult', + required: true + }, + { + arg: 'linesMax', + type: 'number', + description: 'Maximum number of lines of tickets to catapult', + required: true + }, + { + arg: 'warehouseFk', + type: 'number', + description: 'Warehouse identifier', + required: true + }, + { + arg: 'filter', + type: 'object', + description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string` + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/getTicketsFuture`, + verb: 'GET' + } + }); + + Self.getTicketsFuture = async (ctx, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + const conn = Self.dataSource.connector; + const args = ctx.args; + const stmts = []; + let stmt; + + stmt = new ParameterizedSQL( + `CALL vn.ticket_canbePostponed(?,?,?,?,?)`, + [args.originDated, args.futureDated, args.litersMax, args.linesMax, args.warehouseFk]); + + stmts.push(stmt); + stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); + + stmt = new ParameterizedSQL(` + CREATE TEMPORARY TABLE tmp.sale_getProblems + (INDEX (ticketFk)) + ENGINE = MEMORY + SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped + FROM tmp.filter f + LEFT JOIN alertLevel al ON al.id = f.alertLevel + WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)`); + stmts.push(stmt); + + stmts.push('CALL ticket_getProblems(FALSE)'); + + stmt = new ParameterizedSQL(` + SELECT f.*, tp.* + FROM tmp.filter f + LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = f.id`); + + if (args.problems != undefined && (!args.from && !args.to)) + throw new UserError('Choose a date range or days forward'); + + const myWhere = buildFilter(ctx.args, (param, value) => { + switch (param) { + // case 'search': + // return /^\d+$/.test(value) + // ? {'t.id': {inq: value}} + // : {'t.nickname': {like: `%${value}%`}}; + case 'shipped': + return {'shipped': {like: `%${value}%`}}; + // case 'refFk': + // return {'t.refFk': value}; + + // case 'provinceFk': + // return {'t.provinceFk': value}; + // case 'stateFk': + // return {'t.stateFk': value}; + // case 'alertLevel': + // return {'t.alertLevel': value}; + // case 'pending': + // if (value) { + // return {and: [ + // {'t.alertLevel': 0}, + // {'t.alertLevelCode': {nin: [ + // 'OK', + // 'BOARDING', + // 'PRINTED', + // 'PRINTED_AUTO', + // 'PICKER_DESIGNED' + // ]}} + // ]}; + // } else { + // return {and: [ + // {'t.alertLevel': {gt: 0}} + // ]}; + // } + // case 'agencyModeFk': + // case 'warehouseFk': + // param = `t.${param}`; + // return {[param]: value}; + } + }); + let finalFilter = {}; + finalFilter = mergeFilters(finalFilter, {where: myWhere}); + if (finalFilter.where) + stmt.merge(conn.makeWhere(finalFilter.where)); + + let condition; + let hasProblem; + let range; + let hasWhere; + switch (args.problems) { + case true: + condition = `or`; + hasProblem = true; + range = {neq: null}; + hasWhere = true; + break; + + case false: + condition = `and`; + hasProblem = null; + range = null; + hasWhere = true; + break; + } + + const problems = {[condition]: [ + {'tp.isFreezed': hasProblem}, + {'tp.risk': hasProblem}, + {'tp.hasTicketRequest': hasProblem}, + {'tp.itemShortage': range} + ]}; + + if (hasWhere) + stmt.merge(conn.makeWhere(problems)); + + const ticketsIndex = stmts.push(stmt) - 1; + + stmts.push( + `DROP TEMPORARY TABLE + tmp.filter, + tmp.ticket_problems`); + + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + return result[ticketsIndex]; + }; +}; \ No newline at end of file diff --git a/modules/ticket/back/methods/ticket-future/moveTicketsFuture.js b/modules/ticket/back/methods/ticket-future/moveTicketsFuture.js new file mode 100644 index 000000000..940a83c33 --- /dev/null +++ b/modules/ticket/back/methods/ticket-future/moveTicketsFuture.js @@ -0,0 +1,68 @@ +const LoopBackContext = require('loopback-context'); + +module.exports = Self => { + Self.remoteMethodCtx('moveTicketsFuture', { + description: 'Move specified tickets to the future', + accessType: 'WRITE', + accepts: [ + { + arg: 'tickets', + type: ['object'], + description: 'The array of tickets', + required: false + } + ], + returns: { + type: 'string', + root: true + }, + http: { + path: `/moveTicketsFuture`, + verb: 'POST' + } + }); + + Self.moveTicketsFuture = async (ctx, tickets, options) => { + const loopBackContext = LoopBackContext.getCurrentContext(); + const httpCtx = {req: loopBackContext.active}; + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + for (let ticket of tickets) { + console.log(ticket); + try { + if(!ticket.id || !ticket.ticketFuture) continue; + await models.Sale.updateAll({ticketFk: ticket.id}, {ticketFk: ticket.ticketFuture}, myOptions); + await models.Ticket.setDeleted(ctx, ticket.id, myOptions); + if (tx) + { + const httpRequest = httpCtx.req.http.req; + const $t = httpRequest.__; + const origin = httpRequest.headers.origin; + const fullPath = `${origin}/#!/ticket/${ticket.ticketFuture}/summary`; + const message = $t('MOVE_TICKET_CONFIRMATION', { + originDated: new Date(ticket.originETD).toLocaleDateString('es-ES'), + futureDated: new Date(ticket.destETD).toLocaleDateString('es-ES'), + id: ticket.id, + tfId: ticket.ticketFuture, + fullPath + }); + await tx.commit(); + await models.Chat.sendCheckingPresence(httpCtx, ticket.workerFk, message); + } + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; + }; +}; \ No newline at end of file diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 21e800b36..82d8e3b1a 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -91,5 +91,8 @@ }, "TicketConfig": { "dataSource": "vn" + }, + "TicketFuture": { + "dataSource": "vn" } } diff --git a/modules/ticket/back/models/ticket-future.json b/modules/ticket/back/models/ticket-future.json new file mode 100644 index 000000000..5d0b24dbc --- /dev/null +++ b/modules/ticket/back/models/ticket-future.json @@ -0,0 +1,12 @@ +{ + "name": "TicketFuture", + "base": "PersistedModel", + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] + } \ No newline at end of file diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index c05130552..649a83160 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -4,6 +4,8 @@ const LoopBackContext = require('loopback-context'); module.exports = Self => { // Methods require('./ticket-methods')(Self); + require('../methods/ticket-future/getTicketsFuture')(Self); + require('../methods/ticket-future/moveTicketsFuture')(Self); Self.observe('before save', async function(ctx) { const loopBackContext = LoopBackContext.getCurrentContext(); diff --git a/modules/ticket/front/future-search-panel/index.html b/modules/ticket/front/future-search-panel/index.html new file mode 100644 index 000000000..4f5486d62 --- /dev/null +++ b/modules/ticket/front/future-search-panel/index.html @@ -0,0 +1,86 @@ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/modules/ticket/front/future-search-panel/index.js b/modules/ticket/front/future-search-panel/index.js new file mode 100644 index 000000000..bbc2148e7 --- /dev/null +++ b/modules/ticket/front/future-search-panel/index.js @@ -0,0 +1,30 @@ +import ngModule from '../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +class Controller extends SearchPanel { + constructor($, $element) { + super($, $element); + this.filter = this.$.filter; + } + + get from() { + return this._from; + } + + set from(value) { + this._from = value; + } + + get to() { + return this._to; + } + + set to(value) { + this._to = value; + } +} + +ngModule.vnComponent('vnFutureTicketSearchPanel', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/ticket/front/future-search-panel/locale/en.yml b/modules/ticket/front/future-search-panel/locale/en.yml new file mode 100644 index 000000000..0440752c5 --- /dev/null +++ b/modules/ticket/front/future-search-panel/locale/en.yml @@ -0,0 +1 @@ +Future tickets: Tickets a futuro \ No newline at end of file diff --git a/modules/ticket/front/future-search-panel/locale/es.yml b/modules/ticket/front/future-search-panel/locale/es.yml new file mode 100644 index 000000000..e459c3cd9 --- /dev/null +++ b/modules/ticket/front/future-search-panel/locale/es.yml @@ -0,0 +1,13 @@ +Future tickets: Tickets a futuro +Origin date: Fecha origen +Destination date: Fecha destino +Origin ETD: ETD origen +Destination ETD: ETD destino +Max Lines Origin: Líneas máx. origen +Max Liters Origin: Litros máx. origen +Origin ITP: ITP origen +Destination ITP: ITP destino +With problems: Con problemas +Warehouse: Almacén +Origin Agrupated State: Estado agrupado origen +Destination Agrupated State: Estado agrupado destino diff --git a/modules/ticket/front/future/index.html b/modules/ticket/front/future/index.html new file mode 100644 index 000000000..9dbb5a4ca --- /dev/null +++ b/modules/ticket/front/future/index.html @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Problems + + Origin ID + + Origin ETD + + Origin State + + IPT + + Liters + + Available Lines + + Destination ID + + Destination ETD + + Destination State + + IPT +
+ + + + + + + + + + + + + + + + + {{::ticket.id}} + + {{::ticket.originETD | date: 'dd/MM/yyyy'}} + + {{::ticket.state}} + {{::ticket.ipt}}{{::ticket.liters}}{{::ticket.lines}} + {{::ticket.ticketFuture}} + + {{::ticket.destETD | date: 'dd/MM/yyyy'}} + + {{::ticket.tfState}} + {{::ticket.tfIpt}}
+
+
+
+ + + + \ No newline at end of file diff --git a/modules/ticket/front/future/index.js b/modules/ticket/front/future/index.js new file mode 100644 index 000000000..6dda5812d --- /dev/null +++ b/modules/ticket/front/future/index.js @@ -0,0 +1,107 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.$checkAll = false; + + const originDated = new Date(); + const futureDated = new Date(); + const warehouseFk = 1; + const litersMax = 9999; + const linesMax = 9999; + + this.defaultFilter = { + originDated, + futureDated, + warehouseFk, + litersMax, + linesMax + }; + + this.smartTableOptions = { + activeButtons: { + search: true + }, + columns: [{ + field:'problems', + searchable: false + }, + { + field:'ETD', + searchable: false + }, + { + field:'tfETD', + searchable: false + }] + }; + } + + get checked() { + const tickets = this.$.model.data || []; + const checkedLines = []; + for (let ticket of tickets) { + if (ticket.checked) + checkedLines.push(ticket); + } + + return checkedLines; + } + + moveTicketsFuture() + { + let params = { + tickets: this.checked + }; + this.$http.post('Tickets/moveTicketsFuture', params); + this.reload(); + } + + compareDate(date) { + let today = new Date(); + today.setHours(0, 0, 0, 0); + let timeTicket = new Date(date); + timeTicket.setHours(0, 0, 0, 0); + + let comparation = today - timeTicket; + + if (comparation == 0) + return 'warning'; + if (comparation < 0) + return 'success'; + } + + stateColor(state) { + if (state === 'OK') + return 'success'; + else if (state === 'FREE') + return 'notice'; + } + + exprBuilder(param, value) { + switch (param) { + case 'shipped': + return {'shipped': {like: `%${value}%`}}; + } + } + + dateRange(value) { + const minHour = new Date(value); + minHour.setHours(0, 0, 0, 0); + const maxHour = new Date(value); + maxHour.setHours(23, 59, 59, 59); + + return [minHour, maxHour]; + } + + reload() { + this.$.model.refresh(); + } +} + +ngModule.vnComponent('vnTicketFuture', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/ticket/front/future/locale/es.yml b/modules/ticket/front/future/locale/es.yml new file mode 100644 index 000000000..946611e66 --- /dev/null +++ b/modules/ticket/front/future/locale/es.yml @@ -0,0 +1,15 @@ +Future tickets: Tickets a futuro +Search tickets: Buscar tickets +Search future tickets by date: Buscar tickets por fecha +Problems: Problemas +Origin ID: ID origen +Closing: Cierre +Origin State: Estado origen +Destination State: Estado destino +Liters: Litros +Available Lines: Líneas disponibles +Destination ID: ID destino +Destination ETD: ETD Destino +Origin ETD: ETD Origen +Move tickets: Mover tickets +Move confirmation: ¿Desea mover {0} tickets hacia el futuro? diff --git a/modules/ticket/front/future/style.scss b/modules/ticket/front/future/style.scss new file mode 100644 index 000000000..e69de29bb diff --git a/modules/ticket/front/index.js b/modules/ticket/front/index.js index 0558d251d..6106a22eb 100644 --- a/modules/ticket/front/index.js +++ b/modules/ticket/front/index.js @@ -34,3 +34,5 @@ import './dms/create'; import './dms/edit'; import './sms'; import './boxing'; +import './future'; +import './future-search-panel'; diff --git a/modules/ticket/front/routes.json b/modules/ticket/front/routes.json index 4be8e2183..62f43a098 100644 --- a/modules/ticket/front/routes.json +++ b/modules/ticket/front/routes.json @@ -7,7 +7,8 @@ "menus": { "main": [ {"state": "ticket.index", "icon": "icon-ticket"}, - {"state": "ticket.weekly.index", "icon": "schedule"} + {"state": "ticket.weekly.index", "icon": "schedule"}, + {"state": "ticket.future", "icon": "double_arrow"} ], "card": [ {"state": "ticket.card.basicData.stepOne", "icon": "settings"}, @@ -283,6 +284,12 @@ "params": { "ticket": "$ctrl.ticket" } + }, + { + "url": "/future", + "state": "ticket.future", + "component": "vn-ticket-future", + "description": "Future tickets" } ] } From ff000661385eb8c6e496bc4a971f2da7e61c383f Mon Sep 17 00:00:00 2001 From: alexandre Date: Tue, 8 Nov 2022 15:56:58 +0100 Subject: [PATCH 2/6] refs #4700 tests front y fixtures --- db/dump/fixtures.sql | 10 +- .../sales-monitor/specs/salesFilter.spec.js | 10 +- .../methods/ticket-future/getTicketsFuture.js | 173 +++++++++++------- .../spec/getTicketsFuture.spec.js | 165 +++++++++++++++++ .../moveTicketsFuture.js => ticket/merge.js} | 13 +- .../back/methods/ticket/specs/filter.spec.js | 12 +- .../ticket/specs/priceDifference.spec.js | 2 +- modules/ticket/back/models/ticket-methods.js | 2 + modules/ticket/back/models/ticket.js | 2 - .../front/future-search-panel/index.html | 8 +- .../ticket/front/future-search-panel/index.js | 16 -- .../front/future-search-panel/locale/es.yml | 4 +- modules/ticket/front/future/index.html | 56 +++--- modules/ticket/front/future/index.js | 65 ++++--- modules/ticket/front/future/index.spec.js | 113 ++++++++++++ modules/ticket/front/future/locale/en.yml | 1 + modules/ticket/front/future/locale/es.yml | 3 +- modules/ticket/front/future/style.scss | 0 18 files changed, 486 insertions(+), 169 deletions(-) create mode 100644 modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js rename modules/ticket/back/methods/{ticket-future/moveTicketsFuture.js => ticket/merge.js} (89%) create mode 100644 modules/ticket/front/future/index.spec.js create mode 100644 modules/ticket/front/future/locale/en.yml delete mode 100644 modules/ticket/front/future/style.scss diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 5cadaf77c..2cd0c425e 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -686,7 +686,10 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF (24 ,NULL, 8, 1, 7, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 5, 5, 1, util.VN_CURDATE()), (25 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()), (26 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'An incredibly long alias for testing purposes', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()), - (27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()); + (27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()), + (28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()), + (29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()), + (30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()); INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`) VALUES @@ -984,7 +987,10 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric (30, 4, 18, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()), (31, 2, 23, 'Melee weapon combat fist 15cm', -5, 7.08, 0, 0, 0, util.VN_CURDATE()), (32, 1, 24, 'Ranged weapon longbow 2m', -1, 8.07, 0, 0, 0, util.VN_CURDATE()), - (33, 5, 14, 'Ranged weapon pistol 9mm', 50, 1.79, 0, 0, 0, util.VN_CURDATE()); + (33, 5, 14, 'Ranged weapon pistol 9mm', 50, 1.79, 0, 0, 0, util.VN_CURDATE()), + (34, 4, 28, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()), + (35, 4, 29, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()), + (36, 4, 30, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()); INSERT INTO `vn`.`saleChecked`(`saleFk`, `isChecked`) VALUES diff --git a/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js b/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js index 0682cef09..9fcbf028f 100644 --- a/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js +++ b/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js @@ -11,7 +11,7 @@ describe('SalesMonitor salesFilter()', () => { const filter = {order: 'id DESC'}; const result = await models.SalesMonitor.salesFilter(ctx, filter, options); - expect(result.length).toEqual(27); + expect(result.length).toEqual(30); await tx.rollback(); } catch (e) { @@ -39,7 +39,7 @@ describe('SalesMonitor salesFilter()', () => { const filter = {}; const result = await models.SalesMonitor.salesFilter(ctx, filter, options); - expect(result.length).toEqual(16); + expect(result.length).toEqual(19); await tx.rollback(); } catch (e) { @@ -87,7 +87,7 @@ describe('SalesMonitor salesFilter()', () => { const filter = {}; const result = await models.SalesMonitor.salesFilter(ctx, filter, options); - expect(result.length).toEqual(27); + expect(result.length).toEqual(30); await tx.rollback(); } catch (e) { @@ -130,7 +130,7 @@ describe('SalesMonitor salesFilter()', () => { const length = result.length; const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; - expect(length).toEqual(10); + expect(length).toEqual(13); expect(anyResult.state).toMatch(/(Libre|Arreglar)/); await tx.rollback(); @@ -171,7 +171,7 @@ describe('SalesMonitor salesFilter()', () => { const filter = {}; const result = await models.SalesMonitor.salesFilter(ctx, filter, options); - expect(result.length).toEqual(23); + expect(result.length).toEqual(26); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/methods/ticket-future/getTicketsFuture.js b/modules/ticket/back/methods/ticket-future/getTicketsFuture.js index 5b83e50be..0b21a9202 100644 --- a/modules/ticket/back/methods/ticket-future/getTicketsFuture.js +++ b/modules/ticket/back/methods/ticket-future/getTicketsFuture.js @@ -1,10 +1,11 @@ const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const buildFilter = require('vn-loopback/util/filter').buildFilter; const mergeFilters = require('vn-loopback/util/filter').mergeFilters; +const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('getTicketsFuture', { - description: 'Find all instances of the model matched by filter from the data source.', + description: 'Find all tickets that can be moved to the future', accessType: 'READ', accepts: [ { @@ -37,6 +38,60 @@ module.exports = Self => { description: 'Warehouse identifier', required: true }, + { + arg: 'shipped', + type: 'date', + description: 'Origin shipped', + required: false + }, + { + arg: 'tfShipped', + type: 'date', + description: 'Destination shipped', + required: false + }, + { + arg: 'ipt', + type: 'string', + description: 'Origin Item Packaging Type', + required: false + }, + { + arg: 'tfIpt', + type: 'string', + description: 'Destination Item Packaging Type', + required: false + }, + { + arg: 'id', + type: 'number', + description: 'Origin id', + required: false + }, + { + arg: 'tfId', + type: 'number', + description: 'Destination id', + required: false + }, + { + arg: 'state', + type: 'string', + description: 'Origin state', + required: false + }, + { + arg: 'tfState', + type: 'string', + description: 'Destination state', + required: false + }, + { + arg: 'problems', + type: 'boolean', + description: `Whether to show only tickets with problems`, + required: false + }, { arg: 'filter', type: 'object', @@ -53,11 +108,11 @@ module.exports = Self => { } }); - Self.getTicketsFuture = async (ctx, options) => { + Self.getTicketsFuture = async (ctx, filter, options) => { const myOptions = {}; if (typeof options == 'object') - Object.assign(myOptions, options); + Object.assign(myOptions, options); const conn = Self.dataSource.connector; const args = ctx.args; const stmts = []; @@ -71,10 +126,10 @@ module.exports = Self => { stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); stmt = new ParameterizedSQL(` - CREATE TEMPORARY TABLE tmp.sale_getProblems + CREATE TEMPORARY TABLE tmp.sale_getProblems (INDEX (ticketFk)) ENGINE = MEMORY - SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped + SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped FROM tmp.filter f LEFT JOIN alertLevel al ON al.id = f.alertLevel WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)`); @@ -87,80 +142,64 @@ module.exports = Self => { FROM tmp.filter f LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = f.id`); - if (args.problems != undefined && (!args.from && !args.to)) + if (args.problems != undefined && (!args.originDated && !args.futureDated)) throw new UserError('Choose a date range or days forward'); - const myWhere = buildFilter(ctx.args, (param, value) => { + const searchBarWhere = buildFilter(args, (param, value) => { switch (param) { - // case 'search': - // return /^\d+$/.test(value) - // ? {'t.id': {inq: value}} - // : {'t.nickname': {like: `%${value}%`}}; - case 'shipped': - return {'shipped': {like: `%${value}%`}}; - // case 'refFk': - // return {'t.refFk': value}; - - // case 'provinceFk': - // return {'t.provinceFk': value}; - // case 'stateFk': - // return {'t.stateFk': value}; - // case 'alertLevel': - // return {'t.alertLevel': value}; - // case 'pending': - // if (value) { - // return {and: [ - // {'t.alertLevel': 0}, - // {'t.alertLevelCode': {nin: [ - // 'OK', - // 'BOARDING', - // 'PRINTED', - // 'PRINTED_AUTO', - // 'PICKER_DESIGNED' - // ]}} - // ]}; - // } else { - // return {and: [ - // {'t.alertLevel': {gt: 0}} - // ]}; - // } - // case 'agencyModeFk': - // case 'warehouseFk': - // param = `t.${param}`; - // return {[param]: value}; + case 'id': + return { 'f.id': value }; + case 'tfId': + return { 'f.ticketFuture': value }; + case 'shipped': + return { 'f.shipped': value }; + case 'tfShipped': + return { 'f.tfShipped': value }; + case 'ipt': + return { 'f.ipt': value }; + case 'tfIpt': + return { 'f.tfIpt': value }; + case 'state': + return { 'f.state': { like: `%${value}%` } }; + case 'tfState': + return { 'f.tfState': { like: `%${value}%` } }; } }); - let finalFilter = {}; - finalFilter = mergeFilters(finalFilter, {where: myWhere}); - if (finalFilter.where) - stmt.merge(conn.makeWhere(finalFilter.where)); + + filter = mergeFilters(args.filter, { where: searchBarWhere }); + stmt.merge(conn.makeSuffix(filter)); let condition; let hasProblem; let range; let hasWhere; switch (args.problems) { - case true: - condition = `or`; - hasProblem = true; - range = {neq: null}; - hasWhere = true; - break; + case true: + condition = `or`; + hasProblem = true; + range = { neq: null }; + hasWhere = true; + break; - case false: - condition = `and`; - hasProblem = null; - range = null; - hasWhere = true; - break; + case false: + condition = `and`; + hasProblem = null; + range = null; + hasWhere = true; + break; } - const problems = {[condition]: [ - {'tp.isFreezed': hasProblem}, - {'tp.risk': hasProblem}, - {'tp.hasTicketRequest': hasProblem}, - {'tp.itemShortage': range} - ]}; + const problems = { + [condition]: [ + { 'tp.isFreezed': hasProblem }, + { 'tp.risk': hasProblem }, + { 'tp.hasTicketRequest': hasProblem }, + { 'tp.itemShortage': range }, + { 'tp.hasComponentLack': hasProblem }, + { 'tp.isTaxDataChecked': !hasProblem }, + { 'tp.isTooLittle': hasProblem } + ] + }; if (hasWhere) stmt.merge(conn.makeWhere(problems)); @@ -168,7 +207,7 @@ module.exports = Self => { const ticketsIndex = stmts.push(stmt) - 1; stmts.push( - `DROP TEMPORARY TABLE + `DROP TEMPORARY TABLE tmp.filter, tmp.ticket_problems`); @@ -176,4 +215,4 @@ module.exports = Self => { const result = await conn.executeStmt(sql, myOptions); return result[ticketsIndex]; }; -}; \ No newline at end of file +}; diff --git a/modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js b/modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js new file mode 100644 index 000000000..0bab7a98a --- /dev/null +++ b/modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js @@ -0,0 +1,165 @@ +const models = require('vn-loopback/server/server').models; + +fdescribe('TicketFuture getTicketsFuture()', () => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + it('should return the tickets passing the required data', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the problems on true', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + problems: true + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the problems on false', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + problems: false + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the problems on null', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + problems: null + }; + + const ctx = {req: {accessToken: {userId: 9}}, args}; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the correct origin shipped', async() => { + let tomorrow = new Date(today.getDate() + 1); + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + shipped: today + }; + + const ctx = {req: {accessToken: {userId: 9}}, args}; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + // try { + // const options = {transaction: tx}; + + // const args = { + // originDated: today, + // futureDated: today, + // litersMax: 9999, + // linesMax: 9999, + // warehouseFk: 1, + // shipped: tomorrow + // }; + + // const ctx = {req: {accessToken: {userId: 9}}, args}; + // const result = await models.Ticket.getTicketsFuture(ctx, options); + + // expect(result.length).toEqual(0); + + // await tx.rollback(); + // } catch (e) { + // await tx.rollback(); + // throw e; + // } + }); + + +}); diff --git a/modules/ticket/back/methods/ticket-future/moveTicketsFuture.js b/modules/ticket/back/methods/ticket/merge.js similarity index 89% rename from modules/ticket/back/methods/ticket-future/moveTicketsFuture.js rename to modules/ticket/back/methods/ticket/merge.js index 940a83c33..da1a150c8 100644 --- a/modules/ticket/back/methods/ticket-future/moveTicketsFuture.js +++ b/modules/ticket/back/methods/ticket/merge.js @@ -1,8 +1,8 @@ const LoopBackContext = require('loopback-context'); module.exports = Self => { - Self.remoteMethodCtx('moveTicketsFuture', { - description: 'Move specified tickets to the future', + Self.remoteMethodCtx('merge', { + description: 'Merge one ticket into another', accessType: 'WRITE', accepts: [ { @@ -17,12 +17,12 @@ module.exports = Self => { root: true }, http: { - path: `/moveTicketsFuture`, + path: `/merge`, verb: 'POST' } }); - Self.moveTicketsFuture = async (ctx, tickets, options) => { + Self.merge = async (ctx, tickets, options) => { const loopBackContext = LoopBackContext.getCurrentContext(); const httpCtx = {req: loopBackContext.active}; const models = Self.app.models; @@ -36,9 +36,8 @@ module.exports = Self => { tx = await Self.beginTransaction({}); myOptions.transaction = tx; } - + for (let ticket of tickets) { - console.log(ticket); try { if(!ticket.id || !ticket.ticketFuture) continue; await models.Sale.updateAll({ticketFk: ticket.id}, {ticketFk: ticket.ticketFuture}, myOptions); @@ -65,4 +64,4 @@ module.exports = Self => { } }; }; -}; \ No newline at end of file +}; diff --git a/modules/ticket/back/methods/ticket/specs/filter.spec.js b/modules/ticket/back/methods/ticket/specs/filter.spec.js index c3dc40092..e2ab43aea 100644 --- a/modules/ticket/back/methods/ticket/specs/filter.spec.js +++ b/modules/ticket/back/methods/ticket/specs/filter.spec.js @@ -11,7 +11,7 @@ describe('ticket filter()', () => { const filter = {order: 'id DESC'}; const result = await models.Ticket.filter(ctx, filter, options); - expect(result.length).toEqual(27); + expect(result.length).toEqual(30); await tx.rollback(); } catch (e) { @@ -87,7 +87,7 @@ describe('ticket filter()', () => { const filter = {}; const result = await models.Ticket.filter(ctx, filter, options); - expect(result.length).toEqual(27); + expect(result.length).toEqual(30); await tx.rollback(); } catch (e) { @@ -130,7 +130,7 @@ describe('ticket filter()', () => { const length = result.length; const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; - expect(length).toEqual(10); + expect(length).toEqual(13); expect(anyResult.state).toMatch(/(Libre|Arreglar)/); await tx.rollback(); @@ -175,7 +175,7 @@ describe('ticket filter()', () => { const filter = {}; const result = await models.Ticket.filter(ctx, filter, options); - expect(result.length).toEqual(23); + expect(result.length).toEqual(26); await tx.rollback(); } catch (e) { @@ -232,7 +232,7 @@ describe('ticket filter()', () => { const filter = {}; const result = await models.Ticket.filter(ctx, filter, options); - expect(result.length).toEqual(22); + expect(result.length).toEqual(25); await tx.rollback(); } catch (e) { @@ -270,7 +270,7 @@ describe('ticket filter()', () => { const filter = {}; const result = await models.Ticket.filter(ctx, filter, options); - expect(result.length).toEqual(27); + expect(result.length).toEqual(30); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js index d8c785baa..96d29c46f 100644 --- a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js +++ b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js @@ -87,7 +87,7 @@ describe('sale priceDifference()', () => { const secondtItem = result.items[1]; expect(firstItem.movable).toEqual(410); - expect(secondtItem.movable).toEqual(1870); + expect(secondtItem.movable).toEqual(1810); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index 9255e52e6..92036d77f 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -33,4 +33,6 @@ module.exports = function(Self) { require('../methods/ticket/closeByTicket')(Self); require('../methods/ticket/closeByAgency')(Self); require('../methods/ticket/closeByRoute')(Self); + require('../methods/ticket-future/getTicketsFuture')(Self); + require('../methods/ticket/merge')(Self); }; diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index 649a83160..c05130552 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -4,8 +4,6 @@ const LoopBackContext = require('loopback-context'); module.exports = Self => { // Methods require('./ticket-methods')(Self); - require('../methods/ticket-future/getTicketsFuture')(Self); - require('../methods/ticket-future/moveTicketsFuture')(Self); Self.observe('before save', async function(ctx) { const loopBackContext = LoopBackContext.getCurrentContext(); diff --git a/modules/ticket/front/future-search-panel/index.html b/modules/ticket/front/future-search-panel/index.html index 4f5486d62..d69b1ba9e 100644 --- a/modules/ticket/front/future-search-panel/index.html +++ b/modules/ticket/front/future-search-panel/index.html @@ -44,13 +44,13 @@ + label="Origin IPT" + ng-model="filter.ipt"> + label="Destination IPT" + ng-model="filter.tfIpt"> diff --git a/modules/ticket/front/future-search-panel/index.js b/modules/ticket/front/future-search-panel/index.js index bbc2148e7..c747af62c 100644 --- a/modules/ticket/front/future-search-panel/index.js +++ b/modules/ticket/front/future-search-panel/index.js @@ -6,22 +6,6 @@ class Controller extends SearchPanel { super($, $element); this.filter = this.$.filter; } - - get from() { - return this._from; - } - - set from(value) { - this._from = value; - } - - get to() { - return this._to; - } - - set to(value) { - this._to = value; - } } ngModule.vnComponent('vnFutureTicketSearchPanel', { diff --git a/modules/ticket/front/future-search-panel/locale/es.yml b/modules/ticket/front/future-search-panel/locale/es.yml index e459c3cd9..413f9e5f9 100644 --- a/modules/ticket/front/future-search-panel/locale/es.yml +++ b/modules/ticket/front/future-search-panel/locale/es.yml @@ -5,8 +5,8 @@ Origin ETD: ETD origen Destination ETD: ETD destino Max Lines Origin: Líneas máx. origen Max Liters Origin: Litros máx. origen -Origin ITP: ITP origen -Destination ITP: ITP destino +Origin IPT: IPT origen +Destination IPT: IPT destino With problems: Con problemas Warehouse: Almacén Origin Agrupated State: Estado agrupado origen diff --git a/modules/ticket/front/future/index.html b/modules/ticket/front/future/index.html index 9dbb5a4ca..021fc1390 100644 --- a/modules/ticket/front/future/index.html +++ b/modules/ticket/front/future/index.html @@ -12,13 +12,13 @@ info="Search future tickets by date" auto-state="false" model="model" - filter="$ctrl.defaultFilter"> + filter="::$ctrl.filter"> - Origin ID - + Origin ETD @@ -62,7 +62,7 @@ Destination ID - + Destination ETD @@ -76,7 +76,7 @@ - @@ -119,33 +119,43 @@ icon="icon-components"> - {{::ticket.id}} - {{::ticket.originETD | date: 'dd/MM/yyyy'}} + + {{::ticket.originETD | date: 'dd/MM/yyyy'}} + - + - {{::ticket.state}} - + {{::ticket.state}} + + {{::ticket.ipt}} {{::ticket.liters}} {{::ticket.lines}} - + - {{::ticket.ticketFuture}} - - - {{::ticket.destETD | date: 'dd/MM/yyyy'}} + {{::ticket.ticketFuture}} + - + + {{::ticket.destETD | date: 'dd/MM/yyyy'}} + + + + - {{::ticket.tfState}} - + {{::ticket.tfState}} + + {{::ticket.tfIpt}} @@ -156,9 +166,9 @@ - - \ No newline at end of file + diff --git a/modules/ticket/front/future/index.js b/modules/ticket/front/future/index.js index 6dda5812d..479d4b000 100644 --- a/modules/ticket/front/future/index.js +++ b/modules/ticket/front/future/index.js @@ -25,40 +25,20 @@ export default class Controller extends Section { search: true }, columns: [{ - field:'problems', + field: 'problems', searchable: false }, { - field:'ETD', + field: 'originETD', searchable: false }, { - field:'tfETD', + field: 'destETD', searchable: false }] }; } - get checked() { - const tickets = this.$.model.data || []; - const checkedLines = []; - for (let ticket of tickets) { - if (ticket.checked) - checkedLines.push(ticket); - } - - return checkedLines; - } - - moveTicketsFuture() - { - let params = { - tickets: this.checked - }; - this.$http.post('Tickets/moveTicketsFuture', params); - this.reload(); - } - compareDate(date) { let today = new Date(); today.setHours(0, 0, 0, 0); @@ -73,20 +53,24 @@ export default class Controller extends Section { return 'success'; } + get checked() { + const tickets = this.$.model.data || []; + const checkedLines = []; + for (let ticket of tickets) { + if (ticket.checked) + checkedLines.push(ticket); + } + + return checkedLines; + } + stateColor(state) { if (state === 'OK') return 'success'; else if (state === 'FREE') return 'notice'; } - - exprBuilder(param, value) { - switch (param) { - case 'shipped': - return {'shipped': {like: `%${value}%`}}; - } - } - + dateRange(value) { const minHour = new Date(value); minHour.setHours(0, 0, 0, 0); @@ -96,11 +80,26 @@ export default class Controller extends Section { return [minHour, maxHour]; } - reload() { - this.$.model.refresh(); + get confirmationMessage() { + if (!this.$.model) return 0; + + return this.$t(`Move confirmation`, { + checked: this.checked.length + }); + } + + moveTicketsFuture() { + let params = { tickets: this.checked }; + return this.$http.post('Tickets/merge', params) + .then(() => { + this.$.model.refresh(); + this.vnApp.showSuccess(this.$t('Success')); + }); } } +Controller.$inject = ['$element', '$scope']; + ngModule.vnComponent('vnTicketFuture', { template: require('./index.html'), controller: Controller diff --git a/modules/ticket/front/future/index.spec.js b/modules/ticket/front/future/index.spec.js new file mode 100644 index 000000000..003b277ee --- /dev/null +++ b/modules/ticket/front/future/index.spec.js @@ -0,0 +1,113 @@ +import './index.js'; +import crudModel from 'core/mocks/crud-model'; + +describe('Component vnTicketFuture', () => { + let controller; + let $httpBackend; + let $window; + + beforeEach(ngModule('ticket') + ); + + beforeEach(inject(($componentController, _$window_, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + $window = _$window_; + const $element = angular.element(''); + controller = $componentController('vnTicketFuture', { $element }); + controller.$.model = crudModel; + controller.$.model.data = [{ + id: 1, + checked: true, + state: "OK" + }, { + id: 2, + checked: true, + state: "FREE" + }]; + })); + + describe('compareDate()', () => { + it('should return warning when the date is the present', () => { + let today = new Date(); + let result = controller.compareDate(today); + + expect(result).toEqual('warning'); + }); + + it('should return sucess when the date is in the future', () => { + let futureDate = new Date(); + futureDate = futureDate.setDate(futureDate.getDate() + 10); + let result = controller.compareDate(futureDate); + + expect(result).toEqual('success'); + }); + + it('should return undefined when the date is in the past', () => { + let pastDate = new Date(); + pastDate = pastDate.setDate(pastDate.getDate() - 10); + let result = controller.compareDate(pastDate); + + expect(result).toEqual(undefined); + }); + }); + + describe('checked()', () => { + it('should return an array of checked tickets', () => { + const result = controller.checked; + const firstRow = result[0]; + const secondRow = result[1]; + + expect(result.length).toEqual(2); + expect(firstRow.id).toEqual(1); + expect(secondRow.id).toEqual(2); + }); + }); + + describe('stateColor()', () => { + it('should return success to the OK tickets', () => { + const ok = controller.stateColor(controller.$.model.data[0].state); + const notOk = controller.stateColor(controller.$.model.data[1].state); + expect(ok).toEqual('success'); + expect(notOk).not.toEqual('success'); + }); + + it('should return success to the FREE tickets', () => { + const notFree = controller.stateColor(controller.$.model.data[0].state); + const free = controller.stateColor(controller.$.model.data[1].state); + expect(free).toEqual('notice'); + expect(notFree).not.toEqual('notice'); + }); + }); + + describe('dateRange()', () => { + it('should return two dates with the hours at the start and end of the given date', () => { + const now = new Date(); + + const today = now.getDate(); + + const dateRange = controller.dateRange(now); + const start = dateRange[0].toString(); + const end = dateRange[1].toString(); + + expect(start).toContain(today); + expect(start).toContain('00:00:00'); + + expect(end).toContain(today); + expect(end).toContain('23:59:59'); + }); + }); + + describe('moveTicketsFuture()', () => { + it('should make an HTTP Post query', () => { + jest.spyOn(controller.$.model, 'refresh'); + jest.spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.expectPOST(`Tickets/merge`).respond(); + controller.moveTicketsFuture(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + }); + }); +}); diff --git a/modules/ticket/front/future/locale/en.yml b/modules/ticket/front/future/locale/en.yml new file mode 100644 index 000000000..7b0a377b8 --- /dev/null +++ b/modules/ticket/front/future/locale/en.yml @@ -0,0 +1 @@ +Move confirmation: Do you want to move {{checked}} tickets to the future? diff --git a/modules/ticket/front/future/locale/es.yml b/modules/ticket/front/future/locale/es.yml index 946611e66..937f206e9 100644 --- a/modules/ticket/front/future/locale/es.yml +++ b/modules/ticket/front/future/locale/es.yml @@ -12,4 +12,5 @@ Destination ID: ID destino Destination ETD: ETD Destino Origin ETD: ETD Origen Move tickets: Mover tickets -Move confirmation: ¿Desea mover {0} tickets hacia el futuro? +Move confirmation: ¿Desea mover {{checked}} tickets hacia el futuro? +Success: Tickets movidos correctamente diff --git a/modules/ticket/front/future/style.scss b/modules/ticket/front/future/style.scss deleted file mode 100644 index e69de29bb..000000000 From 117e2c03f517041e992087de85b4385488a3b271 Mon Sep 17 00:00:00 2001 From: alexandre Date: Thu, 10 Nov 2022 15:54:00 +0100 Subject: [PATCH 3/6] refs #4700 tests back y empezando e2e --- .../10501-november/00-ticket_canMerge.sql | 2 +- .../00-ticket_canbePostposed.sql | 25 +- e2e/helpers/selectors.js | 21 ++ e2e/paths/05-ticket/20_future.spec.js | 126 +++++++ .../methods/ticket-future/getTicketsFuture.js | 84 ++--- .../spec/getTicketsFuture.spec.js | 335 ++++++++++++++++-- modules/ticket/back/methods/ticket/merge.js | 28 +- .../back/methods/ticket/specs/merge.spec.js | 58 +++ .../front/future-search-panel/index.html | 69 ++-- .../ticket/front/future-search-panel/index.js | 30 ++ .../front/future-search-panel/locale/en.yml | 10 +- .../front/future-search-panel/locale/es.yml | 18 +- modules/ticket/front/future/index.html | 9 +- modules/ticket/front/future/index.js | 62 +++- modules/ticket/front/future/locale/en.yml | 4 + modules/ticket/front/future/locale/es.yml | 6 + 16 files changed, 739 insertions(+), 148 deletions(-) create mode 100644 e2e/paths/05-ticket/20_future.spec.js create mode 100644 modules/ticket/back/methods/ticket/specs/merge.spec.js diff --git a/db/changes/10501-november/00-ticket_canMerge.sql b/db/changes/10501-november/00-ticket_canMerge.sql index 6db3637ac..7e01f2fab 100644 --- a/db/changes/10501-november/00-ticket_canMerge.sql +++ b/db/changes/10501-november/00-ticket_canMerge.sql @@ -4,6 +4,6 @@ DELIMITER $$ $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canMerge`(vDated DATE, vScopeDays INT, vLitersMax INT, vLinesMax INT, vWarehouseFk INT) BEGIN - CALL vn.ticket_canbePostponed(vDated,TIMESTAMPADD(DAY, vScopeDays, vDated),vLitersMax,vLinesMax,vWarehouseFk); + CALL vn.ticket_canbePostponed(vDated,TIMESTAMPADD(DAY, vScopeDays, vDated),vLitersMax,vLinesMax,vWarehouseFk); END $$ DELIMITER ; diff --git a/db/changes/10501-november/00-ticket_canbePostposed.sql b/db/changes/10501-november/00-ticket_canbePostposed.sql index 6ad9df154..113b09324 100644 --- a/db/changes/10501-november/00-ticket_canbePostposed.sql +++ b/db/changes/10501-november/00-ticket_canbePostposed.sql @@ -3,7 +3,7 @@ DROP PROCEDURE IF EXISTS vn.ticket_canbePostponed; DELIMITER $$ $$ CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canbePostponed`(vOriginDated DATE, vFutureDated DATE, vLitersMax INT, vLinesMax INT, vWarehouseFk INT) -BEGIN +BEGIN /** * Devuelve un listado de tickets susceptibles de fusionarse con otros tickets en el futuro * @@ -13,25 +13,27 @@ BEGIN * @param vLinesMax Número máximo de lineas de los tickets a catapultar * @param vWarehouseFk Identificador de vn.warehouse */ - DROP TEMPORARY TABLE IF EXISTS tmp.filter; - CREATE TEMPORARY TABLE tmp.filter + DROP TEMPORARY TABLE IF EXISTS tmp.filter; + CREATE TEMPORARY TABLE tmp.filter (INDEX (id)) SELECT sv.ticketFk id, GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt, CAST(sum(litros) AS DECIMAL(10,0)) liters, - CAST(count(*) AS DECIMAL(10,0)) `lines`, - st.name state, - sub2.id ticketFuture, + CAST(count(*) AS DECIMAL(10,0)) `lines`, + st.name state, + sub2.id ticketFuture, t.landed originETD, sub2.landed destETD, sub2.iptd tfIpt, sub2.state tfState, t.clientFk, - t.warehouseFk, - ts.alertLevel, + t.warehouseFk, + ts.alertLevel, t.shipped, sub2.shipped tfShipped, - t.workerFk + t.workerFk, + st.code code, + sub2.code tfCode FROM vn.saleVolume sv JOIN vn.sale s ON s.id = sv.saleFk JOIN vn.item i ON i.id = s.itemFk @@ -39,7 +41,7 @@ BEGIN JOIN vn.address a ON a.id = t.addressFk JOIN vn.province p ON p.id = a.provinceFk JOIN vn.country c ON c.id = p.countryFk - JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.ticketState ts ON ts.ticketFk = t.id JOIN vn.state st ON st.id = ts.stateFk JOIN vn.alertLevel al ON al.id = ts.alertLevel LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id @@ -52,6 +54,7 @@ BEGIN t.landed, t.shipped, st.name state, + st.code code, GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) iptd FROM vn.ticket t JOIN vn.ticketState ts ON ts.ticketFk = t.id @@ -67,7 +70,7 @@ BEGIN ) sub2 ON sub2.addressFk = t.addressFk AND t.id != sub2.id WHERE t.shipped BETWEEN vOriginDated AND util.dayend(vOriginDated) AND t.warehouseFk = vWarehouseFk - AND al.code = 'FREE' + AND al.code = 'FREE' AND tp.ticketFk IS NULL GROUP BY sv.ticketFk HAVING liters <= IFNULL(vLitersMax, 9999) AND `lines` <= IFNULL(vLinesMax, 9999) AND ticketFuture; diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index cd6d39795..749e243d0 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -712,6 +712,27 @@ export default { saveImport: 'button[response="accept"]', anyDocument: 'vn-ticket-dms-index > vn-data-viewer vn-tbody vn-tr' }, + ticketFuture: { + openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]', + originDated: 'vn-date-picker[label="Origin ETD"]', + futureDated: 'vn-date-picker[label="Destination ETD"]', + shipped: 'vn-date-picker[label="Origin date"]', + tfShipped: 'vn-date-picker[label="Destination date"]', + linesMax: 'vn-textfield[label="Max Lines"]', + litersMax: 'vn-textfield[label="Max Liters"]', + ipt: 'vn-autocomplete[label="Origin IPT"]', + tfIpt: 'vn-autocomplete[label="Destination IPT"]', + state: 'vn-autocomplete[label="Origin Grouped State"]', + tfState: 'vn-autocomplete[label="Destination Grouped State"]', + warehouseFk: 'vn-autocomplete[label="Warehouse"]', + problems: 'vn-check[label="With problems"]', + tableButtonSearch: 'vn-button[vn-tooltip="Search"]', + moveButton: 'vn-button[vn-tooltip="Future tickets"]', + tableId: 'vn-textfield[ng-model="searchProps[\'id\']"]', + tableTfId: 'vn-textfield[ng-model="searchProps[\'ticketFuture\']"]', + submit: 'vn-submit[label="Search"]', + table: 'tbody > tr:not(.empty-rows, #searchRow)', + }, createStateView: { state: 'vn-autocomplete[ng-model="$ctrl.stateFk"]', worker: 'vn-autocomplete[ng-model="$ctrl.workerFk"]', diff --git a/e2e/paths/05-ticket/20_future.spec.js b/e2e/paths/05-ticket/20_future.spec.js new file mode 100644 index 000000000..c9c4559a7 --- /dev/null +++ b/e2e/paths/05-ticket/20_future.spec.js @@ -0,0 +1,126 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Ticket Future path', () => { + let browser; + let page; + + beforeAll(async () => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('employee', 'ticket'); + await page.accessToSection('ticket.future'); + }); + + afterAll(async () => { + await browser.close(); + }); + + const now = new Date(); + const tomorrow = new Date(now.getDate() + 1); + it('should search with the required data', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.originDated, now); + await page.pickDate(selectors.ticketFuture.futureDated, now); + await page.waitToClick(selectors.ticketFuture.linesMax); + await page.write(selectors.ticketFuture.linesMax, '9999'); + await page.write(selectors.ticketFuture.litersMax, '9999'); + await page.autocompleteSearch(selectors.ticketFuture.warehouseFk, 'Warehouse One'); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should search with the origin shipped today', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.shipped, now); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should search with the origin shipped tomorrow', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.shipped, tomorrow); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + }); + + it('should search with the destination shipped today', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.clearInput(selectors.ticketFuture.shipped); + await page.pickDate(selectors.ticketFuture.tfShipped, now); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should search with the destination shipped tomorrow', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.tfShipped, tomorrow); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + }); + + it('should search with the origin IPT', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.clearInput(selectors.ticketFuture.tfShipped); + await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal'); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + }); + + it('should search with the destination IPT', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.clearInput(selectors.ticketFuture.ipt); + await page.autocompleteSearch(selectors.ticketFuture.tfIpt, 'Horizontal'); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + }); + + it('should search with the origin grouped state', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.clearInput(selectors.ticketFuture.tfIpt); + await page.autocompleteSearch(selectors.ticketFuture.state, 'Free'); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 3); + }); + + it('should search with the destination grouped state', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.clearInput(selectors.ticketFuture.state); + await page.autocompleteSearch(selectors.ticketFuture.tfState, 'Free'); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + }); + + it('should search with problems selected', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.clearInput(selectors.ticketFuture.tfState); + await page.waitToClick(selectors.ticketFuture.problems); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should search with no problems selected', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.problems); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + }); + + it('should search with an ID Origin', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.problems); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.write(selectors.ticketFuture.tableId, "13"); + await page.keyboard.press("Enter"); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + }); + it('should search with an ID Destination', async () => { + await page.clearInput(selectors.ticketFuture.tableId); + await page.write(selectors.ticketFuture.tableTfId, "12"); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + }); + + + // const message = await page.waitForSnackbar(); +}); diff --git a/modules/ticket/back/methods/ticket-future/getTicketsFuture.js b/modules/ticket/back/methods/ticket-future/getTicketsFuture.js index 0b21a9202..132df064f 100644 --- a/modules/ticket/back/methods/ticket-future/getTicketsFuture.js +++ b/modules/ticket/back/methods/ticket-future/getTicketsFuture.js @@ -108,44 +108,15 @@ module.exports = Self => { } }); - Self.getTicketsFuture = async (ctx, filter, options) => { + Self.getTicketsFuture = async (ctx, options) => { + const args = ctx.args; + const conn = Self.dataSource.connector; const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); - const conn = Self.dataSource.connector; - const args = ctx.args; - const stmts = []; - let stmt; - stmt = new ParameterizedSQL( - `CALL vn.ticket_canbePostponed(?,?,?,?,?)`, - [args.originDated, args.futureDated, args.litersMax, args.linesMax, args.warehouseFk]); - - stmts.push(stmt); - stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); - - stmt = new ParameterizedSQL(` - CREATE TEMPORARY TABLE tmp.sale_getProblems - (INDEX (ticketFk)) - ENGINE = MEMORY - SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped - FROM tmp.filter f - LEFT JOIN alertLevel al ON al.id = f.alertLevel - WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)`); - stmts.push(stmt); - - stmts.push('CALL ticket_getProblems(FALSE)'); - - stmt = new ParameterizedSQL(` - SELECT f.*, tp.* - FROM tmp.filter f - LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = f.id`); - - if (args.problems != undefined && (!args.originDated && !args.futureDated)) - throw new UserError('Choose a date range or days forward'); - - const searchBarWhere = buildFilter(args, (param, value) => { + const where = buildFilter(ctx.args, (param, value) => { switch (param) { case 'id': return { 'f.id': value }; @@ -160,14 +131,43 @@ module.exports = Self => { case 'tfIpt': return { 'f.tfIpt': value }; case 'state': - return { 'f.state': { like: `%${value}%` } }; + return { 'f.code': { like: `%${value}%` } }; case 'tfState': - return { 'f.tfState': { like: `%${value}%` } }; + return { 'f.tfCode': { like: `%${value}%` } }; } }); - filter = mergeFilters(args.filter, { where: searchBarWhere }); - stmt.merge(conn.makeSuffix(filter)); + let filter = mergeFilters(ctx.args.filter, { where }); + const stmts = []; + let stmt; + + stmt = new ParameterizedSQL( + `CALL vn.ticket_canbePostponed(?,?,?,?,?)`, + [args.originDated, args.futureDated, args.litersMax, args.linesMax, args.warehouseFk]); + + stmts.push(stmt); + + stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); + + stmt = new ParameterizedSQL(` + CREATE TEMPORARY TABLE tmp.sale_getProblems + (INDEX (ticketFk)) + ENGINE = MEMORY + SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped + FROM tmp.filter f + LEFT JOIN alertLevel al ON al.id = f.alertLevel + WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)`); + stmts.push(stmt); + + stmts.push('CALL ticket_getProblems(FALSE)'); + + stmt = new ParameterizedSQL(` + SELECT f.*, tp.* + FROM tmp.filter f + LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = f.id`); + + if (args.problems != undefined && (!args.originDated && !args.futureDated)) + throw new UserError('Choose a date range or days forward'); let condition; let hasProblem; @@ -196,13 +196,15 @@ module.exports = Self => { { 'tp.hasTicketRequest': hasProblem }, { 'tp.itemShortage': range }, { 'tp.hasComponentLack': hasProblem }, - { 'tp.isTaxDataChecked': !hasProblem }, { 'tp.isTooLittle': hasProblem } ] }; - if (hasWhere) - stmt.merge(conn.makeWhere(problems)); + if (hasWhere) { + filter = mergeFilters(filter, { where: problems }); + } + stmt.merge(conn.makeWhere(filter.where)); + const ticketsIndex = stmts.push(stmt) - 1; @@ -212,7 +214,9 @@ module.exports = Self => { tmp.ticket_problems`); const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + return result[ticketsIndex]; }; }; diff --git a/modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js b/modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js index 0bab7a98a..502ea3074 100644 --- a/modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js +++ b/modules/ticket/back/methods/ticket-future/spec/getTicketsFuture.spec.js @@ -1,8 +1,9 @@ const models = require('vn-loopback/server/server').models; -fdescribe('TicketFuture getTicketsFuture()', () => { +describe('TicketFuture getTicketsFuture()', () => { const today = new Date(); today.setHours(0, 0, 0, 0); + const tomorrow = new Date(today.getDate() + 1); it('should return the tickets passing the required data', async () => { const tx = await models.Ticket.beginTransaction({}); @@ -83,11 +84,11 @@ fdescribe('TicketFuture getTicketsFuture()', () => { } }); - it('should return the tickets matching the problems on null', async() => { + it('should return the tickets matching the problems on null', async () => { const tx = await models.Ticket.beginTransaction({}); try { - const options = {transaction: tx}; + const options = { transaction: tx }; const args = { originDated: today, @@ -98,7 +99,7 @@ fdescribe('TicketFuture getTicketsFuture()', () => { problems: null }; - const ctx = {req: {accessToken: {userId: 9}}, args}; + const ctx = { req: { accessToken: { userId: 9 } }, args }; const result = await models.Ticket.getTicketsFuture(ctx, options); expect(result.length).toEqual(4); @@ -110,12 +111,11 @@ fdescribe('TicketFuture getTicketsFuture()', () => { } }); - it('should return the tickets matching the correct origin shipped', async() => { - let tomorrow = new Date(today.getDate() + 1); + it('should return the tickets matching the correct origin shipped', async () => { const tx = await models.Ticket.beginTransaction({}); try { - const options = {transaction: tx}; + const options = { transaction: tx }; const args = { originDated: today, @@ -126,7 +126,7 @@ fdescribe('TicketFuture getTicketsFuture()', () => { shipped: today }; - const ctx = {req: {accessToken: {userId: 9}}, args}; + const ctx = { req: { accessToken: { userId: 9 } }, args }; const result = await models.Ticket.getTicketsFuture(ctx, options); expect(result.length).toEqual(4); @@ -136,30 +136,303 @@ fdescribe('TicketFuture getTicketsFuture()', () => { await tx.rollback(); throw e; } - - // try { - // const options = {transaction: tx}; - - // const args = { - // originDated: today, - // futureDated: today, - // litersMax: 9999, - // linesMax: 9999, - // warehouseFk: 1, - // shipped: tomorrow - // }; - - // const ctx = {req: {accessToken: {userId: 9}}, args}; - // const result = await models.Ticket.getTicketsFuture(ctx, options); - - // expect(result.length).toEqual(0); - - // await tx.rollback(); - // } catch (e) { - // await tx.rollback(); - // throw e; - // } }); + it('should return the tickets matching the an incorrect origin shipped', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + shipped: tomorrow + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the correct destination shipped', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + tfShipped: today + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the an incorrect destination shipped', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + tfShipped: tomorrow + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the OK State in origin date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + state: "OK" + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the OK State in destination date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + tfState: "OK" + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the correct IPT in origin date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + ipt: null + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the incorrect IPT in origin date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + ipt: 0 + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the correct IPT in destination date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + tfIpt: null + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the incorrect IPT in destination date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + tfIpt: 0 + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the ID in origin date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + id: 13 + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the tickets matching the ID in destination date', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + + const args = { + originDated: today, + futureDated: today, + litersMax: 9999, + linesMax: 9999, + warehouseFk: 1, + tfId: 12 + }; + + const ctx = { req: { accessToken: { userId: 9 } }, args }; + const result = await models.Ticket.getTicketsFuture(ctx, options); + + expect(result.length).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); diff --git a/modules/ticket/back/methods/ticket/merge.js b/modules/ticket/back/methods/ticket/merge.js index da1a150c8..356259a26 100644 --- a/modules/ticket/back/methods/ticket/merge.js +++ b/modules/ticket/back/methods/ticket/merge.js @@ -1,5 +1,3 @@ -const LoopBackContext = require('loopback-context'); - module.exports = Self => { Self.remoteMethodCtx('merge', { description: 'Merge one ticket into another', @@ -23,8 +21,9 @@ module.exports = Self => { }); Self.merge = async (ctx, tickets, options) => { - const loopBackContext = LoopBackContext.getCurrentContext(); - const httpCtx = {req: loopBackContext.active}; + const httpRequest = ctx.req; + const $t = httpRequest.__; + const origin = httpRequest.headers.origin; const models = Self.app.models; const myOptions = {}; let tx; @@ -39,24 +38,21 @@ module.exports = Self => { for (let ticket of tickets) { try { + const fullPath = `${origin}/#!/ticket/${ticket.ticketFuture}/summary`; + const message = $t('MOVE_TICKET_CONFIRMATION', { + originDated: new Date(ticket.originETD).toLocaleDateString('es-ES'), + futureDated: new Date(ticket.destETD).toLocaleDateString('es-ES'), + id: ticket.id, + tfId: ticket.ticketFuture, + fullPath + }); if(!ticket.id || !ticket.ticketFuture) continue; await models.Sale.updateAll({ticketFk: ticket.id}, {ticketFk: ticket.ticketFuture}, myOptions); await models.Ticket.setDeleted(ctx, ticket.id, myOptions); + await models.Chat.sendCheckingPresence(ctx, ticket.workerFk, message); if (tx) { - const httpRequest = httpCtx.req.http.req; - const $t = httpRequest.__; - const origin = httpRequest.headers.origin; - const fullPath = `${origin}/#!/ticket/${ticket.ticketFuture}/summary`; - const message = $t('MOVE_TICKET_CONFIRMATION', { - originDated: new Date(ticket.originETD).toLocaleDateString('es-ES'), - futureDated: new Date(ticket.destETD).toLocaleDateString('es-ES'), - id: ticket.id, - tfId: ticket.ticketFuture, - fullPath - }); await tx.commit(); - await models.Chat.sendCheckingPresence(httpCtx, ticket.workerFk, message); } } catch (e) { if (tx) await tx.rollback(); diff --git a/modules/ticket/back/methods/ticket/specs/merge.spec.js b/modules/ticket/back/methods/ticket/specs/merge.spec.js new file mode 100644 index 000000000..713f86ad6 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/merge.spec.js @@ -0,0 +1,58 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('ticket merge()', () => { + const tickets = [{ + id: 13, + ticketFuture: 12, + workerFk: 1, + originETD: new Date(), + destETD: new Date() + }]; + + const activeCtx = { + accessToken: { userId: 9 }, + }; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + const ctx = { + req: { + accessToken: { userId: 9 }, + headers: { origin: 'http://localhost:5000' }, + } + }; + ctx.req.__ = value => { + return value; + }; + + it('should merge two tickets', async () => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = { transaction: tx }; + const chatNotificationBeforeMerge = await models.Chat.find(); + + await models.Ticket.merge(ctx, tickets, options); + + const createdTicketLog = await models.TicketLog.find({ where: { originFk: tickets[0].id } }, options); + const deletedTicket = await models.Ticket.findOne({ where: { id: tickets[0].id } }, options); + const salesTicketFuture = await models.Sale.find({ where: { ticketFk: tickets[0].ticketFuture } }, options); + const chatNotificationAfterMerge = await models.Chat.find(); + + expect(createdTicketLog.length).toEqual(1); + expect(deletedTicket.isDeleted).toEqual(true); + expect(salesTicketFuture.length).toEqual(2); + expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/front/future-search-panel/index.html b/modules/ticket/front/future-search-panel/index.html index d69b1ba9e..1b3ae453e 100644 --- a/modules/ticket/front/future-search-panel/index.html +++ b/modules/ticket/front/future-search-panel/index.html @@ -18,52 +18,76 @@ vn-one label="Origin ETD" ng-model="filter.originDated" - required="true"> + required="true" + info="ETD"> + required="true" + info="ETD"> - - - + + {{name}} + + + - + value-field="code" + show-field="name" + ng-model="filter.tfIpt" + info="IPT"> + + {{name}} + + - - - + {{name}} + + + - + + {{name}} + + - - + diff --git a/modules/ticket/front/future-search-panel/index.js b/modules/ticket/front/future-search-panel/index.js index c747af62c..1a1f0e4c5 100644 --- a/modules/ticket/front/future-search-panel/index.js +++ b/modules/ticket/front/future-search-panel/index.js @@ -5,6 +5,36 @@ class Controller extends SearchPanel { constructor($, $element) { super($, $element); this.filter = this.$.filter; + this.getGroupedStates(); + this.getItemPackingTypes(); + } + + getGroupedStates() { + let groupedStates = []; + this.$http.get('AlertLevels').then(res => { + for (let state of res.data) { + groupedStates.push({ + id: state.id, + code: state.code, + name: this.$t(state.code) + }); + } + this.groupedStates = groupedStates; + }); + } + + getItemPackingTypes() { + let itemPackingTypes = []; + this.$http.get('ItemPackingTypes').then(res => { + for (let ipt of res.data) { + itemPackingTypes.push({ + id: ipt.id, + code: ipt.code, + name: this.$t(ipt.code) + }); + } + this.itemPackingTypes = itemPackingTypes; + }); } } diff --git a/modules/ticket/front/future-search-panel/locale/en.yml b/modules/ticket/front/future-search-panel/locale/en.yml index 0440752c5..fe71865cb 100644 --- a/modules/ticket/front/future-search-panel/locale/en.yml +++ b/modules/ticket/front/future-search-panel/locale/en.yml @@ -1 +1,9 @@ -Future tickets: Tickets a futuro \ No newline at end of file +Future tickets: Tickets a futuro +FREE: Free +DELIVERED: Delivered +ON_PREPARATION: On preparation +PACKED: Packed +F: Fruits and vegetables +V: Vertical +H: Horizontal +P: Feed diff --git a/modules/ticket/front/future-search-panel/locale/es.yml b/modules/ticket/front/future-search-panel/locale/es.yml index 413f9e5f9..82deba538 100644 --- a/modules/ticket/front/future-search-panel/locale/es.yml +++ b/modules/ticket/front/future-search-panel/locale/es.yml @@ -3,11 +3,21 @@ Origin date: Fecha origen Destination date: Fecha destino Origin ETD: ETD origen Destination ETD: ETD destino -Max Lines Origin: Líneas máx. origen -Max Liters Origin: Litros máx. origen +Max Lines: Líneas máx. +Max Liters: Litros máx. Origin IPT: IPT origen Destination IPT: IPT destino With problems: Con problemas Warehouse: Almacén -Origin Agrupated State: Estado agrupado origen -Destination Agrupated State: Estado agrupado destino +Origin Grouped State: Estado agrupado origen +Destination Grouped State: Estado agrupado destino +FREE: Libre +DELIVERED: Servido +ON_PREPARATION: En preparacion +PACKED: Encajado +F: Frutas y verduras +V: Vertical +H: Horizontal +P: Pienso +ETD: Tiempo estimado de entrega +IPT: Encajado diff --git a/modules/ticket/front/future/index.html b/modules/ticket/front/future/index.html index 021fc1390..31d4ae0d9 100644 --- a/modules/ticket/front/future/index.html +++ b/modules/ticket/front/future/index.html @@ -1,7 +1,6 @@ @@ -11,14 +10,14 @@ placeholder="Search tickets" info="Search future tickets by date" auto-state="false" - model="model" - filter="::$ctrl.filter"> + model="model"> IPT - + Liters - + Available Lines diff --git a/modules/ticket/front/future/index.js b/modules/ticket/front/future/index.js index 479d4b000..7c9b6122e 100644 --- a/modules/ticket/front/future/index.js +++ b/modules/ticket/front/future/index.js @@ -6,23 +6,9 @@ export default class Controller extends Section { super($element, $); this.$checkAll = false; - const originDated = new Date(); - const futureDated = new Date(); - const warehouseFk = 1; - const litersMax = 9999; - const linesMax = 9999; - - this.defaultFilter = { - originDated, - futureDated, - warehouseFk, - litersMax, - linesMax - }; - this.smartTableOptions = { activeButtons: { - search: true + search: true, }, columns: [{ field: 'problems', @@ -35,7 +21,32 @@ export default class Controller extends Section { { field: 'destETD', searchable: false - }] + }, + { + field: 'state', + searchable: false + }, + { + field: 'tfState', + searchable: false + }, + { + field: 'ipt', + autocomplete: { + url: 'ItemPackingTypes', + showField: 'description', + valueField: 'code' + } + }, + { + field: 'tfIpt', + autocomplete: { + url: 'ItemPackingTypes', + showField: 'description', + valueField: 'code' + } + }, + ] }; } @@ -67,7 +78,7 @@ export default class Controller extends Section { stateColor(state) { if (state === 'OK') return 'success'; - else if (state === 'FREE') + else if (state === 'Libre') return 'notice'; } @@ -96,6 +107,23 @@ export default class Controller extends Section { this.vnApp.showSuccess(this.$t('Success')); }); } + + exprBuilder(param, value) { + switch (param) { + case 'id': + return { 'id': value }; + case 'ticketFuture': + return { 'ticketFuture': value }; + case 'litersMax': + return { 'liters': value }; + case 'linesMax': + return { 'lines': value }; + case 'ipt': + return { 'ipt': value }; + case 'tfIpt': + return { 'tfIpt': value }; + } + } } Controller.$inject = ['$element', '$scope']; diff --git a/modules/ticket/front/future/locale/en.yml b/modules/ticket/front/future/locale/en.yml index 7b0a377b8..8e2340a0b 100644 --- a/modules/ticket/front/future/locale/en.yml +++ b/modules/ticket/front/future/locale/en.yml @@ -1 +1,5 @@ Move confirmation: Do you want to move {{checked}} tickets to the future? +FREE: Free +DELIVERED: Delivered +ON_PREPARATION: On preparation +PACKED: Packed diff --git a/modules/ticket/front/future/locale/es.yml b/modules/ticket/front/future/locale/es.yml index 937f206e9..9be0be6a4 100644 --- a/modules/ticket/front/future/locale/es.yml +++ b/modules/ticket/front/future/locale/es.yml @@ -14,3 +14,9 @@ Origin ETD: ETD Origen Move tickets: Mover tickets Move confirmation: ¿Desea mover {{checked}} tickets hacia el futuro? Success: Tickets movidos correctamente +ETD: Tiempo estimado de entrega +IPT: Encajado +FREE: Libre +DELIVERED: Servido +ON_PREPARATION: En preparacion +PACKED: Encajado From c16210dd8a4106ed1acc2c77d9e8e25ccd7f94c6 Mon Sep 17 00:00:00 2001 From: alexandre Date: Fri, 11 Nov 2022 15:58:11 +0100 Subject: [PATCH 4/6] refs #4700 working on e2e --- .../00-ticket_canMerge.sql | 0 .../00-ticket_canbePostposed.sql | 0 e2e/helpers/selectors.js | 13 +- e2e/paths/04-item/08_regularize.spec.js | 4 +- .../05-ticket/01-sale/02_edit_sale.spec.js | 2 +- e2e/paths/05-ticket/20_future.spec.js | 205 ++++++++++++++++-- loopback/locale/en.json | 7 +- modules/ticket/back/methods/ticket/merge.js | 23 +- modules/ticket/front/future/index.spec.js | 2 +- modules/ticket/front/future/locale/en.yml | 1 + 10 files changed, 220 insertions(+), 37 deletions(-) rename db/changes/{10501-november => 10503-november}/00-ticket_canMerge.sql (100%) rename db/changes/{10501-november => 10503-november}/00-ticket_canbePostposed.sql (100%) diff --git a/db/changes/10501-november/00-ticket_canMerge.sql b/db/changes/10503-november/00-ticket_canMerge.sql similarity index 100% rename from db/changes/10501-november/00-ticket_canMerge.sql rename to db/changes/10503-november/00-ticket_canMerge.sql diff --git a/db/changes/10501-november/00-ticket_canbePostposed.sql b/db/changes/10503-november/00-ticket_canbePostposed.sql similarity index 100% rename from db/changes/10501-november/00-ticket_canbePostposed.sql rename to db/changes/10503-november/00-ticket_canbePostposed.sql diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 749e243d0..52ea34950 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -722,16 +722,23 @@ export default { litersMax: 'vn-textfield[label="Max Liters"]', ipt: 'vn-autocomplete[label="Origin IPT"]', tfIpt: 'vn-autocomplete[label="Destination IPT"]', + tableIpt: 'vn-autocomplete[name="ipt"]', + tableTfIpt: 'vn-autocomplete[name="tfIpt"]', state: 'vn-autocomplete[label="Origin Grouped State"]', tfState: 'vn-autocomplete[label="Destination Grouped State"]', warehouseFk: 'vn-autocomplete[label="Warehouse"]', problems: 'vn-check[label="With problems"]', tableButtonSearch: 'vn-button[vn-tooltip="Search"]', moveButton: 'vn-button[vn-tooltip="Future tickets"]', - tableId: 'vn-textfield[ng-model="searchProps[\'id\']"]', - tableTfId: 'vn-textfield[ng-model="searchProps[\'ticketFuture\']"]', + acceptButton: '.vn-confirm.shown button[response="accept"]', + firstCheck: 'tbody > tr:nth-child(1) > td > vn-check', + multiCheck: 'vn-multi-check', + tableId: 'vn-textfield[name="id"]', + tableTfId: 'vn-textfield[name="ticketFuture"]', + tableLiters: 'vn-textfield[name="litersMax"]', + tableLines: 'vn-textfield[name="linesMax"]', submit: 'vn-submit[label="Search"]', - table: 'tbody > tr:not(.empty-rows, #searchRow)', + table: 'tbody > tr:not(.empty-rows)' }, createStateView: { state: 'vn-autocomplete[ng-model="$ctrl.stateFk"]', diff --git a/e2e/paths/04-item/08_regularize.spec.js b/e2e/paths/04-item/08_regularize.spec.js index 400df666f..2e09a9f63 100644 --- a/e2e/paths/04-item/08_regularize.spec.js +++ b/e2e/paths/04-item/08_regularize.spec.js @@ -127,8 +127,8 @@ describe('Item regularize path', () => { await page.waitForState('ticket.index'); }); - it('should search for the ticket with id 28 once again', async() => { - await page.accessToSearchResult('28'); + it('should search for the ticket with id 31 once again', async() => { + await page.accessToSearchResult('31'); await page.waitForState('ticket.card.summary'); }); diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js index d776f417d..67dfd83bf 100644 --- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js @@ -291,7 +291,7 @@ describe('Ticket Edit sale path', () => { it('should confirm the transfered quantity is the correct one', async() => { const result = await page.waitToGetProperty(selectors.ticketSales.secondSaleQuantityCell, 'innerText'); - expect(result).toContain('10'); + expect(result).toContain('20'); }); it('should go back to the original ticket sales section', async() => { diff --git a/e2e/paths/05-ticket/20_future.spec.js b/e2e/paths/05-ticket/20_future.spec.js index c9c4559a7..9572ac746 100644 --- a/e2e/paths/05-ticket/20_future.spec.js +++ b/e2e/paths/05-ticket/20_future.spec.js @@ -18,14 +18,69 @@ describe('Ticket Future path', () => { const now = new Date(); const tomorrow = new Date(now.getDate() + 1); + const ticket = { + originDated: now, + futureDated: now, + linesMax: '9999', + litersMax: '9999', + warehouseFk: 'Warehouse One' + }; + + it('should show errors snackbar because of the required data', async () => { + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.originDated, ticket.originDated); + await page.pickDate(selectors.ticketFuture.futureDated, ticket.futureDated); + await page.write(selectors.ticketFuture.linesMax, ticket.linesMax); + await page.write(selectors.ticketFuture.litersMax, ticket.litersMax); + + await page.waitToClick(selectors.ticketFuture.submit); + let message = await page.waitForSnackbar(); + expect(message.text).toContain('warehouseFk is a required argument'); + + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.originDated, ticket.originDated); + await page.pickDate(selectors.ticketFuture.futureDated, ticket.futureDated); + await page.write(selectors.ticketFuture.linesMax, ticket.linesMax); + await page.autocompleteSearch(selectors.ticketFuture.warehouseFk, ticket.warehouseFk); + await page.waitToClick(selectors.ticketFuture.submit); + message = await page.waitForSnackbar(); + expect(message.text).toContain('litersMax is a required argument'); + + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.originDated, ticket.originDated); + await page.pickDate(selectors.ticketFuture.futureDated, ticket.futureDated); + await page.write(selectors.ticketFuture.litersMax, ticket.litersMax); + await page.autocompleteSearch(selectors.ticketFuture.warehouseFk, ticket.warehouseFk); + await page.waitToClick(selectors.ticketFuture.submit); + message = await page.waitForSnackbar(); + expect(message.text).toContain('linesMax is a required argument'); + + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.originDated, ticket.originDated); + await page.write(selectors.ticketFuture.linesMax, ticket.linesMax); + await page.write(selectors.ticketFuture.litersMax, ticket.litersMax); + await page.autocompleteSearch(selectors.ticketFuture.warehouseFk, ticket.warehouseFk); + await page.waitToClick(selectors.ticketFuture.submit); + message = await page.waitForSnackbar(); + expect(message.text).toContain('futureDated is a required argument'); + + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.pickDate(selectors.ticketFuture.futureDated, ticket.futureDated); + await page.write(selectors.ticketFuture.linesMax, ticket.linesMax); + await page.write(selectors.ticketFuture.litersMax, ticket.litersMax); + await page.autocompleteSearch(selectors.ticketFuture.warehouseFk, ticket.warehouseFk); + await page.waitToClick(selectors.ticketFuture.submit); + message = await page.waitForSnackbar(); + expect(message.text).toContain('originDated is a required argument'); + }); + it('should search with the required data', async () => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.pickDate(selectors.ticketFuture.originDated, now); - await page.pickDate(selectors.ticketFuture.futureDated, now); - await page.waitToClick(selectors.ticketFuture.linesMax); - await page.write(selectors.ticketFuture.linesMax, '9999'); - await page.write(selectors.ticketFuture.litersMax, '9999'); - await page.autocompleteSearch(selectors.ticketFuture.warehouseFk, 'Warehouse One'); + await page.pickDate(selectors.ticketFuture.originDated, ticket.originDated); + await page.pickDate(selectors.ticketFuture.futureDated, ticket.futureDated); + await page.write(selectors.ticketFuture.linesMax, ticket.linesMax); + await page.write(selectors.ticketFuture.litersMax, ticket.litersMax); + await page.autocompleteSearch(selectors.ticketFuture.warehouseFk, ticket.warehouseFk); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); @@ -61,7 +116,14 @@ describe('Ticket Future path', () => { it('should search with the origin IPT', async () => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + + await page.clearInput(selectors.ticketFuture.shipped); await page.clearInput(selectors.ticketFuture.tfShipped); + await page.clearInput(selectors.ticketFuture.ipt); + await page.clearInput(selectors.ticketFuture.tfIpt); + await page.clearInput(selectors.ticketFuture.state); + await page.clearInput(selectors.ticketFuture.tfState); + await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal'); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); @@ -69,7 +131,14 @@ describe('Ticket Future path', () => { it('should search with the destination IPT', async () => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + + await page.clearInput(selectors.ticketFuture.shipped); + await page.clearInput(selectors.ticketFuture.tfShipped); await page.clearInput(selectors.ticketFuture.ipt); + await page.clearInput(selectors.ticketFuture.tfIpt); + await page.clearInput(selectors.ticketFuture.state); + await page.clearInput(selectors.ticketFuture.tfState); + await page.autocompleteSearch(selectors.ticketFuture.tfIpt, 'Horizontal'); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); @@ -77,7 +146,14 @@ describe('Ticket Future path', () => { it('should search with the origin grouped state', async () => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + + await page.clearInput(selectors.ticketFuture.shipped); + await page.clearInput(selectors.ticketFuture.tfShipped); + await page.clearInput(selectors.ticketFuture.ipt); await page.clearInput(selectors.ticketFuture.tfIpt); + await page.clearInput(selectors.ticketFuture.state); + await page.clearInput(selectors.ticketFuture.tfState); + await page.autocompleteSearch(selectors.ticketFuture.state, 'Free'); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 3); @@ -85,7 +161,14 @@ describe('Ticket Future path', () => { it('should search with the destination grouped state', async () => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + + await page.clearInput(selectors.ticketFuture.shipped); + await page.clearInput(selectors.ticketFuture.tfShipped); + await page.clearInput(selectors.ticketFuture.ipt); + await page.clearInput(selectors.ticketFuture.tfIpt); await page.clearInput(selectors.ticketFuture.state); + await page.clearInput(selectors.ticketFuture.tfState); + await page.autocompleteSearch(selectors.ticketFuture.tfState, 'Free'); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); @@ -93,7 +176,14 @@ describe('Ticket Future path', () => { it('should search with problems selected', async () => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + + await page.clearInput(selectors.ticketFuture.shipped); + await page.clearInput(selectors.ticketFuture.tfShipped); + await page.clearInput(selectors.ticketFuture.ipt); + await page.clearInput(selectors.ticketFuture.tfIpt); + await page.clearInput(selectors.ticketFuture.state); await page.clearInput(selectors.ticketFuture.tfState); + await page.waitToClick(selectors.ticketFuture.problems); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); @@ -104,23 +194,108 @@ describe('Ticket Future path', () => { await page.waitToClick(selectors.ticketFuture.problems); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.clearInput(selectors.ticketFuture.problems); + await page.waitToClick(selectors.ticketFuture.submit); }); - it('should search with an ID Origin', async () => { - await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketFuture.problems); - await page.waitToClick(selectors.ticketFuture.submit); + it('should search in smart-table with an ID Origin', async () => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); await page.write(selectors.ticketFuture.tableId, "13"); await page.keyboard.press("Enter"); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 2); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search with an ID Destination', async () => { - await page.clearInput(selectors.ticketFuture.tableId); + + it('should search in smart-table with an ID Destination', async () => { + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); await page.write(selectors.ticketFuture.tableTfId, "12"); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + await page.keyboard.press("Enter"); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); + it('should search in smart-table with an IPT Origin', async () => { + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.autocompleteSearch(selectors.ticketFuture.tableIpt, 'Vertical'); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should search in smart-table with an IPT Destination', async () => { + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.autocompleteSearch(selectors.ticketFuture.tableTfIpt, 'Vertical'); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should search in smart-table with especified Lines', async () => { + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.write(selectors.ticketFuture.tableLines, "0"); + await page.keyboard.press("Enter"); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.write(selectors.ticketFuture.tableLines, "1"); + await page.keyboard.press("Enter"); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should search in smart-table with especified Liters', async () => { + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.write(selectors.ticketFuture.tableLiters, "0"); + await page.keyboard.press("Enter"); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.write(selectors.ticketFuture.tableLiters, "28"); + await page.keyboard.press("Enter"); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); + + await page.waitToClick(selectors.ticketFuture.tableButtonSearch); + await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketFuture.submit); + await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); + }); + + it('should check the second ticket and move to the future', async () => { + await page.waitToClick(selectors.ticketFuture.multiCheck); + await page.waitToClick(selectors.ticketFuture.firstCheck); + await page.waitToClick(selectors.ticketFuture.moveButton); + await page.waitToClick(selectors.ticketFuture.acceptButton); + const message = await page.waitForSnackbar(); + expect(message.text).toContain('Tickets moved successfully!'); + }); - // const message = await page.waitForSnackbar(); }); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 1e151294f..8ec38e425 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -134,6 +134,7 @@ "Password does not meet requirements": "Password does not meet requirements", "You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies", "Not enough privileges to edit a client": "Not enough privileges to edit a client", - "You don't have grant privilege": "You don't have grant privilege", - "You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user" -} + "You don't have grant privilege": "You don't have grant privilege", + "You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user", + "MOVE_TICKET_CONFIRMATION": "MOVE_TICKET_CONFIRMATION" +} \ No newline at end of file diff --git a/modules/ticket/back/methods/ticket/merge.js b/modules/ticket/back/methods/ticket/merge.js index 356259a26..e7f815128 100644 --- a/modules/ticket/back/methods/ticket/merge.js +++ b/modules/ticket/back/methods/ticket/merge.js @@ -36,8 +36,8 @@ module.exports = Self => { myOptions.transaction = tx; } - for (let ticket of tickets) { - try { + try { + for (let ticket of tickets) { const fullPath = `${origin}/#!/ticket/${ticket.ticketFuture}/summary`; const message = $t('MOVE_TICKET_CONFIRMATION', { originDated: new Date(ticket.originETD).toLocaleDateString('es-ES'), @@ -46,18 +46,17 @@ module.exports = Self => { tfId: ticket.ticketFuture, fullPath }); - if(!ticket.id || !ticket.ticketFuture) continue; - await models.Sale.updateAll({ticketFk: ticket.id}, {ticketFk: ticket.ticketFuture}, myOptions); + if (!ticket.id || !ticket.ticketFuture) continue; + await models.Sale.updateAll({ ticketFk: ticket.id }, { ticketFk: ticket.ticketFuture }, myOptions); await models.Ticket.setDeleted(ctx, ticket.id, myOptions); await models.Chat.sendCheckingPresence(ctx, ticket.workerFk, message); - if (tx) - { - await tx.commit(); - } - } catch (e) { - if (tx) await tx.rollback(); - throw e; + }; + if (tx) { + await tx.commit(); } - }; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; }; diff --git a/modules/ticket/front/future/index.spec.js b/modules/ticket/front/future/index.spec.js index 003b277ee..63deebc4f 100644 --- a/modules/ticket/front/future/index.spec.js +++ b/modules/ticket/front/future/index.spec.js @@ -22,7 +22,7 @@ describe('Component vnTicketFuture', () => { }, { id: 2, checked: true, - state: "FREE" + state: "Libre" }]; })); diff --git a/modules/ticket/front/future/locale/en.yml b/modules/ticket/front/future/locale/en.yml index 8e2340a0b..66d3ce269 100644 --- a/modules/ticket/front/future/locale/en.yml +++ b/modules/ticket/front/future/locale/en.yml @@ -3,3 +3,4 @@ FREE: Free DELIVERED: Delivered ON_PREPARATION: On preparation PACKED: Packed +Success: Tickets moved successfully! From 0ef64481b9d4a57799acfc503ead59113c7083c3 Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 14 Nov 2022 08:51:15 +0100 Subject: [PATCH 5/6] refs #4700 e2e finished --- e2e/paths/05-ticket/20_future.spec.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/e2e/paths/05-ticket/20_future.spec.js b/e2e/paths/05-ticket/20_future.spec.js index 9572ac746..23c2db77a 100644 --- a/e2e/paths/05-ticket/20_future.spec.js +++ b/e2e/paths/05-ticket/20_future.spec.js @@ -172,11 +172,8 @@ describe('Ticket Future path', () => { await page.autocompleteSearch(selectors.ticketFuture.tfState, 'Free'); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); - }); - it('should search with problems selected', async () => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.clearInput(selectors.ticketFuture.shipped); await page.clearInput(selectors.ticketFuture.tfShipped); await page.clearInput(selectors.ticketFuture.ipt); @@ -184,21 +181,10 @@ describe('Ticket Future path', () => { await page.clearInput(selectors.ticketFuture.state); await page.clearInput(selectors.ticketFuture.tfState); - await page.waitToClick(selectors.ticketFuture.problems); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search with no problems selected', async () => { - await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketFuture.problems); - await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); - await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); - await page.clearInput(selectors.ticketFuture.problems); - await page.waitToClick(selectors.ticketFuture.submit); - }); - it('should search in smart-table with an ID Origin', async () => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); await page.write(selectors.ticketFuture.tableId, "13"); @@ -289,7 +275,7 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should check the second ticket and move to the future', async () => { + it('should check the three last tickets and move to the future', async () => { await page.waitToClick(selectors.ticketFuture.multiCheck); await page.waitToClick(selectors.ticketFuture.firstCheck); await page.waitToClick(selectors.ticketFuture.moveButton); From 03ea2b4f1130b4ce607176b6e64e1315e26b4fc9 Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 14 Nov 2022 08:55:27 +0100 Subject: [PATCH 6/6] refs #4700 translations --- loopback/locale/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 8ec38e425..84ca88a64 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -136,5 +136,5 @@ "Not enough privileges to edit a client": "Not enough privileges to edit a client", "You don't have grant privilege": "You don't have grant privilege", "You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user", - "MOVE_TICKET_CONFIRMATION": "MOVE_TICKET_CONFIRMATION" -} \ No newline at end of file + "MOVE_TICKET_CONFIRMATION": "Ticket {{id}} ({{originDated}}) merged with {{tfId}} ({{futureDated}}) [{{fullPath}}]" +}