diff --git a/db/Dockerfile b/db/Dockerfile index c376e6893..60e25319f 100644 --- a/db/Dockerfile +++ b/db/Dockerfile @@ -35,5 +35,5 @@ ENTRYPOINT ["docker-start.sh"] CMD ["mysqld"] -#HEALTHCHECK --interval=5s --timeout=10s --retries=200 \ -# CMD mysqladmin ping -h 127.0.0.1 -u root || exit 1 +HEALTHCHECK --interval=5s --timeout=10s --retries=200 \ + CMD mysqladmin ping -h 127.0.0.1 -u root || exit 1 diff --git a/db/changes/10170-NOFallas/00-aclWorkerDms.sql b/db/changes/10170-NOFallas/00-aclWorkerDms.sql deleted file mode 100644 index 46e56f77e..000000000 --- a/db/changes/10170-NOFallas/00-aclWorkerDms.sql +++ /dev/null @@ -1,5 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) -VALUES -('WorkerDms', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'), -('WorkerDms', 'downloadFile', 'READ', 'ALLOW', 'ROLE', 'employee'); -DELETE FROM `salix`.`ACL` WHERE (`id` = '205'); diff --git a/db/changes/10170-NOFallas/00-zone_getEvents.sql b/db/changes/10170-NOFallas/00-zone_getEvents.sql deleted file mode 100644 index 991150db7..000000000 --- a/db/changes/10170-NOFallas/00-zone_getEvents.sql +++ /dev/null @@ -1,119 +0,0 @@ -USE `vn`; -DROP procedure IF EXISTS `zone_getEvents`; - -DELIMITER $$ -USE `vn`$$ -CREATE DEFINER=`root`@`%` PROCEDURE `zone_getEvents`( - vGeoFk INT, - vAgencyModeFk INT) -BEGIN -/** - * Returns available events for the passed province/postcode and agency. - * - * @param vGeoFk The geo id - * @param vAgencyModeFk The agency mode id - */ - DECLARE vDeliveryMethodFk VARCHAR(255); - - DROP TEMPORARY TABLE IF EXISTS tZone; - CREATE TEMPORARY TABLE tZone - (id INT PRIMARY KEY) - ENGINE = MEMORY; - - SELECT dm.`code` INTO vDeliveryMethodFk - FROM agencyMode am - JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk - WHERE am.id = vAgencyModeFk; - - IF vDeliveryMethodFk = 'PICKUP' THEN - INSERT INTO tZone - SELECT id - FROM zone - WHERE agencyModeFk = vAgencyModeFk; - ELSE - CALL zone_getFromGeo(vGeoFk); - - IF vAgencyModeFk IS NOT NULL THEN - INSERT INTO tZone - SELECT t.id - FROM tmp.zone t - JOIN zone z ON z.id = t.id - WHERE z.agencyModeFk = vAgencyModeFk; - ELSE - INSERT INTO tZone - SELECT t.id - FROM tmp.zone t - JOIN zone z ON z.id = t.id - JOIN agencyMode am ON am.id = z.agencyModeFk - JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk - WHERE dm.`code` IN ('AGENCY', 'DELIVERY'); - END IF; - DROP TEMPORARY TABLE tmp.zone; - END IF; - - SELECT e.zoneFk, e.`type`, e.dated, e.`started`, e.`ended`, e.weekDays - FROM tZone t - JOIN zoneEvent e ON e.zoneFk = t.id; - - SELECT e.zoneFk, e.dated - FROM tZone t - JOIN zoneExclusion e ON e.zoneFk = t.id; - - DROP TEMPORARY TABLE tZone; -END$$ - -DELIMITER ; - -USE `vn`; -DROP procedure IF EXISTS `zone_getEvents__`; - -DELIMITER $$ -USE `vn`$$ -CREATE DEFINER=`root`@`%` PROCEDURE `zone_getEvents__`( - vProvinceFk INT, - vPostCode VARCHAR(255), - vAgencyModeFk INT) -BEGIN -/** - * Returns available events for the passed province/postcode and agency. - * - * @param vAgencyModeFk The agency mode id - * @param vProvinceFk The province id - * @param vPostCode The postcode or %NULL to use the province - */ - - DECLARE vGeoFk INT; - - IF vPostCode IS NOT NULL THEN - SELECT p.geoFk INTO vGeoFk - FROM postCode p - JOIN town t ON t.id = p.townFk - WHERE p.`code` = vPostCode - AND t.provinceFk = vProvinceFk; - ELSE - SELECT geoFk INTO vGeoFk - FROM province - WHERE id = vProvinceFk; - END IF; - - CALL zone_getFromGeo(vGeoFk); - - IF vAgencyModeFk IS NOT NULL THEN - DELETE t FROM tmp.zone t - JOIN zone z ON z.id = t.id - WHERE z.agencyModeFk != vAgencyModeFk; - END IF; - - SELECT e.zoneFk, e.`type`, e.dated, e.`started`, e.`ended`, e.weekDays - FROM tmp.zone t - JOIN zoneEvent e ON e.zoneFk = t.id; - - SELECT e.zoneFk, e.dated - FROM tmp.zone t - JOIN zoneExclusion e ON e.zoneFk = t.id; - - DROP TEMPORARY TABLE tmp.zone; -END$$ - -DELIMITER ; - diff --git a/db/changes/10170-NOFallas/00-zone_getWarehouse.sql b/db/changes/10170-NOFallas/00-zone_getWarehouse.sql deleted file mode 100644 index a65643ff5..000000000 --- a/db/changes/10170-NOFallas/00-zone_getWarehouse.sql +++ /dev/null @@ -1,87 +0,0 @@ -USE `vn`; -DROP procedure IF EXISTS `zone_getWarehouse`; - -DELIMITER $$ -USE `vn`$$ -CREATE DEFINER=`root`@`%` PROCEDURE `zone_getWarehouse`(vAddress INT, vLanded DATE, vWarehouse INT) -BEGIN -/** - * Devuelve el listado de agencias disponibles para la fecha, - * dirección y almacén pasados. - * - * @param vAddress - * @param vWarehouse warehouse - * @param vLanded Fecha de recogida - * @select Listado de agencias disponibles - */ - - CALL zone_getFromGeo(address_getGeo(vAddress)); - CALL zone_getOptionsForLanding(vLanded, FALSE); - - SELECT am.id agencyModeFk, - am.name agencyMode, - am.description, - am.deliveryMethodFk, - TIMESTAMPADD(DAY, -zo.travelingDays, vLanded) shipped, - zw.warehouseFk, - z.id zoneFk - FROM tmp.zoneOption zo - JOIN zone z ON z.id = zo.zoneFk - JOIN agencyMode am ON am.id = z.agencyModeFk - JOIN zoneWarehouse zw ON zw.zoneFk = zo.zoneFk - WHERE zw.warehouseFk = vWarehouse - GROUP BY z.agencyModeFk - ORDER BY agencyMode; - - DROP TEMPORARY TABLE - tmp.zone, - tmp.zoneOption; - -END$$ - -DELIMITER ; - -USE `vn`; -DROP procedure IF EXISTS `zone_getWarehouse__`; - -DELIMITER $$ -USE `vn`$$ -CREATE DEFINER=`root`@`%` PROCEDURE `zone_getWarehouse__`(vAddress INT, vLanded DATE, vWarehouse INT) -BEGIN -/** - * Devuelve el listado de agencias disponibles para la fecha, - * dirección y almacén pasados. - * - * @param vAddress - * @param vWarehouse warehouse - * @param vLanded Fecha de recogida - * @select Listado de agencias disponibles - */ - - CALL zone_getFromGeo(address_getGeo(vAddress)); - CALL zone_getOptionsForLanding(vLanded, FALSE); - - SELECT am.id agencyModeFk, - am.name agencyMode, - am.description, - am.deliveryMethodFk, - TIMESTAMPADD(DAY, -zo.travelingDays, vLanded) shipped, - zw.warehouseFk, - z.id zoneFk - FROM tmp.zoneOption zo - JOIN zone z ON z.id = zo.zoneFk - JOIN agencyMode am ON am.id = z.agencyModeFk - JOIN zoneWarehouse zw ON zw.zoneFk = zo.zoneFk - WHERE zw.warehouseFk - GROUP BY z.agencyModeFk - ORDER BY agencyMode; - - DROP TEMPORARY TABLE - tmp.zone, - tmp.zoneOption; - -END$$ - -DELIMITER ; - - diff --git a/db/changes/10190-PostErte/00-ACL.sql b/db/changes/10190-PostErte/00-ACL.sql index 2a2673fcc..8ef44224a 100644 --- a/db/changes/10190-PostErte/00-ACL.sql +++ b/db/changes/10190-PostErte/00-ACL.sql @@ -1,4 +1,5 @@ UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213'; +UPDATE `salix`.`ACL` SET `property` = 'deleteSales' WHERE (`id` = '80'); INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('CustomsAgent', '*', '*', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/PASTE_COMMENT_REMINDER b/db/changes/PASTE_COMMENT_REMINDER new file mode 100644 index 000000000..07b707920 --- /dev/null +++ b/db/changes/PASTE_COMMENT_REMINDER @@ -0,0 +1,4 @@ +/* +Hay una versión en salix que machacará toda esta función/procedimiento +avisa a ___ de los cambios que quieres hacer +*/ \ No newline at end of file diff --git a/db/docker.js b/db/docker.js index 0a221a0be..0cb912be6 100644 --- a/db/docker.js +++ b/db/docker.js @@ -39,8 +39,7 @@ module.exports = class Docker { let runChown = process.platform != 'linux'; - const healthCheck = `--health-cmd='mysqladmin ping --silent'`; - const container = await this.execP(`docker run ${healthCheck} --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`); + const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`); this.id = container.stdout; try { @@ -54,7 +53,7 @@ module.exports = class Docker { this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort']; } - if (runChown) await this.wait(); + await this.waitForHealthy(); } catch (err) { if (this.isRandom) await this.rm(); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index ed2ee8a8f..acd0fbdfb 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1472,6 +1472,9 @@ INSERT INTO `vn`.`clientContact`(`id`, `clientFk`, `name`, `phone`) (3, 101, 'contact 3', 222333444), (4, 102, 'contact 1', 876543219); +INSERT INTO `vn`.`workerManaExcluded`(`workerFk`) + VALUES + (9); /* el mana de los trabajadores lo podemos poner a mano en la tabla si lo calculamos antes, pero si hazemos alguna modificacion en alguna tabla que utiliza para calcularlo ya no seria correcto @@ -1678,11 +1681,13 @@ INSERT INTO `vn`.`workCenterHoliday` (`workCenterFk`, `days`, `year`) ('1', '24.5', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR))), ('5', '23', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR))); -INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `code`) +INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `code`, `holidayEntitlementRate`) VALUES - (1, 'Holidays', '#FF4444', 'holiday'), - (2, 'Leave of absence', '#C71585', 'absence'), - (6, 'Half holiday', '#E65F00', 'halfHoliday'); + (1, 'Holidays', '#FF4444', 'holiday', 0), + (2, 'Leave of absence', '#C71585', 'absence', 0), + (6, 'Half holiday', '#E65F00', 'halfHoliday', 0), + (20, 'Furlough', '#97B92F', 'furlough', 1), + (21, 'Furlough half day', '#778899', 'halfFurlough', 0.5); INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`, `date`) VALUES diff --git a/e2e/helpers/extensions.js b/e2e/helpers/extensions.js index 636819860..a3c52748d 100644 --- a/e2e/helpers/extensions.js +++ b/e2e/helpers/extensions.js @@ -558,7 +558,7 @@ let actions = { }, selector); }, - closePopup: async function(selector) { + closePopup: async function() { await Promise.all([ this.keyboard.press('Escape'), this.waitFor('.vn-popup', {hidden: true}), diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 1f1ce7da5..cb29afac9 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -401,8 +401,9 @@ export default { createButton: `button[type=submit]` }, ticketDescriptor: { - idLabelValue: 'vn-ticket-descriptor vn-label-value[label="Id"]', + id: 'vn-descriptor-content div.top > div', stateLabelValue: 'vn-ticket-descriptor vn-label-value[label="State"]', + isDeletedIcon: 'vn-ticket-descriptor vn-icon[icon="icon-deletedTicket"]', goBackToModuleIndexButton: 'vn-ticket-descriptor a[ui-sref="ticket.index"]', moreMenu: 'vn-ticket-descriptor vn-icon-button[icon=more_vert]', moreMenuAddStowaway: '.vn-menu [name="addStowaway"]', @@ -445,20 +446,23 @@ export default { savePackagesButton: `button[type=submit]` }, ticketSales: { + setOk: 'vn-ticket-sale vn-tool-bar > vn-button[label="Ok"] > button', saleButton: 'vn-left-menu a[ui-sref="ticket.card.sale"]', saleLine: 'vn-table div > vn-tbody > vn-tr', saleDescriptorPopover: '.vn-popover.shown vn-item-descriptor', - saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.item.id})"]', + saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.descriptor.id})"]', descriptorItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a', newItemFromCatalogButton: 'vn-ticket-sale vn-float-button[icon="add"]', newItemButton: 'vn-ticket-sale vn-card vn-icon-button[icon="add_circle"]', - moreMenu: 'vn-ticket-sale vn-tool-bar > vn-button-menu[vn-id="more-button"] > div > button', - moreMenuCreateClaim: '.vn-drop-down.shown li[name="Add claim"]', - moreMenuReserve: '.vn-drop-down.shown li[name="Mark as reserved"]', - moreMenuUnmarkReseved: '.vn-drop-down.shown li[name="Unmark as reserved"]', - moreMenuUpdateDiscount: '.vn-drop-down.shown li[name="Update discount"]', + moreMenu: 'vn-ticket-sale vn-button[label="More"]', + moreMenuCreateClaim: 'vn-item[name="claim"]', + moreMenuReserve: 'vn-item[name="reserve"]', + moreMenuUnmarkReseved: 'vn-item[name="unreserve"]', + moreMenuUpdateDiscount: 'vn-item[name="discount"]', + moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input', transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text', transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable', + firstSaleId: 'vn-ticket-sale vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span', firstSaleClaimIcon: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) vn-icon[icon="icon-claims"]', firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img', firstSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(1)', @@ -468,9 +472,9 @@ export default { firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(5)', firstSaleIdAutocomplete: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > vn-autocomplete', firstSalePrice: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(7) > span', - firstSalePriceInput: '.vn-popover.shown [ng-model="$ctrl.editedPrice"]', + firstSalePriceInput: '.vn-popover.shown input[ng-model="$ctrl.field"]', firstSaleDiscount: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(8) > span', - firstSaleDiscountInput: '.vn-popover.shown [ng-model="$ctrl.newDiscount"]', + firstSaleDiscountInput: '.vn-popover.shown [ng-model="$ctrl.field"]', firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(9)', firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)', firstSaleColour: 'vn-ticket-sale vn-tr:nth-child(1) vn-fetched-tags section', @@ -486,20 +490,22 @@ export default { secondSaleIdInput: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete', secondSaleIdAutocomplete: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-autocomplete', secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number', + secondSaleQuantityCell: 'vn-ticket-sale > div > vn-card > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(5)', secondSaleConceptCell: 'vn-ticket-sale vn-tbody > :nth-child(2) > :nth-child(6)', secondSaleConceptInput: 'vn-ticket-sale vn-tbody > :nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield', - totalImport: 'vn-ticket-sale > vn-vertical > vn-card > vn-vertical > vn-horizontal > vn-one > p:nth-child(3) > strong', + totalImport: 'vn-ticket-sale vn-one.taxes > p:nth-child(3) > strong', selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check', secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]', thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[ng-model="sale.checked"]', deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]', transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]', - moveToTicketInput: '.vn-popover.shown vn-textfield[ng-model="$ctrl.transfer.ticketId"]', + moveToTicketInput: 'form vn-input-number[ng-model="$ctrl.transfer.ticketId"] input', moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]', moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]', acceptDeleteLineButton: '.vn-confirm.shown button[response=accept]', acceptDeleteTicketButton: '.vn-confirm.shown button[response=accept]', - stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]' + stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]', + stateMenuFixOption: '' }, ticketTracking: { trackingButton: 'vn-left-menu a[ui-sref="ticket.card.tracking.index"]', diff --git a/e2e/paths/05-ticket/01-sale/01_list_sales.spec.js b/e2e/paths/05-ticket/01-sale/01_list_sales.spec.js index f97447c06..f883d08dc 100644 --- a/e2e/paths/05-ticket/01-sale/01_list_sales.spec.js +++ b/e2e/paths/05-ticket/01-sale/01_list_sales.spec.js @@ -72,7 +72,8 @@ describe('Ticket List sale path', () => { }, {}, selectors.ticketSales.secondSaleIdAutocomplete, searchValue); await page.keyboard.press('Enter'); - await page.write(selectors.ticketSales.secondSaleQuantity, '1'); + await page.waitToClick(selectors.ticketSales.secondSaleQuantityCell); + await page.type(selectors.ticketSales.secondSaleQuantity, '1'); await page.keyboard.press('Enter'); const message = await page.waitForSnackbar(); diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js index a22b0e022..a7b24e4a3 100644 --- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js @@ -1,8 +1,7 @@ import selectors from '../../../helpers/selectors.js'; import getBrowser from '../../../helpers/puppeteer'; -// #1632 [e2e] ticket.sale - Transferir líneas -xdescribe('Ticket Edit sale path', () => { +describe('Ticket Edit sale path', () => { let browser; let page; @@ -10,7 +9,7 @@ xdescribe('Ticket Edit sale path', () => { browser = await getBrowser(); page = browser.page; await page.loginAndModule('salesPerson', 'ticket'); - await page.accessToSearchResult(16); + await page.accessToSearchResult('16'); await page.accessToSection('ticket.card.sale'); }); @@ -20,29 +19,50 @@ xdescribe('Ticket Edit sale path', () => { it(`should click on the first sale claim icon to navigate over there`, async() => { await page.waitToClick(selectors.ticketSales.firstSaleClaimIcon); - await page.wait(selectors.claimBasicData.claimState); - const url = await page.parsedUrl(); - - expect(url.hash).toEqual('#!/claim/2/basic-data'); + await page.waitForState('claim.card.basicData'); }); it('should navigate to the tickets index', async() => { await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.wait(selectors.globalItems.applicationsMenuVisible); await page.waitToClick(selectors.globalItems.ticketsButton); - await page.wait(selectors.ticketsIndex.topbarSearch); - const url = await page.parsedUrl(); - - expect(url.hash).toEqual('#!/ticket/index'); + await page.waitForState('ticket.index'); }); it(`should search for a ticket and then navigate to it's sales`, async() => { - await page.accessToSearchResult(16); + await page.accessToSearchResult('16'); await page.accessToSection('ticket.card.sale'); - await page.waitForURL('/sale'); - const url = await page.parsedUrl(); + }); - expect(url.hash).toContain('/sale'); + it(`should set the ticket as libre`, async() => { + await page.waitToClick(selectors.ticketSales.stateMenuButton); + await page.write('body > div > div > div.content > div.filter.ng-scope > vn-textfield', 'libre'); + await page.waitFor(500); + await page.keyboard.press('Enter'); + const message = await page.waitForSnackbar(); + + expect(message.type).toBe('success'); + }); + + it(`should check it's state is libre now`, async() => { + await page.waitForTextInElement(selectors.ticketDescriptor.stateLabelValue, 'Libre'); + const result = await page.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText'); + + expect(result).toEqual('State Libre'); + }); + + it(`should set the ticket as OK`, async() => { + await page.waitToClick(selectors.ticketSales.setOk); + const message = await page.waitForSnackbar(); + + expect(message.type).toBe('success'); + }); + + it(`should check it's state is OK now`, async() => { + await page.waitForTextInElement(selectors.ticketDescriptor.stateLabelValue, 'OK'); + const result = await page.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText'); + + expect(result).toEqual('State OK'); }); it(`should check the zoomed image isn't present`, async() => { @@ -52,26 +72,20 @@ xdescribe('Ticket Edit sale path', () => { }); it(`should click on the thumbnail image of the 1st sale and see the zoomed image`, async() => { - await page.clickIfVisible(selectors.ticketSales.firstSaleThumbnailImage); + await page.waitToClick(selectors.ticketSales.firstSaleThumbnailImage); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage); expect(result).toEqual(1); }); it(`should click on the zoomed image to close it`, async() => { - await page.clickIfVisible(selectors.ticketSales.firstSaleZoomedImage); + await page.waitToClick(selectors.ticketSales.firstSaleZoomedImage); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage); expect(result).toEqual(0); }); - it(`should confirm the item descriptor insnt visible yet`, async() => { - const visible = await page.isVisible(selectors.ticketSales.saleDescriptorPopover); - - expect(visible).toBeFalsy(); - }); - - it(`should click on the first sale ID making the item descriptor visible`, async() => { + it(`should click on the first sale ID making now the item descriptor visible`, async() => { await page.waitToClick(selectors.ticketSales.firstSaleId); await page.waitImgLoad(selectors.ticketSales.firstSaleDescriptorImage); const visible = await page.isVisible(selectors.ticketSales.saleDescriptorPopover); @@ -80,14 +94,14 @@ xdescribe('Ticket Edit sale path', () => { }); it(`should click on the descriptor image of the 1st sale and see the zoomed image`, async() => { - await page.clickIfVisible('vn-item-descriptor img'); + await page.waitToClick('vn-item-descriptor img'); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage); expect(result).toEqual(1); }); it(`should now click on the zoomed image to close it`, async() => { - await page.clickIfVisible(selectors.ticketSales.firstSaleZoomedImage); + await page.waitToClick(selectors.ticketSales.firstSaleZoomedImage); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage); expect(result).toEqual(0); @@ -95,50 +109,46 @@ xdescribe('Ticket Edit sale path', () => { it(`should click on the summary icon of the item-descriptor to access to the item summary`, async() => { await page.waitToClick(selectors.ticketSales.saleDescriptorPopoverSummaryButton); - await page.waitForURL('/summary'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/summary'); + await page.waitForState('item.card.summary'); }); it('should return to ticket sales section', async() => { await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.wait(selectors.globalItems.applicationsMenuVisible); await page.waitToClick(selectors.globalItems.ticketsButton); - await page.accessToSearchResult(16); + await page.accessToSearchResult('16'); await page.accessToSection('ticket.card.sale'); - await page.waitForURL('/sale'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/sale'); }); it('should try to add a higher quantity value and then receive an error', async() => { - await page.focusElement(selectors.ticketSales.firstSaleQuantityCell); - await page.write(selectors.ticketSales.firstSaleQuantity, '11\u000d'); + await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell); + await page.type(selectors.ticketSales.firstSaleQuantity, '11\u000d'); const message = await page.waitForSnackbar(); expect(message.text).toBe('The new quantity should be smaller than the old one'); }); it('should remove 1 from the first sale quantity', async() => { - await page.focusElement(selectors.ticketSales.firstSaleQuantityCell); - await page.write(selectors.ticketSales.firstSaleQuantity, '9\u000d'); + await page.waitFor(500); + await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell); + await page.waitFor(selectors.ticketSales.firstSaleQuantity); + await page.type(selectors.ticketSales.firstSaleQuantity, '9\u000d'); const message = await page.waitForSnackbar(); expect(message.type).toBe('success'); }); it('should update the price', async() => { - await page.waitToClick(`${selectors.ticketSales.firstSalePrice} > span`); - await page.write(selectors.ticketSales.firstSalePriceInput, '5\u000d'); + await page.waitToClick(selectors.ticketSales.firstSalePrice); + await page.waitFor(selectors.ticketSales.firstSalePriceInput); + await page.type(selectors.ticketSales.firstSalePriceInput, '5\u000d'); const message = await page.waitForSnackbar(); expect(message.type).toBe('success'); }); it('should confirm the price have been updated', async() => { - const result = await page.waitToGetProperty(`${selectors.ticketSales.firstSalePrice} span`, 'innerText'); + const result = await page.waitToGetProperty(selectors.ticketSales.firstSalePrice, 'innerText'); expect(result).toContain('5.00'); }); @@ -150,16 +160,17 @@ xdescribe('Ticket Edit sale path', () => { }); it('should update the discount', async() => { - await page.waitToClick(`${selectors.ticketSales.firstSaleDiscount} > span`); - await page.write(selectors.ticketSales.firstSaleDiscountInput, '50\u000d'); + await page.waitToClick(selectors.ticketSales.firstSaleDiscount); + await page.waitFor(selectors.ticketSales.firstSaleDiscountInput); + await page.type(selectors.ticketSales.firstSaleDiscountInput, '50\u000d'); const message = await page.waitForSnackbar(); expect(message.type).toBe('success'); }); it('should confirm the discount have been updated', async() => { - await page.waitForTextInElement(`${selectors.ticketSales.firstSaleDiscount} > span`, '50.00%'); - const result = await page.waitToGetProperty(`${selectors.ticketSales.firstSaleDiscount} > span`, 'innerText'); + await page.waitForTextInElement(selectors.ticketSales.firstSaleDiscount, '50.00%'); + const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleDiscount, 'innerText'); expect(result).toContain('50.00%'); }); @@ -175,48 +186,31 @@ xdescribe('Ticket Edit sale path', () => { await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); - await page.wait(selectors.claimBasicData.claimState); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('basic-data'); + await page.waitForState('claim.card.basicData'); }); it('should click on the Claims button of the top bar menu', async() => { await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.wait(selectors.globalItems.applicationsMenuVisible); await page.waitToClick(selectors.globalItems.claimsButton); - await page.wait(selectors.claimsIndex.searchClaimInput); - const url = await page.parsedUrl(); - - expect(url.hash).toEqual('#!/claim/index'); + await page.waitForState('claim.index'); }); it('should search for the claim with id 4', async() => { - await page.write(selectors.claimsIndex.searchClaimInput, 4); - await page.waitToClick(selectors.claimsIndex.searchButton); - await page.waitForNumberOfElements(selectors.claimsIndex.searchResult, 1); - const result = await page.countElement(selectors.claimsIndex.searchResult); - - expect(result).toEqual(1); + await page.accessToSearchResult('4'); + await page.waitForState('claim.card.summary'); }); it('should click the Tickets button of the top bar menu', async() => { await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.wait(selectors.globalItems.applicationsMenuVisible); await page.waitToClick(selectors.globalItems.ticketsButton); - await page.wait(selectors.ticketsIndex.topbarSearch); - const url = await page.parsedUrl(); - - expect(url.hash).toEqual('#!/ticket/index'); + await page.waitForState('ticket.index'); }); it('should search for a ticket then access to the sales section', async() => { - await page.accessToSearchResult(16); + await page.accessToSearchResult('16'); await page.accessToSection('ticket.card.sale'); - await page.waitForURL('/sale'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/sale'); }); it('should select the third sale and delete it', async() => { @@ -236,18 +230,15 @@ xdescribe('Ticket Edit sale path', () => { }); it('should select the second sale and transfer it to a valid ticket', async() => { - const targetTicketId = 12; + const targetTicketId = '12'; await page.waitToClick(selectors.ticketSales.secondSaleCheckbox); await page.waitToClick(selectors.ticketSales.transferSaleButton); - await page.focusElement(selectors.ticketSales.transferQuantityCell); - await page.write(selectors.ticketSales.transferQuantityInput, '10\u000d'); - await page.write(selectors.ticketSales.moveToTicketInput, targetTicketId); + await page.waitToClick(selectors.ticketSales.transferQuantityCell); + await page.type(selectors.ticketSales.transferQuantityInput, '10\u000d'); + await page.type(selectors.ticketSales.moveToTicketInput, targetTicketId); await page.waitToClick(selectors.ticketSales.moveToTicketButton); - await page.waitForURL(`ticket/${targetTicketId}/sale`); - const result = await page.parsedUrl(); - - expect(result.hash).toContain(`ticket/${targetTicketId}/sale`); + await page.expectURL(`ticket/${targetTicketId}/sale`); }); it('should confirm the transfered line is the correct one', async() => { @@ -265,12 +256,8 @@ xdescribe('Ticket Edit sale path', () => { it('should go back to the original ticket sales section', async() => { await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton); - await page.accessToSearchResult(16); + await page.accessToSearchResult('16'); await page.accessToSection('ticket.card.sale'); - await page.waitForURL('/sale'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/sale'); }); it(`should confirm the original ticket has still three lines`, async() => { @@ -288,25 +275,18 @@ xdescribe('Ticket Edit sale path', () => { it('should go back to the receiver ticket sales section', async() => { await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton); - await page.accessToSearchResult(12); + await page.accessToSearchResult('12'); await page.accessToSection('ticket.card.sale'); - await page.waitForURL('/sale'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/sale'); }); it('should transfer the sale back to the original ticket', async() => { - const targetTicketId = 16; + const targetTicketId = '16'; await page.waitToClick(selectors.ticketSales.secondSaleCheckbox); await page.waitToClick(selectors.ticketSales.transferSaleButton); - await page.write(selectors.ticketSales.moveToTicketInput, targetTicketId); + await page.type(selectors.ticketSales.moveToTicketInput, targetTicketId); await page.waitToClick(selectors.ticketSales.moveToTicketButton); - await page.waitForURL(`ticket/${targetTicketId}/sale`); - const result = await page.parsedUrl(); - - expect(result.hash).toContain(`ticket/${targetTicketId}/sale`); + await page.expectURL(`ticket/${targetTicketId}/sale`); }); it('should confirm the original ticket received the line', async() => { @@ -321,35 +301,29 @@ xdescribe('Ticket Edit sale path', () => { await page.waitToClick(selectors.ticketSales.firstSaleCheckbox); await page.waitToClick(selectors.ticketSales.transferSaleButton); await page.waitToClick(selectors.ticketSales.moveToNewTicketButton); - await page.waitToClick(selectors.ticketSales.acceptDeleteTicketButton); const message = await page.waitForSnackbar(); expect(message.text).toBe(`You can't create a ticket for a inactive client`); + + await page.closePopup(); }); it('should go now to the ticket sales section of an active, not frozen client', async() => { await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton); - await page.accessToSearchResult(13); + await page.accessToSearchResult('13'); await page.accessToSection('ticket.card.sale'); - await page.waitForURL('/sale'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/sale'); }); it(`should select all sales, tranfer them to a new ticket and delete the sender ticket as it would've been left empty`, async() => { - const senderTicketId = 13; + const senderTicketId = '13'; await page.waitToClick(selectors.ticketSales.selectAllSalesCheckbox); await page.waitToClick(selectors.ticketSales.transferSaleButton); await page.waitToClick(selectors.ticketSales.moveToNewTicketButton); - await page.waitToClick(selectors.ticketSales.acceptDeleteTicketButton); - await page.wait((selector, ticketId) => { - return document.querySelector(selector).innerText.toLowerCase().indexOf(`${ticketId}`) == -1; - }, selectors.ticketDescriptor.idLabelValue, senderTicketId); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/sale'); + await page.evaluate((selector, ticketId) => { + return document.querySelector(selector).innerText.toLowerCase().indexOf(`#${ticketId}`) == -1; + }, selectors.ticketDescriptor.id, senderTicketId); + await page.waitForState('ticket.card.sale'); }); it('should confirm the new ticket received the line', async() => { @@ -368,6 +342,7 @@ xdescribe('Ticket Edit sale path', () => { await page.waitToClick(selectors.ticketSales.firstSaleCheckbox); await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenuReserve); + await page.closePopup(); await page.waitForClassNotPresent(selectors.ticketSales.firstSaleReservedIcon, 'ng-hide'); const result = await page.isVisible(selectors.ticketSales.firstSaleReservedIcon); @@ -384,10 +359,12 @@ xdescribe('Ticket Edit sale path', () => { }); it('should update all sales discount', async() => { + await page.closePopup(); await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenuUpdateDiscount); - // .write(selectors.ticketSales.moreMenuUpdateDiscountInput, 100) can't find the selector on app (deleted the selector), menu option was removed? - await page.write('body', '\u000d'); + await page.waitForSelector(selectors.ticketSales.moreMenuUpdateDiscountInput); + await page.type(selectors.ticketSales.moreMenuUpdateDiscountInput, '100'); + await page.keyboard.press('Enter'); await page.waitForTextInElement(selectors.ticketSales.totalImport, '0.00'); const result = await page.waitToGetProperty(selectors.ticketSales.totalImport, 'innerText'); @@ -396,75 +373,12 @@ xdescribe('Ticket Edit sale path', () => { it('should log in as Production role and go to a target ticket summary', async() => { await page.loginAndModule('production', 'ticket'); - await page.accessToSearchResult(13); - await page.waitForURL('/summary'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/summary'); + await page.accessToSearchResult('13'); + await page.waitForState('ticket.card.summary'); }); - it(`should check it's state is deleted`, async() => { - const result = await page.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText'); - - expect(result).toEqual('State Eliminado'); - }); - - describe('when state is preparation and loged as Production', () => { - it(`should not be able to edit the sale price`, async() => { - await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton); - await page.accessToSearchResult(8); - await page.accessToSection('ticket.card.sale'); - await page.waitToClick(selectors.ticketSales.firstSalePrice); - const result = await page.exists(selectors.ticketSales.firstSalePriceInput); - - expect(result).toBeFalsy(); - }); - - it(`should not be able to edit the sale discount`, async() => { - await page.waitToClick(selectors.ticketSales.firstSaleDiscount); - const result = await page.exists(selectors.ticketSales.firstSaleDiscountInput); - - expect(result).toBeFalsy(); - }); - - it(`should not be able to edit the sale state`, async() => { - await page.waitToClick(selectors.ticketSales.stateMenuButton); - const result = await page.exists(selectors.ticketSales.stateMenuOptions); - - expect(result).toBeFalsy(); - }); - - it('should log in as salesPerson then go to the sales of a target ticket', async() => { - await page.loginAndModule('salesPerson', 'ticket'); - await page.accessToSearchResult(8); - await page.accessToSection('ticket.card.sale'); - await page.waitForURL('/sale'); - const url = await page.parsedUrl(); - - expect(url.hash).toContain('/sale'); - }); - }); - - describe('when state is preparation and loged as salesPerson', () => { - it(`shouldn't be able to edit the sale price`, async() => { - await page.waitToClick(selectors.ticketSales.firstSalePrice); - const result = await page.exists(selectors.ticketSales.firstSalePriceInput); - - expect(result).toBeFalsy(); - }); - - it(`should be able to edit the sale discount`, async() => { - await page.waitToClick(selectors.ticketSales.firstSaleDiscount); - const result = await page.exists(selectors.ticketSales.firstSaleDiscountInput); - - expect(result).toBeFalsy(); - }); - - it(`should not be able to edit the sale state`, async() => { - await page.waitToClick(selectors.ticketSales.stateMenuButton); - const result = await page.exists(selectors.ticketSales.stateMenuOptions); - - expect(result).toBeFalsy(); - }); + it(`should check the ticket is deleted`, async() => { + await page.waitForSelector(selectors.ticketDescriptor.isDeletedIcon); + await page.waitForClassPresent(selectors.ticketDescriptor.isDeletedIcon, 'bright'); }); }); diff --git a/e2e/paths/05-ticket/05_tracking_state.spec.js b/e2e/paths/05-ticket/05_tracking_state.spec.js index 34f3c4d44..53f082007 100644 --- a/e2e/paths/05-ticket/05_tracking_state.spec.js +++ b/e2e/paths/05-ticket/05_tracking_state.spec.js @@ -48,6 +48,7 @@ describe('Ticket Create new tracking state path', () => { }); it('should now access to the create state view by clicking the create floating button', async() => { + await page.waitFor('.vn-popup', {hidden: true}), await page.waitToClick(selectors.ticketTracking.createStateButton); await page.waitForState('ticket.card.tracking.edit'); }); diff --git a/e2e/paths/05-ticket/10_request.spec.js b/e2e/paths/05-ticket/10_request.spec.js index 334b8ba5e..afab9b9e9 100644 --- a/e2e/paths/05-ticket/10_request.spec.js +++ b/e2e/paths/05-ticket/10_request.spec.js @@ -18,6 +18,7 @@ describe('Ticket purchase request path', () => { }); it('should add a new request', async() => { + await page.waitFor('.vn-popup', {hidden: true}), await page.waitToClick(selectors.ticketRequests.addRequestButton); await page.write(selectors.ticketRequests.descriptionInput, 'New stuff'); await page.write(selectors.ticketRequests.quantity, '9'); diff --git a/e2e/paths/05-ticket/16_summary.spec.js b/e2e/paths/05-ticket/16_summary.spec.js index f6808651e..70d7592d7 100644 --- a/e2e/paths/05-ticket/16_summary.spec.js +++ b/e2e/paths/05-ticket/16_summary.spec.js @@ -58,7 +58,7 @@ describe('Ticket Summary path', () => { expect(result).toContain('000002'); }); - it(`should click on the first sale ID making the item descriptor visible`, async() => { + it(`should click on the first sale ID to make the item descriptor visible`, async() => { await page.waitToClick(selectors.ticketSummary.firstSaleItemId); await page.waitImgLoad(selectors.ticketSummary.firstSaleDescriptorImage); const visible = await page.isVisible(selectors.ticketSummary.itemDescriptorPopover); diff --git a/e2e/paths/07-order/03_lines.spec.js b/e2e/paths/07-order/03_lines.spec.js index c054bbaa3..e74eaae23 100644 --- a/e2e/paths/07-order/03_lines.spec.js +++ b/e2e/paths/07-order/03_lines.spec.js @@ -8,7 +8,7 @@ describe('Order lines', () => { browser = await getBrowser(); page = browser.page; await page.loginAndModule('employee', 'order'); - await page.accessToSearchResult('16'); + await page.accessToSearchResult('8'); await page.accessToSection('order.card.line'); }); @@ -20,7 +20,7 @@ describe('Order lines', () => { const result = await page .waitToGetProperty(selectors.orderLine.orderSubtotal, 'innerText'); - expect(result).toContain('135.60'); + expect(result).toContain('112.30'); }); it('should delete the first line in the order', async() => { @@ -32,11 +32,11 @@ describe('Order lines', () => { }); it('should confirm the order subtotal has changed', async() => { - await page.waitForTextInElement(selectors.orderLine.orderSubtotal, '90.10'); + await page.waitForTextInElement(selectors.orderLine.orderSubtotal, '92.80'); const result = await page .waitToGetProperty(selectors.orderLine.orderSubtotal, 'innerText'); - expect(result).toContain('90.10'); + expect(result).toContain('92.80'); }); it('should confirm the whole order and redirect to ticket index filtered by clientFk', async() => { diff --git a/front/core/components/input-number/index.spec.js b/front/core/components/input-number/index.spec.js index d1bbe251c..0e0f8c4a5 100644 --- a/front/core/components/input-number/index.spec.js +++ b/front/core/components/input-number/index.spec.js @@ -7,7 +7,7 @@ describe('Component vnInputNumber', () => { beforeEach(ngModule('vnCore')); beforeEach(angular.mock.inject(($compile, $rootScope) => { - $element = $compile(``)($rootScope); + $element = $compile(``)($rootScope); $ctrl = $element.controller('vnInputNumber'); })); @@ -20,12 +20,10 @@ describe('Component vnInputNumber', () => { $ctrl.field = -1; $ctrl.min = 0; - // FIXME: Input validation doesn't work with Jest? - // expect($ctrl.shownError).toContain('Please select a value that is no less than 0'); - expect($ctrl.shownError).toBeNull(); + expect($ctrl.shownError).toContain('Constraints not satisfied'); }); - it(`should unset error property when value is upper than min`, () => { + it(`should unset error property when value is greater than min`, () => { $ctrl.field = 1; $ctrl.min = 0; @@ -34,19 +32,16 @@ describe('Component vnInputNumber', () => { }); describe('max() setter', () => { - it(`should set error property when value is upper than max`, () => { + it(`should set error property when value is greater than max`, () => { $ctrl.field = 1; $ctrl.max = 0; - // FIXME: Input validation doesn't work with Jest? - // expect($ctrl.shownError).toContain('Please select a value that is no more than 0'); - expect($ctrl.shownError).toBeNull(); + expect($ctrl.shownError).toContain('Constraints not satisfied'); }); - // FIXME: Input validation doesn't work with Jest? it(`should unset error property when value is lower than max`, () => { $ctrl.field = -1; - $ctrl.min = 0; + $ctrl.max = 0; expect($ctrl.shownError).toBeNull(); }); @@ -54,14 +49,12 @@ describe('Component vnInputNumber', () => { describe('step() setter', () => { it(`should increase value when add icon is clicked`, () => { - $ctrl.step = 1; - $ctrl.field = 1; + $ctrl.input.step = 1; + $ctrl.input.value = 0; - // FIXME: Doesn't work with Jest? - // $ctrl.stepUp(); - // $element[0].querySelector('vn-icon[icon=add]').click(); + $ctrl.stepUp(); - expect($ctrl.field).toBe(1); + expect($ctrl.input.value).toBe('1'); }); }); }); diff --git a/front/core/components/multi-check/multi-check.js b/front/core/components/multi-check/multi-check.js index d354c9eef..5c11e955e 100644 --- a/front/core/components/multi-check/multi-check.js +++ b/front/core/components/multi-check/multi-check.js @@ -68,6 +68,7 @@ export default class MultiCheck extends FormInput { this.checkAll = value; this.toggle(); + this.emit('change', value); } /** diff --git a/front/salix/components/descriptor/index.html b/front/salix/components/descriptor/index.html index 20631333c..366bfab5d 100644 --- a/front/salix/components/descriptor/index.html +++ b/front/salix/components/descriptor/index.html @@ -30,7 +30,7 @@
{{$ctrl.description}}
-
+
{{$ctrl.descriptor.id | id}}
diff --git a/front/salix/components/descriptor/index.js b/front/salix/components/descriptor/index.js index d87a4c395..3d6dd1b3f 100644 --- a/front/salix/components/descriptor/index.js +++ b/front/salix/components/descriptor/index.js @@ -94,7 +94,8 @@ ngModule.vnComponent('vnDescriptor', { transclude: { btnOne: '?btnOne', btnTwo: '?btnTwo', - btnThree: '?btnThree' + btnThree: '?btnThree', + btnFour: '?btnFour' } }); diff --git a/front/salix/components/descriptor/style.scss b/front/salix/components/descriptor/style.scss index 03a9002d5..9649d61e5 100644 --- a/front/salix/components/descriptor/style.scss +++ b/front/salix/components/descriptor/style.scss @@ -88,7 +88,7 @@ vn-descriptor-content { display: flex; align-items: center; justify-content: center; - padding: 0 $spacing-md; + padding: 0 $spacing-sm; margin: 0 $spacing-sm; & > vn-icon { diff --git a/gulpfile.js b/gulpfile.js index b946b48de..096c44584 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -83,8 +83,6 @@ async function backTestOnce(done) { port: container.dbConf.port }); - log('[Debug] dataSources', dataSources.vn); - let bootOptions = {dataSources}; let app = require(`./loopback/server/server`); @@ -92,8 +90,6 @@ async function backTestOnce(done) { try { app.boot(bootOptions); - log('[Debug] back started'); - await new Promise((resolve, reject) => { const jasmine = require('gulp-jasmine'); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 6f20b947b..114b07edf 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -67,5 +67,6 @@ "Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", "Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member", "Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}", - "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment" + "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment", + "NOT_ZONE_WITH_THIS_PARAMETERS": "NOT_ZONE_WITH_THIS_PARAMETERS" } \ No newline at end of file diff --git a/modules/claim/back/methods/claim/createFromSales.js b/modules/claim/back/methods/claim/createFromSales.js index bf62a526e..989b87c95 100644 --- a/modules/claim/back/methods/claim/createFromSales.js +++ b/modules/claim/back/methods/claim/createFromSales.js @@ -4,17 +4,15 @@ module.exports = Self => { Self.remoteMethodCtx('createFromSales', { description: 'Create a claim', accepts: [{ - arg: 'claim', - type: 'object', + arg: 'ticketId', + type: 'Number', required: true, - description: ' newTicketFk, clientFk, ticketCreated', - http: {source: 'body'} + description: 'The origin ticket id' }, { arg: 'sales', - type: 'object', + type: ['Object'], required: true, - description: '[sales IDs]', - http: {source: 'body'} + description: 'The claimed sales' }], returns: { type: 'object', @@ -26,28 +24,27 @@ module.exports = Self => { } }); - Self.createFromSales = async(ctx, params) => { - let models = Self.app.models; - let userId = ctx.req.accessToken.userId; - let tx = await Self.beginTransaction({}); + Self.createFromSales = async(ctx, ticketId, sales) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const tx = await Self.beginTransaction({}); try { let options = {transaction: tx}; - const ticketId = params.claim.ticketFk; const ticket = await models.Ticket.findById(ticketId, null, options); if (ticket.isDeleted) throw new UserError(`You can't create a claim for a removed ticket`); - const worker = await models.Worker.findOne({ - where: {userFk: userId} + const newClaim = await Self.create({ + ticketFk: ticketId, + clientFk: ticket.clientFk, + ticketCreated: ticket.shipped, + workerFk: userId }, options); + const promises = []; - params.claim.workerFk = worker.id; - let newClaim = await Self.create(params.claim, options); - let promises = []; - - for (const sale of params.sales) { + for (const sale of sales) { const newClaimBeginning = models.ClaimBeginning.create({ saleFk: sale.id, claimFk: newClaim.id, diff --git a/modules/claim/back/methods/claim/specs/createFromSales.spec.js b/modules/claim/back/methods/claim/specs/createFromSales.spec.js index 226aeba53..5353e640a 100644 --- a/modules/claim/back/methods/claim/specs/createFromSales.spec.js +++ b/modules/claim/back/methods/claim/specs/createFromSales.spec.js @@ -1,7 +1,6 @@ const app = require('vn-loopback/server/server'); -describe('Claim Create', () => { - let newDate = new Date(); +describe('Claim createFromSales()', () => { let createdClaimFk; afterAll(async done => { @@ -10,28 +9,18 @@ describe('Claim Create', () => { done(); }); - let newClaim = { - ticketFk: 2, - clientFk: 101, - ticketCreated: newDate - }; - - let newSale = [{ + const ticketId = 2; + const newSale = [{ id: 3, instance: 0, quantity: 10 }]; - - let params = {claim: newClaim, sales: newSale}; - let ctx = {req: {accessToken: {userId: 1}}}; + const ctx = {req: {accessToken: {userId: 1}}}; it('should create a new claim', async() => { - let claim = await app.models.Claim.createFromSales(ctx, params); + let claim = await app.models.Claim.createFromSales(ctx, ticketId, newSale); - expect(claim.ticketFk).toEqual(newClaim.ticketFk); - expect(claim.clientFk).toEqual(newClaim.clientFk); - expect(claim.ticketCreated).toEqual(newClaim.ticketCreated); - expect(claim.workerFk).toEqual(newClaim.workerFk); + expect(claim.ticketFk).toEqual(ticketId); let claimBeginning = await app.models.ClaimBeginning.findOne({where: {claimFk: claim.id}}); @@ -44,7 +33,7 @@ describe('Claim Create', () => { it('should not be able to create a claim if exists that sale', async() => { let error; - await app.models.Claim.createFromSales(ctx, params) + await app.models.Claim.createFromSales(ctx, ticketId, newSale) .catch(e => { error = e; diff --git a/modules/client/back/methods/client/canCreateTicket.js b/modules/client/back/methods/client/canCreateTicket.js new file mode 100644 index 000000000..a47a5f6f3 --- /dev/null +++ b/modules/client/back/methods/client/canCreateTicket.js @@ -0,0 +1,31 @@ +module.exports = Self => { + Self.remoteMethod('canCreateTicket', { + description: 'Checks if the client is active', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'string', + required: true, + description: 'The user id', + http: {source: 'path'} + }], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/:id/canCreateTicket`, + verb: 'GET' + } + }); + + Self.canCreateTicket = async id => { + const client = await Self.app.models.Client.findById(id); + const canCreateTicket = client && client.isActive; + if (!canCreateTicket) + return false; + + return true; + }; +}; diff --git a/modules/client/back/methods/client/isValidClient.js b/modules/client/back/methods/client/isValidClient.js index 241121927..11f183563 100644 --- a/modules/client/back/methods/client/isValidClient.js +++ b/modules/client/back/methods/client/isValidClient.js @@ -44,7 +44,7 @@ module.exports = Self => { return role.name === 'employee'; }); - if (!roleNames.length || isEmployee > -1 ) return false; + if (!roleNames.length || isEmployee > -1) return false; return true; }; diff --git a/modules/client/back/methods/client/specs/canCreateTicket.spec.js b/modules/client/back/methods/client/specs/canCreateTicket.spec.js new file mode 100644 index 000000000..9eb1db334 --- /dev/null +++ b/modules/client/back/methods/client/specs/canCreateTicket.spec.js @@ -0,0 +1,17 @@ +const app = require('vn-loopback/server/server'); + +describe('Client canCreateTicket', () => { + it('should receive true if the client is active', async() => { + let id = 105; + let canCreateTicket = await app.models.Client.canCreateTicket(id); + + expect(canCreateTicket).toBeTruthy(); + }); + + it(`should receive false if the client isn't active`, async() => { + let id = 106; + let canCreateTicket = await app.models.Client.canCreateTicket(id); + + expect(canCreateTicket).toBe(false); + }); +}); diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 056b49d01..66ab1fdc7 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -11,6 +11,7 @@ module.exports = Self => { require('../methods/client/createWithUser')(Self); require('../methods/client/listWorkers')(Self); require('../methods/client/hasCustomerRole')(Self); + require('../methods/client/canCreateTicket')(Self); require('../methods/client/isValidClient')(Self); require('../methods/client/addressesPropagateRe')(Self); require('../methods/client/getDebt')(Self); diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js index a0014a60d..211a2e7bb 100644 --- a/modules/route/back/methods/route/getTickets.js +++ b/modules/route/back/methods/route/getTickets.js @@ -29,7 +29,7 @@ module.exports = Self => { order: 'priority', include: [ { - relation: 'state', + relation: 'ticketState', scope: { fields: ['id', 'stateFk'], include: [{relation: 'state'}] diff --git a/modules/ticket/back/methods/sale/deleteSales.js b/modules/ticket/back/methods/sale/deleteSales.js new file mode 100644 index 000000000..d11223d3b --- /dev/null +++ b/modules/ticket/back/methods/sale/deleteSales.js @@ -0,0 +1,43 @@ +let UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('deleteSales', { + description: 'Deletes the selected sales', + accessType: 'WRITE', + accepts: [{ + arg: 'sales', + type: ['Object'], + required: true, + description: 'The sales to remove' + }, + { + arg: 'ticketId', + type: 'Number', + required: true, + description: 'The ticket id' + }], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/deleteSales`, + verb: 'POST' + } + }); + + Self.deleteSales = async(ctx, sales, ticketId) => { + const models = Self.app.models; + const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId); + if (!isTicketEditable) + throw new UserError(`The sales of this ticket can't be modified`); + + const promises = []; + for (let sale of sales) { + const deletedSale = models.Sale.destroyById(sale.id); + promises.push(deletedSale); + } + + return Promise.all(promises); + }; +}; diff --git a/modules/ticket/back/methods/sale/removes.js b/modules/ticket/back/methods/sale/removes.js deleted file mode 100644 index f4385072f..000000000 --- a/modules/ticket/back/methods/sale/removes.js +++ /dev/null @@ -1,35 +0,0 @@ -let UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethodCtx('removes', { - description: 'Change the state of a ticket', - accessType: 'WRITE', - accepts: [{ - arg: 'params', - type: 'object', - required: true, - description: '[sales IDs], actualTicketFk', - http: {source: 'body'} - }], - returns: { - type: 'string', - root: true - }, - http: { - path: `/removes`, - verb: 'post' - } - }); - - Self.removes = async(ctx, params) => { - let thisTicketIsEditable = await Self.app.models.Ticket.isEditable(ctx, params.actualTicketFk); - if (!thisTicketIsEditable) - throw new UserError(`The sales of this ticket can't be modified`); - - let promises = []; - for (let i = 0; i < params.sales.length; i++) - promises.push(Self.app.models.Sale.destroyById(params.sales[i].id)); - - return Promise.all(promises); - }; -}; diff --git a/modules/ticket/back/methods/sale/reserve.js b/modules/ticket/back/methods/sale/reserve.js index 7e6a6bb3a..e054e1ec8 100644 --- a/modules/ticket/back/methods/sale/reserve.js +++ b/modules/ticket/back/methods/sale/reserve.js @@ -6,28 +6,43 @@ module.exports = Self => { description: 'Change the state of a ticket', accessType: 'WRITE', accepts: [{ - arg: 'params', - type: 'object', + arg: 'ticketId', + type: 'Number', required: true, - description: '[sales IDs], ticketFk, reserved', - http: {source: 'body'} + description: 'The ticket id' + }, { + arg: 'sales', + type: ['Object'], + required: true, + description: 'The sale to reserve' + }, + { + arg: 'reserved', + type: 'Boolean', + required: true }], returns: { - type: 'string', + type: ['Object'], root: true }, http: { path: `/reserve`, - verb: 'post' + verb: 'POST' } }); - Self.reserve = async(ctx, params) => { - let thisTicketIsEditable = await Self.app.models.Ticket.isEditable(ctx, params.ticketFk); - if (!thisTicketIsEditable) + Self.reserve = async(ctx, ticketId, sales, reserved) => { + const models = Self.app.models; + const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId); + if (!isTicketEditable) throw new UserError(`The sales of this ticket can't be modified`); - for (let i = 0; i < params.sales.length; i++) - await Self.app.models.Sale.update({id: params.sales[i].id}, {reserved: params.reserved}); + const promises = []; + for (let sale of sales) { + const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved}); + promises.push(reservedSale); + } + + return Promise.all(promises); }; }; diff --git a/modules/ticket/back/methods/sale/specs/removes.spec.js b/modules/ticket/back/methods/sale/specs/deleteSales.js similarity index 66% rename from modules/ticket/back/methods/sale/specs/removes.spec.js rename to modules/ticket/back/methods/sale/specs/deleteSales.js index a12bc54ee..c23ac6758 100644 --- a/modules/ticket/back/methods/sale/specs/removes.spec.js +++ b/modules/ticket/back/methods/sale/specs/deleteSales.js @@ -1,6 +1,6 @@ const app = require('vn-loopback/server/server'); -describe('sale removes()', () => { +describe('sale deleteSales()', () => { let sale; let newsale; @@ -16,13 +16,11 @@ describe('sale removes()', () => { let ctx = {req: {accessToken: {userId: 9}}}; let error; - let params = { - sales: [{id: 1, instance: 0}, {id: 2, instance: 1}], - actualTicketFk: 2 - }; + const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}]; + const ticketId = 2; try { - await app.models.Sale.removes(ctx, params); + await app.models.Sale.deleteSales(ctx, sales, ticketId); } catch (e) { error = e; } @@ -32,12 +30,11 @@ describe('sale removes()', () => { it('should delete the sales', async() => { let ctx = {req: {accessToken: {userId: 9}}}; - let params = { - sales: [{id: newsale.id, instance: 0}], - actualTicketFk: 16 - }; - let res = await app.models.Sale.removes(ctx, params); + const sales = [{id: newsale.id, instance: 0}]; + const ticketId = 16; + + let res = await app.models.Sale.deleteSales(ctx, sales, ticketId); expect(res).toEqual([{count: 1}]); }); diff --git a/modules/ticket/back/methods/sale/specs/reserve.spec.js b/modules/ticket/back/methods/sale/specs/reserve.spec.js index a86d357a8..5379b1487 100644 --- a/modules/ticket/back/methods/sale/specs/reserve.spec.js +++ b/modules/ticket/back/methods/sale/specs/reserve.spec.js @@ -1,6 +1,7 @@ const app = require('vn-loopback/server/server'); describe('sale reserve()', () => { + const ctx = {req: {accessToken: {userId: 9}}}; afterAll(async done => { let ctx = {req: {accessToken: {userId: 9}}}; let params = { @@ -17,13 +18,12 @@ describe('sale reserve()', () => { }); it('should throw an error if the ticket can not be modified', async() => { - let ctx = {req: {accessToken: {userId: 9}}}; let error; - let params = {ticketFk: 2, - sales: [{id: 5}], - reserved: false}; + const ticketId = 2; + const sales = [{id: 5}]; + const reserved = false; - await app.models.Sale.reserve(ctx, params) + await app.models.Sale.reserve(ctx, ticketId, sales, reserved) .catch(response => { expect(response).toEqual(new Error(`The sales of this ticket can't be modified`)); error = response; @@ -38,18 +38,13 @@ describe('sale reserve()', () => { expect(originalTicketSales[0].reserved).toEqual(0); expect(originalTicketSales[1].reserved).toEqual(0); - let ctx = {req: {accessToken: {userId: 9}}}; - let params = { - sales: [ - {id: 7}, - {id: 8}], - ticketFk: 11, - reserved: true - }; + const ticketId = 11; + const sales = [{id: 7}, {id: 8}]; + const reserved = true; - await app.models.Sale.reserve(ctx, params); + await app.models.Sale.reserve(ctx, ticketId, sales, reserved); - let reservedTicketSales = await app.models.Ticket.getSales(11); + const reservedTicketSales = await app.models.Ticket.getSales(ticketId); expect(reservedTicketSales[0].reserved).toEqual(1); expect(reservedTicketSales[1].reserved).toEqual(1); diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index dac388034..200eeb444 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -56,7 +56,9 @@ module.exports = Self => { throw new UserError(`The sales of this ticket can't be modified`); const userId = ctx.req.accessToken.userId; + let usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options); + let componentCode = usesMana ? 'mana' : 'buyerDiscount'; let discount = await models.Component.findOne({where: {code: componentCode}}, options); @@ -80,7 +82,6 @@ module.exports = Self => { value: componentValue }, options); } - await sale.updateAttributes({price: newPrice}, options); query = `CALL vn.manaSpellersRequery(?)`; diff --git a/modules/ticket/back/methods/ticket/getPossibleStowaways.js b/modules/ticket/back/methods/ticket/getPossibleStowaways.js index 3565d64ab..675270840 100644 --- a/modules/ticket/back/methods/ticket/getPossibleStowaways.js +++ b/modules/ticket/back/methods/ticket/getPossibleStowaways.js @@ -38,7 +38,6 @@ module.exports = Self => { let highestDate = new Date(ship.shipped.getTime()); highestDate.setHours(23, 59, 59); - let possibleStowaways = await models.Ticket.find({ where: { id: {neq: ticketFk}, @@ -53,7 +52,7 @@ module.exports = Self => { include: [ {relation: 'agencyMode'}, {relation: 'warehouse'}, - {relation: 'state', + {relation: 'ticketState', scope: { fields: ['stateFk'], include: { diff --git a/modules/ticket/back/methods/ticket/getSalesPersonMana.js b/modules/ticket/back/methods/ticket/getSalesPersonMana.js index 8ac1d7a38..42faec61b 100644 --- a/modules/ticket/back/methods/ticket/getSalesPersonMana.js +++ b/modules/ticket/back/methods/ticket/getSalesPersonMana.js @@ -1,12 +1,12 @@ module.exports = Self => { Self.remoteMethod('getSalesPersonMana', { - description: 'Returns mana of a salesperson of a ticket', + description: 'Returns the mana amount of a salesperson for a ticket', accessType: 'READ', accepts: [{ arg: 'id', type: 'number', required: true, - description: 'ticket id', + description: 'The ticket id', http: {source: 'path'} }], returns: { @@ -18,8 +18,9 @@ module.exports = Self => { } }); - Self.getSalesPersonMana = async ticketFk => { - let ticket = await Self.app.models.Ticket.findById(ticketFk, { + Self.getSalesPersonMana = async ticketId => { + const models = Self.app.models; + const ticket = await models.Ticket.findById(ticketId, { include: [{ relation: 'client', scope: { @@ -29,10 +30,12 @@ module.exports = Self => { fields: ['id', 'clientFk'] }); - if (!ticket) - return 0; + if (!ticket) return 0; - let mana = await Self.app.models.WorkerMana.findOne({where: {workerFk: ticket.client().salesPersonFk}, fields: 'amount'}); + const mana = await models.WorkerMana.findOne({ + where: { + workerFk: ticket.client().salesPersonFk + }, fields: 'amount'}); return mana ? mana.amount : 0; }; diff --git a/modules/ticket/back/methods/ticket/new.js b/modules/ticket/back/methods/ticket/new.js index 43fbf62e7..2763f1bd0 100644 --- a/modules/ticket/back/methods/ticket/new.js +++ b/modules/ticket/back/methods/ticket/new.js @@ -82,7 +82,8 @@ module.exports = Self => { agencyMode = await models.AgencyMode.findById(agencyModeId); if (address.client().type().code === 'normal' && (!agencyMode || agencyMode.code != 'refund')) { - if (!address.client().isActive) + const canCreateTicket = await models.Client.canCreateTicket(clientId); + if (!canCreateTicket) throw new UserError(`You can't create a ticket for a inactive client`); } diff --git a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js index 111897efe..42689c73e 100644 --- a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js +++ b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js @@ -8,7 +8,7 @@ describe('sale updateDiscount()', () => { beforeAll(async done => { originalSale = await app.models.Sale.findById(originalSaleId); - let manaDiscount = await app.models.Component.findOne({where: {code: 'mana'}}); + let manaDiscount = await app.models.Component.findOne({where: {code: 'buyerDiscount'}}); componentId = manaDiscount.id; let ticket = await app.models.Ticket.findById(originalSale.ticketFk); @@ -26,7 +26,6 @@ describe('sale updateDiscount()', () => { done(); }); - it('should throw an error if no sales were selected', async() => { let ctx = {req: {accessToken: {userId: 9}}}; let error; @@ -76,6 +75,28 @@ describe('sale updateDiscount()', () => { }); it('should update the discount if the salesPerson has mana', async() => { + let ctx = {req: {accessToken: {userId: 18}}}; + const ticketId = 11; + const sales = [originalSaleId]; + const newDiscount = 100; + let manaDiscount = await app.models.Component.findOne({where: {code: 'mana'}}); + componentId = manaDiscount.id; + + await app.models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount); + + let updatedSale = await app.models.Sale.findById(originalSaleId); + let createdSaleComponent = await app.models.SaleComponent.findOne({ + where: { + componentFk: componentId, + saleFk: originalSaleId + } + }); + + expect(createdSaleComponent.componentFk).toEqual(componentId); + expect(updatedSale.discount).toEqual(100); + }); + + it('should update the discount and add company discount component if the worker does not have mana', async() => { let ctx = {req: {accessToken: {userId: 9}}}; const ticketId = 11; const sales = [originalSaleId]; diff --git a/modules/ticket/back/methods/ticket/summary.js b/modules/ticket/back/methods/ticket/summary.js index 2035b4d31..79a7c24d9 100644 --- a/modules/ticket/back/methods/ticket/summary.js +++ b/modules/ticket/back/methods/ticket/summary.js @@ -90,7 +90,7 @@ module.exports = Self => { } } }, { - relation: 'state', + relation: 'ticketState', scope: { fields: ['stateFk'], include: { diff --git a/modules/ticket/back/methods/ticket/transferSales.js b/modules/ticket/back/methods/ticket/transferSales.js index c2257bf09..541f19615 100644 --- a/modules/ticket/back/methods/ticket/transferSales.js +++ b/modules/ticket/back/methods/ticket/transferSales.js @@ -55,8 +55,14 @@ module.exports = Self => { where: {ticketFk: id} }, options); - if (!ticketId) + if (!ticketId) { + const ticket = await models.Ticket.findById(id); + const canCreateTicket = await models.Client.canCreateTicket(ticket.clientFk); + if (!canCreateTicket) + throw new UserError(`You can't create a ticket for a inactive client`); + ticketId = await cloneTicket(originalTicket, options); + } const map = new Map(); for (const sale of originalSales) diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index 3b69c6b68..7c9f9ae48 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -35,7 +35,6 @@ module.exports = Self => { }); Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => { - const userId = ctx.req.accessToken.userId; const models = Self.app.models; const tx = await Self.beginTransaction({}); @@ -69,6 +68,7 @@ module.exports = Self => { if (!allFromSameTicket) throw new UserError('All sales must belong to the same ticket'); + const userId = ctx.req.accessToken.userId; const isLocked = await models.Ticket.isLocked(id); const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson'); const state = await Self.app.models.TicketState.findOne({ @@ -79,18 +79,9 @@ module.exports = Self => { if (isLocked || (!isSalesPerson && alertLevel > 0)) throw new UserError(`The sales of this ticket can't be modified`); - const ticket = await models.Ticket.findById(id, { - include: { - relation: 'client', - scope: { - fields: ['salesPersonFk'] - } - }, - }, options); - const salesPersonId = ticket.client().salesPersonFk; const usesMana = await models.WorkerMana.findOne({ where: { - workerFk: salesPersonId + workerFk: userId }, fields: 'amount'}, options); @@ -99,7 +90,6 @@ module.exports = Self => { where: {code: componentCode}}, options); const componentId = discountComponent.id; - let promises = []; for (let sale of sales) { const value = ((-sale.price * newDiscount) / 100); @@ -115,10 +105,8 @@ module.exports = Self => { await Promise.all(promises); - if (salesPersonId) { - const query = `call vn.manaSpellersRequery(?)`; - await Self.rawSql(query, [salesPersonId], options); - } + const query = `call vn.manaSpellersRequery(?)`; + await Self.rawSql(query, [userId], options); await tx.commit(); } catch (error) { diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index 4ffde08b3..4885071fd 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -1,7 +1,7 @@ module.exports = Self => { require('../methods/sale/getClaimableFromTicket')(Self); require('../methods/sale/reserve')(Self); - require('../methods/sale/removes')(Self); + require('../methods/sale/deleteSales')(Self); require('../methods/sale/updatePrice')(Self); require('../methods/sale/updateQuantity')(Self); require('../methods/sale/updateConcept')(Self); diff --git a/modules/ticket/back/models/ticket.json b/modules/ticket/back/models/ticket.json index 21c34232b..63b69b914 100644 --- a/modules/ticket/back/models/ticket.json +++ b/modules/ticket/back/models/ticket.json @@ -110,7 +110,7 @@ "model": "TicketObservation", "foreignKey": "ticketFk" }, - "state": { + "ticketState": { "type": "hasOne", "model": "TicketState", "foreignKey": "ticketFk" diff --git a/modules/ticket/front/card/index.js b/modules/ticket/front/card/index.js index 8dce23aa7..80174b5e2 100644 --- a/modules/ticket/front/card/index.js +++ b/modules/ticket/front/card/index.js @@ -45,7 +45,7 @@ class Controller extends ModuleCard { }, }, }, { - relation: 'state', + relation: 'ticketState', scope: { fields: ['stateFk'], include: { diff --git a/modules/ticket/front/descriptor/addStowaway.html b/modules/ticket/front/descriptor/addStowaway.html index f05a6584f..56bb321d9 100644 --- a/modules/ticket/front/descriptor/addStowaway.html +++ b/modules/ticket/front/descriptor/addStowaway.html @@ -26,7 +26,7 @@ {{ticket.landed | date: 'dd/MM/yyyy'}} {{ticket.agencyMode.name}} {{ticket.warehouse.name}} - {{ticket.state.state.name}} + {{ticket.ticketState.state.name}} diff --git a/modules/ticket/front/descriptor/index.html b/modules/ticket/front/descriptor/index.html index 2f0400f6b..1ad27aac3 100644 --- a/modules/ticket/front/descriptor/index.html +++ b/modules/ticket/front/descriptor/index.html @@ -87,7 +87,7 @@
+ value="{{$ctrl.ticket.ticketState.state.name}}">
+ + +
+
+ + +
+
-
+
{ this.isEditable = res.data; @@ -210,7 +216,7 @@ class Controller extends Descriptor { } } }, { - relation: 'state', + relation: 'ticketState', scope: { fields: ['stateFk'], include: { diff --git a/modules/ticket/front/index.js b/modules/ticket/front/index.js index c3f15b214..195f172e0 100644 --- a/modules/ticket/front/index.js +++ b/modules/ticket/front/index.js @@ -18,7 +18,6 @@ import './expedition'; import './volume'; import './package/index'; import './sale'; -import './sale/editDiscount'; import './tracking/index'; import './tracking/edit'; import './sale-checked'; diff --git a/modules/ticket/front/sale/editDiscount.html b/modules/ticket/front/sale/editDiscount.html deleted file mode 100644 index c23645784..000000000 --- a/modules/ticket/front/sale/editDiscount.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
MANÁ: {{$ctrl.mana | currency: 'EUR':0}}
-
-
- - -
-

New price

-

{{$ctrl.newPrice | currency: 'EUR':2}}

-
-
\ No newline at end of file diff --git a/modules/ticket/front/sale/editDiscount.js b/modules/ticket/front/sale/editDiscount.js deleted file mode 100644 index c39077476..000000000 --- a/modules/ticket/front/sale/editDiscount.js +++ /dev/null @@ -1,86 +0,0 @@ -import ngModule from '../module'; -import Component from 'core/lib/component'; - -class Controller extends Component { - set edit(value) { - this._edit = value; - this.clearDiscount(); - this.setNewDiscount(); - } - - get edit() { - return this._edit; - } - - set bulk(value) { - this._bulk = value; - this.setNewDiscount(); - } - - get bulk() { - return this._bulk; - } - - get newDiscount() { - return this._newDiscount; - } - - set newDiscount(value) { - this._newDiscount = value; - this.updateNewPrice(); - } - - updateNewPrice() { - if (this.newDiscount && this.edit[0]) - this.newPrice = (this.edit[0].quantity * this.edit[0].price) - ((this.newDiscount * (this.edit[0].quantity * this.edit[0].price)) / 100); - } - - setNewDiscount() { - if (!this.newDiscount && this.edit[0]) - this.newDiscount = this.edit[0].discount; - } - - updateDiscount() { - let salesIds = []; - let modified = false; - - if (this.newDiscount == null) return; - - for (let i = 0; i < this.edit.length; i++) { - if (this.newDiscount != this.edit[0].discount || this.bulk || !this.newDiscount) { - salesIds.push(this.edit[i].id); - modified = true; - } - } - - if (modified) { - const params = {salesIds: salesIds, newDiscount: this.newDiscount}; - const query = `Tickets/${this.$params.id}/updateDiscount`; - this.$http.post(query, params).then(() => { - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - this.clearDiscount(); - modified = false; - }).catch(e => { - this.vnApp.showError(e.message); - }); - - this.onHide(); - } else - this.vnApp.showError(this.$translate.instant('There are no changes to save')); - } - - clearDiscount() { - this.newDiscount = null; - } -} - -ngModule.component('vnTicketSaleEditDiscount', { - template: require('./editDiscount.html'), - controller: Controller, - bindings: { - edit: ' + on-change="$ctrl.changeState(value)"> - - + ng-click="moreOptions.show($event)" + ng-show="$ctrl.hasSelectedSales()"> + -

Subtotal {{$ctrl.subtotal | currency: 'EUR':2}}

-

VAT {{$ctrl.VAT | currency: 'EUR':2}}

-

Total {{$ctrl.total | currency: 'EUR':2}}

+

Subtotal {{$ctrl.subtotal | currency: 'EUR': 2}}

+

VAT {{$ctrl.VAT | currency: 'EUR': 2}}

+

Total {{$ctrl.total | currency: 'EUR': 2}}

- + @@ -74,20 +69,19 @@ - - @@ -103,13 +97,12 @@ zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{sale.image}}" on-error-src/> - + {{sale.itemFk}} - - {{id}} - {{name}} + {{::id}} - {{::name}} - + {{sale.quantity}} + on-change="$ctrl.changeQuantity(sale)"> - - - - + sub-name="::sale.subName"> - @@ -161,14 +147,14 @@ {{sale.price | currency: 'EUR':2}} {{(sale.discount / 100) | percentage}} @@ -196,7 +182,7 @@ ng-show="$ctrl.isEditable" ng-click="$ctrl.newOrderFromTicket()" icon="add" - vn-tooltip="Add item" + vn-tooltip="Add item to basket" vn-bind="+" fixed-bottom-right> @@ -206,60 +192,96 @@ ticket-fk="$ctrl.ticket.id"> - + -
- +
+ -
+
-
MANÁ: {{$ctrl.mana | currency: 'EUR':0}}
+
MANÁ: {{::$ctrl.edit.mana | currency: 'EUR': 0}}

New price

-

{{$ctrl.newPrice | currency: 'EUR':2}}

+

+ {{$ctrl.getNewPrice() | currency: 'EUR': 2}} +

- + -
- +
+ - - +
+
+
Mana: {{::$ctrl.edit.mana | currency: 'EUR':0}}
+
+
+ + +
+

New price

+

+ {{$ctrl.getNewPrice() | currency: 'EUR': 2}} +

+
+
+
+ + + + + +
+
+ + +
+
+ Mana: {{::$ctrl.edit.mana | currency: 'EUR': 0}} +
+
+
+
+
@@ -306,10 +328,10 @@ Id - Shipped - Agency - Warehouse - Address + Shipped + Agency + Warehouse + Address @@ -352,28 +374,6 @@
- - - - - - - - - - + on-response="$ctrl.removeSales()"> - \ No newline at end of file + + + + + Send shortage SMS + + + Recalculate price + + + Update discount + + + Add claim + + + Mark as reserved + + + Unmark as reserved + + \ No newline at end of file diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index 3d7e38f42..bd3fa4813 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -5,32 +5,6 @@ import './style.scss'; class Controller extends Section { constructor($element, $) { super($element, $); - this.edit = {}; - this.moreOptions = [ - { - name: 'Send shortage SMS', - callback: this.showSMSDialog - }, { - name: 'Mark as reserved', - callback: this.markAsReserved, - show: () => this.isEditable - }, { - name: 'Unmark as reserved', - callback: this.unmarkAsReserved, - show: () => this.isEditable - }, { - name: 'Update discount', - callback: this.showEditDialog, - show: () => this.isEditable - }, { - name: 'Add claim', - callback: this.createClaim - }, { - name: 'Recalculate price', - callback: this.calculateSalePrice, - show: () => this.hasOneSaleSelected() - }, - ]; this._sales = []; } @@ -53,21 +27,17 @@ class Controller extends Section { this.refreshTotal(); } - get editedPrice() { - return this._editedPrice; + get ticketState() { + if (!this.ticket) return null; + + return this.ticket.ticketState.state.code; } - set editedPrice(value) { - this._editedPrice = value; - this.updateNewPrice(); + get total() { + return this.subtotal + this.VAT; } - refreshTotal() { - this.loadSubTotal(); - this.loadVAT(); - } - - loadSubTotal() { + getSubTotal() { if (!this.$params.id || !this.sales) return; this.$http.get(`Tickets/${this.$params.id}/subtotal`).then(res => { this.subtotal = res.data || 0.0; @@ -75,13 +45,21 @@ class Controller extends Section { } getSaleTotal(sale) { - if (!sale.quantity || !sale.price) - return; + if (sale.quantity == null || sale.price == null) + return null; - return sale.quantity * sale.price * ((100 - sale.discount) / 100); + const price = sale.quantity * sale.price; + const discount = (sale.discount * price) / 100; + + return price - discount; } - loadVAT() { + getMana() { + this.$http.get(`Tickets/${this.$params.id}/getSalesPersonMana`) + .then(res => this.edit.mana = res.data); + } + + getVat() { this.VAT = 0.0; if (!this.$params.id || !this.sales) return; this.$http.get(`Tickets/${this.$params.id}/getVAT`).then(res => { @@ -89,32 +67,9 @@ class Controller extends Section { }); } - get total() { - return this.subtotal + this.VAT; - } - - onMoreOpen() { - let options = this.moreOptions.filter(option => { - const hasShowProperty = Object.hasOwnProperty.call(option, 'show'); - const shouldShow = !hasShowProperty || option.show === true || - typeof option.show === 'function' && option.show(); - - return (shouldShow && (option.always || this.isChecked)); - }); - this.$.moreButton.data = options; - } - - onMoreChange(callback) { - callback.call(this); - } - - get isChecked() { - if (this.sales) { - for (let instance of this.sales) - if (instance.checked) return true; - } - - return false; + refreshTotal() { + this.getSubTotal(); + this.getVat(); } /** @@ -122,7 +77,7 @@ class Controller extends Section { * * @return {Array} Checked instances */ - checkedLines() { + selectedSales() { if (!this.sales) return; return this.sales.filter(sale => { @@ -130,6 +85,38 @@ class Controller extends Section { }); } + selectedValidSales() { + if (!this.sales) return; + + const selectedSales = this.selectedSales(); + return selectedSales.filter(sale => { + return sale.id != undefined; + }); + } + + /** + * Returns the total of checked instances + * + * @return {Number} Total checked instances + */ + selectedSalesCount() { + const selectedSales = this.selectedSales(); + if (selectedSales) + return selectedSales.length; + + return 0; + } + + hasSelectedSales() { + return this.selectedSalesCount() > 0; + } + + hasOneSaleSelected() { + if (this.selectedSalesCount() === 1) + return true; + return false; + } + /** * Returns new instances * @@ -143,99 +130,53 @@ class Controller extends Section { }); } - /** - * Returns an array of indexes - * from checked instances - * - * @return {Array} Indexes of checked instances - */ - checkedLinesIndex() { - if (!this.sales) return; - - let indexes = []; - this.sales.forEach((sale, index) => { - if (sale.checked) indexes.push(index); - }); - - return indexes; + resetChanges() { + if (this.newInstances().length === 0) + this.$.watcher.updateOriginalData(); } - firstCheckedLine() { - const checkedLines = this.checkedLines(); - if (checkedLines) - return checkedLines[0]; + changeState(value) { + const params = {ticketFk: this.$params.id, code: value}; + return this.$http.post('TicketTrackings/changeState', params).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + this.card.reload(); + }).finally(() => this.resetChanges()); } - /** - * Returns the total of checked instances - * - * @return {Number} Total checked instances - */ - totalCheckedLines() { - const checkedLines = this.checkedLines(); - if (checkedLines) - return checkedLines.length; + removeSales() { + const sales = this.selectedValidSales(); + const params = {sales: sales, ticketId: this.ticket.id}; + this.$http.post(`Sales/deleteSales`, params).then(() => { + this.removeSelectedSales(); + this.vnApp.showSuccess(this.$t('Data saved!')); + }).finally(() => this.resetChanges()); } - removeCheckedLines() { - const sales = this.checkedLines(); - + removeSelectedSales() { + const sales = this.selectedSales(); sales.forEach(sale => { const index = this.sales.indexOf(sale); this.sales.splice(index, 1); }); - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - this.refreshTotal(); } - onStateOkClick() { - let filter = {where: {code: 'OK'}, fields: ['id']}; - let json = encodeURIComponent(JSON.stringify(filter)); - return this.$http.get(`States?filter=${json}`).then(res => { - this.onStateChange(res.data[0].id); - }); - } - - onStateChange(value) { - let params = {ticketFk: this.$state.params.id, stateFk: value}; - this.$http.post('TicketTrackings/changeState', params).then(() => { - this.card.reload(); - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - }).finally(() => { - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - }); - } - - onRemoveLinesClick(response) { - if (response === 'accept') { - let sales = this.checkedLines(); - - // Remove unsaved instances - sales.forEach((sale, index) => { - if (!sale.id) sales.splice(index); - }); - - let params = {sales: sales, actualTicketFk: this.ticket.id}; - let query = `Sales/removes`; - this.$http.post(query, params).then(() => { - this.removeCheckedLines(); - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - }); - } + createClaim() { + const sales = this.selectedValidSales(); + const params = {ticketId: this.ticket.id, sales: sales}; + this.resetChanges(); + this.$http.post(`Claims/createFromSales`, params) + .then(res => this.$state.go('claim.card.basicData', {id: res.data.id})); } showTransferPopover(event) { this.setTransferParams(); - this.$.transfer.parent = event.target; - this.$.transfer.show(); + this.$.transfer.show(event); } setTransferParams() { - const checkedSales = JSON.stringify(this.checkedLines()); + const checkedSales = JSON.stringify(this.selectedValidSales()); const sales = JSON.parse(checkedSales); this.transfer = { lastActiveTickets: [], @@ -258,99 +199,123 @@ class Controller extends Section { this.$.watcher.updateOriginalData(); const query = `tickets/${this.ticket.id}/transferSales`; - this.$http.post(query, params).then(res => { - this.goToTicket(res.data.id); - }); - } - - createClaim() { - const claim = { - ticketFk: this.ticket.id, - clientFk: this.ticket.clientFk, - ticketCreated: this.ticket.shipped - }; - const sales = this.checkedLines(); - - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - - this.$http.post(`Claims/createFromSales`, {claim: claim, sales: sales}).then(res => { - this.$state.go('claim.card.basicData', {id: res.data.id}); - }); - } - - goToTicket(ticketId) { - this.$state.go('ticket.card.sale', {id: ticketId}); - } - - // Slesperson Mana - getManaSalespersonMana() { - this.$http.get(`Tickets/${this.$state.params.id}/getSalesPersonMana`).then(res => { - this.mana = res.data; - }); + this.$http.post(query, params) + .then(res => this.$state.go('ticket.card.sale', {id: res.data.id})); } showEditPricePopover(event, sale) { if (!this.isEditable) return; - this.sale = sale; - this.newPrice = this.sale.price; + this.edit = { - ticketFk: this.ticket.id, - id: sale.id, - quantity: sale.quantity + price: sale.price, + sale: sale }; - this.$.editPricePopover.parent = event.target; - this.$.editPricePopover.show(); + this.$.editPricePopover.show(event); } updatePrice() { - if (this.newPrice && this.newPrice != this.sale.price) { - const query = `Sales/${this.edit.id}/updatePrice`; - this.$http.post(query, {newPrice: this.newPrice}).then(res => { - this.sale.price = res.data.price; - - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - }).finally(() => { - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - }); + const sale = this.edit.sale; + const newPrice = this.edit.price; + if (newPrice != null && newPrice != sale.price) { + const query = `Sales/${sale.id}/updatePrice`; + this.$http.post(query, {newPrice}).then(res => { + sale.price = res.data.price; + this.edit = null; + this.refreshTotal(); + this.vnApp.showSuccess(this.$t('Data saved!')); + }).finally(() => this.resetChanges()); } this.$.editPricePopover.hide(); } - updateNewPrice() { - this.newPrice = this.sale.quantity * this.newPrice - ((this.sale.discount * (this.sale.quantity * this.newPrice)) / 100); - } - showEditDiscountPopover(event, sale) { if (this.isLocked) return; - this.sale = sale; - this.edit = [{ - ticketFk: this.ticket.id, - id: sale.id, - quantity: sale.quantity, - price: sale.price, - discount: sale.discount - }]; - this.$.editPopover.parent = event.target; - this.$.editPopover.show(); + this.edit = { + discount: sale.discount, + sale: sale + }; + + this.$.editDiscount.show(event); } - showEditDialog() { - this.edit = this.checkedLines(); - this.$.editDialog.show(); + showEditDiscountDialog(event) { + if (this.isLocked) return; + + this.edit = { + discount: null, + sales: this.selectedValidSales() + }; + + this.$.editDiscountDialog.show(event); } - hideEditDialog() { - this.$.model.refresh(); - this.$.editDialog.hide(); + changeDiscount() { + const sale = this.edit.sale; + const newDiscount = this.edit.discount; + if (newDiscount != null && newDiscount != sale.discount) + this.updateDiscount([sale]); + + this.$.editDiscount.hide(); } - hideEditPopover() { - this.$.model.refresh(); - this.$.editPopover.hide(); + changeMultipleDiscount() { + const sales = this.edit.sales; + const newDiscount = this.edit.discount; + const hasChanges = sales.some(sale => { + return sale.discount != newDiscount; + }); + + if (newDiscount != null && hasChanges) + this.updateDiscount(sales); + + this.$.editDiscountDialog.hide(); + } + + updateDiscount(sales) { + const saleIds = sales.map(sale => { + return sale.id; + }); + + const params = {salesIds: saleIds, newDiscount: this.edit.discount}; + const query = `Tickets/${this.$params.id}/updateDiscount`; + this.$http.post(query, params).then(() => { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + + for (let sale of sales) + sale.discount = this.edit.discount; + + this.edit = null; + this.refreshTotal(); + }).finally(() => this.resetChanges()); + } + + getNewPrice() { + if (this.edit) { + const sale = this.edit.sale; + let newDiscount = sale.discount; + let newPrice = this.edit.price || sale.price; + + if (this.edit.discount != null) + newDiscount = this.edit.discount; + + if (this.edit.price != null) + newPrice = this.edit.price; + + const price = sale.quantity * newPrice; + const discount = (newDiscount * price) / 100; + + return price - discount; + } + + return 0; + } + + hasReserves() { + return this.sales.some(sale => { + return sale.reserved == true; + }); } /* @@ -368,26 +333,13 @@ class Controller extends Section { } setReserved(reserved) { - let selectedSales = this.checkedLines(); - let params = {sales: selectedSales, ticketFk: this.ticket.id, reserved: reserved}; - - let reservedSales = new Map(); - this.$http.post(`Sales/reserve`, params).then(res => { - let isReserved = res.config.data.reserved; - - res.config.data.sales.forEach(sale => { - reservedSales.set(sale.id, {reserved: isReserved}); + const selectedSales = this.selectedValidSales(); + const params = {ticketId: this.ticket.id, sales: selectedSales, reserved: reserved}; + this.$http.post(`Sales/reserve`, params).then(() => { + selectedSales.forEach(sale => { + sale.reserved = reserved; }); - - this.sales.forEach(sale => { - const reservedSale = reservedSales.get(sale.id); - if (reservedSale) - sale.reserved = reservedSale.reserved; - }); - }).finally(() => { - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - }); + }).finally(() => this.resetChanges()); } newOrderFromTicket() { @@ -395,7 +347,7 @@ class Controller extends Section { const path = this.$state.href('order.card.catalog', {id: res.data}); window.open(path, '_blank'); - this.vnApp.showSuccess(this.$translate.instant('Order created')); + this.vnApp.showSuccess(this.$t('Order created')); }); } @@ -404,7 +356,7 @@ class Controller extends Section { const client = this.ticket.client; const phone = address.mobile || address.phone || client.mobile || client.phone; - const sales = this.checkedLines(); + const sales = this.selectedValidSales(); const items = sales.map(sale => { return `${sale.quantity} ${sale.concept}`; }); @@ -417,7 +369,7 @@ class Controller extends Section { this.newSMS = { destinationFk: this.ticket.clientFk, destination: phone, - message: this.$translate.instant('Product not available', params) + message: this.$t('Product not available', params) }; this.$.sms.open(); } @@ -433,8 +385,8 @@ class Controller extends Section { * Creates a new sale if it's a new instance * Updates the sale quantity for existing instance */ - onChangeQuantity(sale) { - if (!sale.quantity) return; + changeQuantity(sale) { + if (!sale.itemFk || !sale.quantity) return; if (!sale.id) return this.addSale(sale); @@ -443,37 +395,30 @@ class Controller extends Section { } /* - * Updates a sale quantity + * Changes a sale quantity */ updateQuantity(sale) { - const data = {quantity: parseInt(sale.quantity)}; - const query = `Sales/${sale.id}/updateQuantity`; - this.$http.post(query, data).then(() => { - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + const data = {quantity: sale.quantity}; + this.$http.post(`Sales/${sale.id}/updateQuantity`, data).then(() => { + this.refreshTotal(); + this.vnApp.showSuccess(this.$t('Data saved!')); }).catch(e => { this.$.model.refresh(); throw e; - }).finally(() => { - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - }); + }).finally(() => this.resetChanges()); } /* - * Updates a sale concept + * Changes a sale concept */ updateConcept(sale) { const data = {newConcept: sale.concept}; - const query = `Sales/${sale.id}/updateConcept`; - this.$http.post(query, data).then(() => { - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + this.$http.post(`Sales/${sale.id}/updateConcept`, data).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); }).catch(e => { this.$.model.refresh(); throw e; - }).finally(() => { - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - }); + }).finally(() => this.resetChanges()); } /* @@ -489,7 +434,6 @@ class Controller extends Section { if (!res.data) return; const newSale = res.data; - sale.id = newSale.id; sale.image = newSale.item.image; sale.subName = newSale.item.subName; @@ -499,37 +443,29 @@ class Controller extends Section { sale.price = newSale.price; sale.item = newSale.item; - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - }).finally(() => { - if (this.newInstances().length === 0) - this.$.watcher.updateOriginalData(); - }); + this.refreshTotal(); + this.vnApp.showSuccess(this.$t('Data saved!')); + }).finally(() => this.resetChanges()); } isTicketEditable() { - this.$http.get(`Tickets/${this.$state.params.id}/isEditable`).then(res => { - this.isEditable = res.data; - }); + this.$http.get(`Tickets/${this.$params.id}/isEditable`) + .then(res => this.isEditable = res.data); } isTicketLocked() { - this.$http.get(`Tickets/${this.$state.params.id}/isLocked`).then(res => { - this.isLocked = res.data; - }); - } - - hasOneSaleSelected() { - if (this.totalCheckedLines() === 1) - return true; - return false; + this.$http.get(`Tickets/${this.$params.id}/isLocked`) + .then(res => this.isLocked = res.data); } calculateSalePrice() { - const sale = this.checkedLines()[0]; + const sale = this.selectedValidSales()[0]; + if (!sale) return; const query = `Sales/${sale.id}/recalculatePrice`; - this.$http.post(query).then(res => { - this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + this.$http.post(query).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); this.$.model.refresh(); + this.refreshTotal(); }); } diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js new file mode 100644 index 000000000..ba08d9733 --- /dev/null +++ b/modules/ticket/front/sale/index.spec.js @@ -0,0 +1,728 @@ +import './index.js'; +import watcher from 'core/mocks/watcher'; +import crudModel from 'core/mocks/crud-model'; + +describe('Ticket', () => { + describe('Component vnTicketSale', () => { + let controller; + let $scope; + let $state; + let $httpBackend; + + beforeEach(ngModule('ticket')); + + beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_, _$httpBackend_) => { + const ticket = { + id: 1, + clientFk: 101, + shipped: 1, + created: new Date(), + client: {salesPersonFk: 1}, + address: {mobile: 111111111} + }; + const sales = [ + { + id: 1, + concept: 'Item 1', + quantity: 5, + price: 23.5, + discount: 0, + }, { + id: 4, + concept: 'Item 2', + quantity: 20, + price: 5.5, + discount: 0, + } + ]; + + $state = _$state_; + $scope = $rootScope.$new(); + $scope.watcher = watcher; + $scope.sms = {open: () => {}}; + $scope.ticket = ticket; + $scope.model = crudModel; + $httpBackend = _$httpBackend_; + Object.defineProperties($state.params, { + id: { + value: ticket.id, + writable: true + }, + go: { + value: () => {}, + writable: false + } + }); + const $element = angular.element(''); + controller = $componentController('vnTicketSale', {$element, $scope}); + controller.card = {reload: () => {}}; + controller._ticket = ticket; + controller._sales = sales; + })); + + describe('ticket() setter', () => { + it('should set the ticket data an then call the isTicketEditable() and isTicketLocked() methods', () => { + jest.spyOn(controller, 'isTicketEditable').mockReturnThis(); + jest.spyOn(controller, 'isTicketLocked').mockReturnThis(); + + controller.ticket = {id: 1}; + + expect(controller.isTicketEditable).toHaveBeenCalledWith(); + expect(controller.isTicketLocked).toHaveBeenCalledWith(); + }); + }); + + describe('sales() setter', () => { + it('should set the sales data an then call the refreshTotal() method', () => { + jest.spyOn(controller, 'refreshTotal').mockReturnThis(); + + controller.sales = [{id: 1}]; + + expect(controller.refreshTotal).toHaveBeenCalledWith(); + }); + }); + + describe('getSubTotal()', () => { + it('should make an HTTP GET query and then set the subtotal property', () => { + const expectedAmount = 128; + + $httpBackend.expect('GET', 'Tickets/1/subtotal').respond(200, expectedAmount); + controller.getSubTotal(); + $httpBackend.flush(); + + expect(controller.subtotal).toEqual(expectedAmount); + }); + }); + + describe('getSaleTotal()', () => { + it('should return the sale total amount', () => { + const sale = { + quantity: 10, + price: 2, + discount: 10 + }; + + const expectedAmount = 18; + const result = controller.getSaleTotal(sale); + + expect(result).toEqual(expectedAmount); + }); + }); + + describe('getMana()', () => { + it('should make an HTTP GET query and return the worker mana', () => { + controller.edit = {}; + const expectedAmount = 250; + + $httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount); + controller.getMana(); + $httpBackend.flush(); + + expect(controller.edit.mana).toEqual(expectedAmount); + }); + }); + + describe('getVat()', () => { + it('should make an HTTP GET query and return the ticket VAT', () => { + controller.edit = {}; + const expectedAmount = 67; + + $httpBackend.expect('GET', 'Tickets/1/getVAT').respond(200, expectedAmount); + controller.getVat(); + $httpBackend.flush(); + + expect(controller.VAT).toEqual(expectedAmount); + }); + }); + + describe('selectedSales()', () => { + it('should return a list of selected sales', () => { + controller.sales[1].checked = true; + + const expectedSaleId = 4; + const result = controller.selectedSales(); + const firstSelectedSale = result[0]; + + expect(result.length).toEqual(1); + expect(firstSelectedSale.id).toEqual(expectedSaleId); + }); + }); + + describe('selectedValidSales()', () => { + it('should return a list of selected sales having a sale id', () => { + const newEmptySale = {quantity: 10, checked: true}; + controller.sales.push(newEmptySale); + controller.sales[0].checked = true; + + const expectedSaleId = 1; + const result = controller.selectedValidSales(); + const firstSelectedSale = result[0]; + + expect(result.length).toEqual(1); + expect(firstSelectedSale.id).toEqual(expectedSaleId); + }); + }); + + describe('selectedSalesCount()', () => { + it('should return the number of selected sales', () => { + controller.sales[0].checked = true; + + const result = controller.selectedSalesCount(); + + expect(result).toEqual(1); + }); + }); + + describe('hasSelectedSales()', () => { + it('should return truthy if atleast one sale is selected', () => { + controller.sales[0].checked = true; + + const result = controller.hasSelectedSales(); + + expect(result).toBeTruthy(); + }); + }); + + describe('hasOneSaleSelected()', () => { + it('should return truthy if just one sale is selected', () => { + controller.sales[0].checked = true; + + const result = controller.hasOneSaleSelected(); + + expect(result).toBeTruthy(); + }); + + it('should return falsy if more than one sale is selected', () => { + controller.sales[0].checked = true; + controller.sales[1].checked = true; + + const result = controller.hasOneSaleSelected(); + + expect(result).toBeFalsy(); + }); + }); + + describe('newInstances()', () => { + it(`should return a list of sales that doesn't have an id`, () => { + const newEmptySale = {quantity: 10, checked: true}; + controller.sales.push(newEmptySale); + + const result = controller.newInstances(); + const firstNewSale = result[0]; + + expect(result.length).toEqual(1); + expect(firstNewSale.id).toBeUndefined(); + expect(firstNewSale.quantity).toEqual(10); + }); + }); + + describe('resetChanges()', () => { + it(`should not call the watcher updateOriginalData() method`, () => { + jest.spyOn(controller.$.watcher, 'updateOriginalData').mockReturnThis(); + + const newEmptySale = {quantity: 10}; + controller.sales.push(newEmptySale); + controller.resetChanges(); + + expect(controller.$.watcher.updateOriginalData).not.toHaveBeenCalledWith(); + }); + + it(`should call the watcher updateOriginalData() method`, () => { + jest.spyOn(controller.$.watcher, 'updateOriginalData').mockReturnThis(); + + controller.resetChanges(); + + expect(controller.$.watcher.updateOriginalData).toHaveBeenCalledWith(); + }); + }); + + describe('changeState()', () => { + it('should make an HTTP post query, then call the showSuccess(), reload() and resetChanges() methods', () => { + jest.spyOn(controller.card, 'reload').mockReturnThis(); + jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + + const expectedParams = {ticketFk: 1, code: 'OK'}; + $httpBackend.expect('POST', `TicketTrackings/changeState`, expectedParams).respond(200); + controller.changeState('OK'); + $httpBackend.flush(); + + expect(controller.card.reload).toHaveBeenCalledWith(); + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.resetChanges).toHaveBeenCalledWith(); + }); + }); + + describe('removeSales()', () => { + it('should make an HTTP post query, then call the showSuccess(), removeSelectedSales() and resetChanges() methods', () => { + jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); + jest.spyOn(controller, 'removeSelectedSales').mockReturnThis(); + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + + const firstSale = controller.sales[0]; + firstSale.checked = true; + const expectedParams = {sales: [firstSale], ticketId: 1}; + $httpBackend.expect('POST', `Sales/deleteSales`, expectedParams).respond(200); + controller.removeSales(); + $httpBackend.flush(); + + expect(controller.removeSelectedSales).toHaveBeenCalledWith(); + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.resetChanges).toHaveBeenCalledWith(); + }); + }); + + describe('removeSelectedSales()', () => { + it('should remove the selected sales from the controller sale property', () => { + jest.spyOn(controller, 'refreshTotal').mockReturnThis(); + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + + const firstSale = controller.sales[0]; + firstSale.checked = true; + + controller.removeSelectedSales(); + + const lastSale = controller.sales[0]; + + expect(controller.sales.length).toEqual(1); + expect(lastSale.id).toEqual(4); + expect(controller.refreshTotal).toHaveBeenCalledWith(); + }); + }); + + describe('createClaim()', () => { + it('should perform a query and call windows open', () => { + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + jest.spyOn(controller.$state, 'go').mockReturnThis(); + + const newEmptySale = {quantity: 10}; + controller.sales.push(newEmptySale); + const firstSale = controller.sales[0]; + firstSale.checked = true; + + const expectedParams = {ticketId: 1, sales: [firstSale]}; + $httpBackend.expect('POST', `Claims/createFromSales`, expectedParams).respond(200, {id: 1}); + controller.createClaim(); + $httpBackend.flush(); + + expect(controller.resetChanges).toHaveBeenCalledWith(); + expect(controller.$state.go).toHaveBeenCalledWith('claim.card.basicData', {id: 1}); + }); + }); + + describe('setTransferParams()', () => { + it('should define the transfer object on the controller and its default properties', () => { + const firstSale = controller.sales[0]; + firstSale.checked = true; + const expectedResponse = [firstSale]; + + $httpBackend.expect('GET', `clients/101/lastActiveTickets?ticketId=1`).respond(expectedResponse); + controller.setTransferParams(); + $httpBackend.flush(); + + const lastActiveTickets = controller.transfer.lastActiveTickets; + + expect(controller.transfer).toBeDefined(); + expect(lastActiveTickets).toBeDefined(); + expect(lastActiveTickets[0].id).toEqual(1); + }); + }); + + describe('transferSales()', () => { + it('should transfer sales to a ticket and then call to the $state go() method', () => { + jest.spyOn(controller.$state, 'go').mockReturnThis(); + + controller.transfer = { + sales: [{id: 1, itemFk: 1}, {id: 2, itemFk: 4}] + }; + + const ticketIdToTransfer = 13; + const expectedResponse = {id: ticketIdToTransfer}; + const params = { + ticketId: 13, + sales: controller.transfer.sales + }; + + $httpBackend.expect('POST', `tickets/1/transferSales`, params).respond(expectedResponse); + controller.transferSales(ticketIdToTransfer); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: ticketIdToTransfer}); + }); + }); + + describe('updatePrice()', () => { + it('should make an HTTP POST query, update the sale price and then call to the resetChanges() method', () => { + jest.spyOn(controller, 'refreshTotal').mockReturnThis(); + jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + controller.$.editPricePopover = {hide: jest.fn()}; + controller.edit = { + price: 2, + sale: selectedSale + }; + const expectedParams = {newPrice: 2}; + $httpBackend.expect('POST', `Sales/1/updatePrice`, expectedParams).respond(200, {price: 2}); + controller.updatePrice(); + $httpBackend.flush(); + + expect(selectedSale.price).toEqual(2); + expect(controller.refreshTotal).toHaveBeenCalledWith(); + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.$.editPricePopover.hide).toHaveBeenCalledWith(); + expect(controller.resetChanges).toHaveBeenCalledWith(); + }); + }); + + describe('changeDiscount()', () => { + it('should call to the updateDiscount() method and then to the editDiscount hide() method', () => { + jest.spyOn(controller, 'updateDiscount').mockReturnThis(); + + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + const expectedSale = selectedSale; + controller.$.editDiscount = {hide: jest.fn()}; + controller.edit = { + discount: 10, + sale: selectedSale + }; + + controller.changeDiscount(); + + expect(controller.updateDiscount).toHaveBeenCalledWith([expectedSale]); + expect(controller.$.editDiscount.hide).toHaveBeenCalledWith(); + }); + }); + + describe('changeMultipleDiscount()', () => { + it('should call to the updateDiscount() method and then to the editDiscountDialog hide() method', () => { + jest.spyOn(controller, 'updateDiscount').mockReturnThis(); + + const firstSelectedSale = controller.sales[0]; + firstSelectedSale.checked = true; + + const secondSelectedSale = controller.sales[1]; + secondSelectedSale.checked = true; + + const expectedSales = [firstSelectedSale, secondSelectedSale]; + controller.$.editDiscountDialog = {hide: jest.fn()}; + controller.edit = { + discount: 10, + sales: expectedSales + }; + + controller.changeMultipleDiscount(); + + expect(controller.updateDiscount).toHaveBeenCalledWith(expectedSales); + expect(controller.$.editDiscountDialog.hide).toHaveBeenCalledWith(); + }); + + it('should not call to the updateDiscount() method and then to the editDiscountDialog hide() method', () => { + jest.spyOn(controller, 'updateDiscount').mockReturnThis(); + + const firstSelectedSale = controller.sales[0]; + firstSelectedSale.checked = true; + firstSelectedSale.discount = 10; + + const secondSelectedSale = controller.sales[1]; + secondSelectedSale.checked = true; + secondSelectedSale.discount = 10; + + const expectedSales = [firstSelectedSale, secondSelectedSale]; + controller.$.editDiscountDialog = {hide: jest.fn()}; + controller.edit = { + discount: 10, + sales: expectedSales + }; + + controller.changeMultipleDiscount(); + + expect(controller.updateDiscount).not.toHaveBeenCalledWith(expectedSales); + expect(controller.$.editDiscountDialog.hide).toHaveBeenCalledWith(); + }); + }); + + describe('updateDiscount()', () => { + it('should make an HTTP POST query, update the sales discount and then call to the resetChanges() method', () => { + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); + jest.spyOn(controller, 'refreshTotal').mockReturnThis(); + + const expectedDiscount = 10; + const firstSelectedSale = controller.sales[0]; + firstSelectedSale.checked = true; + + const secondSelectedSale = controller.sales[1]; + secondSelectedSale.checked = true; + + controller.edit = { + discount: expectedDiscount + }; + + const expectedSales = [firstSelectedSale, secondSelectedSale]; + const expectedSaleIds = [firstSelectedSale.id, secondSelectedSale.id]; + const expectedParams = {salesIds: expectedSaleIds, newDiscount: expectedDiscount}; + $httpBackend.expect('POST', `Tickets/1/updateDiscount`, expectedParams).respond(200, {discount: 10}); + controller.updateDiscount(expectedSales); + $httpBackend.flush(); + + expect(firstSelectedSale.discount).toEqual(expectedDiscount); + expect(secondSelectedSale.discount).toEqual(expectedDiscount); + expect(controller.refreshTotal).toHaveBeenCalledWith(); + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.resetChanges).toHaveBeenCalledWith(); + }); + }); + + describe('getNewPrice()', () => { + it('should return the total price simulation from a price change', () => { + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + controller.edit = { + price: 2, + sale: selectedSale + }; + + const expectedAmount = 10; + const result = controller.getNewPrice(); + + expect(result).toEqual(expectedAmount); + }); + + it('should return the total price simulation from a discount change', () => { + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + controller.edit = { + discount: 10, + sale: selectedSale + }; + + const expectedAmount = 105.75; + const result = controller.getNewPrice(); + + expect(result).toEqual(expectedAmount); + }); + }); + + describe('hasReserves()', () => { + it('should return true for any sale marked as reserved', () => { + const selectedSale = controller.sales[0]; + selectedSale.reserved = true; + + const result = controller.hasReserves(); + + expect(result).toBeTruthy(); + }); + }); + + describe('unmarkAsReserved()', () => { + it('should call setReserved with false', () => { + jest.spyOn(controller, 'setReserved'); + + controller.unmarkAsReserved(false); + + expect(controller.setReserved).toHaveBeenCalledWith(false); + }); + }); + + describe('markAsReserved()', () => { + it('should call setReserved with true', () => { + jest.spyOn(controller, 'setReserved'); + + controller.markAsReserved(true); + + expect(controller.setReserved).toHaveBeenCalledWith(true); + }); + }); + + describe('setReserved()', () => { + it('should call getCheckedLines, $.index.accept and make a query ', () => { + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + const expectedParams = { + sales: [selectedSale], + ticketId: 1, + reserved: false + }; + + $httpBackend.expectPOST(`Sales/reserve`, expectedParams).respond(); + controller.unmarkAsReserved(false); + $httpBackend.flush(); + }); + }); + + describe('newOrderFromTicket()', () => { + it('should make an HTTP post query and then open the new order on a new tab', () => { + const expectedParams = {ticketFk: 1}; + const expectedResponse = {id: 123}; + + window.open = jasmine.createSpy('open'); + controller.$state.href = jasmine.createSpy('href') + .and.returnValue('/somePath'); + + $httpBackend.expect('POST', `Orders/newFromTicket`, expectedParams).respond(expectedResponse); + controller.newOrderFromTicket(); + $httpBackend.flush(); + + expect(window.open).toHaveBeenCalledWith('/somePath', '_blank'); + }); + }); + + describe('showSMSDialog()', () => { + it('should open an SMS dialog with specified data', () => { + jest.spyOn(controller.$.sms, 'open'); + + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + controller.showSMSDialog(); + + expect(controller.$.sms.open).toHaveBeenCalledWith(); + expect(controller.newSMS.destination).toEqual(111111111); + expect(controller.newSMS.message).not.toEqual(''); + }); + }); + + describe('changeQuantity()', () => { + it('should not call addSale() or updateQuantity() methods', () => { + jest.spyOn(controller, 'addSale'); + jest.spyOn(controller, 'updateQuantity'); + + const sale = {itemFk: 4}; + controller.changeQuantity(sale); + + expect(controller.addSale).not.toHaveBeenCalled(); + expect(controller.updateQuantity).not.toHaveBeenCalled(); + }); + + it('should call addSale() method', () => { + jest.spyOn(controller, 'addSale'); + + const sale = {itemFk: 4, quantity: 5}; + controller.changeQuantity(sale); + + expect(controller.addSale).toHaveBeenCalledWith(sale); + }); + + it('should call updateQuantity() method', () => { + jest.spyOn(controller, 'addSale'); + jest.spyOn(controller, 'updateQuantity'); + + const sale = {id: 1, itemFk: 4, quantity: 5}; + controller.changeQuantity(sale); + + expect(controller.addSale).not.toHaveBeenCalled(); + expect(controller.updateQuantity).toHaveBeenCalledWith(sale); + }); + }); + + describe('updateQuantity()', () => { + it('should make a POST query saving sale quantity', () => { + jest.spyOn(controller, 'refreshTotal').mockReturnThis(); + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + selectedSale.quantity = 10; + + const expectedParams = {quantity: 10}; + $httpBackend.expect('POST', `Sales/1/updateQuantity`, expectedParams).respond(); + controller.updateQuantity(selectedSale); + $httpBackend.flush(); + + expect(controller.refreshTotal).toHaveBeenCalledWith(); + expect(controller.resetChanges).toHaveBeenCalledWith(); + }); + }); + + describe('updateConcept()', () => { + it('should make a POST query saving sale concept', () => { + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + selectedSale.concept = 'My new weapon'; + + const expectedParams = {newConcept: 'My new weapon'}; + $httpBackend.expect('POST', `Sales/1/updateConcept`, expectedParams).respond(); + controller.updateConcept(selectedSale); + $httpBackend.flush(); + + expect(controller.resetChanges).toHaveBeenCalledWith(); + }); + }); + + describe('addSale()', () => { + it('should make a POST query adding a new sale', () => { + jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); + jest.spyOn(controller, 'refreshTotal').mockReturnThis(); + jest.spyOn(controller, 'resetChanges').mockReturnThis(); + + const newSale = {itemFk: 4, quantity: 10}; + const expectedParams = {itemId: 4, quantity: 10}; + const expectedResult = { + id: 30, + quantity: 10, + discount: 0, + price: 0, + itemFk: 4, + item: { + subName: 'Item subName', + image: '30.png' + } + }; + + $httpBackend.expect('POST', `tickets/1/addSale`, expectedParams).respond(expectedResult); + controller.addSale(newSale); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.refreshTotal).toHaveBeenCalledWith(); + expect(controller.resetChanges).toHaveBeenCalledWith(); + }); + }); + + describe('isTicketEditable()', () => { + it('should make a HTTP GET query and set the isEditable property on the controller', () => { + $httpBackend.expect('GET', `Tickets/1/isEditable`).respond(200, true); + controller.isTicketEditable(); + $httpBackend.flush(); + + expect(controller.isEditable).toBeDefined(); + expect(controller.isEditable).toBeTruthy(); + }); + }); + + describe('isTicketLocked()', () => { + it('should make a HTTP GET query and set the isLocked property on the controller', () => { + $httpBackend.expect('GET', `Tickets/1/isLocked`).respond(200, false); + controller.isTicketLocked(); + $httpBackend.flush(); + + expect(controller.isLocked).toBeDefined(); + expect(controller.isLocked).toBeFalsy(); + }); + }); + + describe('calculateSalePrice()', () => { + it('should make an HTTP post query ', () => { + jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); + jest.spyOn(controller.$.model, 'refresh').mockReturnThis(); + jest.spyOn(controller, 'refreshTotal').mockReturnThis(); + + const selectedSale = controller.sales[0]; + selectedSale.checked = true; + + $httpBackend.expect('POST', `Sales/1/recalculatePrice`).respond(200); + controller.calculateSalePrice(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.$.model.refresh).toHaveBeenCalledWith(); + expect(controller.refreshTotal).toHaveBeenCalledWith(); + }); + }); + }); +}); diff --git a/modules/ticket/front/sale/locale/es.yml b/modules/ticket/front/sale/locale/es.yml index 908b76bc6..fd98304b0 100644 --- a/modules/ticket/front/sale/locale/es.yml +++ b/modules/ticket/front/sale/locale/es.yml @@ -1,5 +1,6 @@ New price: Nuevo precio Add item: Añadir artículo +Add item to basket: Añadir artículo a la cesta Add turn: Añadir a turno Delete ticket: Eliminar ticket Mark as reserved: Marcar como reservado diff --git a/modules/ticket/front/sale/specs/editDiscount.spec.js b/modules/ticket/front/sale/specs/editDiscount.spec.js deleted file mode 100644 index 8eccfe74f..000000000 --- a/modules/ticket/front/sale/specs/editDiscount.spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import '../editDiscount.js'; - -describe('Ticket', () => { - describe('Component vnTicketSaleEditDiscount', () => { - let controller; - let $httpBackend; - let $state; - let $scope; - - beforeEach(ngModule('ticket')); - - beforeEach(angular.mock.inject(($componentController, _$state_, _$httpBackend_, $rootScope) => { - $httpBackend = _$httpBackend_; - $scope = $rootScope.$new(); - $scope.index = {model: {instances: [{id: 1}, {id: 2}]}, accept: () => { - return { - then: () => {} - }; - }}; - $state = _$state_; - $state.params.id = 11; - const $element = angular.element(''); - controller = $componentController('vnTicketSaleEditDiscount', {$element, $scope}); - controller._edit = [{id: 3}]; - controller.onHide = () => {}; - })); - - describe('edit() setter', () => { - it('should set _edit value and call setNewDiscount', () => { - jest.spyOn(controller, 'setNewDiscount'); - controller.edit = {id: 1}; - - expect(controller.edit).toEqual({id: 1}); - expect(controller.setNewDiscount).toHaveBeenCalledWith(); - }); - }); - - describe('bulk() setter', () => { - it('should set _bulk value and call setNewDiscount', () => { - jest.spyOn(controller, 'setNewDiscount'); - controller.bulk = true; - - expect(controller.bulk).toEqual(true); - expect(controller.setNewDiscount).toHaveBeenCalledWith(); - }); - }); - - describe('setNewDiscount()', () => { - it('should set NewDiscount to edit[0].discount value if it doesnt exists', () => { - controller.edit = [{discount: 1}]; - controller.setNewDiscount(); - - expect(controller.newDiscount).toEqual(1); - }); - }); - - describe('updateDiscount()', () => { - it('should make a query if the discount value has been modified or the bulk value is true', () => { - controller.bulk = true; - controller.newDiscount = 15; - - $httpBackend.expectPOST(`Tickets/11/updateDiscount`).respond(); - controller.updateDiscount(); - - $httpBackend.flush(); - }); - - it(`should throw if there's no changes on discount and it isn't bulk`, () => { - controller.bulk = false; - controller.newDiscount = 15; - controller.edit = [{discount: 15}]; - jest.spyOn(controller.vnApp, 'showError'); - controller.updateDiscount(); - - expect(controller.vnApp.showError).toHaveBeenCalledWith('There are no changes to save'); - }); - }); - - describe('clearDiscount()', () => { - it('should set newDiscount to null', () => { - controller.clearDiscount(); - - expect(controller.newDiscount).toEqual(null); - }); - }); - }); -}); diff --git a/modules/ticket/front/sale/specs/index.spec.js b/modules/ticket/front/sale/specs/index.spec.js deleted file mode 100644 index 1c8fbda53..000000000 --- a/modules/ticket/front/sale/specs/index.spec.js +++ /dev/null @@ -1,409 +0,0 @@ -import '../index.js'; -import watcher from 'core/mocks/watcher'; -import crudModel from 'core/mocks/crud-model'; - -describe('Ticket', () => { - describe('Component vnTicketSale', () => { - let controller; - let $scope; - let $state; - let $httpBackend; - - const ticket = { - id: 1, - clientFk: 101, - shipped: 1, - created: new Date(), - client: {salesPersonFk: 1}, - address: {mobile: 111111111} - }; - const sales = [ - { - id: 1, - concept: 'Item 1', - quantity: 5, - price: 23.5, - discount: 0, - }, { - id: 4, - concept: 'Item 2', - quantity: 20, - price: 5.5, - discount: 0, - } - ]; - - beforeEach(ngModule('ticket')); - - beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_, _$httpBackend_) => { - $state = _$state_; - $scope = $rootScope.$new(); - $scope.watcher = watcher; - $scope.sms = {open: () => {}}; - $scope.ticket = ticket; - $scope.model = crudModel; - $httpBackend = _$httpBackend_; - Object.defineProperties($state.params, { - id: { - value: ticket.id, - writable: true - }, - go: { - value: () => {}, - writable: false - } - }); - const $element = angular.element(''); - controller = $componentController('vnTicketSale', {$element, $scope}); - controller.card = {reload: () => {}}; - controller.ticket = ticket; - controller.sales = sales; - })); - - describe('createClaim()', () => { - it('should perform a query and call windows open', () => { - jest.spyOn(controller.$state, 'go'); - - const claim = {id: 1}; - const sales = [{id: 1}, {id: 2}]; - - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - $httpBackend.when('POST', `Claims/createFromSales`, {claim: claim, sales: sales}).respond(claim); - $httpBackend.expect('POST', `Claims/createFromSales`).respond(claim); - controller.createClaim(); - $httpBackend.flush(); - - expect(controller.subtotal).toEqual(227.5); - expect(controller.VAT).toEqual(10.5); - expect(controller.total).toEqual(238); - expect(controller.$state.go).toHaveBeenCalledWith('claim.card.basicData', {id: 1}); - }); - }); - - describe('isChecked() getter', () => { - it('should set isChecked value to true when one of the instances has the value checked to true', () => { - controller.sales[0].checked = true; - - expect(controller.isChecked).toBeTruthy(); - }); - }); - - describe('checkedLines()', () => { - it('should make an array of the instances with the property checked true()', () => { - let sale = controller.sales[0]; - sale.checked = true; - let expectedResult = [sale]; - - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - let result = controller.checkedLines(); - $httpBackend.flush(); - - expect(result).toEqual(expectedResult); - }); - }); - - describe('onStateOkClick()', () => { - it('should perform a get and then call a function', () => { - let filter = {where: {code: 'OK'}, fields: ['id']}; - filter = encodeURIComponent(JSON.stringify(filter)); - let res = [{id: 3}]; - jest.spyOn(controller, 'onStateChange').mockReturnThis(); - - $httpBackend.whenGET(`States?filter=${filter}`).respond(res); - $httpBackend.expectGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.expectGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.onStateOkClick(); - $httpBackend.flush(); - - expect(controller.onStateChange).toHaveBeenCalledWith(3); - }); - }); - - describe('onStateChange()', () => { - it('should perform a post and then call a function', () => { - $httpBackend.expectPOST(`TicketTrackings/changeState`).respond(); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.onStateChange(3); - $httpBackend.flush(); - }); - }); - - describe('onRemoveLinesClick()', () => { - it('should call getCheckedLines, call removeInstances, and make a query', () => { - controller.sales[0].checked = true; - - $httpBackend.whenPOST(`Sales/removes`).respond(); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.onRemoveLinesClick('accept'); - $httpBackend.flush(); - - expect(controller.sales.length).toEqual(1); - }); - }); - - describe('unmarkAsReserved()', () => { - it('should call setReserved with false', () => { - jest.spyOn(controller, 'setReserved'); - - controller.unmarkAsReserved(false); - - expect(controller.setReserved).toHaveBeenCalledWith(false); - }); - }); - - describe('markAsReserved()', () => { - it('should call setReserved with true', () => { - jest.spyOn(controller, 'setReserved'); - - controller.markAsReserved(true); - - expect(controller.setReserved).toHaveBeenCalledWith(true); - }); - }); - - describe('setReserved()', () => { - it('should call getCheckedLines, $.index.accept and make a query ', () => { - const sale = controller.sales[0]; - sale.checked = true; - let expectedRequest = { - sales: [sale], - ticketFk: ticket.id, - reserved: false - }; - - $httpBackend.expectPOST(`Sales/reserve`, expectedRequest).respond(); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.unmarkAsReserved(false); - $httpBackend.flush(); - }); - }); - - describe('showSMSDialog()', () => { - it('should open an SMS dialog with specified data', () => { - jest.spyOn(controller.$.sms, 'open'); - - controller.sales[0].checked = true; - controller.showSMSDialog(); - - expect(controller.$.sms.open).toHaveBeenCalledWith(); - expect(controller.newSMS.destination).toEqual(111111111); - expect(controller.newSMS.message).not.toEqual(''); - }); - }); - - describe('onChangeQuantity()', () => { - it('should not call addSale() or updateQuantity() methods', () => { - jest.spyOn(controller, 'addSale'); - jest.spyOn(controller, 'updateQuantity'); - - const sale = {itemFk: 4}; - controller.onChangeQuantity(sale); - - expect(controller.addSale).not.toHaveBeenCalled(); - expect(controller.updateQuantity).not.toHaveBeenCalled(); - }); - - it('should call addSale() method', () => { - jest.spyOn(controller, 'addSale'); - - const sale = {itemFk: 4, quantity: 5}; - controller.onChangeQuantity(sale); - - expect(controller.addSale).toHaveBeenCalledWith(sale); - }); - - it('should call updateQuantity() method', () => { - jest.spyOn(controller, 'updateQuantity'); - jest.spyOn(controller, 'addSale'); - - const sale = {id: 1, itemFk: 4, quantity: 5}; - controller.onChangeQuantity(sale); - - expect(controller.addSale).not.toHaveBeenCalled(); - expect(controller.updateQuantity).toHaveBeenCalledWith(sale); - }); - }); - - describe('updateQuantity()', () => { - it('should make a POST query saving sale quantity', () => { - jest.spyOn(controller.$.watcher, 'updateOriginalData'); - const data = {quantity: 10}; - const sale = sales[0]; - sale.quantity = 10; - - $httpBackend.when('POST', `Sales/4/updateQuantity`, data).respond(); - $httpBackend.expect('POST', `Sales/4/updateQuantity`, data).respond(); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.updateQuantity(sale); - $httpBackend.flush(); - - expect(controller.$.watcher.updateOriginalData).toHaveBeenCalledWith(); - }); - }); - - describe('updateConcept()', () => { - it('should make a POST query saving sale concept', () => { - jest.spyOn(controller.$.watcher, 'updateOriginalData'); - const data = {newConcept: 'My new weapon'}; - const sale = sales[0]; - sale.concept = 'My new weapon'; - - $httpBackend.when('POST', `Sales/4/updateConcept`, data).respond(); - $httpBackend.expect('POST', `Sales/4/updateConcept`, data).respond(); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.updateConcept(sale); - $httpBackend.flush(); - - expect(controller.$.watcher.updateOriginalData).toHaveBeenCalledWith(); - }); - }); - - describe('addSale()', () => { - it('should make a POST query adding a new sale', () => { - jest.spyOn(controller.$.watcher, 'updateOriginalData'); - const newSale = {itemFk: 4, quantity: 10}; - const params = {itemId: 4, quantity: 10}; - - const expectedResult = { - id: 30, - quantity: 10, - discount: 0, - price: 0, - itemFk: 4, - item: { - subName: 'Item subName', - image: '30.png' - } - }; - - $httpBackend.when('POST', `tickets/1/addSale`, params).respond(expectedResult); - $httpBackend.expect('POST', `tickets/1/addSale`, params).respond(expectedResult); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.addSale(newSale); - $httpBackend.flush(); - - expect(controller.$.watcher.updateOriginalData).toHaveBeenCalledWith(); - }); - }); - - describe('transferSales()', () => { - it('should transfer sales to a ticket', () => { - jest.spyOn(controller, 'goToTicket'); - controller.transfer = { - sales: [{id: 1, itemFk: 1}, {id: 2, itemFk: 4}] - }; - - const expectedResponse = {id: 13}; - const params = { - ticketId: 13, - sales: controller.transfer.sales - }; - - $httpBackend.when('POST', `tickets/1/transferSales`, params).respond(expectedResponse); - $httpBackend.expect('POST', `tickets/1/transferSales`, params).respond(expectedResponse); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.transferSales(13); - $httpBackend.flush(); - - expect(controller.goToTicket).toHaveBeenCalledWith(13); - }); - }); - - describe('setTransferParams()', () => { - it('should define the transfer object on the controller and its default properties', () => { - let sale = controller.sales[0]; - sale.checked = true; - const expectedResponse = [sale]; - - $httpBackend.when('GET', `clients/101/lastActiveTickets?ticketId=1`).respond(expectedResponse); - $httpBackend.expect('GET', `clients/101/lastActiveTickets?ticketId=1`).respond(expectedResponse); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.setTransferParams(); - $httpBackend.flush(); - - const lastActiveTickets = controller.transfer.lastActiveTickets; - - expect(controller.transfer).toBeDefined(); - expect(lastActiveTickets).toBeDefined(); - expect(lastActiveTickets[0].id).toEqual(4); - }); - }); - - describe('newOrderFromTicket()', () => { - it('should make an HTTP post query and then open the new order on a new tab', () => { - const params = {ticketFk: 1}; - const expectedResponse = {id: 123}; - - window.open = jasmine.createSpy('open'); - controller.$state.href = jasmine.createSpy('href') - .and.returnValue('/somePath'); - - $httpBackend.when('POST', `Orders/newFromTicket`, params).respond(expectedResponse); - $httpBackend.expect('POST', `Orders/newFromTicket`, params).respond(expectedResponse); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - controller.newOrderFromTicket(); - $httpBackend.flush(); - - expect(window.open).toHaveBeenCalledWith('/somePath', '_blank'); - }); - }); - - describe('hasOneSaleSelected()', () => { - it('should return true if just one sale is selected', () => { - controller.sales[0].checked = true; - - expect(controller.hasOneSaleSelected()).toBeTruthy(); - }); - }); - - describe('calculateSalePrice()', () => { - it('should make an HTTP post query ', () => { - controller.sales[0].checked = true; - - $httpBackend.when('POST', `Sales/4/recalculatePrice`).respond(200); - $httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5); - $httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5); - $httpBackend.whenGET(`Tickets/1/isEditable`).respond(); - $httpBackend.whenGET(`Tickets/1/isLocked`).respond(); - - controller.calculateSalePrice(); - $httpBackend.flush(); - }); - }); - }); -}); diff --git a/modules/ticket/front/sale/style.scss b/modules/ticket/front/sale/style.scss index 142c72295..a1c36f271 100644 --- a/modules/ticket/front/sale/style.scss +++ b/modules/ticket/front/sale/style.scss @@ -24,7 +24,7 @@ vn-ticket-sale { } } vn-dialog.edit { - @extend .edit-price; + @extend .edit-popover; &>div{ padding: 0!important; @@ -81,8 +81,9 @@ vn-ticket-sale { width: 400px } } -.edit-price { +.vn-popover .edit-popover { min-width: 200px; + text-align: center; section.header { background-color: $color-main; diff --git a/modules/ticket/front/summary/index.html b/modules/ticket/front/summary/index.html index 78b941fb1..8b73c9c48 100644 --- a/modules/ticket/front/summary/index.html +++ b/modules/ticket/front/summary/index.html @@ -16,7 +16,7 @@ + value="{{$ctrl.summary.ticketState.state.name}}"> diff --git a/modules/ticket/front/summary/index.js b/modules/ticket/front/summary/index.js index 864f118d2..980ccd449 100644 --- a/modules/ticket/front/summary/index.js +++ b/modules/ticket/front/summary/index.js @@ -31,7 +31,7 @@ class Controller extends Section { get isEditable() { try { - return !this.ticket.state.state.alertLevel; + return !this.ticket.ticketState.state.alertLevel; } catch (e) {} return true; diff --git a/modules/worker/back/methods/worker-mana/getCurrentWorkerMana.js b/modules/worker/back/methods/worker-mana/getCurrentWorkerMana.js index 7be2312e6..fa34af475 100644 --- a/modules/worker/back/methods/worker-mana/getCurrentWorkerMana.js +++ b/modules/worker/back/methods/worker-mana/getCurrentWorkerMana.js @@ -14,16 +14,13 @@ module.exports = Self => { }); Self.getCurrentWorkerMana = async ctx => { - let currentClientId = ctx.req.accessToken.userId; - let currentWorker = await Self.app.models.Worker.findOne({ - where: {userFk: currentClientId}, - fields: 'id' - }); - let currentWorkerMana = await Self.app.models.WorkerMana.findOne({ - where: {workerFk: currentWorker.id}, + let userId = ctx.req.accessToken.userId; + + let workerMana = await Self.app.models.WorkerMana.findOne({ + where: {workerFk: userId}, fields: 'amount' }); - return currentWorkerMana ? currentWorkerMana.amount : 0; + return workerMana ? workerMana.amount : 0; }; }; diff --git a/modules/zone/back/methods/zone/deleteZone.js b/modules/zone/back/methods/zone/deleteZone.js index d5dd42d7b..baa442491 100644 --- a/modules/zone/back/methods/zone/deleteZone.js +++ b/modules/zone/back/methods/zone/deleteZone.js @@ -32,7 +32,7 @@ module.exports = Self => { zoneFk: id }, include: { - relation: 'state', + relation: 'ticketState', scope: { fields: ['id', 'alertLevel', 'code'] } @@ -49,7 +49,7 @@ module.exports = Self => { ticketList.forEach(ticket => { promises.push(ticket.updateAttributes({zoneFk: null}, options)); - if (ticket.state().alertLevel == 0 && ticket.shipped >= today) { + if (ticket.ticketState().alertLevel == 0 && ticket.shipped >= today) { promises.push(models.TicketTracking.create({ ticketFk: ticket.id, stateFk: fixingState.id, diff --git a/modules/zone/front/calendar/index.spec.js b/modules/zone/front/calendar/index.spec.js index 6ea524020..00645b317 100644 --- a/modules/zone/front/calendar/index.spec.js +++ b/modules/zone/front/calendar/index.spec.js @@ -33,6 +33,7 @@ describe('component vnZoneCalendar', () => { describe('step()', () => { it('should set the date month to 4 months backwards', () => { const now = new Date(); + now.setDate(15); now.setMonth(now.getMonth() - 4); controller.step(-1); @@ -45,6 +46,7 @@ describe('component vnZoneCalendar', () => { it('should set the date month to 4 months forwards', () => { const now = new Date(); + now.setDate(15); now.setMonth(now.getMonth() + 4); controller.step(1); diff --git a/print/templates/reports/claim-pickup-order/locale/es.yml b/print/templates/reports/claim-pickup-order/locale/es.yml index b37793b70..54990a05c 100644 --- a/print/templates/reports/claim-pickup-order/locale/es.yml +++ b/print/templates/reports/claim-pickup-order/locale/es.yml @@ -11,5 +11,5 @@ clientSignature: Firma del cliente claim: Reclamación {0} sections: agency: - description: 'Para agilizar tu recogida, por favor, pónte en contacto con la oficina + description: 'Para agilizar su recogida, por favor, póngase en contacto con la oficina de integrados.
Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) (agomezf@integra2.es)'