8179-testToMaster #3176

Merged
alexm merged 407 commits from 8179-testToMaster into master 2024-11-12 06:41:52 +00:00
72 changed files with 360 additions and 3516 deletions
Showing only changes of commit 0525081c52 - Show all commits

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), 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'); ('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 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), (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), (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()), (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()), (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()), (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`) 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 VALUES
@ -2441,7 +2442,8 @@ INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`, `
(1107, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle', 1), (1107, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle', 1),
(1107, CONCAT(util.VN_CURDATE(), ' 14:50'), TRUE, 'out', 1); (1107, CONCAT(util.VN_CURDATE(), ' 14:50'), TRUE, 'out', 1);
INSERT INTO `vn`.`dmsType`(`id`, `name`, `readRoleFk`, `writeRoleFk`, `code`) INSERT INTO `vn`.`dmsType`
(`id`, `name`, `readRoleFk`, `writeRoleFk`, `code`)
VALUES VALUES
(1, 'Facturas Recibidas', NULL, NULL, 'invoiceIn'), (1, 'Facturas Recibidas', NULL, NULL, 'invoiceIn'),
(2, 'Doc oficial', NULL, NULL, 'officialDoc'), (2, 'Doc oficial', NULL, NULL, 'officialDoc'),
@ -2464,7 +2466,8 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `readRoleFk`, `writeRoleFk`, `code`)
(19, 'inmovilizado', NULL, NULL, 'fixedAssets'), (19, 'inmovilizado', NULL, NULL, 'fixedAssets'),
(20, 'Reclamación', 1, 1, 'claim'), (20, 'Reclamación', 1, 1, 'claim'),
(21, 'Entrada', 1, 1, 'entry'), (21, 'Entrada', 1, 1, 'entry'),
(22, 'Proveedor', 1, 1, 'supplier'); (22, 'Proveedor', 1, 1, 'supplier'),
(23, 'Termografos', 35, 35, 'thermograph');
INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`) INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`)
VALUES VALUES
@ -2472,7 +2475,7 @@ INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `wa
(2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', util.VN_CURDATE()), (2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', util.VN_CURDATE()),
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', util.VN_CURDATE()), (3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', util.VN_CURDATE()),
(4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', util.VN_CURDATE()), (4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', util.VN_CURDATE()),
(5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', util.VN_CURDATE()), (5, 23, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', util.VN_CURDATE()),
(6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', util.VN_CURDATE()), (6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', util.VN_CURDATE()),
(7, 20, '7.jpg', 'image/jpeg', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', util.VN_CURDATE()), (7, 20, '7.jpg', 'image/jpeg', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', util.VN_CURDATE()),
(8, 20, '8.mp4', 'video/mp4', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', util.VN_CURDATE()), (8, 20, '8.mp4', 'video/mp4', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', util.VN_CURDATE()),

View File

@ -52,13 +52,13 @@ BEGIN
FROM vn.sale s FROM vn.sale s
JOIN vn.ticket t ON t.id = s.ticketFk JOIN vn.ticket t ON t.id = s.ticketFk
LEFT JOIN vn.itemShelvingSale iss ON iss.saleFk = s.id 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 iss.saleFk IS NULL
AND t.warehouseFk = vWarehouseFk AND t.warehouseFk = vWarehouseFk
GROUP BY s.itemFk GROUP BY s.itemFk
) )
SELECT i.id itemFk, SELECT i.id itemFk,
CAST(sd.quantity AS INT) advanceable, LEAST(CAST(sd.quantity AS INT), sk.stock) advanceable,
i.longName, i.longName,
i.subName, i.subName,
i.tag5, 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

@ -4,5 +4,14 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`travelThermograph_befor
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
SET NEW.editorFk = account.myUser_getId(); SET NEW.editorFk = account.myUser_getId();
IF NEW.travelFk IS NULL AND
(SELECT COUNT(*) FROM travelThermograph
WHERE thermographFk = NEW.thermographFk
AND travelFk IS NULL
AND id <> NEW.id) > 0
THEN
CALL util.throw('Duplicate thermographFk without travelFk not allowed.');
END IF;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -4,5 +4,14 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`travelThermograph_befor
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
SET NEW.editorFk = account.myUser_getId(); SET NEW.editorFk = account.myUser_getId();
IF NEW.travelFk IS NULL AND
(SELECT COUNT(*) FROM travelThermograph
WHERE thermographFk = NEW.thermographFk
AND travelFk IS NULL
AND id <> NEW.id) > 0
THEN
CALL util.throw('Duplicate thermographFk without travelFk not allowed.');
END IF;
END$$ END$$
DELIMITER ; DELIMITER ;

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": { "EntryType": {
"dataSource": "vn" "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

@ -117,7 +117,8 @@ module.exports = Self => {
result: state, result: state,
maxTemperature, maxTemperature,
minTemperature, minTemperature,
temperatureFk temperatureFk,
warehouseFk: warehouseId,
}, myOptions); }, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -4,7 +4,7 @@ describe('Thermograph saveThermograph()', () => {
const ctx = beforeAll.getCtx(); const ctx = beforeAll.getCtx();
const travelFk = 1; const travelFk = 1;
const thermographId = '138350-0'; const thermographId = '138350-0';
const warehouseFk = '1'; const warehouseFk = 1;
const state = 'COMPLETED'; const state = 'COMPLETED';
const maxTemperature = 30; const maxTemperature = 30;
const minTemperature = 10; const minTemperature = 10;
@ -41,7 +41,7 @@ describe('Thermograph saveThermograph()', () => {
maxTemperature, maxTemperature,
minTemperature, minTemperature,
temperatureFk, temperatureFk,
null, warehouseFk,
null, null,
null, null,
null, null,

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'; export * from './module';
import './main'; 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 { export default class Zone extends ModuleMain {
constructor($element, $) { constructor($element, $) {
super($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": { "menus": {
"main": [ "main": [
{"state": "zone.index", "icon": "icon-zone"}, {"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": [ "keybindings": [
@ -34,79 +25,6 @@
"state": "zone.index", "state": "zone.index",
"component": "vn-zone-index", "component": "vn-zone-index",
"description": "Zones" "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();
});
});
});