Merge pull request '4159-tableQ_filter' (!1069) from 4159-tableQ_filter into dev
gitea/salix/pipeline/head This commit looks good Details

fixes #4159
This commit is contained in:
Alex Moreno 2022-11-03 07:52:21 +00:00
commit aa467dd565
14 changed files with 451 additions and 126 deletions

View File

@ -394,11 +394,18 @@ export default {
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]', 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"]', 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"]', 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"]',
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchItemType: 'vn-item-search-panel vn-autocomplete[ng-model="filter.typeFk"]',
advancedSearchButton: 'vn-item-search-panel button[type=submit]',
advancedSmartTableButton: 'vn-item-index vn-button[icon="search"]',
advancedSmartTableGrouping: 'vn-item-index vn-textfield[name=grouping]',
weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]', weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button' saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
}, },
itemFixedPrice: { itemFixedPrice: {
add: 'vn-fixed-price vn-icon-button[icon="add_circle"]', add: 'vn-fixed-price vn-icon-button[icon="add_circle"]',
firstItemID: 'vn-fixed-price tr:nth-child(2) vn-autocomplete[ng-model="price.itemFk"]',
fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)', fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)',
fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]', fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]',
fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]', fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]',
@ -408,7 +415,8 @@ export default {
fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]', fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]',
fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]', fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]',
fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]', fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]',
fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]' fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]',
orderColumnId: 'vn-fixed-price th[field="itemFk"]'
}, },
itemCreateView: { itemCreateView: {
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]', temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',

View File

@ -0,0 +1,77 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('SmartTable SearchBar integration', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesPerson', 'item');
await page.waitToClick(selectors.globalItems.searchButton);
});
afterAll(async() => {
await browser.close();
});
describe('as filters', () => {
it('should search by type in searchBar', async() => {
await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
await page.waitToClick(selectors.itemsIndex.advancedSearchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3);
});
it('should reload page and have same results', async() => {
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3);
});
it('should search by grouping in smartTable', async() => {
await page.waitToClick(selectors.itemsIndex.advancedSmartTableButton);
await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
});
it('should now reload page and have same results', async() => {
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
});
});
describe('as orders', () => {
it('should order by first id', async() => {
await page.loginAndModule('developer', 'item');
await page.accessToSection('item.fixedPrice');
await page.doSearch();
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('1');
});
it('should order by last id', async() => {
await page.waitToClick(selectors.itemFixedPrice.orderColumnId);
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('13');
});
it('should reload page and have same order', async() => {
await page.reload({
waitUntil: 'networkidle2'
});
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('13');
});
});
});

View File

@ -99,6 +99,18 @@ export default class CrudModel extends ModelProxy {
return this.refresh(); return this.refresh();
} }
/**
* Applies a new filter to the model.
*
* @param {Object} params Custom parameters
* @return {Promise} The request promise
*/
applyParams(params) {
this.userParams = params;
return this.refresh();
}
removeFilter() { removeFilter() {
return this.applyFilter(null, null); return this.applyFilter(null, null);
} }

View File

@ -139,8 +139,12 @@ export default class Searchbar extends Component {
} }
removeParam(index) { removeParam(index) {
const field = this.params[index].key;
this.filterSanitizer(field);
this.params.splice(index, 1); this.params.splice(index, 1);
this.doSearch(this.fromBar(), 'bar'); this.toRemove = field;
this.doSearch(this.fromBar(), 'removeBar');
} }
fromBar() { fromBar() {
@ -163,7 +167,7 @@ export default class Searchbar extends Component {
let keys = Object.keys(filter); let keys = Object.keys(filter);
keys.forEach(key => { keys.forEach(key => {
if (key == 'search') return; if (key == 'search' || key == 'tableQ' || key == 'tableOrder') return;
let value = filter[key]; let value = filter[key];
let chip; let chip;
@ -198,6 +202,7 @@ export default class Searchbar extends Component {
let promise = this.onSearch({$params: filter}); let promise = this.onSearch({$params: filter});
promise = promise || this.$q.resolve(); promise = promise || this.$q.resolve();
promise.then(data => this.onFilter(filter, source, data)); promise.then(data => this.onFilter(filter, source, data));
this.toBar(filter);
} }
onFilter(filter, source, data) { onFilter(filter, source, data) {
@ -238,8 +243,11 @@ export default class Searchbar extends Component {
} else { } else {
state = this.searchState; state = this.searchState;
if (filter) if (filter) {
if (this.tableQ)
filter.tableQ = this.tableQ;
params = {q: JSON.stringify(filter)}; params = {q: JSON.stringify(filter)};
}
if (this.$state.is(state)) if (this.$state.is(state))
opts = {location: 'replace'}; opts = {location: 'replace'};
} }
@ -247,6 +255,12 @@ export default class Searchbar extends Component {
this.filter = filter; this.filter = filter;
if (source == 'removeBar') {
delete params[this.toRemove];
delete this.model.userParams[this.toRemove];
this.model.refresh();
}
if (!filter && this.model) if (!filter && this.model)
this.model.clear(); this.model.clear();
if (source != 'state') if (source != 'state')
@ -269,9 +283,14 @@ export default class Searchbar extends Component {
this.model.clear(); this.model.clear();
return; return;
} }
if (Object.keys(filter).length === 0) {
this.filterSanitizer('search');
if (this.model.userParams)
delete this.model.userParams['search'];
}
let where = null; let where = null;
let params = null; let params = {};
if (this.exprBuilder) { if (this.exprBuilder) {
where = buildFilter(filter, where = buildFilter(filter,
@ -283,9 +302,89 @@ export default class Searchbar extends Component {
params = this.fetchParams({$params: params}); params = this.fetchParams({$params: params});
} }
this.tableQ = null;
const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length;
if (hasParams) {
const stateFilter = JSON.parse(this.$params.q);
for (let param in stateFilter) {
if (param != 'tableQ' && param != 'orderQ')
this.filterSanitizer(param);
}
for (let param in this.suggestedFilter) {
this.filterSanitizer(param);
delete stateFilter[param];
}
this.tableQ = stateFilter.tableQ;
for (let param in stateFilter.tableQ)
params[param] = stateFilter.tableQ[param];
Object.assign(stateFilter, params);
return this.model.applyParams(params)
.then(() => this.model.data);
}
return this.model.applyFilter(where ? {where} : null, params) return this.model.applyFilter(where ? {where} : null, params)
.then(() => this.model.data); .then(() => this.model.data);
} }
filterSanitizer(field) {
if (!field) return;
const userFilter = this.model.userFilter;
const userParams = this.model.userParams;
const where = userFilter && userFilter.where;
if (this.model.userParams)
delete this.model.userParams[field];
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).length == 0)
delete userFilter.where;
}
function removeProp(obj, targetProp, prop) {
if (prop == targetProp)
delete obj[prop];
if (prop === 'and' || prop === 'or' && obj[prop]) {
const arrayCopy = obj[prop].slice();
for (let param of arrayCopy) {
const [key] = Object.keys(param);
const index = obj[prop].findIndex(param => {
return Object.keys(param)[0] == key;
});
if (key == targetProp)
obj[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, field, key);
if (Object.keys(param).length == 0)
obj[prop].splice(index, 1);
}
if (obj[prop].length == 0)
delete obj[prop];
}
}
return {userFilter, userParams};
}
} }
ngModule.vnComponent('vnSearchbar', { ngModule.vnComponent('vnSearchbar', {

View File

@ -6,7 +6,7 @@ describe('Component vnSearchbar', () => {
let $state; let $state;
let $params; let $params;
let $scope; let $scope;
let filter = {id: 1, search: 'needle'}; const filter = {id: 1, search: 'needle'};
beforeEach(ngModule('vnCore', $stateProvider => { beforeEach(ngModule('vnCore', $stateProvider => {
$stateProvider $stateProvider
@ -70,8 +70,8 @@ describe('Component vnSearchbar', () => {
describe('filter() setter', () => { describe('filter() setter', () => {
it(`should update the bar params and search`, () => { it(`should update the bar params and search`, () => {
let withoutHours = new Date(2000, 1, 1); const withoutHours = new Date(2000, 1, 1);
let withHours = new Date(withoutHours.getTime()); const withHours = new Date(withoutHours.getTime());
withHours.setHours(12, 30, 15, 10); withHours.setHours(12, 30, 15, 10);
controller.filter = { controller.filter = {
@ -83,8 +83,8 @@ describe('Component vnSearchbar', () => {
myObjectProp: {myProp: 1} myObjectProp: {myProp: 1}
}; };
let chips = {}; const chips = {};
for (let param of controller.params || []) for (const param of controller.params || [])
chips[param.key] = param.chip; chips[param.key] = param.chip;
expect(controller.searchString).toBe('needle'); expect(controller.searchString).toBe('needle');
@ -172,13 +172,22 @@ describe('Component vnSearchbar', () => {
describe('removeParam()', () => { describe('removeParam()', () => {
it(`should remove the parameter from the filter`, () => { it(`should remove the parameter from the filter`, () => {
jest.spyOn(controller, 'doSearch'); jest.spyOn(controller, 'doSearch');
controller.model = {
refresh: jest.fn(),
userParams: {
id: 1
}
};
controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve());
jest.spyOn(controller.model, 'applyParams');
controller.filter = filter; controller.filter = filter;
controller.removeParam(0); controller.removeParam(0);
expect(controller.doSearch).toHaveBeenCalledWith({ expect(controller.doSearch).toHaveBeenCalledWith({
search: 'needle' search: 'needle'
}, 'bar'); }, 'removeBar');
}); });
}); });
@ -199,7 +208,7 @@ describe('Component vnSearchbar', () => {
it(`should go to the summary state when one result`, () => { it(`should go to the summary state when one result`, () => {
jest.spyOn($state, 'go'); jest.spyOn($state, 'go');
let data = [{id: 1}]; const data = [{id: 1}];
controller.baseState = 'foo'; controller.baseState = 'foo';
controller.onFilter(filter, 'any', data); controller.onFilter(filter, 'any', data);
@ -214,7 +223,7 @@ describe('Component vnSearchbar', () => {
$scope.$apply(); $scope.$apply();
jest.spyOn($state, 'go'); jest.spyOn($state, 'go');
let data = [{id: 1}]; const data = [{id: 1}];
controller.baseState = 'foo'; controller.baseState = 'foo';
controller.onFilter(filter, 'any', data); controller.onFilter(filter, 'any', data);
@ -229,7 +238,7 @@ describe('Component vnSearchbar', () => {
$scope.$apply(); $scope.$apply();
jest.spyOn($state, 'go'); jest.spyOn($state, 'go');
let data = [{id: 1}]; const data = [{id: 1}];
controller.baseState = 'foo'; controller.baseState = 'foo';
controller.onFilter(filter, 'any', data); controller.onFilter(filter, 'any', data);
@ -247,7 +256,7 @@ describe('Component vnSearchbar', () => {
controller.onFilter(filter, 'any'); controller.onFilter(filter, 'any');
$scope.$apply(); $scope.$apply();
let queryParams = {q: JSON.stringify(filter)}; const queryParams = {q: JSON.stringify(filter)};
expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined); expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined);
expect(controller.filter).toEqual(filter); expect(controller.filter).toEqual(filter);

View File

@ -103,3 +103,4 @@
</div> </div>
</tpl-body> </tpl-body>
</vn-popover> </vn-popover>

View File

@ -15,9 +15,17 @@ export default class SmartTable extends Component {
this.$inputsScope; this.$inputsScope;
this.columns = []; this.columns = [];
this.autoSave = false; this.autoSave = false;
this.autoState = true;
this.transclude(); this.transclude();
} }
$onChanges() {
if (this.model) {
this.defaultFilter();
this.defaultOrder();
}
}
$onDestroy() { $onDestroy() {
const styleElement = document.querySelector('style[id="smart-table"]'); const styleElement = document.querySelector('style[id="smart-table"]');
if (this.$.css && styleElement) if (this.$.css && styleElement)
@ -47,10 +55,8 @@ export default class SmartTable extends Component {
set model(value) { set model(value) {
this._model = value; this._model = value;
if (value) { if (value)
this.$.model = value; this.$.model = value;
this.defaultOrder();
}
} }
getDefaultViewConfig() { getDefaultViewConfig() {
@ -160,8 +166,36 @@ export default class SmartTable extends Component {
} }
} }
defaultFilter() {
if (this.disabledTableFilter || !this.$params.q) return;
const stateFilter = JSON.parse(this.$params.q).tableQ;
if (!stateFilter || !this.exprBuilder) return;
const columns = this.columns.map(column => column.field);
this.displaySearch();
if (!this.$inputsScope.searchProps)
this.$inputsScope.searchProps = {};
for (let param in stateFilter) {
if (columns.includes(param)) {
const whereParams = {[param]: stateFilter[param]};
Object.assign(this.$inputsScope.searchProps, whereParams);
this.addFilter(param, stateFilter[param]);
}
}
}
defaultOrder() { defaultOrder() {
const order = this.model.order; if (this.disabledTableOrder) return;
let stateOrder;
if (this.$params.q)
stateOrder = JSON.parse(this.$params.q).tableOrder;
const order = stateOrder ? stateOrder : this.model.order;
if (!order) return; if (!order) return;
const orderFields = order.split(', '); const orderFields = order.split(', ');
@ -195,6 +229,9 @@ export default class SmartTable extends Component {
this.setPriority(column.element, priority); this.setPriority(column.element, priority);
} }
} }
this.model.order = order;
this.refresh();
} }
registerColumns() { registerColumns() {
@ -395,28 +432,54 @@ export default class SmartTable extends Component {
} }
searchByColumn(field) { searchByColumn(field) {
const searchCriteria = this.$inputsScope.searchProps[field];
const emptySearch = searchCriteria === '' || searchCriteria == null;
const filters = this.filterSanitizer(field); const filters = this.filterSanitizer(field);
if (filters && filters.userFilter) if (filters && filters.userFilter)
this.model.userFilter = filters.userFilter; this.model.userFilter = filters.userFilter;
if (!emptySearch) this.addFilter(field, this.$inputsScope.searchProps[field]);
this.addFilter(field, this.$inputsScope.searchProps[field]); }
else this.model.refresh();
searchPropsSanitizer() {
if (!this.$inputsScope || !this.$inputsScope.searchProps) return null;
let searchProps = this.$inputsScope.searchProps;
const searchPropsArray = Object.entries(searchProps);
searchProps = searchPropsArray.filter(
([key, value]) => value && value != ''
);
return Object.fromEntries(searchProps);
} }
addFilter(field, value) { addFilter(field, value) {
let where = {[field]: value}; if (value == '') value = null;
if (this.exprBuilder) { let stateFilter = {tableQ: {}};
where = buildFilter(where, (param, value) => if (this.$params.q) {
this.exprBuilder({param, value}) stateFilter = JSON.parse(this.$params.q);
); if (!stateFilter.tableQ)
stateFilter.tableQ = {};
delete stateFilter.tableQ[field];
} }
this.model.addFilter({where}); const whereParams = {[field]: value};
if (value) {
let where = {[field]: value};
if (this.exprBuilder) {
where = buildFilter(whereParams, (param, value) =>
this.exprBuilder({param, value})
);
}
this.model.addFilter({where});
}
const searchProps = this.searchPropsSanitizer();
Object.assign(stateFilter.tableQ, searchProps);
const params = {q: JSON.stringify(stateFilter)};
this.$state.go(this.$state.current.name, params, {location: 'replace'});
this.refresh();
} }
applySort() { applySort() {
@ -426,7 +489,18 @@ export default class SmartTable extends Component {
if (order) if (order)
this.model.order = order; this.model.order = order;
this.model.refresh(); let stateFilter = {tableOrder: {}};
if (this.$params.q) {
stateFilter = JSON.parse(this.$params.q);
if (!stateFilter.tableOrder)
stateFilter.tableOrder = {};
}
stateFilter.tableOrder = order;
const params = {q: JSON.stringify(stateFilter)};
this.$state.go(this.$state.current.name, params, {location: 'replace'});
this.refresh();
} }
filterSanitizer(field) { filterSanitizer(field) {
@ -535,6 +609,8 @@ ngModule.vnComponent('smartTable', {
autoSave: '<?', autoSave: '<?',
exprBuilder: '&?', exprBuilder: '&?',
defaultNewData: '&?', defaultNewData: '&?',
options: '<?' options: '<?',
disabledTableFilter: '<?',
disabledTableOrder: '<?',
} }
}); });

View File

@ -9,6 +9,11 @@ describe('Component smartTable', () => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$element = $compile(`<smart-table></smart-table>`)($rootScope); $element = $compile(`<smart-table></smart-table>`)($rootScope);
controller = $element.controller('smartTable'); controller = $element.controller('smartTable');
controller.model = {
refresh: jest.fn().mockReturnValue(new Promise(resolve => resolve())),
addFilter: jest.fn(),
userParams: {}
};
})); }));
afterEach(() => { afterEach(() => {
@ -83,7 +88,7 @@ describe('Component smartTable', () => {
describe('defaultOrder', () => { describe('defaultOrder', () => {
it('should insert a new object to the controller sortCriteria with a sortType value of "ASC"', () => { it('should insert a new object to the controller sortCriteria with a sortType value of "ASC"', () => {
const element = document.createElement('div'); const element = document.createElement('div');
controller.model = {order: 'id'}; controller.model.order = 'id';
controller.columns = [ controller.columns = [
{field: 'id', element: element}, {field: 'id', element: element},
{field: 'test1', element: element}, {field: 'test1', element: element},
@ -101,7 +106,8 @@ describe('Component smartTable', () => {
it('should add new entries to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => { it('should add new entries to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => {
const element = document.createElement('div'); const element = document.createElement('div');
controller.model = {order: 'test1, id DESC'}; controller.model.order = 'test1, id DESC';
controller.columns = [ controller.columns = [
{field: 'id', element: element}, {field: 'id', element: element},
{field: 'test1', element: element}, {field: 'test1', element: element},
@ -125,8 +131,6 @@ describe('Component smartTable', () => {
describe('addFilter()', () => { describe('addFilter()', () => {
it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => { it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => {
controller.model = {addFilter: jest.fn()};
controller.addFilter('myField', 'myValue'); controller.addFilter('myField', 'myValue');
const expectedFilter = { const expectedFilter = {
@ -140,7 +144,6 @@ describe('Component smartTable', () => {
it('should call the model addFilter() with a built where filter resultant of exprBuilder()', () => { it('should call the model addFilter() with a built where filter resultant of exprBuilder()', () => {
controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'}); controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'});
controller.model = {addFilter: jest.fn()};
controller.addFilter('myField', 'myValue'); controller.addFilter('myField', 'myValue');
@ -155,35 +158,48 @@ describe('Component smartTable', () => {
}); });
describe('applySort()', () => { describe('applySort()', () => {
it('should call the model refresh() without making changes on the model order', () => { it('should call the $state go and model refresh without making changes on the model order', () => {
controller.model = {refresh: jest.fn()}; controller.$state = {
go: jest.fn(),
current: {
name: 'section'
}
};
jest.spyOn(controller, 'refresh');
controller.applySort(); controller.applySort();
expect(controller.model.order).toBeUndefined(); expect(controller.model.order).toBeUndefined();
expect(controller.model.refresh).toHaveBeenCalled(); expect(controller.$state.go).toHaveBeenCalled();
expect(controller.refresh).toHaveBeenCalled();
}); });
it('should call the model.refresh() after setting model order according to the controller sortCriteria', () => { it('should call the $state go and model refresh after setting model order according to the controller sortCriteria', () => {
controller.model = {refresh: jest.fn()};
const orderBy = {field: 'myField', sortType: 'ASC'}; const orderBy = {field: 'myField', sortType: 'ASC'};
controller.$state = {
go: jest.fn(),
current: {
name: 'section'
}
};
jest.spyOn(controller, 'refresh');
controller.sortCriteria = [orderBy]; controller.sortCriteria = [orderBy];
controller.applySort(); controller.applySort();
expect(controller.model.order).toEqual(`${orderBy.field} ${orderBy.sortType}`); expect(controller.model.order).toEqual(`${orderBy.field} ${orderBy.sortType}`);
expect(controller.model.refresh).toHaveBeenCalled(); expect(controller.$state.go).toHaveBeenCalled();
expect(controller.refresh).toHaveBeenCalled();
}); });
}); });
describe('filterSanitizer()', () => { describe('filterSanitizer()', () => {
it('should remove the where filter after leaving no fields in it', () => { it('should remove the where filter after leaving no fields in it', () => {
controller.model = { controller.model.userFilter = {
userFilter: { where: {fieldToRemove: 'valueToRemove'}
where: {fieldToRemove: 'valueToRemove'}
},
userParams: {}
}; };
controller.model.userParams = {};
const result = controller.filterSanitizer('fieldToRemove'); const result = controller.filterSanitizer('fieldToRemove');
@ -193,23 +209,21 @@ describe('Component smartTable', () => {
}); });
it('should remove the where filter after leaving no fields and "empty ands/ors" in it', () => { it('should remove the where filter after leaving no fields and "empty ands/ors" in it', () => {
controller.model = { controller.model.userFilter = {
userFilter: { where: {
where: { and: [
and: [ {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, {
{ or: [
or: [ {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, ]
] }
} ]
] }
} },
}, controller.model.userParams = {};
userParams: {}
};
const result = controller.filterSanitizer('aFieldToRemove'); const result = controller.filterSanitizer('aFieldToRemove');
@ -219,24 +233,22 @@ describe('Component smartTable', () => {
}); });
it('should not remove the where filter after leaving no empty "ands/ors" in it', () => { it('should not remove the where filter after leaving no empty "ands/ors" in it', () => {
controller.model = { controller.model.userFilter = {
userFilter: { where: {
where: { and: [
and: [ {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, {
{ or: [
or: [ {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, {aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'}, ]
] }
} ],
], or: [{dontKillMe: 'thanks'}]
or: [{dontKillMe: 'thanks'}] }
}
},
userParams: {}
}; };
controller.model.userParams = {};
const result = controller.filterSanitizer('aFieldToRemove'); const result = controller.filterSanitizer('aFieldToRemove');
@ -249,7 +261,7 @@ describe('Component smartTable', () => {
describe('saveAll()', () => { describe('saveAll()', () => {
it('should throw an error if there are no changes to save in the model', () => { it('should throw an error if there are no changes to save in the model', () => {
jest.spyOn(controller.vnApp, 'showError'); jest.spyOn(controller.vnApp, 'showError');
controller.model = {isChanged: false}; controller.model.isChanged = false;
controller.saveAll(); controller.saveAll();
expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save'); expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save');
@ -258,10 +270,8 @@ describe('Component smartTable', () => {
it('should call the showSuccess() if there are changes to save in the model', done => { it('should call the showSuccess() if there are changes to save in the model', done => {
jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnApp, 'showSuccess');
controller.model = { controller.model.save = jest.fn().mockReturnValue(Promise.resolve());
save: jest.fn().mockReturnValue(Promise.resolve()), controller.model.isChanged = true;
isChanged: true
};
controller.saveAll().then(() => { controller.saveAll().then(() => {
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
@ -269,4 +279,43 @@ describe('Component smartTable', () => {
}).catch(done.fail); }).catch(done.fail);
}); });
}); });
describe('defaultFilter()', () => {
it('should call model refresh and model addFilter with filter', () => {
controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'});
controller.$params = {
q: '{"tableQ": {"fieldName":"value"}}'
};
controller.columns = [
{field: 'fieldName'}
];
controller.$inputsScope = {
searchProps: {}
};
jest.spyOn(controller, 'refresh');
controller.defaultFilter();
expect(controller.model.addFilter).toHaveBeenCalled();
expect(controller.refresh).toHaveBeenCalled();
});
});
describe('searchPropsSanitizer()', () => {
it('should searchProps sanitize', () => {
controller.$inputsScope = {
searchProps: {
filterOne: '1',
filterTwo: ''
}
};
const searchPropsExpected = {
filterOne: '1'
};
const newSearchProps = controller.searchPropsSanitizer();
expect(newSearchProps).toEqual(searchPropsExpected);
});
});
}); });

View File

@ -73,7 +73,7 @@
{ {
"url": "/note", "url": "/note",
"state": "claim.card.note", "state": "claim.card.note",
"component": "ui-view", "component": "ui-view",
"abstract": true, "abstract": true,
"acl": ["salesPerson"] "acl": ["salesPerson"]
}, },
@ -105,7 +105,7 @@
"acl": ["claimManager"] "acl": ["claimManager"]
}, },
{ {
"url": "/action", "url": "/action?q",
"state": "claim.card.action", "state": "claim.card.action",
"component": "vn-claim-action", "component": "vn-claim-action",
"description": "Action", "description": "Action",
@ -131,4 +131,4 @@
"acl": ["claimManager"] "acl": ["claimManager"]
} }
] ]
} }

View File

@ -20,7 +20,7 @@
{"state": "client.card.credit.index", "icon": "credit_card"}, {"state": "client.card.credit.index", "icon": "credit_card"},
{"state": "client.card.greuge.index", "icon": "work"}, {"state": "client.card.greuge.index", "icon": "work"},
{"state": "client.card.balance.index", "icon": "icon-invoice"}, {"state": "client.card.balance.index", "icon": "icon-invoice"},
{"state": "client.card.recovery.index", "icon": "icon-recovery"}, {"state": "client.card.recovery.index", "icon": "icon-recovery"},
{"state": "client.card.webAccess", "icon": "cloud"}, {"state": "client.card.webAccess", "icon": "cloud"},
{"state": "client.card.log", "icon": "history"}, {"state": "client.card.log", "icon": "history"},
{ {
@ -37,7 +37,7 @@
{"state": "client.card.unpaid", "icon": "icon-defaulter"} {"state": "client.card.unpaid", "icon": "icon-defaulter"}
] ]
} }
] ]
}, },
"keybindings": [ "keybindings": [
{"key": "c", "state": "client.index"} {"key": "c", "state": "client.index"}
@ -147,7 +147,7 @@
{ {
"url": "/note", "url": "/note",
"state": "client.card.note", "state": "client.card.note",
"component": "ui-view", "component": "ui-view",
"abstract": true "abstract": true
}, },
{ {
@ -236,7 +236,7 @@
"client": "$ctrl.client" "client": "$ctrl.client"
} }
}, },
{ {
"url": "/create?payed&companyFk&bankFk&payedAmount", "url": "/create?payed&companyFk&bankFk&payedAmount",
"state": "client.card.balance.create", "state": "client.card.balance.create",
"component": "vn-client-balance-create", "component": "vn-client-balance-create",
@ -406,13 +406,13 @@
} }
}, },
{ {
"url": "/defaulter", "url": "/defaulter?q",
"state": "client.defaulter", "state": "client.defaulter",
"component": "vn-client-defaulter", "component": "vn-client-defaulter",
"description": "Defaulter" "description": "Defaulter"
}, },
{ {
"url" : "/notification", "url" : "/notification?q",
"state": "client.notification", "state": "client.notification",
"component": "vn-client-notification", "component": "vn-client-notification",
"description": "Notifications" "description": "Notifications"
@ -424,7 +424,7 @@
"description": "Unpaid" "description": "Unpaid"
}, },
{ {
"url": "/extended-list", "url": "/extended-list?q",
"state": "client.extendedList", "state": "client.extendedList",
"component": "vn-client-extended-list", "component": "vn-client-extended-list",
"description": "Extended list" "description": "Extended list"

View File

@ -23,9 +23,9 @@
</vn-portal> </vn-portal>
<div class="vn-w-xl"> <div class="vn-w-xl">
<vn-card> <vn-card>
<smart-table <smart-table
model="model" model="model"
options="$ctrl.smartTableOptions" options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)"> expr-builder="$ctrl.exprBuilder(param, value)">
<slot-table> <slot-table>
<table> <table>
@ -34,18 +34,18 @@
<th field="itemFk"> <th field="itemFk">
<span translate>Item ID</span> <span translate>Item ID</span>
</th> </th>
<th field="itemName"> <th field="name">
<span translate>Description</span> <span translate>Description</span>
</th> </th>
<th field="warehouseFk"> <th field="warehouseFk">
<span translate>Warehouse</span> <span translate>Warehouse</span>
</th> </th>
<th <th
field="rate2" field="rate2"
vn-tooltip="Price By Unit"> vn-tooltip="Price By Unit">
<span translate>P.P.U.</span> <span translate>P.P.U.</span>
</th> </th>
<th <th
field="rate3" field="rate3"
vn-tooltip="Price By Package"> vn-tooltip="Price By Package">
<span translate>P.P.P.</span> <span translate>P.P.P.</span>
@ -170,7 +170,7 @@
</vn-icon-button> </vn-icon-button>
</div> </div>
</slot-table> </slot-table>
</smart-table> </smart-table>
</vn-card> </vn-card>
</div> </div>
<vn-item-descriptor-popover <vn-item-descriptor-popover
@ -182,4 +182,4 @@
on-accept="$ctrl.removePrice($data.$index)" on-accept="$ctrl.removePrice($data.$index)"
question="Are you sure you want to continue?" question="Are you sure you want to continue?"
message="This row will be removed"> message="This row will be removed">
</vn-confirm> </vn-confirm>

View File

@ -12,14 +12,6 @@ export default class Controller extends Section {
}, },
defaultSearch: true, defaultSearch: true,
columns: [ columns: [
{
field: 'itemName',
autocomplete: {
url: 'Items',
showField: 'name',
valueField: 'id'
}
},
{ {
field: 'warehouseFk', field: 'warehouseFk',
autocomplete: { autocomplete: {
@ -105,8 +97,8 @@ export default class Controller extends Section {
exprBuilder(param, value) { exprBuilder(param, value) {
switch (param) { switch (param) {
case 'itemName': case 'name':
return {'i.id': value}; return {'i.name': {like: `%${value}%`}};
case 'itemFk': case 'itemFk':
case 'warehouseFk': case 'warehouseFk':
case 'rate2': case 'rate2':

View File

@ -19,22 +19,24 @@
</vn-none> </vn-none>
</vn-horizontal> </vn-horizontal>
<vn-card vn-id="card"> <vn-card vn-id="card">
<smart-table <smart-table
model="model" model="model"
options="$ctrl.smartTableOptions" options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)" expr-builder="$ctrl.exprBuilder(param, value)"
disabled-table-filter="true"
disabled-table-order="true"
class="scrollable sm"> class="scrollable sm">
<slot-actions> <slot-actions>
<vn-horizontal> <vn-horizontal>
<vn-date-picker <vn-date-picker
class="vn-pa-xs" class="vn-pa-xs"
label="From" label="From"
ng-model="$ctrl.dateFrom" ng-model="$ctrl.dateFrom"
on-change="$ctrl.addFilterDate()"> on-change="$ctrl.addFilterDate()">
</vn-date-picker> </vn-date-picker>
<vn-date-picker <vn-date-picker
class="vn-pa-xs" class="vn-pa-xs"
label="To" label="To"
ng-model="$ctrl.dateTo" ng-model="$ctrl.dateTo"
on-change="$ctrl.addFilterDate()"> on-change="$ctrl.addFilterDate()">
</vn-date-picker> </vn-date-picker>
@ -100,9 +102,9 @@
</slot-pagination> </slot-pagination>
</smart-table> </smart-table>
</vn-card> </vn-card>
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
vn-id="workerDescriptor"> vn-id="workerDescriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>
<vn-client-descriptor-popover <vn-client-descriptor-popover
vn-id="clientDescriptor"> vn-id="clientDescriptor">
</vn-client-descriptor-popover> </vn-client-descriptor-popover>

View File

@ -39,7 +39,7 @@
"abstract": true, "abstract": true,
"component": "vn-route-card" "component": "vn-route-card"
}, { }, {
"url": "/agency-term", "url": "/agency-term?q",
"abstract": true, "abstract": true,
"state": "route.agencyTerm", "state": "route.agencyTerm",
"component": "ui-view" "component": "ui-view"
@ -49,12 +49,12 @@
"component": "vn-agency-term-index", "component": "vn-agency-term-index",
"description": "Autonomous", "description": "Autonomous",
"acl": ["administrative"] "acl": ["administrative"]
},{ },{
"url": "/createInvoiceIn?q", "url": "/createInvoiceIn?q",
"state": "route.agencyTerm.createInvoiceIn", "state": "route.agencyTerm.createInvoiceIn",
"component": "vn-agency-term-create-invoice-in", "component": "vn-agency-term-create-invoice-in",
"description": "File management", "description": "File management",
"params": { "params": {
"route": "$ctrl.route" "route": "$ctrl.route"
}, },
"acl": ["administrative"] "acl": ["administrative"]
@ -92,4 +92,4 @@
"acl": ["delivery"] "acl": ["delivery"]
} }
] ]
} }