Merge branch 'dev' into 2534-sage_taxDataChecked
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2020-11-04 12:39:30 +00:00
commit c5421ee543
155 changed files with 40753 additions and 4318 deletions

View File

@ -9,7 +9,7 @@
"properties": {
"id": {
"type": "number",
"required": true
"id": true
},
"name": {
"type": "string",
@ -56,7 +56,8 @@
"roles": {
"type": "hasMany",
"model": "RoleRole",
"foreignKey": "role"
"foreignKey": "role",
"primaryKey": "roleFk"
},
"emailUser": {
"type": "hasOne",

View File

@ -0,0 +1,9 @@
ALTER TABLE `vn`.`observationType`
ADD COLUMN `code` VARCHAR(45) NOT NULL AFTER `description`;
UPDATE `vn`.`observationType` SET `code` = 'itemPicker' WHERE (`id` = '1');
UPDATE `vn`.`observationType` SET `code` = 'packager' WHERE (`id` = '2');
UPDATE `vn`.`observationType` SET `code` = 'salesPerson' WHERE (`id` = '4');
UPDATE `vn`.`observationType` SET `code` = 'administrative' WHERE (`id` = '5');
UPDATE `vn`.`observationType` SET `code` = 'weight' WHERE (`id` = '6');
UPDATE `vn`.`observationType` SET `code` = 'delivery' WHERE (`id` = '3');

View File

@ -0,0 +1,7 @@
ALTER TABLE `account`.`roleRole`
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD PRIMARY KEY (`id`);
UPDATE `account`.`role` SET id = 100 WHERE `name` = 'root';
CALL account.role_sync;

View File

@ -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 ;

View File

@ -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';

View File

@ -0,0 +1,20 @@
CREATE TABLE `vn`.`supplierLog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`originFk` int(11) NOT NULL,
`userFk` int(10) unsigned NOT NULL,
`action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`description` text CHARACTER SET utf8 DEFAULT NULL,
`changedModel` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`oldInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
`newInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
`changedModelId` int(11) DEFAULT NULL,
`changedModelValue` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `logSupplier_ibfk_1` (`originFk`),
KEY `supplierLog_ibfk_2` (`userFk`),
CONSTRAINT `supplierLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `supplier` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `supplierLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -0,0 +1,24 @@
CREATE TABLE `vn`.`supplierContact` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`supplierFk` INT(11) NULL DEFAULT NULL,
`phone` VARCHAR(16) NULL DEFAULT NULL,
`mobile` VARCHAR(16) NULL DEFAULT NULL,
`email` VARCHAR(255) NULL DEFAULT NULL,
`observation` TEXT NULL DEFAULT NULL,
`name` VARCHAR(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
ALTER TABLE `vn`.`supplierContact`
ADD CONSTRAINT `supplier_id`
FOREIGN KEY (`supplierFk`)
REFERENCES `vn`.`supplier` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE;
INSERT INTO vn.supplierContact(supplierFk,phone,mobile,email,observation,`name`)
SELECT r.Id_Proveedor,c.Telefono,c.Movil,c.email,c.Notas,concat(c.Nombre," ", IFNULL(c.Apellidos,""))
FROM vn2008.Contactos c
JOIN vn2008.Relaciones r ON r.Id_Contacto = c.Id_Contacto
JOIN vn.supplier s ON s.id = r.Id_Proveedor;

View File

@ -0,0 +1,25 @@
DROP TRIGGER IF EXISTS `vn`.`ticket_afterUpdate`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` TRIGGER `ticket_afterUpdate`
AFTER UPDATE ON `ticket`
FOR EACH ROW
BEGIN
IF !(NEW.id <=> OLD.id)
OR !(NEW.warehouseFk <=> OLD.warehouseFk)
OR !(NEW.shipped <=> OLD.shipped) THEN
CALL stock.log_add('ticket', NEW.id, OLD.id);
END IF;
IF NEW.clientFk = 2067 AND !(NEW.clientFk <=> OLD.clientFk) THEN
-- Fallo que se insertan no se sabe como tickets en este cliente
INSERT INTO vn.mail SET
`sender` = 'jgallego@verdnatura.es',
`replyTo` = 'jgallego@verdnatura.es',
`subject` = 'Modificado ticket al cliente 2067',
`body` = CONCAT(account.myUserGetName(), ' ha modificado el ticket ',
NEW.id);
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('supplier', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('SupplierContact', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative');

View File

@ -304,36 +304,36 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
(10, 'Jessica Jones', 'NYCC 2015 Poster', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 1),
(11, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 111, 10, NULL, NULL, 0, 1),
(12, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 112, 10, NULL, NULL, 0, 1),
(101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(118, 'address 18', 'Somewhere over the rainbow', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(121, 'address 21', 'the bat cave', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 0),
(122, 'address 22', 'NY roofs', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 0),
(123, 'address 23', 'The phone box', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 0),
(124, 'address 24', 'Stark tower Silla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 0),
(125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 0),
(126, 'address 26', 'Many places', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 0),
(127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 0),
(128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 0),
(129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 0),
(130, 'address 30', 'Non valid address', 'Silla', 46460, 1, 1111111111, 222222222, 0, 101, 2, NULL, NULL, 0, 0);
(101, 'Somewhere in Thailand', 'address 01', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(102, 'Somewhere in Poland', 'address 02', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(103, 'Somewhere in Japan', 'address 03', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(104, 'Somewhere in Spain', 'address 04', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(105, 'Somewhere in Potugal', 'address 05', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(106, 'Somewhere in UK', 'address 06', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(107, 'Somewhere in Valencia', 'address 07', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(108, 'Somewhere in Silla', 'address 08', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(109, 'Somewhere in London', 'address 09', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(110, 'Somewhere in Algemesi', 'address 10', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(111, 'Somewhere in Carlet', 'address 11', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(112, 'Somewhere in Campanar', 'address 12', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(113, 'Somewhere in Malilla', 'address 13', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(114, 'Somewhere in France', 'address 14', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(115, 'Somewhere in Birmingham', 'address 15', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(116, 'Somewhere in Scotland', 'address 16', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(117, 'Somewhere in nowhere', 'address 17', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(118, 'Somewhere over the rainbow', 'address 18', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(119, 'Somewhere in Alberic', 'address 19', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(120, 'Somewhere in Montortal', 'address 20', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(121, 'the bat cave', 'address 21', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 0),
(122, 'NY roofs', 'address 22', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 0),
(123, 'The phone box', 'address 23', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 0),
(124, 'Stark tower Silla', 'address 24', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 0),
(125, 'The plastic cell', 'address 25', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 0),
(126, 'Many places', 'address 26', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 0),
(127, 'Your pocket', 'address 27', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 0),
(128, 'Cerebro', 'address 28', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 0),
(129, 'Luke Cages Bar', 'address 29', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 0),
(130, 'Non valid address', 'address 30', 'Silla', 46460, 1, 1111111111, 222222222, 0, 101, 2, NULL, NULL, 0, 0);
INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`)
SELECT name, CONCAT(name, 'Street'), 'SILLA', 46460, 1, 1, id, 2, 1
@ -400,18 +400,20 @@ INSERT INTO `vn`.`clientObservation`(`id`, `clientFk`, `workerFk`, `text`, `crea
(9, 109, 18, 'HULK SMASH! ...', CURDATE()),
(10, 110, 18, 'They say everyone is born a hero. But if you let it, life will push you over the line until you are the villain.', CURDATE());
INSERT INTO `vn`.`observationType`(`id`,`description`)
INSERT INTO `vn`.`observationType`(`id`,`description`, `code`)
VALUES
(1,'observation one'),
(2,'observation two'),
(3,'observation three'),
(4,'comercial');
(1, 'observation one', 'observation one'),
(2, 'observation two', 'observation two'),
(3, 'observation three', 'observation three'),
(4, 'comercial', 'salesPerson'),
(5, 'delivery', 'delivery');
INSERT INTO `vn`.`addressObservation`(`id`,`addressFk`,`observationTypeFk`,`description`)
VALUES
(1, 121, 1, 'under the floor'),
(2, 121, 2, 'wears leather and goes out at night'),
(3, 121, 3, 'care with the dog');
(3, 121, 3, 'care with the dog'),
(5, 122, 5, 'Delivery after 10am');
INSERT INTO `vn`.`creditClassification`(`id`, `client`, `dateStart`, `dateEnd`)
VALUES
@ -605,7 +607,8 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
(8, 23, 2, 'wears leather and goes out at night'),
(9, 23, 3, 'care with the dog'),
(10, 23, 4, 'Reclama ticket: 8'),
(11, 24, 4, 'Reclama ticket: 7');
(11, 24, 4, 'Reclama ticket: 7'),
(12, 11, 5, 'Delivery after 10am');
-- FIX for state hours on local, inter_afterInsert
UPDATE vncontrol.inter SET odbc_date = DATE_ADD(CURDATE(), INTERVAL -10 SECOND);
@ -1208,11 +1211,18 @@ INSERT INTO `vn`.`annualAverageInvoiced`(`clientFk`, `invoiced`)
(104, 500),
(105, 5000);
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `phone`, `payDay`)
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `transactionTypeSageFk`)
VALUES
(1, 'Plants SL', 'Plants nick', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 123456789, 15),
(2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2, 987654321, 10),
(442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 987123654, 15);
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, NULL, NULL),
(2, 'Farmer King', 'The farmer', 4000020002, 1, 'B22222222', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 8),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, 'C33333333', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, NULL, NULL);
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES
(1, 1, 123121212, 654789123, 'supplier1@email.es', 'observation1', 'the boss'),
(2, 1, 987456132, NULL, NULL, NULL, 'the salesperson'),
(3, 2, 321654987, NULL, 'supplier2@email.es', NULL, NULL),
(4, 442, 321654987, NULL, NULL, 'observation442', NULL);
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
VALUES

View File

@ -316,10 +316,23 @@ let actions = {
},
waitForTextInElement: async function(selector, text) {
await this.waitForSelector(selector);
return await this.waitForFunction((selector, text) => {
return document.querySelector(selector).innerText.toLowerCase().includes(text.toLowerCase());
}, {}, selector, text);
const expectedText = text.toLowerCase();
return new Promise((resolve, reject) => {
let attempts = 0;
const interval = setInterval(async() => {
const currentText = await this.evaluate(selector => {
return document.querySelector(selector).innerText.toLowerCase();
}, selector);
if (currentText === expectedText || attempts === 40) {
clearInterval(interval);
resolve(currentText);
}
attempts += 1;
}, 100);
}).then(result => {
return expect(result).toContain(expectedText);
});
},
selectorFormater: function(selector) {

View File

@ -123,19 +123,19 @@ export default {
streetAddress: 'vn-textfield[ng-model="$ctrl.address.street"]',
postcode: 'vn-datalist[ng-model="$ctrl.address.postalCode"]',
city: 'vn-datalist[ng-model="$ctrl.address.city"]',
province: 'vn-autocomplete[ng-model="$ctrl.address.provinceId"]',
agency: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeId"]',
province: 'vn-autocomplete[ng-model="$ctrl.address.provinceFk"]',
agency: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeFk"]',
phone: 'vn-textfield[ng-model="$ctrl.address.phone"]',
mobileInput: 'vn-textfield[ng-model="$ctrl.address.mobile"]',
defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]',
incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsId"]',
addNewCustomsAgent: 'vn-client-address-create vn-autocomplete[ng-model="$ctrl.address.customsAgentId"] vn-icon-button[icon="add_circle"]',
incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsFk"]',
addNewCustomsAgent: 'vn-client-address-create vn-autocomplete[ng-model="$ctrl.address.customsAgentFk"] vn-icon-button[icon="add_circle"]',
newCustomsAgentFiscalID: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.nif"]',
newCustomsAgentFiscalName: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.fiscalName"]',
newCustomsAgentStreet: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.street"]',
newCustomsAgentPhone: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.phone"]',
saveNewCustomsAgentButton: 'button[response="accept"]',
customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentId"]',
customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentFk"]',
secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]',
firstEditAddress: 'vn-client-address-index div:nth-child(1) > a',
secondEditAddress: 'vn-client-address-index div:nth-child(2) > a',
@ -901,5 +901,27 @@ export default {
newEntryTravel: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.travelFk"]',
newEntryCompany: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
saveNewEntry: 'vn-entry-create button[type="submit"]'
},
supplierSummary: {
header: 'vn-supplier-summary > vn-card > h5',
basicDataId: 'vn-supplier-summary vn-label-value[label="Id"]',
fiscalAddressTaxNumber: 'vn-supplier-summary vn-label-value[label="Tax number"]',
billingDataPayMethod: 'vn-supplier-summary vn-label-value[label="Pay method"]'
},
supplierDescriptor: {
alias: 'vn-supplier-descriptor vn-label-value[label="Alias"]',
clientButton: 'vn-supplier-descriptor vn-icon[icon="person"]',
entriesButton: 'vn-supplier-descriptor vn-icon[icon="icon-entry"]',
},
supplierContact: {
anyContact: 'vn-supplier-contact > form > vn-card > div',
addNewContact: 'vn-supplier-contact vn-icon[icon="add_circle"]',
thirdContactName: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.name"]',
thirdContactPhone: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.phone"]',
thirdContactMobile: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.mobile"]',
thirdContactEmail: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.email"]',
thirdContactNotes: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.observation"]',
saveButton: 'vn-supplier-contact button[type="submit"]',
thirdContactDeleteButton: 'vn-supplier-contact div:nth-child(3) vn-icon-button[icon="delete"]'
}
};

View File

@ -19,7 +19,7 @@ describe('Login path', async() => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.type).toBe('error');
expect(message.text).toBe('Invalid login, remember that distinction is made between uppercase and lowercase');
expect(state).toBe('login');
});
@ -28,7 +28,7 @@ describe('Login path', async() => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.type).toBe('error');
expect(message.text).toBe('Invalid login, remember that distinction is made between uppercase and lowercase');
expect(state).toBe('login');
});
@ -37,7 +37,7 @@ describe('Login path', async() => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.type).toBe('error');
expect(message.text).toBe('Please enter your username');
expect(state).toBe('login');
});
});

View File

@ -119,7 +119,7 @@ describe('Client create path', () => {
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should click on the Clients button of the top bar menu', async() => {

View File

@ -37,7 +37,7 @@ describe('Client Edit basicData path', () => {
await page.waitToClick(selectors.clientBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the name have been edited', async() => {
@ -101,7 +101,7 @@ describe('Client Edit basicData path', () => {
await page.waitToClick(selectors.clientBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should now confirm the name have been edited', async() => {

View File

@ -93,7 +93,7 @@ describe('Client Edit fiscalData path', () => {
await page.waitToClick(selectors.globalItems.acceptButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should propagate the Equalization tax', async() => {
@ -119,7 +119,7 @@ describe('Client Edit fiscalData path', () => {
await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
// confirm all addresses have now EQtax checked step 1
@ -155,7 +155,7 @@ describe('Client Edit fiscalData path', () => {
await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should propagate the Equalization tax changes', async() => {
@ -289,7 +289,7 @@ describe('Client Edit fiscalData path', () => {
await page.waitToClick(selectors.clientAddresses.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
// confirm invoice by address checkbox gets checked if the EQtax differs between addresses step 3

View File

@ -91,18 +91,18 @@ describe('Client Add address path', () => {
await page.waitToClick(selectors.clientAddresses.secondMakeDefaultStar);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the default address is the expected one`, async() => {
await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'Somewhere in Thailand');
await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'somewhere in new york');
const result = await page.waitToGetProperty(selectors.clientAddresses.defaultAddress, 'innerText');
expect(result).toContain('Somewhere in Thailand');
expect(result).toContain('Somewhere in New York');
});
it(`should click on the edit icon of the default address`, async() => {
await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'Somewhere in Thailand');
await page.waitForTextInElement(selectors.clientAddresses.defaultAddress, 'somewhere in new york');
await page.waitToClick(selectors.clientAddresses.firstEditAddress);
await page.waitForState('client.card.address.edit');
});

View File

@ -48,6 +48,6 @@ describe('Client add address notes path', () => {
await page.waitToClick(selectors.clientAddresses.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});

View File

@ -23,7 +23,7 @@ describe('Client Edit web access path', () => {
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm web access is now unchecked', async() => {

View File

@ -31,7 +31,7 @@ describe('Client Add notes path', () => {
await page.waitToClick(selectors.clientNotes.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the note was created', async() => {

View File

@ -27,7 +27,7 @@ describe('Client Add credit path', () => {
await page.waitToClick(selectors.clientCredit.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the credit was updated', async() => {

View File

@ -36,7 +36,7 @@ describe('Client Add greuge path', () => {
await page.waitToClick(selectors.clientGreuge.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the greuge was added to the list', async() => {

View File

@ -32,7 +32,7 @@ describe('Client lock verified data path', () => {
await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the social name have been edited', async() => {
@ -64,7 +64,7 @@ describe('Client lock verified data path', () => {
await page.waitToClick(selectors.globalItems.acceptButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm Verified data checkbox is checked', async() => {
@ -81,7 +81,7 @@ describe('Client lock verified data path', () => {
await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should again confirm the social name have been edited', async() => {
@ -135,7 +135,7 @@ describe('Client lock verified data path', () => {
await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should now confirm the social name have been edited once and for all', async() => {

View File

@ -22,7 +22,7 @@ describe('Client log path', () => {
await page.waitToClick(selectors.clientBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should navigate to the log section', async() => {

View File

@ -20,7 +20,7 @@ describe('Client balance path', () => {
await page.autocompleteSearch(selectors.globalItems.userLocalCompany, 'CCs');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should access to the balance section to check the data shown matches the local settings', async() => {
@ -35,7 +35,7 @@ describe('Client balance path', () => {
await page.clearInput(selectors.globalItems.userConfigThirdAutocomplete);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should click the new payment button', async() => {
@ -51,7 +51,7 @@ describe('Client balance path', () => {
await page.waitToClick(selectors.clientBalance.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should edit the 1st line reference', async() => {
@ -60,7 +60,7 @@ describe('Client balance path', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check balance is now 0, the reference was saved and the company is now VNL becouse the user local settings were removed', async() => {
@ -85,7 +85,7 @@ describe('Client balance path', () => {
await page.waitToClick(selectors.clientBalance.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check balance is now -100', async() => {
@ -101,7 +101,7 @@ describe('Client balance path', () => {
await page.waitToClick(selectors.clientBalance.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check balance is now 50', async() => {

View File

@ -22,7 +22,7 @@ describe('Client DMS', () => {
await page.respondToDialog('accept');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should click on the first document line worker name making the descriptor visible`, async() => {

View File

@ -24,7 +24,7 @@ describe('Client contacts', () => {
await page.waitToClick(selectors.clientContacts.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should delete de contact', async() => {
@ -33,6 +33,6 @@ describe('Client contacts', () => {
await page.waitToClick(selectors.clientContacts.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});

View File

@ -26,7 +26,7 @@ describe('Worker basic data path', () => {
await page.waitToClick(selectors.workerBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should reload the section then check the name was edited', async() => {

View File

@ -93,12 +93,8 @@ describe('Worker time control path', () => {
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 8 hours`, async() => {
it(`should check Hank Pym worked 7 hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '07:00 h.');
const result = await page
.waitToGetProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText');
expect(result).toEqual('07:00 h.');
});
});
@ -149,9 +145,6 @@ describe('Worker time control path', () => {
it(`should check Hank Pym worked 8 happy hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.tuesdayWorkedHours, '08:00 h.');
const result = await page.waitToGetProperty(selectors.workerTimeControl.tuesdayWorkedHours, 'innerText');
expect(result).toEqual('08:00 h.');
});
});
@ -202,9 +195,6 @@ describe('Worker time control path', () => {
it(`should check Hank Pym worked 8 cheerfull hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.wednesdayWorkedHours, '08:00 h.');
const result = await page.waitToGetProperty(selectors.workerTimeControl.wednesdayWorkedHours, 'innerText');
expect(result).toEqual('08:00 h.');
});
});
@ -252,9 +242,6 @@ describe('Worker time control path', () => {
it(`should check Hank Pym worked 8 joyfull hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.thursdayWorkedHours, '08:00 h.');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thursdayWorkedHours, 'innerText');
expect(result).toEqual('08:00 h.');
});
});
@ -301,9 +288,6 @@ describe('Worker time control path', () => {
it(`should check Hank Pym worked 8 hours with a smile on his face`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.fridayWorkedHours, '08:00 h.');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fridayWorkedHours, 'innerText');
expect(result).toEqual('08:00 h.');
});
});
});
@ -346,9 +330,6 @@ describe('Worker time control path', () => {
it(`should check Hank Pym worked 8 hours with all his will`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.saturdayWorkedHours, '08:00 h.');
const result = await page.waitToGetProperty(selectors.workerTimeControl.saturdayWorkedHours, 'innerText');
expect(result).toEqual('08:00 h.');
});
});
@ -375,19 +356,12 @@ describe('Worker time control path', () => {
it(`should check Hank Pym worked 8 glad hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.sundayWorkedHours, '08:00 h.');
const result = await page.waitToGetProperty(selectors.workerTimeControl.sundayWorkedHours, 'innerText');
expect(result).toEqual('08:00 h.');
});
it(`should check Hank Pym doesn't have hours set on the next months first week`, async() => {
await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '00:00 h.');
const wholeWeekHours = await page
.waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText');
expect(wholeWeekHours).toEqual('00:00 h.');
});
it(`should check he didn't scan in this week yet`, async() => {
@ -410,11 +384,8 @@ describe('Worker time control path', () => {
await page.accessToSection('worker.card.timeControl');
});
it('should check his hours are alright', async() => {
it('should check his weekly hours are alright', async() => {
await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '55:00 h.');
const wholeWeekHours = await page.waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText');
expect(wholeWeekHours).toEqual('55:00 h.');
});
});
});

View File

@ -24,7 +24,7 @@ describe('Worker calendar path', () => {
expect(result).toContain(' 5 ');
});
it('should set two days as holidays on the calendar', async() => {
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst);
@ -50,9 +50,8 @@ describe('Worker calendar path', () => {
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.mayEighth);
});
await page.waitFor(reasonableTimeBetweenClicks);
it('should check the total holidays increased by 1.5', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 6.5 ');

View File

@ -38,7 +38,7 @@ describe('Item Edit basic data path', () => {
await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should create a new intrastat`, async() => {
@ -57,7 +57,7 @@ describe('Item Edit basic data path', () => {
await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the item name was edited`, async() => {

View File

@ -23,7 +23,7 @@ describe('Item edit tax path', () => {
await page.waitToClick(selectors.itemTax.submitTaxButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the first item tax class was edited`, async() => {

View File

@ -26,7 +26,7 @@ describe('Item create tags path', () => {
await page.waitToClick(selectors.itemTags.submitItemTagsButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the fourth row data is the expected one`, async() => {

View File

@ -25,7 +25,7 @@ describe('Item create niche path', () => {
await page.waitToClick(selectors.itemNiches.submitNichesButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the first niche is the expected one`, async() => {

View File

@ -23,7 +23,7 @@ describe('Item Create botanical path', () => {
await page.waitToClick(selectors.itemBotanical.submitBotanicalButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the botanical for the item was created`, async() => {
@ -58,7 +58,7 @@ describe('Item Create botanical path', () => {
await page.waitToClick(selectors.itemBotanical.submitBotanicalButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the botanical for the item was edited`, async() => {

View File

@ -23,7 +23,7 @@ describe('Item Create barcodes path', () => {
await page.waitToClick(selectors.itemBarcodes.submitBarcodesButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the barcode 5 is created and it is now the third barcode as the first was deleted`, async() => {

View File

@ -45,7 +45,7 @@ describe('Item Create/Clone path', () => {
await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm Infinity Gauntlet item was created', async() => {

View File

@ -20,7 +20,7 @@ describe('Item regularize path', () => {
await page.autocompleteSearch(selectors.globalItems.userLocalWarehouse, 'Warehouse Four');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check the local settings were saved', async() => {
@ -51,7 +51,7 @@ describe('Item regularize path', () => {
await page.waitToClick(selectors.itemDescriptor.regularizeSaveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should click on the Tickets button of the top bar menu', async() => {
@ -70,7 +70,7 @@ describe('Item regularize path', () => {
await page.clearInput(selectors.globalItems.userConfigFirstAutocomplete);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should search for the ticket with alias missing', async() => {
@ -114,7 +114,7 @@ describe('Item regularize path', () => {
await page.waitToClick(selectors.itemDescriptor.regularizeSaveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should again click on the Tickets button of the top bar menu', async() => {

View File

@ -36,7 +36,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should navigate forth and back to see the images column is still visible', async() => {
@ -70,7 +70,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should now navigate forth and back to see the ids column is now visible', async() => {

View File

@ -34,7 +34,7 @@ describe('Item log path', () => {
await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should return to the items index by clicking the return to items button', async() => {

View File

@ -29,7 +29,7 @@ describe('Item descriptor path', () => {
await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should reload the section and check the inactive icon is bright', async() => {
@ -45,6 +45,6 @@ describe('Item descriptor path', () => {
await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});

View File

@ -77,7 +77,7 @@ describe('Ticket List sale path', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should update the description of the new sale', async() => {
@ -86,7 +86,7 @@ describe('Ticket List sale path', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should add a third empty item to the sale list', async() => {
@ -104,7 +104,7 @@ describe('Ticket List sale path', () => {
await page.waitToClick(selectors.globalItems.acceptButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should verify there's only 1 single line remaining`, async() => {

View File

@ -41,7 +41,7 @@ describe('Ticket Edit sale path', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should check it's state is libre now`, async() => {
@ -55,7 +55,7 @@ describe('Ticket Edit sale path', () => {
await page.waitToClick(selectors.ticketSales.setOk);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should check it's state is OK now`, async() => {
@ -135,7 +135,7 @@ describe('Ticket Edit sale path', () => {
await page.type(selectors.ticketSales.firstSaleQuantity, '9\u000d');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should update the price', async() => {
@ -144,7 +144,7 @@ describe('Ticket Edit sale path', () => {
await page.type(selectors.ticketSales.firstSalePriceInput, '5\u000d');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the price have been updated', async() => {
@ -165,7 +165,7 @@ describe('Ticket Edit sale path', () => {
await page.type(selectors.ticketSales.firstSaleDiscountInput, '50\u000d');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the discount have been updated', async() => {
@ -224,7 +224,7 @@ describe('Ticket Edit sale path', () => {
await page.waitForSpinnerLoad();
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the third sale was deleted`, async() => {

View File

@ -24,7 +24,7 @@ describe('Ticket Create notes path', () => {
await page.waitToClick(selectors.ticketNotes.submitNotesButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the note is the expected one', async() => {
@ -45,6 +45,6 @@ describe('Ticket Create notes path', () => {
await page.waitToClick(selectors.ticketNotes.submitNotesButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});

View File

@ -51,7 +51,7 @@ describe('Ticket Create packages path', () => {
await page.waitToClick(selectors.ticketPackages.savePackagesButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the first select is the expected one`, async() => {

View File

@ -36,7 +36,7 @@ describe('Ticket Create new tracking state path', () => {
await page.waitToClick(selectors.createStateView.saveStateButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});
@ -74,7 +74,7 @@ describe('Ticket Create new tracking state path', () => {
await page.waitToClick(selectors.createStateView.saveStateButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});
});

View File

@ -33,7 +33,7 @@ describe('Ticket descriptor path', () => {
await page.waitToClick(selectors.ticketDescriptor.thursdayButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should again click on the Tickets button of the top bar menu', async() => {
@ -68,7 +68,7 @@ describe('Ticket descriptor path', () => {
await page.waitToClick(selectors.ticketDescriptor.saturdayButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should click on the Tickets button of the top bar menu once again', async() => {
@ -97,7 +97,7 @@ describe('Ticket descriptor path', () => {
await page.waitToClick(selectors.ticketsIndex.acceptDeleteTurn);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the sixth weekly ticket was deleted', async() => {
@ -111,11 +111,11 @@ describe('Ticket descriptor path', () => {
await page.autocompleteSearch(selectors.ticketsIndex.firstWeeklyTicketAgency, 'Silla247');
let message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
await page.clearInput(selectors.ticketsIndex.firstWeeklyTicketAgency);
message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});

View File

@ -27,7 +27,7 @@ describe('Ticket purchase request path', () => {
await page.waitToClick(selectors.ticketRequests.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should have been redirected to the request index', async() => {
@ -39,24 +39,24 @@ describe('Ticket purchase request path', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the new request was added', async() => {
it('should check the new request was added', async() => {
await page.reloadSection('ticket.card.request.index');
const result = await page.waitToGetProperty(selectors.ticketRequests.thirdRequestQuantity, 'value');
expect(result).toEqual('99');
});
it(`should confirm first request can't be edited as its state is different to new`, async() => {
it(`should check the first request can't be edited as its state is different to new`, async() => {
await page.waitForClassPresent(selectors.ticketRequests.firstRequestQuantity, 'disabled');
const result = await page.isDisabled(selectors.ticketRequests.firstRequestQuantity);
expect(result).toBe(true);
});
it(`should confirm second request can't be edited as its state is different to new`, async() => {
it(`should check the second request can't be edited as its state is different to new`, async() => {
await page.waitForClassPresent(selectors.ticketRequests.secondRequestQuantity, 'disabled');
const result = await page.isDisabled(selectors.ticketRequests.secondRequestQuantity);
@ -67,10 +67,10 @@ describe('Ticket purchase request path', () => {
await page.waitToClick(selectors.ticketRequests.thirdRemoveRequestButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the request was deleted', async() => {
it('should check the request was deleted', async() => {
await page.reloadSection('ticket.card.request.index');
await page.wait(selectors.ticketRequests.addRequestButton);
await page.waitForSelector(selectors.ticketRequests.thirdDescription, {hidden: true});

View File

@ -100,7 +100,7 @@ describe('Ticket descriptor path', () => {
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
xit(`should check the state of the stowaway ticket is embarked`, async() => {
@ -122,7 +122,7 @@ describe('Ticket descriptor path', () => {
await page.waitToClick(selectors.ticketDescriptor.acceptDeleteStowawayButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the ship buton doesn't exisist any more`, async() => {

View File

@ -83,7 +83,7 @@ describe('Ticket services path', () => {
await page.waitToClick(selectors.ticketService.saveServiceButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the service description was created correctly', async() => {
@ -114,7 +114,7 @@ describe('Ticket services path', () => {
await page.waitToClick(selectors.ticketService.saveServiceButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the service was removed`, async() => {

View File

@ -31,7 +31,7 @@ describe('Ticket create path', () => {
await page.waitToClick(selectors.createTicketView.createButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check the url is now the summary of the ticket', async() => {
@ -55,7 +55,7 @@ describe('Ticket create path', () => {
await page.waitToClick(selectors.createTicketView.createButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check the url is now the summary of the created ticket', async() => {
@ -68,7 +68,7 @@ describe('Ticket create path', () => {
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should delete the current ticket', async() => {
@ -77,7 +77,7 @@ describe('Ticket create path', () => {
await page.waitToClick(selectors.ticketDescriptor.acceptDialog);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Ticket deleted. You can undo this action within the first hour');
});
it('should search for the stowaway ticket of the previously deleted ticket', async() => {

View File

@ -80,7 +80,7 @@ describe('Ticket Summary path', () => {
await page.waitToClick(selectors.ticketSummary.setOk);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the ticket state was updated', async() => {

View File

@ -29,7 +29,7 @@ describe('Ticket log path', () => {
await page.waitToClick(selectors.ticketNotes.submitNotesButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should navigate to the log section', async() => {

View File

@ -42,7 +42,7 @@ describe('Ticket index payout path', () => {
await page.waitToClick(selectors.ticketsIndex.submitPayout);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should navigate to the client balance section and check a new balance line was entered', async() => {

View File

@ -27,7 +27,7 @@ describe('Claim edit basic data path', () => {
await page.waitToClick(selectors.claimBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should have been redirected to the next section of claims as the role is claimManager`, async() => {
@ -40,7 +40,7 @@ describe('Claim edit basic data path', () => {
await page.waitToClick(selectors.claimBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the claim state was edited', async() => {
@ -71,6 +71,6 @@ describe('Claim edit basic data path', () => {
await page.waitToClick(selectors.claimBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
});

View File

@ -28,7 +28,7 @@ describe('Claim development', () => {
await page.waitToClick(selectors.claimDevelopment.saveDevelopmentButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should redirect to the next section of claims as the role is claimManager`, async() => {
@ -45,7 +45,7 @@ describe('Claim development', () => {
await page.waitToClick(selectors.claimDevelopment.saveDevelopmentButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the first development is the expected one', async() => {

View File

@ -23,7 +23,7 @@ xdescribe('Claim detail', () => {
await page.waitToClick(selectors.claimDetail.firstClaimableSaleFromTicket);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the claim contains now two items', async() => {
@ -38,7 +38,7 @@ xdescribe('Claim detail', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the first item quantity, and the claimed total were correctly edited', async() => {
@ -67,7 +67,7 @@ xdescribe('Claim detail', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check the mana is the expected one', async() => {
@ -81,7 +81,7 @@ xdescribe('Claim detail', () => {
await page.waitToClick(selectors.claimDetail.secondItemDeleteButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the claim contains now one item', async() => {
@ -95,7 +95,7 @@ xdescribe('Claim detail', () => {
await page.waitToClick(selectors.claimDetail.firstClaimableSaleFromTicket);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should have been redirected to the next section in claims`, async() => {

View File

@ -21,7 +21,7 @@ describe('Claim action path', () => {
await page.waitToClick(selectors.claimAction.importClaimButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should import the second importable ticket', async() => {
@ -33,7 +33,7 @@ describe('Claim action path', () => {
await page.waitToClick(selectors.claimAction.secondImportableTicket);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should edit the second line destination field', async() => {
@ -41,14 +41,14 @@ describe('Claim action path', () => {
await page.autocompleteSearch(selectors.claimAction.secondLineDestination, 'Bueno');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should delete the first line', async() => {
await page.waitToClick(selectors.claimAction.firstDeleteLine);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should refresh the view to check the remaining line is the expected one', async() => {
@ -62,14 +62,14 @@ describe('Claim action path', () => {
await page.waitToClick(selectors.claimAction.firstDeleteLine);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should check the "is paid with mana" checkbox', async() => {
await page.waitToClick(selectors.claimAction.isPaidWithManaCheckbox);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the "is paid with mana" is checked', async() => {

View File

@ -29,13 +29,13 @@ describe('Order summary path', () => {
it('should check the summary contains the order alias', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.alias, 'innerText');
expect(result).toEqual('address 26');
expect(result).toEqual('Many places');
});
it('should check the summary contains the order consignee', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.consignee, 'innerText');
expect(result).toEqual('Many places - Silla (Province one)');
expect(result).toEqual('address 26 - Silla (Province one)');
});
it('should check the summary contains the order subtotal', async() => {

View File

@ -81,7 +81,7 @@ describe('Order edit basic data path', () => {
await page.waitToClick(selectors.orderBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should now confirm the client have been edited', async() => {

View File

@ -28,7 +28,7 @@ describe('Order lines', () => {
await page.waitToClick(selectors.orderLine.confirmButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the order subtotal has changed', async() => {

View File

@ -33,7 +33,7 @@ describe('Route basic Data path', () => {
await page.waitToClick(selectors.routeBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the worker was edited', async() => {

View File

@ -51,7 +51,7 @@ describe('Route create path', () => {
await page.waitToClick(selectors.createRouteView.submitButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should confirm the redirection to the created route summary`, async() => {

View File

@ -23,7 +23,7 @@ xdescribe('Route basic Data path', () => {
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should confirm the buscamanButton is disabled', async() => {

View File

@ -51,7 +51,7 @@ describe('Travel thermograph path', () => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
expect(state).toBe('travel.card.thermograph.index');
});

View File

@ -52,7 +52,7 @@ describe('Travel basic data path', () => {
await page.waitToClick(selectors.travelBasicDada.save);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should reload the section and check the reference was saved', async() => {

View File

@ -40,7 +40,7 @@ describe('Zone basic data path', () => {
await page.waitToClick(selectors.zoneBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it('should now reload the section', async() => {

View File

@ -21,10 +21,10 @@ describe('Entry summary path', () => {
});
it(`should display details from the entry on the header`, async() => {
await page.waitForTextInElement(selectors.entrySummary.header, 'The king');
await page.waitForTextInElement(selectors.entrySummary.header, 'The farmer');
const result = await page.waitToGetProperty(selectors.entrySummary.header, 'innerText');
expect(result).toContain('The king');
expect(result).toContain('The farmer');
});
it('should display some entry details like the reference', async() => {

View File

@ -0,0 +1,84 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Supplier summary & descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'supplier');
await page.accessToSearchResult('1');
});
afterAll(async() => {
await browser.close();
});
// summary
it('should reach the second entry summary section', async() => {
await page.waitForState('supplier.card.summary');
});
it(`should confirm there's data on the summary header`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.header, 'innerText');
expect(result).toContain('Plants SL - 1');
});
it(`should confirm there's data on the summary basic data`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.basicDataId, 'innerText');
expect(result).toContain('Id 1');
});
it(`should confirm there's data on the summary fiscal address`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.fiscalAddressTaxNumber, 'innerText');
expect(result).toContain('Tax number 06089160W');
});
it(`should confirm there's data on the summary fiscal pay method`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.billingDataPayMethod, 'innerText');
expect(result).toContain('Pay method PayMethod one');
});
// descriptor
it(`should confirm there's data on the descriptor`, async() => {
const result = await page.waitToGetProperty(selectors.supplierDescriptor.alias, 'innerText');
expect(result).toContain('Plants nick');
});
it(`should navigate to the supplier's client summary using the icon client button`, async() => {
await page.waitToClick(selectors.supplierDescriptor.clientButton);
await page.waitForState('client.card.summary');
});
it(`should navigate back to the supplier`, async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.waitForState('home');
await page.selectModule('supplier');
await page.accessToSearchResult('1');
await page.waitForState('supplier.card.summary');
});
it(`should navigate to the supplier's entries`, async() => {
await page.waitToClick(selectors.supplierDescriptor.entriesButton);
await page.waitForState('entry.index');
});
it(`should navigate back to suppliers but a different one this time`, async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.waitForState('home');
await page.selectModule('supplier');
await page.accessToSearchResult('2');
await page.waitForState('supplier.card.summary');
});
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});
});
});

View File

@ -0,0 +1,84 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Supplier contact path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'supplier');
await page.accessToSearchResult('1');
await page.accessToSection('supplier.card.contact');
});
afterAll(async() => {
await browser.close();
});
it('should create a new contact', async() => {
await page.waitToClick(selectors.supplierContact.addNewContact);
await page.write(selectors.supplierContact.thirdContactName, 'The tester');
await page.write(selectors.supplierContact.thirdContactPhone, '99 999 99 99');
await page.write(selectors.supplierContact.thirdContactMobile, '555 55 55 55');
await page.write(selectors.supplierContact.thirdContactEmail, 'testing@puppeteer.com');
await page.write(selectors.supplierContact.thirdContactNotes, 'the end to end integration tester');
await page.waitToClick(selectors.supplierContact.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toBe('Data saved!');
});
it(`should reload the section and count the contacts`, async() => {
await page.reloadSection('supplier.card.contact');
const result = await page.countElement(selectors.supplierContact.anyContact);
expect(result).toEqual(3);
});
it(`should check the new contact name was saved correctly`, async() => {
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactName, 'value');
expect(result).toContain('The tester');
});
it(`should check the new contact phone was saved correctly`, async() => {
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactPhone, 'value');
expect(result).toContain('99 999 99 99');
});
it(`should check the new contact mobile was saved correctly`, async() => {
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactMobile, 'value');
expect(result).toContain('555 55 55 55');
});
it(`should check the new contact email was saved correctly`, async() => {
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactEmail, 'value');
expect(result).toContain('testing@puppeteer.com');
});
it(`should check the new contact note was saved correctly`, async() => {
const result = await page.waitToGetProperty(selectors.supplierContact.thirdContactNotes, 'value');
expect(result).toContain('the end to end integration tester');
});
it(`should remove the created contact`, async() => {
await page.waitToClick(selectors.supplierContact.thirdContactDeleteButton, 'value');
await page.waitToClick(selectors.supplierContact.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toBe('Data saved!');
});
it(`should reload the section and count the amount of contacts went back to 2`, async() => {
await page.reloadSection('supplier.card.contact');
const result = await page.countElement(selectors.supplierContact.anyContact);
expect(result).toEqual(2);
});
});

5148
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -163,6 +163,9 @@ function e2eSingleRun() {
`${__dirname}/e2e/paths/08*/*[sS]pec.js`,
`${__dirname}/e2e/paths/09*/*[sS]pec.js`,
`${__dirname}/e2e/paths/10*/*[sS]pec.js`,
`${__dirname}/e2e/paths/11*/*[sS]pec.js`,
`${__dirname}/e2e/paths/12*/*[sS]pec.js`,
`${__dirname}/e2e/paths/13*/*[sS]pec.js`,
`${__dirname}/e2e/paths/**/*[sS]pec.js`
];

View File

@ -154,7 +154,8 @@ module.exports = function(Self) {
const showFieldNames = [
'name',
'description',
'code'
'code',
'nickname'
];
for (field of showFieldNames) {
const propField = properties && properties[field];

View File

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

View File

@ -0,0 +1,36 @@
const SyncEngine = require('../../util/sync-engine');
module.exports = Self => {
Self.remoteMethod('syncAll', {
description: 'Synchronizes user database with LDAP and Samba',
http: {
path: `/syncAll`,
verb: 'PATCH'
}
});
Self.syncAll = async function() {
let $ = Self.app.models;
let engine = new SyncEngine();
await engine.init($);
let usersToSync = await engine.getUsers();
usersToSync = Array.from(usersToSync.values())
.sort((a, b) => a.localeCompare(b));
for (let user of usersToSync) {
try {
console.log(`Synchronizing user '${user}'`);
await engine.sync(user);
console.log(` -> '${user}' sinchronized`);
} catch (err) {
console.error(` -> '${user}' synchronization error:`, err.message);
}
}
await engine.deinit();
await $.RoleInherit.sync();
};
};

View File

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

View File

@ -5,6 +5,9 @@
"LdapConfig": {
"dataSource": "vn"
},
"Mail": {
"dataSource": "vn"
},
"MailAlias": {
"dataSource": "vn"
},
@ -34,8 +37,5 @@
},
"UserSync": {
"dataSource": "vn"
},
"Mail": {
"dataSource": "vn"
}
}

View File

@ -11,7 +11,7 @@
"type": "number",
"id": true
},
"host": {
"server": {
"type": "string",
"required": true
},
@ -23,10 +23,7 @@
"type": "string",
"required": true
},
"baseDn": {
"type": "string"
},
"filter": {
"userDn": {
"type": "string"
},
"groupDn": {

View File

@ -7,7 +7,7 @@
}
},
"properties": {
"role": {
"id": {
"id": true
}
},

View File

@ -18,7 +18,16 @@
"sshUser": {
"type": "string"
},
"sshPass": {
"sshPassword": {
"type": "string"
},
"adUser": {
"type": "string"
},
"adPassword": {
"type": "string"
},
"userDn": {
"type": "string"
}
}

View File

@ -2,4 +2,5 @@
module.exports = Self => {
require('../methods/user-account/sync')(Self);
require('../methods/user-account/sync-by-id')(Self);
require('../methods/user-account/sync-all')(Self);
};

View File

@ -0,0 +1,52 @@
/**
* Base class for user synchronizators.
*
* @property {Array<Model>} $
* @property {Object} accountConfig
* @property {Object} mailConfig
*/
class SyncConnector {
/**
* Initalizes the connector.
*/
async init() {
return true;
}
/**
* Get users to synchronize.
*
* @param {Set} usersToSync Set where users are added
*/
async getUsers(usersToSync) {}
/**
* Synchronizes a user.
*
* @param {Object} info User information
* @param {String} userName The user name
* @param {String} password Thepassword
*/
async sync(info, userName, password) {}
/**
* Synchronizes user groups.
*
* @param {User} user Instace of user
* @param {String} userName The user name
*/
async syncGroups(user, userName) {}
/**
* Synchronizes roles.
*/
async syncRoles() {}
/**
* Deinitalizes the connector.
*/
async deinit() {}
}
SyncConnector.connectors = [];
module.exports = SyncConnector;

View File

@ -0,0 +1,57 @@
const SyncConnector = require('./sync-connector');
class SyncDb extends SyncConnector {
async sync(info, userName, password) {
let {$} = this;
let {user} = info;
if (user && user.active) {
let bcryptPassword = password
? $.User.hashPassword(password)
: user.bcryptPassword;
await $.Account.upsertWithWhere({id: user.id},
{bcryptPassword}
);
let dbUser = {
id: user.id,
username: userName,
email: user.email,
created: user.created,
updated: user.updated
};
if (bcryptPassword)
dbUser.password = bcryptPassword;
if (await $.user.exists(user.id))
await $.user.replaceById(user.id, dbUser);
else
await $.user.create(dbUser);
} else
await $.user.destroyAll({username: userName});
}
async getUsers(usersToSync) {
let accounts = await this.$.UserAccount.find({
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name'],
where: {active: true}
}
}
});
for (let account of accounts) {
let user = account.user();
if (!user) continue;
usersToSync.add(user.name);
}
}
}
SyncConnector.connectors.push(SyncDb);
module.exports = SyncDb;

View File

@ -0,0 +1,127 @@
const SyncConnector = require('./sync-connector');
require('./sync-db');
require('./sync-sip');
require('./sync-ldap');
require('./sync-samba');
module.exports = class SyncEngine {
async init($) {
let accountConfig = await $.AccountConfig.findOne({
fields: ['homedir', 'shell', 'idBase']
});
let mailConfig = await $.MailConfig.findOne({
fields: ['domain']
});
let connectors = [];
for (let ConnectorClass of SyncConnector.connectors) {
let connector = new ConnectorClass();
Object.assign(connector, {
engine: this,
$,
accountConfig,
mailConfig
});
if (!await connector.init()) continue;
connectors.push(connector);
}
Object.assign(this, {
connectors,
$,
accountConfig,
mailConfig
});
}
async deinit() {
for (let connector of this.connectors)
await connector.deinit();
}
async sync(userName, password, syncGroups) {
let {
$,
accountConfig,
mailConfig
} = this;
if (!userName) return;
userName = userName.toLowerCase();
// Skip conflicting users
if (['administrator', 'root'].indexOf(userName) >= 0)
return;
let user = await $.Account.findOne({
where: {name: userName},
fields: [
'id',
'nickname',
'email',
'lang',
'roleFk',
'sync',
'active',
'created',
'bcryptPassword',
'updated'
],
include: {
relation: 'roles',
scope: {
include: {
relation: 'inherits',
scope: {
fields: ['name']
}
}
}
}
});
let info = {
user,
hasAccount: false
};
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 = [];
for (let connector of this.connectors) {
try {
await connector.sync(info, userName, password);
if (syncGroups)
await connector.syncGroups(info, userName);
} catch (err) {
errs.push(err);
}
}
if (errs.length) throw errs[0];
}
async syncRoles() {
for (let connector of this.connectors)
await connector.syncRoles();
}
async getUsers() {
let usersToSync = new Set();
for (let connector of this.connectors)
await connector.getUsers(usersToSync);
return usersToSync;
}
};

View File

@ -0,0 +1,293 @@
const SyncConnector = require('./sync-connector');
const nthash = require('smbhash').nthash;
const ldap = require('./ldapjs-extra');
const crypto = require('crypto');
class SyncLdap extends SyncConnector {
async init() {
let ldapConfig = await this.$.LdapConfig.findOne({
fields: [
'server',
'rdn',
'password',
'userDn',
'groupDn'
]
});
if (!ldapConfig) return false;
let client = ldap.createClient({
url: ldapConfig.server
});
await client.bind(ldapConfig.rdn, ldapConfig.password);
Object.assign(this, {
ldapConfig,
client
});
return true;
}
async deinit() {
if (this.client)
await this.client.unbind();
}
async sync(info, userName, password) {
let {
ldapConfig,
client,
accountConfig
} = this;
let {user} = info;
let res = await client.search(ldapConfig.userDn, {
scope: 'sub',
attributes: ['userPassword', 'sambaNTPassword'],
filter: `&(uid=${userName})`
});
let oldUser;
await new Promise((resolve, reject) => {
res.on('error', reject);
res.on('searchEntry', e => oldUser = e.object);
res.on('end', resolve);
});
try {
let dn = `uid=${userName},${ldapConfig.userDn}`;
await client.del(dn);
} catch (e) {
if (e.name !== 'NoSuchObjectError') throw e;
}
if (!info.hasAccount) {
if (oldUser)
console.log(` -> '${userName}' removed from LDAP`);
return;
}
let nickname = user.nickname || userName;
let nameArgs = nickname.trim().split(' ');
let sn = nameArgs.length > 1
? nameArgs.splice(1).join(' ')
: '-';
let dn = `uid=${userName},${ldapConfig.userDn}`;
let newEntry = {
uid: userName,
objectClass: [
'inetOrgPerson',
'posixAccount',
'sambaSamAccount'
],
cn: nickname,
displayName: nickname,
givenName: nameArgs[0],
sn,
mail: info.corporateMail,
preferredLanguage: user.lang,
homeDirectory: `${accountConfig.homedir}/${userName}`,
loginShell: accountConfig.shell,
uidNumber: info.uidNumber,
gidNumber: accountConfig.idBase + user.roleFk,
sambaSID: '-'
};
if (password) {
let salt = crypto
.randomBytes(8)
.toString('base64');
let hash = crypto.createHash('sha1');
hash.update(password);
hash.update(salt, 'binary');
let digest = hash.digest('binary');
let ssha = Buffer
.from(digest + salt, 'binary')
.toString('base64');
Object.assign(newEntry, {
userPassword: `{SSHA}${ssha}`,
sambaNTPassword: nthash(password)
});
} else if (oldUser) {
Object.assign(newEntry, {
userPassword: oldUser.userPassword,
sambaNTPassword: oldUser.sambaNTPassword
});
}
for (let prop in newEntry) {
if (newEntry[prop] == null)
delete newEntry[prop];
}
await client.add(dn, newEntry);
}
async syncGroups(info, userName) {
let {
ldapConfig,
client
} = this;
let res = await client.search(ldapConfig.groupDn, {
scope: 'sub',
attributes: ['dn'],
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
});
let oldGroups = [];
await new Promise((resolve, reject) => {
res.on('error', reject);
res.on('searchEntry', e => oldGroups.push(e.object));
res.on('end', resolve);
});
let reqs = [];
for (let oldGroup of oldGroups) {
let change = new ldap.Change({
operation: 'delete',
modification: {memberUid: userName}
});
reqs.push(client.modify(oldGroup.dn, change));
}
await Promise.all(reqs);
if (!info.hasAccount) return;
reqs = [];
for (let role of info.user.roles()) {
let change = new ldap.Change({
operation: 'add',
modification: {memberUid: userName}
});
let roleName = role.inherits().name;
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
reqs.push(client.modify(dn, change));
}
await Promise.all(reqs);
}
async getUsers(usersToSync) {
let {
ldapConfig,
client
} = this;
let res = await client.search(ldapConfig.userDn, {
scope: 'sub',
attributes: ['uid'],
filter: `uid=*`
});
await new Promise((resolve, reject) => {
res.on('error', reject);
res.on('searchEntry', e => usersToSync.add(e.object.uid));
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);
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;
}

View File

@ -0,0 +1,125 @@
const SyncConnector = require('./sync-connector');
const ssh = require('node-ssh');
const ldap = require('./ldapjs-extra');
class SyncSamba extends SyncConnector {
async init() {
let sambaConfig = await this.$.SambaConfig.findOne({
fields: [
'host',
'sshUser',
'sshPassword',
'adUser',
'adPassword',
'userDn'
]
});
if (!sambaConfig) return false;
let client = new ssh.NodeSSH();
await client.connect({
host: sambaConfig.host,
username: sambaConfig.sshUser,
password: sambaConfig.sshPassword
});
let adClient = ldap.createClient({
url: `ldaps://${sambaConfig.host}:636`,
tlsOptions: {rejectUnauthorized: false}
});
Object.assign(this, {
sambaConfig,
client,
adClient
});
return true;
}
async deinit() {
if (!this.client) return;
await this.client.dispose();
await this.adClient.unbind();
}
async sync(info, userName, password) {
let {client} = this;
if (info.hasAccount) {
try {
await client.exec('samba-tool user create', [
userName,
'--uid-number', `${info.uidNumber}`,
'--mail-address', info.corporateMail,
'--random-password'
]);
await client.exec('samba-tool user setexpiry', [
userName,
'--noexpiry'
]);
await client.exec('mkhomedir_helper', [
userName,
'0027'
]);
} catch (e) {}
await client.exec('samba-tool user enable', [
userName
]);
if (password) {
await client.exec('samba-tool user setpassword', [
userName,
'--newpassword', password
]);
}
} else {
try {
await client.exec('samba-tool user disable', [
userName
]);
console.log(` -> '${userName}' disabled on Samba`);
} catch (e) {}
}
}
/**
* 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) {
let {
sambaConfig,
adClient
} = 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);
});
}
}
SyncConnector.connectors.push(SyncSamba);
module.exports = SyncSamba;

View File

@ -0,0 +1,15 @@
const SyncConnector = require('./sync-connector');
class SyncSip extends SyncConnector {
async sync(info, userName, password) {
if (!info.hasAccount || !password) return;
await this.$.Account.rawSql('CALL pbx.sip_setPassword(?, ?)',
[info.user.id, password]
);
}
}
SyncConnector.connectors.push(SyncSip);
module.exports = SyncSip;

View File

@ -15,3 +15,6 @@ import './basic-data';
import './mail-forwarding';
import './aliases';
import './roles';
import './ldap';
import './samba';
import './posix';

View File

@ -0,0 +1,95 @@
<vn-watcher
vn-id="watcher"
url="LdapConfigs"
data="$ctrl.config"
id-value="1"
form="form">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<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
label="Server"
ng-model="$ctrl.config.server"
rule="LdapConfig"
vn-focus>
</vn-textfield>
<vn-textfield
label="RDN"
ng-model="$ctrl.config.rdn"
rule="LdapConfig">
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.config.password"
type="password"
rule="LdapConfig">
</vn-textfield>
<vn-textfield
label="User DN"
ng-model="$ctrl.config.userDn"
rule="LdapConfig">
</vn-textfield>
<vn-textfield
label="Group DN"
ng-model="$ctrl.config.groupDn"
rule="LdapConfig">
</vn-textfield>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-button
label="Undo changes"
ng-if="watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
<vn-button
label="Synchronize now"
ng-if="watcher.hasData"
ng-click="$ctrl.onSynchronizeAll()">
</vn-button>
<vn-button
label="Synchronize user"
ng-if="watcher.hasData"
ng-click="syncUser.show()">
</vn-button>
</vn-button-bar>
<vn-submit
icon="save"
vn-tooltip="Save"
class="round"
fixed-bottom-right>
</vn-submit>
</form>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onUserSync()"
on-close="$ctrl.onPassClose()">
<tpl-body>
<vn-textfield
label="Username"
ng-model="$ctrl.syncUser">
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
info="If password is not specified, just user attributes are synchronized">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,30 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import UserError from 'core/lib/user-error';
export default class Controller extends Section {
onSynchronizeAll() {
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
this.$http.patch(`UserAccounts/syncAll`)
.then(() => this.vnApp.showSuccess(this.$t('Users synchronized!')));
}
onUserSync() {
if (!this.syncUser)
throw new UserError('Please enter the username');
let params = {password: this.syncPassword};
return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
}
onSyncClose() {
this.syncUser = '';
this.syncPassword = '';
}
}
ngModule.component('vnAccountLdap', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,16 @@
Enable synchronization: Habilitar sincronización
Server: Servidor
RDN: RDN
User DN: DN usuarios
Filter: Filtro
Group DN: DN grupos
Synchronize now: Sincronizar ahora
Synchronize user: Sincronizar usuario
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
Synchronizing in the background: Sincronizando en segundo plano
Users synchronized!: ¡Usuarios sincronizados!
Username: Nombre de usuario
Synchronize: Sincronizar
Please enter the username: Por favor introduce el nombre de usuario
User synchronized!: ¡Usuario sincronizado!

View File

@ -0,0 +1,69 @@
<vn-watcher
vn-id="watcher"
url="AccountConfigs"
data="$ctrl.config"
id-value="1"
form="form">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-textfield
label="Homedir"
ng-model="$ctrl.config.homedir"
rule="AccountConfig"
vn-focus>
</vn-textfield>
<vn-textfield
label="Shell"
ng-model="$ctrl.config.shell"
rule="AccountConfig">
</vn-textfield>
<vn-input-number
label="Id base"
ng-model="$ctrl.config.idBase"
rule="AccountConfig">
</vn-input-number>
<vn-horizontal>
<vn-input-number
label="Min"
ng-model="$ctrl.config.min"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Max"
ng-model="$ctrl.config.max"
rule="AccountConfig">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Warn"
ng-model="$ctrl.config.warn"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Inact"
ng-model="$ctrl.config.inact"
rule="AccountConfig">
</vn-input-number>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-button
label="Undo changes"
ng-if="watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
<vn-submit
icon="save"
vn-tooltip="Save"
class="round"
fixed-bottom-right>
</vn-submit>
</form>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {}
ngModule.component('vnAccountPosix', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,16 @@
Host: Host
RDN: RDN
Base DN: DN base
Password should be base64 encoded: La contraseña debe estar codificada en base64
Filter: Filtro
Group DN: DN grupos
Synchronize now: Sincronizar ahora
Synchronize user: Sincronizar usuario
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
Synchronizing in the background: Sincronizando en segundo plano
LDAP users synchronized: Usuarios LDAP sincronizados
Username: Nombre de usuario
Synchronize: Sincronizar
Please enter the username: Por favor introduce el nombre de usuario
User synchronized: Usuario sincronizado

View File

@ -9,6 +9,9 @@
{"state": "account.index", "icon": "face"},
{"state": "account.role", "icon": "group"},
{"state": "account.alias", "icon": "email"},
{"state": "account.posix", "icon": "accessibility"},
{"state": "account.ldap", "icon": "account_tree"},
{"state": "account.samba", "icon": "desktop_windows"},
{"state": "account.acl", "icon": "check"},
{"state": "account.connections", "icon": "share"}
],
@ -174,6 +177,24 @@
"state": "account.alias.card.users",
"component": "vn-alias-users",
"description": "Users"
}, {
"url": "/posix",
"state": "account.posix",
"component": "vn-account-posix",
"description": "Posix",
"acl": ["developer"]
}, {
"url": "/ldap",
"state": "account.ldap",
"component": "vn-account-ldap",
"description": "LDAP",
"acl": ["developer"]
}, {
"url": "/samba",
"state": "account.samba",
"component": "vn-account-samba",
"description": "Samba",
"acl": ["developer"]
}, {
"url": "/acl?q",
"state": "account.acl",

View File

@ -0,0 +1,70 @@
<vn-watcher
vn-id="watcher"
url="SambaConfigs"
data="$ctrl.config"
id-value="1"
form="form">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<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
label="Host"
ng-model="$ctrl.config.host"
rule="SambaConfig"
vn-focus>
</vn-textfield>
<vn-textfield
label="SSH user"
ng-model="$ctrl.config.sshUser"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="SSH password"
ng-model="$ctrl.config.sshPassword"
type="password"
rule="SambaConfig">
</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-card>
<vn-button-bar>
<vn-button
label="Undo changes"
ng-if="watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
<vn-submit
icon="save"
vn-tooltip="Save"
class="round"
fixed-bottom-right>
</vn-submit>
</form>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {}
ngModule.component('vnAccountSamba', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,7 @@
Enable synchronization: Habilitar sincronización
Host: Host
SSH user: Usuario SSH
SSH password: Contraseña SSH
AD user: Usuario AD
AD password: Contraseña AD
User DN: DN usuarios

View File

@ -1,12 +1,12 @@
module.exports = Self => {
Self.remoteMethod('getSummary', {
description: 'Updates the item taxes',
description: 'Return the claim summary',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
description: 'The claim id',
http: {source: 'path'}
}],
returns: {

View File

@ -56,7 +56,7 @@ describe('Update Claim', () => {
hasToPickUp: false
}
};
await app.models.Claim.updateClaim(ctx, newClaim.id,);
await app.models.Claim.updateClaim(ctx, newClaim.id);
let updatedClaim = await app.models.Claim.findById(newClaim.id);

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