Merge branch 'dev' into 3338-worker_calendar
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2021-11-18 14:01:25 +00:00
commit c1adba3249
135 changed files with 2273 additions and 868 deletions

View File

@ -27,7 +27,7 @@ module.exports = Self => {
Self.sendCheckingPresence = async(ctx, recipientId, message, options) => {
if (!recipientId) return false;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -60,7 +60,7 @@ module.exports = Self => {
const args = ctx.args;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -54,7 +54,7 @@ module.exports = Self => {
const args = ctx.args;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -16,7 +16,7 @@ module.exports = function(Self) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -31,7 +31,7 @@ module.exports = function(Self) {
const userId = ctx.req.accessToken.userId;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -23,7 +23,7 @@ module.exports = function(Self) {
const userId = ctx.req.accessToken.userId;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -7,8 +7,7 @@ module.exports = function(Self) {
required: true,
description: `Code of the table you ask its configuration`,
http: {source: 'body'}
}
],
}],
returns: {
type: 'object',
root: true
@ -29,6 +28,6 @@ module.exports = function(Self) {
config.userFk = ctx.req.accessToken.userId;
return await Self.app.models.UserConfigView.create(config);
return Self.app.models.UserConfigView.create(config);
};
};

View File

@ -29,6 +29,9 @@
"ChatConfig": {
"dataSource": "vn"
},
"DefaultViewConfig": {
"dataSource": "vn"
},
"Delivery": {
"dataSource": "vn"
},

View File

@ -0,0 +1,25 @@
{
"name": "DefaultViewConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "salix.defaultViewConfig"
}
},
"properties": {
"tableCode": {
"id": true,
"type": "string",
"required": true
},
"columns": {
"type": "object"
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -0,0 +1,14 @@
CREATE TABLE `salix`.`defaultViewConfig`
(
tableCode VARCHAR(25) not null,
columns JSON not null
)
comment 'The default configuration of columns for views';
INSERT INTO `salix`.`defaultViewConfig` (tableCode, columns)
VALUES
('itemsIndex', '{"intrastat":false,"stemMultiplier":false,"landed":false}'),
('latestBuys', '{"intrastat":false,"description":false,"density":false,"isActive":false,"freightValue":false,"packageValue":false,"isIgnored":false,"price2":false,"minPrice":true,"ektFk":false,"weight":false,"id":true,"packing":true,"grouping":true,"quantity":true,"size":false,"name":true,"code":true,"origin":true,"family":true,"entryFk":true,"buyingValue":true,"comissionValue":false,"price3":true,"packageFk":true,"packingOut":true}'),
('ticketsMonitor', '{"id":false}');

View File

@ -1,9 +1,9 @@
DROP PROCEDURE IF EXISTS vn.item_getBalance;
DROP PROCEDURE IF EXISTS `vn`.`item_getBalance`;
DELIMITER $$
$$
CREATE
definer = root@`%` procedure vn.item_getBalance(IN vItemId int, IN vWarehouse int)
definer = root@`%` procedure `vn`.`item_getBalance`(IN vItemId int, IN vWarehouse int)
BEGIN
DECLARE vDateInventory DATETIME;
DECLARE vCurdate DATE DEFAULT CURDATE();

View File

@ -739,7 +739,7 @@ INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `
(3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0),
(4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1),
(5, 'CON', 'Container', 3, 1, NULL, 35, 1),
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 35, 0);
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 16, 0);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES
@ -796,25 +796,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('SER', 'Services'),
('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`)
VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT'),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT'),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT'),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT'),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT'),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT'),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT'),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT'),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT'),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT'),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT'),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT'),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT'),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT'),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB'),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB'),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT');
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 0),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT', 1),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0);
-- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2

View File

@ -341,48 +341,32 @@ let actions = {
},
waitForTextInElement: async function(selector, text) {
const expectedText = text.toLowerCase();
return new Promise((resolve, reject) => {
let attempts = 0;
const interval = setInterval(async() => {
const currentText = await this.evaluate(selector => {
return document.querySelector(selector).innerText.toLowerCase();
}, selector);
if (currentText === expectedText || attempts === 40) {
clearInterval(interval);
resolve(currentText);
}
attempts += 1;
}, 100);
}).then(result => {
return expect(result).toContain(expectedText);
});
await this.waitForFunction((selector, text) => {
if (document.querySelector(selector)) {
const innerText = document.querySelector(selector).innerText.toLowerCase();
const expectedText = text.toLowerCase();
if (innerText.includes(expectedText))
return innerText;
}
}, {}, selector, text);
},
waitForTextInField: async function(selector, text) {
let builtSelector = await this.selectorFormater(selector);
await this.waitForSelector(builtSelector);
const expectedText = text.toLowerCase();
return new Promise((resolve, reject) => {
let attempts = 0;
const interval = setInterval(async() => {
const currentText = await this.evaluate(selector => {
return document.querySelector(selector).value.toLowerCase();
}, builtSelector);
const builtSelector = await this.selectorFormater(selector);
const expectedValue = text.toLowerCase();
if (currentText === expectedText || attempts === 40) {
clearInterval(interval);
resolve(currentText);
try {
await this.waitForFunction((selector, text) => {
const element = document.querySelector(selector);
if (element) {
const value = element.value.toLowerCase();
if (value.includes(text))
return true;
}
attempts += 1;
}, 100);
}).then(result => {
if (result === '')
return expect(result).toEqual(expectedText);
return expect(result).toContain(expectedText);
});
}, {}, builtSelector, expectedValue);
} catch (error) {
throw new Error(`${text} wasn't the value of ${builtSelector}, ${error}`);
}
},
selectorFormater: function(selector) {

View File

@ -313,27 +313,26 @@ export default {
},
itemsIndex: {
createItemButton: `vn-float-button`,
firstSearchResult: 'vn-item-index a:nth-child(1)',
searchResult: 'vn-item-index a.vn-tr',
firstResultPreviewButton: 'vn-item-index vn-tbody > :nth-child(1) .buttons > [icon="preview"]',
firstSearchResult: 'vn-item-index tbody tr:nth-child(1)',
searchResult: 'vn-item-index tbody tr:not(.empty-rows)',
firstResultPreviewButton: 'vn-item-index tbody > :nth-child(1) .buttons > [icon="preview"]',
searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]',
acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]',
closeItemSummaryPreview: '.vn-popup.shown',
fieldsToShowButton: 'vn-item-index vn-table > div > div > vn-icon-button[icon="more_vert"]',
fieldsToShowForm: '.vn-popover.shown .content',
firstItemImage: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > img',
firstItemImageTd: 'vn-item-index vn-table a:nth-child(1) vn-td:nth-child(1)',
firstItemId: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(2)',
idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(1) > vn-check',
stemsCheckbox: '.vn-popover.shown vn-horizontal:nth-child(2) > vn-check',
sizeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check',
typeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(5) > vn-check',
categoryCheckbox: '.vn-popover.shown vn-horizontal:nth-child(6) > vn-check',
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(7) > vn-check',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(8) > vn-check',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(9) > vn-check',
destinyCheckbox: '.vn-popover.shown vn-horizontal:nth-child(10) > vn-check',
taxClassCheckbox: '.vn-popover.shown vn-horizontal:nth-child(11) > vn-check',
shownColumns: 'vn-item-index vn-button[id="shownColumns"]',
shownColumnsList: '.vn-popover.shown .content',
firstItemImage: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(1) > img',
firstItemImageTd: 'vn-item-index smart-table tr:nth-child(1) td:nth-child(1)',
firstItemId: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(2)',
idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Identifier"]',
stemsCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Stems"]',
sizeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Size"]',
typeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Type"]',
categoryCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Category"]',
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]',
densityCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Density"]',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
},
itemFixedPrice: {
@ -1087,7 +1086,7 @@ export default {
allBuyCheckbox: 'vn-entry-buy-index thead vn-check',
firstBuyCheckbox: 'vn-entry-buy-index tbody:nth-child(2) vn-check',
deleteBuysButton: 'vn-entry-buy-index vn-button[icon="delete"]',
addBuyButton: 'vn-entry-buy-index vn-icon[icon="add_circle"]',
addBuyButton: 'vn-entry-buy-index vn-icon[icon="add"]',
secondBuyPackingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price3"]',
secondBuyGroupingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price2"]',
secondBuyPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.buyingValue"]',
@ -1109,9 +1108,9 @@ export default {
importBuysButton: 'vn-entry-buy-import button[type="submit"]'
},
entryLatestBuys: {
firstBuy: 'vn-entry-latest-buys vn-tbody > a:nth-child(1)',
allBuysCheckBox: 'vn-entry-latest-buys vn-thead vn-check',
secondBuyCheckBox: 'vn-entry-latest-buys a:nth-child(2) vn-check[ng-model="buy.checked"]',
firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(1)',
allBuysCheckBox: 'vn-entry-latest-buys thead vn-check',
secondBuyCheckBox: 'vn-entry-latest-buys tbody tr:nth-child(2) vn-check[ng-model="buy.$checked"]',
editBuysButton: 'vn-entry-latest-buys vn-button[icon="edit"]',
fieldAutocomplete: 'vn-autocomplete[ng-model="$ctrl.editedColumn.field"]',
newValueInput: 'vn-textfield[ng-model="$ctrl.editedColumn.newValue"]',

View File

@ -19,7 +19,9 @@ describe('Login path', async() => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.text).toContain('Invalid login, remember that distinction is made between uppercase and lowercase');
const errorMessage = 'Invalid login, remember that distinction is made between uppercase and lowercase';
expect(message.text).toContain(errorMessage);
expect(state).toBe('login');
});
@ -28,7 +30,9 @@ describe('Login path', async() => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.text).toContain('Invalid login, remember that distinction is made between uppercase and lowercase');
const errorMessage = 'Invalid login, remember that distinction is made between uppercase and lowercase';
expect(message.text).toContain(errorMessage);
expect(state).toBe('login');
});

View File

@ -83,6 +83,7 @@ describe('Client create path', () => {
expect(message.text).toContain('Some fields are invalid');
});
/* Tarea #3370
it(`should attempt to create a new user with all it's data but wrong business type`, async() => {
await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
@ -91,9 +92,12 @@ describe('Client create path', () => {
const message = await page.waitForSnackbar();
expect(message.text).toContain('Some fields are invalid');
});
});*/
it(`should attempt to create a new user with all it's data but wrong postal code`, async() => {
await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.clearInput(selectors.createClientView.postcode);
await page.write(selectors.createClientView.postcode, '479999');

View File

@ -112,7 +112,7 @@ describe('Client Edit fiscalData path', () => {
expect(message.text).toContain('Cannot check Equalization Tax in this NIF/CIF');
});
it('should finally edit the fixcal data correctly as VIES isnt checked and fiscal id is valid for EQtax', async() => {
it('should edit the fiscal data correctly as VIES isnt checked and fiscal id is valid for EQtax', async() => {
await page.clearInput(selectors.clientFiscalData.fiscalId);
await page.write(selectors.clientFiscalData.fiscalId, '94980061C');
await page.waitToClick(selectors.clientFiscalData.saveButton);

View File

@ -38,10 +38,13 @@ describe('Client Edit billing data path', () => {
await page.autocompleteSearch(selectors.clientBillingData.newBankEntityCountry, 'España');
await page.write(selectors.clientBillingData.newBankEntityCode, '9999');
await page.waitToClick(selectors.clientBillingData.acceptBankEntityButton);
const message = await page.waitForSnackbar();
await page.waitForTextInField(selectors.clientBillingData.swiftBic, 'Gotham City Bank');
const newcode = await page.waitToGetProperty(selectors.clientBillingData.swiftBic, 'value');
expect(newcode).toEqual('GTHMCT Gotham City Bank');
expect(message.text).toContain('Data saved!');
});
it(`should confirm the IBAN pay method was sucessfully saved`, async() => {

View File

@ -16,13 +16,13 @@ describe('Item summary path', () => {
it('should search for an item', async() => {
await page.doSearch('Ranged weapon');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon');
await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
const isVisible = await page.isVisible(selectors.itemSummary.basicData);
expect(nResults).toBe(3);
expect(resultsCount).toBe(3);
expect(isVisible).toBeTruthy();
});
@ -61,12 +61,12 @@ describe('Item summary path', () => {
it('should search for other item', async() => {
await page.doSearch('Melee Reinforced');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
await page.waitForSelector(selectors.itemSummary.basicData, {visible: true});
expect(nResults).toBe(2);
expect(resultsCount).toBe(2);
});
it(`should now check the item summary preview shows fields from basic data`, async() => {

View File

@ -0,0 +1,71 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Item Create', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'item');
});
afterAll(async() => {
await browser.close();
});
it(`should search for the item Infinity Gauntlet to confirm it isn't created yet`, async() => {
await page.doSearch('Infinity Gauntlet');
const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
expect(resultsCount).toEqual(0);
});
it('should access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should return to the item index by clickig the cancel button', async() => {
await page.waitToClick(selectors.itemCreateView.cancelButton);
await page.waitForState('item.index');
});
it('should now access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should create the Infinity Gauntlet item', async() => {
await page.write(selectors.itemCreateView.temporalName, 'Infinity Gauntlet');
await page.autocompleteSearch(selectors.itemCreateView.type, 'Crisantemo');
await page.autocompleteSearch(selectors.itemCreateView.intrastat, 'Coral y materiales similares');
await page.autocompleteSearch(selectors.itemCreateView.origin, 'Holand');
await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should confirm Infinity Gauntlet item was created', async() => {
let result = await page
.waitToGetProperty(selectors.itemBasicData.name, 'value');
expect(result).toEqual('Infinity Gauntlet');
result = await page
.waitToGetProperty(selectors.itemBasicData.type, 'value');
expect(result).toEqual('Crisantemo');
result = await page
.waitToGetProperty(selectors.itemBasicData.intrastat, 'value');
expect(result).toEqual('5080000 Coral y materiales similares');
result = await page
.waitToGetProperty(selectors.itemBasicData.origin, 'value');
expect(result).toEqual('Holand');
});
});

View File

@ -1,105 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Item Create/Clone path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'item');
});
afterAll(async() => {
await browser.close();
});
describe('create', () => {
it(`should search for the item Infinity Gauntlet to confirm it isn't created yet`, async() => {
await page.doSearch('Infinity Gauntlet');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
expect(nResults).toEqual(0);
});
it('should access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should return to the item index by clickig the cancel button', async() => {
await page.waitToClick(selectors.itemCreateView.cancelButton);
await page.waitForState('item.index');
});
it('should now access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should create the Infinity Gauntlet item', async() => {
await page.write(selectors.itemCreateView.temporalName, 'Infinity Gauntlet');
await page.autocompleteSearch(selectors.itemCreateView.type, 'Crisantemo');
await page.autocompleteSearch(selectors.itemCreateView.intrastat, 'Coral y materiales similares');
await page.autocompleteSearch(selectors.itemCreateView.origin, 'Holand');
await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should confirm Infinity Gauntlet item was created', async() => {
let result = await page
.waitToGetProperty(selectors.itemBasicData.name, 'value');
expect(result).toEqual('Infinity Gauntlet');
result = await page
.waitToGetProperty(selectors.itemBasicData.type, 'value');
expect(result).toEqual('Crisantemo');
result = await page
.waitToGetProperty(selectors.itemBasicData.intrastat, 'value');
expect(result).toEqual('5080000 Coral y materiales similares');
result = await page
.waitToGetProperty(selectors.itemBasicData.origin, 'value');
expect(result).toEqual('Holand');
});
});
// Issue #2201
// When there is just one result you're redirected automatically to it, so
// it's not possible to use the clone option.
xdescribe('clone', () => {
it('should return to the items index by clicking the return to items button', async() => {
await page.waitToClick(selectors.itemBasicData.goToItemIndexButton);
await page.waitForSelector(selectors.itemsIndex.createItemButton);
await page.waitForState('item.index');
});
it(`should search for the item Infinity Gauntlet`, async() => {
await page.doSearch('Infinity Gauntlet');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
expect(nResults).toEqual(1);
});
it(`should clone the Infinity Gauntlet`, async() => {
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Infinity Gauntlet');
await page.waitToClick(selectors.itemsIndex.searchResultCloneButton);
await page.waitToClick(selectors.itemsIndex.acceptClonationAlertButton);
await page.waitForState('item.tags');
});
it('should search for the item Infinity Gauntlet and find two', async() => {
await page.doSearch('Infinity Gauntlet');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
expect(nResults).toEqual(2);
});
});
});

View File

@ -16,8 +16,8 @@ describe('Item index path', () => {
});
it('should click on the fields to show button to open the list of columns to show', async() => {
await page.waitToClick(selectors.itemsIndex.fieldsToShowButton);
const visible = await page.isVisible(selectors.itemsIndex.fieldsToShowForm);
await page.waitToClick(selectors.itemsIndex.shownColumns);
const visible = await page.isVisible(selectors.itemsIndex.shownColumnsList);
expect(visible).toBeTruthy();
});
@ -31,7 +31,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.destinyCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();
@ -39,6 +39,7 @@ describe('Item index path', () => {
});
it('should navigate forth and back to see the images column is still visible', async() => {
await page.closePopup();
await page.waitToClick(selectors.itemsIndex.firstSearchResult);
await page.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton);
await page.waitToClick(selectors.globalItems.searchButton);
@ -54,7 +55,7 @@ describe('Item index path', () => {
});
it('should mark all unchecked boxes to leave the index as it was', async() => {
await page.waitToClick(selectors.itemsIndex.fieldsToShowButton);
await page.waitToClick(selectors.itemsIndex.shownColumns);
await page.waitToClick(selectors.itemsIndex.idCheckbox);
await page.waitToClick(selectors.itemsIndex.stemsCheckbox);
await page.waitToClick(selectors.itemsIndex.sizeCheckbox);
@ -63,7 +64,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.destinyCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();
@ -71,6 +72,7 @@ describe('Item index path', () => {
});
it('should now navigate forth and back to see the ids column is now visible', async() => {
await page.closePopup();
await page.waitToClick(selectors.itemsIndex.firstSearchResult);
await page.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton);
await page.waitToClick(selectors.globalItems.searchButton);

View File

@ -62,7 +62,7 @@ describe('Ticket Create packages path', () => {
expect(result).toEqual('7 : Container medical box 1m');
});
it(`should confirm the first quantity is just a number and the string part was ignored by the imput number`, async() => {
it(`should confirm quantity is just a number and the string part was ignored by the imput number`, async() => {
await page.waitForTextInField(selectors.ticketPackages.firstQuantity, '-99');
const result = await page.waitToGetProperty(selectors.ticketPackages.firstQuantity, 'value');

View File

@ -31,7 +31,7 @@ describe('Entry lastest buys path', () => {
await page.waitForSelector(selectors.entryLatestBuys.fieldAutocomplete, {visible: true});
});
it('should search for the "Description" field and type a new description for the items in each selected buy', async() => {
it('should search for the "Description" and type a new one for the items in each selected buy', async() => {
await page.autocompleteSearch(selectors.entryLatestBuys.fieldAutocomplete, 'Description');
await page.write(selectors.entryLatestBuys.newValueInput, 'Crafted item');
await page.waitToClick(selectors.entryLatestBuys.acceptEditBuysDialog);

View File

@ -28,7 +28,7 @@ describe('Entry import, create and edit buys path', () => {
await page.waitForState('entry.card.buy.import');
});
it('should fill the form, import the designated JSON file and select items for each import and confirm import', async() => {
it('should fill the form, import the a JSON file and select items for each import and confirm import', async() => {
let currentDir = process.cwd();
let filePath = `${currentDir}/e2e/assets/07_import_buys.json`;
@ -42,7 +42,8 @@ describe('Entry import, create and edit buys path', () => {
await page.waitForTextInField(selectors.entryBuys.observation, '729-6340 2846');
await page.autocompleteSearch(selectors.entryBuys.firstImportedItem, 'Ranged Reinforced weapon pistol 9mm');
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, 'Melee Reinforced weapon heavy shield 1x0.5m');
const itemName = 'Melee Reinforced weapon heavy shield 1x0.5m';
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, itemName);
await page.autocompleteSearch(selectors.entryBuys.thirdImportedItem, 'Container medical box 1m');
await page.autocompleteSearch(selectors.entryBuys.fourthImportedItem, 'Container ammo box 1m');
@ -88,37 +89,37 @@ describe('Entry import, create and edit buys path', () => {
it('should edit the newest buy', async() => {
await page.clearInput(selectors.entryBuys.secondBuyPackingPrice);
await page.waitForTextInField(selectors.entryBuys.secondBuyPackingPrice, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPackingPrice, '100');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyGroupingPrice);
await page.waitForTextInField(selectors.entryBuys.secondBuyGroupingPrice, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyGroupingPrice, '200');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyPrice);
await page.waitForTextInField(selectors.entryBuys.secondBuyPrice, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPrice, '300');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyGrouping);
await page.waitForTextInField(selectors.entryBuys.secondBuyGrouping, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyGrouping, '400');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyPacking);
await page.waitForTextInField(selectors.entryBuys.secondBuyPacking, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPacking, '500');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyWeight);
await page.waitForTextInField(selectors.entryBuys.secondBuyWeight, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyWeight, '600');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyStickers);
await page.waitForTextInField(selectors.entryBuys.secondBuyStickers, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyStickers, '700');
await page.waitForSnackbar();
@ -126,7 +127,7 @@ describe('Entry import, create and edit buys path', () => {
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyQuantity);
await page.waitForTextInField(selectors.entryBuys.secondBuyQuantity, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyQuantity, '800');
});

View File

@ -49,7 +49,7 @@ export default class Contextmenu {
get rowIndex() {
if (!this.row) return null;
const table = this.row.closest('vn-table, .vn-table');
const table = this.row.closest('table, vn-table, .vn-table');
const rows = table.querySelectorAll('[ng-repeat]');
return Array.from(rows).findIndex(
@ -67,13 +67,13 @@ export default class Contextmenu {
get cell() {
if (!this.target) return null;
return this.target.closest('vn-td, .vn-td, vn-td-editable');
return this.target.closest('td, vn-td, .vn-td, vn-td-editable');
}
get cellIndex() {
if (!this.row) return null;
const cells = this.row.querySelectorAll('vn-td, .vn-td, vn-td-editable');
const cells = this.row.querySelectorAll('td, vn-td, .vn-td, vn-td-editable');
return Array.from(cells).findIndex(
cellItem => cellItem == this.cell
);
@ -82,8 +82,8 @@ export default class Contextmenu {
get rowHeader() {
if (!this.row) return null;
const table = this.row.closest('vn-table, .vn-table');
const headerCells = table && table.querySelectorAll('vn-thead vn-th');
const table = this.row.closest('table, vn-table, .vn-table');
const headerCells = table && table.querySelectorAll('thead th, vn-thead vn-th');
const headerCell = headerCells && headerCells[this.cellIndex];
return headerCell;
@ -147,7 +147,7 @@ export default class Contextmenu {
*/
isActionAllowed() {
if (!this.target) return false;
const isTableCell = this.target.closest('vn-td, .vn-td');
const isTableCell = this.target.closest('td, vn-td, .vn-td');
return isTableCell && this.fieldName;
}
@ -172,9 +172,28 @@ export default class Contextmenu {
excludeSelection() {
let where = {[this.fieldName]: {neq: this.fieldValue}};
if (this.exprBuilder) {
where = buildFilter(where, (param, value) =>
this.exprBuilder({param, value})
);
where = {[this.fieldName]: this.fieldValue};
where = buildFilter(where, (param, value) => {
const expr = this.exprBuilder({param, value});
const props = Object.keys(expr);
let newExpr = {};
for (let prop of props) {
if (expr[prop].like) {
const operator = expr[prop].like;
newExpr[prop] = {nlike: operator};
} else if (expr[prop].between) {
const operator = expr[prop].between;
newExpr = {
or: [
{[prop]: {lt: operator[0]}},
{[prop]: {gt: operator[1]}},
]
};
} else
newExpr[prop] = {neq: this.fieldValue};
}
return newExpr;
});
}
this.model.addFilter({where});
@ -208,15 +227,22 @@ export default class Contextmenu {
if (prop == findProp)
delete instance[prop];
if (prop === 'and') {
for (let [index, param] of instance[prop].entries()) {
if (prop === 'and' || prop === 'or') {
const instanceCopy = instance[prop].slice();
for (let param of instanceCopy) {
const [key] = Object.keys(param);
const index = instance[prop].findIndex(param => {
return Object.keys(param)[0] == key;
});
if (key == findProp)
instance[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, filterKey, key);
}
if (instance[prop].length == 0)
delete instance[prop];
}
}

View File

@ -222,7 +222,7 @@
}
}
&.focused {
background-color: $color-bg-panel;
background-color: $color-font-dark;
& > .container {
& > .infix > .control > * {

View File

@ -52,3 +52,4 @@ import './wday-picker';
import './datalist';
import './contextmenu';
import './rating';
import './smart-table';

View File

@ -145,9 +145,8 @@ export default class MultiCheck extends FormInput {
toggle() {
const data = this.model.data;
if (!data) return;
data.forEach(el => {
for (let el of data)
el[this.checkField] = this.checkAll;
});
}
}
@ -156,8 +155,9 @@ ngModule.vnComponent('vnMultiCheck', {
controller: MultiCheck,
bindings: {
model: '<',
checkField: '<?',
checkField: '@?',
checkAll: '=?',
checked: '=?',
disabled: '<?'
}
});

View File

@ -0,0 +1,102 @@
<div class="vn-pa-md">
<vn-horizontal class="actions">
<div class="actions-left">
<vn-button icon="view_column"
id="shownColumns"
ng-if="$ctrl.options.activeButtons.shownColumns"
ng-click="smartTableColumns.show($event)"
vn-tooltip="Shown columns">
</vn-button>
<div ng-transclude="actions"></div>
</div>
<div class="actions-right">
<div class="totalRows" ng-if="$ctrl.model.data">
{{model.data.length}}
<span translate>results</span>
</div>
<vn-button icon="search"
ng-if="$ctrl.options.activeButtons.search"
ng-click="$ctrl.displaySearch()"
vn-tooltip="Search">
</vn-button>
<div class="button-group"
ng-if="$ctrl.options.activeButtons.crud">
<vn-button icon="add"
ng-click="$ctrl.createRow()"
vn-tooltip="Add new row">
</vn-button>
<vn-button icon="undo"
ng-click="$ctrl.model.undoChanges()"
vn-tooltip="Undo">
</vn-button>
<vn-button icon="delete"
ng-click="deleteConfirmation.show($event)"
ng-show="$ctrl.checkedRows.length > 0"
vn-tooltip="Remove selected rows">
</vn-button>
<vn-button icon="save"
ng-click="$ctrl.saveAll()"
vn-tooltip="Save data">
</vn-button>
</div>
<vn-button icon="refresh"
ng-click="$ctrl.model.refresh()"
vn-tooltip="Refresh">
</vn-button>
</div>
</vn-horizontal>
<div id="table"></div>
<vn-pagination
ng-if="$ctrl.model"
model="$ctrl.model"
class="vn-pt-md">
</vn-pagination>
</div>
<vn-confirm
vn-id="deleteConfirmation"
on-accept="$ctrl.deleteAll()"
question="Are you sure you want to continue?"
message="Remove selected rows">
</vn-confirm>
<vn-crud-model
ng-if="$ctrl.viewConfigId"
vn-id="userViewModel"
url="UserConfigViews"
link="{tableCode: $ctrl.viewConfigId, userFk: $ctrl.currentUserId}"
data="$ctrl.viewConfig"
auto-load="true">
</vn-crud-model>
<vn-popover vn-id="smart-table-columns" message="Fields to show">
<tpl-body>
<div class="smart-table-columns vn-pa-md vn-w-sm">
<vn-horizontal>
<h6 translate style="margin:0">Shown columns</h6>
<vn-icon
vn-none
icon="info"
color-marginal
vn-tooltip="Check the columns you want to see"/>
</vn-horizontal>
<div class="vn-mb-md">
<vn-check label="Tick all"
ng-model="$ctrl.checkAll">
</vn-check>
</div>
<vn-horizontal class="vn-mb-md">
<vn-check ng-repeat="column in $ctrl.columns"
label="{{column.caption}}"
ng-model="$ctrl.viewConfig[0].configuration[column.field]">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-button
label="Save"
ng-click="$ctrl.saveViewConfig()">
</vn-button>
</vn-horizontal>
</div>
</tpl-body>
</vn-popover>

View File

@ -0,0 +1,454 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import {buildFilter} from 'vn-loopback/util/filter';
import angular from 'angular';
import {camelToKebab} from '../../lib/string';
import './style.scss';
import './table.scss';
export default class SmartTable extends Component {
constructor($element, $, $transclude) {
super($element, $);
this.currentUserId = window.localStorage.currentUserWorkerId;
this.$transclude = $transclude;
this.sortCriteria = [];
this.$inputsScope;
this.columns = [];
this.autoSave = false;
this.transclude();
}
$onDestroy() {
const styleElement = document.querySelector('style[id="smart-table"]');
if (this.$.css && styleElement)
styleElement.parentNode.removeChild(styleElement);
}
get options() {
return this._options;
}
set options(options) {
this._options = options;
if (!options) return;
const activeButtons = options.activeButtons;
const missingId = activeButtons && activeButtons.shownColumns && !this.viewConfigId;
if (missingId)
throw new Error('vnSmartTable: View identifier not defined');
}
get model() {
return this._model;
}
set model(value) {
this._model = value;
if (value)
this.$.model = value;
}
get viewConfigId() {
return this._viewConfigId;
}
set viewConfigId(value) {
this._viewConfigId = value;
/* if (value) {
this.defaultViewConfig = {};
const url = 'DefaultViewConfigs';
const filter = {where: {tableCode: value}};
this.$http.get(url, {filter})
.then(res => {
if (res && res.data.length) {
const columns = res.data[0].columns;
this.defaultViewConfig = columns;
}
});
} */
}
getDefaultViewConfig() {
const url = 'DefaultViewConfigs';
const filter = {where: {tableCode: this.viewConfigId}};
return this.$http.get(url, {filter})
.then(res => {
if (res && res.data.length)
return res.data[0].columns;
});
}
get viewConfig() {
return this._viewConfig;
}
set viewConfig(value) {
this._viewConfig = value;
if (!value) return;
if (!value.length) {
this.getDefaultViewConfig().then(columns => {
const defaultViewConfig = columns ? columns : {};
const userViewModel = this.$.userViewModel;
for (const column of this.columns) {
if (defaultViewConfig[column.field] == undefined)
defaultViewConfig[column.field] = true;
}
userViewModel.insert({
userFk: this.currentUserId,
tableConfig: this.viewConfigId,
configuration: defaultViewConfig
});
}).finally(() => this.applyViewConfig());
} else
this.applyViewConfig();
}
get checkedRows() {
const model = this.model;
if (model && model.data)
return model.data.filter(row => row.$checked);
return null;
}
get checkAll() {
return this._checkAll;
}
set checkAll(value) {
this._checkAll = value;
if (value !== undefined) {
const shownColumns = this.viewConfig[0].configuration;
for (let param in shownColumns)
shownColumns[param] = value;
}
}
transclude() {
const slotTable = this.element.querySelector('#table');
this.$transclude($clone => {
const table = $clone[0];
slotTable.appendChild(table);
this.registerColumns();
this.emptyDataRows();
}, null, 'table');
}
saveViewConfig() {
const userViewModel = this.$.userViewModel;
const [viewConfig] = userViewModel.data;
viewConfig.configuration = Object.assign({}, viewConfig.configuration);
userViewModel.save()
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => this.applyViewConfig())
.then(() => this.$.smartTableColumns.hide());
}
applyViewConfig() {
const userViewModel = this.$.userViewModel;
const [viewConfig] = userViewModel.data;
const selectors = [];
for (const column of this.columns) {
if (viewConfig.configuration[column.field] == false) {
const baseSelector = `smart-table[view-config-id="${this.viewConfigId}"] table`;
selectors.push(`${baseSelector} thead > tr > th:nth-child(${column.index + 1})`);
selectors.push(`${baseSelector} tbody > tr > td:nth-child(${column.index + 1})`);
}
}
let styleElement = document.querySelector('style[id="smart-table"]');
if (styleElement)
styleElement.parentNode.removeChild(styleElement);
if (selectors.length) {
const rule = selectors.join(', ') + '{display: none}';
this.$.css = document.createElement('style');
this.$.css.setAttribute('id', 'smart-table');
document.head.appendChild(this.$.css);
this.$.css.appendChild(document.createTextNode(rule));
}
}
registerColumns() {
const header = this.element.querySelector('thead > tr');
if (!header) return;
const columns = header.querySelectorAll('th');
// Click handler
for (const [index, column] of columns.entries()) {
const field = column.getAttribute('field');
if (field) {
const columnElement = angular.element(column);
const caption = columnElement.text().trim();
this.columns.push({field, caption, index});
column.addEventListener('click', () => this.orderHandler(column));
}
}
}
emptyDataRows() {
const header = this.element.querySelector('thead > tr');
const columns = header.querySelectorAll('th');
const tbody = this.element.querySelector('tbody');
if (tbody) {
const noSearch = this.$compile(`
<tr class="empty-rows" ng-if="!model.data">
<td colspan="${columns.length}" translate>Enter a new search</td>
</tr>
`)(this.$);
tbody.appendChild(noSearch[0]);
const noRows = this.$compile(`
<tr class="empty-rows" ng-if="model.data.length == 0">
<td colspan="${columns.length}" translate>No data</td>
</tr>
`)(this.$);
tbody.appendChild(noRows[0]);
}
}
orderHandler(element) {
const field = element.getAttribute('field');
const existingCriteria = this.sortCriteria.find(criteria => {
return criteria.field == field;
});
const isASC = existingCriteria && existingCriteria.sortType == 'ASC';
const isDESC = existingCriteria && existingCriteria.sortType == 'DESC';
if (!existingCriteria) {
this.sortCriteria.push({field: field, sortType: 'ASC'});
element.classList.remove('desc');
element.classList.add('asc');
}
if (isDESC) {
this.sortCriteria.splice(this.sortCriteria.findIndex(criteria => {
return criteria.field == field;
}), 1);
element.classList.remove('desc');
element.classList.remove('asc');
}
if (isASC) {
existingCriteria.sortType = 'DESC';
element.classList.remove('asc');
element.classList.add('desc');
}
this.applySort();
}
displaySearch() {
const header = this.element.querySelector('thead > tr');
if (!header) return;
const tbody = this.element.querySelector('tbody');
const columns = header.querySelectorAll('th');
const hasSearchRow = tbody.querySelector('tr#searchRow');
if (hasSearchRow) {
if (this.$inputsScope)
this.$inputsScope.$destroy();
return hasSearchRow.remove();
}
const searchRow = document.createElement('tr');
searchRow.setAttribute('id', 'searchRow');
this.$inputsScope = this.$.$new();
for (let column of columns) {
const field = column.getAttribute('field');
const cell = document.createElement('td');
if (field) {
let input;
let options;
const columnOptions = this.options && this.options.columns;
if (columnOptions)
options = columnOptions.find(column => column.field == field);
if (options && options.searchable == false) {
searchRow.appendChild(cell);
continue;
}
if (options && options.autocomplete) {
let props = ``;
const autocomplete = options.autocomplete;
for (const prop in autocomplete)
props += `${camelToKebab(prop)}="${autocomplete[prop]}"\n`;
input = this.$compile(`
<vn-autocomplete
class="dense"
name="${field}"
ng-model="searchProps['${field}']"
${props}
on-change="$ctrl.searchByColumn('${field}')"
clear-disabled="true"
/>`)(this.$inputsScope);
} else {
input = this.$compile(`
<vn-textfield
class="dense"
name="${field}"
ng-model="searchProps['${field}']"
ng-keydown="$ctrl.searchWithEvent($event, '${field}')"
clear-disabled="true"
/>`)(this.$inputsScope);
}
cell.appendChild(input[0]);
}
searchRow.appendChild(cell);
}
tbody.prepend(searchRow);
}
searchWithEvent($event, field) {
if ($event.key != 'Enter') return;
this.searchByColumn(field);
}
searchByColumn(field) {
const searchCriteria = this.$inputsScope.searchProps[field];
const emptySearch = searchCriteria == '' || null;
const filters = this.filterSanitizer(field);
if (filters && filters.userFilter)
this.model.userFilter = filters.userFilter;
if (!emptySearch)
this.addFilter(field, this.$inputsScope.searchProps[field]);
else this.model.refresh();
}
addFilter(field, value) {
let where = {[field]: value};
if (this.exprBuilder) {
where = buildFilter(where, (param, value) =>
this.exprBuilder({param, value})
);
}
this.model.addFilter({where});
}
applySort() {
let order = this.sortCriteria.map(criteria => `${criteria.field} ${criteria.sortType}`);
order = order.join(', ');
if (order)
this.model.order = order;
this.model.refresh();
}
filterSanitizer(field) {
const userFilter = this.model.userFilter;
const userParams = this.model.userParams;
const where = userFilter && userFilter.where;
if (this.exprBuilder) {
const param = this.exprBuilder({
param: field,
value: null
});
if (param) [field] = Object.keys(param);
}
if (!where) return;
const whereKeys = Object.keys(where);
for (let key of whereKeys) {
removeProp(where, field, key);
if (!Object.keys(where))
delete userFilter.where;
}
function removeProp(instance, findProp, prop) {
if (prop == findProp)
delete instance[prop];
if (prop === 'and') {
for (let [index, param] of instance[prop].entries()) {
const [key] = Object.keys(param);
if (key == findProp)
instance[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, field, key);
}
}
}
return {userFilter, userParams};
}
removeFilter() {
this.model.applyFilter(userFilter, userParams);
}
createRow() {
let data = {};
if (this.defaultNewData)
data = this.defaultNewData();
this.model.insert(data);
}
deleteAll() {
for (let row of this.checkedRows)
this.model.removeRow(row);
if (this.autoSave)
this.saveAll();
}
saveAll() {
const model = this.model;
if (!model.isChanged)
return this.vnApp.showError(this.$t('No changes to save'));
this.model.save()
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
}
SmartTable.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('smartTable', {
template: require('./index.html'),
controller: SmartTable,
transclude: {
table: '?slotTable',
actions: '?slotActions'
},
bindings: {
model: '<?',
viewConfigId: '@?',
autoSave: '<?',
exprBuilder: '&?',
defaultNewData: '&?',
options: '<?'
}
});

View File

@ -0,0 +1,9 @@
Remove selected rows: Eliminar líneas seleccionadas
Add new row: Añadir nueva fila
Undo: Deshacer
Save data: Guardar datos
Shown columns: Columnas visibles
Check the columns you want to see: Marca las columnas que quieres ver
Showing: Mostrando
results: resultados
Tick all: Marcar todas

View File

@ -0,0 +1,146 @@
@import "effects";
@import "variables";
smart-table {
th[field] {
overflow: visible;
cursor: pointer;
align-items: center;
}
th[field][number] {
& > :before {
vertical-align: middle;
font-family: 'Material Icons';
content: 'arrow_downward';
color: $color-spacer;
margin-right: 2px;
opacity: 0
}
&.asc > :before, &.desc > :before {
color: $color-font;
opacity: 1;
}
&.asc > :before {
content: 'arrow_upward';
}
&.desc > :before {
content: 'arrow_downward';
}
&:hover > :before {
opacity: 1;
}
}
th[field]:not([number]) {
& > :after {
vertical-align: middle;
font-family: 'Material Icons';
content: 'arrow_downward';
color: $color-spacer;
margin-left: 2px;
opacity: 0
}
&.asc > :after, &.desc > :after {
color: $color-font;
opacity: 1;
}
&.asc > :after {
content: 'arrow_upward';
}
&.desc > :after {
content: 'arrow_downward';
}
&:hover > :after {
opacity: 1;
}
}
tr[vn-anchor] {
@extend %clickable;
}
.totalRows {
color: $color-font-secondary;
}
.actions-left,
.actions-right {
display: flex;
align-items: center;
.button-group {
display: flex;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .3);
& > vn-button {
box-shadow: 0 0 0 0
}
}
}
.actions-left {
justify-content: flex-start;
slot-actions > vn-button,
& > vn-button,
.button-group {
margin-right: 10px
}
slot-actions {
display: flex
}
}
.actions-right {
justify-content: flex-end;
& > vn-button,
.button-group {
margin-left: 10px
}
}
#table {
overflow-x: auto;
margin-top: 15px
}
vn-tbody a[ng-repeat].vn-tr:focus {
background-color: $color-primary-light
}
.new-row {
background-color: $color-success-light
}
.changed-row {
background-color: $color-primary-light
}
}
.smart-table-columns {
h6 {
color: $color-font-secondary
}
& > vn-horizontal {
align-items: flex-start;
flex-wrap: wrap;
}
vn-check {
flex: initial;
width: 33%
}
}

View File

@ -0,0 +1,111 @@
@import "effects";
@import "variables";
smart-table table {
width: 100%;
border-collapse: collapse;
& > thead {
border-bottom: 2px solid $color-spacer;
& > * > th {
font-weight: normal;
}
}
& > tfoot {
border-top: 2px solid $color-spacer;
}
thead, tbody, tfoot {
& > * {
& > th {
color: $color-font-light;
}
& > th,
& > td {
overflow: hidden;
}
& > th,
& > td {
text-align: left;
padding: 5px;
white-space: nowrap;
text-overflow: ellipsis;
&[number] {
text-align: right;
}
&[centered] {
text-align: center;
}
&[shrink] {
width: 1px;
text-align: center;
}
&[shrink-date] {
width: 100px;
max-width: 100px;
}
&[shrink-datetime] {
width: 150px;
max-width: 150px;
}
&[expand] {
max-width: 400px;
min-width: 0;
}
&[actions] {
width: 1px;
& > * {
vertical-align: middle;
}
}
vn-icon.bright, i.bright {
color: #f7931e;
}
}
}
}
tbody > * {
border-bottom: 1px solid $color-spacer-light;
&:last-child {
border-bottom: none;
}
& > td {
.chip {
padding: 4px;
border-radius: 4px;
&.notice {
background-color: $color-notice-medium;
color: $color-font-bg;
}
&.success {
background-color: $color-success-medium;
color: $color-font-bg;
}
&.warning {
background-color: $color-main-medium;
color: $color-font-bg;
}
&.alert {
background-color: $color-alert-medium;
color: $color-font-bg;
}
&.message {
background-color: $color-bg-dark;
color: $color-font-dark;
}
}
}
}
.vn-check {
margin: 0;
}
.empty-rows > td {
color: $color-font-secondary;
font-size: 1.375rem;
text-align: center;
}
}

View File

@ -132,23 +132,26 @@ vn-table {
.chip {
padding: 4px;
border-radius: 4px;
color: $color-font-bg;
&.notice {
background-color: $color-notice-medium
background-color: $color-notice-medium;
color: $color-font-bg;
}
&.success {
background-color: $color-success-medium;
color: $color-font-bg;
}
&.warning {
background-color: $color-main-medium;
color: $color-font-bg;
}
&.alert {
background-color: $color-alert-medium;
color: $color-font-bg;
}
&.message {
background-color: $color-bg-dark;
color: $color-font-dark;
background-color: $color-bg-dark
}
}
vn-icon-menu {
@ -195,7 +198,7 @@ vn-table.scrollable > .vn-table,
thead vn-th,
thead th {
border-bottom: 2px solid $color-spacer;
background-color: #FFF;
background-color: $color-bg-panel;
position: sticky;
z-index: 9;
top: 0

View File

@ -16,3 +16,4 @@ import './droppable';
import './http-click';
import './http-submit';
import './anchor';

View File

@ -23,30 +23,30 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-basketadd:before {
content: "\e901";
.icon-preserved:before {
content: "\e963";
}
.icon-addperson:before {
content: "\e955";
.icon-treatments:before {
content: "\e964";
}
.icon-supplierfalse:before {
.icon-funeral:before {
content: "\e965";
}
.icon-handmadeArtificial:before {
content: "\e966";
}
.icon-fixedPrice:before {
content: "\e962";
}
.icon-invoice-in-create:before {
content: "\e948";
.icon-accounts:before {
content: "\e95f";
}
.icon-invoiceOut:before {
.icon-clientConsumption:before {
content: "\e960";
}
.icon-invoiceIn:before {
.icon-lastBuy:before {
content: "\e961";
}
.icon-supplier:before {
content: "\e936";
}
.icon-latestBuy:before {
content: "\e95f";
}
.icon-zone:before {
content: "\e95d";
}
@ -86,6 +86,9 @@
.icon-deliveryprices:before {
content: "\e956";
}
.icon-basketadd:before {
content: "\e955";
}
.icon-catalog:before {
content: "\e952";
}
@ -128,9 +131,15 @@
.icon-actions:before {
content: "\e900";
}
.icon-addperson:before {
content: "\e901";
}
.icon-albaran:before {
content: "\e902";
}
.icon-apps:before {
content: "\e948";
}
.icon-artificial:before {
content: "\e903";
}
@ -239,6 +248,9 @@
.icon-mandatory:before {
content: "\e921";
}
.icon-niche:before {
content: "\e922";
}
.icon-no036:before {
content: "\e923";
}
@ -302,6 +314,9 @@
.icon-stowaway:before {
content: "\e92c";
}
.icon-supplier:before {
content: "\e936";
}
.icon-tags:before {
content: "\e937";
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,6 +1,6 @@
@import "./util";
$font-size: 12pt;
$font-size: 11pt;
$menu-width: 256px;
$topbar-height: 56px;
$mobile-width: 800px;
@ -24,7 +24,7 @@ $spacing-xl: 70px;
// Light theme
$color-primary: #f7931e;
/* $color-primary: #f7931e;
$color-secondary: $color-primary;
$color-font: #222;
@ -72,28 +72,36 @@ $color-success-light: lighten($color-success, 35%);
$color-notice-medium: lighten($color-notice, 20%);
$color-notice-light: lighten($color-notice, 35%);
$color-alert-medium: lighten($color-alert, 20%);
$color-alert-light: lighten($color-alert, 35%);
$color-alert-light: lighten($color-alert, 35%); */
/**/
// Dark theme
/*
$color-header: #3d3d3d;
$color-bg: #222;
$color-bg-dark: #222;
$color-primary: #f7931e;
$color-secondary: $color-primary;
$color-font: #eee;
$color-font-light: #aaa;
$color-font-secondary: #777;
$color-font-dark: white;
$color-font-link: #66bfff;
$color-font-bg: rgba(0, 0, 0, .8);
$color-font-link: #005a9a;
$color-font-bg-marginal: rgba(0, 0, 0, .4);
$color-font-bg-dark: rgba(255, 255, 255, .7);
$color-font-bg-dark-marginal: rgba(255, 255, 255, .4);
$color-header: #3d3d3d;
$color-menu-header: #3d3d3d;
$color-bg: #222;
$color-bg-dark: #222;
$color-active: #666;
$color-active-font: white;
$color-bg-panel: #3c3b3b;
$color-main: #f7931e;
$color-marginal: #ccc;
$color-main: $color-primary;
$color-marginal: #111;
$color-success: #a3d131;
$color-notice: #32b1ce;
$color-alert: #f42121;
$color-alert: #fa3939;
$color-button: $color-secondary;
$color-spacer: rgba(255, 255, 255, .3);
$color-spacer-light: rgba(255, 255, 255, .12);
@ -105,15 +113,18 @@ $color-hover-cd: rgba(255, 255, 255, .1);
$color-hover-dc: .7;
$color-disabled: .6;
$color-font-link: lighten($color-main, 10%);
$color-main-medium: darken($color-main, 20%);
$color-main-light: darken($color-main, 35%);
$color-success-medium: darken($color-success, 20%);
$color-success-light: darken($color-success, 35%);
$color-notice-medium: darken($color-notice, 20%);
$color-notice-light: darken($color-notice, 35%);
$color-alert-medium: darken($color-alert, 20%);
$color-alert-light: darken($color-alert, 35%);
$color-primary-medium: lighten($color-primary, 20%);
$color-primary-light: lighten($color-primary, 35%);
$color-font-link-medium: lighten($color-font-link, 20%);
$color-font-link-light: lighten($color-font-link, 35%);
$color-main-medium: lighten($color-main, 20%);
$color-main-light: lighten($color-main, 35%);
$color-success-medium: lighten($color-success, 20%);
$color-success-light: lighten($color-success, 35%);
$color-notice-medium: lighten($color-notice, 20%);
$color-notice-light: lighten($color-notice, 35%);
$color-alert-medium: lighten($color-alert, 20%);
$color-alert-light: lighten($color-alert, 35%);
/**/
// Border

View File

@ -1,5 +1,11 @@
<vn-crud-model vn-id="model" url="{{$ctrl.url}}" filter="$ctrl.filter" link="{originFk: $ctrl.originId}"
data="$ctrl.logs" limit="20" auto-load="true">
<vn-crud-model
vn-id="model"
url="{{$ctrl.url}}"
filter="$ctrl.filter"
link="{originFk: $ctrl.originId}"
data="$ctrl.logs"
limit="20"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model" class="vn-w-xl">
<vn-card>

View File

@ -68,7 +68,6 @@ ui-view > .vn-summary {
padding: 7px;
padding-bottom: 4px; /* Bottom line-height fix */
font-weight: lighter;
background-color: $color-main-light;
border-bottom: 1px solid $color-main;
white-space: nowrap;
overflow: hidden;
@ -78,7 +77,7 @@ ui-view > .vn-summary {
display: block;
}
a {
color: $color-font;
color: $color-font-link;
}
}
h4 span:after {
@ -87,7 +86,7 @@ ui-view > .vn-summary {
position: absolute;
right: 5px;
text-transform: none;
color: $color-spacer
color: $color-font-link;
}
& > * {
margin: $spacing-sm;

View File

@ -128,7 +128,8 @@ async function launchBackTest(done) {
if (err)
throw err;
}
launchBackTest.description = `Runs the backend tests once using a random container, can receive --ci arg to save reports on a xml file`;
launchBackTest.description = `
Runs the backend tests once using a random container, can receive --ci arg to save reports on a xml file`;
// Backend tests

View File

@ -63,7 +63,7 @@ module.exports = Self => {
};
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -20,7 +20,7 @@ module.exports = Self => {
Self.removeFile = async(ctx, id, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -22,7 +22,7 @@ module.exports = Self => {
let userId = ctx.req.accessToken.userId;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -24,7 +24,7 @@ module.exports = Self => {
const resolvedState = 3;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -44,7 +44,7 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId;
const args = ctx.args;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -30,7 +30,7 @@ module.exports = Self => {
Self.updateClaimAction = async(ctx, id, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -54,7 +54,7 @@ module.exports = Self => {
Self.uploadFile = async(ctx, id, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -25,7 +25,7 @@ module.exports = function(Self) {
Self.canBeInvoiced = async(id, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -135,8 +135,7 @@
"type": "string",
"mysql": {
"columnName": "businessTypeFk"
},
"required": true
}
}
},
"relations": {

View File

@ -68,7 +68,7 @@ module.exports = Self => {
Self.addBuy = async(ctx, options) => {
const conn = Self.dataSource.connector;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -21,7 +21,7 @@ module.exports = Self => {
Self.deleteBuys = async(ctx, options) => {
const models = Self.app.models;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -32,7 +32,7 @@ module.exports = Self => {
Self.editLatestBuys = async(field, newValue, lines, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -108,7 +108,7 @@ module.exports = Self => {
});
Self.filter = async(ctx, filter, options) => {
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -29,7 +29,7 @@ module.exports = Self => {
Self.getBuys = async(id, filter, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -21,7 +21,7 @@ module.exports = Self => {
Self.getEntry = async(id, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -26,7 +26,7 @@ module.exports = Self => {
Self.importBuysPreview = async(id, buys, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -76,7 +76,7 @@ module.exports = Self => {
});
Self.latestBuysFilter = async(ctx, filter, options) => {
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -63,8 +63,21 @@ describe('Entry', () => {
}
]}`;
const expectedBuys = [
{'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
{'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
{
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
];
controller.fillData(rawData);
controller.$.$apply();
@ -81,8 +94,21 @@ describe('Entry', () => {
describe('fetchBuys()', () => {
it(`should perform a query to fetch the buys data`, () => {
const buys = [
{'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
{'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
{
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
];
const serializedParams = $httpParamSerializer({buys});
@ -105,17 +131,31 @@ describe('Entry', () => {
observation: '123456',
ref: '1, 2',
buys: [
{'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
{'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
{
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
]
};
controller.onSubmit();
expect(controller.vnApp.showError).toHaveBeenCalledWith(`Some of the imported buys doesn't have an item`);
const message = `Some of the imported buys doesn't have an item`;
expect(controller.vnApp.showError).toHaveBeenCalledWith(message);
});
it(`should perform a query to update columns`, () => {
it(`should now perform a query to update columns`, () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$state.go = jest.fn();
@ -123,8 +163,22 @@ describe('Entry', () => {
observation: '123456',
ref: '1, 2',
buys: [
{'itemFk': 10, 'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200},
{'itemFk': 11, 'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125}
{
'itemFk': 10,
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'itemFk': 11,
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
]
};
const params = controller.import;

View File

@ -188,22 +188,19 @@
<tr><td></td></tr>
</tbody>
</table>
<div>
<vn-icon-button
vn-one
vn-tooltip="Add buy"
vn-bind="+"
icon="add_circle"
ng-click="model.insert({})">
</vn-icon-button>
</div>
</div>
</vn-card>
</vn-data-viewer>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<a ui-sref="entry.card.buy.import"
<vn-button class="round md vn-mb-sm"
ng-click="model.insert({})"
icon="add"
vn-tooltip="Add buy"
tooltip-position="left"
vn-bind="+">
</vn-button>
<a ui-sref="entry.card.buy.import" >
<vn-button class="round md vn-mb-sm"
icon="publish"
vn-tooltip="Import buys"

View File

@ -15,16 +15,16 @@ vn-entry-buy-index vn-card {
tbody tr:nth-child(1),
tbody tr:nth-child(2) {
border-left: 1px solid $color-marginal;
border-right: 1px solid $color-marginal;
border-left: 1px solid $color-spacer;
border-right: 1px solid $color-spacer;
}
tbody tr:nth-child(2) {
border-bottom: 1px solid $color-marginal;
border-bottom: 1px solid $color-spacer;
}
tbody{
border-bottom: 1px solid $color-marginal;
border-bottom: 1px solid $color-spacer;
}
tbody:last-child {

View File

@ -16,146 +16,203 @@
auto-state="false">
</vn-searchbar>
</vn-portal>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl">
<vn-card>
<vn-table
<vn-card>
<smart-table
model="model"
show-fields="$ctrl.showFields"
vn-smart-table="latestBuys">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th field="Image">Picture</vn-th>
<vn-th smart-table-ignore field="id">Id</vn-th>
<vn-th field="packing">Packing</vn-th>
<vn-th field="grouping">Grouping</vn-th>
<vn-th field="quantity">Quantity</vn-th>
<vn-th field="description" style="text-align: center">Description</vn-th>
<vn-th field="size">Size</vn-th>
<vn-th field="name" style="text-align: center">Tags</vn-th>
<vn-th field="code">Type</vn-th>
<vn-th field="intrastat">Intrastat</vn-th>
<vn-th field="origin">Origin</vn-th>
<vn-th field="density">Density</vn-th>
<vn-th field="isActive">Active</vn-th>
<vn-th field="family">Family</vn-th>
<vn-th field="entryFk">Entry</vn-th>
<vn-th field="buyingValue">Buying value</vn-th>
<vn-th field="freightValue">Freight value</vn-th>
<vn-th field="comissionValue" expand>Commission value</vn-th>
<vn-th field="packageValue" expand>Package value</vn-th>
<vn-th field="isIgnored">Is ignored</vn-th>
<vn-th expand field="price2">Grouping price</vn-th>
<vn-th expand field="price3">Packing price</vn-th>
<vn-th field="minPrice">Min price</vn-th>
<vn-th field="ektFk">Ekt</vn-th>
<vn-th field="weight">Weight</vn-th>
<vn-th field="packageFk" expand>PackageName</vn-th>
<vn-th field="packingOut" expand>PackingOut</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="buy in $ctrl.buys"
class="clickable vn-tr search-result"
ui-sref="entry.card.buy.index({id: {{::buy.entryFk}}})">
<vn-td shrink>
<vn-check
ng-model="buy.checked"
vn-click-stop>
</vn-check>
</vn-td>
<vn-td shrink >
<img
ng-src="{{::$root.imagePath('catalog', '50x50', buy.itemFk)}}"
zoom-image="{{::$root.imagePath('catalog', '1600x900', buy.itemFk)}}"
vn-click-stop
on-error-src/>
</vn-td>
<vn-td shrink>
<span
vn-click-stop="itemDescriptor.show($event, buy.itemFk)"
class="link">
{{::buy.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': buy.groupingMode == 2}">
<span translate>{{::buy.packing | dashIfEmpty}}</span>
</vn-chip>
</vn-td>
<vn-td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 1 ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': buy.groupingMode == 1}">
<span translate>{{::buy.grouping | dashIfEmpty}}</span>
</vn-chip>
</vn-td>
<vn-td number>{{::buy.quantity}}</vn-td>
<vn-td vn-two title="{{::buy.description}}">
{{::buy.description | dashIfEmpty}}
</vn-td>
<vn-td number>{{::buy.size}}</vn-td>
<vn-td vn-fetched-tags>
<div>
<vn-one title="{{::buy.name}}">{{::buy.name}}</vn-one>
<vn-one ng-if="::buy.subName">
<h3 title="{{::buy.subName}}">{{::buy.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="::buy"
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td shrink title="{{::buy.type}}">
{{::buy.code}}
</vn-td>
<vn-td shrink title="{{::item.intrastat}}">
{{::buy.intrastat}}
</vn-td>
<vn-td shrink>{{::buy.origin}}</vn-td>
<vn-td shrink>{{::buy.density}}</vn-td>
<vn-td shrink>
<vn-check
disabled="true"
ng-model="::buy.isActive">
</vn-check>
</vn-td>
<vn-td shrink>{{::buy.family}}</vn-td>
<vn-td shrink>
<span
vn-click-stop="entryDescriptor.show($event, buy.entryFk)"
class="link">
{{::buy.entryFk}}
</span>
</vn-td>
<vn-td number>{{::buy.buyingValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.freightValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.comissionValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.packageValue | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-check
disabled="true"
ng-model="::buy.isIgnored">
</vn-check>
</vn-td>
<vn-td number>{{::buy.price2 | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.price3 | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.minPrice | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.ektFk | dashIfEmpty}}</vn-td>
<vn-td number>{{::buy.weight}}</vn-td>
<vn-td number>{{::buy.packageFk}}</vn-td>
<vn-td number>{{::buy.packingOut}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
view-config-id="latestBuys"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-table>
<table>
<thead>
<tr>
<th shrink>
<vn-multi-check
checked="$ctrl.checkAll"
model="model"
check-field="$checked">
</vn-multi-check>
</th>
<th translate>Picture</th>
<th field="id">
<span translate>Identifier</span>
</th>
<th field="packing" number>
<span translate>Packing</span>
</th>
<th field="grouping" number>
<span translate>Grouping</span>
</th>
<th field="quantity" number>
<span translate>Quantity</span>
</th>
<th field="description">
<span translate>Description</span>
</th>
<th field="size">
<span translate>Size</span>
</th>
<th field="name">
<span translate>Tags</span>
</th>
<th field="code">
<span translate>Type</span>
</th>
<th field="intrastat">
<span translate>Intrastat</span>
</th>
<th field="origin">
<span translate>Origin</span>
</th>
<th field="density">
<span translate>Density</span>
</th>
<th field="isActive">
<span translate>Active</span>
</th>
<th field="family">
<span translate>Family</span>
</th>
<th field="entryFk">
<span translate>Entry</span>
</th>
<th field="buyingValue" number>
<span translate>Buying value</span>
</th>
<th field="freightValue" number>
<span translate>Freight value</span>
</th>
<th field="comissionValue" number>
<span translate>Commission value</span>
</th>
<th field="packageValue" number>
<span translate>Package value</span>
</th>
<th field="isIgnored">
<span translate>Is ignored</span>
</th>
<th field="price2" number>
<span translate>Grouping</span>
</th>
<th field="price3" number>
<span translate>Packing</span>
</th>
<th field="minPrice" number>
<span translate>Min</span>
</th>
<th field="ektFk">
<span translate>Ekt</span>
</th>
<th field="weight">
<span translate>Weight</span>
</th>
<th field="packageFk">
<span translate>Package</span>
</th>
<th field="packingOut">
<span translate>Package out</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="buy in $ctrl.buys"
vn-anchor="::{
state: 'entry.card.buy.index',
params: {id: {{::buy.entryFk}}}
}">
<td>
<vn-check
ng-model="buy.$checked"
vn-click-stop>
</vn-check>
</td>
<td >
<img
ng-src="{{::$root.imagePath('catalog', '50x50', buy.itemFk)}}"
zoom-image="{{::$root.imagePath('catalog', '1600x900', buy.itemFk)}}"
vn-click-stop
on-error-src/>
</td>
<td>
<span
vn-click-stop="itemDescriptor.show($event, buy.itemFk)"
class="link">
{{::buy.itemFk}}
</span>
</td>
<td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': buy.groupingMode == 2}">
<span translate>{{::buy.packing | dashIfEmpty}}</span>
</vn-chip>
</td>
<td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 1 ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': buy.groupingMode == 1}">
<span translate>{{::buy.grouping | dashIfEmpty}}</span>
</vn-chip>
</td>
<td number>{{::buy.quantity}}</td>
<td vn-two title="{{::buy.description}}">
{{::buy.description | dashIfEmpty}}
</td>
<td number>{{::buy.size}}</td>
<td vn-fetched-tags>
<div>
<vn-one title="{{::buy.name}}">{{::buy.name}}</vn-one>
<vn-one ng-if="::buy.subName">
<h3 title="{{::buy.subName}}">{{::buy.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="::buy"
tabindex="-1">
</vn-fetched-tags>
</td>
<td title="{{::buy.type}}">
{{::buy.code}}
</td>
<td title="{{::item.intrastat}}">
{{::buy.intrastat}}
</td>
<td>{{::buy.origin}}</td>
<td>{{::buy.density}}</td>
<td>
<vn-check
disabled="true"
ng-model="::buy.isActive">
</vn-check>
</td>
<td>{{::buy.family}}</td>
<td>
<span
vn-click-stop="entryDescriptor.show($event, buy.entryFk)"
class="link">
{{::buy.entryFk}}
</span>
</td>
<td number>{{::buy.buyingValue | currency: 'EUR':2}}</td>
<td number>{{::buy.freightValue | currency: 'EUR':2}}</td>
<td number>{{::buy.comissionValue | currency: 'EUR':2}}</td>
<td number>{{::buy.packageValue | currency: 'EUR':2}}</td>
<td>
<vn-check
disabled="true"
ng-model="::buy.isIgnored">
</vn-check>
</td>
<td number>{{::buy.price2 | currency: 'EUR':2}}</td>
<td number>{{::buy.price3 | currency: 'EUR':2}}</td>
<td number>{{::buy.minPrice | currency: 'EUR':2}}</td>
<td>{{::buy.ektFk | dashIfEmpty}}</td>
<td>{{::buy.weight}}</td>
<td>{{::buy.packageFk}}</td>
<td>{{::buy.packingOut}}</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<vn-button class="round sm vn-mb-sm"

View File

@ -5,11 +5,64 @@ import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.showFields = {
id: false,
actions: false
};
this.editedColumn;
this.$checkAll = false;
this.smartTableOptions = {
activeButtons: {
search: true,
shownColumns: true,
},
columns: [
{
field: 'code',
autocomplete: {
url: 'ItemTypes',
showField: 'code',
valueField: 'code',
}
},
{
field: 'origin',
autocomplete: {
url: 'Origins',
showField: 'code',
valueField: 'code'
}
},
{
field: 'family',
autocomplete: {
url: 'ItemFamilies',
valueField: 'code',
showField: 'code'
}
},
{
field: 'intrastat',
autocomplete: {
url: 'Intrastats',
showField: 'description',
valueField: 'description'
}
},
{
field: 'packageFk',
autocomplete: {
url: 'Packagings',
showField: 'id'
}
},
{
field: 'isActive',
searchable: false
},
{
field: 'isIgnored',
searchable: false
},
]
};
}
get columns() {
@ -33,21 +86,54 @@ export default class Controller extends Section {
const buys = this.$.model.data || [];
const checkedBuys = [];
for (let buy of buys) {
if (buy.checked)
if (buy.$checked)
checkedBuys.push(buy);
}
return checkedBuys;
}
uncheck() {
const lines = this.checked;
for (let line of lines) {
if (line.checked)
line.checked = false;
exprBuilder(param, value) {
switch (param) {
case 'id':
case 'size':
case 'density':
case 'isActive':
case 'family':
case 'minPrice':
case 'packingOut':
return {[`i.${param}`]: value};
case 'name':
case 'description':
return {[`i.${param}`]: {like: `%${value}%`}};
case 'code':
return {'it.code': value};
case 'intrastat':
return {'intr.description': value};
case 'origin':
return {'ori.code': value};
case 'packing':
case 'grouping':
case 'quantity':
case 'entryFk':
case 'buyingValue':
case 'freightValue':
case 'comissionValue':
case 'packageValue':
case 'isIgnored':
case 'price2':
case 'price3':
case 'ektFk':
case 'weight':
case 'packageFk':
return {[`b.${param}`]: value};
}
}
uncheck() {
this.checkAll = false;
}
get totalChecked() {
return this.checked.length;
}
@ -57,7 +143,7 @@ export default class Controller extends Section {
for (let row of this.checked)
rowsToEdit.push({id: row.id, itemFk: row.itemFk});
let data = {
const data = {
field: this.editedColumn.field,
newValue: this.editedColumn.newValue,
lines: rowsToEdit

View File

@ -7,9 +7,9 @@ describe('Entry', () => {
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, $compile, $rootScope, _$httpBackend_) => {
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
let $element = $compile('<vn-entry-latest-buys></vn-entry-latest-buys')($rootScope);
const $element = angular.element('<vn-entry-latest-buys></vn-entry-latest-buys');
controller = $componentController('vnEntryLatestBuys', {$element});
controller.$ = {
model: {refresh: () => {}},
@ -31,10 +31,10 @@ describe('Entry', () => {
describe('get checked', () => {
it(`should return a set of checked lines`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
{$checked: true, id: 1},
{$checked: true, id: 2},
{$checked: true, id: 3},
{$checked: false, id: 4},
];
let result = controller.checked;
@ -43,38 +43,10 @@ describe('Entry', () => {
});
});
describe('uncheck()', () => {
it(`should clear the selection of lines on the controller`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
];
let result = controller.checked;
expect(result.length).toEqual(3);
controller.uncheck();
result = controller.checked;
expect(result.length).toEqual(0);
});
});
describe('onEditAccept()', () => {
it(`should perform a query to update columns`, () => {
$httpBackend.whenGET('UserConfigViews/getConfig?tableCode=latestBuys').respond([]);
$httpBackend.whenGET('Buys/latestBuysFilter?filter=%7B%22limit%22:20%7D').respond([
{entryFk: 1},
{entryFk: 2},
{entryFk: 3},
{entryFk: 4}
]);
controller.editedColumn = {field: 'my field', newValue: 'the new value'};
let query = 'Buys/editLatestBuys';
const query = 'Buys/editLatestBuys';
$httpBackend.expectPOST(query).respond();
controller.onEditAccept();

View File

@ -14,4 +14,4 @@ Field to edit: Campo a editar
PackageName: Cubo
Edit: Editar
buy(s): compra(s)
PackingOut: Packing envíos
Package out: Embalaje envíos

View File

@ -2,7 +2,8 @@
vn-id="model"
url="Entries/filter"
limit="20"
auto-load="true">
auto-load="true"
order="landed DESC, id DESC">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar

View File

@ -7,7 +7,7 @@
"menus": {
"main": [
{"state": "entry.index", "icon": "icon-entry"},
{"state": "entry.latestBuys", "icon": "contact_support"}
{"state": "entry.latestBuys", "icon": "icon-lastBuy"}
],
"card": [
{"state": "entry.card.basicData", "icon": "settings"},

View File

@ -9,13 +9,13 @@ vn-entry-summary .summary {
}
tbody tr:nth-child(1) {
border-top: 1px solid $color-marginal;
border-top: 1px solid $color-spacer;
}
tbody tr:nth-child(1),
tbody tr:nth-child(2) {
border-left: 1px solid $color-marginal;
border-right: 1px solid $color-marginal
border-left: 1px solid $color-spacer;
border-right: 1px solid $color-spacer
}
tbody tr:nth-child(3) {

View File

@ -78,7 +78,7 @@ module.exports = Self => {
{
arg: 'isBooked',
type: 'boolean',
description: 'Whether the the invoice is booked or not',
description: 'Whether the invoice is booked or not',
},
],
returns: {
@ -95,7 +95,7 @@ module.exports = Self => {
const conn = Self.dataSource.connector;
const args = ctx.args;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -20,7 +20,7 @@ module.exports = Self => {
});
Self.summary = async(id, options) => {
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -23,7 +23,7 @@ module.exports = Self => {
Self.book = async(ref, options) => {
const models = Self.app.models;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -35,7 +35,7 @@ module.exports = Self => {
throw new UserError(`Action not allowed on the test environment`);
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -35,7 +35,7 @@ module.exports = Self => {
{
arg: 'hasPdf',
type: 'boolean',
description: 'Whether the the invoiceOut has PDF or not',
description: 'Whether the invoiceOut has PDF or not',
http: {source: 'query'}
},
{
@ -87,7 +87,7 @@ module.exports = Self => {
Self.filter = async(ctx, filter, options) => {
const conn = Self.dataSource.connector;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -29,7 +29,7 @@ module.exports = Self => {
Self.getTickets = async(id, filter, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -21,7 +21,7 @@ module.exports = Self => {
Self.summary = async(id, options) => {
let summary = {};
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -0,0 +1,44 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('activeBuyers', {
description: 'Returns a list of buyers for the given item type',
accepts: [{
arg: 'filter',
type: 'object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/activeBuyers`,
verb: 'GET'
}
});
Self.activeBuyers = async(filter, options) => {
const conn = Self.dataSource.connector;
const where = {isActive: true};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
filter = mergeFilters(filter, {where});
let stmt = new ParameterizedSQL(
`SELECT DISTINCT w.id workerFk, w.firstName, w.lastName, u.name, u.nickname
FROM worker w
JOIN itemType it ON it.workerFk = w.id
JOIN account.user u ON u.id = w.id
JOIN item i ON i.typeFk = it.id`,
null, myOptions);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -41,10 +41,10 @@ module.exports = Self => {
{
arg: 'isActive',
type: 'boolean',
description: 'Whether the the item is or not active',
description: 'Whether the item is or not active',
},
{
arg: 'salesPersonFk',
arg: 'buyerFk',
type: 'integer',
description: 'The buyer of the item',
},
@ -62,6 +62,11 @@ module.exports = Self => {
arg: 'landed',
type: 'date',
description: 'The item last buy landed date',
},
{
arg: 'isFloramondo',
type: 'boolean',
description: 'Whether the the item is or not floramondo',
}
],
returns: {
@ -104,16 +109,16 @@ module.exports = Self => {
? {or: [{'i.id': value}, codeWhere]}
: {or: [{'i.name': {like: `%${value}%`}}, codeWhere]};
case 'id':
return {'i.id': value};
case 'isActive':
return {'i.isActive': value};
case 'typeFk':
case 'isFloramondo':
param = `i.${param}`;
return {[param]: value};
case 'multiplier':
return {'i.stemMultiplier': value};
case 'typeFk':
return {'i.typeFk': value};
case 'categoryFk':
return {'ic.id': value};
case 'salesPersonFk':
case 'buyerFk':
return {'it.workerFk': value};
case 'origin':
return {'ori.code': value};
@ -146,6 +151,7 @@ module.exports = Self => {
i.density,
i.stemMultiplier,
i.typeFk,
i.isFloramondo,
it.name AS typeName,
it.workerFk AS buyerFk,
u.name AS userName,

View File

@ -0,0 +1,24 @@
const models = require('vn-loopback/server/server').models;
describe('Worker activeBuyers', () => {
it('should return the buyers in itemType as result', async() => {
const tx = await models.Item.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {};
const result = await models.Item.activeBuyers(filter, options);
const firstWorker = result[0];
const secondWorker = result[1];
expect(result.length).toEqual(2);
expect(firstWorker.nickname).toEqual('logisticBossNick');
expect(secondWorker.nickname).toEqual('buyerNick');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -60,4 +60,23 @@ describe('item filter()', () => {
throw e;
}
});
it('should return 2 result filtering by isFloramondo checkbox', async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};
try {
const filter = {};
const ctx = {args: {filter: filter, isFloramondo: true}};
const result = await models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(2);
expect(result[0].id).toEqual(13);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -23,6 +23,9 @@
"ItemCategory": {
"dataSource": "vn"
},
"ItemFamily": {
"dataSource": "vn"
},
"ItemLog": {
"dataSource": "vn"
},

View File

@ -8,12 +8,12 @@
},
"properties": {
"id": {
"type": "Number",
"type": "number",
"id": true,
"description": "Identifier"
},
"description": {
"type": "String"
"type": "string"
}
},
"relations": {

View File

@ -8,18 +8,18 @@
},
"properties": {
"id": {
"type": "Number",
"type": "number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String"
"type": "string"
},
"display": {
"type": "Boolean"
"type": "boolean"
},
"icon": {
"type": "String"
"type": "string"
}
},
"relations": {

View File

@ -0,0 +1,27 @@
{
"name": "ItemFamily",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemFamily"
}
},
"properties": {
"code": {
"type": "string",
"id": true,
"description": "Identifier"
},
"description": {
"type": "string"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -8,21 +8,21 @@
},
"properties": {
"id": {
"type": "Number",
"type": "number",
"id": true,
"description": "Identifier"
},
"code": {
"type": "String"
"type": "string"
},
"name": {
"type": "String"
"type": "string"
},
"life": {
"type": "Number"
"type": "number"
},
"isPackaging": {
"type": "Boolean"
"type": "boolean"
}
},
"relations": {

View File

@ -14,6 +14,7 @@ module.exports = Self => {
require('../methods/item/getWasteByWorker')(Self);
require('../methods/item/getWasteByItem')(Self);
require('../methods/item/createIntrastat')(Self);
require('../methods/item/activeBuyers')(Self);
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});

View File

@ -140,6 +140,9 @@
},
"isFragile": {
"type": "boolean"
},
"isFloramondo": {
"type": "boolean"
}
},
"relations": {

View File

@ -8,15 +8,15 @@
},
"properties": {
"id": {
"type": "Number",
"type": "number",
"id": true,
"description": "Identifier"
},
"code": {
"type": "String"
"type": "string"
},
"name": {
"type": "String"
"type": "string"
}
},
"acls": [

View File

@ -44,7 +44,7 @@
<vn-autocomplete
vn-one
ng-model="filter.buyerFk"
url="Workers/activeWithRolee"
url="Workers/activeWithRole"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"

View File

@ -116,7 +116,8 @@
class="dense"
vn-focus
ng-model="price.minPrice"
on-change="$ctrl.upsertPrice(price)">
on-change="$ctrl.upsertPrice(price)"
step="0.01">
</vn-input-number>
</field>
</vn-td-editable>
@ -140,7 +141,7 @@
<vn-icon-button
icon="delete"
vn-tooltip="Delete"
ng-click="$ctrl.removePrice($index)">
ng-click="deleteFixedPrice.show({$index})">
</vn-icon-button>
</vn-td>
</vn-tr>
@ -154,9 +155,19 @@
ng-click="model.insert()">
</vn-icon-button>
</div>
<vn-pagination
model="model"
class="vn-pt-md">
</vn-pagination>
</vn-card>
</div>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
</vn-item-descriptor-popover>
<vn-confirm
vn-id="deleteFixedPrice"
on-accept="$ctrl.removePrice($data.$index)"
question="Are you sure you want to continue?"
message="This row will be removed">
</vn-confirm>

View File

@ -1,4 +1,5 @@
Fixed prices: Precios fijados
Search prices by item ID or code: Buscar por ID de artículo o código
Search fixed prices: Buscar precios fijados
Add fixed price: Añadir precio fijado
Add fixed price: Añadir precio fijado
This row will be removed: Esta linea se eliminará

View File

@ -1,115 +1,148 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-w-xl vn-mb-xl">
<vn-card>
<vn-table
model="model"
show-fields="$ctrl.showFields"
vn-smart-table="itemIndex">
<vn-thead>
<vn-tr>
<vn-th shrink></vn-th>
<vn-th field="id" shrink>Id</vn-th>
<vn-th field="grouping" shrink>Grouping</vn-th>
<vn-th field="packing" shrink>Packing</vn-th>
<vn-th field="name">Description</vn-th>
<vn-th field="stems" shrink>Stems</vn-th>
<vn-th field="size" shrink>Size</vn-th>
<vn-th field="typeFk" shrink>Type</vn-th>
<vn-th field="category" shrink>Category</vn-th>
<vn-th field="intrastat" shrink>Intrastat</vn-th>
<vn-th field="origin" shrink>Origin</vn-th>
<vn-th field="salesperson" shrink>Buyer</vn-th>
<vn-th field="density" shrink>Density</vn-th>
<vn-th field="stemMultiplier" shrink>Multiplier</vn-th>
<vn-th field="active" shrink>Active</vn-th>
<vn-th field="landed" shrink-date>Landed</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="item in model.data"
class="clickable vn-tr search-result"
ui-sref="item.card.summary({id: item.id})">
<vn-td shrink>
<img
ng-src="{{::$root.imagePath('catalog', '50x50', item.id)}}"
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
vn-click-stop
on-error-src/>
</vn-td>
<vn-td shrink>
<span
vn-click-stop="itemDescriptor.show($event, item.id)"
class="link">
{{::item.id}}
</span>
</vn-td>
<vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td>
<vn-td shrink>{{::item.packing | dashIfEmpty}}</vn-td>
<vn-td vn-fetched-tags>
<div>
<vn-one title="{{::item.name}}">{{::item.name}}</vn-one>
<vn-one ng-if="::item.subName">
<h3 title="{{::item.subName}}">{{::item.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="item"
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td shrink>{{::item.stems}}</vn-td>
<vn-td shrink>{{::item.size}}</vn-td>
<vn-td shrink title="{{::item.typeName}}">
{{::item.typeName}}
</vn-td>
<vn-td shrink title="{{::item.category}}">
{{::item.category}}
</vn-td>
<vn-td shrink title="{{::item.intrastat}}">
{{::item.intrastat}}
</vn-td>
<vn-td shrink>{{::item.origin}}</vn-td>
<vn-td shrink title="{{::item.userName}}">
<span
class="link"
vn-click-stop="workerDescriptor.show($event, item.buyerFk)">
{{::item.userName}}
</span>
</vn-td>
<vn-td shrink>{{::item.density}}</vn-td>
<vn-td shrink >{{::item.stemMultiplier}}</vn-td>
<vn-td shrink>
<vn-check
disabled="true"
ng-model="::item.isActive">
</vn-check>
</vn-td>
<vn-td shrink-date>{{::item.landed | date:'dd/MM/yyyy'}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
vn-click-stop="clone.show(item.id)"
vn-tooltip="Clone"
icon="icon-clone">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.preview(item)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-card>
<smart-table
model="model"
view-config-id="itemsIndex"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-table>
<table>
<thead>
<tr>
<th shrink></th>
<th field="id">
<span translate>Identifier</span>
</th>
<th field="grouping">
<span translate>Grouping</span>
</th>
<th field="packing">
<span translate>Packing</span>
</th>
<th field="name">
<span translate>Description</span>
</th>
<th field="stems">
<span translate>Stems</span>
</th>
<th field="size">
<span translate>Size</span>
</th>
<th field="typeFk">
<span translate>Type</span>
</th>
<th field="category">
<span translate>Category</span>
</th>
<th field="intrastat">
<span translate>Intrastat</span>
</th>
<th field="origin">
<span translate>Origin</span>
</th>
<th field="buyerFk">
<span translate>Buyer</span>
</th>
<th field="density">
<span translate>Density</span>
</th>
<th field="stemMultiplier">
<span translate>Multiplier</span>
</th>
<th field="active">
<span translate>Active</span>
</th>
<th field="landed">
<span translate>Landed</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in model.data"
vn-anchor="::{
state: 'item.card.summary',
params: {id: item.id}
}">
<td>
<img
ng-src="{{::$root.imagePath('catalog', '50x50', item.id)}}"
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
vn-click-stop
on-error-src/>
</td>
<td>
<span
vn-click-stop="itemDescriptor.show($event, item.id)"
class="link">
{{::item.id}}
</span>
</td>
<td>{{::item.grouping | dashIfEmpty}}</td>
<td>{{::item.packing | dashIfEmpty}}</td>
<td vn-fetched-tags>
<div>
<vn-one title="{{::item.name}}">{{::item.name}}</vn-one>
<vn-one ng-if="::item.subName">
<h3 title="{{::item.subName}}">{{::item.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="item"
tabindex="-1">
</vn-fetched-tags>
</td>
<td>{{::item.stems}}</td>
<td>{{::item.size}}</td>
<td title="{{::item.typeName}}">
{{::item.typeName}}
</td>
<td title="{{::item.category}}">
{{::item.category}}
</td>
<td title="{{::item.intrastat}}">
{{::item.intrastat}}
</td>
<td>{{::item.origin}}</td>
<td title="{{::item.userName}}">
<span
class="link"
vn-click-stop="workerDescriptor.show($event, item.buyerFk)">
{{::item.userName}}
</span>
</td>
<td>{{::item.density}}</td>
<td>{{::item.stemMultiplier}}</td>
<td>
<vn-check
disabled="true"
ng-model="::item.isActive">
</vn-check>
</td>
<td shrink-date>{{::item.landed | date:'dd/MM/yyyy'}}</td>
<td>
<vn-horizontal class="buttons">
<vn-icon-button
vn-click-stop="clone.show(item.id)"
vn-tooltip="Clone"
icon="icon-clone">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.preview(item)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-horizontal>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<a ui-sref="item.create" vn-tooltip="New item" vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
@ -133,7 +166,7 @@
</vn-popup>
<vn-contextmenu
vn-id="contextmenu"
targets="['vn-data-viewer']"
targets="['smart-table']"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>

View File

@ -5,9 +5,61 @@ import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.showFields = {
id: false,
actions: false
this.smartTableOptions = {
activeButtons: {
search: true,
shownColumns: true,
},
columns: [
{
field: 'category',
autocomplete: {
url: 'ItemCategories',
valueField: 'name',
}
},
{
field: 'origin',
autocomplete: {
url: 'Origins',
showField: 'code',
valueField: 'code'
}
},
{
field: 'typeFk',
autocomplete: {
url: 'ItemTypes',
}
},
{
field: 'intrastat',
autocomplete: {
url: 'Intrastats',
showField: 'description',
valueField: 'description'
}
},
{
field: 'buyerFk',
autocomplete: {
url: 'Workers/activeWithRole',
where: `{role: {inq: ['logistic', 'buyer']}}`,
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'active',
searchable: false
},
{
field: 'landed',
searchable: false
},
]
};
}
@ -15,7 +67,7 @@ class Controller extends Section {
switch (param) {
case 'category':
return {'ic.name': value};
case 'salesPersonFk':
case 'buyerFk':
return {'it.workerFk': value};
case 'grouping':
return {'b.grouping': value};
@ -27,9 +79,10 @@ class Controller extends Section {
return {'i.typeFk': value};
case 'intrastat':
return {'intr.description': value};
case 'name':
return {'i.name': {like: `%${value}%`}};
case 'id':
case 'size':
case 'name':
case 'subname':
case 'isActive':
case 'density':

View File

@ -23,7 +23,7 @@ vn-item-product {
}
}
vn-table {
table {
img {
border-radius: 50%;
width: 50px;

View File

@ -23,7 +23,6 @@
</vn-horizontal>
</vn-card>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl vn-pa-md">

View File

@ -1,16 +1,19 @@
<vn-crud-model
vn-id="model"
url="Items/filter"
user-params="::$ctrl.filterParams"
limit="12"
order="isActive DESC, name, id"
data="items">
data="items"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-item-search-panel"
info="Search items by id, name or barcode"
suggested-filter="{isActive: true}"
suggested-filter="$ctrl.filterParams"
filter="$ctrl.filterParams"
model="model">
</vn-searchbar>
</vn-portal>

View File

@ -1,7 +1,15 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Items extends ModuleMain {
constructor($element, $) {
super($element, $);
export default class Items extends ModuleMain {}
this.filterParams = {
isActive: true,
isFloramondo: false
};
}
}
ngModule.vnComponent('vnItems', {
controller: Items,

View File

@ -9,7 +9,7 @@
{"state": "item.index", "icon": "icon-item"},
{"state": "item.request", "icon": "pan_tool"},
{"state": "item.waste.index", "icon": "icon-claims"},
{"state": "item.fixedPrice", "icon": "contact_support"}
{"state": "item.fixedPrice", "icon": "icon-fixedPrice"}
],
"card": [
{"state": "item.card.basicData", "icon": "settings"},

Some files were not shown because too many files have changed in this diff Show More