This commit is contained in:
Bernat 2019-10-18 08:38:25 +02:00
commit 1cedd6cbfd
35 changed files with 832 additions and 374 deletions

View File

@ -351,16 +351,6 @@ INSERT INTO `vn`.`creditInsurance`(`id`, `creditClassification`, `credit`, `crea
(2, 2, 6000, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), NULL),
(3, 3, 10000, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), NULL);
INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agencyModeFk`, `description`, `m3`, `cost`, `started`, `finished`)
VALUES
(1, '1899-12-30 12:15:00', 56, CURDATE(), 1, 7, 'first route', 2.7, 10, CURDATE(), CURDATE()),
(2, '1899-12-30 13:20:00', 56, CURDATE(), 1, 7, 'second route', 0.9, 20, CURDATE(), CURDATE()),
(3, '1899-12-30 14:30:00', 56, CURDATE(), 2, 7, 'third route', 1.1, 30, CURDATE(), CURDATE()),
(4, '1899-12-30 15:45:00', 56, CURDATE(), 3, 7, 'fourth route', 0.1, 40, CURDATE(), CURDATE()),
(5, '1899-12-30 16:00:00', 56, CURDATE(), 4, 8, 'fifth route', NULL, 50, CURDATE(), CURDATE()),
(6, NULL, 57, CURDATE(), 5, 8, 'sixth route', NULL, 60, CURDATE(), CURDATE()),
(7, NULL, 57, CURDATE(), 6, NULL, 'seventh route', NULL, 70, CURDATE(), CURDATE());
INSERT INTO `vn2008`.`empresa_grupo`(`empresa_grupo_id`, `grupo`)
VALUES
(1, 'Wayne Industries');
@ -456,32 +446,42 @@ INSERT INTO `vn`.`zone` (`id`, `name`, `hour`, `warehouseFk`, `agencyModeFk`, `t
(12, 'Zone entanglement', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 4, 4, 0, 0, 0),
(13, 'Zone quantum break', CONCAT(CURRENT_DATE(), ' ', TIME('22:00')), 5, 5, 0, 0, 0);
INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agencyModeFk`, `description`, `m3`, `cost`, `started`, `finished`, `zoneFk`)
VALUES
(1, '1899-12-30 12:15:00', 56, CURDATE(), 1, 1, 'first route', 1.8, 10, CURDATE(), CURDATE(), 1),
(2, '1899-12-30 13:20:00', 56, CURDATE(), 1, 2, 'second route', 0.2, 20, CURDATE(), CURDATE(), 9),
(3, '1899-12-30 14:30:00', 56, CURDATE(), 2, 3, 'third route', 0.5, 30, CURDATE(), CURDATE(), 10),
(4, '1899-12-30 15:45:00', 56, CURDATE(), 3, 4, 'fourth route', 0, 40, CURDATE(), CURDATE(), 12),
(5, '1899-12-30 16:00:00', 56, CURDATE(), 4, 5, 'fifth route', 0.1, 50, CURDATE(), CURDATE(), 13),
(6, NULL, 57, CURDATE(), 5, 7, 'sixth route', 1.7, 60, CURDATE(), CURDATE(), 3),
(7, NULL, 57, CURDATE(), 6, 8, 'seventh route', 0, 70, CURDATE(), CURDATE(), 5);
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `created`)
VALUES
(1 , 3, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Bat cave', 121, 'T1111111', 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(2 , 1, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T1111111', 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3 , 1, 7, 1, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T2222222', 0, 3, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(4 , 3, 2, 1, 1, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T3333333', 0, 9, DATE_ADD(CURDATE(), INTERVAL -3 MONTH)),
(5 , 3, 3, 3, 1, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T4444444', 0, 10, DATE_ADD(CURDATE(), INTERVAL -4 MONTH)),
(6 , 1, 3, 3, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Mountain Drive Gotham', 1, 'A1111111', 0, 10, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(7 , NULL, 7, 1, 2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Mountain Drive Gotham', 1, NULL, 0, 3, CURDATE()),
(8 , NULL, 7, 1, 2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Bat cave', 121, NULL, 0, 3, CURDATE()),
(9 , NULL, 7, 1, 2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Stark tower', 124, NULL, 0, 3, CURDATE()),
(10, 1, 1, 5, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'Ingram Street', 2, NULL, 0, 11, CURDATE()),
(11, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()),
(12, 1, 1, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()),
(13, 1, 7, 1, 2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()),
(14, 1, 2, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()),
(15, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()),
(16, 1, 7, 1, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(17, 1, 7, 2, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(18, 1, 4, 4, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, CURDATE()),
(19, 1, 5, 5, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, 13, CURDATE()),
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(21, NULL, 5, 5, NULL, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(22, NULL, 5, 5, NULL, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(23, NULL, 10, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, NULL, 0, 8, CURDATE()),
(24 ,NULL, 10, 1, NULL, CURDATE(), CURDATE(), 101, 'Bruce Wayne', 1, NULL, 0, 8, CURDATE());
(1 , 3, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Bat cave', 121, 'T1111111', 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(2 , 1, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T1111111', 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3 , 1, 7, 1, 6, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T2222222', 0, 3, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(4 , 3, 2, 1, 2, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T3333333', 0, 9, DATE_ADD(CURDATE(), INTERVAL -3 MONTH)),
(5 , 3, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T4444444', 0, 10, DATE_ADD(CURDATE(), INTERVAL -4 MONTH)),
(6 , 1, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Mountain Drive Gotham', 1, 'A1111111', 0, 10, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(7 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Mountain Drive Gotham', 1, NULL, 0, 3, CURDATE()),
(8 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Bat cave', 121, NULL, 0, 3, CURDATE()),
(9 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Stark tower', 124, NULL, 0, 3, CURDATE()),
(10, 1, 1, 5, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'Ingram Street', 2, NULL, 0, 1, CURDATE()),
(11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()),
(12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()),
(13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, CURDATE()),
(14, 1, 2, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()),
(15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()),
(16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(18, 1, 4, 4, 4, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, CURDATE()),
(19, 1, 5, 5, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, 13, CURDATE()),
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(21, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(22, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(23, NULL, 8, 1, 7, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, NULL, 0, 5, CURDATE()),
(24 ,NULL, 8, 1, 7, CURDATE(), CURDATE(), 101, 'Bruce Wayne', 1, NULL, 0, 5, CURDATE());
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES

View File

@ -358,7 +358,7 @@ describe('Worker time control path', () => {
it(`should check Hank Pym doesn't have hours set on the next months first week`, async() => {
const wholeWeekHours = await nightmare
.waitToClick(selectors.workerTimeControl.nextMonthButton)
.waitToClick(selectors.workerTimeControl.nextMonthButton)
.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '00:00 Hours')
.waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText');
expect(wholeWeekHours).toEqual('00:00 Hours');

View File

@ -30,6 +30,7 @@ describe('Ticket Edit basic data path', () => {
it(`should confirm the zone autocomplete is enabled for the role productionBoss`, async() => {
const disabled = await nightmare
.waitForSpinnerLoad()
.wait(selectors.ticketBasicData.zoneAutocomplete)
.evaluate(selector => {
return document.querySelector(selector).disabled;

View File

@ -0,0 +1,20 @@
<div class="node clickable">
<vn-icon
class="arrow"
ng-class="{invisible: !$ctrl.item.sons}"
icon="keyboard_arrow_down"
translate-attr="::{title: 'Toggle'}">
</vn-icon>
<section class="content"></section>
<section class="buttons" ng-if="::!$ctrl.treeview.readOnly">
<vn-icon-button translate-attr="::{title: 'Remove'}"
icon="delete"
ng-click="$ctrl.treeview.onRemove($ctrl.item)"
ng-if="$ctrl.item.parent">
</vn-icon-button>
<vn-icon-button translate-attr="::{title: 'Create'}"
icon="add_circle"
ng-click="$ctrl.treeview.onCreate($ctrl.item)">
</vn-icon-button>
</section>
</div>

View File

@ -2,22 +2,33 @@ import ngModule from '../../module';
class Controller {
constructor($element, $scope, $compile) {
this.$element = $element;
this.$scope = $scope;
this.$compile = $compile;
this.element = $element[0];
this.element.$ctrl = this;
this.element.droppable = true;
this.dropCount = 0;
this.element.classList.add('vn-droppable');
}
$onInit() {
const transcludeElement = this.element.querySelector('.content');
const content = angular.element(transcludeElement);
if (this.item.parent) {
this.treeview.$transclude(($clone, $scope) => {
this.$contentScope = $scope;
$scope.item = this.item;
this.$element.append($clone);
content.append($clone);
});
this.element.draggable = true;
this.element.classList.add('vn-draggable');
} else {
let template = `<span translate>{{$ctrl.treeview.rootLabel}}</span>`;
let $clone = this.$compile(template)(this.$scope);
this.$element.append($clone);
content.append($clone);
}
}
@ -25,10 +36,21 @@ class Controller {
if (this.$contentScope)
this.$contentScope.$destroy();
}
dragEnter() {
this.dropCount++;
if (element != this.dropping) {
this.undrop();
if (element) element.classList.add('dropping');
this.dropping = element;
}
}
}
Controller.$inject = ['$element', '$scope', '$compile'];
ngModule.component('vnTreeviewContent', {
ngModule.component('vnTreeviewChild', {
template: require('./child.html'),
controller: Controller,
bindings: {
item: '<'

View File

@ -1,34 +1,11 @@
<ul ng-if="$ctrl.items">
<li ng-repeat="item in $ctrl.items" >
<div
ng-class="{expanded: item.active}"
ng-click="$ctrl.onClick($event, item)"
class="node clickable">
<vn-icon
class="arrow"
ng-class="{invisible: !item.sons}"
icon="keyboard_arrow_down"
translate-attr="::{title: 'Toggle'}">
</vn-icon>
<vn-treeview-content
item="::item">
</vn-treeview-content>
<section class="buttons" ng-if="::!$ctrl.treeview.readOnly">
<vn-icon-button translate-attr="::{title: 'Remove'}"
icon="delete"
ng-click="$ctrl.treeview.onRemove(item)"
ng-if="item.parent">
</vn-icon-button>
<vn-icon-button translate-attr="::{title: 'Create'}"
icon="add_circle"
ng-click="$ctrl.treeview.onCreate(item)">
</vn-icon-button>
</section>
</div>
<li ng-repeat="item in $ctrl.items">
<vn-treeview-child item="item" ng-class="{expanded: item.active}"
ng-click="$ctrl.onClick($event, item)">
</vn-treeview-child>
<vn-treeview-childs
items="item.childs"
parent="::item">
items="item.childs">
</vn-treeview-childs>
</li>
</ul>

View File

@ -7,10 +7,6 @@ class Controller extends Component {
event.preventDefault();
this.treeview.onToggle(item);
}
onDrop(item, dragged, dropped) {
this.treeview.onDrop(item, dragged, dropped);
}
}
ngModule.component('vnTreeviewChilds', {

View File

@ -1,18 +1,110 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import './style.scss';
import './childs';
import './content';
import './child';
/**
* Treeview
*/
export default class Treeview extends Component {
constructor($element, $scope, $transclude) {
constructor($element, $scope, $transclude, $window) {
super($element, $scope);
this.$transclude = $transclude;
this.$window = $window;
this.readOnly = true;
this.element.addEventListener('dragstart',
event => this.dragStart(event));
this.element.addEventListener('dragend',
event => this.dragEnd(event));
this.element.addEventListener('dragover',
event => this.dragOver(event));
this.element.addEventListener('drop',
event => this.drop(event));
this.element.addEventListener('dragenter',
event => this.dragEnter(event));
this.element.addEventListener('dragleave',
event => this.dragLeave(event));
this.dropCount = 0;
}
undrop() {
if (!this.dropping) return;
this.dropping.classList.remove('dropping');
this.dropping = null;
}
findDroppable(event) {
let element = event.target;
while (element != this.element && !element.droppable)
element = element.parentNode;
if (element == this.element)
return null;
return element;
}
dragOver(event) {
this.dragClientY = event.clientY;
// Prevents page reload
event.preventDefault();
}
onDragInterval() {
if (this.dragClientY > 0 && this.dragClientY < 75)
this.$window.scrollTo(0, this.$window.scrollY - 25);
}
dragStart(event) {
event.target.classList.add('dragging');
event.dataTransfer.setData('text', event.target.id);
const element = this.findDroppable(event);
this.dragging = element;
this.interval = setInterval(() => this.onDragInterval(), 100);
}
dragEnd(event) {
event.target.classList.remove('dragging');
this.undrop();
this.dropCount = 0;
this.dragging = null;
clearInterval(this.interval);
}
dragEnter(event) {
let element = this.findDroppable(event);
if (element) this.dropCount++;
if (element != this.dropping) {
this.undrop();
if (element) element.classList.add('dropping');
this.dropping = element;
}
}
dragLeave(event) {
let element = this.findDroppable(event);
if (element) {
this.dropCount--;
if (this.dropCount == 0) this.undrop();
}
}
drop(event) {
event.preventDefault();
this.element.classList.remove('dropping');
const $dropped = this.dropping.$ctrl.item;
const $dragged = this.dragging.$ctrl.item;
if ($dropped != $dragged.parent)
this.emit('drop', {$dropped, $dragged});
}
get data() {
@ -109,7 +201,10 @@ export default class Treeview extends Component {
let childs = parent.childs;
if (!childs) childs = [];
childs.push(item);
if (!parent.active)
this.unfold(parent);
else
childs.push(item);
if (this.sortFunc) {
childs = childs.sort((a, b) =>
@ -120,12 +215,30 @@ export default class Treeview extends Component {
if (parent) parent.sons++;
}
onDrop(item, dragged, dropped) {
this.emit('drop', {item, dragged, dropped});
move(item, newParent) {
if (newParent == item) return;
if (item.parent) {
const parent = item.parent;
const childs = parent.childs;
const index = childs.indexOf(item);
parent.sons--;
childs.splice(index, 1);
}
item.parent = newParent;
if (!newParent.active) {
this.unfold(newParent).then(() => {
item.parent.sons++;
});
} else
this.create(item);
}
}
Treeview.$inject = ['$element', '$scope', '$transclude'];
Treeview.$inject = ['$element', '$scope', '$transclude', '$window'];
ngModule.component('vnTreeview', {
template: require('./index.html'),

View File

@ -0,0 +1,260 @@
describe('Component vnTreeview', () => {
let controller;
let $element;
beforeEach(angular.mock.module('vnCore', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(inject(($compile, $rootScope) => {
$element = $compile(`<vn-treeview></vn-treeview>`)($rootScope);
controller = $element.controller('vnTreeview');
const promise = new Promise(() => {
return {name: 'My item'};
});
controller.fetchFunc = () => {
return promise;
};
controller.createFunc = () => {
return promise;
};
controller.removeFunc = () => {
return promise;
};
controller.sortFunc = () => {
return promise;
};
}));
afterEach(() => {
$element.remove();
});
// See how to test DOM element in Jest
xdescribe('undrop()', () => {
it(`should reset all drop events and properties`, () => {
controller.dropping = angular.element(`<vn-treeview-child class="dropping"></vn-treeview-child>`);
spyOn(controller.dropping.classList, 'remove');
controller.undrop();
expect(controller.dropping).toBeNull();
});
});
describe('dragOver()', () => {
it(`should set the dragClientY property`, () => {
const event = new Event('dragover');
event.clientY = 100;
controller.dragOver(event);
expect(controller.dragClientY).toEqual(100);
});
});
describe('data() setter', () => {
it(`should set the items property nested into a root element`, () => {
const items = [{name: 'Item1'}, {name: 'Item2'}];
controller.data = items;
const rootItem = controller.items[0];
expect(rootItem.childs).toEqual(items);
});
});
describe('fetch()', () => {
it(`should call the fetchFunc() method`, () => {
spyOn(controller, 'fetchFunc').and.returnValue(
new Promise(resolve => resolve([{name: 'My item'}]))
);
controller.fetch().then(() => {
expect(controller.data).toBeDefined();
});
expect(controller.fetchFunc).toHaveBeenCalledWith();
});
});
describe('setParent()', () => {
it(`should set the parent property recursively to each element of an item list`, () => {
spyOn(controller, 'setParent').and.callThrough();
const items = [{name: 'Item1'}, {name: 'Item2', childs: [
{name: 'Item3'}
]}];
const rootItem = {name: 'Nested tree', sons: items};
controller.setParent(rootItem, items);
expect(items[0].parent).toEqual(rootItem);
expect(items[1].parent).toEqual(rootItem);
expect(controller.setParent).toHaveBeenCalledWith(rootItem, items[1].childs);
});
});
describe('onToggle()', () => {
it(`should call the fold() or unfold() methods`, () => {
spyOn(controller, 'fold');
spyOn(controller, 'unfold');
const item = {name: 'My item'};
controller.onToggle(item);
item.active = true;
controller.onToggle(item);
expect(controller.unfold).toHaveBeenCalledWith(item);
expect(controller.fold).toHaveBeenCalledWith(item);
});
});
describe('fold()', () => {
it(`should remove the childs and set the active property to false`, () => {
const item = {name: 'My item', childs: [{name: 'Item 1'}], active: true};
controller.fold(item);
expect(item.childs).toBeUndefined();
expect(item.active).toBeFalsy();
});
});
describe('unfold()', () => {
it(`should unfold a parent item`, () => {
const expectedResponse = [{name: 'Item 1'}, {name: 'Item 2'}];
spyOn(controller, 'fetchFunc').and.returnValue(
new Promise(resolve => resolve(expectedResponse))
);
spyOn(controller, 'setParent');
spyOn(controller, 'sortFunc');
const parent = {name: 'My item', sons: 1};
const child = {name: 'Item 1'};
child.parent = parent;
parent.childs = [child];
controller.unfold(parent).then(() => {
expect(controller.fetchFunc).toHaveBeenCalledWith({$item: parent});
expect(controller.setParent).toHaveBeenCalledWith(parent, expectedResponse);
expect(controller.sortFunc).toHaveBeenCalledWith(jasmine.any(Object));
expect(parent.active).toBeTruthy();
});
});
});
describe('onRemove()', () => {
it(`should call the removeFunc() method`, () => {
spyOn(controller, 'removeFunc');
const item = {name: 'My item'};
controller.onRemove(item);
expect(controller.removeFunc).toHaveBeenCalledWith({$item: item});
});
});
describe('remove()', () => {
it(`should remove a child element`, () => {
const parent = {name: 'My item', sons: 1};
const child = {name: 'Item 1'};
child.parent = parent;
parent.childs = [child];
controller.remove(child);
expect(parent.childs).toEqual([]);
expect(parent.sons).toEqual(0);
});
});
describe('onCreate()', () => {
it(`should call the createFunc() method`, () => {
spyOn(controller, 'createFunc');
const parent = {name: 'My item'};
controller.onCreate(parent);
expect(controller.createFunc).toHaveBeenCalledWith({$parent: parent});
});
});
describe('create()', () => {
it(`should unfold an inactive parent and then create a child`, () => {
spyOn(controller, 'unfold');
spyOn(controller, 'sortFunc');
const parent = {name: 'My item', sons: 2, childs: [
{name: 'Item 1'},
{name: 'Item 2'}
]};
const child = {name: 'Item 3'};
child.parent = parent;
parent.childs.push(child);
controller.create(child);
expect(parent.sons).toEqual(3);
expect(controller.unfold).toHaveBeenCalledWith(parent);
expect(controller.sortFunc).toHaveBeenCalledWith(jasmine.any(Object));
expect(controller.sortFunc).toHaveBeenCalledTimes(2);
});
it(`should create a child on an active parent`, () => {
spyOn(controller, 'unfold');
spyOn(controller, 'sortFunc');
const parent = {name: 'My item', sons: 2, childs: [
{name: 'Item 1'},
{name: 'Item 2'}
], active: true};
const child = {name: 'Item 3'};
child.parent = parent;
controller.create(child);
expect(parent.sons).toEqual(3);
expect(controller.unfold).not.toHaveBeenCalledWith(parent);
expect(controller.sortFunc).toHaveBeenCalledWith(jasmine.any(Object));
expect(controller.sortFunc).toHaveBeenCalledTimes(2);
});
});
describe('move()', () => {
it(`should move an item to anocher parent and then unfold the parent`, () => {
spyOn(controller, 'unfold').and.returnValue(
new Promise(resolve => resolve())
);
const newParent = {name: 'My item 2', sons: 0};
const parent = {name: 'My item', sons: 3, childs: [
{name: 'Item 1'},
{name: 'Item 2'}
]};
const child = {name: 'Item 3'};
child.parent = parent;
parent.childs.push(child);
controller.move(child, newParent);
expect(parent.sons).toEqual(2);
expect(controller.unfold).toHaveBeenCalledWith(newParent);
});
it(`should move an item to anocher parent`, () => {
spyOn(controller, 'unfold');
spyOn(controller, 'create');
const newParent = {name: 'My item 2', sons: 0, active: true};
const parent = {name: 'My item', sons: 3, childs: [
{name: 'Item 1'},
{name: 'Item 2'}
]};
const child = {name: 'Item 3'};
child.parent = parent;
parent.childs.push(child);
controller.move(child, newParent);
expect(parent.sons).toEqual(2);
expect(controller.unfold).not.toHaveBeenCalledWith(newParent);
});
});
});

View File

@ -10,22 +10,6 @@ vn-treeview-childs {
li {
list-style: none;
& > .node {
@extend %clickable;
display: flex;
padding: 5px;
align-items: center;
}
& > div > .arrow {
min-width: 24px;
margin-right: 10px;
transition: transform 200ms;
}
& > div.expanded > .arrow {
transform: rotate(180deg);
}
ul {
padding-left: 2.2em;
}
@ -45,8 +29,28 @@ vn-treeview-childs {
.node:hover > .buttons {
display: block
}
.content {
flex-grow: 1
}
}
vn-treeview-content {
flex-grow: 1
}
vn-treeview-child {
font-size: 16px;
display: block;
.node {
@extend %clickable;
display: flex;
padding: 5px;
align-items: center;
}
& > div > .arrow {
min-width: 24px;
margin-right: 10px;
transition: transform 200ms;
}
&.expanded > div > .arrow {
transform: rotate(180deg);
}
}

View File

@ -1,43 +0,0 @@
import ngModule from '../module';
/**
* Enables a draggable element and his drag events
*
* @return {Object} The directive
*/
export function directive() {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
const element = $element[0];
const isDraggable = $attrs.vnDraggable === 'true';
if (!isDraggable) return;
// Set draggable style properties
element.style.cursor = 'move';
// Enable as draggable element
element.setAttribute('draggable', true);
/**
* Fires when a drag event starts
*/
element.addEventListener('dragstart', event => {
element.style.opacity = 0.5;
event.stopPropagation();
});
/**
* Fires when a drag event ends
*/
element.addEventListener('dragend', event => {
element.style.opacity = 1;
event.stopPropagation();
});
}
};
}
ngModule.directive('vnDraggable', directive);

View File

@ -1,68 +1,43 @@
import ngModule from '../module';
import './droppable.scss';
export function directive($parse) {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
const element = $element[0];
const onDropEvent = $parse($attrs.onDrop);
const isDroppable = $attrs.vnDroppable === 'true';
class Controller {
constructor($element, $, $attrs) {
this.element = $element[0];
this.$ = $;
this.$attrs = $attrs;
if (!isDroppable) return;
this.element.addEventListener('dragover',
event => event.preventDefault()); // Prevents page reload
this.element.addEventListener('drop',
event => this.drop(event));
this.element.addEventListener('dragenter',
event => this.dragEnter(event));
this.element.addEventListener('dragleave',
event => this.dragLeave(event));
}
/**
* Captures current dragging element
*/
element.addEventListener('dragstart', () => {
this.dragged = element;
});
dragEnter(event) {
this.droppedElement = event.target;
this.element.classList.add('dropping');
}
/**
* Enter droppable area event
*/
element.addEventListener('dragenter', event => {
element.classList.add('active');
dragLeave(event) {
if (this.droppedElement === event.target)
this.element.classList.remove('dropping');
}
event.stopImmediatePropagation();
event.preventDefault();
}, false);
/**
* Exit droppable area event
*/
element.addEventListener('dragleave', event => {
element.classList.remove('active');
event.stopImmediatePropagation();
event.preventDefault();
});
/**
* Prevent dragover for allowing
* dispatch drop event
*/
element.addEventListener('dragover', event => {
event.stopPropagation();
event.preventDefault();
});
/**
* Fires when a drop events
*/
element.addEventListener('drop', event => {
element.classList.remove('active');
onDropEvent($scope, {event});
event.stopPropagation();
event.preventDefault();
});
}
};
drop(event) {
if (event.defaultPrevented) return;
event.preventDefault();
this.element.classList.remove('dropping');
this.$.$eval(this.$attrs.vnDroppable, {$event: event});
}
}
Controller.$inject = ['$element', '$scope', '$attrs'];
directive.$inject = ['$parse'];
ngModule.directive('vnDroppable', directive);
ngModule.directive('vnDroppable', () => {
return {
controller: Controller
};
});

View File

@ -1,11 +1,25 @@
@import "./variables";
.vn-droppable,
.vn-draggable,
[vn-droppable] {
border: 2px dashed transparent;
border-radius: 0.5em;
transition: all 0.5s;
}
&.active {
.vn-droppable,
[vn-droppable] {
display: block;
&.dropping {
background-color: $color-hover-cd;
border: 2px dashed $color-bg-dark;
border-color: $color-bg-dark;
}
}
.vn-draggable.dragging {
background-color: $color-main-light;
border-color: $color-main;
}

View File

@ -11,7 +11,6 @@ import './bind';
import './repeat-last';
import './title';
import './uvc';
import './draggable';
import './droppable';
import './http-click';
import './http-submit';

View File

@ -24,10 +24,12 @@ export default class EventEmitter {
*/
off(callback) {
if (!this.$events) return;
for (let event in this.$events)
for (let i = 0; i < event.length; i++)
for (let event in this.$events) {
for (let i = 0; i < event.length; i++) {
if (event[i].callback === callback)
event.splice(i--, 1);
}
}
}
/**
@ -37,10 +39,12 @@ export default class EventEmitter {
*/
disconnect(thisArg) {
if (!this.$events) return;
for (let event in this.$events)
for (let i = 0; i < event.length; i++)
for (let event in this.$events) {
for (let i = 0; i < event.length; i++) {
if (event[i].thisArg === thisArg)
event.splice(i--, 1);
}
}
}
/**
@ -72,9 +76,10 @@ export default class EventEmitter {
if (this[prop])
this[prop].disconnect(this);
this[prop] = value;
if (value)
if (value) {
for (let event in handlers)
value.on(event, handlers[event], this);
}
}
}
}

View File

@ -109,6 +109,8 @@
"is invalid": "is invalid",
"The postcode doesn't exists. Ensure you put the correct format": "El código postal no existe. Asegúrate de ponerlo con el formato correcto",
"The department name can't be repeated": "El nombre del departamento no puede repetirse",
"You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado",
"This phone already exists": "Este teléfono ya existe"
"This phone already exists": "Este teléfono ya existe",
"You cannot move a parent to any of its sons": "You cannot move a parent to any of its sons",
"You cannot move a parent to its own sons": "You cannot move a parent to its own sons",
"You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado"
}

View File

@ -1,14 +1,14 @@
@import "variables";
vn-treeview-content {
& > vn-check:not(.indeterminate) {
vn-treeview-child {
.content > vn-check:not(.indeterminate) {
color: $color-main;
& > .btn {
border-color: $color-main;
}
}
& > vn-check.checked {
.content > vn-check.checked {
color: $color-main;
}
}

View File

@ -1,121 +1,116 @@
<vn-crud-model auto-load="false"
<vn-crud-model auto-load="true"
vn-id="model"
url="claim/api/ClaimEnds"
filter="$ctrl.filter"
data="$ctrl.salesClaimed">
</vn-crud-model>
<vn-vertical compact>
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal>
<div class="totalBox" ng-show="$ctrl.salesClaimed.length > 0">
<vn-label-value label="Total claimed"
value="{{$ctrl.claimedTotal | currency: 'EUR':2}}">
</vn-label-value>
</div>
</vn-horizontal>
<vn-horizontal>
<vn-tool-bar class="vn-mb-md">
<vn-button
label="Import claim"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedState"
vn-http-click="$ctrl.importToNewRefundTicket()"p
vn-tooltip="Imports claim details">
</vn-button>
<vn-button
label="Import ticket"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedState"
ng-click="$ctrl.showLastTickets($event)"
vn-tooltip="Imports ticket lines">
</vn-button>
<vn-input-range
vn-one
label="Responsability"
value="$ctrl.claim.responsibility"
max="$ctrl.maxResponsibility"
min="1"
step="1"
vn-acl="salesAssistant"
on-change="$ctrl.saveResponsibility(value)">
</vn-input-range>
</vn-tool-bar>
<vn-one
style = "text-align:right">
<vn-check
vn-one
label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana"
vn-acl="salesAssistant"
on-change="$ctrl.saveMana(value)">
</vn-check>
</vn-one>
</vn-horizontal>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th number>Id</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Destination</vn-th>
<vn-th>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc.</vn-th>
<vn-th number>Total</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="saleClaimed in $ctrl.salesClaimed"
vn-repeat-last on-last="$ctrl.focusLastInput()">
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, saleClaimed.sale.itemFk)"
class="link">
{{saleClaimed.sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>
<span
class="link"
ng-click="$ctrl.showTicketDescriptor($event, saleClaimed.sale.ticketFk)">
{{::saleClaimed.sale.ticketFk}}
<vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right"
ng-if="$ctrl.salesClaimed.length > 0">
<vn-label-value label="Total claimed"
value="{{$ctrl.claimedTotal | currency: 'EUR':2}}">
</vn-label-value>
</vn-card>
<vn-card class="vn-pa-lg vn-w-lg">
<section class="header">
<vn-tool-bar class="vn-mb-md">
<vn-button
label="Import claim"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedState"
vn-http-click="$ctrl.importToNewRefundTicket()"p
translate-attr="{title: 'Imports claim details'}">
</vn-button>
<vn-button
label="Import ticket"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedState"
ng-click="$ctrl.showLastTickets($event)"
translate-attr="{title: 'Imports ticket lines'}">
</vn-button>
<vn-input-range vn-none
label="Responsability"
value="$ctrl.claim.responsibility"
max="$ctrl.maxResponsibility"
min="1"
step="1"
vn-acl="salesAssistant"
on-change="$ctrl.saveResponsibility(value)">
</vn-input-range>
</vn-tool-bar>
<vn-check vn-one
label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana"
vn-acl="salesAssistant"
on-change="$ctrl.saveMana(value)">
</vn-check>
</section>
<vn-data-viewer model="model">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th number>Id</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Destination</vn-th>
<vn-th>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc.</vn-th>
<vn-th number>Total</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="saleClaimed in $ctrl.salesClaimed"
vn-repeat-last on-last="$ctrl.focusLastInput()">
<vn-td number>
<span
ng-click="$ctrl.showDescriptor($event, saleClaimed.sale.itemFk)"
class="link">
{{::saleClaimed.sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td>
<vn-autocomplete
vn-one
id="claimDestinationFk"
ng-model="saleClaimed.claimDestinationFk"
url="/claim/api/ClaimDestinations"
fields="['id','description']"
value-field="id"
show-field="description"
on-change="$ctrl.setClaimDestination(saleClaimed.id, value)">
</vn-autocomplete>
</vn-td>
<vn-td>{{saleClaimed.sale.ticket.landed | dateTime: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{saleClaimed.sale.quantity}}</vn-td>
<vn-td expand>{{saleClaimed.sale.concept}}</vn-td>
<vn-td number>{{saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>{{saleClaimed.sale.discount}} %</vn-td>
<vn-td number>
{{saleClaimed.sale.quantity * saleClaimed.sale.price *
((100 - saleClaimed.sale.discount) / 100) | currency: 'EUR':2}}
</vn-td>
<vn-td shrink>
<vn-icon-button
vn-tooltip="Remove line"
icon="delete"
ng-click="$ctrl.deleteClaimedSale(saleClaimed.id)"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-vertical>
</vn-card>
</vn-td>
<vn-td number>
<span
class="link"
ng-click="$ctrl.showTicketDescriptor($event, saleClaimed.sale.ticketFk)">
{{::saleClaimed.sale.ticketFk}}
</span>
</vn-td>
<vn-td>
<vn-autocomplete vn-one
id="claimDestinationFk"
ng-model="saleClaimed.claimDestinationFk"
url="/claim/api/ClaimDestinations"
fields="['id','description']"
value-field="id"
show-field="description"
on-change="$ctrl.setClaimDestination(saleClaimed.id, value)">
</vn-autocomplete>
</vn-td>
<vn-td>{{::saleClaimed.sale.ticket.landed | dateTime: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::saleClaimed.sale.quantity}}</vn-td>
<vn-td expand>{{::saleClaimed.sale.concept}}</vn-td>
<vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::saleClaimed.sale.discount}} %</vn-td>
<vn-td number>
{{saleClaimed.sale.quantity * saleClaimed.sale.price *
((100 - saleClaimed.sale.discount) / 100) | currency: 'EUR':2}}
</vn-td>
<vn-td shrink>
<vn-icon-button
vn-tooltip="Remove line"
icon="delete"
ng-click="$ctrl.deleteClaimedSale(saleClaimed.id)"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-data-viewer>
<vn-button-bar>
<vn-button
label="Regularize"
@ -123,11 +118,7 @@
vn-http-click="$ctrl.regularize()">
</vn-button>
</vn-button-bar>
<!-- WIP
<a ng-click="$ctrl.openAddSalesDialog()" vn-tooltip="New item" vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
-->
</vn-card>
<!-- Add Lines Dialog -->
<vn-dialog vn-id="addSales">

View File

@ -1,4 +1,19 @@
vn-claim-action {
.header {
display: flex;
justify-content: space-between;
align-items: center;
align-content: center;
vn-tool-bar {
flex: 1
}
vn-check {
flex: none;
}
}
vn-dialog[vn-id=addSales] {
tpl-body {
width: 950px;

View File

@ -5,7 +5,7 @@
data="$ctrl.photos">
</vn-crud-model>
<section class="drop-zone" vn-droppable="true" on-drop="$ctrl.onDrop(event)">
<section class="drop-zone" vn-droppable="$ctrl.onDrop($event)">
<section><vn-icon icon="add_circle"></vn-icon></section>
<section translate>Drag & Drop files here...</section>
</section>

View File

@ -36,8 +36,9 @@ class Controller {
}
}
onDrop(event) {
const files = event.dataTransfer.files;
onDrop($event) {
console.log($event);
const files = $event.dataTransfer.files;
this.setDefaultParams().then(() => {
this.dms.files = files;
this.create();

View File

@ -117,6 +117,6 @@ describe('Route filter()', () => {
let result = await app.models.Route.filter(ctx);
expect(result.length).toEqual(4);
expect(result.length).toEqual(1);
});
});

View File

@ -4,6 +4,6 @@ describe('route getTickets()', () => {
it('should return the tickets for a given route', async() => {
let result = await app.models.Route.getTickets(2);
expect(result.length).toEqual(4);
expect(result.length).toEqual(1);
});
});

View File

@ -1,7 +1,7 @@
const app = require('vn-loopback/server/server');
describe('route guessPriority()', () => {
const targetRouteId = 2;
const targetRouteId = 7;
let routeTicketsToRestore;
afterAll(async done => {
@ -17,25 +17,23 @@ describe('route guessPriority()', () => {
it('should confirm the tickets in the target route have no priority yet', async() => {
routeTicketsToRestore = await app.models.Ticket.find({where: {routeFk: targetRouteId}});
expect(routeTicketsToRestore.length).toEqual(4);
expect(routeTicketsToRestore.length).toEqual(2);
expect(routeTicketsToRestore[0].id).toEqual(23);
expect(routeTicketsToRestore[0].priority).toBeNull();
expect(routeTicketsToRestore[0].id).toEqual(7);
expect(routeTicketsToRestore[1].id).toEqual(24);
expect(routeTicketsToRestore[1].priority).toBeNull();
expect(routeTicketsToRestore[1].id).toEqual(8);
expect(routeTicketsToRestore[2].priority).toBeNull();
expect(routeTicketsToRestore[2].id).toEqual(9);
});
it('should call guessPriority() and then check the tickets in the target route now have their priorities defined', async() => {
await app.models.Route.guessPriority(targetRouteId);
let routeTickets = await app.models.Ticket.find({where: {routeFk: targetRouteId}, fields: ['id', 'priority']});
expect(routeTickets.length).toEqual(4);
expect(routeTickets[0].priority).toEqual(1);
expect(routeTickets[0].id).toEqual(7);
expect(routeTickets[1].priority).toEqual(3);
expect(routeTickets[1].id).toEqual(8);
expect(routeTickets[2].priority).toEqual(2);
expect(routeTickets[2].id).toEqual(9);
expect(routeTickets.length).toEqual(2);
expect(routeTickets[0].id).toEqual(23);
expect(routeTickets[0].priority).toEqual(3);
expect(routeTickets[1].id).toEqual(24);
expect(routeTickets[1].priority).toEqual(1);
});
});

View File

@ -11,7 +11,7 @@ describe('route summary()', () => {
const result = await app.models.Route.summary(1);
const agency = result.route.agencyMode();
expect(agency.name).toEqual('Silla247');
expect(agency.name).toEqual('inhouse pickup');
});
it(`should return a summary object containing it's vehicle`, async() => {
@ -31,6 +31,6 @@ describe('route summary()', () => {
it(`should return a summary object containing data from the tickets`, async() => {
const result = await app.models.Route.summary(2);
expect(result.tickets.length).toEqual(4);
expect(result.tickets.length).toEqual(1);
});
});

View File

@ -10,8 +10,8 @@ describe('route updateVolume()', () => {
afterAll(async done => {
await originalRoute.updateAttributes({m3: 2.7});
await ticketToRestore.updateAttributes({routeFk: 2});
await originalRoute.updateAttributes({m3: 1.8});
await ticketToRestore.updateAttributes({routeFk: null});
await app.models.RouteLog.destroyById(logIdToDestroy);
done();
});
@ -19,12 +19,12 @@ describe('route updateVolume()', () => {
it('should confirm the original volume of the route is the expected', async() => {
originalRoute = await app.models.Route.findById(routeId);
expect(originalRoute.m3).toEqual(2.7);
expect(originalRoute.m3).toEqual(1.8);
});
it('should confirm the route volume is updated when a ticket is added', async() => {
ticketToRestore = await app.models.Ticket.findById(8);
let updatedTicket = await app.models.Ticket.findById(8);
ticketToRestore = await app.models.Ticket.findById(14);
let updatedTicket = await app.models.Ticket.findById(14);
await updatedTicket.updateAttributes({routeFk: routeId});
await app.models.Route.updateVolume(ctx, routeId);
@ -36,8 +36,9 @@ describe('route updateVolume()', () => {
it('should confirm the change is logged', async() => {
let logs = await app.models.RouteLog.find({fields: ['id', 'newInstance']});
let m3Log = logs.filter(log => {
return log.newInstance.m3 === 3.1;
return log.newInstance.m3 === 1.9;
});
logIdToDestroy = m3Log[0].id;

View File

@ -1,6 +1,6 @@
import './index.js';
fdescribe('Ticket component vnTicketService', () => {
describe('Ticket component vnTicketService', () => {
let controller;
let $httpBackend;
let $httpParamSerializer;

View File

@ -0,0 +1,42 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('moveChild', {
description: 'Changes the parent of a child department',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'Number',
description: 'The department id',
http: {source: 'path'}
}, {
arg: 'parentId',
type: 'Number',
description: 'New parent id',
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/moveChild`,
verb: 'POST'
}
});
Self.moveChild = async(id, parentId = null) => {
const models = Self.app.models;
const child = await models.Department.findById(id);
if (id == parentId) return;
if (parentId) {
const parent = await models.Department.findById(parentId);
if (child.lft < parent.lft && child.rgt > parent.rgt)
throw new UserError('You cannot move a parent to its own sons');
}
return child.updateAttribute('parentFk', parentId);
};
};

View File

@ -0,0 +1,18 @@
const app = require('vn-loopback/server/server');
describe('department createChild()', () => {
let createdChild;
afterAll(async done => {
await createdChild.destroy();
done();
});
it('should create a new child', async() => {
const parentId = null;
createdChild = await app.models.Department.createChild(parentId, 'My new department');
expect(createdChild.name).toEqual('My new department');
expect(createdChild.parentFk).toBeNull();
});
});

View File

@ -0,0 +1,23 @@
const app = require('vn-loopback/server/server');
describe('department moveChild()', () => {
let updatedChild;
afterAll(async done => {
const child = await app.models.Department.findById(updatedChild.id);
await child.updateAttribute('parentFk', null);
done();
});
it('should move a child department to a new parent', async() => {
const childId = 22;
const parentId = 1;
const child = await app.models.Department.findById(childId);
expect(child.parentFk).toBeNull();
updatedChild = await app.models.Department.moveChild(childId, parentId);
expect(updatedChild.parentFk).toEqual(1);
});
});

View File

@ -0,0 +1,21 @@
const app = require('vn-loopback/server/server');
describe('department removeChild()', () => {
let removedChild;
afterAll(async done => {
await app.models.Department.create(removedChild);
done();
});
it('should remove a child department', async() => {
const childId = 1;
removedChild = await app.models.Department.findById(childId);
const result = await app.models.Department.removeChild(childId);
const existsChild = await app.models.Department.findById(childId);
expect(result.count).toEqual(1);
expect(existsChild).toBeNull();
});
});

View File

@ -2,4 +2,5 @@ module.exports = Self => {
require('../methods/department/getLeaves')(Self);
require('../methods/department/createChild')(Self);
require('../methods/department/removeChild')(Self);
require('../methods/department/moveChild')(Self);
};

View File

@ -16,6 +16,15 @@
},
"parentFk": {
"type": "Number"
},
"lft": {
"type": "Number"
},
"rgt": {
"type": "Number"
},
"sons": {
"type": "Number"
}
}
}

View File

@ -10,7 +10,10 @@
fetch-func="$ctrl.onFetch($item)"
remove-func="$ctrl.onRemove($item)"
create-func="$ctrl.onCreate($parent)"
sort-func="$ctrl.onSort($a, $b)">
sort-func="$ctrl.onSort($a, $b)"
on-drop="$ctrl.onDrop($dropped, $dragged)"
on-drag-start="$ctrl.onDragStart(item)"
on-drag-end="$ctrl.onDragEnd(item)">
{{::item.name}}
</vn-treeview>
</vn-card>

View File

@ -23,19 +23,13 @@ class Controller {
return a.name.localeCompare(b.name);
}
/* onDrop(item, dragged, dropped) {
if (dropped.scope.item) {
const droppedItem = dropped.scope.item;
const draggedItem = dragged.scope.item;
if (droppedItem.childs)
droppedItem.childs.push(Object.assign({}, draggedItem));
dragged.element.remove();
this.$scope.$apply();
}
} */
onDrop(dropped, dragged) {
const params = dropped ? {parentId: dropped.id} : null;
const query = `/api/departments/${dragged.id}/moveChild`;
this.$http.post(query, params).then(() => {
this.$.treeview.move(dragged, dropped);
});
}
onCreate(parent) {
this.newChild = {
@ -62,12 +56,8 @@ class Controller {
if (parent && parent.id)
params.parentId = parent.id;
if (!parent.active)
this.$.treeview.unfold(parent);
const query = `/api/departments/createChild`;
this.$http.post(query, params).then(res => {
const parent = this.newChild.parent;
const item = res.data;
item.parent = parent;