Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 7874-createObservationType
gitea/salix/pipeline/pr-dev This commit looks good
Details
gitea/salix/pipeline/pr-dev This commit looks good
Details
This commit is contained in:
commit
3d831e290b
|
@ -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),
|
||||
(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),
|
||||
(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),
|
||||
(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)
|
||||
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
|
||||
(id, name, isSalaryAgreed, isVariable, isException)
|
||||
VALUES
|
||||
|
|
|
@ -15,7 +15,7 @@ BEGIN
|
|||
SELECT DISTINCT warehouseFk
|
||||
FROM orderRow
|
||||
WHERE orderFk = vOrderFk
|
||||
AND shipped = util.VN_CURDATE();
|
||||
AND shipment = util.VN_CURDATE();
|
||||
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
|
||||
|
||||
|
|
|
@ -5,22 +5,26 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`expeditionPallet_buil
|
|||
vWorkerFk INT,
|
||||
OUT vPalletFk INT
|
||||
)
|
||||
BEGIN
|
||||
/** Construye un pallet de expediciones.
|
||||
proc: BEGIN
|
||||
/**
|
||||
* Builds an expedition pallet.
|
||||
*
|
||||
* Primero comprueba si esas expediciones ya pertenecen a otro pallet,
|
||||
* en cuyo caso actualiza ese pallet.
|
||||
* First, it checks if these expeditions already belong to another pallet,
|
||||
* in which case it returns an error.
|
||||
*
|
||||
* @param vExpeditions JSON_ARRAY con esta estructura [exp1, exp2, exp3, ...]
|
||||
* @param vArcId INT Identificador de arcRead
|
||||
* @param vWorkerFk INT Identificador de worker
|
||||
* @param out vPalletFk Identificador de expeditionPallet
|
||||
* @param vExpeditions JSON_ARRAY with this structure [exp1, exp2, exp3, ...]
|
||||
* @param vArcId INT Identifier of arcRead
|
||||
* @param vWorkerFk INT Identifier of worker
|
||||
* @param out vPalletFk Identifier of expeditionPallet
|
||||
*/
|
||||
|
||||
DECLARE vCounter INT;
|
||||
DECLARE vExpeditionFk INT;
|
||||
DECLARE vTruckFk INT;
|
||||
DECLARE vPrinterFk INT;
|
||||
DECLARE vExpeditionStateTypeFk INT;
|
||||
DECLARE vFreeExpeditionCount INT;
|
||||
DECLARE vExpeditionWithPallet INT;
|
||||
|
||||
CREATE OR REPLACE TEMPORARY TABLE tExpedition (
|
||||
expeditionFk INT,
|
||||
|
@ -44,48 +48,63 @@ BEGIN
|
|||
WHERE e.id = vExpeditionFk;
|
||||
END WHILE;
|
||||
|
||||
SELECT palletFk INTO vPalletFk
|
||||
FROM (
|
||||
SELECT palletFk, count(*) n
|
||||
FROM tExpedition
|
||||
WHERE palletFk > 0
|
||||
GROUP BY palletFk
|
||||
ORDER BY n DESC
|
||||
LIMIT 100
|
||||
) sub
|
||||
LIMIT 1;
|
||||
SELECT COUNT(expeditionFk) INTO vFreeExpeditionCount
|
||||
FROM tExpedition
|
||||
WHERE palletFk IS NULL;
|
||||
|
||||
IF vPalletFk IS NULL THEN
|
||||
SELECT roadmapStopFk INTO vTruckFk
|
||||
FROM (
|
||||
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;
|
||||
SELECT COUNT(expeditionFk) INTO vExpeditionWithPallet
|
||||
FROM tExpedition
|
||||
WHERE palletFk;
|
||||
|
||||
IF vTruckFk IS NULL THEN
|
||||
CALL util.throw ('TRUCK_NOT_AVAILABLE');
|
||||
END IF;
|
||||
|
||||
INSERT INTO expeditionPallet SET truckFk = vTruckFk;
|
||||
|
||||
SET vPalletFk = LAST_INSERT_ID();
|
||||
IF vExpeditionWithPallet THEN
|
||||
UPDATE arcRead
|
||||
SET error = (
|
||||
SELECT GROUP_CONCAT(expeditionFk SEPARATOR ', ')
|
||||
FROM tExpedition
|
||||
WHERE palletFk
|
||||
)
|
||||
WHERE id = vArcId;
|
||||
LEAVE proc;
|
||||
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)
|
||||
SELECT expeditionFk, vPalletFk, vWorkerFk
|
||||
FROM tExpedition
|
||||
ON DUPLICATE KEY UPDATE palletFk = vPalletFk, workerFk = vWorkerFk;
|
||||
WHERE palletFk IS NULL;
|
||||
|
||||
SELECT id INTO vExpeditionStateTypeFk
|
||||
FROM expeditionStateType
|
||||
WHERE code = 'PALLETIZED';
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -8,17 +8,18 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`item_getSimilar`(
|
|||
)
|
||||
BEGIN
|
||||
/**
|
||||
* Propone articulos ordenados, con la cantidad
|
||||
* de veces usado y segun sus caracteristicas.
|
||||
*
|
||||
* @param vSelf Id de artículo
|
||||
* @param vWarehouseFk Id de almacen
|
||||
* @param vDated Fecha
|
||||
* @param vShowType Mostrar tipos
|
||||
* @param vDaysInForward Días de alcance para las ventas
|
||||
*/
|
||||
* Propone articulos ordenados, con la cantidad
|
||||
* de veces usado y segun sus caracteristicas.
|
||||
*
|
||||
* @param vSelf Id de artículo
|
||||
* @param vWarehouseFk Id de almacen
|
||||
* @param vDated Fecha
|
||||
* @param vShowType Mostrar tipos
|
||||
* @param vDaysInForward Días de alcance para las ventas (https://redmine.verdnatura.es/issues/7956#note-4)
|
||||
*/
|
||||
DECLARE vAvailableCalcFk INT;
|
||||
DECLARE vVisibleCalcFk INT;
|
||||
DECLARE vTypeFk INT;
|
||||
DECLARE vPriority INT DEFAULT 1;
|
||||
|
||||
CALL cache.available_refresh(vAvailableCalcFk, FALSE, vWarehouseFk, vDated);
|
||||
|
@ -42,19 +43,9 @@ BEGIN
|
|||
AND it.priority = vPriority
|
||||
LEFT JOIN vn.tag t ON t.id = it.tagFk
|
||||
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,
|
||||
LEAST(CAST(sd.quantity AS INT), v.visible) advanceable,
|
||||
NULL advanceable, -- https://redmine.verdnatura.es/issues/7956#note-4
|
||||
i.longName,
|
||||
i.subName,
|
||||
i.tag5,
|
||||
|
@ -79,7 +70,6 @@ BEGIN
|
|||
v.visible located,
|
||||
b.price2
|
||||
FROM vn.item i
|
||||
LEFT JOIN sold sd ON sd.itemFk = i.id
|
||||
JOIN cache.available a ON a.item_id = i.id
|
||||
AND a.calc_id = vAvailableCalcFk
|
||||
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.buy b ON b.id = lb.buy_id
|
||||
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.id <> vSelf
|
||||
ORDER BY (a.available > 0) DESC,
|
||||
`counter` DESC,
|
||||
(t.name = its.name) DESC,
|
||||
(it.value = its.value) DESC,
|
||||
(i.tag5 = its.tag5) DESC,
|
||||
match5 DESC,
|
||||
(i.tag6 = its.tag6) DESC,
|
||||
match6 DESC,
|
||||
(i.tag7 = its.tag7) DESC,
|
||||
match7 DESC,
|
||||
(i.tag8 = its.tag8) DESC,
|
||||
match8 DESC
|
||||
ORDER BY `counter` DESC,
|
||||
(t.name = its.name) DESC,
|
||||
(it.value = its.value) DESC,
|
||||
(i.tag5 = its.tag5) DESC,
|
||||
match5 DESC,
|
||||
(i.tag6 = its.tag6) DESC,
|
||||
match6 DESC,
|
||||
(i.tag7 = its.tag7) DESC,
|
||||
match7 DESC,
|
||||
(i.tag8 = its.tag8) DESC,
|
||||
match8 DESC
|
||||
LIMIT 100;
|
||||
END$$
|
||||
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)
|
||||
newRoute = newRoute.concat(`?table=${this.logger.$params.q}`);
|
||||
if (this.logger.$params.q) {
|
||||
let tableValue = 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})
|
||||
.then(res => {
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<th field="clientFk">
|
||||
<span translate>Client</span>
|
||||
</th>
|
||||
<th>
|
||||
<th field="isWorker">
|
||||
<span translate>Es trabajador</span>
|
||||
</th>
|
||||
<th field="salesPersonFk">
|
||||
|
|
|
@ -57,6 +57,11 @@ export default class Controller extends Section {
|
|||
field: 'observation',
|
||||
searchable: false
|
||||
},
|
||||
{
|
||||
field: 'isWorker',
|
||||
checkbox: true,
|
||||
|
||||
},
|
||||
{
|
||||
field: 'created',
|
||||
datepicker: true
|
||||
|
@ -73,9 +78,6 @@ export default class Controller extends Section {
|
|||
|
||||
set defaulters(value) {
|
||||
if (!value || !value.length) return;
|
||||
for (let defaulter of value)
|
||||
defaulter.isWorker = defaulter.businessTypeFk === 'worker';
|
||||
|
||||
this._defaulters = value;
|
||||
}
|
||||
|
||||
|
@ -164,6 +166,8 @@ export default class Controller extends Section {
|
|||
|
||||
exprBuilder(param, value) {
|
||||
switch (param) {
|
||||
case 'isWorker':
|
||||
return {isWorker: value};
|
||||
case 'creditInsurance':
|
||||
case 'amount':
|
||||
case 'clientFk':
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('Entry filter()', () => {
|
|||
|
||||
const result = await models.Entry.filter(ctx, options);
|
||||
|
||||
expect(result.length).toEqual(11);
|
||||
expect(result.length).toEqual(12);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -152,7 +152,7 @@ describe('Entry filter()', () => {
|
|||
|
||||
const result = await models.Entry.filter(ctx, options);
|
||||
|
||||
expect(result.length).toEqual(10);
|
||||
expect(result.length).toEqual(11);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
|
@ -45,8 +45,8 @@ module.exports = Self => {
|
|||
i.id itemFk,
|
||||
i.name itemName,
|
||||
ti.quantity,
|
||||
(ac.conversionCoefficient * (ti.quantity / b.packing) * buy_getVolume(b.id))
|
||||
/ (vc.trolleyM3 * 1000000) volume,
|
||||
ROUND((ac.conversionCoefficient * (ti.quantity / b.packing) * buy_getVolume(b.id))
|
||||
/ (vc.trolleyM3 * 1000000),1) volume,
|
||||
b.packagingFk packagingFk,
|
||||
b.packing
|
||||
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';
|
||||
|
||||
import './main';
|
||||
import './descriptor';
|
||||
import './descriptor-popover';
|
||||
import './summary';
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
"main": [
|
||||
{"state": "entry.index", "icon": "icon-entry"},
|
||||
{"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": [
|
||||
|
@ -27,6 +33,90 @@
|
|||
"component": "vn-entry-index",
|
||||
"description": "Entries",
|
||||
"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');
|
||||
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();
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
const maxDate = Date.vnNew();
|
||||
|
@ -13,7 +13,7 @@ describe('item lastEntriesFilter()', () => {
|
|||
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
||||
const result = await models.Item.lastEntriesFilter(filter, options);
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
expect(result.length).toEqual(2);
|
||||
|
||||
await tx.rollback();
|
||||
} 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();
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
minDate.setMonth(minDate.getMonth() - 2, 1);
|
||||
|
@ -37,7 +37,7 @@ describe('item lastEntriesFilter()', () => {
|
|||
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
||||
const result = await models.Item.lastEntriesFilter(filter, options);
|
||||
|
||||
expect(result.length).toEqual(5);
|
||||
expect(result.length).toEqual(6);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Items",
|
||||
"icon": "icon-item",
|
||||
"validations" : true,
|
||||
"dependencies": ["worker", "client", "ticket"],
|
||||
"dependencies": ["worker", "client", "ticket", "entry"],
|
||||
"menus": {
|
||||
"main": [
|
||||
{"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';
|
||||
|
||||
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';
|
||||
|
||||
export default class Order extends ModuleMain {
|
||||
$postLink() {
|
||||
this.filter = {showEmpty: false};
|
||||
constructor($element, $) {
|
||||
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,
|
||||
"component": "vn-order",
|
||||
"description": "Orders"
|
||||
},
|
||||
},
|
||||
{
|
||||
"url": "/index?q",
|
||||
"state": "order.index",
|
||||
"component": "vn-order-index",
|
||||
"description": "Orders"
|
||||
},
|
||||
},
|
||||
{
|
||||
"url": "/:id",
|
||||
"state": "order.card",
|
||||
"abstract": true,
|
||||
"component": "vn-order-card"
|
||||
},
|
||||
},
|
||||
{
|
||||
"url": "/summary",
|
||||
"state": "order.card.summary",
|
||||
|
@ -49,48 +49,6 @@
|
|||
"params": {
|
||||
"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();
|
||||
let user;
|
||||
let account;
|
||||
let userEmail;
|
||||
ctx.args.recipients = reportMail ? reportMail.split(',').map(email => email.trim()) : [];
|
||||
|
||||
if (workerFk) {
|
||||
user = await models.VnUser.findById(workerFk, {
|
||||
|
@ -50,17 +48,10 @@ module.exports = Self => {
|
|||
account = await models.Account.findById(workerFk);
|
||||
}
|
||||
|
||||
if (user?.active && account)
|
||||
userEmail = user.emailUser().email;
|
||||
|
||||
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 (user?.active && account) ctx.args.recipient = user.emailUser().email;
|
||||
else ctx.args.recipient = reportMail;
|
||||
|
||||
if (!ctx.args.recipient) throw new UserError('An email is necessary');
|
||||
return Self.sendTemplate(ctx, 'driver-route');
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Travels",
|
||||
"icon": "local_airport",
|
||||
"validations": true,
|
||||
"dependencies": ["worker"],
|
||||
"dependencies": ["worker", "entry"],
|
||||
"menus": {
|
||||
"main": [
|
||||
{"state": "travel.index", "icon": "local_airport"},
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
"@babel/register": "^7.7.7",
|
||||
"@commitlint/cli": "^19.2.1",
|
||||
"@commitlint/config-conventional": "^19.1.0",
|
||||
"@verdnatura/myt": "^1.6.11",
|
||||
"@verdnatura/myt": "^1.6.12",
|
||||
"angular-mocks": "^1.7.9",
|
||||
"babel-jest": "^26.0.1",
|
||||
"babel-loader": "^8.2.4",
|
||||
|
|
|
@ -143,8 +143,8 @@ devDependencies:
|
|||
specifier: ^19.1.0
|
||||
version: 19.1.0
|
||||
'@verdnatura/myt':
|
||||
specifier: ^1.6.11
|
||||
version: 1.6.11
|
||||
specifier: ^1.6.12
|
||||
version: 1.6.12
|
||||
angular-mocks:
|
||||
specifier: ^1.7.9
|
||||
version: 1.8.3
|
||||
|
@ -2846,8 +2846,8 @@ packages:
|
|||
dev: false
|
||||
optional: true
|
||||
|
||||
/@verdnatura/myt@1.6.11:
|
||||
resolution: {integrity: sha512-uqdbSJSznBBzAoRkvBt600nUMEPL1PJ2v73eWMZbaoGUMiZiNAehYjs4gIrObP1cxC85JOx97XoLpG0BzPsaig==}
|
||||
/@verdnatura/myt@1.6.12:
|
||||
resolution: {integrity: sha512-t/SiDuQW9KJkcjhwQ9AkrcoTwghxQ7IyQ56e+88eYdoMi24l6bQGF0wHzMaIPRfQAoR8hqgfMOief4OAqW4Iqw==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@sqltools/formatter': 1.2.5
|
||||
|
@ -6548,7 +6548,7 @@ packages:
|
|||
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==}
|
||||
engines: {node: '>= 4.0'}
|
||||
os: [darwin]
|
||||
deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2
|
||||
deprecated: Upgrade to fsevents v2 to mitigate potential security issues
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
|
@ -10485,7 +10485,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
semver: 7.5.4
|
||||
semver: 7.6.0
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
|
@ -12501,6 +12501,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/semver@7.6.0:
|
||||
resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
|
||||
|
@ -12508,7 +12509,6 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/send@0.18.0(supports-color@6.1.0):
|
||||
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
|
||||
|
|
Loading…
Reference in New Issue