2350-order_index #334

Merged
joan merged 11 commits from 2350-order_index into dev 2020-07-16 12:02:15 +00:00
65 changed files with 1478 additions and 1574 deletions
Showing only changes of commit 122dacffb5 - Show all commits

View File

@ -35,5 +35,5 @@ ENTRYPOINT ["docker-start.sh"]
CMD ["mysqld"] CMD ["mysqld"]
#HEALTHCHECK --interval=5s --timeout=10s --retries=200 \ HEALTHCHECK --interval=5s --timeout=10s --retries=200 \
# CMD mysqladmin ping -h 127.0.0.1 -u root || exit 1 CMD mysqladmin ping -h 127.0.0.1 -u root || exit 1

View File

@ -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');

View File

@ -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 ;

View File

@ -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 ;

View File

@ -1,4 +1,5 @@
UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213'; 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'); INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('CustomsAgent', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -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
*/

View File

@ -39,8 +39,7 @@ module.exports = class Docker {
let runChown = process.platform != 'linux'; let runChown = process.platform != 'linux';
const healthCheck = `--health-cmd='mysqladmin ping --silent'`; const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
const container = await this.execP(`docker run ${healthCheck} --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
this.id = container.stdout; this.id = container.stdout;
try { try {
@ -54,7 +53,7 @@ module.exports = class Docker {
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort']; this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
} }
if (runChown) await this.wait(); await this.waitForHealthy();
} catch (err) { } catch (err) {
if (this.isRandom) if (this.isRandom)
await this.rm(); await this.rm();

View File

@ -1472,6 +1472,9 @@ INSERT INTO `vn`.`clientContact`(`id`, `clientFk`, `name`, `phone`)
(3, 101, 'contact 3', 222333444), (3, 101, 'contact 3', 222333444),
(4, 102, 'contact 1', 876543219); (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, 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 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))), ('1', '24.5', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR))),
('5', '23', 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 VALUES
(1, 'Holidays', '#FF4444', 'holiday'), (1, 'Holidays', '#FF4444', 'holiday', 0),
(2, 'Leave of absence', '#C71585', 'absence'), (2, 'Leave of absence', '#C71585', 'absence', 0),
(6, 'Half holiday', '#E65F00', 'halfHoliday'); (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`) INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`, `date`)
VALUES VALUES

View File

@ -558,7 +558,7 @@ let actions = {
}, selector); }, selector);
}, },
closePopup: async function(selector) { closePopup: async function() {
await Promise.all([ await Promise.all([
this.keyboard.press('Escape'), this.keyboard.press('Escape'),
this.waitFor('.vn-popup', {hidden: true}), this.waitFor('.vn-popup', {hidden: true}),

View File

@ -401,8 +401,9 @@ export default {
createButton: `button[type=submit]` createButton: `button[type=submit]`
}, },
ticketDescriptor: { 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"]', 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"]', goBackToModuleIndexButton: 'vn-ticket-descriptor a[ui-sref="ticket.index"]',
moreMenu: 'vn-ticket-descriptor vn-icon-button[icon=more_vert]', moreMenu: 'vn-ticket-descriptor vn-icon-button[icon=more_vert]',
moreMenuAddStowaway: '.vn-menu [name="addStowaway"]', moreMenuAddStowaway: '.vn-menu [name="addStowaway"]',
@ -445,20 +446,23 @@ export default {
savePackagesButton: `button[type=submit]` savePackagesButton: `button[type=submit]`
}, },
ticketSales: { ticketSales: {
setOk: 'vn-ticket-sale vn-tool-bar > vn-button[label="Ok"] > button',
saleButton: 'vn-left-menu a[ui-sref="ticket.card.sale"]', saleButton: 'vn-left-menu a[ui-sref="ticket.card.sale"]',
saleLine: 'vn-table div > vn-tbody > vn-tr', saleLine: 'vn-table div > vn-tbody > vn-tr',
saleDescriptorPopover: '.vn-popover.shown vn-item-descriptor', 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', descriptorItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a',
newItemFromCatalogButton: 'vn-ticket-sale vn-float-button[icon="add"]', newItemFromCatalogButton: 'vn-ticket-sale vn-float-button[icon="add"]',
newItemButton: 'vn-ticket-sale vn-card vn-icon-button[icon="add_circle"]', 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', moreMenu: 'vn-ticket-sale vn-button[label="More"]',
moreMenuCreateClaim: '.vn-drop-down.shown li[name="Add claim"]', moreMenuCreateClaim: 'vn-item[name="claim"]',
moreMenuReserve: '.vn-drop-down.shown li[name="Mark as reserved"]', moreMenuReserve: 'vn-item[name="reserve"]',
moreMenuUnmarkReseved: '.vn-drop-down.shown li[name="Unmark as reserved"]', moreMenuUnmarkReseved: 'vn-item[name="unreserve"]',
moreMenuUpdateDiscount: '.vn-drop-down.shown li[name="Update discount"]', 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', 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', 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"]', 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', firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
firstSaleText: 'vn-table div > vn-tbody > vn-tr:nth-child(1)', 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)', 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', 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', 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', 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)', 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)', 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', 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', 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', 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', 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)', 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', 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', selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check',
secondSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(2) vn-check[ng-model="sale.checked"]', 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"]', 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"]', deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]',
transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]', 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"]', moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]',
moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]', moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]',
acceptDeleteLineButton: '.vn-confirm.shown button[response=accept]', acceptDeleteLineButton: '.vn-confirm.shown button[response=accept]',
acceptDeleteTicketButton: '.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: { ticketTracking: {
trackingButton: 'vn-left-menu a[ui-sref="ticket.card.tracking.index"]', trackingButton: 'vn-left-menu a[ui-sref="ticket.card.tracking.index"]',

View File

@ -72,7 +72,8 @@ describe('Ticket List sale path', () => {
}, {}, selectors.ticketSales.secondSaleIdAutocomplete, searchValue); }, {}, selectors.ticketSales.secondSaleIdAutocomplete, searchValue);
await page.keyboard.press('Enter'); 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'); await page.keyboard.press('Enter');
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();

View File

@ -1,8 +1,7 @@
import selectors from '../../../helpers/selectors.js'; import selectors from '../../../helpers/selectors.js';
import getBrowser from '../../../helpers/puppeteer'; import getBrowser from '../../../helpers/puppeteer';
// #1632 [e2e] ticket.sale - Transferir líneas describe('Ticket Edit sale path', () => {
xdescribe('Ticket Edit sale path', () => {
let browser; let browser;
let page; let page;
@ -10,7 +9,7 @@ xdescribe('Ticket Edit sale path', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('salesPerson', 'ticket'); await page.loginAndModule('salesPerson', 'ticket');
await page.accessToSearchResult(16); await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale'); 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() => { it(`should click on the first sale claim icon to navigate over there`, async() => {
await page.waitToClick(selectors.ticketSales.firstSaleClaimIcon); await page.waitToClick(selectors.ticketSales.firstSaleClaimIcon);
await page.wait(selectors.claimBasicData.claimState); await page.waitForState('claim.card.basicData');
const url = await page.parsedUrl();
expect(url.hash).toEqual('#!/claim/2/basic-data');
}); });
it('should navigate to the tickets index', async() => { it('should navigate to the tickets index', async() => {
await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.wait(selectors.globalItems.applicationsMenuVisible); await page.wait(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.ticketsButton); await page.waitToClick(selectors.globalItems.ticketsButton);
await page.wait(selectors.ticketsIndex.topbarSearch); await page.waitForState('ticket.index');
const url = await page.parsedUrl();
expect(url.hash).toEqual('#!/ticket/index');
}); });
it(`should search for a ticket and then navigate to it's sales`, async() => { 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.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() => { 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() => { 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); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should click on the zoomed image to close it`, async() => { 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); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
expect(result).toEqual(0); expect(result).toEqual(0);
}); });
it(`should confirm the item descriptor insnt visible yet`, async() => { it(`should click on the first sale ID making now the item descriptor visible`, 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() => {
await page.waitToClick(selectors.ticketSales.firstSaleId); await page.waitToClick(selectors.ticketSales.firstSaleId);
await page.waitImgLoad(selectors.ticketSales.firstSaleDescriptorImage); await page.waitImgLoad(selectors.ticketSales.firstSaleDescriptorImage);
const visible = await page.isVisible(selectors.ticketSales.saleDescriptorPopover); 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() => { 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); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
it(`should now click on the zoomed image to close it`, async() => { 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); const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
expect(result).toEqual(0); 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() => { 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.waitToClick(selectors.ticketSales.saleDescriptorPopoverSummaryButton);
await page.waitForURL('/summary'); await page.waitForState('item.card.summary');
const url = await page.parsedUrl();
expect(url.hash).toContain('/summary');
}); });
it('should return to ticket sales section', async() => { it('should return to ticket sales section', async() => {
await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.wait(selectors.globalItems.applicationsMenuVisible); await page.wait(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.ticketsButton); await page.waitToClick(selectors.globalItems.ticketsButton);
await page.accessToSearchResult(16); await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale'); 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() => { it('should try to add a higher quantity value and then receive an error', async() => {
await page.focusElement(selectors.ticketSales.firstSaleQuantityCell); await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell);
await page.write(selectors.ticketSales.firstSaleQuantity, '11\u000d'); await page.type(selectors.ticketSales.firstSaleQuantity, '11\u000d');
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toBe('The new quantity should be smaller than the old one'); expect(message.text).toBe('The new quantity should be smaller than the old one');
}); });
it('should remove 1 from the first sale quantity', async() => { it('should remove 1 from the first sale quantity', async() => {
await page.focusElement(selectors.ticketSales.firstSaleQuantityCell); await page.waitFor(500);
await page.write(selectors.ticketSales.firstSaleQuantity, '9\u000d'); 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(); const message = await page.waitForSnackbar();
expect(message.type).toBe('success'); expect(message.type).toBe('success');
}); });
it('should update the price', async() => { it('should update the price', async() => {
await page.waitToClick(`${selectors.ticketSales.firstSalePrice} > span`); await page.waitToClick(selectors.ticketSales.firstSalePrice);
await page.write(selectors.ticketSales.firstSalePriceInput, '5\u000d'); await page.waitFor(selectors.ticketSales.firstSalePriceInput);
await page.type(selectors.ticketSales.firstSalePriceInput, '5\u000d');
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.type).toBe('success'); expect(message.type).toBe('success');
}); });
it('should confirm the price have been updated', async() => { 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'); expect(result).toContain('5.00');
}); });
@ -150,16 +160,17 @@ xdescribe('Ticket Edit sale path', () => {
}); });
it('should update the discount', async() => { it('should update the discount', async() => {
await page.waitToClick(`${selectors.ticketSales.firstSaleDiscount} > span`); await page.waitToClick(selectors.ticketSales.firstSaleDiscount);
await page.write(selectors.ticketSales.firstSaleDiscountInput, '50\u000d'); await page.waitFor(selectors.ticketSales.firstSaleDiscountInput);
await page.type(selectors.ticketSales.firstSaleDiscountInput, '50\u000d');
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.type).toBe('success'); expect(message.type).toBe('success');
}); });
it('should confirm the discount have been updated', async() => { it('should confirm the discount have been updated', async() => {
await page.waitForTextInElement(`${selectors.ticketSales.firstSaleDiscount} > span`, '50.00%'); await page.waitForTextInElement(selectors.ticketSales.firstSaleDiscount, '50.00%');
const result = await page.waitToGetProperty(`${selectors.ticketSales.firstSaleDiscount} > span`, 'innerText'); const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleDiscount, 'innerText');
expect(result).toContain('50.00%'); 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.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);
await page.wait(selectors.claimBasicData.claimState); await page.waitForState('claim.card.basicData');
const url = await page.parsedUrl();
expect(url.hash).toContain('basic-data');
}); });
it('should click on the Claims button of the top bar menu', async() => { it('should click on the Claims button of the top bar menu', async() => {
await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.wait(selectors.globalItems.applicationsMenuVisible); await page.wait(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.claimsButton); await page.waitToClick(selectors.globalItems.claimsButton);
await page.wait(selectors.claimsIndex.searchClaimInput); await page.waitForState('claim.index');
const url = await page.parsedUrl();
expect(url.hash).toEqual('#!/claim/index');
}); });
it('should search for the claim with id 4', async() => { it('should search for the claim with id 4', async() => {
await page.write(selectors.claimsIndex.searchClaimInput, 4); await page.accessToSearchResult('4');
await page.waitToClick(selectors.claimsIndex.searchButton); await page.waitForState('claim.card.summary');
await page.waitForNumberOfElements(selectors.claimsIndex.searchResult, 1);
const result = await page.countElement(selectors.claimsIndex.searchResult);
expect(result).toEqual(1);
}); });
it('should click the Tickets button of the top bar menu', async() => { it('should click the Tickets button of the top bar menu', async() => {
await page.waitToClick(selectors.globalItems.applicationsMenuButton); await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.wait(selectors.globalItems.applicationsMenuVisible); await page.wait(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.ticketsButton); await page.waitToClick(selectors.globalItems.ticketsButton);
await page.wait(selectors.ticketsIndex.topbarSearch); await page.waitForState('ticket.index');
const url = await page.parsedUrl();
expect(url.hash).toEqual('#!/ticket/index');
}); });
it('should search for a ticket then access to the sales section', async() => { 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.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() => { 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() => { 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.secondSaleCheckbox);
await page.waitToClick(selectors.ticketSales.transferSaleButton); await page.waitToClick(selectors.ticketSales.transferSaleButton);
await page.focusElement(selectors.ticketSales.transferQuantityCell); await page.waitToClick(selectors.ticketSales.transferQuantityCell);
await page.write(selectors.ticketSales.transferQuantityInput, '10\u000d'); await page.type(selectors.ticketSales.transferQuantityInput, '10\u000d');
await page.write(selectors.ticketSales.moveToTicketInput, targetTicketId); await page.type(selectors.ticketSales.moveToTicketInput, targetTicketId);
await page.waitToClick(selectors.ticketSales.moveToTicketButton); await page.waitToClick(selectors.ticketSales.moveToTicketButton);
await page.waitForURL(`ticket/${targetTicketId}/sale`); await page.expectURL(`ticket/${targetTicketId}/sale`);
const result = await page.parsedUrl();
expect(result.hash).toContain(`ticket/${targetTicketId}/sale`);
}); });
it('should confirm the transfered line is the correct one', async() => { 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() => { it('should go back to the original ticket sales section', async() => {
await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton); await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton);
await page.accessToSearchResult(16); await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale'); 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() => { 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() => { it('should go back to the receiver ticket sales section', async() => {
await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton); await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton);
await page.accessToSearchResult(12); await page.accessToSearchResult('12');
await page.accessToSection('ticket.card.sale'); 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() => { 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.secondSaleCheckbox);
await page.waitToClick(selectors.ticketSales.transferSaleButton); 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.waitToClick(selectors.ticketSales.moveToTicketButton);
await page.waitForURL(`ticket/${targetTicketId}/sale`); await page.expectURL(`ticket/${targetTicketId}/sale`);
const result = await page.parsedUrl();
expect(result.hash).toContain(`ticket/${targetTicketId}/sale`);
}); });
it('should confirm the original ticket received the line', async() => { 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.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.transferSaleButton); await page.waitToClick(selectors.ticketSales.transferSaleButton);
await page.waitToClick(selectors.ticketSales.moveToNewTicketButton); await page.waitToClick(selectors.ticketSales.moveToNewTicketButton);
await page.waitToClick(selectors.ticketSales.acceptDeleteTicketButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toBe(`You can't create a ticket for a inactive client`); 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() => { it('should go now to the ticket sales section of an active, not frozen client', async() => {
await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton); await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton);
await page.accessToSearchResult(13); await page.accessToSearchResult('13');
await page.accessToSection('ticket.card.sale'); 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() => { 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.selectAllSalesCheckbox);
await page.waitToClick(selectors.ticketSales.transferSaleButton); await page.waitToClick(selectors.ticketSales.transferSaleButton);
await page.waitToClick(selectors.ticketSales.moveToNewTicketButton); await page.waitToClick(selectors.ticketSales.moveToNewTicketButton);
await page.waitToClick(selectors.ticketSales.acceptDeleteTicketButton); await page.evaluate((selector, ticketId) => {
await page.wait((selector, ticketId) => { return document.querySelector(selector).innerText.toLowerCase().indexOf(`#${ticketId}`) == -1;
return document.querySelector(selector).innerText.toLowerCase().indexOf(`${ticketId}`) == -1; }, selectors.ticketDescriptor.id, senderTicketId);
}, selectors.ticketDescriptor.idLabelValue, senderTicketId); await page.waitForState('ticket.card.sale');
const url = await page.parsedUrl();
expect(url.hash).toContain('/sale');
}); });
it('should confirm the new ticket received the line', async() => { 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.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuReserve); await page.waitToClick(selectors.ticketSales.moreMenuReserve);
await page.closePopup();
await page.waitForClassNotPresent(selectors.ticketSales.firstSaleReservedIcon, 'ng-hide'); await page.waitForClassNotPresent(selectors.ticketSales.firstSaleReservedIcon, 'ng-hide');
const result = await page.isVisible(selectors.ticketSales.firstSaleReservedIcon); const result = await page.isVisible(selectors.ticketSales.firstSaleReservedIcon);
@ -384,10 +359,12 @@ xdescribe('Ticket Edit sale path', () => {
}); });
it('should update all sales discount', async() => { it('should update all sales discount', async() => {
await page.closePopup();
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuUpdateDiscount); 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.waitForSelector(selectors.ticketSales.moreMenuUpdateDiscountInput);
await page.write('body', '\u000d'); await page.type(selectors.ticketSales.moreMenuUpdateDiscountInput, '100');
await page.keyboard.press('Enter');
await page.waitForTextInElement(selectors.ticketSales.totalImport, '0.00'); await page.waitForTextInElement(selectors.ticketSales.totalImport, '0.00');
const result = await page.waitToGetProperty(selectors.ticketSales.totalImport, 'innerText'); 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() => { it('should log in as Production role and go to a target ticket summary', async() => {
await page.loginAndModule('production', 'ticket'); await page.loginAndModule('production', 'ticket');
await page.accessToSearchResult(13); await page.accessToSearchResult('13');
await page.waitForURL('/summary'); await page.waitForState('ticket.card.summary');
const url = await page.parsedUrl();
expect(url.hash).toContain('/summary');
}); });
it(`should check it's state is deleted`, async() => { it(`should check the ticket is deleted`, async() => {
const result = await page.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText'); await page.waitForSelector(selectors.ticketDescriptor.isDeletedIcon);
await page.waitForClassPresent(selectors.ticketDescriptor.isDeletedIcon, 'bright');
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();
});
}); });
}); });

View File

@ -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() => { 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.waitToClick(selectors.ticketTracking.createStateButton);
await page.waitForState('ticket.card.tracking.edit'); await page.waitForState('ticket.card.tracking.edit');
}); });

View File

@ -18,6 +18,7 @@ describe('Ticket purchase request path', () => {
}); });
it('should add a new request', async() => { it('should add a new request', async() => {
await page.waitFor('.vn-popup', {hidden: true}),
await page.waitToClick(selectors.ticketRequests.addRequestButton); await page.waitToClick(selectors.ticketRequests.addRequestButton);
await page.write(selectors.ticketRequests.descriptionInput, 'New stuff'); await page.write(selectors.ticketRequests.descriptionInput, 'New stuff');
await page.write(selectors.ticketRequests.quantity, '9'); await page.write(selectors.ticketRequests.quantity, '9');

View File

@ -58,7 +58,7 @@ describe('Ticket Summary path', () => {
expect(result).toContain('000002'); 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.waitToClick(selectors.ticketSummary.firstSaleItemId);
await page.waitImgLoad(selectors.ticketSummary.firstSaleDescriptorImage); await page.waitImgLoad(selectors.ticketSummary.firstSaleDescriptorImage);
const visible = await page.isVisible(selectors.ticketSummary.itemDescriptorPopover); const visible = await page.isVisible(selectors.ticketSummary.itemDescriptorPopover);

View File

@ -8,7 +8,7 @@ describe('Order lines', () => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('employee', 'order'); await page.loginAndModule('employee', 'order');
await page.accessToSearchResult('16'); await page.accessToSearchResult('8');
await page.accessToSection('order.card.line'); await page.accessToSection('order.card.line');
}); });
@ -20,7 +20,7 @@ describe('Order lines', () => {
const result = await page const result = await page
.waitToGetProperty(selectors.orderLine.orderSubtotal, 'innerText'); .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() => { 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() => { 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 const result = await page
.waitToGetProperty(selectors.orderLine.orderSubtotal, 'innerText'); .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() => { it('should confirm the whole order and redirect to ticket index filtered by clientFk', async() => {

View File

@ -7,7 +7,7 @@ describe('Component vnInputNumber', () => {
beforeEach(ngModule('vnCore')); beforeEach(ngModule('vnCore'));
beforeEach(angular.mock.inject(($compile, $rootScope) => { beforeEach(angular.mock.inject(($compile, $rootScope) => {
$element = $compile(`<vn-input-number></vn-input-number>`)($rootScope); $element = $compile(`<vn-input-number ng-model="model" />`)($rootScope);
$ctrl = $element.controller('vnInputNumber'); $ctrl = $element.controller('vnInputNumber');
})); }));
@ -20,12 +20,10 @@ describe('Component vnInputNumber', () => {
$ctrl.field = -1; $ctrl.field = -1;
$ctrl.min = 0; $ctrl.min = 0;
// FIXME: Input validation doesn't work with Jest? expect($ctrl.shownError).toContain('Constraints not satisfied');
// expect($ctrl.shownError).toContain('Please select a value that is no less than 0');
expect($ctrl.shownError).toBeNull();
}); });
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.field = 1;
$ctrl.min = 0; $ctrl.min = 0;
@ -34,19 +32,16 @@ describe('Component vnInputNumber', () => {
}); });
describe('max() setter', () => { 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.field = 1;
$ctrl.max = 0; $ctrl.max = 0;
// FIXME: Input validation doesn't work with Jest? expect($ctrl.shownError).toContain('Constraints not satisfied');
// expect($ctrl.shownError).toContain('Please select a value that is no more than 0');
expect($ctrl.shownError).toBeNull();
}); });
// FIXME: Input validation doesn't work with Jest?
it(`should unset error property when value is lower than max`, () => { it(`should unset error property when value is lower than max`, () => {
$ctrl.field = -1; $ctrl.field = -1;
$ctrl.min = 0; $ctrl.max = 0;
expect($ctrl.shownError).toBeNull(); expect($ctrl.shownError).toBeNull();
}); });
@ -54,14 +49,12 @@ describe('Component vnInputNumber', () => {
describe('step() setter', () => { describe('step() setter', () => {
it(`should increase value when add icon is clicked`, () => { it(`should increase value when add icon is clicked`, () => {
$ctrl.step = 1; $ctrl.input.step = 1;
$ctrl.field = 1; $ctrl.input.value = 0;
// FIXME: Doesn't work with Jest? $ctrl.stepUp();
// $ctrl.stepUp();
// $element[0].querySelector('vn-icon[icon=add]').click();
expect($ctrl.field).toBe(1); expect($ctrl.input.value).toBe('1');
}); });
}); });
}); });

View File

@ -68,6 +68,7 @@ export default class MultiCheck extends FormInput {
this.checkAll = value; this.checkAll = value;
this.toggle(); this.toggle();
this.emit('change', value);
} }
/** /**

View File

@ -30,7 +30,7 @@
<div class="body"> <div class="body">
<div class="top"> <div class="top">
<h5>{{$ctrl.description}}</h5> <h5>{{$ctrl.description}}</h5>
<div class="text-secondary"> <div>
{{$ctrl.descriptor.id | id}} {{$ctrl.descriptor.id | id}}
</div> </div>
</div> </div>

View File

@ -94,7 +94,8 @@ ngModule.vnComponent('vnDescriptor', {
transclude: { transclude: {
btnOne: '?btnOne', btnOne: '?btnOne',
btnTwo: '?btnTwo', btnTwo: '?btnTwo',
btnThree: '?btnThree' btnThree: '?btnThree',
btnFour: '?btnFour'
} }
}); });

View File

@ -88,7 +88,7 @@ vn-descriptor-content {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 $spacing-md; padding: 0 $spacing-sm;
margin: 0 $spacing-sm; margin: 0 $spacing-sm;
& > vn-icon { & > vn-icon {

View File

@ -83,8 +83,6 @@ async function backTestOnce(done) {
port: container.dbConf.port port: container.dbConf.port
}); });
log('[Debug] dataSources', dataSources.vn);
let bootOptions = {dataSources}; let bootOptions = {dataSources};
let app = require(`./loopback/server/server`); let app = require(`./loopback/server/server`);
@ -92,8 +90,6 @@ async function backTestOnce(done) {
try { try {
app.boot(bootOptions); app.boot(bootOptions);
log('[Debug] back started');
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const jasmine = require('gulp-jasmine'); const jasmine = require('gulp-jasmine');

View File

@ -67,5 +67,6 @@
"Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", "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", "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}}", "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"
} }

View File

@ -4,17 +4,15 @@ module.exports = Self => {
Self.remoteMethodCtx('createFromSales', { Self.remoteMethodCtx('createFromSales', {
description: 'Create a claim', description: 'Create a claim',
accepts: [{ accepts: [{
arg: 'claim', arg: 'ticketId',
type: 'object', type: 'Number',
required: true, required: true,
description: ' newTicketFk, clientFk, ticketCreated', description: 'The origin ticket id'
http: {source: 'body'}
}, { }, {
arg: 'sales', arg: 'sales',
type: 'object', type: ['Object'],
required: true, required: true,
description: '[sales IDs]', description: 'The claimed sales'
http: {source: 'body'}
}], }],
returns: { returns: {
type: 'object', type: 'object',
@ -26,28 +24,27 @@ module.exports = Self => {
} }
}); });
Self.createFromSales = async(ctx, params) => { Self.createFromSales = async(ctx, ticketId, sales) => {
let models = Self.app.models; const models = Self.app.models;
let userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
let tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
try { try {
let options = {transaction: tx}; let options = {transaction: tx};
const ticketId = params.claim.ticketFk;
const ticket = await models.Ticket.findById(ticketId, null, options); const ticket = await models.Ticket.findById(ticketId, null, options);
if (ticket.isDeleted) if (ticket.isDeleted)
throw new UserError(`You can't create a claim for a removed ticket`); throw new UserError(`You can't create a claim for a removed ticket`);
const worker = await models.Worker.findOne({ const newClaim = await Self.create({
where: {userFk: userId} ticketFk: ticketId,
clientFk: ticket.clientFk,
ticketCreated: ticket.shipped,
workerFk: userId
}, options); }, options);
const promises = [];
params.claim.workerFk = worker.id; for (const sale of sales) {
let newClaim = await Self.create(params.claim, options);
let promises = [];
for (const sale of params.sales) {
const newClaimBeginning = models.ClaimBeginning.create({ const newClaimBeginning = models.ClaimBeginning.create({
saleFk: sale.id, saleFk: sale.id,
claimFk: newClaim.id, claimFk: newClaim.id,

View File

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Claim Create', () => { describe('Claim createFromSales()', () => {
let newDate = new Date();
let createdClaimFk; let createdClaimFk;
afterAll(async done => { afterAll(async done => {
@ -10,28 +9,18 @@ describe('Claim Create', () => {
done(); done();
}); });
let newClaim = { const ticketId = 2;
ticketFk: 2, const newSale = [{
clientFk: 101,
ticketCreated: newDate
};
let newSale = [{
id: 3, id: 3,
instance: 0, instance: 0,
quantity: 10 quantity: 10
}]; }];
const ctx = {req: {accessToken: {userId: 1}}};
let params = {claim: newClaim, sales: newSale};
let ctx = {req: {accessToken: {userId: 1}}};
it('should create a new claim', async() => { 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.ticketFk).toEqual(ticketId);
expect(claim.clientFk).toEqual(newClaim.clientFk);
expect(claim.ticketCreated).toEqual(newClaim.ticketCreated);
expect(claim.workerFk).toEqual(newClaim.workerFk);
let claimBeginning = await app.models.ClaimBeginning.findOne({where: {claimFk: claim.id}}); 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() => { it('should not be able to create a claim if exists that sale', async() => {
let error; let error;
await app.models.Claim.createFromSales(ctx, params) await app.models.Claim.createFromSales(ctx, ticketId, newSale)
.catch(e => { .catch(e => {
error = e; error = e;

View File

@ -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;
};
};

View File

@ -44,7 +44,7 @@ module.exports = Self => {
return role.name === 'employee'; return role.name === 'employee';
}); });
if (!roleNames.length || isEmployee > -1 ) return false; if (!roleNames.length || isEmployee > -1) return false;
return true; return true;
}; };

View File

@ -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);
});
});

View File

@ -11,6 +11,7 @@ module.exports = Self => {
require('../methods/client/createWithUser')(Self); require('../methods/client/createWithUser')(Self);
require('../methods/client/listWorkers')(Self); require('../methods/client/listWorkers')(Self);
require('../methods/client/hasCustomerRole')(Self); require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/canCreateTicket')(Self);
require('../methods/client/isValidClient')(Self); require('../methods/client/isValidClient')(Self);
require('../methods/client/addressesPropagateRe')(Self); require('../methods/client/addressesPropagateRe')(Self);
require('../methods/client/getDebt')(Self); require('../methods/client/getDebt')(Self);

View File

@ -29,7 +29,7 @@ module.exports = Self => {
order: 'priority', order: 'priority',
include: [ include: [
{ {
relation: 'state', relation: 'ticketState',
scope: { scope: {
fields: ['id', 'stateFk'], fields: ['id', 'stateFk'],
include: [{relation: 'state'}] include: [{relation: 'state'}]

View File

@ -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);
};
};

View File

@ -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);
};
};

View File

@ -6,28 +6,43 @@ module.exports = Self => {
description: 'Change the state of a ticket', description: 'Change the state of a ticket',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'params', arg: 'ticketId',
type: 'object', type: 'Number',
required: true, required: true,
description: '[sales IDs], ticketFk, reserved', description: 'The ticket id'
http: {source: 'body'} }, {
arg: 'sales',
type: ['Object'],
required: true,
description: 'The sale to reserve'
},
{
arg: 'reserved',
type: 'Boolean',
required: true
}], }],
returns: { returns: {
type: 'string', type: ['Object'],
root: true root: true
}, },
http: { http: {
path: `/reserve`, path: `/reserve`,
verb: 'post' verb: 'POST'
} }
}); });
Self.reserve = async(ctx, params) => { Self.reserve = async(ctx, ticketId, sales, reserved) => {
let thisTicketIsEditable = await Self.app.models.Ticket.isEditable(ctx, params.ticketFk); const models = Self.app.models;
if (!thisTicketIsEditable) const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
if (!isTicketEditable)
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
for (let i = 0; i < params.sales.length; i++) const promises = [];
await Self.app.models.Sale.update({id: params.sales[i].id}, {reserved: params.reserved}); for (let sale of sales) {
const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved});
promises.push(reservedSale);
}
return Promise.all(promises);
}; };
}; };

View File

@ -1,6 +1,6 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('sale removes()', () => { describe('sale deleteSales()', () => {
let sale; let sale;
let newsale; let newsale;
@ -16,13 +16,11 @@ describe('sale removes()', () => {
let ctx = {req: {accessToken: {userId: 9}}}; let ctx = {req: {accessToken: {userId: 9}}};
let error; let error;
let params = { const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
sales: [{id: 1, instance: 0}, {id: 2, instance: 1}], const ticketId = 2;
actualTicketFk: 2
};
try { try {
await app.models.Sale.removes(ctx, params); await app.models.Sale.deleteSales(ctx, sales, ticketId);
} catch (e) { } catch (e) {
error = e; error = e;
} }
@ -32,12 +30,11 @@ describe('sale removes()', () => {
it('should delete the sales', async() => { it('should delete the sales', async() => {
let ctx = {req: {accessToken: {userId: 9}}}; 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}]); expect(res).toEqual([{count: 1}]);
}); });

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('sale reserve()', () => { describe('sale reserve()', () => {
const ctx = {req: {accessToken: {userId: 9}}};
afterAll(async done => { afterAll(async done => {
let ctx = {req: {accessToken: {userId: 9}}}; let ctx = {req: {accessToken: {userId: 9}}};
let params = { let params = {
@ -17,13 +18,12 @@ describe('sale reserve()', () => {
}); });
it('should throw an error if the ticket can not be modified', async() => { it('should throw an error if the ticket can not be modified', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let error; let error;
let params = {ticketFk: 2, const ticketId = 2;
sales: [{id: 5}], const sales = [{id: 5}];
reserved: false}; const reserved = false;
await app.models.Sale.reserve(ctx, params) await app.models.Sale.reserve(ctx, ticketId, sales, reserved)
.catch(response => { .catch(response => {
expect(response).toEqual(new Error(`The sales of this ticket can't be modified`)); expect(response).toEqual(new Error(`The sales of this ticket can't be modified`));
error = response; error = response;
@ -38,18 +38,13 @@ describe('sale reserve()', () => {
expect(originalTicketSales[0].reserved).toEqual(0); expect(originalTicketSales[0].reserved).toEqual(0);
expect(originalTicketSales[1].reserved).toEqual(0); expect(originalTicketSales[1].reserved).toEqual(0);
let ctx = {req: {accessToken: {userId: 9}}}; const ticketId = 11;
let params = { const sales = [{id: 7}, {id: 8}];
sales: [ const reserved = true;
{id: 7},
{id: 8}],
ticketFk: 11,
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[0].reserved).toEqual(1);
expect(reservedTicketSales[1].reserved).toEqual(1); expect(reservedTicketSales[1].reserved).toEqual(1);

View File

@ -56,7 +56,9 @@ module.exports = Self => {
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
let usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options); let usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options);
let componentCode = usesMana ? 'mana' : 'buyerDiscount'; let componentCode = usesMana ? 'mana' : 'buyerDiscount';
let discount = await models.Component.findOne({where: {code: componentCode}}, options); let discount = await models.Component.findOne({where: {code: componentCode}}, options);
@ -80,7 +82,6 @@ module.exports = Self => {
value: componentValue value: componentValue
}, options); }, options);
} }
await sale.updateAttributes({price: newPrice}, options); await sale.updateAttributes({price: newPrice}, options);
query = `CALL vn.manaSpellersRequery(?)`; query = `CALL vn.manaSpellersRequery(?)`;

View File

@ -38,7 +38,6 @@ module.exports = Self => {
let highestDate = new Date(ship.shipped.getTime()); let highestDate = new Date(ship.shipped.getTime());
highestDate.setHours(23, 59, 59); highestDate.setHours(23, 59, 59);
let possibleStowaways = await models.Ticket.find({ let possibleStowaways = await models.Ticket.find({
where: { where: {
id: {neq: ticketFk}, id: {neq: ticketFk},
@ -53,7 +52,7 @@ module.exports = Self => {
include: [ include: [
{relation: 'agencyMode'}, {relation: 'agencyMode'},
{relation: 'warehouse'}, {relation: 'warehouse'},
{relation: 'state', {relation: 'ticketState',
scope: { scope: {
fields: ['stateFk'], fields: ['stateFk'],
include: { include: {

View File

@ -1,12 +1,12 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('getSalesPersonMana', { 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', accessType: 'READ',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'number', type: 'number',
required: true, required: true,
description: 'ticket id', description: 'The ticket id',
http: {source: 'path'} http: {source: 'path'}
}], }],
returns: { returns: {
@ -18,8 +18,9 @@ module.exports = Self => {
} }
}); });
Self.getSalesPersonMana = async ticketFk => { Self.getSalesPersonMana = async ticketId => {
let ticket = await Self.app.models.Ticket.findById(ticketFk, { const models = Self.app.models;
const ticket = await models.Ticket.findById(ticketId, {
include: [{ include: [{
relation: 'client', relation: 'client',
scope: { scope: {
@ -29,10 +30,12 @@ module.exports = Self => {
fields: ['id', 'clientFk'] fields: ['id', 'clientFk']
}); });
if (!ticket) if (!ticket) return 0;
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; return mana ? mana.amount : 0;
}; };

View File

@ -82,7 +82,8 @@ module.exports = Self => {
agencyMode = await models.AgencyMode.findById(agencyModeId); agencyMode = await models.AgencyMode.findById(agencyModeId);
if (address.client().type().code === 'normal' && (!agencyMode || agencyMode.code != 'refund')) { 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`); throw new UserError(`You can't create a ticket for a inactive client`);
} }

View File

@ -8,7 +8,7 @@ describe('sale updateDiscount()', () => {
beforeAll(async done => { beforeAll(async done => {
originalSale = await app.models.Sale.findById(originalSaleId); 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; componentId = manaDiscount.id;
let ticket = await app.models.Ticket.findById(originalSale.ticketFk); let ticket = await app.models.Ticket.findById(originalSale.ticketFk);
@ -26,7 +26,6 @@ describe('sale updateDiscount()', () => {
done(); done();
}); });
it('should throw an error if no sales were selected', async() => { it('should throw an error if no sales were selected', async() => {
let ctx = {req: {accessToken: {userId: 9}}}; let ctx = {req: {accessToken: {userId: 9}}};
let error; let error;
@ -76,6 +75,28 @@ describe('sale updateDiscount()', () => {
}); });
it('should update the discount if the salesPerson has mana', async() => { 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}}}; let ctx = {req: {accessToken: {userId: 9}}};
const ticketId = 11; const ticketId = 11;
const sales = [originalSaleId]; const sales = [originalSaleId];

View File

@ -90,7 +90,7 @@ module.exports = Self => {
} }
} }
}, { }, {
relation: 'state', relation: 'ticketState',
scope: { scope: {
fields: ['stateFk'], fields: ['stateFk'],
include: { include: {

View File

@ -55,8 +55,14 @@ module.exports = Self => {
where: {ticketFk: id} where: {ticketFk: id}
}, options); }, 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); ticketId = await cloneTicket(originalTicket, options);
}
const map = new Map(); const map = new Map();
for (const sale of originalSales) for (const sale of originalSales)

View File

@ -35,7 +35,6 @@ module.exports = Self => {
}); });
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => { Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
@ -69,6 +68,7 @@ module.exports = Self => {
if (!allFromSameTicket) if (!allFromSameTicket)
throw new UserError('All sales must belong to the same ticket'); 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 isLocked = await models.Ticket.isLocked(id);
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson'); const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson');
const state = await Self.app.models.TicketState.findOne({ const state = await Self.app.models.TicketState.findOne({
@ -79,18 +79,9 @@ module.exports = Self => {
if (isLocked || (!isSalesPerson && alertLevel > 0)) if (isLocked || (!isSalesPerson && alertLevel > 0))
throw new UserError(`The sales of this ticket can't be modified`); 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({ const usesMana = await models.WorkerMana.findOne({
where: { where: {
workerFk: salesPersonId workerFk: userId
}, },
fields: 'amount'}, options); fields: 'amount'}, options);
@ -99,7 +90,6 @@ module.exports = Self => {
where: {code: componentCode}}, options); where: {code: componentCode}}, options);
const componentId = discountComponent.id; const componentId = discountComponent.id;
let promises = []; let promises = [];
for (let sale of sales) { for (let sale of sales) {
const value = ((-sale.price * newDiscount) / 100); const value = ((-sale.price * newDiscount) / 100);
@ -115,10 +105,8 @@ module.exports = Self => {
await Promise.all(promises); await Promise.all(promises);
if (salesPersonId) { const query = `call vn.manaSpellersRequery(?)`;
const query = `call vn.manaSpellersRequery(?)`; await Self.rawSql(query, [userId], options);
await Self.rawSql(query, [salesPersonId], options);
}
await tx.commit(); await tx.commit();
} catch (error) { } catch (error) {

View File

@ -1,7 +1,7 @@
module.exports = Self => { module.exports = Self => {
require('../methods/sale/getClaimableFromTicket')(Self); require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/reserve')(Self); require('../methods/sale/reserve')(Self);
require('../methods/sale/removes')(Self); require('../methods/sale/deleteSales')(Self);
require('../methods/sale/updatePrice')(Self); require('../methods/sale/updatePrice')(Self);
require('../methods/sale/updateQuantity')(Self); require('../methods/sale/updateQuantity')(Self);
require('../methods/sale/updateConcept')(Self); require('../methods/sale/updateConcept')(Self);

View File

@ -110,7 +110,7 @@
"model": "TicketObservation", "model": "TicketObservation",
"foreignKey": "ticketFk" "foreignKey": "ticketFk"
}, },
"state": { "ticketState": {
"type": "hasOne", "type": "hasOne",
"model": "TicketState", "model": "TicketState",
"foreignKey": "ticketFk" "foreignKey": "ticketFk"

View File

@ -45,7 +45,7 @@ class Controller extends ModuleCard {
}, },
}, },
}, { }, {
relation: 'state', relation: 'ticketState',
scope: { scope: {
fields: ['stateFk'], fields: ['stateFk'],
include: { include: {

View File

@ -26,7 +26,7 @@
<vn-td>{{ticket.landed | date: 'dd/MM/yyyy'}}</vn-td> <vn-td>{{ticket.landed | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td>{{ticket.agencyMode.name}}</vn-td> <vn-td>{{ticket.agencyMode.name}}</vn-td>
<vn-td>{{ticket.warehouse.name}}</vn-td> <vn-td>{{ticket.warehouse.name}}</vn-td>
<vn-td>{{ticket.state.state.name}}</vn-td> <vn-td>{{ticket.ticketState.state.name}}</vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>

View File

@ -87,7 +87,7 @@
<div class="attributes"> <div class="attributes">
<vn-label-value <vn-label-value
label="State" label="State"
value="{{$ctrl.ticket.state.state.name}}"> value="{{$ctrl.ticket.ticketState.state.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value <vn-label-value
label="Sales person" label="Sales person"
@ -146,6 +146,20 @@
</vn-quick-link> </vn-quick-link>
</div> </div>
<div ng-transclude="btnTwo"> <div ng-transclude="btnTwo">
<vn-quick-link
tooltip="Client ticket list"
state="['ticket.index', {q: $ctrl.filter}]"
icon="icon-ticket">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
<vn-quick-link
tooltip="New order"
state="['order.create', {clientFk: $ctrl.ticket.clientFk}]"
icon="icon-basketadd">
</vn-quick-link>
</div>
<div ng-transclude="btnFour">
<vn-quick-link <vn-quick-link
ng-if="$ctrl.ticket.stowaway" ng-if="$ctrl.ticket.stowaway"
tooltip="Ship stowaways" tooltip="Ship stowaways"
@ -153,7 +167,7 @@
icon="icon-stowaway"> icon="icon-stowaway">
</vn-quick-link> </vn-quick-link>
</div> </div>
<div ng-transclude="btnThree"> <div ng-transclude="btnFour">
<vn-quick-link <vn-quick-link
ng-if="$ctrl.ticket.ship" ng-if="$ctrl.ticket.ship"
tooltip="Stowaway" tooltip="Stowaway"

View File

@ -38,6 +38,12 @@ class Controller extends Descriptor {
return this.ticket.stowaway || this.ticket.ship; return this.ticket.stowaway || this.ticket.ship;
} }
get filter() {
if (this.ticket)
return JSON.stringify({clientFk: this.ticket.clientFk});
return null;
}
isTicketEditable() { isTicketEditable() {
this.$http.get(`Tickets/${this.$state.params.id}/isEditable`).then(res => { this.$http.get(`Tickets/${this.$state.params.id}/isEditable`).then(res => {
this.isEditable = res.data; this.isEditable = res.data;
@ -210,7 +216,7 @@ class Controller extends Descriptor {
} }
} }
}, { }, {
relation: 'state', relation: 'ticketState',
scope: { scope: {
fields: ['stateFk'], fields: ['stateFk'],
include: { include: {

View File

@ -18,7 +18,6 @@ import './expedition';
import './volume'; import './volume';
import './package/index'; import './package/index';
import './sale'; import './sale';
import './sale/editDiscount';
import './tracking/index'; import './tracking/index';
import './tracking/edit'; import './tracking/edit';
import './sale-checked'; import './sale-checked';

View File

@ -1,16 +0,0 @@
<section class="header vn-pa-md">
<h5>MANÁ: {{$ctrl.mana | currency: 'EUR':0}}</h5>
</section>
<div class="vn-pa-md">
<vn-input-number
vn-focus
label="Discount"
ng-model="$ctrl.newDiscount"
on-change="$ctrl.updateDiscount()"
suffix="%">
</vn-input-number>
<div class="simulator">
<p class="simulatorTitle" translate>New price</p>
<p>{{$ctrl.newPrice | currency: 'EUR':2}}</p>
</div>
</div>

View File

@ -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: '<?',
mana: '<?',
bulk: '<?',
onHide: '&'
}
});

View File

@ -12,53 +12,48 @@
<vn-horizontal class="header"> <vn-horizontal class="header">
<vn-tool-bar class="vn-mb-md"> <vn-tool-bar class="vn-mb-md">
<vn-button <vn-button
disabled="!$ctrl.isEditable" disabled="!$ctrl.isEditable || $ctrl.ticketState == 'OK'"
label="Ok" label="Ok"
vn-http-click="$ctrl.onStateOkClick()" vn-http-click="$ctrl.changeState('OK')"
vn-tooltip="Change ticket state to 'Ok'"> vn-tooltip="Change ticket state to 'Ok'">
</vn-button> </vn-button>
<vn-button-menu <vn-button-menu
disabled="!$ctrl.isEditable" disabled="!$ctrl.isEditable"
label="State" label="State"
value-field="id" value-field="code"
url="States/editableStates" url="States/editableStates"
on-change="$ctrl.onStateChange(value)"> on-change="$ctrl.changeState(value)">
</vn-button-menu> </vn-button-menu>
<vn-button-menu <vn-button icon="keyboard_arrow_down"
ng-show="$ctrl.isChecked"
vn-id="more-button"
label="More" label="More"
show-filter="false" ng-click="moreOptions.show($event)"
value-field="callback" ng-show="$ctrl.hasSelectedSales()">
translate-fields="['name']" </vn-button>
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-button-menu>
<vn-button <vn-button
disabled="!$ctrl.isChecked || !$ctrl.isEditable" disabled="!$ctrl.hasSelectedSales() || !$ctrl.isEditable"
ng-click="deleteLines.show()" ng-click="deleteLines.show()"
vn-tooltip="Remove lines" vn-tooltip="Remove lines"
icon="delete"> icon="delete">
</vn-button> </vn-button>
<vn-button <vn-button
disabled="!$ctrl.isChecked || !$ctrl.isEditable" disabled="!$ctrl.hasSelectedSales() || !$ctrl.isEditable"
ng-click="$ctrl.showTransferPopover($event)" ng-click="$ctrl.showTransferPopover($event)"
vn-tooltip="Transfer lines" vn-tooltip="Transfer lines"
icon="call_split"> icon="call_split">
</vn-button> </vn-button>
</vn-tool-bar> </vn-tool-bar>
<vn-one class="taxes" ng-if="$ctrl.sales.length > 0"> <vn-one class="taxes" ng-if="$ctrl.sales.length > 0">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.subtotal | currency: 'EUR':2}}</p> <p><vn-label translate>Subtotal</vn-label> {{$ctrl.subtotal | currency: 'EUR': 2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.VAT | currency: 'EUR':2}}</p> <p><vn-label translate>VAT</vn-label> {{$ctrl.VAT | currency: 'EUR': 2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.total | currency: 'EUR':2}}</strong></p> <p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.total | currency: 'EUR': 2}}</strong></p>
</vn-one> </vn-one>
</vn-horizontal> </vn-horizontal>
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th shrink> <vn-th shrink>
<vn-multi-check <vn-multi-check model="model"
model="model"> on-change="$ctrl.resetChanges()">
</vn-multi-check> </vn-multi-check>
</vn-th> </vn-th>
<vn-th shrink></vn-th> <vn-th shrink></vn-th>
@ -74,20 +69,19 @@
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.sales"> <vn-tr ng-repeat="sale in $ctrl.sales">
<vn-td shrink> <vn-td shrink>
<vn-check <vn-check tabindex="-1"
ng-model="sale.checked"> ng-model="sale.checked">
</vn-check> </vn-check>
</vn-td> </vn-td>
<vn-td shrink> <vn-td shrink>
<a ui-sref="claim.card.basicData({id: sale.claim.claimFk})"> <a ui-sref="claim.card.basicData({id: sale.claim.claimFk})">
<vn-icon <vn-icon icon="icon-claims"
ng-show="sale.claim.claimFk" ng-show="sale.claim.claimFk"
icon="icon-claims"
vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}"> vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}">
</vn-icon> </vn-icon>
</a> </a>
<vn-icon <vn-icon
ng-show="sale.visible < 0 || sale.available < 0" ng-show="::(sale.visible < 0 || sale.available < 0)"
color-main color-main
icon="warning" icon="warning"
vn-tooltip="Visible: {{::sale.visible || 0}} <br> {{::$ctrl.$t('Available')}}: {{::sale.available || 0}}"> vn-tooltip="Visible: {{::sale.visible || 0}} <br> {{::$ctrl.$t('Available')}}: {{::sale.available || 0}}">
@ -103,13 +97,12 @@
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{sale.image}}" zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{sale.image}}"
on-error-src/> on-error-src/>
</vn-td> </vn-td>
<vn-td vn-focus number> <vn-td number>
<span class="link" ng-if="sale.id" <span class="link" ng-if="sale.id"
ng-click="descriptor.show($event, sale.itemFk, sale.id)"> ng-click="descriptor.show($event, sale.itemFk, sale.id)">
{{sale.itemFk}} {{sale.itemFk}}
</span> </span>
<vn-autocomplete <vn-autocomplete ng-if="!sale.id"
ng-if="!sale.id"
vn-focus vn-focus
vn-one vn-one
url="Items" url="Items"
@ -117,42 +110,35 @@
show-field="name" show-field="name"
value-field="id" value-field="id"
search-function="$ctrl.itemSearchFunc($search)" search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.onChangeQuantity(sale)" on-change="$ctrl.changeQuantity(sale)"
order="id DESC" order="id DESC"
tabindex="1"> tabindex="1">
<tpl-item> <tpl-item>
{{id}} - {{name}} {{::id}} - {{::name}}
</tpl-item> </tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-td> </vn-td>
<vn-td-editable ng-if="sale.id" disabled="!$ctrl.isEditable" number> <vn-td-editable disabled="!$ctrl.isEditable" number>
<text>{{sale.quantity}}</text> <text>{{sale.quantity}}</text>
<field> <field>
<vn-input-number class="dense" <vn-input-number class="dense"
vn-focus vn-focus
ng-model="sale.quantity" ng-model="sale.quantity"
on-change="$ctrl.onChangeQuantity(sale)"> on-change="$ctrl.changeQuantity(sale)">
</vn-input-number> </vn-input-number>
</field> </field>
</vn-td-editable> </vn-td-editable>
<vn-td ng-if="!sale.id" number>
<vn-input-number
ng-model="sale.quantity"
on-change="$ctrl.onChangeQuantity(sale)"
tabindex="2">
</vn-input-number>
</vn-td>
<vn-td-editable disabled="!sale.id || !$ctrl.isEditable" expand> <vn-td-editable disabled="!sale.id || !$ctrl.isEditable" expand>
<text> <text>
<vn-fetched-tags <vn-fetched-tags
max-length="6" max-length="6"
item="sale.item" item="::sale.item"
name="sale.concept" name="sale.concept"
sub-name="sale.subName"> sub-name="::sale.subName">
</vn-fetched-tags> </vn-fetched-tags>
</text> </text>
<field> <field>
<vn-textfield class="dense" <vn-textfield class="dense" vn-focus
vn-id="concept" vn-id="concept"
ng-model="sale.concept" ng-model="sale.concept"
on-change="$ctrl.updateConcept(sale)"> on-change="$ctrl.updateConcept(sale)">
@ -161,14 +147,14 @@
</vn-td-editable> </vn-td-editable>
<vn-td number> <vn-td number>
<span ng-class="{'link': $ctrl.isEditable}" <span ng-class="{'link': $ctrl.isEditable}"
title="{{$ctrl.isEditable ? 'Edit price' : ''}}" translate-attr="{title: $ctrl.isEditable ? 'Edit price' : ''}"
ng-click="$ctrl.showEditPricePopover($event, sale)"> ng-click="$ctrl.showEditPricePopover($event, sale)">
{{sale.price | currency: 'EUR':2}} {{sale.price | currency: 'EUR':2}}
</span> </span>
</vn-td> </vn-td>
<vn-td number> <vn-td number>
<span ng-class="{'link': !$ctrl.isLocked}" <span ng-class="{'link': !$ctrl.isLocked}"
title="{{!$ctrl.isLocked ? 'Edit discount' : ''}}" translate-attr="{title: !$ctrl.isLocked ? 'Edit discount' : ''}"
ng-click="$ctrl.showEditDiscountPopover($event, sale)" ng-click="$ctrl.showEditDiscountPopover($event, sale)"
ng-if="sale.id"> ng-if="sale.id">
{{(sale.discount / 100) | percentage}} {{(sale.discount / 100) | percentage}}
@ -196,7 +182,7 @@
ng-show="$ctrl.isEditable" ng-show="$ctrl.isEditable"
ng-click="$ctrl.newOrderFromTicket()" ng-click="$ctrl.newOrderFromTicket()"
icon="add" icon="add"
vn-tooltip="Add item" vn-tooltip="Add item to basket"
vn-bind="+" vn-bind="+"
fixed-bottom-right> fixed-bottom-right>
</vn-float-button> </vn-float-button>
@ -206,60 +192,96 @@
ticket-fk="$ctrl.ticket.id"> ticket-fk="$ctrl.ticket.id">
</vn-item-descriptor-popover> </vn-item-descriptor-popover>
<!-- Edit Price Popover --> <!-- Price Popover -->
<vn-popover <vn-popover
vn-id="edit-price-popover" vn-id="edit-price-popover"
on-open="$ctrl.getManaSalespersonMana()" on-open="$ctrl.getMana()">
on-close="$ctrl.mana = null"> <div class="edit-popover">
<div class="edit-price"> <vn-spinner class="vn-pa-xs"
<vn-spinner ng-if="$ctrl.edit.mana == null"
ng-if="$ctrl.mana == null"
style="padding: 1em;"
enable="true"> enable="true">
</vn-spinner> </vn-spinner>
<div ng-if="$ctrl.mana != null"> <div ng-if="$ctrl.edit.mana != null">
<section class="header vn-pa-md"> <section class="header vn-pa-md">
<h5>MANÁ: {{$ctrl.mana | currency: 'EUR':0}}</h5> <h5>MANÁ: {{::$ctrl.edit.mana | currency: 'EUR': 0}}</h5>
</section> </section>
<div class="vn-pa-md"> <div class="vn-pa-md">
<vn-input-number <vn-input-number
vn-focus vn-focus
label="Price" label="Price"
ng-model="$ctrl.newPrice" ng-model="$ctrl.edit.price"
step="0.01" step="0.01"
on-change="$ctrl.updatePrice()" on-change="$ctrl.updatePrice()"
suffix="€"> suffix="€">
</vn-input-number> </vn-input-number>
<div class="simulator"> <div class="simulator">
<p class="simulatorTitle" translate>New price</p> <p class="simulatorTitle" translate>New price</p>
<p>{{$ctrl.newPrice | currency: 'EUR':2}}</p> <p>
<strong>{{$ctrl.getNewPrice() | currency: 'EUR': 2}}</strong>
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</vn-popover> </vn-popover>
<!-- Edit Popover --> <!-- Discount popover -->
<vn-popover <vn-popover
vn-id="edit-popover" vn-id="editDiscount"
on-open="$ctrl.getManaSalespersonMana()" on-open="$ctrl.getMana()">
on-close="$ctrl.mana = null"> <div class="edit-popover">
<div class="edit-price"> <vn-spinner class="vn-pa-xs"
<vn-spinner ng-if="$ctrl.edit.mana == null"
ng-if="$ctrl.mana == null"
style="padding: 1em;"
enable="true"> enable="true">
</vn-spinner> </vn-spinner>
<vn-ticket-sale-edit-discount <div ng-if="$ctrl.edit.mana != null">
ng-if="$ctrl.mana != null" <section class="header vn-pa-md">
mana="$ctrl.mana" <h5>Mana: {{::$ctrl.edit.mana | currency: 'EUR':0}}</h5>
bulk="false" </section>
edit="$ctrl.edit" <div class="vn-pa-md">
on-hide="$ctrl.hideEditPopover()"> <vn-input-number
</vn-ticket-sale-edit-discount> vn-focus
label="Discount"
ng-model="$ctrl.edit.discount"
on-change="$ctrl.changeDiscount()"
suffix="%">
</vn-input-number>
<div class="simulator">
<p class="simulatorTitle" translate>New price</p>
<p>
<strong>{{$ctrl.getNewPrice() | currency: 'EUR': 2}}</strong>
</p>
</div>
</div>
</div>
</div> </div>
</vn-popover> </vn-popover>
<!-- Multiple discount dialog -->
<vn-dialog vn-id="editDiscountDialog"
on-open="$ctrl.getMana()"
message="Edit discount">
<tpl-body>
<vn-spinner class="vn-pa-xs"
ng-if="$ctrl.edit.mana == null"
enable="true">
</vn-spinner>
<div ng-if="$ctrl.edit.mana != null">
<div class="vn-pa-md">
<vn-input-number vn-focus
label="Discount"
ng-model="$ctrl.edit.discount"
on-change="$ctrl.changeMultipleDiscount()"
suffix="%">
</vn-input-number>
</div>
<section class="header vn-pa-md">
<span>Mana: <strong>{{::$ctrl.edit.mana | currency: 'EUR': 0}}</strong></span>
</section>
</div>
</tpl-body>
</vn-dialog>
<!-- Transfer Popover --> <!-- Transfer Popover -->
<vn-popover vn-id="transfer"> <vn-popover vn-id="transfer">
<div class="vn-pa-md transfer"> <div class="vn-pa-md transfer">
@ -306,10 +328,10 @@
<thead> <thead>
<tr> <tr>
<th number>Id</th> <th number>Id</th>
<th number>Shipped</th> <th number translate>Shipped</th>
<th number>Agency</th> <th number translate>Agency</th>
<th number>Warehouse</th> <th number translate>Warehouse</th>
<th number>Address</th> <th number translate>Address</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -352,28 +374,6 @@
</div> </div>
</vn-popover> </vn-popover>
<!-- Edit Dialog -->
<vn-dialog
vn-id="editDialog"
class="edit"
on-open="$ctrl.getManaSalespersonMana()"
on-close="$ctrl.mana = null">
<tpl-body>
<vn-spinner
ng-if="$ctrl.mana == null"
style="padding: 1em;"
enable="true">
</vn-spinner>
<vn-ticket-sale-edit-discount
ng-if="$ctrl.mana != null"
mana="$ctrl.mana"
bulk="true"
edit="$ctrl.edit"
on-hide="$ctrl.hideEditDialog()">
</vn-ticket-sale-edit-discount>
</tpl-body>
</vn-dialog>
<!-- SMS Dialog --> <!-- SMS Dialog -->
<vn-ticket-sms <vn-ticket-sms
vn-id="sms" vn-id="sms"
@ -384,7 +384,7 @@
vn-id="delete-lines" vn-id="delete-lines"
question="You are going to delete lines of the ticket" question="You are going to delete lines of the ticket"
message="Continue anyway?" message="Continue anyway?"
on-response="$ctrl.onRemoveLinesClick($response)"> on-response="$ctrl.removeSales()">
</vn-confirm> </vn-confirm>
<vn-confirm <vn-confirm
@ -392,4 +392,40 @@
question="Do you want to delete it?" question="Do you want to delete it?"
message="This ticket is now empty" message="This ticket is now empty"
on-response="$ctrl.transferSales($ctrl.transfer.ticketId, $response)"> on-response="$ctrl.transferSales($ctrl.transfer.ticketId, $response)">
</vn-confirm> </vn-confirm>
<vn-menu vn-id="moreOptions">
<vn-item translate
name="sms"
ng-click="$ctrl.showSMSDialog()">
Send shortage SMS
</vn-item>
<vn-item translate
name="calculatePrice"
ng-click="$ctrl.calculateSalePrice()"
ng-if="$ctrl.isEditable && $ctrl.hasOneSaleSelected()">
Recalculate price
</vn-item>
<vn-item translate
name="discount"
ng-click="$ctrl.showEditDiscountDialog($event)">
Update discount
</vn-item>
<vn-item translate
name="claim"
ng-click="$ctrl.createClaim()">
Add claim
</vn-item>
<vn-item translate
name="reserve"
ng-click="$ctrl.markAsReserved()"
ng-if="$ctrl.isEditable">
Mark as reserved
</vn-item>
<vn-item translate
name="unreserve"
ng-click="$ctrl.unmarkAsReserved()"
ng-if="$ctrl.isEditable && $ctrl.hasReserves()">
Unmark as reserved
</vn-item>
</vn-menu>

View File

@ -5,32 +5,6 @@ import './style.scss';
class Controller extends Section { class Controller extends Section {
constructor($element, $) { constructor($element, $) {
super($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 = []; this._sales = [];
} }
@ -53,21 +27,17 @@ class Controller extends Section {
this.refreshTotal(); this.refreshTotal();
} }
get editedPrice() { get ticketState() {
return this._editedPrice; if (!this.ticket) return null;
return this.ticket.ticketState.state.code;
} }
set editedPrice(value) { get total() {
this._editedPrice = value; return this.subtotal + this.VAT;
this.updateNewPrice();
} }
refreshTotal() { getSubTotal() {
this.loadSubTotal();
this.loadVAT();
}
loadSubTotal() {
if (!this.$params.id || !this.sales) return; if (!this.$params.id || !this.sales) return;
this.$http.get(`Tickets/${this.$params.id}/subtotal`).then(res => { this.$http.get(`Tickets/${this.$params.id}/subtotal`).then(res => {
this.subtotal = res.data || 0.0; this.subtotal = res.data || 0.0;
@ -75,13 +45,21 @@ class Controller extends Section {
} }
getSaleTotal(sale) { getSaleTotal(sale) {
if (!sale.quantity || !sale.price) if (sale.quantity == null || sale.price == null)
return; 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; this.VAT = 0.0;
if (!this.$params.id || !this.sales) return; if (!this.$params.id || !this.sales) return;
this.$http.get(`Tickets/${this.$params.id}/getVAT`).then(res => { this.$http.get(`Tickets/${this.$params.id}/getVAT`).then(res => {
@ -89,32 +67,9 @@ class Controller extends Section {
}); });
} }
get total() { refreshTotal() {
return this.subtotal + this.VAT; this.getSubTotal();
} this.getVat();
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;
} }
/** /**
@ -122,7 +77,7 @@ class Controller extends Section {
* *
* @return {Array} Checked instances * @return {Array} Checked instances
*/ */
checkedLines() { selectedSales() {
if (!this.sales) return; if (!this.sales) return;
return this.sales.filter(sale => { 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 * Returns new instances
* *
@ -143,99 +130,53 @@ class Controller extends Section {
}); });
} }
/** resetChanges() {
* Returns an array of indexes if (this.newInstances().length === 0)
* from checked instances this.$.watcher.updateOriginalData();
*
* @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;
} }
firstCheckedLine() { changeState(value) {
const checkedLines = this.checkedLines(); const params = {ticketFk: this.$params.id, code: value};
if (checkedLines) return this.$http.post('TicketTrackings/changeState', params).then(() => {
return checkedLines[0]; this.vnApp.showSuccess(this.$t('Data saved!'));
this.card.reload();
}).finally(() => this.resetChanges());
} }
/** removeSales() {
* Returns the total of checked instances const sales = this.selectedValidSales();
* const params = {sales: sales, ticketId: this.ticket.id};
* @return {Number} Total checked instances this.$http.post(`Sales/deleteSales`, params).then(() => {
*/ this.removeSelectedSales();
totalCheckedLines() { this.vnApp.showSuccess(this.$t('Data saved!'));
const checkedLines = this.checkedLines(); }).finally(() => this.resetChanges());
if (checkedLines)
return checkedLines.length;
} }
removeCheckedLines() { removeSelectedSales() {
const sales = this.checkedLines(); const sales = this.selectedSales();
sales.forEach(sale => { sales.forEach(sale => {
const index = this.sales.indexOf(sale); const index = this.sales.indexOf(sale);
this.sales.splice(index, 1); this.sales.splice(index, 1);
}); });
if (this.newInstances().length === 0)
this.$.watcher.updateOriginalData();
this.refreshTotal(); this.refreshTotal();
} }
onStateOkClick() { createClaim() {
let filter = {where: {code: 'OK'}, fields: ['id']}; const sales = this.selectedValidSales();
let json = encodeURIComponent(JSON.stringify(filter)); const params = {ticketId: this.ticket.id, sales: sales};
return this.$http.get(`States?filter=${json}`).then(res => { this.resetChanges();
this.onStateChange(res.data[0].id); this.$http.post(`Claims/createFromSales`, params)
}); .then(res => this.$state.go('claim.card.basicData', {id: res.data.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!'));
});
}
} }
showTransferPopover(event) { showTransferPopover(event) {
this.setTransferParams(); this.setTransferParams();
this.$.transfer.parent = event.target; this.$.transfer.show(event);
this.$.transfer.show();
} }
setTransferParams() { setTransferParams() {
const checkedSales = JSON.stringify(this.checkedLines()); const checkedSales = JSON.stringify(this.selectedValidSales());
const sales = JSON.parse(checkedSales); const sales = JSON.parse(checkedSales);
this.transfer = { this.transfer = {
lastActiveTickets: [], lastActiveTickets: [],
@ -258,99 +199,123 @@ class Controller extends Section {
this.$.watcher.updateOriginalData(); this.$.watcher.updateOriginalData();
const query = `tickets/${this.ticket.id}/transferSales`; const query = `tickets/${this.ticket.id}/transferSales`;
this.$http.post(query, params).then(res => { this.$http.post(query, params)
this.goToTicket(res.data.id); .then(res => this.$state.go('ticket.card.sale', {id: 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;
});
} }
showEditPricePopover(event, sale) { showEditPricePopover(event, sale) {
if (!this.isEditable) return; if (!this.isEditable) return;
this.sale = sale;
this.newPrice = this.sale.price;
this.edit = { this.edit = {
ticketFk: this.ticket.id, price: sale.price,
id: sale.id, sale: sale
quantity: sale.quantity
}; };
this.$.editPricePopover.parent = event.target; this.$.editPricePopover.show(event);
this.$.editPricePopover.show();
} }
updatePrice() { updatePrice() {
if (this.newPrice && this.newPrice != this.sale.price) { const sale = this.edit.sale;
const query = `Sales/${this.edit.id}/updatePrice`; const newPrice = this.edit.price;
this.$http.post(query, {newPrice: this.newPrice}).then(res => { if (newPrice != null && newPrice != sale.price) {
this.sale.price = res.data.price; const query = `Sales/${sale.id}/updatePrice`;
this.$http.post(query, {newPrice}).then(res => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!')); sale.price = res.data.price;
}).finally(() => { this.edit = null;
if (this.newInstances().length === 0) this.refreshTotal();
this.$.watcher.updateOriginalData(); this.vnApp.showSuccess(this.$t('Data saved!'));
}); }).finally(() => this.resetChanges());
} }
this.$.editPricePopover.hide(); this.$.editPricePopover.hide();
} }
updateNewPrice() {
this.newPrice = this.sale.quantity * this.newPrice - ((this.sale.discount * (this.sale.quantity * this.newPrice)) / 100);
}
showEditDiscountPopover(event, sale) { showEditDiscountPopover(event, sale) {
if (this.isLocked) return; if (this.isLocked) return;
this.sale = sale; this.edit = {
this.edit = [{ discount: sale.discount,
ticketFk: this.ticket.id, sale: sale
id: sale.id, };
quantity: sale.quantity,
price: sale.price, this.$.editDiscount.show(event);
discount: sale.discount
}];
this.$.editPopover.parent = event.target;
this.$.editPopover.show();
} }
showEditDialog() { showEditDiscountDialog(event) {
this.edit = this.checkedLines(); if (this.isLocked) return;
this.$.editDialog.show();
this.edit = {
discount: null,
sales: this.selectedValidSales()
};
this.$.editDiscountDialog.show(event);
} }
hideEditDialog() { changeDiscount() {
this.$.model.refresh(); const sale = this.edit.sale;
this.$.editDialog.hide(); const newDiscount = this.edit.discount;
if (newDiscount != null && newDiscount != sale.discount)
this.updateDiscount([sale]);
this.$.editDiscount.hide();
} }
hideEditPopover() { changeMultipleDiscount() {
this.$.model.refresh(); const sales = this.edit.sales;
this.$.editPopover.hide(); 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) { setReserved(reserved) {
let selectedSales = this.checkedLines(); const selectedSales = this.selectedValidSales();
let params = {sales: selectedSales, ticketFk: this.ticket.id, reserved: reserved}; const params = {ticketId: this.ticket.id, sales: selectedSales, reserved: reserved};
this.$http.post(`Sales/reserve`, params).then(() => {
let reservedSales = new Map(); selectedSales.forEach(sale => {
this.$http.post(`Sales/reserve`, params).then(res => { sale.reserved = reserved;
let isReserved = res.config.data.reserved;
res.config.data.sales.forEach(sale => {
reservedSales.set(sale.id, {reserved: isReserved});
}); });
}).finally(() => this.resetChanges());
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();
});
} }
newOrderFromTicket() { newOrderFromTicket() {
@ -395,7 +347,7 @@ class Controller extends Section {
const path = this.$state.href('order.card.catalog', {id: res.data}); const path = this.$state.href('order.card.catalog', {id: res.data});
window.open(path, '_blank'); 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 client = this.ticket.client;
const phone = address.mobile || address.phone || const phone = address.mobile || address.phone ||
client.mobile || client.phone; client.mobile || client.phone;
const sales = this.checkedLines(); const sales = this.selectedValidSales();
const items = sales.map(sale => { const items = sales.map(sale => {
return `${sale.quantity} ${sale.concept}`; return `${sale.quantity} ${sale.concept}`;
}); });
@ -417,7 +369,7 @@ class Controller extends Section {
this.newSMS = { this.newSMS = {
destinationFk: this.ticket.clientFk, destinationFk: this.ticket.clientFk,
destination: phone, destination: phone,
message: this.$translate.instant('Product not available', params) message: this.$t('Product not available', params)
}; };
this.$.sms.open(); this.$.sms.open();
} }
@ -433,8 +385,8 @@ class Controller extends Section {
* Creates a new sale if it's a new instance * Creates a new sale if it's a new instance
* Updates the sale quantity for existing instance * Updates the sale quantity for existing instance
*/ */
onChangeQuantity(sale) { changeQuantity(sale) {
if (!sale.quantity) return; if (!sale.itemFk || !sale.quantity) return;
if (!sale.id) if (!sale.id)
return this.addSale(sale); return this.addSale(sale);
@ -443,37 +395,30 @@ class Controller extends Section {
} }
/* /*
* Updates a sale quantity * Changes a sale quantity
*/ */
updateQuantity(sale) { updateQuantity(sale) {
const data = {quantity: parseInt(sale.quantity)}; const data = {quantity: sale.quantity};
const query = `Sales/${sale.id}/updateQuantity`; this.$http.post(`Sales/${sale.id}/updateQuantity`, data).then(() => {
this.$http.post(query, data).then(() => { this.refreshTotal();
this.vnApp.showSuccess(this.$translate.instant('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}).catch(e => { }).catch(e => {
this.$.model.refresh(); this.$.model.refresh();
throw e; throw e;
}).finally(() => { }).finally(() => this.resetChanges());
if (this.newInstances().length === 0)
this.$.watcher.updateOriginalData();
});
} }
/* /*
* Updates a sale concept * Changes a sale concept
*/ */
updateConcept(sale) { updateConcept(sale) {
const data = {newConcept: sale.concept}; const data = {newConcept: sale.concept};
const query = `Sales/${sale.id}/updateConcept`; this.$http.post(`Sales/${sale.id}/updateConcept`, data).then(() => {
this.$http.post(query, data).then(() => { this.vnApp.showSuccess(this.$t('Data saved!'));
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
}).catch(e => { }).catch(e => {
this.$.model.refresh(); this.$.model.refresh();
throw e; throw e;
}).finally(() => { }).finally(() => this.resetChanges());
if (this.newInstances().length === 0)
this.$.watcher.updateOriginalData();
});
} }
/* /*
@ -489,7 +434,6 @@ class Controller extends Section {
if (!res.data) return; if (!res.data) return;
const newSale = res.data; const newSale = res.data;
sale.id = newSale.id; sale.id = newSale.id;
sale.image = newSale.item.image; sale.image = newSale.item.image;
sale.subName = newSale.item.subName; sale.subName = newSale.item.subName;
@ -499,37 +443,29 @@ class Controller extends Section {
sale.price = newSale.price; sale.price = newSale.price;
sale.item = newSale.item; sale.item = newSale.item;
this.vnApp.showSuccess(this.$translate.instant('Data saved!')); this.refreshTotal();
}).finally(() => { this.vnApp.showSuccess(this.$t('Data saved!'));
if (this.newInstances().length === 0) }).finally(() => this.resetChanges());
this.$.watcher.updateOriginalData();
});
} }
isTicketEditable() { isTicketEditable() {
this.$http.get(`Tickets/${this.$state.params.id}/isEditable`).then(res => { this.$http.get(`Tickets/${this.$params.id}/isEditable`)
this.isEditable = res.data; .then(res => this.isEditable = res.data);
});
} }
isTicketLocked() { isTicketLocked() {
this.$http.get(`Tickets/${this.$state.params.id}/isLocked`).then(res => { this.$http.get(`Tickets/${this.$params.id}/isLocked`)
this.isLocked = res.data; .then(res => this.isLocked = res.data);
});
}
hasOneSaleSelected() {
if (this.totalCheckedLines() === 1)
return true;
return false;
} }
calculateSalePrice() { calculateSalePrice() {
const sale = this.checkedLines()[0]; const sale = this.selectedValidSales()[0];
if (!sale) return;
const query = `Sales/${sale.id}/recalculatePrice`; const query = `Sales/${sale.id}/recalculatePrice`;
this.$http.post(query).then(res => { this.$http.post(query).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.model.refresh(); this.$.model.refresh();
this.refreshTotal();
}); });
} }

View File

@ -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('<vn-ticket-sale></vn-ticket-sale>');
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();
});
});
});
});

View File

@ -1,5 +1,6 @@
New price: Nuevo precio New price: Nuevo precio
Add item: Añadir artículo Add item: Añadir artículo
Add item to basket: Añadir artículo a la cesta
Add turn: Añadir a turno Add turn: Añadir a turno
Delete ticket: Eliminar ticket Delete ticket: Eliminar ticket
Mark as reserved: Marcar como reservado Mark as reserved: Marcar como reservado

View File

@ -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('<vn-ticket-sale-edit-discount></vn-ticket-sale-edit-discount>');
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);
});
});
});
});

View File

@ -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('<vn-ticket-sale></vn-ticket-sale>');
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();
});
});
});
});

View File

@ -24,7 +24,7 @@ vn-ticket-sale {
} }
} }
vn-dialog.edit { vn-dialog.edit {
@extend .edit-price; @extend .edit-popover;
&>div{ &>div{
padding: 0!important; padding: 0!important;
@ -81,8 +81,9 @@ vn-ticket-sale {
width: 400px width: 400px
} }
} }
.edit-price { .vn-popover .edit-popover {
min-width: 200px; min-width: 200px;
text-align: center;
section.header { section.header {
background-color: $color-main; background-color: $color-main;

View File

@ -16,7 +16,7 @@
<vn-horizontal> <vn-horizontal>
<vn-one> <vn-one>
<vn-label-value label="State" <vn-label-value label="State"
value="{{$ctrl.summary.state.state.name}}"> value="{{$ctrl.summary.ticketState.state.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Salesperson" <vn-label-value label="Salesperson"
value="{{$ctrl.summary.client.salesPerson.user.nickname}}"> value="{{$ctrl.summary.client.salesPerson.user.nickname}}">

View File

@ -31,7 +31,7 @@ class Controller extends Section {
get isEditable() { get isEditable() {
try { try {
return !this.ticket.state.state.alertLevel; return !this.ticket.ticketState.state.alertLevel;
} catch (e) {} } catch (e) {}
return true; return true;

View File

@ -14,16 +14,13 @@ module.exports = Self => {
}); });
Self.getCurrentWorkerMana = async ctx => { Self.getCurrentWorkerMana = async ctx => {
let currentClientId = ctx.req.accessToken.userId; let userId = ctx.req.accessToken.userId;
let currentWorker = await Self.app.models.Worker.findOne({
where: {userFk: currentClientId}, let workerMana = await Self.app.models.WorkerMana.findOne({
fields: 'id' where: {workerFk: userId},
});
let currentWorkerMana = await Self.app.models.WorkerMana.findOne({
where: {workerFk: currentWorker.id},
fields: 'amount' fields: 'amount'
}); });
return currentWorkerMana ? currentWorkerMana.amount : 0; return workerMana ? workerMana.amount : 0;
}; };
}; };

View File

@ -32,7 +32,7 @@ module.exports = Self => {
zoneFk: id zoneFk: id
}, },
include: { include: {
relation: 'state', relation: 'ticketState',
scope: { scope: {
fields: ['id', 'alertLevel', 'code'] fields: ['id', 'alertLevel', 'code']
} }
@ -49,7 +49,7 @@ module.exports = Self => {
ticketList.forEach(ticket => { ticketList.forEach(ticket => {
promises.push(ticket.updateAttributes({zoneFk: null}, options)); 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({ promises.push(models.TicketTracking.create({
ticketFk: ticket.id, ticketFk: ticket.id,
stateFk: fixingState.id, stateFk: fixingState.id,

View File

@ -33,6 +33,7 @@ describe('component vnZoneCalendar', () => {
describe('step()', () => { describe('step()', () => {
it('should set the date month to 4 months backwards', () => { it('should set the date month to 4 months backwards', () => {
const now = new Date(); const now = new Date();
now.setDate(15);
now.setMonth(now.getMonth() - 4); now.setMonth(now.getMonth() - 4);
controller.step(-1); controller.step(-1);
@ -45,6 +46,7 @@ describe('component vnZoneCalendar', () => {
it('should set the date month to 4 months forwards', () => { it('should set the date month to 4 months forwards', () => {
const now = new Date(); const now = new Date();
now.setDate(15);
now.setMonth(now.getMonth() + 4); now.setMonth(now.getMonth() + 4);
controller.step(1); controller.step(1);

View File

@ -11,5 +11,5 @@ clientSignature: Firma del cliente
claim: Reclamación {0} claim: Reclamación {0}
sections: sections:
agency: 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. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) <em>(agomezf@integra2.es)</em>' de integrados. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) <em>(agomezf@integra2.es)</em>'