diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dbc270b9..0573a6790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,17 @@ 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). +## [2340.01] - 2023-10-05 + +### Added +### Changed + +### Fixed + ## [2338.01] - 2023-09-21 ### Added - +- (Ticket -> Servicios) Se pueden abonar servicios ### Changed - (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia diff --git a/back/model-config.json b/back/model-config.json index b88956dee..ebc0e321b 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -15,6 +15,9 @@ }, "Bank": { "dataSource": "vn" + }, + "Buyer": { + "dataSource": "vn" }, "Campaign": { "dataSource": "vn" diff --git a/back/models/buyer.json b/back/models/buyer.json new file mode 100644 index 000000000..a17d3b538 --- /dev/null +++ b/back/models/buyer.json @@ -0,0 +1,28 @@ +{ + "name": "Buyer", + "base": "VnModel", + "options": { + "mysql": { + "table": "buyer" + } + }, + "properties": { + "userFk": { + "type": "number", + "required": true, + "id": true + }, + "nickname": { + "type": "string", + "required": true + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "employee", + "permission": "ALLOW" + } + ] +} diff --git a/db/changes/233601/00-createClaimReader.sql b/db/changes/233601/00-createClaimReader.sql index 666bf232e..e913c0ed9 100644 --- a/db/changes/233601/00-createClaimReader.sql +++ b/db/changes/233601/00-createClaimReader.sql @@ -1,4 +1,4 @@ -INSERT INTO `account`.`role` (`id`, `name`, `description`, `hasLogin`) +INSERT INTO `account`.`role` (`name`, `description`, `hasLogin`) VALUES ('claimViewer','Trabajadores que consulta las reclamaciones ',1); INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`) @@ -10,7 +10,7 @@ INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`) 'buyer', 'deliveryBoss', 'handmadeBoss' - ) + ); DELETE FROM `salix`.`ACL` WHERE `model`= 'claim' @@ -28,5 +28,4 @@ INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`princip INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) VALUES ('Claim','findById','READ','ALLOW','ROLE','claimViewer'); INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) - VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer'); - + VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer'); \ No newline at end of file diff --git a/db/changes/233801/00-firstScript.sql b/db/changes/233801/00-firstScript.sql new file mode 100644 index 000000000..47b96b3bc --- /dev/null +++ b/db/changes/233801/00-firstScript.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`province` ADD CONSTRAINT `countryName_UN` UNIQUE KEY (`countryFk`,`name`); diff --git a/db/changes/233401/01-deviceLog_acl.sql b/db/changes/233801/01-deviceLog_acl.sql similarity index 100% rename from db/changes/233401/01-deviceLog_acl.sql rename to db/changes/233801/01-deviceLog_acl.sql diff --git a/db/changes/234001/.gitkeep b/db/changes/234001/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index fe3633723..1b18eb866 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -671,8 +671,8 @@ export default { firstAddServiceTypeButton: 'vn-ticket-service vn-icon-button[vn-tooltip="New service type"]', firstServiceType: 'vn-ticket-service vn-autocomplete[ng-model="service.ticketServiceTypeFk"]', firstQuantity: 'vn-ticket-service vn-input-number[ng-model="service.quantity"]', - firstPrice: 'vn-ticket-service vn-horizontal:nth-child(1) vn-input-number[ng-model="service.price"]', - fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]', + firstPrice: 'vn-ticket-service vn-horizontal:nth-child(2) vn-input-number[ng-model="service.price"]', + fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(2) vn-icon-button[icon="delete"]', newServiceTypeName: '.vn-dialog.shown vn-textfield[ng-model="newServiceType.name"]', serviceLine: 'vn-ticket-service > form > vn-card > vn-one:nth-child(2) > vn-horizontal', saveServiceButton: 'button[type=submit]', diff --git a/front/core/lib/validator.js b/front/core/lib/validator.js index b0b8f7389..51134f30a 100644 --- a/front/core/lib/validator.js +++ b/front/core/lib/validator.js @@ -5,6 +5,10 @@ export const validators = { if (validator.isEmpty(value ? String(value) : '')) throw new Error(_($translate, `Value can't be empty`)); }, + negative: $translate => { + if (validator < 0) + throw new Error(_($translate, `Negative numbers are not allowed. Please enter a valid number.`)); + }, absence: ($translate, value) => { if (!validator.isEmpty(value)) throw new Error(_($translate, `Value should be empty`)); @@ -104,9 +108,8 @@ export function checkNull($translate, value, conf) { export function _($translate, text, params = []) { text = $translate.instant(text); - for (let i = 0; i < params.length; i++) { + for (let i = 0; i < params.length; i++) text = text.replace('%s', params[i]); - } return text; } diff --git a/front/core/locale/es.yml b/front/core/locale/es.yml index e9c2311b4..96c34d98c 100644 --- a/front/core/locale/es.yml +++ b/front/core/locale/es.yml @@ -16,6 +16,7 @@ Value can't be empty: El valor no puede estar vacío Value should be empty: El valor debe estar vacío Value should be integer: El valor debe ser entero Value should be a number: El valor debe ser numérico +Negative numbers are not allowed. Please enter a valid number: No se permiten números negativos. Por favor, ingrese un número válido Invalid value: Valor incorrecto Value can't be blank: El valor no puede estar en blanco Value can't be null: El valor no puede ser nulo diff --git a/loopback/locale/es.json b/loopback/locale/es.json index ccd63e0ff..73a3920d2 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -307,7 +307,7 @@ "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado", "This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s", - "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", + "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", "You don't have enough privileges.": "No tienes suficientes permisos.", diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index 15a55ec8c..c622913bb 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -255,7 +255,7 @@ value="{{$ctrl.summary.averageInvoiced.invoiced | currency: 'EUR':2}}"> + value="{{$ctrl.claimingRate($ctrl.summary.claimsRatio.claimingRate / 100) | percentage}}"> diff --git a/modules/supplier/back/models/supplier-account.js b/modules/supplier/back/models/supplier-account.js index 691e72580..7c68e2c98 100644 --- a/modules/supplier/back/models/supplier-account.js +++ b/modules/supplier/back/models/supplier-account.js @@ -13,7 +13,7 @@ module.exports = Self => { const bankEntity = await Self.app.models.BankEntity.findById(this.bankEntityFk); const filter = { fields: ['code'], - where: {id: bankEntity.countryFk} + where: {id: bankEntity?.countryFk} }; const country = await Self.app.models.Country.findOne(filter); diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js index 12f240ae2..3c41aab1e 100644 --- a/modules/ticket/back/methods/sale/refund.js +++ b/modules/ticket/back/methods/sale/refund.js @@ -5,8 +5,7 @@ module.exports = Self => { accepts: [ { arg: 'salesIds', - type: ['number'], - required: true + type: ['number'] }, { arg: 'servicesIds', @@ -41,6 +40,7 @@ module.exports = Self => { myOptions.transaction = tx; } + let refundTicket = null; try { const refundAgencyMode = await models.AgencyMode.findOne({ include: { @@ -55,42 +55,55 @@ module.exports = Self => { const refoundZoneId = refundAgencyMode.zones()[0].id; - const salesFilter = { - where: {id: {inq: salesIds}}, - include: { - relation: 'components', - scope: { - fields: ['saleFk', 'componentFk', 'value'] + if (salesIds) { + const salesFilter = { + where: {id: {inq: salesIds}}, + include: { + relation: 'components', + scope: { + fields: ['saleFk', 'componentFk', 'value'] + } } + }; + const sales = await models.Sale.find(salesFilter, myOptions); + const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))]; + + const now = Date.vnNew(); + const [firstTicketId] = ticketsIds; + + // eslint-disable-next-line max-len + refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions); + + for (const sale of sales) { + const createdSale = await models.Sale.create({ + ticketFk: refundTicket.id, + itemFk: sale.itemFk, + quantity: - sale.quantity, + concept: sale.concept, + price: sale.price, + discount: sale.discount, + }, myOptions); + + const components = sale.components(); + for (const component of components) + component.saleFk = createdSale.id; + + await models.SaleComponent.create(components, myOptions); } - }; - const sales = await models.Sale.find(salesFilter, myOptions); - const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))]; - - let [firstTicketId] = ticketsIds; - if (!firstTicketId) { - [ticketServices] = await models.TicketService.find({where: {id: {inq: servicesIds}}}, myOptions); - firstTicketId = ticketServices.ticketFk; } - const now = Date.vnNew(); - const refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions); + if (!refundTicket) { + const servicesFilter = { + where: {id: {inq: servicesIds}} + }; + const services = await models.TicketService.find(servicesFilter, myOptions); + const ticketsIds = [...new Set(services.map(service => service.ticketFk))]; - for (const sale of sales) { - const createdSale = await models.Sale.create({ - ticketFk: refundTicket.id, - itemFk: sale.itemFk, - quantity: - sale.quantity, - concept: sale.concept, - price: sale.price, - discount: sale.discount, - }, myOptions); + const now = Date.vnNew(); + const [firstTicketId] = ticketsIds; - const components = sale.components(); - for (const component of components) - component.saleFk = createdSale.id; - - await models.SaleComponent.create(components, myOptions); + // eslint-disable-next-line max-len + refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions); } if (servicesIds && servicesIds.length > 0) { @@ -101,8 +114,8 @@ module.exports = Self => { for (const service of services) { await models.TicketService.create({ description: service.description, - quantity: - service.quantity, - price: service.price, + quantity: service.quantity, + price: - service.price, taxClassFk: service.taxClassFk, ticketFk: refundTicket.id, ticketServiceTypeFk: service.ticketServiceTypeFk, diff --git a/modules/ticket/front/services/index.html b/modules/ticket/front/services/index.html index bb5505ce6..bc288a8a2 100644 --- a/modules/ticket/front/services/index.html +++ b/modules/ticket/front/services/index.html @@ -1,4 +1,4 @@ -
- + + + + + + + + rule="TicketService" + min="0"> - + - - - - \ No newline at end of file + diff --git a/modules/ticket/front/services/index.js b/modules/ticket/front/services/index.js index 4866fd8bb..d8c209ea4 100644 --- a/modules/ticket/front/services/index.js +++ b/modules/ticket/front/services/index.js @@ -6,6 +6,8 @@ class Controller extends Section { $onInit() { this.services = []; this.getDefaultTaxClass(); + this.isDataSaved = false; + this.checkeds = []; } getDefaultTaxClass() { @@ -49,6 +51,26 @@ class Controller extends Section { .then(() => this.$.model.refresh()) .then(() => this.$.watcher.notifySaved()); } + + createRefund() { + if (!this.checkeds.length) return; + + const params = {servicesIds: this.checkeds, withWarehouse: false}; + const query = 'Sales/refund'; + this.$http.post(query, params).then(res => { + const refundTicket = res.data; + this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { + ticketId: refundTicket.id + })); + this.$state.go('ticket.card.sale', {id: refundTicket.id}); + }); + } + + addChecked(id) { + if (this.checkeds.includes(id)) + return this.checkeds = this.checkeds.filter(check => check != id); + this.checkeds.push(id); + } } ngModule.vnComponent('vnTicketService', { diff --git a/modules/ticket/front/services/index.spec.js b/modules/ticket/front/services/index.spec.js index affe8a6e7..9293d4c65 100644 --- a/modules/ticket/front/services/index.spec.js +++ b/modules/ticket/front/services/index.spec.js @@ -19,12 +19,11 @@ describe('Ticket component vnTicketService', () => { describe('getDefaultTaxClass', () => { it('should set the default tax class in the controller', () => { - $httpBackend.whenRoute('GET', `TaxClasses/findOne`) - .respond({ - id: 4000, - name: 'Whatever', - code: 'GG' - }); + $httpBackend.whenRoute('GET', `TaxClasses/findOne`).respond({ + id: 4000, + name: 'Whatever', + code: 'GG', + }); controller.getDefaultTaxClass(); $httpBackend.flush(); @@ -49,15 +48,15 @@ describe('Ticket component vnTicketService', () => { it('should set the description of the selected service upon service type creation', () => { const service = { id: 1, - quantity: 10 + quantity: 10, }; $scope.newServiceType = { - name: 'Totally new stuff' + name: 'Totally new stuff', }; $httpBackend.when('POST', 'TicketServiceTypes').respond({ id: 4001, - name: 'Totally new stuff' + name: 'Totally new stuff', }); controller.onNewServiceTypeAccept(service); $httpBackend.flush(); @@ -65,4 +64,20 @@ describe('Ticket component vnTicketService', () => { expect(service.ticketServiceTypeFk).toEqual(4001); }); }); + + describe('addChecked', () => { + it('should add an item to the checkeds array', () => { + controller.checkeds = []; + controller.addChecked(1); + + expect(controller.checkeds).toEqual([1]); + }); + + it('should remove an item if it is already in the checkeds array', () => { + controller.checkeds = [1, 2, 3]; + controller.addChecked(2); + + expect(controller.checkeds).toEqual([1, 3]); + }); + }); }); diff --git a/modules/ticket/front/services/locale/es.yml b/modules/ticket/front/services/locale/es.yml index 08788fc09..08e6c968c 100644 --- a/modules/ticket/front/services/locale/es.yml +++ b/modules/ticket/front/services/locale/es.yml @@ -2,4 +2,6 @@ Service: Servicios Tax class: Tipo IVA Add service: Añadir servicio Remove service: Quitar servicio -New service type: Nuevo tipo de servicio \ No newline at end of file +New service type: Nuevo tipo de servicio +Pay: Abonar +To create services with negative amounts mark the service on the source ticket and press the pay button.: Para crear sevicios con cantidades negativas marcar servicio en el ticket origen y apretar el boton abonar. \ No newline at end of file diff --git a/package.json b/package.json index 44a651af4..d250071a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.38.01", + "version": "23.40.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", diff --git a/print/templates/email/printer-setup/locale/es.yml b/print/templates/email/printer-setup/locale/es.yml index b96d6d5f4..39e83b1a8 100644 --- a/print/templates/email/printer-setup/locale/es.yml +++ b/print/templates/email/printer-setup/locale/es.yml @@ -8,8 +8,8 @@ description: https://www.youtube.com/watch?v=qhb0kgQF3o8. También necesitarás el GoLabel, el programa para imprimir las cintas. - downloadFrom: Puedes descargarlo desde este enlace https://godex.s3-accelerate.amazonaws.com/_6f5glRrVhQAEBGhdUsqJA.file?v01 + downloadFrom: Puedes descargarlo desde este enlace https://cdn.verdnatura.es/public/GoLabel.zip downloadDriver: En este enlace puedes descargar el driver de la impresora https://es.seagullscientific.com/support/downloads/drivers/godex/download/ sections: @@ -40,4 +40,4 @@ help: Cualquier duda que te surja, no dudes en consultarla, ¡estamos pa atenderte! salesPersonName: Soy tu comercial y mi nombre es salesPersonPhone: Teléfono y whatsapp -salesPersonEmail: Dirección de e-mail \ No newline at end of file +salesPersonEmail: Dirección de e-mail