diff --git a/db/changes/10480-june/00-ACL.sql b/db/changes/10480-june/00-ACL.sql index 2e863be34..b13e56e21 100644 --- a/db/changes/10480-june/00-ACL.sql +++ b/db/changes/10480-june/00-ACL.sql @@ -1,6 +1,21 @@ INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId) VALUES + ('InvoiceOut','refund','WRITE','ALLOW','ROLE','invoicing'), + ('InvoiceOut','refund','WRITE','ALLOW','ROLE','salesAssistant'), + ('InvoiceOut','refund','WRITE','ALLOW','ROLE','claimManager'), + ('Ticket','refund','WRITE','ALLOW','ROLE','invoicing'), + ('Ticket','refund','WRITE','ALLOW','ROLE','salesAssistant'), + ('Ticket','refund','WRITE','ALLOW','ROLE','claimManager'), + ('Sale','refund','WRITE','ALLOW','ROLE','salesAssistant'), + ('Sale','refund','WRITE','ALLOW','ROLE','claimManager'), + ('TicketRefund','*','WRITE','ALLOW','ROLE','invoicing'), ('ClaimObservation','*','WRITE','ALLOW','ROLE','salesPerson'), ('ClaimObservation','*','READ','ALLOW','ROLE','salesPerson'), ('Client','setPassword','WRITE','ALLOW','ROLE','salesPerson'), ('Client','updateUser','WRITE','ALLOW','ROLE','salesPerson'); + +DELETE FROM `salix`.`ACL` WHERE id=313; + +UPDATE `salix`.`ACL` + SET principalId='invoicing' + WHERE id=297; \ No newline at end of file diff --git a/db/changes/10480-june/00-ticketRefund_beforeUpsert.sql b/db/changes/10480-june/00-ticketRefund_beforeUpsert.sql new file mode 100644 index 000000000..e6506c5d7 --- /dev/null +++ b/db/changes/10480-june/00-ticketRefund_beforeUpsert.sql @@ -0,0 +1,21 @@ +DROP PROCEDURE IF EXISTS `vn`.`ticketRefund_beforeUpsert`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketRefund_beforeUpsert`(vRefundTicketFk INT, vOriginalTicketFk INT) +BEGIN + DECLARE vAlreadyExists BOOLEAN DEFAULT FALSE; + + IF vRefundTicketFk = vOriginalTicketFk THEN + CALL util.throw('Original ticket and refund ticket has same id'); + END IF; + + SELECT COUNT(*) INTO vAlreadyExists + FROM ticketRefund + WHERE originalTicketFk = vOriginalTicketFk; + + IF vAlreadyExists > 0 THEN + CALL util.throw('This ticket is already a refund'); + END IF; +END$$ +DELIMITER ; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 6f1516d5c..239137a9c 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2230,6 +2230,10 @@ INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `weekDays`) (8, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun'), (10, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun'); +INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `started`, `ended`) + VALUES + (9, 'range', DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR)); + INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`) VALUES (1106, CONCAT(util.VN_CURDATE(), ' 07:00'), TRUE, 'in'), diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 2c0e8231a..015b620d1 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -37102,6 +37102,7 @@ CREATE TABLE `ticket` ( `zoneBonus` decimal(10,2) DEFAULT NULL, `totalWithVat` decimal(10,2) DEFAULT NULL COMMENT 'cache calculada del total con iva', `totalWithoutVat` decimal(10,2) DEFAULT NULL COMMENT 'cache calculada del total sin iva', + `weight` decimal(10,2) DEFAULT NULL COMMENT 'En caso de informar, se utilizará su valor para calcular el peso de la factura', PRIMARY KEY (`id`), KEY `Id_Cliente` (`clientFk`), KEY `Id_Consigna` (`addressFk`), @@ -37124,7 +37125,7 @@ CREATE TABLE `ticket` ( CONSTRAINT `ticket_ibfk_9` FOREIGN KEY (`routeFk`) REFERENCES `route` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `tickets_fk11` FOREIGN KEY (`collectionFk__`) REFERENCES `collection` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT `tickets_zone_fk` FOREIGN KEY (`zoneFk`) REFERENCES `zone` (`id`) ON UPDATE CASCADE -) ENGINE=InnoDBDEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB AUTO_INCREMENT=3750016 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; @@ -43256,6 +43257,40 @@ BEGIN ) t1; RETURN totalAmount; +END ;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ; +/*!50003 DROP FUNCTION IF EXISTS `invoiceOutAmount` */; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8mb4 */ ; +/*!50003 SET character_set_results = utf8mb4 */ ; +/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ; +DELIMITER ;; +CREATE DEFINER=`root`@`localhost` FUNCTION `invoiceOut_getWeight`(vInvoice VARCHAR(15)) RETURNS decimal(10,2) + READS SQL DATA +BEGIN + DECLARE vTotalWeight DECIMAL(10,2); + + SELECT SUM(CAST(IFNULL(i.stems, 1) * s.quantity * + IF(ic.grams, ic.grams, i.density * ic.cm3delivery / 1000) + / 1000 AS DECIMAL(10,2))) + INTO vTotalWeight + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN itemCost ic ON ic.itemFk = i.id + AND ic.warehouseFk = t.warehouseFk + WHERE t.refFk = vInvoice + AND i.intrastatFk ; + + RETURN vTotalWeight; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; diff --git a/e2e/paths/05-ticket/13_services.spec.js b/e2e/paths/05-ticket/13_services.spec.js index 03e57b588..50df23582 100644 --- a/e2e/paths/05-ticket/13_services.spec.js +++ b/e2e/paths/05-ticket/13_services.spec.js @@ -23,9 +23,9 @@ describe('Ticket services path', () => { await page.waitForClassPresent(selectors.ticketService.firstAddServiceTypeButton, 'disabled'); await page.waitToClick(selectors.ticketService.addServiceButton); await page.waitForSelector(selectors.ticketService.firstAddServiceTypeButton); - const result = await page.isDisabled(selectors.ticketService.firstAddServiceTypeButton); + const disabled = await page.isDisabled(selectors.ticketService.firstAddServiceTypeButton); - expect(result).toBe(true); + expect(disabled).toBe(true); }); it('should receive an error if you attempt to save a service without access rights', async() => { diff --git a/modules/invoiceIn/front/tax/index.spec.js b/modules/invoiceIn/front/tax/index.spec.js index c62ada9ca..52114afe5 100644 --- a/modules/invoiceIn/front/tax/index.spec.js +++ b/modules/invoiceIn/front/tax/index.spec.js @@ -1,7 +1,6 @@ import './index.js'; import watcher from 'core/mocks/watcher'; import crudModel from 'core/mocks/crud-model'; -const UserError = require('vn-loopback/util/user-error'); describe('InvoiceIn', () => { describe('Component tax', () => { diff --git a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js index 116e8b3fc..c2fdbcbba 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js +++ b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js @@ -61,7 +61,7 @@ module.exports = Self => { responseType: 'stream', params: { authorization: auth.id, - invoiceId: id + refFk: invoiceOut.ref } }).then(async response => { const issued = invoiceOut.issued; diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index f1138dd51..19dea5b1a 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -9,7 +9,7 @@ module.exports = Self => { accepts: [ { arg: 'id', - type: 'String', + type: 'string', description: 'The invoice id', http: {source: 'path'} } @@ -21,16 +21,16 @@ module.exports = Self => { root: true }, { arg: 'Content-Type', - type: 'String', + type: 'string', http: {target: 'header'} }, { arg: 'Content-Disposition', - type: 'String', + type: 'string', http: {target: 'header'} } ], http: { - path: `/:id/download`, + path: '/:id/download', verb: 'GET' } }); diff --git a/modules/invoiceOut/back/methods/invoiceOut/refund.js b/modules/invoiceOut/back/methods/invoiceOut/refund.js new file mode 100644 index 000000000..7ad6b03ec --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/refund.js @@ -0,0 +1,49 @@ +module.exports = Self => { + Self.remoteMethod('refund', { + description: 'Create refund tickets with sales and services if provided', + accessType: 'WRITE', + accepts: [{ + arg: 'ref', + type: 'string', + description: 'The invoice reference' + }], + returns: { + type: ['number'], + root: true + }, + http: { + path: '/refund', + verb: 'post' + } + }); + + Self.refund = async(ref, options) => { + 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; + } + + try { + const filter = {where: {refFk: ref}}; + const tickets = await models.Ticket.find(filter, myOptions); + + const ticketsIds = tickets.map(ticket => ticket.id); + + const refundedTickets = await models.Ticket.refund(ticketsIds, myOptions); + + if (tx) await tx.commit(); + + return refundedTickets; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/refund.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/refund.spec.js new file mode 100644 index 000000000..628318d42 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/refund.spec.js @@ -0,0 +1,28 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('InvoiceOut refund()', () => { + const userId = 5; + const activeCtx = { + accessToken: {userId: userId}, + }; + + it('should return the ids for the created refund tickets', async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + const result = await models.InvoiceOut.refund('T1111111', options); + + expect(result.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 3b2822ada..c8c97702f 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -8,4 +8,5 @@ module.exports = Self => { require('../methods/invoiceOut/createPdf')(Self); require('../methods/invoiceOut/createManualInvoice')(Self); require('../methods/invoiceOut/globalInvoicing')(Self); + require('../methods/invoiceOut/refund')(Self); }; diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html index ef4c9a62e..1c0919288 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.html +++ b/modules/invoiceOut/front/descriptor-menu/index.html @@ -80,6 +80,8 @@ ng-click="refundConfirmation.show()" name="refundInvoice" vn-tooltip="Create a single ticket with all the content of the current invoice" + vn-acl="invoicing, claimManager, salesAssistant" + vn-acl-action="remove" translate> Refund diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js index b884e50cb..2b6d90ebf 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.js +++ b/modules/invoiceOut/front/descriptor-menu/index.js @@ -84,7 +84,7 @@ class Controller extends Section { showCsvInvoice() { this.vnReport.showCsv('invoice', { recipientId: this.invoiceOut.client.id, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } @@ -95,7 +95,7 @@ class Controller extends Section { return this.vnEmail.send('invoice', { recipientId: this.invoiceOut.client.id, recipient: $data.email, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } @@ -106,43 +106,23 @@ class Controller extends Section { return this.vnEmail.sendCsv('invoice', { recipientId: this.invoiceOut.client.id, recipient: $data.email, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } showExportationLetter() { this.vnReport.show('exportation', { recipientId: this.invoiceOut.client.id, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } - async refundInvoiceOut() { - let filter = { - where: {refFk: this.invoiceOut.ref} - }; - const tickets = await this.$http.get('Tickets', {filter}); - this.tickets = tickets.data; - this.ticketsIds = []; - for (let ticket of this.tickets) - this.ticketsIds.push(ticket.id); - - filter = { - where: {ticketFk: {inq: this.ticketsIds}} - }; - const sales = await this.$http.get('Sales', {filter}); - this.sales = sales.data; - - const ticketServices = await this.$http.get('TicketServices', {filter}); - this.services = ticketServices.data; - - const params = { - sales: this.sales, - services: this.services - }; - const query = `Sales/refund`; - return this.$http.post(query, params).then(res => { - this.$state.go('ticket.card.sale', {id: res.data}); + refundInvoiceOut() { + const query = 'InvoiceOuts/refund'; + const params = {ref: this.invoiceOut.ref}; + this.$http.post(query, params).then(res => { + const ticketIds = res.data.map(ticket => ticket.id).join(', '); + this.vnApp.showSuccess(this.$t('The following refund tickets have been created', {ticketIds})); }); } } diff --git a/modules/invoiceOut/front/descriptor-menu/index.spec.js b/modules/invoiceOut/front/descriptor-menu/index.spec.js index c84c97a57..da7c87894 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.spec.js +++ b/modules/invoiceOut/front/descriptor-menu/index.spec.js @@ -6,7 +6,8 @@ describe('vnInvoiceOutDescriptorMenu', () => { let $httpParamSerializer; const invoiceOut = { id: 1, - client: {id: 1101} + client: {id: 1101}, + ref: 'T1111111' }; beforeEach(ngModule('invoiceOut')); @@ -15,14 +16,17 @@ describe('vnInvoiceOutDescriptorMenu', () => { $httpBackend = _$httpBackend_; $httpParamSerializer = _$httpParamSerializer_; controller = $componentController('vnInvoiceOutDescriptorMenu', {$element: null}); + controller.invoiceOut = { + id: 1, + ref: 'T1111111', + client: {id: 1101} + }; })); describe('createPdfInvoice()', () => { it('should make a query to the createPdf() endpoint and show a success snackbar', () => { jest.spyOn(controller.vnApp, 'showSuccess'); - controller.invoiceOut = invoiceOut; - $httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond(); $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond(); controller.createPdfInvoice(); @@ -36,11 +40,9 @@ describe('vnInvoiceOutDescriptorMenu', () => { it('should make a query to the csv invoice download endpoint and show a message snackbar', () => { jest.spyOn(window, 'open').mockReturnThis(); - controller.invoiceOut = invoiceOut; - const expectedParams = { - invoiceId: invoiceOut.id, - recipientId: invoiceOut.client.id + recipientId: invoiceOut.client.id, + refFk: invoiceOut.ref }; const serializedParams = $httpParamSerializer(expectedParams); const expectedPath = `api/csv/invoice/download?${serializedParams}`; @@ -52,7 +54,6 @@ describe('vnInvoiceOutDescriptorMenu', () => { describe('deleteInvoiceOut()', () => { it(`should make a query and call showSuccess()`, () => { - controller.invoiceOut = invoiceOut; controller.$state.reload = jest.fn(); jest.spyOn(controller.vnApp, 'showSuccess'); @@ -65,7 +66,6 @@ describe('vnInvoiceOutDescriptorMenu', () => { }); it(`should make a query and call showSuccess() after state.go if the state wasn't in invoiceOut module`, () => { - controller.invoiceOut = invoiceOut; jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); jest.spyOn(controller.vnApp, 'showSuccess'); controller.$state.current.name = 'invoiceOut.card.something'; @@ -83,13 +83,11 @@ describe('vnInvoiceOutDescriptorMenu', () => { it('should make a query to the email invoice endpoint and show a message snackbar', () => { jest.spyOn(controller.vnApp, 'showMessage'); - controller.invoiceOut = invoiceOut; - const $data = {email: 'brucebanner@gothamcity.com'}; const expectedParams = { - invoiceId: invoiceOut.id, recipient: $data.email, - recipientId: invoiceOut.client.id + recipientId: invoiceOut.client.id, + refFk: invoiceOut.ref }; const serializedParams = $httpParamSerializer(expectedParams); @@ -105,13 +103,11 @@ describe('vnInvoiceOutDescriptorMenu', () => { it('should make a query to the csv invoice send endpoint and show a message snackbar', () => { jest.spyOn(controller.vnApp, 'showMessage'); - controller.invoiceOut = invoiceOut; - const $data = {email: 'brucebanner@gothamcity.com'}; const expectedParams = { - invoiceId: invoiceOut.id, recipient: $data.email, - recipientId: invoiceOut.client.id + recipientId: invoiceOut.client.id, + refFk: invoiceOut.ref }; const serializedParams = $httpParamSerializer(expectedParams); @@ -123,33 +119,16 @@ describe('vnInvoiceOutDescriptorMenu', () => { }); }); - // #4084 review with Juan - xdescribe('refundInvoiceOut()', () => { - it('should make a query and go to ticket.card.sale', () => { - controller.$state.go = jest.fn(); + describe('refundInvoiceOut()', () => { + it('should make a query and show a success message', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + const params = {ref: controller.invoiceOut.ref}; - const invoiceOut = { - id: 1, - ref: 'T1111111' - }; - controller.invoiceOut = invoiceOut; - const tickets = [{id: 1}]; - const sales = [{id: 1}]; - const services = [{id: 2}]; - - $httpBackend.expectGET(`Tickets`).respond(tickets); - $httpBackend.expectGET(`Sales`).respond(sales); - $httpBackend.expectGET(`TicketServices`).respond(services); - - const expectedParams = { - sales: sales, - services: services - }; - $httpBackend.expectPOST(`Sales/refund`, expectedParams).respond(); + $httpBackend.expectPOST(`InvoiceOuts/refund`, params).respond([{id: 1}, {id: 2}]); controller.refundInvoiceOut(); $httpBackend.flush(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: undefined}); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); }); }); }); diff --git a/modules/invoiceOut/front/descriptor-menu/locale/en.yml b/modules/invoiceOut/front/descriptor-menu/locale/en.yml new file mode 100644 index 000000000..d299155d7 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/locale/en.yml @@ -0,0 +1 @@ +The following refund tickets have been created: "The following refund tickets have been created: {{ticketIds}}" diff --git a/modules/invoiceOut/front/descriptor-menu/locale/es.yml b/modules/invoiceOut/front/descriptor-menu/locale/es.yml index 8949f1f91..488c1a3f8 100644 --- a/modules/invoiceOut/front/descriptor-menu/locale/es.yml +++ b/modules/invoiceOut/front/descriptor-menu/locale/es.yml @@ -17,3 +17,4 @@ Create a single ticket with all the content of the current invoice: Crear un tic Regenerate PDF invoice: Regenerar PDF factura The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado The email can't be empty: El correo no puede estar vacío +The following refund tickets have been created: "Se han creado los siguientes tickets de abono: {{ticketIds}}" diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js index 703fb4ba7..66c0550e0 100644 --- a/modules/ticket/back/methods/sale/refund.js +++ b/modules/ticket/back/methods/sale/refund.js @@ -1,23 +1,20 @@ -const UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { - Self.remoteMethodCtx('refund', { - description: 'Create ticket refund with lines and services changing the sign to the quantites', + Self.remoteMethod('refund', { + description: 'Create refund tickets with sales and services if provided', accessType: 'WRITE', - accepts: [{ - arg: 'sales', - description: 'The sales', - type: ['object'], - required: false - }, - { - arg: 'services', - type: ['object'], - required: false, - description: 'The services' - }], + accepts: [ + { + arg: 'salesIds', + type: ['number'], + required: true + }, + { + arg: 'servicesIds', + type: ['number'] + }, + ], returns: { - type: 'number', + type: ['number'], root: true }, http: { @@ -26,7 +23,8 @@ module.exports = Self => { } }); - Self.refund = async(ctx, sales, services, options) => { + Self.refund = async(salesIds, servicesIds, options) => { + const models = Self.app.models; const myOptions = {}; let tx; @@ -39,56 +37,103 @@ module.exports = Self => { } try { - const userId = ctx.req.accessToken.userId; + const refundAgencyMode = await models.AgencyMode.findOne({ + include: { + relation: 'zones', + scope: { + limit: 1, + field: ['id', 'name'] + } + }, + where: {code: 'refund'} + }, myOptions); - const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager'); - const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant'); - const hasValidRole = isClaimManager || isSalesAssistant; + const refoundZoneId = refundAgencyMode.zones()[0].id; - if (!hasValidRole) - throw new UserError(`You don't have privileges to create refund`); + 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 salesIds = []; - if (sales) { - for (let sale of sales) - salesIds.push(sale.id); - } else - salesIds.push(null); + const refundTickets = []; - const servicesIds = []; - if (services && services.length) { - for (let service of services) - servicesIds.push(service.id); - } else - servicesIds.push(null); + const now = new Date(); + const mappedTickets = new Map(); - const query = ` - DROP TEMPORARY TABLE IF EXISTS tmp.sale; - DROP TEMPORARY TABLE IF EXISTS tmp.ticketService; + for (let ticketId of ticketsIds) { + const filter = {include: {relation: 'address'}}; + const ticket = await models.Ticket.findById(ticketId, filter, myOptions); - CREATE TEMPORARY TABLE tmp.sale - SELECT s.id, s.itemFk, s.quantity, s.concept, s.price, s.discount, s.ticketFk - FROM sale s - WHERE s.id IN (?); + const refundTicket = await models.Ticket.create({ + clientFk: ticket.clientFk, + shipped: now, + addressFk: ticket.address().id, + agencyModeFk: refundAgencyMode.id, + nickname: ticket.address().nickname, + warehouseFk: ticket.warehouseFk, + companyFk: ticket.companyFk, + landed: now, + zoneFk: refoundZoneId + }, myOptions); - CREATE TEMPORARY TABLE tmp.ticketService - SELECT ts.description, ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk, ts.ticketFk - FROM ticketService ts - WHERE ts.id IN (?); + refundTickets.push(refundTicket); - CALL vn.ticket_doRefund(@newTicket); + mappedTickets.set(ticketId, refundTicket.id); - DROP TEMPORARY TABLE tmp.sale; - DROP TEMPORARY TABLE tmp.ticketService;`; + await models.TicketRefund.create({ + refundTicketFk: refundTicket.id, + originalTicketFk: ticket.id, + }, myOptions); + } - await Self.rawSql(query, [salesIds, servicesIds], myOptions); + for (const sale of sales) { + const refundTicketId = mappedTickets.get(sale.ticketFk); + const createdSale = await models.Sale.create({ + ticketFk: refundTicketId, + itemFk: sale.itemFk, + quantity: sale.quantity, + concept: sale.concept, + price: sale.price, + discount: sale.discount, + }, myOptions); - const [newTicket] = await Self.rawSql('SELECT @newTicket id', null, myOptions); - const newTicketId = newTicket.id; + const components = sale.components(); + for (const component of components) + component.saleFk = createdSale.id; + + await models.SaleComponent.create(components, myOptions); + } + + if (servicesIds && servicesIds.length > 0) { + const servicesFilter = { + where: {id: {inq: servicesIds}} + }; + const services = await models.TicketService.find(servicesFilter, myOptions); + + for (const service of services) { + const refundTicketId = mappedTickets.get(service.ticketFk); + + await models.TicketService.create({ + description: service.description, + quantity: service.quantity, + price: service.price, + taxClassFk: service.taxClassFk, + ticketFk: refundTicketId, + ticketServiceTypeFk: service.ticketServiceTypeFk, + }, myOptions); + } + } if (tx) await tx.commit(); - return newTicketId; + return refundTickets; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/ticket/back/methods/sale/specs/refund.spec.js b/modules/ticket/back/methods/sale/specs/refund.spec.js index 5cb353055..74077cf29 100644 --- a/modules/ticket/back/methods/sale/specs/refund.spec.js +++ b/modules/ticket/back/methods/sale/specs/refund.spec.js @@ -1,23 +1,30 @@ const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); describe('sale refund()', () => { - const sales = [ - {id: 7, ticketFk: 11}, - {id: 8, ticketFk: 11} - ]; - const services = [{id: 1}]; + const userId = 5; + const activeCtx = { + accessToken: {userId: userId}, + }; - it('should create ticket with the selected lines changing the sign to the quantites', async() => { + const servicesIds = [3]; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should create ticket with the selected lines', async() => { const tx = await models.Sale.beginTransaction({}); - const ctx = {req: {accessToken: {userId: 9}}}; + const salesIds = [7, 8]; try { const options = {transaction: tx}; - const response = await models.Sale.refund(ctx, sales, services, options); - const [newTicketId] = await models.Sale.rawSql('SELECT MAX(t.id) id FROM vn.ticket t;', null, options); + const response = await models.Sale.refund(salesIds, servicesIds, options); - expect(response).toEqual(newTicketId.id); + expect(response.length).toBeGreaterThanOrEqual(1); await tx.rollback(); } catch (e) { @@ -26,24 +33,53 @@ describe('sale refund()', () => { } }); - it(`should throw an error if the user doesn't have privileges to create a refund`, async() => { + it('should create a ticket for each unique ticketFk in the sales', async() => { const tx = await models.Sale.beginTransaction({}); - const ctx = {req: {accessToken: {userId: 1}}}; - - let error; + const salesIds = [6, 7]; try { const options = {transaction: tx}; - await models.Sale.refund(ctx, sales, services, options); + const tickets = await models.Sale.refund(salesIds, servicesIds, options); + + const ticketsIds = tickets.map(ticket => ticket.id); + + const refundedTickets = await models.Ticket.find({ + where: { + id: { + inq: ticketsIds + } + }, + include: [ + { + relation: 'ticketSales', + scope: { + include: { + relation: 'components' + } + } + }, + { + relation: 'ticketServices', + } + ] + }, options); + + const firstRefoundedTicket = refundedTickets[0]; + const secondRefoundedTicket = refundedTickets[1]; + const salesLength = firstRefoundedTicket.ticketSales().length; + const componentsLength = firstRefoundedTicket.ticketSales()[0].components().length; + const servicesLength = secondRefoundedTicket.ticketServices().length; + + expect(refundedTickets.length).toEqual(2); + expect(salesLength).toEqual(1); + expect(componentsLength).toEqual(4); + expect(servicesLength).toBeGreaterThanOrEqual(1); await tx.rollback(); } catch (e) { await tx.rollback(); - error = e; + throw e; } - - expect(error).toBeDefined(); - expect(error.message).toEqual(`You don't have privileges to create refund`); }); }); diff --git a/modules/ticket/back/methods/ticket/refund.js b/modules/ticket/back/methods/ticket/refund.js new file mode 100644 index 000000000..620c8b0c7 --- /dev/null +++ b/modules/ticket/back/methods/ticket/refund.js @@ -0,0 +1,54 @@ +module.exports = Self => { + Self.remoteMethod('refund', { + description: 'Create refund tickets with all their sales and services', + accessType: 'WRITE', + accepts: [ + { + arg: 'ticketsIds', + type: ['number'], + required: true + }, + ], + returns: { + type: ['number'], + root: true + }, + http: { + path: `/refund`, + verb: 'post' + } + }); + + Self.refund = async(ticketsIds, options) => { + 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; + } + + try { + const filter = {where: {ticketFk: {inq: ticketsIds}}}; + + const sales = await models.Sale.find(filter, myOptions); + const salesIds = sales.map(sale => sale.id); + + const services = await models.TicketService.find(filter, myOptions); + const servicesIds = services.map(service => service.id); + + const refundedTickets = await models.Sale.refund(salesIds, servicesIds, myOptions); + + if (tx) await tx.commit(); + + return refundedTickets; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 0908503a5..8a6ac0c00 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -56,6 +56,9 @@ "TicketPackaging": { "dataSource": "vn" }, + "TicketRefund": { + "dataSource": "vn" + }, "TicketRequest": { "dataSource": "vn" }, diff --git a/modules/ticket/back/models/ticket-refund.json b/modules/ticket/back/models/ticket-refund.json new file mode 100644 index 000000000..8fd0e2306 --- /dev/null +++ b/modules/ticket/back/models/ticket-refund.json @@ -0,0 +1,40 @@ +{ + "name": "TicketRefund", + "base": "Loggable", + "options": { + "mysql": { + "table": "ticketRefund" + } + }, + "log": { + "model": "TicketLog", + "relation": "originalTicket" + }, + "properties": { + "id": { + "id": true, + "type": "number", + "description": "Identifier" + }, + "refundTicketFk": { + "type": "number", + "required": true + }, + "originalTicketFk": { + "type": "number", + "required": true + } + }, + "relations": { + "refundTicket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "refundTicketFk" + }, + "originalTicket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "originalTicketFk" + } + } +} diff --git a/modules/ticket/back/models/ticket-service.js b/modules/ticket/back/models/ticket-service.js index aa94c42e3..209727ee4 100644 --- a/modules/ticket/back/models/ticket-service.js +++ b/modules/ticket/back/models/ticket-service.js @@ -3,17 +3,18 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.observe('before save', async ctx => { const models = Self.app.models; - let changes = ctx.currentInstance || ctx.instance; - if (changes) { - let ticketId = changes.ticketFk; - let isLocked = await models.Ticket.isLocked(ticketId); + const changes = ctx.currentInstance || ctx.instance; + + if (changes && !ctx.isNewInstance) { + const ticketId = changes.ticketFk; + const isLocked = await models.Ticket.isLocked(ticketId); if (isLocked) throw new UserError(`The current ticket can't be modified`); + } - if (changes.ticketServiceTypeFk) { - const ticketServiceType = await models.TicketServiceType.findById(changes.ticketServiceTypeFk); - changes.description = ticketServiceType.name; - } + if (changes && changes.ticketServiceTypeFk) { + const ticketServiceType = await models.TicketServiceType.findById(changes.ticketServiceTypeFk); + changes.description = ticketServiceType.name; } }); diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index 300893968..47d105824 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -27,6 +27,7 @@ module.exports = Self => { require('../methods/ticket/isLocked')(Self); require('../methods/ticket/freightCost')(Self); require('../methods/ticket/getComponentsSum')(Self); + require('../methods/ticket/refund')(Self); Self.observe('before save', async function(ctx) { const loopBackContext = LoopBackContext.getCurrentContext(); diff --git a/modules/ticket/back/models/ticket.json b/modules/ticket/back/models/ticket.json index 80604a713..09b01d213 100644 --- a/modules/ticket/back/models/ticket.json +++ b/modules/ticket/back/models/ticket.json @@ -105,6 +105,16 @@ "model": "TicketPackaging", "foreignKey": "ticketFk" }, + "ticketSales": { + "type": "hasMany", + "model": "Sale", + "foreignKey": "ticketFk" + }, + "ticketServices": { + "type": "hasMany", + "model": "TicketService", + "foreignKey": "ticketFk" + }, "tracking": { "type": "hasMany", "model": "TicketTracking", diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index cfd817c45..ea84743bc 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -127,6 +127,8 @@ Refund all diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 120091089..379942cdd 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -253,22 +253,14 @@ class Controller extends Section { } async refund() { - const filter = { - where: {ticketFk: this.id} - }; - const sales = await this.$http.get('Sales', {filter}); - this.sales = sales.data; - - const ticketServices = await this.$http.get('TicketServices', {filter}); - this.services = ticketServices.data; - - const params = { - sales: this.sales, - services: this.services - }; - const query = `Sales/refund`; + const params = {ticketsIds: [this.id]}; + const query = 'Tickets/refund'; return this.$http.post(query, params).then(res => { - this.$state.go('ticket.card.sale', {id: res.data}); + 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}); }); } } diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 65da73ca0..2d08a7846 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -245,27 +245,20 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { }); }); - // #4084 review with Juan - xdescribe('refund()', () => { + describe('refund()', () => { it('should make a query and go to ticket.card.sale', () => { controller.$state.go = jest.fn(); controller._id = ticket.id; - const sales = [{id: 1}]; - const services = [{id: 2}]; - $httpBackend.expectGET(`Sales`).respond(sales); - $httpBackend.expectGET(`TicketServices`).respond(services); - - const expectedParams = { - sales: sales, - services: services + const params = { + ticketsIds: [16] }; - $httpBackend.expectPOST(`Sales/refund`, expectedParams).respond(); + $httpBackend.expectPOST('Tickets/refund', params).respond([{id: 99}]); controller.refund(); $httpBackend.flush(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: undefined}); + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: 99}); }); }); diff --git a/modules/ticket/front/descriptor-menu/locale/en.yml b/modules/ticket/front/descriptor-menu/locale/en.yml new file mode 100644 index 000000000..03f93db8d --- /dev/null +++ b/modules/ticket/front/descriptor-menu/locale/en.yml @@ -0,0 +1 @@ +The following refund ticket have been created: "The following refund ticket have been created: {{ticketId}}" \ No newline at end of file diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index 060d03154..b65159a3c 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -8,4 +8,5 @@ Send CSV: Enviar CSV Send CSV Delivery Note: Enviar albarán en CSV Send PDF Delivery Note: Enviar albarán en PDF Show Proforma: Ver proforma -Refund all: Abonar todo \ No newline at end of file +Refund all: Abonar todo +The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" \ No newline at end of file diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index f4e5840f3..42eb10cb0 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -514,7 +514,7 @@ Refund diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index 987333e28..2724bb97d 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -483,11 +483,18 @@ class Controller extends Section { const sales = this.selectedValidSales(); if (!sales) return; - const params = {sales: sales}; - const query = `Sales/refund`; - this.resetChanges(); + const salesIds = sales.map(sale => sale.id); + + const params = {salesIds: salesIds}; + const query = 'Sales/refund'; this.$http.post(query, params).then(res => { - this.$state.go('ticket.card.sale', {id: res.data}); + 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}); + + this.resetChanges(); }); } diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index a8ac2f3de..28d874932 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -704,18 +704,19 @@ describe('Ticket', () => { }); describe('createRefund()', () => { - it('should make an HTTP POST query and then call to the $state go() method', () => { + it('should make a query and then navigate to the created ticket sales section', () => { jest.spyOn(controller, 'selectedValidSales').mockReturnValue(controller.sales); - jest.spyOn(controller, 'resetChanges'); jest.spyOn(controller.$state, 'go'); - - const expectedId = 9999; - $httpBackend.expect('POST', `Sales/refund`).respond(200, expectedId); + const params = { + salesIds: [1, 4], + }; + const refundTicket = {id: 99}; + const createdTickets = [refundTicket]; + $httpBackend.expect('POST', 'Sales/refund', params).respond(200, createdTickets); controller.createRefund(); $httpBackend.flush(); - expect(controller.resetChanges).toHaveBeenCalledWith(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: expectedId}); + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', refundTicket); }); }); diff --git a/modules/zone/back/methods/zone/getEventsFiltered.js b/modules/zone/back/methods/zone/getEventsFiltered.js index f466b4cfa..b7875785d 100644 --- a/modules/zone/back/methods/zone/getEventsFiltered.js +++ b/modules/zone/back/methods/zone/getEventsFiltered.js @@ -44,12 +44,16 @@ module.exports = Self => { OR (type = 'range' AND ( (started BETWEEN ? AND ?) - OR (ended BETWEEN ? AND ?) + OR + (ended BETWEEN ? AND ?) + OR + (started <= ? AND ended >= ?) ) ) ) ORDER BY type='indefinitely' DESC, type='range' DESC, type='day' DESC;`; - const events = await Self.rawSql(query, [zoneFk, started, ended, started, ended, started, ended], myOptions); + const events = await Self.rawSql(query, + [zoneFk, started, ended, started, ended, started, ended, started, ended], myOptions); query = ` SELECT e.* diff --git a/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js b/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js index ffa416282..8160ee05e 100644 --- a/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js +++ b/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js @@ -6,8 +6,9 @@ describe('zone getEventsFiltered()', () => { try { const options = {transaction: tx}; + const today = new Date(); - let result = await models.Zone.getEventsFiltered(10, '2021-10-01', '2021-10-02', options); + const result = await models.Zone.getEventsFiltered(10, today, today, options); expect(result.events.length).toEqual(1); expect(result.exclusions.length).toEqual(0); @@ -18,4 +19,46 @@ describe('zone getEventsFiltered()', () => { throw e; } }); + + describe('event range type', () => { + it('should return events and exclusions for the zone 9 in a range of dates', async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const today = new Date(); + + const result = await models.Zone.getEventsFiltered(9, today, today, options); + + expect(result.events.length).toEqual(1); + expect(result.exclusions.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should not return events and exclusions for the zone 9 in a range of dates', async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const date = new Date(); + date.setFullYear(date.getFullYear() - 2); + const dateTomorrow = new Date(date.setDate(date.getDate() + 1)); + + const result = await models.Zone.getEventsFiltered(9, date, dateTomorrow, options); + + expect(result.events.length).toEqual(0); + expect(result.exclusions.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + }); }); diff --git a/modules/zone/back/models/agency-mode.json b/modules/zone/back/models/agency-mode.json index 027cec190..99ed43b97 100644 --- a/modules/zone/back/models/agency-mode.json +++ b/modules/zone/back/models/agency-mode.json @@ -55,6 +55,11 @@ "type": "belongsTo", "model": "DeliveryMethod", "foreignKey": "deliveryMethodFk" + }, + "zones": { + "type": "hasMany", + "model": "Zone", + "foreignKey": "agencyModeFk" } }, "acls": [ diff --git a/print/methods/closure/closure.js b/print/methods/closure/closure.js index c87a8e007..67a2538e8 100644 --- a/print/methods/closure/closure.js +++ b/print/methods/closure/closure.js @@ -31,7 +31,7 @@ module.exports = { if (invoiceOut) { const args = Object.assign({ - invoiceId: invoiceOut.id, + refFk: invoiceOut.ref, recipientId: ticket.clientFk, recipient: ticket.recipient, replyTo: ticket.salesPersonEmail @@ -118,7 +118,7 @@ module.exports = { await db.rawSql(` INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?) - `, [ticket.clientFk, sample.id, ticket.companyFk]) + `, [ticket.clientFk, sample.id, ticket.companyFk]); } } catch (error) { // Domain not found diff --git a/print/methods/csv/invoice/download.js b/print/methods/csv/invoice/download.js index 593d2d8d0..9cca99f2d 100644 --- a/print/methods/csv/invoice/download.js +++ b/print/methods/csv/invoice/download.js @@ -7,13 +7,13 @@ const sqlPath = path.join(__dirname, 'sql'); module.exports = async function(request, response, next) { try { const reqArgs = request.query; - if (!reqArgs.invoiceId) - throw new Error('The argument invoiceId is required'); + if (!reqArgs.refFk) + throw new Error('The argument refFk is required'); - const invoiceId = reqArgs.invoiceId; - const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]); + const refFk = reqArgs.refFk; + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [refFk]); const content = toCSV(sales); - const fileName = `invoice_${invoiceId}.csv`; + const fileName = `invoice_${refFk}.csv`; response.setHeader('Content-type', 'text/csv'); response.setHeader('Content-Disposition', `inline; filename="${fileName}"`); diff --git a/print/methods/csv/invoice/send.js b/print/methods/csv/invoice/send.js index 919d7aeb1..2729e4a2b 100644 --- a/print/methods/csv/invoice/send.js +++ b/print/methods/csv/invoice/send.js @@ -8,22 +8,22 @@ const sqlPath = path.join(__dirname, 'sql'); module.exports = async function(request, response, next) { try { const reqArgs = request.query; - if (!reqArgs.invoiceId) - throw new Error('The argument invoiceId is required'); + if (!reqArgs.refFk) + throw new Error('The argument refFk is required'); - const invoiceId = reqArgs.invoiceId; - const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [invoiceId]); - const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]); + const refFk = reqArgs.refFk; + const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [refFk]); + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [refFk]); const args = Object.assign({ - invoiceId: (String(invoice.id)), + refFk: invoice.refFk, recipientId: invoice.clientFk, recipient: invoice.recipient, replyTo: invoice.salesPersonEmail }, response.locals); const content = toCSV(sales); - const fileName = `invoice_${invoiceId}.csv`; + const fileName = `invoice_${refFk}.csv`; const email = new Email('invoice', args); await email.send({ overrideAttachments: true, diff --git a/print/methods/csv/invoice/sql/invoice.sql b/print/methods/csv/invoice/sql/invoice.sql index 853aaddc0..d484766a0 100644 --- a/print/methods/csv/invoice/sql/invoice.sql +++ b/print/methods/csv/invoice/sql/invoice.sql @@ -6,4 +6,5 @@ SELECT FROM invoiceOut io JOIN client c ON c.id = io.clientFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/methods/csv/invoice/sql/sales.sql b/print/methods/csv/invoice/sql/sales.sql index 34b5af1f7..33fe62476 100644 --- a/print/methods/csv/invoice/sql/sales.sql +++ b/print/methods/csv/invoice/sql/sales.sql @@ -31,5 +31,5 @@ FROM sale s AND itc.countryFk = s2.countryFk JOIN taxClass tc ON tc.id = itc.taxClassFk JOIN invoiceOut io ON io.ref = t.refFk -WHERE io.id = ? +WHERE t.refFk = ? ORDER BY s.ticketFk, s.created \ No newline at end of file diff --git a/print/methods/schedule/invoice.js b/print/methods/schedule/invoice.js index 7128cebbd..87c696075 100644 --- a/print/methods/schedule/invoice.js +++ b/print/methods/schedule/invoice.js @@ -38,7 +38,7 @@ module.exports = async function(request, response, next) { connection.query('START TRANSACTION'); const args = Object.assign({ - invoiceId: invoiceOut.id, + refFk: invoiceOut.ref, recipientId: invoiceOut.clientFk, recipient: invoiceOut.recipient, replyTo: invoiceOut.salesPersonEmail diff --git a/print/templates/email/invoice/invoice.js b/print/templates/email/invoice/invoice.js index d92b65cb3..6f6ea8683 100755 --- a/print/templates/email/invoice/invoice.js +++ b/print/templates/email/invoice/invoice.js @@ -5,11 +5,11 @@ const emailFooter = new Component('email-footer'); module.exports = { name: 'invoice', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); }, }, components: { @@ -17,7 +17,7 @@ module.exports = { 'email-footer': emailFooter.build() }, props: { - invoiceId: { + refFk: { type: [Number, String], required: true } diff --git a/print/templates/email/invoice/sql/invoice.sql b/print/templates/email/invoice/sql/invoice.sql index 195621a36..b6f845fb0 100644 --- a/print/templates/email/invoice/sql/invoice.sql +++ b/print/templates/email/invoice/sql/invoice.sql @@ -4,4 +4,4 @@ SELECT FROM invoiceOut io JOIN ticket t ON t.refFk = io.ref JOIN client c ON c.id = io.clientFk -WHERE io.id = ? \ No newline at end of file +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/exportation/exportation.js b/print/templates/reports/exportation/exportation.js index fbf663249..a7e018c48 100755 --- a/print/templates/reports/exportation/exportation.js +++ b/print/templates/reports/exportation/exportation.js @@ -5,14 +5,14 @@ const reportFooter = new Component('report-footer'); module.exports = { name: 'exportation', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); if (!this.invoice) throw new Error('Something went wrong'); }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); } }, computed: { @@ -27,7 +27,7 @@ module.exports = { 'report-footer': reportFooter.build() }, props: { - invoiceId: { + refFk: { type: [Number, String], required: true } diff --git a/print/templates/reports/exportation/sql/invoice.sql b/print/templates/reports/exportation/sql/invoice.sql index 8e92333dd..7ea55e481 100644 --- a/print/templates/reports/exportation/sql/invoice.sql +++ b/print/templates/reports/exportation/sql/invoice.sql @@ -3,4 +3,5 @@ SELECT io.ref, io.issued FROM invoiceOut io -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice-incoterms/invoice-incoterms.js b/print/templates/reports/invoice-incoterms/invoice-incoterms.js index 99e23e15f..99a4e2e73 100755 --- a/print/templates/reports/invoice-incoterms/invoice-incoterms.js +++ b/print/templates/reports/invoice-incoterms/invoice-incoterms.js @@ -5,9 +5,9 @@ const reportFooter = new Component('report-footer'); module.exports = { name: 'invoice-incoterms', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); - this.client = await this.fetchClient(this.invoiceId); - this.incoterms = await this.fetchIncoterms(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); + this.client = await this.fetchClient(this.refFk); + this.incoterms = await this.fetchIncoterms(this.refFk); if (!this.invoice) throw new Error('Something went wrong'); @@ -16,14 +16,14 @@ module.exports = { }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); }, - fetchClient(invoiceId) { - return this.findOneFromDef('client', [invoiceId]); + fetchClient(refFk) { + return this.findOneFromDef('client', [refFk]); }, - fetchIncoterms(invoiceId) { - return this.findOneFromDef('incoterms', [invoiceId, invoiceId, invoiceId]); + fetchIncoterms(refFk) { + return this.findOneFromDef('incoterms', [refFk, refFk, refFk]); } }, components: { @@ -31,7 +31,7 @@ module.exports = { 'report-footer': reportFooter.build() }, props: { - invoiceId: { + refFk: { type: [Number, String], required: true } diff --git a/print/templates/reports/invoice-incoterms/sql/client.sql b/print/templates/reports/invoice-incoterms/sql/client.sql index dd6035222..3e66c15c9 100644 --- a/print/templates/reports/invoice-incoterms/sql/client.sql +++ b/print/templates/reports/invoice-incoterms/sql/client.sql @@ -9,4 +9,5 @@ FROM vn.invoiceOut io JOIN vn.country cty ON cty.id = c.countryFk LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial AND ios.taxAreaFk = 'CEE' -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice-incoterms/sql/incoterms.sql b/print/templates/reports/invoice-incoterms/sql/incoterms.sql index 5d894a4b2..6bb895129 100644 --- a/print/templates/reports/invoice-incoterms/sql/incoterms.sql +++ b/print/templates/reports/invoice-incoterms/sql/incoterms.sql @@ -51,7 +51,7 @@ SELECT io.issued, FROM vn.invoiceOut io JOIN vn.ticket t ON t.refFk = io.ref JOIN vn.saleVolume sv ON sv.ticketFk = t.id - WHERE io.id = ? + WHERE t.refFk = ? ) sub2 ON TRUE JOIN vn.itemTaxCountry itc ON itc.countryFk = su.countryFk AND itc.itemFk = s.itemFk JOIN vn.taxClass tc ON tc.id = itc.taxClassFk @@ -66,6 +66,7 @@ SELECT io.issued, JOIN vn.sale s ON t.id = s.ticketFk JOIN vn.item i ON i.id = s.itemFk JOIN vn.intrastat ir ON ir.id = i.intrastatFk - WHERE io.id = ? + WHERE t.refFk = ? )sub3 ON TRUE - WHERE io.id = ? + WHERE t.refFk = ? + diff --git a/print/templates/reports/invoice-incoterms/sql/invoice.sql b/print/templates/reports/invoice-incoterms/sql/invoice.sql index b9a929183..571ea2af3 100644 --- a/print/templates/reports/invoice-incoterms/sql/invoice.sql +++ b/print/templates/reports/invoice-incoterms/sql/invoice.sql @@ -14,4 +14,5 @@ FROM invoiceOut io JOIN company cny ON cny.id = io.companyFk JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk LEFT JOIN invoiceOutSerial ios ON ios.code = io.serial -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index c5abfad7e..fd4acd4b5 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -7,15 +7,15 @@ const invoiceIncoterms = new Report('invoice-incoterms'); module.exports = { name: 'invoice', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); - this.client = await this.fetchClient(this.invoiceId); - this.taxes = await this.fetchTaxes(this.invoiceId); - this.intrastat = await this.fetchIntrastat(this.invoiceId); - this.rectified = await this.fetchRectified(this.invoiceId); - this.hasIncoterms = await this.fetchHasIncoterms(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); + this.client = await this.fetchClient(this.refFk); + this.taxes = await this.fetchTaxes(this.refFk); + this.intrastat = await this.fetchIntrastat(this.refFk); + this.rectified = await this.fetchRectified(this.refFk); + this.hasIncoterms = await this.fetchHasIncoterms(this.refFk); - const tickets = await this.fetchTickets(this.invoiceId); - const sales = await this.fetchSales(this.invoiceId); + const tickets = await this.fetchTickets(this.refFk); + const sales = await this.fetchSales(this.refFk); const map = new Map(); @@ -65,29 +65,29 @@ module.exports = { } }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); }, - fetchClient(invoiceId) { - return this.findOneFromDef('client', [invoiceId]); + fetchClient(refFk) { + return this.findOneFromDef('client', [refFk]); }, - fetchTickets(invoiceId) { - return this.rawSqlFromDef('tickets', [invoiceId]); + fetchTickets(refFk) { + return this.rawSqlFromDef('tickets', [refFk]); }, - fetchSales(invoiceId) { - return this.rawSqlFromDef('sales', [invoiceId, invoiceId]); + fetchSales(refFk) { + return this.rawSqlFromDef('sales', [refFk, refFk]); }, - fetchTaxes(invoiceId) { - return this.rawSqlFromDef(`taxes`, [invoiceId]); + fetchTaxes(refFk) { + return this.rawSqlFromDef(`taxes`, [refFk]); }, - fetchIntrastat(invoiceId) { - return this.rawSqlFromDef(`intrastat`, [invoiceId]); + fetchIntrastat(refFk) { + return this.rawSqlFromDef(`intrastat`, [refFk, refFk, refFk]); }, - fetchRectified(invoiceId) { - return this.rawSqlFromDef(`rectified`, [invoiceId]); + fetchRectified(refFk) { + return this.rawSqlFromDef(`rectified`, [refFk]); }, - fetchHasIncoterms(invoiceId) { - return this.findValueFromDef(`hasIncoterms`, [invoiceId]); + fetchHasIncoterms(refFk) { + return this.findValueFromDef(`hasIncoterms`, [refFk]); }, saleImport(sale) { const price = sale.quantity * sale.price; @@ -115,9 +115,8 @@ module.exports = { 'invoice-incoterms': invoiceIncoterms.build() }, props: { - invoiceId: { - type: [Number, String], - required: true + refFk: { + type: String } } }; diff --git a/print/templates/reports/invoice/sql/client.sql b/print/templates/reports/invoice/sql/client.sql index dd6035222..4c35838f2 100644 --- a/print/templates/reports/invoice/sql/client.sql +++ b/print/templates/reports/invoice/sql/client.sql @@ -9,4 +9,5 @@ FROM vn.invoiceOut io JOIN vn.country cty ON cty.id = c.countryFk LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial AND ios.taxAreaFk = 'CEE' -WHERE io.id = ? \ No newline at end of file + LEFT JOIN vn.ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/hasIncoterms.sql b/print/templates/reports/invoice/sql/hasIncoterms.sql index 27f61f57c..40a6db384 100644 --- a/print/templates/reports/invoice/sql/hasIncoterms.sql +++ b/print/templates/reports/invoice/sql/hasIncoterms.sql @@ -3,6 +3,6 @@ SELECT IF(incotermsFk IS NULL, FALSE, TRUE) AS hasIncoterms JOIN invoiceOut io ON io.ref = t.refFk JOIN client c ON c.id = t.clientFk JOIN address a ON a.id = t.addressFk - WHERE io.id = ? + WHERE t.refFk = ? AND IF(c.hasToinvoiceByAddress = FALSE, c.defaultAddressFk, TRUE) LIMIT 1 \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/intrastat.sql b/print/templates/reports/invoice/sql/intrastat.sql index 6bf72c158..e2ee47667 100644 --- a/print/templates/reports/invoice/sql/intrastat.sql +++ b/print/templates/reports/invoice/sql/intrastat.sql @@ -1,18 +1,22 @@ -SELECT - ir.id AS code, - ir.description AS description, - CAST(SUM(IFNULL(i.stems,1) * s.quantity) AS DECIMAL(10,2)) as stems, - CAST(SUM(IF(sv.physicalWeight, sv.physicalWeight, i.density * sub.cm3delivery/1000000)) AS DECIMAL(10,2)) netKg, - CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) AS subtotal - FROM vn.sale s - LEFT JOIN (SELECT ic.itemFk, ic.cm3, ic.cm3delivery - FROM vn.itemCost ic - WHERE ic.cm3 - GROUP BY ic.itemFk) sub ON s.itemFk = sub.itemFk - LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id - LEFT JOIN vn.ticket t ON t.id = s.ticketFk - LEFT JOIN vn.invoiceOut io ON io.ref = t.refFk - LEFT JOIN vn.item i ON i.id = s.itemFk - JOIN vn.intrastat ir ON ir.id = i.intrastatFk - WHERE io.id = ? - GROUP BY i.intrastatFk; \ No newline at end of file +SELECT + ir.id code, + ir.description description, + CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, + CAST(SUM(CAST(IFNULL(i.stems, 1) * s.quantity * IF(ic.grams, ic.grams, i.density * ic.cm3delivery / 1000) / 1000 AS DECIMAL(10,2)) * + IF(sub.weight, sub.weight / vn.invoiceOut_getWeight(?), 1)) AS DECIMAL(10,2)) netKg, + CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal + FROM vn.ticket t + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk + JOIN vn.intrastat ir ON ir.id = i.intrastatFk + LEFT JOIN ( + SELECT t2.weight + FROM vn.ticket t2 + WHERE refFk = ? AND weight + LIMIT 1 + ) sub ON TRUE + WHERE t.refFk = ? + AND i.intrastatFk + GROUP BY i.intrastatFk + ORDER BY i.intrastatFk; \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/invoice.sql b/print/templates/reports/invoice/sql/invoice.sql index aacbb0016..0f12e4f53 100644 --- a/print/templates/reports/invoice/sql/invoice.sql +++ b/print/templates/reports/invoice/sql/invoice.sql @@ -13,4 +13,5 @@ FROM invoiceOut io JOIN company cny ON cny.id = io.companyFk JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk LEFT JOIN invoiceOutSerial ios ON ios.code = io.serial -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/rectified.sql b/print/templates/reports/invoice/sql/rectified.sql index 1255b973c..ea814a05a 100644 --- a/print/templates/reports/invoice/sql/rectified.sql +++ b/print/templates/reports/invoice/sql/rectified.sql @@ -6,4 +6,5 @@ SELECT FROM vn.invoiceCorrection ic JOIN vn.invoiceOut io ON io.id = ic.correctedFk JOIN vn.invoiceCorrectionType ict ON ict.id = ic.invoiceCorrectionTypeFk -where ic.correctingFk = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/sales.sql b/print/templates/reports/invoice/sql/sales.sql index cff8794db..f5721a594 100644 --- a/print/templates/reports/invoice/sql/sales.sql +++ b/print/templates/reports/invoice/sql/sales.sql @@ -37,7 +37,7 @@ SELECT JOIN vn.itemTaxCountry itc ON itc.countryFk = su.countryFk AND itc.itemFk = s.itemFk JOIN vn.taxClass tc ON tc.id = itc.taxClassFk - WHERE io.id = ? + WHERE t.refFk = ? UNION ALL SELECT io.ref, @@ -69,4 +69,4 @@ SELECT JOIN vn.company co ON co.id = io.companyFk JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk JOIN vn.taxClass tc ON tc.id = ts.taxClassFk - WHERE io.id = ? \ No newline at end of file + WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/taxes.sql b/print/templates/reports/invoice/sql/taxes.sql index 19b1cc00e..2203d8b8a 100644 --- a/print/templates/reports/invoice/sql/taxes.sql +++ b/print/templates/reports/invoice/sql/taxes.sql @@ -6,6 +6,7 @@ SELECT FROM invoiceOutTax iot JOIN pgc ON pgc.code = iot.pgcFk LEFT JOIN pgcEqu pe ON pe.equFk = pgc.code - JOIN invoiceOut io ON io.id = iot.invoiceOutFk - WHERE invoiceOutFk = ? + JOIN invoiceOut io ON io.id = iot.invoiceOutFk + LEFT JOIN ticket t ON t.refFk = io.ref + WHERE t.refFk = ? ORDER BY iot.id \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/tickets.sql b/print/templates/reports/invoice/sql/tickets.sql index 7d135bd03..162f043e2 100644 --- a/print/templates/reports/invoice/sql/tickets.sql +++ b/print/templates/reports/invoice/sql/tickets.sql @@ -4,5 +4,5 @@ SELECT t.nickname FROM invoiceOut io JOIN ticket t ON t.refFk = io.ref -WHERE io.id = ? +WHERE t.refFk = ? ORDER BY t.shipped \ No newline at end of file