Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 7366-travelMigration

This commit is contained in:
Carlos Satorres 2024-10-01 12:40:12 +02:00
commit 99bd78db36
88 changed files with 349 additions and 3529 deletions

View File

@ -1544,7 +1544,7 @@ INSERT INTO `bs`.`waste`(`buyerFk`, `year`, `week`, `itemFk`, `itemTypeFk`, `sal
('103', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 6, 1, '186', '0', '51', '53.12', '56.20', '56.20', '56.20'),
('103', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 7, 1, '277', '0', '53.12', '56.20', '56.20', '56.20', '56.20');
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packagingFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`, `printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
INSERT INTO vn.buy(id,entryFk,itemFk,buyingValue,quantity,packagingFk,stickers,freightValue,packageValue,comissionValue,packing,grouping,groupingMode,location,price1,price2,price3,printedStickers,isChecked,isIgnored,weight,created)
VALUES
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 'packing', NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, util.VN_CURDATE() - INTERVAL 2 MONTH),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 'packing', NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, util.VN_CURDATE() - INTERVAL 1 MONTH),
@ -1560,7 +1560,8 @@ INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packagi
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 'grouping', NULL, 0.00, 1.75, 1.67, 0, 1, 0, 4, util.VN_CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 'packing', NULL, 0.00, 99.6, 99.4, 0, 1, 0, 4, util.VN_CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 'grouping', NULL, 0.00, 7.30, 7.00, 0, 1, 0, 4, util.VN_CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 'grouping', NULL, 0.00, 1.75, 1.67, 0, 1, 0, 4, util.VN_CURDATE());
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 'grouping', NULL, 0.00, 1.75, 1.67, 0, 1, 0, 4, util.VN_CURDATE()),
(16, 99,1,50.0000, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 'packing', NULL, 0.00, 99.60, 99.40, 0, 1, 0, 1.00, '2024-07-30 08:13:51.000');
INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_id`, `agency_id`, `address_id`, `company_id`, `note`, `source_app`, `confirmed`,`total`, `date_make`, `first_row_stamp`, `confirm_date`)
VALUES

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` EVENT `vn`.`travel_setDelivered`
CREATE OR REPLACE DEFINER=`vn`@`localhost` EVENT `vn`.`travel_setDelivered`
ON SCHEDULE EVERY 1 DAY
STARTS '2024-07-12 00:10:00.000'
ON COMPLETION PRESERVE

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`buy_getUltimate`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`buy_getUltimate`(
vItemFk INT,
vWarehouseFk SMALLINT,
vDated DATE

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`buy_getUltimateFromInterval`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`buy_getUltimateFromInterval`(
vItemFk INT,
vWarehouseFk SMALLINT,
vStarted DATE,

View File

@ -55,24 +55,20 @@ BEGIN
SELECT ts.saleFk,
ts.itemFk,
CAST(0 AS DECIMAL(10,0)) saleOrder,
IF(ish.visible > 0 OR iss.id, 1, 100000) *
IFNULL(p2.pickingOrder, p.pickingOrder) `order`,
TO_SECONDS(IF(iss.id,
iss.created - INTERVAL vCurrentYear YEAR,
ish.created - INTERVAL YEAR(ish.created) YEAR)) priority,
(IF(ish.visible > 0 OR iss.id, 1, 100000) *
COALESCE(p2.pickingOrder, p.pickingOrder)) `order`,
TO_SECONDS(COALESCE(iss.created, ish.created)) - TO_SECONDS(MAKEDATE(IFNULL(YEAR(iss.created), YEAR(ish.created)), 1)) priority,
CONCAT(
IF(iss.id,
CONCAT('< ', IFNULL(wk.`code`, '---'),' > '),
''),
p.`code`) COLLATE utf8_general_ci placement,
IF(iss.id, CONCAT('< ', COALESCE(wk.`code`, '---'),' > '), ''),
p.`code`
) COLLATE utf8_general_ci placement,
sh.priority shelvingPriority,
sh.code COLLATE utf8_general_ci shelving,
ish.created,
ish.visible,
IFNULL(
IF(st.code = 'previousByPacking', ish.packing, g.`grouping`),
1) `grouping`,
st.code = 'previousPrepared' isPreviousPrepared,
COALESCE(
IF(st.code = 'previousByPacking', ish.packing, g.`grouping`),1) `grouping`,
(st.code = 'previousPrepared') isPreviousPrepared,
iss.id itemShelvingSaleFk,
ts.ticketFk,
iss.id,
@ -80,11 +76,12 @@ BEGIN
iss.userFk,
ts.quantity
FROM tSale ts
LEFT JOIN (SELECT DISTINCT saleFk
LEFT JOIN (SELECT st.saleFk
FROM saleTracking st
JOIN state s ON s.id = st.stateFk
WHERE st.isChecked
AND s.semaphore = 1) st ON st.saleFk = ts.saleFk
AND s.semaphore = 1
GROUP BY st.saleFk) st ON st.saleFk = ts.saleFk
JOIN itemShelving ish ON ish.itemFk = ts.itemFk
JOIN shelving sh ON sh.code = ish.shelvingFk
JOIN parking p ON p.id = sh.parkingFk
@ -100,7 +97,7 @@ BEGIN
LEFT JOIN parking p2 ON p2.id = sg.parkingFk
WHERE w.id = vWarehouseFk
AND NOT sc.isHideForPickers
HAVING (iss.id AND st.saleFk) OR salePreviousPrepared IS NULL;
AND ((iss.id AND st.saleFk) OR st.saleFk IS NULL);
CREATE OR REPLACE TEMPORARY TABLE tSalePlacementList2
(INDEX(saleFk), INDEX(olderPriority))

View File

@ -23,7 +23,7 @@ BEGIN
JOIN vn.ticketCollection tc ON tc.ticketFk = tob.ticketFk
LEFT JOIN vn.observationType ot ON ot.id = tob.observationTypeFk
WHERE ot.`code` = 'itemPicker'
AND tc.collectionFk = vParamFk
AND tc.collectionFk = vParamFk OR tc.ticketFk = vParamFk
)
SELECT t.id ticketFk,
IF(!(vItemPackingTypeFk <=> 'V'), cc.code, CONCAT(SUBSTRING('ABCDEFGH', tc.wagon, 1), '-', tc.`level`)) `level`,

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`collection_mergeSales`(vCollectionFk INT)
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`collection_mergeSales`(vCollectionFk INT)
BEGIN
DECLARE vDone BOOL;
DECLARE vTicketFk INT;

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemMinimumQuantity_check`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`itemMinimumQuantity_check`(
vSelf INT,
vItemFk INT,
vStarted DATE,

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelvingSale_addBySaleGroup`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`itemShelvingSale_addBySaleGroup`(
vSaleGroupFk INT(11)
)
BEGIN

View File

@ -52,13 +52,13 @@ BEGIN
FROM vn.sale s
JOIN vn.ticket t ON t.id = s.ticketFk
LEFT JOIN vn.itemShelvingSale iss ON iss.saleFk = s.id
WHERE t.shipped BETWEEN CURDATE() AND CURDATE() + INTERVAL vDaysInForward DAY
WHERE t.shipped >= CURDATE() + INTERVAL vDaysInForward DAY
AND iss.saleFk IS NULL
AND t.warehouseFk = vWarehouseFk
GROUP BY s.itemFk
)
SELECT i.id itemFk,
CAST(sd.quantity AS INT) advanceable,
LEAST(CAST(sd.quantity AS INT), sk.stock) advanceable,
i.longName,
i.subName,
i.tag5,

View File

@ -0,0 +1,62 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`stockBought_calculate`(
vDated DATE
)
proc: BEGIN
/**
* Calculate the stock of the auction warehouse from the inventory date to vDated
* without taking into account the outputs of the same day vDated
*
* @param vDated Date to calculate the stock.
*/
IF vDated < util.VN_CURDATE() THEN
LEAVE proc;
END IF;
CREATE OR REPLACE TEMPORARY TABLE tStockBought
SELECT workerFk, reserve
FROM stockBought
WHERE dated = vDated
AND reserve;
DELETE FROM stockBought WHERE dated = vDated;
CALL item_calculateStock(vDated);
INSERT INTO stockBought(workerFk, bought, dated)
SELECT it.workerFk,
ROUND(SUM(
(ti.quantity / b.packing) *
buy_getVolume(b.id)
) / vc.palletM3 / 1000000, 1) bought,
vDated
FROM itemType it
JOIN item i ON i.typeFk = it.id
LEFT JOIN tmp.item ti ON ti.itemFk = i.id
JOIN itemCategory ic ON ic.id = it.categoryFk
JOIN warehouse wh ON wh.code = 'VNH'
JOIN tmp.buyUltimate bu ON bu.itemFk = i.id
AND bu.warehouseFk = wh.id
JOIN buy b ON b.id = bu.buyFk
JOIN volumeConfig vc
WHERE ic.display
GROUP BY it.workerFk
HAVING bought;
UPDATE stockBought s
JOIN tStockBought ts ON ts.workerFk = s.workerFk
SET s.reserve = ts.reserve
WHERE s.dated = vDated;
INSERT INTO stockBought (workerFk, reserve, dated)
SELECT ts.workerFk, ts.reserve, vDated
FROM tStockBought ts
WHERE ts.workerFk NOT IN (
SELECT workerFk
FROM stockBought
WHERE dated = vDated
);
DROP TEMPORARY TABLE tStockBought, tmp.item, tmp.buyUltimate;
END$$
DELIMITER ;

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE vn.supplier_statementWithEntries(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE vn.supplier_statementWithEntries(
vSupplierFk INT,
vCurrencyFk INT,
vCompanyFk INT,

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_mergeSales`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_mergeSales`(
vSelf INT
)
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setProblemRiskByClient`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setProblemRiskByClient`(
vClientFk INT
)
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setVolume`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setVolume`(
vSelf INT
)
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setVolumeItemCost`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setVolumeItemCost`(
vItemFk INT
)
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`host_beforeInsert`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`host_beforeInsert`
BEFORE INSERT ON `host`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`roadmap_beforeInsert`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`roadmap_beforeInsert`
BEFORE INSERT ON `roadmap`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`roadmap_beforeUpdate`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`roadmap_beforeUpdate`
BEFORE UPDATE ON `roadmap`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeInsert`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeInsert`
BEFORE INSERT ON `saleGroupDetail`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`saleGroupDetail_afterDelete`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`saleGroupDetail_afterDelete`
AFTER DELETE ON `saleGroupDetail`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeUpdate`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeUpdate`
BEFORE UPDATE ON `saleGroupDetail`
FOR EACH ROW
BEGIN

View File

@ -0,0 +1,30 @@
-- Place your SQL code here
-- vn.stockBought definition
CREATE TABLE IF NOT EXISTS vn.stockBought (
id INT UNSIGNED auto_increment NOT NULL,
workerFk int(10) unsigned NOT NULL,
bought decimal(10,2) NOT NULL COMMENT 'purchase volume in m3 for the day',
reserve decimal(10,2) NULL COMMENT 'reserved volume in m3 for the day',
dated DATE NOT NULL DEFAULT current_timestamp(),
CONSTRAINT stockBought_pk PRIMARY KEY (id),
CONSTRAINT stockBought_unique UNIQUE KEY (workerFk,dated),
CONSTRAINT stockBought_worker_FK FOREIGN KEY (workerFk) REFERENCES vn.worker(id)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb3
COLLATE=utf8mb3_unicode_ci;
INSERT IGNORE vn.stockBought (workerFk, bought, reserve, dated)
SELECT userFk, SUM(buyed), SUM(IFNULL(reserved,0)), dated
FROM vn.stockBuyed
WHERE userFk IS NOT NULL
AND buyed IS NOT NULL
GROUP BY userFk, dated;
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('StockBought','*','READ','ALLOW','ROLE','buyer'),
('StockBought','*','WRITE','ALLOW','ROLE','buyer'),
('Buyer','*','READ','ALLOW','ROLE','buyer');

View File

@ -1,68 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('SmartTable SearchBar integration', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesPerson', 'item');
await page.waitToClick(selectors.globalItems.searchButton);
});
afterAll(async() => {
await browser.close();
});
it('should search by type in searchBar, reload page and have same results', async() => {
await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
await page.waitToClick(selectors.itemsIndex.advancedSearchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4);
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4);
await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
});
it('should filter in section without smart-table and search in searchBar go to zone section', async() => {
await page.loginAndModule('salesPerson', 'zone');
await page.waitToClick(selectors.globalItems.searchButton);
await page.doSearch('A');
const firstCount = await page.countElement(selectors.zoneIndex.searchResult);
await page.doSearch('A');
const secondCount = await page.countElement(selectors.zoneIndex.searchResult);
expect(firstCount).toEqual(7);
expect(secondCount).toEqual(7);
});
it('should order orders by first id and order by last id, reload page and have same order', async() => {
await page.loginAndModule('developer', 'item');
await page.accessToSection('item.fixedPrice');
await page.keyboard.press('Enter');
await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '1');
await page.waitToClick(selectors.itemFixedPrice.orderColumnId);
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '3');
});
});

View File

@ -1,104 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Zone basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('deliveryAssistant',
'zone'); // turns up the zone module name and route aint the same lol
await page.accessToSearchResult('10');
await page.accessToSection('zone.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should reach the basic data section', async() => {
await page.waitForState('zone.card.basicData');
});
it('should edit de form and then save', async() => {
await page.clearInput(selectors.zoneBasicData.name);
await page.write(selectors.zoneBasicData.name, 'Brimstone teleportation');
await page.autocompleteSearch(selectors.zoneBasicData.agency, 'Quantum break device');
await page.clearInput(selectors.zoneBasicData.maxVolume);
await page.write(selectors.zoneBasicData.maxVolume, '10');
await page.clearInput(selectors.zoneBasicData.travelingDays);
await page.write(selectors.zoneBasicData.travelingDays, '1');
await page.clearInput(selectors.zoneBasicData.closing);
await page.pickTime(selectors.zoneBasicData.closing, '21:00');
await page.clearInput(selectors.zoneBasicData.price);
await page.write(selectors.zoneBasicData.price, '999');
await page.clearInput(selectors.zoneBasicData.bonus);
await page.write(selectors.zoneBasicData.bonus, '100');
await page.clearInput(selectors.zoneBasicData.inflation);
await page.write(selectors.zoneBasicData.inflation, '200');
await page.waitToClick(selectors.zoneBasicData.volumetric);
await page.waitToClick(selectors.zoneBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should now reload the section', async() => {
await page.reloadSection('zone.card.basicData');
});
it('should confirm the name was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.name, 'value');
expect(result).toEqual('Brimstone teleportation');
});
it('should confirm the agency was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.agency, 'value');
expect(result).toEqual('Quantum break device');
});
it('should confirm the max volume was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.maxVolume, 'value');
expect(result).toEqual('10');
});
it('should confirm the traveling days were updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.travelingDays, 'value');
expect(result).toEqual('1');
});
it('should confirm the closing hour was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.closing, 'value');
expect(result).toEqual('21:00');
});
it('should confirm the price was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.price, 'value');
expect(result).toEqual('999');
});
it('should confirm the bonus was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.bonus, 'value');
expect(result).toEqual('100');
});
it('should confirm the inflation was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.inflation, 'value');
expect(result).toEqual('200');
});
it('should confirm the volumetric checkbox was checked', async() => {
await page.waitForClassPresent(selectors.zoneBasicData.volumetric, 'checked');
});
});

View File

@ -1,32 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Zone descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('deliveryAssistant', 'zone');
await page.accessToSearchResult('13');
});
afterAll(async() => {
await browser.close();
});
it('should eliminate the zone using the descriptor option', async() => {
await page.waitToClick(selectors.zoneDescriptor.menu);
await page.waitToClick(selectors.zoneDescriptor.deleteZone);
await page.respondToDialog('accept');
await page.waitForState('zone.index');
});
it('should search for the deleted zone to find no results', async() => {
await page.doSearch('13');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(0);
});
});

View File

@ -0,0 +1,58 @@
module.exports = Self => {
Self.remoteMethod('getStockBought', {
description: 'Returns the stock bought for a given date',
accessType: 'READ',
accepts: [{
arg: 'workerFk',
type: 'number',
description: 'The id for a buyer',
},
{
arg: 'dated',
type: 'date',
description: 'The date to filter',
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getStockBought`,
verb: 'GET'
}
});
Self.getStockBought = async(workerFk, dated = Date.vnNew()) => {
const models = Self.app.models;
const today = Date.vnNew();
dated.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
await models.StockBought.rawSql(`CALL vn.stockBought_calculate(?)`, [dated]);
const filter = {
where: {dated},
include: [
{
fields: ['workerFk', 'reserve', 'bought'],
relation: 'worker',
scope: {
include: [
{
relation: 'user',
scope: {
fields: ['id', 'name']
}
}
]
}
}
]
};
if (workerFk) filter.where.workerFk = workerFk;
return models.StockBought.find(filter);
};
};

View File

@ -0,0 +1,74 @@
module.exports = Self => {
Self.remoteMethod('getStockBoughtDetail', {
description: 'Returns the detail of stock bought for a given date and a worker',
accessType: 'READ',
accepts: [{
arg: 'workerFk',
type: 'number',
description: 'The worker to filter',
required: true,
}, {
arg: 'dated',
type: 'string',
description: 'The date to filter',
required: true,
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getStockBoughtDetail`,
verb: 'GET'
}
});
Self.getStockBoughtDetail = async(workerFk, dated) => {
const models = Self.app.models;
const myOptions = {};
let tx;
let result;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
await models.StockBought.rawSql(`CALL vn.item_calculateStock(?)`, [dated], myOptions);
result = await Self.rawSql(
`SELECT b.entryFk entryFk,
i.id itemFk,
i.name itemName,
ti.quantity,
(ac.conversionCoefficient * (ti.quantity / b.packing) * buy_getVolume(b.id))
/ (vc.trolleyM3 * 1000000) volume,
b.packagingFk packagingFk,
b.packing
FROM tmp.item ti
JOIN item i ON i.id = ti.itemFk
JOIN itemType it ON i.typeFk = it.id
JOIN itemCategory ic ON ic.id = it.categoryFk
JOIN worker w ON w.id = it.workerFk
JOIN auctionConfig ac
JOIN tmp.buyUltimate bu ON bu.itemFk = i.id
AND bu.warehouseFk = ac.warehouseFk
JOIN buy b ON b.id = bu.buyFk
JOIN volumeConfig vc
WHERE ic.display
AND w.id = ?`,
[workerFk], myOptions
);
await Self.rawSql(`DROP TEMPORARY TABLE tmp.item, tmp.buyUltimate;`, [], myOptions);
if (tx) await tx.commit();
return result;
} catch (e) {
await tx.rollback();
throw e;
}
};
};

View File

@ -25,5 +25,8 @@
},
"EntryType": {
"dataSource": "vn"
},
"StockBought": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,10 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/stock-bought/getStockBought')(Self);
require('../methods/stock-bought/getStockBoughtDetail')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`This buyer has already made a reservation for this date`);
return err;
});
};

View File

@ -0,0 +1,34 @@
{
"name": "StockBought",
"base": "VnModel",
"options": {
"mysql": {
"table": "stockBought"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"workerFk": {
"type": "number"
},
"bought": {
"type": "number"
},
"reserve": {
"type": "number"
},
"dated": {
"type": "date"
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
}
}
}

View File

@ -82,7 +82,7 @@ module.exports = Self => {
const getDataOperator = await models.Operator.findOne({
where: {workerFk: user.id},
fields: ['numberOfWagons', 'warehouseFk', 'itemPackingTypeFk', 'sectorFk', 'sector',
'trainFk', 'train', 'labelerFk', 'printer'],
'trainFk', 'train', 'labelerFk', 'printer', 'isOnReservationMode'],
include: [
{
relation: 'sector',

View File

@ -1,114 +0,0 @@
<mg-ajax path="Zones/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.zone"
form="form"
save="patch">
</vn-watcher>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Name"
ng-model="$ctrl.zone.name"
vn-acl="deliveryAssistant"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.zone.agencyModeFk"
url="AgencyModes"
show-field="name"
value-field="id"
label="Agency"
vn-acl="deliveryAssistant"
rule>
</vn-autocomplete>
<vn-input-number
vn-one
label="Max m³"
ng-model="$ctrl.zone.itemMaxSize"
min="0"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-input-number
vn-one
label="Maximum m³"
ng-model="$ctrl.zone.m3Max"
min="0"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Traveling days"
ng-model="$ctrl.zone.travelingDays"
min="0"
step="1"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-input-time
label="Closing"
ng-model="$ctrl.zone.hour"
vn-acl="deliveryAssistant"
rule>
</vn-input-time>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Price"
ng-model="$ctrl.zone.price"
min="0"
step="0.01"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-input-number
label="Bonus"
ng-model="$ctrl.zone.bonus"
step="0.01"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
vn-one
label="Inflation"
ng-model="$ctrl.zone.inflation"
min="0"
step="0.01"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-check
vn-one
label="Volumetric"
ng-model="$ctrl.zone.isVolumetric"
vn-acl="deliveryAssistant"
rule>
</vn-check>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
vn-acl="deliveryAssistant"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
onSubmit() {
this.$.watcher.submit().then(() =>
this.card.reload()
);
}
}
ngModule.vnComponent('vnZoneBasicData', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
},
require: {
card: '^vnZoneCard'
}
});

View File

@ -1,33 +0,0 @@
<vn-card>
<div class="header">
<vn-button
icon="navigate_before"
ng-click="$ctrl.step(-1)"
class="flat">
</vn-button>
<div>
<span translate>{{$ctrl.firstDay | date:'MMMM'}}</span>
<span>{{$ctrl.firstDay | date:'yyyy'}} -</span>
<span translate>{{$ctrl.lastDay | date:'MMMM'}}</span>
<span>{{$ctrl.lastDay | date:'yyyy'}}</span>
</div>
<vn-button
icon="navigate_next"
ng-click="$ctrl.step(1)"
class="flat">
</vn-button>
</div>
<div class="calendars vn-pa-md">
<vn-calendar
ng-repeat="date in $ctrl.months track by date.getTime()"
default-date="date"
display-controls="false"
hide-contiguous="true"
has-events="$ctrl.hasEvents($day)"
get-class="$ctrl.getClass($day)"
on-selection="$ctrl.onSelection($event, $days, $type, $weekday)"
class="vn-pa-md"
style="min-width: 250px; flex: 1;">
</vn-calendar>
</div>
</vn-card>

View File

@ -1,191 +0,0 @@
import ngModule from '../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
constructor($element, $, vnWeekDays) {
super($element, $);
this.vnWeekDays = vnWeekDays;
this.nMonths = 4;
let date = Date.vnNew();
date.setDate(1);
date.setHours(0, 0, 0, 0);
this.date = date;
}
get date() {
return this._date;
}
set date(value) {
this._date = value;
let stamp = value.getTime();
let firstDay = new Date(stamp);
firstDay.setDate(1);
this.firstDay = firstDay;
let lastDay = new Date(stamp);
lastDay.setMonth(lastDay.getMonth() + this.nMonths);
lastDay.setDate(0);
this.lastDay = lastDay;
this.months = [];
for (let i = 0; i < this.nMonths; i++) {
let monthDate = new Date(stamp);
monthDate.setMonth(value.getMonth() + i);
this.months.push(monthDate);
}
this.refreshEvents();
}
step(direction) {
let date = new Date(this.date.getTime());
date.setMonth(date.getMonth() + (this.nMonths * direction));
this.date = date;
this.emit('step');
}
get data() {
return this._data;
}
set data(value) {
this._data = value;
value = value || {};
this.events = value.events;
function toStamp(date) {
return date && new Date(date).setHours(0, 0, 0, 0);
}
this.exclusions = {};
let exclusions = value.exclusions;
if (exclusions) {
for (let exclusion of exclusions) {
let stamp = toStamp(exclusion.dated);
if (!this.exclusions[stamp]) this.exclusions[stamp] = [];
this.exclusions[stamp].push(exclusion);
}
}
this.geoExclusions = {};
let geoExclusions = value.geoExclusions;
if (geoExclusions) {
for (let geoExclusion of geoExclusions) {
let stamp = toStamp(geoExclusion.dated);
if (!this.geoExclusions[stamp]) this.geoExclusions[stamp] = [];
this.geoExclusions[stamp].push(geoExclusion);
}
}
let events = value.events;
if (events) {
for (let event of events) {
event.dated = toStamp(event.dated);
event.ended = toStamp(event.ended);
event.started = toStamp(event.started);
event.wdays = this.vnWeekDays.fromSet(event.weekDays);
}
}
this.refreshEvents();
let calendars = this.element.querySelectorAll('vn-calendar');
for (let calendar of calendars)
calendar.$ctrl.repaint();
}
refreshEvents() {
this.days = {};
if (!this.data) return;
let day = new Date(this.firstDay.getTime());
while (day <= this.lastDay) {
let stamp = day.getTime();
let wday = day.getDay();
let dayEvents = [];
let exclusions = this.exclusions[stamp] || [];
if (this.events) {
for (let event of this.events) {
let match;
switch (event.type) {
case 'day':
match = event.dated == stamp;
break;
default:
match = event.wdays[wday]
&& (!event.started || stamp >= event.started)
&& (!event.ended || stamp <= event.ended);
break;
}
if (match && !exclusions.find(e => e.zoneFk == event.zoneFk))
dayEvents.push(event);
}
}
if (dayEvents.length)
this.days[stamp] = dayEvents;
day.setDate(day.getDate() + 1);
}
}
onSelection($event, $days, $type, $weekday) {
let $events = [];
let $exclusions = [];
let $geoExclusions = [];
for (let day of $days) {
let stamp = day.getTime();
$events = $events.concat(this.days[stamp] || []);
$exclusions = $exclusions.concat(this.exclusions[stamp] || []);
$geoExclusions = $geoExclusions.concat(this.geoExclusions[stamp] || []);
}
this.emit('selection', {
$event,
$days,
$type,
$weekday,
$events,
$exclusions,
$geoExclusions
});
}
hasEvents(day) {
let stamp = day.getTime();
return this.days[stamp] || this.exclusions[stamp] || this.geoExclusions[stamp];
}
getClass(day) {
let stamp = day.getTime();
if (this.geoExclusions[stamp])
return 'geoExcluded';
else if (this.exclusions[stamp])
return 'excluded';
else return '';
}
}
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.vnComponent('vnZoneCalendar', {
template: require('./index.html'),
controller: Controller,
bindings: {
data: '<?'
}
});

View File

@ -1,172 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('component vnZoneCalendar', () => {
let $scope;
let controller;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
const $element = angular.element(`<vn-zone-calendar></vn-zone-calendar>`);
controller = $componentController('vnZoneCalendar', {$element, $scope});
controller.$.model = crudModel;
controller.zone = {id: 1};
controller.days = [];
controller.exclusions = [];
controller.geoExclusions = [];
}));
describe('date() setter', () => {
it('should set the month property and then call the refreshEvents() method', () => {
jest.spyOn(controller, 'refreshEvents').mockReturnThis();
controller.date = Date.vnNew();
expect(controller.refreshEvents).toHaveBeenCalledWith();
expect(controller.months.length).toEqual(4);
});
});
describe('step()', () => {
it('should set the date month to 4 months backwards', () => {
const now = Date.vnNew();
now.setDate(15);
now.setMonth(now.getMonth() - 4);
controller.step(-1);
const expectedMonth = now.getMonth();
const currentMonth = controller.date.getMonth();
expect(currentMonth).toEqual(expectedMonth);
});
it('should set the date month to 4 months forwards', () => {
const now = Date.vnNew();
now.setDate(15);
now.setMonth(now.getMonth() + 4);
controller.step(1);
const expectedMonth = now.getMonth();
const currentMonth = controller.date.getMonth();
expect(currentMonth).toEqual(expectedMonth);
});
});
describe('data() setter', () => {
it('should set the events, exclusions and geoExclusions and then call the refreshEvents() method', () => {
jest.spyOn(controller, 'refreshEvents').mockReturnThis();
controller.data = {
exclusions: [{
dated: Date.vnNew()
}],
events: [{
dated: Date.vnNew()
}],
geoExclusions: [{
dated: Date.vnNew()
}],
};
expect(controller.refreshEvents).toHaveBeenCalledWith();
expect(controller.events).toBeDefined();
expect(controller.events.length).toEqual(1);
expect(controller.exclusions).toBeDefined();
expect(controller.geoExclusions).toBeDefined();
expect(Object.keys(controller.exclusions).length).toEqual(1);
});
});
describe('refreshEvents()', () => {
it('should fill the days property with the events.', () => {
controller.data = [];
controller.firstDay = Date.vnNew();
const lastDay = Date.vnNew();
lastDay.setDate(lastDay.getDate() + 10);
controller.lastDay = lastDay;
const firstEventStamp = controller.firstDay.getTime();
const lastEventStamp = controller.lastDay.getTime();
controller.events = [{
type: 'day',
dated: firstEventStamp
},
{
type: 'day',
dated: lastEventStamp
}];
controller.refreshEvents();
const expectedDays = Object.keys(controller.days);
expect(expectedDays.length).toEqual(2);
});
});
describe('onSelection()', () => {
it('should call the emit() method', () => {
jest.spyOn(controller, 'emit');
const $event = {};
const $days = [Date.vnNew()];
const $type = 'day';
const $weekday = 1;
controller.onSelection($event, $days, $type, $weekday);
expect(controller.emit).toHaveBeenCalledWith('selection',
{
$days: $days,
$event: {},
$events: [],
$exclusions: [],
$type: 'day',
$weekday: 1,
$geoExclusions: [],
}
);
});
});
describe('hasEvents()', () => {
it('should return true for an existing event on a date', () => {
const dated = Date.vnNew();
controller.days[dated.getTime()] = true;
const result = controller.hasEvents(dated);
expect(result).toBeTruthy();
});
});
describe('getClass()', () => {
it('should return the className "excluded" for an excluded date', () => {
const dated = Date.vnNew();
controller.exclusions = [];
controller.exclusions[dated.getTime()] = true;
const result = controller.getClass(dated);
expect(result).toEqual('excluded');
});
it('should return the className "geoExcluded" for a date with geo excluded', () => {
const dated = Date.vnNew();
controller.geoExclusions = [];
controller.geoExclusions[dated.getTime()] = true;
const result = controller.getClass(dated);
expect(result).toEqual('geoExcluded');
});
});
});

View File

@ -1,43 +0,0 @@
@import "variables";
vn-zone-calendar {
display: block;
& > vn-card {
& > .header {
display: flex;
align-items: center;
justify-content: space-between;
background-color: $color-main;
color: white;
font-weight: bold;
height: 45px;
& > .vn-button {
color: inherit;
height: 100%;
}
}
& > .calendars {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
& > .vn-calendar {
max-width: 288px;
#days-container .day {
&.event .day-number {
background-color: $color-success;
}
&.excluded .day-number {
background-color: $color-alert;
}
&.geoExcluded .day-number {
background-color: $color-main;
}
}
}
}
}
}

View File

@ -1,5 +0,0 @@
<vn-portal slot="menu">
<vn-zone-descriptor zone="$ctrl.zone"></vn-zone-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
let filter = {
include: {
relation: 'agencyMode',
scope: {fields: ['name']}
}
};
this.$http.get(`Zones/${this.$params.id}`, {filter})
.then(res => this.zone = res.data);
}
}
ngModule.vnComponent('vnZoneCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,26 +0,0 @@
import './index.js';
describe('Zone Component vnZoneCard', () => {
let controller;
let $httpBackend;
let data = {id: 1, name: 'fooName'};
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, _$httpBackend_, $stateParams) => {
$httpBackend = _$httpBackend_;
let $element = angular.element('<div></div>');
controller = $componentController('vnZoneCard', {$element});
$stateParams.id = data.id;
$httpBackend.whenRoute('GET', 'Zones/:id').respond(data);
}));
it('should request data and set it on the controller', () => {
controller.reload();
$httpBackend.flush();
expect(controller.zone).toEqual(data);
});
});

View File

@ -1,98 +0,0 @@
<vn-watcher
vn-id="watcher"
url="Zones"
data="$ctrl.zone"
insert-mode="true"
form="form">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses"
order="name">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="AgencyModes/isActive"
data="activeAgencyModes"
order="name">
</vn-crud-model>
<form
name="form"
vn-http-submit="$ctrl.onSubmit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Name"
ng-model="$ctrl.zone.name"
rule
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
label="Warehouse"
ng-model="$ctrl.zone.warehouseFk"
data="warehouses"
show-field="name"
value-field="id"
label="Warehouse"
rule>
</vn-autocomplete>
<vn-autocomplete
label="Agency"
ng-model="$ctrl.zone.agencyModeFk"
data="activeAgencyModes"
show-field="name"
value-field="id"
label="Agency"
rule>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Traveling days"
ng-model="$ctrl.zone.travelingDays"
min="0"
step="1"
rule>
</vn-input-number>
<vn-input-time
label="Closing hour"
ng-model="$ctrl.zone.hour"
rule>
</vn-input-time>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Price"
ng-model="$ctrl.zone.price"
min="0"
step="0.01"
rule>
</vn-input-number>
<vn-input-number
label="Bonus"
ng-model="$ctrl.zone.bonus"
min="0"
step="0.01"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-check ng-model="$ctrl.zone.isVolumetric" label="Volumetric"></vn-check>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="zone.index">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,24 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
$onInit() {
this.zone = {
travelingDays: 0,
price: 0.20,
bonus: 0.20,
hour: Date.vnNew()
};
}
onSubmit() {
return this.$.watcher.submit().then(res =>
this.$state.go('zone.card.location', {id: res.data.id})
);
}
}
ngModule.vnComponent('vnZoneCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,40 +0,0 @@
import './index';
import watcher from 'core/mocks/watcher';
describe('Zone Component vnZoneCreate', () => {
let $scope;
let $state;
let controller;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = watcher;
$scope.watcher.submit = () => {
return {
then: callback => {
callback({data: {id: 1234}});
}
};
};
const $element = angular.element('<vn-zone-create></vn-zone-create>');
controller = $componentController('vnZoneCreate', {$element, $scope});
}));
describe('onSubmit()', () => {
it(`should call submit() on the watcher then expect a callback`, () => {
jest.spyOn($state, 'go');
controller.zone = {
name: 'Zone One'
};
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('zone.card.location', Object({id: 1234}));
});
});
});

View File

@ -1,3 +0,0 @@
Traveling days: Días de viaje
Closing hour: Hora de cierre
Bonus: Bonificación

View File

@ -1,102 +0,0 @@
<div class="vn-w-md">
<vn-zone-calendar
data="data"
on-selection="$ctrl.onSelection($event, $events, $days)">
</vn-zone-calendar>
</div>
<vn-side-menu side="right">
<form ng-submit="$ctrl.fetchData()" class="vn-pa-md">
<vn-radio
label="Pick up"
val="pickUp"
ng-model="$ctrl.deliveryMethodFk"
on-change="$ctrl.agencyModeFk = null"
tabindex="-1">
</vn-radio>
<vn-radio
label="Delivery"
val="delivery"
ng-model="$ctrl.deliveryMethodFk"
on-change="$ctrl.agencyModeFk = null"
class="vn-mb-sm"
tabindex="-1">
</vn-radio>
<vn-autocomplete
vn-one
ng-if="$ctrl.deliveryMethodFk === 'delivery'"
vn-focus
label="Postcode"
ng-model="$ctrl.geoFk"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="geoFk"
show-field="code">
<tpl-item>
<div>
{{code}} {{town.name}}
</div>
<div class="text-caption text-secondary">
{{town.province.name}}, {{town.province.country.name}}
</div>
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
label="{{$ctrl.deliveryMethodFk == 'delivery' ? 'Agency' : 'Warehouse'}}"
ng-model="$ctrl.agencyModeFk"
url="AgencyModes/isActive"
where="$ctrl.agencyFilter"
vn-id="agencymode">
</vn-autocomplete>
<vn-submit label="Query" class="vn-mt-sm"></vn-submit>
</form>
</vn-side-menu>
<!-- Zone Popover -->
<vn-popover vn-id="zoneEvents">
<div class="zoneEvents">
<div class="header vn-pa-sm" translate>Zones</div>
<vn-data-viewer
data="::$ctrl.zoneClosing"
class="vn-w-md vn-mb-xl">
<vn-card>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="name" expand>Name</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="hour" shrink>Closing</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="zone in $ctrl.zoneClosing"
ui-sref="zone.card.summary({id: zone.id})"
class="clickable search-result">
<vn-td number>{{::zone.id}}</vn-td>
<vn-td expand>{{::zone.name}}</vn-td>
<vn-td>{{::zone.agencyModeName}}</vn-td>
<vn-td shrink>{{::zone.hour | date: 'HH:mm'}}</vn-td>
<vn-td number>{{::zone.price | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
vn-click-stop="$ctrl.preview(zone)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
</div>
</vn-popover>
<vn-popup vn-id="summary">
<vn-zone-summary zone="$ctrl.selectedZone"></vn-zone-summary>
</vn-popup>

View File

@ -1,95 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
$onInit() {
this.setParams();
}
$postLink() {
this.deliveryMethodFk = 'delivery';
}
setParams() {
const hasParams = this.$params.deliveryMethodFk || this.$params.geoFk || this.$params.agencyModeFk;
if (hasParams) {
if (this.$params.deliveryMethodFk)
this.deliveryMethodFk = this.$params.deliveryMethodFk;
if (this.$params.geoFk)
this.geoFk = this.$params.geoFk;
if (this.$params.agencyModeFk)
this.agencyModeFk = this.$params.agencyModeFk;
this.fetchData();
}
}
fetchData() {
const params = {
deliveryMethodFk: this.deliveryMethodFk,
geoFk: this.geoFk,
agencyModeFk: this.agencyModeFk
};
this.$.data = null;
this.$http.get(`Zones/getEvents`, {params})
.then(res => {
let data = res.data;
this.$.data = data;
if (!data.events.length)
this.vnApp.showMessage(this.$t('No service for the specified zone'));
this.$state.go(this.$state.current.name, params);
});
}
get deliveryMethodFk() {
return this._deliveryMethodFk;
}
set deliveryMethodFk(value) {
this._deliveryMethodFk = value;
let filter;
if (value === 'pickUp')
filter = {where: {code: 'PICKUP'}};
else
filter = {where: {code: {inq: ['DELIVERY', 'AGENCY']}}};
this.$http.get(`DeliveryMethods`, {filter}).then(res => {
const deliveryMethods = res.data.map(deliveryMethod => deliveryMethod.id);
this.agencyFilter = {deliveryMethodFk: {inq: deliveryMethods}};
});
}
onSelection($event, $events, $days) {
if (!$events.length) return;
const day = $days[0];
const zoneIds = [];
for (let event of $events)
zoneIds.push(event.zoneFk);
const params = {
zoneIds: zoneIds,
date: day
};
this.$http.post(`Zones/getZoneClosing`, params)
.then(res => this.zoneClosing = res.data)
.then(() => this.$.zoneEvents.show($event.target));
}
preview(zone) {
this.selectedZone = zone;
this.$.summary.show();
}
}
ngModule.vnComponent('vnZoneDeliveryDays', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,126 +0,0 @@
import './index.js';
import popover from 'core/mocks/popover';
import crudModel from 'core/mocks/crud-model';
describe('Zone Component vnZoneDeliveryDays', () => {
let $httpBackend;
let controller;
let $element;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$element = angular.element('<vn-zone-delivery-days></vn-zone-delivery-days');
controller = $componentController('vnZoneDeliveryDays', {$element});
controller.$.zoneEvents = popover;
controller.$.params = {};
controller.$.zoneModel = crudModel;
}));
describe('deliveryMethodFk() setter', () => {
it('should set the deliveryMethodFk property as pickup and then perform a query that sets the filter', () => {
$httpBackend.expect('GET', 'DeliveryMethods').respond([{id: 999}]);
controller.deliveryMethodFk = 'pickUp';
$httpBackend.flush();
expect(controller.agencyFilter).toEqual({deliveryMethodFk: {inq: [999]}});
});
});
describe('setParams()', () => {
it('should do nothing when no params are received', () => {
controller.setParams();
expect(controller.deliveryMethodFk).toBeUndefined();
expect(controller.geoFk).toBeUndefined();
expect(controller.agencyModeFk).toBeUndefined();
});
it('should set the controller properties when the params are provided', () => {
controller.$params = {
deliveryMethodFk: 3,
geoFk: 2,
agencyModeFk: 1
};
controller.setParams();
expect(controller.deliveryMethodFk).toEqual(controller.$params.deliveryMethodFk);
expect(controller.geoFk).toEqual(controller.$params.geoFk);
expect(controller.agencyModeFk).toEqual(controller.$params.agencyModeFk);
});
});
describe('fetchData()', () => {
it('should make an HTTP GET query and then call the showMessage() method', () => {
jest.spyOn(controller.vnApp, 'showMessage');
jest.spyOn(controller.$state, 'go');
controller.agencyModeFk = 1;
controller.deliveryMethodFk = 2;
controller.geoFk = 3;
controller.$state.current.name = 'myState';
const expectedData = {events: []};
const url = 'Zones/getEvents?agencyModeFk=1&deliveryMethodFk=2&geoFk=3';
$httpBackend.when('GET', 'DeliveryMethods').respond([]);
$httpBackend.expect('GET', url).respond({events: []});
controller.fetchData();
$httpBackend.flush();
expect(controller.$.data).toEqual(expectedData);
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('No service for the specified zone');
expect(controller.$state.go).toHaveBeenCalledWith(
controller.$state.current.name,
{
agencyModeFk: 1,
deliveryMethodFk: 2,
geoFk: 3
}
);
});
});
describe('onSelection()', () => {
it('should not call the show popover method if events array is empty', () => {
jest.spyOn(controller.$.zoneEvents, 'show');
const event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent(event);
const events = [];
controller.onSelection(event, events);
expect(controller.$.zoneEvents.show).not.toHaveBeenCalled();
});
it('should call the show() method and call getZoneClosing() with the expected ids', () => {
jest.spyOn(controller.$.zoneEvents, 'show');
const event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent(event);
const day = Date.vnNew();
const events = [
{zoneFk: 1},
{zoneFk: 2},
{zoneFk: 8}
];
const params = {
zoneIds: [1, 2, 8],
date: [day][0]
};
const response = [{id: 1, hour: ''}];
$httpBackend.when('POST', 'Zones/getZoneClosing', params).respond({response});
controller.onSelection(event, events, [day]);
$httpBackend.flush();
expect(controller.$.zoneEvents.show).toHaveBeenCalledWith(target);
expect(controller.zoneClosing.id).toEqual(response.id);
});
});
});

View File

@ -1,36 +0,0 @@
@import "variables";
vn-zone-delivery-days {
vn-zone-calendar {
display: flex;
justify-content: center;
flex-wrap: wrap;
& > vn-calendar {
min-width: 264px;
}
}
form {
display: flex;
flex-direction: column;
}
}
.zoneEvents {
width: 700px;
max-height: 450px;
vn-data-viewer {
margin-bottom: 0;
vn-pagination {
padding: 0
}
}
& > .header {
background-color: $color-main;
color: white;
font-weight: bold;
text-align: center
}
}

View File

@ -1,4 +0,0 @@
<slot-descriptor>
<vn-zone-descriptor>
</vn-zone-descriptor>
</slot-descriptor>

View File

@ -1,9 +0,0 @@
import ngModule from '../module';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover {}
ngModule.vnComponent('vnZoneDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: Controller
});

View File

@ -1,57 +0,0 @@
<vn-descriptor-content
module="zone"
description="$ctrl.zone.name"
summary="$ctrl.$.summary">
<slot-menu>
<vn-item class="vn-item"
ng-click="$ctrl.onDelete()"
translate>
Delete
</vn-item>
<vn-item
ng-click="clone.show()"
name="cloneZone"
translate>
Clone
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<vn-label-value
label="Agency"
value="{{$ctrl.zone.agencyMode.name}}">
</vn-label-value>
<vn-label-value
label="Closing hour"
value="{{$ctrl.zone.hour | date: 'HH:mm'}}">
</vn-label-value>
<vn-label-value
label="Traveling days"
value="{{$ctrl.zone.travelingDays}}">
</vn-label-value>
<vn-label-value
label="Price"
value="{{$ctrl.zone.price | currency: 'EUR': 2}}">
</vn-label-value>
<vn-label-value
label="Bonus"
value="{{$ctrl.zone.bonus | currency: 'EUR': 2}}">
</vn-label-value>
</div>
</slot-body>
</vn-descriptor-content>
<vn-popup vn-id="summary">
<vn-zone-summary zone="$ctrl.zone"></vn-zone-summary>
</vn-popup>
<vn-confirm
vn-id="deleteZone"
on-accept="$ctrl.deleteZone()"
message="This zone will be removed">
</vn-confirm>
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept()"
question="Do you want to clone this zone?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -1,65 +0,0 @@
import ngModule from '../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get zone() {
return this.entity;
}
set zone(value) {
this.entity = value;
}
loadData() {
const filter = {
include: [
{
relation: 'agencyMode',
scope: {
fields: ['name'],
}
}
]
};
return this.getData(`Zones/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
onDelete() {
const $t = this.$translate.instant;
const today = Date.vnNew();
today.setHours(0, 0, 0, 0);
const filter = {where: {zoneFk: this.id, shipped: {gte: today}}};
this.$http.get(`Tickets`, {filter}).then(res => {
const ticketsAmount = res.data.length;
if (ticketsAmount) {
const params = {ticketsAmount};
const question = $t('This zone contains tickets', params, null, null, 'sanitizeParameters');
this.$.deleteZone.question = question;
this.$.deleteZone.show();
} else
this.deleteZone();
});
}
deleteZone() {
return this.$http.post(`Zones/${this.id}/deleteZone`).then(() => {
this.$state.go('zone.index');
this.vnApp.showSuccess(this.$t('Zone deleted'));
});
}
onCloneAccept() {
return this.$http.post(`Zones/${this.id}/clone`).
then(res => this.$state.go('zone.card.basicData', {id: res.data.id}));
}
}
ngModule.vnComponent('vnZoneDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
}
});

View File

@ -1,74 +0,0 @@
import './index.js';
describe('Zone descriptor', () => {
let $httpBackend;
let controller;
let $element;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$element = angular.element('<vn-zone-descriptor></vn-zone-descriptor');
controller = $componentController('vnZoneDescriptor', {$element});
controller.zone = {id: 1};
controller.id = 1;
controller.$.deleteZone = {
hide: () => {},
show: () => {}
};
}));
describe('onDelete()', () => {
it('should make an HTTP POST query and then call the deleteZone show() method', () => {
jest.spyOn(controller.$.deleteZone, 'show');
const expectedData = [{id: 16}];
$httpBackend.when('GET', 'Tickets').respond(expectedData);
controller.onDelete();
$httpBackend.flush();
expect(controller.$.deleteZone.show).toHaveBeenCalledWith();
});
it('should make an HTTP POST query and then call the deleteZone() method', () => {
jest.spyOn(controller, 'deleteZone').mockReturnThis();
const expectedData = [];
$httpBackend.when('GET', 'Tickets').respond(expectedData);
controller.onDelete();
$httpBackend.flush();
expect(controller.deleteZone).toHaveBeenCalledWith();
});
});
describe('deleteZone()', () => {
it('should make an HTTP POST query and then call the showMessage() method', () => {
jest.spyOn(controller.$state, 'go').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess');
const stateName = 'zone.index';
$httpBackend.when('POST', 'Zones/1/deleteZone').respond(200);
controller.deleteZone();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith(stateName);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Zone deleted');
});
});
describe('onCloneAccept()', () => {
it('should make an HTTP POST query and then call the state go() method', () => {
jest.spyOn(controller.$state, 'go').mockReturnThis();
const stateName = 'zone.card.basicData';
const expectedData = {id: 1};
$httpBackend.when('POST', 'Zones/1/clone').respond(expectedData);
controller.onCloneAccept();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith(stateName, expectedData);
});
});
});

View File

@ -1,4 +0,0 @@
This zone contains tickets: Esta zona contiene {{ticketsAmount}} tickets por servir. ¿Seguro que quieres eliminar esta zona?
Do you want to clone this zone?: ¿Quieres clonar esta zona?
All it's properties will be copied: Todas sus propiedades serán copiadas
Zone deleted: Zona eliminada

View File

@ -1,277 +0,0 @@
<vn-zone-calendar
id="calendar"
vn-id="calendar"
data="data"
on-selection="$ctrl.onSelection($days, $type, $weekday, $events, $exclusions, $geoExclusions)"
on-step="$ctrl.refresh()"
class="vn-w-md">
</vn-zone-calendar>
<vn-side-menu side="right">
<div class="vn-pa-md">
<h6
class="text-secondary"
style="font-weight: normal;"
translate>
Edit mode
</h6>
<vn-vertical>
<vn-radio
label="Include"
val="include"
ng-model="$ctrl.editMode">
</vn-radio>
<vn-radio
label="Exclude"
val="exclude"
ng-model="$ctrl.editMode">
</vn-radio>
</vn-vertical>
</div>
<h6
class="text-secondary vn-px-md"
style="font-weight: normal;"
translate>
Events
</h6>
<vn-data-viewer
data="data.events"
is-loading="!data.events">
<div class="vn-list separated">
<a
ng-repeat="row in data.events"
translate-attr="{title: 'Edit'}"
ng-click="$ctrl.onEditClick(row, $event)"
class="vn-item">
<vn-item-section>
<div
ng-if="::row.type == 'day'"
class="vn-mb-sm">
{{::row.dated | date:'dd/MM/yy'}}
</div>
<div
ng-if="::row.type != 'day'"
class="vn-mb-sm ellipsize">
<span ng-if="row.weekDays">
{{::$ctrl.formatWdays(row.weekDays)}}
</span>
<span ng-if="row.type == 'range'">
({{::row.started | date:'dd/MM/yy'}} - {{::row.ended | date:'dd/MM/yy'}})
</span>
</div>
<vn-label-value
label="Closing"
value="{{::row.hour | date:'HH:mm'}}">
</vn-label-value>
<vn-label-value
label="Traveling days"
value="{{::row.travelingDays}}">
</vn-label-value>
<vn-label-value
label="Price"
value="{{::row.price | currency:'EUR':2}}">
</vn-label-value>
<vn-label-value
label="Bonus"
value="{{::row.bonus | currency:'EUR':2}}">
</vn-label-value>
<vn-label-value
label="Max m³"
value="{{::row.m3Max}}">
</vn-label-value>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
icon="delete"
translate-attr="{title: 'Delete'}"
ng-click="$ctrl.onDeleteClick(row.id, $event)">
</vn-icon-button>
</vn-item-section>
</a>
</div>
</vn-data-viewer>
</vn-side-menu>
<vn-float-button
ng-click="$ctrl.createInclusion('weekday')"
icon="add"
vn-tooltip="Add event"
vn-bind="+"
fixed-bottom-right>
</vn-float-button>
<vn-dialog
vn-id="includeDialog"
on-response="$ctrl.onIncludeResponse($response)"
message="{{$ctrl.isNew ? 'Add event' : 'Edit event'}}">
<tpl-body>
<vn-vertical>
<vn-vertical class="vn-pb-md">
<vn-radio
ng-model="$ctrl.selected.type"
label="One day"
val="day">
</vn-radio>
<vn-radio
ng-model="$ctrl.selected.type"
label="Indefinitely"
val="indefinitely">
</vn-radio>
<vn-radio
ng-model="$ctrl.selected.type"
label="Range of dates"
val="range">
</vn-radio>
</vn-vertical>
<vn-wday-picker
ng-if="$ctrl.selected.type != 'day'"
ng-model="$ctrl.selected.wdays"
class="vn-mt-sm vn-mb-md">
</vn-wday-picker>
<vn-date-picker
ng-if="$ctrl.selected.type == 'day'"
label="Day"
ng-model="$ctrl.selected.dated">
</vn-date-picker>
<vn-horizontal
ng-if="$ctrl.selected.type == 'range'">
<vn-date-picker
label="From"
ng-model="$ctrl.selected.started">
</vn-date-picker>
<vn-date-picker
label="To"
ng-model="$ctrl.selected.ended">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-input-time
label="Closing"
ng-model="$ctrl.selected.hour">
</vn-input-time>
<vn-input-number
label="Traveling days"
ng-model="$ctrl.selected.travelingDays"
min="0"
step="1">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Price"
ng-model="$ctrl.selected.price"
min="0"
step="0.01">
</vn-input-number>
<vn-input-number
label="Bonus"
ng-model="$ctrl.selected.bonus"
min="0"
step="0.01">
</vn-input-number>
</vn-horizontal>
<vn-input-number
label="Max m³"
ng-model="$ctrl.selected.m3Max"
min="0"
step="0.01">
</vn-input-number>
</vn-vertical>
</tpl-body>
<tpl-buttons>
<input
type="button"
response="cancel"
translate-attr="{value: 'Cancel'}">
</input>
<input
type="button"
ng-if="!$ctrl.isNew"
response="delete"
translate-attr="{value: 'Delete'}">
</input>
<button response="accept">
<span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span>
</button>
</tpl-buttons>
</vn-dialog>
<vn-confirm
vn-id="confirm"
message="This item will be deleted"
question="Are you sure you want to continue?">
</vn-confirm>
<vn-dialog
vn-id="excludeDialog"
on-response="$ctrl.onExcludeResponse($response)"
message="{{$ctrl.isNew ? 'Add exclusion' : 'Edit exclusion'}}"
on-open="$ctrl.onSearch($params)"
on-close="$ctrl.resetExclusions()">
<tpl-body>
<vn-date-picker
label="Day"
ng-model="$ctrl.excludeSelected.dated">
</vn-date-picker>
<vn-vertical class="width">
<vn-vertical class="vn-pb-md">
<vn-radio
ng-model="$ctrl.excludeSelected.type"
label="All"
on-change="$ctrl.test()"
val="all">
</vn-radio>
<vn-radio
ng-model="$ctrl.excludeSelected.type"
label="Specific locations"
on-change="$ctrl.onSearch($params)"
val="specificLocations">
</vn-radio>
</vn-vertical>
<vn-crud-model
vn-id="model"
url="Zones/{{$ctrl.$params.id}}/getLeaves"
filter="$ctrl.filter">
</vn-crud-model>
<div ng-if="$ctrl.excludeSelected.type == 'specificLocations'">
<vn-textfield
label="Search"
ng-keydown="$ctrl.onKeyDown($event)"
ng-model="$ctrl.excludeSearch">
<prepend>
<vn-icon icon="search"></vn-icon>
</prepend>
</vn-textfield>
<div class="treeview">
<vn-treeview
vn-id="treeview"
root-label="Locations where it is not distributed"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check
ng-model="item.checked"
ng-click="$event.preventDefault()"
on-change="$ctrl.onItemCheck(item.id, value)"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
</div>
</div>
</vn-vertical>
</tpl-body>
<tpl-buttons>
<input
type="button"
response="cancel"
translate-attr="{value: 'Cancel'}"
tabindex="0">
</input>
<input
type="button"
ng-if="!$ctrl.isNew"
response="delete"
translate-attr="{value: 'Delete'}"
tabindex="0">
</input>
<button response="accept">
<span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span>
</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,316 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $, vnWeekDays) {
super($element, $);
this.vnWeekDays = vnWeekDays;
this.editMode = 'include';
}
$onInit() {
this.refresh();
}
get path() {
return `Zones/${this.$params.id}/events`;
}
get exclusionsPath() {
return `Zones/${this.$params.id}/exclusions`;
}
get checked() {
const geos = this.$.model.data || [];
const checkedLines = [];
for (let geo of geos) {
if (geo.checked)
checkedLines.push(geo);
}
return checkedLines;
}
refresh() {
this.$.data = null;
this.$.$applyAsync(() => {
const params = {
zoneFk: this.$params.id,
started: this.$.calendar.firstDay,
ended: this.$.calendar.lastDay
};
this.$http.get(`Zones/getEventsFiltered`, {params}).then(res => {
const data = res.data;
this.$.data = data;
});
});
}
formatWdays(weekDays) {
if (!weekDays) return;
let abrWdays = weekDays
.split(',')
.map(wday => this.vnWeekDays.map[wday].localeAbr);
return abrWdays.length < 7
? abrWdays.join(', ')
: this.$t('Everyday');
}
onSelection(days, type, weekday, events, exclusions, exclusionGeos) {
if (this.editMode == 'include') {
if (events.length)
return this.editInclusion(events[0]);
return this.createInclusion(type, days, weekday);
} else if (this.editMode == 'exclude') {
if (exclusions.length || exclusionGeos.length)
return this.editExclusion(exclusions[0] || {}, exclusionGeos);
return this.createExclusion(days);
}
}
editExclusion(exclusion, exclusionGeos) {
this.isNew = false;
this.excludeSelected = angular.copy(exclusion);
this.excludeSelected.type = exclusionGeos.length ?
'specificLocations' : 'all';
this.exclusionGeos = new Set();
if (exclusionGeos.length) {
this.excludeSelected.id = exclusionGeos[0].zoneExclusionFk;
exclusionGeos.forEach(x => this.exclusionGeos.add(x.geoFk));
}
this.$.excludeDialog.show();
}
createExclusion(days) {
this.isNew = true;
this.excludeSelected = {
type: 'all',
dated: days[0]
};
this.exclusionGeos = new Set();
this.$.excludeDialog.show();
}
onEditClick(row, event) {
if (event.defaultPrevented) return;
this.editInclusion(row);
}
editInclusion(row) {
this.isNew = false;
this.selected = angular.copy(row);
this.selected.wdays = this.vnWeekDays.fromSet(row.weekDays);
this.$.includeDialog.show();
}
createInclusion(type, days, weekday) {
this.isNew = true;
if (type == 'weekday') {
let wdays = [];
if (weekday) wdays[weekday] = true;
this.selected = {
type: 'indefinitely',
wdays
};
} else {
this.selected = {
type: 'day',
dated: days[0]
};
}
this.$.includeDialog.show();
}
onIncludeResponse(response) {
switch (response) {
case 'accept': {
let selected = this.selected;
let type = selected.type;
selected.weekDays = this.vnWeekDays.toSet(selected.wdays);
if (type == 'day')
selected.weekDays = '';
else
selected.dated = null;
if (type != 'range') {
selected.started = null;
selected.ended = null;
}
let req;
if (this.isNew)
req = this.$http.post(this.path, selected);
else
req = this.$http.put(`${this.path}/${selected.id}`, selected);
return req.then(() => {
this.selected = null;
this.isNew = null;
this.refresh();
});
}
case 'delete':
return this.onDelete(this.selected.id)
.then(response => response == 'accept');
}
}
onExcludeResponse(response) {
const type = this.excludeSelected.type;
switch (response) {
case 'accept': {
if (type == 'all')
return this.exclusionCreate();
return this.exclusionGeoCreate();
}
case 'delete':
return this.exclusionDelete(this.excludeSelected);
}
}
onDeleteClick(id, event) {
if (event.defaultPrevented) return;
event.preventDefault();
this.onDelete(id);
}
onDelete(id) {
return this.$.confirm.show(
response => this.onDeleteResponse(response, id));
}
onDeleteResponse(response, id) {
if (response != 'accept' || !id) return;
return this.$http.delete(`${this.path}/${id}`)
.then(() => this.refresh());
}
exclusionCreate() {
const excludeSelected = this.excludeSelected;
const dated = excludeSelected.dated;
let req;
if (this.isNew)
req = this.$http.post(this.exclusionsPath, [{dated}]);
if (!this.isNew)
req = this.$http.put(`${this.exclusionsPath}/${excludeSelected.id}`, {dated});
return req.then(() => {
this.refresh();
});
}
exclusionGeoCreate() {
const excludeSelected = this.excludeSelected;
let req;
const geoIds = [];
this.exclusionGeos.forEach(id => geoIds.push(id));
if (this.isNew) {
const params = {
zoneFk: parseInt(this.$params.id),
date: excludeSelected.dated,
geoIds
};
req = this.$http.post(`Zones/exclusionGeo`, params);
} else {
const params = {
zoneExclusionFk: this.excludeSelected.id,
geoIds
};
req = this.$http.post(`Zones/updateExclusionGeo`, params);
}
return req.then(() => this.refresh());
}
exclusionDelete(exclusion) {
const path = `${this.exclusionsPath}/${exclusion.id}`;
return this.$http.delete(path)
.then(() => this.refresh());
}
set excludeSearch(value) {
this._excludeSearch = value;
if (!value) this.onSearch();
}
get excludeSearch() {
return this._excludeSearch;
}
onKeyDown(event) {
if (event.key == 'Enter') {
event.preventDefault();
this.onSearch();
}
}
onSearch() {
const params = {search: this._excludeSearch};
if (this.excludeSelected.type == 'specificLocations') {
this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.getChecked(data);
this.$.treeview.data = data;
});
}
}
onFetch(item) {
const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.getChecked(data);
return data;
});
}
onSort(a, b) {
if (b.selected !== a.selected) {
if (a.selected == null)
return 1;
if (b.selected == null)
return -1;
return b.selected - a.selected;
}
return a.name.localeCompare(b.name);
}
getChecked(data) {
for (let geo of data) {
geo.checked = this.exclusionGeos.has(geo.id);
if (geo.childs) this.getChecked(geo.childs);
}
}
onItemCheck(geoId, checked) {
if (checked)
this.exclusionGeos.add(geoId);
else
this.exclusionGeos.delete(geoId);
}
}
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.vnComponent('vnZoneEvents', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
},
require: {
card: '^vnZoneCard'
}
});

View File

@ -1,340 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('component vnZoneEvents', () => {
let $scope;
let controller;
let $httpBackend;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element(`<vn-zone-events></vn-zone-events>`);
controller = $componentController('vnZoneEvents', {$element, $scope});
controller.$params = {id: 1};
}));
describe('refresh()', () => {
it('should set the zone and then call both getSummary() and getWarehouses()', () => {
const date = '2021-10-01';
controller.$params.id = 999;
controller.$.calendar = {
firstDay: date,
lastDay: date
};
const params = {
zoneFk: controller.$params.id,
started: date,
ended: date
};
const query = `Zones/getEventsFiltered?ended=${date}&started=${date}&zoneFk=${params.zoneFk}`;
const response = {
events: 'myEvents',
exclusions: 'myExclusions',
geoExclusions: 'myGeoExclusions',
};
$httpBackend.whenGET(query).respond(response);
controller.refresh();
$httpBackend.flush();
const data = controller.$.data;
expect(data.events).toBeDefined();
expect(data.exclusions).toBeDefined();
});
});
describe('onSelection()', () => {
it('should call the editInclusion() method', () => {
jest.spyOn(controller, 'editInclusion').mockReturnThis();
const weekday = {};
const days = [];
const type = 'EventType';
const events = [{name: 'Event'}];
const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'include';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.editInclusion).toHaveBeenCalledWith({name: 'Event'});
});
it('should call the createInclusion() method', () => {
jest.spyOn(controller, 'createInclusion').mockReturnThis();
const weekday = {dated: Date.vnNew()};
const days = [weekday];
const type = 'EventType';
const events = [];
const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'include';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.createInclusion).toHaveBeenCalledWith(type, days, weekday);
});
it('should call the editExclusion() method with exclusions', () => {
jest.spyOn(controller, 'editExclusion').mockReturnThis();
const weekday = {};
const days = [];
const type = 'EventType';
const events = [];
const exclusions = [{name: 'Exclusion'}];
const exclusionsGeo = [];
controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.editExclusion).toHaveBeenCalled();
});
it('should call the editExclusion() method with exclusionsGeo', () => {
jest.spyOn(controller, 'editExclusion').mockReturnThis();
const weekday = {};
const days = [];
const type = 'EventType';
const events = [];
const exclusions = [];
const exclusionsGeo = [{name: 'GeoExclusion'}];
controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.editExclusion).toHaveBeenCalled();
});
it('should call the createExclusion() method', () => {
jest.spyOn(controller, 'createExclusion').mockReturnThis();
const weekday = {};
const days = [{dated: Date.vnNew()}];
const type = 'EventType';
const events = [];
const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.createExclusion).toHaveBeenCalledWith(days);
});
});
describe('editExclusion()', () => {
it('shoud set the excludeSelected.type = "specificLocations" and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const exclusionGeos = [{id: 1}];
const exclusions = [];
controller.editExclusion(exclusions, exclusionGeos);
expect(controller.excludeSelected.type).toEqual('specificLocations');
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
it('shoud set the excludeSelected.type = "all" and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const exclusionGeos = [];
const exclusions = [{id: 1}];
controller.editExclusion(exclusions, exclusionGeos);
expect(controller.excludeSelected.type).toEqual('all');
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
});
describe('createExclusion()', () => {
it('shoud set the excludeSelected property and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const days = [Date.vnNew()];
controller.createExclusion(days);
expect(controller.excludeSelected).toBeDefined();
expect(controller.isNew).toBeTruthy();
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
});
describe('createInclusion()', () => {
it('shoud set the selected property and then call the includeDialog show() method', () => {
controller.$.includeDialog = {show: jest.fn()};
const type = 'weekday';
const days = [Date.vnNew()];
const weekday = 1;
controller.createInclusion(type, days, weekday);
const selection = controller.selected;
const firstWeekday = selection.wdays[weekday];
expect(selection.type).toEqual('indefinitely');
expect(firstWeekday).toBeTruthy();
expect(controller.isNew).toBeTruthy();
expect(controller.$.includeDialog.show).toHaveBeenCalledWith();
});
it('shoud set the selected property with the first day and then call the includeDialog show() method', () => {
controller.$.includeDialog = {show: jest.fn()};
const type = 'nonListedType';
const days = [Date.vnNew()];
const weekday = 1;
controller.createInclusion(type, days, weekday);
const selection = controller.selected;
expect(selection.type).toEqual('day');
expect(selection.dated).toEqual(days[0]);
expect(controller.isNew).toBeTruthy();
expect(controller.$.includeDialog.show).toHaveBeenCalledWith();
});
});
describe('onIncludeResponse()', () => {
it('shoud call the onDelete() method', () => {
jest.spyOn(controller, 'onDelete').mockReturnValue(
new Promise(accept => accept())
);
controller.selected = {id: 1};
controller.onIncludeResponse('delete');
expect(controller.onDelete).toHaveBeenCalledWith(1);
});
it('shoud make an HTTP POST query to create a new one and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
controller.selected = {id: 1};
controller.isNew = true;
$httpBackend.when('POST', `Zones/1/events`).respond(200);
controller.onIncludeResponse('accept');
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
it('shoud make an HTTP PUT query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
controller.selected = {id: 1};
controller.isNew = false;
const eventId = 1;
$httpBackend.when('PUT', `Zones/1/events/${eventId}`).respond(200);
controller.onIncludeResponse('accept');
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('onExcludeResponse()', () => {
it('should call the exclusionCreate() method', () => {
jest.spyOn(controller, 'exclusionCreate').mockReturnThis();
controller.excludeSelected = {type: 'all'};
controller.onExcludeResponse('accept');
expect(controller.exclusionCreate).toHaveBeenCalledWith();
});
it('should call the exclusionGeoCreate() method', () => {
jest.spyOn(controller, 'exclusionGeoCreate').mockReturnThis();
controller.excludeSelected = {type: 'specificLocations'};
controller.onExcludeResponse('accept');
expect(controller.exclusionGeoCreate).toHaveBeenCalledWith();
});
it('should call the exclusionDelete() method', () => {
jest.spyOn(controller, 'exclusionDelete').mockReturnThis();
controller.excludeSelected = {id: 1, type: 'all'};
controller.onExcludeResponse('delete');
expect(controller.exclusionDelete).toHaveBeenCalledWith(controller.excludeSelected);
});
});
describe('onDeleteResponse()', () => {
it('shoud make an HTTP DELETE query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
const eventId = 1;
$httpBackend.expect('DELETE', `Zones/1/events/1`).respond({id: 1});
controller.onDeleteResponse('accept', eventId);
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('exclusionCreate()', () => {
it('shoud make an HTTP POST query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
controller.excludeSelected = {};
controller.isNew = true;
$httpBackend.expect('POST', `Zones/1/exclusions`).respond({id: 1});
controller.exclusionCreate();
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('exclusionDelete()', () => {
it('shoud make an HTTP DELETE query once and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
const exclusions = {id: 1};
const firstExclusionId = 1;
$httpBackend.expectDELETE(`Zones/1/exclusions/${firstExclusionId}`).respond(200);
controller.exclusionDelete(exclusions);
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('onSearch()', () => {
it('should call the applyFilter() method and then set the data', () => {
jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]);
controller.$.treeview = {};
controller.$.model = crudModel;
controller.excludeSelected = {type: 'specificLocations'};
controller._excludeSearch = 'es';
controller.onSearch();
const treeviewData = controller.$.treeview.data;
expect(treeviewData).toBeDefined();
expect(treeviewData.length).toEqual(3);
});
});
describe('onFetch()', () => {
it('should call the applyFilter() method and then return the model data', () => {
jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]);
controller.$.model = crudModel;
const result = controller.onFetch();
expect(result.length).toEqual(3);
});
});
});

View File

@ -1,12 +0,0 @@
Edit mode: Modo de edición
Include: Incluir
Exclude: Excluir
Events: Eventos
Add event: Añadir evento
Edit event: Editar evento
All: Todo
Specific locations: Localizaciones concretas
Locations where it is not distributed: Localizaciones en las que no se reparte
You must select a location: Debes seleccionar una localización
Add exclusion: Añadir exclusión
Edit exclusion: Editar exclusión

View File

@ -1,11 +0,0 @@
@import "variables";
.width{
width: 600px
}
.treeview{
max-height: 300px;
overflow: auto;
}

View File

@ -1,19 +1,3 @@
export * from './module';
import './main';
import './index/';
import './delivery-days';
import './summary';
import './card';
import './descriptor';
import './descriptor-popover';
import './search-panel';
import './create';
import './basic-data';
import './warehouses';
import './events';
import './calendar';
import './location';
import './calendar';
import './upcoming-deliveries';
import './log';

View File

@ -1,68 +0,0 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-w-md vn-mb-xl">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="name" expand>Name</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="hour" shrink>Closing</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a
ng-repeat="zone in model.data"
ui-sref="zone.card.summary({id: zone.id})"
class="clickable vn-tr search-result">
<vn-td number>{{::zone.id}}</vn-td>
<vn-td expand>{{::zone.name}}</vn-td>
<vn-td expand>{{::zone.agencyMode.name}}</vn-td>
<vn-td shrink>{{::zone.hour | date: 'HH:mm'}}</vn-td>
<vn-td number>{{::zone.price | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
vn-click-stop="clone.show(zone)"
vn-tooltip="Clone"
icon="icon-clone"
vn-acl="deliveryAssistant"
vn-acl-action="remove">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.preview(zone)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-popup vn-id="summary">
<vn-zone-summary zone="$ctrl.selectedZone"></vn-zone-summary>
</vn-popup>
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept($data)"
question="Do you want to clone this zone?"
message="All it's properties will be copied">
</vn-confirm>
<a ui-sref="zone.create"
vn-tooltip="New zone"
vn-bind="+"
fixed-bottom-right>
<vn-float-button
icon="add"
vn-acl="deliveryAssistant"
vn-acl-action="remove">
</vn-float-button>
</a>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
preview(zone) {
this.selectedZone = zone;
this.$.summary.show();
}
onCloneAccept(zone) {
return this.$http.post(`Zones/${zone.id}/clone`)
.then(res => {
this.$state.go('zone.card.basicData', {id: res.data.id});
});
}
}
ngModule.vnComponent('vnZoneIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,2 +0,0 @@
Do you want to clone this zone?: ¿Seguro que quieres clonar esta zona?
All it's properties will be copied: Todas sus propiedades serán copiadas

View File

@ -1,28 +0,0 @@
<vn-crud-model
vn-id="model"
url="Zones/{{$ctrl.$params.id}}/getLeaves"
filter="$ctrl.filter">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
on-search="$ctrl.onSearch($params)"
auto-state="false">
</vn-searchbar>
</vn-portal>
<div class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-treeview
vn-id="treeview"
root-label="Locations"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check acl-role="deliveryAssistant"
ng-model="item.selected"
on-change="$ctrl.onSelection(value, item)"
triple-state="true"
ng-click="$event.preventDefault()"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
</vn-card>
</div>

View File

@ -1,56 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
onSearch(params) {
this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.$.treeview.data = data;
});
}
onFetch(item) {
const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params)
.then(() => this.$.model.data);
}
onSort(a, b) {
if (b.selected !== a.selected) {
if (a.selected == null)
return 1;
if (b.selected == null)
return -1;
return b.selected - a.selected;
}
return a.name.localeCompare(b.name);
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return {name: {like: `%${value}%`}};
}
}
onSelection(value, item) {
if (value == null)
value = undefined;
const params = {geoId: item.id, isIncluded: value};
const path = `zones/${this.zone.id}/toggleIsIncluded`;
this.$http.post(path, params);
}
}
ngModule.vnComponent('vnZoneLocation', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
},
require: {
card: '^vnZoneCard'
}
});

View File

@ -1,50 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('component vnZoneLocation', () => {
let $scope;
let controller;
let $httpBackend;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element(`<vn-zone-location></vn-zone-location>`);
controller = $componentController('vnZoneLocation', {$element, $scope});
controller.$.model = crudModel;
controller.zone = {id: 1};
}));
describe('onSearch()', () => {
it('should call the applyFilter() method and then set the data', () => {
controller.$.treeview = {};
controller.onSearch({});
const treeviewData = controller.$.treeview.data;
expect(treeviewData).toBeDefined();
expect(treeviewData.length).toEqual(3);
});
});
describe('onFetch()', () => {
it('should call the applyFilter() method and then return the model data', () => {
const result = controller.onFetch();
expect(result.length).toEqual(3);
});
});
describe('onSelection()', () => {
it('should make an HTTP POST query', () => {
const item = {id: 123};
const expectedParams = {geoId: 123, isIncluded: true};
$httpBackend.expect('POST', `zones/1/toggleIsIncluded`, expectedParams).respond(200);
controller.onSelection(true, item);
$httpBackend.flush();
});
});
});

View File

@ -1,21 +0,0 @@
@import "variables";
vn-zone-location {
vn-treeview-child {
.content > .vn-check:not(.indeterminate):not(.checked) {
color: $color-alert;
& > .btn {
border-color: $color-alert;
}
}
.content > .vn-check.checked {
color: $color-notice;
& > .btn {
background-color: $color-notice;
border-color: $color-notice
}
}
}
}

View File

@ -1 +0,0 @@
<vn-log url="ZoneLogs" origin-id="$ctrl.$params.id"></vn-log>

View File

@ -1,7 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnZoneLog', {
template: require('./index.html'),
controller: Section,
});

View File

@ -1,19 +0,0 @@
<vn-crud-model
vn-id="model"
url="Zones"
filter="::$ctrl.filter"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
info="Search zone by id or name"
panel="vn-zone-search-panel"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -4,25 +4,10 @@ import ModuleMain from 'salix/components/module-main';
export default class Zone extends ModuleMain {
constructor($element, $) {
super($element, $);
this.filter = {
include: {
relation: 'agencyMode',
scope: {fields: ['name']}
}
};
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
case 'name':
return {[param]: {like: `%${value}%`}};
case 'agencyModeFk':
return {[param]: value};
}
async $onInit() {
this.$state.go('home');
window.location.href = await this.vnApp.getUrl(`zone/`);
}
}

View File

@ -1,30 +0,0 @@
import './index.js';
describe('Zone Component vnZone', () => {
let controller;
beforeEach(ngModule('zone'));
beforeEach(inject($componentController => {
const $element = angular.element('<vn-zone></vn-zone>');
controller = $componentController('vnZone', {$element});
}));
describe('exprBuilder()', () => {
it('should return a formated object with the id in case of search', () => {
let param = 'search';
let value = 1;
let result = controller.exprBuilder(param, value);
expect(result).toEqual({id: 1});
});
it('should return a formated object with the agencyModeFk in case of agencyModeFk', () => {
let param = 'agencyModeFk';
let value = 'My Delivery';
let result = controller.exprBuilder(param, value);
expect(result).toEqual({agencyModeFk: 'My Delivery'});
});
});
});

View File

@ -7,15 +7,6 @@
"menus": {
"main": [
{"state": "zone.index", "icon": "icon-zone"},
{"state": "zone.deliveryDays", "icon": "today"},
{"state": "zone.upcomingDeliveries", "icon": "today"}
],
"card": [
{"state": "zone.card.basicData", "icon": "settings"},
{"state": "zone.card.location", "icon": "my_location"},
{"state": "zone.card.warehouses", "icon": "home"},
{"state": "zone.card.log", "icon": "history"},
{"state": "zone.card.events", "icon": "today"}
]
},
"keybindings": [
@ -34,79 +25,6 @@
"state": "zone.index",
"component": "vn-zone-index",
"description": "Zones"
},
{
"url": "/delivery-days?q&deliveryMethodFk&geoFk&agencyModeFk",
"state": "zone.deliveryDays",
"component": "vn-zone-delivery-days",
"description": "Delivery days"
},
{
"url": "/upcoming-deliveries",
"state": "zone.upcomingDeliveries",
"component": "vn-upcoming-deliveries",
"description": "Upcoming deliveries"
},
{
"url": "/create",
"state": "zone.create",
"component": "vn-zone-create",
"description": "New zone"
},
{
"url": "/:id",
"state": "zone.card",
"component": "vn-zone-card",
"abstract": true,
"description": "Detail"
},
{
"url": "/summary",
"state": "zone.card.summary",
"component": "vn-zone-summary",
"description": "Summary",
"params": {
"zone": "$ctrl.zone"
}
},
{
"url": "/basic-data",
"state": "zone.card.basicData",
"component": "vn-zone-basic-data",
"description": "Basic data",
"params": {
"zone": "$ctrl.zone"
}
},
{
"url": "/warehouses",
"state": "zone.card.warehouses",
"component": "vn-zone-warehouses",
"description": "Warehouses"
},
{
"url": "/events?q",
"state": "zone.card.events",
"component": "vn-zone-events",
"description": "Calendar",
"params": {
"zone": "$ctrl.zone"
}
},
{
"url": "/location?q",
"state": "zone.card.location",
"component": "vn-zone-location",
"description": "Locations",
"params": {
"zone": "$ctrl.zone"
}
},
{
"url" : "/log",
"state": "zone.card.log",
"component": "vn-zone-log",
"description": "Log"
}
]
}

View File

@ -1,34 +0,0 @@
<div class="search-panel">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="General search"
ng-model="filter.search"
info="Search zone by id or name"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="filter.name">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Agency"
ng-model="filter.agencyModeFk"
url="AgencyModes/isActive"
where="{deliveryMethodFk: {neq: null}}"
value-field="id"
show-field="name">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,7 +0,0 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
ngModule.vnComponent('vnZoneSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
});

View File

@ -1,59 +0,0 @@
<vn-card class="summary">
<h5>
<a
ng-if="::$ctrl.summary.id"
vn-tooltip="Go to the zone"
ui-sref="zone.card.summary({id: {{::$ctrl.summary.id}}})"
name="goToSummary">
<vn-icon-button icon="launch"></vn-icon-button>
</a>
<span>#{{$ctrl.summary.id}} - {{$ctrl.summary.name}}</span>
</h5>
<vn-horizontal class="vn-pa-md">
<vn-one>
<vn-label-value label="Agency"
value="{{$ctrl.summary.agencyMode.name}}">
</vn-label-value>
<vn-label-value label="Price"
value="{{$ctrl.summary.price | currency: 'EUR': 2}}">
</vn-label-value>
<vn-label-value label="Bonus"
value="{{$ctrl.summary.bonus | currency: 'EUR': 2}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-label-value label="Closing hour"
value="{{$ctrl.summary.hour | date: 'HH:mm'}}">
</vn-label-value>
<vn-label-value label="Traveling days"
value="{{$ctrl.summary.travelingDays}}">
</vn-label-value>
<vn-vertical>
<vn-check label="Volumetric" disabled="true"
ng-model="$ctrl.summary.isVolumetric">
</vn-check>
</vn-vertical>
</vn-one>
</vn-horizontal>
<vn-horizontal class="vn-pa-md">
<vn-auto>
<h4>
<a ui-sref="zone.card.warehouses({id:$ctrl.zone.id})">
<span translate vn-tooltip="Go to">Warehouse</span>
</a>
</h4>
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th>Name</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="zoneWarehouse in $ctrl.zoneWarehouses">
<vn-td>{{zoneWarehouse.warehouse.name}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-auto>
</vn-horizontal>
</vn-card>

View File

@ -1,56 +0,0 @@
import ngModule from '../module';
import Summary from 'salix/components/summary';
class Controller extends Summary {
get zone() {
return this._zone;
}
set zone(value) {
this._zone = value;
if (!value) return;
this.getSummary();
this.getWarehouses();
}
getSummary() {
const params = {
filter: {
include: {
relation: 'agencyMode',
fields: ['name']
},
where: {
id: this.zone.id
}
}
};
this.$http.get(`Zones/findOne`, {params}).then(res => {
this.summary = res.data;
});
}
getWarehouses() {
const params = {
filter: {
include: {
relation: 'warehouse',
fields: ['name']
}
}
};
this.$http.get(`Zones/${this.zone.id}/warehouses`, {params}).then(res => {
this.zoneWarehouses = res.data;
});
}
}
ngModule.vnComponent('vnZoneSummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
}
});

View File

@ -1,76 +0,0 @@
import './index';
describe('component vnZoneSummary', () => {
let $scope;
let controller;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$scope = $rootScope.$new();
const $element = angular.element(`<vn-zone-summary></vn-zone-summary>`);
controller = $componentController('vnZoneSummary', {$element, $scope});
}));
describe('zone setter', () => {
it('should set the zone and then call both getSummary() and getWarehouses()', () => {
jest.spyOn(controller, 'getSummary');
jest.spyOn(controller, 'getWarehouses');
controller.zone = {id: 1};
expect(controller.getSummary).toHaveBeenCalledWith();
expect(controller.getWarehouses).toHaveBeenCalledWith();
});
});
describe('getSummary()', () => {
it('should perform a get and then store data on the controller', () => {
controller._zone = {id: 1};
let params = {
filter: {
include: {
relation: 'agencyMode',
fields: ['name']
},
where: {
id: controller._zone.id
}
}
};
const serializedParams = $httpParamSerializer(params);
const query = `Zones/findOne?${serializedParams}`;
$httpBackend.expectGET(query).respond({id: 1});
controller.getSummary();
$httpBackend.flush();
expect(controller.summary).toBeDefined();
});
});
describe('getWarehouses()', () => {
it('should make an HTTP get query and then store data on the controller', () => {
controller._zone = {id: 1};
const params = {
filter: {
include: {
relation: 'warehouse',
fields: ['name']
}
}
};
const serializedParams = $httpParamSerializer(params);
const query = `Zones/1/warehouses?${serializedParams}`;
$httpBackend.expect('GET', query).respond([{id: 1}]);
controller.getWarehouses();
$httpBackend.flush();
expect(controller.zoneWarehouses.length).toEqual(1);
});
});
});

View File

@ -1,31 +0,0 @@
<vn-crud-model
vn-id="model"
url="Zones/getUpcomingDeliveries"
data="details"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card>
<section ng-repeat="detail in details" class="vn-pa-md">
<vn-horizontal class="header">
<h5>{{$ctrl.getWeekDay(detail.shipped)}} - {{detail.shipped | date: 'dd/MM/yyyy'}}</h5>
</vn-horizontal>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th class="waste-family">Province</vn-th>
<vn-th number>Closing</vn-th>
<vn-th number>Id</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="zone in detail.lines">
<vn-td class="waste-family">{{::zone.name}}</vn-td>
<vn-td number>{{::zone.hour}}</vn-td>
<vn-td number>{{::zone.zoneFk}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</section>
</vn-card>
</vn-data-viewer>

View File

@ -1,23 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $, vnWeekDays) {
super($element, $);
this.days = vnWeekDays.days;
}
getWeekDay(jsonDate) {
const weekDay = new Date(jsonDate).getDay();
return this.days[weekDay].locale;
}
}
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.vnComponent('vnUpcomingDeliveries', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,22 +0,0 @@
import './index';
describe('component vnUpcomingDeliveries', () => {
let $scope;
let controller;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
const $element = angular.element(`<vn-upcoming-deliveries></vn-upcoming-deliveries>`);
controller = $componentController('vnUpcomingDeliveries', {$element, $scope});
}));
describe('getWeekDay()', () => {
it('should retrieve a weekday for a json passed', () => {
let jsonDate = '1970-01-01T22:00:00.000Z';
expect(controller.getWeekDay(jsonDate)).toEqual('Thursday');
});
});
});

View File

@ -1,3 +0,0 @@
Family: Familia
Percentage: Porcentaje
Dwindle: Mermas

View File

@ -1,26 +0,0 @@
@import "variables";
vn-upcoming-deliveries {
.header {
margin-bottom: 16px;
text-transform: uppercase;
font-size: 1.25rem;
line-height: 1;
padding: 7px;
padding-bottom: 7px;
padding-bottom: 4px;
font-weight: lighter;
background-color: $color-main-light;
border-bottom: 1px solid $color-primary;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background-color: $color-bg;
}
vn-table vn-th.waste-family,
vn-table vn-td.waste-family {
max-width: 64px;
width: 64px
}
}

View File

@ -1,57 +0,0 @@
<vn-data-viewer
data="data"
is-loading="!data"
class="vn-w-xs">
<vn-card>
<vn-table>
<vn-tbody>
<vn-tr ng-repeat="row in data | orderBy:'warehouse.name'">
<vn-td>{{::row.warehouse.name}}</vn-td>
<vn-td shrink>
<vn-icon-button
icon="delete"
translate-attr="{title: 'Delete'}"
ng-click="$ctrl.onDelete(row)">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-float-button
icon="add"
translate-attr="{title: 'Add'}"
vn-bind="+"
ng-click="$ctrl.onCreate()"
fixed-bottom-right>
</vn-float-button>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses"
order="name">
</vn-crud-model>
<vn-dialog
vn-id="dialog"
on-accept="$ctrl.onSave()">
<tpl-body>
<vn-autocomplete
ng-model="$ctrl.selected.warehouseFk"
data="warehouses"
show-field="name"
value-field="id"
label="Warehouse">
</vn-autocomplete>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
<vn-confirm
vn-id="confirm"
message="This item will be deleted"
question="Are you sure you want to continue?"
on-accept="$ctrl.delete()">
</vn-confirm>

View File

@ -1,56 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
$onInit() {
this.refresh();
}
get path() {
return `Zones/${this.$params.id}/warehouses`;
}
refresh() {
let filter = {include: 'warehouse'};
this.$http.get(this.path, {params: {filter}})
.then(res => this.$.data = res.data);
}
onCreate() {
this.selected = {};
this.$.dialog.show();
}
onSave() {
this.$http.post(this.path, this.selected)
.then(() => {
this.selected = null;
this.isNew = null;
this.$.dialog.hide();
this.refresh();
});
return false;
}
onDelete(row) {
this.$.confirm.show();
this.deleteRow = row;
}
delete() {
let row = this.deleteRow;
if (!row) return;
return this.$http.delete(`${this.path}/${row.id}`)
.then(() => {
let index = this.$.data.indexOf(row);
if (index !== -1) this.$.data.splice(index, 1);
this.deleteRow = null;
});
}
}
ngModule.vnComponent('vnZoneWarehouses', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,60 +0,0 @@
import './index.js';
describe('Zone warehouses', () => {
let $httpBackend;
let $httpParamSerializer;
let controller;
let $element;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$element = angular.element('<vn-zone-warehouses></vn-zone-warehouses');
controller = $componentController('vnZoneWarehouses', {$element});
controller.zone = {id: 1};
controller.$params = {id: 1};
controller.$.dialog = {hide: jest.fn()};
}));
describe('refresh()', () => {
it('should make an HTTP GET query and then set the data', () => {
const params = {filter: {include: 'warehouse'}};
const serializedParams = $httpParamSerializer(params);
const path = `Zones/1/warehouses?${serializedParams}`;
$httpBackend.expect('GET', path).respond([{id: 1, name: 'Warehouse one'}]);
controller.refresh();
$httpBackend.flush();
expect(controller.$.data).toBeDefined();
});
});
describe('onSave()', () => {
it('should make an HTTP POST query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
$httpBackend.expect('POST', `Zones/1/warehouses`).respond(200);
controller.onSave();
$httpBackend.flush();
expect(controller.selected).toBeNull();
expect(controller.isNew).toBeNull();
expect(controller.$.dialog.hide).toHaveBeenCalledWith();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('delete()', () => {
it('should make an HTTP DELETE query and then set deleteRow property to null value', () => {
controller.deleteRow = {id: 1};
controller.$.data = [{id: 1}];
$httpBackend.expect('DELETE', `Zones/1/warehouses/1`).respond(200);
controller.delete();
$httpBackend.flush();
expect(controller.deleteRow).toBeNull();
});
});
});