diff --git a/db/changes/10411-january/00-ticket_getMovable.sql b/db/changes/10411-january/00-ticket_getMovable.sql new file mode 100644 index 0000000000..5f5b0a93a3 --- /dev/null +++ b/db/changes/10411-january/00-ticket_getMovable.sql @@ -0,0 +1,43 @@ +DROP PROCEDURE IF EXISTS `vn`.`ticket_getMovable`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`ticket_getMovable`(vTicketFk INT, vDatedNew DATETIME, vWarehouseFk INT) +BEGIN +/** + * Cálcula el stock movible para los artículos de un ticket + * + * @param vTicketFk -> Ticket + * @param vDatedNew -> Nueva fecha + * @return Sales con Movible +*/ + DECLARE vDatedOld DATETIME; + + SELECT t.shipped INTO vDatedOld + FROM ticket t + WHERE t.id = vTicketFk; + + CALL itemStock(vWarehouseFk, DATE_SUB(vDatedNew, INTERVAL 1 DAY), NULL); + CALL item_getMinacum(vWarehouseFk, vDatedNew, DATEDIFF(vDatedOld, vDatedNew), NULL); + + SELECT s.id, + s.itemFk, + s.quantity, + s.concept, + s.price, + s.reserved, + s.discount, + i.image, + i.subName, + il.stock + IFNULL(im.amount, 0) AS movable + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + LEFT JOIN tmp.itemMinacum im ON im.itemFk = s.itemFk AND im.warehouseFk = vWarehouseFk + LEFT JOIN tmp.itemList il ON il.itemFk = s.itemFk + WHERE t.id = vTicketFk; + + DROP TEMPORARY TABLE IF EXISTS tmp.itemList; + DROP TEMPORARY TABLE IF EXISTS tmp.itemMinacum; +END$$ +DELIMITER ; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index ed03cba1e7..07eaf23fdd 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -625,6 +625,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF (25 ,NULL, 8, 1, NULL, CURDATE(), CURDATE(), 1101, 'Bruce Wayne', 1, NULL, 0, 1, 5, 1, CURDATE()), (26 ,NULL, 8, 1, NULL, CURDATE(), CURDATE(), 1101, 'An incredibly long alias for testing purposes', 1, NULL, 0, 1, 5, 1, CURDATE()), (27 ,NULL, 8, 1, NULL, CURDATE(), CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, CURDATE()); + INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`) VALUES (1, 11, 1, 'ready'), @@ -899,7 +900,8 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric (29, 4, 17, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, CURDATE()), (30, 4, 18, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, CURDATE()), (31, 2, 23, 'Melee weapon combat fist 15cm', -5, 7.08, 0, 0, 0, CURDATE()), - (32, 1, 24, 'Ranged weapon longbow 2m', -1, 8.07, 0, 0, 0, CURDATE()); + (32, 1, 24, 'Ranged weapon longbow 2m', -1, 8.07, 0, 0, 0, CURDATE()), + (33, 5, 14, 'Ranged weapon pistol 9mm', 50, 1.79, 0, 0, 0, CURDATE()); INSERT INTO `vn`.`saleChecked`(`saleFk`, `isChecked`) VALUES @@ -2432,4 +2434,12 @@ INSERT INTO `vn`.`expeditionScan` (`id`, `expeditionFk`, `scanned`, `palletFk`) CALL `cache`.`last_buy_refresh`(FALSE); UPDATE `vn`.`item` SET `genericFk` = 9 - WHERE `id` = 2; \ No newline at end of file + WHERE `id` = 2; + +INSERT INTO `bs`.`defaulter` (`clientFk`, `amount`, `created`, `defaulterSinced`) + VALUES + (1101, 500, CURDATE(), CURDATE()), + (1102, 500, CURDATE(), CURDATE()), + (1103, 500, CURDATE(), CURDATE()), + (1107, 500, CURDATE(), CURDATE()), + (1109, 500, CURDATE(), CURDATE()); diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 2900a285ba..f0a5c37b5f 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -304,6 +304,16 @@ export default { saveNewInsuranceCredit: 'vn-client-credit-insurance-insurance-create button[type="submit"]', anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr', }, + clientDefaulter: { + anyClient: 'vn-client-defaulter-index vn-tbody > vn-tr', + firstClientName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2) > span', + firstSalesPersonName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(3) > span', + firstObservation: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]', + allDefaulterCheckbox: 'vn-client-defaulter-index vn-thead vn-multi-check', + addObservationButton: 'vn-client-defaulter-index vn-button[icon="icon-notes"]', + observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', + saveButton: 'button[response="accept"]' + }, clientContacts: { addContactButton: 'vn-client-contact vn-icon[icon="add_circle"]', name: 'vn-client-contact vn-textfield[ng-model="contact.name"]', @@ -526,6 +536,7 @@ export default { acceptDialog: '.vn-dialog.shown button[response="accept"]', acceptChangeHourButton: '.vn-dialog.shown button[response="accept"]', descriptorDeliveryDate: 'vn-ticket-descriptor slot-body > .attributes > vn-label-value:nth-child(4) > section > span', + descriptorDeliveryAgency: 'vn-ticket-descriptor slot-body > .attributes > vn-label-value:nth-child(5) > section > span', acceptInvoiceOutButton: '.vn-confirm.shown button[response="accept"]', acceptDeleteStowawayButton: '.vn-dialog.shown button[response="accept"]' }, @@ -603,10 +614,12 @@ export default { ticketBasicData: { agency: 'vn-autocomplete[ng-model="$ctrl.agencyModeId"]', zone: 'vn-autocomplete[ng-model="$ctrl.zoneId"]', + shipped: 'vn-date-picker[ng-model="$ctrl.shipped"]', nextStepButton: 'vn-step-control .buttons > section:last-child vn-button', finalizeButton: 'vn-step-control .buttons > section:last-child button[type=submit]', stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > vn-side-menu div:nth-child(4)', chargesReason: 'vn-ticket-basic-data-step-two div:nth-child(3) > vn-radio', + withoutNegatives: 'vn-check[ng-model="$ctrl.ticket.withoutNegatives"]', }, ticketComponents: { base: 'vn-ticket-components > vn-side-menu div:nth-child(1) > div:nth-child(2)' diff --git a/e2e/paths/02-client/21_defaulter.spec.js b/e2e/paths/02-client/21_defaulter.spec.js new file mode 100644 index 0000000000..89b5c5761d --- /dev/null +++ b/e2e/paths/02-client/21_defaulter.spec.js @@ -0,0 +1,73 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Client defaulter path', () => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('insurance', 'client'); + await page.accessToSection('client.defaulter.index'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should count the amount of clients in the turns section', async() => { + const result = await page.countElement(selectors.clientDefaulter.anyClient); + + expect(result).toEqual(5); + }); + + it('should check contain expected client', async() => { + const clientName = + await page.waitToGetProperty(selectors.clientDefaulter.firstClientName, 'innerText'); + const salesPersonName = + await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText'); + + expect(clientName).toEqual('Ororo Munroe'); + expect(salesPersonName).toEqual('salesPerson'); + }); + + it('should first observation not changed', async() => { + const expectedObservation = 'Madness, as you know, is like gravity, all it takes is a little push'; + const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value'); + + expect(result).toContain(expectedObservation); + }); + + it('should not add empty observation', async() => { + await page.waitToClick(selectors.clientDefaulter.allDefaulterCheckbox); + + await page.waitToClick(selectors.clientDefaulter.addObservationButton); + await page.write(selectors.clientDefaulter.observation, ''); + await page.waitToClick(selectors.clientDefaulter.saveButton); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain(`The message can't be empty`); + }); + + it('shoul checked all defaulters', async() => { + await page.loginAndModule('insurance', 'client'); + await page.accessToSection('client.defaulter.index'); + + await page.waitToClick(selectors.clientDefaulter.allDefaulterCheckbox); + }); + + it('should add observation for all clients', async() => { + await page.waitToClick(selectors.clientDefaulter.addObservationButton); + await page.write(selectors.clientDefaulter.observation, 'My new observation'); + await page.waitToClick(selectors.clientDefaulter.saveButton); + }); + + it('should first observation changed', async() => { + const message = await page.waitForSnackbar(); + const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value'); + + expect(message.text).toContain('Observation saved!'); + expect(result).toContain('My new observation'); + }); +}); diff --git a/e2e/paths/05-ticket/06_basic_data_steps.spec.js b/e2e/paths/05-ticket/06_basic_data_steps.spec.js index a5f9a60cf5..7a09edf06d 100644 --- a/e2e/paths/05-ticket/06_basic_data_steps.spec.js +++ b/e2e/paths/05-ticket/06_basic_data_steps.spec.js @@ -83,4 +83,62 @@ describe('Ticket Edit basic data path', () => { await page.waitToClick(selectors.ticketBasicData.finalizeButton); await page.waitForState('ticket.card.summary'); }); + + it(`should not find ticket`, async() => { + await page.doSearch('29'); + const count = await page.countElement(selectors.ticketsIndex.searchResult); + + expect(count).toEqual(0); + }); + + it(`should split ticket without negatives`, async() => { + const newAgency = 'Silla247'; + const newDate = new Date(); + newDate.setDate(newDate.getDate() + 1); + + await page.accessToSearchResult('14'); + await page.accessToSection('ticket.card.basicData.stepOne'); + + await page.autocompleteSearch(selectors.ticketBasicData.agency, newAgency); + await page.pickDate(selectors.ticketBasicData.shipped, newDate); + + await page.waitToClick(selectors.ticketBasicData.nextStepButton); + + await page.waitToClick(selectors.ticketBasicData.withoutNegatives); + await page.waitToClick(selectors.ticketBasicData.finalizeButton); + + await page.waitForState('ticket.card.summary'); + + const newTicketAgency = await page + .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryAgency, 'innerText'); + const newTicketDate = await page + .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryDate, 'innerText'); + + expect(newAgency).toEqual(newTicketAgency); + expect(newTicketDate).toContain(newDate.getDate()); + }); + + it(`should new ticket have sale of old ticket`, async() => { + await page.accessToSection('ticket.card.sale'); + await page.waitForState('ticket.card.sale'); + + const item = await page.waitToGetProperty(selectors.ticketSales.firstSaleId, 'innerText'); + + expect(item).toEqual('4'); + }); + + it(`should old ticket have old date and agency`, async() => { + const oldDate = new Date(); + const oldAgency = 'Super-Man delivery'; + + await page.accessToSearchResult('14'); + + const oldTicketAgency = await page + .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryAgency, 'innerText'); + const oldTicketDate = await page + .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryDate, 'innerText'); + + expect(oldTicketAgency).toEqual(oldAgency); + expect(oldTicketDate).toContain(oldDate.getDate()); + }); }); diff --git a/front/core/styles/icons/salixfont.css b/front/core/styles/icons/salixfont.css index 0ce465db72..24406837f6 100644 --- a/front/core/styles/icons/salixfont.css +++ b/front/core/styles/icons/salixfont.css @@ -23,59 +23,207 @@ -moz-osx-font-smoothing: grayscale; } -.icon-isTooLittle:before { - content: "\e91b"; + +.icon-100:before { + content: "\e95a"; +} +.icon-accessory:before { + content: "\e90a"; +} +.icon-account:before { + content: "\e92a"; +} +.icon-actions:before { + content: "\e960"; +} +.icon-addperson:before { + content: "\e90e"; +} +.icon-agency:before { + content: "\e938"; +} +.icon-albaran:before { + content: "\e94d"; +} +.icon-anonymous:before { + content: "\e930"; +} +.icon-apps:before { + content: "\e951"; +} +.icon-artificial:before { + content: "\e90b"; +} +.icon-attach:before { + content: "\e92e"; +} +.icon-barcode:before { + content: "\e971"; +} +.icon-basket:before { + content: "\e914"; +} +.icon-basketadd:before { + content: "\e913"; +} +.icon-bin:before { + content: "\e96f"; +} +.icon-botanical:before { + content: "\e972"; +} +.icon-bucket:before { + content: "\e97a"; + color: #000; +} +.icon-buscaman:before { + content: "\e93b"; +} +.icon-buyrequest:before { + content: "\e932"; +} +.icon-calc_volum .path1:before { + content: "\e915"; + color: rgb(0, 0, 0); +} +.icon-calc_volum .path2:before { + content: "\e916"; + margin-left: -1em; + color: rgb(0, 0, 0); +} +.icon-calc_volum .path3:before { + content: "\e917"; + margin-left: -1em; + color: rgb(0, 0, 0); +} +.icon-calc_volum .path4:before { + content: "\e918"; + margin-left: -1em; + color: rgb(0, 0, 0); +} +.icon-calc_volum .path5:before { + content: "\e919"; + margin-left: -1em; + color: rgb(0, 0, 0); +} +.icon-calc_volum .path6:before { + content: "\e91a"; + margin-left: -1em; + color: rgb(255, 255, 255); +} +.icon-calendar:before { + content: "\e93d"; +} +.icon-catalog:before { + content: "\e937"; +} +.icon-claims:before { + content: "\e963"; +} +.icon-client:before { + content: "\e928"; +} +.icon-clone:before { + content: "\e973"; +} +.icon-columnadd:before { + content: "\e954"; +} +.icon-columndelete:before { + content: "\e953"; +} +.icon-components:before { + content: "\e946"; +} +.icon-consignatarios:before { + content: "\e93f"; +} +.icon-control:before { + content: "\e949"; +} +.icon-credit:before { + content: "\e927"; +} +.icon-defaulter:before { + content: "\e94b"; +} +.icon-deletedTicket:before { + content: "\e935"; +} +.icon-deleteline:before { + content: "\e955"; +} +.icon-delivery:before { + content: "\e939"; +} +.icon-deliveryprices:before { + content: "\e91c"; +} +.icon-details:before { + content: "\e961"; +} +.icon-dfiscales:before { + content: "\e984"; +} +.icon-disabled:before { + content: "\e921"; +} +.icon-doc:before { + content: "\e977"; +} +.icon-entry:before { + content: "\e934"; +} +.icon-exit:before { + content: "\e92f"; +} +.icon-eye:before { + content: "\e976"; +} +.icon-fixedPrice:before { + content: "\e90d"; +} +.icon-flower:before { + content: "\e90c"; } .icon-frozen:before { content: "\e900"; } -.icon-Person:before { - content: "\e901"; -} -.icon-handmadeArtificial:before { - content: "\e902"; -} .icon-fruit:before { content: "\e903"; } .icon-funeral:before { content: "\e904"; } -.icon-treatments:before { - content: "\e905"; -} -.icon-preserved:before { - content: "\e906"; -} .icon-greenery:before { content: "\e907"; } -.icon-plant:before { - content: "\e908"; +.icon-greuge:before { + content: "\e944"; +} +.icon-grid:before { + content: "\e980"; } .icon-handmade:before { content: "\e909"; } -.icon-accessory:before { - content: "\e90a"; +.icon-handmadeArtificial:before { + content: "\e902"; } -.icon-artificial:before { - content: "\e90b"; +.icon-headercol:before { + content: "\e958"; } -.icon-flower:before { - content: "\e90c"; +.icon-history:before { + content: "\e968"; } -.icon-fixedPrice:before { - content: "\e90d"; +.icon-info:before { + content: "\e952"; } -.icon-addperson:before { - content: "\e90e"; +.icon-inventory:before { + content: "\e92b"; } -.icon-supplierfalse:before { - content: "\e90f"; -} -.icon-invoice-out:before { - content: "\e910"; +.icon-invoice:before { + content: "\e923"; } .icon-invoice-in:before { content: "\e911"; @@ -83,338 +231,174 @@ .icon-invoice-in-create:before { content: "\e912"; } -.icon-basketadd:before { - content: "\e913"; +.icon-invoice-out:before { + content: "\e910"; } -.icon-basket:before { - content: "\e914"; -} -.icon-calc_volum .path1:before { - content: "\e915"; -} -.icon-calc_volum .path2:before { - content: "\e916"; - margin-left: -1em; -} -.icon-calc_volum .path3:before { - content: "\e917"; - margin-left: -1em; -} -.icon-calc_volum .path4:before { - content: "\e918"; - margin-left: -1em; -} -.icon-calc_volum .path5:before { - content: "\e919"; - margin-left: -1em; -} -.icon-calc_volum .path6:before { - content: "\e91a"; - margin-left: -1em; -} -.icon-deliveryprices:before { - content: "\e91c"; -} -.icon-onlinepayment:before { - content: "\e91d"; -} -.icon-risk:before { - content: "\e91e"; -} -.icon-noweb:before { - content: "\e91f"; -} -.icon-no036:before { - content: "\e920"; -} -.icon-inactive:before { - content: "\e921"; -} -.icon-unavailable:before { - content: "\e922"; -} -.icon-invoice-01:before { - content: "\e923"; -} -.icon-invoice:before { - content: "\e924"; -} -.icon-supplier:before { - content: "\e925"; -} -.icon-client2:before { - content: "\e926"; -} -.icon-supplier2:before { - content: "\e927"; -} -.icon-client:before { - content: "\e928"; -} -.icon-shipment-01:before { - content: "\e929"; -} -.icon-inventory:before { - content: "\e92b"; -} -.icon-zone:before { - content: "\e92c"; -} -.icon-wiki:before { - content: "\e92d"; -} -.icon-attach:before { - content: "\e92e"; -} -.icon-zone2:before { - content: "\e92f"; -} -.icon-anonymous:before { - content: "\e930"; -} -.icon-net:before { - content: "\e931"; -} -.icon-buyrequest:before { - content: "\e932"; -} -.icon-thermometer:before { - content: "\e933"; -} -.icon-entry:before { - content: "\e934"; -} -.icon-deletedTicket:before { - content: "\e935"; -} -.icon-deliveryprices-01:before { - content: "\e936"; -} -.icon-catalog:before { - content: "\e937"; -} -.icon-agency:before { - content: "\e938"; -} -.icon-delivery:before { - content: "\e939"; -} -.icon-wand:before { - content: "\e93a"; -} -.icon-buscaman:before { - content: "\e93b"; -} -.icon-pbx:before { - content: "\e93c"; -} -.icon-calendar:before { - content: "\e93d"; -} -.icon-splitline:before { - content: "\e93e"; -} -.icon-consignatarios:before { - content: "\e93f"; -} -.icon-tax:before { - content: "\e940"; -} -.icon-notes:before { - content: "\e941"; -} -.icon-lines:before { - content: "\e942"; -} -.icon-languaje:before { - content: "\e943"; -} -.icon-greuge:before { - content: "\e944"; -} -.icon-credit:before { - content: "\e945"; -} -.icon-components:before { - content: "\e946"; -} -.icon-pets:before { - content: "\e947"; -} -.icon-linesprepaired:before { - content: "\e948"; -} -.icon-control:before { - content: "\e949"; -} -.icon-revision:before { - content: "\e94a"; -} -.icon-newinvoices:before { - content: "\e94b"; -} -.icon-services:before { - content: "\e94c"; -} -.icon-newalbaran:before { - content: "\e94d"; -} -.icon-solunion:before { - content: "\e94e"; -} -.icon-stowaway:before { - content: "\e94f"; -} -.icon-exit:before { - content: "\e950"; -} -.icon-apps:before { - content: "\e951"; -} -.icon-info:before { - content: "\e952"; -} -.icon-columndelete:before { - content: "\e953"; -} -.icon-columnadd:before { - content: "\e954"; -} -.icon-deleteline:before { - content: "\e955"; +.icon-isTooLittle:before { + content: "\e91b"; } .icon-item:before { content: "\e956"; } -.icon-worker:before { - content: "\e957"; +.icon-languaje:before { + content: "\e926"; } -.icon-headercol:before { - content: "\e958"; +.icon-lines:before { + content: "\e942"; } -.icon-reserva:before { - content: "\e959"; +.icon-linesprepaired:before { + content: "\e948"; } -.icon-100:before { - content: "\e95a"; -} -.icon-noweb1:before { - content: "\e95b"; -} -.icon-settings1:before { - content: "\e95c"; -} -.icon-sign:before { - content: "\e95d"; -} -.icon-polizon:before { - content: "\e95e"; -} -.icon-solclaim:before { - content: "\e95f"; -} -.icon-actions:before { - content: "\e960"; -} -.icon-details:before { - content: "\e961"; -} -.icon-traceability:before { - content: "\e962"; -} -.icon-claims:before { - content: "\e963"; -} -.icon-regentry:before { - content: "\e964"; -} -.icon-regentry-1:before { - content: "\e965"; -} -.icon-transaction:before { - content: "\e966"; -} -.icon-history:before { - content: "\e968"; -} -.icon-entry:before { - content: "\e969"; +.icon-logout:before { + content: "\e936"; } .icon-mana:before { content: "\e96a"; } -.icon-ticket:before { - content: "\e96b"; +.icon-mandatory:before { + content: "\e97b"; +} +.icon-net:before { + content: "\e931"; } .icon-niche:before { content: "\e96c"; } -.icon-tags:before { - content: "\e96d"; +.icon-no036:before { + content: "\e920"; } -.icon-volume:before { - content: "\e96e"; +.icon-noPayMethod:before { + content: "\e905"; } -.icon-bin:before { - content: "\e96f"; +.icon-notes:before { + content: "\e941"; } -.icon-splur:before { - content: "\e970"; +.icon-noweb:before { + content: "\e91f"; } -.icon-barcode:before { - content: "\e971"; -} -.icon-botanical:before { - content: "\e972"; -} -.icon-clone:before { - content: "\e973"; -} -.icon-photo:before { - content: "\e974"; -} -.icon-sms:before { - content: "\e975"; -} -.icon-eye:before { - content: "\e976"; -} -.icon-doc:before { - content: "\e977"; +.icon-onlinepayment:before { + content: "\e91d"; } .icon-package:before { content: "\e978"; } -.icon-settings:before { - content: "\e979"; +.icon-payment:before { + content: "\e97e"; } -.icon-bucket:before { - content: "\e97a"; +.icon-pbx:before { + content: "\e93c"; } -.icon-mandatory:before { - content: "\e97b"; +.icon-Person:before { + content: "\e901"; +} +.icon-pets:before { + content: "\e947"; +} +.icon-photo:before { + content: "\e924"; +} +.icon-planta:before { + content: "\e908"; +} +.icon-polizon:before { + content: "\e95e"; +} +.icon-preserved:before { + content: "\e906"; } .icon-recovery:before { content: "\e97c"; } -.icon-payment:before { - content: "\e97e"; +.icon-regentry:before { + content: "\e964"; } -.icon-invoices:before { - content: "\e97f"; +.icon-reserva:before { + content: "\e959"; } -.icon-grid:before { - content: "\e980"; +.icon-revision:before { + content: "\e94a"; } -.icon-logout:before { - content: "\e981"; +.icon-risk:before { + content: "\e91e"; +} +.icon-services:before { + content: "\e94c"; +} +.icon-settings:before { + content: "\e979"; +} +.icon-shipment-01:before { + content: "\e929"; +} +.icon-sign:before { + content: "\e95d"; +} +.icon-sms:before { + content: "\e975"; +} +.icon-solclaim:before { + content: "\e95f"; +} +.icon-solunion:before { + content: "\e94e"; +} +.icon-splitline:before { + content: "\e93e"; +} +.icon-splur:before { + content: "\e970"; +} +.icon-stowaway:before { + content: "\e94f"; +} +.icon-supplier:before { + content: "\e925"; +} +.icon-supplierfalse:before { + content: "\e90f"; +} +.icon-tags:before { + content: "\e96d"; +} +.icon-tax:before { + content: "\e940"; +} +.icon-thermometer:before { + content: "\e933"; +} +.icon-ticket:before { + content: "\e96b"; +} +.icon-ticketAdd:before { + content: "\e945"; +} +.icon-traceability:before { + content: "\e962"; +} +.icon-transaction:before { + content: "\e966"; +} +.icon-treatments:before { + content: "\e922"; +} +.icon-unavailable:before { + content: "\e92c"; +} +.icon-volume:before { + content: "\e96e"; +} +.icon-wand:before { + content: "\e93a"; } .icon-web:before { content: "\e982"; } -.icon-albaran:before { - content: "\e983"; +.icon-wiki:before { + content: "\e92d"; } -.icon-dfiscales:before { - content: "\e984"; +.icon-worker:before { + content: "\e957"; +} +.icon-zone:before { + content: "\e943"; } diff --git a/front/core/styles/icons/salixfont.eot b/front/core/styles/icons/salixfont.eot index 98a111250f..9325d3dae5 100644 Binary files a/front/core/styles/icons/salixfont.eot and b/front/core/styles/icons/salixfont.eot differ diff --git a/front/core/styles/icons/salixfont.svg b/front/core/styles/icons/salixfont.svg index 5a77169f59..22c33c181e 100644 --- a/front/core/styles/icons/salixfont.svg +++ b/front/core/styles/icons/salixfont.svg @@ -12,19 +12,19 @@ - + - + - - + + - + - - + + @@ -37,30 +37,31 @@ - + - - - - + + + + - - + + - + + - + - + - - + + @@ -72,21 +73,20 @@ - - + + - + - + - + - @@ -97,8 +97,6 @@ - - @@ -107,10 +105,8 @@ - - @@ -121,7 +117,6 @@ - @@ -130,11 +125,8 @@ - - + - - \ No newline at end of file diff --git a/front/core/styles/icons/salixfont.ttf b/front/core/styles/icons/salixfont.ttf index e0b54f54d9..05df06213d 100644 Binary files a/front/core/styles/icons/salixfont.ttf and b/front/core/styles/icons/salixfont.ttf differ diff --git a/front/core/styles/icons/salixfont.woff b/front/core/styles/icons/salixfont.woff index 4fa201895b..70fca22945 100644 Binary files a/front/core/styles/icons/salixfont.woff and b/front/core/styles/icons/salixfont.woff differ diff --git a/modules/client/back/methods/defaulter/filter.js b/modules/client/back/methods/defaulter/filter.js new file mode 100644 index 0000000000..c06d1c51b2 --- /dev/null +++ b/modules/client/back/methods/defaulter/filter.js @@ -0,0 +1,90 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('filter', { + description: 'Find all instances of the model matched by filter from the data source.', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + http: {source: 'query'} + }, + { + arg: 'search', + type: 'string', + description: `If it's and integer searchs by id, otherwise it searchs by name` + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/filter`, + verb: 'GET' + } + }); + + Self.filter = async(ctx, filter, options) => { + const conn = Self.dataSource.connector; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return {or: [ + {'d.clientFk': value}, + {'d.clientName': {like: `%${value}%`}} + ]}; + } + }); + + filter = mergeFilters(ctx.args.filter, {where}); + + const stmts = []; + + const stmt = new ParameterizedSQL( + `SELECT * + FROM ( + SELECT + DISTINCT c.id clientFk, + c.name clientName, + c.salesPersonFk, + u.name salesPersonName, + d.amount, + co.created, + CONCAT(DATE(co.created), ' ', co.text) observation, + uw.id workerFk, + uw.name workerName, + c.creditInsurance, + d.defaulterSinced + FROM vn.defaulter d + JOIN vn.client c ON c.id = d.clientFk + LEFT JOIN vn.clientObservation co ON co.clientFk = c.id + LEFT JOIN account.user u ON u.id = c.salesPersonFk + LEFT JOIN account.user uw ON uw.id = co.workerFk + WHERE + d.created = CURDATE() + AND d.amount > 0 + ORDER BY co.created DESC) d` + ); + + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge(`GROUP BY d.clientFk`); + stmt.merge(conn.makeOrderBy(filter.order)); + + const itemsIndex = stmts.push(stmt) - 1; + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + + return itemsIndex === 0 ? result : result[itemsIndex]; + }; +}; diff --git a/modules/client/back/methods/defaulter/specs/filter.spec.js b/modules/client/back/methods/defaulter/specs/filter.spec.js new file mode 100644 index 0000000000..145bb51321 --- /dev/null +++ b/modules/client/back/methods/defaulter/specs/filter.spec.js @@ -0,0 +1,63 @@ +const models = require('vn-loopback/server/server').models; + +describe('defaulter filter()', () => { + const authUserId = 9; + it('should all return the tickets matching the filter', async() => { + const tx = await models.Defaulter.beginTransaction({}); + + try { + const options = {transaction: tx}; + const filter = {}; + const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}}; + + const result = await models.Defaulter.filter(ctx, null, options); + const firstRow = result[0]; + + expect(firstRow.clientFk).toEqual(1101); + expect(result.length).toEqual(5); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the defaulter with id', async() => { + const tx = await models.Defaulter.beginTransaction({}); + + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}}; + + const result = await models.Defaulter.filter(ctx, null, options); + const firstRow = result[0]; + + expect(firstRow.clientFk).toEqual(1101); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the defaulter matching the client name', async() => { + const tx = await models.Defaulter.beginTransaction({}); + + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}}; + + const result = await models.Defaulter.filter(ctx, null, options); + const firstRow = result[0]; + + expect(firstRow.clientName).toEqual('Bruce Wayne'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/client/back/models/defaulter.js b/modules/client/back/models/defaulter.js new file mode 100644 index 0000000000..13bb1a6146 --- /dev/null +++ b/modules/client/back/models/defaulter.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/defaulter/filter')(Self); +}; diff --git a/modules/client/back/models/defaulter.json b/modules/client/back/models/defaulter.json index 8d50356f17..8293264359 100644 --- a/modules/client/back/models/defaulter.json +++ b/modules/client/back/models/defaulter.json @@ -8,6 +8,9 @@ } }, "properties": { + "id": { + "type": "Number" + }, "created": { "type": "Date" }, diff --git a/modules/client/front/defaulter/index.html b/modules/client/front/defaulter/index.html new file mode 100644 index 0000000000..121556df24 --- /dev/null +++ b/modules/client/front/defaulter/index.html @@ -0,0 +1,186 @@ + + + + + + + + + +
+
+
Total
+ + +
+
+
+ + +
+
+ + + + + + + + Client + Comercial + + Balance D. + + + Author + + Last observation + + Credit I. + + From + + + + + + + + + + + {{::defaulter.clientName}} + + + + + {{::defaulter.salesPersonName | dashIfEmpty}} + + + {{::defaulter.amount}} + + + {{::defaulter.workerName | dashIfEmpty}} + + + + + + + {{::defaulter.creditInsurance}} + {{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}} + + + +
+
+ + + + + + + + + + + + + + Filter by selection + + + Exclude selection + + + Remove filter + + + Remove all filters + + + Copy value + + + + + + + +
+
{{$ctrl.$t('Add observation to all selected clients', {total: $ctrl.checked.length})}}
+ + + + +
+
+ + + + +
diff --git a/modules/client/front/defaulter/index.js b/modules/client/front/defaulter/index.js new file mode 100644 index 0000000000..76afeb1605 --- /dev/null +++ b/modules/client/front/defaulter/index.js @@ -0,0 +1,65 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; +import UserError from 'core/lib/user-error'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.defaulter = {}; + } + + get balanceDueTotal() { + let balanceDueTotal = 0; + + if (this.checked.length > 0) { + for (let defaulter of this.checked) + balanceDueTotal += defaulter.amount; + + return balanceDueTotal; + } + + return balanceDueTotal; + } + + get checked() { + const clients = this.$.model.data || []; + const checkedLines = []; + for (let defaulter of clients) { + if (defaulter.checked) + checkedLines.push(defaulter); + } + + return checkedLines; + } + + onResponse() { + if (!this.defaulter.observation) + throw new UserError(`The message can't be empty`); + + const params = []; + for (let defaulter of this.checked) { + params.push({ + text: this.defaulter.observation, + clientFk: defaulter.clientFk + }); + } + + this.$http.post(`ClientObservations`, params) .then(() => { + this.vnApp.showMessage(this.$t('Observation saved!')); + this.$state.reload(); + }); + } + + exprBuilder(param, value) { + switch (param) { + case 'clientName': + case 'salesPersonFk': + return {[`d.${param}`]: value}; + } + } +} + +ngModule.vnComponent('vnClientDefaulterIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/client/front/defaulter/index.spec.js b/modules/client/front/defaulter/index.spec.js new file mode 100644 index 0000000000..6428952ec6 --- /dev/null +++ b/modules/client/front/defaulter/index.spec.js @@ -0,0 +1,98 @@ +import './index'; +import crudModel from 'core/mocks/crud-model'; + +describe('client defaulter', () => { + describe('Component vnClientDefaulterIndex', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('client')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + const $element = angular.element(''); + controller = $componentController('vnClientDefaulterIndex', {$element}); + controller.$.model = crudModel; + controller.$.model.data = [ + {clientFk: 1101, amount: 125}, + {clientFk: 1102, amount: 500}, + {clientFk: 1103, amount: 250} + ]; + })); + + describe('checked() getter', () => { + it('should return the checked lines', () => { + const data = controller.$.model.data; + data[1].checked = true; + data[2].checked = true; + + const checkedRows = controller.checked; + + const firstCheckedRow = checkedRows[0]; + const secondCheckedRow = checkedRows[1]; + + expect(firstCheckedRow.clientFk).toEqual(1102); + expect(secondCheckedRow.clientFk).toEqual(1103); + }); + }); + + describe('balanceDueTotal() getter', () => { + it('should return balance due total', () => { + const data = controller.$.model.data; + data[1].checked = true; + data[2].checked = true; + + const checkedRows = controller.checked; + const expectedAmount = checkedRows[0].amount + checkedRows[1].amount; + + const result = controller.balanceDueTotal; + + expect(result).toEqual(expectedAmount); + }); + }); + + describe('onResponse()', () => { + it('should return error for empty message', () => { + let error; + try { + controller.onResponse(); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.message).toBe(`The message can't be empty`); + }); + + it('should return saved message', () => { + const data = controller.$.model.data; + data[1].checked = true; + controller.defaulter = {observation: 'My new observation'}; + + const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}]; + + jest.spyOn(controller.vnApp, 'showMessage'); + $httpBackend.expect('POST', `ClientObservations`, params).respond(200, params); + + controller.onResponse(); + $httpBackend.flush(); + + expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Observation saved!'); + }); + }); + + describe('exprBuilder()', () => { + it('should search by sales person', () => { + let expr = controller.exprBuilder('salesPersonFk', '5'); + + expect(expr).toEqual({'d.salesPersonFk': '5'}); + }); + + it('should search by client name', () => { + let expr = controller.exprBuilder('clientName', '1foo'); + + expect(expr).toEqual({'d.clientName': '1foo'}); + }); + }); + }); +}); diff --git a/modules/client/front/defaulter/locale/es.yml b/modules/client/front/defaulter/locale/es.yml new file mode 100644 index 0000000000..172a3125dc --- /dev/null +++ b/modules/client/front/defaulter/locale/es.yml @@ -0,0 +1,7 @@ +Last observation: Última observación +Add observation: Añadir observación +Search client: Buscar clientes +Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s) +Credit I.: Crédito A. +Balance D.: Saldo V. +Worker who made the last observation: Trabajador que ha realizado la última observación \ No newline at end of file diff --git a/modules/client/front/index.js b/modules/client/front/index.js index 758b94e3f5..6b35d392a3 100644 --- a/modules/client/front/index.js +++ b/modules/client/front/index.js @@ -44,3 +44,4 @@ import './dms/create'; import './dms/edit'; import './consumption'; import './consumption-search-panel'; +import './defaulter'; diff --git a/modules/client/front/locale/es.yml b/modules/client/front/locale/es.yml index 1a5a570a7f..107931377a 100644 --- a/modules/client/front/locale/es.yml +++ b/modules/client/front/locale/es.yml @@ -33,6 +33,7 @@ Search client by id or name: Buscar clientes por identificador o nombre # Sections Clients: Clientes +Defaulter: Morosos New client: Nuevo cliente Fiscal data: Datos fiscales Billing data: Forma de pago diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json index 765fbc6371..753948f8eb 100644 --- a/modules/client/front/routes.json +++ b/modules/client/front/routes.json @@ -6,7 +6,8 @@ "dependencies": ["worker", "invoiceOut"], "menus": { "main": [ - {"state": "client.index", "icon": "person"} + {"state": "client.index", "icon": "person"}, + {"state": "client.defaulter.index", "icon": "icon-defaulter"} ], "card": [ {"state": "client.card.basicData", "icon": "settings"}, @@ -360,6 +361,18 @@ "params": { "client": "$ctrl.client" } + }, + { + "url": "/defaulter", + "state": "client.defaulter", + "component": "ui-view", + "description": "Defaulter" + }, + { + "url": "/index?q", + "state": "client.defaulter.index", + "component": "vn-client-defaulter-index", + "description": "Defaulter" } ] } diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index 53e5fedc84..de06212c76 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -77,6 +77,12 @@ module.exports = Self => { type: 'number', description: 'Action id', required: true + }, + { + arg: 'isWithoutNegatives', + type: 'boolean', + description: 'Is whithout negatives', + required: true }], returns: { type: ['object'], @@ -127,6 +133,18 @@ module.exports = Self => { } } + if (args.isWithoutNegatives) { + const query = `CALL ticket_getMovable(?,?,?)`; + const params = [args.id, args.shipped, args.warehouseFk]; + const [salesMovable] = await Self.rawSql(query, params, myOptions); + + const salesNewTicket = salesMovable.filter(sale => (sale.movable ?? 0) >= sale.quantity); + if (salesNewTicket.length) { + const newTicket = await models.Ticket.transferSales(ctx, args.id, null, salesNewTicket, myOptions); + args.id = newTicket.id; + } + } + const originalTicket = await models.Ticket.findOne({ where: {id: args.id}, fields: [ @@ -230,8 +248,9 @@ module.exports = Self => { await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); } + res.id = args.id; if (tx) await tx.commit(); - + return res; } catch (e) { if (tx) await tx.rollback(); diff --git a/modules/ticket/back/methods/ticket/priceDifference.js b/modules/ticket/back/methods/ticket/priceDifference.js index 1856cb08f3..c91956ece0 100644 --- a/modules/ticket/back/methods/ticket/priceDifference.js +++ b/modules/ticket/back/methods/ticket/priceDifference.js @@ -40,6 +40,12 @@ module.exports = Self => { type: 'number', description: 'The warehouse id', required: true + }, + { + arg: 'shipped', + type: 'date', + description: 'shipped', + required: true }], returns: { type: ['object'], @@ -104,19 +110,32 @@ module.exports = Self => { totalDifference: 0.00, }; - const query = `CALL vn.ticket_priceDifference(?, ?, ?, ?, ?)`; - const params = [args.id, args.landed, args.addressId, args.zoneId, args.warehouseId]; + // Get items movable + const ticketOrigin = await models.Ticket.findById(args.id, null, myOptions); + const differenceShipped = ticketOrigin.shipped.getTime() != args.shipped.getTime(); + const differenceWarehouse = ticketOrigin.warehouseFk != args.warehouseId; + + salesObj.haveDifferences = differenceShipped || differenceWarehouse; + + let query = `CALL ticket_getMovable(?,?,?)`; + let params = [args.id, args.shipped, args.warehouseId]; + const [salesMovable] = await Self.rawSql(query, params, myOptions); + + const itemMovable = new Map(); + for (sale of salesMovable) + itemMovable.set(sale.id, sale.movable ?? 0); + + // Sale price component, one per sale + query = `CALL vn.ticket_priceDifference(?, ?, ?, ?, ?)`; + params = [args.id, args.landed, args.addressId, args.zoneId, args.warehouseId]; const [difComponents] = await Self.rawSql(query, params, myOptions); const map = new Map(); - - // Sale price component, one per sale for (difComponent of difComponents) map.set(difComponent.saleFk, difComponent); for (sale of salesObj.items) { const difComponent = map.get(sale.id); - if (difComponent) { sale.component = difComponent; @@ -129,10 +148,11 @@ module.exports = Self => { salesObj.totalUnitPrice += sale.price; salesObj.totalUnitPrice = round(salesObj.totalUnitPrice); + sale.movable = itemMovable.get(sale.id); } if (tx) await tx.commit(); - + return salesObj; } catch (e) { if (tx) await tx.rollback(); diff --git a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js index 9fa69b595e..2aa2a07c4c 100644 --- a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js +++ b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js @@ -45,7 +45,8 @@ describe('ticket componentUpdate()', () => { shipped: today, landed: tomorrow, isDeleted: false, - option: 1 + option: 1, + isWithoutNegatives: false }; let ctx = { @@ -94,7 +95,8 @@ describe('ticket componentUpdate()', () => { shipped: today, landed: tomorrow, isDeleted: false, - option: 1 + option: 1, + isWithoutNegatives: false }; const ctx = { @@ -134,4 +136,60 @@ describe('ticket componentUpdate()', () => { throw e; } }); + + it('should change warehouse and without negatives', async() => { + const tx = await models.SaleComponent.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const saleToTransfer = 27; + const originDate = today; + const newDate = tomorrow; + const ticketID = 14; + newDate.setHours(0, 0, 0, 0, 0); + originDate.setHours(0, 0, 0, 0, 0); + + const args = { + id: ticketID, + clientFk: 1104, + agencyModeFk: 2, + addressFk: 4, + zoneFk: 9, + warehouseFk: 1, + companyFk: 442, + shipped: newDate, + landed: tomorrow, + isDeleted: false, + option: 1, + isWithoutNegatives: true + }; + + const ctx = { + args: args, + req: { + accessToken: {userId: 9}, + headers: {origin: 'http://localhost'}, + __: value => { + return value; + } + } + }; + await models.Ticket.componentUpdate(ctx, options); + + const [newTicketID] = await models.Ticket.rawSql('SELECT MAX(id) as id FROM ticket', null, options); + const oldTicket = await models.Ticket.findById(ticketID, null, options); + const newTicket = await models.Ticket.findById(newTicketID.id, null, options); + const newTicketSale = await models.Sale.findOne({where: {ticketFk: args.id}}, options); + + expect(oldTicket.shipped).toEqual(originDate); + expect(newTicket.shipped).toEqual(newDate); + expect(newTicketSale.id).toEqual(saleToTransfer); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); diff --git a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js index fed899d779..e9aa5030a1 100644 --- a/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js +++ b/modules/ticket/back/methods/ticket/specs/priceDifference.spec.js @@ -15,6 +15,7 @@ describe('sale priceDifference()', () => { ctx.args = { id: 16, landed: tomorrow, + shipped: tomorrow, addressId: 126, agencyModeId: 7, zoneId: 3, @@ -45,6 +46,7 @@ describe('sale priceDifference()', () => { ctx.args = { id: 1, landed: new Date(), + shipped: new Date(), addressId: 121, zoneId: 3, warehouseId: 1 @@ -59,4 +61,38 @@ describe('sale priceDifference()', () => { expect(error).toEqual(new UserError(`The sales of this ticket can't be modified`)); }); + + it('should return ticket movable', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + + const ctx = {req: {accessToken: {userId: 1106}}}; + ctx.args = { + id: 11, + shipped: tomorrow, + landed: tomorrow, + addressId: 122, + agencyModeId: 7, + zoneId: 3, + warehouseId: 1 + }; + + const result = await models.Ticket.priceDifference(ctx, options); + const firstItem = result.items[0]; + const secondtItem = result.items[1]; + + expect(firstItem.movable).toEqual(440); + expect(secondtItem.movable).toEqual(1980); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); diff --git a/modules/ticket/front/basic-data/step-one/index.js b/modules/ticket/front/basic-data/step-one/index.js index 215f36a46e..f532265e22 100644 --- a/modules/ticket/front/basic-data/step-one/index.js +++ b/modules/ticket/front/basic-data/step-one/index.js @@ -201,7 +201,8 @@ class Controller extends Component { addressId: this.ticket.addressFk, agencyModeId: this.ticket.agencyModeFk, zoneId: this.ticket.zoneFk, - warehouseId: this.ticket.warehouseFk + warehouseId: this.ticket.warehouseFk, + shipped: this.ticket.shipped }; return this.$http.post(query, params).then(res => { diff --git a/modules/ticket/front/basic-data/step-two/index.html b/modules/ticket/front/basic-data/step-two/index.html index 439f2b527f..092c9e7462 100644 --- a/modules/ticket/front/basic-data/step-two/index.html +++ b/modules/ticket/front/basic-data/step-two/index.html @@ -9,6 +9,7 @@ Item Description + Movable Quantity Price (PPU) New (PPU) @@ -31,6 +32,13 @@ tabindex="-1"> + + + {{::sale.movable}} + + {{::sale.quantity}} {{::sale.price | currency: 'EUR': 2}} {{::sale.component.newPrice | currency: 'EUR': 2}} @@ -66,6 +74,13 @@ +
+ + +
diff --git a/modules/ticket/front/basic-data/step-two/index.js b/modules/ticket/front/basic-data/step-two/index.js index 7ffdb8315d..c12647aa5c 100644 --- a/modules/ticket/front/basic-data/step-two/index.js +++ b/modules/ticket/front/basic-data/step-two/index.js @@ -20,6 +20,7 @@ class Controller extends Component { this.getTotalNewPrice(); this.getTotalDifferenceOfPrice(); this.loadDefaultTicketAction(); + this.ticketHaveNegatives(); } loadDefaultTicketAction() { @@ -63,6 +64,22 @@ class Controller extends Component { this.totalPriceDifference = totalPriceDifference; } + ticketHaveNegatives() { + let haveNegatives = false; + let haveNotNegatives = false; + const haveDifferences = this.ticket.sale.haveDifferences; + + this.ticket.sale.items.forEach(item => { + if (item.quantity > item.movable) + haveNegatives = true; + else + haveNotNegatives = true; + }); + + this.ticket.withoutNegatives = false; + this.haveNegatives = (haveNegatives && haveNotNegatives && haveDifferences); + } + onSubmit() { if (!this.ticket.option) { return this.vnApp.showError( @@ -70,8 +87,8 @@ class Controller extends Component { ); } - let query = `tickets/${this.ticket.id}/componentUpdate`; - let params = { + const query = `tickets/${this.ticket.id}/componentUpdate`; + const params = { clientFk: this.ticket.clientFk, nickname: this.ticket.nickname, agencyModeFk: this.ticket.agencyModeFk, @@ -82,16 +99,20 @@ class Controller extends Component { shipped: this.ticket.shipped, landed: this.ticket.landed, isDeleted: this.ticket.isDeleted, - option: parseInt(this.ticket.option) + option: parseInt(this.ticket.option), + isWithoutNegatives: this.ticket.withoutNegatives }; - this.$http.post(query, params).then(res => { - this.vnApp.showMessage( - this.$t(`The ticket has been unrouted`) - ); - this.card.reload(); - this.$state.go('ticket.card.summary', {id: this.$params.id}); - }); + this.$http.post(query, params) + .then(res => { + this.ticketToMove = res.data.id; + this.vnApp.showMessage( + this.$t(`The ticket has been unrouted`) + ); + }) + .finally(() => { + this.$state.go('ticket.card.summary', {id: this.ticketToMove}); + }); } } diff --git a/modules/ticket/front/basic-data/step-two/index.spec.js b/modules/ticket/front/basic-data/step-two/index.spec.js index ea82687165..ac85a7818b 100644 --- a/modules/ticket/front/basic-data/step-two/index.spec.js +++ b/modules/ticket/front/basic-data/step-two/index.spec.js @@ -64,5 +64,103 @@ describe('Ticket', () => { expect(controller.totalPriceDifference).toEqual(0.3); }); }); + + describe('ticketHaveNegatives()', () => { + it('should show if ticket have any negative, have differences, but not all sale are negative', () => { + controller.ticket = { + sale: { + items: [ + { + item: 1, + quantity: 2, + movable: 1 + }, + { + item: 2, + quantity: 1, + movable: 5 + } + ], + haveDifferences: true + } + }; + + controller.ticketHaveNegatives(); + + expect(controller.haveNegatives).toEqual(true); + }); + + it('should not show if ticket not have any negative', () => { + controller.ticket = { + sale: { + items: [ + { + item: 1, + quantity: 2, + movable: 1 + }, + { + item: 2, + quantity: 2, + movable: 1 + } + ], + haveDifferences: true + } + }; + + controller.ticketHaveNegatives(); + + expect(controller.haveNegatives).toEqual(false); + }); + + it('should not show if all sale are negative', () => { + controller.ticket = { + sale: { + items: [ + { + item: 1, + quantity: 2, + movable: 1 + }, + { + item: 2, + quantity: 2, + movable: 1 + } + ], + haveDifferences: true + } + }; + + controller.ticketHaveNegatives(); + + expect(controller.haveNegatives).toEqual(false); + }); + + it('should not show if ticket not have differences', () => { + controller.ticket = { + sale: { + items: [ + { + item: 1, + quantity: 2, + movable: 1 + }, + { + item: 2, + quantity: 1, + movable: 2 + } + ], + haveDifferences: false + } + }; + + controller.ticketHaveNegatives(); + + expect(controller.haveNegatives).toEqual(false); + }); + }); }); }); diff --git a/modules/ticket/front/basic-data/step-two/locale/es.yml b/modules/ticket/front/basic-data/step-two/locale/es.yml index a2a07991bf..e1f1e0bfc2 100644 --- a/modules/ticket/front/basic-data/step-two/locale/es.yml +++ b/modules/ticket/front/basic-data/step-two/locale/es.yml @@ -5,4 +5,7 @@ Charge difference to: Cargar diferencia a The ticket has been unrouted: El ticket ha sido desenrutado Price: Precio New price: Nuevo precio -Price difference: Diferencia de precio \ No newline at end of file +Price difference: Diferencia de precio +Create without negatives: Crear sin negativos +Clone this ticket with the changes and only sales availables: Clona este ticket con los cambios y solo las ventas disponibles. +Movable: Movible \ No newline at end of file