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 @@ - \ 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: ' +
  • +
    + + + + +
    + + + + +
    +
    + + +
  • + \ 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: ' { + 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: ' 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 @@ - -
    -
    +
    + - + + {{::item.name}} -
    - - + + + + +
    + model="$ctrl.newChild.name"> @@ -45,4 +44,4 @@ - \ No newline at end of file + diff --git a/modules/worker/front/department/index.js b/modules/worker/front/department/index.js index 2047c06c7..1a72681bc 100644 --- a/modules/worker/front/department/index.js +++ b/modules/worker/front/department/index.js @@ -2,31 +2,28 @@ import ngModule from '../module'; class Controller { constructor($scope, $http, vnApp, $translate) { - this.$scope = $scope; + this.$ = $scope; this.$http = $http; this.vnApp = vnApp; this.$translate = $translate; - this.params = {parentFk: 1}; - this.icons = [{icon: 'delete', tooltip: 'Delete', callback: this.onDelete}]; - this.newNode = { - name: '' - }; } - onCreate(parent) { - if (parent instanceof Object) - this.newNode.parentFk = parent.id; - - this.selectedNode = {parent}; - this.$scope.createNode.show(); + $postLink() { + this.$.treeview.fetch(); } - onDelete(item, parent, index) { - this.selectedNode = {id: item.id, parent, index}; - this.$scope.deleteNode.show(); + onFetch(item) { + const params = item ? {parentId: item.id} : null; + return this.$.model.applyFilter({}, params).then(() => { + return this.$.model.data; + }); } - onDrop(item, dragged, dropped) { + onSort(a, b) { + return a.name.localeCompare(b.name); + } + + /* onDrop(item, dragged, dropped) { if (dropped.scope.item) { const droppedItem = dropped.scope.item; const draggedItem = dragged.scope.item; @@ -38,27 +35,43 @@ class Controller { this.$scope.$apply(); } + } */ + + onCreate(parent) { + this.newChild = { + parent: parent, + name: '' + }; + + this.$.createNode.show(); } onCreateDialogOpen() { - this.newNode.name = ''; + this.newChild.name = ''; } onCreateResponse(response) { if (response == 'ACCEPT') { try { - if (!this.newNode.name) + if (!this.newChild.name) throw new Error(`Name can't be empty`); - this.$http.post(`/worker/api/Departments/nodeAdd`, this.newNode).then(response => { - if (response.data) { - let parent = this.selectedNode.parent; - if ((parent instanceof Object) && !(parent instanceof Array)) { - const childs = parent.childs; - childs.push(response.data); - } else if ((parent instanceof Object) && (parent instanceof Array)) - parent.push(response.data); - } + const params = {name: this.newChild.name}; + const parent = this.newChild.parent; + + 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; + + this.$.treeview.create(item); }); } catch (e) { this.vnApp.showError(this.$translate.instant(e.message)); @@ -68,17 +81,17 @@ class Controller { return true; } + onRemove(item) { + this.removedChild = item; + this.$.deleteNode.show(); + } + onRemoveResponse(response) { if (response === 'ACCEPT') { - const path = `/worker/api/Departments/nodeDelete`; - this.$http.post(path, {parentFk: this.selectedNode.id}).then(() => { - let parent = this.selectedNode.parent; - - if ((parent instanceof Object) && !(parent instanceof Array)) { - const childs = parent.childs; - childs.splice(this.selectedNode.index, 1); - } else if ((parent instanceof Object) && (parent instanceof Array)) - parent.splice(this.selectedNode.index, 1); + const childId = this.removedChild.id; + const path = `/api/departments/${childId}/removeChild`; + this.$http.post(path).then(() => { + this.$.treeview.remove(this.removedChild); }); } }