diff --git a/db/changes/10091-iberflora/00-department.sql b/db/changes/10091-iberflora/00-department.sql
new file mode 100644
index 000000000..5c2889985
--- /dev/null
+++ b/db/changes/10091-iberflora/00-department.sql
@@ -0,0 +1,83 @@
+ALTER TABLE `vn2008`.`department`
+ADD COLUMN `parentFk` INT UNSIGNED NULL AFTER `sons`,
+ADD COLUMN `path` VARCHAR(255) NULL AFTER `parentFk`,
+CHANGE COLUMN `sons` `sons` DECIMAL(10,0) NOT NULL DEFAULT '0' ;
+
+USE `vn`;
+CREATE
+ OR REPLACE ALGORITHM = UNDEFINED
+ DEFINER = `root`@`%`
+ SQL SECURITY DEFINER
+VIEW `department` AS
+ SELECT
+ `b`.`department_id` AS `id`,
+ `b`.`name` AS `name`,
+ `b`.`production` AS `isProduction`,
+ `b`.`parentFk` AS `parentFk`,
+ `b`.`path` AS `path`,
+ `b`.`lft` AS `lft`,
+ `b`.`rgt` AS `rgt`,
+ `b`.`isSelected` AS `isSelected`,
+ `b`.`depth` AS `depth`,
+ `b`.`sons` AS `sons`
+ FROM
+ `vn2008`.`department` `b`;
+
+DROP TRIGGER IF EXISTS `vn2008`.`department_AFTER_DELETE`;
+
+DELIMITER $$
+USE `vn2008`$$
+CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_AFTER_DELETE`
+ AFTER DELETE ON `department` FOR EACH ROW
+BEGIN
+ UPDATE vn.department_recalc SET isChanged = TRUE;
+END$$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS `vn2008`.`department_BEFORE_INSERT`;
+
+DELIMITER $$
+USE `vn2008`$$
+CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_BEFORE_INSERT`
+ BEFORE INSERT ON `department` FOR EACH ROW
+BEGIN
+ UPDATE vn.department_recalc SET isChanged = TRUE;
+END$$
+DELIMITER ;
+
+DROP TRIGGER IF EXISTS `vn2008`.`department_AFTER_UPDATE`;
+
+DELIMITER $$
+USE `vn2008`$$
+CREATE DEFINER = CURRENT_USER TRIGGER `vn2008`.`department_AFTER_UPDATE`
+ AFTER UPDATE ON `department` FOR EACH ROW
+BEGIN
+ IF !(OLD.parentFk <=> NEW.parentFk) THEN
+ UPDATE vn.department_recalc SET isChanged = TRUE;
+ END IF;
+END$$
+DELIMITER ;
+
+CREATE TABLE `vn`.`department_recalc` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `isChanged` TINYINT(4) NOT NULL,
+ PRIMARY KEY (`id`));
+
+INSERT INTO `vn`.`department_recalc` (`id`, `isChanged`) VALUES ('1', '0');
+
+
+ALTER TABLE `vn2008`.`department`
+CHANGE COLUMN `lft` `lft` INT(11) NULL ,
+CHANGE COLUMN `rgt` `rgt` INT(11) NULL ;
+
+ALTER TABLE `vn2008`.`department`
+DROP INDEX `rgt_UNIQUE` ,
+DROP INDEX `lft_UNIQUE` ;
+;
+
+ALTER TABLE `vn2008`.`department`
+ADD INDEX `lft_rgt_depth_idx` (`lft` ASC, `rgt` ASC, `depth` ASC);
+;
+
+
+UPDATE vn.department SET lft = NULL, rgt = NULL;
diff --git a/db/changes/10091-iberflora/00-department_calcTree.sql b/db/changes/10091-iberflora/00-department_calcTree.sql
new file mode 100644
index 000000000..5f518d2e2
--- /dev/null
+++ b/db/changes/10091-iberflora/00-department_calcTree.sql
@@ -0,0 +1,37 @@
+USE `vn`;
+DROP procedure IF EXISTS `department_calcTree`;
+
+DELIMITER $$
+USE `vn`$$
+CREATE DEFINER=`root`@`%` PROCEDURE `department_calcTree`()
+BEGIN
+/**
+ * Calculates the #path, #lft, #rgt, #sons and #depth columns of
+ * the #department table. To build the tree, it uses the #parentFk
+ * column.
+ */
+ DECLARE vIndex INT DEFAULT 0;
+ DECLARE vSons INT;
+
+ DROP TEMPORARY TABLE IF EXISTS tNestedTree;
+ CREATE TEMPORARY TABLE tNestedTree
+ SELECT id, path, lft, rgt, depth, sons
+ FROM department LIMIT 0;
+
+ SET max_sp_recursion_depth = 5;
+ CALL department_calcTreeRec(NULL, '/', 0, vIndex, vSons);
+ SET max_sp_recursion_depth = 0;
+
+ UPDATE department z
+ JOIN tNestedTree t ON t.id = z.id
+ SET z.path = t.path,
+ z.lft = t.lft,
+ z.rgt = t.rgt,
+ z.depth = t.depth,
+ z.sons = t.sons;
+
+ DROP TEMPORARY TABLE tNestedTree;
+END$$
+
+DELIMITER ;
+
diff --git a/db/changes/10091-iberflora/00-department_calcTreeRec.sql b/db/changes/10091-iberflora/00-department_calcTreeRec.sql
new file mode 100644
index 000000000..1f24270e2
--- /dev/null
+++ b/db/changes/10091-iberflora/00-department_calcTreeRec.sql
@@ -0,0 +1,75 @@
+USE `vn`;
+DROP procedure IF EXISTS `department_calcTreeRec`;
+
+DELIMITER $$
+USE `vn`$$
+CREATE DEFINER=`root`@`%` PROCEDURE `department_calcTreeRec`(
+ vSelf INT,
+ vPath VARCHAR(255),
+ vDepth INT,
+ INOUT vIndex INT,
+ OUT vSons INT
+)
+BEGIN
+/**
+ * Calculates and sets the #path, #lft, #rgt, #sons and #depth
+ * columns for all children of the passed node. Once calculated
+ * the last node rgt index and the number of sons are returned.
+ * To update it's children, this procedure calls itself recursively
+ * for each one.
+ *
+ * @vSelf The node identifier
+ * @vPath The initial path
+ * @vDepth The initial depth
+ * @vIndex The initial lft index
+ * @vSons The number of direct sons
+ */
+ DECLARE vChildFk INT;
+ DECLARE vLft INT;
+ DECLARE vMySons INT;
+ DECLARE vDone BOOL;
+ DECLARE vChildren CURSOR FOR
+ SELECT id FROM department
+ WHERE (vSelf IS NULL AND parentFk IS NULL)
+ OR (vSelf IS NOT NULL AND parentFk = vSelf);
+
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
+
+ SET vSons = 0;
+
+ OPEN vChildren;
+ myLoop: LOOP
+ SET vDone = FALSE;
+ FETCH vChildren INTO vChildFk;
+
+ IF vDone THEN
+ LEAVE myLoop;
+ END IF;
+
+ SET vIndex = vIndex + 1;
+ SET vLft = vIndex;
+ SET vSons = vSons + 1;
+
+ CALL department_calcTreeRec(
+ vChildFk,
+ CONCAT(vPath, vChildFk, '/'),
+ vDepth + 1,
+ vIndex,
+ vMySons
+ );
+
+ SET vIndex = vIndex + 1;
+
+ INSERT INTO tNestedTree
+ SET id = vChildFk,
+ path = vPath,
+ lft = vLft,
+ rgt = vIndex,
+ depth = vDepth,
+ sons = vMySons;
+ END LOOP;
+ CLOSE vChildren;
+END$$
+
+DELIMITER ;
+
diff --git a/db/changes/10091-iberflora/00-department_doCalc.sql b/db/changes/10091-iberflora/00-department_doCalc.sql
new file mode 100644
index 000000000..ba5c16c48
--- /dev/null
+++ b/db/changes/10091-iberflora/00-department_doCalc.sql
@@ -0,0 +1,35 @@
+USE `vn`;
+DROP procedure IF EXISTS `department_doCalc`;
+
+DELIMITER $$
+USE `vn`$$
+CREATE DEFINER=`root`@`%` PROCEDURE `department_doCalc`()
+proc: BEGIN
+/**
+ * Recalculates the department tree.
+ */
+ DECLARE vIsChanged BOOL;
+
+ DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
+ BEGIN
+ DO RELEASE_LOCK('vn.department_doCalc');
+ RESIGNAL;
+ END;
+
+ IF !GET_LOCK('vn.department_doCalc', 0) THEN
+ LEAVE proc;
+ END IF;
+
+ SELECT isChanged INTO vIsChanged
+ FROM department_recalc;
+
+ IF vIsChanged THEN
+ UPDATE department_recalc SET isChanged = FALSE;
+ CALL vn.department_calcTree;
+ END IF;
+
+ DO RELEASE_LOCK('vn.department_doCalc');
+END$$
+
+DELIMITER ;
+
diff --git a/db/changes/10091-iberflora/00-department_getLeaves.sql b/db/changes/10091-iberflora/00-department_getLeaves.sql
new file mode 100644
index 000000000..436f67dbf
--- /dev/null
+++ b/db/changes/10091-iberflora/00-department_getLeaves.sql
@@ -0,0 +1,84 @@
+USE `vn`;
+DROP procedure IF EXISTS `department_getLeaves`;
+
+DELIMITER $$
+USE `vn`$$
+CREATE DEFINER=`root`@`%` PROCEDURE `department_getLeaves`(
+ vParentFk INT,
+ vSearch VARCHAR(255)
+)
+BEGIN
+ DECLARE vIsNumber BOOL;
+ DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != '';
+
+ DROP TEMPORARY TABLE IF EXISTS tNodes;
+ CREATE TEMPORARY TABLE tNodes
+ (UNIQUE (id))
+ ENGINE = MEMORY
+ SELECT id FROM department LIMIT 0;
+
+ IF vIsSearch THEN
+ SET vIsNumber = vSearch REGEXP '^[0-9]+$';
+
+ INSERT INTO tNodes
+ SELECT id FROM department
+ WHERE (vIsNumber AND `name` = vSearch)
+ OR (!vIsNumber AND `name` LIKE CONCAT('%', vSearch, '%'))
+ LIMIT 1000;
+ END IF;
+
+ IF vParentFk IS NULL THEN
+ DROP TEMPORARY TABLE IF EXISTS tChilds;
+ CREATE TEMPORARY TABLE tChilds
+ ENGINE = MEMORY
+ SELECT id FROM tNodes;
+
+ DROP TEMPORARY TABLE IF EXISTS tParents;
+ CREATE TEMPORARY TABLE tParents
+ ENGINE = MEMORY
+ SELECT id FROM department LIMIT 0;
+
+ myLoop: LOOP
+ DELETE FROM tParents;
+ INSERT INTO tParents
+ SELECT parentFk id
+ FROM department g
+ JOIN tChilds c ON c.id = g.id
+ WHERE g.parentFk IS NOT NULL;
+
+ INSERT IGNORE INTO tNodes
+ SELECT id FROM tParents;
+
+ IF ROW_COUNT() = 0 THEN
+ LEAVE myLoop;
+ END IF;
+
+ DELETE FROM tChilds;
+ INSERT INTO tChilds
+ SELECT id FROM tParents;
+ END LOOP;
+
+ DROP TEMPORARY TABLE
+ tChilds,
+ tParents;
+ END IF;
+
+ IF !vIsSearch THEN
+ INSERT IGNORE INTO tNodes
+ SELECT id FROM department
+ WHERE parentFk <=> vParentFk;
+ END IF;
+
+ SELECT d.id,
+ d.`name`,
+ d.parentFk,
+ d.sons
+ FROM department d
+ JOIN tNodes n ON n.id = d.id
+ ORDER BY depth, `name`;
+
+ DROP TEMPORARY TABLE tNodes;
+END$$
+
+DELIMITER ;
+
diff --git a/front/core/components/icon-button/icon-button.js b/front/core/components/icon-button/icon-button.js
index b4022ccec..ba3ccae3e 100644
--- a/front/core/components/icon-button/icon-button.js
+++ b/front/core/components/icon-button/icon-button.js
@@ -3,23 +3,28 @@ import './style.scss';
export default class IconButton {
constructor($element) {
- if ($element[0].getAttribute('tabindex') == null)
- $element[0].tabIndex = 0;
+ this.element = $element[0];
- $element.on('keyup', event => this.onKeyDown(event, $element));
- let button = $element[0].querySelector('button');
- $element[0].addEventListener('click', event => {
- if (this.disabled || button.disabled)
- event.stopImmediatePropagation();
- });
+ if (this.element.getAttribute('tabindex') == null)
+ this.element.tabIndex = 0;
+
+ this.element.addEventListener('keyup', e => this.onKeyup(e));
+ this.element.addEventListener('click', e => this.onClick(e));
}
- onKeyDown(event, $element) {
+ onKeyup(event) {
+ if (event.code == 'Space')
+ this.onClick(event);
+ }
+
+ onClick(event) {
if (event.defaultPrevented) return;
- if (event.keyCode == 13) {
- event.preventDefault();
- $element.triggerHandler('click');
- }
+ event.preventDefault();
+
+ // FIXME: Don't use Event.stopPropagation()
+ let button = this.element.querySelector('button');
+ if (this.disabled || button.disabled)
+ event.stopImmediatePropagation();
}
}
diff --git a/front/core/components/index.js b/front/core/components/index.js
index b6533d504..7bd1b83c9 100644
--- a/front/core/components/index.js
+++ b/front/core/components/index.js
@@ -45,4 +45,3 @@ import './table';
import './td-editable';
import './th';
import './treeview';
-import './treeview/child';
diff --git a/front/core/components/treeview/child.html b/front/core/components/treeview/child.html
deleted file mode 100644
index bb0bcb569..000000000
--- a/front/core/components/treeview/child.html
+++ /dev/null
@@ -1,61 +0,0 @@
-
- -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
- Create new one
-
-
-
-
\ No newline at end of file
diff --git a/front/core/components/treeview/child.js b/front/core/components/treeview/child.js
deleted file mode 100644
index 4441963f0..000000000
--- a/front/core/components/treeview/child.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import ngModule from '../../module';
-import Component from '../../lib/component';
-
-class Controller extends Component {
- constructor($element, $scope) {
- super($element, $scope);
- this.$scope = $scope;
- }
-
- toggle(event, item) {
- if (event.defaultPrevented || !item.sons) return;
- event.preventDefault();
- this.treeview.onToggle(item);
- }
-
- select(item, value) {
- this.treeview.onSelection(item, value);
- }
-
- onIconClick(icon, item, parent, index) {
- let parentController = this.parentScope.$ctrl;
- icon.callback.call(parentController, item, parent, index);
- }
-
- onCreate(parent) {
- this.treeview.onCreate(parent);
- }
-
- onDrop(item, dragged, dropped) {
- this.treeview.onDrop(item, dragged, dropped);
- }
-
- get isInsertable() {
- return Array.isArray(this.parent) || this.parent.childs;
- }
-}
-
-ngModule.component('vnTreeviewChild', {
- template: require('./child.html'),
- controller: Controller,
- bindings: {
- items: '<',
- parent: '<',
- icons: '',
- disabled: '',
- selectable: '',
- editable: '',
- draggable: '',
- droppable: '',
- aclRole: '',
- parentScope: '<'
- },
- require: {
- treeview: '^vnTreeview'
- }
-});
diff --git a/front/core/components/treeview/childs.html b/front/core/components/treeview/childs.html
new file mode 100644
index 000000000..2dd7e77ef
--- /dev/null
+++ b/front/core/components/treeview/childs.html
@@ -0,0 +1,34 @@
+
+
\ No newline at end of file
diff --git a/front/core/components/treeview/childs.js b/front/core/components/treeview/childs.js
new file mode 100644
index 000000000..a9bd42077
--- /dev/null
+++ b/front/core/components/treeview/childs.js
@@ -0,0 +1,26 @@
+import ngModule from '../../module';
+import Component from '../../lib/component';
+
+class Controller extends Component {
+ onClick(event, item) {
+ if (event.defaultPrevented || !item.sons) return;
+ event.preventDefault();
+ this.treeview.onToggle(item);
+ }
+
+ onDrop(item, dragged, dropped) {
+ this.treeview.onDrop(item, dragged, dropped);
+ }
+}
+
+ngModule.component('vnTreeviewChilds', {
+ template: require('./childs.html'),
+ controller: Controller,
+ bindings: {
+ items: '<',
+ parent: ''
+ },
+ require: {
+ treeview: '^vnTreeview'
+ }
+});
diff --git a/front/core/components/treeview/content.js b/front/core/components/treeview/content.js
new file mode 100644
index 000000000..506117d4f
--- /dev/null
+++ b/front/core/components/treeview/content.js
@@ -0,0 +1,39 @@
+import ngModule from '../../module';
+
+class Controller {
+ constructor($element, $scope, $compile) {
+ this.$element = $element;
+ this.$scope = $scope;
+ this.$compile = $compile;
+ }
+
+ $onInit() {
+ if (this.item.parent) {
+ this.treeview.$transclude(($clone, $scope) => {
+ this.$contentScope = $scope;
+ $scope.item = this.item;
+ this.$element.append($clone);
+ });
+ } else {
+ let template = `{{$ctrl.treeview.rootLabel}}`;
+ let $clone = this.$compile(template)(this.$scope);
+ this.$element.append($clone);
+ }
+ }
+
+ $onDestroy() {
+ if (this.$contentScope)
+ this.$contentScope.$destroy();
+ }
+}
+Controller.$inject = ['$element', '$scope', '$compile'];
+
+ngModule.component('vnTreeviewContent', {
+ controller: Controller,
+ bindings: {
+ item: '<'
+ },
+ require: {
+ treeview: '^vnTreeview'
+ }
+});
diff --git a/front/core/components/treeview/index.html b/front/core/components/treeview/index.html
index a200c913a..31e6a88a9 100644
--- a/front/core/components/treeview/index.html
+++ b/front/core/components/treeview/index.html
@@ -1,13 +1,3 @@
-
-
+
+
diff --git a/front/core/components/treeview/index.js b/front/core/components/treeview/index.js
index ac4186c58..d9da39215 100644
--- a/front/core/components/treeview/index.js
+++ b/front/core/components/treeview/index.js
@@ -2,73 +2,122 @@ import ngModule from '../../module';
import Component from '../../lib/component';
import './style.scss';
+import './childs';
+import './content';
+
/**
* Treeview
- *
- * @property {String} position The relative position to the parent
*/
export default class Treeview extends Component {
- constructor($element, $scope) {
+ constructor($element, $scope, $transclude) {
super($element, $scope);
- this.$scope = $scope;
- this.data = [];
+ this.$transclude = $transclude;
+ this.readOnly = true;
}
- $onInit() {
- this.refresh();
+ get data() {
+ return this._data;
}
- refresh() {
- this.model.refresh().then(() => {
- this.data = this.model.data;
+ set data(value) {
+ this._data = value;
+
+ const sons = value.length;
+ const rootElement = [{
+ childs: value,
+ active: true,
+ sons: sons
+ }];
+
+ this.setParent(rootElement[0], value);
+
+ this.items = rootElement;
+ }
+
+ fetch() {
+ return this.fetchFunc().then(res =>
+ this.data = res
+ );
+ }
+
+ setParent(parent, childs) {
+ childs.forEach(child => {
+ child.parent = parent;
+
+ if (child.childs)
+ this.setParent(parent, child.childs);
});
}
- /**
- * Emits selection event
- * @param {Object} item - Selected item
- * @param {Boolean} value - Changed value
- */
- onSelection(item, value) {
- this.emit('selection', {item, value});
- }
-
- onCreate(parent) {
- this.emit('create', {parent});
- }
-
onToggle(item) {
if (item.active)
- item.childs = undefined;
- else {
- this.model.applyFilter({}, {parentFk: item.id}).then(() => {
- const newData = this.model.data;
+ this.fold(item);
+ else
+ this.unfold(item);
+ }
- if (item.childs) {
- let childs = item.childs;
- childs.forEach(child => {
- let index = newData.findIndex(newChild => {
- return newChild.id == child.id;
- });
- newData[index] = child;
+ fold(item) {
+ item.childs = undefined;
+ item.active = false;
+ }
+
+ unfold(item) {
+ return this.fetchFunc({$item: item}).then(newData => {
+ this.setParent(item, newData);
+
+ const childs = item.childs;
+ if (childs) {
+ childs.forEach(child => {
+ let index = newData.findIndex(newChild => {
+ return newChild.id == child.id;
});
- }
-
- item.childs = newData.sort((a, b) => {
- if (b.selected !== a.selected) {
- if (a.selected == null)
- return 1;
- if (b.selected == null)
- return -1;
- return b.selected - a.selected;
- }
-
- return a.name.localeCompare(b.name);
+ newData[index] = child;
});
- });
+ }
+
+ if (this.sortFunc) {
+ item.childs = newData.sort((a, b) =>
+ this.sortFunc({$a: a, $b: b})
+ );
+ }
+ }).then(() => item.active = true);
+ }
+
+ onRemove(item) {
+ if (this.removeFunc)
+ this.removeFunc({$item: item});
+ }
+
+ remove(item) {
+ const parent = item.parent;
+ let childs = parent.childs;
+
+ if (!childs) childs = [];
+
+ let index = childs.indexOf(item);
+ childs.splice(index, 1);
+ if (parent) parent.sons--;
+ }
+
+ onCreate(parent) {
+ if (this.createFunc)
+ this.createFunc({$parent: parent});
+ }
+
+ create(item) {
+ const parent = item.parent;
+ let childs = parent.childs;
+ if (!childs) childs = [];
+
+ childs.push(item);
+
+ if (this.sortFunc) {
+ childs = childs.sort((a, b) =>
+ this.sortFunc({$a: a, $b: b})
+ );
}
- item.active = !item.active;
+ if (parent) parent.sons++;
}
onDrop(item, dragged, dropped) {
@@ -76,19 +125,21 @@ export default class Treeview extends Component {
}
}
-Treeview.$inject = ['$element', '$scope'];
+Treeview.$inject = ['$element', '$scope', '$transclude'];
ngModule.component('vnTreeview', {
template: require('./index.html'),
controller: Treeview,
bindings: {
- model: '<',
- icons: '',
- disabled: '',
- selectable: '',
- editable: '',
+ rootLabel: '@',
+ data: '',
draggable: '',
droppable: '',
- aclRole: '@?'
- }
+ fetchFunc: '&',
+ removeFunc: '&?',
+ createFunc: '&?',
+ sortFunc: '&?',
+ readOnly: ''
+ },
+ transclude: true
});
diff --git a/front/core/components/treeview/style.scss b/front/core/components/treeview/style.scss
index 0ea1074a9..b3724a9f1 100644
--- a/front/core/components/treeview/style.scss
+++ b/front/core/components/treeview/style.scss
@@ -1,9 +1,8 @@
@import "effects";
-vn-treeview {
- vn-treeview-child {
- display: block
- }
+vn-treeview-childs {
+ display: block;
+
ul {
padding: 0;
margin: 0;
@@ -11,37 +10,43 @@ vn-treeview {
li {
list-style: none;
- & > div > .arrow {
- min-width: 24px;
- margin-right: 10px;
- transition: transform 200ms;
- }
- &.expanded > div > .arrow {
- transform: rotate(180deg);
- }
& > .node {
@extend %clickable;
display: flex;
padding: 5px;
align-items: center;
-
- & > vn-check:not(.indeterminate) {
- color: $color-main;
-
- & > .btn {
- border-color: $color-main;
- }
- }
- & > vn-check.checked {
- color: $color-main;
- }
+ }
+
+ & > div > .arrow {
+ min-width: 24px;
+ margin-right: 10px;
+ transition: transform 200ms;
+ }
+
+ & > div.expanded > .arrow {
+ transform: rotate(180deg);
}
ul {
padding-left: 2.2em;
}
}
}
+
vn-icon-button {
- padding: 0
+ display: table-cell;
+ vertical-align: middle;
+ padding: 0;
+ }
+
+ .node > .buttons {
+ display: none
+ }
+
+ .node:hover > .buttons {
+ display: block
}
}
+
+vn-treeview-content {
+ flex-grow: 1
+}
\ No newline at end of file
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index b6c70be3a..07c95af32 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -107,5 +107,6 @@
"Invalid quantity": "Cantidad invalida",
"This postal code is not valid": "This postal code is not valid",
"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 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"
}
\ No newline at end of file
diff --git a/modules/agency/back/methods/zone-included/toggleIsIncluded.js b/modules/agency/back/methods/zone-included/toggleIsIncluded.js
deleted file mode 100644
index ed4605296..000000000
--- a/modules/agency/back/methods/zone-included/toggleIsIncluded.js
+++ /dev/null
@@ -1,35 +0,0 @@
-module.exports = Self => {
- Self.remoteMethod('toggleIsIncluded', {
- description: 'Toggle include to delivery',
- accepts: [{
- arg: 'zoneFk',
- type: 'Number',
- required: true,
- },
- {
- arg: 'item',
- type: 'Object',
- required: true,
- }],
- returns: {
- type: 'object',
- root: true
- },
- http: {
- path: `/toggleIsIncluded`,
- verb: 'POST'
- }
- });
-
- Self.toggleIsIncluded = async(zoneFk, item) => {
- if (item.isIncluded === null)
- return Self.destroyAll({zoneFk, geoFk: item.id});
- else {
- return Self.upsert({
- zoneFk: zoneFk,
- geoFk: item.id,
- isIncluded: item.isIncluded
- });
- }
- };
-};
diff --git a/modules/agency/back/methods/zone/getLeaves.js b/modules/agency/back/methods/zone/getLeaves.js
index 50ee54345..c45136d4f 100644
--- a/modules/agency/back/methods/zone/getLeaves.js
+++ b/modules/agency/back/methods/zone/getLeaves.js
@@ -6,10 +6,11 @@ module.exports = Self => {
{
arg: 'id',
type: 'Number',
+ description: 'The zone id',
http: {source: 'path'},
required: true
}, {
- arg: 'parentFk',
+ arg: 'parentId',
type: 'Number',
description: 'Get the children of the specified father',
}, {
@@ -28,10 +29,10 @@ module.exports = Self => {
}
});
- Self.getLeaves = async(id, parentFk = null, search) => {
+ Self.getLeaves = async(id, parentId = null, search) => {
let [res] = await Self.rawSql(
`CALL zone_getLeaves(?, ?, ?)`,
- [id, parentFk, search]
+ [id, parentId, search]
);
let map = new Map();
@@ -49,7 +50,7 @@ module.exports = Self => {
}
}
- let leaves = map.get(parentFk);
+ let leaves = map.get(parentId);
setLeaves(leaves);
return leaves || [];
diff --git a/modules/agency/back/methods/zone/toggleIsIncluded.js b/modules/agency/back/methods/zone/toggleIsIncluded.js
new file mode 100644
index 000000000..ae8f7c571
--- /dev/null
+++ b/modules/agency/back/methods/zone/toggleIsIncluded.js
@@ -0,0 +1,41 @@
+module.exports = Self => {
+ Self.remoteMethod('toggleIsIncluded', {
+ description: 'Toggle include to delivery',
+ accepts: [{
+ arg: 'id',
+ type: 'Number',
+ description: 'The zone id',
+ http: {source: 'path'},
+ required: true
+ }, {
+ arg: 'geoId',
+ type: 'Number',
+ required: true
+ }, {
+ arg: 'isIncluded',
+ type: 'Boolean'
+ }],
+ returns: {
+ type: 'object',
+ root: true
+ },
+ http: {
+ path: `/:id/toggleIsIncluded`,
+ verb: 'POST'
+ }
+ });
+
+ Self.toggleIsIncluded = async(id, geoId, isIncluded) => {
+ const models = Self.app.models;
+
+ if (isIncluded === undefined)
+ return models.ZoneIncluded.destroyAll({zoneFk: id, geoFk: geoId});
+ else {
+ return models.ZoneIncluded.upsert({
+ zoneFk: id,
+ geoFk: geoId,
+ isIncluded: isIncluded
+ });
+ }
+ };
+};
diff --git a/modules/agency/back/models/zone-included.js b/modules/agency/back/models/zone-included.js
deleted file mode 100644
index 6cf1192aa..000000000
--- a/modules/agency/back/models/zone-included.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = Self => {
- require('../methods/zone-included/toggleIsIncluded')(Self);
-};
diff --git a/modules/agency/back/models/zone.js b/modules/agency/back/models/zone.js
index a6695bd02..1f8b0a675 100644
--- a/modules/agency/back/models/zone.js
+++ b/modules/agency/back/models/zone.js
@@ -3,6 +3,7 @@ module.exports = Self => {
require('../methods/zone/editPrices')(Self);
require('../methods/zone/getLeaves')(Self);
require('../methods/zone/getEvents')(Self);
+ require('../methods/zone/toggleIsIncluded')(Self);
Self.validatesPresenceOf('agencyModeFk', {
message: `Agency cannot be blank`
diff --git a/modules/agency/front/location/index.html b/modules/agency/front/location/index.html
index 3b30f424a..67a0fc3ce 100644
--- a/modules/agency/front/location/index.html
+++ b/modules/agency/front/location/index.html
@@ -1,23 +1,25 @@
+ filter="::$ctrl.filter">
-
-
+
+
+
-
\ No newline at end of file
+
diff --git a/modules/agency/front/location/index.js b/modules/agency/front/location/index.js
index 02dbeb71b..13462328f 100644
--- a/modules/agency/front/location/index.js
+++ b/modules/agency/front/location/index.js
@@ -1,10 +1,32 @@
import ngModule from '../module';
import Section from 'core/lib/section';
+import './style.scss';
class Controller extends Section {
onSearch(params) {
- this.$.model.applyFilter(null, params);
- this.$.$applyAsync(() => this.$.treeview.refresh());
+ this.$.model.applyFilter({}, params).then(() => {
+ const data = this.$.model.data;
+ this.$.treeview.data = data;
+ });
+ }
+
+ onFetch(item) {
+ const params = item ? {parentId: item.id} : null;
+ return this.$.model.applyFilter({}, params).then(() => {
+ return this.$.model.data;
+ });
+ }
+
+ onSort(a, b) {
+ if (b.selected !== a.selected) {
+ if (a.selected == null)
+ return 1;
+ if (b.selected == null)
+ return -1;
+ return b.selected - a.selected;
+ }
+
+ return a.name.localeCompare(b.name);
}
exprBuilder(param, value) {
@@ -14,13 +36,11 @@ class Controller extends Section {
}
}
- onSelection(item, isIncluded) {
- let node = Object.assign({}, item);
- node.isIncluded = isIncluded;
- node.childs = []; // Data too large
-
- const path = '/agency/api/ZoneIncludeds/toggleIsIncluded';
- const params = {zoneFk: this.zone.id, item: node};
+ onSelection(value, item) {
+ if (value == null)
+ value = undefined;
+ const params = {geoId: item.id, isIncluded: value};
+ const path = `/api/zones/${this.zone.id}/toggleIsIncluded`;
this.$http.post(path, params);
}
}
diff --git a/modules/agency/front/location/style.scss b/modules/agency/front/location/style.scss
new file mode 100644
index 000000000..a3b237b13
--- /dev/null
+++ b/modules/agency/front/location/style.scss
@@ -0,0 +1,14 @@
+@import "variables";
+
+vn-treeview-content {
+ & > vn-check:not(.indeterminate) {
+ color: $color-main;
+
+ & > .btn {
+ border-color: $color-main;
+ }
+ }
+ & > vn-check.checked {
+ color: $color-main;
+ }
+}
\ No newline at end of file
diff --git a/modules/worker/back/methods/department/createChild.js b/modules/worker/back/methods/department/createChild.js
new file mode 100644
index 000000000..57bcbe5be
--- /dev/null
+++ b/modules/worker/back/methods/department/createChild.js
@@ -0,0 +1,40 @@
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethod('createChild', {
+ description: 'Creates a new child department',
+ accessType: 'WRITE',
+ accepts: [{
+ arg: 'parentId',
+ type: 'Number'
+ },
+ {
+ arg: 'name',
+ type: 'String',
+ required: true,
+ }],
+ returns: {
+ type: 'object',
+ root: true
+ },
+ http: {
+ path: `/createChild`,
+ verb: 'POST'
+ }
+ });
+
+ Self.createChild = async(parentId = null, name) => {
+ const models = Self.app.models;
+ const nameExists = await models.Department.count({name});
+
+ if (nameExists)
+ throw new UserError(`The department name can't be repeated`);
+
+ const newDep = await models.Department.create({
+ parentFk: parentId,
+ name: name
+ });
+
+ return newDep;
+ };
+};
diff --git a/modules/worker/back/methods/department/getLeaves.js b/modules/worker/back/methods/department/getLeaves.js
index 34b776cf0..614f3388e 100644
--- a/modules/worker/back/methods/department/getLeaves.js
+++ b/modules/worker/back/methods/department/getLeaves.js
@@ -1,20 +1,14 @@
-
-const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
-
module.exports = Self => {
Self.remoteMethod('getLeaves', {
- description: 'Returns the first shipped and landed possible for params',
+ description: 'Returns the nodes for a department',
accepts: [{
- arg: 'parentFk',
+ arg: 'parentId',
type: 'Number',
- default: 1,
- required: false,
- },
- {
- arg: 'filter',
- type: 'Object',
- description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
- http: {source: 'query'}
+ description: 'Get the children of the specified father',
+ }, {
+ arg: 'search',
+ type: 'String',
+ description: 'Filter nodes whose name starts with',
}],
returns: {
type: ['object'],
@@ -26,61 +20,30 @@ module.exports = Self => {
}
});
- Self.getLeaves = async(parentFk = 1, filter) => {
- let conn = Self.dataSource.connector;
- let stmt = new ParameterizedSQL(
- `SELECT
- child.id,
- child.name,
- child.lft,
- child.rgt,
- child.depth,
- child.sons
- FROM department parent
- JOIN department child ON child.lft > parent.lft
- AND child.rgt < parent.rgt
- AND child.depth = parent.depth + 1
- WHERE parent.id = ?`, [parentFk]);
+ Self.getLeaves = async(parentId = null, search) => {
+ let [res] = await Self.rawSql(
+ `CALL department_getLeaves(?, ?)`,
+ [parentId, search]
+ );
- // Get nodes from depth greather than Origin
- stmt.merge(conn.makeSuffix(filter));
-
- const nodes = await Self.rawStmt(stmt);
-
- if (nodes.length == 0)
- return nodes;
-
- // Get parent nodes
- const minorDepth = nodes.reduce((a, b) => {
- return b < a ? b : a;
- }).depth;
-
- const parentNodes = nodes.filter(element => {
- return element.depth === minorDepth;
- });
- const leaves = Object.assign([], parentNodes);
-
- nestLeaves(leaves);
-
- function nestLeaves(elements) {
- elements.forEach(element => {
- let childs = Object.assign([], getLeaves(element));
- if (childs.length > 0) {
- element.childs = childs;
- nestLeaves(element.childs);
- }
- });
+ let map = new Map();
+ for (let node of res) {
+ if (!map.has(node.parentFk))
+ map.set(node.parentFk, []);
+ map.get(node.parentFk).push(node);
}
- function getLeaves(parent) {
- let elements = nodes.filter(element => {
- return element.lft > parent.lft && element.rgt < parent.rgt
- && element.depth === parent.depth + 1;
- });
-
- return elements;
+ function setLeaves(nodes) {
+ if (!nodes) return;
+ for (let node of nodes) {
+ node.childs = map.get(node.id);
+ setLeaves(node.childs);
+ }
}
- return leaves;
+ let leaves = map.get(parentId);
+ setLeaves(leaves);
+
+ return leaves || [];
};
};
diff --git a/modules/worker/back/methods/department/nodeAdd.js b/modules/worker/back/methods/department/nodeAdd.js
deleted file mode 100644
index 93a6f81c0..000000000
--- a/modules/worker/back/methods/department/nodeAdd.js
+++ /dev/null
@@ -1,43 +0,0 @@
-
-const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
-
-module.exports = Self => {
- Self.remoteMethod('nodeAdd', {
- description: 'Returns the first shipped and landed possible for params',
- accessType: 'WRITE',
- accepts: [{
- arg: 'parentFk',
- type: 'Number',
- required: false,
- },
- {
- arg: 'name',
- type: 'String',
- required: true,
- }],
- returns: {
- type: 'object',
- root: true
- },
- http: {
- path: `/nodeAdd`,
- verb: 'POST'
- }
- });
-
- Self.nodeAdd = async(parentFk = 1, name) => {
- let stmts = [];
- let conn = Self.dataSource.connector;
- let nodeIndex = stmts.push(new ParameterizedSQL(
- `CALL nst.nodeAdd('vn', 'department', ?, ?)`, [parentFk, name])) - 1;
-
- stmts.push(`CALL nst.nodeRecalc('vn', 'department')`);
-
-
- let sql = ParameterizedSQL.join(stmts, ';');
- let result = await conn.executeStmt(sql);
- let [node] = result[nodeIndex];
-
- return node;
- };
-};
diff --git a/modules/worker/back/methods/department/nodeDelete.js b/modules/worker/back/methods/department/nodeDelete.js
deleted file mode 100644
index 92b354ee5..000000000
--- a/modules/worker/back/methods/department/nodeDelete.js
+++ /dev/null
@@ -1,29 +0,0 @@
-
-const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
-
-module.exports = Self => {
- Self.remoteMethod('nodeDelete', {
- description: 'Returns the first shipped and landed possible for params',
- accessType: 'WRITE',
- accepts: [{
- arg: 'parentFk',
- type: 'Number',
- required: false,
- }],
- returns: {
- type: ['object'],
- root: true
- },
- http: {
- path: `/nodeDelete`,
- verb: 'POST'
- }
- });
-
- Self.nodeDelete = async parentFk => {
- let stmt = new ParameterizedSQL(
- `CALL nst.nodeDelete('vn', 'department', ?)`, [parentFk]);
-
- return await Self.rawStmt(stmt);
- };
-};
diff --git a/modules/worker/back/methods/department/removeChild.js b/modules/worker/back/methods/department/removeChild.js
new file mode 100644
index 000000000..00115ec34
--- /dev/null
+++ b/modules/worker/back/methods/department/removeChild.js
@@ -0,0 +1,27 @@
+module.exports = Self => {
+ Self.remoteMethod('removeChild', {
+ description: 'Removes a child department',
+ accessType: 'WRITE',
+ accepts: [{
+ arg: 'id',
+ type: 'Number',
+ description: 'The department id',
+ http: {source: 'path'}
+ }],
+ returns: {
+ type: 'Object',
+ root: true
+ },
+ http: {
+ path: `/:id/removeChild`,
+ verb: 'POST'
+ }
+ });
+
+ Self.removeChild = async id => {
+ const models = Self.app.models;
+ const department = await models.Department.findById(id);
+
+ return await department.destroy();
+ };
+};
diff --git a/modules/worker/back/models/department.js b/modules/worker/back/models/department.js
index 085c2bf9b..e6905d273 100644
--- a/modules/worker/back/models/department.js
+++ b/modules/worker/back/models/department.js
@@ -1,15 +1,5 @@
-const UserError = require('vn-loopback/util/user-error');
-
module.exports = Self => {
require('../methods/department/getLeaves')(Self);
- require('../methods/department/nodeAdd')(Self);
- require('../methods/department/nodeDelete')(Self);
-
- Self.rewriteDbError(function(err) {
- if (err.code === 'ER_ROW_IS_REFERENCED_2')
- return new UserError(`You cannot remove this department`);
- if (err.code === 'ER_DUP_ENTRY')
- return new UserError(`The department name can't be repeated`);
- return err;
- });
+ require('../methods/department/createChild')(Self);
+ require('../methods/department/removeChild')(Self);
};
diff --git a/modules/worker/back/models/department.json b/modules/worker/back/models/department.json
index 0b978b56c..bb5d5e943 100644
--- a/modules/worker/back/models/department.json
+++ b/modules/worker/back/models/department.json
@@ -13,6 +13,9 @@
},
"name": {
"type": "String"
+ },
+ "parentFk": {
+ "type": "Number"
}
}
}
diff --git a/modules/worker/front/department/index.html b/modules/worker/front/department/index.html
index 4d1b40beb..773662032 100644
--- a/modules/worker/front/department/index.html
+++ b/modules/worker/front/department/index.html
@@ -1,25 +1,24 @@
-
-