Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2568-ticket_basic_data_e2e_fix
This commit is contained in:
commit
01ccef4786
|
@ -2,15 +2,11 @@ const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('campaign upcoming()', () => {
|
describe('campaign upcoming()', () => {
|
||||||
it('should return the upcoming campaign but from the last year', async() => {
|
it('should return the upcoming campaign but from the last year', async() => {
|
||||||
let response = await app.models.Campaign.upcoming();
|
const response = await app.models.Campaign.upcoming();
|
||||||
|
|
||||||
const lastYearDate = new Date();
|
|
||||||
lastYearDate.setFullYear(lastYearDate.getFullYear() - 1);
|
|
||||||
const lastYear = lastYearDate.getFullYear();
|
|
||||||
|
|
||||||
const campaignDated = response.dated;
|
const campaignDated = response.dated;
|
||||||
const campaignYear = campaignDated.getFullYear();
|
const now = new Date();
|
||||||
|
|
||||||
expect(campaignYear).toEqual(lastYear);
|
expect(campaignDated).toEqual(jasmine.any(Date));
|
||||||
|
expect(campaignDated).toBeLessThanOrEqual(now);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,6 +56,9 @@
|
||||||
"Sip": {
|
"Sip": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"SageWithholding": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"UserConfigView": {
|
"UserConfigView": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "SageWithholding",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "sage.TiposRetencion"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "Number",
|
||||||
|
"id": true,
|
||||||
|
"description": "Identifier",
|
||||||
|
"mysql": {
|
||||||
|
"columnName": "CodigoRetencion"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"withholding": {
|
||||||
|
"type": "string",
|
||||||
|
"mysql": {
|
||||||
|
"columnName": "Retencion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,503 @@
|
||||||
|
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, '%');
|
||||||
|
|
||||||
|
IF vIsMysql THEN
|
||||||
|
DELETE FROM mysql.user
|
||||||
|
WHERE `User` LIKE vPrefixedLike;
|
||||||
|
ELSE
|
||||||
|
DELETE FROM mysql.global_priv
|
||||||
|
WHERE `User` LIKE vPrefixedLike;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
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`, '{}'),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'mysql_old_password', JSON_VALUE(t.`Priv`, '$.mysql_old_password'),
|
||||||
|
'mysql_native_password', JSON_VALUE(t.`Priv`, '$.mysql_native_password'),
|
||||||
|
'authentication_string', JSON_VALUE(t.`Priv`, '$.authentication_string')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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 ;
|
|
@ -0,0 +1,10 @@
|
||||||
|
ALTER TABLE account.sambaConfig ADD adUser VARCHAR(255) DEFAULT NULL NULL COMMENT 'Active directory user';
|
||||||
|
ALTER TABLE account.sambaConfig ADD adPassword varchar(255) DEFAULT NULL NULL COMMENT 'Active directory password';
|
||||||
|
ALTER TABLE account.sambaConfig ADD userDn varchar(255) DEFAULT NULL NULL COMMENT 'The base DN for users';
|
||||||
|
ALTER TABLE account.sambaConfig DROP COLUMN uidBase;
|
||||||
|
ALTER TABLE account.sambaConfig CHANGE sshPass sshPassword varchar(255) DEFAULT NULL NULL COMMENT 'The SSH password';
|
||||||
|
|
||||||
|
ALTER TABLE account.ldapConfig DROP COLUMN `filter`;
|
||||||
|
ALTER TABLE account.ldapConfig CHANGE baseDn userDn varchar(255) DEFAULT NULL NULL COMMENT 'The base DN to do the query';
|
||||||
|
ALTER TABLE account.ldapConfig CHANGE host server varchar(255) NOT NULL COMMENT 'The hostname of LDAP server';
|
||||||
|
ALTER TABLE account.ldapConfig MODIFY COLUMN password varchar(255) NOT NULL COMMENT 'The LDAP password';
|
|
@ -0,0 +1,3 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Supplier', 'updateFiscalData', 'WRITE', 'ALLOW', 'ROLE', 'administrative');
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Supplier', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('SupplierLog', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
|
|
@ -604,6 +604,24 @@ INSERT INTO `TiposTransacciones` VALUES (1,'Rég.general/Oper.interiores bienes
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `TiposRetencion`
|
||||||
|
--
|
||||||
|
|
||||||
|
LOCK TABLES `TiposRetencion` WRITE;
|
||||||
|
/*!40000 ALTER TABLE `TiposRetencion` DISABLE KEYS */;
|
||||||
|
INSERT INTO `TiposRetencion` (`CodigoRetencion`, `Retencion`, `PorcentajeRetencion`, `CuentaCargo`, `CuentaAbono`, `ClaveIrpf`, `CuentaCargoANT_`, `CuentaAbonoANT_`, `IdTipoRetencion`) VALUES
|
||||||
|
(1, 'RETENCION ESTIMACION OBJETIVA', '1.0000000000', '4730000000', '4751000000', NULL, NULL, NULL, '03811652-0F3A-44A1-AE1C-B19624525D7F'),
|
||||||
|
(2, 'ACTIVIDADES AGRICOLAS O GANADERAS', '2.0000000000', '4730000000', '4751000000', NULL, NULL, NULL, 'F3F91EF3-FED6-444D-B03C-75B639D13FB4'),
|
||||||
|
(9, 'ACTIVIDADES PROFESIONALES 2 PRIMEROS AÑOS', '9.0000000000', '4730000000', '4751000000', NULL, NULL, NULL, '73F95642-E951-4C91-970A-60C503A4792B'),
|
||||||
|
(15, 'ACTIVIDADES PROFESIONALES', '15.0000000000', '4730000000', '4751000000', '6', NULL, NULL, 'F6BDE0EE-3B01-4023-8FFF-A73AE9AC50D7'),
|
||||||
|
(19, 'ARRENDAMIENTO Y SUBARRENDAMIENTO', '19.0000000000', '4730000000', '4751000000', '8', NULL, NULL, '09B033AE-16E5-4057-8D4A-A7710C8A4FB9');
|
||||||
|
/*!40000 ALTER TABLE `TiposRetencion` ENABLE KEYS */;
|
||||||
|
UNLOCK TABLES;
|
||||||
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
|
|
@ -1211,11 +1211,11 @@ INSERT INTO `vn`.`annualAverageInvoiced`(`clientFk`, `invoiced`)
|
||||||
(104, 500),
|
(104, 500),
|
||||||
(105, 5000);
|
(105, 5000);
|
||||||
|
|
||||||
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `transactionTypeSageFk`)
|
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, NULL, NULL),
|
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1),
|
||||||
(2, 'Farmer King', 'The farmer', 4000020002, 1, 'B22222222', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 8),
|
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8),
|
||||||
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, 'C33333333', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, NULL, NULL);
|
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3);
|
||||||
|
|
||||||
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
|
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -923,5 +923,20 @@ export default {
|
||||||
thirdContactNotes: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.observation"]',
|
thirdContactNotes: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.observation"]',
|
||||||
saveButton: 'vn-supplier-contact button[type="submit"]',
|
saveButton: 'vn-supplier-contact button[type="submit"]',
|
||||||
thirdContactDeleteButton: 'vn-supplier-contact div:nth-child(3) vn-icon-button[icon="delete"]'
|
thirdContactDeleteButton: 'vn-supplier-contact div:nth-child(3) vn-icon-button[icon="delete"]'
|
||||||
|
},
|
||||||
|
supplierBasicData: {
|
||||||
|
|
||||||
|
},
|
||||||
|
supplierFiscalData: {
|
||||||
|
socialName: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.name"]',
|
||||||
|
taxNumber: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.nif"]',
|
||||||
|
account: 'vn-supplier-fiscal-data vn-textfield[ng-model="$ctrl.supplier.account"]',
|
||||||
|
sageTaxType: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.sageTaxTypeFk"]',
|
||||||
|
sageWihholding: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.sageWithholdingFk"]',
|
||||||
|
postCode: 'vn-supplier-fiscal-data vn-datalist[ng-model="$ctrl.supplier.postCode"]',
|
||||||
|
city: 'vn-supplier-fiscal-data vn-datalist[ng-model="$ctrl.supplier.city"]',
|
||||||
|
province: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.provinceFk"]',
|
||||||
|
country: 'vn-supplier-fiscal-data vn-autocomplete[ng-model="$ctrl.supplier.countryFk"]',
|
||||||
|
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,6 +57,8 @@ describe('Client lock verified data path', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check the Verified data checkbox', async() => {
|
it('should check the Verified data checkbox', async() => {
|
||||||
|
await page.autocompleteSearch(selectors.clientFiscalData.sageTax, 'operaciones no sujetas');
|
||||||
|
await page.autocompleteSearch(selectors.clientFiscalData.sageTransaction, 'regularización de inversiones');
|
||||||
await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox);
|
await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox);
|
||||||
await page.waitToClick(selectors.clientFiscalData.saveButton);
|
await page.waitToClick(selectors.clientFiscalData.saveButton);
|
||||||
await page.waitToClick(selectors.globalItems.acceptButton);
|
await page.waitToClick(selectors.globalItems.acceptButton);
|
||||||
|
|
|
@ -79,6 +79,6 @@ describe('Supplier summary & descriptor path', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should check the client button isn't present since this supplier should not be a client`, async() => {
|
it(`should check the client button isn't present since this supplier should not be a client`, async() => {
|
||||||
await page.waitForSelector(selectors.supplierDescriptor.clientButton, {hidden: true});
|
await page.waitForSelector(selectors.supplierDescriptor.clientButton, {visible: false});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
import selectors from '../../helpers/selectors.js';
|
||||||
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
|
describe('Supplier fiscal data path', () => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
await page.loginAndModule('administrative', 'supplier');
|
||||||
|
await page.accessToSearchResult('2');
|
||||||
|
await page.accessToSection('supplier.card.fiscalData');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attempt to edit the fiscal data but fail as the tax number is invalid', async() => {
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.city);
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.province);
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.country);
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.postCode);
|
||||||
|
await page.write(selectors.supplierFiscalData.city, 'Valencia');
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.socialName);
|
||||||
|
await page.write(selectors.supplierFiscalData.socialName, 'Farmer King SL');
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.taxNumber);
|
||||||
|
await page.write(selectors.supplierFiscalData.taxNumber, 'invalid tax number');
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.account);
|
||||||
|
await page.write(selectors.supplierFiscalData.account, 'edited account number');
|
||||||
|
await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva');
|
||||||
|
await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas');
|
||||||
|
|
||||||
|
await page.waitToClick(selectors.supplierFiscalData.saveButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toBe('Invalid Tax number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save the changes as the tax number is valid this time', async() => {
|
||||||
|
await page.clearInput(selectors.supplierFiscalData.taxNumber);
|
||||||
|
await page.write(selectors.supplierFiscalData.taxNumber, '12345678Z');
|
||||||
|
|
||||||
|
await page.waitToClick(selectors.supplierFiscalData.saveButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toBe('Data saved!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reload the section', async() => {
|
||||||
|
await page.reloadSection('supplier.card.fiscalData');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the socialName was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.socialName, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('Farmer King SL');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the taxNumber was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.taxNumber, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('12345678Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the account was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('edited account number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the sageWihholding was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.sageWihholding, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('RETENCION ESTIMACION OBJETIVA');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the sageTaxType was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.sageTaxType, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('Operaciones no sujetas');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the postCode was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.postCode, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('46000');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the city was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.city, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('Valencia');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the province was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.province, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('Province one (España)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check the country was edited', async() => {
|
||||||
|
const result = await page.waitToGetProperty(selectors.supplierFiscalData.country, 'value');
|
||||||
|
|
||||||
|
expect(result).toEqual('España');
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,6 +21,7 @@ let buildDir = 'dist';
|
||||||
|
|
||||||
let backSources = [
|
let backSources = [
|
||||||
'!node_modules',
|
'!node_modules',
|
||||||
|
'!loopback/locale/*.json',
|
||||||
'loopback',
|
'loopback',
|
||||||
'modules/*/back/**',
|
'modules/*/back/**',
|
||||||
'modules/*/back/*',
|
'modules/*/back/*',
|
||||||
|
|
|
@ -81,5 +81,8 @@
|
||||||
"shipped": "Shipped",
|
"shipped": "Shipped",
|
||||||
"landed": "Landed",
|
"landed": "Landed",
|
||||||
"addressFk": "Address",
|
"addressFk": "Address",
|
||||||
"companyFk": "Company"
|
"companyFk": "Company",
|
||||||
|
"You need to fill sage information before you check verified data": "You need to fill sage information before you check verified data",
|
||||||
|
"The social name cannot be empty": "The social name cannot be empty",
|
||||||
|
"The nif cannot be empty": "The nif cannot be empty"
|
||||||
}
|
}
|
|
@ -156,5 +156,8 @@
|
||||||
"shipped": "F. envío",
|
"shipped": "F. envío",
|
||||||
"landed": "F. entrega",
|
"landed": "F. entrega",
|
||||||
"addressFk": "Consignatario",
|
"addressFk": "Consignatario",
|
||||||
"companyFk": "Empresa"
|
"companyFk": "Empresa",
|
||||||
|
"The social name cannot be empty": "La razón social no puede quedar en blanco",
|
||||||
|
"The nif cannot be empty": "El NIF no puede quedar en blanco",
|
||||||
|
"You need to fill sage information before you check verified data": "Debes rellenar la información de sage antes de marcar datos comprobados"
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
const ldap = require('../../util/ldapjs-extra');
|
|
||||||
|
const SyncEngine = require('../../util/sync-engine');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('sync', {
|
Self.remoteMethod('sync', {
|
||||||
|
@ -10,119 +11,17 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.sync = async function() {
|
Self.sync = async function() {
|
||||||
let $ = Self.app.models;
|
let engine = new SyncEngine();
|
||||||
|
await engine.init(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;
|
let err;
|
||||||
try {
|
try {
|
||||||
// Delete roles
|
await engine.syncRoles();
|
||||||
|
|
||||||
let opts = {
|
|
||||||
scope: 'sub',
|
|
||||||
attributes: ['dn'],
|
|
||||||
filter: 'objectClass=posixGroup'
|
|
||||||
};
|
|
||||||
let 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 roleRoles = await $.RoleRole.find({
|
|
||||||
fields: ['role', 'inheritsFrom']
|
|
||||||
});
|
|
||||||
let roleMap = toMap(roleRoles, e => {
|
|
||||||
return {key: e.inheritsFrom, val: e.role};
|
|
||||||
});
|
|
||||||
|
|
||||||
let accounts = await $.UserAccount.find({
|
|
||||||
fields: ['id'],
|
|
||||||
include: {
|
|
||||||
relation: 'user',
|
|
||||||
scope: {
|
|
||||||
fields: ['name', 'roleFk'],
|
|
||||||
where: {active: true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let accountMap = toMap(accounts, e => {
|
|
||||||
let user = e.user();
|
|
||||||
if (!user) return;
|
|
||||||
return {key: user.roleFk, val: user.name};
|
|
||||||
});
|
|
||||||
|
|
||||||
reqs = [];
|
|
||||||
for (let role of roles) {
|
|
||||||
let newEntry = {
|
|
||||||
objectClass: ['top', 'posixGroup'],
|
|
||||||
cn: role.name,
|
|
||||||
gidNumber: accountConfig.idBase + role.id
|
|
||||||
};
|
|
||||||
|
|
||||||
let memberUid = [];
|
|
||||||
for (subrole of roleMap.get(role.id) || [])
|
|
||||||
memberUid = memberUid.concat(accountMap.get(subrole) || []);
|
|
||||||
|
|
||||||
if (memberUid.length) {
|
|
||||||
memberUid.sort((a, b) => a.localeCompare(b));
|
|
||||||
newEntry.memberUid = memberUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
|
|
||||||
reqs.push(client.add(dn, newEntry));
|
|
||||||
}
|
|
||||||
await Promise.all(reqs);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.unbind();
|
await engine.deinit();
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function toMap(array, fn) {
|
|
||||||
let map = new Map();
|
|
||||||
for (let item of array) {
|
|
||||||
let keyVal = fn(item);
|
|
||||||
if (!keyVal) continue;
|
|
||||||
let key = keyVal.key;
|
|
||||||
if (!map.has(key)) map.set(key, []);
|
|
||||||
map.get(key).push(keyVal.val);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,25 +13,24 @@ module.exports = Self => {
|
||||||
Self.syncAll = async function() {
|
Self.syncAll = async function() {
|
||||||
let $ = Self.app.models;
|
let $ = Self.app.models;
|
||||||
|
|
||||||
let se = new SyncEngine();
|
let engine = new SyncEngine();
|
||||||
await se.init($);
|
await engine.init($);
|
||||||
|
|
||||||
let usersToSync = await se.getUsers();
|
let usersToSync = await engine.getUsers();
|
||||||
usersToSync = Array.from(usersToSync.values())
|
usersToSync = Array.from(usersToSync.values())
|
||||||
.sort((a, b) => a.localeCompare(b));
|
.sort((a, b) => a.localeCompare(b));
|
||||||
|
|
||||||
for (let user of usersToSync) {
|
for (let user of usersToSync) {
|
||||||
try {
|
try {
|
||||||
console.log(`Synchronizing user '${user}'`);
|
console.log(`Synchronizing user '${user}'`);
|
||||||
await se.sync(user);
|
await engine.sync(user);
|
||||||
console.log(` -> '${user}' sinchronized`);
|
console.log(` -> '${user}' sinchronized`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(` -> '${user}' synchronization error:`, err.message);
|
console.error(` -> '${user}' synchronization error:`, err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await se.deinit();
|
await engine.deinit();
|
||||||
|
|
||||||
await $.RoleInherit.sync();
|
await $.RoleInherit.sync();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,17 +34,17 @@ module.exports = Self => {
|
||||||
if (user && isSync) return;
|
if (user && isSync) return;
|
||||||
|
|
||||||
let err;
|
let err;
|
||||||
let se = new SyncEngine();
|
let engine = new SyncEngine();
|
||||||
await se.init($);
|
await engine.init($);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await se.sync(userName, password, true);
|
await engine.sync(userName, password, true);
|
||||||
await $.UserSync.destroyById(userName);
|
await $.UserSync.destroyById(userName);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
await se.deinit();
|
await engine.deinit();
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"id": true
|
"id": true
|
||||||
},
|
},
|
||||||
"host": {
|
"server": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
@ -23,10 +23,7 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"baseDn": {
|
"userDn": {
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"filter": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"groupDn": {
|
"groupDn": {
|
||||||
|
|
|
@ -18,7 +18,16 @@
|
||||||
"sshUser": {
|
"sshUser": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"sshPass": {
|
"sshPassword": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"adUser": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"adPassword": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"userDn": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,11 @@ class SyncConnector {
|
||||||
*/
|
*/
|
||||||
async syncGroups(user, userName) {}
|
async syncGroups(user, userName) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes roles.
|
||||||
|
*/
|
||||||
|
async syncRoles() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deinitalizes the connector.
|
* Deinitalizes the connector.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,8 +19,10 @@ module.exports = class SyncEngine {
|
||||||
for (let ConnectorClass of SyncConnector.connectors) {
|
for (let ConnectorClass of SyncConnector.connectors) {
|
||||||
let connector = new ConnectorClass();
|
let connector = new ConnectorClass();
|
||||||
Object.assign(connector, {
|
Object.assign(connector, {
|
||||||
se: this,
|
engine: this,
|
||||||
$
|
$,
|
||||||
|
accountConfig,
|
||||||
|
mailConfig
|
||||||
});
|
});
|
||||||
if (!await connector.init()) continue;
|
if (!await connector.init()) continue;
|
||||||
connectors.push(connector);
|
connectors.push(connector);
|
||||||
|
@ -80,27 +82,20 @@ module.exports = class SyncEngine {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let extraParams;
|
|
||||||
let hasAccount = false;
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
hasAccount = user.active
|
|
||||||
&& await $.UserAccount.exists(user.id);
|
|
||||||
|
|
||||||
extraParams = {
|
|
||||||
corporateMail: `${userName}@${mailConfig.domain}`,
|
|
||||||
uidNumber: accountConfig.idBase + user.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = {
|
let info = {
|
||||||
user,
|
user,
|
||||||
extraParams,
|
hasAccount: false
|
||||||
hasAccount,
|
|
||||||
accountConfig,
|
|
||||||
mailConfig
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
let exists = await $.UserAccount.exists(user.id);
|
||||||
|
Object.assign(info, {
|
||||||
|
hasAccount: user.active && exists,
|
||||||
|
corporateMail: `${userName}@${mailConfig.domain}`,
|
||||||
|
uidNumber: accountConfig.idBase + user.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let errs = [];
|
let errs = [];
|
||||||
|
|
||||||
for (let connector of this.connectors) {
|
for (let connector of this.connectors) {
|
||||||
|
@ -116,6 +111,11 @@ module.exports = class SyncEngine {
|
||||||
if (errs.length) throw errs[0];
|
if (errs.length) throw errs[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncRoles() {
|
||||||
|
for (let connector of this.connectors)
|
||||||
|
await connector.syncRoles();
|
||||||
|
}
|
||||||
|
|
||||||
async getUsers() {
|
async getUsers() {
|
||||||
let usersToSync = new Set();
|
let usersToSync = new Set();
|
||||||
|
|
||||||
|
|
|
@ -7,18 +7,20 @@ const crypto = require('crypto');
|
||||||
class SyncLdap extends SyncConnector {
|
class SyncLdap extends SyncConnector {
|
||||||
async init() {
|
async init() {
|
||||||
let ldapConfig = await this.$.LdapConfig.findOne({
|
let ldapConfig = await this.$.LdapConfig.findOne({
|
||||||
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
fields: [
|
||||||
|
'server',
|
||||||
|
'rdn',
|
||||||
|
'password',
|
||||||
|
'userDn',
|
||||||
|
'groupDn'
|
||||||
|
]
|
||||||
});
|
});
|
||||||
if (!ldapConfig) return false;
|
if (!ldapConfig) return false;
|
||||||
|
|
||||||
let client = ldap.createClient({
|
let client = ldap.createClient({
|
||||||
url: `ldap://${ldapConfig.host}:389`
|
url: ldapConfig.server
|
||||||
});
|
});
|
||||||
|
await client.bind(ldapConfig.rdn, ldapConfig.password);
|
||||||
let ldapPassword = Buffer
|
|
||||||
.from(ldapConfig.password, 'base64')
|
|
||||||
.toString('ascii');
|
|
||||||
await client.bind(ldapConfig.rdn, ldapPassword);
|
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
ldapConfig,
|
ldapConfig,
|
||||||
|
@ -36,16 +38,12 @@ class SyncLdap extends SyncConnector {
|
||||||
let {
|
let {
|
||||||
ldapConfig,
|
ldapConfig,
|
||||||
client,
|
client,
|
||||||
|
accountConfig
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
let {
|
let {user} = info;
|
||||||
user,
|
|
||||||
hasAccount,
|
|
||||||
extraParams,
|
|
||||||
accountConfig
|
|
||||||
} = info;
|
|
||||||
|
|
||||||
let res = await client.search(ldapConfig.baseDn, {
|
let res = await client.search(ldapConfig.userDn, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['userPassword', 'sambaNTPassword'],
|
attributes: ['userPassword', 'sambaNTPassword'],
|
||||||
filter: `&(uid=${userName})`
|
filter: `&(uid=${userName})`
|
||||||
|
@ -59,13 +57,13 @@ class SyncLdap extends SyncConnector {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
let dn = `uid=${userName},${ldapConfig.userDn}`;
|
||||||
await client.del(dn);
|
await client.del(dn);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name !== 'NoSuchObjectError') throw e;
|
if (e.name !== 'NoSuchObjectError') throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasAccount) {
|
if (!info.hasAccount) {
|
||||||
if (oldUser)
|
if (oldUser)
|
||||||
console.log(` -> '${userName}' removed from LDAP`);
|
console.log(` -> '${userName}' removed from LDAP`);
|
||||||
return;
|
return;
|
||||||
|
@ -77,7 +75,7 @@ class SyncLdap extends SyncConnector {
|
||||||
? nameArgs.splice(1).join(' ')
|
? nameArgs.splice(1).join(' ')
|
||||||
: '-';
|
: '-';
|
||||||
|
|
||||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
let dn = `uid=${userName},${ldapConfig.userDn}`;
|
||||||
let newEntry = {
|
let newEntry = {
|
||||||
uid: userName,
|
uid: userName,
|
||||||
objectClass: [
|
objectClass: [
|
||||||
|
@ -89,11 +87,11 @@ class SyncLdap extends SyncConnector {
|
||||||
displayName: nickname,
|
displayName: nickname,
|
||||||
givenName: nameArgs[0],
|
givenName: nameArgs[0],
|
||||||
sn,
|
sn,
|
||||||
mail: extraParams.corporateMail,
|
mail: info.corporateMail,
|
||||||
preferredLanguage: user.lang,
|
preferredLanguage: user.lang,
|
||||||
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
||||||
loginShell: accountConfig.shell,
|
loginShell: accountConfig.shell,
|
||||||
uidNumber: extraParams.uidNumber,
|
uidNumber: info.uidNumber,
|
||||||
gidNumber: accountConfig.idBase + user.roleFk,
|
gidNumber: accountConfig.idBase + user.roleFk,
|
||||||
sambaSID: '-'
|
sambaSID: '-'
|
||||||
};
|
};
|
||||||
|
@ -137,11 +135,6 @@ class SyncLdap extends SyncConnector {
|
||||||
client
|
client
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
let {
|
|
||||||
user,
|
|
||||||
hasAccount
|
|
||||||
} = info;
|
|
||||||
|
|
||||||
let res = await client.search(ldapConfig.groupDn, {
|
let res = await client.search(ldapConfig.groupDn, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['dn'],
|
attributes: ['dn'],
|
||||||
|
@ -165,10 +158,10 @@ class SyncLdap extends SyncConnector {
|
||||||
}
|
}
|
||||||
await Promise.all(reqs);
|
await Promise.all(reqs);
|
||||||
|
|
||||||
if (!hasAccount) return;
|
if (!info.hasAccount) return;
|
||||||
|
|
||||||
reqs = [];
|
reqs = [];
|
||||||
for (let role of user.roles()) {
|
for (let role of info.user.roles()) {
|
||||||
let change = new ldap.Change({
|
let change = new ldap.Change({
|
||||||
operation: 'add',
|
operation: 'add',
|
||||||
modification: {memberUid: userName}
|
modification: {memberUid: userName}
|
||||||
|
@ -186,7 +179,7 @@ class SyncLdap extends SyncConnector {
|
||||||
client
|
client
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
let res = await client.search(ldapConfig.baseDn, {
|
let res = await client.search(ldapConfig.userDn, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['uid'],
|
attributes: ['uid'],
|
||||||
filter: `uid=*`
|
filter: `uid=*`
|
||||||
|
@ -198,7 +191,103 @@ class SyncLdap extends SyncConnector {
|
||||||
res.on('end', resolve);
|
res.on('end', resolve);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncRoles() {
|
||||||
|
let {
|
||||||
|
$,
|
||||||
|
ldapConfig,
|
||||||
|
client,
|
||||||
|
accountConfig
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
// Delete roles
|
||||||
|
|
||||||
|
let opts = {
|
||||||
|
scope: 'sub',
|
||||||
|
attributes: ['dn'],
|
||||||
|
filter: 'objectClass=posixGroup'
|
||||||
|
};
|
||||||
|
let 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 roleRoles = await $.RoleRole.find({
|
||||||
|
fields: ['role', 'inheritsFrom']
|
||||||
|
});
|
||||||
|
let roleMap = toMap(roleRoles, e => {
|
||||||
|
return {key: e.inheritsFrom, val: e.role};
|
||||||
|
});
|
||||||
|
|
||||||
|
let accounts = await $.UserAccount.find({
|
||||||
|
fields: ['id'],
|
||||||
|
include: {
|
||||||
|
relation: 'user',
|
||||||
|
scope: {
|
||||||
|
fields: ['name', 'roleFk'],
|
||||||
|
where: {active: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let accountMap = toMap(accounts, e => {
|
||||||
|
let user = e.user();
|
||||||
|
if (!user) return;
|
||||||
|
return {key: user.roleFk, val: user.name};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log;
|
||||||
|
|
||||||
|
reqs = [];
|
||||||
|
for (let role of roles) {
|
||||||
|
let newEntry = {
|
||||||
|
objectClass: ['top', 'posixGroup'],
|
||||||
|
cn: role.name,
|
||||||
|
gidNumber: accountConfig.idBase + role.id
|
||||||
|
};
|
||||||
|
|
||||||
|
let memberUid = [];
|
||||||
|
for (let subrole of roleMap.get(role.id) || [])
|
||||||
|
memberUid = memberUid.concat(accountMap.get(subrole) || []);
|
||||||
|
|
||||||
|
if (memberUid.length) {
|
||||||
|
memberUid.sort((a, b) => a.localeCompare(b));
|
||||||
|
newEntry.memberUid = memberUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
|
||||||
|
reqs.push(client.add(dn, newEntry));
|
||||||
|
}
|
||||||
|
await Promise.all(reqs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncConnector.connectors.push(SyncLdap);
|
SyncConnector.connectors.push(SyncLdap);
|
||||||
module.exports = SyncLdap;
|
module.exports = SyncLdap;
|
||||||
|
|
||||||
|
function toMap(array, fn) {
|
||||||
|
let map = new Map();
|
||||||
|
for (let item of array) {
|
||||||
|
let keyVal = fn(item);
|
||||||
|
if (!keyVal) continue;
|
||||||
|
let key = keyVal.key;
|
||||||
|
if (!map.has(key)) map.set(key, []);
|
||||||
|
map.get(key).push(keyVal.val);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
|
@ -1,60 +1,71 @@
|
||||||
|
|
||||||
const SyncConnector = require('./sync-connector');
|
const SyncConnector = require('./sync-connector');
|
||||||
const ssh = require('node-ssh');
|
const ssh = require('node-ssh');
|
||||||
|
const ldap = require('./ldapjs-extra');
|
||||||
|
|
||||||
class SyncSamba extends SyncConnector {
|
class SyncSamba extends SyncConnector {
|
||||||
async init() {
|
async init() {
|
||||||
let sambaConfig = await this.$.SambaConfig.findOne({
|
let sambaConfig = await this.$.SambaConfig.findOne({
|
||||||
fields: ['host', 'sshUser', 'sshPass']
|
fields: [
|
||||||
|
'host',
|
||||||
|
'sshUser',
|
||||||
|
'sshPassword',
|
||||||
|
'adUser',
|
||||||
|
'adPassword',
|
||||||
|
'userDn'
|
||||||
|
]
|
||||||
});
|
});
|
||||||
if (!sambaConfig) return false;
|
if (!sambaConfig) return false;
|
||||||
|
|
||||||
let sshPassword = Buffer
|
|
||||||
.from(sambaConfig.sshPass, 'base64')
|
|
||||||
.toString('ascii');
|
|
||||||
|
|
||||||
let client = new ssh.NodeSSH();
|
let client = new ssh.NodeSSH();
|
||||||
await client.connect({
|
await client.connect({
|
||||||
host: sambaConfig.host,
|
host: sambaConfig.host,
|
||||||
username: sambaConfig.sshUser,
|
username: sambaConfig.sshUser,
|
||||||
password: sshPassword
|
password: sambaConfig.sshPassword
|
||||||
|
});
|
||||||
|
|
||||||
|
let adClient = ldap.createClient({
|
||||||
|
url: `ldaps://${sambaConfig.host}:636`,
|
||||||
|
tlsOptions: {rejectUnauthorized: false}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
sambaConfig,
|
sambaConfig,
|
||||||
client
|
client,
|
||||||
|
adClient
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deinit() {
|
async deinit() {
|
||||||
if (this.client)
|
if (!this.client) return;
|
||||||
await this.client.dispose();
|
await this.client.dispose();
|
||||||
|
await this.adClient.unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync(info, userName, password) {
|
async sync(info, userName, password) {
|
||||||
let {
|
let {client} = this;
|
||||||
client
|
|
||||||
} = this;
|
|
||||||
|
|
||||||
let {
|
if (info.hasAccount) {
|
||||||
hasAccount,
|
|
||||||
extraParams
|
|
||||||
} = info;
|
|
||||||
|
|
||||||
if (hasAccount) {
|
|
||||||
try {
|
try {
|
||||||
await client.exec('samba-tool user create', [
|
await client.exec('samba-tool user create', [
|
||||||
userName,
|
userName,
|
||||||
'--uid-number', `${extraParams.uidNumber}`,
|
'--uid-number', `${info.uidNumber}`,
|
||||||
'--mail-address', extraParams.corporateMail,
|
'--mail-address', info.corporateMail,
|
||||||
'--random-password'
|
'--random-password'
|
||||||
]);
|
]);
|
||||||
|
await client.exec('samba-tool user setexpiry', [
|
||||||
|
userName,
|
||||||
|
'--noexpiry'
|
||||||
|
]);
|
||||||
|
await client.exec('mkhomedir_helper', [
|
||||||
|
userName,
|
||||||
|
'0027'
|
||||||
|
]);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
await client.exec('samba-tool user setexpiry', [
|
await client.exec('samba-tool user enable', [
|
||||||
userName,
|
userName
|
||||||
'--noexpiry'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (password) {
|
if (password) {
|
||||||
|
@ -62,15 +73,7 @@ class SyncSamba extends SyncConnector {
|
||||||
userName,
|
userName,
|
||||||
'--newpassword', password
|
'--newpassword', password
|
||||||
]);
|
]);
|
||||||
await client.exec('samba-tool user enable', [
|
|
||||||
userName
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.exec('mkhomedir_helper', [
|
|
||||||
userName,
|
|
||||||
'0027'
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await client.exec('samba-tool user disable', [
|
await client.exec('samba-tool user disable', [
|
||||||
|
@ -81,11 +84,40 @@ class SyncSamba extends SyncConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets enabled users from Samba.
|
||||||
|
*
|
||||||
|
* Summary of userAccountControl flags:
|
||||||
|
* https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
|
||||||
|
*
|
||||||
|
* @param {Set} usersToSync
|
||||||
|
*/
|
||||||
async getUsers(usersToSync) {
|
async getUsers(usersToSync) {
|
||||||
let {client} = this;
|
let {
|
||||||
let res = await client.execCommand('samba-tool user list');
|
sambaConfig,
|
||||||
let users = res.stdout.split('\n');
|
adClient
|
||||||
for (let user of users) usersToSync.add(user.trim());
|
} = this;
|
||||||
|
|
||||||
|
await adClient.bind(sambaConfig.adUser, sambaConfig.adPassword);
|
||||||
|
|
||||||
|
let opts = {
|
||||||
|
scope: 'sub',
|
||||||
|
attributes: ['sAMAccountName'],
|
||||||
|
filter: '(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))'
|
||||||
|
};
|
||||||
|
let res = await adClient.search(sambaConfig.userDn, opts);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
res.on('error', err => {
|
||||||
|
if (err.name === 'NoSuchObjectError')
|
||||||
|
err = new Error(`Object '${sambaConfig.userDn}' does not exist`);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
res.on('searchEntry', e => {
|
||||||
|
usersToSync.add(e.object.sAMAccountName);
|
||||||
|
});
|
||||||
|
res.on('end', resolve);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,17 @@
|
||||||
class="vn-w-md">
|
class="vn-w-md">
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-vertical>
|
<vn-vertical>
|
||||||
|
<vn-check
|
||||||
|
label="Enable synchronization"
|
||||||
|
ng-model="watcher.hasData">
|
||||||
|
</vn-check>
|
||||||
|
</vn-vertical>
|
||||||
|
<vn-vertical
|
||||||
|
ng-if="watcher.hasData"
|
||||||
|
class="vn-mt-md">
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Host"
|
label="Server"
|
||||||
ng-model="$ctrl.config.host"
|
ng-model="$ctrl.config.server"
|
||||||
rule="LdapConfig"
|
rule="LdapConfig"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
@ -25,18 +33,12 @@
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Password"
|
label="Password"
|
||||||
ng-model="$ctrl.config.password"
|
ng-model="$ctrl.config.password"
|
||||||
info="Password should be base64 encoded"
|
|
||||||
type="password"
|
type="password"
|
||||||
rule="LdapConfig">
|
rule="LdapConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Base DN"
|
label="User DN"
|
||||||
ng-model="$ctrl.config.baseDn"
|
ng-model="$ctrl.config.userDn"
|
||||||
rule="LdapConfig">
|
|
||||||
</vn-textfield>
|
|
||||||
<vn-textfield
|
|
||||||
label="Filter"
|
|
||||||
ng-model="$ctrl.config.filter"
|
|
||||||
rule="LdapConfig">
|
rule="LdapConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default class Controller extends Section {
|
||||||
onSynchronizeAll() {
|
onSynchronizeAll() {
|
||||||
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
|
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
|
||||||
this.$http.patch(`UserAccounts/syncAll`)
|
this.$http.patch(`UserAccounts/syncAll`)
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('LDAP users synchronized')));
|
.then(() => this.vnApp.showSuccess(this.$t('Users synchronized!')));
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserSync() {
|
onUserSync() {
|
||||||
|
@ -15,7 +15,7 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
let params = {password: this.syncPassword};
|
let params = {password: this.syncPassword};
|
||||||
return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params)
|
return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params)
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('User synchronized')));
|
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSyncClose() {
|
onSyncClose() {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Host: Host
|
Enable synchronization: Habilitar sincronización
|
||||||
|
Server: Servidor
|
||||||
RDN: RDN
|
RDN: RDN
|
||||||
Base DN: DN base
|
User DN: DN usuarios
|
||||||
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
|
||||||
Filter: Filtro
|
Filter: Filtro
|
||||||
Group DN: DN grupos
|
Group DN: DN grupos
|
||||||
Synchronize now: Sincronizar ahora
|
Synchronize now: Sincronizar ahora
|
||||||
|
@ -9,8 +9,8 @@ Synchronize user: Sincronizar usuario
|
||||||
If password is not specified, just user attributes are synchronized: >-
|
If password is not specified, just user attributes are synchronized: >-
|
||||||
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||||
Synchronizing in the background: Sincronizando en segundo plano
|
Synchronizing in the background: Sincronizando en segundo plano
|
||||||
LDAP users synchronized: Usuarios LDAP sincronizados
|
Users synchronized!: ¡Usuarios sincronizados!
|
||||||
Username: Nombre de usuario
|
Username: Nombre de usuario
|
||||||
Synchronize: Sincronizar
|
Synchronize: Sincronizar
|
||||||
Please enter the username: Por favor introduce el nombre de usuario
|
Please enter the username: Por favor introduce el nombre de usuario
|
||||||
User synchronized: Usuario sincronizado
|
User synchronized!: ¡Usuario sincronizado!
|
||||||
|
|
|
@ -11,24 +11,47 @@
|
||||||
class="vn-w-md">
|
class="vn-w-md">
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-vertical>
|
<vn-vertical>
|
||||||
|
<vn-check
|
||||||
|
label="Enable synchronization"
|
||||||
|
ng-model="watcher.hasData">
|
||||||
|
</vn-check>
|
||||||
|
</vn-vertical>
|
||||||
|
<vn-vertical
|
||||||
|
ng-if="watcher.hasData"
|
||||||
|
class="vn-mt-md">
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="SSH host"
|
label="Host"
|
||||||
ng-model="$ctrl.config.host"
|
ng-model="$ctrl.config.host"
|
||||||
rule="SambaConfig"
|
rule="SambaConfig"
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="User"
|
label="SSH user"
|
||||||
ng-model="$ctrl.config.sshUser"
|
ng-model="$ctrl.config.sshUser"
|
||||||
rule="SambaConfig">
|
rule="SambaConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Password"
|
label="SSH password"
|
||||||
ng-model="$ctrl.config.sshPass"
|
ng-model="$ctrl.config.sshPassword"
|
||||||
info="Password should be base64 encoded"
|
|
||||||
type="password"
|
type="password"
|
||||||
rule="SambaConfig">
|
rule="SambaConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="AD user"
|
||||||
|
ng-model="$ctrl.config.adUser"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="AD password"
|
||||||
|
ng-model="$ctrl.config.adPassword"
|
||||||
|
type="password"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="User DN"
|
||||||
|
ng-model="$ctrl.config.userDn"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
</vn-vertical>
|
</vn-vertical>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-button-bar>
|
<vn-button-bar>
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
SSH host: Host SSH
|
Enable synchronization: Habilitar sincronización
|
||||||
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
Host: Host
|
||||||
|
SSH user: Usuario SSH
|
||||||
|
SSH password: Contraseña SSH
|
||||||
|
AD user: Usuario AD
|
||||||
|
AD password: Contraseña AD
|
||||||
|
User DN: DN usuarios
|
||||||
|
|
|
@ -2,9 +2,11 @@ const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('Client updateFiscalData', () => {
|
describe('Client updateFiscalData', () => {
|
||||||
const clientId = 101;
|
const clientId = 101;
|
||||||
|
const employeeId = 1;
|
||||||
|
const salesAssistantId = 21;
|
||||||
|
const administrativeId = 5;
|
||||||
afterAll(async done => {
|
afterAll(async done => {
|
||||||
const clientId = 101;
|
const ctx = {req: {accessToken: {userId: administrativeId}}};
|
||||||
const ctx = {req: {accessToken: {userId: 5}}};
|
|
||||||
ctx.args = {postcode: 46460};
|
ctx.args = {postcode: 46460};
|
||||||
|
|
||||||
await app.models.Client.updateFiscalData(ctx, clientId);
|
await app.models.Client.updateFiscalData(ctx, clientId);
|
||||||
|
@ -12,8 +14,8 @@ describe('Client updateFiscalData', () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an error if the user is not administrative and the isTaxDataChecked value is true', async() => {
|
it('should return an error if the user is not salesAssistant and the isTaxDataChecked value is true', async() => {
|
||||||
const ctx = {req: {accessToken: {userId: 1}}};
|
const ctx = {req: {accessToken: {userId: employeeId}}};
|
||||||
ctx.args = {};
|
ctx.args = {};
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
|
@ -22,11 +24,30 @@ describe('Client updateFiscalData', () => {
|
||||||
error = e;
|
error = e;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(error.message).toBeDefined();
|
expect(error.message).toEqual(`You can't make changes on a client with verified data`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an error if the salesAssistant did not fill the sage data before checking verified data', async() => {
|
||||||
|
const client = await app.models.Client.findById(clientId);
|
||||||
|
await client.updateAttribute('isTaxDataChecked', false);
|
||||||
|
|
||||||
|
const ctx = {req: {accessToken: {userId: salesAssistantId}}};
|
||||||
|
ctx.args = {isTaxDataChecked: true};
|
||||||
|
|
||||||
|
let error;
|
||||||
|
await app.models.Client.updateFiscalData(ctx, clientId)
|
||||||
|
.catch(e => {
|
||||||
|
error = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error.message).toEqual('You need to fill sage information before you check verified data');
|
||||||
|
|
||||||
|
// Restores
|
||||||
|
await client.updateAttribute('isTaxDataChecked', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the client fiscal data and return the count if changes made', async() => {
|
it('should update the client fiscal data and return the count if changes made', async() => {
|
||||||
const ctx = {req: {accessToken: {userId: 5}}};
|
const ctx = {req: {accessToken: {userId: administrativeId}}};
|
||||||
ctx.args = {postcode: 46680};
|
ctx.args = {postcode: 46680};
|
||||||
|
|
||||||
const client = await app.models.Client.findById(clientId);
|
const client = await app.models.Client.findById(clientId);
|
||||||
|
|
|
@ -45,15 +45,15 @@ module.exports = Self => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'sageTaxTypeFk',
|
arg: 'sageTaxTypeFk',
|
||||||
type: 'number'
|
type: 'any'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'sageTransactionTypeFk',
|
arg: 'sageTransactionTypeFk',
|
||||||
type: 'number'
|
type: 'any'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'transferorFk',
|
arg: 'transferorFk',
|
||||||
type: 'number'
|
type: 'any'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'hasToInvoiceByAddress',
|
arg: 'hasToInvoiceByAddress',
|
||||||
|
@ -118,6 +118,15 @@ module.exports = Self => {
|
||||||
if (!isSalesAssistant && client.isTaxDataChecked)
|
if (!isSalesAssistant && client.isTaxDataChecked)
|
||||||
throw new UserError(`You can't make changes on a client with verified data`);
|
throw new UserError(`You can't make changes on a client with verified data`);
|
||||||
|
|
||||||
|
// Sage data validation
|
||||||
|
const taxDataChecked = args.isTaxDataChecked;
|
||||||
|
const sageTaxChecked = client.sageTaxTypeFk || args.sageTaxTypeFk;
|
||||||
|
const sageTransactionChecked = client.sageTransactionTypeFk || args.sageTransactionTypeFk;
|
||||||
|
const hasSageData = sageTaxChecked && sageTransactionChecked;
|
||||||
|
|
||||||
|
if (taxDataChecked && !hasSageData)
|
||||||
|
throw new UserError(`You need to fill sage information before you check verified data`);
|
||||||
|
|
||||||
if (args.despiteOfClient) {
|
if (args.despiteOfClient) {
|
||||||
const logRecord = {
|
const logRecord = {
|
||||||
originFk: clientId,
|
originFk: clientId,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
let request = require('request-promise-native');
|
const request = require('request-promise-native');
|
||||||
let UserError = require('vn-loopback/util/user-error');
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
let getFinalState = require('vn-loopback/util/hook').getFinalState;
|
const getFinalState = require('vn-loopback/util/hook').getFinalState;
|
||||||
let isMultiple = require('vn-loopback/util/hook').isMultiple;
|
const isMultiple = require('vn-loopback/util/hook').isMultiple;
|
||||||
|
const validateTin = require('vn-loopback/util/validateTin');
|
||||||
|
const validateIban = require('vn-loopback/util/validateIban');
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
|
@ -63,7 +65,7 @@ module.exports = Self => {
|
||||||
Self.validateAsync('iban', ibanNeedsValidation, {
|
Self.validateAsync('iban', ibanNeedsValidation, {
|
||||||
message: 'The IBAN does not have the correct format'
|
message: 'The IBAN does not have the correct format'
|
||||||
});
|
});
|
||||||
let validateIban = require('../validations/validateIban');
|
|
||||||
async function ibanNeedsValidation(err, done) {
|
async function ibanNeedsValidation(err, done) {
|
||||||
let filter = {
|
let filter = {
|
||||||
fields: ['code'],
|
fields: ['code'],
|
||||||
|
@ -83,7 +85,6 @@ module.exports = Self => {
|
||||||
message: 'Invalid TIN'
|
message: 'Invalid TIN'
|
||||||
});
|
});
|
||||||
|
|
||||||
let validateTin = require('../validations/validateTin');
|
|
||||||
async function tinIsValid(err, done) {
|
async function tinIsValid(err, done) {
|
||||||
if (!this.isTaxDataChecked)
|
if (!this.isTaxDataChecked)
|
||||||
return done();
|
return done();
|
||||||
|
@ -187,7 +188,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
// Validate socialName format
|
// Validate socialName format
|
||||||
const hasChanges = orgData && changes;
|
const hasChanges = orgData && changes;
|
||||||
const socialName = changes.socialName || orgData.socialName;
|
const socialName = changes && changes.socialName || orgData && orgData.socialName;
|
||||||
const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked);
|
const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked);
|
||||||
|
|
||||||
const socialNameChanged = hasChanges
|
const socialNameChanged = hasChanges
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default class Controller extends Section {
|
||||||
if (!this.address.provinceFk)
|
if (!this.address.provinceFk)
|
||||||
this.address.provinceFk = province.id;
|
this.address.provinceFk = province.id;
|
||||||
|
|
||||||
if (postcodes.length === 1)
|
if (!this.address.postalCode && postcodes.length === 1)
|
||||||
this.address.postalCode = postcodes[0].code;
|
this.address.postalCode = postcodes[0].code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@
|
||||||
label="Social name"
|
label="Social name"
|
||||||
ng-model="$ctrl.client.socialName"
|
ng-model="$ctrl.client.socialName"
|
||||||
rule
|
rule
|
||||||
info="You can use letters and spaces">
|
info="You can use letters and spaces"
|
||||||
|
required="true">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
vn-one
|
vn-one
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
show-field="vat"
|
show-field="vat"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
label="Sage tax type"
|
label="Sage tax type"
|
||||||
|
vn-acl="salesAssistant"
|
||||||
rule>
|
rule>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-autocomplete vn-one
|
<vn-autocomplete vn-one
|
||||||
|
@ -71,6 +73,7 @@
|
||||||
show-field="transaction"
|
show-field="transaction"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
label="Sage transaction type"
|
label="Sage transaction type"
|
||||||
|
vn-acl="salesAssistant"
|
||||||
rule>
|
rule>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-autocomplete vn-one
|
<vn-autocomplete vn-one
|
||||||
|
@ -82,6 +85,7 @@
|
||||||
value-field="id"
|
value-field="id"
|
||||||
label="Previous client"
|
label="Previous client"
|
||||||
info="In case of a company succession, specify the grantor company"
|
info="In case of a company succession, specify the grantor company"
|
||||||
|
vn-acl="salesAssistant"
|
||||||
rule>
|
rule>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
|
|
|
@ -128,7 +128,7 @@ export default class Controller extends Section {
|
||||||
if (!this.client.countryFk)
|
if (!this.client.countryFk)
|
||||||
this.client.countryFk = country.id;
|
this.client.countryFk = country.id;
|
||||||
|
|
||||||
if (postcodes.length === 1)
|
if (!this.client.postcode && postcodes.length === 1)
|
||||||
this.client.postcode = postcodes[0].code;
|
this.client.postcode = postcodes[0].code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ Has to invoice: Factura
|
||||||
Notify by email: Notificar vía e-mail
|
Notify by email: Notificar vía e-mail
|
||||||
Country: País
|
Country: País
|
||||||
Street: Domicilio fiscal
|
Street: Domicilio fiscal
|
||||||
City: Municipio
|
City: Ciudad
|
||||||
Postcode: Código postal
|
Postcode: Código postal
|
||||||
Province: Provincia
|
Province: Provincia
|
||||||
Address: Consignatario
|
Address: Consignatario
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe('Supplier getSummary()', () => {
|
||||||
expect(supplier.id).toEqual(1);
|
expect(supplier.id).toEqual(1);
|
||||||
expect(supplier.name).toEqual('Plants SL');
|
expect(supplier.name).toEqual('Plants SL');
|
||||||
expect(supplier.nif).toEqual('06089160W');
|
expect(supplier.nif).toEqual('06089160W');
|
||||||
expect(supplier.account).toEqual(4100000001);
|
expect(supplier.account).toEqual('4100000001');
|
||||||
expect(supplier.payDay).toEqual(15);
|
expect(supplier.payDay).toEqual(15);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
|
describe('Supplier updateFiscalData', () => {
|
||||||
|
const supplierId = 1;
|
||||||
|
const administrativeId = 5;
|
||||||
|
const employeeId = 1;
|
||||||
|
const defaultData = {
|
||||||
|
name: 'Plants SL',
|
||||||
|
nif: '06089160W',
|
||||||
|
account: '4100000001',
|
||||||
|
sageTaxTypeFk: 4,
|
||||||
|
sageWithholdingFk: 1,
|
||||||
|
sageTransactionTypeFk: 1,
|
||||||
|
postCode: '15214',
|
||||||
|
city: 'PONTEVEDRA',
|
||||||
|
provinceFk: 1,
|
||||||
|
countryFk: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return an error if the user is not administrative', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: employeeId}}};
|
||||||
|
ctx.args = {};
|
||||||
|
|
||||||
|
let error;
|
||||||
|
await app.models.Supplier.updateFiscalData(ctx, supplierId)
|
||||||
|
.catch(e => {
|
||||||
|
error = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error.message).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check that the supplier fiscal data is untainted', async() => {
|
||||||
|
const supplier = await app.models.Supplier.findById(supplierId);
|
||||||
|
|
||||||
|
expect(supplier.name).toEqual(defaultData.name);
|
||||||
|
expect(supplier.nif).toEqual(defaultData.nif);
|
||||||
|
expect(supplier.account).toEqual(defaultData.account);
|
||||||
|
expect(supplier.sageTaxTypeFk).toEqual(defaultData.sageTaxTypeFk);
|
||||||
|
expect(supplier.sageWithholdingFk).toEqual(defaultData.sageWithholdingFk);
|
||||||
|
expect(supplier.sageTransactionTypeFk).toEqual(defaultData.sageTransactionTypeFk);
|
||||||
|
expect(supplier.postCode).toEqual(defaultData.postCode);
|
||||||
|
expect(supplier.city).toEqual(defaultData.city);
|
||||||
|
expect(supplier.provinceFk).toEqual(defaultData.provinceFk);
|
||||||
|
expect(supplier.countryFk).toEqual(defaultData.countryFk);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the supplier fiscal data and return the count if changes made', async() => {
|
||||||
|
const activeCtx = {
|
||||||
|
accessToken: {userId: administrativeId},
|
||||||
|
};
|
||||||
|
const ctx = {req: activeCtx};
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||||
|
active: activeCtx
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.args = {
|
||||||
|
name: 'Weapon Dealer',
|
||||||
|
nif: 'A68446004',
|
||||||
|
account: '4000000005',
|
||||||
|
sageTaxTypeFk: 5,
|
||||||
|
sageWithholdingFk: 2,
|
||||||
|
sageTransactionTypeFk: 2,
|
||||||
|
postCode: '46460',
|
||||||
|
city: 'VALENCIA',
|
||||||
|
provinceFk: 2,
|
||||||
|
countryFk: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await app.models.Supplier.updateFiscalData(ctx, supplierId);
|
||||||
|
|
||||||
|
expect(result.name).toEqual('Weapon Dealer');
|
||||||
|
expect(result.nif).toEqual('A68446004');
|
||||||
|
expect(result.account).toEqual('4000000005');
|
||||||
|
expect(result.sageTaxTypeFk).toEqual(5);
|
||||||
|
expect(result.sageWithholdingFk).toEqual(2);
|
||||||
|
expect(result.sageTransactionTypeFk).toEqual(2);
|
||||||
|
expect(result.postCode).toEqual('46460');
|
||||||
|
expect(result.city).toEqual('VALENCIA');
|
||||||
|
expect(result.provinceFk).toEqual(2);
|
||||||
|
expect(result.countryFk).toEqual(1);
|
||||||
|
|
||||||
|
// Restores
|
||||||
|
ctx.args = defaultData;
|
||||||
|
await app.models.Supplier.updateFiscalData(ctx, supplierId);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,78 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('updateFiscalData', {
|
||||||
|
description: 'Updates fiscal data of a supplier',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'ctx',
|
||||||
|
type: 'Object',
|
||||||
|
http: {source: 'context'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The supplier id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'name',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'nif',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'account',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'sageTaxTypeFk',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'sageWithholdingFk',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'sageTransactionTypeFk',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'postCode',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'city',
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'provinceFk',
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'countryFk',
|
||||||
|
type: 'number'
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
arg: 'res',
|
||||||
|
type: 'string',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/updateFiscalData`,
|
||||||
|
verb: 'PATCH'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.updateFiscalData = async(ctx, supplierId) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const args = ctx.args;
|
||||||
|
const supplier = await models.Supplier.findById(supplierId);
|
||||||
|
|
||||||
|
// Remove unwanted properties
|
||||||
|
delete args.ctx;
|
||||||
|
delete args.id;
|
||||||
|
|
||||||
|
return supplier.updateAttributes(args);
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,4 +1,85 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
const validateTin = require('vn-loopback/util/validateTin');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/supplier/filter')(Self);
|
require('../methods/supplier/filter')(Self);
|
||||||
require('../methods/supplier/getSummary')(Self);
|
require('../methods/supplier/getSummary')(Self);
|
||||||
|
require('../methods/supplier/updateFiscalData')(Self);
|
||||||
|
|
||||||
|
Self.validatesPresenceOf('name', {
|
||||||
|
message: 'The social name cannot be empty'
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validatesUniquenessOf('name', {
|
||||||
|
message: 'The supplier name must be unique'
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validatesPresenceOf('city', {
|
||||||
|
message: 'City cannot be empty'
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validatesPresenceOf('nif', {
|
||||||
|
message: 'The nif cannot be empty'
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validatesUniquenessOf('nif', {
|
||||||
|
message: 'TIN must be unique'
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validateAsync('nif', tinIsValid, {
|
||||||
|
message: 'Invalid TIN'
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validatesLengthOf('postCode', {
|
||||||
|
allowNull: true,
|
||||||
|
allowBlank: true,
|
||||||
|
min: 3, max: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.validateAsync('postCode', hasValidPostcode, {
|
||||||
|
message: `The postcode doesn't exist. Please enter a correct one`
|
||||||
|
});
|
||||||
|
|
||||||
|
async function hasValidPostcode(err, done) {
|
||||||
|
if (!this.postcode)
|
||||||
|
return done();
|
||||||
|
|
||||||
|
const models = Self.app.models;
|
||||||
|
const postcode = await models.Postcode.findById(this.postcode);
|
||||||
|
|
||||||
|
if (!postcode) err();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tinIsValid(err, done) {
|
||||||
|
const filter = {
|
||||||
|
fields: ['code'],
|
||||||
|
where: {id: this.countryFk}
|
||||||
|
};
|
||||||
|
const country = await Self.app.models.Country.findOne(filter);
|
||||||
|
const code = country ? country.code.toLowerCase() : null;
|
||||||
|
|
||||||
|
if (!this.nif || !validateTin(this.nif, code))
|
||||||
|
err();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAlpha(value) {
|
||||||
|
const regexp = new RegExp(/^[ñça-zA-Z0-9\s]*$/i);
|
||||||
|
|
||||||
|
return regexp.test(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self.observe('before save', async function(ctx) {
|
||||||
|
let changes = ctx.data || ctx.instance;
|
||||||
|
let orgData = ctx.currentInstance;
|
||||||
|
|
||||||
|
const socialName = changes.name || orgData.name;
|
||||||
|
const hasChanges = orgData && changes;
|
||||||
|
const socialNameChanged = hasChanges
|
||||||
|
&& orgData.socialName != socialName;
|
||||||
|
|
||||||
|
if ((socialNameChanged) && !isAlpha(socialName))
|
||||||
|
throw new UserError('The socialName has an invalid format');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"type": "String"
|
"type": "String"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"type": "Number"
|
"type": "String"
|
||||||
},
|
},
|
||||||
"countryFk": {
|
"countryFk": {
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
},
|
},
|
||||||
"postCode": {
|
"postCode": {
|
||||||
"type": "Number"
|
"type": "String"
|
||||||
},
|
},
|
||||||
"payMethodFk": {
|
"payMethodFk": {
|
||||||
"type": "Number"
|
"type": "Number"
|
||||||
|
@ -77,7 +77,25 @@
|
||||||
},
|
},
|
||||||
"nickname": {
|
"nickname": {
|
||||||
"type": "String"
|
"type": "String"
|
||||||
}
|
},
|
||||||
|
"sageTaxTypeFk": {
|
||||||
|
"type": "number",
|
||||||
|
"mysql": {
|
||||||
|
"columnName": "taxTypeSageFk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sageTransactionTypeFk": {
|
||||||
|
"type": "number",
|
||||||
|
"mysql": {
|
||||||
|
"columnName": "transactionTypeSageFk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sageWithholdingFk": {
|
||||||
|
"type": "number",
|
||||||
|
"mysql": {
|
||||||
|
"columnName": "withholdingSageFk"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
"payMethod": {
|
"payMethod": {
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
<mg-ajax path="Suppliers/{{patch.params.id}}/updateFiscalData" options="vnPatch"></mg-ajax>
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
data="$ctrl.supplier"
|
||||||
|
id-field="id"
|
||||||
|
form="form"
|
||||||
|
save="patch">
|
||||||
|
</vn-watcher>
|
||||||
|
<vn-crud-model
|
||||||
|
auto-load="true"
|
||||||
|
url="Provinces/location"
|
||||||
|
data="provincesLocation"
|
||||||
|
order="name">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-crud-model
|
||||||
|
auto-load="true"
|
||||||
|
url="Countries"
|
||||||
|
data="countries"
|
||||||
|
order="country">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-crud-model
|
||||||
|
auto-load="true"
|
||||||
|
url="SageTaxTypes"
|
||||||
|
data="sageTaxTypes"
|
||||||
|
order="vat">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-crud-model
|
||||||
|
auto-load="true"
|
||||||
|
url="SageTransactionTypes"
|
||||||
|
data="sageTransactionTypes"
|
||||||
|
order="transaction">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-crud-model
|
||||||
|
auto-load="true"
|
||||||
|
url="SageWithholdings"
|
||||||
|
data="sageWithholdings"
|
||||||
|
order="withholding">
|
||||||
|
</vn-crud-model>
|
||||||
|
<form name="form" vn-http-submit="watcher.submit()" class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
vn-two
|
||||||
|
vn-focus
|
||||||
|
label="Social name"
|
||||||
|
ng-model="$ctrl.supplier.name"
|
||||||
|
info="You can use letters and spaces"
|
||||||
|
required="true"
|
||||||
|
rule>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Tax number"
|
||||||
|
ng-model="$ctrl.supplier.nif"
|
||||||
|
required="true"
|
||||||
|
rule>
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Account"
|
||||||
|
ng-model="$ctrl.supplier.account"
|
||||||
|
rule>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-autocomplete vn-one
|
||||||
|
ng-model="$ctrl.supplier.sageTaxTypeFk"
|
||||||
|
data="sageTaxTypes"
|
||||||
|
show-field="vat"
|
||||||
|
value-field="id"
|
||||||
|
label="Sage tax type"
|
||||||
|
rule>
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete vn-one
|
||||||
|
ng-model="$ctrl.supplier.sageWithholdingFk"
|
||||||
|
data="sageWithholdings"
|
||||||
|
show-field="withholding"
|
||||||
|
value-field="id"
|
||||||
|
label="Sage withholding"
|
||||||
|
rule>
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete vn-one
|
||||||
|
ng-model="$ctrl.supplier.sageTransactionTypeFk"
|
||||||
|
data="sageTransactionTypes"
|
||||||
|
show-field="transaction"
|
||||||
|
value-field="id"
|
||||||
|
label="Sage transaction type"
|
||||||
|
rule>
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-datalist vn-one
|
||||||
|
label="Postcode"
|
||||||
|
ng-model="$ctrl.supplier.postCode"
|
||||||
|
selection="$ctrl.postcode"
|
||||||
|
url="Postcodes/location"
|
||||||
|
fields="['code','townFk']"
|
||||||
|
order="code, townFk"
|
||||||
|
value-field="code"
|
||||||
|
show-field="code"
|
||||||
|
rule>
|
||||||
|
<tpl-item>
|
||||||
|
{{code}} - {{town.name}} ({{town.province.name}},
|
||||||
|
{{town.province.country.country}})
|
||||||
|
</tpl-item>
|
||||||
|
<append>
|
||||||
|
<vn-icon-button
|
||||||
|
icon="add_circle"
|
||||||
|
vn-tooltip="New postcode"
|
||||||
|
ng-click="postcode.open()"
|
||||||
|
vn-acl="deliveryBoss"
|
||||||
|
vn-acl-action="remove">
|
||||||
|
</vn-icon-button>
|
||||||
|
</append>
|
||||||
|
</vn-datalist>
|
||||||
|
<vn-datalist vn-id="town" vn-one
|
||||||
|
label="City"
|
||||||
|
ng-model="$ctrl.supplier.city"
|
||||||
|
selection="$ctrl.town"
|
||||||
|
url="Towns/location"
|
||||||
|
fields="['id', 'name', 'provinceFk']"
|
||||||
|
show-field="name"
|
||||||
|
value-field="name"
|
||||||
|
required="true"
|
||||||
|
rule>
|
||||||
|
<tpl-item>
|
||||||
|
{{name}}, {{province.name}}
|
||||||
|
({{province.country.country}})
|
||||||
|
</tpl-item>
|
||||||
|
</vn-datalist>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete vn-id="province" vn-one
|
||||||
|
label="Province"
|
||||||
|
ng-model="$ctrl.supplier.provinceFk"
|
||||||
|
selection="$ctrl.province"
|
||||||
|
data="provincesLocation"
|
||||||
|
fields="['id', 'name', 'countryFk']"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
rule>
|
||||||
|
<tpl-item>{{name}} ({{country.country}})</tpl-item>
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete vn-id="country" vn-one
|
||||||
|
ng-model="$ctrl.supplier.countryFk"
|
||||||
|
data="countries"
|
||||||
|
show-field="country"
|
||||||
|
value-field="id"
|
||||||
|
label="Country"
|
||||||
|
rule>
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-submit label="Save"></vn-submit>
|
||||||
|
<vn-button label="Undo changes" ng-if="$ctrl.$.form.$dirty" ng-click="watcher.loadOriginalData()"></vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
</form>
|
||||||
|
<!-- New postcode dialog -->
|
||||||
|
<vn-geo-postcode
|
||||||
|
vn-id="postcode"
|
||||||
|
on-response="$ctrl.onResponse($response)">
|
||||||
|
</vn-geo-postcode>
|
|
@ -0,0 +1,84 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
export default class Controller extends Section {
|
||||||
|
get province() {
|
||||||
|
return this._province;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Province auto complete
|
||||||
|
set province(selection) {
|
||||||
|
this._province = selection;
|
||||||
|
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
|
const country = selection.country;
|
||||||
|
|
||||||
|
if (!this.supplier.countryFk)
|
||||||
|
this.supplier.countryFk = country.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get town() {
|
||||||
|
return this._town;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Town auto complete
|
||||||
|
set town(selection) {
|
||||||
|
this._town = selection;
|
||||||
|
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
|
const province = selection.province;
|
||||||
|
const country = province.country;
|
||||||
|
const postcodes = selection.postcodes;
|
||||||
|
|
||||||
|
if (!this.supplier.provinceFk)
|
||||||
|
this.supplier.provinceFk = province.id;
|
||||||
|
|
||||||
|
if (!this.supplier.countryFk)
|
||||||
|
this.supplier.countryFk = country.id;
|
||||||
|
|
||||||
|
if (!this.supplier.postCode && postcodes.length === 1)
|
||||||
|
this.supplier.postCode = postcodes[0].code;
|
||||||
|
}
|
||||||
|
|
||||||
|
get postcode() {
|
||||||
|
return this._postcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Postcode auto complete
|
||||||
|
set postcode(selection) {
|
||||||
|
const oldValue = this._postcode;
|
||||||
|
this._postcode = selection;
|
||||||
|
|
||||||
|
if (!selection || !oldValue) return;
|
||||||
|
|
||||||
|
const town = selection.town;
|
||||||
|
const province = town.province;
|
||||||
|
const country = province.country;
|
||||||
|
|
||||||
|
if (!this.supplier.city)
|
||||||
|
this.supplier.city = town.name;
|
||||||
|
|
||||||
|
if (!this.supplier.provinceFk)
|
||||||
|
this.supplier.provinceFk = province.id;
|
||||||
|
|
||||||
|
if (!this.supplier.countryFk)
|
||||||
|
this.supplier.countryFk = country.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse(response) {
|
||||||
|
this.supplier.postCode = response.code;
|
||||||
|
this.supplier.city = response.city;
|
||||||
|
this.supplier.provinceFk = response.provinceFk;
|
||||||
|
this.supplier.countryFk = response.countryFk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnSupplierFiscalData', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
supplier: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,109 @@
|
||||||
|
import './index';
|
||||||
|
import watcher from 'core/mocks/watcher';
|
||||||
|
|
||||||
|
describe('Supplier', () => {
|
||||||
|
describe('Component vnSupplierFiscalData', () => {
|
||||||
|
let $scope;
|
||||||
|
let $element;
|
||||||
|
let controller;
|
||||||
|
|
||||||
|
beforeEach(ngModule('supplier'));
|
||||||
|
|
||||||
|
beforeEach(inject(($componentController, $rootScope) => {
|
||||||
|
$scope = $rootScope.$new();
|
||||||
|
$scope.watcher = watcher;
|
||||||
|
$scope.watcher.orgData = {id: 1};
|
||||||
|
$element = angular.element('<vn-supplier-fiscal-data></supplier-fiscal-data>');
|
||||||
|
controller = $componentController('vnSupplierFiscalData', {$element, $scope});
|
||||||
|
controller.card = {reload: () => {}};
|
||||||
|
controller.supplier = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Batman'
|
||||||
|
};
|
||||||
|
|
||||||
|
controller._province = {};
|
||||||
|
controller._town = {};
|
||||||
|
controller._postcode = {};
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('province() setter', () => {
|
||||||
|
it(`should set countryFk property`, () => {
|
||||||
|
controller.supplier.countryFk = null;
|
||||||
|
controller.province = {
|
||||||
|
id: 1,
|
||||||
|
name: 'New york',
|
||||||
|
country: {
|
||||||
|
id: 2,
|
||||||
|
name: 'USA'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(controller.supplier.countryFk).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('town() setter', () => {
|
||||||
|
it(`should set provinceFk property`, () => {
|
||||||
|
controller.town = {
|
||||||
|
provinceFk: 1,
|
||||||
|
code: 46001,
|
||||||
|
province: {
|
||||||
|
id: 1,
|
||||||
|
name: 'New york',
|
||||||
|
country: {
|
||||||
|
id: 2,
|
||||||
|
name: 'USA'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postcodes: []
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(controller.supplier.provinceFk).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should set provinceFk property and fill the postalCode if there's just one`, () => {
|
||||||
|
controller.town = {
|
||||||
|
provinceFk: 1,
|
||||||
|
code: 46001,
|
||||||
|
province: {
|
||||||
|
id: 1,
|
||||||
|
name: 'New york',
|
||||||
|
country: {
|
||||||
|
id: 2,
|
||||||
|
name: 'USA'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postcodes: [{code: '46001'}]
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(controller.supplier.provinceFk).toEqual(1);
|
||||||
|
expect(controller.supplier.postCode).toEqual('46001');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('postcode() setter', () => {
|
||||||
|
it(`should set the town, provinceFk and contryFk properties`, () => {
|
||||||
|
controller.postcode = {
|
||||||
|
townFk: 1,
|
||||||
|
code: 46001,
|
||||||
|
town: {
|
||||||
|
id: 1,
|
||||||
|
name: 'New York',
|
||||||
|
province: {
|
||||||
|
id: 1,
|
||||||
|
name: 'New york',
|
||||||
|
country: {
|
||||||
|
id: 2,
|
||||||
|
name: 'USA'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(controller.supplier.city).toEqual('New York');
|
||||||
|
expect(controller.supplier.provinceFk).toEqual(1);
|
||||||
|
expect(controller.supplier.countryFk).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
Sage tax type: Tipo de impuesto Sage
|
||||||
|
Sage transaction type: Tipo de transacción Sage
|
||||||
|
Sage withholding: Retención Sage
|
|
@ -5,6 +5,7 @@ import './card';
|
||||||
import './descriptor';
|
import './descriptor';
|
||||||
import './index/';
|
import './index/';
|
||||||
import './search-panel';
|
import './search-panel';
|
||||||
import './log';
|
|
||||||
import './summary';
|
import './summary';
|
||||||
|
import './fiscal-data';
|
||||||
import './contact';
|
import './contact';
|
||||||
|
import './log';
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
{"state": "supplier.index", "icon": "icon-supplier"}
|
{"state": "supplier.index", "icon": "icon-supplier"}
|
||||||
],
|
],
|
||||||
"card": [
|
"card": [
|
||||||
|
{"state": "supplier.card.basicData", "icon": "settings"},
|
||||||
|
{"state": "supplier.card.fiscalData", "icon": "account_balance"},
|
||||||
{"state": "supplier.card.contact", "icon": "contact_phone"},
|
{"state": "supplier.card.contact", "icon": "contact_phone"},
|
||||||
{"state": "supplier.card.log", "icon": "history"}
|
{"state": "supplier.card.log", "icon": "history"}
|
||||||
]
|
]
|
||||||
|
@ -41,8 +43,24 @@
|
||||||
"params": {
|
"params": {
|
||||||
"supplier": "$ctrl.supplier"
|
"supplier": "$ctrl.supplier"
|
||||||
}
|
}
|
||||||
},
|
}, {
|
||||||
{
|
"url": "/basic-data",
|
||||||
|
"state": "supplier.card.basicData",
|
||||||
|
"component": "vn-supplier-basic-data",
|
||||||
|
"description": "Basic data",
|
||||||
|
"params": {
|
||||||
|
"supplier": "$ctrl.supplier"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"url": "/fiscal-data",
|
||||||
|
"state": "supplier.card.fiscalData",
|
||||||
|
"component": "vn-supplier-fiscal-data",
|
||||||
|
"description": "Fiscal data",
|
||||||
|
"params": {
|
||||||
|
"supplier": "$ctrl.supplier"
|
||||||
|
},
|
||||||
|
"acl": ["administrative"]
|
||||||
|
}, {
|
||||||
"url" : "/log",
|
"url" : "/log",
|
||||||
"state": "supplier.card.log",
|
"state": "supplier.card.log",
|
||||||
"component": "vn-supplier-log",
|
"component": "vn-supplier-log",
|
||||||
|
|
Loading…
Reference in New Issue