Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2145_e2e_linux_fix

This commit is contained in:
Bernat 2020-03-10 08:03:24 +01:00
commit 1ee0acf310
144 changed files with 4844 additions and 2250 deletions

View File

@ -10,8 +10,7 @@ module.exports = Self => {
type: 'Number', type: 'Number',
description: 'The document id', description: 'The document id',
http: {source: 'path'} http: {source: 'path'}
}, }, {
{
arg: 'warehouseId', arg: 'warehouseId',
type: 'Number', type: 'Number',
description: 'The warehouse id' description: 'The warehouse id'
@ -44,9 +43,9 @@ module.exports = Self => {
} }
}); });
Self.updateFile = async(ctx, id, warehouseId, companyId, Self.updateFile = async(ctx, id, options) => {
dmsTypeId, reference, description, hasFileAttached, options) => {
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args;
let tx; let tx;
let myOptions = {}; let myOptions = {};
@ -60,20 +59,20 @@ module.exports = Self => {
} }
try { try {
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dmsTypeId); const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId);
if (!hasWriteRole) if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const dms = await Self.findById(id, null, myOptions); const dms = await Self.findById(id, null, myOptions);
await dms.updateAttributes({ await dms.updateAttributes({
dmsTypeFk: dmsTypeId, dmsTypeFk: args.dmsTypeId,
companyFk: companyId, companyFk: args.companyId,
warehouseFk: warehouseId, warehouseFk: args.warehouseId,
reference: reference, reference: args.reference,
description: description description: args.description
}, myOptions); }, myOptions);
if (hasFileAttached) if (args.hasFileAttached)
await uploadNewFile(ctx, dms, myOptions); await uploadNewFile(ctx, dms, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -35,6 +35,13 @@
"foreignKey": "zoneFk" "foreignKey": "zoneFk"
} }
}, },
"scopes": {
"location": {
"include": {
"relation": "country"
}
}
},
"acls": [ "acls": [
{ {
"accessType": "READ", "accessType": "READ",

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)),
@ -1901,11 +1910,11 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c
VALUES VALUES
(1, 'Facturas Recibidas', 'recibidas', NULL, NULL, 'invoiceIn'), (1, 'Facturas Recibidas', 'recibidas', NULL, NULL, 'invoiceIn'),
(2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'), (2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'),
(3, 'Laboral', 'laboral', NULL, NULL, 'hhrrData'), (3, 'Laboral', 'laboral', 37, 37, 'hhrrData'),
(4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'), (4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'),
(5, 'Otros', 'otros', 1, 1, 'miscellaneous'), (5, 'Otros', 'otros', 1, 1, 'miscellaneous'),
(6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'), (6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'),
(7, 'IAE Clientes', 'IAE_Clientes', NULL, NULL, 'economicActivitiesTax'), (7, 'IAE Clientes', 'IAE_Clientes', 1, 1, 'economicActivitiesTax'),
(8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'), (8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'),
(9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'), (9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'),
(10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'), (10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'),
@ -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

@ -9,6 +9,7 @@ export default {
invoiceOutButton: '.modules-menu > li[ui-sref="invoiceOut.index"]', invoiceOutButton: '.modules-menu > li[ui-sref="invoiceOut.index"]',
claimsButton: '.modules-menu > li[ui-sref="claim.index"]', claimsButton: '.modules-menu > li[ui-sref="claim.index"]',
returnToModuleIndexButton: 'a[ui-sref="order.index"]', returnToModuleIndexButton: 'a[ui-sref="order.index"]',
homeButton: 'vn-topbar > div.side.start > a',
userMenuButton: '#user', userMenuButton: '#user',
userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]', userLocalWarehouse: '.user-popover vn-autocomplete[ng-model="$ctrl.localWarehouseFk"]',
userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]', userLocalBank: '.user-popover vn-autocomplete[ng-model="$ctrl.localBankFk"]',
@ -43,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"]',
@ -75,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"]',
@ -113,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"]',
@ -192,7 +193,7 @@ export default {
}, },
dms: { dms: {
deleteFileButton: 'vn-client-dms-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]', deleteFileButton: 'vn-client-dms-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
firstDocWorker: 'vn-client-dms-index vn-td:nth-child(8) > span', firstDocWorker: 'vn-client-dms-index vn-td:nth-child(7) > span',
firstDocWorkerDescriptor: '.vn-popover.shown vn-worker-descriptor', firstDocWorkerDescriptor: '.vn-popover.shown vn-worker-descriptor',
acceptDeleteButton: '.vn-confirm.shown button[response="accept"]' acceptDeleteButton: '.vn-confirm.shown button[response="accept"]'
}, },
@ -365,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)',
@ -524,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',
@ -630,6 +632,16 @@ export default {
createButton: 'button[type=submit]', createButton: 'button[type=submit]',
cancelButton: 'vn-button[href="#!/client/index"]' cancelButton: 'vn-button[href="#!/client/index"]'
}, },
orderSummary: {
header: 'vn-order-summary h5',
id: 'vn-order-summary vn-one:nth-child(1) > vn-label-value:nth-child(1) span',
alias: 'vn-order-summary vn-one:nth-child(1) > vn-label-value:nth-child(2) span',
consignee: 'vn-order-summary vn-one:nth-child(2) > vn-label-value:nth-child(3) span',
subtotal: 'vn-order-summary vn-one.taxes > p:nth-child(1)',
vat: 'vn-order-summary vn-one.taxes > p:nth-child(2)',
total: 'vn-order-summary vn-one.taxes > p:nth-child(3)',
sale: 'vn-order-summary vn-tbody > vn-tr',
},
orderCatalog: { orderCatalog: {
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]', plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
type: 'vn-autocomplete[data="$ctrl.itemTypes"]', type: 'vn-autocomplete[data="$ctrl.itemTypes"]',
@ -700,6 +712,17 @@ export default {
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]', firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',
confirmButton: '.vn-confirm.shown button[response="accept"]' confirmButton: '.vn-confirm.shown button[response="accept"]'
}, },
workerSummary: {
header: 'vn-worker-summary h5',
id: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(2) > section > span',
email: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(3) > section > span',
department: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(4) > section > span',
userId: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(2) > section > span',
userName: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(3) > section > span',
role: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(4) > section > span',
extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span',
},
workerBasicData: { workerBasicData: {
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]', name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]', surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
@ -783,10 +806,25 @@ export default {
ticketOne: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(1)', ticketOne: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(1)',
ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)' ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)'
}, },
travelBasicDada: {
reference: 'vn-travel-basic-data vn-textfield[ng-model="$ctrl.travel.ref"]',
agency: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]',
shippingDate: 'vn-travel-basic-data vn-date-picker[ng-model="$ctrl.travel.shipped"]',
deliveryDate: 'vn-travel-basic-data vn-date-picker[ng-model="$ctrl.travel.landed"]',
outputWarehouse: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.warehouseOutFk"]',
inputWarehouse: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.warehouseInFk"]',
delivered: 'vn-travel-basic-data vn-check[ng-model="$ctrl.travel.isDelivered"]',
received: 'vn-travel-basic-data vn-check[ng-model="$ctrl.travel.isReceived"]',
save: 'vn-travel-basic-data vn-submit[label="Save"]',
undoChanges: 'vn-travel-basic-data vn-button[label="Undo changes"]'
},
travelLog: {
firstLogFirstTD: 'vn-travel-log vn-tbody > vn-tr > vn-td:nth-child(1) > div'
},
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="cloud_upload"]', uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="attach_file"]',
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]'
}, },
@ -806,5 +844,10 @@ export default {
header: 'vn-entry-summary > vn-card > h5', header: 'vn-entry-summary > vn-card > h5',
reference: 'vn-entry-summary vn-label-value[label="Reference"]', reference: 'vn-entry-summary vn-label-value[label="Reference"]',
confirmed: 'vn-entry-summary vn-check[label="Confirmed"]', confirmed: 'vn-entry-summary vn-check[label="Confirmed"]',
},
entryDescriptor: {
agency: 'vn-entry-descriptor div.body vn-label-value:nth-child(3) span',
travelsQuicklink: 'vn-entry-descriptor vn-quick-links > a:nth-child(1)',
entriesQuicklink: 'vn-entry-descriptor vn-quick-links > a:nth-child(2)'
} }
}; };

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

@ -16,6 +16,12 @@ describe('Client Add notes path', () => {
await browser.close(); await browser.close();
}); });
it(`should reach the notes index`, async() => {
let url = await page.expectURL('/note');
expect(url).toBe(true);
});
it(`should click on the add note button`, async() => { it(`should click on the add note button`, async() => {
await page.waitToClick(selectors.clientNotes.addNoteFloatButton); await page.waitToClick(selectors.clientNotes.addNoteFloatButton);
let url = await page.expectURL('/note/create'); let url = await page.expectURL('/note/create');

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,71 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'worker');
await page.accessToSearchResult('agencyNick');
});
afterAll(async() => {
await browser.close();
});
it('should reach the employee summary section', async() => {
const url = await page.expectURL('#!/worker/3/summary');
expect(url).toBe(true);
});
it('should check the summary contains the name and userName on the header', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.header, 'innerText');
expect(result).toEqual('agency agency');
});
it('should check the summary contains the basic data id', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.id, 'innerText');
expect(result).toEqual('3');
});
it('should check the summary contains the basic data email', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.email, 'innerText');
expect(result).toEqual('agency@verdnatura.es');
});
it('should check the summary contains the basic data department', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.department, 'innerText');
expect(result).toEqual('CAMARA');
});
it('should check the summary contains the user data id', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.userId, 'innerText');
expect(result).toEqual('3');
});
it('should check the summary contains the user data name', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.userName, 'innerText');
expect(result).toEqual('agency');
});
it('should check the summary contains the user data role', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.role, 'innerText');
expect(result).toEqual('agency');
});
it('should check the summary contains the user data extension', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.extension, 'innerText');
expect(result).toEqual('1101');
});
});

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

@ -0,0 +1,65 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Order summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'order');
await page.accessToSearchResult('16');
});
afterAll(async() => {
await browser.close();
});
it('should reach the order summary section', async() => {
const url = await page.expectURL('#!/order/16/summary');
expect(url).toBe(true);
});
it('should check the summary contains the order id', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.id, 'innerText');
expect(result).toEqual('16');
});
it('should check the summary contains the order alias', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.alias, 'innerText');
expect(result).toEqual('address 26');
});
it('should check the summary contains the order consignee', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.consignee, 'innerText');
expect(result).toEqual('Many places - Silla (Province one)');
});
it('should check the summary contains the order subtotal', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.subtotal, 'innerText');
expect(result.length).toBeGreaterThan(1);
});
it('should check the summary contains the order vat', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.vat, 'innerText');
expect(result.length).toBeGreaterThan(1);
});
it('should check the summary contains the order total', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.total, 'innerText');
expect(result.length).toBeGreaterThan(1);
});
it('should check the summary contains the order sales', async() => {
const result = await page.countElement(selectors.orderSummary.sale);
expect(result).toBeGreaterThan(0);
});
});

View File

@ -0,0 +1,102 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Travel basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'travel');
await page.accessToSearchResult('3');
await page.accessToSection('travel.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should reach the thermograph section', async() => {
const result = await page.expectURL('/basic-data');
expect(result).toBe(true);
});
it('should set a wrong delivery date then receive an error on submit', async() => {
await page.datePicker(selectors.travelBasicDada.deliveryDate, -1, null);
await page.waitToClick(selectors.travelBasicDada.save);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Landing cannot be lesser than shipment');
});
it('should undo the changes', async() => {
await page.waitToClick(selectors.travelBasicDada.undoChanges);
await page.waitToClick(selectors.travelBasicDada.save);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('No changes to save');
});
it('should now edit the whole form then save', async() => {
await page.clearInput(selectors.travelBasicDada.reference);
await page.write(selectors.travelBasicDada.reference, 'new reference!');
await page.waitFor(2000);
await page.autocompleteSearch(selectors.travelBasicDada.agency, 'Entanglement');
await page.autocompleteSearch(selectors.travelBasicDada.outputWarehouse, 'Warehouse Three');
await page.autocompleteSearch(selectors.travelBasicDada.inputWarehouse, 'Warehouse Four');
await page.waitToClick(selectors.travelBasicDada.delivered);
await page.waitToClick(selectors.travelBasicDada.received);
await page.waitToClick(selectors.travelBasicDada.save);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it('should reload the section and check the reference was saved', async() => {
await page.reloadSection('travel.card.basicData');
const result = await page.waitToGetProperty(selectors.travelBasicDada.reference, 'value');
expect(result).toEqual('new reference!');
});
it('should check the agency was saved', async() => {
const result = await page.waitToGetProperty(selectors.travelBasicDada.agency, 'value');
expect(result).toEqual('Entanglement');
});
it('should check the output warehouse date was saved', async() => {
const result = await page.waitToGetProperty(selectors.travelBasicDada.outputWarehouse, 'value');
expect(result).toEqual('Warehouse Three');
});
it('should check the input warehouse date was saved', async() => {
const result = await page.waitToGetProperty(selectors.travelBasicDada.inputWarehouse, 'value');
expect(result).toEqual('Warehouse Four');
});
it(`should check the delivered checkbox was saved even tho it doesn't make sense`, async() => {
await page.waitForClassPresent(selectors.travelBasicDada.delivered, 'checked');
});
it(`should check the received checkbox was saved even tho it doesn't make sense`, async() => {
await page.waitForClassPresent(selectors.travelBasicDada.received, 'checked');
});
it('should navigate to the travel logs', async() => {
await page.accessToSection('travel.card.log');
const result = await page.expectURL('/log');
expect(result).toBe(true);
});
it('should check the 1st log contains details from the changes made', async() => {
const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText');
expect(result).toContain('new reference!');
});
});

View File

@ -0,0 +1,61 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('2');
});
afterAll(async() => {
await browser.close();
});
it('should reach the second entry summary section', async() => {
let url = await page.expectURL('#!/entry/2/summary');
expect(url).toBe(true);
});
it('should show some entry information', async() => {
const result = await page.waitToGetProperty(selectors.entryDescriptor.agency, 'innerText');
expect(result).toContain('inhouse pickup');
});
it('should click the travels button to be redirected to the travels index filtered by the current agency', async() => {
await page.waitToClick(selectors.entryDescriptor.travelsQuicklink);
const url = await page.expectURL('/travel/index');
const filter = await page.expectURL('agencyFk');
expect(url).toBe(true);
expect(filter).toBe(true);
});
it('should go back to the entry summary', async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.selectModule('entry');
await page.accessToSearchResult('2');
let url = await page.expectURL('#!/entry/2/summary');
expect(url).toBe(true);
});
it('should click the entries button to be redirected to the entries index filtered by the current supplier', async() => {
await page.waitToClick(selectors.entryDescriptor.entriesQuicklink);
const url = await page.expectURL('/entry/index');
const supplierFilter = await page.expectURL('supplierFk');
const toFilter = await page.expectURL('to');
const fromFilter = await page.expectURL('from');
expect(url).toBe(true);
expect(supplierFilter).toBe(true);
expect(toFilter).toBe(true);
expect(fromFilter).toBe(true);
});
});

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

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

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="cloud_upload" icon="attach_file"
vn-tooltip="Select a file" vn-tooltip="Select a file"
ng-click="$ctrl.openFileSelector()"> ng-click="$ctrl.openFileSelector()">
</vn-icon> </vn-icon>

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 => {
console.log(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

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

@ -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,6 @@
"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"
} }

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

@ -16,7 +16,7 @@ describe('Client isValidClient', () => {
}); });
it('should call the isValidClient() method with an unexistant id and receive false', async() => { it('should call the isValidClient() method with an unexistant id and receive false', async() => {
let id = 999999; let id = 999;
let result = await app.models.Client.isValidClient(id); let result = await app.models.Client.isValidClient(id);
expect(result).toBeFalsy(); expect(result).toBeFalsy();

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}},
{{town.province.country.country}})
</tpl-item>
<append>
<vn-icon-button <vn-icon-button
vn-auto
class="vn-my-md"
icon="add_circle" icon="add_circle"
vn-tooltip="New postcode" vn-tooltip="New postcode"
ng-click="postcode.open()"> ng-click="postcode.open()"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button> </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>
<vn-icon-button
icon="add_circle" icon="add_circle"
vn-tooltip="New postcode" vn-tooltip="New postcode"
ng-click="postcode.open()"> ng-click="postcode.open()"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button> </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

@ -56,7 +56,16 @@
label="File" label="File"
ng-model="$ctrl.dms.files" ng-model="$ctrl.dms.files"
on-change="$ctrl.onFileChange($files)" on-change="$ctrl.onFileChange($files)"
accept=".pdf, .png, .jpg, .jpeg, application/zip, application/rar, application/x-7z-compressed"> accept="{{$ctrl.allowedContentTypes}}"
required="true"
multiple="true">
<append>
<vn-icon vn-none
color-marginal
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</append>
</vn-input-file> </vn-input-file>
</vn-horizontal> </vn-horizontal>
<vn-vertical> <vn-vertical>

View File

@ -53,11 +53,6 @@
{{::document.dms.description}} {{::document.dms.description}}
</span> </span>
</vn-td> </vn-td>
<vn-td shrink>
<vn-check disabled="true"
ng-model="document.dms.hasFile">
</vn-check>
</vn-td>
<vn-td shrink> <vn-td shrink>
<a target="_blank" <a target="_blank"
title="{{'Download file' | translate}}" title="{{'Download file' | translate}}"

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,26 +35,21 @@ 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) { } catch (e) {
this.vnApp.showError(this.$translate.instant(e.message)); this.vnApp.showError(this.$translate.instant(e.message));
return false; 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

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

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

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

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

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

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

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

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'},
@ -39,6 +37,7 @@ class Controller {
showRouteReport() { showRouteReport() {
const user = this.route.worker.user; const user = this.route.worker.user;
const params = { const params = {
authorization: this.vnToken.token,
clientId: user.id, clientId: user.id,
routeId: this.route.id routeId: this.route.id
}; };
@ -54,9 +53,7 @@ class Controller {
clientId: user.id, clientId: user.id,
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 +73,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

@ -6,3 +6,4 @@ 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

@ -34,7 +34,7 @@ describe('ticket-request confirm()', () => {
expect(error.message).toEqual(`That item doesn't exists`); expect(error.message).toEqual(`That item doesn't exists`);
}); });
it(`should throw an error if the item is not available`, async() => { it('should throw an error if the item is not available', async() => {
const requestId = 5; const requestId = 5;
const itemId = 4; const itemId = 4;
const quantity = 99999; const quantity = 99999;

View File

@ -67,6 +67,10 @@ module.exports = Self => {
arg: 'problems', arg: 'problems',
type: 'Boolean', type: 'Boolean',
description: `Whether to show only tickets with problems` description: `Whether to show only tickets with problems`
}, {
arg: 'pending',
type: 'Boolean',
description: `Whether to show only tickets with state 'Pending'`
}, { }, {
arg: 'mine', arg: 'mine',
type: 'Boolean', type: 'Boolean',
@ -130,7 +134,7 @@ module.exports = Self => {
dateTo.setHours(23, 59, 0, 0); dateTo.setHours(23, 59, 0, 0);
} }
let where = buildFilter(ctx.args, (param, value) => { const where = buildFilter(ctx.args, (param, value) => {
switch (param) { switch (param) {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
@ -155,6 +159,17 @@ module.exports = Self => {
return {'c.salesPersonFk': {inq: teamIds}}; return {'c.salesPersonFk': {inq: teamIds}};
case 'alertLevel': case 'alertLevel':
return {'ts.alertLevel': value}; return {'ts.alertLevel': value};
case 'pending':
if (value) {
return {and: [
{'st.alertLevel': 0},
{'st.code': {neq: 'OK'}}
]};
} else {
return {and: [
{'st.alertLevel': {gt: 0}}
]};
}
case 'id': case 'id':
case 'clientFk': case 'clientFk':
case 'agencyModeFk': case 'agencyModeFk':
@ -244,7 +259,6 @@ module.exports = Self => {
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id
LEFT JOIN tmp.ticketTotal tt ON tt.ticketFk = f.id`); LEFT JOIN tmp.ticketTotal tt ON tt.ticketFk = f.id`);
let condition; let condition;
let hasProblem; let hasProblem;
let range; let range;

View File

@ -32,7 +32,7 @@ module.exports = Self => {
let alertLevel = state ? state.alertLevel : null; let alertLevel = state ? state.alertLevel : null;
let ticket = await Self.app.models.Ticket.findById(id, { let ticket = await Self.app.models.Ticket.findById(id, {
fields: ['isDeleted', 'clientFk', 'refFk'], fields: ['clientFk'],
include: [{ include: [{
relation: 'client', relation: 'client',
scope: { scope: {
@ -42,13 +42,13 @@ module.exports = Self => {
} }
}] }]
}); });
const isLocked = await Self.app.models.Ticket.isLocked(id);
const isDeleted = ticket && ticket.isDeleted; const alertLevelGreaterThanZero = (alertLevel && alertLevel > 0);
const isOnDelivery = (alertLevel && alertLevel > 0);
const isNormalClient = ticket && ticket.client().type().code == 'normal'; const isNormalClient = ticket && ticket.client().type().code == 'normal';
const isInvoiced = ticket && ticket.refFk; const validAlertAndRoleNormalClient = (alertLevelGreaterThanZero && isNormalClient && !isValidRole);
if (!ticket || isInvoiced || isDeleted || (isOnDelivery && isNormalClient && !isValidRole)) if (!ticket || validAlertAndRoleNormalClient || isLocked)
return false; return false;
return true; return true;

View File

@ -0,0 +1,35 @@
module.exports = Self => {
Self.remoteMethod('isLocked', {
description: 'Check if a ticket is invoiced or deleted',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'the ticket id',
http: {source: 'path'}
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/:id/isLocked`,
verb: 'get'
}
});
Self.isLocked = async id => {
const ticket = await Self.app.models.Ticket.findById(id, {
fields: ['isDeleted', 'refFk']
});
const isDeleted = ticket && ticket.isDeleted;
const isInvoiced = ticket && ticket.refFk;
if (!ticket || isInvoiced || isDeleted)
return true;
return false;
};
};

View File

@ -41,6 +41,34 @@ describe('ticket filter()', () => {
const firstRow = result[0]; const firstRow = result[0];
expect(result.length).toEqual(1); expect(result.length).toEqual(1);
expect(firstRow.ticketFk).toEqual(11); expect(firstRow.id).toEqual(11);
});
it('should return the tickets with grouped state "Pending" and not "Ok"', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: true}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
const firstRow = result[0];
const secondRow = result[1];
const thirdRow = result[2];
expect(result.length).toEqual(3);
expect(firstRow.state).toEqual('Arreglar');
expect(secondRow.state).toEqual('Arreglar');
expect(thirdRow.state).toEqual('Arreglar');
});
it('should return the tickets that are not pending', async() => {
const ctx = {req: {accessToken: {userId: 9}}, args: {pending: false}};
const filter = {};
const result = await app.models.Ticket.filter(ctx, filter);
const firstRow = result[0];
const secondRow = result[1];
const thirdRow = result[2];
expect(result.length).toEqual(13);
expect(firstRow.state).toEqual('Entregado');
expect(secondRow.state).toEqual('Entregado');
expect(thirdRow.state).toEqual('Entregado');
}); });
}); });

View File

@ -1,13 +1,6 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('ticket isEditable()', () => { describe('ticket isEditable()', () => {
it('should return false if the given ticket is not editable', async() => {
let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 2);
expect(result).toEqual(false);
});
it('should return false if the given ticket does not exist', async() => { it('should return false if the given ticket does not exist', async() => {
let ctx = {req: {accessToken: {userId: 9}}}; let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 99999); let result = await app.models.Ticket.isEditable(ctx, 99999);
@ -15,37 +8,46 @@ describe('ticket isEditable()', () => {
expect(result).toEqual(false); expect(result).toEqual(false);
}); });
it('should return false if the given ticket isDeleted', async() => { it(`should return false if the given ticket isn't invoiced but isDeleted`, async() => {
let ctx = {req: {accessToken: {userId: 9}}}; let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 19); let deletedTicket = await app.models.Ticket.findOne({
where: {
invoiceOut: null,
isDeleted: true
},
fields: ['id']
});
let result = await app.models.Ticket.isEditable(ctx, deletedTicket.id);
expect(result).toEqual(false); expect(result).toEqual(false);
}); });
it('should return true if the given ticket is editable', async() => { it('should return true if the given ticket is editable', async() => {
let ctx = {req: {accessToken: {userId: 9}}}; let ctx = {req: {accessToken: {userId: 9}}};
let result = await app.models.Ticket.isEditable(ctx, 16); let result = await app.models.Ticket.isEditable(ctx, 16);
expect(result).toEqual(true); expect(result).toEqual(true);
}); });
it('should be able to edit a deleted or invoiced ticket if the role is salesAssistant', async() => { it('should not be able to edit a deleted or invoiced ticket even for salesAssistant', async() => {
let ctx = {req: {accessToken: {userId: 21}}}; let ctx = {req: {accessToken: {userId: 21}}};
let result = await app.models.Ticket.isEditable(ctx, 8); let result = await app.models.Ticket.isEditable(ctx, 19);
expect(result).toEqual(true); expect(result).toEqual(false);
}); });
it('should be able to edit a deleted or invoiced ticket if the role is productionBoss', async() => { it('should not be able to edit a deleted or invoiced ticket even for productionBoss', async() => {
let ctx = {req: {accessToken: {userId: 50}}}; let ctx = {req: {accessToken: {userId: 50}}};
let result = await app.models.Ticket.isEditable(ctx, 8); let result = await app.models.Ticket.isEditable(ctx, 19);
expect(result).toEqual(true); expect(result).toEqual(false);
}); });
it('should not be able to edit a deleted or invoiced ticket if the role is salesPerson', async() => { it('should not be able to edit a deleted or invoiced ticket even for salesPerson', async() => {
let ctx = {req: {accessToken: {userId: 18}}}; let ctx = {req: {accessToken: {userId: 18}}};
let result = await app.models.Ticket.isEditable(ctx, 8); let result = await app.models.Ticket.isEditable(ctx, 19);
expect(result).toEqual(false); expect(result).toEqual(false);
}); });

View File

@ -0,0 +1,34 @@
const app = require('vn-loopback/server/server');
describe('ticket isLocked()', () => {
it('should return true if the given ticket does not exist', async() => {
let result = await app.models.Ticket.isLocked(99999);
expect(result).toEqual(true);
});
it('should return true if the given ticket is invoiced', async() => {
let invoicedTicket = await app.models.Ticket.findOne({
where: {invoiceOut: {neq: null}},
fields: ['id']
});
let result = await app.models.Ticket.isLocked(invoicedTicket.id);
expect(result).toEqual(true);
});
it(`should return true if the given ticket isn't invoiced but deleted`, async() => {
let deletedTicket = await app.models.Ticket.findOne({
where: {
invoiceOut: null,
isDeleted: true
},
fields: ['id']
});
let result = await app.models.Ticket.isLocked(deletedTicket.id);
expect(result).toEqual(true);
});
});

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

@ -35,6 +35,7 @@ module.exports = Self => {
}); });
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => { Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
@ -68,8 +69,14 @@ module.exports = Self => {
if (!allFromSameTicket) if (!allFromSameTicket)
throw new UserError('All sales must belong to the same ticket'); throw new UserError('All sales must belong to the same ticket');
const isEditable = await models.Ticket.isEditable(ctx, id); const isLocked = await models.Ticket.isLocked(id);
if (!isEditable) const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson');
const state = await Self.app.models.TicketState.findOne({
where: {ticketFk: id}
});
const alertLevel = state ? state.alertLevel : null;
if (isLocked || (!isSalesPerson && alertLevel > 0 ))
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {

View File

@ -29,6 +29,7 @@ module.exports = Self => {
require('../methods/ticket/recalculateComponents')(Self); require('../methods/ticket/recalculateComponents')(Self);
require('../methods/ticket/deleteStowaway')(Self); require('../methods/ticket/deleteStowaway')(Self);
require('../methods/ticket/sendSms')(Self); require('../methods/ticket/sendSms')(Self);
require('../methods/ticket/isLocked')(Self);
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) return;

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

@ -2,3 +2,4 @@ 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',
@ -220,7 +220,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}`;
@ -332,7 +333,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

@ -51,11 +51,6 @@
{{::document.dms.description}} {{::document.dms.description}}
</span> </span>
</vn-td> </vn-td>
<vn-td shrink>
<vn-check disabled="true"
field="document.dms.hasFile">
</vn-check>
</vn-td>
<vn-td shrink> <vn-td shrink>
<a target="_blank" <a target="_blank"
title="{{'Download file' | translate}}" title="{{'Download file' | translate}}"

View File

@ -8,17 +8,6 @@
</vn-crud-model> </vn-crud-model>
<vn-auto-search <vn-auto-search
on-search="$ctrl.onSearch($params)"> on-search="$ctrl.onSearch($params)">
<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"
@ -137,13 +126,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-button class="round message xs vn-mb-sm"
icon="icon-recovery"
ng-show="$ctrl.totalChecked > 0"
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-bind="+"
fixed-bottom-right> vn-tooltip="New ticket"
<vn-float-button icon="add"></vn-float-button> tooltip-position="left">
</a> </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;
} }
getScopeDates(days) { getScopeDates(days) {
@ -51,7 +64,7 @@ export default class Controller {
onSearch(params) { onSearch(params) {
if (params) { if (params) {
if (typeof(params.scopeDays) === 'number') if (typeof (params.scopeDays) === 'number')
Object.assign(params, this.getScopeDates(params.scopeDays)); Object.assign(params, this.getScopeDates(params.scopeDays));
// Set default params to 1 scope days // Set default params to 1 scope days
else if (Object.entries(params).length == 0) else if (Object.entries(params).length == 0)

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);
});
});
}); });

View File

@ -165,8 +165,8 @@
</span> </span>
</vn-td> </vn-td>
<vn-td number> <vn-td number>
<span ng-class="{'link': $ctrl.isEditable}" <span ng-class="{'link': !$ctrl.isLocked}"
title="{{$ctrl.isEditable ? 'Edit discount' : ''}}" title="{{!$ctrl.isLocked ? 'Edit discount' : ''}}"
ng-click="$ctrl.showEditDiscountPopover($event, sale)"> ng-click="$ctrl.showEditDiscountPopover($event, sale)">
{{(sale.discount / 100) | percentage}} {{(sale.discount / 100) | percentage}}
</span> </span>

View File

@ -46,6 +46,7 @@ class Controller {
set ticket(value) { set ticket(value) {
this._ticket = value; this._ticket = value;
this.isTicketEditable(); this.isTicketEditable();
this.isTicketLocked();
} }
get sales() { get sales() {
@ -354,7 +355,7 @@ class Controller {
} }
showEditDiscountPopover(event, sale) { showEditDiscountPopover(event, sale) {
if (!this.isEditable) return; if (this.isLocked) return;
this.sale = sale; this.sale = sale;
this.edit = [{ this.edit = [{
@ -540,6 +541,12 @@ class Controller {
}); });
} }
isTicketLocked() {
this.$http.get(`Tickets/${this.$state.params.id}/isLocked`).then(res => {
this.isLocked = res.data;
});
}
hasOneSaleSelected() { hasOneSaleSelected() {
if (this.totalCheckedLines() === 1) if (this.totalCheckedLines() === 1)
return true; return true;

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