worker department #1133
gitea/salix/dev This commit has test failures Details

This commit is contained in:
Joan Sanchez 2019-03-12 15:04:09 +01:00
parent 4c111b31ef
commit 92170c34d5
30 changed files with 639 additions and 81 deletions

View File

@ -1,2 +1,4 @@
INSERT INTO `salix`.`ACL` (`id`,`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (149, 'Sip', '*', 'WRITE', 'ALLOW', 'ROLE', 'hr'); INSERT INTO `salix`.`ACL` (`id`,`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (149, 'Sip', '*', 'WRITE', 'ALLOW', 'ROLE', 'hr');
INSERT INTO `salix`.`ACL` (`id`,`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (150, 'Sip', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); INSERT INTO `salix`.`ACL` (`id`,`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (150, 'Sip', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`id`,`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (151, 'Department','*','READ','ALLOW','ROLE','employee');
INSERT INTO `salix`.`ACL` (`id`,`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES (152, 'Department','*','WRITE','ALLOW','ROLE','hr');

View File

@ -0,0 +1,21 @@
USE `vn2008`;
ALTER TABLE vn2008.department ADD `depth` int DEFAULT 0 NOT NULL;
ALTER TABLE vn2008.department ADD sons int DEFAULT 0 NOT NULL;
USE `vn`;
CREATE
OR REPLACE
VIEW `vn`.`department` AS select
`b`.`department_id` AS `id`,
`b`.`name` AS `name`,
`b`.`father_id` AS `fatherFk`,
`b`.`production` AS `isProduction`,
`b`.`lft` AS `lft`,
`b`.`rgt` AS `rgt`,
`b`.`isSelected` AS `isSelected`,
`b`.`depth` AS `depth`,
`b`.`sons` AS `sons`
from
`vn2008`.`department` `b`;

View File

@ -0,0 +1,83 @@
DROP PROCEDURE IF EXISTS nst.NodeAdd;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `nst`.`nodeAdd`(IN `vScheme` VARCHAR(45), IN `vTable` VARCHAR(45), IN `vParentFk` INT, IN `vChild` VARCHAR(100))
BEGIN
DECLARE vSql TEXT;
DECLARE vTableClone VARCHAR(45);
SET vTableClone = CONCAT(vTable, 'Clone');
CALL util.exec(CONCAT('DROP TEMPORARY TABLE IF EXISTS tmp.', vTableClone));
CALL util.exec(CONCAT(
'CREATE TEMPORARY TABLE tmp.', vTableClone,
' ENGINE = MEMORY'
' SELECT * FROM ', vScheme, '.', vTable
));
-- Check parent childs
SET vSql = sql_printf('
SELECT COUNT(c.id) INTO @childs
FROM %t.%t p
LEFT JOIN %t.%t c ON c.lft BETWEEN p.lft AND p.rgt AND c.id != %v
WHERE p.id = %v',
vScheme, vTable, 'tmp', vTableClone, vParentFk, vParentFk);
SET @qrySql := vSql;
PREPARE stmt FROM @qrySql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Select left from last child
IF @childs = 0 THEN
SET vSql = sql_printf('SELECT lft INTO @vLeft FROM %t.%t WHERE id = %v', vScheme, vTable, vParentFk);
SET @qrySql := vSql;
ELSE
SET vSql = sql_printf('
SELECT c.rgt INTO @vLeft
FROM %t.%t p
JOIN %t.%t c ON c.lft BETWEEN p.lft AND p.rgt
WHERE p.id = %v
ORDER BY c.lft
DESC LIMIT 1',
vScheme, vTable, 'tmp', vTableClone, vParentFk);
SET @qrySql := vSql;
END IF;
PREPARE stmt FROM @qrySql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Update right
SET vSql = sql_printf('UPDATE %t.%t SET rgt = rgt + 2 WHERE rgt > %v ORDER BY rgt DESC', vScheme, vTable, @vLeft);
SET @qrySql := vSql;
PREPARE stmt FROM @qrySql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET vSql = sql_printf('UPDATE %t.%t SET lft = lft + 2 WHERE lft > %v ORDER BY lft DESC', vScheme, vTable, @vLeft);
SET @qrySql := vSql;
PREPARE stmt FROM @qrySql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Escape character
SET vChild = REPLACE(vChild, "'", "\\'");
-- Add child
SET vSql = sql_printf('INSERT INTO %t.%t (name, lft, rgt) VALUES (%v, %v, %v)', vScheme, vTable, vChild, @vLeft + 1, @vLeft + 2);
SET @qrySql := vSql;
PREPARE stmt FROM @qrySql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SELECT id, name, lft, rgt, depth, sons FROM vn.department
WHERE id = LAST_INSERT_ID();
CALL util.exec(CONCAT('DROP TEMPORARY TABLE tmp.', vTableClone));
END$$
DELIMITER ;

View File

@ -0,0 +1,28 @@
DROP PROCEDURE IF EXISTS nst.nodeRecalc;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `nst`.`nodeRecalc`(IN `vScheme` VARCHAR(45), IN `vTable` VARCHAR(45))
BEGIN
CALL util.exec (sql_printf (
'UPDATE %t.%t d
JOIN (SELECT
node.id,
COUNT(parent.id) - 1 as depth,
cast((node.rgt - node.lft - 1) / 2 as DECIMAL) as sons
FROM
%t.%t AS node,
%t.%t AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.id
ORDER BY node.lft) n ON n.id = d.id
SET d.`depth` = n.depth, d.sons = n.sons',
vScheme,
vTable,
vScheme,
vTable,
vScheme,
vTable
));
END$$
DELIMITER ;

View File

@ -0,0 +1,4 @@
USE `vn`;
CREATE UNIQUE INDEX zoneGeo_lft_IDX USING BTREE ON vn.zoneGeo (lft);
CREATE UNIQUE INDEX zoneGeo_rgt_IDX USING BTREE ON vn.zoneGeo (rgt);

View File

@ -1,25 +1,54 @@
<ul ng-if="$ctrl.items"> <ul ng-if="::$ctrl.items">
<li ng-repeat="item in $ctrl.items" <li ng-repeat="item in $ctrl.items"
ng-class="{ ng-class="{
'expanded': item.active, 'expanded': item.active,
'collapsed': !item.active, 'collapsed': !item.active,
'included': item.isIncluded == 1, 'included': item.selected == 1,
'excluded': item.isIncluded == 0 'excluded': item.selected == 0
}"> }">
<vn-horizontal> <vn-horizontal>
<vn-auto class="actions"> <vn-auto class="actions">
<vn-icon icon="keyboard_arrow_down" <vn-icon icon="keyboard_arrow_down" title="{{'Toggle' | translate}}"
ng-show="item.sons > 0" ng-click="$ctrl.toggle(item, $event)">
ng-click="$ctrl.toggle(item, $event)" >
</vn-icon> </vn-icon>
</vn-auto> </vn-auto>
<div class="description"> <vn-one class="description">
<vn-check vn-auto field="item.isIncluded" <vn-check vn-auto vn-acl="{{$ctrl.aclRole}}"
on-change="$ctrl.select(item, value)" triple-state="true"> ng-if="$ctrl.selectable"
field="item.selected"
disabled="$ctrl.disabled"
on-change="$ctrl.select(item, value)"
triple-state="true">
</vn-check> </vn-check>
{{::item.name}} {{::item.name}}
</vn-one>
<vn-auto>
<vn-icon-button icon="{{icon.icon}}"
ng-repeat="icon in $ctrl.icons"
ng-click="$ctrl.onClick(icon, item, $ctrl.parent, $parent.$index)"
vn-acl="{{$ctrl.aclRole}}" vn-acl-action="remove">
</vn-icon-button>
</vn-auto>
</vn-horizontal>
<vn-treeview-child items="item.childs" parent="item"
selectable="$ctrl.selectable"
disabled="$ctrl.disabled"
editable="$ctrl.editable"
icons="$ctrl.icons"
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">
<vn-horizontal>
<vn-auto>
<vn-icon-button icon="add_circle"></vn-icon-button>
</vn-auto>
<div class="description" translate>
Create new one
</div> </div>
</vn-horizontal> </vn-horizontal>
<vn-treeview-child items="item.childs"></vn-treeview-child>
</li> </li>
</ul> </ul>

View File

@ -4,6 +4,7 @@ import Component from '../../lib/component';
class Controller extends Component { class Controller extends Component {
constructor($element, $scope) { constructor($element, $scope) {
super($element, $scope); super($element, $scope);
this.$scope = $scope;
} }
toggle(item) { toggle(item) {
@ -13,13 +14,33 @@ class Controller extends Component {
select(item, value) { select(item, value) {
this.treeview.onSelection(item, value); this.treeview.onSelection(item, value);
} }
onClick(icon, item, parent, index) {
let parentScope = this.$scope.$parent.$parent;
let parentController = parentScope.$ctrl;
icon.callback.call(parentController, item, parent, index);
}
onCreate(parent) {
this.treeview.onCreate(parent);
}
get isInsertable() {
return Array.isArray(this.parent) || this.parent.childs;
}
} }
ngModule.component('vnTreeviewChild', { ngModule.component('vnTreeviewChild', {
template: require('./child.html'), template: require('./child.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
items: '<' items: '<',
parent: '<',
icons: '<?',
disabled: '<?',
selectable: '<?',
editable: '<?',
aclRole: '<?',
}, },
require: { require: {
treeview: '^vnTreeview' treeview: '^vnTreeview'

View File

@ -1 +1,9 @@
<vn-treeview-child items="$ctrl.data"></vn-treeview-child> <vn-treeview-child
items="$ctrl.data"
parent="$ctrl.data"
selectable="$ctrl.selectable"
editable="$ctrl.editable"
disabled="$ctrl.disabled"
icons="$ctrl.icons"
acl-role="$ctrl.aclRole">
</vn-treeview-child>

View File

@ -23,10 +23,19 @@ export default class Treeview extends Component {
}); });
} }
/**
* Emits selection event
* @param {Object} item - Selected item
* @param {Boolean} value - Changed value
*/
onSelection(item, value) { onSelection(item, value) {
this.emit('selection', {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; item.childs = undefined;
@ -45,12 +54,12 @@ export default class Treeview extends Component {
} }
item.childs = newData.sort((a, b) => { item.childs = newData.sort((a, b) => {
if (b.isIncluded !== a.isIncluded) { if (b.selected !== a.selected) {
if (a.isIncluded == null) if (a.selected == null)
return 1; return 1;
if (b.isIncluded == null) if (b.selected == null)
return -1; return -1;
return b.isIncluded - a.isIncluded; return b.selected - a.selected;
} }
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
@ -68,6 +77,11 @@ ngModule.component('vnTreeview', {
template: require('./index.html'), template: require('./index.html'),
controller: Treeview, controller: Treeview,
bindings: { bindings: {
model: '<' model: '<',
icons: '<?',
disabled: '<?',
selectable: '<?',
editable: '<?',
aclRole: '@?'
} }
}); });

View File

@ -21,7 +21,7 @@ vn-treeview {
} }
li ul { li ul {
padding: 0 1.8em; padding-left: 1.8em;
} }
li > vn-horizontal { li > vn-horizontal {
@ -62,4 +62,8 @@ vn-treeview {
} }
} }
} }
vn-icon-button {
padding: 0
}
} }

View File

@ -36,7 +36,7 @@ function vnAcl(aclService, $timeout) {
return conditions; return conditions;
} }
function permissionElement($element, action) { function permissionElement($scope, $element, action) {
if (!aclService.hasAny(acls)) { if (!aclService.hasAny(acls)) {
if (action === 'disabled') { if (action === 'disabled') {
let input = $element[0]; let input = $element[0];
@ -72,9 +72,13 @@ function vnAcl(aclService, $timeout) {
priority: -1, priority: -1,
link: function($scope, $element, $attrs) { link: function($scope, $element, $attrs) {
acls = $attrs.vnAcl.split(',').map(i => i.trim()); acls = $attrs.vnAcl.split(',').map(i => i.trim());
if (acls[0] == '') return;
let action = $attrs.vnAclAction || 'disabled'; let action = $attrs.vnAclAction || 'disabled';
let conditions = getDynamicConditions($attrs); let conditions = getDynamicConditions($attrs);
permissionElement($element, action);
permissionElement($scope, $element, action);
if (Object.keys(conditions).length) { if (Object.keys(conditions).length) {
let watchConditions = $scope.$watch(() => { let watchConditions = $scope.$watch(() => {
@ -82,7 +86,7 @@ function vnAcl(aclService, $timeout) {
let hasPermission = $scope.$eval($attrs[attrName]); let hasPermission = $scope.$eval($attrs[attrName]);
if (!hasPermission) { if (!hasPermission) {
updateAcls(conditions[attrName].role, hasPermission); updateAcls(conditions[attrName].role, hasPermission);
permissionElement($element, action); permissionElement($scope, $element, action);
delete conditions[attrName]; delete conditions[attrName];
} }
}); });

View File

@ -39,4 +39,6 @@ November: Noviembre
December: Diciembre December: Diciembre
Has delivery: Hay reparto Has delivery: Hay reparto
Loading: Cargando Loading: Cargando
Fields to show: Campos a mostrar Fields to show: Campos a mostrar
Create new one: Crear nuevo
Toggle: Desplegar/Plegar

View File

@ -128,44 +128,33 @@ module.exports = function(Self) {
return replaceErrFunc(err); return replaceErrFunc(err);
} }
function rewriteMethod(methodName) {
const realMethod = this[methodName];
return async(data, options, cb) => {
if (options instanceof Function) {
cb = options;
options = null;
}
try {
await realMethod.call(this, data, options);
if (cb) cb();
} catch (err) {
let myErr = replaceErr(err, replaceErrFunc);
if (cb)
cb(myErr);
else
throw myErr;
}
};
}
this.once('attached', () => { this.once('attached', () => {
let realUpsert = this.upsert; this.remove =
this.upsert = async(data, options, cb) => { this.deleteAll =
if (options instanceof Function) { this.destroyAll = rewriteMethod.call(this, 'remove');
cb = options; this.upsert = rewriteMethod.call(this, 'upsert');
options = null; this.create = rewriteMethod.call(this, 'create');
}
try {
await realUpsert.call(this, data, options);
if (cb) cb();
} catch (err) {
let myErr = replaceErr(err, replaceErrFunc);
if (cb)
cb(myErr);
else
throw myErr;
}
};
let realCreate = this.create;
this.create = async(data, options, cb) => {
if (options instanceof Function) {
cb = options;
options = null;
}
try {
await realCreate.call(this, data, options);
if (cb) cb();
} catch (err) {
let myErr = replaceErr(err, replaceErrFunc);
if (cb)
cb(myErr);
else
throw myErr;
}
};
}); });
}, },

View File

@ -38,5 +38,6 @@
"You can't create a ticket for a frozen client": "You can't create a ticket for a frozen client", "You can't create a ticket for a frozen client": "You can't create a ticket for a frozen client",
"can't be blank": "can't be blank", "can't be blank": "can't be blank",
"Street cannot be empty": "Street cannot be empty", "Street cannot be empty": "Street cannot be empty",
"City cannot be empty": "City cannot be empty" "City cannot be empty": "City cannot be empty",
"EXTENSION_INVALID_FORMAT": "EXTENSION_INVALID_FORMAT"
} }

View File

@ -72,5 +72,6 @@
"Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido", "Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido",
"Street cannot be empty": "Dirección no puede estar en blanco", "Street cannot be empty": "Dirección no puede estar en blanco",
"City cannot be empty": "Cuidad no puede estar en blanco", "City cannot be empty": "Cuidad no puede estar en blanco",
"Code cannot be blank": "Código no puede estar en blanco" "Code cannot be blank": "Código no puede estar en blanco",
"You cannot remove this department": "No puedes eliminar este departamento"
} }

View File

@ -73,7 +73,7 @@ module.exports = Self => {
zg.rgt, zg.rgt,
zg.depth, zg.depth,
zg.sons, zg.sons,
IF(ch.id = zg.id, isIncluded, null) isIncluded IF(ch.id = zg.id, isIncluded, null) selected
FROM zoneGeo zg FROM zoneGeo zg
JOIN tmp.checkedChilds ch JOIN tmp.checkedChilds ch
ON zg.lft <= ch.lft AND zg.rgt >= ch.rgt ON zg.lft <= ch.lft AND zg.rgt >= ch.rgt
@ -86,7 +86,7 @@ module.exports = Self => {
child.rgt, child.rgt,
child.depth, child.depth,
child.sons, child.sons,
zi.isIncluded zi.isIncluded AS selected
FROM zoneGeo parent FROM zoneGeo parent
JOIN zoneGeo child ON child.lft > parent.lft JOIN zoneGeo child ON child.lft > parent.lft
AND child.rgt < parent.rgt AND child.rgt < parent.rgt
@ -122,9 +122,11 @@ module.exports = Self => {
function nestLeaves(elements) { function nestLeaves(elements) {
elements.forEach(element => { elements.forEach(element => {
element.childs = Object.assign([], getLeaves(element)); let childs = Object.assign([], getLeaves(element));
if (childs.length > 0) {
nestLeaves(element.childs); element.childs = childs;
nestLeaves(element.childs);
}
}); });
} }
@ -142,12 +144,12 @@ module.exports = Self => {
function sortNodes(nodes) { function sortNodes(nodes) {
return nodes.sort((a, b) => { return nodes.sort((a, b) => {
if (b.isIncluded !== a.isIncluded) { if (b.selected !== a.selected) {
if (a.isIncluded == null) if (a.selected == null)
return 1; return 1;
if (b.isIncluded == null) if (b.selected == null)
return -1; return -1;
return b.isIncluded - a.isIncluded; return b.selected - a.selected;
} }
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);

View File

@ -12,7 +12,7 @@
on-search="$ctrl.onSearch()" on-search="$ctrl.onSearch()"
vn-focus> vn-focus>
</vn-searchbar> </vn-searchbar>
<vn-treeview vn-id="treeview" model="model" <vn-treeview vn-id="treeview" model="model" selectable="true" acl-role="deliveryBoss"
on-selection="$ctrl.onSelection(item, value)"> on-selection="$ctrl.onSelection(item, value)">
</vn-treeview> </vn-treeview>
</vn-card> </vn-card>

View File

@ -0,0 +1,87 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('getLeaves', {
description: 'Returns the first shipped and landed possible for params',
accessType: '',
accepts: [{
arg: 'parentFk',
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'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getLeaves`,
verb: 'GET'
}
});
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]);
// 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);
}
});
}
function getLeaves(parent) {
let elements = nodes.filter(element => {
return element.lft > parent.lft && element.rgt < parent.rgt
&& element.depth === parent.depth + 1;
});
return elements;
}
return leaves;
};
};

View File

@ -0,0 +1,43 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('nodeAdd', {
description: 'Returns the first shipped and landed possible for params',
accessType: '',
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;
};
};

View File

@ -0,0 +1,29 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('nodeDelete', {
description: 'Returns the first shipped and landed possible for params',
accessType: '',
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);
};
};

View File

@ -0,0 +1,19 @@
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`);
return err;
});
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`The department name can't be repeated`);
return err;
});
};

View File

@ -0,0 +1,45 @@
<vn-crud-model
vn-id="model"
url="/worker/api/departments/getLeaves"
params="::$ctrl.params"
auto-load="false">
</vn-crud-model>
<form name="form">
<div margin-medium>
<vn-card margin-medium-v pad-medium>
<vn-treeview vn-id="treeview" model="model"
on-selection="$ctrl.onSelection(item, value)"
on-create="$ctrl.onCreate(parent)"
icons="$ctrl.icons" editable="true" acl-role="hr">
</vn-treeview>
</vn-card>
</div>
</form>
<vn-confirm
vn-id="deleteNode"
on-response="$ctrl.onRemoveResponse(response)"
question="Delete department"
message="Are you sure you want to delete it?">
</vn-confirm>
<!-- Create department dialog -->
<vn-dialog
vn-id="createNode"
on-open="$ctrl.onCreateDialogOpen()"
on-response="$ctrl.onCreateResponse(response)">
<tpl-body>
<h5 pad-small-v translate>New department</h5>
<vn-horizontal>
<vn-textfield vn-one
label="Name"
model="$ctrl.newNode.name">
</vn-textfield>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
<button response="ACCEPT" translate>Create</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,78 @@
import ngModule from '../module';
class Controller {
constructor($scope, $http, vnApp, $translate) {
this.$scope = $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();
}
onDelete(item, parent, index) {
this.selectedNode = {id: item.id, parent, index};
this.$scope.deleteNode.show();
}
onCreateDialogOpen() {
this.newNode.name = '';
}
onCreateResponse(response) {
if (response == 'ACCEPT') {
try {
if (!this.newNode.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);
}
});
} catch (e) {
this.vnApp.showError(this.$translate.instant(e.message));
return false;
}
}
return true;
}
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);
});
}
}
}
Controller.$inject = ['$scope', '$http', 'vnApp', '$translate'];
ngModule.component('vnWorkerDepartment', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,3 @@
New department: Nuevo departamento
Delete department: Eliminar departamento
Are you sure you want to delete it?: ¿Seguro que quieres eliminarlo?

View File

@ -8,3 +8,4 @@ import './descriptor-popover';
import './search-panel'; import './search-panel';
import './basic-data'; import './basic-data';
import './pbx'; import './pbx';
import './department';

View File

@ -8,11 +8,23 @@
<div class="content-block"> <div class="content-block">
<div class="vn-list"> <div class="vn-list">
<vn-card pad-medium-h> <vn-card pad-medium-h>
<vn-searchbar <vn-horizontal>
panel="vn-worker-search-panel" <vn-searchbar
on-search="$ctrl.onSearch($params)" style="width: 100%"
vn-focus> panel="vn-worker-search-panel"
</vn-searchbar> on-search="$ctrl.onSearch($params)"
vn-focus>
</vn-searchbar>
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)">
</vn-icon-menu>
</vn-horizontal>
</vn-card> </vn-card>
<vn-card margin-medium-v> <vn-card margin-medium-v>
<a <a

View File

@ -1,11 +1,16 @@
import ngModule from '../module'; import ngModule from '../module';
import './style.scss';
export default class Controller { export default class Controller {
constructor($) { constructor($, $state) {
this.$state = $state;
Object.assign(this, { Object.assign(this, {
$, $,
selectedWorker: null, selectedWorker: null,
}); });
this.moreOptions = [
{callback: () => this.$state.go('worker.department'), name: 'Departments'}
];
} }
onSearch(params) { onSearch(params) {
@ -22,9 +27,13 @@ export default class Controller {
this.$.preview.show(); this.$.preview.show();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
} }
onMoreChange(callback) {
callback.call(this);
}
} }
Controller.$inject = ['$scope']; Controller.$inject = ['$scope', '$state'];
ngModule.component('vnWorkerIndex', { ngModule.component('vnWorkerIndex', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -0,0 +1,11 @@
@import "variables";
vn-worker-index vn-icon-menu {
padding-top: 30px;
padding-left: 10px;
color: $color-main;
li {
color: initial;
}
}

View File

@ -13,3 +13,4 @@ View worker: Ver trabajador
Worker id: Id trabajador Worker id: Id trabajador
Fiscal Identifier: NIF Fiscal Identifier: NIF
User name: Usuario User name: Usuario
Departments: Departamentos

View File

@ -33,7 +33,8 @@
"component": "vn-worker-card", "component": "vn-worker-card",
"abstract": true, "abstract": true,
"description": "Detail" "description": "Detail"
}, { },
{
"url": "/basic-data", "url": "/basic-data",
"state": "worker.card.basicData", "state": "worker.card.basicData",
"component": "vn-worker-basic-data", "component": "vn-worker-basic-data",
@ -51,6 +52,12 @@
"worker": "$ctrl.worker" "worker": "$ctrl.worker"
}, },
"acl": ["hr"] "acl": ["hr"]
},
{
"url" : "/department",
"state": "worker.department",
"component": "vn-worker-department",
"description": "Departments"
} }
] ]
} }