diff --git a/CHANGELOG.md b/CHANGELOG.md index 41374b4df..91ce818a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2304.01] - 2023-02-09 +## [2306.01] - 2023-02-23 ### Added -- (Trabajadores -> Nuevo trabajador) Nueva sección +- ### Changed - @@ -16,6 +16,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - +## [2304.01] - 2023-02-09 + +### Added +- (Rutas) Al descargar varias facturas se comprime en un zip +- (Trabajadores -> Nuevo trabajador) Nueva sección +- (Tickets -> Adelantar tickets) Añadidos campos "líneas" y "litros" al ticket origen +- (Tickets -> Adelantar tickets) Nuevo icono muestra cuando las agencias de los tickets origen/destino son distintas + +### Changed +- (Entradas -> Compras) Cambiados los campos "Precio Grouping/Packing" por "PVP" y "Precio" por "Coste" +- (Artículos -> Últimas entradas) Cambiados los campos "P.P.U." y "P.P.P." por "PVP" +- (Rutas -> Sumario/Tickets) Actualizados campos de los tickets +- (Proveedores -> Crear/Editar) Permite añadir Proveedores con la misma razón social pero con países distintos +- (Tickets -> Adelantar tickets) Cambiados selectores de estado por checks "Pendiente origen/destino" +- (Tickets -> Adelantar tickets) Cambiado stock de destino a origen. + +### Fixed +- (Artículos -> Etiquetas) Permite intercambiar la relevancia entre dos etiquetas. +- (Cliente -> Datos Fiscales) No se permite seleccionar 'Notificar vía e-mail' a los clientes sin e-mail +- (Tickets -> Datos básicos) Permite guardar la hora de envío +- (Tickets -> Añadir pago) Eliminado "null" en las referencias +- (Tickets -> Adelantar tickets) Permite ordenar por importe +- (Tickets -> Adelantar tickets) El filtrado por encajado muestra también los tickets sin tipo de encajado + ## [2302.01] - 2023-01-26 ### Added diff --git a/db/changes/230201/00-SupplierUniqueKey.sql b/db/changes/230201/00-SupplierUniqueKey.sql new file mode 100644 index 000000000..9c0d4a192 --- /dev/null +++ b/db/changes/230201/00-SupplierUniqueKey.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`supplier` ADD UNIQUE (name, countryFk); diff --git a/db/changes/230401/00-ACL_tag_update.sql b/db/changes/230401/00-ACL_tag_update.sql new file mode 100644 index 000000000..3c103e990 --- /dev/null +++ b/db/changes/230401/00-ACL_tag_update.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Tag', 'onSubmit', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/230401/00-ticket_canAdvance.sql b/db/changes/230401/00-ticket_canAdvance.sql new file mode 100644 index 000000000..fd9d451bf --- /dev/null +++ b/db/changes/230401/00-ticket_canAdvance.sql @@ -0,0 +1,110 @@ +DROP PROCEDURE IF EXISTS vn.ticket_canAdvance; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT) +BEGIN +/** + * Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar. + * + * @param vDateFuture Fecha de los tickets que se quieren adelantar. + * @param vDateToAdvance Fecha a cuando se quiere adelantar. + * @param vWarehouseFk Almacén + */ + + DECLARE vDateInventory DATE; + + SELECT inventoried INTO vDateInventory FROM vn.config; + + DROP TEMPORARY TABLE IF EXISTS tmp.stock; + CREATE TEMPORARY TABLE tmp.stock + (itemFk INT PRIMARY KEY, + amount INT) + ENGINE = MEMORY; + + INSERT INTO tmp.stock(itemFk, amount) + SELECT itemFk, SUM(quantity) amount FROM + ( + SELECT itemFk, quantity + FROM vn.itemTicketOut + WHERE shipped >= vDateInventory + AND shipped < vDateFuture + AND warehouseFk = vWarehouseFk + UNION ALL + SELECT itemFk, quantity + FROM vn.itemEntryIn + WHERE landed >= vDateInventory + AND landed < vDateFuture + AND isVirtualStock = FALSE + AND warehouseInFk = vWarehouseFk + UNION ALL + SELECT itemFk, quantity + FROM vn.itemEntryOut + WHERE shipped >= vDateInventory + AND shipped < vDateFuture + AND warehouseOutFk = vWarehouseFk + ) t + GROUP BY itemFk HAVING amount != 0; + + DROP TEMPORARY TABLE IF EXISTS tmp.filter; + CREATE TEMPORARY TABLE tmp.filter + (INDEX (id)) + SELECT s.ticketFk futureId, + t2.ticketFk id, + count(DISTINCT s.id) saleCount, + t2.state, + t2.isNotValidated, + st.name futureState, + st.isNotValidated futureIsNotValidated, + GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt, + t2.ipt, + t.workerFk, + CAST(SUM(litros) AS DECIMAL(10,0)) futureLiters, + CAST(COUNT(*) AS DECIMAL(10,0)) `futureLines`, + t2.shipped, + t.shipped futureShipped, + t2.totalWithVat, + t.totalWithVat futureTotalWithVat, + t2.agency, + am.name futureAgency, + t2.lines, + t2.liters, + SUM((s.quantity <= IFNULL(st.amount,0))) hasStock + FROM vn.ticket t + JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.saleVolume sv ON t.id = sv.ticketFk + JOIN (SELECT + t2.id ticketFk, + t2.addressFk, + st.isNotValidated, + st.name state, + GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt, + t2.shipped, + t2.totalWithVat, + am.name agency, + CAST(SUM(litros) AS DECIMAL(10,0)) liters, + CAST(COUNT(*) AS DECIMAL(10,0)) `lines` + FROM vn.ticket t2 + JOIN vn.saleVolume sv ON t2.id = sv.ticketFk + JOIN vn.sale s ON s.ticketFk = t2.id + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.ticketState ts ON ts.ticketFk = t2.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.agencyMode am ON t2.agencyModeFk = am.id + LEFT JOIN vn.itemPackingType ipt ON ipt.code = i.itemPackingTypeFk + WHERE t2.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance) + AND t2.warehouseFk = vWarehouseFk + GROUP BY t2.id) t2 ON t2.addressFk = t.addressFk + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.agencyMode am ON t.agencyModeFk = am.id + LEFT JOIN vn.itemPackingType ipt ON ipt.code = i.itemPackingTypeFk + LEFT JOIN tmp.stock st ON st.itemFk = s.itemFk + WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture) + AND t.warehouseFk = vWarehouseFk + GROUP BY t.id; + + DROP TEMPORARY TABLE tmp.stock; +END$$ +DELIMITER ; diff --git a/db/changes/230401/00-updateIsToBeMailed.sql b/db/changes/230401/00-updateIsToBeMailed.sql new file mode 100644 index 000000000..1bb177f57 --- /dev/null +++ b/db/changes/230401/00-updateIsToBeMailed.sql @@ -0,0 +1,6 @@ +UPDATE `vn`.`client` + SET isToBeMailed = FALSE + WHERE + mailAddress is NULL + AND email is NULL + AND isToBeMailed = TRUE; diff --git a/db/changes/230601/.gitkeep b/db/changes/230601/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 772d6055d..4626279e4 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -80203,4 +80203,3 @@ USE `vncontrol`; -- Dump completed on 2022-11-21 7:57:28 - diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 19fe66cc1..e3b6851c8 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -778,8 +778,8 @@ export default { ipt: 'vn-autocomplete[label="Destination IPT"]', tableIpt: 'vn-autocomplete[name="ipt"]', tableFutureIpt: 'vn-autocomplete[name="futureIpt"]', - futureState: 'vn-autocomplete[label="Origin Grouped State"]', - state: 'vn-autocomplete[label="Destination Grouped State"]', + futureState: 'vn-check[label="Pending Origin"]', + state: 'vn-check[label="Pending Destination"]', warehouseFk: 'vn-autocomplete[label="Warehouse"]', tableButtonSearch: 'vn-button[vn-tooltip="Search"]', moveButton: 'vn-button[vn-tooltip="Advance tickets"]', @@ -944,9 +944,9 @@ export default { routeSummary: { header: 'vn-route-summary > vn-card > h5', cost: 'vn-route-summary vn-label-value[label="Cost"]', - firstTicketID: 'vn-route-summary vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2) > span', + firstTicketID: 'vn-route-summary vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(10) > span', firstTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor', - firstAlias: 'vn-route-summary vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(3) > span', + firstAlias: 'vn-route-summary vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(5) > span', firstClientDescriptor: '.vn-popover.shown vn-client-descriptor', goToRouteSummaryButton: 'vn-route-summary > vn-card > h5 > a', diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js index bba7ced89..b5da799fc 100644 --- a/e2e/paths/03-worker/04_time_control.spec.js +++ b/e2e/paths/03-worker/04_time_control.spec.js @@ -23,6 +23,7 @@ describe('Worker time control path', () => { it('should go to the next month, go to current month and go 1 month in the past', async() => { let date = new Date(); + date.setDate(1); date.setMonth(date.getMonth() + 1); let month = date.toLocaleString('default', {month: 'long'}); @@ -32,6 +33,7 @@ describe('Worker time control path', () => { expect(result).toContain(month); date = new Date(); + date.setDate(1); month = date.toLocaleString('default', {month: 'long'}); await page.click(selectors.workerTimeControl.previousMonthButton); @@ -40,6 +42,7 @@ describe('Worker time control path', () => { expect(result).toContain(month); date = new Date(); + date.setDate(1); date.setMonth(date.getMonth() - 1); const timestamp = Math.round(date.getTime() / 1000); month = date.toLocaleString('default', {month: 'long'}); diff --git a/e2e/paths/05-ticket/22_advance.spec.js b/e2e/paths/05-ticket/22_advance.spec.js index 6aaa81591..3a6234fe9 100644 --- a/e2e/paths/05-ticket/22_advance.spec.js +++ b/e2e/paths/05-ticket/22_advance.spec.js @@ -50,7 +50,7 @@ describe('Ticket Advance path', () => { await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'Horizontal'); await page.waitToClick(selectors.ticketAdvance.submit); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0); + await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); await page.clearInput(selectors.ticketAdvance.ipt); @@ -62,7 +62,7 @@ describe('Ticket Advance path', () => { await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'Horizontal'); await page.waitToClick(selectors.ticketAdvance.submit); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0); + await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); await page.clearInput(selectors.ticketAdvance.futureIpt); @@ -70,26 +70,36 @@ describe('Ticket Advance path', () => { await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); }); - it('should search with the origin grouped state', async() => { + it('should search with the origin pending state', async() => { await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); - await page.autocompleteSearch(selectors.ticketAdvance.futureState, 'Free'); + await page.waitToClick(selectors.ticketAdvance.futureState); await page.waitToClick(selectors.ticketAdvance.submit); await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); - await page.clearInput(selectors.ticketAdvance.futureState); + await page.waitToClick(selectors.ticketAdvance.futureState); + await page.waitToClick(selectors.ticketAdvance.submit); + await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0); + + await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketAdvance.futureState); await page.waitToClick(selectors.ticketAdvance.submit); await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); }); it('should search with the destination grouped state', async() => { await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); - await page.autocompleteSearch(selectors.ticketAdvance.state, 'Free'); + await page.waitToClick(selectors.ticketAdvance.state); await page.waitToClick(selectors.ticketAdvance.submit); await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0); await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); - await page.clearInput(selectors.ticketAdvance.state); + await page.waitToClick(selectors.ticketAdvance.state); + await page.waitToClick(selectors.ticketAdvance.submit); + await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); + + await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); + await page.waitToClick(selectors.ticketAdvance.state); await page.waitToClick(selectors.ticketAdvance.submit); await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); }); @@ -116,42 +126,7 @@ describe('Ticket Advance path', () => { await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); }); - it('should search in smart-table with stock', async() => { - await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); - await page.write(selectors.ticketAdvance.tableStock, '5'); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 2); - - await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); - await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketAdvance.submit); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); - }); - - it('should search in smart-table with especified Lines', async() => { - await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); - await page.write(selectors.ticketAdvance.tableLines, '0'); - await page.keyboard.press('Enter'); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); - - await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); - await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketAdvance.submit); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); - }); - - it('should search in smart-table with especified Liters', async() => { - await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); - await page.write(selectors.ticketAdvance.tableLiters, '0'); - await page.keyboard.press('Enter'); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); - - await page.waitToClick(selectors.ticketAdvance.tableButtonSearch); - await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton); - await page.waitToClick(selectors.ticketAdvance.submit); - await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1); - }); - - it('should check the three last tickets and move to the future', async() => { + it('should check the one ticket and move to the present', async() => { await page.waitToClick(selectors.ticketAdvance.multiCheck); await page.waitToClick(selectors.ticketAdvance.moveButton); await page.waitToClick(selectors.ticketAdvance.acceptButton); diff --git a/front/core/components/button-menu/index.js b/front/core/components/button-menu/index.js index c19962b08..e9e4e5ab7 100644 --- a/front/core/components/button-menu/index.js +++ b/front/core/components/button-menu/index.js @@ -87,6 +87,7 @@ ngModule.vnComponent('vnButtonMenu', { selectFields: ' { const created = res.data; // Apply new data to created instances - for (let i = 0; i < orgCreates.length; i++) { - const row = orgCreates[i]; + for (let i = 0; i < creates.length; i++) { + const row = creates[i]; row[pk] = created[i][pk]; for (let prop in row) { diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index bc943996d..68cd5a047 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -42,6 +42,7 @@ vn-log { & > td.after, & > th.after { width: 40%; + white-space: pre-line; } } } diff --git a/loopback/locale/en.json b/loopback/locale/en.json index a406b55a5..c810e5a69 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -67,7 +67,7 @@ "Changed client paymethod": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})", "Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})", "Change quantity": "{{concept}} change of {{oldQuantity}} to {{newQuantity}}", - "Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked", + "Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked", "Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*", "Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*", "Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", @@ -137,15 +137,18 @@ "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", - "Claim pickup order sent": "Claim pickup order sent [{{claimId}}]({{{claimUrl}}}) to client *{{clientName}}*", + "Claim pickup order sent": "Claim pickup order sent [{{claimId}}]({{{claimUrl}}}) to client *{{clientName}}*", "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", - "Email verify": "Email verify", + "Email verify": "Email verify", "Ticket merged": "Ticket [{{originId}}]({{{originFullPath}}}) ({{{originDated}}}) merged with [{{destinationId}}]({{{destinationFullPath}}}) ({{{destinationDated}}})", - "Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production", "App locked": "App locked by user {{userId}}", - "The sales of the receiver ticket can't be modified": "The sales of the receiver ticket can't be modified", - "Receipt's bank was not found": "Receipt's bank was not found", - "This receipt was not compensated": "This receipt was not compensated", - "Client's email was not found": "Client's email was not found" + "The sales of the receiver ticket can't be modified": "The sales of the receiver ticket can't be modified", + "Receipt's bank was not found": "Receipt's bank was not found", + "This receipt was not compensated": "This receipt was not compensated", + "Client's email was not found": "Client's email was not found", + "It is not possible to modify tracked sales": "It is not possible to modify tracked sales", + "It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo", + "It is not possible to modify cloned sales": "It is not possible to modify cloned sales", + "Valid priorities: 1,2,3": "Valid priorities: 1,2,3" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index d65054f37..ad6d53d64 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -84,7 +84,6 @@ "The current ticket can't be modified": "El ticket actual no puede ser modificado", "The current claim can't be modified": "La reclamación actual no puede ser modificada", "The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas", - "Sale(s) blocked, contact production": "Linea(s) bloqueada(s), contacte con produccion", "Please select at least one sale": "Por favor selecciona al menos una linea", "All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket", "NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", @@ -259,5 +258,11 @@ "Try again": "Vuelve a intentarlo", "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9", "Failed to upload file": "Error al subir archivo", - "The DOCUWARE PDF document does not exists": "The DOCUWARE PDF document does not exists" + "The DOCUWARE PDF document does not exists": "El documento PDF Docuware no existe", + "It is not possible to modify tracked sales": "No es posible modificar líneas de pedido que se hayan empezado a preparar", + "It is not possible to modify sales that their articles are from Floramondo": "No es posible modificar líneas de pedido cuyos artículos sean de Floramondo", + "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", + "A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.", + "There is no assigned email for this client": "No hay correo asignado para este cliente" } + diff --git a/modules/claim/front/card/index.js b/modules/claim/front/card/index.js index 8fb35db75..4a8677c90 100644 --- a/modules/claim/front/card/index.js +++ b/modules/claim/front/card/index.js @@ -19,10 +19,26 @@ class Controller extends ModuleCard { }, { relation: 'ticket', scope: { - fields: ['zoneFk'], - include: { - relation: 'zone' - } + fields: ['zoneFk', 'addressFk'], + include: [ + { + relation: 'zone', + scope: { + fields: ['name'] + } + }, + { + relation: 'address', + scope: { + fields: ['provinceFk'], + include: { + relation: 'province', + scope: { + fields: ['name'] + } + } + } + }] } }, { relation: 'claimState', diff --git a/modules/claim/front/descriptor/index.html b/modules/claim/front/descriptor/index.html index 90246824b..f346ecf17 100644 --- a/modules/claim/front/descriptor/index.html +++ b/modules/claim/front/descriptor/index.html @@ -55,9 +55,13 @@ - {{$ctrl.claim.ticket.zoneFk}} + {{$ctrl.claim.ticket.zone.name}} + + { done(); } + Self.validateAsync('isToBeMailed', isToBeMailed, { + message: 'There is no assigned email for this client' + }); + + function isToBeMailed(err, done) { + if (this.isToBeMailed == true && !this.email) + err(); + done(); + } + Self.validateAsync('defaultAddressFk', isActive, {message: 'Unable to default a disabled consignee'} ); diff --git a/modules/client/front/balance/create/index.js b/modules/client/front/balance/create/index.js index 35b2e0b38..b64129e2e 100644 --- a/modules/client/front/balance/create/index.js +++ b/modules/client/front/balance/create/index.js @@ -59,13 +59,11 @@ class Controller extends Dialog { if (value) { const accountingType = value.accountingType; - if (this.originalDescription) { - this.receipt.description = - `${accountingType && accountingType.receiptDescription}, ${this.originalDescription}`; - } else { - this.receipt.description = - `${accountingType && accountingType.receiptDescription}`; - } + if (accountingType.receiptDescription != null) { + this.receipt.description = accountingType.receiptDescription; + if (this.originalDescription) this.receipt.description += `, ${this.originalDescription}`; + } else if (this.originalDescription) + this.receipt.description = this.originalDescription; this.maxAmount = accountingType && accountingType.maxAmount; this.receipt.payed = new Date(); diff --git a/modules/entry/front/latest-buys/locale/es.yml b/modules/entry/front/latest-buys/locale/es.yml index 8d70844fa..795e3f5f4 100644 --- a/modules/entry/front/latest-buys/locale/es.yml +++ b/modules/entry/front/latest-buys/locale/es.yml @@ -1,5 +1,5 @@ Edit buy(s): Editar compra(s) -Buying value: Precio +Buying value: Coste Freight value: Porte Commission value: Comisión Package value: Embalaje @@ -16,4 +16,4 @@ Field to edit: Campo a editar PackageName: Cubo Edit: Editar buy(s): compra(s) -Package out: Embalaje envíos \ No newline at end of file +Package out: Embalaje envíos diff --git a/modules/entry/front/summary/index.html b/modules/entry/front/summary/index.html index 04844ab99..8f1cd924d 100644 --- a/modules/entry/front/summary/index.html +++ b/modules/entry/front/summary/index.html @@ -38,7 +38,7 @@ - {{$ctrl.entryData.travel.ref}} @@ -114,8 +114,7 @@ Grouping Buying value Import - Grouping price - Packing price + PVP @@ -125,19 +124,18 @@ {{::line.packageFk | dashIfEmpty}} {{::line.weight}} - + {{::line.packing | dashIfEmpty}} - + {{::line.grouping | dashIfEmpty}} {{::line.buyingValue | currency: 'EUR':2}} {{::line.quantity * line.buyingValue | currency: 'EUR':2}} - {{::line.price2 | currency: 'EUR':2}} - {{::line.price3 | currency: 'EUR':2}} + {{::line.price2 | currency: 'EUR':2 | dashIfEmpty}} / {{::line.price3 | currency: 'EUR':2 | dashIfEmpty}} @@ -195,7 +193,7 @@ vn-id="item-descriptor" warehouse-fk="$ctrl.vnConfig.warehouseFk"> - diff --git a/modules/item/back/methods/tag/onSubmit.js b/modules/item/back/methods/tag/onSubmit.js new file mode 100644 index 000000000..7abbe60d4 --- /dev/null +++ b/modules/item/back/methods/tag/onSubmit.js @@ -0,0 +1,81 @@ + +module.exports = function(Self) { + Self.remoteMethodCtx('onSubmit', { + description: 'Save model changes', + accessType: 'WRITE', + accepts: [ + { + arg: 'creates', + type: ['object'], + description: 'The itemTags records to create' + }, { + arg: 'deletes', + type: ['number'], + description: 'The itemTags ids to delete' + }, { + arg: 'updates', + type: ['object'], + description: 'The itemTags records to update' + }, { + arg: 'maxPriority', + type: 'number', + description: 'The maxPriority value' + } + ], + returns: { + root: true, + type: 'object' + }, + http: { + verb: 'PATCH', + path: '/onSubmit' + } + }); + + Self.onSubmit = async(ctx, options) => { + const models = Self.app.models; + const args = ctx.args; + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + if (args.deletes) { + for (const itemTagId of args.deletes) + await models.ItemTag.destroyById(itemTagId, myOptions); + } + + if (args.updates) { + for (const row of args.updates) { + if (row.data.priority) { + const itemTag = await models.ItemTag.findById(row.where.id, null, myOptions); + await itemTag.updateAttributes({ + priority: row.data.priority + args.maxPriority + }, myOptions); + } + } + for (const row of args.updates) { + const itemTag = await models.ItemTag.findById(row.where.id, null, myOptions); + await itemTag.updateAttributes(row.data, myOptions); + } + } + + if (args.creates) { + for (const itemTag of args.creates) + await models.ItemTag.create(itemTag, myOptions); + } + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/item/back/methods/tag/specs/onSubmit.spec.js b/modules/item/back/methods/tag/specs/onSubmit.spec.js new file mode 100644 index 000000000..f24aad7e4 --- /dev/null +++ b/modules/item/back/methods/tag/specs/onSubmit.spec.js @@ -0,0 +1,96 @@ +const models = require('vn-loopback/server/server').models; + +describe('tag onSubmit()', () => { + it('should delete a tag', async() => { + const tx = await models.Item.beginTransaction({}); + const options = {transaction: tx}; + + try { + const deletes = [40]; + const ctx = { + args: { + deletes: deletes + } + }; + await models.Tag.onSubmit(ctx, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should update a tag', async() => { + const tx = await models.Item.beginTransaction({}); + const options = {transaction: tx}; + + try { + const updates = [{data: {value: 'Container Test'}, where: {id: 36}}]; + const ctx = { + args: { + updates: updates + } + }; + await models.Tag.onSubmit(ctx, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should create a tag', async() => { + const tx = await models.Item.beginTransaction({}); + const options = {transaction: tx}; + + try { + const creates = [{ + 'itemFk': '6', + 'priority': 8, + '$orgIndex': null, + '$oldData': null, + '$isNew': true, + 'tagFk': 3, + 'value': 'madera' + }]; + const ctx = { + args: { + creates: creates + } + }; + await models.Tag.onSubmit(ctx, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should swap priority for two tags', async() => { + const tx = await models.Item.beginTransaction({}); + const options = {transaction: tx}; + + try { + const updates = [ + {data: {priority: 2}, where: {id: 36}}, + {data: {priority: 1}, where: {id: 37}} + ]; + const ctx = { + args: { + updates: updates, + maxPriority: 7, + + } + }; + await models.Tag.onSubmit(ctx, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/item/back/models/tag.js b/modules/item/back/models/tag.js index 43fbc0db3..92760e34f 100644 --- a/modules/item/back/models/tag.js +++ b/modules/item/back/models/tag.js @@ -1,3 +1,4 @@ module.exports = Self => { require('../methods/tag/filterValue')(Self); + require('../methods/tag/onSubmit')(Self); }; diff --git a/modules/item/front/last-entries/index.html b/modules/item/front/last-entries/index.html index 0348d4f66..1c2db10a5 100644 --- a/modules/item/front/last-entries/index.html +++ b/modules/item/front/last-entries/index.html @@ -9,15 +9,15 @@ - + @@ -35,8 +35,7 @@ Warehouse Landed Entry - P.P.U - P.P.P + PVP Label Packing Grouping @@ -51,7 +50,7 @@ - @@ -65,30 +64,31 @@ {{::entry.entryFk | dashIfEmpty}} - {{::entry.price2 | dashIfEmpty}} - {{::entry.price3 | dashIfEmpty}} + + {{::entry.price2 | currency: 'EUR':2 | dashIfEmpty}} / {{::entry.price3 | currency: 'EUR':2 | dashIfEmpty}} + {{entry.stickers | dashIfEmpty}} - + {{::entry.packing | dashIfEmpty}} - + {{::entry.grouping | dashIfEmpty}} {{::entry.stems | dashIfEmpty}} {{::entry.quantity}} - - {{::entry.cost | dashIfEmpty}} + {{::$ctrl.$t('Cost')}}: {{::entry.buyingValue | currency: 'EUR':2 | dashIfEmpty}}
+ {{::$ctrl.$t('Package')}}: {{::entry.packageValue | currency: 'EUR':2 | dashIfEmpty}}
+ {{::$ctrl.$t('Freight')}}: {{::entry.freightValue | currency: 'EUR':2 | dashIfEmpty}}
+ {{::$ctrl.$t('Comission')}}: {{::entry.comissionValue | currency: 'EUR':2 | dashIfEmpty}}"> + {{::entry.cost | currency: 'EUR':2 | dashIfEmpty}}
{{::entry.weight | dashIfEmpty}} @@ -113,24 +113,24 @@ ng-click="contextmenu.filterBySelection()"> Filter by selection - Exclude selection - Remove filter - Remove all filters - Copy value - \ No newline at end of file + diff --git a/modules/item/front/tags/index.html b/modules/item/front/tags/index.html index c040b9984..f9b5370fa 100644 --- a/modules/item/front/tags/index.html +++ b/modules/item/front/tags/index.html @@ -19,7 +19,7 @@ data="tags" auto-load="true"> -
+ - \ No newline at end of file + diff --git a/modules/item/front/tags/index.js b/modules/item/front/tags/index.js index 3b3cd58ef..bfa1f3f46 100644 --- a/modules/item/front/tags/index.js +++ b/modules/item/front/tags/index.js @@ -29,11 +29,17 @@ class Controller extends Section { } onSubmit() { - this.$.watcher.check(); - this.$.model.save().then(() => { + const changes = this.$.model.getChanges(); + const data = { + creates: changes.creates, + deletes: changes.deletes, + updates: changes.updates, + maxPriority: this.getHighestPriority() + }; + this.$http.patch(`Tags/onSubmit`, data).then(() => { + this.$.model.refresh(); this.$.watcher.notifySaved(); this.$.watcher.updateOriginalData(); - this.card.reload(); }); } } diff --git a/modules/route/back/methods/route/downloadZip.js b/modules/route/back/methods/route/downloadZip.js new file mode 100644 index 000000000..597f1d1f6 --- /dev/null +++ b/modules/route/back/methods/route/downloadZip.js @@ -0,0 +1,62 @@ +const JSZip = require('jszip'); + +module.exports = Self => { + Self.remoteMethodCtx('downloadZip', { + description: 'Download a zip file with multiple routes pdfs', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'string', + description: 'The routes ids', + } + ], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, { + arg: 'Content-Type', + type: 'string', + http: {target: 'header'} + }, { + arg: 'Content-Disposition', + type: 'string', + http: {target: 'header'} + } + ], + http: { + path: '/downloadZip', + verb: 'GET' + } + }); + + Self.downloadZip = async function(ctx, id, options) { + const models = Self.app.models; + const myOptions = {}; + const zip = new JSZip(); + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const ids = id.split(','); + for (let id of ids) { + ctx.args.id = id; + const routePdf = await models.Route.driverRoutePdf(ctx, id); + const fileName = extractFileName(routePdf[2]); + const body = routePdf[0]; + + zip.file(fileName, body); + } + + const stream = zip.generateNodeStream({streamFiles: true}); + + return [stream, 'application/zip', `filename="download.zip"`]; + }; + + function extractFileName(str) { + const matches = str.match(/"(.*?)"/); + return matches ? matches[1] : str; + } +}; diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js index 708644c1a..1eb9e27f5 100644 --- a/modules/route/back/methods/route/getTickets.js +++ b/modules/route/back/methods/route/getTickets.js @@ -50,14 +50,17 @@ module.exports = Self => { am.name AS agencyModeName, u.nickname AS userNickname, vn.ticketTotalVolume(t.id) AS volume, - tob.description + tob.description, + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt FROM vn.route r JOIN ticket t ON t.routeFk = r.id + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk LEFT JOIN ticketState ts ON ts.ticketFk = t.id LEFT JOIN state st ON st.id = ts.stateFk LEFT JOIN warehouse wh ON wh.id = t.warehouseFk LEFT JOIN observationType ot ON ot.code = 'delivery' - LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id + LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id AND tob.observationTypeFk = ot.id LEFT JOIN address a ON a.id = t.addressFk LEFT JOIN agencyMode am ON am.id = t.agencyModeFk @@ -70,7 +73,9 @@ module.exports = Self => { const where = filter.where; where['r.id'] = filter.id; - stmt.merge(conn.makeSuffix(filter)); + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge(conn.makeGroupBy('t.id')); + stmt.merge(conn.makeOrderBy(filter.order)); const tickets = await conn.executeStmt(stmt, myOptions); diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js index 08cabd30e..883f4597e 100644 --- a/modules/route/back/models/route.js +++ b/modules/route/back/models/route.js @@ -13,6 +13,7 @@ module.exports = Self => { require('../methods/route/driverRoutePdf')(Self); require('../methods/route/driverRouteEmail')(Self); require('../methods/route/sendSms')(Self); + require('../methods/route/downloadZip')(Self); Self.validate('kmStart', validateDistance, { message: 'Distance must be lesser than 1000' diff --git a/modules/route/front/index/index.js b/modules/route/front/index/index.js index 9258c8fac..94f6db09c 100644 --- a/modules/route/front/index/index.js +++ b/modules/route/front/index/index.js @@ -34,12 +34,22 @@ export default class Controller extends Section { } showRouteReport() { - const routes = []; + const routesIds = []; for (let route of this.checked) - routes.push(route.id); - const routesId = routes.join(','); + routesIds.push(route.id); + const stringRoutesIds = routesIds.join(','); - this.vnReport.show(`Routes/${routesId}/driver-route-pdf`); + if (this.checked.length <= 1) { + const url = `api/Routes/${stringRoutesIds}/driver-route-pdf?access_token=${this.vnToken.token}`; + window.open(url, '_blank'); + } else { + const serializedParams = this.$httpParamSerializer({ + access_token: this.vnToken.token, + id: stringRoutesIds + }); + const url = `api/Routes/downloadZip?${serializedParams}`; + window.open(url, '_blank'); + } } openClonationDialog() { diff --git a/modules/route/front/index/index.spec.js b/modules/route/front/index/index.spec.js index 05dd56433..c1f414d5e 100644 --- a/modules/route/front/index/index.spec.js +++ b/modules/route/front/index/index.spec.js @@ -44,17 +44,15 @@ describe('Component vnRouteIndex', () => { describe('showRouteReport()', () => { it('should call to the vnReport show method', () => { - controller.vnReport.show = jest.fn(); + jest.spyOn(window, 'open').mockReturnThis(); const data = controller.$.model.data; data[0].checked = true; data[2].checked = true; - const routeIds = '1,3'; - controller.showRouteReport(); - expect(controller.vnReport.show).toHaveBeenCalledWith(`Routes/${routeIds}/driver-route-pdf`); + expect(window.open).toHaveBeenCalled(); }); }); diff --git a/modules/route/front/summary/index.html b/modules/route/front/summary/index.html index 86f558634..a64ad4ff7 100644 --- a/modules/route/front/summary/index.html +++ b/modules/route/front/summary/index.html @@ -10,26 +10,26 @@ - - - - - {{$ctrl.summary.route.worker.user.name}} - @@ -40,35 +40,35 @@ - - - -

- Ticket

-

Ticket @@ -77,45 +77,49 @@ Order - Ticket id - Alias + Street + City + PC + Client + Warehouse Packages - Warehouse - PC - Street + Packaging + Ticket {{ticket.priority | dashIfEmpty}} + {{ticket.street}} + {{ticket.city}} + {{ticket.postalCode}} + + + {{ticket.nickname}} + + + {{ticket.warehouseName}} + {{ticket.packages}} + {{ticket.volume}} + {{ticket.ipt}} - {{ticket.id}} - - {{ticket.nickname}} - - - {{ticket.packages}} - {{ticket.volume}} - {{ticket.warehouseName}} - {{ticket.postalCode}} - {{ticket.street}} - - - + + @@ -123,12 +127,12 @@ - - diff --git a/modules/route/front/tickets/index.html b/modules/route/front/tickets/index.html index dae894ac7..18d6fb160 100644 --- a/modules/route/front/tickets/index.html +++ b/modules/route/front/tickets/index.html @@ -6,9 +6,9 @@ data="$ctrl.tickets" auto-load="true"> - -
@@ -25,18 +25,18 @@ vn-tooltip="Open buscaman" icon="icon-buscaman"> - - - City PC Client + Warehouse Packages + Packaging Ticket @@ -100,8 +102,10 @@ {{::ticket.nickname}} + {{ticket.warehouseName}} {{::ticket.packages}} {{::ticket.volume | number:2}} + {{::ticket.ipt}}
- - @@ -160,7 +164,7 @@ - - - \ No newline at end of file + diff --git a/modules/shelving/front/routes.json b/modules/shelving/front/routes.json index 09a8e389b..4059e5095 100644 --- a/modules/shelving/front/routes.json +++ b/modules/shelving/front/routes.json @@ -1,12 +1,12 @@ { "module": "shelving", "name": "Shelvings", - "icon" : "contact_support", + "icon" : "icon-inventory", "dependencies": ["worker"], "validations" : true, "menus": { "main": [ - {"state": "shelving.index", "icon": "contact_support"} + {"state": "shelving.index", "icon": "icon-inventory"} ], "card": [ {"state": "shelving.card.basicData", "icon": "settings"}, @@ -20,7 +20,7 @@ "abstract": true, "component": "vn-shelving", "description": "Shelvings" - }, + }, { "url": "/index?q", "state": "shelving.index", @@ -32,13 +32,13 @@ "state": "shelving.create", "component": "vn-shelving-create", "description": "New shelving" - }, + }, { "url": "/:id", "state": "shelving.card", "abstract": true, "component": "vn-shelving-card" - }, + }, { "url": "/summary", "state": "shelving.card.summary", @@ -47,7 +47,7 @@ "params": { "shelving": "$ctrl.shelving" } - }, + }, { "url": "/basic-data", "state": "shelving.card.basicData", @@ -56,7 +56,7 @@ "params": { "shelving": "$ctrl.shelving" } - }, + }, { "url" : "/log", "state": "shelving.card.log", @@ -64,4 +64,4 @@ "description": "Log" } ] -} \ No newline at end of file +} diff --git a/modules/supplier/back/models/supplier.js b/modules/supplier/back/models/supplier.js index 44549c65c..4e509aafc 100644 --- a/modules/supplier/back/models/supplier.js +++ b/modules/supplier/back/models/supplier.js @@ -16,10 +16,6 @@ module.exports = Self => { message: 'The social name cannot be empty' }); - Self.validatesUniquenessOf('name', { - message: 'The supplier name must be unique' - }); - if (this.city) { Self.validatesPresenceOf('city', { message: 'City cannot be empty' @@ -117,6 +113,27 @@ module.exports = Self => { throw new UserError('You can not modify is pay method checked'); }); + Self.validateAsync('name', 'countryFk', hasSupplierSameName, { + message: 'A supplier with the same name already exists. Change the country.' + }); + + async function hasSupplierSameName(err, done) { + if (!this.name || !this.countryFk) done(); + const supplier = await Self.app.models.Supplier.findOne( + { + where: { + name: this.name, + countryFk: this.countryFk + }, + fields: ['id'] + }); + + if (supplier && supplier.id != this.id) + err(); + + done(); + } + Self.observe('before save', async function(ctx) { const changes = ctx.data || ctx.instance; const orgData = ctx.currentInstance; diff --git a/modules/supplier/front/agency-term/index/index.html b/modules/supplier/front/agency-term/index/index.html index 9d53226c5..44c6deba9 100644 --- a/modules/supplier/front/agency-term/index/index.html +++ b/modules/supplier/front/agency-term/index/index.html @@ -24,36 +24,42 @@ diff --git a/modules/ticket/back/methods/sale/canEdit.js b/modules/ticket/back/methods/sale/canEdit.js index f44bd6743..3091ebca7 100644 --- a/modules/ticket/back/methods/sale/canEdit.js +++ b/modules/ticket/back/methods/sale/canEdit.js @@ -56,6 +56,13 @@ module.exports = Self => { const shouldEditCloned = canEditCloned || !hasSaleCloned; const shouldEditFloramondo = canEditFloramondo || !hasSaleFloramondo; - return shouldEditTracked && shouldEditCloned && shouldEditFloramondo; + if (!shouldEditTracked) + throw new UserError('It is not possible to modify tracked sales'); + if (!shouldEditCloned) + throw new UserError('It is not possible to modify cloned sales'); + if (!shouldEditFloramondo) + throw new UserError('It is not possible to modify sales that their articles are from Floramondo'); + + return true; }; }; diff --git a/modules/ticket/back/methods/sale/deleteSales.js b/modules/ticket/back/methods/sale/deleteSales.js index c045b9197..5d1463a66 100644 --- a/modules/ticket/back/methods/sale/deleteSales.js +++ b/modules/ticket/back/methods/sale/deleteSales.js @@ -43,9 +43,7 @@ module.exports = Self => { try { const saleIds = sales.map(sale => sale.id); - const canEditSales = await models.Sale.canEdit(ctx, saleIds, myOptions); - if (!canEditSales) - throw new UserError(`Sale(s) blocked, please contact production`); + await models.Sale.canEdit(ctx, saleIds, myOptions); const ticket = await models.Ticket.findById(ticketId, { include: { diff --git a/modules/ticket/back/methods/sale/recalculatePrice.js b/modules/ticket/back/methods/sale/recalculatePrice.js index 38c68d7f6..2c8e6768b 100644 --- a/modules/ticket/back/methods/sale/recalculatePrice.js +++ b/modules/ticket/back/methods/sale/recalculatePrice.js @@ -37,9 +37,7 @@ module.exports = Self => { try { const salesIds = sales.map(sale => sale.id); - const canEditSale = await models.Sale.canEdit(ctx, salesIds, myOptions); - if (!canEditSale) - throw new UserError(`Sale(s) blocked, please contact production`); + await models.Sale.canEdit(ctx, salesIds, myOptions); const query = ` DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales; diff --git a/modules/ticket/back/methods/sale/reserve.js b/modules/ticket/back/methods/sale/reserve.js index 648e6de23..2dc368af6 100644 --- a/modules/ticket/back/methods/sale/reserve.js +++ b/modules/ticket/back/methods/sale/reserve.js @@ -51,9 +51,7 @@ module.exports = Self => { try { const salesIds = sales.map(sale => sale.id); - const canEditSale = await models.Sale.canEdit(ctx, salesIds, myOptions); - if (!canEditSale) - throw new UserError(`Sale(s) blocked, please contact production`); + await models.Sale.canEdit(ctx, salesIds, myOptions); let changesMade = ''; const promises = []; diff --git a/modules/ticket/back/methods/sale/specs/canEdit.spec.js b/modules/ticket/back/methods/sale/specs/canEdit.spec.js index 2aa873df5..58d8f0635 100644 --- a/modules/ticket/back/methods/sale/specs/canEdit.spec.js +++ b/modules/ticket/back/methods/sale/specs/canEdit.spec.js @@ -50,7 +50,7 @@ describe('sale canEdit()', () => { it('should return false if any of the sales has a saleTracking record', async() => { const tx = await models.Sale.beginTransaction({}); - + let error; try { const options = {transaction: tx}; @@ -59,15 +59,15 @@ describe('sale canEdit()', () => { const sales = [31]; - const result = await models.Sale.canEdit(ctx, sales, options); - - expect(result).toEqual(false); - + await models.Sale.canEdit(ctx, sales, options); await tx.rollback(); } catch (e) { await tx.rollback(); - throw e; + error = e; } + + expect(error).toEqual( + new Error('It is not possible to modify tracked sales')); }); }); @@ -75,22 +75,22 @@ describe('sale canEdit()', () => { const saleCloned = [29]; it('should return false if any of the sales is cloned', async() => { const tx = await models.Sale.beginTransaction({}); - + let error; try { const options = {transaction: tx}; const buyerId = 35; const ctx = {req: {accessToken: {userId: buyerId}}}; - const result = await models.Sale.canEdit(ctx, saleCloned, options); - - expect(result).toEqual(false); - + await models.Sale.canEdit(ctx, saleCloned, options); await tx.rollback(); } catch (e) { await tx.rollback(); - throw e; + error = e; } + + expect(error).toEqual( + new Error('It is not possible to modify cloned sales')); }); it('should return true if any of the sales is cloned and has the correct role', async() => { @@ -130,7 +130,7 @@ describe('sale canEdit()', () => { it('should return false if any of the sales isFloramondo', async() => { const tx = await models.Sale.beginTransaction({}); const sales = [26]; - + let error; try { const options = {transaction: tx}; @@ -140,15 +140,15 @@ describe('sale canEdit()', () => { const saleToEdit = await models.Sale.findById(sales[0], null, options); await saleToEdit.updateAttribute('itemFk', 9, options); - const result = await models.Sale.canEdit(ctx, sales, options); - - expect(result).toEqual(false); - + await models.Sale.canEdit(ctx, sales, options); await tx.rollback(); } catch (e) { await tx.rollback(); - throw e; + error = e; } + + expect(error).toEqual( + new Error('It is not possible to modify sales that their articles are from Floramondo')); }); it('should return true if any of the sales is of isFloramondo and has the correct role', async() => { diff --git a/modules/ticket/back/methods/sale/updateConcept.js b/modules/ticket/back/methods/sale/updateConcept.js index 0730f85e2..dcd25dcbb 100644 --- a/modules/ticket/back/methods/sale/updateConcept.js +++ b/modules/ticket/back/methods/sale/updateConcept.js @@ -40,10 +40,7 @@ module.exports = Self => { try { const currentLine = await models.Sale.findById(id, null, myOptions); - const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions); - - if (!canEditSale) - throw new UserError(`Sale(s) blocked, please contact production`); + await models.Sale.canEdit(ctx, [id], myOptions); const line = await currentLine.updateAttributes({concept: newConcept}, myOptions); diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index 8f27e1af5..505de5180 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -66,9 +66,7 @@ module.exports = Self => { const sale = await models.Sale.findById(id, filter, myOptions); - const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions); - if (!canEditSale) - throw new UserError(`Sale(s) blocked, please contact production`); + await models.Sale.canEdit(ctx, [id], myOptions); const oldPrice = sale.price; const userId = ctx.req.accessToken.userId; diff --git a/modules/ticket/back/methods/sale/updateQuantity.js b/modules/ticket/back/methods/sale/updateQuantity.js index 8cf0720ce..d2927c65c 100644 --- a/modules/ticket/back/methods/sale/updateQuantity.js +++ b/modules/ticket/back/methods/sale/updateQuantity.js @@ -41,9 +41,7 @@ module.exports = Self => { } try { - const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions); - if (!canEditSale) - throw new UserError(`Sale(s) blocked, please contact production`); + await models.Sale.canEdit(ctx, [id], myOptions); const filter = { include: { diff --git a/modules/ticket/back/methods/state/editableStates.js b/modules/ticket/back/methods/state/editableStates.js index 2c90ac43b..115876f26 100644 --- a/modules/ticket/back/methods/state/editableStates.js +++ b/modules/ticket/back/methods/state/editableStates.js @@ -24,7 +24,7 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - let statesList = await models.State.find({where: filter.where}, myOptions); + let statesList = await models.State.find(filter, myOptions); const isProduction = await models.Account.hasRole(userId, 'production', myOptions); const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson', myOptions); const isAdministrative = await models.Account.hasRole(userId, 'administrative', myOptions); diff --git a/modules/ticket/back/methods/ticket/getTicketsAdvance.js b/modules/ticket/back/methods/ticket/getTicketsAdvance.js index 19571bb51..1e1646cba 100644 --- a/modules/ticket/back/methods/ticket/getTicketsAdvance.js +++ b/modules/ticket/back/methods/ticket/getTicketsAdvance.js @@ -50,14 +50,14 @@ module.exports = Self => { required: false }, { - arg: 'state', - type: 'string', + arg: 'isNotValidated', + type: 'boolean', description: 'Origin state', required: false }, { - arg: 'futureState', - type: 'string', + arg: 'futureIsNotValidated', + type: 'boolean', description: 'Destination state', required: false }, @@ -92,13 +92,23 @@ module.exports = Self => { case 'futureId': return {'f.futureId': value}; case 'ipt': - return {'f.ipt': value}; + return {or: + [ + {'f.ipt': {like: `%${value}%`}}, + {'f.ipt': null} + ] + }; case 'futureIpt': - return {'f.futureIpt': value}; - case 'state': - return {'f.stateCode': {like: `%${value}%`}}; - case 'futureState': - return {'f.futureStateCode': {like: `%${value}%`}}; + return {or: + [ + {'f.futureIpt': {like: `%${value}%`}}, + {'f.futureIpt': null} + ] + }; + case 'isNotValidated': + return {'f.isNotValidated': value}; + case 'futureIsNotValidated': + return {'f.futureIsNotValidated': value}; } }); diff --git a/modules/ticket/back/methods/ticket/specs/getTicketsAdvance.spec.js b/modules/ticket/back/methods/ticket/specs/getTicketsAdvance.spec.js index aab053127..7d3bc174d 100644 --- a/modules/ticket/back/methods/ticket/specs/getTicketsAdvance.spec.js +++ b/modules/ticket/back/methods/ticket/specs/getTicketsAdvance.spec.js @@ -29,7 +29,7 @@ describe('TicketFuture getTicketsAdvance()', () => { } }); - it('should return the tickets matching the origin grouped state', async() => { + it('should return the tickets matching the origin pending state', async() => { const tx = await models.Ticket.beginTransaction({}); try { @@ -39,7 +39,7 @@ describe('TicketFuture getTicketsAdvance()', () => { dateFuture: tomorrow, dateToAdvance: today, warehouseFk: 1, - state: 'OK' + futureIsNotValidated: true }; const ctx = {req: {accessToken: {userId: 9}}, args}; @@ -54,7 +54,7 @@ describe('TicketFuture getTicketsAdvance()', () => { } }); - it('should return the tickets matching the destination grouped state', async() => { + it('should return the tickets matching the destination pending state', async() => { const tx = await models.Ticket.beginTransaction({}); try { @@ -64,13 +64,13 @@ describe('TicketFuture getTicketsAdvance()', () => { dateFuture: tomorrow, dateToAdvance: today, warehouseFk: 1, - futureState: 'FREE' + isNotValidated: true }; const ctx = {req: {accessToken: {userId: 9}}, args}; const result = await models.Ticket.getTicketsAdvance(ctx, options); - expect(result.length).toBeGreaterThan(0); + expect(result.length).toEqual(0); await tx.rollback(); } catch (e) { @@ -89,7 +89,7 @@ describe('TicketFuture getTicketsAdvance()', () => { dateFuture: tomorrow, dateToAdvance: today, warehouseFk: 1, - ipt: 'Vertical' + ipt: 'V' }; const ctx = {req: {accessToken: {userId: 9}}, args}; @@ -114,7 +114,7 @@ describe('TicketFuture getTicketsAdvance()', () => { dateFuture: tomorrow, dateToAdvance: today, warehouseFk: 1, - tfIpt: 'Vertical' + tfIpt: 'V' }; const ctx = {req: {accessToken: {userId: 9}}, args}; diff --git a/modules/ticket/front/advance-search-panel/index.html b/modules/ticket/front/advance-search-panel/index.html index e8d5dc60d..dfe1f6b08 100644 --- a/modules/ticket/front/advance-search-panel/index.html +++ b/modules/ticket/front/advance-search-panel/index.html @@ -39,26 +39,18 @@ - - - {{name}} - - - - - {{name}} - - + + + + { - 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 = []; const filter = { diff --git a/modules/ticket/front/advance-search-panel/locale/es.yml b/modules/ticket/front/advance-search-panel/locale/es.yml index 3dce7dae5..4ea2fc737 100644 --- a/modules/ticket/front/advance-search-panel/locale/es.yml +++ b/modules/ticket/front/advance-search-panel/locale/es.yml @@ -1 +1,3 @@ Advance tickets: Adelantar tickets +Pending Origin: Pendiente origen +Pending Destination: Pendiente destino diff --git a/modules/ticket/front/advance/index.html b/modules/ticket/front/advance/index.html index f63c0fbf7..3dd52b909 100644 --- a/modules/ticket/front/advance/index.html +++ b/modules/ticket/front/advance/index.html @@ -32,8 +32,8 @@ - Origin - Destination + Origin + Destination @@ -43,19 +43,30 @@ check-field="checked"> + + ID Date - + IPT State - + + Liters + + + Stock + + + Lines + + Import @@ -64,7 +75,7 @@ Date - + IPT @@ -73,13 +84,10 @@ Liters - - Stock - Lines - + Import @@ -92,6 +100,13 @@ vn-click-stop> + + + + + {{::ticket.futureLiters | dashIfEmpty}} + {{::ticket.hasStock | dashIfEmpty}} + {{::ticket.futureLines | dashIfEmpty}} {{::(ticket.futureTotalWithVat ? ticket.futureTotalWithVat : 0) | currency: 'EUR': 2}} @@ -136,7 +154,6 @@ {{::ticket.liters | dashIfEmpty}} - {{::ticket.hasStock | dashIfEmpty}} {{::ticket.lines | dashIfEmpty}} diff --git a/modules/ticket/front/advance/index.js b/modules/ticket/front/advance/index.js index b770440f1..a29d2db97 100644 --- a/modules/ticket/front/advance/index.js +++ b/modules/ticket/front/advance/index.js @@ -1,5 +1,6 @@ import ngModule from '../module'; import Section from 'salix/components/section'; +import './style.scss'; export default class Controller extends Section { constructor($element, $) { @@ -128,6 +129,11 @@ export default class Controller extends Section { }); } + agencies(futureAgency, agency) { + return this.$t(`Origin agency`, {agency: futureAgency}) + + '
' + this.$t(`Destination agency`, {agency: agency}); + } + moveTicketsAdvance() { let ticketsToMove = []; this.checked.forEach(ticket => { @@ -157,6 +163,10 @@ export default class Controller extends Section { return {'liters': value}; case 'lines': return {'lines': value}; + case 'futureLiters': + return {'futureLiters': value}; + case 'futureLines': + return {'futureLines': value}; case 'ipt': return {'ipt': value}; case 'futureIpt': diff --git a/modules/ticket/front/advance/locale/es.yml b/modules/ticket/front/advance/locale/es.yml index b444fbdd3..da22cd433 100644 --- a/modules/ticket/front/advance/locale/es.yml +++ b/modules/ticket/front/advance/locale/es.yml @@ -4,3 +4,6 @@ Advance confirmation: ¿Desea adelantar {{checked}} tickets? Success: Tickets movidos correctamente Lines: Líneas Liters: Litros +Item Packing Type: Encajado +Origin agency: "Agencia origen: {{agency}}" +Destination agency: "Agencia destino: {{agency}}" diff --git a/modules/ticket/front/advance/style.scss b/modules/ticket/front/advance/style.scss new file mode 100644 index 000000000..8fa9de438 --- /dev/null +++ b/modules/ticket/front/advance/style.scss @@ -0,0 +1,7 @@ +@import "variables"; + +vn-ticket-advance{ + vn-icon { + color: #f7931e + } +} diff --git a/modules/ticket/front/basic-data/step-one/index.js b/modules/ticket/front/basic-data/step-one/index.js index f532265e2..99782de44 100644 --- a/modules/ticket/front/basic-data/step-one/index.js +++ b/modules/ticket/front/basic-data/step-one/index.js @@ -75,8 +75,10 @@ class Controller extends Component { } set shipped(value) { + if (new Date(this.ticket.shipped).toDateString() != value.toDateString()) + value.setHours(0, 0, 0, 0); + this.ticket.shipped = value; - this.ticket.shipped.setHours(0, 0, 0, 0); this.getLanded({ shipped: value, addressFk: this.ticket.addressFk, diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 97f6a2a81..fe259cf87 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -22,6 +22,7 @@ disabled="!$ctrl.isEditable" label="State" value-field="code" + fields="['id', 'name', 'alertLevel', 'code']" url="States/editableStates" on-change="$ctrl.changeState(value)"> diff --git a/modules/ticket/front/summary/index.html b/modules/ticket/front/summary/index.html index fe49a301f..af44ed67c 100644 --- a/modules/ticket/front/summary/index.html +++ b/modules/ticket/front/summary/index.html @@ -1,6 +1,6 @@
- - Ticket #{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}} + Ticket #{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}} ({{$ctrl.summary.client.id}}) - {{$ctrl.summary.nickname}} - -
- - {{$ctrl.summary.client.salesPersonUser.name}} - @@ -47,11 +48,11 @@ {{$ctrl.summary.zone.name}} - - {{$ctrl.summary.routeFk}} @@ -66,17 +67,17 @@ - - -

- Sale @@ -146,13 +147,13 @@ vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claimBeginning.claimFk}}"> - - @@ -170,22 +171,22 @@ - {{sale.itemFk | zeroFill:6}} - {{::sale.visible}} - {{::sale.available}} @@ -216,7 +217,7 @@

- Packages @@ -241,7 +242,7 @@

- Service @@ -276,7 +277,7 @@

- Purchase request @@ -304,7 +305,7 @@ {{::request.quantity}} {{::request.price}} - @@ -336,9 +337,9 @@ - - - \ No newline at end of file + diff --git a/package.json b/package.json index 46b7d4ffc..f9828624d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.04.01", + "version": "23.06.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", diff --git a/print/templates/reports/receipt/receipt.html b/print/templates/reports/receipt/receipt.html index e0bab5ecf..be0bfc375 100644 --- a/print/templates/reports/receipt/receipt.html +++ b/print/templates/reports/receipt/receipt.html @@ -4,9 +4,9 @@

{{$t('title')}}

- Recibo de {{client.socialName}}, la cantidad de - {{receipt.amountPaid}} € en concepto de 'entrega a cuenta', quedando pendiente en - la cuenta del cliente un saldo de {{receipt.amountUnpaid}} €. + Recibo #{{receipt.id}} de {{client.socialName}}, + la cantidad de {{receipt.amountPaid}} € + en concepto de 'entrega a cuenta'.

diff --git a/print/templates/reports/receipt/sql/receipt.sql b/print/templates/reports/receipt/sql/receipt.sql index b8f5a4112..4094d25b3 100644 --- a/print/templates/reports/receipt/sql/receipt.sql +++ b/print/templates/reports/receipt/sql/receipt.sql @@ -1,11 +1,10 @@ -SELECT - r.id, - r.amountPaid, - cr.amount AS amountUnpaid, - r.payed, +SELECT + r.id, + r.amountPaid, + r.payed, r.companyFk FROM receipt r JOIN client c ON c.id = r.clientFk JOIN vn.clientRisk cr ON cr.clientFk = c.id AND cr.companyFk = r.companyFk -WHERE r.id = ? \ No newline at end of file +WHERE r.id = ?