8093-devToTest_2442_3 #3100
|
@ -185,6 +185,7 @@ INSERT INTO `vn`.`warehouse`(`id`, `name`, `code`, `isComparative`, `isInventory
|
||||||
(3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0),
|
(3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0),
|
||||||
(4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1),
|
(4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1),
|
||||||
(5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0),
|
(5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0),
|
||||||
|
(6, 'Warehouse six', 'vnh', 1, 1, 1, 1, 0, 0, 1, 1, 0, 0),
|
||||||
(13, 'Inventory', 'inv', 1, 1, 1, 0, 0, 0, 1, 0, 0, 0),
|
(13, 'Inventory', 'inv', 1, 1, 1, 0, 0, 0, 1, 0, 0, 0),
|
||||||
(60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0);
|
(60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
@ -3941,6 +3942,11 @@ INSERT INTO vn.medicalReview
|
||||||
(id, workerFk, centerFk, `date`, `time`, isFit, amount, invoice, remark)
|
(id, workerFk, centerFk, `date`, `time`, isFit, amount, invoice, remark)
|
||||||
VALUES(3, 9, 2, '2000-01-01', '8:00', 1, 150.0, NULL, NULL);
|
VALUES(3, 9, 2, '2000-01-01', '8:00', 1, 150.0, NULL, NULL);
|
||||||
|
|
||||||
|
INSERT INTO vn.stockBought (workerFk, bought, reserve, dated)
|
||||||
|
VALUES(35, 1.00, 1.00, '2001-01-01');
|
||||||
|
INSERT INTO vn.auctionConfig (id,conversionCoefficient,warehouseFk)
|
||||||
|
VALUES (1,0.6,6);
|
||||||
|
|
||||||
INSERT INTO vn.payrollComponent
|
INSERT INTO vn.payrollComponent
|
||||||
(id, name, isSalaryAgreed, isVariable, isException)
|
(id, name, isSalaryAgreed, isVariable, isException)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -5,22 +5,26 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`expeditionPallet_buil
|
||||||
vWorkerFk INT,
|
vWorkerFk INT,
|
||||||
OUT vPalletFk INT
|
OUT vPalletFk INT
|
||||||
)
|
)
|
||||||
BEGIN
|
proc: BEGIN
|
||||||
/** Construye un pallet de expediciones.
|
/**
|
||||||
|
* Builds an expedition pallet.
|
||||||
*
|
*
|
||||||
* Primero comprueba si esas expediciones ya pertenecen a otro pallet,
|
* First, it checks if these expeditions already belong to another pallet,
|
||||||
* en cuyo caso actualiza ese pallet.
|
* in which case it returns an error.
|
||||||
*
|
*
|
||||||
* @param vExpeditions JSON_ARRAY con esta estructura [exp1, exp2, exp3, ...]
|
* @param vExpeditions JSON_ARRAY with this structure [exp1, exp2, exp3, ...]
|
||||||
* @param vArcId INT Identificador de arcRead
|
* @param vArcId INT Identifier of arcRead
|
||||||
* @param vWorkerFk INT Identificador de worker
|
* @param vWorkerFk INT Identifier of worker
|
||||||
* @param out vPalletFk Identificador de expeditionPallet
|
* @param out vPalletFk Identifier of expeditionPallet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DECLARE vCounter INT;
|
DECLARE vCounter INT;
|
||||||
DECLARE vExpeditionFk INT;
|
DECLARE vExpeditionFk INT;
|
||||||
DECLARE vTruckFk INT;
|
DECLARE vTruckFk INT;
|
||||||
DECLARE vPrinterFk INT;
|
DECLARE vPrinterFk INT;
|
||||||
DECLARE vExpeditionStateTypeFk INT;
|
DECLARE vExpeditionStateTypeFk INT;
|
||||||
|
DECLARE vFreeExpeditionCount INT;
|
||||||
|
DECLARE vExpeditionWithPallet INT;
|
||||||
|
|
||||||
CREATE OR REPLACE TEMPORARY TABLE tExpedition (
|
CREATE OR REPLACE TEMPORARY TABLE tExpedition (
|
||||||
expeditionFk INT,
|
expeditionFk INT,
|
||||||
|
@ -44,48 +48,63 @@ BEGIN
|
||||||
WHERE e.id = vExpeditionFk;
|
WHERE e.id = vExpeditionFk;
|
||||||
END WHILE;
|
END WHILE;
|
||||||
|
|
||||||
SELECT palletFk INTO vPalletFk
|
SELECT COUNT(expeditionFk) INTO vFreeExpeditionCount
|
||||||
FROM (
|
FROM tExpedition
|
||||||
SELECT palletFk, count(*) n
|
WHERE palletFk IS NULL;
|
||||||
FROM tExpedition
|
|
||||||
WHERE palletFk > 0
|
|
||||||
GROUP BY palletFk
|
|
||||||
ORDER BY n DESC
|
|
||||||
LIMIT 100
|
|
||||||
) sub
|
|
||||||
LIMIT 1;
|
|
||||||
|
|
||||||
IF vPalletFk IS NULL THEN
|
SELECT COUNT(expeditionFk) INTO vExpeditionWithPallet
|
||||||
SELECT roadmapStopFk INTO vTruckFk
|
FROM tExpedition
|
||||||
FROM (
|
WHERE palletFk;
|
||||||
SELECT rm.roadmapStopFk, count(*) n
|
|
||||||
FROM routesMonitor rm
|
|
||||||
JOIN tExpedition e ON e.routeFk = rm.routeFk
|
|
||||||
GROUP BY roadmapStopFk
|
|
||||||
ORDER BY n DESC
|
|
||||||
LIMIT 1
|
|
||||||
) sub;
|
|
||||||
|
|
||||||
IF vTruckFk IS NULL THEN
|
IF vExpeditionWithPallet THEN
|
||||||
CALL util.throw ('TRUCK_NOT_AVAILABLE');
|
UPDATE arcRead
|
||||||
END IF;
|
SET error = (
|
||||||
|
SELECT GROUP_CONCAT(expeditionFk SEPARATOR ', ')
|
||||||
INSERT INTO expeditionPallet SET truckFk = vTruckFk;
|
FROM tExpedition
|
||||||
|
WHERE palletFk
|
||||||
SET vPalletFk = LAST_INSERT_ID();
|
)
|
||||||
|
WHERE id = vArcId;
|
||||||
|
LEAVE proc;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
IF NOT vFreeExpeditionCount THEN
|
||||||
|
CALL util.throw ('NO_FREE_EXPEDITIONS');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT roadmapStopFk INTO vTruckFk
|
||||||
|
FROM (
|
||||||
|
SELECT rm.roadmapStopFk, count(*) n
|
||||||
|
FROM routesMonitor rm
|
||||||
|
JOIN tExpedition e ON e.routeFk = rm.routeFk
|
||||||
|
WHERE e.palletFk IS NULL
|
||||||
|
GROUP BY roadmapStopFk
|
||||||
|
ORDER BY n DESC
|
||||||
|
LIMIT 1
|
||||||
|
) sub;
|
||||||
|
|
||||||
|
IF vTruckFk IS NULL THEN
|
||||||
|
CALL util.throw ('TRUCK_NOT_AVAILABLE');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO expeditionPallet SET truckFk = vTruckFk;
|
||||||
|
|
||||||
|
SET vPalletFk = LAST_INSERT_ID();
|
||||||
|
|
||||||
INSERT INTO expeditionScan(expeditionFk, palletFk, workerFk)
|
INSERT INTO expeditionScan(expeditionFk, palletFk, workerFk)
|
||||||
SELECT expeditionFk, vPalletFk, vWorkerFk
|
SELECT expeditionFk, vPalletFk, vWorkerFk
|
||||||
FROM tExpedition
|
FROM tExpedition
|
||||||
ON DUPLICATE KEY UPDATE palletFk = vPalletFk, workerFk = vWorkerFk;
|
WHERE palletFk IS NULL;
|
||||||
|
|
||||||
SELECT id INTO vExpeditionStateTypeFk
|
SELECT id INTO vExpeditionStateTypeFk
|
||||||
FROM expeditionStateType
|
FROM expeditionStateType
|
||||||
WHERE code = 'PALLETIZED';
|
WHERE code = 'PALLETIZED';
|
||||||
|
|
||||||
INSERT INTO expeditionState(expeditionFk, typeFk)
|
INSERT INTO expeditionState(expeditionFk, typeFk)
|
||||||
SELECT expeditionFk, vExpeditionStateTypeFk FROM tExpedition;
|
SELECT expeditionFk, vExpeditionStateTypeFk
|
||||||
|
FROM tExpedition
|
||||||
|
WHERE palletFk IS NULL;
|
||||||
|
|
||||||
|
UPDATE arcRead SET error = NULL WHERE id = vArcId;
|
||||||
|
|
||||||
SELECT printerFk INTO vPrinterFk FROM arcRead WHERE id = vArcId;
|
SELECT printerFk INTO vPrinterFk FROM arcRead WHERE id = vArcId;
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,18 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`item_getSimilar`(
|
||||||
)
|
)
|
||||||
BEGIN
|
BEGIN
|
||||||
/**
|
/**
|
||||||
* Propone articulos ordenados, con la cantidad
|
* Propone articulos ordenados, con la cantidad
|
||||||
* de veces usado y segun sus caracteristicas.
|
* de veces usado y segun sus caracteristicas.
|
||||||
*
|
*
|
||||||
* @param vSelf Id de artículo
|
* @param vSelf Id de artículo
|
||||||
* @param vWarehouseFk Id de almacen
|
* @param vWarehouseFk Id de almacen
|
||||||
* @param vDated Fecha
|
* @param vDated Fecha
|
||||||
* @param vShowType Mostrar tipos
|
* @param vShowType Mostrar tipos
|
||||||
* @param vDaysInForward Días de alcance para las ventas
|
* @param vDaysInForward Días de alcance para las ventas (https://redmine.verdnatura.es/issues/7956#note-4)
|
||||||
*/
|
*/
|
||||||
DECLARE vAvailableCalcFk INT;
|
DECLARE vAvailableCalcFk INT;
|
||||||
DECLARE vVisibleCalcFk INT;
|
DECLARE vVisibleCalcFk INT;
|
||||||
|
DECLARE vTypeFk INT;
|
||||||
DECLARE vPriority INT DEFAULT 1;
|
DECLARE vPriority INT DEFAULT 1;
|
||||||
|
|
||||||
CALL cache.available_refresh(vAvailableCalcFk, FALSE, vWarehouseFk, vDated);
|
CALL cache.available_refresh(vAvailableCalcFk, FALSE, vWarehouseFk, vDated);
|
||||||
|
@ -42,19 +43,9 @@ BEGIN
|
||||||
AND it.priority = vPriority
|
AND it.priority = vPriority
|
||||||
LEFT JOIN vn.tag t ON t.id = it.tagFk
|
LEFT JOIN vn.tag t ON t.id = it.tagFk
|
||||||
WHERE i.id = vSelf
|
WHERE i.id = vSelf
|
||||||
),
|
|
||||||
sold AS (
|
|
||||||
SELECT SUM(s.quantity) quantity, s.itemFk
|
|
||||||
FROM vn.sale s
|
|
||||||
JOIN vn.ticket t ON t.id = s.ticketFk
|
|
||||||
LEFT JOIN vn.itemShelvingSale iss ON iss.saleFk = s.id
|
|
||||||
WHERE t.shipped >= CURDATE() + INTERVAL vDaysInForward DAY
|
|
||||||
AND iss.saleFk IS NULL
|
|
||||||
AND t.warehouseFk = vWarehouseFk
|
|
||||||
GROUP BY s.itemFk
|
|
||||||
)
|
)
|
||||||
SELECT i.id itemFk,
|
SELECT i.id itemFk,
|
||||||
LEAST(CAST(sd.quantity AS INT), v.visible) advanceable,
|
NULL advanceable, -- https://redmine.verdnatura.es/issues/7956#note-4
|
||||||
i.longName,
|
i.longName,
|
||||||
i.subName,
|
i.subName,
|
||||||
i.tag5,
|
i.tag5,
|
||||||
|
@ -79,7 +70,6 @@ BEGIN
|
||||||
v.visible located,
|
v.visible located,
|
||||||
b.price2
|
b.price2
|
||||||
FROM vn.item i
|
FROM vn.item i
|
||||||
LEFT JOIN sold sd ON sd.itemFk = i.id
|
|
||||||
JOIN cache.available a ON a.item_id = i.id
|
JOIN cache.available a ON a.item_id = i.id
|
||||||
AND a.calc_id = vAvailableCalcFk
|
AND a.calc_id = vAvailableCalcFk
|
||||||
LEFT JOIN cache.visible v ON v.item_id = i.id
|
LEFT JOIN cache.visible v ON v.item_id = i.id
|
||||||
|
@ -93,21 +83,20 @@ BEGIN
|
||||||
LEFT JOIN vn.tag t ON t.id = it.tagFk
|
LEFT JOIN vn.tag t ON t.id = it.tagFk
|
||||||
LEFT JOIN vn.buy b ON b.id = lb.buy_id
|
LEFT JOIN vn.buy b ON b.id = lb.buy_id
|
||||||
JOIN itemTags its
|
JOIN itemTags its
|
||||||
WHERE (a.available > 0 OR sd.quantity < v.visible)
|
WHERE a.available > 0
|
||||||
AND (i.typeFk = its.typeFk OR NOT vShowType)
|
AND (i.typeFk = its.typeFk OR NOT vShowType)
|
||||||
AND i.id <> vSelf
|
AND i.id <> vSelf
|
||||||
ORDER BY (a.available > 0) DESC,
|
ORDER BY `counter` DESC,
|
||||||
`counter` DESC,
|
(t.name = its.name) DESC,
|
||||||
(t.name = its.name) DESC,
|
(it.value = its.value) DESC,
|
||||||
(it.value = its.value) DESC,
|
(i.tag5 = its.tag5) DESC,
|
||||||
(i.tag5 = its.tag5) DESC,
|
match5 DESC,
|
||||||
match5 DESC,
|
(i.tag6 = its.tag6) DESC,
|
||||||
(i.tag6 = its.tag6) DESC,
|
match6 DESC,
|
||||||
match6 DESC,
|
(i.tag7 = its.tag7) DESC,
|
||||||
(i.tag7 = its.tag7) DESC,
|
match7 DESC,
|
||||||
match7 DESC,
|
(i.tag8 = its.tag8) DESC,
|
||||||
(i.tag8 = its.tag8) DESC,
|
match8 DESC
|
||||||
match8 DESC
|
|
||||||
LIMIT 100;
|
LIMIT 100;
|
||||||
END$$
|
END$$
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
|
||||||
|
|
||||||
const $ = {
|
|
||||||
id: 'vn-order-summary vn-one:nth-child(1) > vn-label-value:nth-child(1) span',
|
|
||||||
alias: 'vn-order-summary vn-one:nth-child(1) > vn-label-value:nth-child(2) span',
|
|
||||||
consignee: 'vn-order-summary vn-one:nth-child(2) > vn-label-value:nth-child(6) span',
|
|
||||||
subtotal: 'vn-order-summary vn-one.taxes > p:nth-child(1)',
|
|
||||||
vat: 'vn-order-summary vn-one.taxes > p:nth-child(2)',
|
|
||||||
total: 'vn-order-summary vn-one.taxes > p:nth-child(3)',
|
|
||||||
sale: 'vn-order-summary vn-tbody > vn-tr',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Order summary path', () => {
|
|
||||||
let browser;
|
|
||||||
let page;
|
|
||||||
beforeAll(async() => {
|
|
||||||
browser = await getBrowser();
|
|
||||||
page = browser.page;
|
|
||||||
await page.loginAndModule('employee', 'order');
|
|
||||||
await page.accessToSearchResult('16');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async() => {
|
|
||||||
await browser.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reach the order summary section and check data', async() => {
|
|
||||||
await page.waitForState('order.card.summary');
|
|
||||||
|
|
||||||
const id = await page.innerText($.id);
|
|
||||||
const alias = await page.innerText($.alias);
|
|
||||||
const consignee = await page.innerText($.consignee);
|
|
||||||
const subtotal = await page.innerText($.subtotal);
|
|
||||||
const vat = await page.innerText($.vat);
|
|
||||||
const total = await page.innerText($.total);
|
|
||||||
const sale = await page.countElement($.sale);
|
|
||||||
|
|
||||||
expect(id).toEqual('16');
|
|
||||||
expect(alias).toEqual('Many places');
|
|
||||||
expect(consignee).toEqual('address 26 - Gotham (Province one)');
|
|
||||||
expect(subtotal.length).toBeGreaterThan(1);
|
|
||||||
expect(vat.length).toBeGreaterThan(1);
|
|
||||||
expect(total.length).toBeGreaterThan(1);
|
|
||||||
expect(sale).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,69 +0,0 @@
|
||||||
import selectors from '../../helpers/selectors.js';
|
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
|
||||||
|
|
||||||
const $ = {
|
|
||||||
form: 'vn-order-basic-data form',
|
|
||||||
observation: 'vn-order-basic-data form [vn-name="note"]',
|
|
||||||
saveButton: `vn-order-basic-data form button[type=submit]`,
|
|
||||||
acceptButton: '.vn-confirm.shown button[response="accept"]'
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Order edit basic data path', () => {
|
|
||||||
let browser;
|
|
||||||
let page;
|
|
||||||
|
|
||||||
beforeAll(async() => {
|
|
||||||
browser = await getBrowser();
|
|
||||||
page = browser.page;
|
|
||||||
|
|
||||||
await page.loginAndModule('employee', 'order');
|
|
||||||
await page.accessToSearchResult('1');
|
|
||||||
await page.accessToSection('order.card.basicData');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async() => {
|
|
||||||
await browser.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when confirmed order', () => {
|
|
||||||
it('should not be able to change the client', async() => {
|
|
||||||
const message = await page.sendForm($.form, {
|
|
||||||
client: 'Tony Stark',
|
|
||||||
address: 'Tony Stark',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(message.text).toContain(`You can't make changes on the basic data`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when new order', () => {
|
|
||||||
it('should create an order and edit its basic data', async() => {
|
|
||||||
await page.waitToClick(selectors.globalItems.returnToModuleIndexButton);
|
|
||||||
await page.waitToClick($.acceptButton);
|
|
||||||
await page.waitForContentLoaded();
|
|
||||||
await page.waitToClick(selectors.ordersIndex.createOrderButton);
|
|
||||||
await page.waitForState('order.create');
|
|
||||||
|
|
||||||
await page.autocompleteSearch(selectors.createOrderView.client, 'Jessica Jones');
|
|
||||||
await page.pickDate(selectors.createOrderView.landedDatePicker);
|
|
||||||
await page.autocompleteSearch(selectors.createOrderView.agency, 'Other agency');
|
|
||||||
await page.waitToClick(selectors.createOrderView.createButton);
|
|
||||||
await page.waitForState('order.card.catalog');
|
|
||||||
|
|
||||||
await page.accessToSection('order.card.basicData');
|
|
||||||
|
|
||||||
const values = {
|
|
||||||
client: 'Tony Stark',
|
|
||||||
address: 'Tony Stark',
|
|
||||||
agencyMode: 'Other agency'
|
|
||||||
};
|
|
||||||
|
|
||||||
const message = await page.sendForm($.form, values);
|
|
||||||
await page.reloadSection('order.card.basicData');
|
|
||||||
const formValues = await page.fetchForm($.form, Object.keys(values));
|
|
||||||
|
|
||||||
expect(message.isSuccess).toBeTrue();
|
|
||||||
expect(formValues).toEqual(values);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,48 +0,0 @@
|
||||||
import selectors from '../../helpers/selectors.js';
|
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
|
||||||
|
|
||||||
describe('Order lines', () => {
|
|
||||||
let browser;
|
|
||||||
let page;
|
|
||||||
beforeAll(async() => {
|
|
||||||
browser = await getBrowser();
|
|
||||||
page = browser.page;
|
|
||||||
await page.loginAndModule('employee', 'order');
|
|
||||||
await page.accessToSearchResult('8');
|
|
||||||
await page.accessToSection('order.card.line');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async() => {
|
|
||||||
await browser.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should check the order subtotal', async() => {
|
|
||||||
const result = await page
|
|
||||||
.waitToGetProperty(selectors.orderLine.orderSubtotal, 'innerText');
|
|
||||||
|
|
||||||
expect(result).toContain('112.30');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete the first line in the order', async() => {
|
|
||||||
await page.waitToClick(selectors.orderLine.firstLineDeleteButton);
|
|
||||||
await page.waitToClick(selectors.orderLine.confirmButton);
|
|
||||||
const message = await page.waitForSnackbar();
|
|
||||||
|
|
||||||
expect(message.text).toContain('Data saved!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should confirm the order subtotal has changed', async() => {
|
|
||||||
await page.waitForTextInElement(selectors.orderLine.orderSubtotal, '92.80');
|
|
||||||
const result = await page
|
|
||||||
.waitToGetProperty(selectors.orderLine.orderSubtotal, 'innerText');
|
|
||||||
|
|
||||||
expect(result).toContain('92.80');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should confirm the whole order and redirect to ticket index filtered by clientFk', async() => {
|
|
||||||
await page.waitToClick(selectors.orderLine.confirmOrder);
|
|
||||||
|
|
||||||
await page.expectURL('ticket/index');
|
|
||||||
await page.expectURL('clientFk');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,97 +0,0 @@
|
||||||
import selectors from '../../helpers/selectors.js';
|
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
|
||||||
|
|
||||||
describe('Order catalog', () => {
|
|
||||||
let browser;
|
|
||||||
let page;
|
|
||||||
|
|
||||||
beforeAll(async() => {
|
|
||||||
browser = await getBrowser();
|
|
||||||
page = browser.page;
|
|
||||||
await page.loginAndModule('employee', 'order');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async() => {
|
|
||||||
await browser.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should open the create new order form', async() => {
|
|
||||||
await page.waitToClick(selectors.ordersIndex.createOrderButton);
|
|
||||||
await page.waitForState('order.create');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create a new order', async() => {
|
|
||||||
await page.autocompleteSearch(selectors.createOrderView.client, 'Tony Stark');
|
|
||||||
await page.pickDate(selectors.createOrderView.landedDatePicker);
|
|
||||||
await page.autocompleteSearch(selectors.createOrderView.agency, 'Other agency');
|
|
||||||
await page.waitToClick(selectors.createOrderView.createButton);
|
|
||||||
await page.waitForState('order.card.catalog');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add the realm and type filters and obtain results', async() => {
|
|
||||||
await page.waitToClick(selectors.orderCatalog.plantRealmButton);
|
|
||||||
await page.autocompleteSearch(selectors.orderCatalog.type, 'Anthurium');
|
|
||||||
await page.waitForNumberOfElements('section.product', 4);
|
|
||||||
const result = await page.countElement('section.product');
|
|
||||||
|
|
||||||
expect(result).toEqual(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perfom an "OR" search for the item tag colors silver and brown', async() => {
|
|
||||||
await page.waitToClick(selectors.orderCatalog.openTagSearch);
|
|
||||||
await page.autocompleteSearch(selectors.orderCatalog.tag, 'Color');
|
|
||||||
await page.autocompleteSearch(selectors.orderCatalog.firstTagAutocomplete, 'silver');
|
|
||||||
await page.waitToClick(selectors.orderCatalog.addTagButton);
|
|
||||||
await page.autocompleteSearch(selectors.orderCatalog.secondTagAutocomplete, 'brown');
|
|
||||||
await page.waitToClick(selectors.orderCatalog.searchTagButton);
|
|
||||||
await page.waitForNumberOfElements('section.product', 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perfom an "OR" search for the item tag tallos 2 and 9', async() => {
|
|
||||||
await page.waitToClick(selectors.orderCatalog.openTagSearch);
|
|
||||||
await page.autocompleteSearch(selectors.orderCatalog.tag, 'Tallos');
|
|
||||||
await page.write(selectors.orderCatalog.firstTagValue, '2');
|
|
||||||
await page.waitToClick(selectors.orderCatalog.addTagButton);
|
|
||||||
await page.write(selectors.orderCatalog.secondTagValue, '9');
|
|
||||||
await page.waitToClick(selectors.orderCatalog.searchTagButton);
|
|
||||||
await page.waitForNumberOfElements('section.product', 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perform a general search for category', async() => {
|
|
||||||
await page.write(selectors.orderCatalog.itemTagValue, 'concussion');
|
|
||||||
await page.keyboard.press('Enter');
|
|
||||||
await page.waitForNumberOfElements('section.product', 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should perfom an "AND" search for the item tag tallos 2', async() => {
|
|
||||||
await page.waitToClick(selectors.orderCatalog.openTagSearch);
|
|
||||||
await page.autocompleteSearch(selectors.orderCatalog.tag, 'Tallos');
|
|
||||||
await page.write(selectors.orderCatalog.firstTagValue, '2');
|
|
||||||
await page.waitToClick(selectors.orderCatalog.searchTagButton);
|
|
||||||
await page.waitForNumberOfElements('section.product', 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove the tag filters and have 4 results', async() => {
|
|
||||||
await page.waitForContentLoaded();
|
|
||||||
await page.waitToClick(selectors.orderCatalog.sixthFilterRemoveButton);
|
|
||||||
await page.waitForContentLoaded();
|
|
||||||
await page.waitToClick(selectors.orderCatalog.fifthFilterRemoveButton);
|
|
||||||
await page.waitForContentLoaded();
|
|
||||||
await page.waitToClick(selectors.orderCatalog.fourthFilterRemoveButton);
|
|
||||||
await page.waitForContentLoaded();
|
|
||||||
await page.waitToClick(selectors.orderCatalog.thirdFilterRemoveButton);
|
|
||||||
|
|
||||||
await page.waitForNumberOfElements('.product', 4);
|
|
||||||
const result = await page.countElement('section.product');
|
|
||||||
|
|
||||||
expect(result).toEqual(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should search for an item by id', async() => {
|
|
||||||
await page.accessToSearchResult('2');
|
|
||||||
await page.waitForNumberOfElements('section.product', 1);
|
|
||||||
const result = await page.countElement('section.product');
|
|
||||||
|
|
||||||
expect(result).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,34 +0,0 @@
|
||||||
import selectors from '../../helpers/selectors.js';
|
|
||||||
import getBrowser from '../../helpers/puppeteer';
|
|
||||||
|
|
||||||
describe('Order Index', () => {
|
|
||||||
let browser;
|
|
||||||
let page;
|
|
||||||
|
|
||||||
beforeAll(async() => {
|
|
||||||
browser = await getBrowser();
|
|
||||||
page = browser.page;
|
|
||||||
await page.loginAndModule('employee', 'order');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async() => {
|
|
||||||
await browser.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should check the second search result doesn't contain a total of 0€`, async() => {
|
|
||||||
await page.waitToClick(selectors.globalItems.searchButton);
|
|
||||||
const result = await page.waitToGetProperty(selectors.ordersIndex.secondSearchResultTotal, 'innerText');
|
|
||||||
|
|
||||||
expect(result).not.toContain('0.00');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should search including empty orders', async() => {
|
|
||||||
await page.waitToClick(selectors.ordersIndex.openAdvancedSearch);
|
|
||||||
await page.waitToClick(selectors.ordersIndex.advancedSearchShowEmptyCheckbox);
|
|
||||||
await page.waitToClick(selectors.ordersIndex.advancedSearchButton);
|
|
||||||
await page.waitForTextInElement(selectors.ordersIndex.secondSearchResultTotal, '0.00');
|
|
||||||
const result = await page.waitToGetProperty(selectors.ordersIndex.secondSearchResultTotal, 'innerText');
|
|
||||||
|
|
||||||
expect(result).toContain('0.00');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -66,10 +66,16 @@ export default class App {
|
||||||
]}
|
]}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.logger.$params.q) {
|
||||||
if (this.logger.$params.q)
|
let tableValue = this.logger.$params.q;
|
||||||
newRoute = newRoute.concat(`?table=${this.logger.$params.q}`);
|
const q = JSON.parse(tableValue);
|
||||||
|
if (typeof q === 'number')
|
||||||
|
tableValue = JSON.stringify({id: tableValue});
|
||||||
|
newRoute = newRoute.concat(`?table=${tableValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.logger.$params.id && newRoute.indexOf(this.logger.$params.id) < 0)
|
||||||
|
newRoute = newRoute.concat(`${this.logger.$params.id}`);
|
||||||
|
|
||||||
return this.logger.$http.get('Urls/findOne', {filter})
|
return this.logger.$http.get('Urls/findOne', {filter})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
<th field="clientFk">
|
<th field="clientFk">
|
||||||
<span translate>Client</span>
|
<span translate>Client</span>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th field="isWorker">
|
||||||
<span translate>Es trabajador</span>
|
<span translate>Es trabajador</span>
|
||||||
</th>
|
</th>
|
||||||
<th field="salesPersonFk">
|
<th field="salesPersonFk">
|
||||||
|
|
|
@ -57,6 +57,11 @@ export default class Controller extends Section {
|
||||||
field: 'observation',
|
field: 'observation',
|
||||||
searchable: false
|
searchable: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'isWorker',
|
||||||
|
checkbox: true,
|
||||||
|
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'created',
|
field: 'created',
|
||||||
datepicker: true
|
datepicker: true
|
||||||
|
@ -73,9 +78,6 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
set defaulters(value) {
|
set defaulters(value) {
|
||||||
if (!value || !value.length) return;
|
if (!value || !value.length) return;
|
||||||
for (let defaulter of value)
|
|
||||||
defaulter.isWorker = defaulter.businessTypeFk === 'worker';
|
|
||||||
|
|
||||||
this._defaulters = value;
|
this._defaulters = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +166,8 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
exprBuilder(param, value) {
|
exprBuilder(param, value) {
|
||||||
switch (param) {
|
switch (param) {
|
||||||
|
case 'isWorker':
|
||||||
|
return {isWorker: value};
|
||||||
case 'creditInsurance':
|
case 'creditInsurance':
|
||||||
case 'amount':
|
case 'amount':
|
||||||
case 'clientFk':
|
case 'clientFk':
|
||||||
|
|
|
@ -39,7 +39,7 @@ describe('Entry filter()', () => {
|
||||||
|
|
||||||
const result = await models.Entry.filter(ctx, options);
|
const result = await models.Entry.filter(ctx, options);
|
||||||
|
|
||||||
expect(result.length).toEqual(11);
|
expect(result.length).toEqual(12);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -152,7 +152,7 @@ describe('Entry filter()', () => {
|
||||||
|
|
||||||
const result = await models.Entry.filter(ctx, options);
|
const result = await models.Entry.filter(ctx, options);
|
||||||
|
|
||||||
expect(result.length).toEqual(10);
|
expect(result.length).toEqual(11);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -45,8 +45,8 @@ module.exports = Self => {
|
||||||
i.id itemFk,
|
i.id itemFk,
|
||||||
i.name itemName,
|
i.name itemName,
|
||||||
ti.quantity,
|
ti.quantity,
|
||||||
(ac.conversionCoefficient * (ti.quantity / b.packing) * buy_getVolume(b.id))
|
ROUND((ac.conversionCoefficient * (ti.quantity / b.packing) * buy_getVolume(b.id))
|
||||||
/ (vc.trolleyM3 * 1000000) volume,
|
/ (vc.trolleyM3 * 1000000),1) volume,
|
||||||
b.packagingFk packagingFk,
|
b.packagingFk packagingFk,
|
||||||
b.packing
|
b.packing
|
||||||
FROM tmp.item ti
|
FROM tmp.item ti
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<slot-descriptor>
|
||||||
|
<vn-entry-descriptor></vn-entry-descriptor>
|
||||||
|
</slot-descriptor>
|
|
@ -0,0 +1,9 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import DescriptorPopover from 'salix/components/descriptor-popover';
|
||||||
|
|
||||||
|
class Controller extends DescriptorPopover {}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnEntryDescriptorPopover', {
|
||||||
|
slotTemplate: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,65 @@
|
||||||
|
<vn-descriptor-content
|
||||||
|
module="entry"
|
||||||
|
description="$ctrl.entry.supplier.nickname"
|
||||||
|
summary="$ctrl.$.summary">
|
||||||
|
<slot-menu>
|
||||||
|
<vn-item
|
||||||
|
ng-click="$ctrl.showEntryReport()"
|
||||||
|
translate>
|
||||||
|
Show entry report
|
||||||
|
</vn-item>
|
||||||
|
</slot-menu>
|
||||||
|
<slot-body>
|
||||||
|
<div class="attributes">
|
||||||
|
<vn-label-value label="Agency "
|
||||||
|
value="{{$ctrl.entry.travel.agency.name}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Landed"
|
||||||
|
value="{{$ctrl.entry.travel.landed | date: 'dd/MM/yyyy'}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Warehouse Out"
|
||||||
|
value="{{$ctrl.entry.travel.warehouseOut.name}}">
|
||||||
|
</vn-label-value>
|
||||||
|
</div>
|
||||||
|
<div class="icons">
|
||||||
|
<vn-icon
|
||||||
|
vn-tooltip="Is inventory entry"
|
||||||
|
icon="icon-inventory"
|
||||||
|
ng-if="$ctrl.entry.isExcludedFromAvailable">
|
||||||
|
</vn-icon>
|
||||||
|
<vn-icon
|
||||||
|
vn-tooltip="Is virtual entry"
|
||||||
|
icon="icon-net"
|
||||||
|
ng-if="$ctrl.entry.isRaid">
|
||||||
|
</vn-icon>
|
||||||
|
</div>
|
||||||
|
<div class="quicklinks">
|
||||||
|
<div ng-transclude="btnOne">
|
||||||
|
<vn-quick-link
|
||||||
|
tooltip="Supplier card"
|
||||||
|
state="['supplier.index', {q: $ctrl.entry.supplier.id }]"
|
||||||
|
icon="icon-supplier">
|
||||||
|
</vn-quick-link>
|
||||||
|
</div>
|
||||||
|
<div ng-transclude="btnTwo">
|
||||||
|
<vn-quick-link
|
||||||
|
tooltip="All travels with current agency"
|
||||||
|
state="['travel.index', {q: $ctrl.travelFilter}]"
|
||||||
|
icon="local_airport">
|
||||||
|
</vn-quick-link>
|
||||||
|
</div>
|
||||||
|
<div ng-transclude="btnThree">
|
||||||
|
<vn-quick-link
|
||||||
|
tooltip="All entries with current supplier"
|
||||||
|
state="['entry.index', {q: $ctrl.entryFilter}]"
|
||||||
|
icon="icon-entry">
|
||||||
|
</vn-quick-link>
|
||||||
|
</div>
|
||||||
|
<div ng-transclude="btnThree">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</slot-body>
|
||||||
|
</vn-descriptor-content>
|
||||||
|
<vn-popup vn-id="summary">
|
||||||
|
<vn-entry-summary entry="$ctrl.entry"></vn-entry-summary>
|
||||||
|
</vn-popup>
|
|
@ -0,0 +1,99 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Descriptor from 'salix/components/descriptor';
|
||||||
|
|
||||||
|
class Controller extends Descriptor {
|
||||||
|
get entry() {
|
||||||
|
return this.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
set entry(value) {
|
||||||
|
this.entity = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get travelFilter() {
|
||||||
|
let travelFilter;
|
||||||
|
const entryTravel = this.entry && this.entry.travel;
|
||||||
|
|
||||||
|
if (entryTravel && entryTravel.agencyModeFk) {
|
||||||
|
travelFilter = this.entry && JSON.stringify({
|
||||||
|
agencyModeFk: entryTravel.agencyModeFk
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return travelFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
get entryFilter() {
|
||||||
|
let entryTravel = this.entry && this.entry.travel;
|
||||||
|
|
||||||
|
if (!entryTravel || !entryTravel.landed) return null;
|
||||||
|
|
||||||
|
const date = new Date(entryTravel.landed);
|
||||||
|
date.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const from = new Date(date.getTime());
|
||||||
|
from.setDate(from.getDate() - 10);
|
||||||
|
|
||||||
|
const to = new Date(date.getTime());
|
||||||
|
to.setDate(to.getDate() + 10);
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
supplierFk: this.entry.supplierFk,
|
||||||
|
from,
|
||||||
|
to
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData() {
|
||||||
|
const filter = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'travel',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'agency',
|
||||||
|
scope: {
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'warehouseOut',
|
||||||
|
scope: {
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'warehouseIn',
|
||||||
|
scope: {
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'supplier',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'nickname']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.getData(`Entries/${this.id}`, {filter})
|
||||||
|
.then(res => this.entity = res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
showEntryReport() {
|
||||||
|
this.vnReport.show(`Entries/${this.id}/entry-order-pdf`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnEntryDescriptor', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
entry: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,3 +1,6 @@
|
||||||
export * from './module';
|
export * from './module';
|
||||||
|
|
||||||
import './main';
|
import './main';
|
||||||
|
import './descriptor';
|
||||||
|
import './descriptor-popover';
|
||||||
|
import './summary';
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
"main": [
|
"main": [
|
||||||
{"state": "entry.index", "icon": "icon-entry"},
|
{"state": "entry.index", "icon": "icon-entry"},
|
||||||
{"state": "entry.latestBuys", "icon": "contact_support"}
|
{"state": "entry.latestBuys", "icon": "contact_support"}
|
||||||
|
],
|
||||||
|
"card": [
|
||||||
|
{"state": "entry.card.basicData", "icon": "settings"},
|
||||||
|
{"state": "entry.card.buy.index", "icon": "icon-lines"},
|
||||||
|
{"state": "entry.card.observation", "icon": "insert_drive_file"},
|
||||||
|
{"state": "entry.card.log", "icon": "history"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"keybindings": [
|
"keybindings": [
|
||||||
|
@ -27,6 +33,90 @@
|
||||||
"component": "vn-entry-index",
|
"component": "vn-entry-index",
|
||||||
"description": "Entries",
|
"description": "Entries",
|
||||||
"acl": ["buyer", "administrative"]
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/latest-buys?q",
|
||||||
|
"state": "entry.latestBuys",
|
||||||
|
"component": "vn-entry-latest-buys",
|
||||||
|
"description": "Latest buys",
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/create?supplierFk&travelFk&companyFk",
|
||||||
|
"state": "entry.create",
|
||||||
|
"component": "vn-entry-create",
|
||||||
|
"description": "New entry",
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/:id",
|
||||||
|
"state": "entry.card",
|
||||||
|
"abstract": true,
|
||||||
|
"component": "vn-entry-card"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/summary",
|
||||||
|
"state": "entry.card.summary",
|
||||||
|
"component": "vn-entry-summary",
|
||||||
|
"description": "Summary",
|
||||||
|
"params": {
|
||||||
|
"entry": "$ctrl.entry"
|
||||||
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/basic-data",
|
||||||
|
"state": "entry.card.basicData",
|
||||||
|
"component": "vn-entry-basic-data",
|
||||||
|
"description": "Basic data",
|
||||||
|
"params": {
|
||||||
|
"entry": "$ctrl.entry"
|
||||||
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/observation",
|
||||||
|
"state": "entry.card.observation",
|
||||||
|
"component": "vn-entry-observation",
|
||||||
|
"description": "Notes",
|
||||||
|
"params": {
|
||||||
|
"entry": "$ctrl.entry"
|
||||||
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url" : "/log",
|
||||||
|
"state": "entry.card.log",
|
||||||
|
"component": "vn-entry-log",
|
||||||
|
"description": "Log",
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/buy",
|
||||||
|
"state": "entry.card.buy",
|
||||||
|
"abstract": true,
|
||||||
|
"component": "ui-view",
|
||||||
|
"acl": ["buyer"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url" : "/index",
|
||||||
|
"state": "entry.card.buy.index",
|
||||||
|
"component": "vn-entry-buy-index",
|
||||||
|
"description": "Buys",
|
||||||
|
"params": {
|
||||||
|
"entry": "$ctrl.entry"
|
||||||
|
},
|
||||||
|
"acl": ["buyer", "administrative"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url" : "/import",
|
||||||
|
"state": "entry.card.buy.import",
|
||||||
|
"component": "vn-entry-buy-import",
|
||||||
|
"description": "Import buys",
|
||||||
|
"params": {
|
||||||
|
"entry": "$ctrl.entry"
|
||||||
|
},
|
||||||
|
"acl": ["buyer"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
<vn-crud-model
|
||||||
|
vn-id="buysModel"
|
||||||
|
url="Entries/{{$ctrl.entry.id}}/getBuys"
|
||||||
|
limit="5"
|
||||||
|
data="buys"
|
||||||
|
auto-load="true">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-card class="summary">
|
||||||
|
<h5>
|
||||||
|
<a ng-if="::$ctrl.entryData.id"
|
||||||
|
vn-tooltip="Go to the entry"
|
||||||
|
ui-sref="entry.card.summary({id: {{::$ctrl.entryData.id}}})"
|
||||||
|
name="goToSummary">
|
||||||
|
<vn-icon-button icon="launch"></vn-icon-button>
|
||||||
|
</a>
|
||||||
|
<span> #{{$ctrl.entryData.id}} - {{$ctrl.entryData.supplier.nickname}}</span>
|
||||||
|
</h5>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-one>
|
||||||
|
<vn-label-value label="Commission"
|
||||||
|
value="{{$ctrl.entryData.commission}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Currency"
|
||||||
|
value="{{$ctrl.entryData.currency.name}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Company"
|
||||||
|
value="{{$ctrl.entryData.company.code}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Reference"
|
||||||
|
value="{{$ctrl.entryData.reference}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Invoice number"
|
||||||
|
value="{{$ctrl.entryData.invoiceNumber}}">
|
||||||
|
</vn-label-value>
|
||||||
|
</vn-one>
|
||||||
|
<vn-one>
|
||||||
|
<vn-label-value label="Reference">
|
||||||
|
<span
|
||||||
|
ng-click="travelDescriptor.show($event, $ctrl.entryData.travel.id)"
|
||||||
|
class="link">
|
||||||
|
{{$ctrl.entryData.travel.ref}}
|
||||||
|
</span>
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Agency"
|
||||||
|
value="{{$ctrl.entryData.travel.agency.name}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Shipped"
|
||||||
|
value="{{$ctrl.entryData.travel.shipped | date: 'dd/MM/yyyy'}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Warehouse Out"
|
||||||
|
value="{{$ctrl.entryData.travel.warehouseOut.name}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-check
|
||||||
|
label="Delivered"
|
||||||
|
ng-model="$ctrl.entryData.travel.isDelivered"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
<vn-label-value label="Landed"
|
||||||
|
value="{{$ctrl.entryData.travel.landed | date: 'dd/MM/yyyy'}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Warehouse In"
|
||||||
|
value="{{$ctrl.entryData.travel.warehouseIn.name}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-check
|
||||||
|
label="Received"
|
||||||
|
ng-model="$ctrl.entryData.travel.isReceived"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
</vn-one>
|
||||||
|
<vn-one>
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-check
|
||||||
|
label="Ordered"
|
||||||
|
ng-model="$ctrl.entryData.isOrdered"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
<vn-check
|
||||||
|
label="Confirmed"
|
||||||
|
ng-model="$ctrl.entryData.isConfirmed"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
<vn-check
|
||||||
|
label="Booked"
|
||||||
|
ng-model="$ctrl.entryData.isBooked"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
<vn-check
|
||||||
|
label="Raid"
|
||||||
|
ng-model="$ctrl.entryData.isRaid"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
<vn-check
|
||||||
|
label="Inventory"
|
||||||
|
ng-model="$ctrl.entryData.isExcludedFromAvailable"
|
||||||
|
disabled="true">
|
||||||
|
</vn-check>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-one>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-auto name="buys">
|
||||||
|
<h4 translate>Buys</h4>
|
||||||
|
<table class="vn-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th translate center field="quantity">Quantity</th>
|
||||||
|
<th translate center field="sticker">Stickers</th>
|
||||||
|
<th translate center field="packagingFk">Package</th>
|
||||||
|
<th translate center field="weight">Weight</th>
|
||||||
|
<th translate center field="packing">Packing</th>
|
||||||
|
<th translate center field="grouping">Grouping</th>
|
||||||
|
<th translate center field="buyingValue">Buying value</th>
|
||||||
|
<th translate center field="price3">Import</th>
|
||||||
|
<th translate center expand field="price">PVP</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody ng-repeat="line in buys">
|
||||||
|
<tr>
|
||||||
|
<td center title="{{::line.quantity}}">{{::line.quantity}}</td>
|
||||||
|
<td center title="{{::line.stickers | dashIfEmpty}}">{{::line.stickers | dashIfEmpty}}</td>
|
||||||
|
<td center title="{{::line.packagingFk | dashIfEmpty}}">{{::line.packagingFk | dashIfEmpty}}</td>
|
||||||
|
<td center title="{{::line.weight}}">{{::line.weight}}</td>
|
||||||
|
<td center>
|
||||||
|
<vn-chip class="transparent" translate-attr="line.groupingMode == 'packing' ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': line.groupingMode == 'packing'}">
|
||||||
|
<span>{{::line.packing | dashIfEmpty}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
</td>
|
||||||
|
<td center>
|
||||||
|
<vn-chip class="transparent" translate-attr="line.groupingMode == 'grouping' ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': line.groupingMode == 'grouping'}">
|
||||||
|
<span>{{::line.grouping | dashIfEmpty}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
</vn-td>
|
||||||
|
<td center title="{{::line.buyingValue | currency: 'EUR':2}}">{{::line.buyingValue | currency: 'EUR':2}}</td>
|
||||||
|
<td center title="{{::line.quantity * line.buyingValue | currency: 'EUR':2}}">{{::line.quantity * line.buyingValue | currency: 'EUR':2}}</td>
|
||||||
|
<td center title="Grouping / Packing">{{::line.price2 | currency: 'EUR':2 | dashIfEmpty}} / {{::line.price3 | currency: 'EUR':2 | dashIfEmpty}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="dark-row">
|
||||||
|
<td shrink>
|
||||||
|
<span
|
||||||
|
translate-attr="{title: 'Item type'}">
|
||||||
|
{{::line.item.itemType.code}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td shrink>
|
||||||
|
<span
|
||||||
|
ng-click="itemDescriptor.show($event, line.item.id)"
|
||||||
|
class="link">
|
||||||
|
{{::line.item.id}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td number shrink>
|
||||||
|
<span
|
||||||
|
translate-attr="{title: 'Item size'}">
|
||||||
|
{{::line.item.size}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td center>
|
||||||
|
<span
|
||||||
|
translate-attr="{title: 'Minimum price'}">
|
||||||
|
{{::line.item.minPrice | currency: 'EUR':2}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td vn-fetched-tags colspan="6">
|
||||||
|
<div>
|
||||||
|
<vn-one title="{{::line.item.concept}}">{{::line.item.name}}</vn-one>
|
||||||
|
<vn-one ng-if="::line.item.subName">
|
||||||
|
<h3 title="{{::line.item.subName}}">{{::line.item.subName}}</h3>
|
||||||
|
</vn-one>
|
||||||
|
</div>
|
||||||
|
<vn-fetched-tags
|
||||||
|
max-length="6"
|
||||||
|
item="::line.item"
|
||||||
|
tabindex="-1">
|
||||||
|
</vn-fetched-tags>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="empty-row">
|
||||||
|
<td colspan="10"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<vn-pagination
|
||||||
|
model="buysModel"
|
||||||
|
class="vn-pt-xs">
|
||||||
|
</vn-pagination>
|
||||||
|
</vn-auto>
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-card>
|
||||||
|
<vn-item-descriptor-popover
|
||||||
|
vn-id="item-descriptor"
|
||||||
|
warehouse-fk="$ctrl.vnConfig.warehouseFk">
|
||||||
|
</vn-item-descriptor-popover>
|
||||||
|
<vn-travel-descriptor-popover
|
||||||
|
vn-id="travelDescriptor">
|
||||||
|
</vn-travel-descriptor-popover>
|
|
@ -0,0 +1,33 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import './style.scss';
|
||||||
|
import Summary from 'salix/components/summary';
|
||||||
|
|
||||||
|
class Controller extends Summary {
|
||||||
|
get entry() {
|
||||||
|
if (!this._entry)
|
||||||
|
return this.$params;
|
||||||
|
|
||||||
|
return this._entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
set entry(value) {
|
||||||
|
this._entry = value;
|
||||||
|
|
||||||
|
if (value && value.id)
|
||||||
|
this.getEntryData();
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntryData() {
|
||||||
|
return this.$http.get(`Entries/${this.entry.id}/getEntry`).then(response => {
|
||||||
|
this.entryData = response.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnEntrySummary', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
entry: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
|
|
||||||
|
vn-entry-summary .summary {
|
||||||
|
max-width: $width-lg;
|
||||||
|
|
||||||
|
.dark-row {
|
||||||
|
background-color: lighten($color-marginal, 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(1) {
|
||||||
|
border-top: $border-thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(1),
|
||||||
|
tbody tr:nth-child(2) {
|
||||||
|
border-left: $border-thin;
|
||||||
|
border-right: $border-thin
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:nth-child(3) {
|
||||||
|
height: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$color-font-link-medium: lighten($color-font-link, 20%)
|
|
@ -1,6 +1,6 @@
|
||||||
const {models} = require('vn-loopback/server/server');
|
const {models} = require('vn-loopback/server/server');
|
||||||
describe('item lastEntriesFilter()', () => {
|
describe('item lastEntriesFilter()', () => {
|
||||||
it('should return one entry for the given item', async() => {
|
it('should return two entry for the given item', async() => {
|
||||||
const minDate = Date.vnNew();
|
const minDate = Date.vnNew();
|
||||||
minDate.setHours(0, 0, 0, 0);
|
minDate.setHours(0, 0, 0, 0);
|
||||||
const maxDate = Date.vnNew();
|
const maxDate = Date.vnNew();
|
||||||
|
@ -13,7 +13,7 @@ describe('item lastEntriesFilter()', () => {
|
||||||
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
||||||
const result = await models.Item.lastEntriesFilter(filter, options);
|
const result = await models.Item.lastEntriesFilter(filter, options);
|
||||||
|
|
||||||
expect(result.length).toEqual(1);
|
expect(result.length).toEqual(2);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -22,7 +22,7 @@ describe('item lastEntriesFilter()', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return five entries for the given item', async() => {
|
it('should return six entries for the given item', async() => {
|
||||||
const minDate = Date.vnNew();
|
const minDate = Date.vnNew();
|
||||||
minDate.setHours(0, 0, 0, 0);
|
minDate.setHours(0, 0, 0, 0);
|
||||||
minDate.setMonth(minDate.getMonth() - 2, 1);
|
minDate.setMonth(minDate.getMonth() - 2, 1);
|
||||||
|
@ -37,7 +37,7 @@ describe('item lastEntriesFilter()', () => {
|
||||||
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
||||||
const result = await models.Item.lastEntriesFilter(filter, options);
|
const result = await models.Item.lastEntriesFilter(filter, options);
|
||||||
|
|
||||||
expect(result.length).toEqual(5);
|
expect(result.length).toEqual(6);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Items",
|
"name": "Items",
|
||||||
"icon": "icon-item",
|
"icon": "icon-item",
|
||||||
"validations" : true,
|
"validations" : true,
|
||||||
"dependencies": ["worker", "client", "ticket"],
|
"dependencies": ["worker", "client", "ticket", "entry"],
|
||||||
"menus": {
|
"menus": {
|
||||||
"main": [
|
"main": [
|
||||||
{"state": "item.index", "icon": "icon-item"},
|
{"state": "item.index", "icon": "icon-item"},
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
<mg-ajax path="Orders/{{patch.params.id}}/updateBasicData" options="vnPatch"></mg-ajax>
|
|
||||||
<vn-crud-model
|
|
||||||
autoload="true"
|
|
||||||
url="Addresses"
|
|
||||||
data="address"
|
|
||||||
order="nickname"
|
|
||||||
vn-id="address-model">
|
|
||||||
</vn-crud-model>
|
|
||||||
<vn-watcher
|
|
||||||
vn-id="watcher"
|
|
||||||
data="$ctrl.order"
|
|
||||||
form="form"
|
|
||||||
save="patch">
|
|
||||||
</vn-watcher>
|
|
||||||
<form name="form" ng-submit="watcher.submit()" class="vn-w-md">
|
|
||||||
<vn-card class="vn-pa-lg">
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-autocomplete
|
|
||||||
vn-one
|
|
||||||
url="Clients"
|
|
||||||
label="Client"
|
|
||||||
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}"
|
|
||||||
show-field="name"
|
|
||||||
value-field="id"
|
|
||||||
ng-model="$ctrl.order.clientFk"
|
|
||||||
vn-name="client"
|
|
||||||
selection="$ctrl.selection"
|
|
||||||
fields="['defaultAddressFk']">
|
|
||||||
<tpl-item>
|
|
||||||
<div>{{::name}}</div>
|
|
||||||
<div class="text-secondary text-caption">#{{::id}}</div>
|
|
||||||
</tpl-item>
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-autocomplete
|
|
||||||
vn-one
|
|
||||||
fields="['id', 'nickname']"
|
|
||||||
data="address"
|
|
||||||
label="Address"
|
|
||||||
search-function="$search"
|
|
||||||
show-field="nickname"
|
|
||||||
value-field="id"
|
|
||||||
ng-model="$ctrl.order.addressFk"
|
|
||||||
vn-name="address"
|
|
||||||
on-change="$ctrl.getAvailableAgencies()">
|
|
||||||
<tpl-item>{{::nickname}}</tpl-item>
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-date-picker
|
|
||||||
vn-one
|
|
||||||
label="Landed"
|
|
||||||
ng-model="$ctrl.order.landed"
|
|
||||||
vn-name="landed"
|
|
||||||
on-change="$ctrl.getAvailableAgencies()">
|
|
||||||
</vn-date-picker>
|
|
||||||
<vn-autocomplete
|
|
||||||
disabled="!$ctrl.order.addressFk || !$ctrl.order.landed"
|
|
||||||
data="$ctrl._availableAgencies"
|
|
||||||
label="Agency"
|
|
||||||
show-field="agencyMode"
|
|
||||||
value-field="agencyModeFk"
|
|
||||||
ng-model="$ctrl.order.agencyModeFk"
|
|
||||||
vn-name="agencyMode">
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-textarea
|
|
||||||
vn-one
|
|
||||||
label="Notes"
|
|
||||||
ng-model="$ctrl.order.note"
|
|
||||||
vn-name="note"
|
|
||||||
rule>
|
|
||||||
</vn-textarea>
|
|
||||||
</vn-horizontal>
|
|
||||||
</vn-card>
|
|
||||||
<vn-button-bar>
|
|
||||||
<vn-submit
|
|
||||||
disabled="!watcher.dataChanged()"
|
|
||||||
label="Save">
|
|
||||||
</vn-submit>
|
|
||||||
<vn-button
|
|
||||||
class="cancel"
|
|
||||||
label="Undo changes"
|
|
||||||
disabled="!watcher.dataChanged()"
|
|
||||||
ng-click="watcher.loadOriginalData()">
|
|
||||||
</vn-button>
|
|
||||||
</vn-button-bar>
|
|
||||||
</form>
|
|
|
@ -1,57 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Section from 'salix/components/section';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
class Controller extends Section {
|
|
||||||
constructor($element, $) {
|
|
||||||
super($element, $);
|
|
||||||
let isDirty = false;
|
|
||||||
this.$.$watch('$ctrl.selection', newValue => {
|
|
||||||
if (newValue) {
|
|
||||||
this.$.addressModel.where = {clientFk: newValue.id};
|
|
||||||
this.$.addressModel.refresh();
|
|
||||||
if (isDirty)
|
|
||||||
this.order.addressFk = newValue.defaultAddressFk;
|
|
||||||
isDirty = true;
|
|
||||||
} else {
|
|
||||||
this.$.addressModel.clear();
|
|
||||||
if (isDirty)
|
|
||||||
this.order.addressFk = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
set order(value = {}) {
|
|
||||||
this._order = value;
|
|
||||||
|
|
||||||
const agencyModeFk = value.agencyModeFk;
|
|
||||||
this.getAvailableAgencies();
|
|
||||||
this._order.agencyModeFk = agencyModeFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
get order() {
|
|
||||||
return this._order;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAvailableAgencies() {
|
|
||||||
const order = this.order;
|
|
||||||
order.agencyModeFk = null;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
addressFk: order.addressFk,
|
|
||||||
landed: order.landed
|
|
||||||
};
|
|
||||||
if (params.landed && params.addressFk) {
|
|
||||||
this.$http.get(`Agencies/landsThatDay`, {params})
|
|
||||||
.then(res => this._availableAgencies = res.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderBasicData', {
|
|
||||||
controller: Controller,
|
|
||||||
template: require('./index.html'),
|
|
||||||
bindings: {
|
|
||||||
order: '<'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,67 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderBasicData', () => {
|
|
||||||
let $httpBackend;
|
|
||||||
let $httpParamSerializer;
|
|
||||||
let controller;
|
|
||||||
let $scope;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($compile, _$httpBackend_, $rootScope, _$httpParamSerializer_) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
$httpParamSerializer = _$httpParamSerializer_;
|
|
||||||
$scope = $rootScope.$new();
|
|
||||||
|
|
||||||
$httpBackend.whenRoute('GET', 'Addresses')
|
|
||||||
.respond([{id: 2, nickname: 'address 2'}]);
|
|
||||||
$httpBackend.whenRoute('GET', 'Clients')
|
|
||||||
.respond([{id: 1, defaultAddressFk: 1}]);
|
|
||||||
$scope.order = {clientFk: 1, addressFk: 1};
|
|
||||||
|
|
||||||
let $element = $compile('<vn-order-basic-data order="order"></vn-order-basic-data>')($scope);
|
|
||||||
$httpBackend.flush();
|
|
||||||
controller = $element.controller('vnOrderBasicData');
|
|
||||||
}));
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
$scope.$destroy();
|
|
||||||
$element.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('constructor()', () => {
|
|
||||||
it('should update the address after the client changes', async() => {
|
|
||||||
const addressId = 999;
|
|
||||||
const id = 444;
|
|
||||||
|
|
||||||
controller.selection = {id: id, defaultAddressFk: addressId};
|
|
||||||
$scope.$digest();
|
|
||||||
|
|
||||||
expect(controller.order.addressFk).toEqual(addressId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getAvailableAgencies()', () => {
|
|
||||||
it('should set agencyModeFk to null and get the available agencies if the order has landed and client', async() => {
|
|
||||||
controller.order.agencyModeFk = 999;
|
|
||||||
controller.order.addressFk = 999;
|
|
||||||
controller.order.landed = Date.vnNew();
|
|
||||||
|
|
||||||
const expectedAgencies = [{id: 1}, {id: 2}];
|
|
||||||
|
|
||||||
const paramsObj = {
|
|
||||||
addressFk: controller.order.addressFk,
|
|
||||||
landed: controller.order.landed
|
|
||||||
};
|
|
||||||
const serializedParams = $httpParamSerializer(paramsObj);
|
|
||||||
$httpBackend.expect('GET', `Agencies/landsThatDay?${serializedParams}`).respond(expectedAgencies);
|
|
||||||
controller.getAvailableAgencies();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.order.agencyModeFk).toBeDefined();
|
|
||||||
expect(controller._availableAgencies).toEqual(expectedAgencies);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
This form has been disabled because there are lines in this order or it's confirmed: Este formulario ha sido deshabilitado por que esta orden contiene líneas o está confirmada
|
|
|
@ -1,9 +0,0 @@
|
||||||
vn-order-basic-data {
|
|
||||||
.disabledForm {
|
|
||||||
text-align: center;
|
|
||||||
color: red;
|
|
||||||
span {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
<vn-portal slot="menu">
|
|
||||||
<vn-order-descriptor order="$ctrl.order"></vn-order-descriptor>
|
|
||||||
<vn-left-menu source="card"></vn-left-menu>
|
|
||||||
</vn-portal>
|
|
||||||
<ui-view></ui-view>
|
|
|
@ -1,58 +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']
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
relation: 'address',
|
|
||||||
scope: {
|
|
||||||
fields: ['nickname']
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
relation: 'rows',
|
|
||||||
scope: {
|
|
||||||
fields: ['id']
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
relation: 'client',
|
|
||||||
scope: {
|
|
||||||
fields: [
|
|
||||||
'salesPersonFk',
|
|
||||||
'name',
|
|
||||||
'isActive',
|
|
||||||
'isFreezed',
|
|
||||||
'isTaxDataChecked'
|
|
||||||
],
|
|
||||||
include: {
|
|
||||||
relation: 'salesPersonUser',
|
|
||||||
scope: {
|
|
||||||
fields: ['id', 'name']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.$q.all([
|
|
||||||
this.$http.get(`Orders/${this.$params.id}`, {filter})
|
|
||||||
.then(res => this.order = res.data),
|
|
||||||
this.$http.get(`Orders/${this.$params.id}/getTotal`)
|
|
||||||
.then(res => ({total: res.data}))
|
|
||||||
]).then(res => {
|
|
||||||
this.order = Object.assign.apply(null, res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderCard', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller
|
|
||||||
});
|
|
|
@ -1,31 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderCard', () => {
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
let data = {id: 1, name: 'fooName'};
|
|
||||||
let total = 10.5;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$httpBackend_, $stateParams) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
|
|
||||||
let $element = angular.element('<div></div>');
|
|
||||||
controller = $componentController('vnOrderCard', {$element});
|
|
||||||
|
|
||||||
$stateParams.id = data.id;
|
|
||||||
$httpBackend.whenRoute('GET', 'Orders/:id').respond(data);
|
|
||||||
$httpBackend.whenRoute('GET', 'Orders/:id/getTotal').respond(200, total);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should request data and total, merge them, and set it on the controller', () => {
|
|
||||||
controller.reload();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.order).toEqual(Object.assign({}, data, {total}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
<div class="vn-pa-lg" style="min-width: 18em">
|
|
||||||
<form name="form" ng-submit="$ctrl.onSearch()">
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-autocomplete
|
|
||||||
vn-id="tag"
|
|
||||||
vn-one
|
|
||||||
selection="filter.tagSelection"
|
|
||||||
ng-model="filter.tagFk"
|
|
||||||
data="$ctrl.resultTags"
|
|
||||||
show-field="name"
|
|
||||||
label="Tag"
|
|
||||||
on-change="itemTag.value = null">
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal ng-repeat="tagValue in filter.values">
|
|
||||||
<vn-textfield
|
|
||||||
vn-one
|
|
||||||
ng-show="tag.selection.isFree != false"
|
|
||||||
vn-id="text"
|
|
||||||
label="Value"
|
|
||||||
ng-model="tagValue.value">
|
|
||||||
</vn-textfield>
|
|
||||||
<vn-autocomplete
|
|
||||||
vn-one
|
|
||||||
ng-show="tag.selection.isFree == false"
|
|
||||||
url="{{'Tags/' + tag.selection.id + '/filterValue'}}"
|
|
||||||
search-function="{value: $search}"
|
|
||||||
label="Value"
|
|
||||||
ng-model="tagValue.value"
|
|
||||||
show-field="value"
|
|
||||||
value-field="value">
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-icon-button
|
|
||||||
vn-none
|
|
||||||
vn-tooltip="Remove tag"
|
|
||||||
icon="delete"
|
|
||||||
ng-click="filter.values.splice($index, 1)"
|
|
||||||
tabindex="-1">
|
|
||||||
</vn-icon-button>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-icon-button
|
|
||||||
vn-none
|
|
||||||
vn-bind="+"
|
|
||||||
vn-tooltip="Add value"
|
|
||||||
icon="add_circle"
|
|
||||||
ng-click="$ctrl.addValue()">
|
|
||||||
</vn-icon-button>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal class="vn-mt-lg">
|
|
||||||
<vn-submit label="Search"></vn-submit>
|
|
||||||
</vn-horizontal>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
|
@ -1,38 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import SearchPanel from 'core/components/searchbar/search-panel';
|
|
||||||
|
|
||||||
class Controller extends SearchPanel {
|
|
||||||
constructor($element, $) {
|
|
||||||
super($element, $);
|
|
||||||
|
|
||||||
this.filter = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
get filter() {
|
|
||||||
return this.$.filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
set filter(value) {
|
|
||||||
if (!value)
|
|
||||||
value = {};
|
|
||||||
if (!value.values)
|
|
||||||
value.values = [{}];
|
|
||||||
|
|
||||||
this.$.filter = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
addValue() {
|
|
||||||
this.filter.values.push({});
|
|
||||||
setTimeout(() => this.parentPopover.relocate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderCatalogSearchPanel', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
onSubmit: '&?',
|
|
||||||
parentPopover: '<?',
|
|
||||||
resultTags: '<?'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,81 +0,0 @@
|
||||||
<vn-data-viewer
|
|
||||||
model="$ctrl.model">
|
|
||||||
<vn-horizontal class="catalog-list">
|
|
||||||
<section ng-repeat="item in $ctrl.model.data" class="product">
|
|
||||||
<vn-card>
|
|
||||||
<div class="image">
|
|
||||||
<div ng-if="::item.hex != null" class="item-color-background">
|
|
||||||
<div class="item-color" ng-style="{'background-color': '#' + item.hex}"></div>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
ng-src="{{::$root.imagePath('catalog', '200x200', item.id)}}"
|
|
||||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
|
|
||||||
on-error-src/>
|
|
||||||
</div>
|
|
||||||
<div class="description">
|
|
||||||
<h3 class="link"
|
|
||||||
ng-click="itemDescriptor.show($event, item.id)">
|
|
||||||
{{::item.name}}
|
|
||||||
</h3>
|
|
||||||
<h4 class="ellipsize">
|
|
||||||
<span translate-attr="::{title: item.subName}">{{::item.subName}}</span>
|
|
||||||
</h4>
|
|
||||||
<div class="tags">
|
|
||||||
<vn-label-value
|
|
||||||
ng-if="::item.value5"
|
|
||||||
label="{{::item.tag5}}"
|
|
||||||
value="{{::item.value5}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
ng-if="::item.value6"
|
|
||||||
label="{{::item.tag6}}"
|
|
||||||
value="{{::item.value6}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
ng-if="::item.value7"
|
|
||||||
label="{{::item.tag7}}"
|
|
||||||
value="{{::item.value7}}">
|
|
||||||
</vn-label-value>
|
|
||||||
</div>
|
|
||||||
<vn-horizontal
|
|
||||||
class="text-right text-caption alert vn-mr-xs"
|
|
||||||
ng-if="::item.minQuantity">
|
|
||||||
<vn-one>
|
|
||||||
<vn-icon
|
|
||||||
icon="production_quantity_limits"
|
|
||||||
translate-attr="{title: 'Minimal quantity'}"
|
|
||||||
class="text-subtitle1">
|
|
||||||
</vn-icon>
|
|
||||||
</vn-one>
|
|
||||||
{{::item.minQuantity}}
|
|
||||||
</vn-horizontal>
|
|
||||||
<div class="footer">
|
|
||||||
<div class="price">
|
|
||||||
<vn-one>
|
|
||||||
<span>{{::item.available}}</span>
|
|
||||||
<span translate>to</span>
|
|
||||||
<span>{{::item.price | currency:'EUR':2}}</span>
|
|
||||||
</vn-one>
|
|
||||||
<vn-icon-button vn-none
|
|
||||||
icon="add_circle"
|
|
||||||
ng-click="pricesPopover.show($event, item)"
|
|
||||||
vn-tooltip="Add">
|
|
||||||
</vn-icon-button>
|
|
||||||
</div>
|
|
||||||
<div class="priceKg" ng-show="::item.priceKg">
|
|
||||||
<span>Precio por kilo {{::item.priceKg | currency: 'EUR'}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</vn-card>
|
|
||||||
</section>
|
|
||||||
</vn-horizontal>
|
|
||||||
</vn-data-viewer>
|
|
||||||
<vn-order-prices-popover
|
|
||||||
vn-id="prices-popover"
|
|
||||||
order="$ctrl.order">
|
|
||||||
</vn-order-prices-popover>
|
|
||||||
<vn-item-descriptor-popover
|
|
||||||
vn-id="item-descriptor"
|
|
||||||
warehouse-fk="$ctrl.vnConfig.warehouseFk">
|
|
||||||
</vn-item-descriptor-popover>
|
|
|
@ -1,12 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Component from 'core/lib/component';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderCatalogView', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Component,
|
|
||||||
bindings: {
|
|
||||||
order: '<',
|
|
||||||
model: '<'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
Order created: Orden creada
|
|
|
@ -1,50 +0,0 @@
|
||||||
@import "variables";
|
|
||||||
|
|
||||||
vn-order-catalog {
|
|
||||||
.catalog-header {
|
|
||||||
border-bottom: $border-thin;
|
|
||||||
padding: $spacing-md;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
& > vn-one {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: $color-font-secondary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& > vn-auto {
|
|
||||||
width: 448px;
|
|
||||||
display: flex;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
& > * {
|
|
||||||
padding-left: $spacing-md;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.catalog-list {
|
|
||||||
padding-top: $spacing-sm;
|
|
||||||
}
|
|
||||||
.item-color-background {
|
|
||||||
background: linear-gradient($color-bg-panel, $color-main);
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-left: 140px;
|
|
||||||
margin-top: 140px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.item-color {
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.alert {
|
|
||||||
color: $color-alert;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
<vn-crud-model
|
|
||||||
url="ItemCategories"
|
|
||||||
data="categories"
|
|
||||||
auto-load="true">
|
|
||||||
</vn-crud-model>
|
|
||||||
<vn-crud-model
|
|
||||||
vn-id="model"
|
|
||||||
url="Orders/CatalogFilter"
|
|
||||||
params="{orderFk: $ctrl.$params.id}"
|
|
||||||
limit="50"
|
|
||||||
data="$ctrl.items">
|
|
||||||
</vn-crud-model>
|
|
||||||
<vn-portal slot="topbar">
|
|
||||||
<vn-searchbar vn-id="searchbar"
|
|
||||||
auto-state="false"
|
|
||||||
info="Search by item id or name"
|
|
||||||
on-search="$ctrl.onSearch($params)">
|
|
||||||
</vn-searchbar>
|
|
||||||
</vn-portal>
|
|
||||||
<vn-order-catalog-view
|
|
||||||
model="model"
|
|
||||||
order="$ctrl.order">
|
|
||||||
</vn-order-catalog-view>
|
|
||||||
<vn-side-menu side="right">
|
|
||||||
<vn-horizontal class="item-category">
|
|
||||||
<vn-autocomplete vn-id="category"
|
|
||||||
data="categories"
|
|
||||||
ng-model="$ctrl.categoryId"
|
|
||||||
show-field="name"
|
|
||||||
value-field="id"
|
|
||||||
label="Category">
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-one ng-repeat="category in categories">
|
|
||||||
<vn-icon
|
|
||||||
ng-class="{'active': $ctrl.categoryId == category.id}"
|
|
||||||
icon="{{::category.icon}}"
|
|
||||||
vn-tooltip="{{::category.name}}"
|
|
||||||
ng-click="$ctrl.changeCategory(category.id)">
|
|
||||||
</vn-icon>
|
|
||||||
</vn-one>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-vertical class="input">
|
|
||||||
<vn-autocomplete vn-id="type"
|
|
||||||
data="$ctrl.itemTypes"
|
|
||||||
ng-model="$ctrl.typeId"
|
|
||||||
show-field="name"
|
|
||||||
value-field="id"
|
|
||||||
label="Type"
|
|
||||||
fields="['categoryFk']"
|
|
||||||
include="'category'">
|
|
||||||
<tpl-item>
|
|
||||||
<div>{{name}}</div>
|
|
||||||
<div class="text-caption text-secondary">
|
|
||||||
{{categoryName}}
|
|
||||||
</div>
|
|
||||||
</tpl-item>
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-vertical>
|
|
||||||
<vn-vertical class="input vn-pt-md">
|
|
||||||
<vn-autocomplete
|
|
||||||
vn-id="field"
|
|
||||||
data="$ctrl.orderFields"
|
|
||||||
ng-model="$ctrl.orderField"
|
|
||||||
selection="$ctrl.orderSelection"
|
|
||||||
translate-fields="['name']"
|
|
||||||
order="priority DESC"
|
|
||||||
show-field="name"
|
|
||||||
value-field="field"
|
|
||||||
label="Order by"
|
|
||||||
disabled="!model.data">
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-autocomplete
|
|
||||||
data="$ctrl.orderWays"
|
|
||||||
ng-model="$ctrl.orderWay"
|
|
||||||
translate-fields="['name']"
|
|
||||||
show-field="name"
|
|
||||||
value-field="way"
|
|
||||||
label="Order"
|
|
||||||
disabled="!model.data">
|
|
||||||
</vn-autocomplete>
|
|
||||||
<div ng-if="false && model.moreRows">
|
|
||||||
<span translate>More than</span> {{model.limit}} <span translate>results</span>
|
|
||||||
</div>
|
|
||||||
</vn-vertical>
|
|
||||||
<vn-vertical class="input vn-pt-md">
|
|
||||||
<vn-textfield vn-one
|
|
||||||
vn-id="search"
|
|
||||||
ng-keyUp="$ctrl.onSearchByTag($event)"
|
|
||||||
label="Search tag">
|
|
||||||
<prepend>
|
|
||||||
<vn-icon icon="search"></vn-icon>
|
|
||||||
</prepend>
|
|
||||||
<append>
|
|
||||||
<vn-icon
|
|
||||||
icon="keyboard_arrow_down"
|
|
||||||
ng-click="$ctrl.openPanel($event)"
|
|
||||||
style="cursor: pointer;">
|
|
||||||
</vn-icon>
|
|
||||||
</append>
|
|
||||||
</vn-textfield>
|
|
||||||
</vn-vertical>
|
|
||||||
<vn-popover
|
|
||||||
vn-id="popover"
|
|
||||||
on-close="$ctrl.onPopoverClose()">
|
|
||||||
<vn-order-catalog-search-panel
|
|
||||||
on-submit="$ctrl.onPanelSubmit($filter)"
|
|
||||||
parent-popover="popover"
|
|
||||||
result-tags="$ctrl.resultTags">
|
|
||||||
</vn-order-catalog-search-panel>
|
|
||||||
</vn-popover>
|
|
||||||
<div class="chips">
|
|
||||||
<vn-chip
|
|
||||||
ng-if="$ctrl.itemId"
|
|
||||||
removable="true"
|
|
||||||
vn-tooltip="Item id"
|
|
||||||
on-remove="$ctrl.removeItemId()"
|
|
||||||
class="colored">
|
|
||||||
<span>Id: {{$ctrl.itemId}}</span>
|
|
||||||
</vn-chip>
|
|
||||||
<vn-chip
|
|
||||||
ng-if="$ctrl.itemName"
|
|
||||||
removable="true"
|
|
||||||
vn-tooltip="Item"
|
|
||||||
on-remove="$ctrl.removeItemName()"
|
|
||||||
class="colored">
|
|
||||||
<div>
|
|
||||||
<span>
|
|
||||||
<span translate>Name</span>:
|
|
||||||
</span>
|
|
||||||
<span>{{$ctrl.itemName}}</span>
|
|
||||||
</div>
|
|
||||||
</vn-chip>
|
|
||||||
<vn-chip
|
|
||||||
ng-if="category.selection"
|
|
||||||
removable="true"
|
|
||||||
vn-tooltip="Category"
|
|
||||||
on-remove="$ctrl.categoryId = null"
|
|
||||||
class="colored">
|
|
||||||
<span translate>{{category.selection.name}}</span>
|
|
||||||
</vn-chip>
|
|
||||||
<vn-chip
|
|
||||||
ng-if="type.selection"
|
|
||||||
removable="true"
|
|
||||||
vn-tooltip="Type"
|
|
||||||
on-remove="$ctrl.typeId = null"
|
|
||||||
class="colored">
|
|
||||||
<span translate>{{type.selection.name}}</span>
|
|
||||||
</vn-chip>
|
|
||||||
<vn-chip
|
|
||||||
ng-repeat="tagGroup in $ctrl.tagGroups"
|
|
||||||
removable="true"
|
|
||||||
on-remove="$ctrl.remove($index)"
|
|
||||||
vn-tooltip="{{::$ctrl.formatTooltip(tagGroup)}}"
|
|
||||||
class="colored">
|
|
||||||
<div>
|
|
||||||
<span ng-if="::tagGroup.tagFk">
|
|
||||||
<span translate>{{::tagGroup.tagSelection.name}}</span>:
|
|
||||||
</span>
|
|
||||||
<span ng-repeat="tagValue in tagGroup.values">
|
|
||||||
<span ng-if="$index > 0">,</span>
|
|
||||||
<span>"{{::tagValue.value}}"</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</vn-chip>
|
|
||||||
</div>
|
|
||||||
</vn-side-menu>
|
|
|
@ -1,377 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Section from 'salix/components/section';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
class Controller extends Section {
|
|
||||||
constructor($element, $) {
|
|
||||||
super($element, $);
|
|
||||||
this.itemTypes = [];
|
|
||||||
this._tagGroups = [];
|
|
||||||
|
|
||||||
// Static autocomplete data
|
|
||||||
this.orderWays = [
|
|
||||||
{way: 'ASC', name: 'Ascendant'},
|
|
||||||
{way: 'DESC', name: 'Descendant'},
|
|
||||||
];
|
|
||||||
this.defaultOrderFields = [
|
|
||||||
{field: 'relevancy DESC, name', name: 'Relevancy', priority: 999},
|
|
||||||
{field: 'showOrder, price', name: 'Color and price', priority: 999},
|
|
||||||
{field: 'name', name: 'Name', priority: 999},
|
|
||||||
{field: 'price', name: 'Price', priority: 999}
|
|
||||||
];
|
|
||||||
this.orderFields = [].concat(this.defaultOrderFields);
|
|
||||||
this._orderWay = this.orderWays[0].way;
|
|
||||||
this.orderField = this.orderFields[0].field;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onChanges() {
|
|
||||||
this.getData().then(() => {
|
|
||||||
if (this.order && this.order.isConfirmed)
|
|
||||||
this.$state.go('order.card.line');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getData() {
|
|
||||||
return this.$http.get(`Orders/${this.$params.id}`)
|
|
||||||
.then(res => this.order = res.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fills order autocomplete with tags
|
|
||||||
* obtained from last filtered
|
|
||||||
*/
|
|
||||||
get order() {
|
|
||||||
return this._order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets filter values from state params
|
|
||||||
*
|
|
||||||
* @param {Object} value - Order data
|
|
||||||
*/
|
|
||||||
set order(value) {
|
|
||||||
this._order = value;
|
|
||||||
|
|
||||||
if (!value) return;
|
|
||||||
|
|
||||||
this.$.$applyAsync(() => {
|
|
||||||
if (this.$params.categoryId)
|
|
||||||
this.categoryId = parseInt(this.$params.categoryId);
|
|
||||||
|
|
||||||
if (this.$params.typeId)
|
|
||||||
this.typeId = parseInt(this.$params.typeId);
|
|
||||||
|
|
||||||
if (this.$params.tagGroups)
|
|
||||||
this.tagGroups = JSON.parse(this.$params.tagGroups);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get items() {
|
|
||||||
return this._items;
|
|
||||||
}
|
|
||||||
|
|
||||||
set items(value) {
|
|
||||||
this._items = value;
|
|
||||||
|
|
||||||
if (!value) return;
|
|
||||||
|
|
||||||
this.fetchResultTags(value);
|
|
||||||
this.buildOrderFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
get categoryId() {
|
|
||||||
return this._categoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
set categoryId(value = null) {
|
|
||||||
this._categoryId = value;
|
|
||||||
this.itemTypes = [];
|
|
||||||
this.typeId = null;
|
|
||||||
|
|
||||||
this.updateStateParams();
|
|
||||||
|
|
||||||
if (this.tagGroups.length > 0)
|
|
||||||
this.applyFilters();
|
|
||||||
|
|
||||||
if (value)
|
|
||||||
this.updateItemTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
changeCategory(id) {
|
|
||||||
if (this._categoryId == id) id = null;
|
|
||||||
this.categoryId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get typeId() {
|
|
||||||
return this._typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
set typeId(value) {
|
|
||||||
this._typeId = value;
|
|
||||||
|
|
||||||
this.updateStateParams();
|
|
||||||
|
|
||||||
if (value || this.tagGroups.length > 0)
|
|
||||||
this.applyFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
get tagGroups() {
|
|
||||||
return this._tagGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
set tagGroups(value) {
|
|
||||||
this._tagGroups = value;
|
|
||||||
|
|
||||||
this.updateStateParams();
|
|
||||||
|
|
||||||
if (value.length)
|
|
||||||
this.applyFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get order way ASC/DESC
|
|
||||||
*/
|
|
||||||
get orderWay() {
|
|
||||||
return this._orderWay;
|
|
||||||
}
|
|
||||||
|
|
||||||
set orderWay(value) {
|
|
||||||
this._orderWay = value;
|
|
||||||
if (value) this.applyOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the order way selection
|
|
||||||
*/
|
|
||||||
get orderSelection() {
|
|
||||||
return this._orderSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
set orderSelection(value) {
|
|
||||||
this._orderSelection = value;
|
|
||||||
|
|
||||||
if (value) this.applyOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply order to model
|
|
||||||
*/
|
|
||||||
applyOrder() {
|
|
||||||
if (this.typeId || this.tagGroups.length > 0 || this.itemName)
|
|
||||||
this.$.model.addFilter(null, {orderBy: this.getOrderBy()});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns order param
|
|
||||||
*
|
|
||||||
* @return {Object} - Order param
|
|
||||||
*/
|
|
||||||
getOrderBy() {
|
|
||||||
const isTag = !!(this.orderSelection && this.orderSelection.isTag);
|
|
||||||
return {
|
|
||||||
field: this.orderField,
|
|
||||||
way: this.orderWay,
|
|
||||||
isTag: isTag
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes item type dropdown data
|
|
||||||
*/
|
|
||||||
updateItemTypes() {
|
|
||||||
let params = {
|
|
||||||
itemCategoryId: this.categoryId
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = `Orders/${this.order.id}/getItemTypeAvailable`;
|
|
||||||
this.$http.get(query, {params}).then(res =>
|
|
||||||
this.itemTypes = res.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search by tag value
|
|
||||||
* @param {object} event
|
|
||||||
*/
|
|
||||||
onSearchByTag(event) {
|
|
||||||
const value = this.$.search.value;
|
|
||||||
if (event.key !== 'Enter' || !value) return;
|
|
||||||
this.tagGroups.push({values: [{value: value}]});
|
|
||||||
this.$.search.value = null;
|
|
||||||
this.updateStateParams();
|
|
||||||
this.applyFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(index) {
|
|
||||||
this.tagGroups.splice(index, 1);
|
|
||||||
this.updateStateParams();
|
|
||||||
|
|
||||||
if (this.tagGroups.length >= 0 || this.itemId || this.typeId)
|
|
||||||
this.applyFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItemId() {
|
|
||||||
this.itemId = null;
|
|
||||||
this.$.searchbar.doSearch({}, 'bar');
|
|
||||||
}
|
|
||||||
|
|
||||||
removeItemName() {
|
|
||||||
this.itemName = null;
|
|
||||||
this.$.searchbar.doSearch({}, 'bar');
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilters(filter = {}) {
|
|
||||||
let newParams = {};
|
|
||||||
let newFilter = Object.assign({}, filter);
|
|
||||||
const model = this.$.model;
|
|
||||||
|
|
||||||
if (this.categoryId)
|
|
||||||
newFilter.categoryFk = this.categoryId;
|
|
||||||
|
|
||||||
if (this.typeId)
|
|
||||||
newFilter.typeFk = this.typeId;
|
|
||||||
|
|
||||||
newParams = {
|
|
||||||
orderFk: this.$params.id,
|
|
||||||
orderBy: this.getOrderBy(),
|
|
||||||
tagGroups: this.tagGroups,
|
|
||||||
};
|
|
||||||
|
|
||||||
return model.applyFilter({where: newFilter}, newParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
openPanel(event) {
|
|
||||||
if (event.defaultPrevented) return;
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.panelFilter = {};
|
|
||||||
this.$.popover.show(this.$.search.element);
|
|
||||||
}
|
|
||||||
|
|
||||||
onPanelSubmit(filter) {
|
|
||||||
this.$.popover.hide();
|
|
||||||
const values = filter.values;
|
|
||||||
const nonEmptyValues = values.filter(tagValue => {
|
|
||||||
return tagValue.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
filter.values = nonEmptyValues;
|
|
||||||
|
|
||||||
if (filter.tagFk && nonEmptyValues.length) {
|
|
||||||
this.tagGroups.push(filter);
|
|
||||||
this.updateStateParams();
|
|
||||||
this.applyFilters();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates url state params from filter values
|
|
||||||
*/
|
|
||||||
updateStateParams() {
|
|
||||||
const params = {};
|
|
||||||
|
|
||||||
params.categoryId = undefined;
|
|
||||||
if (this.categoryId)
|
|
||||||
params.categoryId = this.categoryId;
|
|
||||||
|
|
||||||
params.typeId = undefined;
|
|
||||||
if (this.typeId)
|
|
||||||
params.typeId = this.typeId;
|
|
||||||
|
|
||||||
params.tagGroups = undefined;
|
|
||||||
if (this.tagGroups && this.tagGroups.length)
|
|
||||||
params.tagGroups = JSON.stringify(this.sanitizedTagGroupParam());
|
|
||||||
|
|
||||||
this.$state.go(this.$state.current.name, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
sanitizedTagGroupParam() {
|
|
||||||
const tagGroups = [];
|
|
||||||
for (let tagGroup of this.tagGroups) {
|
|
||||||
const tagParam = {values: []};
|
|
||||||
|
|
||||||
for (let tagValue of tagGroup.values)
|
|
||||||
tagParam.values.push({value: tagValue.value});
|
|
||||||
|
|
||||||
if (tagGroup.tagFk)
|
|
||||||
tagParam.tagFk = tagGroup.tagFk;
|
|
||||||
|
|
||||||
if (tagGroup.tagSelection) {
|
|
||||||
tagParam.tagSelection = {
|
|
||||||
name: tagGroup.tagSelection.name
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
tagGroups.push(tagParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tagGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchResultTags(items) {
|
|
||||||
const resultTags = [];
|
|
||||||
for (let item of items) {
|
|
||||||
for (let itemTag of item.tags) {
|
|
||||||
const alreadyAdded = resultTags.findIndex(tag => {
|
|
||||||
return tag.tagFk == itemTag.tagFk;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (alreadyAdded == -1)
|
|
||||||
resultTags.push({...itemTag, priority: 1});
|
|
||||||
else
|
|
||||||
resultTags[alreadyAdded].priority += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.resultTags = resultTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
buildOrderFilter() {
|
|
||||||
const filter = [].concat(this.defaultOrderFields);
|
|
||||||
for (let tag of this.resultTags)
|
|
||||||
filter.push({...tag, field: tag.id, isTag: true});
|
|
||||||
|
|
||||||
this.orderFields = filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearch(params) {
|
|
||||||
if (!params) return;
|
|
||||||
|
|
||||||
this.itemId = null;
|
|
||||||
this.itemName = null;
|
|
||||||
|
|
||||||
if (params.search) {
|
|
||||||
if (/^\d+$/.test(params.search)) {
|
|
||||||
this.itemId = params.search;
|
|
||||||
return this.applyFilters({
|
|
||||||
'i.id': params.search
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.itemName = params.search;
|
|
||||||
return this.applyFilters({
|
|
||||||
'i.name': {like: `%${params.search}%`}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else return this.applyFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
formatTooltip(tagGroup) {
|
|
||||||
const tagValues = tagGroup.values;
|
|
||||||
|
|
||||||
let title = '';
|
|
||||||
if (tagGroup.tagFk) {
|
|
||||||
const tagName = tagGroup.tagSelection.name;
|
|
||||||
title += `${tagName}: `;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let [i, tagValue] of tagValues.entries()) {
|
|
||||||
if (i > 0) title += ', ';
|
|
||||||
title += `"${tagValue.value}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${title}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderCatalog', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller
|
|
||||||
});
|
|
|
@ -1,386 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
import crudModel from 'core/mocks/crud-model';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderCatalog', () => {
|
|
||||||
let $scope;
|
|
||||||
let $state;
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$state_, _$httpBackend_, $rootScope) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
$scope = $rootScope.$new();
|
|
||||||
$scope.model = crudModel;
|
|
||||||
$scope.search = {};
|
|
||||||
$scope.itemId = {};
|
|
||||||
$state = _$state_;
|
|
||||||
$state.current.name = 'my.current.state';
|
|
||||||
const $element = angular.element('<vn-order-catalog></vn-order-catalog>');
|
|
||||||
controller = $componentController('vnOrderCatalog', {$element, $scope});
|
|
||||||
controller._order = {id: 4};
|
|
||||||
controller.$params = {
|
|
||||||
categoryId: 1,
|
|
||||||
typeId: 2,
|
|
||||||
id: 4
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('getData()', () => {
|
|
||||||
it(`should make a query an fetch the order data`, () => {
|
|
||||||
controller._order = null;
|
|
||||||
|
|
||||||
$httpBackend.expect('GET', `Orders/4`).respond(200, {id: 4, isConfirmed: true});
|
|
||||||
$httpBackend.expect('GET', `Orders/4/getItemTypeAvailable?itemCategoryId=1`).respond();
|
|
||||||
controller.getData();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
const order = controller.order;
|
|
||||||
|
|
||||||
expect(order.id).toEqual(4);
|
|
||||||
expect(order.isConfirmed).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('order() setter', () => {
|
|
||||||
it(`should call scope $applyAsync() method and apply filters from state params`, () => {
|
|
||||||
$httpBackend.expect('GET', `Orders/4/getItemTypeAvailable?itemCategoryId=1`).respond();
|
|
||||||
controller.order = {id: 4};
|
|
||||||
|
|
||||||
$scope.$apply();
|
|
||||||
|
|
||||||
expect(controller.categoryId).toEqual(1);
|
|
||||||
expect(controller.typeId).toEqual(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('items() setter', () => {
|
|
||||||
it(`should return an object with order params`, () => {
|
|
||||||
jest.spyOn(controller, 'fetchResultTags');
|
|
||||||
jest.spyOn(controller, 'buildOrderFilter');
|
|
||||||
|
|
||||||
const expectedResult = [{field: 'showOrder, price', name: 'Color and price', priority: 999}];
|
|
||||||
const items = [{id: 1, name: 'My Item', tags: [
|
|
||||||
{tagFk: 4, name: 'Length'},
|
|
||||||
{tagFk: 5, name: 'Color'}
|
|
||||||
]}];
|
|
||||||
controller.items = items;
|
|
||||||
|
|
||||||
expect(controller.orderFields.length).toEqual(6);
|
|
||||||
expect(controller.orderFields).toEqual(jasmine.arrayContaining(expectedResult));
|
|
||||||
expect(controller.fetchResultTags).toHaveBeenCalledWith(items);
|
|
||||||
expect(controller.buildOrderFilter).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('categoryId() setter', () => {
|
|
||||||
it(`should set category property to null, call updateStateParams() method and not call applyFilters()`, () => {
|
|
||||||
jest.spyOn(controller, 'updateStateParams');
|
|
||||||
|
|
||||||
controller.categoryId = null;
|
|
||||||
|
|
||||||
expect(controller.updateStateParams).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
|
|
||||||
jest.spyOn(controller, 'updateStateParams');
|
|
||||||
|
|
||||||
controller.categoryId = 2;
|
|
||||||
|
|
||||||
expect(controller.updateStateParams).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('changeCategory()', () => {
|
|
||||||
it(`should set categoryId property to null if the new value equals to the old one`, () => {
|
|
||||||
controller.categoryId = 2;
|
|
||||||
controller.changeCategory(2);
|
|
||||||
|
|
||||||
expect(controller.categoryId).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should set categoryId property`, () => {
|
|
||||||
controller.categoryId = 2;
|
|
||||||
controller.changeCategory(1);
|
|
||||||
|
|
||||||
expect(controller.categoryId).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('typeId() setter', () => {
|
|
||||||
it(`should set type property to null, call updateStateParams() method and not call applyFilters()`, () => {
|
|
||||||
jest.spyOn(controller, 'updateStateParams');
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
controller.typeId = null;
|
|
||||||
|
|
||||||
expect(controller.updateStateParams).toHaveBeenCalledWith();
|
|
||||||
expect(controller.applyFilters).not.toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
|
|
||||||
jest.spyOn(controller, 'updateStateParams');
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
controller.typeId = 2;
|
|
||||||
|
|
||||||
expect(controller.updateStateParams).toHaveBeenCalledWith();
|
|
||||||
expect(controller.applyFilters).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('tagGroups() setter', () => {
|
|
||||||
it(`should set tagGroups property and then call updateStateParams() and applyFilters() methods`, () => {
|
|
||||||
jest.spyOn(controller, 'updateStateParams');
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
controller.tagGroups = [{tagFk: 11, values: [{value: 'Brown'}]}];
|
|
||||||
|
|
||||||
expect(controller.updateStateParams).toHaveBeenCalledWith();
|
|
||||||
expect(controller.applyFilters).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('onSearchByTag()', () => {
|
|
||||||
it(`should not add a new tag if the event key code doesn't equals to 'Enter'`, () => {
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
controller.order = {id: 4};
|
|
||||||
controller.$.search.value = 'Brown';
|
|
||||||
controller.onSearchByTag({key: 'Tab'});
|
|
||||||
|
|
||||||
expect(controller.applyFilters).not.toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should add a new tag if the event key code equals to 'Enter' an then call applyFilters()`, () => {
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
controller.order = {id: 4};
|
|
||||||
controller.$.search.value = 'Brown';
|
|
||||||
controller.onSearchByTag({key: 'Enter'});
|
|
||||||
|
|
||||||
expect(controller.applyFilters).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('onSearch()', () => {
|
|
||||||
it(`should apply a filter by item id an then call the applyFilters method`, () => {
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
const itemId = 1;
|
|
||||||
controller.onSearch({search: itemId});
|
|
||||||
|
|
||||||
expect(controller.applyFilters).toHaveBeenCalledWith({
|
|
||||||
'i.id': itemId
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should apply a filter by item name an then call the applyFilters method`, () => {
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
const itemName = 'Bow';
|
|
||||||
controller.onSearch({search: itemName});
|
|
||||||
|
|
||||||
expect(controller.applyFilters).toHaveBeenCalledWith({
|
|
||||||
'i.name': {like: `%${itemName}%`}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('applyFilters()', () => {
|
|
||||||
it(`should call model applyFilter() method with a new filter`, () => {
|
|
||||||
jest.spyOn(controller.$.model, 'applyFilter');
|
|
||||||
|
|
||||||
controller._categoryId = 2;
|
|
||||||
controller._typeId = 4;
|
|
||||||
|
|
||||||
controller.applyFilters();
|
|
||||||
|
|
||||||
expect(controller.$.model.applyFilter).toHaveBeenCalledWith(
|
|
||||||
{where: {categoryFk: 2, typeFk: 4}},
|
|
||||||
{orderFk: 4, orderBy: controller.getOrderBy(), tagGroups: []});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('remove()', () => {
|
|
||||||
it(`should remove a tag from tags property`, () => {
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
controller.tagGroups = [
|
|
||||||
{tagFk: 1, values: [{value: 'Brown'}]},
|
|
||||||
{tagFk: 67, values: [{value: 'Concussion'}]}
|
|
||||||
];
|
|
||||||
controller.remove(0);
|
|
||||||
|
|
||||||
const firstTag = controller.tagGroups[0];
|
|
||||||
|
|
||||||
expect(controller.tagGroups.length).toEqual(1);
|
|
||||||
expect(firstTag.tagFk).toEqual(67);
|
|
||||||
expect(controller.applyFilters).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should remove a tag from tags property and call applyFilters() if there's no more tags`, () => {
|
|
||||||
jest.spyOn(controller, 'applyFilters');
|
|
||||||
|
|
||||||
controller._categoryId = 1;
|
|
||||||
controller._typeId = 1;
|
|
||||||
controller.tagGroups = [{tagFk: 1, values: [{value: 'Blue'}]}];
|
|
||||||
controller.remove(0);
|
|
||||||
|
|
||||||
expect(controller.tagGroups.length).toEqual(0);
|
|
||||||
expect(controller.applyFilters).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateStateParams()', () => {
|
|
||||||
it(`should call state go() method passing category and type state params`, () => {
|
|
||||||
jest.spyOn(controller.$state, 'go');
|
|
||||||
|
|
||||||
controller._categoryId = 2;
|
|
||||||
controller._typeId = 4;
|
|
||||||
controller._tagGroups = [
|
|
||||||
{tagFk: 67, values: [{value: 'Concussion'}], tagSelection: {name: 'Category'}}
|
|
||||||
];
|
|
||||||
const tagGroups = JSON.stringify([
|
|
||||||
{values: [{value: 'Concussion'}], tagFk: 67, tagSelection: {name: 'Category'}}
|
|
||||||
]);
|
|
||||||
const expectedResult = {categoryId: 2, typeId: 4, tagGroups: tagGroups};
|
|
||||||
controller.updateStateParams();
|
|
||||||
|
|
||||||
expect(controller.$state.go).toHaveBeenCalledWith('my.current.state', expectedResult);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getOrderBy()', () => {
|
|
||||||
it(`should return an object with order params`, () => {
|
|
||||||
controller.orderField = 'relevancy DESC, name';
|
|
||||||
controller.orderWay = 'DESC';
|
|
||||||
let expectedResult = {
|
|
||||||
field: 'relevancy DESC, name',
|
|
||||||
way: 'DESC',
|
|
||||||
isTag: false
|
|
||||||
};
|
|
||||||
let result = controller.getOrderBy();
|
|
||||||
|
|
||||||
expect(result).toEqual(expectedResult);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('applyOrder()', () => {
|
|
||||||
it(`should apply order param to model calling getOrderBy()`, () => {
|
|
||||||
jest.spyOn(controller, 'getOrderBy');
|
|
||||||
jest.spyOn(controller.$.model, 'addFilter');
|
|
||||||
|
|
||||||
controller.field = 'relevancy DESC, name';
|
|
||||||
controller.way = 'ASC';
|
|
||||||
controller._categoryId = 1;
|
|
||||||
controller._typeId = 1;
|
|
||||||
let expectedOrder = {orderBy: controller.getOrderBy()};
|
|
||||||
|
|
||||||
controller.applyOrder();
|
|
||||||
|
|
||||||
expect(controller.getOrderBy).toHaveBeenCalledWith();
|
|
||||||
expect(controller.$.model.addFilter).toHaveBeenCalledWith(null, expectedOrder);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('fetchResultTags()', () => {
|
|
||||||
it(`should create an array of non repeated tags then set the resultTags property`, () => {
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
id: 1, name: 'My Item 1', tags: [
|
|
||||||
{tagFk: 4, name: 'Length', value: 1},
|
|
||||||
{tagFk: 5, name: 'Color', value: 'red'}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2, name: 'My Item 2', tags: [
|
|
||||||
{tagFk: 4, name: 'Length', value: 1},
|
|
||||||
{tagFk: 5, name: 'Color', value: 'blue'}
|
|
||||||
]
|
|
||||||
}];
|
|
||||||
controller.fetchResultTags(items);
|
|
||||||
|
|
||||||
expect(controller.resultTags.length).toEqual(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('buildOrderFilter()', () => {
|
|
||||||
it(`should create an array of non repeated tags plus default filters and then set the orderFields property`, () => {
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
id: 1, name: 'My Item 1', tags: [
|
|
||||||
{tagFk: 4, name: 'Length'},
|
|
||||||
{tagFk: 5, name: 'Color'}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2, name: 'My Item 2', tags: [
|
|
||||||
{tagFk: 5, name: 'Color'},
|
|
||||||
{tagFk: 6, name: 'Relevancy'}
|
|
||||||
]
|
|
||||||
}];
|
|
||||||
|
|
||||||
controller.fetchResultTags(items);
|
|
||||||
controller.buildOrderFilter();
|
|
||||||
|
|
||||||
expect(controller.orderFields.length).toEqual(7);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('formatTooltip()', () => {
|
|
||||||
it(`should return a formatted text with the tag name and values`, () => {
|
|
||||||
const tagGroup = {
|
|
||||||
values: [{value: 'Silver'}, {value: 'Brown'}],
|
|
||||||
tagFk: 1,
|
|
||||||
tagSelection: {
|
|
||||||
name: 'Color'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = controller.formatTooltip(tagGroup);
|
|
||||||
|
|
||||||
expect(result).toEqual(`Color: "Silver", "Brown"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should return a formatted text with the tag value`, () => {
|
|
||||||
const tagGroup = {
|
|
||||||
values: [{value: 'Silver'}]
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = controller.formatTooltip(tagGroup);
|
|
||||||
|
|
||||||
expect(result).toEqual(`"Silver"`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sanitizedTagGroupParam()', () => {
|
|
||||||
it(`should return an array of tags`, () => {
|
|
||||||
const dirtyTagGroups = [{
|
|
||||||
values: [{value: 'Silver'}, {value: 'Brown'}],
|
|
||||||
tagFk: 1,
|
|
||||||
tagSelection: {
|
|
||||||
name: 'Color',
|
|
||||||
$orgRow: {name: 'Color'}
|
|
||||||
},
|
|
||||||
$orgIndex: 1
|
|
||||||
}];
|
|
||||||
controller.tagGroups = dirtyTagGroups;
|
|
||||||
|
|
||||||
const expectedResult = [{
|
|
||||||
values: [{value: 'Silver'}, {value: 'Brown'}],
|
|
||||||
tagFk: 1,
|
|
||||||
tagSelection: {
|
|
||||||
name: 'Color'
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
const result = controller.sanitizedTagGroupParam();
|
|
||||||
|
|
||||||
expect(result).toEqual(expect.objectContaining(expectedResult));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
Name: Nombre
|
|
||||||
Search by item id or name: Buscar por id de artículo o nombre
|
|
||||||
OR: O
|
|
|
@ -1,54 +0,0 @@
|
||||||
@import "variables";
|
|
||||||
|
|
||||||
vn-order-catalog vn-side-menu div {
|
|
||||||
& > .input {
|
|
||||||
padding-left: $spacing-md;
|
|
||||||
padding-right: $spacing-md;
|
|
||||||
border-color: $color-spacer;
|
|
||||||
border-bottom: $border-thin;
|
|
||||||
}
|
|
||||||
.item-category {
|
|
||||||
padding: $spacing-sm;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
vn-autocomplete[vn-id="category"] {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
|
|
||||||
& > vn-one {
|
|
||||||
padding: $spacing-sm;
|
|
||||||
min-width: 33.33%;
|
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
& > vn-icon {
|
|
||||||
padding: $spacing-sm;
|
|
||||||
background-color: $color-font-secondary;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: $color-main;
|
|
||||||
color: #FFF
|
|
||||||
}
|
|
||||||
& > i:before {
|
|
||||||
font-size: 2.6rem;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.chips {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: $spacing-md;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
vn-autocomplete[vn-id="type"] .list {
|
|
||||||
max-height: 320px
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
<vn-autocomplete
|
|
||||||
vn-focus
|
|
||||||
vn-id="client"
|
|
||||||
url="Clients"
|
|
||||||
label="Client"
|
|
||||||
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}"
|
|
||||||
show-field="name"
|
|
||||||
value-field="id"
|
|
||||||
ng-model="$ctrl.clientFk"
|
|
||||||
vn-name="client"
|
|
||||||
order="id">
|
|
||||||
<tpl-item>{{id}}: {{name}}</tpl-item>
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-autocomplete
|
|
||||||
disabled="!$ctrl.clientFk"
|
|
||||||
url="{{ $ctrl.clientFk ? 'Clients/'+ $ctrl.clientFk +'/addresses' : null }}"
|
|
||||||
fields="['nickname', 'street', 'city']"
|
|
||||||
ng-model="$ctrl.addressFk"
|
|
||||||
vn-name="address"
|
|
||||||
show-field="nickname"
|
|
||||||
value-field="id"
|
|
||||||
label="Address">
|
|
||||||
<tpl-item>{{nickname}}: {{street}}, {{city}}</tpl-item>
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-date-picker
|
|
||||||
label="Landed"
|
|
||||||
ng-model="$ctrl.landed"
|
|
||||||
vn-name="landed">
|
|
||||||
</vn-date-picker>
|
|
||||||
<vn-autocomplete
|
|
||||||
disabled="!$ctrl.clientFk || !$ctrl.landed"
|
|
||||||
data="$ctrl._availableAgencies"
|
|
||||||
label="Agency"
|
|
||||||
show-field="agencyMode"
|
|
||||||
value-field="agencyModeFk"
|
|
||||||
ng-model="$ctrl.order.agencyModeFk"
|
|
||||||
vn-name="agencyMode">
|
|
||||||
</vn-autocomplete>
|
|
|
@ -1,114 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Component from 'core/lib/component';
|
|
||||||
|
|
||||||
class Controller extends Component {
|
|
||||||
constructor($element, $) {
|
|
||||||
super($element, $);
|
|
||||||
this.order = {};
|
|
||||||
this.clientFk = this.$params.clientFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
if (this.$params && this.$params.clientFk)
|
|
||||||
this.clientFk = this.$params.clientFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
set order(value) {
|
|
||||||
if (value)
|
|
||||||
this._order = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get order() {
|
|
||||||
return this._order;
|
|
||||||
}
|
|
||||||
|
|
||||||
set clientFk(value) {
|
|
||||||
this.order.clientFk = value;
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
let filter = {
|
|
||||||
include: {
|
|
||||||
relation: 'defaultAddress',
|
|
||||||
scope: {
|
|
||||||
fields: 'id'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: {id: value}
|
|
||||||
};
|
|
||||||
filter = encodeURIComponent(JSON.stringify(filter));
|
|
||||||
let query = `Clients?filter=${filter}`;
|
|
||||||
this.$http.get(query).then(res => {
|
|
||||||
if (res.data) {
|
|
||||||
let client = res.data[0];
|
|
||||||
let defaultAddress = client.defaultAddress;
|
|
||||||
this.addressFk = defaultAddress.id;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
this.addressFk = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get clientFk() {
|
|
||||||
return this.order.clientFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
set addressFk(value) {
|
|
||||||
this.order.addressFk = value;
|
|
||||||
this.getAvailableAgencies();
|
|
||||||
}
|
|
||||||
|
|
||||||
get addressFk() {
|
|
||||||
return this.order.addressFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
set landed(value) {
|
|
||||||
this.order.landed = value;
|
|
||||||
this.getAvailableAgencies();
|
|
||||||
}
|
|
||||||
|
|
||||||
get landed() {
|
|
||||||
return this.order.landed;
|
|
||||||
}
|
|
||||||
|
|
||||||
get warehouseFk() {
|
|
||||||
return this.order.warehouseFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAvailableAgencies() {
|
|
||||||
let order = this.order;
|
|
||||||
order.agencyModeFk = null;
|
|
||||||
|
|
||||||
let params = {
|
|
||||||
addressFk: order.addressFk,
|
|
||||||
landed: order.landed
|
|
||||||
};
|
|
||||||
if (params.landed && params.addressFk) {
|
|
||||||
this.$http.get(`Agencies/landsThatDay`, {params})
|
|
||||||
.then(res => this._availableAgencies = res.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
this.createOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
createOrder() {
|
|
||||||
let params = {
|
|
||||||
landed: this.order.landed,
|
|
||||||
addressId: this.order.addressFk,
|
|
||||||
agencyModeId: this.order.agencyModeFk
|
|
||||||
};
|
|
||||||
this.$http.post(`Orders/new`, params).then(res => {
|
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
|
||||||
this.$state.go('order.card.catalog', {id: res.data});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderCreateCard', {
|
|
||||||
template: require('./card.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
order: '<?'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,104 +0,0 @@
|
||||||
import './card.js';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderCreateCard', () => {
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
let $scope;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$httpBackend_, _vnApp_, $rootScope) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
$scope = $rootScope.$new();
|
|
||||||
const $element = angular.element('<vn-order-create-card></vn-order-create-card>');
|
|
||||||
controller = $componentController('vnOrderCreateCard', {$element, $scope});
|
|
||||||
controller.item = {id: 3};
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('set order', () => {
|
|
||||||
it(`should set order if the value given is not null`, () => {
|
|
||||||
controller.order = 1;
|
|
||||||
|
|
||||||
expect(controller.order).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('set clientFk', () => {
|
|
||||||
it(`should set addressFk to null and clientFk to a value and set addressFk to a value given`, () => {
|
|
||||||
let filter = {
|
|
||||||
include: {
|
|
||||||
relation: 'defaultAddress',
|
|
||||||
scope: {
|
|
||||||
fields: 'id'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
where: {id: 2}
|
|
||||||
};
|
|
||||||
filter = encodeURIComponent(JSON.stringify(filter));
|
|
||||||
let response = [
|
|
||||||
{
|
|
||||||
defaultAddress: {id: 1}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
$httpBackend.whenGET(`Clients?filter=${filter}`).respond(response);
|
|
||||||
$httpBackend.expectGET(`Clients?filter=${filter}`);
|
|
||||||
|
|
||||||
controller.clientFk = 2;
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.clientFk).toEqual(2);
|
|
||||||
expect(controller.order.addressFk).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('set addressFk', () => {
|
|
||||||
it(`should set agencyModeFk property to null and addressFk to a value`, () => {
|
|
||||||
controller.addressFk = 1101;
|
|
||||||
|
|
||||||
expect(controller.addressFk).toEqual(1101);
|
|
||||||
expect(controller.order.agencyModeFk).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getAvailableAgencies()', () => {
|
|
||||||
it(`should make a query if landed and addressFk exists`, () => {
|
|
||||||
controller.order.addressFk = 1101;
|
|
||||||
controller.order.landed = 1101;
|
|
||||||
|
|
||||||
$httpBackend.whenRoute('GET', 'Agencies/landsThatDay')
|
|
||||||
.respond({data: 1});
|
|
||||||
|
|
||||||
controller.getAvailableAgencies();
|
|
||||||
$httpBackend.flush();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('onSubmit()', () => {
|
|
||||||
it(`should call createOrder()`, () => {
|
|
||||||
jest.spyOn(controller, 'createOrder');
|
|
||||||
controller.onSubmit();
|
|
||||||
|
|
||||||
expect(controller.createOrder).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createOrder()', () => {
|
|
||||||
it(`should make a query, call vnApp.showSuccess and $state.go if the response is defined`, () => {
|
|
||||||
controller.order.landed = 1101;
|
|
||||||
controller.order.addressFk = 1101;
|
|
||||||
controller.order.agencyModeFk = 1101;
|
|
||||||
|
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
|
||||||
jest.spyOn(controller.$state, 'go');
|
|
||||||
$httpBackend.expect('POST', 'Orders/new', {landed: 1101, addressId: 1101, agencyModeId: 1101}).respond(200, 1);
|
|
||||||
controller.createOrder();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
|
||||||
expect(controller.$state.go).toHaveBeenCalledWith('order.card.catalog', {id: 1});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<div class="vn-w-md">
|
|
||||||
<vn-card class="vn-pa-lg">
|
|
||||||
<vn-order-create-card vn-id="card" on-save=""></vn-order-create-card>
|
|
||||||
</vn-card>
|
|
||||||
<vn-button-bar>
|
|
||||||
<vn-submit
|
|
||||||
ng-click="$ctrl.onSubmit()"
|
|
||||||
label="Create">
|
|
||||||
</vn-submit>
|
|
||||||
<vn-button
|
|
||||||
class="cancel"
|
|
||||||
label="Cancel"
|
|
||||||
ui-sref="order.index">
|
|
||||||
</vn-button>
|
|
||||||
</vn-button-bar>
|
|
||||||
</div>
|
|
|
@ -1,14 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Section from 'salix/components/section';
|
|
||||||
|
|
||||||
class Controller extends Section {
|
|
||||||
async onSubmit() {
|
|
||||||
let newOrderID = await this.$.card.createOrder();
|
|
||||||
this.$state.go('order.card.summary', {id: newOrderID});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderCreate', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller
|
|
||||||
});
|
|
|
@ -1,34 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderCreate', () => {
|
|
||||||
let $scope;
|
|
||||||
let controller;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, $rootScope) => {
|
|
||||||
$scope = $rootScope.$new();
|
|
||||||
$scope.card = {createOrder: () => {}};
|
|
||||||
const $element = angular.element('<vn-order-create></vn-order-create>');
|
|
||||||
controller = $componentController('vnOrderCreate', {$element, $scope});
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('onSubmit()', () => {
|
|
||||||
it(`should call createOrder()`, () => {
|
|
||||||
jest.spyOn(controller.$.card, 'createOrder');
|
|
||||||
controller.onSubmit();
|
|
||||||
|
|
||||||
expect(controller.$.card.createOrder).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should call go()`, async() => {
|
|
||||||
jest.spyOn(controller.$state, 'go');
|
|
||||||
await controller.onSubmit();
|
|
||||||
|
|
||||||
expect(controller.$state.go).toHaveBeenCalledWith('order.card.summary', {id: undefined});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
You can't create an order for a frozen client: No puedes crear una orden a un cliente congelado
|
|
||||||
You can't create an order for an inactive client: No puedes crear una orden a un cliente inactivo
|
|
||||||
You can't create an order for a client that doesn't has tax data verified:
|
|
||||||
No puedes crear una orden a un cliente cuyos datos fiscales no han sido verificados
|
|
||||||
You can't create an order for a client that has a debt: No puedes crear una orden a un cliente que tiene deuda
|
|
||||||
New order: Nueva orden
|
|
|
@ -1,78 +0,0 @@
|
||||||
<vn-descriptor-content
|
|
||||||
module="order"
|
|
||||||
description="$ctrl.order.client.name"
|
|
||||||
summary="$ctrl.$.summary">
|
|
||||||
<slot-menu>
|
|
||||||
<vn-item
|
|
||||||
ng-click="deleteOrderConfirmation.show()"
|
|
||||||
translate>
|
|
||||||
Delete order
|
|
||||||
</vn-item>
|
|
||||||
</slot-menu>
|
|
||||||
<slot-body>
|
|
||||||
<div class="attributes">
|
|
||||||
<vn-label-value
|
|
||||||
label="State"
|
|
||||||
value="{{$ctrl.$t($ctrl.order.isConfirmed ? 'Confirmed' : 'Not confirmed')}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
label="Sales person">
|
|
||||||
<span
|
|
||||||
ng-click="workerDescriptor.show($event, $ctrl.order.client.salesPersonFk)"
|
|
||||||
class="link">
|
|
||||||
{{$ctrl.order.client.salesPersonUser.name}}
|
|
||||||
</span>
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
label="Landed"
|
|
||||||
value="{{$ctrl.order.landed | date: 'dd/MM/yyyy' }}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
label="Agency"
|
|
||||||
value="{{$ctrl.order.agencyMode.name}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
label="Alias"
|
|
||||||
value="{{$ctrl.order.address.nickname}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
label="Items"
|
|
||||||
value="{{$ctrl.order.rows.length || 0}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
label="Total"
|
|
||||||
value="{{$ctrl.order.total | currency: 'EUR': 2}}">
|
|
||||||
</vn-label-value>
|
|
||||||
</div>
|
|
||||||
<div class="quicklinks">
|
|
||||||
<div ng-transclude="btnOne">
|
|
||||||
<vn-quick-link
|
|
||||||
tooltip="Order ticket list"
|
|
||||||
state="['ticket.index', {q: $ctrl.ticketFilter}]"
|
|
||||||
icon="icon-ticket">
|
|
||||||
</vn-quick-link>
|
|
||||||
</div>
|
|
||||||
<div ng-transclude="btnTwo">
|
|
||||||
<vn-quick-link
|
|
||||||
tooltip="Client card"
|
|
||||||
state="['client.card.summary', {id: $ctrl.order.clientFk}]"
|
|
||||||
icon="person">
|
|
||||||
</vn-quick-link>
|
|
||||||
</div>
|
|
||||||
<div ng-transclude="btnThree">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</slot-body>
|
|
||||||
</vn-descriptor-content>
|
|
||||||
<vn-confirm
|
|
||||||
vn-id="deleteOrderConfirmation"
|
|
||||||
on-accept="$ctrl.deleteOrder()"
|
|
||||||
message="You are going to delete this order"
|
|
||||||
question="continue anyway?">
|
|
||||||
</vn-confirm>
|
|
||||||
<vn-worker-descriptor-popover
|
|
||||||
vn-id="workerDescriptor">
|
|
||||||
</vn-worker-descriptor-popover>
|
|
||||||
<vn-popup vn-id="summary">
|
|
||||||
<vn-order-summary order="$ctrl.order"></vn-order-summary>
|
|
||||||
</vn-popup>
|
|
|
@ -1,32 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Descriptor from 'salix/components/descriptor';
|
|
||||||
|
|
||||||
class Controller extends Descriptor {
|
|
||||||
get order() {
|
|
||||||
return this.entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
set order(value) {
|
|
||||||
this.entity = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ticketFilter() {
|
|
||||||
return JSON.stringify({orderFk: this.id});
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteOrder() {
|
|
||||||
return this.$http.delete(`Orders/${this.id}`)
|
|
||||||
.then(() => {
|
|
||||||
this.$state.go('order.index');
|
|
||||||
this.vnApp.showSuccess(this.$t('Order deleted'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderDescriptor', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
order: '<'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,29 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
|
|
||||||
describe('Order Component vnOrderDescriptor', () => {
|
|
||||||
let $httpBackend;
|
|
||||||
let controller;
|
|
||||||
const order = {id: 1};
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$httpBackend_) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
controller = $componentController('vnOrderDescriptor', {$element: null}, {order});
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('deleteOrder()', () => {
|
|
||||||
it(`should perform a DELETE query`, () => {
|
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
|
||||||
jest.spyOn(controller.$state, 'go');
|
|
||||||
|
|
||||||
$httpBackend.expectDELETE(`Orders/${order.id}`).respond();
|
|
||||||
controller.deleteOrder();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
|
||||||
expect(controller.$state.go).toHaveBeenCalledWith('order.index');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
Client: Cliente
|
|
||||||
Confirmed: Confirmado
|
|
||||||
Not confirmed: Sin confirmar
|
|
||||||
State: Estado
|
|
||||||
Landed: F. entrega
|
|
||||||
Items: Articulos
|
|
||||||
Agency: Agencia
|
|
||||||
Sales person: Comercial
|
|
||||||
Order ticket list: Ticket del pedido
|
|
||||||
Delete order: Eliminar pedido
|
|
||||||
You are going to delete this order: El pedido se eliminará
|
|
||||||
continue anyway?: ¿Continuar de todos modos?
|
|
|
@ -1,17 +1,3 @@
|
||||||
export * from './module';
|
export * from './module';
|
||||||
|
|
||||||
import './main';
|
import './main';
|
||||||
import './index/';
|
|
||||||
import './card';
|
|
||||||
import './descriptor';
|
|
||||||
import './search-panel';
|
|
||||||
import './catalog-search-panel';
|
|
||||||
import './catalog-view';
|
|
||||||
import './catalog';
|
|
||||||
import './summary';
|
|
||||||
import './line';
|
|
||||||
import './prices-popover';
|
|
||||||
import './volume';
|
|
||||||
import './create';
|
|
||||||
import './create/card';
|
|
||||||
import './basic-data';
|
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
|
|
||||||
<vn-auto-search
|
|
||||||
model="model">
|
|
||||||
</vn-auto-search>
|
|
||||||
<vn-data-viewer
|
|
||||||
model="model"
|
|
||||||
class="vn-mb-xl">
|
|
||||||
<vn-card>
|
|
||||||
<vn-table model="model">
|
|
||||||
<vn-thead>
|
|
||||||
<vn-tr>
|
|
||||||
<vn-th field="id" number>Id</vn-th>
|
|
||||||
<vn-th field="salesPersonFk">Sales person</vn-th>
|
|
||||||
<vn-th field="clientFk">Client</vn-th>
|
|
||||||
<vn-th field="isConfirmed" center>Confirmed</vn-th>
|
|
||||||
<vn-th field="created" center expand>Created</vn-th>
|
|
||||||
<vn-th field="landed" shrink-date>Landed</vn-th>
|
|
||||||
<vn-th field="created" center>Hour</vn-th>
|
|
||||||
<vn-th field="agencyName" center>Agency</vn-th>
|
|
||||||
<vn-th field="total" center>Total</vn-th>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-thead>
|
|
||||||
<vn-tbody>
|
|
||||||
<a
|
|
||||||
ng-repeat="order in model.data"
|
|
||||||
class="clickable search-result vn-tr"
|
|
||||||
ui-sref="order.card.summary({id: {{::order.id}}})">
|
|
||||||
<vn-td number>{{::order.id}}</vn-td>
|
|
||||||
<vn-td expand>
|
|
||||||
<span
|
|
||||||
vn-click-stop="workerDescriptor.show($event, order.salesPersonFk)"
|
|
||||||
class="link" >
|
|
||||||
{{::order.name | dashIfEmpty}}
|
|
||||||
</span>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td>
|
|
||||||
<span
|
|
||||||
vn-click-stop="clientDescriptor.show($event, order.clientFk)"
|
|
||||||
class="link">
|
|
||||||
{{::order.clientName}}
|
|
||||||
</span>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td center>
|
|
||||||
<vn-check
|
|
||||||
ng-model="order.isConfirmed"
|
|
||||||
disabled="true">
|
|
||||||
</vn-check>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td shrink-datetime>{{::order.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
|
|
||||||
<vn-td shrink-date>
|
|
||||||
<span class="chip {{$ctrl.compareDate(order.landed)}}">
|
|
||||||
{{::order.landed | date:'dd/MM/yyyy'}}
|
|
||||||
</span>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td shrink>{{::(order.hourTheoretical
|
|
||||||
? order.hourTheoretical
|
|
||||||
: order.hourEffective) | dashIfEmpty
|
|
||||||
}}</vn-td>
|
|
||||||
<vn-td expand>{{::order.agencyName}}</vn-td>
|
|
||||||
<vn-td number>{{::order.total | currency: 'EUR': 2 | dashIfEmpty}}</vn-td>
|
|
||||||
<vn-td shrink>
|
|
||||||
<vn-icon-button
|
|
||||||
vn-click-stop="$ctrl.preview(order)"
|
|
||||||
icon="preview"
|
|
||||||
vn-tooltip="Preview">
|
|
||||||
</vn-icon-button>
|
|
||||||
</vn-td>
|
|
||||||
</a>
|
|
||||||
</vn-tbody>
|
|
||||||
</vn-table>
|
|
||||||
</vn-card>
|
|
||||||
</vn-data-viewer>
|
|
||||||
<a
|
|
||||||
ui-sref="order.create"
|
|
||||||
vn-bind="+"
|
|
||||||
vn-tooltip="New order"
|
|
||||||
fixed-bottom-right>
|
|
||||||
<vn-float-button icon="add"></vn-float-button>
|
|
||||||
</a>
|
|
||||||
<vn-client-descriptor-popover
|
|
||||||
vn-id="clientDescriptor">
|
|
||||||
</vn-client-descriptor-popover>
|
|
||||||
<vn-worker-descriptor-popover
|
|
||||||
vn-id="workerDescriptor">
|
|
||||||
</vn-worker-descriptor-popover>
|
|
||||||
<vn-popup vn-id="summary">
|
|
||||||
<vn-order-summary
|
|
||||||
order="$ctrl.selectedOrder">
|
|
||||||
</vn-order-summary>
|
|
||||||
</vn-popup>
|
|
|
@ -1,26 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Section from 'salix/components/section';
|
|
||||||
|
|
||||||
export default class Controller extends Section {
|
|
||||||
preview(order) {
|
|
||||||
this.selectedOrder = order;
|
|
||||||
this.$.summary.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
compareDate(date) {
|
|
||||||
let today = Date.vnNew();
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
date = new Date(date);
|
|
||||||
date.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const timeDifference = today - date;
|
|
||||||
if (timeDifference == 0) return 'warning';
|
|
||||||
if (timeDifference < 0) return 'success';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderIndex', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller
|
|
||||||
});
|
|
|
@ -1,67 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
describe('Component vnOrderIndex', () => {
|
|
||||||
let controller;
|
|
||||||
let $window;
|
|
||||||
let orders = [{
|
|
||||||
id: 1,
|
|
||||||
clientFk: 1,
|
|
||||||
isConfirmed: false
|
|
||||||
}, {
|
|
||||||
id: 2,
|
|
||||||
clientFk: 1,
|
|
||||||
isConfirmed: false
|
|
||||||
}, {
|
|
||||||
id: 3,
|
|
||||||
clientFk: 1,
|
|
||||||
isConfirmed: true
|
|
||||||
}];
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$window_) => {
|
|
||||||
$window = _$window_;
|
|
||||||
const $element = angular.element('<vn-order-index></vn-order-index>');
|
|
||||||
controller = $componentController('vnOrderIndex', {$element});
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('compareDate()', () => {
|
|
||||||
it('should return warning when the date is the present', () => {
|
|
||||||
let curDate = Date.vnNew();
|
|
||||||
let result = controller.compareDate(curDate);
|
|
||||||
|
|
||||||
expect(result).toEqual('warning');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return sucess when the date is in the future', () => {
|
|
||||||
let futureDate = Date.vnNew();
|
|
||||||
futureDate = futureDate.setDate(futureDate.getDate() + 10);
|
|
||||||
let result = controller.compareDate(futureDate);
|
|
||||||
|
|
||||||
expect(result).toEqual('success');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return undefined when the date is in the past', () => {
|
|
||||||
let pastDate = Date.vnNew();
|
|
||||||
pastDate = pastDate.setDate(pastDate.getDate() - 10);
|
|
||||||
let result = controller.compareDate(pastDate);
|
|
||||||
|
|
||||||
expect(result).toEqual(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('preview()', () => {
|
|
||||||
it('should show the dialog summary', () => {
|
|
||||||
controller.$.summary = {show: () => {}};
|
|
||||||
jest.spyOn(controller.$.summary, 'show');
|
|
||||||
|
|
||||||
let event = new MouseEvent('click', {
|
|
||||||
view: $window,
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true
|
|
||||||
});
|
|
||||||
controller.preview(event, orders[0]);
|
|
||||||
|
|
||||||
expect(controller.$.summary.show).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,96 +0,0 @@
|
||||||
<vn-data-viewer data="$ctrl.rows" class="vn-w-lg">
|
|
||||||
<vn-card class="vn-pa-lg header" ng-if="$ctrl.rows.length > 0">
|
|
||||||
<div>
|
|
||||||
<vn-label translate>Subtotal</vn-label>
|
|
||||||
{{$ctrl.subtotal | currency: 'EUR':2}}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<vn-label translate>VAT</vn-label>
|
|
||||||
{{$ctrl.VAT | currency: 'EUR':2}}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<vn-label>Total</vn-label>
|
|
||||||
{{$ctrl.order.total | currency: 'EUR':2}}
|
|
||||||
</div>
|
|
||||||
</vn-card>
|
|
||||||
<vn-card class="vn-mt-md">
|
|
||||||
<vn-table>
|
|
||||||
<vn-thead>
|
|
||||||
<vn-tr>
|
|
||||||
<vn-th></vn-th>
|
|
||||||
<vn-th number>Id</vn-th>
|
|
||||||
<vn-th>Description</vn-th>
|
|
||||||
<vn-th>Warehouse</vn-th>
|
|
||||||
<vn-th>Shipped</vn-th>
|
|
||||||
<vn-th number>Quantity</vn-th>
|
|
||||||
<vn-th number>Price</vn-th>
|
|
||||||
<vn-th number>Amount</vn-th>
|
|
||||||
<vn-th ng-if="::!$ctrl.order.isConfirmed"></vn-th>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-thead>
|
|
||||||
<vn-tbody>
|
|
||||||
<vn-tr ng-repeat="row in $ctrl.rows">
|
|
||||||
<vn-td shrink>
|
|
||||||
<img
|
|
||||||
ng-src="{{::$root.imagePath('catalog', '50x50', row.item.id)}}"
|
|
||||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', row.item.id)}}"
|
|
||||||
on-error-src/>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td number>
|
|
||||||
<span ng-click="itemDescriptor.show($event, row.itemFk)"
|
|
||||||
class="link">
|
|
||||||
{{::row.itemFk}}
|
|
||||||
</span>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td vn-fetched-tags>
|
|
||||||
<div>
|
|
||||||
<vn-one title="{{::row.item.name}}">{{::row.item.name}}</vn-one>
|
|
||||||
<vn-one ng-if="::row.item.subName">
|
|
||||||
<h3 title="{{::row.item.subName}}">{{::row.item.subName}}</h3>
|
|
||||||
</vn-one>
|
|
||||||
</div>
|
|
||||||
<vn-fetched-tags
|
|
||||||
max-length="6"
|
|
||||||
item="::row.item"
|
|
||||||
tabindex="-1">
|
|
||||||
</vn-fetched-tags>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td>{{::row.warehouse.name}}</vn-td>
|
|
||||||
<vn-td>{{::row.shipped | date: 'dd/MM/yyyy'}}</vn-td>
|
|
||||||
<vn-td number>{{::row.quantity}}</vn-td>
|
|
||||||
<vn-td number>
|
|
||||||
{{::row.price | currency: 'EUR':2}}
|
|
||||||
</vn-td>
|
|
||||||
<vn-td number>
|
|
||||||
{{::row.price * row.quantity | currency: 'EUR':2}}
|
|
||||||
</vn-td>
|
|
||||||
<vn-td shrink ng-if="::!$ctrl.order.isConfirmed">
|
|
||||||
<vn-icon-button
|
|
||||||
vn-tooltip="Remove item"
|
|
||||||
icon="delete"
|
|
||||||
ng-click="deleteRow.show($index)"
|
|
||||||
tabindex="-1">
|
|
||||||
</vn-icon-button>
|
|
||||||
</vn-td>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-tbody>
|
|
||||||
</vn-table>
|
|
||||||
</vn-card>
|
|
||||||
</vn-data-viewer>
|
|
||||||
<vn-float-button
|
|
||||||
icon="check"
|
|
||||||
vn-tooltip="Confirm"
|
|
||||||
ng-click="$ctrl.save()"
|
|
||||||
ng-if="!$ctrl.order.isConfirmed"
|
|
||||||
fixed-bottom-right>
|
|
||||||
</vn-float-button>
|
|
||||||
<vn-item-descriptor-popover
|
|
||||||
vn-id="item-descriptor"
|
|
||||||
warehouse-fk="$ctrl.vnConfig.warehouseFk">
|
|
||||||
</vn-item-descriptor-popover>
|
|
||||||
<vn-confirm
|
|
||||||
vn-id="delete-row"
|
|
||||||
on-accept="$ctrl.deleteRow($data)"
|
|
||||||
question="Delete row"
|
|
||||||
message="Are you sure you want to delete this row?">
|
|
||||||
</vn-confirm>
|
|
|
@ -1,70 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Section from 'salix/components/section';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
class Controller extends Section {
|
|
||||||
$onInit() {
|
|
||||||
this.getRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
set order(value) {
|
|
||||||
this._order = value;
|
|
||||||
this.getVAT();
|
|
||||||
}
|
|
||||||
|
|
||||||
get order() {
|
|
||||||
return this._order;
|
|
||||||
}
|
|
||||||
|
|
||||||
get subtotal() {
|
|
||||||
return this.order ? this.order.total - this.VAT : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRows() {
|
|
||||||
let filter = {
|
|
||||||
where: {orderFk: this.$params.id},
|
|
||||||
include: [
|
|
||||||
{relation: 'item'},
|
|
||||||
{relation: 'warehouse'}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
this.$http.get(`OrderRows`, {filter})
|
|
||||||
.then(res => this.rows = res.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
getVAT() {
|
|
||||||
this.$http.get(`Orders/${this.$params.id}/getVAT`)
|
|
||||||
.then(res => this.VAT = res.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteRow(index) {
|
|
||||||
let [row] = this.rows.splice(index, 1);
|
|
||||||
let params = {
|
|
||||||
rows: [row.id],
|
|
||||||
actualOrderId: this.$params.id
|
|
||||||
};
|
|
||||||
return this.$http.post(`OrderRows/removes`, params)
|
|
||||||
.then(() => this.card.reload())
|
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.$http.post(`Orders/${this.$params.id}/confirm`).then(() => {
|
|
||||||
this.vnApp.showSuccess(this.$t('Order confirmed'));
|
|
||||||
this.$state.go(`ticket.index`, {
|
|
||||||
q: JSON.stringify({clientFk: this.order.clientFk})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderLine', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
order: '<'
|
|
||||||
},
|
|
||||||
require: {
|
|
||||||
card: '^vnOrderCard'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,66 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderLine', () => {
|
|
||||||
let $state;
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
|
|
||||||
const vat = 10.5;
|
|
||||||
const rows = [
|
|
||||||
{
|
|
||||||
quantity: 4,
|
|
||||||
price: 10.5
|
|
||||||
}, {
|
|
||||||
quantity: 3,
|
|
||||||
price: 2.4
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$state_, _$httpBackend_) => {
|
|
||||||
$state = _$state_;
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
|
|
||||||
$state.params.id = 1;
|
|
||||||
$httpBackend.whenGET(`OrderRows`).respond(rows);
|
|
||||||
$httpBackend.whenRoute('GET', `Orders/:id/getVAT`).respond(200, vat);
|
|
||||||
|
|
||||||
controller = $componentController('vnOrderLine', {$element: null});
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('getRows()', () => {
|
|
||||||
it('should make a query to get the rows of a given order', () => {
|
|
||||||
controller.getRows();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.rows).toEqual(rows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getVAT()', () => {
|
|
||||||
it('should make a query to get the VAT of a given order', () => {
|
|
||||||
controller.getVAT();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.VAT).toBe(vat);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deleteRow()', () => {
|
|
||||||
it('should remove a row from rows and add save the data if the response is accept', () => {
|
|
||||||
controller.getRows();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
controller.card = {reload: jasmine.createSpy('reload')};
|
|
||||||
$httpBackend.expectPOST(`OrderRows/removes`).respond();
|
|
||||||
controller.deleteRow(0);
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.rows.length).toBe(1);
|
|
||||||
expect(controller.card.reload).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,3 +0,0 @@
|
||||||
Delete row: Eliminar linea
|
|
||||||
Order confirmed: Pedido confirmado
|
|
||||||
Are you sure you want to delete this row?: ¿Estas seguro de que quieres eliminar esta línea?
|
|
|
@ -1,18 +0,0 @@
|
||||||
@import "./variables";
|
|
||||||
|
|
||||||
vn-order-line {
|
|
||||||
vn-table {
|
|
||||||
img {
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
margin-bottom: $spacing-xs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,12 @@ import ngModule from '../module';
|
||||||
import ModuleMain from 'salix/components/module-main';
|
import ModuleMain from 'salix/components/module-main';
|
||||||
|
|
||||||
export default class Order extends ModuleMain {
|
export default class Order extends ModuleMain {
|
||||||
$postLink() {
|
constructor($element, $) {
|
||||||
this.filter = {showEmpty: false};
|
super($element, $);
|
||||||
|
}
|
||||||
|
async $onInit() {
|
||||||
|
this.$state.go('home');
|
||||||
|
window.location.href = await this.vnApp.getUrl(`order/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
<default>
|
|
||||||
<form name="form" class="prices">
|
|
||||||
<vn-table>
|
|
||||||
<vn-tbody>
|
|
||||||
<vn-tr ng-repeat="price in $ctrl.prices">
|
|
||||||
<vn-td class="warehouse" expand>
|
|
||||||
<span
|
|
||||||
class="text"
|
|
||||||
title="{{::price.warehouse}}">
|
|
||||||
{{::price.warehouse}}
|
|
||||||
</span>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td number expand>
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
ng-click="$ctrl.addQuantity(price)"
|
|
||||||
class="link unselectable">{{::price.grouping}}</span>
|
|
||||||
<span> x {{::price.price | currency: 'EUR': 2}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="price-kg" ng-show="::price.priceKg">
|
|
||||||
{{::price.priceKg | currency: 'EUR'}}/Kg
|
|
||||||
</div>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td shrink>
|
|
||||||
<!-- Focus first element -->
|
|
||||||
<vn-input-number ng-if="$index === 0"
|
|
||||||
min="0"
|
|
||||||
name="quantity"
|
|
||||||
ng-model="price.quantity"
|
|
||||||
step="price.grouping"
|
|
||||||
class="dense"
|
|
||||||
vn-focus>
|
|
||||||
</vn-input-number>
|
|
||||||
<vn-input-number ng-if="$index > 0"
|
|
||||||
min="0"
|
|
||||||
name="quantity"
|
|
||||||
ng-model="price.quantity"
|
|
||||||
step="price.grouping"
|
|
||||||
class="dense">
|
|
||||||
</vn-input-number>
|
|
||||||
</vn-td>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-tbody>
|
|
||||||
</vn-table>
|
|
||||||
<div class="footer vn-pa-md">
|
|
||||||
<vn-submit
|
|
||||||
label="Add"
|
|
||||||
ng-click="$ctrl.submit()">
|
|
||||||
</vn-submit>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</default>
|
|
|
@ -1,115 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Popover from 'core/components/popover';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
class Controller extends Popover {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
this.totalBasquet = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
set prices(value) {
|
|
||||||
this._prices = value;
|
|
||||||
if (value && value[0].grouping)
|
|
||||||
this.getTotalQuantity();
|
|
||||||
}
|
|
||||||
|
|
||||||
get prices() {
|
|
||||||
return this._prices;
|
|
||||||
}
|
|
||||||
|
|
||||||
show(parent, item) {
|
|
||||||
this.id = item.id;
|
|
||||||
this.item = JSON.parse(JSON.stringify(item));
|
|
||||||
this.maxQuantity = this.item.available;
|
|
||||||
this.prices = this.item.prices;
|
|
||||||
|
|
||||||
super.show(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
this.id = null;
|
|
||||||
this.item = {};
|
|
||||||
this.tags = {};
|
|
||||||
this._prices = {};
|
|
||||||
this.totalQuantity = 0;
|
|
||||||
super.onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotalQuantity() {
|
|
||||||
let total = 0;
|
|
||||||
for (let price of this.prices) {
|
|
||||||
if (!price.quantity) price.quantity = 0;
|
|
||||||
total += price.quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.totalQuantity = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
addQuantity(price) {
|
|
||||||
this.getTotalQuantity();
|
|
||||||
const quantity = this.totalQuantity + price.grouping;
|
|
||||||
if (quantity <= this.maxQuantity)
|
|
||||||
price.quantity += price.grouping;
|
|
||||||
}
|
|
||||||
|
|
||||||
getGroupings() {
|
|
||||||
const filledRows = [];
|
|
||||||
for (let priceOption of this.prices) {
|
|
||||||
if (priceOption.quantity && priceOption.quantity > 0) {
|
|
||||||
const priceMatch = filledRows.find(row => {
|
|
||||||
return row.warehouseFk == priceOption.warehouseFk
|
|
||||||
&& row.price == priceOption.price;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!priceMatch)
|
|
||||||
filledRows.push(Object.assign({}, priceOption));
|
|
||||||
else priceMatch.quantity += priceOption.quantity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filledRows;
|
|
||||||
}
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
const filledRows = this.getGroupings();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const hasInvalidGropings = filledRows.some(row =>
|
|
||||||
row.quantity % row.grouping != 0
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filledRows.length <= 0)
|
|
||||||
throw new Error('First you must add some quantity');
|
|
||||||
|
|
||||||
if (hasInvalidGropings)
|
|
||||||
throw new Error(`The amounts doesn't match with the grouping`);
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
orderFk: this.order.id,
|
|
||||||
items: filledRows
|
|
||||||
};
|
|
||||||
this.$http.post(`OrderRows/addToOrder`, params)
|
|
||||||
.then(() => {
|
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
|
||||||
this.hide();
|
|
||||||
if (this.card) this.card.reload();
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
this.vnApp.showError(this.$t(e.message));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderPricesPopover', {
|
|
||||||
slotTemplate: require('./index.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
order: '<'
|
|
||||||
},
|
|
||||||
require: {
|
|
||||||
card: '?^vnOrderCard'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,171 +0,0 @@
|
||||||
import './index.js';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderPricesPopover', () => {
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
let orderId = 16;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
const $scope = $rootScope.$new();
|
|
||||||
const $element = angular.element('<vn-order-prices-popover></vn-order-prices-popover>');
|
|
||||||
const $transclude = {
|
|
||||||
$$boundTransclude: {
|
|
||||||
$$slots: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
controller = $componentController('vnOrderPricesPopover', {$element, $scope, $transclude});
|
|
||||||
controller._prices = [
|
|
||||||
{warehouseFk: 1, grouping: 10, quantity: 0},
|
|
||||||
{warehouseFk: 1, grouping: 100, quantity: 100}
|
|
||||||
];
|
|
||||||
controller.item = {available: 1000};
|
|
||||||
controller.maxQuantity = 1000;
|
|
||||||
controller.order = {id: orderId};
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('prices() setter', () => {
|
|
||||||
it('should call to the getTotalQuantity() method', () => {
|
|
||||||
controller.getTotalQuantity = jest.fn();
|
|
||||||
|
|
||||||
controller.prices = [
|
|
||||||
{grouping: 10, quantity: 0},
|
|
||||||
{grouping: 100, quantity: 0},
|
|
||||||
{grouping: 1000, quantity: 0},
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(controller.getTotalQuantity).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getTotalQuantity()', () => {
|
|
||||||
it('should set the totalQuantity property', () => {
|
|
||||||
controller.getTotalQuantity();
|
|
||||||
|
|
||||||
expect(controller.totalQuantity).toEqual(100);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('addQuantity()', () => {
|
|
||||||
it('should call to the getTotalQuantity() method and NOT set the quantity property', () => {
|
|
||||||
jest.spyOn(controller, 'getTotalQuantity');
|
|
||||||
|
|
||||||
controller.prices = [
|
|
||||||
{grouping: 10, quantity: 0},
|
|
||||||
{grouping: 100, quantity: 0},
|
|
||||||
{grouping: 1000, quantity: 1000},
|
|
||||||
];
|
|
||||||
|
|
||||||
const oneThousandGrouping = controller.prices[2];
|
|
||||||
|
|
||||||
expect(oneThousandGrouping.quantity).toEqual(1000);
|
|
||||||
|
|
||||||
controller.addQuantity(oneThousandGrouping);
|
|
||||||
|
|
||||||
expect(controller.getTotalQuantity).toHaveBeenCalledWith();
|
|
||||||
expect(oneThousandGrouping.quantity).toEqual(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call to the getTotalQuantity() method and then set the quantity property', () => {
|
|
||||||
jest.spyOn(controller, 'getTotalQuantity');
|
|
||||||
|
|
||||||
const oneHandredGrouping = controller.prices[1];
|
|
||||||
controller.addQuantity(oneHandredGrouping);
|
|
||||||
|
|
||||||
expect(controller.getTotalQuantity).toHaveBeenCalledWith();
|
|
||||||
expect(oneHandredGrouping.quantity).toEqual(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getGroupings()', () => {
|
|
||||||
it('should return a row with the total filled quantity', () => {
|
|
||||||
jest.spyOn(controller, 'getTotalQuantity');
|
|
||||||
|
|
||||||
controller.prices = [
|
|
||||||
{warehouseFk: 1, grouping: 10, quantity: 10},
|
|
||||||
{warehouseFk: 1, grouping: 100, quantity: 100},
|
|
||||||
{warehouseFk: 1, grouping: 1000, quantity: 1000},
|
|
||||||
];
|
|
||||||
|
|
||||||
const rows = controller.getGroupings();
|
|
||||||
const firstRow = rows[0];
|
|
||||||
|
|
||||||
expect(rows.length).toEqual(1);
|
|
||||||
expect(firstRow.quantity).toEqual(1110);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return two filled rows with a quantity', () => {
|
|
||||||
jest.spyOn(controller, 'getTotalQuantity');
|
|
||||||
|
|
||||||
controller.prices = [
|
|
||||||
{warehouseFk: 1, grouping: 10, quantity: 10},
|
|
||||||
{warehouseFk: 2, grouping: 10, quantity: 10},
|
|
||||||
{warehouseFk: 1, grouping: 100, quantity: 0},
|
|
||||||
{warehouseFk: 1, grouping: 1000, quantity: 1000},
|
|
||||||
];
|
|
||||||
|
|
||||||
const rows = controller.getGroupings();
|
|
||||||
const firstRow = rows[0];
|
|
||||||
const secondRow = rows[1];
|
|
||||||
|
|
||||||
expect(rows.length).toEqual(2);
|
|
||||||
expect(firstRow.quantity).toEqual(1010);
|
|
||||||
expect(secondRow.quantity).toEqual(10);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('submit()', () => {
|
|
||||||
it('should throw an error if none of the rows contains a quantity', () => {
|
|
||||||
jest.spyOn(controller, 'getTotalQuantity');
|
|
||||||
jest.spyOn(controller.vnApp, 'showError');
|
|
||||||
|
|
||||||
controller.prices = [
|
|
||||||
{warehouseFk: 1, grouping: 10, quantity: 0},
|
|
||||||
{warehouseFk: 1, grouping: 100, quantity: 0}
|
|
||||||
];
|
|
||||||
|
|
||||||
controller.submit();
|
|
||||||
|
|
||||||
expect(controller.vnApp.showError).toHaveBeenCalledWith(`First you must add some quantity`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should throw an error if the quantity doesn't match the grouping value`, () => {
|
|
||||||
jest.spyOn(controller, 'getTotalQuantity');
|
|
||||||
jest.spyOn(controller.vnApp, 'showError');
|
|
||||||
|
|
||||||
controller.prices = [
|
|
||||||
{warehouseFk: 1, grouping: 10, quantity: 0},
|
|
||||||
{warehouseFk: 1, grouping: 100, quantity: 1101}
|
|
||||||
];
|
|
||||||
|
|
||||||
controller.submit();
|
|
||||||
|
|
||||||
expect(controller.vnApp.showError).toHaveBeenCalledWith(`The amounts doesn't match with the grouping`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should should make an http query and then show a success message', () => {
|
|
||||||
jest.spyOn(controller, 'getTotalQuantity');
|
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
|
||||||
|
|
||||||
controller.prices = [
|
|
||||||
{warehouseFk: 1, grouping: 10, quantity: 0},
|
|
||||||
{warehouseFk: 1, grouping: 100, quantity: 100}
|
|
||||||
];
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
orderFk: orderId,
|
|
||||||
items: [{warehouseFk: 1, grouping: 100, quantity: 100}]
|
|
||||||
};
|
|
||||||
|
|
||||||
$httpBackend.expectPOST('OrderRows/addToOrder', params).respond(200);
|
|
||||||
controller.submit();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith(`Data saved!`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,3 +0,0 @@
|
||||||
Qty.: Cant.
|
|
||||||
First you must add some quantity: Primero debes agregar alguna cantidad
|
|
||||||
The amounts doesn't match with the grouping: Las cantidades no coinciden con el grouping
|
|
|
@ -1,18 +0,0 @@
|
||||||
@import "variables";
|
|
||||||
|
|
||||||
.vn-order-prices-popover .content {
|
|
||||||
.prices {
|
|
||||||
vn-table {
|
|
||||||
.price-kg {
|
|
||||||
color: $color-font-secondary;
|
|
||||||
font-size: .75rem
|
|
||||||
}
|
|
||||||
.vn-input-number {
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,19 +28,19 @@
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"component": "vn-order",
|
"component": "vn-order",
|
||||||
"description": "Orders"
|
"description": "Orders"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/index?q",
|
"url": "/index?q",
|
||||||
"state": "order.index",
|
"state": "order.index",
|
||||||
"component": "vn-order-index",
|
"component": "vn-order-index",
|
||||||
"description": "Orders"
|
"description": "Orders"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/:id",
|
"url": "/:id",
|
||||||
"state": "order.card",
|
"state": "order.card",
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"component": "vn-order-card"
|
"component": "vn-order-card"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/summary",
|
"url": "/summary",
|
||||||
"state": "order.card.summary",
|
"state": "order.card.summary",
|
||||||
|
@ -49,48 +49,6 @@
|
||||||
"params": {
|
"params": {
|
||||||
"order": "$ctrl.order"
|
"order": "$ctrl.order"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/catalog?q&categoryId&typeId&tagGroups",
|
|
||||||
"state": "order.card.catalog",
|
|
||||||
"component": "vn-order-catalog",
|
|
||||||
"description": "Catalog",
|
|
||||||
"params": {
|
|
||||||
"order": "$ctrl.order"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/volume",
|
|
||||||
"state": "order.card.volume",
|
|
||||||
"component": "vn-order-volume",
|
|
||||||
"description": "Volume",
|
|
||||||
"params": {
|
|
||||||
"order": "$ctrl.order"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/line",
|
|
||||||
"state": "order.card.line",
|
|
||||||
"component": "vn-order-line",
|
|
||||||
"description": "Lines",
|
|
||||||
"params": {
|
|
||||||
"order": "$ctrl.order"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/create?clientFk",
|
|
||||||
"state": "order.create",
|
|
||||||
"component": "vn-order-create",
|
|
||||||
"description": "New order"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "/basic-data",
|
|
||||||
"state": "order.card.basicData",
|
|
||||||
"component": "vn-order-basic-data",
|
|
||||||
"description": "Basic data",
|
|
||||||
"params": {
|
|
||||||
"order": "$ctrl.order"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,86 +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 orders by ticket id"
|
|
||||||
vn-focus>
|
|
||||||
</vn-textfield>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-textfield
|
|
||||||
vn-one
|
|
||||||
label="Client id"
|
|
||||||
ng-model="filter.clientFk">
|
|
||||||
</vn-textfield>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-autocomplete
|
|
||||||
vn-one
|
|
||||||
label="Agency"
|
|
||||||
ng-model="filter.agencyModeFk"
|
|
||||||
url="AgencyModes/isActive"
|
|
||||||
show-field="name"
|
|
||||||
value-field="id">
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-worker-autocomplete
|
|
||||||
vn-one
|
|
||||||
ng-model="filter.workerFk"
|
|
||||||
departments="['VT']"
|
|
||||||
show-field="nickname"
|
|
||||||
label="Sales person">
|
|
||||||
</vn-worker-autocomplete>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-date-picker
|
|
||||||
vn-one
|
|
||||||
label="From landed"
|
|
||||||
ng-model="filter.from">
|
|
||||||
</vn-date-picker>
|
|
||||||
<vn-date-picker
|
|
||||||
vn-one
|
|
||||||
label="To landed"
|
|
||||||
ng-model="filter.to">
|
|
||||||
</vn-date-picker>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-textfield
|
|
||||||
vn-one
|
|
||||||
label="Order id"
|
|
||||||
ng-model="filter.orderFk">
|
|
||||||
</vn-textfield>
|
|
||||||
<vn-autocomplete
|
|
||||||
vn-one
|
|
||||||
label="Application"
|
|
||||||
ng-model="filter.sourceApp"
|
|
||||||
url="Orders/getSourceValues"
|
|
||||||
show-field="value"
|
|
||||||
value-field="value">
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-check
|
|
||||||
vn-one
|
|
||||||
label="My team"
|
|
||||||
ng-model="filter.myTeam"
|
|
||||||
triple-state="true">
|
|
||||||
</vn-check>
|
|
||||||
<vn-check
|
|
||||||
vn-one
|
|
||||||
label="Order confirmed"
|
|
||||||
triple-state="true"
|
|
||||||
ng-model="filter.isConfirmed">
|
|
||||||
</vn-check>
|
|
||||||
<vn-check
|
|
||||||
vn-one
|
|
||||||
label="Show empty"
|
|
||||||
ng-model="filter.showEmpty">
|
|
||||||
</vn-check>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal class="vn-mt-lg">
|
|
||||||
<vn-submit label="Search"></vn-submit>
|
|
||||||
</vn-horizontal>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
|
@ -1,7 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import SearchPanel from 'core/components/searchbar/search-panel';
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderSearchPanel', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: SearchPanel
|
|
||||||
});
|
|
|
@ -1,11 +0,0 @@
|
||||||
Order id: Id cesta
|
|
||||||
Client id: Id cliente
|
|
||||||
From landed: Desde f. entrega
|
|
||||||
To landed: Hasta f. entrega
|
|
||||||
To: Hasta
|
|
||||||
Agency: Agencia
|
|
||||||
Application: Aplicación
|
|
||||||
SalesPerson: Comercial
|
|
||||||
Order confirmed: Pedido confirmado
|
|
||||||
Show empty: Mostrar vacías
|
|
||||||
Search orders by ticket id: Buscar pedido por id ticket
|
|
|
@ -1,131 +0,0 @@
|
||||||
<vn-card class="summary">
|
|
||||||
<h5>
|
|
||||||
<a ng-if="::$ctrl.summary.id"
|
|
||||||
vn-tooltip="Go to the order"
|
|
||||||
ui-sref="order.card.summary({id: {{::$ctrl.summary.id}}})"
|
|
||||||
name="goToSummary">
|
|
||||||
<vn-icon-button icon="launch"></vn-icon-button>
|
|
||||||
</a>
|
|
||||||
<span>
|
|
||||||
<span translate>Basket</span> #{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}}
|
|
||||||
({{$ctrl.summary.client.id}})
|
|
||||||
</span>
|
|
||||||
<vn-button
|
|
||||||
disabled="$ctrl.order.isConfirmed"
|
|
||||||
class="flat"
|
|
||||||
style="color: inherit;"
|
|
||||||
label="Confirm"
|
|
||||||
ng-click="$ctrl.save()"
|
|
||||||
vn-tooltip="Confirm lines">
|
|
||||||
</vn-button>
|
|
||||||
</h5>
|
|
||||||
<vn-horizontal class="ticketSummary__data">
|
|
||||||
<vn-one>
|
|
||||||
<vn-label-value label="Id"
|
|
||||||
value="{{$ctrl.summary.id}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value label="Nickname">
|
|
||||||
<span
|
|
||||||
ng-click="clientDescriptor.show($event, $ctrl.summary.clientFk)"
|
|
||||||
class="link">
|
|
||||||
{{$ctrl.summary.address.nickname}}
|
|
||||||
</span>
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value label="Company"
|
|
||||||
value="{{$ctrl.summary.address.companyFk}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-check label="Confirmed" disabled="true"
|
|
||||||
ng-model="$ctrl.summary.isConfirmed">
|
|
||||||
</vn-check>
|
|
||||||
</vn-one>
|
|
||||||
<vn-one>
|
|
||||||
<vn-label-value label="Created"
|
|
||||||
value="{{$ctrl.summary.created | date: 'dd/MM/yyyy HH:mm'}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value label="Confirmed"
|
|
||||||
value="{{$ctrl.summary.confirmed | date: 'dd/MM/yyyy HH:mm'}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value label="Landed"
|
|
||||||
value="{{$ctrl.summary.landed | date: 'dd/MM/yyyy HH:mm'}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value label="Phone">
|
|
||||||
<vn-link-phone
|
|
||||||
phone-number="$ctrl.summary.address.phone"
|
|
||||||
></vn-link-phone>
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value label="Created from"
|
|
||||||
value="{{$ctrl.summary.sourceApp}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value label="Address" no-ellipsize
|
|
||||||
value="{{$ctrl.formattedAddress}}">
|
|
||||||
</vn-label-value>
|
|
||||||
</vn-one>
|
|
||||||
<vn-one>
|
|
||||||
<vn-label-value label="Notes" no-ellipsize
|
|
||||||
value="{{$ctrl.summary.note}}">
|
|
||||||
</vn-label-value>
|
|
||||||
</vn-one>
|
|
||||||
<vn-one class="taxes">
|
|
||||||
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.summary.subTotal | currency: 'EUR':2}}</p>
|
|
||||||
<p><vn-label translate>VAT</vn-label> {{$ctrl.summary.VAT | currency: 'EUR':2}}</p>
|
|
||||||
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.summary.total | currency: 'EUR':2}}</strong></p>
|
|
||||||
</vn-one>
|
|
||||||
<vn-auto>
|
|
||||||
<vn-table>
|
|
||||||
<vn-thead>
|
|
||||||
<vn-tr>
|
|
||||||
<vn-th shrink></vn-th>
|
|
||||||
<vn-th shrink>Item</vn-th>
|
|
||||||
<vn-th>Description</vn-th>
|
|
||||||
<vn-th number>Quantity</vn-th>
|
|
||||||
<vn-th number>Price</vn-th>
|
|
||||||
<vn-th number>Amount</vn-th>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-thead>
|
|
||||||
<vn-tbody>
|
|
||||||
<vn-tr ng-repeat="row in $ctrl.summary.rows track by row.id">
|
|
||||||
<vn-td shrink>
|
|
||||||
<vn-icon
|
|
||||||
ng-show="row.visible || row.available"
|
|
||||||
color-main
|
|
||||||
icon="warning"
|
|
||||||
vn-tooltip="Visible: {{::row.visible || 0}} <br> {{::$ctrl.translate.instant('Available')}} {{::row.available || 0}}">
|
|
||||||
</vn-icon>
|
|
||||||
<vn-icon ng-show="row.reserved" icon="icon-reserva"></vn-icon>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td shrink>
|
|
||||||
<span
|
|
||||||
ng-click="itemDescriptor.show($event, row.itemFk)"
|
|
||||||
class="link">
|
|
||||||
{{::row.itemFk}}
|
|
||||||
</span>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td vn-fetched-tags>
|
|
||||||
<div>
|
|
||||||
<vn-one title="{{::row.item.name}}">{{::row.item.name}}</vn-one>
|
|
||||||
<vn-one ng-if="::row.item.subName">
|
|
||||||
<h3 title="{{::row.item.subName}}">{{::row.item.subName}}</h3>
|
|
||||||
</vn-one>
|
|
||||||
</div>
|
|
||||||
<vn-fetched-tags
|
|
||||||
max-length="6"
|
|
||||||
item="::row.item"
|
|
||||||
tabindex="-1">
|
|
||||||
</vn-fetched-tags>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td number>{{::row.quantity}}</vn-td>
|
|
||||||
<vn-td number>{{::row.price | currency: 'EUR':2}}</vn-td>
|
|
||||||
<vn-td number>{{::row.quantity * row.price | currency: 'EUR':2}}</vn-td>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-tbody>
|
|
||||||
</table>
|
|
||||||
</vn-auto>
|
|
||||||
</vn-horizontal>
|
|
||||||
</vn-card>
|
|
||||||
<vn-item-descriptor-popover
|
|
||||||
vn-id="item-descriptor"
|
|
||||||
warehouse-fk="$ctrl.vnConfig.warehouseFk">
|
|
||||||
</vn-item-descriptor-popover>
|
|
||||||
<vn-client-descriptor-popover
|
|
||||||
vn-id="client-descriptor">
|
|
||||||
</vn-client-descriptor-popover>
|
|
|
@ -1,41 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Summary from 'salix/components/summary';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
class Controller extends Summary {
|
|
||||||
setSummary() {
|
|
||||||
this.$http.get(`Orders/${this.order.id}/summary`)
|
|
||||||
.then(res => this.summary = res.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
get formattedAddress() {
|
|
||||||
if (!this.summary) return null;
|
|
||||||
|
|
||||||
let address = this.summary.address;
|
|
||||||
let province = address.province ? `(${address.province.name})` : '';
|
|
||||||
|
|
||||||
return `${address.street} - ${address.city} ${province}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onChanges() {
|
|
||||||
if (this.order && this.order.id)
|
|
||||||
this.setSummary();
|
|
||||||
}
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.$http.post(`Orders/${this.order.id}/confirm`).then(() => {
|
|
||||||
this.vnApp.showSuccess(this.$t('Order confirmed'));
|
|
||||||
this.$state.go(`ticket.index`, {
|
|
||||||
q: JSON.stringify({clientFk: this.order.clientFk})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderSummary', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
order: '<'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,47 +0,0 @@
|
||||||
import './index';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderSummary', () => {
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, _$httpBackend_) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
const $element = angular.element('<vn-order-summary></vn-order-summary>');
|
|
||||||
controller = $componentController('vnOrderSummary', {$element});
|
|
||||||
controller.order = {id: 1};
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('getSummary()', () => {
|
|
||||||
it('should now perform a GET query and define the summary property', () => {
|
|
||||||
let res = {
|
|
||||||
id: 1,
|
|
||||||
nickname: 'Batman'
|
|
||||||
};
|
|
||||||
$httpBackend.expectGET(`Orders/1/summary`).respond(res);
|
|
||||||
controller.setSummary();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.summary).toEqual(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('formattedAddress()', () => {
|
|
||||||
it('should return a full fromatted address with city and province', () => {
|
|
||||||
controller.summary = {
|
|
||||||
address: {
|
|
||||||
province: {
|
|
||||||
name: 'Gotham'
|
|
||||||
},
|
|
||||||
street: '1007 Mountain Drive',
|
|
||||||
city: 'Gotham'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(controller.formattedAddress).toEqual('1007 Mountain Drive - Gotham (Gotham)');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,20 +0,0 @@
|
||||||
@import "./variables";
|
|
||||||
|
|
||||||
vn-order-summary .summary{
|
|
||||||
max-width: $width-lg;
|
|
||||||
|
|
||||||
& > vn-horizontal > vn-one {
|
|
||||||
min-width: 160px;
|
|
||||||
|
|
||||||
&.taxes {
|
|
||||||
border: $border-thin-light;
|
|
||||||
text-align: right;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
& > p {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
margin: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
<vn-crud-model
|
|
||||||
auto-load="true"
|
|
||||||
vn-id="model"
|
|
||||||
url="OrderRows"
|
|
||||||
filter="::$ctrl.filter"
|
|
||||||
link="{orderFk: $ctrl.$params.id}"
|
|
||||||
limit="20"
|
|
||||||
data="rows"
|
|
||||||
on-data-change="$ctrl.onDataChange()">
|
|
||||||
</vn-crud-model>
|
|
||||||
<mg-ajax path="Orders/{{$ctrl.$params.id}}/getTotalVolume" options="mgEdit"></mg-ajax>
|
|
||||||
<vn-data-viewer model="model" class="header vn-w-lg">
|
|
||||||
<vn-card class="vn-pa-lg">
|
|
||||||
<vn-label-value
|
|
||||||
label="Total"
|
|
||||||
value="{{::edit.model.totalVolume}} M³">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-label-value
|
|
||||||
label="Cajas"
|
|
||||||
value="{{::edit.model.totalBoxes | dashIfEmpty}} U">
|
|
||||||
</vn-label-value>
|
|
||||||
</vn-card>
|
|
||||||
<vn-card class="vn-mt-md">
|
|
||||||
<vn-table model="model">
|
|
||||||
<vn-thead>
|
|
||||||
<vn-tr>
|
|
||||||
<vn-th shrink field="itemFk" number>Item</vn-th>
|
|
||||||
<vn-th>Description</vn-th>
|
|
||||||
<vn-th shrink field="quantity" number>Quantity</vn-th>
|
|
||||||
<vn-th shrink number>m³ per quantity</vn-th>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-thead>
|
|
||||||
<vn-tbody>
|
|
||||||
<vn-tr ng-repeat="row in rows">
|
|
||||||
<vn-td shrink number>
|
|
||||||
<span
|
|
||||||
ng-click="itemDescriptor.show($event, row.itemFk)"
|
|
||||||
class="link">
|
|
||||||
{{::row.itemFk}}
|
|
||||||
</span>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td vn-fetched-tags>
|
|
||||||
<div>
|
|
||||||
<vn-one title="{{::row.item.name}}">{{::row.item.name}}</vn-one>
|
|
||||||
<vn-one ng-if="::row.item.subName">
|
|
||||||
<h3 title="{{::row.item.subName}}">{{::row.item.subName}}</h3>
|
|
||||||
</vn-one>
|
|
||||||
</div>
|
|
||||||
<vn-fetched-tags
|
|
||||||
max-length="6"
|
|
||||||
item="::row.item"
|
|
||||||
tabindex="-1">
|
|
||||||
</vn-fetched-tags>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td shrink number>{{::row.quantity}}</vn-td>
|
|
||||||
<vn-td shrink number>{{::row.volume | number:3}}</vn-td>
|
|
||||||
</vn-tr>
|
|
||||||
</vn-tbody>
|
|
||||||
</vn-table>
|
|
||||||
</vn-card>
|
|
||||||
</vn-data-viewer>
|
|
||||||
<vn-item-descriptor-popover
|
|
||||||
vn-id="item-descriptor"
|
|
||||||
warehouse-fk="$ctrl.vnConfig.warehouseFk">
|
|
||||||
</vn-item-descriptor-popover>
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
import Section from 'salix/components/section';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
class Controller extends Section {
|
|
||||||
constructor($element, $) {
|
|
||||||
super($element, $);
|
|
||||||
this.filter = {
|
|
||||||
include: {
|
|
||||||
relation: 'item'
|
|
||||||
},
|
|
||||||
order: 'itemFk'
|
|
||||||
};
|
|
||||||
this.order = {};
|
|
||||||
this.ticketVolumes = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
onDataChange() {
|
|
||||||
this.$http.get(`Orders/${this.$params.id}/getVolumes`)
|
|
||||||
.then(res => {
|
|
||||||
this.$.model.data.forEach(order => {
|
|
||||||
res.data.volumes.forEach(volume => {
|
|
||||||
if (order.itemFk === volume.itemFk)
|
|
||||||
order.volume = volume.volume;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnOrderVolume', {
|
|
||||||
template: require('./index.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
order: '<'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,42 +0,0 @@
|
||||||
import './index';
|
|
||||||
|
|
||||||
describe('Order', () => {
|
|
||||||
describe('Component vnOrderVolume', () => {
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
let $scope;
|
|
||||||
|
|
||||||
beforeEach(ngModule('order'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, $state, _$httpBackend_, $rootScope) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
$scope = $rootScope.$new();
|
|
||||||
$scope.model = {
|
|
||||||
data: [
|
|
||||||
{itemFk: 1},
|
|
||||||
{itemFk: 2}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
$state.params.id = 1;
|
|
||||||
const $element = angular.element('<vn-order-volume></vn-order-volume>');
|
|
||||||
controller = $componentController('vnOrderVolume', {$element, $scope});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should join the sale volumes to its respective sale', () => {
|
|
||||||
let response = {
|
|
||||||
volumes: [
|
|
||||||
{itemFk: 1, volume: 0.008},
|
|
||||||
{itemFk: 2, volume: 0.003}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
$httpBackend.expectGET(`Orders/1/getVolumes`).respond(response);
|
|
||||||
controller.onDataChange();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.$.model.data[0].volume).toBe(0.008);
|
|
||||||
expect(controller.$.model.data[1].volume).toBe(0.003);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
@import "./variables";
|
|
||||||
|
|
||||||
vn-order-volume {
|
|
||||||
.header {
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
margin-bottom: $spacing-xs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -39,8 +39,6 @@ module.exports = Self => {
|
||||||
const {reportMail} = agencyMode();
|
const {reportMail} = agencyMode();
|
||||||
let user;
|
let user;
|
||||||
let account;
|
let account;
|
||||||
let userEmail;
|
|
||||||
ctx.args.recipients = reportMail ? reportMail.split(',').map(email => email.trim()) : [];
|
|
||||||
|
|
||||||
if (workerFk) {
|
if (workerFk) {
|
||||||
user = await models.VnUser.findById(workerFk, {
|
user = await models.VnUser.findById(workerFk, {
|
||||||
|
@ -50,17 +48,10 @@ module.exports = Self => {
|
||||||
account = await models.Account.findById(workerFk);
|
account = await models.Account.findById(workerFk);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user?.active && account)
|
if (user?.active && account) ctx.args.recipient = user.emailUser().email;
|
||||||
userEmail = user.emailUser().email;
|
else ctx.args.recipient = reportMail;
|
||||||
|
|
||||||
if (userEmail)
|
|
||||||
ctx.args.recipients.push(userEmail);
|
|
||||||
|
|
||||||
ctx.args.recipients = [...new Set(ctx.args.recipients)];
|
|
||||||
|
|
||||||
if (!ctx.args.recipients.length)
|
|
||||||
throw new UserError('An email is necessary');
|
|
||||||
|
|
||||||
|
if (!ctx.args.recipient) throw new UserError('An email is necessary');
|
||||||
return Self.sendTemplate(ctx, 'driver-route');
|
return Self.sendTemplate(ctx, 'driver-route');
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "Travels",
|
"name": "Travels",
|
||||||
"icon": "local_airport",
|
"icon": "local_airport",
|
||||||
"validations": true,
|
"validations": true,
|
||||||
"dependencies": ["worker"],
|
"dependencies": ["worker", "entry"],
|
||||||
"menus": {
|
"menus": {
|
||||||
"main": [
|
"main": [
|
||||||
{"state": "travel.index", "icon": "local_airport"},
|
{"state": "travel.index", "icon": "local_airport"},
|
||||||
|
|
Loading…
Reference in New Issue