Merge
This commit is contained in:
commit
fc09d76422
|
@ -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;
|
|
@ -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 ;
|
||||||
|
|
|
@ -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 ;
|
||||||
|
|
|
@ -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 ;
|
||||||
|
|
|
@ -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 ;
|
||||||
|
|
|
@ -260,6 +260,7 @@ export default {
|
||||||
submitItemTagsButton: `vn-item-tags ${components.vnSubmit}`
|
submitItemTagsButton: `vn-item-tags ${components.vnSubmit}`
|
||||||
},
|
},
|
||||||
itemTax: {
|
itemTax: {
|
||||||
|
undoChangesButton: 'vn-item-tax vn-button-bar > vn-button[label="Undo changes"]',
|
||||||
firstClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(1) > vn-autocomplete[field="tax.taxClassFk"]',
|
firstClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(1) > vn-autocomplete[field="tax.taxClassFk"]',
|
||||||
secondClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(2) > vn-autocomplete[field="tax.taxClassFk"]',
|
secondClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(2) > vn-autocomplete[field="tax.taxClassFk"]',
|
||||||
thirdClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(3) > vn-autocomplete[field="tax.taxClassFk"]',
|
thirdClassAutocomplete: 'vn-item-tax vn-horizontal:nth-child(3) > vn-autocomplete[field="tax.taxClassFk"]',
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import selectors from '../../helpers/selectors.js';
|
import selectors from '../../helpers/selectors.js';
|
||||||
import createNightmare from '../../helpers/nightmare';
|
import createNightmare from '../../helpers/nightmare';
|
||||||
|
|
||||||
// #1702 Autocomplete no siempre refresca al cancelar formulario
|
describe('Item edit tax path', () => {
|
||||||
xdescribe('Item edit tax path', () => {
|
|
||||||
const nightmare = createNightmare();
|
const nightmare = createNightmare();
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -14,9 +13,9 @@ xdescribe('Item edit tax path', () => {
|
||||||
|
|
||||||
it(`should add the item tax to all countries`, async() => {
|
it(`should add the item tax to all countries`, async() => {
|
||||||
const result = await nightmare
|
const result = await nightmare
|
||||||
.autocompleteSearch(selectors.itemTax.firstClassAutocomplete, 'Reduced VAT')
|
.autocompleteSearch(selectors.itemTax.firstClassAutocomplete, 'General VAT')
|
||||||
.autocompleteSearch(selectors.itemTax.secondClassAutocomplete, 'General VAT')
|
.autocompleteSearch(selectors.itemTax.secondClassAutocomplete, 'General VAT')
|
||||||
.autocompleteSearch(selectors.itemTax.thirdClassAutocomplete, 'Reduced VAT')
|
.autocompleteSearch(selectors.itemTax.thirdClassAutocomplete, 'General VAT')
|
||||||
.waitToClick(selectors.itemTax.submitTaxButton)
|
.waitToClick(selectors.itemTax.submitTaxButton)
|
||||||
.waitForLastSnackbar();
|
.waitForLastSnackbar();
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ xdescribe('Item edit tax path', () => {
|
||||||
.reloadSection('item.card.tax')
|
.reloadSection('item.card.tax')
|
||||||
.waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value');
|
.waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value');
|
||||||
|
|
||||||
expect(firstVatType).toEqual('Reduced VAT');
|
expect(firstVatType).toEqual('General VAT');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should confirm the second item tax class was edited`, async() => {
|
it(`should confirm the second item tax class was edited`, async() => {
|
||||||
|
@ -42,6 +41,22 @@ xdescribe('Item edit tax path', () => {
|
||||||
const thirdVatType = await nightmare
|
const thirdVatType = await nightmare
|
||||||
.waitToGetProperty(`${selectors.itemTax.thirdClassAutocomplete} input`, 'value');
|
.waitToGetProperty(`${selectors.itemTax.thirdClassAutocomplete} input`, 'value');
|
||||||
|
|
||||||
expect(thirdVatType).toEqual('Reduced VAT');
|
expect(thirdVatType).toEqual('General VAT');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should edit the first class without saving the form`, async() => {
|
||||||
|
const firstVatType = await nightmare
|
||||||
|
.autocompleteSearch(selectors.itemTax.firstClassAutocomplete, 'Reduced VAT')
|
||||||
|
.waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value');
|
||||||
|
|
||||||
|
expect(firstVatType).toEqual('Reduced VAT');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should now click the undo changes button and see the changes works`, async() => {
|
||||||
|
const firstVatType = await nightmare
|
||||||
|
.waitToClick(selectors.itemTax.undoChangesButton)
|
||||||
|
.waitToGetProperty(`${selectors.itemTax.firstClassAutocomplete} input`, 'value');
|
||||||
|
|
||||||
|
expect(firstVatType).toEqual('General VAT');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -121,7 +121,7 @@ export default class Autocomplete extends Field {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const selection = this.fetchSelection();
|
const selection = this.fetchSelection();
|
||||||
if (!this.selection)
|
|
||||||
this.selection = selection;
|
this.selection = selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,23 +3,28 @@ import './style.scss';
|
||||||
|
|
||||||
export default class IconButton {
|
export default class IconButton {
|
||||||
constructor($element) {
|
constructor($element) {
|
||||||
if ($element[0].getAttribute('tabindex') == null)
|
this.element = $element[0];
|
||||||
$element[0].tabIndex = 0;
|
|
||||||
|
|
||||||
$element.on('keyup', event => this.onKeyDown(event, $element));
|
if (this.element.getAttribute('tabindex') == null)
|
||||||
let button = $element[0].querySelector('button');
|
this.element.tabIndex = 0;
|
||||||
$element[0].addEventListener('click', event => {
|
|
||||||
|
this.element.addEventListener('keyup', e => this.onKeyup(e));
|
||||||
|
this.element.addEventListener('click', e => this.onClick(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyup(event) {
|
||||||
|
if (event.code == 'Space')
|
||||||
|
this.onClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(event) {
|
||||||
|
if (event.defaultPrevented) return;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// FIXME: Don't use Event.stopPropagation()
|
||||||
|
let button = this.element.querySelector('button');
|
||||||
if (this.disabled || button.disabled)
|
if (this.disabled || button.disabled)
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyDown(event, $element) {
|
|
||||||
if (event.defaultPrevented) return;
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
event.preventDefault();
|
|
||||||
$element.triggerHandler('click');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,4 +45,3 @@ import './table';
|
||||||
import './td-editable';
|
import './td-editable';
|
||||||
import './th';
|
import './th';
|
||||||
import './treeview';
|
import './treeview';
|
||||||
import './treeview/child';
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
<ul ng-if="::$ctrl.items">
|
|
||||||
<li
|
|
||||||
ng-repeat="item in $ctrl.items"
|
|
||||||
on-drop="$ctrl.onDrop(item, dragged, dropped)"
|
|
||||||
vn-draggable="{{::$ctrl.draggable}}"
|
|
||||||
vn-droppable="{{::$ctrl.droppable}}"
|
|
||||||
ng-class="{expanded: item.active}">
|
|
||||||
<div
|
|
||||||
ng-click="$ctrl.toggle($event, item)"
|
|
||||||
class="node clickable">
|
|
||||||
<vn-icon
|
|
||||||
class="arrow"
|
|
||||||
ng-class="{invisible: item.sons == 0}"
|
|
||||||
icon="keyboard_arrow_down"
|
|
||||||
translate-attr="{title: 'Toggle'}">
|
|
||||||
</vn-icon>
|
|
||||||
<vn-check
|
|
||||||
vn-acl="{{$ctrl.aclRole}}"
|
|
||||||
ng-if="$ctrl.selectable"
|
|
||||||
field="item.selected"
|
|
||||||
disabled="$ctrl.disabled"
|
|
||||||
on-change="$ctrl.select(item, value)"
|
|
||||||
triple-state="true"
|
|
||||||
label="{{::item.name}}">
|
|
||||||
</vn-check>
|
|
||||||
<vn-icon-button
|
|
||||||
icon="{{icon.icon}}"
|
|
||||||
ng-repeat="icon in $ctrl.icons"
|
|
||||||
ng-click="$ctrl.onIconClick(icon, item, $ctrl.parent, $parent.$index)"
|
|
||||||
vn-acl="{{$ctrl.aclRole}}"
|
|
||||||
vn-acl-action="remove">
|
|
||||||
</vn-icon-button>
|
|
||||||
</div>
|
|
||||||
<vn-treeview-child
|
|
||||||
items="item.childs"
|
|
||||||
parent="item"
|
|
||||||
selectable="$ctrl.selectable"
|
|
||||||
disabled="$ctrl.disabled"
|
|
||||||
editable="$ctrl.editable"
|
|
||||||
draggable="::$ctrl.draggable"
|
|
||||||
droppable="::$ctrl.droppable"
|
|
||||||
icons="::$ctrl.icons"
|
|
||||||
parent-scope="::$ctrl.parentScope"
|
|
||||||
acl-role="$ctrl.aclRole">
|
|
||||||
</vn-treeview-child>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
ng-if="$ctrl.isInsertable && $ctrl.editable"
|
|
||||||
ng-click="$ctrl.onCreate($ctrl.parent)"
|
|
||||||
vn-acl="{{$ctrl.aclRole}}"
|
|
||||||
vn-acl-action="remove">
|
|
||||||
<div class="node">
|
|
||||||
<vn-icon-button
|
|
||||||
icon="add_circle">
|
|
||||||
</vn-icon-button>
|
|
||||||
<div class="description" translate>
|
|
||||||
Create new one
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
|
@ -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'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<vn-treeview-childs
|
||||||
|
items="item.childs"
|
||||||
|
parent="::item">
|
||||||
|
</vn-treeview-childs>
|
||||||
|
</li>
|
||||||
|
</ul>
|
|
@ -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'
|
||||||
|
}
|
||||||
|
});
|
|
@ -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 = `<span translate>{{$ctrl.treeview.rootLabel}}</span>`;
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,13 +1,3 @@
|
||||||
<vn-treeview-child
|
<vn-treeview-childs
|
||||||
acl-role="$ctrl.aclRole"
|
items="$ctrl.items">
|
||||||
items="$ctrl.data"
|
</vn-treeview-childs>
|
||||||
parent="$ctrl.data"
|
|
||||||
selectable="$ctrl.selectable"
|
|
||||||
editable="$ctrl.editable"
|
|
||||||
disabled="$ctrl.disabled"
|
|
||||||
icons="$ctrl.icons"
|
|
||||||
parent-scope="$ctrl.$scope.$parent"
|
|
||||||
draggable="$ctrl.draggable"
|
|
||||||
droppable="$ctrl.droppable"
|
|
||||||
vn-droppable="{{$ctrl.droppable}}">
|
|
||||||
</vn-treeview-child>
|
|
||||||
|
|
|
@ -2,50 +2,71 @@ import ngModule from '../../module';
|
||||||
import Component from '../../lib/component';
|
import Component from '../../lib/component';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
|
import './childs';
|
||||||
|
import './content';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Treeview
|
* Treeview
|
||||||
*
|
|
||||||
* @property {String} position The relative position to the parent
|
|
||||||
*/
|
*/
|
||||||
export default class Treeview extends Component {
|
export default class Treeview extends Component {
|
||||||
constructor($element, $scope) {
|
constructor($element, $scope, $transclude) {
|
||||||
super($element, $scope);
|
super($element, $scope);
|
||||||
this.$scope = $scope;
|
this.$transclude = $transclude;
|
||||||
this.data = [];
|
this.readOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$onInit() {
|
get data() {
|
||||||
this.refresh();
|
return this._data;
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
set data(value) {
|
||||||
this.model.refresh().then(() => {
|
this._data = value;
|
||||||
this.data = this.model.data;
|
|
||||||
|
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) {
|
onToggle(item) {
|
||||||
if (item.active)
|
if (item.active)
|
||||||
item.childs = undefined;
|
this.fold(item);
|
||||||
else {
|
else
|
||||||
this.model.applyFilter({}, {parentFk: item.id}).then(() => {
|
this.unfold(item);
|
||||||
const newData = this.model.data;
|
}
|
||||||
|
|
||||||
if (item.childs) {
|
fold(item) {
|
||||||
let childs = item.childs;
|
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 => {
|
childs.forEach(child => {
|
||||||
let index = newData.findIndex(newChild => {
|
let index = newData.findIndex(newChild => {
|
||||||
return newChild.id == child.id;
|
return newChild.id == child.id;
|
||||||
|
@ -54,21 +75,49 @@ export default class Treeview extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
item.childs = newData.sort((a, b) => {
|
if (this.sortFunc) {
|
||||||
if (b.selected !== a.selected) {
|
item.childs = newData.sort((a, b) =>
|
||||||
if (a.selected == null)
|
this.sortFunc({$a: a, $b: b})
|
||||||
return 1;
|
);
|
||||||
if (b.selected == null)
|
}
|
||||||
return -1;
|
}).then(() => item.active = true);
|
||||||
return b.selected - a.selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.name.localeCompare(b.name);
|
onRemove(item) {
|
||||||
});
|
if (this.removeFunc)
|
||||||
});
|
this.removeFunc({$item: item});
|
||||||
}
|
}
|
||||||
|
|
||||||
item.active = !item.active;
|
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})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent) parent.sons++;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop(item, dragged, dropped) {
|
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', {
|
ngModule.component('vnTreeview', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Treeview,
|
controller: Treeview,
|
||||||
bindings: {
|
bindings: {
|
||||||
model: '<',
|
rootLabel: '@',
|
||||||
icons: '<?',
|
data: '<?',
|
||||||
disabled: '<?',
|
|
||||||
selectable: '<?',
|
|
||||||
editable: '<?',
|
|
||||||
draggable: '<?',
|
draggable: '<?',
|
||||||
droppable: '<?',
|
droppable: '<?',
|
||||||
aclRole: '@?'
|
fetchFunc: '&',
|
||||||
}
|
removeFunc: '&?',
|
||||||
|
createFunc: '&?',
|
||||||
|
sortFunc: '&?',
|
||||||
|
readOnly: '<?'
|
||||||
|
},
|
||||||
|
transclude: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
@import "effects";
|
@import "effects";
|
||||||
|
|
||||||
vn-treeview {
|
vn-treeview-childs {
|
||||||
vn-treeview-child {
|
display: block;
|
||||||
display: block
|
|
||||||
}
|
|
||||||
ul {
|
ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -11,37 +10,43 @@ vn-treeview {
|
||||||
li {
|
li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
& > div > .arrow {
|
|
||||||
min-width: 24px;
|
|
||||||
margin-right: 10px;
|
|
||||||
transition: transform 200ms;
|
|
||||||
}
|
|
||||||
&.expanded > div > .arrow {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
& > .node {
|
& > .node {
|
||||||
@extend %clickable;
|
@extend %clickable;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
& > vn-check:not(.indeterminate) {
|
& > div > .arrow {
|
||||||
color: $color-main;
|
min-width: 24px;
|
||||||
|
margin-right: 10px;
|
||||||
|
transition: transform 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
& > .btn {
|
& > div.expanded > .arrow {
|
||||||
border-color: $color-main;
|
transform: rotate(180deg);
|
||||||
}
|
|
||||||
}
|
|
||||||
& > vn-check.checked {
|
|
||||||
color: $color-main;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
padding-left: 2.2em;
|
padding-left: 2.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vn-icon-button {
|
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
|
||||||
|
}
|
|
@ -223,11 +223,8 @@ export default class Watcher extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOriginalData() {
|
loadOriginalData() {
|
||||||
Object.keys(this.data).forEach(key => {
|
const orgData = JSON.parse(JSON.stringify(this.orgData));
|
||||||
delete this.data[key];
|
this.data = Object.assign(this.data, orgData);
|
||||||
});
|
|
||||||
|
|
||||||
this.data = Object.assign(this.data, this.orgData);
|
|
||||||
this.setPristine();
|
this.setPristine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,6 @@ vn-dialog.modal-form {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tpl-body {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
table {
|
table {
|
||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,5 @@
|
||||||
"You can't delete a confirmed order": "You can't delete a confirmed order",
|
"You can't delete a confirmed order": "You can't delete a confirmed order",
|
||||||
"Value has an invalid format": "Value has an invalid format",
|
"Value has an invalid format": "Value has an invalid format",
|
||||||
"The postcode doesn't exists. Ensure you put the correct format": "The postcode doesn't exists. Ensure you put the correct format",
|
"The postcode doesn't exists. Ensure you put the correct format": "The postcode doesn't exists. Ensure you put the correct format",
|
||||||
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
|
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket"
|
||||||
"is not a valid date": "is not a valid date",
|
|
||||||
"not zone with this parameters": "not zone with this parameters"
|
|
||||||
}
|
}
|
|
@ -108,5 +108,5 @@
|
||||||
"This postal code is not valid": "This postal code is not valid",
|
"This postal code is not valid": "This postal code is not valid",
|
||||||
"is invalid": "is invalid",
|
"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",
|
||||||
"not zone with this parameters": "not zone with this parameters"
|
"The department name can't be repeated": "El nombre del departamento no puede repetirse"
|
||||||
}
|
}
|
|
@ -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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -6,10 +6,11 @@ module.exports = Self => {
|
||||||
{
|
{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
|
description: 'The zone id',
|
||||||
http: {source: 'path'},
|
http: {source: 'path'},
|
||||||
required: true
|
required: true
|
||||||
}, {
|
}, {
|
||||||
arg: 'parentFk',
|
arg: 'parentId',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
description: 'Get the children of the specified father',
|
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(
|
let [res] = await Self.rawSql(
|
||||||
`CALL zone_getLeaves(?, ?, ?)`,
|
`CALL zone_getLeaves(?, ?, ?)`,
|
||||||
[id, parentFk, search]
|
[id, parentId, search]
|
||||||
);
|
);
|
||||||
|
|
||||||
let map = new Map();
|
let map = new Map();
|
||||||
|
@ -49,7 +50,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let leaves = map.get(parentFk);
|
let leaves = map.get(parentId);
|
||||||
setLeaves(leaves);
|
setLeaves(leaves);
|
||||||
|
|
||||||
return leaves || [];
|
return leaves || [];
|
||||||
|
|
|
@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
require('../methods/zone-included/toggleIsIncluded')(Self);
|
|
||||||
};
|
|
|
@ -3,6 +3,7 @@ module.exports = Self => {
|
||||||
require('../methods/zone/editPrices')(Self);
|
require('../methods/zone/editPrices')(Self);
|
||||||
require('../methods/zone/getLeaves')(Self);
|
require('../methods/zone/getLeaves')(Self);
|
||||||
require('../methods/zone/getEvents')(Self);
|
require('../methods/zone/getEvents')(Self);
|
||||||
|
require('../methods/zone/toggleIsIncluded')(Self);
|
||||||
|
|
||||||
Self.validatesPresenceOf('agencyModeFk', {
|
Self.validatesPresenceOf('agencyModeFk', {
|
||||||
message: `Agency cannot be blank`
|
message: `Agency cannot be blank`
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="model"
|
vn-id="model"
|
||||||
url="/api/Zones/{{$ctrl.$stateParams.id}}/getLeaves"
|
url="/api/Zones/{{$ctrl.$stateParams.id}}/getLeaves"
|
||||||
filter="::$ctrl.filter"
|
filter="::$ctrl.filter">
|
||||||
auto-load="false">
|
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<div class="vn-w-md">
|
<div class="vn-w-md">
|
||||||
<vn-card class="vn-px-lg">
|
<vn-card class="vn-px-lg">
|
||||||
<vn-searchbar
|
<vn-searchbar auto-load="false"
|
||||||
on-search="$ctrl.onSearch($params)"
|
on-search="$ctrl.onSearch($params)"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-searchbar>
|
</vn-searchbar>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-card class="vn-pa-lg vn-mt-md">
|
<vn-card class="vn-pa-lg vn-mt-md">
|
||||||
<vn-treeview
|
<vn-treeview vn-id="treeview" root-label="Locations"
|
||||||
vn-id="treeview"
|
fetch-func="$ctrl.onFetch($item)"
|
||||||
model="model"
|
sort-func="$ctrl.onSort($a, $b)">
|
||||||
acl-role="deliveryBoss"
|
<vn-check
|
||||||
on-selection="$ctrl.onSelection(item, value)"
|
field="item.selected"
|
||||||
selectable="true">
|
on-change="$ctrl.onSelection(value, item)"
|
||||||
|
triple-state="true"
|
||||||
|
label="{{::item.name}}">
|
||||||
|
</vn-check>
|
||||||
</vn-treeview>
|
</vn-treeview>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</div>
|
</div>
|
|
@ -1,10 +1,32 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
import Section from 'core/lib/section';
|
import Section from 'core/lib/section';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
onSearch(params) {
|
onSearch(params) {
|
||||||
this.$.model.applyFilter(null, params);
|
this.$.model.applyFilter({}, params).then(() => {
|
||||||
this.$.$applyAsync(() => this.$.treeview.refresh());
|
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) {
|
exprBuilder(param, value) {
|
||||||
|
@ -14,13 +36,11 @@ class Controller extends Section {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelection(item, isIncluded) {
|
onSelection(value, item) {
|
||||||
let node = Object.assign({}, item);
|
if (value == null)
|
||||||
node.isIncluded = isIncluded;
|
value = undefined;
|
||||||
node.childs = []; // Data too large
|
const params = {geoId: item.id, isIncluded: value};
|
||||||
|
const path = `/api/zones/${this.zone.id}/toggleIsIncluded`;
|
||||||
const path = '/agency/api/ZoneIncludeds/toggleIsIncluded';
|
|
||||||
const params = {zoneFk: this.zone.id, item: node};
|
|
||||||
this.$http.post(path, params);
|
this.$http.post(path, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,8 +86,9 @@ module.exports = Self => {
|
||||||
return {'i.hasPdf': value};
|
return {'i.hasPdf': value};
|
||||||
case 'created':
|
case 'created':
|
||||||
return {'i.created': value};
|
return {'i.created': value};
|
||||||
case 'amount':
|
|
||||||
case 'clientFk':
|
case 'clientFk':
|
||||||
|
return {'i.clientFk': value};
|
||||||
|
case 'amount':
|
||||||
case 'companyFk':
|
case 'companyFk':
|
||||||
case 'issued':
|
case 'issued':
|
||||||
case 'dued':
|
case 'dued':
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
url="/item/api/TaxClasses"
|
url="api/TaxClasses"
|
||||||
fields="['id', 'description', 'code']"
|
fields="['id', 'description', 'code']"
|
||||||
data="classes"
|
data="classes"
|
||||||
auto-load="true">
|
auto-load="true">
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
</vn-watcher>
|
</vn-watcher>
|
||||||
<form name="form" ng-submit="$ctrl.submit()" compact>
|
<form name="form" ng-submit="$ctrl.submit()" compact>
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-horizontal ng-repeat="tax in $ctrl.taxes track by $index">
|
<vn-horizontal ng-repeat="tax in $ctrl.taxes">
|
||||||
<vn-textfield vn-one
|
<vn-textfield vn-one
|
||||||
label="Country"
|
label="Country"
|
||||||
model="tax.country.country"
|
model="tax.country.country"
|
||||||
|
@ -21,7 +21,6 @@
|
||||||
label="Class"
|
label="Class"
|
||||||
field="tax.taxClassFk"
|
field="tax.taxClassFk"
|
||||||
data="classes"
|
data="classes"
|
||||||
value-field="id"
|
|
||||||
show-field="description">
|
show-field="description">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
|
|
||||||
export default class Controller {
|
export default class Controller {
|
||||||
constructor($stateParams, $http, $translate, vnApp) {
|
constructor($stateParams, $http, $translate, $scope) {
|
||||||
|
this.$ = $scope;
|
||||||
this.$http = $http;
|
this.$http = $http;
|
||||||
this.$stateParams = $stateParams;
|
this.$stateParams = $stateParams;
|
||||||
this._ = $translate;
|
this._ = $translate;
|
||||||
this.vnApp = vnApp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
|
@ -21,9 +21,8 @@ export default class Controller {
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
|
||||||
let urlFilter = encodeURIComponent(JSON.stringify(filter));
|
let url = `api/Items/${this.$stateParams.id}/taxes`;
|
||||||
let url = `/item/api/Items/${this.$stateParams.id}/taxes?filter=${urlFilter}`;
|
this.$http.get(url, {params: {filter}}).then(json => {
|
||||||
this.$http.get(url).then(json => {
|
|
||||||
this.taxes = json.data;
|
this.taxes = json.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -33,14 +32,16 @@ export default class Controller {
|
||||||
for (let tax of this.taxes)
|
for (let tax of this.taxes)
|
||||||
data.push({id: tax.id, taxClassFk: tax.taxClassFk});
|
data.push({id: tax.id, taxClassFk: tax.taxClassFk});
|
||||||
|
|
||||||
let url = `/item/api/Items/updateTaxes`;
|
this.$.watcher.check();
|
||||||
this.$http.post(url, data).then(
|
let url = `api/Items/updateTaxes`;
|
||||||
() => this.vnApp.showSuccess(this._.instant('Data saved!'))
|
this.$http.post(url, data).then(() => {
|
||||||
);
|
this.$.watcher.notifySaved();
|
||||||
|
this.$.watcher.updateOriginalData();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$stateParams', '$http', '$translate', 'vnApp'];
|
Controller.$inject = ['$stateParams', '$http', '$translate', '$scope'];
|
||||||
|
|
||||||
ngModule.component('vnItemTax', {
|
ngModule.component('vnItemTax', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
|
|
|
@ -2,65 +2,65 @@ import './index.js';
|
||||||
|
|
||||||
describe('Item', () => {
|
describe('Item', () => {
|
||||||
describe('Component vnItemTax', () => {
|
describe('Component vnItemTax', () => {
|
||||||
|
let $element;
|
||||||
let $stateParams;
|
let $stateParams;
|
||||||
let controller;
|
let controller;
|
||||||
let $httpBackend;
|
let $httpBackend;
|
||||||
let vnApp;
|
|
||||||
|
|
||||||
beforeEach(angular.mock.module('item', $translateProvider => {
|
beforeEach(angular.mock.module('item', $translateProvider => {
|
||||||
$translateProvider.translations('en', {});
|
$translateProvider.translations('en', {});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, _$stateParams_, _vnApp_) => {
|
beforeEach(angular.mock.inject((_$httpBackend_, $rootScope, _$stateParams_, $compile) => {
|
||||||
$stateParams = _$stateParams_;
|
$stateParams = _$stateParams_;
|
||||||
$stateParams.id = 1;
|
$stateParams.id = 1;
|
||||||
$httpBackend = _$httpBackend_;
|
$httpBackend = _$httpBackend_;
|
||||||
vnApp = _vnApp_;
|
|
||||||
spyOn(vnApp, 'showSuccess');
|
$httpBackend.whenGET(url => url.startsWith(`api/TaxClasses`))
|
||||||
controller = $componentController('vnItemTax', {$state: $stateParams});
|
.respond([
|
||||||
|
{id: 1, description: 'Reduced VAT', code: 'R'},
|
||||||
|
{id: 2, description: 'General VAT', code: 'G'}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$httpBackend.whenGET(url => url.startsWith(`api/Items/${$stateParams.id}/taxes`))
|
||||||
|
.respond([
|
||||||
|
{id: 1, taxClassFk: 1}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$element = $compile(`<vn-item-tax></vn-item-tax`)($rootScope);
|
||||||
|
controller = $element.controller('vnItemTax');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
$element.remove();
|
||||||
|
});
|
||||||
|
|
||||||
describe('getTaxes()', () => {
|
describe('getTaxes()', () => {
|
||||||
it('should perform a query to set the array of taxes into the controller', () => {
|
it('should perform a query to set the array of taxes into the controller', () => {
|
||||||
let filter = {
|
|
||||||
fields: ['id', 'countryFk', 'taxClassFk'],
|
|
||||||
include: [{
|
|
||||||
relation: 'country',
|
|
||||||
scope: {fields: ['country']}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
let response = [{id: 1, taxClassFk: 1}];
|
|
||||||
filter = encodeURIComponent(JSON.stringify(filter));
|
|
||||||
$httpBackend.when('GET', `/item/api/Items/1/taxes?filter=${filter}`).respond(response);
|
|
||||||
$httpBackend.expect('GET', `/item/api/Items/1/taxes?filter=${filter}`);
|
|
||||||
controller.$onInit();
|
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.taxes).toEqual(response);
|
expect(controller.taxes[0].id).toEqual(1);
|
||||||
|
expect(controller.taxes[0].taxClassFk).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('submit()', () => {
|
describe('submit()', () => {
|
||||||
it('should perform a post to update taxes', () => {
|
it('should perform a post to update taxes', () => {
|
||||||
let filter = {
|
spyOn(controller.$.watcher, 'notifySaved');
|
||||||
fields: ['id', 'countryFk', 'taxClassFk'],
|
spyOn(controller.$.watcher, 'updateOriginalData');
|
||||||
include: [{
|
controller.taxes = [
|
||||||
relation: 'country',
|
{id: 37, countryFk: 1, taxClassFk: 1, country: {id: 1, country: 'España'}}
|
||||||
scope: {fields: ['country']}
|
];
|
||||||
}]
|
controller.$.watcher.data = [
|
||||||
};
|
{id: 37, countryFk: 1, taxClassFk: 2, country: {id: 1, country: 'España'}}
|
||||||
let response = [{id: 1, taxClassFk: 1}];
|
];
|
||||||
filter = encodeURIComponent(JSON.stringify(filter));
|
|
||||||
$httpBackend.when('GET', `/item/api/Items/1/taxes?filter=${filter}`).respond(response);
|
|
||||||
controller.$onInit();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
$httpBackend.when('POST', `/item/api/Items/updateTaxes`).respond('ok');
|
$httpBackend.whenPOST(`api/Items/updateTaxes`).respond('oki doki');
|
||||||
$httpBackend.expect('POST', `/item/api/Items/updateTaxes`);
|
|
||||||
controller.submit();
|
controller.submit();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
expect(controller.$.watcher.notifySaved).toHaveBeenCalledWith();
|
||||||
|
expect(controller.$.watcher.updateOriginalData).toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,9 +8,6 @@
|
||||||
<vn-label-value label="Nickname"
|
<vn-label-value label="Nickname"
|
||||||
value="{{$ctrl.summary.address.nickname}}">
|
value="{{$ctrl.summary.address.nickname}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Warehouse"
|
|
||||||
value="{{$ctrl.summary.sourceApp}}">
|
|
||||||
</vn-label-value>
|
|
||||||
<vn-check label="Confirmed" disabled="true"
|
<vn-check label="Confirmed" disabled="true"
|
||||||
field="$ctrl.summary.isConfirmed">
|
field="$ctrl.summary.isConfirmed">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
|
@ -27,6 +24,9 @@
|
||||||
<vn-label-value label="Phone"
|
<vn-label-value label="Phone"
|
||||||
value="{{$ctrl.summary.address.phone}}">
|
value="{{$ctrl.summary.address.phone}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Created from"
|
||||||
|
value="{{$ctrl.summary.sourceApp}}">
|
||||||
|
</vn-label-value>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-one>
|
<vn-one>
|
||||||
<vn-label-value label="{{'Notes'}}"
|
<vn-label-value label="{{'Notes'}}"
|
||||||
|
|
|
@ -45,7 +45,7 @@ module.exports = Self => {
|
||||||
clientFk: ship.clientFk,
|
clientFk: ship.clientFk,
|
||||||
addressFk: ship.addressFk,
|
addressFk: ship.addressFk,
|
||||||
agencyModeFk: ship.agencyModeFk,
|
agencyModeFk: ship.agencyModeFk,
|
||||||
warehouse: {neq: ship.warehouseFk},
|
warehouseFk: {neq: ship.warehouseFk},
|
||||||
shipped: {
|
shipped: {
|
||||||
between: [lowestDate.toJSON(), highestDate.toJSON()]
|
between: [lowestDate.toJSON(), highestDate.toJSON()]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<vn-crud-model
|
<vn-crud-model vn-id="model"
|
||||||
url="/api/Tickets/{{$ctrl.$stateParams.id}}/getPossibleStowaways"
|
url="/api/Tickets/{{$ctrl.$stateParams.id}}/getPossibleStowaways"
|
||||||
vn-id="model"
|
|
||||||
data="possibleStowaways">
|
data="possibleStowaways">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<vn-dialog
|
<vn-dialog
|
||||||
vn-id="dialog"
|
vn-id="dialog"
|
||||||
class="modal-form"
|
class="modal-form"
|
||||||
on-open="model.reload()">
|
on-open="model.refresh()">
|
||||||
<tpl-body>
|
<tpl-body>
|
||||||
<vn-horizontal class="header vn-pa-md">
|
<vn-horizontal class="header vn-pa-md vn-w-lg">
|
||||||
<h5><span translate>Stowaways to add</span></h5>
|
<h5><span translate>Stowaways to add</span></h5>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal class="vn-pa-md">
|
<vn-horizontal class="vn-pa-md vn-w-lg">
|
||||||
|
<vn-data-viewer class="vn-w-xs" model="model">
|
||||||
<vn-table model="model" auto-load="false">
|
<vn-table model="model" auto-load="false">
|
||||||
<vn-thead>
|
<vn-thead>
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
|
@ -32,6 +32,7 @@
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
</vn-tbody>
|
</vn-tbody>
|
||||||
</vn-table>
|
</vn-table>
|
||||||
|
</vn-data-viewer>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
</vn-dialog>
|
</vn-dialog>
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,20 +1,14 @@
|
||||||
|
|
||||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('getLeaves', {
|
Self.remoteMethod('getLeaves', {
|
||||||
description: 'Returns the first shipped and landed possible for params',
|
description: 'Returns the nodes for a department',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'parentFk',
|
arg: 'parentId',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
default: 1,
|
description: 'Get the children of the specified father',
|
||||||
required: false,
|
}, {
|
||||||
},
|
arg: 'search',
|
||||||
{
|
type: 'String',
|
||||||
arg: 'filter',
|
description: 'Filter nodes whose name starts with',
|
||||||
type: 'Object',
|
|
||||||
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
|
|
||||||
http: {source: 'query'}
|
|
||||||
}],
|
}],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['object'],
|
type: ['object'],
|
||||||
|
@ -26,61 +20,30 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.getLeaves = async(parentFk = 1, filter) => {
|
Self.getLeaves = async(parentId = null, search) => {
|
||||||
let conn = Self.dataSource.connector;
|
let [res] = await Self.rawSql(
|
||||||
let stmt = new ParameterizedSQL(
|
`CALL department_getLeaves(?, ?)`,
|
||||||
`SELECT
|
[parentId, search]
|
||||||
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]);
|
|
||||||
|
|
||||||
// Get nodes from depth greather than Origin
|
let map = new Map();
|
||||||
stmt.merge(conn.makeSuffix(filter));
|
for (let node of res) {
|
||||||
|
if (!map.has(node.parentFk))
|
||||||
const nodes = await Self.rawStmt(stmt);
|
map.set(node.parentFk, []);
|
||||||
|
map.get(node.parentFk).push(node);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLeaves(parent) {
|
function setLeaves(nodes) {
|
||||||
let elements = nodes.filter(element => {
|
if (!nodes) return;
|
||||||
return element.lft > parent.lft && element.rgt < parent.rgt
|
for (let node of nodes) {
|
||||||
&& element.depth === parent.depth + 1;
|
node.childs = map.get(node.id);
|
||||||
});
|
setLeaves(node.childs);
|
||||||
|
}
|
||||||
return elements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return leaves;
|
let leaves = map.get(parentId);
|
||||||
|
setLeaves(leaves);
|
||||||
|
|
||||||
|
return leaves || [];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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();
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,15 +1,5 @@
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/department/getLeaves')(Self);
|
require('../methods/department/getLeaves')(Self);
|
||||||
require('../methods/department/nodeAdd')(Self);
|
require('../methods/department/createChild')(Self);
|
||||||
require('../methods/department/nodeDelete')(Self);
|
require('../methods/department/removeChild')(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;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "String"
|
"type": "String"
|
||||||
|
},
|
||||||
|
"parentFk": {
|
||||||
|
"type": "Number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,24 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="model"
|
vn-id="model"
|
||||||
url="/worker/api/departments/getLeaves"
|
url="/worker/api/departments/getLeaves"
|
||||||
params="::$ctrl.params"
|
|
||||||
auto-load="false">
|
auto-load="false">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
|
<div class="content-block" compact>
|
||||||
<form name="form">
|
<form name="form" compact>
|
||||||
<div class="vn-ma-md">
|
|
||||||
<vn-card class="vn-my-md vn-pa-md">
|
<vn-card class="vn-my-md vn-pa-md">
|
||||||
<vn-treeview vn-id="treeview" model="model"
|
<vn-treeview vn-id="treeview" root-label="Departments" read-only="false"
|
||||||
on-selection="$ctrl.onSelection(item, value)"
|
fetch-func="$ctrl.onFetch($item)"
|
||||||
on-create="$ctrl.onCreate(parent)"
|
remove-func="$ctrl.onRemove($item)"
|
||||||
on-drop="$ctrl.onDrop(item, dragged, dropped)"
|
create-func="$ctrl.onCreate($parent)"
|
||||||
icons="$ctrl.icons"
|
sort-func="$ctrl.onSort($a, $b)">
|
||||||
draggable="true" droppable="true"
|
{{::item.name}}
|
||||||
acl-role="hr" editable="true">
|
|
||||||
</vn-treeview>
|
</vn-treeview>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</div>
|
<vn-button-bar>
|
||||||
|
<vn-button ui-sref="worker.index" label="Back"></vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
vn-id="deleteNode"
|
vn-id="deleteNode"
|
||||||
on-response="$ctrl.onRemoveResponse(response)"
|
on-response="$ctrl.onRemoveResponse(response)"
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-textfield vn-one
|
<vn-textfield vn-one
|
||||||
label="Name"
|
label="Name"
|
||||||
model="$ctrl.newNode.name">
|
model="$ctrl.newChild.name">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
|
|
|
@ -2,31 +2,28 @@ import ngModule from '../module';
|
||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
constructor($scope, $http, vnApp, $translate) {
|
constructor($scope, $http, vnApp, $translate) {
|
||||||
this.$scope = $scope;
|
this.$ = $scope;
|
||||||
this.$http = $http;
|
this.$http = $http;
|
||||||
this.vnApp = vnApp;
|
this.vnApp = vnApp;
|
||||||
this.$translate = $translate;
|
this.$translate = $translate;
|
||||||
this.params = {parentFk: 1};
|
|
||||||
this.icons = [{icon: 'delete', tooltip: 'Delete', callback: this.onDelete}];
|
|
||||||
this.newNode = {
|
|
||||||
name: ''
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreate(parent) {
|
$postLink() {
|
||||||
if (parent instanceof Object)
|
this.$.treeview.fetch();
|
||||||
this.newNode.parentFk = parent.id;
|
|
||||||
|
|
||||||
this.selectedNode = {parent};
|
|
||||||
this.$scope.createNode.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDelete(item, parent, index) {
|
onFetch(item) {
|
||||||
this.selectedNode = {id: item.id, parent, index};
|
const params = item ? {parentId: item.id} : null;
|
||||||
this.$scope.deleteNode.show();
|
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) {
|
if (dropped.scope.item) {
|
||||||
const droppedItem = dropped.scope.item;
|
const droppedItem = dropped.scope.item;
|
||||||
const draggedItem = dragged.scope.item;
|
const draggedItem = dragged.scope.item;
|
||||||
|
@ -38,27 +35,43 @@ class Controller {
|
||||||
|
|
||||||
this.$scope.$apply();
|
this.$scope.$apply();
|
||||||
}
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
onCreate(parent) {
|
||||||
|
this.newChild = {
|
||||||
|
parent: parent,
|
||||||
|
name: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$.createNode.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateDialogOpen() {
|
onCreateDialogOpen() {
|
||||||
this.newNode.name = '';
|
this.newChild.name = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateResponse(response) {
|
onCreateResponse(response) {
|
||||||
if (response == 'ACCEPT') {
|
if (response == 'ACCEPT') {
|
||||||
try {
|
try {
|
||||||
if (!this.newNode.name)
|
if (!this.newChild.name)
|
||||||
throw new Error(`Name can't be empty`);
|
throw new Error(`Name can't be empty`);
|
||||||
|
|
||||||
this.$http.post(`/worker/api/Departments/nodeAdd`, this.newNode).then(response => {
|
const params = {name: this.newChild.name};
|
||||||
if (response.data) {
|
const parent = this.newChild.parent;
|
||||||
let parent = this.selectedNode.parent;
|
|
||||||
if ((parent instanceof Object) && !(parent instanceof Array)) {
|
if (parent && parent.id)
|
||||||
const childs = parent.childs;
|
params.parentId = parent.id;
|
||||||
childs.push(response.data);
|
|
||||||
} else if ((parent instanceof Object) && (parent instanceof Array))
|
if (!parent.active)
|
||||||
parent.push(response.data);
|
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) {
|
} catch (e) {
|
||||||
this.vnApp.showError(this.$translate.instant(e.message));
|
this.vnApp.showError(this.$translate.instant(e.message));
|
||||||
|
@ -68,17 +81,17 @@ class Controller {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRemove(item) {
|
||||||
|
this.removedChild = item;
|
||||||
|
this.$.deleteNode.show();
|
||||||
|
}
|
||||||
|
|
||||||
onRemoveResponse(response) {
|
onRemoveResponse(response) {
|
||||||
if (response === 'ACCEPT') {
|
if (response === 'ACCEPT') {
|
||||||
const path = `/worker/api/Departments/nodeDelete`;
|
const childId = this.removedChild.id;
|
||||||
this.$http.post(path, {parentFk: this.selectedNode.id}).then(() => {
|
const path = `/api/departments/${childId}/removeChild`;
|
||||||
let parent = this.selectedNode.parent;
|
this.$http.post(path).then(() => {
|
||||||
|
this.$.treeview.remove(this.removedChild);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue