Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 1992-goWhenOneResult

This commit is contained in:
Juan Ferrer 2020-03-16 18:33:39 +01:00
commit 56a21edeca
141 changed files with 6847 additions and 2651 deletions

View File

@ -1,46 +1,53 @@
{ {
"name": "Province", "name": "Province",
"description": "Provinces of every country", "description": "Provinces of every country",
"base": "VnModel", "base": "VnModel",
"options": { "options": {
"mysql": { "mysql": {
"table": "province" "table": "province"
} }
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
}, },
"name": { "properties": {
"type": "string", "id": {
"required": true "type": "Number",
} "id": true,
}, "description": "Identifier"
"relations": { },
"country": { "name": {
"type": "belongsTo", "type": "string",
"model": "Country", "required": true
"foreignKey": "countryFk" }
}, },
"warehouse": { "relations": {
"type": "belongsTo", "country": {
"model": "Warehouse", "type": "belongsTo",
"foreignKey": "warehouseFk" "model": "Country",
"foreignKey": "countryFk"
},
"warehouse": {
"type": "belongsTo",
"model": "Warehouse",
"foreignKey": "warehouseFk"
},
"zone": {
"type": "belongsTo",
"model": "Zone",
"foreignKey": "zoneFk"
}
}, },
"zone": { "scopes": {
"type": "belongsTo", "location": {
"model": "Zone", "include": {
"foreignKey": "zoneFk" "relation": "country"
} }
}, }
"acls": [ },
{ "acls": [
"accessType": "READ", {
"principalType": "ROLE", "accessType": "READ",
"principalId": "$everyone", "principalType": "ROLE",
"permission": "ALLOW" "principalId": "$everyone",
} "permission": "ALLOW"
] }
]
} }

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES ('EntryLog', '*', 'READ', 'ALLOW', 'ROLE', 'buyer');

View File

@ -0,0 +1,182 @@
DROP procedure IF EXISTS `vn`.`workerTimeControl_check`;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`workerTimeControl_check`(vUserFk INT, vTabletFk VARCHAR(100), vTimed DATETIME)
BEGIN
/**
* Verifica si el empleado puede fichar en el momento actual, si puede fichar llama a vn.workerTimeControlAdd
* @param vUserFk Identificador del trabajador
* @return Retorna si encuentra un problema 'odd','maxTimeWork','breakDay','breakWeek' ;
* En caso de tener algun problema retorna el primero que encuentra
*/
DECLARE vLastIn DATETIME ;
DECLARE vLastOut DATETIME ;
DECLARE vDayWorkMax INT;
DECLARE vDayBreak INT;
DECLARE vWeekBreak INT ;
DECLARE vWeekMaxBreak INT;
DECLARE vWeekScope INT;
DECLARE vWeekMaxScope INT;
DECLARE vDayStayMax INT;
DECLARE vAskInOut INT;
DECLARE vTimedWorked INT;
DECLARE vCalendarStateType VARCHAR(20) DEFAULT NULL;
DECLARE vDepartmentFk INT;
DECLARE vTo VARCHAR(50) DEFAULT NULL;
DECLARE vUserName VARCHAR(50) DEFAULT NULL;
DECLARE vBody VARCHAR(255) DEFAULT NULL;
IF (vTimed IS NULL) THEN
SET vTimed = NOW();
END IF;
SELECT dayBreak, weekBreak, weekScope, dayWorkMax, dayStayMax, weekMaxBreak, weekMaxScope, askInOut
INTO vDayBreak, vWeekBreak, vWeekScope, vDayWorkMax, vDayStayMax, vWeekMaxBreak, vWeekMaxScope, vAskInOut
FROM vn.workerTimeControlParams;
SELECT MAX(timed) INTO vLastIn
FROM vn.workerTimeControl
WHERE userFk = vUserFk AND
direction = 'in';
SELECT MAX(timed) INTO vLastOut
FROM vn.workerTimeControl
WHERE userFk = vUserFk AND
direction = 'out';
SELECT email INTO vTo
FROM vn.worker w
WHERE w.id = (SELECT bossFk FROM vn.worker WHERE id = vUserFk);
SELECT CONCAT(firstName,' ',lastName) INTO vUserName
FROM vn.worker w
WHERE w.id = vUserFk;
-- VERIFICAR CONTRATO EN VIGOR
IF (SELECT COUNT(*)
FROM postgresql.business b
JOIN postgresql.profile pr ON pr.profile_id = b.client_id
JOIN postgresql.person p ON p.person_id = pr.person_id
JOIN vn.worker w ON w.id = p.id_trabajador
WHERE w.userFk = vUserFk AND
b.date_start <= CURDATE() AND
IFNULL(b.date_end,CURDATE()) >= CURDATE()
) = 0 THEN
-- ENVIAMOS CORREO AL BOSSFK
SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"No hay un contrato en vigor") INTO vBody;
CALL vn.mail_insert(vTo,vTo,'error al fichar',vBody);
CALL util.throw("No hay un contrato en vigor");
END IF;
-- VERIFICAR DEPARTAMENTO
IF vTabletFk IS NOT NULL THEN
IF ( SELECT COUNT(*)
FROM vn.tabletDepartment td
JOIN vn.workerTimeControlUserInfo wtcu ON wtcu.departmentFk = td.departmentFk
WHERE td.tabletFk = vTabletFk AND wtcu.userFk = vUserFk
) = 0 THEN
-- ENVIAMOS CORREO AL BOSSFK
SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"No perteneces a este departamento.") INTO vBody;
CALL vn.mail_insert(vTo,vTo,'error al fichar',vBody);
CALL util.throw("No perteneces a este departamento.");
END IF;
END IF;
SELECT IFNULL(dayBreak, vDayBreak) INTO vDayBreak
FROM postgresql.business b
JOIN postgresql.profile pr ON pr.profile_id = b.client_id
JOIN postgresql.person p ON p.person_id = pr.person_id
JOIN postgresql. business_labour bl ON b.business_id = bl.business_id
JOIN postgresql.professional_category pc ON bl.professional_category_id = pc.professional_category_id
WHERE p.id_trabajador = vUserFk AND
b.date_start <= DATE(vTimed) AND
IFNULL(b.date_end, DATE(vTimed)) >= DATE(vTimed);
-- VERIFICAR DESCANSO DIARIO
-- 12 / 9 horas dependiendo del valor de vDayBreak
IF UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(vLastOut) < vDayBreak THEN
-- ENVIAMOS CORREO AL BOSSFK
SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Descansos ", FORMAT(vDayBreak/3600,0) ," h") INTO vBody;
CALL vn.mail_insert(vTo,vTo,'error al fichar',vBody);
CALL util.throw(CONCAT("Descansos ", FORMAT(vDayBreak/3600,0) ," h"));
END IF;
-- VERIFICAR FICHADAS IMPARES DEL ÃÆÅ¡LTIMO DÃÆÍA QUE SE FICHÃÆââ¬Å“
IF (SELECT MOD(COUNT(*),2) -- <>0
FROM vn.workerTimeControl
WHERE userFk = vUserFk AND
timed >= vLastIn
) THEN
-- ENVIAMOS CORREO AL BOSSFK
SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Dias con fichadas impares") INTO vBody;
CALL vn.mail_insert(vTo,vTo,'error al fichar',vBody);
CALL util.throw("Dias con fichadas impares");
END IF;
-- VERIFICAR VACACIONES
SELECT cs.type INTO vCalendarStateType
FROM postgresql.calendar_employee ce
JOIN postgresql.business b USING(business_id)
JOIN postgresql.profile pr ON pr.profile_id = b.client_id
JOIN postgresql.person p ON p.person_id = pr.person_id
JOIN postgresql.calendar_state cs USING(calendar_state_id)
JOIN vn.worker w ON w.id = p.id_trabajador
WHERE ce.date = CURDATE() AND
cs.isAllowedToWork = FALSE AND
w.userFk = vUserFk
LIMIT 1;
IF(LENGTH(vCalendarStateType)) THEN
-- ENVIAMOS CORREO AL BOSSFK
SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Vacaciones") INTO vBody;
CALL vn.mail_insert(vTo,vTo,'error al fichar',vBody);
CALL util.throw(vCalendarStateType);
END IF;
-- VERIFICAR DESCANSO SEMANAL
SET @vHasBreakWeek:= FALSE;
SET @vLastTimed:= UNIX_TIMESTAMP((vTimed - INTERVAL vWeekScope SECOND));
DROP TEMPORARY TABLE IF EXISTS tmp.trash;
CREATE TEMPORARY TABLE tmp.trash
SELECT IF(vWeekBreak-(UNIX_TIMESTAMP(timed)-@vLastTimed) <= 0, @vHasBreakWeek:=TRUE, TRUE) alias,
@vLastTimed:= UNIX_TIMESTAMP(timed)
FROM workerTimeControl
WHERE timed>= (vTimed - INTERVAL vWeekScope SECOND) AND
userFk= vUserFk AND
direction IN ('in','out')
ORDER BY timed ASC;
IF UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(vLastOut) < vWeekBreak AND @vHasBreakWeek = FALSE THEN -- REVISA SI EL DESCANSO SE HA REALIZADO DESPUÃÆââ¬Â°S DE LA ÃÆÅ¡LTIMA FICHADA
SET @vHasBreakWeek:= FALSE;
SET @vLastTimed:= UNIX_TIMESTAMP((vTimed - INTERVAL vWeekMaxScope SECOND));
DROP TEMPORARY TABLE tmp.trash;
CREATE TEMPORARY TABLE tmp.trash
SELECT IF(vWeekMaxBreak-(UNIX_TIMESTAMP(timed)-@vLastTimed) <= 0, @vHasBreakWeek:=TRUE, TRUE) alias,
@vLastTimed:= UNIX_TIMESTAMP(timed)
FROM workerTimeControl
WHERE timed>= (vTimed - INTERVAL vWeekMaxScope SECOND) AND
userFk= vUserFk AND
direction IN ('in','out')
ORDER BY timed ASC;
IF UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(vLastOut) < vWeekMaxBreak AND @vHasBreakWeek = FALSE THEN -- REVISA SI EL DESCANSO SE HA REALIZADO DESPUÃÆââ¬Â°S DE LA ÃÆÅ¡LTIMA FICHADA
-- ENVIAMOS CORREO AL BOSSFK
SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Descansos ", FORMAT(vWeekMaxBreak/3600,0) ," h") INTO vBody;
CALL vn.mail_insert(vTo,vTo,'error al fichar',vBody);
CALL util.throw(CONCAT( "Descansos ", FORMAT(vWeekMaxBreak/3600,0) ," h"));
END IF;
-- ENVIAMOS CORREO AL BOSSFK
SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Descansos ", FORMAT(vWeekBreak/3600,0) ," h") INTO vBody;
CALL vn.mail_insert(vTo,vTo,'error al fichar',vBody);
CALL util.warn(CONCAT( "Descansos ", FORMAT(vWeekBreak/3600,0) ," h"));
END IF;
DROP TEMPORARY TABLE tmp.trash;
-- Preguntar direcciÃÆón de la fichada
IF UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(vLastIn) >= vAskInOut AND (SELECT MOD(COUNT(*),2)
FROM vn.workerTimeControl WHERE userFk = vUserFk AND timed >= vLastIn) THEN
CALL util.warn("AskInOut");
END IF ;
END$$
DELIMITER ;

View File

@ -1,2 +0,0 @@
ALTER TABLE `vn`.`zoneEvent`
ADD COLUMN m3Max DECIMAL(10,2) UNSIGNED NULL DEFAULT NULL AFTER bonus;

File diff suppressed because one or more lines are too long

View File

@ -39,8 +39,8 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`
FROM `account`.`role` WHERE id <> 20 FROM `account`.`role` WHERE id <> 20
ORDER BY id; ORDER BY id;
INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `userFk`, `bossFk`) INSERT INTO `vn`.`worker`(`id`,`code`, `firstName`, `lastName`, `userFk`, `bossFk`, `email`)
SELECT id,UPPER(LPAD(role, 3, '0')), name, name, id, 9 SELECT id,UPPER(LPAD(role, 3, '0')), name, name, id, 9, 'test@nightmare.es'
FROM `vn`.`user`; FROM `vn`.`user`;
UPDATE `vn`.`worker` SET bossFk = NULL WHERE id = 20; UPDATE `vn`.`worker` SET bossFk = NULL WHERE id = 20;
@ -68,13 +68,13 @@ INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,
(111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'), (111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'),
(112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'); (112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en');
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`) INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `email`)
VALUES VALUES
(106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106), (106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106, 'test@nightmare.es'),
(107, 'ANT', 'Hank' , 'Pym' , 107, 19, 432978107), (107, 'ANT', 'Hank' , 'Pym' , 107, 19, 432978107, 'test@nightmare.es'),
(108, 'DCX', 'Charles' , 'Xavier', 108, 19, 432978108), (108, 'DCX', 'Charles' , 'Xavier', 108, 19, 432978108, 'test@nightmare.es'),
(109, 'HLK', 'Bruce' , 'Banner', 109, 19, 432978109), (109, 'HLK', 'Bruce' , 'Banner', 109, 19, 432978109, 'test@nightmare.es'),
(110, 'JJJ', 'Jessica' , 'Jones' , 110, 19, 432978110); (110, 'JJJ', 'Jessica' , 'Jones' , 110, 19, 432978110, 'test@nightmare.es');
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`) INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`)
VALUES VALUES
@ -197,14 +197,16 @@ INSERT INTO `vn`.`town`(`id`, `name`, `provinceFk`)
(1, 'Valencia', 1), (1, 'Valencia', 1),
(2, 'Silla', 1), (2, 'Silla', 1),
(3, 'Algemesi', 1), (3, 'Algemesi', 1),
(4, 'Alzira', 1); (4, 'Alzira', 1),
(5, 'Quito', 5);
INSERT INTO `vn`.`postCode`(`code`, `townFk`, `geoFk`) INSERT INTO `vn`.`postCode`(`code`, `townFk`, `geoFk`)
VALUES VALUES
('46000', 1, 6), ('46000', 1, 6),
('46460', 2, 6), ('46460', 2, 6),
('46680', 3, 6), ('46680', 3, 6),
('46600', 4, 7); ('46600', 4, 7),
('EC170150', 5, 8);
INSERT INTO `vn`.`clientType`(`id`, `code`, `type`) INSERT INTO `vn`.`clientType`(`id`, `code`, `type`)
VALUES VALUES
@ -554,30 +556,30 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `workerFk`, `created`) INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `workerFk`, `created`)
VALUES VALUES
(1, 16, 5 , DATE_ADD(CURDATE(), INTERVAL -1 MONTH)), (1, 16, 5 , DATE_ADD(NOW(), INTERVAL -1 MONTH)),
(2, 16, 5 , DATE_ADD(CURDATE(), INTERVAL -1 MONTH)), (2, 16, 5 , DATE_ADD(NOW(), INTERVAL -1 MONTH)),
(3, 16, 5 , DATE_ADD(CURDATE(), INTERVAL -2 MONTH)), (3, 16, 5 , DATE_ADD(NOW(), INTERVAL -2 MONTH)),
(4, 16, 5 , DATE_ADD(CURDATE(), INTERVAL -3 MONTH)), (4, 16, 5 , DATE_ADD(NOW(), INTERVAL -3 MONTH)),
(5, 16, 18, DATE_ADD(CURDATE(), INTERVAL -4 MONTH)), (5, 16, 18, DATE_ADD(NOW(), INTERVAL -4 MONTH)),
(6, 16, 18, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)), (6, 16, 18, DATE_ADD(NOW(), INTERVAL -1 MONTH)),
(7, 10, 18, CURDATE()), (7, 10, 18, NOW()),
(8, 5, 19, CURDATE()), (8, 5, 19, NOW()),
(9, 5, 19, CURDATE()), (9, 5, 19, NOW()),
(10, 5, 19, CURDATE()), (10, 5, 19, NOW()),
(11, 3, 19, CURDATE()), (11, 3, 19, NOW()),
(12, 3, 19, CURDATE()), (12, 3, 19, NOW()),
(13, 3, 19, CURDATE()), (13, 3, 19, NOW()),
(14, 3, 19, CURDATE()), (14, 3, 19, NOW()),
(15, 3, 19, CURDATE()), (15, 3, 19, NOW()),
(16, 3, 19, CURDATE()), (16, 3, 19, NOW()),
(17, 3, 19, CURDATE()), (17, 3, 19, NOW()),
(18, 3, 19, CURDATE()), (18, 3, 19, NOW()),
(19, 17, 19, CURDATE()), (19, 17, 19, NOW()),
(20, 1, 19, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)), (20, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
(21, 1, 19, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)), (21, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
(22, 1, 19, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)), (22, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
(23, 16, 21, CURDATE()), (23, 16, 21, NOW()),
(24, 16, 21, CURDATE()); (24, 16, 21, NOW());
INSERT INTO `vn`.`stowaway`(`id`, `shipFk`, `created`) INSERT INTO `vn`.`stowaway`(`id`, `shipFk`, `created`)
VALUES VALUES
@ -1580,6 +1582,13 @@ INSERT INTO `postgresql`.`business_labour`(`business_id`, `notes`, `department_i
SELECT b.business_id, NULL, 23, 1, 0, 1, 1, 1, 1 SELECT b.business_id, NULL, 23, 1, 0, 1, 1, 1, 1
FROM `postgresql`.`business` `b`; FROM `postgresql`.`business` `b`;
UPDATE `postgresql`.`business_labour` bl
JOIN `postgresql`.`business` b ON b.business_id = bl.business_id
JOIN `postgresql`.`profile` pr ON pr.profile_id = b.client_id
JOIN `postgresql`.`person` p ON p.person_id = pr.person_id
SET bl.`professional_category_id` = 31
WHERE p.`Id_trabajador` = 110;
INSERT INTO `postgresql`.`media`(`media_id`, `media_type_id`, `value`, `sort`) INSERT INTO `postgresql`.`media`(`media_id`, `media_type_id`, `value`, `sort`)
VALUES VALUES
(1, 10, 600123321, 0), (1, 10, 600123321, 0),
@ -1677,12 +1686,12 @@ INSERT INTO `vn`.`zoneIncluded` (`zoneFk`, `geoFk`, `isIncluded`)
INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `dated`) INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `dated`)
VALUES VALUES
(1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=2, 2, 9 ) - DAYOFWEEK(CURDATE())) DAY)), (1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=2, 2, 9) - DAYOFWEEK(CURDATE())) DAY)),
(1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=3, 3, 10) - DAYOFWEEK(CURDATE())) DAY)), (1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=3, 3, 10) - DAYOFWEEK(CURDATE())) DAY)),
(1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=4, 4, 11) - DAYOFWEEK(CURDATE())) DAY)), (1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=4, 4, 11) - DAYOFWEEK(CURDATE())) DAY)),
(1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=5, 5, 12) - DAYOFWEEK(CURDATE())) DAY)), (1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=5, 5, 12) - DAYOFWEEK(CURDATE())) DAY)),
(1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=6, 6, 13) - DAYOFWEEK(CURDATE())) DAY)), (1, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=6, 6, 13) - DAYOFWEEK(CURDATE())) DAY)),
(2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=2, 2, 9 ) - DAYOFWEEK(CURDATE())) DAY)), (2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=2, 2, 9) - DAYOFWEEK(CURDATE())) DAY)),
(2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=3, 3, 10) - DAYOFWEEK(CURDATE())) DAY)), (2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=3, 3, 10) - DAYOFWEEK(CURDATE())) DAY)),
(2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=4, 4, 11) - DAYOFWEEK(CURDATE())) DAY)), (2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=4, 4, 11) - DAYOFWEEK(CURDATE())) DAY)),
(2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=5, 5, 12) - DAYOFWEEK(CURDATE())) DAY)), (2, 'day', DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=5, 5, 12) - DAYOFWEEK(CURDATE())) DAY)),
@ -1951,9 +1960,9 @@ INSERT INTO `vn`.`queuePriority`(`id`, `priority`)
(2, 'Normal'), (2, 'Normal'),
(3, 'Baja'); (3, 'Baja');
INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`) INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`, `weekMaxBreak`, `weekMaxScope`, `askInOut`)
VALUES VALUES
(1, 43200, 129600, 734400, 43200, 50400); (1, 43200, 129600, 734400, 43200, 50400, 259200, 1296000, 36000);
INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11'); INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11');
@ -1973,11 +1982,21 @@ INSERT INTO `vn`.`travelThermograph`(`thermographFk`, `created`, `warehouseFk`,
('138350-0', DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 'WARM', NULL, 5), ('138350-0', DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 'WARM', NULL, 5),
('138350-0', CURDATE(), 1, NULL, 'COOL', NULL, NULL); ('138350-0', CURDATE(), 1, NULL, 'COOL', NULL, NULL);
REPLACE INTO `vn`.`incoterms` (`code`, `name`) REPLACE INTO `vn`.`incoterms`(`code`, `name`)
VALUES VALUES
('FAS', 'Free Alongside Ship'); ('FAS', 'Free Alongside Ship');
REPLACE INTO `vn`.`customsAgent` (`id`, `fiscalName`, `street`, `nif`, `phone`, `email`) REPLACE INTO `vn`.`customsAgent`(`id`, `fiscalName`, `street`, `nif`, `phone`, `email`)
VALUES VALUES
(1, 'Agent one', '1007 Mountain Drive, Gotham', 'N1111111111', '111111111', 'agentone@gotham.com'), (1, 'Agent one', '1007 Mountain Drive, Gotham', 'N1111111111', '111111111', 'agentone@gotham.com'),
(2, 'Agent two', '1007 Mountain Drive, Gotham', 'N2222222222', '222222222', 'agenttwo@gotham.com'); (2, 'Agent two', '1007 Mountain Drive, Gotham', 'N2222222222', '222222222', 'agenttwo@gotham.com');
INSERT INTO `vn`.`tabletDepartment`(`tabletFk`, `departmentFk`)
VALUES
(1, 23),
(2, 1);
INSERT INTO `vn`.`tablet`(`uuid`, `name`, `place`, `macwifi`)
VALUES
('1', 'TEST', 'ON THE FIXTURES', '0'),
('2', 'DEV', 'OTHER TABLET', '0');

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,6 @@ describe('ticket ticketCreateWithUser()', () => {
let ticketResult = result[ticketResultIndex][0]; let ticketResult = result[ticketResultIndex][0];
expect(ticketResult.id).toBeGreaterThan(21); expect(ticketResult.id).toBeGreaterThan(21);
expect(ticketResult.clientFk).toEqual(params.clientFk); expect(ticketResult.clientFk).toEqual(params.clientFk);
expect(ticketResult.warehouseFk).toEqual(params.warehouseFk); expect(ticketResult.warehouseFk).toEqual(params.warehouseFk);

View File

@ -0,0 +1,40 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('timeBusiness_calculateByUser()', () => {
it('should return the expected hours for today', async() => {
let start = new Date();
start.setHours(0, 0, 0, 0);
let end = new Date();
end.setHours(0, 0, 0, 0);
let stmts = [];
let stmt;
stmts.push('START TRANSACTION');
let params = {
workerID: 106,
start: start,
end: end
};
stmt = new ParameterizedSQL('CALL vn.timeBusiness_calculateByUser(?, ?, ?)', [
params.workerID,
params.start,
params.end
]);
stmts.push(stmt);
let tableIndex = stmts.push('SELECT * FROM tmp.timeBusinessCalculate') - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let [timeBusinessCalculateTable] = result[tableIndex];
expect(timeBusinessCalculateTable.timeBusinessSeconds).toEqual(28800);
});
});

View File

@ -0,0 +1,107 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('timeControl_calculateByUser()', () => {
it(`should return today's worked hours`, async() => {
let start = new Date();
start.setHours(0, 0, 0, 0);
start.setDate(start.getDate() - 1);
let end = new Date();
end.setHours(0, 0, 0, 0);
end.setDate(end.getDate() + 1);
let stmts = [];
let stmt;
stmts.push('START TRANSACTION');
stmts.push(`
DROP TEMPORARY TABLE IF EXISTS
tmp.timeControlCalculate,
tmp.timeBusinessCalculate
`);
let params = {
workerID: 106,
start: start,
end: end
};
stmt = new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [
params.workerID,
params.start,
params.end
]);
stmts.push(stmt);
let tableIndex = stmts.push('SELECT * FROM tmp.timeControlCalculate') - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let [timeControlCalculateTable] = result[tableIndex];
expect(timeControlCalculateTable.timeWorkSeconds).toEqual(29400);
});
it(`should return the worked hours between last sunday and monday`, async() => {
let lastSunday = new Date();
let daysSinceSunday = lastSunday.getDay();
if (daysSinceSunday === 0) // this means today is sunday but you need the previous sunday :)
daysSinceSunday = 7;
lastSunday.setHours(23, 0, 0, 0);
lastSunday.setDate(lastSunday.getDate() - daysSinceSunday);
let monday = new Date();
let daysSinceMonday = daysSinceSunday - 1; // aiming for monday (today could be monday)
monday.setHours(7, 0, 0, 0);
monday.setDate(monday.getDate() - daysSinceMonday);
let stmts = [];
let stmt;
stmts.push('START TRANSACTION');
stmts.push(`
DROP TEMPORARY TABLE IF EXISTS
tmp.timeControlCalculate,
tmp.timeBusinessCalculate
`);
const workerID = 107;
stmt = new ParameterizedSQL(`
INSERT INTO vn.workerTimeControl(userFk, timed, manual, direction)
VALUES
(?, ?, 1, 'in'),
(?, ?, 1, 'out')
`, [
workerID,
lastSunday,
workerID,
monday
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [
workerID,
lastSunday,
monday
]);
stmts.push(stmt);
let tableIndex = stmts.push('SELECT * FROM tmp.timeControlCalculate') - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let [timeControlCalculateTable] = result[tableIndex];
expect(timeControlCalculateTable.timeWorkSeconds).toEqual(30000);
});
});

View File

@ -0,0 +1,587 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('worker workerTimeControl_check()', () => {
it(`should throw an error if the worker can't sign on that tablet`, async() => {
let stmts = [];
let stmt;
const workerId = 110;
const tabletId = 2;
let err;
stmts.push('START TRANSACTION');
try {
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
await app.models.Worker.rawStmt(sql);
} catch (e) {
err = e;
}
expect(err.sqlMessage).toEqual('No perteneces a este departamento.');
});
it('should check that the worker can sign on that tablet', async() => {
let stmts = [];
let stmt;
const workerId = 110;
const tabletId = 1;
let err;
stmts.push('START TRANSACTION');
try {
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
await app.models.Worker.rawStmt(sql);
} catch (e) {
err = e;
}
expect(err).not.toBeDefined();
});
it('should throw an error if the worker with a special category has not finished the 9h break', async() => {
// dayBreak to 9h in postgresql.professional_category
const workerId = 110;
const tabletId = 1;
let stmts = [];
let stmt;
let sql;
let error;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-17,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-32399,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
error = e;
}
expect(error.sqlMessage).toEqual('Descansos 9 h');
});
it('should check f the worker with a special category has finished the 9h break', async() => {
// dayBreak to 9h in postgresql.professional_category
const workerId = 110;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-17,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-32401,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err).not.toBeDefined();
});
it('should throw an error if the worker has not finished the 12h break', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
let sql;
let error;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-43199,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
error = e;
}
expect(error.sqlMessage).toEqual('Descansos 12 h');
});
it('should throw an error if the worker has finished the 12h break', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-43201,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err).not.toBeDefined();
});
it('should throw an error if the worker has odd entry records', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in")`, [
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('Dias con fichadas impares');
});
it('should throw an error if the worker try to sign on a holiday day', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO postgresql.calendar_employee(business_id,calendar_state_id,date)
VALUES
(?,1,CURDATE())`, [
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('Holidays');
});
it('should throw an error if the worker try to sign with your contract ended', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`UPDATE postgresql.business SET date_end=DATE_ADD(CURDATE(), INTERVAL -1 DAY) WHERE business_id=?`, [
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('No hay un contrato en vigor');
});
it('should throw an error if the worker has not finished the 36h weekly break', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
stmts.push('SET @warn := NULL');
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-120,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-112,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-144,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-136,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-168,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-160,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-192,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-184,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
let warningMessageIndex = stmts.push('SELECT @warn AS warning') - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Worker.rawStmt(sql);
expect(result[warningMessageIndex][0].warning).toEqual('Descansos 36 h');
});
it('should check if the worker has finished the 36h weekly break', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
stmts.push('SET @warn := NULL');
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let warningMessageIndex = stmts.push('SELECT @warn AS warning') - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Worker.rawStmt(sql);
expect(result[warningMessageIndex][0].warning).toBe(null);
});
it('should throw an error if the worker has not finished the 72h biweekly break', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-120,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-112,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-144,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-136,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-168,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-160,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-192,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-184,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-216,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-208,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-240,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-232,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-264,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-256,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-289,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-280,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
stmts.push('SELECT @warn AS warning') - 1;
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('Descansos 72 h');
});
it('should check if the worker has finished the 72h biweekly break', async() => {
const workerId = 109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-120,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-112,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-144,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-136,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-168,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-160,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-192,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-184,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-216,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-208,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-240,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-232,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-264,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-256,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-288,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-280,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
stmts.push('SELECT @warn AS warning') - 1;
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err).not.toBeDefined();
});
});

View File

@ -44,8 +44,8 @@ export default {
taxNumber: 'vn-client-create vn-textfield[ng-model="$ctrl.client.fi"]', taxNumber: 'vn-client-create vn-textfield[ng-model="$ctrl.client.fi"]',
socialName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.socialName"]', socialName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.socialName"]',
street: 'vn-client-create vn-textfield[ng-model="$ctrl.client.street"]', street: 'vn-client-create vn-textfield[ng-model="$ctrl.client.street"]',
postcode: 'vn-client-create vn-textfield[ng-model="$ctrl.client.postcode"]', postcode: 'vn-client-create vn-datalist[ng-model="$ctrl.client.postcode"]',
city: 'vn-client-create vn-textfield[ng-model="$ctrl.client.city"]', city: 'vn-client-create vn-datalist[ng-model="$ctrl.client.city"]',
province: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.provinceFk"]', province: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.provinceFk"]',
country: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.countryFk"]', country: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.countryFk"]',
userName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.userName"]', userName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.userName"]',
@ -76,8 +76,8 @@ export default {
equalizationTaxCheckbox: 'vn-client-fiscal-data vn-check[ng-model="$ctrl.client.isEqualizated"]', equalizationTaxCheckbox: 'vn-client-fiscal-data vn-check[ng-model="$ctrl.client.isEqualizated"]',
acceptPropagationButton: '.vn-confirm.shown button[response=accept]', acceptPropagationButton: '.vn-confirm.shown button[response=accept]',
address: 'vn-client-fiscal-data vn-textfield[ng-model="$ctrl.client.street"]', address: 'vn-client-fiscal-data vn-textfield[ng-model="$ctrl.client.street"]',
postcode: 'vn-client-fiscal-data vn-textfield[ng-model="$ctrl.client.postcode"]', postcode: 'vn-client-fiscal-data vn-datalist[ng-model="$ctrl.client.postcode"]',
city: 'vn-client-fiscal-data vn-textfield[ng-model="$ctrl.client.city"]', city: 'vn-client-fiscal-data vn-datalist[ng-model="$ctrl.client.city"]',
province: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.provinceFk"]', province: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.provinceFk"]',
country: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.countryFk"]', country: 'vn-client-fiscal-data vn-autocomplete[ng-model="$ctrl.client.countryFk"]',
activeCheckbox: 'vn-client-fiscal-data vn-check[label="Active"]', activeCheckbox: 'vn-client-fiscal-data vn-check[label="Active"]',
@ -114,8 +114,8 @@ export default {
defaultCheckbox: 'vn-check[label="Default"]', defaultCheckbox: 'vn-check[label="Default"]',
consignee: 'vn-textfield[ng-model="$ctrl.address.nickname"]', consignee: 'vn-textfield[ng-model="$ctrl.address.nickname"]',
streetAddress: 'vn-textfield[ng-model="$ctrl.address.street"]', streetAddress: 'vn-textfield[ng-model="$ctrl.address.street"]',
postcode: 'vn-textfield[ng-model="$ctrl.address.postalCode"]', postcode: 'vn-datalist[ng-model="$ctrl.address.postalCode"]',
city: 'vn-textfield[ng-model="$ctrl.address.city"]', city: 'vn-datalist[ng-model="$ctrl.address.city"]',
province: 'vn-autocomplete[ng-model="$ctrl.address.provinceId"]', province: 'vn-autocomplete[ng-model="$ctrl.address.provinceId"]',
agency: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeId"]', agency: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeId"]',
phone: 'vn-textfield[ng-model="$ctrl.address.phone"]', phone: 'vn-textfield[ng-model="$ctrl.address.phone"]',
@ -366,7 +366,7 @@ export default {
ticketsIndex: { ticketsIndex: {
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]', openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"]', advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"]',
newTicketButton: 'vn-ticket-index > a', newTicketButton: 'vn-ticket-index a',
searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr', searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr',
searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)',
@ -525,6 +525,7 @@ export default {
}, },
ticketLog: { ticketLog: {
firstTD: 'vn-ticket-log vn-table vn-td:nth-child(1)',
logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]', logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]',
changedBy: 'vn-ticket-log > vn-log vn-tr:nth-child(1) > vn-td:nth-child(2) > span', changedBy: 'vn-ticket-log > vn-log vn-tr:nth-child(1) > vn-td:nth-child(2) > span',
actionTaken: 'vn-ticket-log > vn-log vn-td:nth-child(1) > div > div:nth-child(3) > span.value', actionTaken: 'vn-ticket-log > vn-log vn-td:nth-child(1) > div > div:nth-child(3) > span.value',
@ -823,7 +824,7 @@ export default {
travelThermograph: { travelThermograph: {
add: 'vn-travel-thermograph-index vn-float-button[icon="add"]', add: 'vn-travel-thermograph-index vn-float-button[icon="add"]',
thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]', thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]',
uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="attach_file"]', uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="icon-attach"]',
createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr', createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr',
upload: 'vn-travel-thermograph-create button[type=submit]' upload: 'vn-travel-thermograph-create button[type=submit]'
}, },

View File

@ -87,7 +87,7 @@ describe('Client create path', async() => {
.waitToGetProperty(selectors.createClientView.country, 'value'); .waitToGetProperty(selectors.createClientView.country, 'value');
expect(clientCity).toEqual('Valencia'); expect(clientCity).toEqual('Valencia');
expect(clientProvince).toEqual('Province one'); expect(clientProvince).toContain('Province one');
expect(clientCountry).toEqual('España'); expect(clientCountry).toEqual('España');
}); });

View File

@ -198,11 +198,10 @@ describe('Client Edit fiscalData path', () => {
expect(result).toEqual('Valencia'); expect(result).toEqual('Valencia');
}); });
it(`should confirm the province have been autocompleted`, async() => { it(`should confirm the province have been autocompleted`, async() => {
const result = await page.waitToGetProperty(selectors.clientFiscalData.province, 'value'); const result = await page.waitToGetProperty(selectors.clientFiscalData.province, 'value');
expect(result).toEqual('Province one'); expect(result).toContain('Province one');
}); });
it('should confirm the country have been autocompleted', async() => { it('should confirm the country have been autocompleted', async() => {

View File

@ -25,9 +25,7 @@ describe('Client Add address path', () => {
it('should receive an error after clicking save button as consignee, street and town fields are empty', async() => { it('should receive an error after clicking save button as consignee, street and town fields are empty', async() => {
await page.waitToClick(selectors.clientAddresses.defaultCheckbox); await page.waitToClick(selectors.clientAddresses.defaultCheckbox);
await page.autocompleteSearch(selectors.clientAddresses.province, 'Province five'); await page.write(selectors.clientAddresses.postcode, 'EC170150');
await page.write(selectors.clientAddresses.city, 'Valencia');
await page.write(selectors.clientAddresses.postcode, '46000');
await page.autocompleteSearch(selectors.clientAddresses.agency, 'Entanglement'); await page.autocompleteSearch(selectors.clientAddresses.agency, 'Entanglement');
await page.write(selectors.clientAddresses.phone, '999887744'); await page.write(selectors.clientAddresses.phone, '999887744');
await page.write(selectors.clientAddresses.mobileInput, '999887744'); await page.write(selectors.clientAddresses.mobileInput, '999887744');
@ -37,6 +35,16 @@ describe('Client Add address path', () => {
expect(result).toEqual('Some fields are invalid'); expect(result).toEqual('Some fields are invalid');
}); });
it('should confirm that the city and province are propertly filled', async() => {
const city = await page
.waitToGetProperty(selectors.clientAddresses.city, 'value');
const province = await page
.waitToGetProperty(selectors.clientAddresses.province, 'value');
expect(city).toEqual('Quito');
expect(province).toContain('Province five');
});
it(`should receive an error after clicking save button as consignee, incoterms and customsAgent are empty`, async() => { it(`should receive an error after clicking save button as consignee, incoterms and customsAgent are empty`, async() => {
await page.write(selectors.clientAddresses.consignee, 'Bruce Bunner'); await page.write(selectors.clientAddresses.consignee, 'Bruce Bunner');

View File

@ -35,7 +35,7 @@ describe('Client balance path', () => {
await page.clearInput(selectors.globalItems.userConfigThirdAutocomplete); await page.clearInput(selectors.globalItems.userConfigThirdAutocomplete);
let result = await page.waitForLastSnackbar(); let result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!'); expect(result).toContain('Data saved!');
}); });
it('should click the new payment button', async() => { it('should click the new payment button', async() => {
@ -63,7 +63,6 @@ describe('Client balance path', () => {
let firstBalanceLine = await page let firstBalanceLine = await page
.waitToGetProperty(selectors.clientBalance.firstBalanceLine, 'innerText'); .waitToGetProperty(selectors.clientBalance.firstBalanceLine, 'innerText');
expect(company).toEqual('VNL'); expect(company).toEqual('VNL');
expect(firstBalanceLine).toContain('0.00'); expect(firstBalanceLine).toContain('0.00');
}); });

View File

@ -0,0 +1,65 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Ticket log path', () => {
let browser;
let page;
const ticketId = '5';
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
});
afterAll(async() => {
await browser.close();
});
it('should navigate to the target ticket notes section', async() => {
await page.loginAndModule('employee', 'ticket');
await page.accessToSearchResult(ticketId);
await page.accessToSection('ticket.card.observation');
let url = await page.expectURL('/observation');
expect(url).toBe(true);
});
it('should create a new note for the test', async() => {
await page.waitToClick(selectors.ticketNotes.addNoteButton);
await page.autocompleteSearch(selectors.ticketNotes.firstNoteType, 'observation one');
await page.write(selectors.ticketNotes.firstDescription, 'description');
await page.waitToClick(selectors.ticketNotes.submitNotesButton);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it('should navigate to the log section', async() => {
await page.accessToSection('ticket.card.log');
let url = await page.expectURL('/log');
expect(url).toBe(true);
});
it('should set the viewport width to 1920 to see the table full width', async() => {
await page.setViewport({
width: 1920,
height: 0,
});
const result = await page.waitToGetProperty(selectors.ticketLog.firstTD, 'innerText');
expect(result.length).not.toBeGreaterThan('20');
});
it('should set the viewport width to 800 to see the table shrink and move data to the 1st column', async() => {
await page.setViewport({
width: 800,
height: 0,
});
const result = await page.waitToGetProperty(selectors.ticketLog.firstTD, 'innerText');
expect(result.length).toBeGreaterThan('20');
});
});

View File

@ -50,6 +50,17 @@
} }
} }
} }
&.message {
color: white;
background-color: $color-bg-dark;
&:not(.disabled) {
&:hover,
&:focus {
background-color: lighten($color-bg-dark, 10%);
}
}
}
&.flat { &.flat {
color: $color-button; color: $color-button;
background-color: transparent; background-color: transparent;
@ -75,6 +86,22 @@
& > button > span { & > button > span {
display: none; display: none;
} }
&.xs {
font-size: 0.5em;
}
&.sm {
font-size: 0.7em;
}
&.md {
font-size: 0.9em;
}
&.lg {
font-size: 1.2em;
}
} }
&.disabled { &.disabled {
opacity: .7; opacity: .7;

View File

@ -13,7 +13,7 @@ export default class Datalist extends Textfield {
this._selection = null; this._selection = null;
this.buildInput('text'); this.buildInput('text');
this.input.setAttribute('autocomplete', 'off'); this.autocomplete = 'off';
} }
get field() { get field() {
@ -27,7 +27,7 @@ export default class Datalist extends Textfield {
value = value == '' || value == null ? null : value; value = value == '' || value == null ? null : value;
oldValue = oldValue == '' || oldValue == null ? null : oldValue; oldValue = oldValue == '' || oldValue == null ? null : oldValue;
this.refreshSelection(); if (oldValue === undefined) this.refreshSelection();
if (!value || value === oldValue && this.modelData != null) return; if (!value || value === oldValue && this.modelData != null) return;
@ -52,7 +52,14 @@ export default class Datalist extends Textfield {
validSelection(selection) { validSelection(selection) {
return this.modelData && this.modelData.find(item => { return this.modelData && this.modelData.find(item => {
return item[this.valueField] == selection; let dataValue = item[this.valueField];
if (typeof(dataValue) === 'string')
dataValue = dataValue.toLowerCase();
if (typeof(selection) === 'string')
selection = selection.toLowerCase();
return dataValue == selection;
}); });
} }
@ -251,7 +258,8 @@ export default class Datalist extends Textfield {
fragment.appendChild(option); fragment.appendChild(option);
} }
list.appendChild(fragment); this.$.$applyAsync(() =>
list.appendChild(fragment));
} }
} }

View File

@ -3,7 +3,8 @@
ng-model="$ctrl.search" ng-model="$ctrl.search"
class="dense search" class="dense search"
ng-blur="$ctrl.onFocusOut()" ng-blur="$ctrl.onFocusOut()"
placeholder="{{::'Search' | translate}}"> placeholder="{{::'Search' | translate}}"
autocomplete="off">
</vn-textfield> </vn-textfield>
</div> </div>
<div class="list" tabindex="-1"> <div class="list" tabindex="-1">

View File

@ -132,6 +132,17 @@ export default class Field extends FormInput {
return this.error || this.inputError || null; return this.error || this.inputError || null;
} }
get autocomplete() {
return this._autocomplete;
}
set autocomplete(value) {
this._autocomplete = value;
if (value === 'off')
this.input.setAttribute('autocomplete', 'off');
}
refreshHint() { refreshHint() {
let error = this.shownError; let error = this.shownError;
let hint = error || this.hint; let hint = error || this.hint;
@ -206,6 +217,7 @@ ngModule.vnComponent('vnField', {
controller: Field, controller: Field,
bindings: { bindings: {
type: '@?', type: '@?',
autocomplete: '@?',
placeholder: '@?', placeholder: '@?',
value: '=?', value: '=?',
info: '@?', info: '@?',

View File

@ -30,7 +30,7 @@
ng-click="$ctrl.onClear($event)"> ng-click="$ctrl.onClear($event)">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
icon="attach_file" icon="icon-attach"
vn-tooltip="Select a file" vn-tooltip="Select a file"
ng-click="$ctrl.openFileSelector()"> ng-click="$ctrl.openFileSelector()">
</vn-icon> </vn-icon>

View File

@ -100,7 +100,9 @@ export default class Watcher extends Component {
*/ */
submit() { submit() {
try { try {
this.check(); if (this.requestMethod() !== 'post')
this.check();
else this.isInvalid();
} catch (err) { } catch (err) {
return this.$q.reject(err); return this.$q.reject(err);
} }
@ -120,12 +122,12 @@ export default class Watcher extends Component {
if (this.form) if (this.form)
this.form.$setSubmitted(); this.form.$setSubmitted();
if (!this.dataChanged()) { const isPost = (this.requestMethod() === 'post');
if (!this.dataChanged() && !isPost) {
this.updateOriginalData(); this.updateOriginalData();
return this.$q.resolve(); return this.$q.resolve();
} }
let isPost = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post');
let changedData = isPost let changedData = isPost
? this.data ? this.data
: getModifiedData(this.data, this.orgData); : getModifiedData(this.data, this.orgData);
@ -158,7 +160,6 @@ export default class Watcher extends Component {
}); });
} }
return this.$q((resolve, reject) => { return this.$q((resolve, reject) => {
this.$http.post(this.url, changedData).then( this.$http.post(this.url, changedData).then(
json => this.writeData(json, resolve), json => this.writeData(json, resolve),
@ -166,6 +167,13 @@ export default class Watcher extends Component {
); );
}); });
} }
/**
* return the request method.
*/
requestMethod() {
return this.$attrs.save && this.$attrs.save.toLowerCase();
}
/** /**
* Checks if data is ready to send. * Checks if data is ready to send.
@ -177,6 +185,14 @@ export default class Watcher extends Component {
throw new UserError('No changes to save'); throw new UserError('No changes to save');
} }
/**
* Checks if the form is valid.
*/
isInvalid() {
if (this.form && this.form.$invalid)
throw new UserError('Some fields are invalid');
}
/** /**
* Notifies the user that the data has been saved. * Notifies the user that the data has been saved.
*/ */

View File

@ -0,0 +1,66 @@
describe('Directive http-click', () => {
let $scope;
let element;
let compile;
beforeEach(ngModule('vnCore'));
compile = (_element, _childElement) => {
inject(($compile, $rootScope) => {
$scope = $rootScope.$new();
element = angular.element(_element);
$compile(element)($scope);
$scope.$digest();
});
};
it('should call click function on the element, disable it and then enable it again', () => {
let html = `<input vn-http-click="myEvent()"/>`;
compile(html);
const myPromise = new Promise(resolve => resolve());
$scope.myEvent = () => {
return myPromise;
};
element[0].$ctrl = {disabled: false};
element[0].click();
expect(element[0].$ctrl.disabled).toEqual(true);
let finalValue;
myPromise.then(() => {
finalValue = 'called!';
expect(element[0].$ctrl.disabled).toEqual(false);
}).finally(() => {
expect(finalValue).toEqual('called!');
});
});
it('should call click function on the element and not disable it', () => {
let html = `<input vn-http-click="myEvent()"/>`;
compile(html);
const myPromise = new Promise(resolve => resolve());
$scope.myEvent = () => {
return myPromise;
};
element[0].$ctrl = {disabled: true};
element[0].click();
expect(element[0].$ctrl.disabled).toEqual(true);
let finalValue;
myPromise.then(() => {
finalValue = 'called!';
expect(element[0].$ctrl.disabled).toEqual(true);
}).finally(() => {
expect(finalValue).toEqual('called!');
}).catch(err => {
throw err;
});
});
});

View File

@ -108,7 +108,10 @@ function runFn(
$filter, $filter,
$interpolate, $interpolate,
$window, $window,
vnApp) { vnApp,
vnToken,
vnConfig,
aclService) {
Object.assign(Component.prototype, { Object.assign(Component.prototype, {
$translate, $translate,
$q, $q,
@ -121,7 +124,10 @@ function runFn(
$filter, $filter,
$interpolate, $interpolate,
$window, $window,
vnApp vnApp,
vnToken,
vnConfig,
aclService
}); });
} }
runFn.$inject = [ runFn.$inject = [
@ -136,7 +142,10 @@ runFn.$inject = [
'$filter', '$filter',
'$interpolate', '$interpolate',
'$window', '$window',
'vnApp' 'vnApp',
'vnToken',
'vnConfig',
'aclService'
]; ];
ngModule.run(runFn); ngModule.run(runFn);

View File

@ -2,10 +2,11 @@ import ngModule from '../module';
import getMainRoute from '../lib/get-main-route'; import getMainRoute from '../lib/get-main-route';
export default class Modules { export default class Modules {
constructor(aclService, $window) { constructor(aclService, $window, $translate) {
Object.assign(this, { Object.assign(this, {
aclService, aclService,
$window $window,
$translate
}); });
} }
@ -17,7 +18,7 @@ export default class Modules {
if (this.modules) if (this.modules)
return this.modules; return this.modules;
this.modules = []; const modules = [];
for (let mod of this.$window.routes) { for (let mod of this.$window.routes) {
if (!mod || !mod.routes) continue; if (!mod || !mod.routes) continue;
@ -31,7 +32,7 @@ export default class Modules {
if (res) keyBind = res.key.toUpperCase(); if (res) keyBind = res.key.toUpperCase();
} }
this.modules.push({ modules.push({
name: mod.name || mod.module, name: mod.name || mod.module,
icon: mod.icon || null, icon: mod.icon || null,
route, route,
@ -39,9 +40,16 @@ export default class Modules {
}); });
} }
const sortedModules = modules.sort((a, b) => {
const translatedNameA = this.$translate.instant(a.name);
const translatedNameB = this.$translate.instant(b.name);
return translatedNameA.localeCompare(translatedNameB);
});
this.modules = sortedModules;
return this.modules; return this.modules;
} }
} }
Modules.$inject = ['aclService', '$window']; Modules.$inject = ['aclService', '$window', '$translate'];
ngModule.service('vnModules', Modules); ngModule.service('vnModules', Modules);

View File

@ -23,6 +23,21 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-zone:before {
content: "\e95d";
}
.icon-inventory:before {
content: "\e95e";
}
.icon-wiki:before {
content: "\e968";
}
.icon-attach:before {
content: "\e96c";
}
.icon-zone2:before {
content: "\e96d";
}
.icon-net:before { .icon-net:before {
content: "\e95b"; content: "\e95b";
} }
@ -301,4 +316,4 @@
} }
.icon-worker:before { .icon-worker:before {
content: "\e943"; content: "\e943";
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -1,24 +1,8 @@
<a ng-if="$ctrl.links.btnOne" <a ng-repeat="button in $ctrl.links"
vn-tooltip="{{::$ctrl.links.btnOne.tooltip}}" vn-tooltip="{{::button.tooltip}}"
class="vn-button colored" class="vn-button colored"
ui-sref="{{::$ctrl.links.btnOne.state}}"> ui-sref="{{::button.state}}">
<vn-icon <vn-icon
icon="{{::$ctrl.links.btnOne.icon}}"> icon="{{::button.icon}}">
</vn-icon>
</a>
<a ng-if="$ctrl.links.btnTwo"
vn-tooltip="{{::$ctrl.links.btnTwo.tooltip}}"
class="vn-button colored"
ui-sref="{{::$ctrl.links.btnTwo.state}}">
<vn-icon
icon="{{::$ctrl.links.btnTwo.icon}}">
</vn-icon>
</a>
<a ng-if="$ctrl.links.btnThree"
vn-tooltip="{{::$ctrl.links.btnThree.tooltip}}"
class="vn-button colored"
ui-sref="{{::$ctrl.links.btnThree.state}}">
<vn-icon
icon="{{::$ctrl.links.btnThree.icon}}">
</vn-icon> </vn-icon>
</a> </a>

View File

@ -8,7 +8,7 @@
<div> <div>
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon> <vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
</div> </div>
<h4 ng-bind-html="::$ctrl.getModuleName(mod)"></h4> <h4 ng-bind-html="$ctrl.getModuleName(mod)"></h4>
<span <span
ng-show="::mod.keyBind" ng-show="::mod.keyBind"
vn-tooltip="Ctrl + Alt + {{::mod.keyBind}}"> vn-tooltip="Ctrl + Alt + {{::mod.keyBind}}">

View File

@ -64,5 +64,6 @@
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} (#{{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [#{{ticketId}}]({{{ticketUrl}}})", "Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} (#{{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [#{{ticketId}}]({{{ticketUrl}}})",
"Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", "Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member",
"Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member", "Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member",
"Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}" "Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}",
"Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment"
} }

View File

@ -127,5 +127,7 @@
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} (#{{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [#{{ticketId}}]({{{ticketUrl}}})", "Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} (#{{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [#{{ticketId}}]({{{ticketUrl}}})",
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}",
"ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000" "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
"This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos"
} }

View File

@ -155,7 +155,6 @@ class Controller {
}); });
} }
onUpdateGreugeResponse(response) { onUpdateGreugeResponse(response) {
if (response == 'accept') { if (response == 'accept') {
const promises = []; const promises = [];

View File

@ -1,14 +1,11 @@
import ngModule from '../module'; import ngModule from '../module';
import Component from 'core/lib/component';
class Controller { class Controller extends Component {
constructor($scope, $state, $http, $translate, vnApp, aclService, $httpParamSerializer) { constructor($element, $scope, $httpParamSerializer) {
this.$scope = $scope; super($element, $scope);
this.$state = $state;
this.$http = $http;
this.$translate = $translate;
this.vnApp = vnApp;
this.aclService = aclService;
this.$httpParamSerializer = $httpParamSerializer; this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [ this.moreOptions = [
{callback: this.showPickupOrder, name: 'Show Pickup order'}, {callback: this.showPickupOrder, name: 'Show Pickup order'},
{callback: this.confirmPickupOrder, name: 'Send Pickup order'}, {callback: this.confirmPickupOrder, name: 'Send Pickup order'},
@ -22,7 +19,7 @@ class Controller {
return !hasAclProperty || (hasAclProperty && this.aclService.hasAny([option.acl])); return !hasAclProperty || (hasAclProperty && this.aclService.hasAny([option.acl]));
}); });
this.$scope.moreButton.data = options; this.$.moreButton.data = options;
} }
onMoreChange(callback) { onMoreChange(callback) {
@ -63,7 +60,8 @@ class Controller {
showPickupOrder() { showPickupOrder() {
const params = { const params = {
clientId: this.claim.clientFk, clientId: this.claim.clientFk,
claimId: this.claim.id claimId: this.claim.id,
authorization: this.vnToken.token
}; };
const serializedParams = this.$httpParamSerializer(params); const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/claim-pickup-order?${serializedParams}`; let url = `api/report/claim-pickup-order?${serializedParams}`;
@ -71,7 +69,7 @@ class Controller {
} }
confirmPickupOrder() { confirmPickupOrder() {
this.$scope.confirmPickupOrder.show(); this.$.confirmPickupOrder.show();
} }
sendPickupOrder(response) { sendPickupOrder(response) {
@ -81,16 +79,14 @@ class Controller {
clientId: this.claim.clientFk, clientId: this.claim.clientFk,
claimId: this.claim.id claimId: this.claim.id
}; };
const serializedParams = this.$httpParamSerializer(params); this.$http.get(`email/claim-pickup-order`, {params}).then(
const url = `email/claim-pickup-order?${serializedParams}`;
this.$http.get(url).then(
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!')) () => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
); );
} }
} }
confirmDeleteClaim() { confirmDeleteClaim() {
this.$scope.confirmDeleteClaim.show(); this.$.confirmDeleteClaim.show();
} }
deleteClaim(response) { deleteClaim(response) {
@ -103,7 +99,7 @@ class Controller {
} }
} }
Controller.$inject = ['$scope', '$state', '$http', '$translate', 'vnApp', 'aclService', '$httpParamSerializer']; Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnClaimDescriptor', { ngModule.component('vnClaimDescriptor', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -3,14 +3,19 @@ import './index.js';
describe('Item Component vnClaimDescriptor', () => { describe('Item Component vnClaimDescriptor', () => {
let $httpParamSerializer; let $httpParamSerializer;
let $httpBackend; let $httpBackend;
let $element;
let $scope;
let controller; let controller;
beforeEach(ngModule('claim')); beforeEach(ngModule('claim'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, _$httpParamSerializer_) => { beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_; $httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnClaimDescriptor'); $scope = $rootScope.$new();
$element = angular.element('<vn-claim-descriptor></vn-claim-descriptor>');
controller = $componentController('vnClaimDescriptor', {$element, $scope});
controller.claim = {id: 2, clientFk: 101, client: {email: 'client@email'}}; controller.claim = {id: 2, clientFk: 101, client: {email: 'client@email'}};
})); }));
@ -31,13 +36,13 @@ describe('Item Component vnClaimDescriptor', () => {
describe('confirmPickupOrder()', () => { describe('confirmPickupOrder()', () => {
it('should call confirmPickupOrder.show()', () => { it('should call confirmPickupOrder.show()', () => {
controller.$scope.confirmPickupOrder = { controller.$.confirmPickupOrder = {
show: jasmine.createSpy('show') show: jasmine.createSpy('show')
}; };
controller.claim = {id: 2}; controller.claim = {id: 2};
controller.confirmPickupOrder(); controller.confirmPickupOrder();
expect(controller.$scope.confirmPickupOrder.show).toHaveBeenCalledWith(); expect(controller.$.confirmPickupOrder.show).toHaveBeenCalledWith();
}); });
}); });
@ -63,13 +68,13 @@ describe('Item Component vnClaimDescriptor', () => {
describe('confirmDeleteClaim()', () => { describe('confirmDeleteClaim()', () => {
it('should call confirmDeleteClaim.show()', () => { it('should call confirmDeleteClaim.show()', () => {
controller.$scope.confirmDeleteClaim = { controller.$.confirmDeleteClaim = {
show: jasmine.createSpy('show') show: jasmine.createSpy('show')
}; };
controller.claim = {id: 2}; controller.claim = {id: 2};
controller.confirmDeleteClaim(); controller.confirmDeleteClaim();
expect(controller.$scope.confirmDeleteClaim.show).toHaveBeenCalledWith(); expect(controller.$.confirmDeleteClaim.show).toHaveBeenCalledWith();
}); });
}); });

View File

@ -10,24 +10,17 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield
vn-one
label="Client Id"]
ng-model="filter.clientFk">
</vn-textfield>
<vn-textfield <vn-textfield
vn-one vn-one
label="Client" label="Client"
ng-model="filter.client"> ng-model="filter.client">
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Claim Id"
ng-model="filter.id">
</vn-textfield>
<vn-textfield
vn-one
label="Client Id"
ng-model="filter.clientFk">
</vn-textfield>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete
vn-one vn-one

View File

@ -7,7 +7,7 @@ describe('Client activeWorkersWithRole', () => {
let isSalesPerson = await app.models.Account.hasRole(result[0].id, 'salesPerson'); let isSalesPerson = await app.models.Account.hasRole(result[0].id, 'salesPerson');
expect(result.length).toEqual(14); expect(result.length).toEqual(15);
expect(isSalesPerson).toBeTruthy(); expect(isSalesPerson).toBeTruthy();
}); });
@ -17,7 +17,7 @@ describe('Client activeWorkersWithRole', () => {
let isBuyer = await app.models.Account.hasRole(result[0].id, 'buyer'); let isBuyer = await app.models.Account.hasRole(result[0].id, 'buyer');
expect(result.length).toEqual(11); expect(result.length).toEqual(13);
expect(isBuyer).toBeTruthy(); expect(isBuyer).toBeTruthy();
}); });
}); });

View File

@ -47,7 +47,6 @@ module.exports = Self => {
let xmlParsed; let xmlParsed;
let status; let status;
try { try {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
status = { status = {

View File

@ -191,7 +191,6 @@ module.exports = Self => {
if (socialNameChanged && !isAlpha(changes.socialName)) if (socialNameChanged && !isAlpha(changes.socialName))
throw new UserError('The socialName has an invalid format'); throw new UserError('The socialName has an invalid format');
if (changes.salesPerson === null) { if (changes.salesPerson === null) {
changes.credit = 0; changes.credit = 0;
changes.discount = 0; changes.discount = 0;
@ -238,7 +237,9 @@ module.exports = Self => {
const httpCtx = {req: loopBackContext.active}; const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req; const httpRequest = httpCtx.req.http.req;
const $t = httpRequest.__; const $t = httpRequest.__;
const origin = httpRequest.headers.origin; const headers = httpRequest.headers;
const origin = headers.origin;
const authorization = headers.authorization;
const salesPersonId = instance.salesPersonFk; const salesPersonId = instance.salesPersonFk;
@ -254,12 +255,14 @@ module.exports = Self => {
// Send email to client // Send email to client
if (!instance.email) return; if (!instance.email) return;
const serializedParams = httpParamSerializer({ const params = {
authorization: authorization,
clientId: instance.id, clientId: instance.id,
recipient: instance.email recipient: instance.email
};
await request.get(`${origin}/api/email/payment-update`, {
qs: params
}); });
const query = `${origin}/api/email/payment-update?${serializedParams}`;
await request.get(query);
} }
}); });

View File

@ -39,61 +39,53 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-datalist vn-one
vn-one
vn-id="province"
ng-model="$ctrl.address.provinceId"
url="Provinces"
show-field="name"
value-field="id"
label="Province">
</vn-autocomplete>
<vn-textfield
vn-one
label="City"
ng-model="$ctrl.address.city"
rule>
</vn-textfield>
<!-- <vn-autocomplete vn-id="town" vn-one
label="City"
url="Towns"
fields="['id', 'name']"
where="{provinceFk: province.selection.id}"
show-field="name"
value-field="name"
ng-model="$ctrl.address.city">
</vn-autocomplete>
<vn-autocomplete vn-one
url="Postcodes/location"
fields="['code', 'townFk']"
ng-model="$ctrl.address.postalCode"
selection="$ctrl.postcodeSelection"
search-function="{code: $search}"
where="{townFk: town.selection.id}"
order="code, townFk"
show-field="code"
value-field="code"
label="Postcode">
<tpl-item>
{{code}}, {{town.name}} - {{town.province.name}}
({{town.province.country.country}})
</tpl-item>
</vn-autocomplete> -->
<vn-textfield
vn-one
label="Postcode" label="Postcode"
ng-model="$ctrl.address.postalCode" ng-model="$ctrl.address.postalCode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule> rule>
</vn-textfield> <tpl-item>
<!-- {{code}} - {{town.name}} ({{town.province.name}},
<vn-icon-button {{town.province.country.country}})
vn-auto </tpl-item>
class="vn-my-md" <append>
icon="add_circle" <vn-icon-button
vn-tooltip="New postcode" icon="add_circle"
ng-click="postcode.open()"> vn-tooltip="New postcode"
</vn-icon-button> ng-click="postcode.open()"
--> vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-datalist vn-id="town" vn-one
label="City"
ng-model="$ctrl.address.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
show-field="name"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.country}})
</tpl-item>
</vn-datalist>
<vn-autocomplete vn-id="province" vn-one
label="Province"
ng-model="$ctrl.address.provinceId"
url="Provinces/location"
fields="['id', 'name', 'countryFk']"
show-field="name"
value-field="id"
rule>
<tpl-item>{{name}} ({{country.country}})</tpl-item>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete

View File

@ -11,26 +11,6 @@ export default class Controller extends Component {
}; };
} }
get postcodeSelection() {
return this._postcodeSelection;
}
set postcodeSelection(selection) {
this._postcodeSelection = selection;
if (!selection) return;
const town = selection.town;
const province = town.province;
this.address.city = town.name;
this.address.provinceFk = province.id;
}
onResponse(response) {
this.address.postalCode = response.code;
}
onSubmit() { onSubmit() {
this.$.watcher.submit().then(res => { this.$.watcher.submit().then(res => {
if (this.address.isDefaultAddress) if (this.address.isDefaultAddress)
@ -51,6 +31,46 @@ export default class Controller extends Component {
return this.$http.post(`CustomsAgents`, this.newCustomsAgent) return this.$http.post(`CustomsAgents`, this.newCustomsAgent)
.then(res => this.address.customsAgentFk = res.data.id); .then(res => this.address.customsAgentFk = res.data.id);
} }
get town() {
return this._town;
}
// Town auto complete
set town(selection) {
this._town = selection;
if (!selection) return;
const province = selection.province;
const postcodes = selection.postcodes;
this.address.provinceId = province.id;
if (postcodes.length === 1)
this.address.postalCode = postcodes[0].code;
}
get postcode() {
return this._postcode;
}
// Postcode auto complete
set postcode(selection) {
this._postcode = selection;
if (!selection) return;
const town = selection.town;
const province = town.province;
this.address.city = town.name;
this.address.provinceId = province.id;
}
onResponse(response) {
this.address.postalCode = response.code;
}
} }
Controller.$inject = ['$element', '$scope']; Controller.$inject = ['$element', '$scope'];

View File

@ -53,9 +53,48 @@ describe('Client', () => {
}); });
}); });
describe('postcodeSelection() setter', () => { describe('town() setter', () => {
it(`should set the town, province and contry properties`, () => { it(`should set provinceId property`, () => {
controller.postcodeSelection = { controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: []
};
expect(controller.address.provinceId).toEqual(1);
});
it(`should set provinceId property and fill the postalCode if there's just one`, () => {
controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: [{code: '46001'}]
};
expect(controller.address.provinceId).toEqual(1);
expect(controller.address.postalCode).toEqual('46001');
});
});
describe('postcode() setter', () => {
it(`should set the town and province properties`, () => {
controller.postcode = {
townFk: 1, townFk: 1,
code: 46001, code: 46001,
town: { town: {
@ -73,7 +112,7 @@ describe('Client', () => {
}; };
expect(controller.address.city).toEqual('New York'); expect(controller.address.city).toEqual('New York');
expect(controller.address.provinceFk).toEqual(1); expect(controller.address.provinceId).toEqual(1);
}); });
}); });

View File

@ -56,58 +56,53 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-datalist vn-one
vn-one
vn-id="province"
ng-model="$ctrl.address.provinceFk"
url="Provinces"
show-field="name"
value-field="id"
label="Province">
</vn-autocomplete>
<vn-textfield
vn-one
label="City"
ng-model="$ctrl.address.city"
rule>
</vn-textfield>
<!--
<vn-autocomplete vn-id="town" vn-one
label="City"
url="Towns"
fields="['id', 'name']"
where="{provinceFk: province.selection.id}"
show-field="name"
value-field="name"
ng-model="$ctrl.address.city">
</vn-autocomplete>
<vn-autocomplete vn-one
url="Postcodes/location"
fields="['code', 'townFk']"
ng-model="$ctrl.address.postalCode"
where="{townFk: town.selection.id}"
search-function="{code: $search}"
order="code, townFk"
show-field="code"
value-field="code"
label="Postcode">
</vn-autocomplete>
-->
<vn-textfield
vn-one
label="Postcode" label="Postcode"
ng-model="$ctrl.address.postalCode" ng-model="$ctrl.address.postalCode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule> rule>
</vn-textfield> <tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
<!-- <vn-icon-button {{town.province.country.country}})
vn-auto </tpl-item>
class="vn-my-md" <append>
icon="add_circle" <vn-icon-button
vn-tooltip="New postcode" icon="add_circle"
ng-click="postcode.open()"> vn-tooltip="New postcode"
</vn-icon-button> ng-click="postcode.open()"
--> vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-datalist vn-id="town" vn-one
label="City"
ng-model="$ctrl.address.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
show-field="name"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.country}})
</tpl-item>
</vn-datalist>
<vn-autocomplete vn-id="province" vn-one
label="Province"
ng-model="$ctrl.address.provinceFk"
url="Provinces/location"
fields="['id', 'name', 'countryFk']"
show-field="name"
value-field="id"
rule>
<tpl-item>{{name}} ({{country.country}})</tpl-item>
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete vn-one

View File

@ -15,10 +15,6 @@ export default class Controller extends Component {
this.$state.go('client.card.address.index'); this.$state.go('client.card.address.index');
} }
onResponse(response) {
this.address.postalCode = response.code;
}
onSubmit() { onSubmit() {
this.$.watcher.submit() this.$.watcher.submit()
.then(() => this.$.model.save(true)) .then(() => this.$.model.save(true))
@ -39,6 +35,48 @@ export default class Controller extends Component {
return this.$http.post(`CustomsAgents`, this.newCustomsAgent) return this.$http.post(`CustomsAgents`, this.newCustomsAgent)
.then(res => this.address.customsAgentFk = res.data.id); .then(res => this.address.customsAgentFk = res.data.id);
} }
get town() {
return this._town;
}
// Town auto complete
set town(selection) {
const oldValue = this._town;
this._town = selection;
if (!oldValue) return;
const province = selection.province;
const postcodes = selection.postcodes;
this.address.provinceFk = province.id;
if (postcodes.length === 1)
this.address.postalCode = postcodes[0].code;
}
get postcode() {
return this._postcode;
}
// Postcode auto complete
set postcode(selection) {
const oldValue = this._postcode;
this._postcode = selection;
if (!oldValue) return;
const town = selection.town;
const province = town.province;
this.address.city = town.name;
this.address.provinceFk = province.id;
}
onResponse(response) {
this.address.postalCode = response.code;
}
} }
ngModule.component('vnClientAddressEdit', { ngModule.component('vnClientAddressEdit', {

View File

@ -49,76 +49,63 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-datalist vn-one
vn-id="country" label="Postcode"
vn-one ng-model="$ctrl.client.postcode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule>
<tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
{{town.province.country.country}})
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-datalist vn-id="town" vn-one
label="City"
ng-model="$ctrl.client.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
show-field="name"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.country}})
</tpl-item>
</vn-datalist>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-id="province" vn-one
label="Province"
ng-model="$ctrl.client.provinceFk"
selection="$ctrl.province"
url="Provinces/location"
fields="['id', 'name', 'countryFk']"
show-field="name"
value-field="id"
rule>
<tpl-item>{{name}} ({{country.country}})</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-id="country" vn-one
ng-model="$ctrl.client.countryFk" ng-model="$ctrl.client.countryFk"
url="Countries" url="Countries"
show-field="country" show-field="country"
value-field="id" value-field="id"
label="Country"> label="Country">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete
vn-id="province"
vn-one
url="Provinces"
ng-model="$ctrl.client.provinceFk"
where="{countryFk: country.selection.id}"
show-field="name"
value-field="id"
label="Province">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="City"
ng-model="$ctrl.client.city"
rule>
</vn-textfield>
<!--
<vn-autocomplete vn-id="town" vn-one
label="City"
url="Towns"
fields="['id', 'name']"
where="{provinceFk: province.selection.id}"
show-field="name"
value-field="name"
ng-model="$ctrl.client.city">
</vn-autocomplete>
<vn-icon-button
vn-auto
class="vn-my-md"
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()">
</vn-icon-button>
-->
<vn-textfield
vn-one
label="Postcode"
ng-model="$ctrl.client.postcode"
rule>
</vn-textfield>
<!--
<vn-autocomplete
vn-one
url="Postcodes/location"
fields="['code', 'townFk']"
ng-model="$ctrl.client.postcode"
selection="$ctrl.postcodeSelection"
search-function="{code: $search}"
where="{townFk: town.selection.id}"
order="code, townFk"
show-field="code"
value-field="code"
label="Postcode">
<tpl-item>
{{code}}, {{town.name}} - {{town.province.name}}
({{town.province.country.country}})
</tpl-item>
</vn-autocomplete>
-->
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield

View File

@ -12,12 +12,55 @@ export default class Controller {
}; };
} }
get postcodeSelection() { onSubmit() {
return this._postcodeSelection; return this.$.watcher.submit().then(
json => this.$state.go('client.card.basicData', {id: json.data.id})
);
} }
set postcodeSelection(selection) { get province() {
this._postcodeSelection = selection; return this._province;
}
// Province auto complete
set province(selection) {
this._province = selection;
if (!selection) return;
const country = selection.country;
this.client.countryFk = country.id;
}
get town() {
return this._town;
}
// Town auto complete
set town(selection) {
this._town = selection;
if (!selection) return;
const province = selection.province;
const country = province.country;
const postcodes = selection.postcodes;
this.client.provinceFk = province.id;
this.client.countryFk = country.id;
if (postcodes.length === 1)
this.client.postcode = postcodes[0].code;
}
get postcode() {
return this._postcode;
}
// Postcode auto complete
set postcode(selection) {
this._postcode = selection;
if (!selection) return; if (!selection) return;
@ -33,13 +76,8 @@ export default class Controller {
onResponse(response) { onResponse(response) {
this.client.postcode = response.code; this.client.postcode = response.code;
} }
onSubmit() {
return this.$.watcher.submit().then(
json => this.$state.go('client.card.basicData', {id: json.data.id})
);
}
} }
Controller.$inject = ['$scope', '$state', '$http', '$translate', 'vnApp']; Controller.$inject = ['$scope', '$state', '$http', '$translate', 'vnApp'];
ngModule.component('vnClientCreate', { ngModule.component('vnClientCreate', {

View File

@ -40,9 +40,63 @@ describe('Client', () => {
}); });
}); });
describe('postcodeSelection() setter', () => { describe('province() setter', () => {
it(`should set the town, province and contry properties`, () => { it(`should set countryFk property`, () => {
controller.postcodeSelection = { controller.province = {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
};
expect(controller.client.countryFk).toEqual(2);
});
});
describe('town() setter', () => {
it(`should set provinceFk property`, () => {
controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: []
};
expect(controller.client.provinceFk).toEqual(1);
});
it(`should set provinceFk property and fill the postalCode if there's just one`, () => {
controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: [{code: '46001'}]
};
expect(controller.client.provinceFk).toEqual(1);
expect(controller.client.postcode).toEqual('46001');
});
});
describe('postcode() setter', () => {
it(`should set the town, provinceFk and contryFk properties`, () => {
controller.postcode = {
townFk: 1, townFk: 1,
code: 46001, code: 46001,
town: { town: {

View File

@ -5,6 +5,7 @@ class Controller extends Component {
constructor($element, $, $httpParamSerializer) { constructor($element, $, $httpParamSerializer) {
super($element, $); super($element, $);
this.$httpParamSerializer = $httpParamSerializer; this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [ this.moreOptions = [
{name: 'Simple ticket', callback: this.newTicket}, {name: 'Simple ticket', callback: this.newTicket},
{name: 'Send SMS', callback: this.showSMSDialog}, {name: 'Send SMS', callback: this.showSMSDialog},
@ -72,8 +73,13 @@ class Controller extends Component {
sendConsumerReport(response) { sendConsumerReport(response) {
if (response === 'accept') { if (response === 'accept') {
const data = {from: this.from, to: this.to, clientId: this.client.id}; const params = {
const serializedParams = this.$httpParamSerializer(data); authorization: this.vnToken.token,
clientId: this.client.id,
from: this.from,
to: this.to,
};
const serializedParams = this.$httpParamSerializer(params);
const url = `api/report/campaign-metrics?${serializedParams}`; const url = `api/report/campaign-metrics?${serializedParams}`;
window.open(url); window.open(url);
} }

View File

@ -33,9 +33,57 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-datalist vn-one
vn-one label="Postcode"
vn-id="country" ng-model="$ctrl.client.postcode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule>
<tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
{{town.province.country.country}})
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-datalist vn-id="town" vn-one
label="City"
ng-model="$ctrl.client.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
show-field="name"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.country}})
</tpl-item>
</vn-datalist>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-id="province" vn-one
label="Province"
ng-model="$ctrl.client.provinceFk"
selection="$ctrl.province"
url="Provinces/location"
fields="['id', 'name', 'countryFk']"
show-field="name"
value-field="id"
rule>
<tpl-item>{{name}} ({{country.country}})</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-id="country" vn-one
ng-model="$ctrl.client.countryFk" ng-model="$ctrl.client.countryFk"
url="Countries" url="Countries"
show-field="country" show-field="country"
@ -43,51 +91,6 @@
label="Country" label="Country"
rule> rule>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete
vn-one
vn-id="province"
ng-model="$ctrl.client.provinceFk"
url="Provinces"
where="{countryFk: country.selection.id}"
show-field="name"
value-field="id"
label="Province"
rule>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="City"
ng-model="$ctrl.client.city"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Postcode"
ng-model="$ctrl.client.postcode"
rule>
</vn-textfield>
<!-- <vn-autocomplete vn-id="town" vn-one
label="City"
url="Towns"
fields="['id', 'name']"
where="{provinceFk: province.selection.id}"
show-field="name"
value-field="name"
ng-model="$ctrl.client.city">
</vn-autocomplete>
<vn-autocomplete vn-id="postcode" vn-one
url="Postcodes/location"
fields="['code', 'townFk']"
ng-model="$ctrl.client.postcode"
search-function="{code: $search}"
where="{townFk: town.selection.id}"
order="code, townFk"
show-field="code"
value-field="code"
label="Postcode">
</vn-autocomplete> -->
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-check <vn-check
@ -157,3 +160,8 @@
message="Found a client with this data" message="Found a client with this data"
on-accept="$ctrl.onAcceptDuplication()"> on-accept="$ctrl.onAcceptDuplication()">
</vn-confirm> </vn-confirm>
<!-- New postcode dialog -->
<vn-client-postcode
vn-id="postcode"
on-response="$ctrl.onResponse($response)">
</vn-client-postcode>

View File

@ -5,16 +5,29 @@ export default class Controller extends Component {
onSubmit() { onSubmit() {
const orgData = this.$.watcher.orgData; const orgData = this.$.watcher.orgData;
delete this.client.despiteOfClient; delete this.client.despiteOfClient;
if (!orgData.isTaxDataChecked && this.client.isTaxDataChecked)
const hasContactData = this.client.email || this.client.phone || this.client.mobile;
const hasChangedTaxData = !orgData.isTaxDataChecked && this.client.isTaxDataChecked;
if (hasChangedTaxData && hasContactData)
this.checkExistingClient(); this.checkExistingClient();
else this.save(); else this.save();
} }
checkExistingClient() { checkExistingClient() {
const findParams = [];
if (this.client.email)
findParams.push({email: this.client.email});
if (this.client.phone)
findParams.push({phone: this.client.phone});
if (this.client.mobile)
findParams.push({mobile: this.client.mobile});
const filterObj = { const filterObj = {
where: { where: {
and: [ and: [
{or: [{email: this.client.email}, {phone: this.client.phone}]}, {or: findParams},
{id: {neq: this.client.id}} {id: {neq: this.client.id}}
] ]
} }
@ -32,7 +45,8 @@ export default class Controller extends Component {
this.$.confirmDuplicatedClient.show(); this.$.confirmDuplicatedClient.show();
}).catch(error => { }).catch(error => {
if (error.status == 404) if (error.status == 404)
this.save(); return this.save();
throw error;
}); });
} }
@ -77,6 +91,68 @@ export default class Controller extends Component {
this.$.$apply(); this.$.$apply();
} }
get province() {
return this._province;
}
// Province auto complete
set province(selection) {
const oldValue = this._province;
this._province = selection;
if (!selection || !oldValue) return;
const country = selection.country;
this.client.countryFk = country.id;
}
get town() {
return this._town;
}
// Town auto complete
set town(selection) {
const oldValue = this._town;
this._town = selection;
if (!selection || !oldValue) return;
const province = selection.province;
const country = province.country;
const postcodes = selection.postcodes;
this.client.provinceFk = province.id;
this.client.countryFk = country.id;
if (postcodes.length === 1)
this.client.postcode = postcodes[0].code;
}
get postcode() {
return this._postcode;
}
// Postcode auto complete
set postcode(selection) {
const oldValue = this._postcode;
this._postcode = selection;
if (!selection || !oldValue) return;
const town = selection.town;
const province = town.province;
const country = province.country;
this.client.city = town.name;
this.client.provinceFk = province.id;
this.client.countryFk = country.id;
}
onResponse(response) {
this.client.postcode = response.code;
}
} }
ngModule.component('vnClientFiscalData', { ngModule.component('vnClientFiscalData', {

View File

@ -25,6 +25,10 @@ describe('Client', () => {
isEqualizated: false, isEqualizated: false,
isTaxDataChecked: false isTaxDataChecked: false
}; };
controller.province = {};
controller.town = {};
controller.postcode = {};
})); }));
describe('onSubmit()', () => { describe('onSubmit()', () => {
@ -49,9 +53,30 @@ describe('Client', () => {
}); });
describe('checkExistingClient()', () => { describe('checkExistingClient()', () => {
it('should show a save confirmation when a duplicated client is found and then set the despiteOfClient property', () => { it(`should make a HTTP GET query filtering by email, phone and mobile`, () => {
controller.client.mobile = 222222222;
const filterObj = {
where: {
and: [
{or: [
{email: controller.client.email},
{phone: controller.client.phone},
{mobile: controller.client.mobile}
]},
{id: {neq: controller.client.id}}
]
}
};
const filter = encodeURIComponent(JSON.stringify(filterObj));
$httpBackend.expect('GET', `Clients/findOne?filter=${filter}`).respond(404);
controller.checkExistingClient();
$httpBackend.flush();
});
it(`should show a save confirmation and then set the despiteOfClient property`, () => {
controller.$.confirmDuplicatedClient = {show: () => {}}; controller.$.confirmDuplicatedClient = {show: () => {}};
jest.spyOn(controller.$.confirmDuplicatedClient, 'show'); jest.spyOn(controller.$.confirmDuplicatedClient, 'show');
const filterObj = { const filterObj = {
where: { where: {
and: [ and: [
@ -107,5 +132,84 @@ describe('Client', () => {
$httpBackend.flush(); $httpBackend.flush();
}); });
}); });
describe('province() setter', () => {
it(`should set countryFk property`, () => {
controller.province = {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
};
expect(controller.client.countryFk).toEqual(2);
});
});
describe('town() setter', () => {
it(`should set provinceFk property`, () => {
controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: []
};
expect(controller.client.provinceFk).toEqual(1);
});
it(`should set provinceFk property and fill the postalCode if there's just one`, () => {
controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: [{code: '46001'}]
};
expect(controller.client.provinceFk).toEqual(1);
expect(controller.client.postcode).toEqual('46001');
});
});
describe('postcode() setter', () => {
it(`should set the town, provinceFk and contryFk properties`, () => {
controller.postcode = {
townFk: 1,
code: 46001,
town: {
id: 1,
name: 'New York',
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
}
}
};
expect(controller.client.city).toEqual('New York');
expect(controller.client.provinceFk).toEqual(1);
expect(controller.client.countryFk).toEqual(2);
});
});
}); });
}); });

View File

@ -1,7 +1,7 @@
<vn-dialog class="edit" <vn-dialog class="edit"
vn-id="postcodeDialog" vn-id="postcodeDialog"
on-open="$ctrl.onOpen()" on-open="$ctrl.onOpen()"
on-response="$ctrl.onResponse($response)"> on-accept="$ctrl.onAccept()">
<tpl-body> <tpl-body>
<h5 class="vn-py-sm" translate>New postcode</h5> <h5 class="vn-py-sm" translate>New postcode</h5>
<p translate>Please, ensure you put the correct data!</p> <p translate>Please, ensure you put the correct data!</p>

View File

@ -35,25 +35,20 @@ class Controller extends Component {
this.$.postcode.focus(); this.$.postcode.focus();
} }
onResponse(response) { onAccept() {
if (response == 'accept') { try {
try { if (!this.data.code)
if (!this.data.code) throw new Error(`The postcode can't be empty`);
throw new Error(`The postcode can't be empty`); if (!this.data.townFk)
if (!this.data.townFk) throw new Error(`The town can't be empty`);
throw new Error(`The town can't be empty`);
this.$http.patch(`postcodes`, this.data).then(response => { this.$http.patch(`postcodes`, this.data).then(res => {
if (response.data) { this.vnApp.showMessage(this.$translate.instant('The postcode has been saved'));
this.vnApp.showMessage(this.$translate.instant('The postcode has been saved')); this.emit('response', {$response: res.data});
});
this.emit('response', {response: response.data}); } catch (e) {
} this.vnApp.showError(this.$translate.instant(e.message));
}); return false;
} catch (e) {
this.vnApp.showError(this.$translate.instant(e.message));
return false;
}
} }
return true; return true;
} }

View File

@ -15,7 +15,7 @@ describe('Client', () => {
controller.client = {id: 101}; controller.client = {id: 101};
})); }));
describe('onResponse()', () => { describe('onAccept()', () => {
it('should perform a POST query and show a success snackbar', () => { it('should perform a POST query and show a success snackbar', () => {
let params = {townFk: 1, provinceFk: 1, countryFk: 1, code: '46460'}; let params = {townFk: 1, provinceFk: 1, countryFk: 1, code: '46460'};
controller.data = {townFk: 1, provinceFk: 1, countryFk: 1, code: '46460'}; controller.data = {townFk: 1, provinceFk: 1, countryFk: 1, code: '46460'};
@ -24,7 +24,7 @@ describe('Client', () => {
$httpBackend.when('PATCH', `postcodes`, params).respond(200, params); $httpBackend.when('PATCH', `postcodes`, params).respond(200, params);
$httpBackend.expect('PATCH', `postcodes`, params).respond(params); $httpBackend.expect('PATCH', `postcodes`, params).respond(params);
controller.onResponse('accept'); controller.onAccept();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('The postcode has been saved'); expect(controller.vnApp.showMessage).toHaveBeenCalledWith('The postcode has been saved');

View File

@ -9,18 +9,6 @@
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Client id"
ng-model="filter.id">
</vn-textfield>
<vn-textfield
vn-one
label="Tax number"
ng-model="filter.fi">
</vn-textfield>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
vn-one label="Name" vn-one label="Name"
@ -39,6 +27,11 @@
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield
vn-one
label="Tax number"
ng-model="filter.fi">
</vn-textfield>
<vn-textfield <vn-textfield
vn-one vn-one
label="Social name" label="Social name"

View File

@ -150,7 +150,6 @@ module.exports = Self => {
JOIN vn.currency cu ON cu.id = e.currencyFk` JOIN vn.currency cu ON cu.id = e.currencyFk`
); );
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));
let itemsIndex = stmts.push(stmt) - 1; let itemsIndex = stmts.push(stmt) - 1;

View File

@ -1,5 +1,8 @@
{ {
"Entry": { "Entry": {
"dataSource": "vn" "dataSource": "vn"
},
"EntryLog": {
"dataSource": "vn"
} }
} }

View File

@ -0,0 +1,42 @@
{
"name": "EntryLog",
"base": "VnModel",
"options": {
"mysql": {
"table": "entryLog"
}
},
"properties": {
"id": {
"id": true,
"type": "Number"
},
"originFk": {
"type": "Number",
"required": true
},
"userFk": {
"type": "Number"
},
"action": {
"type": "String",
"required": true
},
"creationDate": {
"type": "Date"
},
"description": {
"type": "String"
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "userFk"
}
},
"scope": {
"order": ["creationDate DESC", "id DESC"]
}
}

View File

@ -1,6 +1,9 @@
{ {
"name": "Entry", "name": "Entry",
"base": "VnModel", "base": "VnModel",
"log": {
"model":"EntryLog"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "entry" "table": "entry"
@ -8,7 +11,7 @@
}, },
"properties": { "properties": {
"id": { "id": {
"type": "Number", "type": "number",
"id": true, "id": true,
"description": "Identifier" "description": "Identifier"
}, },
@ -16,46 +19,49 @@
"type": "date" "type": "date"
}, },
"ref": { "ref": {
"type": "String" "type": "string"
}, },
"isBooked": { "isBooked": {
"type": "Boolean" "type": "boolean"
}, },
"isInventory": { "isInventory": {
"type": "Boolean" "type": "boolean"
}, },
"notes": { "notes": {
"type": "Number" "type": "number"
}, },
"isConfirmed": { "isConfirmed": {
"type": "Boolean" "type": "boolean"
}, },
"isVirtual": { "isVirtual": {
"type": "Boolean", "type": "boolean",
"mysql": { "mysql": {
"columnName": "isRaid" "columnName": "isRaid"
} }
}, },
"isRaid": {
"type": "boolean"
},
"commission": { "commission": {
"type": "Number" "type": "number"
}, },
"isOrdered": { "isOrdered": {
"type": "Boolean" "type": "boolean"
}, },
"created": { "created": {
"type": "date" "type": "date"
}, },
"observation": { "observation": {
"type": "String", "type": "string",
"mysql": { "mysql": {
"columnName": "evaNotes" "columnName": "evaNotes"
} }
}, },
"isBlocked": { "isBlocked": {
"type": "Boolean" "type": "boolean"
}, },
"loadPriority": { "loadPriority": {
"type": "Number" "type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,18 @@
import ngModule from '../module';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $) {
super($element, $);
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.component('vnEntryBuy', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -0,0 +1 @@
Buy: Lineas de entrada

View File

@ -35,6 +35,18 @@
value="{{$ctrl.entry.travel.warehouseOut.name}}"> value="{{$ctrl.entry.travel.warehouseOut.name}}">
</vn-label-value> </vn-label-value>
</div> </div>
<div class="icons">
<vn-icon
vn-tooltip="Is inventory entry"
icon="icon-inventory"
ng-class="{bright: $ctrl.entry.isInventory}">
</vn-icon>
<vn-icon
vn-tooltip="Is virtual entry"
icon="icon-net"
ng-class="{bright: $ctrl.entry.isRaid}">
</vn-icon>
</div>
<vn-quick-links <vn-quick-links
links="$ctrl.quicklinks"> links="$ctrl.quicklinks">
</vn-quick-links> </vn-quick-links>

View File

@ -2,10 +2,10 @@ import ngModule from '../module';
import Component from 'core/lib/component'; import Component from 'core/lib/component';
class Controller extends Component { class Controller extends Component {
constructor($element, $, $httpParamSerializer, vnConfig) { constructor($element, $, $httpParamSerializer) {
super($element, $); super($element, $);
this.vnConfig = vnConfig;
this.$httpParamSerializer = $httpParamSerializer; this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [ this.moreOptions = [
{name: 'Show entry report', callback: this.showEntryReport} {name: 'Show entry report', callback: this.showEntryReport}
]; ];
@ -59,6 +59,7 @@ class Controller extends Component {
showEntryReport() { showEntryReport() {
const params = { const params = {
authorization: this.vnToken.token,
clientId: this.vnConfig.storage.currentUserWorkerId, clientId: this.vnConfig.storage.currentUserWorkerId,
entryId: this.entry.id entryId: this.entry.id
}; };
@ -68,7 +69,7 @@ class Controller extends Component {
} }
} }
Controller.$inject = ['$element', '$scope', '$httpParamSerializer', 'vnConfig']; Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnEntryDescriptor', { ngModule.component('vnEntryDescriptor', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,4 +1,6 @@
Reference: Referencia Reference: Referencia
All travels with current agency: Todos los envios con la agencia actual All travels with current agency: Todos los envios con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual All entries with current supplier: Todas las entradas con el proveedor actual
Show entry report: Ver informe del pedido Show entry report: Ver informe del pedido
Is inventory entry: Es una entrada de inventario
Is virtual entry: Es una redada

View File

@ -6,3 +6,5 @@ import './search-panel';
import './descriptor'; import './descriptor';
import './card'; import './card';
import './summary'; import './summary';
import './log';
import './buy';

View File

@ -30,7 +30,7 @@
ng-show="entry.isInventory" ng-show="entry.isInventory"
class="bright" class="bright"
vn-tooltip="Inventory entry" vn-tooltip="Inventory entry"
icon="icon-anonymous"> icon="icon-inventory">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="entry.isRaid" ng-show="entry.isRaid"

View File

@ -0,0 +1 @@
<vn-log url="EntryLogs" origin-id="$ctrl.$stateParams.id"></vn-log>

View File

@ -0,0 +1,15 @@
import ngModule from '../module';
class Controller {
constructor($scope, $stateParams) {
this.$scope = $scope;
this.$stateParams = $stateParams;
}
}
Controller.$inject = ['$scope', '$stateParams'];
ngModule.component('vnEntryLog', {
template: require('./index.html'),
controller: Controller,
});

View File

@ -0,0 +1 @@
Date: Fecha

View File

@ -9,6 +9,8 @@
{"state": "entry.index", "icon": "icon-entry"} {"state": "entry.index", "icon": "icon-entry"}
], ],
"card": [ "card": [
{"state": "entry.card.buy", "icon": "icon-lines"},
{"state": "entry.card.log", "icon": "history"}
] ]
}, },
"routes": [ "routes": [
@ -36,6 +38,19 @@
"params": { "params": {
"entry": "$ctrl.entry" "entry": "$ctrl.entry"
} }
}, {
"url" : "/log",
"state": "entry.card.log",
"component": "vn-entry-log",
"description": "Log"
}, {
"url" : "/buy",
"state": "entry.card.buy",
"component": "vn-entry-buy",
"description": "Buy",
"params": {
"entry": "$ctrl.entry"
}
} }
] ]
} }

View File

@ -175,5 +175,12 @@
"model": "ItemNiche", "model": "ItemNiche",
"foreignKey": "itemFk" "foreignKey": "itemFk"
} }
},
"scope": {
"where": {
"name": {
"neq": ""
}
}
} }
} }

View File

@ -55,6 +55,10 @@ module.exports = Self => {
arg: 'isConfirmed', arg: 'isConfirmed',
type: 'Boolean', type: 'Boolean',
description: `Order is confirmed` description: `Order is confirmed`
}, {
arg: 'showEmpty',
type: 'boolean',
description: `Show empty orders`
} }
], ],
returns: { returns: {
@ -75,15 +79,16 @@ module.exports = Self => {
{relation: 'collegues'} {relation: 'collegues'}
] ]
}); });
const args = ctx.args;
let teamIds = []; let teamIds = [];
if (worker.collegues().length && ctx.args.myTeam) { if (worker.collegues().length && args.myTeam) {
worker.collegues().forEach(collegue => { worker.collegues().forEach(collegue => {
teamIds.push(collegue.collegueFk); teamIds.push(collegue.collegueFk);
}); });
} }
if (worker.collegues().length === 0 && ctx.args.myTeam) { if (worker.collegues().length === 0 && args.myTeam) {
worker = await Self.app.models.Worker.findOne({ worker = await Self.app.models.Worker.findOne({
fields: ['id'], fields: ['id'],
where: {userFk: ctx.req.accessToken.userId} where: {userFk: ctx.req.accessToken.userId}
@ -91,9 +96,9 @@ module.exports = Self => {
teamIds = [worker && worker.id]; teamIds = [worker && worker.id];
} }
if (ctx.args && ctx.args.myTeam) if (args && args.myTeam)
ctx.args.teamIds = teamIds; args.teamIds = teamIds;
let where = buildFilter(ctx.args, (param, value) => { let where = buildFilter(args, (param, value) => {
switch (param) { switch (param) {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
@ -101,7 +106,6 @@ module.exports = Self => {
: {or: [ : {or: [
{'c.name': {like: `%${value}%`}} {'c.name': {like: `%${value}%`}}
]}; ]};
// return {'o.id': value};
case 'from': case 'from':
return {'o.date_send': {gte: value}}; return {'o.date_send': {gte: value}};
case 'to': case 'to':
@ -120,6 +124,8 @@ module.exports = Self => {
return {'o.confirmed': value ? 1 : 0}; return {'o.confirmed': value ? 1 : 0};
case 'myTeam': case 'myTeam':
return {'c.salesPersonFk': {inq: teamIds}}; return {'c.salesPersonFk': {inq: teamIds}};
case 'showEmpty':
return {'o.total': {neq: value}};
case 'id': case 'id':
param = `o.${param}`; param = `o.${param}`;
return {[param]: value}; return {[param]: value};
@ -159,11 +165,12 @@ module.exports = Self => {
LEFT JOIN account.user u ON u.id = wk.userFk LEFT JOIN account.user u ON u.id = wk.userFk
LEFT JOIN company co ON co.id = o.company_id`); LEFT JOIN company co ON co.id = o.company_id`);
if (ctx.args && ctx.args.ticketFk) { if (args && args.ticketFk) {
stmt.merge({ stmt.merge({
sql: `LEFT JOIN orderTicket ort ON ort.orderFk = o.id` sql: `LEFT JOIN orderTicket ort ON ort.orderFk = o.id`
}); });
} }
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));
stmts.push(stmt); stmts.push(stmt);
@ -176,10 +183,11 @@ module.exports = Self => {
stmts.push('CALL hedera.order_getTotal()'); stmts.push('CALL hedera.order_getTotal()');
let orderIndex = stmts.push(` stmt = new ParameterizedSQL(
SELECT f.*, ot.* `SELECT f.*, ot.*
FROM tmp.filter f FROM tmp.filter f
LEFT JOIN tmp.orderTotal ot ON ot.orderFk = f.id`) - 1; LEFT JOIN tmp.orderTotal ot ON ot.orderFk = f.id`);
const orderIndex = stmts.push(stmt) - 1;
stmts.push(` stmts.push(`
DROP TEMPORARY TABLE DROP TEMPORARY TABLE

View File

@ -1,32 +1,54 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('order filter()', () => { describe('order filter()', () => {
let ctx = { const ctx = {
req: {accessToken: {userId: 9}}, req: {accessToken: {userId: 9}},
args: {}, args: {},
params: {} params: {}
}; };
it('should call the filter method with a basic search', async() => { it('should call the filter method with a basic search', async() => {
let filter = {where: {'o.id': 2}}; const filter = {where: {'o.id': 2}};
let result = await app.models.Order.filter(ctx, filter); const result = await app.models.Order.filter(ctx, filter);
let orderId = result[0].id; const orderId = result[0].id;
expect(orderId).toEqual(2); expect(orderId).toEqual(2);
}); });
it('should call the filter method with a single advanced search', async() => { it('should call the filter method with a single advanced search', async() => {
let filter = {where: {'o.confirmed': false}}; const filter = {where: {'o.confirmed': false}};
let result = await app.models.Order.filter(ctx, filter); const result = await app.models.Order.filter(ctx, filter);
expect(result.length).toEqual(16); expect(result.length).toEqual(16);
}); });
it('should call the filter method with a complex advanced search', async() => { it('should call the filter method with a complex advanced search', async() => {
let filter = {where: {'o.confirmed': false, 'c.salesPersonFk': 19}}; const filter = {where: {'o.confirmed': false, 'c.salesPersonFk': 19}};
let result = await app.models.Order.filter(ctx, filter); const result = await app.models.Order.filter(ctx, filter);
expect(result.length).toEqual(7); expect(result.length).toEqual(7);
expect(result[0].id).toEqual(16); expect(result[0].id).toEqual(16);
}); });
it('should return the orders matching the showEmpty on false', async() => {
const filter = {};
ctx.args = {showEmpty: false};
const result = await app.models.Order.filter(ctx, filter);
const hasEmptyLines = result.some(order => {
return order.total === 0;
});
expect(hasEmptyLines).toBeFalsy();
});
it('should return the orders matching the showEmpty on true', async() => {
const filter = {};
ctx.args = {showEmpty: true};
const result = await app.models.Order.filter(ctx, filter);
const hasEmptyLines = result.some(order => {
return order.total === 0;
});
expect(hasEmptyLines).toBeTruthy();
});
}); });

View File

@ -9,7 +9,8 @@
<vn-searchbar <vn-searchbar
panel="vn-order-search-panel" panel="vn-order-search-panel"
info="Search orders by id" info="Search orders by id"
model="model"> model="model"
filter="$ctrl.filter">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-portal slot="menu"> <vn-portal slot="menu">

View File

@ -1,7 +1,11 @@
import ngModule from '../module'; import ngModule from '../module';
import ModuleMain from 'salix/components/module-main'; import ModuleMain from 'salix/components/module-main';
export default class Order extends ModuleMain {} export default class Order extends ModuleMain {
$postLink() {
this.filter = {showEmpty: false};
}
}
ngModule.vnComponent('vnOrder', { ngModule.vnComponent('vnOrder', {
controller: Order, controller: Order,

View File

@ -10,11 +10,6 @@
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield
vn-one
label="Order id"
ng-model="filter.id">
</vn-textfield>
<vn-textfield <vn-textfield
vn-one vn-one
label="Client id" label="Client id"
@ -81,6 +76,11 @@
triple-state="true" triple-state="true"
ng-model="filter.isConfirmed"> ng-model="filter.isConfirmed">
</vn-check> </vn-check>
<vn-check
vn-one
label="Show empty"
ng-model="filter.showEmpty">
</vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-mt-lg"> <vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit> <vn-submit label="Search"></vn-submit>

View File

@ -1,4 +1,4 @@
Order id: Id pedido Order id: Id cesta
Client id: Id cliente Client id: Id cliente
From landed: Desde f. entrega From landed: Desde f. entrega
To landed: Hasta f. entrega To landed: Hasta f. entrega
@ -6,4 +6,5 @@ To: Hasta
Agency: Agencia Agency: Agencia
Application: Aplicación Application: Aplicación
SalesPerson: Comercial SalesPerson: Comercial
Order confirmed: Pedido confirmado Order confirmed: Pedido confirmado
Show empty: Mostrar vacías

View File

@ -1,12 +1,10 @@
import ngModule from '../module'; import ngModule from '../module';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $scope, $httpParamSerializer) {
super($element, $scope);
class Controller {
constructor($, $http, vnApp, $translate, aclService, $httpParamSerializer) {
this.$http = $http;
this.vnApp = vnApp;
this.$translate = $translate;
this.$ = $;
this.aclService = aclService;
this.$httpParamSerializer = $httpParamSerializer; this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [ this.moreOptions = [
{callback: this.showRouteReport, name: 'Show route report'}, {callback: this.showRouteReport, name: 'Show route report'},
@ -37,9 +35,9 @@ class Controller {
} }
showRouteReport() { showRouteReport() {
const user = this.route.worker.user;
const params = { const params = {
clientId: user.id, authorization: this.vnToken.token,
clientId: this.vnConfig.storage.currentUserWorkerId,
routeId: this.route.id routeId: this.route.id
}; };
const serializedParams = this.$httpParamSerializer(params); const serializedParams = this.$httpParamSerializer(params);
@ -48,15 +46,12 @@ class Controller {
} }
sendRouteReport() { sendRouteReport() {
const user = this.route.worker.user;
const params = { const params = {
recipient: user.emailUser.email, recipient: user.emailUser.email,
clientId: user.id, clientId: this.vnConfig.storage.currentUserWorkerId,
routeId: this.route.id routeId: this.route.id
}; };
const serializedParams = this.$httpParamSerializer(params); this.$http.get(`email/driver-route`, {params}).then(() => {
const url = `email/driver-route?${serializedParams}`;
this.$http.get(url).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Report sent')); this.vnApp.showSuccess(this.$translate.instant('Report sent'));
}); });
} }
@ -76,7 +71,7 @@ class Controller {
} }
} }
Controller.$inject = ['$scope', '$http', 'vnApp', '$translate', 'aclService', '$httpParamSerializer']; Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnRouteDescriptor', { ngModule.component('vnRouteDescriptor', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,6 +1,6 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="Routes/{{$ctrl.$stateParams.id}}/getTickets" url="Routes/{{$ctrl.$params.id}}/getTickets"
order="priority ASC" order="priority ASC"
data="$ctrl.tickets" data="$ctrl.tickets"
auto-load="true"> auto-load="true">
@ -23,7 +23,7 @@
</vn-tool-bar> </vn-tool-bar>
</vn-card> </vn-card>
<vn-card class="vn-mt-md"> <vn-card class="vn-mt-md">
<vn-table model="model" auto-load="false"> <vn-table model="model" auto-load="false" vn-droppable="$ctrl.onDrop($event)">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th shrink> <vn-th shrink>
@ -86,7 +86,7 @@
</vn-td> </vn-td>
<vn-td> <vn-td>
<vn-icon-button <vn-icon-button
translate-attr="::{title: 'Remove ticket'}" translate-attr="{title: 'Remove ticket'}"
icon="delete" icon="delete"
ng-click="$ctrl.showDeleteConfirm(ticket.id)" ng-click="$ctrl.showDeleteConfirm(ticket.id)"
tabindex="-1"> tabindex="-1">

View File

@ -1,26 +1,24 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss'; import './style.scss';
class Controller { class Controller extends Section {
constructor($stateParams, $scope, $translate, $http, vnApp, $filter) { constructor($element, $scope, $filter) {
this.$translate = $translate; super($element, $scope);
this.$stateParams = $stateParams;
this.$ = $scope;
this.$http = $http;
this.vnApp = vnApp;
this.$filter = $filter; this.$filter = $filter;
} }
get route() {
return this._route;
}
set route(value) { set route(value) {
this._route = value; this._route = value;
if (value) if (value)
this.buildPossibleTicketsFilter(); this.buildPossibleTicketsFilter();
} }
get route() {
return this._route;
}
get isChecked() { get isChecked() {
if (this.tickets) { if (this.tickets) {
for (let instance of this.tickets) for (let instance of this.tickets)
@ -104,7 +102,6 @@ class Controller {
}); });
} }
showDeleteConfirm(id) { showDeleteConfirm(id) {
this.selectedTicket = id; this.selectedTicket = id;
this.$.confirm.show(); this.$.confirm.show();
@ -122,7 +119,7 @@ class Controller {
} }
updateVolume() { updateVolume() {
let url = `Routes/${this.$stateParams.id}/updateVolume`; let url = `Routes/${this.$params.id}/updateVolume`;
this.$http.post(url).then(() => { this.$http.post(url).then(() => {
this.card.reload(); this.card.reload();
this.$.model.refresh(); this.$.model.refresh();
@ -130,7 +127,7 @@ class Controller {
} }
guessPriority() { guessPriority() {
let query = `Routes/${this.$stateParams.id}/guessPriority/`; let query = `Routes/${this.$params.id}/guessPriority/`;
this.$http.get(query).then(() => { this.$http.get(query).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Order changed')); this.vnApp.showSuccess(this.$translate.instant('Order changed'));
this.$.model.refresh(); this.$.model.refresh();
@ -171,9 +168,39 @@ class Controller {
} }
return Promise.resolve(); return Promise.resolve();
} }
onDrop($event) {
const ticketId = $event.dataTransfer.getData('Text');
if (isNaN(ticketId)) {
const regexp = new RegExp(/\/ticket\/([0-9]+)\//i);
const matches = ticketId.match(regexp);
if (matches && matches.length)
this.insert(matches[1]);
else
this.vnApp.showError(this.$translate.instant('Ticket not found'));
}
if (!isNaN(ticketId))
this.insert(ticketId);
}
insert(id) {
const params = {routeFk: this.route.id};
this.$http.patch(`Tickets/${id}`, params).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$.model.refresh();
this.card.reload();
}).catch(error => {
if (error.status == 404)
return this.vnApp.showError(this.$translate.instant('Ticket not found'));
throw error;
});
}
} }
Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp', '$filter']; Controller.$inject = ['$element', '$scope', '$filter'];
ngModule.component('vnRouteTickets', { ngModule.component('vnRouteTickets', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,14 +1,20 @@
import './index.js'; import './index';
describe('Route', () => { describe('Route', () => {
let controller; let controller;
let $httpBackend; let $httpBackend;
let $scope;
beforeEach(ngModule('route')); beforeEach(ngModule('route'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => { beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
controller = $componentController('vnRouteTickets'); $scope = $rootScope.$new();
const $element = angular.element('<vn-route-tickets></vn-route-tickets>');
controller = $componentController('vnRouteTickets', {$element, $scope});
controller.route = {id: 1};
controller.$.model = {refresh: () => {}};
controller.card = {reload: () => {}};
})); }));
describe('route setter/getter', () => { describe('route setter/getter', () => {
@ -86,7 +92,6 @@ describe('Route', () => {
describe('setPriority()', () => { describe('setPriority()', () => {
it('should set a ticket priority', () => { it('should set a ticket priority', () => {
controller.$.model = {refresh: () => {}};
jest.spyOn(controller.$.model, 'refresh'); jest.spyOn(controller.$.model, 'refresh');
jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnApp, 'showSuccess');
const ticketId = 1; const ticketId = 1;
@ -175,16 +180,14 @@ describe('Route', () => {
describe('updateVolume()', () => { describe('updateVolume()', () => {
it('should perform a POST query then call both reload and refresh methods', () => { it('should perform a POST query then call both reload and refresh methods', () => {
controller.$.model = {refresh: () => {}}; controller.$params = {id: 999};
controller.card = {reload: () => {}};
controller.$stateParamds = {id: 999};
jest.spyOn(controller.$.model, 'refresh'); jest.spyOn(controller.$.model, 'refresh');
jest.spyOn(controller.card, 'reload'); jest.spyOn(controller.card, 'reload');
let ticketId = 1; let ticketId = 1;
controller.selectedTicket = ticketId; controller.selectedTicket = ticketId;
const url = `Routes/${controller.$stateParams.id}/updateVolume`; const url = `Routes/${controller.$params.id}/updateVolume`;
$httpBackend.expectPOST(url).respond('ok'); $httpBackend.expectPOST(url).respond('ok');
controller.updateVolume(); controller.updateVolume();
$httpBackend.flush(); $httpBackend.flush();
@ -196,12 +199,11 @@ describe('Route', () => {
describe('guessPriority()', () => { describe('guessPriority()', () => {
it('should perform a GET query then call both refresh and showSuccess methods', () => { it('should perform a GET query then call both refresh and showSuccess methods', () => {
controller.$.model = {refresh: () => {}};
jest.spyOn(controller.$.model, 'refresh'); jest.spyOn(controller.$.model, 'refresh');
jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnApp, 'showSuccess');
controller.$stateParams = {id: 99}; controller.$params = {id: 99};
const url = `Routes/${controller.$stateParams.id}/guessPriority/`; const url = `Routes/${controller.$params.id}/guessPriority/`;
$httpBackend.expectGET(url).respond('ok'); $httpBackend.expectGET(url).respond('ok');
controller.guessPriority(); controller.guessPriority();
$httpBackend.flush(); $httpBackend.flush();
@ -288,4 +290,66 @@ describe('Route', () => {
expect(controller.setTicketsRoute('cancel')).toEqual(jasmine.any(Promise)); expect(controller.setTicketsRoute('cancel')).toEqual(jasmine.any(Promise));
}); });
}); });
describe('onDrop()', () => {
it('should call the insert method when dragging a ticket number', () => {
jest.spyOn(controller, 'insert');
const expectedTicketId = '11';
const draggedElement = '11';
const $event = {
dataTransfer: {
getData: () => draggedElement
}
};
controller.onDrop($event);
expect(controller.insert).toHaveBeenCalledWith(expectedTicketId);
});
it('should call the insert method when dragging a ticket link', () => {
jest.spyOn(controller, 'insert');
const expectedTicketId = '11';
const draggedElement = 'http://arkamcity.com/#!/ticket/11/summary';
const $event = {
dataTransfer: {
getData: () => draggedElement
}
};
controller.onDrop($event);
expect(controller.insert).toHaveBeenCalledWith(expectedTicketId);
});
it('should throw an error when dragging an invalid ticket link', () => {
jest.spyOn(controller.vnApp, 'showError');
const draggedElement = 'http://arkamcity.com/#!/item/11/summary';
const $event = {
dataTransfer: {
getData: () => draggedElement
}
};
controller.onDrop($event);
expect(controller.vnApp.showError).toHaveBeenCalledWith('Ticket not found');
});
});
describe('insert()', () => {
it('should make a HTTP patch query and then call both refresh and showSuccess methods', () => {
jest.spyOn(controller.$.model, 'refresh').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess');
const ticketId = 11;
$httpBackend.expect('PATCH', `Tickets/11`).respond({id: 11});
controller.insert(ticketId);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect(controller.$.model.refresh).toHaveBeenCalledWith();
});
});
}); });

View File

@ -5,4 +5,5 @@ Order changed: Orden cambiado
Delete ticket from route?: ¿Quitar el ticket de la ruta? Delete ticket from route?: ¿Quitar el ticket de la ruta?
Sort routes: Ordenar rutas Sort routes: Ordenar rutas
Add ticket: Añadir ticket Add ticket: Añadir ticket
Tickets to add: Tickets a añadir Tickets to add: Tickets a añadir
Ticket not found: No se ha encontrado el ticket

View File

@ -1,4 +1,5 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const diff = require('object-diff');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('componentUpdate', { Self.remoteMethodCtx('componentUpdate', {
@ -11,32 +12,32 @@ module.exports = Self => {
description: 'The ticket id', description: 'The ticket id',
http: {source: 'path'} http: {source: 'path'}
}, { }, {
arg: 'clientId', arg: 'clientFk',
type: 'Number', type: 'Number',
description: 'The client id', description: 'The client id',
required: true required: true
}, { }, {
arg: 'agencyModeId', arg: 'agencyModeFk',
type: 'Number', type: 'Number',
description: 'The agencyMode id', description: 'The agencyMode id',
required: true required: true
}, { }, {
arg: 'addressId', arg: 'addressFk',
type: 'Number', type: 'Number',
description: 'The address id', description: 'The address id',
required: true required: true
}, { }, {
arg: 'zoneId', arg: 'zoneFk',
type: 'Number', type: 'Number',
description: 'The zone id', description: 'The zone id',
required: true required: true
}, { }, {
arg: 'warehouseId', arg: 'warehouseFk',
type: 'Number', type: 'Number',
description: 'The warehouse id', description: 'The warehouse id',
required: true required: true
}, { }, {
arg: 'companyId', arg: 'companyFk',
type: 'Number', type: 'Number',
description: 'The company id', description: 'The company id',
required: true required: true
@ -71,8 +72,8 @@ module.exports = Self => {
} }
}); });
Self.componentUpdate = async(ctx, id, clientId, agencyModeId, addressId, zoneId, warehouseId, Self.componentUpdate = async(ctx, id, clientFk, agencyModeFk, addressFk, zoneFk, warehouseFk,
companyId, shipped, landed, isDeleted, option) => { companyFk, shipped, landed, isDeleted, option) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const isEditable = await models.Ticket.isEditable(ctx, id); const isEditable = await models.Ticket.isEditable(ctx, id);
@ -82,29 +83,49 @@ module.exports = Self => {
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss'); const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss');
if (!isProductionBoss) { if (!isProductionBoss) {
const zoneShipped = await models.Agency.getShipped(landed, addressId, agencyModeId, warehouseId); const zoneShipped = await models.Agency.getShipped(landed, addressFk, agencyModeFk, warehouseFk);
if (!zoneShipped || zoneShipped.zoneFk != zoneId) if (!zoneShipped || zoneShipped.zoneFk != zoneFk)
throw new UserError(`You don't have privileges to change the zone`); throw new UserError(`You don't have privileges to change the zone`);
} }
const originalTicket = await models.Ticket.findById(id, {fields:
['id', 'clientFk', 'agencyModeFk', 'addressFk', 'zoneFk',
'warehouseFk', 'companyFk', 'shipped', 'landed', 'isDeleted']
});
const updatedTicket = Object.assign({}, ctx.args);
delete updatedTicket.ctx;
delete updatedTicket.option;
// Force unroute // Force unroute
const hasToBeUnrouted = true; const hasToBeUnrouted = true;
const propertiesChange = diff(originalTicket, updatedTicket);
let logRecord = {
originFk: id,
userFk: userId,
action: 'update',
changedModel: 'Ticket',
changedModelId: id,
oldInstance: originalTicket,
newInstance: propertiesChange
};
let query = 'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; let query = 'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
let res = await Self.rawSql(query, [ let res = await Self.rawSql(query, [
id, id,
clientId, clientFk,
agencyModeId, agencyModeFk,
addressId, addressFk,
zoneId, zoneFk,
warehouseId, warehouseFk,
companyId, companyFk,
shipped, shipped,
landed, landed,
isDeleted, isDeleted,
hasToBeUnrouted, hasToBeUnrouted,
option option
]); ]);
await models.TicketLog.create(logRecord);
return res; return res;
}; };
}; };

View File

@ -6,7 +6,7 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'clientId', arg: 'clientId',
type: 'Number', type: 'number',
description: `The client id filter`, description: `The client id filter`,
required: true required: true
}, },
@ -22,29 +22,29 @@ module.exports = Self => {
}, },
{ {
arg: 'warehouseId', arg: 'warehouseId',
type: 'Number', type: 'number',
description: `The warehouse id filter`, description: `The warehouse id filter`,
required: true required: true
}, },
{ {
arg: 'companyId', arg: 'companyId',
type: 'Number', type: 'number',
description: `The company id filter` description: `The company id filter`
}, },
{ {
arg: 'addressId', arg: 'addressId',
type: 'Number', type: 'number',
description: `The address id filter`, description: `The address id filter`,
required: true required: true
}, },
{ {
arg: 'agencyModeId', arg: 'agencyModeId',
type: 'Number', type: 'any',
description: `The agencyMode id filter` description: `The agencyMode id filter`
}, },
{ {
arg: 'routeId', arg: 'routeId',
type: 'Number', type: 'number',
description: `The route id filter` description: `The route id filter`
}], }],
returns: { returns: {

View File

@ -39,7 +39,10 @@ describe('ticket componentUpdate()', () => {
const landed = tomorrow; const landed = tomorrow;
const option = 1; const option = 1;
let ctx = {req: {accessToken: {userId: 101}}}; let ctx = {
args: {clientFk: 102,
agencyModeFk: 8},
req: {accessToken: {userId: 101}}};
await app.models.Ticket.componentUpdate(ctx, ticketId, clientId, agencyModeId, addressId, await app.models.Ticket.componentUpdate(ctx, ticketId, clientId, agencyModeId, addressId,
zoneId, warehouseId, companyId, shipped, landed, isDeleted, option); zoneId, warehouseId, companyId, shipped, landed, isDeleted, option);
@ -66,7 +69,11 @@ describe('ticket componentUpdate()', () => {
const landed = tomorrow; const landed = tomorrow;
const option = 1; const option = 1;
let ctx = {req: {accessToken: {userId: 101}}}; let ctx = {
args: {clientFk: 102,
agencyModeFk: 7},
req: {accessToken: {userId: 101}}};
await app.models.Ticket.componentUpdate(ctx, ticketId, clientId, agencyModeId, addressId, await app.models.Ticket.componentUpdate(ctx, ticketId, clientId, agencyModeId, addressId,
zoneId, warehouseId, companyId, shipped, landed, isDeleted, option); zoneId, warehouseId, companyId, shipped, landed, isDeleted, option);

View File

@ -3,13 +3,22 @@ const app = require('vn-loopback/server/server');
describe('ticket makeInvoice()', () => { describe('ticket makeInvoice()', () => {
let invoice; let invoice;
let ticketId = 11; let ticketId = 11;
const okState = 3;
afterAll(async done => { afterAll(async done => {
let ticket = await app.models.Ticket.findById(11); let ticket = await app.models.Ticket.findById(11);
await ticket.updateAttributes({refFk: null}); await ticket.updateAttributes({refFk: null});
let ticketTracking = await app.models.TicketTracking.findOne({order: 'id DESC', limit: 1}); let ticketTrackings = await app.models.TicketTracking.find({
await ticketTracking.destroy(); where: {
ticketFk: ticketId,
stateFk: {neq: okState}
},
order: 'id DESC'
});
for (let state of ticketTrackings)
await state.destroy();
let invoiceOut = await app.models.InvoiceOut.findById(invoice.invoiceFk); let invoiceOut = await app.models.InvoiceOut.findById(invoice.invoiceFk);
await invoiceOut.destroy(); await invoiceOut.destroy();

View File

@ -22,7 +22,7 @@ class Controller {
if (!value || !value.id) return; if (!value || !value.id) return;
this.onChangeClient(value.clientFk); this.clientAddressesList(value.clientFk);
} }
get clientId() { get clientId() {
@ -33,7 +33,10 @@ class Controller {
this.ticket.clientFk = value; this.ticket.clientFk = value;
this.ticket.addressFk = null; this.ticket.addressFk = null;
this.onChangeClient(value); if (!value) return;
this.getClientDefaultAddress(value);
this.clientAddressesList(value);
} }
get addressId() { get addressId() {
@ -68,7 +71,6 @@ class Controller {
} }
} }
get shipped() { get shipped() {
return this.ticket && this.ticket.shipped; return this.ticket && this.ticket.shipped;
} }
@ -127,7 +129,7 @@ class Controller {
/* /*
* Autocompletes address on client change * Autocompletes address on client change
*/ */
onChangeClient(value) { clientAddressesList(value) {
let filter = { let filter = {
include: [ include: [
{ {
@ -153,6 +155,14 @@ class Controller {
}); });
} }
getClientDefaultAddress(value) {
let query = `Clients/${value}`;
this.$http.get(query).then(res => {
if (res.data)
this.ticket.addressFk = res.data.defaultAddressFk;
});
}
/* /*
* Gets an agency from an specified zone * Gets an agency from an specified zone
*/ */

View File

@ -22,18 +22,18 @@ describe('Ticket', () => {
})); }));
describe('ticket() setter', () => { describe('ticket() setter', () => {
it('should set ticket property and call onChangeClient() method', () => { it('should set ticket property and call clientAddressesList() method', () => {
jest.spyOn(controller, 'onChangeClient'); jest.spyOn(controller, 'clientAddressesList');
controller.ticket = {id: 1, clientFk: 101}; controller.ticket = {id: 1, clientFk: 101};
expect(controller.onChangeClient).toHaveBeenCalledWith(101); expect(controller.clientAddressesList).toHaveBeenCalledWith(101);
}); });
it(`should not call onChangeClient() method as the ticket doesn't have an ID`, () => { it(`should not call clientAddressesList() method as the ticket doesn't have an ID`, () => {
jest.spyOn(controller, 'onChangeClient'); jest.spyOn(controller, 'clientAddressesList');
controller.ticket = {}; controller.ticket = {};
expect(controller.onChangeClient).not.toHaveBeenCalledWith(); expect(controller.clientAddressesList).not.toHaveBeenCalledWith();
}); });
}); });
@ -46,12 +46,12 @@ describe('Ticket', () => {
}); });
describe('clientId() setter', () => { describe('clientId() setter', () => {
it('should set clientId property and call onChangeClient() method ', () => { it('should set clientId property and call clientAddressesList() method ', () => {
jest.spyOn(controller, 'onChangeClient'); jest.spyOn(controller, 'clientAddressesList');
controller.ticket = {id: 1, clientId: 101}; controller.ticket = {id: 1, clientId: 101};
controller.clientId = 102; controller.clientId = 102;
expect(controller.onChangeClient).toHaveBeenCalledWith(102); expect(controller.clientAddressesList).toHaveBeenCalledWith(102);
}); });
}); });
@ -155,7 +155,6 @@ describe('Ticket', () => {
}; };
controller.landed = landed; controller.landed = landed;
expect(controller.getShipped).toHaveBeenCalledWith(expectedResult); expect(controller.getShipped).toHaveBeenCalledWith(expectedResult);
}); });
}); });
@ -230,7 +229,7 @@ describe('Ticket', () => {
}); });
}); });
describe('onChangeClient()', () => { describe('clientAddressesList()', () => {
it('should return a list of addresses from choosed client', async() => { it('should return a list of addresses from choosed client', async() => {
const clientId = 102; const clientId = 102;
let filter = { let filter = {
@ -253,7 +252,18 @@ describe('Ticket', () => {
$httpBackend.when('GET', `Clients/${clientId}/addresses?filter=${filter}`).respond(200); $httpBackend.when('GET', `Clients/${clientId}/addresses?filter=${filter}`).respond(200);
$httpBackend.expect('GET', `Clients/${clientId}/addresses?filter=${filter}`); $httpBackend.expect('GET', `Clients/${clientId}/addresses?filter=${filter}`);
controller.onChangeClient(clientId); controller.clientAddressesList(clientId);
$httpBackend.flush();
});
});
describe('getClientDefaultAddress()', () => {
it('should return the default address from choosed client', async() => {
const clientId = 102;
$httpBackend.when('GET', `Clients/${clientId}`).respond(200);
$httpBackend.expect('GET', `Clients/${clientId}`);
controller.getClientDefaultAddress(clientId);
$httpBackend.flush(); $httpBackend.flush();
}); });
}); });

View File

@ -75,12 +75,12 @@ class Controller {
let query = `tickets/${this.ticket.id}/componentUpdate`; let query = `tickets/${this.ticket.id}/componentUpdate`;
let params = { let params = {
clientId: this.ticket.clientFk, clientFk: this.ticket.clientFk,
agencyModeId: this.ticket.agencyModeFk, agencyModeFk: this.ticket.agencyModeFk,
addressId: this.ticket.addressFk, addressFk: this.ticket.addressFk,
zoneId: this.ticket.zoneFk, zoneFk: this.ticket.zoneFk,
warehouseId: this.ticket.warehouseFk, warehouseFk: this.ticket.warehouseFk,
companyId: this.ticket.companyFk, companyFk: this.ticket.companyFk,
shipped: this.ticket.shipped, shipped: this.ticket.shipped,
landed: this.ticket.landed, landed: this.ticket.landed,
isDeleted: this.ticket.isDeleted, isDeleted: this.ticket.isDeleted,

View File

@ -1,4 +1,5 @@
Price (PPU): Precio (Ud.) Price (PPU): Precio (Ud.)
New (PPU): Nuevo (Ud.) New (PPU): Nuevo (Ud.)
Difference: Diferencia Difference: Diferencia
Charge difference to: Cargar diferencia a Charge difference to: Cargar diferencia a
The ticket has been unrouted: El ticket ha sido desenrutado

View File

@ -2,10 +2,10 @@ import ngModule from '../module';
import Component from 'core/lib/component'; import Component from 'core/lib/component';
class Controller extends Component { class Controller extends Component {
constructor($element, $, aclService, $httpParamSerializer) { constructor($element, $, $httpParamSerializer) {
super($element, $); super($element, $);
this.aclService = aclService;
this.$httpParamSerializer = $httpParamSerializer; this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [ this.moreOptions = [
{ {
name: 'Add turn', name: 'Add turn',
@ -16,7 +16,8 @@ class Controller extends Component {
{name: 'Send Delivery Note', callback: this.confirmDeliveryNote}, {name: 'Send Delivery Note', callback: this.confirmDeliveryNote},
{name: 'Delete ticket', callback: this.showDeleteTicketDialog}, {name: 'Delete ticket', callback: this.showDeleteTicketDialog},
{name: 'Change shipped hour', callback: this.showChangeShipped}, {name: 'Change shipped hour', callback: this.showChangeShipped},
{name: 'Send SMS', callback: this.showSMSDialog}, {name: 'SMS Pending payment', callback: this.sendPaymentSms},
{name: 'SMS Minimum import', callback: this.sendImportSms},
{ {
name: 'Add stowaway', name: 'Add stowaway',
callback: this.showAddStowaway, callback: this.showAddStowaway,
@ -220,7 +221,8 @@ class Controller extends Component {
showDeliveryNote() { showDeliveryNote() {
const params = { const params = {
clientId: this.ticket.client.id, clientId: this.ticket.client.id,
ticketId: this.ticket.id ticketId: this.ticket.id,
authorization: this.vnToken.token
}; };
const serializedParams = this.$httpParamSerializer(params); const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/delivery-note?${serializedParams}`; let url = `api/report/delivery-note?${serializedParams}`;
@ -239,17 +241,30 @@ class Controller extends Component {
); );
} }
sendImportSms() {
const params = {
ticketId: this.ticket.id,
created: this.ticket.created
};
const message = this.$params.message || this.$translate.instant('Minimum is needed', params);
this.newSMS = {message};
this.showSMSDialog();
}
sendPaymentSms() {
const message = this.$params.message || this.$translate.instant('Make a payment');
this.newSMS = {message};
this.showSMSDialog();
}
showSMSDialog() { showSMSDialog() {
const address = this.ticket.address; const address = this.ticket.address;
const client = this.ticket.client; const client = this.ticket.client;
const phone = this.$params.phone || address.mobile || address.phone || const phone = this.$params.phone || address.mobile || address.phone ||
client.mobile || client.phone; client.mobile || client.phone;
const message = this.$params.message || this.$translate.instant('SMSPayment');
this.newSMS = { this.newSMS.destinationFk = this.ticket.clientFk;
destinationFk: this.ticket.clientFk, this.newSMS.destination = phone;
destination: phone,
message: message
};
this.$.sms.open(); this.$.sms.open();
} }
@ -332,7 +347,7 @@ class Controller extends Component {
} }
} }
Controller.$inject = ['$element', '$scope', 'aclService', '$httpParamSerializer']; Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnTicketDescriptor', { ngModule.component('vnTicketDescriptor', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -20,7 +20,13 @@ describe('Ticket Component vnTicketDescriptor', () => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_; $httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnTicketDescriptor', {$element}); controller = $componentController('vnTicketDescriptor', {$element});
controller._ticket = {id: 2, invoiceOut: {id: 1}, client: {id: 101, email: 'client@email'}}; controller._ticket = {
id: 2,
clientFk: 101,
invoiceOut: {id: 1},
client: {id: 101, email: 'client@email'},
address: {id: 101, mobile: 111111111, phone: 2222222222}
};
controller.cardReload = ()=> { controller.cardReload = ()=> {
return true; return true;
}; };
@ -161,7 +167,6 @@ describe('Ticket Component vnTicketDescriptor', () => {
}); });
}); });
describe('showAddStowaway()', () => { describe('showAddStowaway()', () => {
it('should show a dialog with a list of tickets available for an stowaway', () => { it('should show a dialog with a list of tickets available for an stowaway', () => {
controller.$.addStowaway = {}; controller.$.addStowaway = {};
@ -223,4 +228,20 @@ describe('Ticket Component vnTicketDescriptor', () => {
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
}); });
}); });
describe('showSMSDialog()', () => {
it('should set the destionationFk and destination properties and then call the sms open() method', () => {
controller.$.sms = {open: () => {}};
jest.spyOn(controller.$.sms, 'open');
const clientId = 101;
const expectedPhone = 111111111;
controller.newSMS = {};
controller.showSMSDialog();
expect(controller.newSMS.destinationFk).toEqual(clientId);
expect(controller.newSMS.destination).toEqual(expectedPhone);
expect(controller.$.sms.open).toHaveBeenCalledWith();
});
});
}); });

View File

@ -1,3 +1,2 @@
SMSPayment: >- Make a payment: "Verdnatura communicates:\rYour order is pending of payment.\rPlease, enter the web page and make the payment with card.\rThank you."
Verdnatura communicates: Your order is pending of payment. Minimum is needed: "Verdnatura communicates:\rA minimum import of 50€ (Without BAT) is needed for your order {{ticketId}} from date {{created | date: 'dd/MM/yyyy'}} to receive it with no extra fees."
Please, enter the web page and make the payment with card. Thank you.

View File

@ -13,7 +13,8 @@ Send Delivery Note: Enviar albarán
Show pallet report: Ver hoja de pallet Show pallet report: Ver hoja de pallet
Change shipped hour: Cambiar hora de envío Change shipped hour: Cambiar hora de envío
Shipped hour: Hora de envío Shipped hour: Hora de envío
SMSPayment: "Verdnatura le comunica:\rSu pedido está pendiente de pago.\rPor favor, entre en la página web y efectue el pago con tarjeta.\rMuchas gracias." Make a payment: "Verdnatura le comunica:\rSu pedido está pendiente de pago.\rPor favor, entre en la página web y efectue el pago con tarjeta.\rMuchas gracias."
Minimum is needed: "Verdnatura le recuerda:\rEs necesario llegar a un importe mínimo de 50€ (Sin IVA) en su pedido {{ticketId}} del día {{created | date: 'dd/MM/yyyy'}} para recibirlo sin portes adicionales."
Ticket invoiced: Ticket facturado Ticket invoiced: Ticket facturado
Make invoice: Crear factura Make invoice: Crear factura
Regenerate invoice: Regenerar factura Regenerate invoice: Regenerar factura
@ -25,4 +26,6 @@ Invoice sent for a regeneration, will be available in a few minutes: La factura
Shipped hour updated: Hora de envio modificada Shipped hour updated: Hora de envio modificada
Deleted ticket: Ticket eliminado Deleted ticket: Ticket eliminado
Recalculate components: Recalcular componentes Recalculate components: Recalcular componentes
Are you sure you want to recalculate the components?: ¿Seguro que quieres recalcular los componentes? Are you sure you want to recalculate the components?: ¿Seguro que quieres recalcular los componentes?
SMS Minimum import: 'SMS Importe minimo'
SMS Pending payment: 'SMS Pago pendiente'

View File

@ -1,16 +1,5 @@
<vn-auto-search <vn-auto-search
model="model"> model="model">
<append style="display: none;">
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-icon-menu>
</append>
</vn-auto-search> </vn-auto-search>
<vn-data-viewer <vn-data-viewer
model="model" model="model"
@ -129,13 +118,28 @@
</vn-table> </vn-table>
</vn-card> </vn-card>
</vn-data-viewer> </vn-data-viewer>
<a
ui-sref="ticket.create" <div fixed-bottom-right>
vn-tooltip="New ticket" <vn-vertical style="align-items: center;">
vn-bind="+" <vn-button class="round sm vn-mb-sm"
fixed-bottom-right> icon="icon-recovery"
<vn-float-button icon="add"></vn-float-button> ng-show="$ctrl.totalChecked > 0"
</a> ng-click="$ctrl.openBalanceDialog()"
vn-tooltip="Payment on account..."
tooltip-position="left">
</vn-button>
<a ui-sref="ticket.create">
<vn-button class="round md vn-mb-sm"
icon="add"
vn-bind="+"
vn-tooltip="New ticket"
tooltip-position="left">
</vn-button>
</a>
</vn-vertical>
</div>
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-ticket-summary ticket="$ctrl.selectedTicket"></vn-ticket-summary> <vn-ticket-summary ticket="$ctrl.selectedTicket"></vn-ticket-summary>
</vn-popup> </vn-popup>

View File

@ -1,4 +1,5 @@
import ngModule from '../module'; import ngModule from '../module';
import UserError from 'core/lib/user-error';
import './style.scss'; import './style.scss';
export default class Controller { export default class Controller {
@ -9,33 +10,45 @@ export default class Controller {
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this.$state = $state; this.$state = $state;
this.selectedTicket = null; this.selectedTicket = null;
this.moreOptions = [
{
name: 'Payment on account...',
always: true,
callback: () => {
this.setBalanceCreateDialog();
this.$.balanceCreateDialog.show();
}
}
];
} }
setBalanceCreateDialog() { openBalanceDialog() {
let data = this.$.tickets; const checkedTickets = this.checked;
let description = []; const description = [];
this.$.balanceCreateDialog.amountPaid = 0; this.$.balanceCreateDialog.amountPaid = 0;
if (data) {
for (let i = 0; i < data.length; i++) { const firstTicketClientId = checkedTickets[0].clientFk;
if (data[i].checked) { const isSameClient = checkedTickets.every(ticket => {
this.$.balanceCreateDialog.amountPaid += data[i].total; return ticket.clientFk == firstTicketClientId;
this.$.balanceCreateDialog.clientFk = data[i].clientFk; });
description.push(`${data[i].id}`);
} if (!isSameClient)
} throw new UserError('You cannot make a payment on account from multiple clients');
for (let ticket of checkedTickets) {
this.$.balanceCreateDialog.amountPaid += ticket.total;
this.$.balanceCreateDialog.clientFk = ticket.clientFk;
description.push(`${ticket.id}`);
} }
this.$.balanceCreateDialog.description = 'Albaran: '; this.$.balanceCreateDialog.description = 'Albaran: ';
this.$.balanceCreateDialog.description += description.join(', '); this.$.balanceCreateDialog.description += description.join(', ');
this.$.balanceCreateDialog.show();
}
get checked() {
const tickets = this.$.tickets || [];
const checkedLines = [];
for (let ticket of tickets) {
if (ticket.checked)
checkedLines.push(ticket);
}
return checkedLines;
}
get totalChecked() {
return this.checked.length;
} }
goToLines(event, ticketFk) { goToLines(event, ticketFk) {

View File

@ -82,12 +82,14 @@ describe('Component vnTicketIndex', () => {
}); });
}); });
describe('setBalanceCreateDialog()', () => { describe('openBalanceDialog()', () => {
it('should fill the object for the component balanceCreateDialog', () => { it('should fill the object for the component balanceCreateDialog', () => {
controller.$.balanceCreateDialog = {show: () => {}};
jest.spyOn(controller.$.balanceCreateDialog, 'show').mockReturnThis();
controller.$.tickets = tickets; controller.$.tickets = tickets;
controller.$.balanceCreateDialog = {};
controller.$.balanceCreateDialog.amountPaid = 0; controller.$.balanceCreateDialog.amountPaid = 0;
controller.setBalanceCreateDialog(); controller.openBalanceDialog();
let description = controller.$.balanceCreateDialog.description; let description = controller.$.balanceCreateDialog.description;
let amountPaid = controller.$.balanceCreateDialog.amountPaid; let amountPaid = controller.$.balanceCreateDialog.amountPaid;
@ -96,4 +98,26 @@ describe('Component vnTicketIndex', () => {
expect(amountPaid).toEqual(50.5); expect(amountPaid).toEqual(50.5);
}); });
}); });
describe('checked()', () => {
it('should return an array of checked tickets', () => {
controller.$.tickets = tickets;
const result = controller.checked;
const firstRow = result[0];
const secondRow = result[1];
expect(result.length).toEqual(2);
expect(firstRow.id).toEqual(2);
expect(secondRow.id).toEqual(3);
});
});
describe('totalChecked()', () => {
it('should return the total number of checked tickets', () => {
controller.$.tickets = tickets;
const result = controller.checked;
expect(result.length).toEqual(2);
});
});
}); });

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