This commit is contained in:
Bernat Exposito Domenech 2020-09-22 14:00:52 +02:00
commit ce1c4e6f60
344 changed files with 10508 additions and 2024 deletions

View File

@ -0,0 +1,34 @@
module.exports = Self => {
Self.remoteMethod('changePassword', {
description: 'Changes the user password',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The user id',
http: {source: 'path'}
}, {
arg: 'oldPassword',
type: 'String',
description: 'The old password',
required: true
}, {
arg: 'newPassword',
type: 'String',
description: 'The new password',
required: true
}
],
http: {
path: `/:id/changePassword`,
verb: 'PATCH'
}
});
Self.changePassword = async function(id, oldPassword, newPassword) {
await Self.rawSql(`CALL account.user_changePassword(?, ?, ?)`,
[id, oldPassword, newPassword]);
await Self.app.models.UserAccount.syncById(id, newPassword);
};
};

View File

@ -26,9 +26,9 @@ module.exports = Self => {
});
Self.login = async function(user, password) {
let $ = Self.app.models;
let token;
let usesEmail = user.indexOf('@') !== -1;
let User = Self.app.models.User;
let loginInfo = {password};
@ -38,7 +38,7 @@ module.exports = Self => {
loginInfo.username = user;
try {
token = await User.login(loginInfo, 'user');
token = await $.User.login(loginInfo, 'user');
} catch (err) {
if (err.code != 'LOGIN_FAILED' || usesEmail)
throw err;
@ -49,17 +49,8 @@ module.exports = Self => {
if (!instance || instance.password !== md5(password || ''))
throw err;
let where = {id: instance.id};
let userData = {
id: instance.id,
username: user,
password: password,
email: instance.email,
created: instance.created,
updated: instance.updated
};
await User.upsertWithWhere(where, userData);
token = await User.login(loginInfo, 'user');
await $.UserAccount.sync(user, password);
token = await $.User.login(loginInfo, 'user');
}
return {token: token.id};

View File

@ -0,0 +1,29 @@
module.exports = Self => {
Self.remoteMethod('setPassword', {
description: 'Sets the user password',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The user id',
http: {source: 'path'}
}, {
arg: 'newPassword',
type: 'String',
description: 'The new password',
required: true
}
],
http: {
path: `/:id/setPassword`,
verb: 'PATCH'
}
});
Self.setPassword = async function(id, newPassword) {
await Self.rawSql(`CALL account.user_setPassword(?, ?)`,
[id, newPassword]);
await Self.app.models.UserAccount.syncById(id, newPassword);
};
};

View File

@ -0,0 +1,9 @@
const app = require('vn-loopback/server/server');
describe('account changePassword()', () => {
it('should throw an error when old password is wrong', async() => {
let req = app.models.Account.changePassword(null, 1, 'wrongOldPass', 'newPass');
await expectAsync(req).toBeRejected();
});
});

View File

@ -0,0 +1,15 @@
const app = require('vn-loopback/server/server');
describe('account changePassword()', () => {
it('should throw an error when password does not meet requirements', async() => {
let req = app.models.Account.setPassword(1, 'insecurePass');
await expectAsync(req).toBeRejected();
});
it('should update password when it passes requirements', async() => {
let req = app.models.Account.setPassword(1, 'Very$ecurePa22.');
await expectAsync(req).toBeResolved();
});
});

View File

@ -38,6 +38,9 @@
"ImageCollectionSize": {
"dataSource": "vn"
},
"Language": {
"dataSource": "vn"
},
"Province": {
"dataSource": "vn"
},

View File

@ -4,6 +4,8 @@ module.exports = Self => {
require('../methods/account/login')(Self);
require('../methods/account/logout')(Self);
require('../methods/account/acl')(Self);
require('../methods/account/change-password')(Self);
require('../methods/account/set-password')(Self);
require('../methods/account/validate-token')(Self);
// Validations
@ -38,17 +40,9 @@ module.exports = Self => {
Self.getCurrentUserData = async function(ctx) {
let userId = ctx.req.accessToken.userId;
let account = await Self.findById(userId, {
return await Self.findById(userId, {
fields: ['id', 'name', 'nickname']
});
let worker = await Self.app.models.Worker.findOne({
fields: ['id'],
where: {userFk: userId}
});
return Object.assign(account, {workerId: worker.id});
};
/**

View File

@ -5,7 +5,7 @@
"mysql": {
"table": "account.user"
}
},
},
"properties": {
"id": {
"type": "number",
@ -24,10 +24,16 @@
"nickname": {
"type": "string"
},
"lang": {
"type": "string"
},
"password": {
"type": "string",
"required": true
},
"bcryptPassword": {
"type": "string"
},
"active": {
"type": "boolean"
},
@ -41,44 +47,47 @@
"type": "date"
}
},
"relations": {
"role": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "roleFk"
},
"emailUser": {
"type": "hasOne",
"model": "EmailUser",
"foreignKey": "userFk"
"relations": {
"role": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "roleFk"
},
"roles": {
"type": "hasMany",
"model": "RoleRole",
"foreignKey": "role"
},
"emailUser": {
"type": "hasOne",
"model": "EmailUser",
"foreignKey": "userFk"
},
"worker": {
"type": "hasOne",
"model": "Worker",
"foreignKey": "userFk"
}
},
"acls": [
{
}
},
"acls": [
{
"property": "login",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
}, {
"property": "logout",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
}, {
"property": "validateToken",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
]
}
]
}

34
back/models/language.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "Language",
"base": "VnModel",
"options": {
"mysql": {
"table": "hedera.language"
}
},
"properties": {
"code": {
"type": "string",
"id": true
},
"name": {
"type": "string",
"required": true
},
"orgName": {
"type": "string",
"required": true
},
"isActive": {
"type": "boolean"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1,2 +0,0 @@
UPDATE `salix`.`ACL` SET `model` = 'Calendar' WHERE (`id` = '155');
UPDATE `salix`.`ACL` SET `model` = 'Calendar' WHERE (`id` = '157');

View File

@ -1,44 +0,0 @@
USE `vn`;
DROP procedure IF EXISTS `vn`.`itemLastEntries`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `itemLastEntries__`(IN `vItem` INT, IN `vDays` DATE)
BEGIN
SELECT
w.id AS warehouseFk,
w.name AS warehouse,
tr.landed,
b.entryFk,
b.isIgnored,
b.price2,
b.price3,
b.stickers,
b.packing,
b.`grouping`,
b.groupingMode,
b.weight,
i.stems,
b.quantity,
b.buyingValue,
b.packageFk ,
s.id AS supplierFk,
s.name AS supplier
FROM itemType it
RIGHT JOIN (entry e
LEFT JOIN supplier s ON s.id = e.supplierFk
RIGHT JOIN buy b ON b.entryFk = e.id
LEFT JOIN item i ON i.id = b.itemFk
LEFT JOIN ink ON ink.id = i.inkFk
LEFT JOIN travel tr ON tr.id = e.travelFk
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
LEFT JOIN origin o ON o.id = i.originFk
) ON it.id = i.typeFk
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id
WHERE b.itemFk = vItem And tr.shipped BETWEEN vDays AND DATE_ADD(CURDATE(), INTERVAl + 10 DAY)
ORDER BY tr.landed DESC , b.id DESC;
END$$
DELIMITER ;
;

View File

@ -1,47 +0,0 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`zoneEstimatedDelivery` AS
SELECT
`t`.`zoneFk` AS `zoneFk`,
CAST((CURDATE() + INTERVAL ((HOUR(`zc`.`hour`) * 60) + MINUTE(`zc`.`hour`)) MINUTE)
AS TIME) AS `hourTheoretical`,
CAST(SUM(`sv`.`volume`) AS DECIMAL (5 , 1 )) AS `totalVolume`,
CAST(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))
AS DECIMAL (5 , 1 )) AS `remainingVolume`,
GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0)) AS `speed`,
CAST((`zc`.`hour` + INTERVAL ((-(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))) * 60) / GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0))) MINUTE)
AS TIME) AS `hourEffective`,
FLOOR(((-(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))) * 60) / GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0)))) AS `minutesLess`,
CAST((`zc`.`hour` + INTERVAL ((-(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))) * 60) / GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0))) MINUTE)
AS TIME) AS `etc`
FROM
((((((((`ticket` `t`
JOIN `ticketStateToday` `tst` ON ((`tst`.`ticket` = `t`.`id`)))
JOIN `state` `s` ON ((`s`.`id` = `tst`.`state`)))
JOIN `saleVolume` `sv` ON ((`sv`.`ticketFk` = `t`.`id`)))
LEFT JOIN `lastHourProduction` `lhp` ON ((`lhp`.`warehouseFk` = `t`.`warehouseFk`)))
JOIN `warehouse` `w` ON ((`w`.`id` = `t`.`warehouseFk`)))
JOIN `warehouseAlias` `wa` ON ((`wa`.`id` = `w`.`aliasFk`)))
LEFT JOIN `zoneClosure` `zc` ON (((`zc`.`zoneFk` = `t`.`zoneFk`)
AND (`zc`.`dated` = CURDATE()))))
LEFT JOIN `cache`.`departure_limit` `dl` ON (((`dl`.`warehouse_id` = `t`.`warehouseFk`)
AND (`dl`.`fecha` = CURDATE()))))
WHERE
((`wa`.`name` = 'Silla')
AND (CAST(`t`.`shipped` AS DATE) = CURDATE()))
GROUP BY `t`.`zoneFk`;

View File

@ -1,16 +0,0 @@
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`zone_ETD` AS
SELECT
`zed`.`zoneFk` AS `zoneFk`,
`zed`.`hourTheoretical` AS `HoraTeórica`,
`zed`.`totalVolume` AS `volumenTotal`,
`zed`.`remainingVolume` AS `volumenPendiente`,
`zed`.`speed` AS `velocidad`,
`zed`.`hourEffective` AS `HoraPráctica`,
`zed`.`minutesLess` AS `minutesLess`,
`zed`.`etc` AS `etc`
FROM
`vn`.`zoneEstimatedDelivery` `zed`

View File

@ -0,0 +1,6 @@
ALTER TABLE `vn`.`entryLog`
ADD COLUMN `changedModel` varchar(45) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
ADD COLUMN `oldInstance` text CHARACTER SET utf8 COLLATE utf8_unicode_ci,
ADD COLUMN `newInstance` text CHARACTER SET utf8 COLLATE utf8_unicode_ci,
ADD COLUMN `changedModelId` int(11) DEFAULT NULL,
ADD COLUMN `changedModelValue` varchar(45) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL;

View File

@ -0,0 +1,127 @@
ALTER TABLE `account`.`role`
MODIFY COLUMN `hasLogin` tinyint(3) unsigned DEFAULT 1 NOT NULL;
ALTER TABLE `account`.`roleInherit`
ADD UNIQUE( `role`, `inheritsFrom`);
ALTER TABLE `account`.`roleInherit`
DROP PRIMARY KEY;
ALTER TABLE `account`.`roleInherit`
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD PRIMARY KEY (`id`);
ALTER TABLE `account`.`mailAlias`
ADD `description` VARCHAR(255) NULL AFTER `alias`;
ALTER TABLE `account`.`mailAliasAccount`
ADD UNIQUE( `mailAlias`, `account`);
ALTER TABLE `account`.`mailAliasAccount`
DROP PRIMARY KEY;
ALTER TABLE `account`.`mailAliasAccount`
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD PRIMARY KEY (`id`);
ALTER TABLE account.ldapConfig
ADD groupDn varchar(255) NULL;
UPDATE account.ldapConfig SET groupDn = 'ou=groups,dc=verdnatura,dc=es';
DROP PROCEDURE IF EXISTS account.user_syncPassword;
ALTER TABLE account.`user`
MODIFY COLUMN sync tinyint(4) DEFAULT 0 NOT NULL COMMENT 'Deprecated';
CREATE TABLE account.userSync (
name varchar(30) NOT NULL,
CONSTRAINT userSync_PK PRIMARY KEY (name)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8
COLLATE=utf8_general_ci;
USE account;
DELIMITER $$
DROP TRIGGER IF EXISTS account.user_beforeUpdate$$
CREATE DEFINER=`root`@`%` TRIGGER `user_beforeUpdate`
BEFORE UPDATE ON `user` FOR EACH ROW
BEGIN
IF !(NEW.`name` <=> OLD.`name`) THEN
CALL user_checkName (NEW.`name`);
END IF;
IF !(NEW.`password` <=> OLD.`password`) THEN
SET NEW.bcryptPassword = NULL;
SET NEW.lastPassChange = NOW();
END IF;
END$$
DROP TRIGGER IF EXISTS account.user_afterUpdate$$
CREATE DEFINER=`root`@`%` TRIGGER `user_afterUpdate`
AFTER UPDATE ON `user` FOR EACH ROW
BEGIN
INSERT IGNORE INTO userSync SET `name` = NEW.`name`;
IF !(OLD.`name` <=> NEW.`name`) THEN
INSERT IGNORE INTO userSync SET `name` = OLD.`name`;
END IF;
IF !(NEW.`role` <=> OLD.`role`)
THEN
INSERT INTO vn.mail SET
`sender` = 'jgallego@verdnatura.es',
`replyTo` = 'jgallego@verdnatura.es',
`subject` = 'Rol modificado',
`body` = CONCAT(myUserGetName(), ' ha modificado el rol del usuario ',
NEW.`name`, ' de ', OLD.role, ' a ', NEW.role);
END IF;
END$$
CREATE DEFINER=`root`@`%` TRIGGER `user_afterInsert`
AFTER INSERT ON `user` FOR EACH ROW
BEGIN
INSERT IGNORE INTO userSync SET `name` = NEW.`name`;
END$$
CREATE DEFINER=`root`@`%` TRIGGER `user_afterDelete`
AFTER DELETE ON `user` FOR EACH ROW
BEGIN
INSERT IGNORE INTO userSync SET `name` = OLD.`name`;
END$$
DROP TRIGGER IF EXISTS account.account_afterInsert$$
CREATE DEFINER=`root`@`%` TRIGGER `account_afterInsert`
AFTER INSERT ON `account` FOR EACH ROW
BEGIN
INSERT IGNORE INTO userSync (`name`)
SELECT `name` FROM `user` WHERE id = NEW.id;
END$$
DROP TRIGGER IF EXISTS account.account_afterDelete$$
CREATE DEFINER=`root`@`%` TRIGGER `account_afterDelete`
AFTER DELETE ON `account` FOR EACH ROW
BEGIN
INSERT IGNORE INTO userSync (`name`)
SELECT `name` FROM `user` WHERE id = OLD.id;
END$$
CREATE TRIGGER role_beforeInsert
BEFORE INSERT ON `role` FOR EACH ROW
BEGIN
CALL role_checkName(NEW.`name`);
END$$
CREATE TRIGGER role_beforeUpdate
BEFORE UPDATE ON `role` FOR EACH ROW
BEGIN
IF !(NEW.`name` <=> OLD.`name`) THEN
CALL role_checkName (NEW.`name`);
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,13 @@
DROP PROCEDURE IF EXISTS account.myUserChangePassword;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserChangePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255))
BEGIN
/**
* @deprecated Use myUser_changePassword()
*/
CALL myUser_changePassword(vOldPassword, vPassword);
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.myUserChangePassword TO account@localhost;

View File

@ -0,0 +1,15 @@
DROP FUNCTION IF EXISTS account.myUserCheckLogin;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserCheckLogin`() RETURNS tinyint(1)
READS SQL DATA
DETERMINISTIC
BEGIN
/**
* @deprecated Use myUser_checkLogin()
*/
RETURN myUser_checkLogin();
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUserCheckLogin TO guest@localhost;

View File

@ -0,0 +1,15 @@
DROP FUNCTION IF EXISTS account.myUserGetId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetId`() RETURNS int(11)
READS SQL DATA
DETERMINISTIC
BEGIN
/**
* @deprecated Use myUser_getId()
*/
RETURN myUser_getId();
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUserGetId TO guest@localhost;

View File

@ -0,0 +1,15 @@
DROP FUNCTION IF EXISTS account.myUserGetName;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetName`() RETURNS varchar(30) CHARSET utf8
NO SQL
DETERMINISTIC
BEGIN
/**
* @deprecated Use myUser_getName()
*/
RETURN myUser_getName();
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUserGetName TO guest@localhost;

View File

@ -0,0 +1,14 @@
DROP FUNCTION IF EXISTS account.myUserHasRole;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* @deprecated Use myUser_hasRole()
*/
RETURN myUser_hasRole(vRoleName);
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUserHasRole TO guest@localhost;

View File

@ -0,0 +1,14 @@
DROP FUNCTION IF EXISTS account.myUserHasRoleId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRoleId`(vRoleId INT) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* @deprecated Use myUser_hasRoleId()
*/
RETURN myUser_hasRoleId(vRoleId);
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUserHasRoleId TO guest@localhost;

View File

@ -0,0 +1,17 @@
DROP PROCEDURE IF EXISTS account.myUser_changePassword;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_changePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255))
BEGIN
/**
* Changes the current user password, if user is in recovery mode ignores the
* current password.
*
* @param vOldPassword The current password
* @param vPassword The new password
*/
CALL user_changePassword(myUser_getId(), vOldPassword, vPassword);
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.myUser_changePassword TO account@localhost;

View File

@ -0,0 +1,29 @@
DROP FUNCTION IF EXISTS account.myUser_checkLogin;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_checkLogin`() RETURNS tinyint(1)
READS SQL DATA
DETERMINISTIC
BEGIN
/**
* Checks that variables @userId and @userName haven't been altered.
*
* @return %TRUE if they are unaltered or unset, otherwise %FALSE
*/
DECLARE vSignature VARCHAR(128);
DECLARE vKey VARCHAR(255);
IF @userId IS NOT NULL
AND @userName IS NOT NULL
AND @userSignature IS NOT NULL
THEN
SELECT loginKey INTO vKey FROM userConfig;
SET vSignature = util.hmacSha2(256, CONCAT_WS('/', @userId, @userName), vKey);
RETURN vSignature = @userSignature;
END IF;
RETURN FALSE;
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUser_checkLogin TO guest@localhost;

View File

@ -0,0 +1,27 @@
DROP FUNCTION IF EXISTS account.myUser_getId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getId`() RETURNS int(11)
READS SQL DATA
DETERMINISTIC
BEGIN
/**
* Returns the current user id.
*
* @return The user id
*/
DECLARE vUser INT DEFAULT NULL;
IF myUser_checkLogin()
THEN
SET vUser = @userId;
ELSE
SELECT id INTO vUser FROM user
WHERE name = LEFT(USER(), INSTR(USER(), '@') - 1);
END IF;
RETURN vUser;
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUser_getId TO guest@localhost;

View File

@ -0,0 +1,27 @@
DROP FUNCTION IF EXISTS account.myUser_getName;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getName`() RETURNS varchar(30) CHARSET utf8
NO SQL
DETERMINISTIC
BEGIN
/**
* Returns the current user name.
*
* @return The user name
*/
DECLARE vUser VARCHAR(30) DEFAULT NULL;
IF myUser_checkLogin()
THEN
SET vUser = @userName;
ELSE
SET vUser = LEFT(USER(), INSTR(USER(), '@') - 1);
END IF;
RETURN vUser;
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUser_getName TO guest@localhost;

View File

@ -0,0 +1,17 @@
DROP FUNCTION IF EXISTS account.myUser_hasRole;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* Checks if current user has/inherits a role.
*
* @param vRoleName Role to check
* @return %TRUE if it has role, %FALSE otherwise
*/
RETURN user_hasRole(myUser_getName(), vRoleName);
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUser_hasRole TO guest@localhost;

View File

@ -0,0 +1,17 @@
DROP FUNCTION IF EXISTS account.myUser_hasRoleId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRoleId`(vRoleId INT) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* Checks if current user has/inherits a role.
*
* @param vRoleName Role id to check
* @return %TRUE if it has role, %FALSE otherwise
*/
RETURN user_hasRoleId(myUserGetName(), vRoleId);
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.myUser_hasRoleId TO guest@localhost;

View File

@ -0,0 +1,29 @@
DROP PROCEDURE IF EXISTS account.myUser_login;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_login`(vUserName VARCHAR(255), vPassword VARCHAR(255))
READS SQL DATA
BEGIN
/**
* Logs in using the user credentials.
*
* @param vUserName The user name
* @param vPassword The user password
*/
DECLARE vAuthIsOk BOOLEAN DEFAULT FALSE;
SELECT COUNT(*) = 1 INTO vAuthIsOk FROM user
WHERE name = vUserName
AND password = MD5(vPassword)
AND active;
IF vAuthIsOk
THEN
CALL myUser_loginWithName (vUserName);
ELSE
CALL util.throw ('INVALID_CREDENTIALS');
END IF;
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.myUser_login TO guest@localhost;

View File

@ -0,0 +1,25 @@
DROP PROCEDURE IF EXISTS account.myUser_loginWithKey;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255))
READS SQL DATA
BEGIN
/**
* Logs in using the user name and MySQL master key.
*
* @param vUserName The user name
* @param vKey The MySQL master key
*/
DECLARE vLoginKey VARCHAR(255);
SELECT loginKey INTO vLoginKey FROM userConfig;
IF vLoginKey = vKey THEN
CALL user_loginWithName(vUserName);
ELSE
CALL util.throw('INVALID_KEY');
END IF;
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.myUser_loginWithKey TO guest@localhost;

View File

@ -0,0 +1,26 @@
DROP PROCEDURE IF EXISTS account.myUser_loginWithName;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithName`(vUserName VARCHAR(255))
READS SQL DATA
BEGIN
/**
* Logs in using only the user name. This procedure is intended to be executed
* by users with a high level of privileges so that normal users should not have
* execute permissions on it.
*
* @param vUserName The user name
*/
DECLARE vUserId INT DEFAULT NULL;
DECLARE vKey VARCHAR(255);
SELECT id INTO vUserId FROM user
WHERE name = vUserName;
SELECT loginKey INTO vKey FROM userConfig;
SET @userId = vUserId;
SET @userName = vUserName;
SET @userSignature = util.hmacSha2(256, CONCAT_WS('/', vUserId, vUserName), vKey);
END$$
DELIMITER ;

View File

@ -0,0 +1,15 @@
DROP PROCEDURE IF EXISTS account.myUser_logout;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_logout`()
BEGIN
/**
* Logouts the user.
*/
SET @userId = NULL;
SET @userName = NULL;
SET @userSignature = NULL;
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.myUser_logout TO account@localhost;

View File

@ -0,0 +1,52 @@
DROP FUNCTION IF EXISTS account.passwordGenerate;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`passwordGenerate`() RETURNS text CHARSET utf8
BEGIN
/**
* Generates a random password that meets the minimum requirements.
*
* @return Generated password
*/
DECLARE vMinLength TINYINT;
DECLARE vMinAlpha TINYINT;
DECLARE vMinUpper TINYINT;
DECLARE vMinDigits TINYINT;
DECLARE vMinPunct TINYINT;
DECLARE vAlpha TINYINT DEFAULT 0;
DECLARE vUpper TINYINT DEFAULT 0;
DECLARE vDigits TINYINT DEFAULT 0;
DECLARE vPunct TINYINT DEFAULT 0;
DECLARE vRandIndex INT;
DECLARE vPwd TEXT DEFAULT '';
DECLARE vAlphaChars TEXT DEFAULT 'abcdefghijklmnopqrstuvwxyz';
DECLARE vUpperChars TEXT DEFAULT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
DECLARE vDigitChars TEXT DEFAULT '1234567890';
DECLARE vPunctChars TEXT DEFAULT '!$%&()=.';
SELECT length, nAlpha, nUpper, nDigits, nPunct
INTO vMinLength, vMinAlpha, vMinUpper, vMinDigits, vMinPunct FROM userPassword;
WHILE LENGTH(vPwd) < vMinLength OR vAlpha < vMinAlpha
OR vUpper < vMinUpper OR vDigits < vMinDigits OR vPunct < vMinPunct DO
SET vRandIndex = FLOOR((RAND() * 4) + 1);
CASE
WHEN vRandIndex = 1 THEN
SET vPwd = CONCAT(vPwd, SUBSTRING(vAlphaChars, FLOOR((RAND() * 26) + 1), 1));
SET vAlpha = vAlpha + 1;
WHEN vRandIndex = 2 THEN
SET vPwd = CONCAT(vPwd, SUBSTRING(vUpperChars, FLOOR((RAND() * 26) + 1), 1));
SET vUpper = vUpper + 1;
WHEN vRandIndex = 3 THEN
SET vPwd = CONCAT(vPwd, SUBSTRING(vDigitChars, FLOOR((RAND() * 10) + 1), 1));
SET vDigits = vDigits + 1;
WHEN vRandIndex = 4 THEN
SET vPwd = CONCAT(vPwd, SUBSTRING(vPunctChars, FLOOR((RAND() * LENGTH(vPunctChars)) + 1), 1));
SET vPunct = vPunct + 1;
END CASE;
END WHILE;
RETURN vPwd;
END$$
DELIMITER ;

View File

@ -0,0 +1,18 @@
DROP PROCEDURE IF EXISTS account.role_checkName;
DELIMITER $$
CREATE PROCEDURE account.role_checkName(vRoleName VARCHAR(255))
BEGIN
/**
* Checks that role name meets the necessary syntax requirements, otherwise it
* throws an exception.
* Role name must be written in camelCase.
*
* @param vRoleName The role name
*/
IF BINARY vRoleName NOT REGEXP '^[a-z][a-zA-Z]+$' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Role name must be written in camelCase';
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,66 @@
DROP PROCEDURE IF EXISTS account.role_getDescendents;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_getDescendents`(vSelf INT)
BEGIN
/**
* Gets the identifiers of all the subroles implemented by a role (Including
* itself).
*
* @param vSelf The role identifier
* @table tmp.role Subroles implemented by the role
*/
DECLARE vIsRoot BOOL;
DROP TEMPORARY TABLE IF EXISTS
tmp.role, parents, childs;
CREATE TEMPORARY TABLE tmp.role
(UNIQUE (id))
ENGINE = MEMORY
SELECT vSelf AS id;
CREATE TEMPORARY TABLE parents
ENGINE = MEMORY
SELECT vSelf AS id;
CREATE TEMPORARY TABLE childs
LIKE parents;
REPEAT
DELETE FROM childs;
INSERT INTO childs
SELECT DISTINCT r.inheritsFrom id
FROM parents p
JOIN roleInherit r ON r.role = p.id
LEFT JOIN tmp.role t ON t.id = r.inheritsFrom
WHERE t.id IS NULL;
DELETE FROM parents;
INSERT INTO parents
SELECT * FROM childs;
INSERT INTO tmp.role
SELECT * FROM childs;
UNTIL ROW_COUNT() <= 0
END REPEAT;
-- If it is root all the roles are added
SELECT COUNT(*) > 0 INTO vIsRoot
FROM tmp.role t
JOIN role r ON r.id = t.id
WHERE r.`name` = 'root';
IF vIsRoot THEN
INSERT IGNORE INTO tmp.role (id)
SELECT id FROM role;
END IF;
-- Cleaning
DROP TEMPORARY TABLE
parents, childs;
END$$
DELIMITER ;

View File

@ -0,0 +1,53 @@
DROP PROCEDURE IF EXISTS account.role_sync;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_sync`()
BEGIN
/**
* Synchronize the @roleRole table with the current role hierarchy. This
* procedure must be called every time the @roleInherit table is modified so
* that the changes made on it are effective.
*/
DECLARE vRoleId INT;
DECLARE vDone BOOL;
DECLARE cur CURSOR FOR
SELECT id FROM role;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DROP TEMPORARY TABLE IF EXISTS tRoleRole;
CREATE TEMPORARY TABLE tRoleRole
ENGINE = MEMORY
SELECT * FROM roleRole LIMIT 0;
OPEN cur;
l: LOOP
SET vDone = FALSE;
FETCH cur INTO vRoleId;
IF vDone THEN
LEAVE l;
END IF;
CALL role_getDescendents(vRoleId);
INSERT INTO tRoleRole (role, inheritsFrom)
SELECT vRoleId, id FROM tmp.role;
DROP TEMPORARY TABLE tmp.role;
END LOOP;
CLOSE cur;
START TRANSACTION;
DELETE FROM roleRole;
INSERT INTO roleRole SELECT * FROM tRoleRole;
COMMIT;
DROP TEMPORARY TABLE tRoleRole;
CALL role_syncPrivileges;
END$$
DELIMITER ;

View File

@ -0,0 +1,494 @@
DROP PROCEDURE IF EXISTS account.role_syncPrivileges;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_syncPrivileges`()
BEGIN
/**
* Synchronizes permissions of MySQL role users based on role hierarchy.
* The computed role users of permission mix will be named according to
* pattern z-[role_name].
*
* If any@localhost user exists, it will be taken as a template for basic
* attributes.
*
* Warning! This procedure should only be called when MySQL privileges
* are modified. If role hierarchy is modified, you must call the role_sync()
* procedure wich calls this internally.
*/
DECLARE vIsMysql BOOL DEFAULT VERSION() NOT LIKE '%MariaDB%';
DECLARE vVersion INT DEFAULT SUBSTRING_INDEX(VERSION(), '.', 1);
DECLARE vTplUser VARCHAR(255) DEFAULT 'any';
DECLARE vTplHost VARCHAR(255) DEFAULT '%';
DECLARE vRoleHost VARCHAR(255) DEFAULT 'localhost';
DECLARE vAllHost VARCHAR(255) DEFAULT '%';
DECLARE vPrefix VARCHAR(2) DEFAULT 'z-';
DECLARE vPrefixedLike VARCHAR(255);
DECLARE vPassword VARCHAR(255) DEFAULT '';
-- Deletes computed role users
SET vPrefixedLike = CONCAT(vPrefix, '%');
DELETE FROM mysql.user
WHERE `User` LIKE vPrefixedLike;
DELETE FROM mysql.db
WHERE `User` LIKE vPrefixedLike;
DELETE FROM mysql.tables_priv
WHERE `User` LIKE vPrefixedLike;
DELETE FROM mysql.columns_priv
WHERE `User` LIKE vPrefixedLike;
DELETE FROM mysql.procs_priv
WHERE `User` LIKE vPrefixedLike;
DELETE FROM mysql.proxies_priv
WHERE `Proxied_user` LIKE vPrefixedLike;
-- Temporary tables
DROP TEMPORARY TABLE IF EXISTS tRole;
CREATE TEMPORARY TABLE tRole
(INDEX (id))
ENGINE = MEMORY
SELECT
id,
`name` role,
CONCAT(vPrefix, `name`) prefixedRole
FROM role
WHERE hasLogin;
DROP TEMPORARY TABLE IF EXISTS tRoleInherit;
CREATE TEMPORARY TABLE tRoleInherit
(INDEX (inheritsFrom))
ENGINE = MEMORY
SELECT
r.prefixedRole,
ri.`name` inheritsFrom
FROM tRole r
JOIN roleRole rr ON rr.role = r.id
JOIN role ri ON ri.id = rr.inheritsFrom;
-- Recreate role users
IF vIsMysql THEN
DROP TEMPORARY TABLE IF EXISTS tUser;
CREATE TEMPORARY TABLE tUser
SELECT
r.prefixedRole `User`,
vTplHost `Host`,
IFNULL(t.`authentication_string`,
'') `authentication_string`,
IFNULL(t.`plugin`,
'mysql_native_password') `plugin`,
IFNULL(IF('' != u.`ssl_type`,
u.`ssl_type`, t.`ssl_type`),
'') `ssl_type`,
IFNULL(IF('' != u.`ssl_cipher`,
u.`ssl_cipher`, t.`ssl_cipher`),
'') `ssl_cipher`,
IFNULL(IF('' != u.`x509_issuer`,
u.`x509_issuer`, t.`x509_issuer`),
'') `x509_issuer`,
IFNULL(IF('' != u.`x509_subject`,
u.`x509_subject`, t.`x509_subject`),
'') `x509_subject`,
IFNULL(IF(0 != u.`max_questions`,
u.`max_questions`, t.`max_questions`),
0) `max_questions`,
IFNULL(IF(0 != u.`max_updates`,
u.`max_updates`, t.`max_updates`),
0) `max_updates`,
IFNULL(IF(0 != u.`max_connections`,
u.`max_connections`, t.`max_connections`),
0) `max_connections`,
IFNULL(IF(0 != u.`max_user_connections`,
u.`max_user_connections`, t.`max_user_connections`),
0) `max_user_connections`
FROM tRole r
LEFT JOIN mysql.user t
ON t.`User` = vTplUser
AND t.`Host` = vRoleHost
LEFT JOIN mysql.user u
ON u.`User` = r.role
AND u.`Host` = vRoleHost;
IF vVersion <= 5 THEN
SELECT `Password` INTO vPassword
FROM mysql.user
WHERE `User` = vTplUser
AND `Host` = vRoleHost;
INSERT INTO mysql.user (
`User`,
`Host`,
`Password`,
`authentication_string`,
`plugin`,
`ssl_type`,
`ssl_cipher`,
`x509_issuer`,
`x509_subject`,
`max_questions`,
`max_updates`,
`max_connections`,
`max_user_connections`
)
SELECT
`User`,
`Host`,
vPassword,
`authentication_string`,
`plugin`,
`ssl_type`,
`ssl_cipher`,
`x509_issuer`,
`x509_subject`,
`max_questions`,
`max_updates`,
`max_connections`,
`max_user_connections`
FROM tUser;
ELSE
INSERT INTO mysql.user (
`User`,
`Host`,
`authentication_string`,
`plugin`,
`ssl_type`,
`ssl_cipher`,
`x509_issuer`,
`x509_subject`,
`max_questions`,
`max_updates`,
`max_connections`,
`max_user_connections`
)
SELECT
`User`,
`Host`,
`authentication_string`,
`plugin`,
`ssl_type`,
`ssl_cipher`,
`x509_issuer`,
`x509_subject`,
`max_questions`,
`max_updates`,
`max_connections`,
`max_user_connections`
FROM tUser;
END IF;
DROP TEMPORARY TABLE IF EXISTS tUser;
ELSE
INSERT INTO mysql.global_priv (
`User`,
`Host`,
`Priv`
)
SELECT
r.prefixedRole,
vTplHost,
JSON_MERGE_PATCH(
IFNULL(t.`Priv`, '{}'),
IFNULL(u.`Priv`, '{}')
)
FROM tRole r
LEFT JOIN mysql.global_priv t
ON t.`User` = vTplUser
AND t.`Host` = vRoleHost
LEFT JOIN mysql.global_priv u
ON u.`User` = r.role
AND u.`Host` = vRoleHost;
END IF;
INSERT INTO mysql.proxies_priv (
`User`,
`Host`,
`Proxied_user`,
`Proxied_host`,
`Grantor`
)
SELECT
'',
vAllHost,
prefixedRole,
vTplHost,
CONCAT(prefixedRole, '@', vTplHost)
FROM tRole;
-- Copies global privileges
DROP TEMPORARY TABLE IF EXISTS tUserPriv;
IF vIsMysql THEN
CREATE TEMPORARY TABLE tUserPriv
(INDEX (prefixedRole))
ENGINE = MEMORY
SELECT
r.prefixedRole,
MAX(u.`Select_priv`) `Select_priv`,
MAX(u.`Insert_priv`) `Insert_priv`,
MAX(u.`Update_priv`) `Update_priv`,
MAX(u.`Delete_priv`) `Delete_priv`,
MAX(u.`Create_priv`) `Create_priv`,
MAX(u.`Drop_priv`) `Drop_priv`,
MAX(u.`Reload_priv`) `Reload_priv`,
MAX(u.`Shutdown_priv`) `Shutdown_priv`,
MAX(u.`Process_priv`) `Process_priv`,
MAX(u.`File_priv`) `File_priv`,
MAX(u.`Grant_priv`) `Grant_priv`,
MAX(u.`References_priv`) `References_priv`,
MAX(u.`Index_priv`) `Index_priv`,
MAX(u.`Alter_priv`) `Alter_priv`,
MAX(u.`Show_db_priv`) `Show_db_priv`,
MAX(u.`Super_priv`) `Super_priv`,
MAX(u.`Create_tmp_table_priv`) `Create_tmp_table_priv`,
MAX(u.`Lock_tables_priv`) `Lock_tables_priv`,
MAX(u.`Execute_priv`) `Execute_priv`,
MAX(u.`Repl_slave_priv`) `Repl_slave_priv`,
MAX(u.`Repl_client_priv`) `Repl_client_priv`,
MAX(u.`Create_view_priv`) `Create_view_priv`,
MAX(u.`Show_view_priv`) `Show_view_priv`,
MAX(u.`Create_routine_priv`) `Create_routine_priv`,
MAX(u.`Alter_routine_priv`) `Alter_routine_priv`,
MAX(u.`Create_user_priv`) `Create_user_priv`,
MAX(u.`Event_priv`) `Event_priv`,
MAX(u.`Trigger_priv`) `Trigger_priv`,
MAX(u.`Create_tablespace_priv`) `Create_tablespace_priv`
FROM tRoleInherit r
JOIN mysql.user u
ON u.`User` = r.inheritsFrom
AND u.`Host`= vRoleHost
GROUP BY r.prefixedRole;
UPDATE mysql.user u
JOIN tUserPriv t
ON u.`User` = t.prefixedRole
AND u.`Host` = vTplHost
SET
u.`Select_priv`
= t.`Select_priv`,
u.`Insert_priv`
= t.`Insert_priv`,
u.`Update_priv`
= t.`Update_priv`,
u.`Delete_priv`
= t.`Delete_priv`,
u.`Create_priv`
= t.`Create_priv`,
u.`Drop_priv`
= t.`Drop_priv`,
u.`Reload_priv`
= t.`Reload_priv`,
u.`Shutdown_priv`
= t.`Shutdown_priv`,
u.`Process_priv`
= t.`Process_priv`,
u.`File_priv`
= t.`File_priv`,
u.`Grant_priv`
= t.`Grant_priv`,
u.`References_priv`
= t.`References_priv`,
u.`Index_priv`
= t.`Index_priv`,
u.`Alter_priv`
= t.`Alter_priv`,
u.`Show_db_priv`
= t.`Show_db_priv`,
u.`Super_priv`
= t.`Super_priv`,
u.`Create_tmp_table_priv`
= t.`Create_tmp_table_priv`,
u.`Lock_tables_priv`
= t.`Lock_tables_priv`,
u.`Execute_priv`
= t.`Execute_priv`,
u.`Repl_slave_priv`
= t.`Repl_slave_priv`,
u.`Repl_client_priv`
= t.`Repl_client_priv`,
u.`Create_view_priv`
= t.`Create_view_priv`,
u.`Show_view_priv`
= t.`Show_view_priv`,
u.`Create_routine_priv`
= t.`Create_routine_priv`,
u.`Alter_routine_priv`
= t.`Alter_routine_priv`,
u.`Create_user_priv`
= t.`Create_user_priv`,
u.`Event_priv`
= t.`Event_priv`,
u.`Trigger_priv`
= t.`Trigger_priv`,
u.`Create_tablespace_priv`
= t.`Create_tablespace_priv`;
ELSE
CREATE TEMPORARY TABLE tUserPriv
(INDEX (prefixedRole))
SELECT
r.prefixedRole,
BIT_OR(JSON_VALUE(p.`Priv`, '$.access')) access
FROM tRoleInherit r
JOIN mysql.global_priv p
ON p.`User` = r.inheritsFrom
AND p.`Host`= vRoleHost
GROUP BY r.prefixedRole;
UPDATE mysql.global_priv p
JOIN tUserPriv t
ON p.`User` = t.prefixedRole
AND p.`Host` = vTplHost
SET
p.`Priv` = JSON_SET(p.`Priv`, '$.access', t.access);
END IF;
DROP TEMPORARY TABLE tUserPriv;
-- Copy schema level privileges
INSERT INTO mysql.db (
`User`,
`Host`,
`Db`,
`Select_priv`,
`Insert_priv`,
`Update_priv`,
`Delete_priv`,
`Create_priv`,
`Drop_priv`,
`Grant_priv`,
`References_priv`,
`Index_priv`,
`Alter_priv`,
`Create_tmp_table_priv`,
`Lock_tables_priv`,
`Create_view_priv`,
`Show_view_priv`,
`Create_routine_priv`,
`Alter_routine_priv`,
`Execute_priv`,
`Event_priv`,
`Trigger_priv`
)
SELECT
r.prefixedRole,
vTplHost,
t.`Db`,
MAX(t.`Select_priv`),
MAX(t.`Insert_priv`),
MAX(t.`Update_priv`),
MAX(t.`Delete_priv`),
MAX(t.`Create_priv`),
MAX(t.`Drop_priv`),
MAX(t.`Grant_priv`),
MAX(t.`References_priv`),
MAX(t.`Index_priv`),
MAX(t.`Alter_priv`),
MAX(t.`Create_tmp_table_priv`),
MAX(t.`Lock_tables_priv`),
MAX(t.`Create_view_priv`),
MAX(t.`Show_view_priv`),
MAX(t.`Create_routine_priv`),
MAX(t.`Alter_routine_priv`),
MAX(t.`Execute_priv`),
MAX(t.`Event_priv`),
MAX(t.`Trigger_priv`)
FROM tRoleInherit r
JOIN mysql.db t
ON t.`User` = r.inheritsFrom
AND t.`Host`= vRoleHost
GROUP BY r.prefixedRole, t.`Db`;
-- Copy table level privileges
INSERT INTO mysql.tables_priv (
`User`,
`Host`,
`Db`,
`Table_name`,
`Grantor`,
`Timestamp`,
`Table_priv`,
`Column_priv`
)
SELECT
r.prefixedRole,
vTplHost,
t.`Db`,
t.`Table_name`,
t.`Grantor`,
MAX(t.`Timestamp`),
IFNULL(GROUP_CONCAT(NULLIF(t.`Table_priv`, '')), ''),
IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '')
FROM tRoleInherit r
JOIN mysql.tables_priv t
ON t.`User` = r.inheritsFrom
AND t.`Host`= vRoleHost
GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`;
-- Copy column level privileges
INSERT INTO mysql.columns_priv (
`User`,
`Host`,
`Db`,
`Table_name`,
`Column_name`,
`Timestamp`,
`Column_priv`
)
SELECT
r.prefixedRole,
vTplHost,
t.`Db`,
t.`Table_name`,
t.`Column_name`,
MAX(t.`Timestamp`),
IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '')
FROM tRoleInherit r
JOIN mysql.columns_priv t
ON t.`User` = r.inheritsFrom
AND t.`Host`= vRoleHost
GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`, t.`Column_name`;
-- Copy routine privileges
INSERT IGNORE INTO mysql.procs_priv (
`User`,
`Host`,
`Db`,
`Routine_name`,
`Routine_type`,
`Grantor`,
`Timestamp`,
`Proc_priv`
)
SELECT
r.prefixedRole,
vTplHost,
t.`Db`,
t.`Routine_name`,
t.`Routine_type`,
t.`Grantor`,
t.`Timestamp`,
t.`Proc_priv`
FROM tRoleInherit r
JOIN mysql.procs_priv t
ON t.`User` = r.inheritsFrom
AND t.`Host`= vRoleHost;
-- Free memory
DROP TEMPORARY TABLE
tRole,
tRoleInherit;
FLUSH PRIVILEGES;
END$$
DELIMITER ;

View File

@ -0,0 +1,15 @@
DROP FUNCTION IF EXISTS account.userGetId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetId`() RETURNS int(11)
READS SQL DATA
DETERMINISTIC
BEGIN
/**
* @deprecated Use myUser_getId()
*/
RETURN myUser_getId();
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.userGetId TO guest@localhost;

View File

@ -0,0 +1,11 @@
DROP FUNCTION IF EXISTS account.userGetMysqlRole;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8
BEGIN
/**
* @deprecated Use user_getMysqlRole()
*/
RETURN user_getMysqlRole();
END$$
DELIMITER ;

View File

@ -0,0 +1,15 @@
DROP FUNCTION IF EXISTS account.userGetName;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetName`() RETURNS varchar(30) CHARSET utf8
NO SQL
DETERMINISTIC
BEGIN
/**
* @deprecated Use myUser_getName()
*/
RETURN myUser_getName();
END$$
DELIMITER ;
GRANT EXECUTE ON FUNCTION account.userGetName TO guest@localhost;

View File

@ -0,0 +1,11 @@
DROP FUNCTION IF EXISTS account.userGetNameFromId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8
BEGIN
/**
* @deprecated Use user_getNameFromId();
*/
RETURN user_getNameFromId(vSelf);
END$$
DELIMITER ;

View File

@ -0,0 +1,12 @@
DROP FUNCTION IF EXISTS account.userHasRole;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* @deprecated Use user_hasRole()
*/
RETURN user_hasRole(vUserName, vRoleName);
END$$
DELIMITER ;

View File

@ -0,0 +1,12 @@
DROP FUNCTION IF EXISTS account.userHasRoleId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* @deprecated Use user_hasRoleId()
*/
RETURN user_hasRoleId(vUser, vRoleId);
END$$
DELIMITER ;

View File

@ -0,0 +1,14 @@
DROP PROCEDURE IF EXISTS account.userLogin;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLogin`(vUserName VARCHAR(255), vPassword VARCHAR(255))
READS SQL DATA
BEGIN
/**
* @deprecated Use myUser_login()
*/
CALL myUser_login(vUserName, vPassword);
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.userLogin TO guest@localhost;

View File

@ -0,0 +1,14 @@
DROP PROCEDURE IF EXISTS account.userLoginWithKey;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255))
READS SQL DATA
BEGIN
/**
* @deprecated Use myUser_loginWithKey()
*/
CALL myUser_loginWithKey(vUserName, vKey);
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.userLoginWithKey TO guest@localhost;

View File

@ -0,0 +1,12 @@
DROP PROCEDURE IF EXISTS account.userLoginWithName;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithName`(vUserName VARCHAR(255))
READS SQL DATA
BEGIN
/**
* @deprecated Use myUser_loginWithName()
*/
CALL myUser_loginWithName(vUserName);
END$$
DELIMITER ;

View File

@ -0,0 +1,14 @@
DROP PROCEDURE IF EXISTS account.myUserLogout;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserLogout`()
BEGIN
/**
* @deprecated Use myUser_Logout()
*/
CALL myUser_logout;
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE account.myUserLogout TO account@localhost;

View File

@ -0,0 +1,16 @@
DROP PROCEDURE IF EXISTS account.userSetPassword;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userSetPassword`(vUserName VARCHAR(255), vPassword VARCHAR(255))
BEGIN
/**
* @deprecated Use user_setPassword()
*/
DECLARE vUserId INT;
SELECT id INTO vUserId
FROM user WHERE `name` = vUserName;
CALL user_setPassword(vUserId, vPassword);
END$$
DELIMITER ;

View File

@ -0,0 +1,27 @@
DROP PROCEDURE IF EXISTS account.user_changePassword;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE account.user_changePassword(vSelf INT, vOldPassword VARCHAR(255), vPassword VARCHAR(255))
BEGIN
/**
* Changes the user password.
*
* @param vSelf The user id
* @param vOldPassword The current password
* @param vPassword The new password
*/
DECLARE vPasswordOk BOOL;
DECLARE vUserName VARCHAR(255);
SELECT `password` = MD5(vOldPassword), `name`
INTO vPasswordOk, vUserName
FROM user WHERE id = vSelf;
IF NOT vPasswordOk THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid password';
END IF;
CALL user_setPassword(vSelf, vPassword);
END$$
DELIMITER ;

View File

@ -0,0 +1,17 @@
DROP PROCEDURE IF EXISTS account.user_checkName;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`user_checkName`(vUserName VARCHAR(255))
BEGIN
/**
* Checks that username meets the necessary syntax requirements, otherwise it
* throws an exception.
* The user name must only contain lowercase letters or, starting with second
* character, numbers or underscores.
*/
IF vUserName NOT REGEXP '^[a-z0-9_-]*$' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'INVALID_USER_NAME';
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,22 @@
DROP FUNCTION IF EXISTS account.user_getMysqlRole;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8
BEGIN
/**
* From a username, it returns the associated MySQL wich should be used when
* using external authentication systems.
*
* @param vUserName The user name
* @return The associated MySQL role
*/
DECLARE vRole VARCHAR(255);
SELECT CONCAT(IF(r.hasLogin, 'z-', ''), r.name) INTO vRole
FROM role r
JOIN user u ON u.role = r.id
WHERE u.name = vUserName;
RETURN vRole;
END$$
DELIMITER ;

View File

@ -0,0 +1,20 @@
DROP FUNCTION IF EXISTS account.user_getNameFromId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8
BEGIN
/**
* Gets user name from it's id.
*
* @param vSelf The user id
* @return The user name
*/
DECLARE vName VARCHAR(30);
SELECT `name` INTO vName
FROM user
WHERE id = vId;
RETURN vSelf;
END$$
DELIMITER ;

View File

@ -0,0 +1,25 @@
DROP FUNCTION IF EXISTS account.user_hasRole;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* Checks if user has/inherits a role.
*
* @param vUserName The user name
* @param vRoleName Role to check
* @return %TRUE if it has role, %FALSE otherwise
*/
DECLARE vHasRole BOOL DEFAULT FALSE;
SELECT COUNT(*) > 0 INTO vHasRole
FROM user u
JOIN roleRole rr ON rr.role = u.role
JOIN role r ON r.id = rr.inheritsFrom
WHERE u.`name` = vUserName
AND r.`name` = vRoleName COLLATE 'utf8_unicode_ci';
RETURN vHasRole;
END$$
DELIMITER ;

View File

@ -0,0 +1,25 @@
DROP FUNCTION IF EXISTS account.user_hasRoleId;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1)
DETERMINISTIC
BEGIN
/**
* Checks if user has/inherits a role.
*
* @param vUserName The user name
* @param vRoleId Role id to check
* @return %TRUE if it has role, %FALSE otherwise
*/
DECLARE vHasRole BOOL DEFAULT FALSE;
SELECT COUNT(*) > 0 INTO vHasRole
FROM user u
JOIN roleRole rr ON rr.role = u.role
JOIN role r ON r.id = rr.inheritsFrom
WHERE u.`name` = vUser
AND r.id = vRoleId;
RETURN vHasRole;
END$$
DELIMITER ;

View File

@ -0,0 +1,21 @@
DROP PROCEDURE IF EXISTS account.user_setPassword;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE account.user_setPassword(vSelf INT, vPassword VARCHAR(255))
BEGIN
/**
* Change the password of the passed as a parameter. Only administrators should
* have execute privileges on the procedure since it does not request the user's
* current password.
*
* @param vSelf The user id
* @param vPassword New password
*/
CALL user_checkPassword(vPassword);
UPDATE user SET
`password` = MD5(vPassword),
`recoverPass` = FALSE
WHERE id = vSelf;
END$$
DELIMITER ;

File diff suppressed because one or more lines are too long

View File

@ -12,10 +12,6 @@ INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
VALUES
('1', '6');
INSERT INTO `account`.`mailConfig` (`id`, `domain`)
VALUES
('1', 'verdnatura.es');
INSERT INTO `vn`.`bionicConfig` (`generalInflationCoeficient`, `minimumDensityVolumetricWeight`, `verdnaturaVolumeBox`, `itemCarryBox`)
VALUES
(1.30, 167.00, 138000, 71);
@ -33,12 +29,16 @@ INSERT INTO `vn`.`packagingConfig`(`upperGap`)
('10');
UPDATE `account`.`role` SET id = 100 WHERE id = 0;
CALL `account`.`role_sync`;
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`)
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en'
FROM `account`.`role` WHERE id <> 20
ORDER BY id;
INSERT INTO `account`.`account`(`id`)
SELECT id FROM `account`.`user`;
INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `userFk`, `bossFk`)
SELECT id,UPPER(LPAD(role, 3, '0')), name, name, id, 9
FROM `vn`.`user`;
@ -68,6 +68,24 @@ INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,
(111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'),
(112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en');
INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`)
VALUES
(1, 'general', 'General mailing list', FALSE),
(2, 'it' , 'IT department' , TRUE),
(3, 'sales' , 'Sales department' , TRUE);
INSERT INTO `account`.`mailAliasAccount`(`mailAlias`, `account`)
VALUES
(1, 1),
(1, 18),
(3, 18),
(1, 9),
(2, 9);
INSERT INTO `account`.`mailForward`(`account`, `forwardTo`)
VALUES
(1, 'employee@domain.local');
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`)
VALUES
(106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106),
@ -88,6 +106,15 @@ INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`,
(19,'Francia', 1, 'FR', 1, 27),
(30,'Canarias', 1, 'IC', 1, 24);
INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`)
VALUES
('ca', 'Català' , 'Catalan' , TRUE),
('en', 'English' , 'English' , TRUE),
('es', 'Español' , 'Spanish' , TRUE),
('fr', 'Français' , 'French' , TRUE),
('mn', 'Португалий', 'Mongolian' , TRUE),
('pt', 'Português' , 'Portuguese', TRUE);
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
VALUES
(1, 'Main Warehouse'),
@ -237,6 +264,7 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`)
(3, 'Daily Bugle'),
(4, 'GCN Channel'),
(5, 'The Newspaper');
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`fax`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`,`mailAddress`,`cplusTerIdNifFk`,`hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`)
VALUES
(101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1111111111, 222222222, 333333333, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5,CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, NULL, 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1),
@ -455,8 +483,6 @@ INSERT INTO `vn`.`expence`(`id`, `taxTypeFk`, `name`, `isWithheld`)
(6210000567, 0, 'Alquiler VNH', 0),
(7001000000, 1, 'Mercaderia', 0);
INSERT INTO `vn`.`invoiceOutExpence`(`id`, `invoiceOutFk`, `amount`, `expenceFk`, `created`)
VALUES
(1, 1, 813.06, 2000000000, CURDATE()),
@ -540,32 +566,32 @@ INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agen
(6, NULL, 57, CURDATE(), 5, 7, 'sixth route', 1.7, 60, CURDATE(), CURDATE(), 3),
(7, NULL, 57, CURDATE(), 6, 8, 'seventh route', 0, 70, CURDATE(), CURDATE(), 5);
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `created`)
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`)
VALUES
(1 , 3, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Bat cave', 121, 'T1111111', 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(2 , 1, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T1111111', 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3 , 1, 7, 1, 6, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T2222222', 0, 3, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(4 , 3, 2, 1, 2, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T3333333', 0, 9, DATE_ADD(CURDATE(), INTERVAL -3 MONTH)),
(5 , 3, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T4444444', 0, 10, DATE_ADD(CURDATE(), INTERVAL -4 MONTH)),
(6 , 1, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Mountain Drive Gotham', 1, 'A1111111', 0, 10, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(7 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Mountain Drive Gotham', 1, NULL, 0, 3, CURDATE()),
(8 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Bat cave', 121, NULL, 0, 3, CURDATE()),
(9 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Stark tower', 124, NULL, 0, 3, CURDATE()),
(10, 1, 1, 5, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'Ingram Street', 2, NULL, 0, 1, CURDATE()),
(11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()),
(12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()),
(13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, CURDATE()),
(14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()),
(15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, CURDATE()),
(16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(18, 1, 4, 4, 4, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, CURDATE()),
(19, 1, 5, 5, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, 13, CURDATE()),
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(21, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(22, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(23, NULL, 8, 1, 7, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, NULL, 0, 5, CURDATE()),
(24 ,NULL, 8, 1, 7, CURDATE(), CURDATE(), 101, 'Bruce Wayne', 1, NULL, 0, 5, CURDATE());
(1 , 3, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Bat cave', 121, 'T1111111', 0, 1, 5, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(2 , 1, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T1111111', 0, 1, 5, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3 , 1, 7, 1, 6, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T2222222', 0, 3, 5, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(4 , 3, 2, 1, 2, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T3333333', 0, 9, 5, 1, DATE_ADD(CURDATE(), INTERVAL -3 MONTH)),
(5 , 3, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 104, 'Stark tower', 124, 'T4444444', 0, 10, 5, 1, DATE_ADD(CURDATE(), INTERVAL -4 MONTH)),
(6 , 1, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 101, 'Mountain Drive Gotham', 1, 'A1111111', 0, 10, 5, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(7 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Mountain Drive Gotham', 1, NULL, 0, 3, 5, 1, CURDATE()),
(8 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'Bat cave', 121, NULL, 0, 3, 5, 1, CURDATE()),
(9 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Stark tower', 124, NULL, 0, 3, 5, 1, CURDATE()),
(10, 1, 1, 5, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, CURDATE()),
(11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, 5, 1, CURDATE()),
(12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, 5, 1, CURDATE()),
(13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, 5, 1, CURDATE()),
(14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, CURDATE()),
(15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, 5, 1, CURDATE()),
(16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, 5, 1, CURDATE()),
(17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, 5, 1, CURDATE()),
(18, 1, 4, 4, 4, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, 5, 1, CURDATE()),
(19, 1, 5, 5, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, NULL, 5, 1, CURDATE()),
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, 5, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(21, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, 5, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(22, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, 5, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(23, NULL, 8, 1, 7, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 101, 'address 21', 121, NULL, 0, 5, 5, 1, CURDATE()),
(24 ,NULL, 8, 1, 7, CURDATE(), CURDATE(), 101, 'Bruce Wayne', 1, NULL, 0, 5, 5, 1, CURDATE());
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES
@ -584,7 +610,6 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
-- FIX for state hours on local, inter_afterInsert
UPDATE vncontrol.inter SET odbc_date = DATE_ADD(CURDATE(), INTERVAL -10 SECOND);
INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `workerFk`, `created`)
VALUES
(1, 16, 5 , DATE_ADD(NOW(), INTERVAL -1 MONTH)),
@ -688,11 +713,13 @@ INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`,`workerFk
(5, 'CON', 'Container', 3, NULL, 35, 1),
(6, 'ALS', 'Alstroemeria', 1, 31, 35, 0);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`)
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES
('YEL', 'Yellow', 1, 1),
('BLU', 'Blue', 1, 2),
('RED', 'Red', 1, 3);
('YEL', 'Yellow', 1, 1, 'F4D03F'),
('BLU', 'Blue', 1, 2, '5DADE2'),
('RED', 'Red', 1, 3, 'EC7063'),
('SLV', 'Silver', 1, 4, 'CACFD2'),
('BRW', 'Brown', 1, 5, 'DC7633');
INSERT INTO `vn`.`origin`(`id`,`code`, `name`)
VALUES
@ -735,20 +762,20 @@ INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`)
VALUES
(1, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL, 0),
(2, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66, 1, NULL, 0),
(3, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 65, 1, NULL, 0),
(4, 1, 60, 'AMR', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, 69, 2, NULL, 0),
(5, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 74, 2, NULL, 0),
(6, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 62, 2, NULL, 0),
(7, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 64, 2, NULL, 0),
(8, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 75, 1, NULL, 0),
(9, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 76, 1, NULL, 0),
(10, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 77, 1, NULL, 0),
(11, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 78, 2, NULL, 0),
(12, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 82, 2, NULL, 0),
(13, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 83, 2, NULL, 0),
(14, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 84, 2, NULL, 0),
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL, 0),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66, 1, NULL, 0),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 65, 1, NULL, 0),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, 69, 2, NULL, 0),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 74, 2, NULL, 0),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 62, 2, NULL, 0),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 64, 2, NULL, 0),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 75, 1, NULL, 0),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 76, 1, NULL, 0),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 77, 1, NULL, 0),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 78, 2, NULL, 0),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 82, 2, NULL, 0),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 83, 2, NULL, 0),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 84, 2, NULL, 0),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL, 0),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL, 0),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 88, 2, NULL, 0);
@ -766,25 +793,9 @@ INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `create
(9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(10, 7, 7, 71, CURDATE(), 1, 1, 1, 18);
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`)
INSERT INTO `vn`.`expeditionBoxVol`(`boxFk`, `m3`, `ratio`)
VALUES
(1, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL),
(2, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66, 1, NULL),
(3, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 65, 1, NULL),
(4, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 69, 2, NULL),
(5, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 74, 2, NULL),
(6, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 62, 2, NULL),
(7, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 64, 2, NULL),
(8, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 75, 1, NULL),
(9, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 76, 1, NULL),
(10, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 77, 1, NULL),
(11, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 78, 2, NULL),
(12, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 82, 2, NULL),
(13, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 83, 2, NULL),
(14, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 84, 2, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 88, 2, NULL);
(71,0.141,1);
INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPackageReturnable`, `created`, `itemFk`, `price`)
VALUES
@ -797,19 +808,6 @@ INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPack
('cc', 1640038.00, 56.00, 220.00, 128.00, 1, CURDATE(), 15, 90.00),
('pallet 100', 2745600.00, 100.00, 220.00, 120.00, 1, CURDATE(), 16, 0.00);
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `packagingFk`)
VALUES
(1, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 1),
(2, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 1),
(3, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 3, 1, 1),
(4, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 4, 1, 1),
(5, 1, 2, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 1),
(6, 7, 3, 71, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 1, 1, 1),
(7, 2, 4, 71, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), 1, 1, 1, 1),
(8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), 1, 1, 1, 1),
(9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 1),
(10, 7, 7, 71, CURDATE(), 1, 1, 1, 1);
INSERT INTO `vn`.`ticketPackaging`(`id`, `ticketFk`, `packagingFk`, `quantity`, `created`, `pvp`)
VALUES
(1, 1, 2, 2, CURDATE(), NULL),
@ -888,7 +886,6 @@ INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
(8, 28, 1.25),
(8, 29, 0.42),
(8, 39, 0.017),
(9, 15, 3.0949),
(9, 21, 0.001),
(9, 28, 53),
@ -1052,7 +1049,6 @@ INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `level`)
VALUES
(1, 1, 1);
INSERT INTO `edi`.`genus`(`genus_id`, `latin_genus_name`, `entry_date`, `expiry_date`, `change_date_time`)
VALUES
(1, 'Abelia' , CURDATE(), NULL, CURDATE()),
@ -1087,85 +1083,85 @@ INSERT INTO `vn`.`itemTag`(`id`,`itemFk`,`tagFk`,`value`,`priority`)
(10, 2, 27, '15cm', 3),
(11, 2, 36, 'Stark Industries', 4),
(12, 2, 1, 'Silver', 5),
(13, 2, 67, 'concussion', 6),
(14, 2, 23, '1', 7),
(13, 2, 67, 'Concussion', 6),
(14, 2, 23, '2', 7),
(15, 3, 56, 'Ranged weapon', 1),
(16, 3, 58, 'sniper rifle', 2),
(17, 3, 4, '300mm', 3),
(18, 3, 36, 'Stark Industries', 4),
(19, 3, 1, 'Green', 5),
(20, 3, 67, 'precission', 6),
(21, 3, 23, '1', 7),
(21, 3, 23, '3', 7),
(22, 4, 56, 'Melee weapon', 1),
(23, 4, 58, 'heavy shield', 2),
(24, 4, 4, '1x0.5m', 3),
(25, 4, 36, 'Stark Industries', 4),
(26, 4, 1, 'Black', 5),
(27, 4, 67, 'containtment', 6),
(28, 4, 23, '1', 7),
(28, 4, 23, '4', 7),
(29, 5, 56, 'Ranged weapon', 1),
(30, 5, 58, 'pistol', 2),
(31, 5, 27, '9mm', 3),
(32, 5, 36, 'Stark Industries', 4),
(33, 5, 1, 'Silver', 5),
(34, 5, 67, 'rapid fire', 6),
(35, 5, 23, '1', 7),
(35, 5, 23, '5', 7),
(36, 6, 56, 'Container', 1),
(37, 6, 58, 'ammo box', 2),
(38, 6, 27, '1m', 3),
(39, 6, 36, 'Stark Industries', 4),
(40, 6, 1, 'Green', 5),
(41, 6, 67, 'supply', 6),
(42, 6, 23, '1', 7),
(42, 6, 23, '6', 7),
(43, 7, 56, 'Container', 1),
(44, 7, 58, 'medical box', 2),
(45, 7, 27, '1m', 3),
(46, 7, 36, 'Stark Industries', 4),
(47, 7, 1, 'White', 5),
(48, 7, 67, 'supply', 6),
(49, 7, 23, '1', 7),
(49, 7, 23, '7', 7),
(50, 8, 56, 'Ranged Reinforced weapon', 1),
(51, 8, 58, '+1 longbow', 2),
(52, 8, 27, '2m', 3),
(53, 8, 36, 'Stark Industries', 4),
(54, 8, 1, 'Brown', 5),
(55, 8, 67, 'precission', 6),
(56, 8, 23, '1', 7),
(56, 8, 23, '8', 7),
(57, 9, 56, 'Melee Reinforced weapon', 1),
(58, 9, 58, 'combat fist', 2),
(59, 9, 27, '15cm', 3),
(60, 9, 36, 'Stark Industries', 4),
(61, 9, 1, 'Silver', 5),
(62, 9, 67, 'concussion', 6),
(63, 9, 23, '1', 7),
(62, 9, 67, 'Concussion', 6),
(63, 9, 23, '9', 7),
(64, 10, 56, 'Ranged Reinforced weapon', 1),
(65, 10, 58, 'sniper rifle', 2),
(66, 10, 4, '300mm', 3),
(67, 10, 36, 'Stark Industries', 4),
(68, 10, 1, 'Green', 5),
(69, 10, 67, 'precission', 6),
(70, 10, 23, '1', 7),
(70, 10, 23, '10', 7),
(71, 11, 56, 'Melee Reinforced weapon', 1),
(72, 11, 58, 'heavy shield', 2),
(73, 11, 4, '1x0.5m', 3),
(74, 11, 36, 'Stark Industries', 4),
(75, 11, 1, 'Black', 5),
(76, 11, 67, 'containtment', 6),
(77, 11, 23, '1', 7),
(77, 11, 23, '11', 7),
(78, 12, 56, 'Ranged Reinforced weapon', 1),
(79, 12, 58, 'pistol', 2),
(80, 12, 27, '9mm', 3),
(81, 12, 36, 'Stark Industries', 4),
(82, 12, 1, 'Silver', 5),
(83, 12, 67, 'rapid fire', 6),
(84, 12, 23, '1', 7),
(84, 12, 23, '12', 7),
(85, 13, 56, 'Chest', 1),
(86, 13, 58, 'ammo box', 2),
(87, 13, 27, '1m', 3),
(88, 13, 36, 'Stark Industries', 4),
(89, 13, 1, 'Green', 5),
(90, 13, 67, 'supply', 6),
(91, 13, 23, '1', 7),
(91, 13, 23, '13', 7),
(92, 14, 56, 'Chest', 1),
(93, 14, 58, 'medical box', 2),
(94, 14, 27, '1m', 3),
@ -1756,39 +1752,39 @@ CALL `vn`.zoneGeo_calcTree(); -- this is an auto calculate for table vn.zoneGeo,
INSERT INTO `vn`.`zoneIncluded` (`zoneFk`, `geoFk`, `isIncluded`)
VALUES
(1, 3, 0),
(1, 4, 0),
(1, 5, 0),
(1, 1, 1),
(2, 3, 0),
(2, 4, 0),
(2, 5, 0),
(2, 1, 1),
(3, 3, 0),
(3, 4, 0),
(3, 5, 0),
(3, 1, 1),
(4, 3, 0),
(4, 4, 0),
(4, 5, 0),
(4, 1, 1),
(5, 3, 1),
(5, 4, 0),
(5, 5, 1),
(5, 1, 1),
(6, 3, 1),
(6, 4, 0),
(6, 5, 1),
(6, 1, 1),
(7, 3, 0),
(7, 4, 0),
(7, 5, 0),
(7, 1, 1),
(8, 3, 0),
(8, 4, 0),
(8, 5, 0),
(8, 1, 1),
(10, 10, 1);
(1, 3, 0),
(1, 4, 0),
(1, 5, 0),
(1, 1, 1),
(2, 3, 0),
(2, 4, 0),
(2, 5, 0),
(2, 1, 1),
(3, 3, 0),
(3, 4, 0),
(3, 5, 0),
(3, 1, 1),
(4, 3, 0),
(4, 4, 0),
(4, 5, 0),
(4, 1, 1),
(5, 3, 1),
(5, 4, 0),
(5, 5, 1),
(5, 1, 1),
(6, 3, 1),
(6, 4, 0),
(6, 5, 1),
(6, 1, 1),
(7, 3, 0),
(7, 4, 0),
(7, 5, 0),
(7, 1, 1),
(8, 3, 0),
(8, 4, 0),
(8, 5, 0),
(8, 1, 1),
(10, 10, 1);
INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `dated`)
VALUES
@ -2012,26 +2008,26 @@ INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`)
INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `code`)
VALUES
(1, 'Facturas Recibidas', 'recibidas', NULL, NULL, 'invoiceIn'),
(2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'),
(3, 'Laboral', 'laboral', 37, 37, 'hhrrData'),
(4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'),
(5, 'Otros', 'otros', 1, 1, 'miscellaneous'),
(6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'),
(7, 'IAE Clientes', 'IAE_Clientes', 1, 1, 'economicActivitiesTax'),
(8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'),
(9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'),
(10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'),
(11, 'Contratos', 'contratos', NULL, NULL, 'contracts'),
(12, 'ley de pagos', 'ley pagos', 1, 1, 'paymentsLaw'),
(13, 'Basura', 'basura', 1, 1, 'trash'),
(14, 'Ticket', 'tickets', 1, 1, 'ticket'),
(15, 'Presupuestos', 'Presupuestos', NULL, NULL, 'budgets'),
(16, 'Logistica', 'logistica', NULL, NULL, 'logistics'),
(17, 'cmr', 'cmr', NULL, NULL, 'cmr'),
(18, 'dua', 'dua', NULL, NULL, 'dua'),
(19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'),
(20, 'Reclamación', 'reclamacion', 1, 1, 'claim');
(1, 'Facturas Recibidas', 'recibidas', NULL, NULL, 'invoiceIn'),
(2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'),
(3, 'Laboral', 'laboral', 37, 37, 'hhrrData'),
(4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'),
(5, 'Otros', 'otros', 1, 1, 'miscellaneous'),
(6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'),
(7, 'IAE Clientes', 'IAE_Clientes', 1, 1, 'economicActivitiesTax'),
(8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'),
(9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'),
(10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'),
(11, 'Contratos', 'contratos', NULL, NULL, 'contracts'),
(12, 'ley de pagos', 'ley pagos', 1, 1, 'paymentsLaw'),
(13, 'Basura', 'basura', 1, 1, 'trash'),
(14, 'Ticket', 'tickets', 1, 1, 'ticket'),
(15, 'Presupuestos', 'Presupuestos', NULL, NULL, 'budgets'),
(16, 'Logistica', 'logistica', NULL, NULL, 'logistics'),
(17, 'cmr', 'cmr', NULL, NULL, 'cmr'),
(18, 'dua', 'dua', NULL, NULL, 'dua'),
(19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'),
(20, 'Reclamación', 'reclamacion', 1, 1, 'claim');
INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`)
VALUES

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,9 @@ TABLES=(
role
roleInherit
roleRole
userPassword
accountConfig
mailConfig
)
dump_tables ${TABLES[@]}

View File

@ -275,7 +275,8 @@ export default {
name: 'vn-item-basic-data vn-textfield[ng-model="$ctrl.item.name"]',
relevancy: 'vn-item-basic-data vn-input-number[ng-model="$ctrl.item.relevancy"]',
origin: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
expense: 'vn-autocomplete[ng-model="$ctrl.item.expenseFk"]',
compression: 'vn-item-basic-data vn-input-number[ng-model="$ctrl.item.compression"]',
isFragile: 'vn-check[ng-model="$ctrl.item.isFragile"]',
longName: 'vn-textfield[ng-model="$ctrl.item.longName"]',
isActiveCheckbox: 'vn-check[label="Active"]',
priceInKgCheckbox: 'vn-check[label="Price in kg"]',
@ -521,11 +522,11 @@ export default {
zone: 'vn-autocomplete[ng-model="$ctrl.zoneId"]',
nextStepButton: 'vn-step-control .buttons > section:last-child vn-button',
finalizeButton: 'vn-step-control .buttons > section:last-child button[type=submit]',
stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two vn-tfoot > vn-tr > :nth-child(6)',
stepTwoTotalPriceDif: 'vn-ticket-basic-data-step-two > vn-side-menu div:nth-child(4)',
chargesReason: 'vn-ticket-basic-data-step-two div:nth-child(3) > vn-radio',
},
ticketComponents: {
base: 'vn-ticket-components [name="base-sum"]'
base: 'vn-ticket-components > vn-side-menu div:nth-child(1) > div:nth-child(2)'
},
ticketRequests: {
addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button',
@ -666,15 +667,21 @@ export default {
},
orderCatalog: {
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
type: 'vn-autocomplete[data="$ctrl.itemTypes"]',
type: 'vn-order-catalog vn-autocomplete[data="$ctrl.itemTypes"]',
itemId: 'vn-order-catalog > vn-side-menu vn-textfield[vn-id="itemId"]',
itemTagValue: 'vn-order-catalog > vn-side-menu vn-datalist[vn-id="search"]',
openTagSearch: 'vn-order-catalog > vn-side-menu > div > vn-vertical > vn-datalist[vn-id="search"] .append i',
itemTagValue: 'vn-order-catalog vn-textfield[vn-id="search"]',
openTagSearch: 'vn-order-catalog vn-vertical:nth-child(4) append > vn-icon > i',
tag: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
tagValue: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"]',
firstTagAutocomplete: 'vn-order-catalog-search-panel vn-horizontal:nth-child(2) vn-autocomplete[ng-model="tagValue.value"]',
secondTagAutocomplete: 'vn-order-catalog-search-panel vn-horizontal:nth-child(3) vn-autocomplete[ng-model="tagValue.value"]',
firstTagValue: 'vn-order-catalog-search-panel vn-horizontal:nth-child(2) vn-textfield[ng-model="tagValue.value"]',
secondTagValue: 'vn-order-catalog-search-panel vn-horizontal:nth-child(3) vn-textfield[ng-model="tagValue.value"]',
addTagButton: 'vn-order-catalog-search-panel vn-icon-button[icon="add_circle"]',
searchTagButton: 'vn-order-catalog-search-panel button[type=submit]',
thirdFilterRemoveButton: 'vn-order-catalog > vn-side-menu .chips > vn-chip:nth-child(3) vn-icon[icon=cancel]',
fourthFilterRemoveButton: 'vn-order-catalog > vn-side-menu .chips > vn-chip:nth-child(4) vn-icon[icon=cancel]',
fifthFilterRemoveButton: 'vn-order-catalog > vn-side-menu .chips > vn-chip:nth-child(5) vn-icon[icon=cancel]',
sixthFilterRemoveButton: 'vn-order-catalog > vn-side-menu .chips > vn-chip:nth-child(6) vn-icon[icon=cancel]',
},
orderBasicData: {
client: 'vn-autocomplete[label="Client"]',
@ -896,6 +903,21 @@ export default {
confirmed: 'vn-entry-summary vn-check[label="Confirmed"]',
anyBuyLine: 'vn-entry-summary tr.dark-row'
},
entryBasicData: {
reference: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.ref"]',
notes: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.notes"]',
observations: 'vn-entry-basic-data vn-textarea[ng-model="$ctrl.entry.observation"]',
supplier: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]',
currency: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.currencyFk"]',
commission: 'vn-entry-basic-data vn-input-number[ng-model="$ctrl.entry.commission"]',
company: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
ordered: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isOrdered"]',
confirmed: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isConfirmed"]',
inventory: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isInventory"]',
raid: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isRaid"]',
booked: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isBooked"]',
save: 'vn-entry-basic-data button[type=submit]',
},
entryDescriptor: {
agency: 'vn-entry-descriptor div.body vn-label-value:nth-child(1) span',
travelsQuicklink: 'vn-entry-descriptor vn-quick-link[icon="local_airport"] > a',

View File

@ -45,6 +45,7 @@ describe('Client balance path', () => {
});
it('should create a new payment that clears the debt', async() => {
await page.closePopup();
await page.waitToClick(selectors.clientBalance.newPaymentButton);
await page.autocompleteSearch(selectors.clientBalance.newPaymentBank, 'Pay on receipt');
await page.waitToClick(selectors.clientBalance.saveButton);

View File

@ -28,11 +28,13 @@ describe('Item Edit basic data path', () => {
await page.clearInput(selectors.itemBasicData.relevancy);
await page.write(selectors.itemBasicData.relevancy, '1');
await page.autocompleteSearch(selectors.itemBasicData.origin, 'Spain');
await page.autocompleteSearch(selectors.itemBasicData.expense, 'Alquiler VNH');
await page.clearInput(selectors.itemBasicData.compression);
await page.write(selectors.itemBasicData.compression, '2');
await page.clearInput(selectors.itemBasicData.longName);
await page.write(selectors.itemBasicData.longName, 'RS Rose of Purity');
await page.waitToClick(selectors.itemBasicData.isActiveCheckbox);
await page.waitToClick(selectors.itemBasicData.priceInKgCheckbox);
await page.waitToClick(selectors.itemBasicData.isFragile);
await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
const message = await page.waitForSnackbar();
@ -93,11 +95,11 @@ describe('Item Edit basic data path', () => {
expect(result).toEqual('Spain');
});
it(`should confirm the item expence was edited`, async() => {
it(`should confirm the item compression was edited`, async() => {
const result = await page
.waitToGetProperty(selectors.itemBasicData.expense, 'value');
.waitToGetProperty(selectors.itemBasicData.compression, 'value');
expect(result).toEqual('Alquiler VNH');
expect(result).toEqual('2');
});
it(`should confirm the item long name was edited`, async() => {
@ -107,6 +109,13 @@ describe('Item Edit basic data path', () => {
expect(result).toEqual('RS Rose of Purity');
});
it('should confirm isFragile checkbox is unchecked', async() => {
const result = await page
.checkboxState(selectors.itemBasicData.isFragile);
expect(result).toBe('checked');
});
it('should confirm isActive checkbox is unchecked', async() => {
const result = await page
.checkboxState(selectors.itemBasicData.isActiveCheckbox);

View File

@ -24,7 +24,6 @@ describe('Ticket List components path', () => {
await page.waitPropertyLength(selectors.ticketComponents.base, 'innerText', minLength);
const base = await page.waitToGetProperty(selectors.ticketComponents.base, 'innerText');
expect(base).toContain('Base');
expect(base.length).toBeGreaterThan(minLength);
});

View File

@ -37,31 +37,50 @@ describe('Order catalog', () => {
expect(result).toEqual(4);
});
it('should search for the item tag value +1 and find two results', async() => {
await page.write(selectors.orderCatalog.itemTagValue, '+1');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements('section.product', 2);
const result = await page.countElement('section.product');
expect(result).toEqual(2);
it('should perfom an "OR" search for the item tag colors silver and brown', async() => {
await page.waitToClick(selectors.orderCatalog.openTagSearch);
await page.autocompleteSearch(selectors.orderCatalog.tag, 'Color');
await page.autocompleteSearch(selectors.orderCatalog.firstTagAutocomplete, 'silver');
await page.waitToClick(selectors.orderCatalog.addTagButton);
await page.autocompleteSearch(selectors.orderCatalog.secondTagAutocomplete, 'brown');
await page.waitToClick(selectors.orderCatalog.searchTagButton);
await page.waitForNumberOfElements('section.product', 4);
});
it('should search for the item tag categoria +1 and find two results', async() => {
it('should perfom an "OR" search for the item tag tallos 2 and 9', async() => {
await page.waitToClick(selectors.orderCatalog.openTagSearch);
await page.autocompleteSearch(selectors.orderCatalog.tag, 'categoria');
await page.write(selectors.orderCatalog.tagValue, '+1');
await page.autocompleteSearch(selectors.orderCatalog.tag, 'Tallos');
await page.write(selectors.orderCatalog.firstTagValue, '2');
await page.waitToClick(selectors.orderCatalog.addTagButton);
await page.write(selectors.orderCatalog.secondTagValue, '9');
await page.waitToClick(selectors.orderCatalog.searchTagButton);
await page.waitForNumberOfElements('section.product', 2);
});
it('should perform a general search for category', async() => {
await page.write(selectors.orderCatalog.itemTagValue, 'concussion');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements('section.product', 2);
});
it('should perfom an "AND" search for the item tag tallos 2', async() => {
await page.waitToClick(selectors.orderCatalog.openTagSearch);
await page.autocompleteSearch(selectors.orderCatalog.tag, 'Tallos');
await page.write(selectors.orderCatalog.firstTagValue, '2');
await page.waitToClick(selectors.orderCatalog.searchTagButton);
await page.waitForNumberOfElements('section.product', 1);
const result = await page.countElement('section.product');
expect(result).toEqual(1);
});
it('should remove the tag filters and have 4 results', async() => {
await page.waitForContentLoaded();
await page.waitToClick(selectors.orderCatalog.sixthFilterRemoveButton);
await page.waitForContentLoaded();
await page.waitToClick(selectors.orderCatalog.fifthFilterRemoveButton);
await page.waitForContentLoaded();
await page.waitToClick(selectors.orderCatalog.fourthFilterRemoveButton);
await page.waitForContentLoaded();
await page.waitToClick(selectors.orderCatalog.thirdFilterRemoveButton);
await page.waitForNumberOfElements('.product', 4);
const result = await page.countElement('section.product');

View File

@ -0,0 +1,113 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('2');
await page.accessToSection('entry.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should edit the basic data', async() => {
await page.write(selectors.entryBasicData.reference, 'new movement 8');
await page.write(selectors.entryBasicData.notes, 'new notes');
await page.write(selectors.entryBasicData.observations, ' edited');
await page.autocompleteSearch(selectors.entryBasicData.supplier, 'Plants nick');
await page.autocompleteSearch(selectors.entryBasicData.currency, 'eur');
await page.clearInput(selectors.entryBasicData.commission);
await page.write(selectors.entryBasicData.commission, '100');
await page.autocompleteSearch(selectors.entryBasicData.company, 'CCs');
await page.waitToClick(selectors.entryBasicData.ordered);
await page.waitToClick(selectors.entryBasicData.confirmed);
await page.waitToClick(selectors.entryBasicData.inventory);
await page.waitToClick(selectors.entryBasicData.raid);
await page.waitToClick(selectors.entryBasicData.booked);
await page.waitToClick(selectors.entryBasicData.save);
const message = await page.waitForSnackbar();
expect(message.text).toBe('Data saved!');
});
it('should confirm the reference was edited', async() => {
await page.reloadSection('entry.card.basicData');
const result = await page.waitToGetProperty(selectors.entryBasicData.reference, 'value');
expect(result).toEqual('new movement 8');
});
it('should confirm the note was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.notes, 'value');
expect(result).toEqual('new notes');
});
it('should confirm the observation was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.observations, 'value');
expect(result).toEqual('observation two edited');
});
it('should confirm the supplier was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.supplier, 'value');
expect(result).toEqual('1 - Plants nick');
});
it('should confirm the currency was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.currency, 'value');
expect(result).toEqual('EUR');
});
it('should confirm the commission was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.commission, 'value');
expect(result).toEqual('100');
});
it('should confirm the company was edited', async() => {
const result = await page.waitToGetProperty(selectors.entryBasicData.company, 'value');
expect(result).toEqual('CCs');
});
it('should confirm ordered was edited', async() => {
const result = await page.checkboxState(selectors.entryBasicData.ordered);
expect(result).toBe('checked');
});
it('should confirm confirmed was edited', async() => {
const result = await page.checkboxState(selectors.entryBasicData.confirmed);
expect(result).toBe('checked');
});
it('should confirm inventory was edited', async() => {
const result = await page.checkboxState(selectors.entryBasicData.inventory);
expect(result).toBe('checked');
});
it('should confirm raid was edited', async() => {
const result = await page.checkboxState(selectors.entryBasicData.raid);
expect(result).toBe('checked');
});
it('should confirm booked was edited', async() => {
const result = await page.checkboxState(selectors.entryBasicData.booked);
expect(result).toBe('checked');
});
});

View File

@ -134,7 +134,7 @@ export default class CrudModel extends ModelProxy {
*/
save() {
if (!this.isChanged)
return null;
return this.$q.resolve();
let deletes = [];
let updates = [];

View File

@ -1,6 +1,7 @@
<div ng-if="$ctrl.isReady">
<div ng-transclude></div>
<vn-pagination
ng-if="$ctrl.model"
model="$ctrl.model"
class="vn-pt-md">
</vn-pagination>

View File

@ -32,7 +32,6 @@ vn-list,
vn-item,
.vn-item {
@extend %clickable;
display: flex;
align-items: center;
color: inherit;
@ -85,4 +84,6 @@ vn-item,
}
}
a.vn-item {
@extend %clickable;
}

View File

@ -1,5 +1,6 @@
import ngModule from '../../module';
import Popover from '../popover';
import './style.scss';
export default class Menu extends Popover {
show(parent) {

View File

@ -0,0 +1,7 @@
@import "./effects";
.vn-menu {
vn-item, .vn-item {
@extend %clickable;
}
}

View File

@ -107,15 +107,16 @@ export default class ModelProxy extends DataModel {
* Removes a row from the model and emits the 'rowRemove' event.
*
* @param {Number} index The row index
* @return {Promise} The save request promise
*/
remove(index) {
let [item] = this.data.splice(index, 1);
let [row] = this.data.splice(index, 1);
let proxiedIndex = this.proxiedData.indexOf(item);
let proxiedIndex = this.proxiedData.indexOf(row);
this.proxiedData.splice(proxiedIndex, 1);
if (!item.$isNew)
this.removed.push(item);
if (!row.$isNew)
this.removed.push(row);
this.isChanged = true;
if (!this.data.length)
@ -125,7 +126,19 @@ export default class ModelProxy extends DataModel {
this.emit('dataUpdate');
if (this.autoSave)
this.save();
return this.save();
else
return this.$q.resolve();
}
/**
* Removes a row from the model and emits the 'rowRemove' event.
*
* @param {Object} row The row object
* @return {Promise} The save request promise
*/
removeRow(row) {
return this.remove(this.data.indexOf(row));
}
/**

View File

@ -1,6 +1,7 @@
@import "variables";
.vn-radio {
& > .btn {
border-radius: 50%;
@ -25,4 +26,8 @@
&.disabled.checked > .btn > .mark {
background-color: $color-font-secondary;
}
> div {
text-overflow: ellipsis;
overflow: hidden;
}
}

View File

@ -1 +1 @@
Search by: Search by {{module | translate}}
Search for: Search {{module}}

View File

@ -1 +1 @@
Search by: Buscar por {{module | translate}}
Search for: Buscar {{module}}

View File

@ -16,6 +16,7 @@ import './style.scss';
* @property {Function} onSearch Function to call when search is submited
* @property {CrudModel} model The model used for searching
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
* @property {String} baseState The base state for searchs
*/
export default class Searchbar extends Component {
constructor($element, $) {
@ -23,6 +24,8 @@ export default class Searchbar extends Component {
this.searchState = '.';
this.placeholder = 'Search';
this.autoState = true;
this.separateIndex = true;
this.entityState = 'card.summary';
this.deregisterCallback = this.$transitions.onSuccess(
{}, transition => this.onStateChange(transition));
@ -33,11 +36,13 @@ export default class Searchbar extends Component {
if (!this.baseState) {
let stateParts = this.$state.current.name.split('.');
this.baseState = stateParts[0];
}
this.searchState = `${this.baseState}.index`;
} else
this.searchState = this.baseState;
this.searchState = `${this.baseState}.index`;
this.placeholder = this.$t('Search by', {
module: this.baseState
let description = this.$state.get(this.baseState).description;
this.placeholder = this.$t('Search for', {
module: this.$t(description).toLowerCase()
});
}
@ -222,7 +227,7 @@ export default class Searchbar extends Component {
subState += '.index';
break;
default:
subState = 'card.summary';
subState = this.entityState;
}
if (this.stateParams)
@ -292,8 +297,10 @@ ngModule.vnComponent('vnSearchbar', {
panel: '@',
info: '@?',
onSearch: '&?',
baseState: '@?',
autoState: '<?',
baseState: '@?',
entityState: '@?',
separateIndex: '<?',
stateParams: '&?',
model: '<?',
exprBuilder: '&?',

View File

@ -2,6 +2,6 @@
<div class="focus-mark"></div>
<div class="mark"></div>
</div>
<span translate>
<div translate>
{{::$ctrl.label}}
</span>
</div>

View File

@ -1,5 +1,6 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import {kebabToCamel} from '../../lib/string';
import './style.scss';
let positions = ['left', 'right', 'up', 'down'];
@ -206,8 +207,8 @@ ngModule.vnComponent('vnTooltip', {
}
});
directive.$inject = ['$document', '$compile', '$templateRequest'];
export function directive($document, $compile, $templateRequest) {
directive.$inject = ['$document', '$compile'];
export function directive($document, $compile) {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {

View File

@ -1,70 +1,220 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import getModifiedData from '../../lib/modified';
import copyObject from '../../lib/copy';
import isEqual from '../../lib/equals';
import isFullEmpty from '../../lib/full-empty';
import UserError from '../../lib/user-error';
import {mergeFilters} from 'vn-loopback/util/filter';
/**
* Component that checks for changes on a specific model property and
* asks the user to save or discard it when the state changes.
* Also it can save the data to the server when the @url and @idField
* properties are provided.
* Component that checks for changes on a specific model property and asks the
* user to save or discard it when the state changes.
* Also it can save the data to the server when the @url and @idField properties
* are provided.
*
* @property {String} idField The id field name, 'id' if not specified
* @property {*} idValue The id field value
* @property {Boolean} isNew Whether is a new instance
* @property {Boolean} insertMode Whether to enable insert mode
* @property {String} url The base HTTP request path
* @property {Boolean} get Whether to fetch initial data
*/
export default class Watcher extends Component {
constructor($element, $, $state, $stateParams, $transitions, $http, vnApp, $translate, $attrs, $q) {
super($element);
Object.assign(this, {
$,
$state,
$stateParams,
$http,
_: $translate,
$attrs,
vnApp,
$q
});
constructor(...args) {
super(...args);
this.idField = 'id';
this.get = true;
this.insertMode = false;
this.state = null;
this.deregisterCallback = $transitions.onStart({},
this.deregisterCallback = this.$transitions.onStart({},
transition => this.callback(transition));
this.updateOriginalData();
this.snapshot();
}
$onInit() {
if (this.get && this.url)
this.fetchData();
else if (this.get && !this.url)
throw new Error('URL parameter ommitted');
}
let fetch = !this.insertMode
&& this.get
&& this.url
&& this.idValue;
$onChanges() {
if (this.data)
this.updateOriginalData();
if (fetch)
this.fetch();
else {
this.isNew = !!this.insertMode;
this.snapshot();
}
}
$onDestroy() {
this.deregisterCallback();
}
/**
* @type {Booelan} The computed instance HTTP path
*/
get instanceUrl() {
return `${this.url}/${this.idValue}`;
}
/**
* @type {Object} The instance data
*/
get data() {
return this._data;
}
set data(value) {
this._data = value;
this.isNew = !!this.insertMode;
this.snapshot();
}
/**
* @type {Booelan} Whether it's popullated with data
*/
get hasData() {
return !!this.data;
}
set hasData(value) {
if (value)
this.fill();
else
this.delete();
}
/**
* @type {Booelan} Whether instance data have been modified
*/
get dirty() {
return this.form && this.form.$dirty || this.dataChanged();
}
dataChanged() {
let data = this.copyInNewObject(this.data);
return !isEqual(data, this.orgData);
fetch() {
let filter = mergeFilters({
fields: this.fields,
where: this.where,
include: this.include
}, this.filter);
let params = filter ? {filter} : null;
return this.$http.get(this.instanceUrl, params)
.then(json => {
this.overwrite(json.data);
this.isNew = false;
this.snapshot();
})
.catch(err => {
if (!(err.name == 'HttpError' && err.status == 404))
throw err;
if (this.autoFill) {
this.insert();
this.snapshot();
}
});
}
fetchData() {
let id = this.data[this.idField];
return this.$http.get(`${this.url}/${id}`).then(
json => {
this.data = copyObject(json.data);
this.updateOriginalData();
}
);
insert(data) {
this.assign({[this.idField]: this.idValue}, data);
this.isNew = true;
this.deleted = null;
}
delete() {
if (!this.hasData) return;
this.deleted = this.makeSnapshot();
this.clear();
this.isNew = false;
}
recover() {
if (!this.deleted) return;
this.restoreSnapshot(this.deleted);
}
fill() {
if (this.hasData)
return;
if (this.deleted)
this.recover();
else if (this.original && this.original.data)
this.reset();
else
this.insert();
}
reset() {
this.restoreSnapshot(this.original);
this.setPristine();
}
snapshot() {
const snapshot = this.makeSnapshot();
if (snapshot.data) {
const idValue = snapshot.data[this.idField];
if (idValue) this.idValue = idValue;
}
this.original = snapshot;
this.orgData = snapshot.data;
this.deleted = null;
this.setPristine();
}
makeSnapshot() {
return {
data: this.copyData(),
isNew: this.isNew,
ref: this.data
};
}
restoreSnapshot(snapshot) {
if (!snapshot) return;
this._data = snapshot.ref;
this.overwrite(snapshot.data);
this.isNew = snapshot.isNew;
this.deleted = null;
}
writeData(res) {
if (this.hasData)
this.assign(res.data);
this.isNew = false;
this.snapshot();
return res;
}
clear() {
this._data = null;
}
overwrite(data) {
if (data) {
if (!this.data) this._data = {};
overwrite(this.data, data);
} else
this._data = null;
}
assign(...args) {
this._data = Object.assign(this.data || {}, ...args);
}
copyData() {
return copyObject(this.data);
}
refresh() {
return this.fetch();
}
dataChanged() {
return !isEqual(this.orgData, this.copyData());
}
/**
@ -100,9 +250,10 @@ export default class Watcher extends Component {
*/
submit() {
try {
if (this.requestMethod() !== 'post')
if (this.isNew)
this.isInvalid();
else
this.check();
else this.isInvalid();
} catch (err) {
return this.$q.reject(err);
}
@ -122,65 +273,42 @@ export default class Watcher extends Component {
if (this.form)
this.form.$setSubmitted();
const isPost = (this.requestMethod() === 'post');
if (!this.dataChanged() && !isPost) {
this.updateOriginalData();
if (!this.dataChanged() && !this.isNew) {
this.snapshot();
return this.$q.resolve();
}
let changedData = isPost
let changedData = this.isNew
? this.data
: getModifiedData(this.data, this.orgData);
let id = this.idField ? this.orgData[this.idField] : null;
// If watcher is associated to mgCrud
if (this.save && this.save.accept) {
if (id)
changedData[this.idField] = id;
this.save.model = changedData;
return this.$q((resolve, reject) => {
this.save.accept().then(
json => this.writeData({data: json}, resolve),
reject
);
});
return this.save.accept()
.then(json => this.writeData({data: json}));
}
// When mgCrud is not used
if (id) {
return this.$q((resolve, reject) => {
this.$http.patch(`${this.url}/${id}`, changedData).then(
json => this.writeData(json, resolve),
reject
);
});
}
let req;
return this.$q((resolve, reject) => {
this.$http.post(this.url, changedData).then(
json => this.writeData(json, resolve),
reject
);
});
}
/**
* return the request method.
*/
if (this.deleted)
req = this.$http.delete(this.instanceUrl);
else if (this.isNew)
req = this.$http.post(this.url, changedData);
else
req = this.$http.patch(this.instanceUrl, changedData);
requestMethod() {
return this.$attrs.save && this.$attrs.save.toLowerCase();
return req.then(res => this.writeData(res));
}
/**
* Checks if data is ready to send.
*/
check() {
if (this.form && this.form.$invalid)
throw new UserError('Some fields are invalid');
this.isInvalid();
if (!this.dirty)
throw new UserError('No changes to save');
}
@ -220,62 +348,82 @@ export default class Watcher extends Component {
onConfirmResponse(response) {
if (response === 'accept') {
if (this.data)
Object.assign(this.data, this.orgData);
this.reset();
this.$state.go(this.state);
} else
this.state = null;
}
writeData(json, resolve) {
Object.assign(this.data, json.data);
this.updateOriginalData();
resolve(json);
}
updateOriginalData() {
this.orgData = this.copyInNewObject(this.data);
this.setPristine();
}
/**
* @deprecated Use reset()
*/
loadOriginalData() {
const orgData = JSON.parse(JSON.stringify(this.orgData));
this.data = Object.assign(this.data, orgData);
this.setPristine();
this.reset();
}
copyInNewObject(data) {
let newCopy = {};
if (data && typeof data === 'object') {
Object.keys(data).forEach(
key => {
let value = data[key];
if (value instanceof Date)
newCopy[key] = new Date(value.getTime());
else if (!isFullEmpty(value)) {
if (typeof value === 'object')
newCopy[key] = this.copyInNewObject(value);
else
newCopy[key] = value;
}
}
);
}
return newCopy;
/**
* @deprecated Use snapshot()
*/
updateOriginalData() {
this.snapshot();
}
}
Watcher.$inject = ['$element', '$scope', '$state', '$stateParams', '$transitions', '$http', 'vnApp', '$translate', '$attrs', '$q'];
Watcher.$inject = ['$element', '$scope'];
function copyObject(data) {
let newCopy;
if (data && typeof data === 'object') {
newCopy = {};
Object.keys(data).forEach(
key => {
let value = data[key];
if (value instanceof Date)
newCopy[key] = new Date(value.getTime());
else if (!isFullEmpty(value)) {
if (typeof value === 'object')
newCopy[key] = copyObject(value);
else
newCopy[key] = value;
}
}
);
} else
newCopy = data;
return newCopy;
}
function clearObject(obj) {
if (!obj) return;
for (let key in obj) {
if (obj.hasOwnProperty(key))
delete obj[key];
}
}
function overwrite(obj, data) {
if (!obj) return;
clearObject(obj);
Object.assign(obj, data);
}
ngModule.vnComponent('vnWatcher', {
template: require('./watcher.html'),
bindings: {
url: '@?',
idField: '@?',
data: '<',
idValue: '<?',
data: '=',
form: '<',
save: '<',
get: '<?'
save: '<?',
get: '<?',
insertMode: '<?',
autoFill: '<?',
filter: '<?',
fields: '<?',
where: '<?',
include: '<?'
},
controller: Watcher
});

View File

@ -1,5 +1,4 @@
import './watcher.js';
import getModifiedData from '../../lib/modified';
describe('Component vnWatcher', () => {
let $scope;
@ -9,10 +8,16 @@ describe('Component vnWatcher', () => {
let controller;
let $attrs;
let $q;
let data;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$state_, _$q_) => {
data = {
id: 1,
foo: 'bar'
};
$scope = $rootScope.$new();
$element = angular.element('<div></div>');
$state = _$state_;
@ -25,38 +30,49 @@ describe('Component vnWatcher', () => {
}));
describe('$onInit()', () => {
it('should call fetchData() if controllers get and url properties are defined', () => {
controller.get = () => {};
controller.url = 'test.com';
jest.spyOn(controller, 'fetchData').mockReturnThis();
it('should set data empty by default', () => {
controller.$onInit();
expect(controller.fetchData).toHaveBeenCalledWith();
expect(controller.data).toBeUndefined();
});
it(`should throw an error if $onInit is called without url defined`, () => {
controller.get = () => {};
it('should set new data when insert mode is enabled', () => {
controller.insertMode = true;
controller.data = data;
expect(function() {
controller.$onInit();
}).toThrowError(/parameter/);
controller.$onInit();
expect(controller.orgData).toEqual(data);
expect(controller.orgData).toEqual(data);
expect(controller.isNew).toBeTruthy();
});
it('should call backend and fetch data if url and idValue properties are defined', () => {
controller.url = 'Foos';
controller.idValue = 1;
$httpBackend.expectGET('Foos/1').respond(data);
controller.$onInit();
$httpBackend.flush();
expect(controller.orgData).toEqual(data);
expect(controller.orgData).toEqual(data);
expect(controller.orgData).not.toBe(controller.data);
});
});
describe('fetchData()', () => {
it(`should perform a query then store the received data into controller.data and call updateOriginalData()`, () => {
jest.spyOn(controller, 'updateOriginalData');
let json = {data: 'some data'};
controller.data = [1];
controller.idField = 0;
controller.url = 'test.com';
$httpBackend.whenGET('test.com/1').respond(json);
$httpBackend.expectGET('test.com/1');
controller.fetchData();
describe('fetch()', () => {
it(`should perform a query then store the received data into data property and make an snapshot into orgData`, () => {
controller.url = 'Bars';
controller.idValue = 1;
$httpBackend.expectGET('Bars/1').respond(data);
controller.$onInit();
$httpBackend.flush();
expect(controller.data).toEqual({data: 'some data'});
expect(controller.updateOriginalData).toHaveBeenCalledWith();
expect(controller.orgData).toEqual(data);
expect(controller.orgData).toEqual(data);
expect(controller.orgData).not.toBe(controller.data);
});
});
@ -95,14 +111,6 @@ describe('Component vnWatcher', () => {
controller.check();
}).toThrowError();
});
it(`should throw error if controller.dirty is true`, () => {
controller.form = {$invalid: true};
expect(function() {
controller.check();
}).toThrowError();
});
});
describe('realSubmit()', () => {
@ -130,59 +138,60 @@ describe('Component vnWatcher', () => {
});
});
describe('when id is defined', () => {
it(`should perform a query then call controller.writeData()`, done => {
controller.dataChanged = () => {
return true;
};
controller.data = {id: 2};
controller.orgData = {id: 1};
let changedData = getModifiedData(controller.data, controller.orgData);
controller.idField = 'id';
controller.url = 'test.com';
let json = {data: 'some data'};
jest.spyOn(controller, 'writeData');
$httpBackend.whenPATCH(`${controller.url}/1`, changedData).respond(json);
$httpBackend.expectPATCH(`${controller.url}/1`);
controller.realSubmit()
.then(() => {
expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function));
done();
}).catch(done.fail);
describe('should perform a PATCH query and save the data', () => {
it(`should perform a query then call controller.writeData()`, () => {
controller.url = 'Foos';
controller.data = data;
const changedData = {baz: 'value'};
Object.assign(controller.data, changedData);
$httpBackend.expectPATCH('Foos/1', changedData).respond({newProp: 'some'});
controller.realSubmit();
$httpBackend.flush();
expect(controller.data).toEqual(Object.assign({}, data, changedData));
});
});
it(`should perform a POST query then call controller.writeData()`, done => {
controller.dataChanged = () => {
return true;
};
controller.data = {id: 2};
controller.orgData = {id: 1};
controller.url = 'test.com';
let json = {data: 'some data'};
jest.spyOn(controller, 'writeData');
$httpBackend.whenPOST(`${controller.url}`, controller.data).respond(json);
$httpBackend.expectPOST(`${controller.url}`, controller.data);
controller.realSubmit()
.then(() => {
expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function));
done();
}).catch(done.fail);
it(`should perform a POST query and save the data`, () => {
controller.insertMode = true;
controller.url = 'Foos';
controller.data = data;
const changedData = {baz: 'value'};
Object.assign(controller.data, changedData);
$httpBackend.expectPOST('Foos', controller.data).respond({newProp: 'some'});
controller.realSubmit();
$httpBackend.flush();
expect(controller.data).toEqual(Object.assign({}, data, changedData));
});
describe('should perform a DELETE query and save empty data', () => {
it(`should perform a query then call controller.writeData()`, () => {
controller.url = 'Foos';
controller.data = data;
controller.delete();
$httpBackend.expectDELETE('Foos/1').respond();
controller.realSubmit();
$httpBackend.flush();
expect(controller.data).toBeNull();
});
});
});
describe('writeData()', () => {
it(`should call Object.asssign() function over controllers.data with json.data, then call updateOriginalData function and finally call resolve() function`, () => {
jest.spyOn(controller, 'updateOriginalData');
controller.data = {};
let json = {data: 'some data'};
let resolve = jasmine.createSpy('resolve');
controller.writeData(json, resolve);
it(`should save data into orgData`, () => {
controller.data = data;
Object.assign(controller.data, {baz: 'value'});
expect(controller.updateOriginalData).toHaveBeenCalledWith();
expect(resolve).toHaveBeenCalledWith(json);
controller.writeData({});
expect(controller.data).toEqual(controller.orgData);
});
});
@ -224,37 +233,38 @@ describe('Component vnWatcher', () => {
describe(`onConfirmResponse()`, () => {
describe(`when response is accept`, () => {
it(`should call Object.assing on controlle.data with controller.orgData then call go() on state`, () => {
let response = 'accept';
controller.data = {};
controller.orgData = {name: 'Batman'};
it(`should reset data them go to state`, () => {
let data = {key: 'value'};
controller.data = data;
data.foo = 'bar';
controller.$state = {go: jasmine.createSpy('go')};
controller.state = 'Batman';
controller.onConfirmResponse(response);
controller.state = 'foo.bar';
controller.onConfirmResponse('accept');
expect(controller.data).toEqual(controller.orgData);
expect(controller.data).toEqual({key: 'value'});
expect(controller.$state.go).toHaveBeenCalledWith(controller.state);
});
});
describe(`when response is not accept`, () => {
it(`should set controller.state to null`, () => {
let response = 'anything but accept';
controller.state = 'Batman';
controller.onConfirmResponse(response);
controller.onConfirmResponse('cancel');
expect(controller.state).toBeFalsy();
});
});
});
describe(`loadOriginalData()`, () => {
it(`should iterate over the current data object, delete all properties then assign the ones from original data`, () => {
controller.data = {name: 'Bruce'};
controller.orgData = {name: 'Batman'};
controller.loadOriginalData();
describe(`reset()`, () => {
it(`should reset data as it was before changing it`, () => {
let data = {key: 'value'};
expect(controller.data).toEqual(controller.orgData);
controller.data = data;
data.foo = 'bar';
controller.reset();
expect(data).toEqual({key: 'value'});
});
});
});

View File

@ -65,7 +65,7 @@ export function directive($translate, $window) {
};
if (form)
$scope.$watch(form.$submitted, refreshError);
$scope.$watch(() => form.$submitted, refreshError);
}
}
ngModule.directive('rule', directive);

View File

@ -25,4 +25,10 @@
%active {
background-color: $color-active;
color: $color-active-font;
&:hover,
&:focus {
background-color: $color-active;
color: $color-active-font;
}
}

View File

@ -18,5 +18,6 @@ export default function moduleImport(moduleName) {
case 'invoiceOut' : return import('invoiceOut/front');
case 'route' : return import('route/front');
case 'entry' : return import('entry/front');
case 'account' : return import('account/front');
}
}

View File

@ -8,13 +8,13 @@
<div class="header">
<a
translate-attr="{title: 'Go to module index'}"
ui-sref="{{::$ctrl.module}}.index"
ui-sref="{{::$ctrl.indexState}}"
name="goToModuleIndex">
<vn-icon icon="{{$ctrl.moduleMap[$ctrl.module].icon}}"></vn-icon>
</a>
<a
translate-attr="{title: 'Preview'}"
ui-sref="{{::$ctrl.module}}.card.summary({id: $ctrl.descriptor.id})">
ui-sref="{{::$ctrl.summaryState}}({id: $ctrl.descriptor.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<vn-icon-button

View File

@ -15,6 +15,8 @@ export default class Descriptor extends Component {
}
$postLink() {
super.$postLink();
const content = this.element.querySelector('vn-descriptor-content');
if (!content) throw new Error('Directive vnDescriptorContent not found');
@ -104,6 +106,14 @@ export class DescriptorContent {
this.$transclude = $transclude;
this.moduleMap = vnModules.getMap();
}
get summaryState() {
return `${this.baseState || this.module}.card.summary`;
}
get indexState() {
return this.baseState || `${this.module}.index`;
}
}
DescriptorContent.$inject = ['$transclude', 'vnModules'];
@ -112,6 +122,7 @@ ngModule.vnComponent('vnDescriptorContent', {
controller: DescriptorContent,
bindings: {
module: '@',
baseState: '@?',
description: '<',
descriptor: '<?'
},

View File

@ -15,7 +15,7 @@ export class Layout extends Component {
getUserData() {
this.$http.get('Accounts/getCurrentUserData').then(json => {
this.$.$root.user = json.data;
window.localStorage.currentUserWorkerId = json.data.workerId;
window.localStorage.currentUserWorkerId = json.data.id;
});
}
}

View File

@ -1,6 +1,6 @@
<ul class="vn-list" ng-if="::$ctrl.items.length > 0">
<li ng-repeat="item in ::$ctrl.items" name="{{::item.description}}">
<a ng-if="!item.external"
<a ng-if="::!item.external"
ui-sref="{{::item.state}}"
class="vn-item"
ng-class="{active: item.active && !item.childs, expanded: item.active}"
@ -15,7 +15,7 @@
<vn-icon icon="keyboard_arrow_down" ng-if="::item.childs.length > 0"></vn-icon>
</vn-item-section>
</a>
<a ng-if="item.external"
<a ng-if="::item.external"
href="{{::item.url}}"
class="vn-item">
<vn-item-section avatar>

View File

@ -32,73 +32,86 @@ export default class LeftMenu {
let moduleIndex = this.$state.current.data.moduleIndex;
let moduleFile = window.routes[moduleIndex] || [];
let menu = moduleFile.menus && moduleFile.menus[this.source] || [];
let items = [];
let addItem = (items, item) => {
let state = states[item.state];
if (state) {
state = state.self;
let acl = state.data.acl;
let cloneItems = (items, parent) => {
let myItems = [];
if (acl && !this.aclService.hasAny(acl))
return;
} else if (!item.external) {
console.warn('wrong left-menu definition');
return;
for (let item of items) {
let state = states[item.state];
if (state) {
state = state.self;
let acl = state.data.acl;
if (acl && !this.aclService.hasAny(acl))
continue;
}
let myItem = {
icon: item.icon,
description: item.description || state.description,
state: item.state,
external: item.external,
url: item.url,
parent
};
if (item.childs) {
let myChilds = cloneItems(item.childs, myItem);
if (myChilds.length > 0) {
myItem.childs = myChilds;
myItems.push(myItem);
}
} else
myItems.push(myItem);
}
items.push({
icon: item.icon,
description: item.description || state.description,
state: item.state,
external: item.external,
url: item.url
});
return myItems;
};
for (let item of menu) {
if (item.state || item.external)
addItem(items, item);
else {
let childs = [];
for (let child of item.childs)
addItem(childs, child);
if (childs.length > 0) {
items.push({
icon: item.icon,
description: item.description,
childs: childs
});
}
}
}
return items;
return cloneItems(menu);
}
activateItem() {
let myState = this.$state.current.name
.split('.')
.slice(0, this._depth)
.join('.');
let re = new RegExp(`^${myState}(\\..*)?$`);
if (!this.items) return;
let currentState = this.$state.current.name;
let maxSpecificity = 0;
let selectedItem;
if (this.items) {
// Check items matching current path
for (let item of this.items) {
item.active = re.test(item.state);
function isParentState(state, currentState) {
if (!state) return 0;
let match = state.match(/^(.*)\.index$/);
if (match) state = match[1];
if (item.childs) {
for (let child of item.childs) {
child.active = re.test(child.state);
if (child.active)
item.active = child.active;
}
let isParent =
currentState.startsWith(`${state}.`) ||
currentState === state;
return isParent
? (state.match(/\./g) || []).length + 1
: 0;
}
function selectItem(items) {
if (!items) return;
for (let item of items) {
item.active = false;
let specificity = isParentState(item.state, currentState);
if (specificity > maxSpecificity) {
selectedItem = item;
maxSpecificity = specificity;
}
selectItem(item.childs);
}
}
// Check items matching current path
selectItem(this.items);
while (selectedItem) {
selectedItem.active = true;
selectedItem = selectedItem.parent;
}
}
setActive(item) {

View File

@ -36,7 +36,7 @@
</vn-icon-button>
</div>
<a
ui-sref="worker.card.summary({id: $root.user.workerId})"
ui-sref="worker.card.summary({id: $root.user.id})"
class="vn-button colored"
translate>
My account

View File

@ -44,6 +44,7 @@ Routes: Rutas
Locator: Localizador
Invoices out: Facturas emitidas
Entries: Entradas
Users: Usuarios
# Common

View File

@ -134,5 +134,14 @@
"This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos",
"This thermograph id already exists": "La id del termógrafo ya existe",
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante"
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante",
"ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED",
"Invalid password": "Invalid password",
"Password does not meet requirements": "Password does not meet requirements",
"Role already assigned": "Role already assigned",
"Invalid role name": "Invalid role name",
"Role name must be written in camelCase": "Role name must be written in camelCase",
"can't be set": "can't be set",
"Email already exists": "Email already exists",
"User already exists": "User already exists"
}

View File

@ -0,0 +1,114 @@
const ldap = require('../../util/ldapjs-extra');
module.exports = Self => {
Self.remoteMethod('sync', {
description: 'Synchronizes the user with the other user databases',
http: {
path: `/sync`,
verb: 'PATCH'
}
});
Self.sync = async function() {
let $ = Self.app.models;
let ldapConfig = await $.LdapConfig.findOne({
fields: ['host', 'rdn', 'password', 'groupDn']
});
let accountConfig = await $.AccountConfig.findOne({
fields: ['idBase']
});
if (!ldapConfig) return;
// Connect
let client = ldap.createClient({
url: `ldap://${ldapConfig.host}:389`
});
let ldapPassword = Buffer
.from(ldapConfig.password, 'base64')
.toString('ascii');
await client.bind(ldapConfig.rdn, ldapPassword);
let err;
try {
// Delete roles
let opts = {
scope: 'sub',
attributes: ['dn'],
filter: 'objectClass=posixGroup'
};
res = await client.search(ldapConfig.groupDn, opts);
let reqs = [];
await new Promise((resolve, reject) => {
res.on('error', err => {
if (err.name === 'NoSuchObjectError')
err = new Error(`Object '${ldapConfig.groupDn}' does not exist`);
reject(err);
});
res.on('searchEntry', e => {
reqs.push(client.del(e.object.dn));
});
res.on('end', resolve);
});
await Promise.all(reqs);
// Recreate roles
let roles = await $.Role.find({
fields: ['id', 'name']
});
let accounts = await $.UserAccount.find({
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name'],
include: {
relation: 'roles',
scope: {
fields: ['inheritsFrom']
}
}
}
}
});
let map = new Map();
for (let account of accounts) {
let user = account.user();
for (let inherit of user.roles()) {
let roleId = inherit.inheritsFrom;
if (!map.has(roleId)) map.set(roleId, []);
map.get(roleId).push(user.name);
}
}
reqs = [];
for (let role of roles) {
let newEntry = {
objectClass: ['top', 'posixGroup'],
cn: role.name,
gidNumber: accountConfig.idBase + role.id
};
let memberUid = map.get(role.id);
if (memberUid) newEntry.memberUid = memberUid;
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
reqs.push(client.add(dn, newEntry));
}
await Promise.all(reqs);
} catch (e) {
err = e;
}
// FIXME: Cannot disconnect, hangs on undind() call
// await client.unbind();
if (err) throw err;
};
};

View File

@ -0,0 +1,27 @@
module.exports = Self => {
Self.remoteMethod('syncById', {
description: 'Synchronizes the user with the other user databases',
accepts: [
{
arg: 'id',
type: 'number',
description: 'The user id',
required: true
}, {
arg: 'password',
type: 'string',
description: 'The password'
}
],
http: {
path: `/:id/syncById`,
verb: 'PATCH'
}
});
Self.syncById = async function(id, password) {
let user = await Self.app.models.Account.findById(id, {fields: ['name']});
await Self.sync(user.name, password);
};
};

View File

@ -0,0 +1,267 @@
const ldap = require('../../util/ldapjs-extra');
const nthash = require('smbhash').nthash;
const ssh = require('node-ssh');
const crypto = require('crypto');
module.exports = Self => {
Self.remoteMethod('sync', {
description: 'Synchronizes the user with the other user databases',
accepts: [
{
arg: 'userName',
type: 'string',
description: 'The user name',
required: true
}, {
arg: 'password',
type: 'string',
description: 'The password'
}
],
http: {
path: `/sync`,
verb: 'PATCH'
}
});
Self.sync = async function(userName, password) {
let $ = Self.app.models;
let user = await $.Account.findOne({
fields: ['id'],
where: {name: userName}
});
let isSync = !await $.UserSync.exists(userName);
if (user && isSync) return;
let accountConfig;
let mailConfig;
let extraParams;
let hasAccount = false;
if (user) {
accountConfig = await $.AccountConfig.findOne({
fields: ['homedir', 'shell', 'idBase']
});
mailConfig = await $.MailConfig.findOne({
fields: ['domain']
});
user = await $.Account.findById(user.id, {
fields: [
'id',
'nickname',
'email',
'lang',
'roleFk',
'sync',
'active',
'created',
'updated'
],
where: {name: userName},
include: {
relation: 'roles',
scope: {
include: {
relation: 'inherits',
scope: {
fields: ['name']
}
}
}
}
});
extraParams = {
corporateMail: `${userName}@${mailConfig.domain}`,
uidNumber: accountConfig.idBase + user.id
};
hasAccount = user.active
&& await $.UserAccount.exists(user.id);
}
if (user) {
let bcryptPassword = $.User.hashPassword(password);
await $.Account.upsertWithWhere({id: user.id},
{bcryptPassword}
);
await $.user.upsert({
id: user.id,
username: userName,
password: bcryptPassword,
email: user.email,
created: user.created,
updated: user.updated
});
}
// SIP
if (hasAccount) {
await Self.rawSql('CALL pbx.sip_setPassword(?, ?)',
[user.id, password]
);
}
// LDAP
let ldapConfig = await $.LdapConfig.findOne({
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
});
if (ldapConfig) {
let ldapClient = ldap.createClient({
url: `ldap://${ldapConfig.host}:389`
});
let ldapPassword = Buffer
.from(ldapConfig.password, 'base64')
.toString('ascii');
await ldapClient.bind(ldapConfig.rdn, ldapPassword);
let err;
try {
// Deletes user
try {
let dn = `uid=${userName},${ldapConfig.baseDn}`;
await ldapClient.del(dn);
} catch (e) {
if (e.name !== 'NoSuchObjectError') throw e;
}
// Removes user from groups
let opts = {
scope: 'sub',
attributes: ['dn'],
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
};
res = await ldapClient.search(ldapConfig.groupDn, opts);
let oldGroups = [];
await new Promise((resolve, reject) => {
res.on('error', reject);
res.on('searchEntry', e => oldGroups.push(e.object));
res.on('end', resolve);
});
let reqs = [];
for (oldGroup of oldGroups) {
let change = new ldap.Change({
operation: 'delete',
modification: {memberUid: userName}
});
reqs.push(ldapClient.modify(oldGroup.dn, change));
}
await Promise.all(reqs);
if (hasAccount) {
// Recreates user
let nameArgs = user.nickname.split(' ');
let sshaPassword = crypto
.createHash('sha1')
.update(password)
.digest('base64');
let dn = `uid=${userName},${ldapConfig.baseDn}`;
let newEntry = {
uid: userName,
objectClass: [
'inetOrgPerson',
'posixAccount',
'sambaSamAccount'
],
cn: user.nickname || userName,
displayName: user.nickname,
givenName: nameArgs[0],
sn: nameArgs[1] || 'Empty',
mail: extraParams.corporateMail,
userPassword: `{SSHA}${sshaPassword}`,
preferredLanguage: user.lang,
homeDirectory: `${accountConfig.homedir}/${userName}`,
loginShell: accountConfig.shell,
uidNumber: extraParams.uidNumber,
gidNumber: accountConfig.idBase + user.roleFk,
sambaSID: '-',
sambaNTPassword: nthash(password)
};
await ldapClient.add(dn, newEntry);
// Adds user to groups
let reqs = [];
for (let role of user.roles()) {
let change = new ldap.Change({
operation: 'add',
modification: {memberUid: userName}
});
let roleName = role.inherits().name;
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
reqs.push(ldapClient.modify(dn, change));
}
await Promise.all(reqs);
}
} catch (e) {
err = e;
}
// FIXME: Cannot disconnect, hangs on undind() call
// await ldapClient.unbind();
if (err) throw err;
}
// Samba
let sambaConfig = await $.SambaConfig.findOne({
fields: ['host', 'sshUser', 'sshPass']
});
if (sambaConfig) {
let sshPassword = Buffer
.from(sambaConfig.sshPass, 'base64')
.toString('ascii');
let sshClient = new ssh.NodeSSH();
await sshClient.connect({
host: sambaConfig.host,
username: sambaConfig.sshUser,
password: sshPassword
});
let commands;
if (hasAccount) {
commands = [
`samba-tool user create "${userName}" `
+ `--uid-number=${extraParams.uidNumber} `
+ `--mail-address="${extraParams.corporateMail}" `
+ `--random-password`,
`samba-tool user setexpiry "${userName}" `
+ `--noexpiry`,
`samba-tool user setpassword "${userName}" `
+ `--newpassword="${password}"`,
`mkhomedir_helper "${userName}" 0027`
];
} else {
commands = [
`samba-tool user delete "${userName}"`
];
}
for (let command of commands)
await sshClient.execCommand(command);
await sshClient.dispose();
}
// Mark as synchronized
await $.UserSync.destroyById(userName);
};
};

View File

@ -0,0 +1,38 @@
{
"AccountConfig": {
"dataSource": "vn"
},
"LdapConfig": {
"dataSource": "vn"
},
"MailAlias": {
"dataSource": "vn"
},
"MailAliasAccount": {
"dataSource": "vn"
},
"MailConfig": {
"dataSource": "vn"
},
"MailForward": {
"dataSource": "vn"
},
"RoleInherit": {
"dataSource": "vn"
},
"RoleRole": {
"dataSource": "vn"
},
"SambaConfig": {
"dataSource": "vn"
},
"UserAccount": {
"dataSource": "vn"
},
"UserPassword": {
"dataSource": "vn"
},
"UserSync": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,43 @@
{
"name": "AccountConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.accountConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"homedir": {
"type": "string",
"required": true
},
"shell": {
"type": "string",
"required": true
},
"idBase": {
"type": "number",
"required": true
},
"min": {
"type": "number",
"required": true
},
"max": {
"type": "number",
"required": true
},
"warn": {
"type": "number",
"required": true
},
"inact": {
"type": "number",
"required": true
}
}
}

View File

@ -0,0 +1,36 @@
{
"name": "LdapConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.ldapConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"host": {
"type": "string",
"required": true
},
"rdn": {
"type": "string",
"required": true
},
"password": {
"type": "string",
"required": true
},
"baseDn": {
"type": "string"
},
"filter": {
"type": "string"
},
"groupDn": {
"type": "string"
}
}
}

View File

@ -0,0 +1,27 @@
{
"name": "MailAliasAccount",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.mailAliasAccount"
}
},
"properties": {
"id": {
"type": "number",
"id": true
}
},
"relations": {
"alias": {
"type": "belongsTo",
"model": "MailAlias",
"foreignKey": "mailAlias"
},
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "account"
}
}
}

View File

@ -0,0 +1,33 @@
{
"name": "MailAlias",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.mailAlias"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"alias": {
"type": "string",
"required": true
},
"description": {
"type": "string"
},
"isPublic": {
"type": "boolean"
}
},
"relations": {
"accounts": {
"type": "hasMany",
"model": "MailAliasAccount",
"foreignKey": "mailAlias",
"property": "id"
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "MailConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.mailConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"domain": {
"type": "string",
"required": true
}
}
}

View File

@ -0,0 +1,25 @@
{
"name": "MailForward",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.mailForward"
}
},
"properties": {
"account": {
"id": true
},
"forwardTo": {
"type": "string",
"required": true
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "account"
}
}
}

Some files were not shown because too many files have changed in this diff Show More