Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2132-extend_from_section
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2020-03-20 12:00:53 +01:00
commit 9b933e7d88
71 changed files with 633 additions and 744 deletions

View File

@ -345,11 +345,6 @@ let actions = {
await this.clearInput('vn-searchbar');
await this.write('vn-searchbar', searchValue);
await this.waitToClick('vn-searchbar vn-icon[icon="search"]');
await this.waitForNumberOfElements('.search-result', 1);
await this.waitForContentLoaded();
await this.evaluate(() => {
return document.querySelector('.search-result').click();
});
await this.waitForContentLoaded();
},

View File

@ -208,7 +208,7 @@ export default {
createItemButton: `vn-float-button`,
firstSearchResult: 'vn-item-index a:nth-child(1)',
searchResult: 'vn-item-index a.vn-tr',
searchResultPreviewButton: 'vn-item-index .buttons > [icon="desktop_windows"]',
searchResultPreviewButton: 'vn-item-index vn-tbody > :nth-child(1) .buttons > [icon="desktop_windows"]',
searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]',
acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]',
topbarSearch: 'vn-topbar',

View File

@ -116,18 +116,7 @@ describe('Client balance path', () => {
});
it('should now search for the user Petter Parker', async() => {
await page.write(selectors.clientsIndex.topbarSearch, 'Petter Parker');
await page.waitToClick(selectors.clientsIndex.searchButton);
await page.waitForNumberOfElements(selectors.clientsIndex.searchResult, 1);
let resultCount = await page.countElement(selectors.clientsIndex.searchResult);
expect(resultCount).toEqual(1);
});
it(`should click on the search result to access to the client's balance`, async() => {
await page.waitForTextInElement(selectors.clientsIndex.searchResult, 'Petter Parker');
await page.waitToClick(selectors.clientsIndex.searchResult);
await page.waitForContentLoaded();
await page.accessToSearchResult('Petter Parker');
await page.waitToClick(selectors.clientBalance.balanceButton);
let url = await page.expectURL('/balance');

View File

@ -16,15 +16,9 @@ describe('Item summary path', () => {
it('should search for an item', async() => {
await page.clearInput(selectors.itemsIndex.topbarSearch);
await page.write(selectors.itemsIndex.topbarSearch, 'Ranged weapon longbow 2m');
await page.write(selectors.itemsIndex.topbarSearch, 'Ranged weapon');
await page.waitToClick(selectors.itemsIndex.searchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
const result = await page.countElement(selectors.itemsIndex.searchResult);
expect(result).toEqual(1);
});
it(`should click on the search result summary button to open the item summary popup`, async() => {
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3);
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon longbow 2m');
await page.waitToClick(selectors.itemsIndex.searchResultPreviewButton);
const isVisible = await page.isVisible(selectors.itemSummary.basicData);
@ -75,24 +69,18 @@ describe('Item summary path', () => {
it('should search for other item', async() => {
await page.clearInput('vn-searchbar');
await page.waitToClick(selectors.itemsIndex.searchButton);
await page.write(selectors.itemsIndex.topbarSearch, 'Melee weapon combat fist 15cm');
await page.write(selectors.itemsIndex.topbarSearch, 'Melee Reinforced');
await page.waitToClick(selectors.itemsIndex.searchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
const result = await page.countElement(selectors.itemsIndex.searchResult);
expect(result).toEqual(1);
});
it(`should now click on the search result summary button to open the item summary popup`, async() => {
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
await page.waitToClick(selectors.itemsIndex.searchResultPreviewButton);
await page.waitForSelector(selectors.itemSummary.basicData, {visible: true});
});
it(`should now check the item summary preview shows fields from basic data`, async() => {
await page.waitForTextInElement(selectors.itemSummary.basicData, 'Melee weapon combat fist 15cm');
await page.waitForTextInElement(selectors.itemSummary.basicData, 'Melee Reinforced weapon combat fist 15cm');
const result = await page.waitToGetProperty(selectors.itemSummary.basicData, 'innerText');
expect(result).toContain('Melee weapon combat fist 15cm');
expect(result).toContain('Melee Reinforced weapon combat fist 15cm');
});
it(`should now check the item summary preview shows fields from tags`, async() => {
@ -102,13 +90,6 @@ describe('Item summary path', () => {
expect(result).toContain('Silver');
});
it(`should now check the item summary preview shows fields from niche`, async() => {
await page.waitForTextInElement(selectors.itemSummary.niche, 'A4');
const result = await page.waitToGetProperty(selectors.itemSummary.niche, 'innerText');
expect(result).toContain('A4');
});
it(`should now check the item summary preview shows fields from botanical`, async() => {
await page.waitForTextInElement(selectors.itemSummary.botanical, '-');
const result = await page.waitToGetProperty(selectors.itemSummary.botanical, 'innerText');
@ -116,20 +97,13 @@ describe('Item summary path', () => {
expect(result).toContain('-');
});
it(`should now check the item summary preview shows fields from barcode`, async() => {
await page.waitForTextInElement(selectors.itemSummary.barcode, '4');
const result = await page.waitToGetProperty(selectors.itemSummary.barcode, 'innerText');
expect(result).toContain('4');
});
it(`should now close the summary popup`, async() => {
await page.closePopup();
await page.waitForSelector(selectors.itemSummary.basicData, {hidden: true});
});
it(`should navigate to the one of the items detailed section`, async() => {
await page.waitToClick(selectors.itemsIndex.searchResult);
await page.accessToSearchResult('Melee weapon combat fist 15cm');
let url = await page.expectURL('summary');
expect(url).toBe(true);

View File

@ -63,7 +63,6 @@ describe('Item Create/Clone path', () => {
expect(result).toEqual('Infinity Gauntlet');
result = await page
.waitToGetProperty(selectors.itemBasicData.type, 'value');
@ -81,7 +80,10 @@ describe('Item Create/Clone path', () => {
});
});
describe('clone', () => {
// 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.wait(selectors.itemsIndex.createItemButton);

View File

@ -33,17 +33,7 @@ describe('Item regularize path', () => {
});
it('should search for an specific item', async() => {
await page.write(selectors.itemsIndex.topbarSearch, 'Ranged weapon pistol 9mm');
await page.waitToClick(selectors.itemsIndex.searchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
const resultCount = await page.countElement(selectors.itemsIndex.searchResult);
expect(resultCount).toEqual(1);
});
it(`should click on the search result to access to the summary`, async() => {
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon pistol 9mm');
await page.waitToClick(selectors.itemsIndex.searchResult);
await page.accessToSearchResult('Ranged weapon pistol 9mm');
let url = await page.expectURL('/summary');
expect(url).toBe(true);
@ -88,18 +78,9 @@ describe('Item regularize path', () => {
});
it('should search for the ticket with alias missing', async() => {
await page.accessToSearchResult('Carol Danvers');
await page.keyboard.press('Escape');
await page.write(selectors.ticketsIndex.topbarSearch, 'missing');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1);
const result = await page.countElement(selectors.ticketsIndex.searchResult);
expect(result).toEqual(1);
});
it(`should click on the search result to access to the ticket summary`, async() => {
await page.waitForTextInElement(selectors.ticketsIndex.searchResult, 'Missing');
await page.waitToClick(selectors.ticketsIndex.searchResult);
await page.accessToSearchResult('missing');
let url = await page.expectURL('/summary');
expect(url).toBe(true);
@ -130,18 +111,7 @@ describe('Item regularize path', () => {
});
it('should search for the item once again', async() => {
await page.clearInput(selectors.itemsIndex.topbarSearch);
await page.write(selectors.itemsIndex.topbarSearch, 'Ranged weapon pistol 9mm');
await page.waitToClick(selectors.itemsIndex.searchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
const resultCount = await page.countElement(selectors.itemsIndex.searchResult);
expect(resultCount).toEqual(1);
});
it(`should click on the search result to access to the item tax`, async() => {
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon pistol 9mm');
await page.waitToClick(selectors.itemsIndex.searchResult);
await page.accessToSearchResult('Ranged weapon pistol 9mm');
let url = await page.expectURL('/summary');
expect(url).toBe(true);
@ -172,17 +142,7 @@ describe('Item regularize path', () => {
});
it('should search for the ticket with id 25 once again', async() => {
await page.write(selectors.ticketsIndex.topbarSearch, '25');
await page.waitToClick(selectors.ticketsIndex.searchButton);
await page.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1);
const result = await page.countElement(selectors.ticketsIndex.searchResult);
expect(result).toEqual(1);
});
it(`should now click on the search result to access to the ticket summary`, async() => {
await page.waitForTextInElement(selectors.ticketsIndex.searchResult, '25');
await page.waitToClick(selectors.ticketsIndex.searchResult);
await page.accessToSearchResult('25');
let url = await page.expectURL('/summary');
expect(url).toBe(true);

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Ticket descriptor path', () => {
fdescribe('Ticket descriptor path', () => {
let browser;
let page;

View File

@ -17,17 +17,7 @@ describe('Ticket descriptor path', () => {
describe('Delete ticket', () => {
it('should search for an specific ticket', async() => {
await page.write(selectors.ticketsIndex.topbarSearch, '18');
await page.waitToClick(selectors.ticketsIndex.searchButton);
await page.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1);
const result = await page.countElement(selectors.ticketsIndex.searchResult);
expect(result).toEqual(1);
});
it(`should click on the search result to access to the ticket summary`, async() => {
await page.waitForTextInElement(selectors.ticketsIndex.searchResult, 'Cerebro');
await page.waitToClick(selectors.ticketsIndex.searchResult);
await page.accessToSearchResult('18');
let url = await page.expectURL('/summary');
expect(url).toBe(true);
@ -78,18 +68,7 @@ describe('Ticket descriptor path', () => {
describe('add stowaway', () => {
it('should search for a ticket', async() => {
await page.clearInput(selectors.ticketsIndex.topbarSearch);
await page.write(selectors.ticketsIndex.topbarSearch, '16');
await page.waitToClick(selectors.ticketsIndex.searchButton);
await page.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1);
const result = await page.countElement(selectors.ticketsIndex.searchResult);
expect(result).toEqual(1);
});
it(`should now click on the search result to access to the ticket summary`, async() => {
await page.waitForTextInElement(selectors.ticketsIndex.searchResult, 'Many Places');
await page.waitToClick(selectors.ticketsIndex.searchResult);
await page.accessToSearchResult('16');
let url = await page.expectURL('/summary');
expect(url).toBe(true);

View File

@ -13,19 +13,31 @@ import './style.scss';
*
* @property {Object} filter A key-value object with filter parameters
* @property {SearchPanel} panel The panel used for advanced searches
* @property {Function} onSearch Function to call when search is submited
* @property {CrudModel} model The model used for searching
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
*/
export default class Controller extends Component {
export default class Searchbar extends Component {
constructor($element, $) {
super($element, $);
this.searchState = '.';
this.autoState = true;
let criteria = {};
this.deregisterCallback = this.$transitions.onSuccess(
criteria, () => this.onStateChange());
{}, transition => this.onStateChange(transition));
}
$postLink() {
this.onStateChange();
if (this.autoState) {
if (!this.baseState) {
let stateParts = this.$state.current.name.split('.');
this.baseState = stateParts[0];
}
this.searchState = `${this.baseState}.index`;
}
this.fetchStateFilter(this.autoLoad);
}
$onDestroy() {
@ -56,7 +68,16 @@ export default class Controller extends Component {
if (value == null) this.params = [];
}
onStateChange() {
onStateChange(transition) {
let ignoreHandler =
!this.element.parentNode
|| transition == this.transition;
if (ignoreHandler) return;
this.fetchStateFilter();
}
fetchStateFilter(autoLoad) {
let filter = null;
if (this.$state.is(this.searchState)) {
@ -68,10 +89,11 @@ export default class Controller extends Component {
}
}
focus(this.element.querySelector('vn-textfield input'));
if (!filter && autoLoad)
filter = {};
}
this.filter = filter;
this.doSearch(filter, 'state');
}
openPanel(event) {
@ -100,25 +122,16 @@ export default class Controller extends Component {
this.$.popover.hide();
filter = compact(filter);
filter = filter != null ? filter : {};
this.doSearch(filter);
this.doSearch(filter, 'panel');
}
onSubmit() {
this.doSearch(this.fromBar());
this.doSearch(this.fromBar(), 'bar');
}
removeParam(index) {
this.params.splice(index, 1);
this.doSearch(this.fromBar());
}
doSearch(filter) {
this.filter = filter;
let opts = this.$state.is(this.searchState)
? {location: 'replace'} : null;
this.$state.go(this.searchState,
{q: JSON.stringify(filter)}, opts);
this.doSearch(this.fromBar(), 'bar');
}
fromBar() {
@ -171,111 +184,135 @@ export default class Controller extends Component {
this.params.push({chip, key, value});
});
}
doSearch(filter, source) {
if (filter === this.filter) return;
let promise = this.onSearch({$params: filter});
promise = promise || this.$q.resolve();
promise.then(data => this.onFilter(filter, source, data));
}
onFilter(filter, source, data) {
let state;
let params;
let opts;
if (filter) {
let isOneResult = this.autoState
&& source != 'state'
&& !angular.equals(filter, {})
&& data
&& data.length == 1;
if (isOneResult) {
let baseDepth = this.baseState.split('.').length;
let stateParts = this.$state.current.name
.split('.')
.slice(baseDepth);
let subState = stateParts[0];
switch (subState) {
case 'card':
subState += `.${stateParts[1]}`;
if (stateParts.length >= 3)
subState += '.index';
break;
default:
subState = 'card.summary';
}
if (this.stateParams)
params = this.stateParams({$row: data[0]});
state = `${this.baseState}.${subState}`;
filter = null;
} else {
state = this.searchState;
if (filter)
params = {q: JSON.stringify(filter)};
if (this.$state.is(state))
opts = {location: 'replace'};
}
}
this.filter = filter;
if (source != 'state')
this.transition = this.$state.go(state, params, opts).transition;
if (source != 'bar')
focus(this.element.querySelector('vn-textfield input'));
}
// Default search handlers
stateParams(params) {
return {id: params.$row.id};
}
onSearch(args) {
if (!this.model) return;
let filter = args.$params;
if (filter === null) {
this.model.clear();
return;
}
let where = null;
let params = null;
if (this.exprBuilder) {
where = buildFilter(filter,
(param, value) => this.exprBuilder({param, value}));
} else {
params = Object.assign({}, filter);
if (this.fetchParams)
params = this.fetchParams({$params: params});
}
return this.model.applyFilter(where ? {where} : null, params)
.then(() => this.model.data);
}
}
ngModule.vnComponent('vnSearchbar', {
controller: Controller,
controller: Searchbar,
template: require('./searchbar.html'),
bindings: {
searchState: '@?',
filter: '<?',
suggestedFilter: '<?',
panel: '@',
info: '@?'
info: '@?',
onSearch: '&?',
baseState: '@?',
autoState: '<?',
stateParams: '&?',
model: '<?',
exprBuilder: '&?',
fetchParams: '&?'
}
});
/**
* @property {CrudModel} model The model used for searching
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
* @property {Function} onSearch Function to call when search is submited
*/
class AutoSearch {
constructor($state, $transitions) {
this.$state = $state;
this.$transitions = $transitions;
let criteria = {to: this.$state.current.name};
this.deregisterCallback = this.$transitions.onSuccess(criteria,
() => this.onStateChange());
this.fetchFilter();
constructor(vnSlotService) {
this.vnSlotService = vnSlotService;
}
$postLink() {
if (this.filter !== null)
this.doSearch();
}
$onDestroy() {
this.deregisterCallback();
}
fetchFilter() {
if (this.$state.params.q) {
try {
this.filter = JSON.parse(this.$state.params.q);
} catch (e) {
console.error(e);
}
} else
this.filter = null;
}
onStateChange() {
this.fetchFilter();
this.doSearch();
}
doSearch() {
let filter = this.filter;
if (filter == null && this.autoload)
filter = {};
if (this.onSearch)
this.onSearch({$params: filter});
if (this.model) {
if (filter !== null) {
let where = buildFilter(filter,
(param, value) => this.exprBuilder({param, value}));
let userParams = {};
let hasParams = false;
if (this.paramBuilder) {
for (let param in filter) {
let value = filter[param];
if (value == null) continue;
let expr = this.paramBuilder({param, value});
if (expr) {
Object.assign(userParams, expr);
hasParams = true;
}
}
}
this.model.applyFilter(
where ? {where} : null,
hasParams ? userParams : null
);
} else
this.model.clear();
}
}
exprBuilder(param, value) {
return {[param]: value};
let searchbar = this.vnSlotService.getContent('topbar');
if (searchbar && searchbar.$ctrl instanceof Searchbar)
this.model = searchbar.$ctrl.model;
}
}
AutoSearch.$inject = ['$state', '$transitions'];
AutoSearch.$inject = ['vnSlotService'];
ngModule.vnComponent('vnAutoSearch', {
controller: AutoSearch,
bindings: {
model: '<?',
onSearch: '&?',
exprBuilder: '&?',
paramBuilder: '&?'
model: '=?'
}
});

View File

@ -8,9 +8,35 @@ describe('Component vnSearchbar', () => {
let $scope;
let filter = {id: 1, search: 'needle'};
beforeEach(ngModule('vnCore'));
beforeEach(ngModule('vnCore', $stateProvider => {
$stateProvider
.state('foo', {
abstract: true
})
.state('foo.index', {
url: '/foo/index'
})
.state('foo.card', {
abstract: true
})
.state('foo.card.summary', {
url: '/foo/:id/summary'
})
.state('foo.card.bar', {
url: '/foo/:id/bar'
})
.state('foo.card.baz', {
abstract: true
})
.state('foo.card.baz.index', {
url: '/foo/:id/baz/index'
})
.state('foo.card.baz.edit', {
url: '/foo/:id/baz/:bazId/edit'
});
}));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_) => {
beforeEach(inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$params = $state.params;
@ -19,25 +45,26 @@ describe('Component vnSearchbar', () => {
$element = angular.element(`<div></div>`);
controller = $componentController('vnSearchbar', {$element, $scope});
controller.panel = 'vn-client-search-panel';
}));
describe('$postLink()', () => {
it(`should fetch the filter from the state if it's in the filter state`, () => {
jest.spyOn(controller, 'doSearch');
controller.autoState = false;
controller.$postLink();
expect(controller.filter).toEqual(filter);
expect(controller.searchString).toBe('needle');
expect(controller.params.length).toBe(1);
expect(controller.doSearch).toHaveBeenCalledWith(filter, 'state');
});
it(`should not fetch the filter from the state if not in the filter state`, () => {
jest.spyOn(controller, 'doSearch');
controller.autoState = false;
controller.searchState = 'other.state';
controller.$postLink();
expect(controller.filter).toBeNull();
expect(controller.searchString).toBeNull();
expect(controller.params.length).toBe(0);
expect(controller.doSearch).toHaveBeenCalledWith(null, 'state');
});
});
@ -67,6 +94,14 @@ describe('Component vnSearchbar', () => {
expect(chips.negated).toBe('not negated');
expect(chips.myObjectProp).toBe('myObjectProp');
});
it(`should clear the filter when null`, () => {
controller.filter = null;
expect(controller.filter).toBeNull();
expect(controller.searchString).toBeNull();
expect(controller.params.length).toBe(0);
});
});
describe('shownFilter() getter', () => {
@ -98,6 +133,7 @@ describe('Component vnSearchbar', () => {
describe('onPanelSubmit()', () => {
it(`should compact and define the filter`, () => {
controller.$.popover = {hide: jasmine.createSpy('hide')};
jest.spyOn(controller, 'doSearch');
const filter = {
id: 1,
@ -110,42 +146,110 @@ describe('Component vnSearchbar', () => {
};
controller.onPanelSubmit(filter);
expect(controller.filter).toEqual({
expect(controller.doSearch).toHaveBeenCalledWith({
id: 1,
myObject: {keepThis: true},
myArray: [true]
});
}, 'panel');
});
});
describe('onSubmit()', () => {
it(`should define the filter`, () => {
jest.spyOn(controller, 'doSearch');
controller.filter = filter;
controller.searchString = 'mySearch';
controller.onSubmit();
expect(controller.filter).toEqual({id: 1, search: 'mySearch'});
expect(controller.doSearch).toHaveBeenCalledWith({
id: 1,
search: 'mySearch'
}, 'bar');
});
});
describe('removeParam()', () => {
it(`should remove the parameter from the filter`, () => {
jest.spyOn(controller, 'doSearch');
controller.filter = filter;
controller.removeParam(0);
expect(controller.filter).toEqual({search: 'needle'});
expect(controller.doSearch).toHaveBeenCalledWith({
search: 'needle'
}, 'bar');
});
});
describe('doSearch()', () => {
it(`should go to the search state and pass the filter as query param`, () => {
it(`should do the filter`, () => {
jest.spyOn(controller, 'onSearch');
jest.spyOn(controller, 'onFilter');
controller.doSearch(filter, 'any');
$scope.$apply();
expect(controller.onSearch).toHaveBeenCalledWith({$params: filter});
expect(controller.onFilter).toHaveBeenCalledWith(filter, 'any', undefined);
});
});
describe('onFilter()', () => {
it(`should go to the summary state when one result`, () => {
jest.spyOn($state, 'go');
let data = [{id: 1}];
controller.baseState = 'foo';
controller.onFilter(filter, 'any', data);
$scope.$apply();
expect($state.go).toHaveBeenCalledWith('foo.card.summary', {id: 1}, undefined);
expect(controller.filter).toEqual(null);
});
it(`should keep the same card state when one result and it's already inside any card state`, () => {
$state.go('foo.card.bar', {id: 1});
$scope.$apply();
jest.spyOn($state, 'go');
let data = [{id: 1}];
controller.baseState = 'foo';
controller.onFilter(filter, 'any', data);
$scope.$apply();
expect($state.go).toHaveBeenCalledWith('foo.card.bar', {id: 1}, undefined);
expect(controller.filter).toEqual(null);
});
it(`should keep the same card state but index when one result and it's already in card state but inside more than three-nested states`, () => {
$state.go('foo.card.baz.edit', {id: 1, bazId: 1});
$scope.$apply();
jest.spyOn($state, 'go');
let data = [{id: 1}];
controller.baseState = 'foo';
controller.onFilter(filter, 'any', data);
$scope.$apply();
expect($state.go).toHaveBeenCalledWith('foo.card.baz.index', {id: 1}, undefined);
expect(controller.filter).toEqual(null);
});
it(`should go to the search state when multiple results and pass the filter as query param`, () => {
jest.spyOn($state, 'go');
controller.autoState = false;
controller.searchState = 'search.state';
controller.doSearch(filter);
controller.onFilter(filter, 'any');
$scope.$apply();
let queryParams = {q: JSON.stringify(filter)};
expect($state.go).toHaveBeenCalledWith('search.state', queryParams, null);
expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined);
expect(controller.filter).toEqual(filter);
});
});

View File

@ -67,6 +67,12 @@ export class SlotService {
$content.remove();
}
getContent(slot) {
if (this.slots[slot])
return this.slots[slot].$element[0].firstElementChild;
return null;
}
refreshContent(slot) {
if (!this.slots[slot]) return;
let $content = this.stacks[slot][0];

View File

@ -2,12 +2,12 @@
import isMobile from './is-mobile';
export default function focus(element) {
if (isMobile) return;
if (isMobile || !element) return;
setTimeout(() => element.focus(), 10);
}
export function select(element) {
if (isMobile) return;
if (isMobile || !element) return;
setTimeout(() => {
element.focus();
if (element.select)

View File

@ -7,6 +7,11 @@ export default class Section extends Component {
super($element, $);
this.element.classList.add('vn-section');
}
stopEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
}
}
ngModule.vnComponent('vnSection', {

View File

@ -63,9 +63,12 @@ export function config($translatePartialLoaderProvider, $httpProvider, $compileP
$httpProvider.interceptors.push('vnInterceptor');
$compileProvider
.debugInfoEnabled(false)
.commentDirectivesEnabled(false)
.cssClassDirectivesEnabled(false);
let env = process.env.NODE_ENV;
if (env && env !== 'development')
$compileProvider.debugInfoEnabled(false);
}
ngModule.config(config);

View File

@ -17,8 +17,8 @@ core.run(vnInterceptor => {
vnInterceptor.setApiPath(null);
});
window.ngModule = function(moduleName) {
return angular.mock.module(moduleName, function($provide, $translateProvider) {
window.ngModule = function(moduleName, ...args) {
let fns = [moduleName, function($provide, $translateProvider) {
// Avoid unexpected request warnings caused by angular translate
// https://angular-translate.github.io/docs/#/guide/22_unit-testing-with-angular-translate
$provide.factory('customLocaleLoader', function($q) {
@ -30,5 +30,10 @@ window.ngModule = function(moduleName) {
});
$translateProvider.useLoader('customLocaleLoader');
});
}];
if (args.length)
fns = fns.concat(args);
return angular.mock.module(...fns);
};

View File

@ -1,13 +1,5 @@
<vn-crud-model
vn-id="model"
url="Claims/filter"
limit="20"
data="claims"
order="claimStateFk ASC, created DESC"
auto-load="true">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -26,7 +18,7 @@
</vn-thead>
<vn-tbody>
<a
ng-repeat="claim in claims"
ng-repeat="claim in model.data"
class="{{::$ctrl.compareDate(ticket.shipped)}} clickable vn-tr search-result"
ui-sref="claim.card.summary({id: claim.id})">
<vn-td number>{{::claim.id}}</vn-td>

View File

@ -39,13 +39,6 @@ export default class Controller extends Section {
onDescriptorLoad() {
this.$.popover.relocate();
}
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
}
ngModule.component('vnClaimIndex', {

View File

@ -1,8 +1,15 @@
<vn-crud-model
vn-id="model"
url="Claims/filter"
limit="20"
order="claimStateFk ASC, created DESC"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="claim.index"
panel="vn-claim-search-panel"
info="Search claim by id or client name">
info="Search claim by id or client name"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -3,7 +3,6 @@ import crudModel from 'core/mocks/crud-model';
describe('Claim', () => {
describe('Component vnClaimPhotos', () => {
let $componentController;
let $scope;
let $httpBackend;
let controller;
@ -11,8 +10,7 @@ describe('Claim', () => {
beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpParamSerializer = _$httpParamSerializer_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();

View File

@ -2,13 +2,11 @@ import './index';
describe('Client', () => {
describe('Component vnClientBalanceIndex', () => {
let $componentController;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
let $scope = $rootScope.$new();
const $element = angular.element('<vn-client-balance-index></vn-client-balance-index>');
controller = $componentController('vnClientBalanceIndex', {$element, $scope});

View File

@ -2,15 +2,13 @@ import './index';
describe('Client', () => {
describe('Component vnClientCreate', () => {
let $componentController;
let $scope;
let $state;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = {

View File

@ -3,15 +3,13 @@ import crudModel from 'core/mocks/crud-model';
describe('Client', () => {
describe('Component vnClientDmsIndex', () => {
let $componentController;
let $scope;
let $httpBackend;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element('<vn-client-dms-index></vn-client-dms-index>');

View File

@ -2,15 +2,13 @@ import './index';
describe('Client', () => {
describe('Component vnClientGreugeCreate', () => {
let $componentController;
let $scope;
let $state;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = {

View File

@ -1,13 +1,6 @@
<vn-crud-model
vn-id="model"
url="Clients"
order="id DESC"
limit="8"
data="clients">
</vn-crud-model>
<vn-auto-search
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -15,7 +8,7 @@
<vn-card>
<div class="vn-list separated">
<a
ng-repeat="client in clients track by client.id"
ng-repeat="client in model.data track by client.id"
ui-sref="client.card.summary(::{id: client.id})"
translate-attr="{title: 'View client'}"
class="vn-item search-result">

View File

@ -2,33 +2,6 @@ import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
case 'phone':
return {
or: [
{phone: value},
{mobile: value}
]
};
case 'name':
case 'socialName':
case 'city':
return {[param]: {like: `%${value}%`}};
case 'email':
return {[param]: {like: `%${value}%`}};
case 'id':
case 'fi':
case 'postcode':
case 'salesPersonFk':
return {[param]: value};
}
}
openSummary(client, event) {
if (event.defaultPrevented) return;
event.preventDefault();

View File

@ -1,8 +1,16 @@
<vn-crud-model
vn-id="model"
url="Clients"
order="id DESC"
limit="8"
data="clients">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="client.index"
panel="vn-client-search-panel"
info="Search client by id or name">
info="Search client by id or name"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -1,7 +1,33 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Client extends ModuleMain {}
export default class Client extends ModuleMain {
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
case 'phone':
return {
or: [
{phone: value},
{mobile: value}
]
};
case 'name':
case 'socialName':
case 'city':
case 'email':
return {[param]: {like: `%${value}%`}};
case 'id':
case 'fi':
case 'postcode':
case 'salesPersonFk':
return {[param]: value};
}
}
}
ngModule.vnComponent('vnClient', {
controller: Client,

View File

@ -1,19 +1,19 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller {
constructor($state) {
this.$state = $state;
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.note = {
clientFk: parseInt($state.params.id),
clientFk: parseInt(this.$params.id),
text: null
};
}
cancel() {
this.$state.go('client.card.note.index', {id: this.$state.params.id});
this.$state.go('client.card.note.index', {id: this.$params.id});
}
}
Controller.$inject = ['$state'];
ngModule.component('vnNoteCreate', {
template: require('./index.html'),

View File

@ -2,17 +2,16 @@ import './index';
describe('Client', () => {
describe('Component vnNoteCreate', () => {
let $componentController;
let $state;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, _$state_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, _$state_) => {
$state = _$state_;
$state.params.id = '1234';
controller = $componentController('vnNoteCreate', {$state: $state});
const $element = angular.element('<vn-note-create></vn-note-create>');
controller = $componentController('vnNoteCreate', {$element, $state});
}));
it('should define clientFk using $state.params.id', () => {

View File

@ -2,15 +2,13 @@ import './index';
describe('Client', () => {
describe('Component vnClientRecoveryCreate', () => {
let $componentController;
let $scope;
let $state;
let controller;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = {

View File

@ -1,13 +1,5 @@
<vn-crud-model
vn-id="model"
url="Entries/filter"
limit="20"
params="::$ctrl.params"
data="entries"
auto-load="true">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -30,7 +22,7 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="entry in entries"
<a ng-repeat="entry in model.data"
class="clickable vn-tr search-result"
ui-sref="entry.card.summary({id: {{::entry.id}}})">
<vn-td shrink>

View File

@ -2,13 +2,6 @@ import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
showTravelDescriptor(event, travelFk) {
if (event.defaultPrevented) return;
event.preventDefault();

View File

@ -1,8 +1,14 @@
<vn-crud-model
vn-id="model"
url="Entries/filter"
limit="20"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="entry.index"
panel="vn-entry-search-panel"
info="Search entrys by id">
info="Search entrys by id"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -1,16 +1,9 @@
<vn-crud-model
vn-id="model"
url="InvoiceOuts/filter"
limit="20"
data="invoiceOuts"
order="issued DESC">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-w-md vn-my-md">
class="vn-w-md">
<vn-card>
<vn-table model="model">
<vn-thead>
@ -27,7 +20,7 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="invoiceOut in invoiceOuts"
<a ng-repeat="invoiceOut in model.data"
class="clickable vn-tr search-result"
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
<vn-td>{{::invoiceOut.ref | dashIfEmpty}}</vn-td>

View File

@ -27,13 +27,6 @@ export default class Controller extends Section {
event.preventDefault();
event.stopImmediatePropagation();
}
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
}
ngModule.component('vnInvoiceOutIndex', {

View File

@ -1,8 +1,14 @@
<vn-crud-model
vn-id="model"
url="InvoiceOuts/filter"
limit="20"
order="issued DESC">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="invoiceOut.index"
panel="vn-invoice-search-panel"
info="Search invoices by reference">
info="Search invoices by reference"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -2,15 +2,13 @@ import './index.js';
describe('Item', () => {
describe('Component vnItemCreate', () => {
let $componentController;
let $scope;
let $state;
let controller;
beforeEach(ngModule('item'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = {

View File

@ -1,12 +1,5 @@
<vn-crud-model
vn-id="model"
url="Items/filter"
limit="12"
order="isActive DESC, name, id"
data="items">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -37,7 +30,7 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="item in items"
<a ng-repeat="item in model.data"
class="clickable vn-tr search-result"
ui-sref="item.card.summary({id: item.id})">
<vn-td shrink>

View File

@ -18,13 +18,6 @@ class Controller extends Section {
event.stopImmediatePropagation();
}
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
showItemDescriptor(event, itemFk) {
if (event.defaultPrevented) return;

View File

@ -1,9 +1,16 @@
<vn-crud-model
vn-id="model"
url="Items/filter"
limit="12"
order="isActive DESC, name, id"
data="items">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="item.index"
panel="vn-item-search-panel"
info="Search items by id, name or barcode"
suggested-filter="{isActive: true}">
suggested-filter="{isActive: true}"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -1,12 +1,6 @@
<vn-crud-model
vn-id="model"
url="Orders/filter"
limit="20"
data="orders"
order="landed DESC, clientFk">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -27,8 +21,10 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="order in orders" class="clickable search-result"
ui-sref="order.card.summary({id: {{::order.id}}})">
<vn-tr
ng-repeat="order in model.data"
class="clickable search-result"
ui-sref="order.card.summary({id: {{::order.id}}})">
<vn-td number>{{::order.id}}</vn-td>
<vn-td expand>
<span class="link" ng-click="$ctrl.showClientDescriptor($event, order.clientFk)">

View File

@ -2,13 +2,6 @@ import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
showClientDescriptor(event, clientFk) {
this.$.clientDescriptor.clientFk = clientFk;
this.$.clientDescriptor.parent = event.target;

View File

@ -1,8 +1,15 @@
<vn-crud-model
vn-id="model"
url="Orders/filter"
limit="20"
data="orders"
order="landed DESC, clientFk">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="order.index"
panel="vn-order-search-panel"
info="Search orders by id"
model="model"
filter="$ctrl.filter">
</vn-searchbar>
</vn-portal>

View File

@ -1,13 +1,5 @@
<vn-crud-model
vn-id="model"
url="Routes/filter"
limit="20"
data="routes"
order="created DESC"
auto-load="true">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -27,7 +19,7 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="route in routes"
<a ng-repeat="route in model.data"
class="clickable vn-tr search-result"
ui-sref="route.card.summary({id: {{::route.id}}})">
<vn-td number>{{::route.id | dashIfEmpty}}</vn-td>

View File

@ -20,13 +20,6 @@ export default class Controller extends Section {
this.routeSelected = route;
this.$.summary.show();
}
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
}
ngModule.component('vnRouteIndex', {

View File

@ -1,9 +1,16 @@
<vn-crud-model
vn-id="model"
url="Routes/filter"
limit="20"
order="created DESC"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="route.index"
panel="vn-route-search-panel"
info="Search routes by id"
filter="$ctrl.filter">
filter="$ctrl.filter"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -3,14 +3,12 @@ import crudModel from 'core/mocks/crud-model';
describe('Ticket', () => {
describe('Component vnTicketDmsIndex', () => {
let $componentController;
let $httpBackend;
let controller;
beforeEach(ngModule('ticket'));
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
const $element = angular.element('<vn-ticket-dms-index></vn-ticket-dms-index>');
controller = $componentController('vnTicketDmsIndex', {$element});

View File

@ -1,13 +1,5 @@
<vn-crud-model
vn-id="model"
url="Tickets/filter"
limit="20"
params="::$ctrl.params"
data="tickets"
order="shipped DESC, zoneHour ASC, zoneMinute ASC, clientFk">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -38,7 +30,7 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="ticket in tickets"
<a ng-repeat="ticket in model.data"
class="clickable vn-tr search-result"
ui-sref="ticket.card.summary({id: {{::ticket.id}}})">
<vn-td shrink>

View File

@ -43,30 +43,6 @@ export default class Controller extends Section {
return this.checked.length;
}
getScopeDates(days) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const daysOnward = new Date(today);
daysOnward.setDate(today.getDate() + days);
daysOnward.setHours(23, 59, 59, 999);
return {from: today, to: daysOnward};
}
onSearch(params) {
if (params) {
if (typeof (params.scopeDays) === 'number')
Object.assign(params, this.getScopeDates(params.scopeDays));
// Set default params to 1 scope days
else if (Object.entries(params).length == 0)
params = this.getScopeDates(1);
this.$.model.applyFilter(null, params);
} else
this.$.model.clear();
}
goToLines(event, ticketFk) {
this.preventDefault(event);
let url = this.$state.href('ticket.card.sale', {id: ticketFk}, {absolute: true});

View File

@ -1,8 +1,15 @@
<vn-crud-model
vn-id="model"
url="Tickets/filter"
limit="20"
order="shipped DESC, zoneHour ASC, zoneMinute ASC, clientFk">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="ticket.index"
panel="vn-ticket-search-panel"
info="Search ticket by id or alias">
info="Search ticket by id or alias"
model="model"
fetch-params="$ctrl.fetchParams($params)">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -1,7 +1,25 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Ticket extends ModuleMain {}
export default class Ticket extends ModuleMain {
fetchParams($params) {
if (!Object.entries($params).length)
$params.scopeDays = 1;
if (typeof $params.scopeDays === 'number') {
const from = new Date();
from.setHours(0, 0, 0, 0);
const to = new Date(from.getTime());
to.setDate(to.getDate() + $params.scopeDays);
to.setHours(23, 59, 59, 999);
Object.assign($params, {from, to});
}
return $params;
}
}
ngModule.vnComponent('vnTicket', {
controller: Ticket,

View File

@ -10,12 +10,11 @@
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
info="Search weekly ticket by id or client id">
info="Search weekly ticket by id or client id"
auto-state="false"
model="model">
</vn-searchbar>
</vn-portal>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-w-md vn-mb-xl">

View File

@ -30,13 +30,6 @@ export default class Controller extends Section {
event.stopImmediatePropagation();
}
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
showClientDescriptor(event, clientFk) {
this.$.clientDescriptor.clientFk = clientFk;
this.$.clientDescriptor.parent = event.target;

View File

@ -1,13 +1,6 @@
<vn-crud-model
vn-id="model"
url="Travels/filter"
limit="20"
params="::$ctrl.params"
data="travels"
order="shipped DESC, landed DESC">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -29,7 +22,7 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="travel in travels"
<a ng-repeat="travel in model.data"
class="clickable vn-tr search-result"
ui-sref="travel.card.summary({id: {{::travel.id}}})">
<vn-td number>{{::travel.id}}</vn-td>
@ -74,7 +67,7 @@
</a>
<vn-confirm
vn-id="clone"
on-response="$ctrl.onCloneAccept($response)"
on-accept="$ctrl.onCloneAccept($data)"
question="Do you want to clone this travel?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -2,63 +2,28 @@ import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
getScopeDates(days) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const daysOnward = new Date(today);
daysOnward.setDate(today.getDate() + days);
daysOnward.setHours(23, 59, 59, 999);
return {shippedFrom: today, shippedTo: daysOnward};
}
onSearch(params) {
if (params) {
let newParams = params;
if (params.scopeDays) {
const scopeDates = this.getScopeDates(params.scopeDays);
Object.assign(newParams, scopeDates);
} else if (Object.entries(params).length == 0)
newParams = this.getScopeDates(1);
this.$.model.applyFilter(null, newParams);
} else
this.$.model.clear();
}
stopEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
}
cloneTravel(event, travel) {
this.stopEvent(event);
this.travelSelected = travel;
this.$.clone.show();
}
onCloneAccept(response) {
if (!(response == 'accept' && this.travelSelected))
return;
if (this.travelSelected) {
const travel = {
ref: this.travelSelected.ref,
agencyModeFk: this.travelSelected.agencyFk,
shipped: this.travelSelected.shipped,
landed: this.travelSelected.landed,
warehouseInFk: this.travelSelected.warehouseInFk,
warehouseOutFk: this.travelSelected.warehouseOutFk
};
const queryParams = JSON.stringify(travel);
this.$state.go('travel.create', {q: queryParams});
}
this.travelSelected = null;
}
preview(event, travel) {
this.stopEvent(event);
this.travelSelected = travel;
this.$.summary.show();
}
cloneTravel(event, travel) {
this.stopEvent(event);
this.$.clone.show(travel);
}
onCloneAccept(travel) {
const params = JSON.stringify({
ref: travel.ref,
agencyModeFk: travel.agencyFk,
shipped: travel.shipped,
landed: travel.landed,
warehouseInFk: travel.warehouseInFk,
warehouseOutFk: travel.warehouseOutFk
});
this.$state.go('travel.create', {q: params});
}
}
ngModule.component('vnTravelIndex', {

View File

@ -1,32 +1,17 @@
import './index.js';
describe('Travel Component vnTravelIndex', () => {
let $componentController;
let controller;
let $window;
let travels = [{
let travel = {
id: 1,
warehouseInFk: 1,
totalEntries: 3,
isDelivered: false
}, {
id: 2,
warehouseInFk: 1,
total: 4,
isDelivered: true
}, {
id: 3,
warehouseInFk: 1,
total: 2,
isDelivered: true
}];
};
beforeEach(angular.mock.module('travel', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(ngModule('travel'));
beforeEach(angular.mock.inject(_$componentController_ => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject($componentController => {
const $element = angular.element('<vn-travel-index></vn-travel-index>');
controller = $componentController('vnTravelIndex', {$element});
controller.$.summary = {show: jasmine.createSpy('show')};
@ -35,74 +20,31 @@ describe('Travel Component vnTravelIndex', () => {
describe('preview()', () => {
it('should show the dialog summary', () => {
let event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
controller.preview(event, travels[0]);
controller.preview(event, travel);
expect(controller.$.summary.show).toHaveBeenCalledWith();
});
});
describe('getScopeDates()', () => {
it('should return a range of dates', () => {
let days = 2; // never put 1 or anything higher than 2
let result = controller.getScopeDates(days);
let from = new Date(result.shippedFrom).getTime();
let to = new Date(result.shippedTo).getTime();
let range = to - from;
const dayInMilliseconds = 24 * 60 * 60 * 1000;
let millisecondsPerAddedDay = dayInMilliseconds - 1;
expect(range - dayInMilliseconds).toEqual(dayInMilliseconds + millisecondsPerAddedDay);
});
});
describe('onCloneAccept()', () => {
it('should do nothing if response is not accept', () => {
jest.spyOn(controller.$state, 'go');
let response = 'ERROR!';
controller.travelSelected = 'check me';
controller.onCloneAccept(response);
expect(controller.$state.go).not.toHaveBeenCalledWith();
expect(controller.travelSelected).toEqual('check me');
});
it('should do nothing if response is accept but travelSelected is not defined in the controller', () => {
jest.spyOn(controller.$state, 'go');
let response = 'accept';
controller.travelSelected = undefined;
controller.onCloneAccept(response);
expect(controller.$state.go).not.toHaveBeenCalledWith();
expect(controller.travelSelected).toBeUndefined();
});
it('should call go() then update travelSelected in the controller', () => {
jest.spyOn(controller.$state, 'go');
let response = 'accept';
controller.travelSelected = {
ref: 1,
agencyFk: 1};
const travel = {
ref: controller.travelSelected.ref,
agencyModeFk: controller.travelSelected.agencyFk
ref: 1,
agencyFk: 1
};
const queryParams = JSON.stringify(travel);
controller.onCloneAccept(response);
const travelParams = {
ref: travel.ref,
agencyModeFk: travel.agencyFk
};
const queryParams = JSON.stringify(travelParams);
controller.onCloneAccept(travel);
expect(controller.$state.go).toHaveBeenCalledWith('travel.create', {q: queryParams});
expect(controller.travelSelected).toBeNull();
});
});
});

View File

@ -1,8 +1,15 @@
<vn-crud-model
vn-id="model"
url="Travels/filter"
limit="20"
order="shipped DESC, landed DESC">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="travel.index"
panel="vn-travel-search-panel"
info="Search travels by id">
info="Search travels by id"
model="model"
fetch-params="$ctrl.fetchParams($params)">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -1,7 +1,25 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Travel extends ModuleMain {}
export default class Travel extends ModuleMain {
fetchParams($params) {
if (!Object.entries($params).length)
$params.scopeDays = 1;
if (typeof $params.scopeDays === 'number') {
const shippedFrom = new Date();
shippedFrom.setHours(0, 0, 0, 0);
const shippedTo = new Date(shippedFrom.getTime());
shippedTo.setDate(shippedTo.getDate() + $params.scopeDays);
shippedTo.setHours(23, 59, 59, 999);
Object.assign($params, {shippedFrom, shippedTo});
}
return $params;
}
}
ngModule.vnComponent('vnTravel', {
controller: Travel,

View File

@ -0,0 +1,24 @@
import './index.js';
describe('Travel Component vnTravel', () => {
let controller;
beforeEach(ngModule('travel'));
beforeEach(angular.mock.inject($componentController => {
let $element = angular.element(`<div></div>`);
controller = $componentController('vnTravel', {$element});
}));
describe('fetchParams()', () => {
it('should return a range of dates with passed scope days', () => {
let params = controller.fetchParams({scopeDays: 2});
let from = params.shippedFrom.getTime();
let to = params.shippedTo.getTime() + 1;
let msInDay = 86400 * 1000;
expect(to - from).toEqual(3 * msInDay);
});
});
});

View File

@ -3,7 +3,6 @@ import crudModel from 'core/mocks/crud-model';
describe('Worker', () => {
describe('Component vnWorkerDmsIndex', () => {
let $componentController;
let $scope;
let $element;
let $httpBackend;
@ -11,8 +10,7 @@ describe('Worker', () => {
beforeEach(ngModule('worker'));
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$element = angular.element(`<vn-worker-dms-index></vn-worker-dms-index`);

View File

@ -1,12 +1,5 @@
<vn-crud-model
vn-id="model"
url="Workers/filter"
limit="20"
order="id"
data="workers">
</vn-crud-model>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -14,7 +7,7 @@
<vn-card>
<div class="vn-list separated">
<a
ng-repeat="worker in workers track by worker.id"
ng-repeat="worker in model.data track by worker.id"
ui-sref="worker.card.summary({id: worker.id})"
translate-attr="{title: 'View worker'}"
class="vn-item search-result">

View File

@ -2,13 +2,6 @@ import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
onSearch(params) {
if (params)
this.$.model.applyFilter(null, params);
else
this.$.model.clear();
}
preview(event, worker) {
if (event.defaultPrevented) return;

View File

@ -1,8 +1,14 @@
<vn-crud-model
vn-id="model"
url="Workers/filter"
limit="20"
order="id">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="worker.index"
panel="vn-worker-search-panel"
info="Search workers by id, firstName, lastName or user name">
info="Search workers by id, firstName, lastName or user name"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -3,15 +3,13 @@ import popover from 'core/mocks/popover';
import crudModel from 'core/mocks/crud-model';
describe('Zone Component vnZoneDeliveryDays', () => {
let $componentController;
let $httpBackend;
let controller;
let $element;
beforeEach(ngModule('zone'));
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$element = angular.element('<vn-zone-delivery-days></vn-zone-delivery-days');
controller = $componentController('vnZoneDeliveryDays', {$element});

View File

@ -1,14 +1,5 @@
<vn-crud-model
vn-id="model"
url="Zones"
filter="::$ctrl.filter"
limit="20"
data="zones"
auto-load="false">
</vn-crud-model>
<vn-auto-search
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
@ -27,7 +18,7 @@
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="zone in zones"
ng-repeat="zone in model.data"
ui-sref="zone.card.summary({id: zone.id})"
class="clickable search-result">
<vn-td number>{{::zone.id}}</vn-td>
@ -61,7 +52,7 @@
</vn-popup>
<vn-confirm
vn-id="clone"
on-response="$ctrl.onCloneAccept($response)"
on-accept="$ctrl.onCloneAccept($data)"
question="Do you want to clone this zone?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -2,72 +2,22 @@ import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
$onInit() {
this.filter = {
include: {
relation: 'agencyMode',
scope: {fields: ['name']}
}
};
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
case 'name':
return {[param]: {like: `%${value}%`}};
case 'agencyModeFk':
return {[param]: value};
}
}
/**
* Clones a zone and all its properties
* @param {Object} event - Event object
* @param {Object} zone - Selected item
*/
clone(event, zone) {
this.stopEvent(event);
this.selectedZone = zone;
this.$.clone.show();
}
/**
* Clone response callback
* @param {String} response - Response string (['accept', 'cancel'])
*/
onCloneAccept(response) {
if (!(response == 'accept' && this.selectedZone)) return;
const query = `Zones/${this.selectedZone.id}/clone`;
this.$http.post(query).then(res => {
if (res && res.data)
this.$state.go('zone.card.basicData', {id: res.data.id});
});
this.selectedZone = null;
}
/**
* Opens a summary modal
* @param {Object} event - Event object
* @param {Object} zone - Selected item
*/
preview(event, zone) {
this.stopEvent(event);
this.selectedZone = zone;
this.$.summary.show();
}
/**
* Prevents normal event propagation
* @param {Object} event - Event object
*/
stopEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
clone(event, zone) {
this.stopEvent(event);
this.$.clone.show(zone);
}
onCloneAccept(zone) {
return this.$http.post(`Zones/${zone.id}/clone`)
.then(res => {
this.$state.go('zone.card.basicData', {id: res.data.id});
});
}
}

View File

@ -4,25 +4,25 @@
filter="::$ctrl.filter">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar></vn-searchbar>
<vn-searchbar
on-search="$ctrl.onSearch($params)"
auto-state="false">
</vn-searchbar>
</vn-portal>
<vn-auto-search
on-search="$ctrl.onSearch($params)">
</vn-auto-search>
<div class="vn-w-md">
<vn-card class="vn-pa-lg vn-mt-md">
<vn-treeview
vn-id="treeview"
root-label="Locations"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check acl-role="deliveryBoss"
ng-model="item.selected"
on-change="$ctrl.onSelection(value, item)"
triple-state="true"
ng-click="$event.preventDefault()"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
<vn-card class="vn-pa-lg">
<vn-treeview
vn-id="treeview"
root-label="Locations"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check acl-role="deliveryBoss"
ng-model="item.selected"
on-change="$ctrl.onSelection(value, item)"
triple-state="true"
ng-click="$event.preventDefault()"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
</vn-card>
</div>

View File

@ -1,8 +1,15 @@
<vn-crud-model
vn-id="model"
url="Zones"
filter="::$ctrl.filter"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
search-state="zone.index"
info="Search zone by id or name"
panel="vn-zone-search-panel"
info="Search zone by id or name">
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">

View File

@ -1,7 +1,30 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Zone extends ModuleMain {}
export default class Zone extends ModuleMain {
constructor($element, $) {
super($element, $);
this.filter = {
include: {
relation: 'agencyMode',
scope: {fields: ['name']}
}
};
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
case 'name':
return {[param]: {like: `%${value}%`}};
case 'agencyModeFk':
return {[param]: value};
}
}
}
ngModule.vnComponent('vnZone', {
controller: Zone,

View File

@ -1,15 +1,13 @@
import './index.js';
describe('Zone Component vnZoneIndex', () => {
let $componentController;
describe('Zone Component vnZone', () => {
let controller;
beforeEach(ngModule('zone'));
beforeEach(angular.mock.inject(_$componentController_ => {
$componentController = _$componentController_;
const $element = angular.element('<vn-zone-index></vn-zone-index>');
controller = $componentController('vnZoneIndex', {$element});
beforeEach(angular.mock.inject($componentController => {
const $element = angular.element('<vn-zone></vn-zone>');
controller = $componentController('vnZone', {$element});
}));
describe('exprBuilder()', () => {

6
package-lock.json generated
View File

@ -8354,7 +8354,7 @@
},
"kind-of": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=",
"dev": true
},
@ -9990,7 +9990,7 @@
},
"jasmine-core": {
"version": "2.99.1",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
"resolved": "http://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
"integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=",
"dev": true
},
@ -17968,7 +17968,7 @@
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
},
"xmlcreate": {