Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 1970-test_crud_model
gitea/salix/1970-test_crud_model This commit looks good Details

This commit is contained in:
Bernat Exposito Domenech 2020-02-26 08:24:21 +01:00
commit 9cad13f166
210 changed files with 3246 additions and 768 deletions

View File

@ -32,6 +32,8 @@ module.exports = Self => {
if (sender.name != recipient) if (sender.name != recipient)
return sendMessage(sender, to, message); return sendMessage(sender, to, message);
return false;
}; };
async function sendMessage(sender, channel, message) { async function sendMessage(sender, channel, message) {

View File

@ -36,7 +36,7 @@ module.exports = Self => {
relation: 'department' relation: 'department'
} }
}); });
const department = workerDepartment.department(); const department = workerDepartment && workerDepartment.department();
const channelName = department.chatName; const channelName = department.chatName;
if (channelName) if (channelName)

View File

@ -9,10 +9,10 @@ describe('chat send()', () => {
expect(response.message).toEqual('Fake notification sent'); expect(response.message).toEqual('Fake notification sent');
}); });
it('should not return a response', async() => { it('should retrun false as response', async() => {
let ctx = {req: {accessToken: {userId: 18}}}; let ctx = {req: {accessToken: {userId: 18}}};
let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something'); let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toBeUndefined(); expect(response).toBeFalsy();
}); });
}); });

View File

@ -1,19 +1,3 @@
ALTER TABLE `vn`.`ticket` ALTER TABLE `vn`.`ticket`
ADD COLUMN `zonePrice` DECIMAL(10,2) NULL DEFAULT NULL AFTER `collectionFk`, ADD COLUMN `zonePrice` DECIMAL(10,2) NULL DEFAULT NULL AFTER `collectionFk`,
ADD COLUMN `zoneBonus` DECIMAL(10,2) NULL DEFAULT NULL AFTER `zonePrice`, ADD COLUMN `zoneBonus` DECIMAL(10,2) NULL DEFAULT NULL AFTER `zonePrice`;
ADD COLUMN `zoneClosure` TIME NULL AFTER `zoneBonus`;
CREATE TABLE vn.`zoneCalcTicket` (
`zoneFk` int(11) NOT NULL PRIMARY KEY,
CONSTRAINT `zoneCalcTicketfk_1` FOREIGN KEY (`zoneFk`) REFERENCES `zone` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DROP EVENT IF EXISTS vn.`zone_doCalc`;
CREATE DEFINER=`root`@`%` EVENT vn.`zone_doCalc`
ON SCHEDULE EVERY 15 SECOND STARTS '2020-01-31 11:32:30'
ON COMPLETION PRESERVE ENABLE
DO CALL util.procNoOverlap('vn.zone_doCalc');
DROP TABLE `vn`.`zoneConfig`;
DROP procedure IF EXISTS vn.`zoneClosure_recalc`;

View File

@ -0,0 +1,96 @@
USE `vn`;
DROP procedure IF EXISTS `ticketCreateWithUser`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketCreateWithUser`(
vClientId INT
,vShipped DATE
,vWarehouseFk INT
,vCompanyFk INT
,vAddressFk INT
,vAgencyModeFk INT
,vRouteFk INT
,vlanded DATE
,vUserId INT
,OUT vNewTicket INT)
BEGIN
DECLARE vZoneFk INT;
DECLARE vPrice DECIMAL(10,2);
DECLARE vBonus DECIMAL(10,2);
IF vClientId IS NULL THEN
CALL util.throw ('CLIENT_NOT_ESPECIFIED');
END IF;
IF NOT vAddressFk OR vAddressFk IS NULL THEN
SELECT id INTO vAddressFk
FROM address
WHERE clientFk = vClientId AND isDefaultAddress;
END IF;
IF vAgencyModeFk IS NOT NULL THEN
CALL vn.zone_getShippedWarehouse(vlanded, vAddressFk, vAgencyModeFk);
SELECT zoneFk, price, bonus INTO vZoneFk, vPrice, vBonus
FROM tmp.zoneGetShipped
WHERE shipped = vShipped AND warehouseFk = vWarehouseFk LIMIT 1;
IF vZoneFk IS NULL OR vZoneFk = 0 THEN
CALL util.throw ('NOT_ZONE_WITH_THIS_PARAMETERS');
END IF;
END IF;
INSERT INTO ticket (
clientFk,
shipped,
addressFk,
agencyModeFk,
nickname,
warehouseFk,
routeFk,
companyFk,
landed,
zoneFk,
zonePrice,
zoneBonus
)
SELECT
vClientId,
vShipped,
a.id,
vAgencyModeFk,
a.nickname,
vWarehouseFk,
IF(vRouteFk,vRouteFk,NULL),
vCompanyFk,
vlanded,
vZoneFk,
vPrice,
vBonus
FROM address a
JOIN agencyMode am ON am.id = a.agencyModeFk
WHERE a.id = vAddressFk;
SET vNewTicket = LAST_INSERT_ID();
INSERT INTO ticketObservation(ticketFk, observationTypeFk, description)
SELECT vNewTicket, ao.observationTypeFk, ao.description
FROM addressObservation ao
JOIN address a ON a.id = ao.addressFk
WHERE a.id = vAddressFk;
INSERT INTO vn.ticketLog
SET originFk = vNewTicket, userFk = vUserId, `action` = 'insert', description = CONCAT('Ha creado el ticket:', ' ', vNewTicket);
IF (SELECT ct.isCreatedAsServed FROM vn.clientType ct JOIN vn.client c ON c.typeFk = ct.code WHERE c.id = vClientId ) <> FALSE THEN
INSERT INTO vncontrol.inter(state_id, Id_Ticket, Id_Trabajador)
SELECT id, vNewTicket, getWorker()
FROM state
WHERE `code` = 'DELIVERED';
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,82 @@
USE `vn`;
DROP procedure IF EXISTS `ticket_componentUpdate`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticket_componentUpdate`(
vTicketFk INT,
vClientFk INT,
vAgencyModeFk INT,
vAddressFk INT,
vZoneFk INT,
vWarehouseFk TINYINT,
vCompanyFk SMALLINT,
vShipped DATETIME,
vLanded DATE,
vIsDeleted BOOLEAN,
vHasToBeUnrouted BOOLEAN,
vOption INT)
BEGIN
DECLARE vPrice DECIMAL(10,2);
DECLARE vBonus DECIMAL(10,2);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
IF (SELECT addressFk FROM ticket WHERE id = vTicketFk) <> vAddressFk THEN
UPDATE ticket t
JOIN address a ON a.id = vAddressFk
SET t.nickname = a.nickname
WHERE t.id = vTicketFk;
END IF;
CALL vn.zone_getShippedWarehouse(vlanded, vAddressFk, vAgencyModeFk);
SELECT zoneFk, price, bonus INTO vZoneFk, vPrice, vBonus
FROM tmp.zoneGetShipped
WHERE shipped = vShipped AND warehouseFk = vWarehouseFk LIMIT 1;
UPDATE ticket t
SET
t.clientFk = vClientFk,
t.agencyModeFk = vAgencyModeFk,
t.addressFk = vAddressFk,
t.zoneFk = vZoneFk,
t.zonePrice = vPrice,
t.zoneBonus = vBonus,
t.warehouseFk = vWarehouseFk,
t.companyFk = vCompanyFk,
t.landed = vLanded,
t.shipped = vShipped,
t.isDeleted = vIsDeleted
WHERE
t.id = vTicketFk;
IF vHasToBeUnrouted THEN
UPDATE ticket t SET t.routeFk = NULL
WHERE t.id = vTicketFk;
END IF;
IF vOption <> 8 THEN
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk))
ENGINE = MEMORY
SELECT id AS saleFk, vWarehouseFk warehouseFk
FROM sale s WHERE s.ticketFk = vTicketFk;
CALL ticketComponentUpdateSale (vOption);
DROP TEMPORARY TABLE tmp.sale;
END IF;
COMMIT;
END$$
DELIMITER ;

View File

@ -1,56 +0,0 @@
USE `vn`;
DROP procedure IF EXISTS `zone_doCalc`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_doCalc`()
proc: BEGIN
/**
* Updates ticket fields related with zone
*/
DECLARE vDone BOOL;
DECLARE vTicketFk INT;
DECLARE vShipped DATE;
DECLARE vZoneFk INT;
DECLARE cCur CURSOR FOR
SELECT t.id, t.shipped, t.zoneFk
FROM zoneCalcTicket zct
JOIN ticket t ON t.zoneFk = zct.zoneFk
WHERE shipped >= CURDATE();
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET vDone = TRUE;
OPEN cCur;
myLoop: LOOP
SET vDone = FALSE;
FETCH cCur INTO vTicketFk, vShipped, vZoneFk;
IF vDone THEN
LEAVE myLoop;
END IF;
DROP TEMPORARY TABLE IF EXISTS tmp.zone;
CREATE TEMPORARY TABLE tmp.zone
(INDEX (id))
ENGINE = MEMORY
SELECT vZoneFk id;
CALL zone_getOptionsForShipment(vShipped, TRUE);
UPDATE ticket t
LEFT JOIN tmp.zoneOption zo ON TRUE
SET zonePrice = zo.price, zoneBonus = zo.bonus, zoneClosure = zo.`hour`
WHERE t.id = vTicketFk;
END LOOP;
CLOSE cCur;
DELETE FROM zoneCalcTicket;
END$$
DELIMITER ;

View File

@ -1 +1,49 @@
USE `vn`; USE `vn`;
DROP procedure IF EXISTS `zoneClosure_recalc`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zoneClosure_recalc`()
proc: BEGIN
/**
* Recalculates the delivery time (hour) for every zone in days + scope in future
*/
DECLARE vScope INT;
DECLARE vCounter INT DEFAULT 0;
DECLARE vShipped DATE DEFAULT CURDATE();
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
DO RELEASE_LOCK('vn.zoneClosure_recalc');
RESIGNAL;
END;
IF NOT GET_LOCK('vn.zoneClosure_recalc', 0) THEN
LEAVE proc;
END IF;
SELECT scope INTO vScope
FROM zoneConfig;
DROP TEMPORARY TABLE IF EXISTS tmp.zone;
CREATE TEMPORARY TABLE tmp.zone
(INDEX (id))
ENGINE = MEMORY
SELECT id FROM zone;
TRUNCATE TABLE zoneClosure;
WHILE vCounter <= vScope DO
CALL zone_getOptionsForShipment(vShipped, TRUE);
INSERT INTO zoneClosure(zoneFk, dated, `hour`)
SELECT zoneFk, vShipped, `hour` FROM tmp.zoneOption;
SET vCounter = vCounter + 1;
SET vShipped = TIMESTAMPADD(DAY, 1, vShipped);
END WHILE;
DROP TEMPORARY TABLE tmp.zone;
DO RELEASE_LOCK('vn.zoneClosure_recalc');
END$$
DELIMITER ;

View File

@ -0,0 +1,62 @@
USE `vn`;
DROP procedure IF EXISTS `zone_doCalcInitialize`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_doCalcInitialize`()
proc: BEGIN
/**
* Initialize ticket
* si en 01-07-20 aun esta este proc, kkear
*/
DECLARE vDone BOOL;
DECLARE vTicketFk INT;
DECLARE vLanded DATE;
DECLARE vZoneFk INT;
DECLARE cCur CURSOR FOR
SELECT t.id, t.landed, t.zoneFk
FROM ticket t
WHERE (zonePrice IS NULL OR zoneBonus IS NULL)
AND landed >= '2019-01-01' AND shipped >= '2019-01-01'
GROUP BY landed, zoneFk;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET vDone = TRUE;
OPEN cCur;
myLoop: LOOP
SET vDone = FALSE;
FETCH cCur INTO vTicketFk, vLanded, vZoneFk;
IF vDone THEN
LEAVE myLoop;
END IF;
DROP TEMPORARY TABLE IF EXISTS tmp.zone;
CREATE TEMPORARY TABLE tmp.zone
(INDEX (id))
ENGINE = MEMORY
SELECT vZoneFk id;
CALL zone_getOptionsForLanding(vLanded, TRUE);
UPDATE ticket t
LEFT JOIN tmp.zoneOption zo ON TRUE
SET zonePrice = zo.price, zoneBonus = zo.bonus
WHERE t.zoneFk = vZoneFk AND landed = vLanded;
UPDATE ticket t
LEFT JOIN vn.zone z ON z.id = t.zoneFk
SET zonePrice = z.price, zoneBonus = z.bonus
WHERE t.zonePrice IS NULL AND z.id = vZoneFk
AND landed >= '2019-01-01' AND shipped >= '2019-01-01';
END LOOP;
CLOSE cCur;
END$$
DELIMITER ;

View File

@ -0,0 +1,66 @@
USE `vn`;
DROP procedure IF EXISTS `zone_getOptionsForLanding`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_getOptionsForLanding`(vLanded DATE, vShowExpiredZones BOOLEAN)
BEGIN
/**
* Gets computed options for the passed zones and delivery date.
*
* @table tmp.zones(id) The zones ids
* @param vLanded The delivery date
* @return tmp.zoneOption The computed options
*/
DROP TEMPORARY TABLE IF EXISTS tmp.zoneOption;
CREATE TEMPORARY TABLE tmp.zoneOption
ENGINE = MEMORY
SELECT
zoneFk,
`hour`,
travelingDays,
price,
bonus,
TIMESTAMPADD(DAY, -travelingDays, vLanded) shipped
FROM (
SELECT t.id zoneFk,
TIME(IFNULL(e.`hour`, z.`hour`)) `hour`,
IFNULL(e.travelingDays, z.travelingDays) travelingDays,
IFNULL(e.price, z.price) price,
IFNULL(e.bonus, z.bonus) bonus
FROM tmp.zone t
JOIN zone z ON z.id = t.id
JOIN zoneEvent e ON e.zoneFk = t.id
WHERE (
e.`type` = 'day'
AND e.dated = vLanded
) OR (
e.`type` != 'day'
AND e.weekDays & (1 << WEEKDAY(vLanded))
AND (e.`started` IS NULL OR vLanded >= e.`started`)
AND (e.`ended` IS NULL OR vLanded <= e.`ended`)
)
ORDER BY
zoneFk,
CASE
WHEN e.`type` = 'day'
THEN 1
WHEN e.`type` = 'range'
THEN 2
ELSE 3
END
) t
GROUP BY zoneFk;
DELETE t FROM tmp.zoneOption t
JOIN zoneExclusion e
ON e.zoneFk = t.zoneFk AND e.`dated` = vLanded;
IF NOT vShowExpiredZones THEN
DELETE FROM tmp.zoneOption
WHERE shipped < CURDATE()
OR (shipped = CURDATE() AND CURTIME() > `hour`);
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,171 @@
USE `vn`;
DROP procedure IF EXISTS `rutasAnalyze`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `rutasAnalyze`(vYear INT, vMonth INT)
BEGIN
/* Analiza los costes de las rutas de reparto y lo almacena en la tabla Rutas_Master
*
* PAK 15/4/2019
*/
DELETE FROM bi.rutasBoard
WHERE year = vYear AND month = vMonth;
-- Rellenamos la tabla con los datos de las rutas VOLUMETRICAS, especialmente con los bultos "virtuales"
INSERT INTO bi.rutasBoard(year,
month,
warehouse_id,
Id_Ruta,
Id_Agencia,
km,
Dia,
Fecha,
Bultos,
Matricula,
Tipo,
Terceros)
SELECT YEAR(r.created),
MONTH(r.created),
GREATEST(1,a.warehouseFk),
r.id,
r.agencyModeFk,
r.kmEnd - r.kmStart,
DAYNAME(r.created),
r.created,
SUM(sv.volume / ebv.m3),
v.numberPlate,
IF(ISNULL(`r`.`cost`), 'P', 'A'),
r.cost
FROM vn.route r
JOIN vn.ticket t ON t.routeFk = r.id
LEFT JOIN vn.zone z ON z.id = t.zoneFk
LEFT JOIN vn.agencyMode am ON am.id = r.agencyModeFk
LEFT JOIN vn.agency a ON a.id = am.agencyFk
LEFT JOIN vn.vehicle v ON v.id = r.vehicleFk
JOIN vn.saleVolume sv ON sv.ticketFk = t.id
JOIN vn.expeditionBoxVol ebv ON ebv.boxFk = 71
WHERE YEAR(r.created) = vYear AND MONTH(r.created) = vMonth
AND z.isVolumetric
GROUP BY r.id;
-- Rellenamos la tabla con los datos de las rutas NO VOLUMETRICAS, especialmente con los bultos "virtuales"
INSERT INTO bi.rutasBoard(year,
month,
warehouse_id,
Id_Ruta,
Id_Agencia,
km,
Dia,
Fecha,
Bultos,
Matricula,
Tipo,
Terceros)
SELECT YEAR(r.created),
MONTH(r.created),
GREATEST(1,a.warehouseFk),
r.id,
r.agencyModeFk,
r.kmEnd - r.kmStart,
DAYNAME(r.created),
r.created,
SUM(t.packages),
v.numberPlate,
IF(ISNULL(`r`.`cost`), 'P', 'A'),
r.cost
FROM vn.route r
JOIN vn.ticket t ON t.routeFk = r.id
LEFT JOIN vn.zone z ON z.id = t.zoneFk
LEFT JOIN vn.agencyMode am ON am.id = r.agencyModeFk
LEFT JOIN vn.agency a ON a.id = am.agencyFk
LEFT JOIN vn.vehicle v ON v.id = r.vehicleFk
WHERE YEAR(r.created) = vYear AND MONTH(r.created) = vMonth
AND z.isVolumetric = FALSE
GROUP BY r.id
ON DUPLICATE KEY UPDATE Bultos = Bultos + VALUES(Bultos);
-- Coste REAL de cada bulto "virtual", de acuerdo con el valor apuntado a mano en la ruta
UPDATE bi.rutasBoard r
INNER JOIN vn2008.Rutas_Master rm ON rm.año = r.year AND rm.mes = r.month AND rm.warehouse_id = r.warehouse_id
SET r.coste_bulto = IF(r.Tipo ='A', r.Terceros, r.km * rm.coste_km ) / r.Bultos
WHERE r.Bultos > 0
AND rm.año = vYear
AND rm.mes = vMonth;
-- Coste PRACTICO de cada bulto, de acuerdo con los componentes de tipo AGENCIA en cada linea de venta
UPDATE bi.rutasBoard r
JOIN (
SELECT t.routeFk, sum(s.quantity * sc.value) practicoTotal
FROM vn.route r
JOIN vn.time tm ON tm.dated = r.created
JOIN vn.ticket t ON t.routeFk = r.id
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.saleComponent sc ON sc.saleFk = s.id
JOIN vn.`component` c ON c.id = sc.componentFk
JOIN vn.componentType ct ON ct.id = c.typeFk
WHERE ct.type = 'agencia'
AND tm.year = vYear
AND tm.month = vMonth
GROUP BY r.id
) sub ON sub.routeFk = r.Id_Ruta
SET r.practico = sub.practicoTotal / r.Bultos;
-- Coste TEORICO de una caja "virtual" para cada ruta, teniendo en cuenta que hay carros, pallets, etc
UPDATE bi.rutasBoard r
JOIN (
SELECT t.routeFk,
SUM(t.zonePrice/ ebv.ratio)/ count(*) AS BultoTeoricoMedio
FROM vn.ticket t
JOIN vn.route r ON r.id = t.routeFk
JOIN vn.time tm ON tm.dated = r.created
JOIN vn.expedition e ON e.ticketFk = t.id
JOIN vn.expeditionBoxVol ebv ON ebv.boxFk = e.isBox
JOIN vn.address ad ON ad.id = t.addressFk
JOIN vn.client c ON c.id = ad.clientFk
LEFT JOIN vn.zone z ON z.id = t.zoneFk
WHERE tm.year = vYear
AND tm.month = vMonth
AND z.isVolumetric = FALSE
GROUP BY t.routeFk) sub ON r.Id_Ruta = sub.routeFk
SET r.teorico = sub.BultoTeoricoMedio;
-- Coste VOLUMETRICO TEORICO de una caja "virtual" para cada ruta
UPDATE bi.rutasBoard r
JOIN (
SELECT t.routeFk,
SUM(freight) AS BultoTeoricoMedio
FROM vn.ticket t
JOIN vn.route r ON r.id = t.routeFk
JOIN vn.time tm ON tm.dated = r.created
JOIN vn.saleVolume sf ON sf.ticketFk = t.id
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.zone z ON z.id = t.zoneFk
WHERE tm.year = vYear
AND tm.month = vMonth
AND z.isVolumetric != FALSE
GROUP BY t.routeFk) sub ON r.Id_Ruta = sub.routeFk
SET r.teorico = sub.BultoTeoricoMedio / r.Bultos;
-- La diferencia entre el teorico y el practico se deberia de cobrar en greuges, cada noche
UPDATE bi.rutasBoard r
JOIN (
SELECT t.routeFk,
Sum(g.amount) AS greuge
FROM vn.ticket t
JOIN vn.route r ON r.id = t.routeFk
JOIN vn.time tm ON tm.dated = r.created
JOIN vn.greuge g ON g.ticketFk = t.id
JOIN vn.greugeType gt ON gt.id = g.greugeTypeFk
WHERE tm.year = vYear
AND tm.month = vMonth
AND gt.name = 'Diferencia portes'
GROUP BY t.routeFk) sub ON r.Id_Ruta = sub.routeFk
SET r.greuge = sub.greuge / r.Bultos;
END$$
DELIMITER ;

View File

@ -0,0 +1,26 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `saleVolume` AS
SELECT
`s`.`ticketFk` AS `ticketFk`,
`s`.`id` AS `saleFk`,
IFNULL(ROUND(((((`i`.`compression` * (GREATEST(`i`.`density`, 167) / 167)) * `ic`.`cm3`) * `s`.`quantity`) / 1000),
2),
0) AS `litros`,
`t`.`routeFk` AS `routeFk`,
`t`.`shipped` AS `shipped`,
(((`s`.`quantity` * `ic`.`cm3`) * `i`.`compression`) / 1000000) AS `volume`,
((((`s`.`quantity` * `ic`.`cm3`) * `i`.`compression`) * (GREATEST(`i`.`density`, 167) / 167)) / 1000000) AS `physicalWeight`,
(((`s`.`quantity` * `ic`.`cm3`) * `i`.`density`) / 1000000) AS `weight`,
(((`s`.`quantity` * `ic`.`cm3`) * `i`.`compression`) / 1000000) AS `physicalVolume`,
((((`s`.`quantity` * `ic`.`cm3`) * `t`.`zonePrice`) * `i`.`compression`) / `cb`.`volume`) AS `freight`
FROM
((((`sale` `s`
JOIN `item` `i` ON ((`i`.`id` = `s`.`itemFk`)))
JOIN `ticket` `t` ON ((`t`.`id` = `s`.`ticketFk`)))
JOIN `packaging` `cb` ON ((`cb`.`id` = '94')))
JOIN `itemCost` `ic` ON (((`ic`.`itemFk` = `s`.`itemFk`)
AND (`ic`.`warehouseFk` = `t`.`warehouseFk`))));

View File

@ -0,0 +1,24 @@
DROP VIEW IF EXISTS `vn`.`saleFreight` ;
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `saleFreight__` AS
SELECT
`s`.`ticketFk` AS `ticketFk`,
`t`.`clientFk` AS `clientFk`,
`t`.`routeFk` AS `routeFk`,
`s`.`id` AS `saleFk`,
`t`.`zoneFk` AS `zoneFk`,
`t`.`companyFk` AS `companyFk`,
`t`.`shipped` AS `shipped`,
`t`.`zonePrice` AS `price`,
((((`s`.`quantity` * `r`.`cm3`) * `t`.`zonePrice`) * `i`.`compression`) / `cb`.`volume`) AS `freight`
FROM
((((`vn`.`sale` `s`
JOIN `vn`.`item` `i` ON ((`i`.`id` = `s`.`itemFk`)))
JOIN `vn`.`ticket` `t` ON ((`t`.`id` = `s`.`ticketFk`)))
JOIN `vn`.`packaging` `cb` ON ((`cb`.`id` = '94')))
JOIN `bi`.`rotacion` `r` ON (((`r`.`Id_Article` = `s`.`itemFk`)
AND (`r`.`warehouse_id` = `t`.`warehouseFk`))));

View File

@ -0,0 +1,43 @@
USE `vn`;
DROP procedure IF EXISTS `zone_getShippedWarehouse`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_getShippedWarehouse`(vLanded DATE, vAddressFk INT, vAgencyModeFk INT)
BEGIN
/**
* Devuelve la mínima fecha de envío para cada warehouse
*
* @param vLanded La fecha de recepcion
* @param vAddressFk Id del consignatario
* @param vAgencyModeFk Id de la agencia
* @return tmp.zoneGetShipped
*/
CALL zone_getFromGeo(address_getGeo(vAddressFk));
CALL zone_getOptionsForLanding(vLanded,TRUE);
DROP TEMPORARY TABLE IF EXISTS tmp.zoneGetShipped;
CREATE TEMPORARY TABLE tmp.zoneGetShipped
ENGINE = MEMORY
SELECT * FROM (
SELECT zo.zoneFk,
TIMESTAMPADD(DAY,-zo.travelingDays, vLanded) shipped,
zo.`hour`,
zw.warehouseFk,
z.agencyModeFk,
zo.price,
zo.bonus
FROM tmp.zoneOption zo
JOIN zoneWarehouse zw ON zw.zoneFk = zo.zoneFk
JOIN zone z ON z.id = zo.zoneFk
WHERE z.agencyModeFk = vAgencyModeFk
ORDER BY shipped) t
GROUP BY warehouseFk;
DROP TEMPORARY TABLE
tmp.zone,
tmp.zoneOption;
END$$
DELIMITER ;

View File

@ -0,0 +1,42 @@
USE `vn`;
DROP procedure IF EXISTS `zone_getAgency`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_getAgency`(vAddress INT, vLanded DATE)
BEGIN
/**
* Devuelve el listado de agencias disponibles para la fecha
* y dirección pasadas.
*
* @param vAddress Id de dirección de envío, %NULL si es recogida
* @param vLanded Fecha de recogida
* @select Listado de agencias disponibles
*/
CALL zone_getFromGeo(address_getGeo(vAddress));
CALL zone_getOptionsForLanding(vLanded, FALSE);
DROP TEMPORARY TABLE IF EXISTS tmp.zoneGetAgency;
CREATE TEMPORARY TABLE tmp.zoneGetAgency
(INDEX (agencyModeFk)) ENGINE = MEMORY
SELECT am.name agencyMode,
am.description,
z.agencyModeFk,
am.deliveryMethodFk,
TIMESTAMPADD(DAY,-zo.travelingDays, vLanded) shipped,
TRUE isIncluded,
zo.zoneFk
FROM tmp.zoneOption zo
JOIN zone z ON z.id = zo.zoneFk
JOIN agencyMode am ON am.id = z.agencyModeFk
GROUP BY agencyModeFk;
DROP TEMPORARY TABLE
tmp.zone,
tmp.zoneOption;
END$$
DELIMITER ;

View File

@ -0,0 +1,18 @@
USE `vn`;
DROP procedure IF EXISTS `zone_getAvailable`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_getAvailable`(vAddress INT, vLanded DATE)
BEGIN
CALL zone_getFromGeo(address_getGeo(vAddress));
CALL zone_getOptionsForLanding(vLanded, FALSE);
SELECT * FROM tmp.zoneOption;
DROP TEMPORARY TABLE
tmp.zone,
tmp.zoneOption;
END$$
DELIMITER ;

View File

@ -0,0 +1,41 @@
USE `vn`;
DROP procedure IF EXISTS `zone_getWarehouse`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `zone_getWarehouse`(vAddress INT, vLanded DATE, vWarehouse INT)
BEGIN
/**
* Devuelve el listado de agencias disponibles para la fecha,
* dirección y almacén pasados.
*
* @param vAddress
* @param vWarehouse warehouse
* @param vLanded Fecha de recogida
* @select Listado de agencias disponibles
*/
CALL zone_getFromGeo(address_getGeo(vAddress));
CALL zone_getOptionsForLanding(vLanded, FALSE);
SELECT am.id agencyModeFk,
am.name agencyMode,
am.description,
am.deliveryMethodFk,
TIMESTAMPADD(DAY, -zo.travelingDays, vLanded) shipped,
zw.warehouseFk,
z.id zoneFk
FROM tmp.zoneOption zo
JOIN zone z ON z.id = zo.zoneFk
JOIN agencyMode am ON am.id = z.agencyModeFk
JOIN zoneWarehouse zw ON zw.zoneFk = zo.zoneFk
WHERE zw.warehouseFk
GROUP BY z.agencyModeFk
ORDER BY agencyMode;
DROP TEMPORARY TABLE
tmp.zone,
tmp.zoneOption;
END$$
DELIMITER ;

View File

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

View File

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

View File

@ -35,7 +35,7 @@ INSERT INTO `vn`.`packagingConfig`(`upperGap`)
UPDATE `account`.`role` SET id = 100 WHERE id = 0; UPDATE `account`.`role` SET id = 100 WHERE id = 0;
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`) INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`)
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'EN' SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en'
FROM `account`.`role` WHERE id <> 20 FROM `account`.`role` WHERE id <> 20
ORDER BY id; ORDER BY id;
@ -55,18 +55,18 @@ INSERT INTO `hedera`.`tpvConfig`(`id`, `currency`, `terminal`, `transactionType`
INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`) INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`)
VALUES VALUES
(101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'ES'), (101, 'BruceWayne', 'Bruce Wayne', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es'),
(102, 'PetterParker', 'Petter Parker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'EN'), (102, 'PetterParker', 'Petter Parker', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en'),
(103, 'ClarkKent', 'Clark Kent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'FR'), (103, 'ClarkKent', 'Clark Kent', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr'),
(104, 'TonyStark', 'Tony Stark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'ES'), (104, 'TonyStark', 'Tony Stark', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es'),
(105, 'MaxEisenhardt', 'Max Eisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'PT'), (105, 'MaxEisenhardt', 'Max Eisenhardt', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt'),
(106, 'DavidCharlesHaller', 'David Charles Haller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'EN'), (106, 'DavidCharlesHaller', 'David Charles Haller', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'en'),
(107, 'HankPym', 'Hank Pym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'EN'), (107, 'HankPym', 'Hank Pym', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'en'),
(108, 'CharlesXavier', 'Charles Xavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'EN'), (108, 'CharlesXavier', 'Charles Xavier', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'en'),
(109, 'BruceBanner', 'Bruce Banner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'EN'), (109, 'BruceBanner', 'Bruce Banner', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'en'),
(110, 'JessicaJones', 'Jessica Jones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'EN'), (110, 'JessicaJones', 'Jessica Jones', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'en'),
(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`)
VALUES VALUES
@ -499,6 +499,8 @@ INSERT INTO `vn`.`zoneWarehouse` (`id`, `zoneFk`, `warehouseFk`)
(12, 12, 4), (12, 12, 4),
(13, 13, 5); (13, 13, 5);
INSERT INTO `vn`.`zoneConfig` (`scope`) VALUES ('1');
INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agencyModeFk`, `description`, `m3`, `cost`, `started`, `finished`, `zoneFk`) INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agencyModeFk`, `description`, `m3`, `cost`, `started`, `finished`, `zoneFk`)
VALUES VALUES
(1, '1899-12-30 12:15:00', 56, CURDATE(), 1, 1, 'first route', 1.8, 10, CURDATE(), CURDATE(), 1), (1, '1899-12-30 12:15:00', 56, CURDATE(), 1, 1, 'first route', 1.8, 10, CURDATE(), CURDATE(), 1),
@ -684,9 +686,15 @@ INSERT INTO `vn`.`taxCode`(`id`, `dated`, `code`, `taxTypeFk`, `rate`, `equaliza
INSERT INTO `vn`.`taxClass`(`id`, `description`, `code`) INSERT INTO `vn`.`taxClass`(`id`, `description`, `code`)
VALUES VALUES
(1, 'Reduced VAT','R'), (1, 'Reduced VAT', 'R'),
(2, 'General VAT', 'G'); (2, 'General VAT', 'G');
INSERT INTO `vn`.`taxClassCode`(`taxClassFk`, `effectived`, `taxCodeFk`)
VALUES
(1, CURDATE(), 1),
(1, CURDATE(), 21),
(2, CURDATE(), 2);
INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`) INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`)
VALUES VALUES
(05080000, 'Coral y materiales similares', 2, 2), (05080000, 'Coral y materiales similares', 2, 2),
@ -1095,11 +1103,11 @@ INSERT INTO `vn`.`annualAverageInvoiced`(`clientFk`, `invoiced`)
(104, 500), (104, 500),
(105, 5000); (105, 5000);
INSERT INTO `vn`.`supplier`(`id`, `name`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`) INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`)
VALUES VALUES
(1, 'Plants SL', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1), (1, 'Plants SL', 'Plants nick', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1),
(2, 'Flower King', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2), (2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2),
(442, 'Verdnatura Levante SL', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2); (442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2);
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`) INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
VALUES VALUES
@ -1123,17 +1131,19 @@ INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseO
(4, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 50.00, 500, 'fourth travel', 0), (4, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 50.00, 500, 'fourth travel', 0),
(5, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 2, 1, 50.00, 500, 'fifth travel', 1), (5, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 2, 1, 50.00, 500, 'fifth travel', 1),
(6, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 2, 1, 50.00, 500, 'sixth travel', 1), (6, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 2, 1, 50.00, 500, 'sixth travel', 1),
(7, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 2, 1, 50.00, 500, 'seventh travel', 1); (7, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 2, 1, 50.00, 500, 'seventh travel', 1),
(8, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 2, 1, 50.00, 500, 'eight travel', 1);
INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `companyFk`, `ref`, `notes`, `evaNotes`) INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `companyFk`, `ref`,`isInventory`, `isRaid`, `notes`, `evaNotes`)
VALUES VALUES
(1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 442, 'Movement 1', 'this is the note one', 'observation one'), (1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 442, 'Movement 1', 0, 0, '', ''),
(2, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 442, 'Movement 2', 'this is the note two', 'observation two'), (2, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 442, 'Movement 2', 0, 0, 'this is the note two', 'observation two'),
(3, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 442, 'Movement 3', 'this is the note three', 'observation three'), (3, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 442, 'Movement 3', 0, 0, 'this is the note three', 'observation three'),
(4, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 69, 'Movement 4', 'this is the note four', 'observation four'), (4, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 69, 'Movement 4', 0, 0, 'this is the note four', 'observation four'),
(5, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 442, 'Movement 5', 'this is the note five', 'observation five'), (5, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 5, 442, 'Movement 5', 0, 0, 'this is the note five', 'observation five'),
(6, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 6, 442, 'Movement 6', 'this is the note six', 'observation six'), (6, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 6, 442, 'Movement 6', 0, 0, 'this is the note six', 'observation six'),
(7, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 7, 442, 'Movement 7', 'this is the note seven', 'observation seven'); (7, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 7, 442, 'Movement 7', 0, 0, 'this is the note seven', 'observation seven'),
(8, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 7, 442, 'Movement 8', 1, 1, '', '');
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`) INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES VALUES
@ -1522,7 +1532,7 @@ INSERT INTO `vn`.`ticketRequest`(`id`, `description`, `requesterFk`, `attenderFk
VALUES VALUES
(1, 'Ranged weapon longbow 2m', 18, 35, 5, 1, 9.10, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)), (1, 'Ranged weapon longbow 2m', 18, 35, 5, 1, 9.10, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
(2, 'Melee weapon combat first 15cm', 18, 35, 10, 2, 1.07, 0, NULL, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)), (2, 'Melee weapon combat first 15cm', 18, 35, 10, 2, 1.07, 0, NULL, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
(3, 'Melee weapon heavy shield 1x0.5m', 18, 35, 20, 4, 3.06, 0, NULL, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY)), (3, 'Melee weapon heavy shield 1x0.5m', 18, 35, 20, NULL, 3.06, NULL, NULL, 23, CURDATE()),
(4, 'Melee weapon combat first 15cm', 18, 35, 15, NULL, 1.30, NULL, NULL, 11, CURDATE()), (4, 'Melee weapon combat first 15cm', 18, 35, 15, NULL, 1.30, NULL, NULL, 11, CURDATE()),
(5, 'Melee weapon combat first 15cm', 18, 35, 15, 4, 1.30, 0, NULL, 18, CURDATE()); (5, 'Melee weapon combat first 15cm', 18, 35, 15, 4, 1.30, 0, NULL, 18, CURDATE());

View File

@ -179,6 +179,13 @@ let actions = {
await this.click(selector); await this.click(selector);
}, },
writeOnEditableTD: async function(selector, text) {
let builtSelector = await this.selectorFormater(selector);
await this.waitToClick(selector);
await this.type(builtSelector, text);
await this.keyboard.press('Enter');
},
focusElement: async function(selector) { focusElement: async function(selector) {
await this.wait(selector); await this.wait(selector);
return await this.evaluate(selector => { return await this.evaluate(selector => {
@ -284,22 +291,14 @@ let actions = {
}, {}, selector, text); }, {}, selector, text);
}, },
selectorFormater: async function(selector) { selectorFormater: function(selector) {
let builtSelector = `${selector} input`;
if (selector.includes('vn-autocomplete'))
return builtSelector = `${selector} input`;
if (selector.includes('vn-textarea')) if (selector.includes('vn-textarea'))
return builtSelector = `${selector} textarea`; return `${selector} textarea`;
if (selector.includes('vn-textfield'))
return builtSelector = `${selector} input`;
if (selector.includes('vn-input-file')) if (selector.includes('vn-input-file'))
return builtSelector = `${selector} section`; return `${selector} section`;
return builtSelector; return `${selector} input`;
}, },
waitForTextInField: async function(selector, text) { waitForTextInField: async function(selector, text) {

View File

@ -27,6 +27,17 @@ export default {
createClientButton: `vn-float-button`, createClientButton: `vn-float-button`,
othersButton: 'vn-left-menu li[name="Others"] > a' othersButton: 'vn-left-menu li[name="Others"] > a'
}, },
clientSummary: {
header: 'vn-client-summary > vn-card > h5',
email: 'vn-client-summary vn-label-value[label="Email"]',
street: 'vn-client-summary vn-label-value[label="Street"]',
verifiedData: 'vn-client-summary > vn-card > vn-horizontal vn-check[ng-model="$ctrl.summary.isTaxDataChecked"]',
payMethod: 'vn-client-summary vn-label-value[label="Pay method"]',
defaultAdressName: 'vn-client-summary vn-label-value[label="Name"]',
userName: 'vn-client-summary vn-label-value[label="User"]',
rate: 'vn-client-summary vn-label-value[label="Rate"]',
credit: 'vn-client-summary vn-label-value[label="Credit"]',
},
createClientView: { createClientView: {
name: 'vn-client-create vn-textfield[ng-model="$ctrl.client.name"]', name: 'vn-client-create vn-textfield[ng-model="$ctrl.client.name"]',
taxNumber: 'vn-client-create vn-textfield[ng-model="$ctrl.client.fi"]', taxNumber: 'vn-client-create vn-textfield[ng-model="$ctrl.client.fi"]',
@ -239,6 +250,17 @@ export default {
inactiveIcon: 'vn-item-descriptor vn-icon[icon="icon-unavailable"]', inactiveIcon: 'vn-item-descriptor vn-icon[icon="icon-unavailable"]',
navigateBackToIndex: 'vn-item-descriptor vn-icon[icon="chevron_left"]' navigateBackToIndex: 'vn-item-descriptor vn-icon[icon="chevron_left"]'
}, },
itemRequest: {
firstRequestItemID: 'vn-item-request vn-tbody > vn-tr:nth-child(1) > vn-td-editable:nth-child(7)',
firstRequestQuantity: 'vn-item-request vn-tbody > vn-tr:nth-child(1) > vn-td-editable:nth-child(8)',
firstRequestConcept: 'vn-item-request vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(9)',
secondRequestStatus: 'vn-item-request vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(10)',
firstRequestStatus: 'vn-item-request vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(10)',
secondRequestDecline: 'vn-item-request vn-tbody > vn-tr:nth-child(1) vn-icon-button[icon="thumb_down"]',
declineReason: 'vn-textarea[ng-model="$ctrl.denyObservation"]',
acceptDeclineReason: 'button[response="accept"]',
},
itemBasicData: { itemBasicData: {
basicDataButton: 'vn-left-menu a[ui-sref="item.card.basicData"]', basicDataButton: 'vn-left-menu a[ui-sref="item.card.basicData"]',
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]', goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
@ -251,6 +273,10 @@ export default {
longName: 'vn-textfield[ng-model="$ctrl.item.longName"]', longName: 'vn-textfield[ng-model="$ctrl.item.longName"]',
isActiveCheckbox: 'vn-check[label="Active"]', isActiveCheckbox: 'vn-check[label="Active"]',
priceInKgCheckbox: 'vn-check[label="Price in kg"]', priceInKgCheckbox: 'vn-check[label="Price in kg"]',
newIntrastatButton: 'vn-item-basic-data vn-icon-button[vn-tooltip="New intrastat"] > button',
newIntrastatId: '.vn-dialog.shown vn-input-number[ng-model="$ctrl.newIntrastat.intrastatId"]',
newIntrastatDescription: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.newIntrastat.description"]',
acceptIntrastatButton: '.vn-dialog.shown button[response="accept"]',
submitBasicDataButton: `button[type=submit]` submitBasicDataButton: `button[type=submit]`
}, },
itemTags: { itemTags: {
@ -605,9 +631,9 @@ export default {
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"]',
itemId: 'vn-order-catalog > vn-side-menu vn-textfield[ng-model="$ctrl.itemId"]', itemId: 'vn-order-catalog > vn-side-menu vn-textfield[vn-id="itemId"]',
itemTagValue: 'vn-order-catalog > vn-side-menu vn-textfield[ng-model="$ctrl.value"]', itemTagValue: 'vn-order-catalog > vn-side-menu vn-datalist[vn-id="search"]',
openTagSearch: 'vn-order-catalog > vn-side-menu > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i', openTagSearch: 'vn-order-catalog > vn-side-menu > div > vn-vertical > vn-datalist[vn-id="search"] .append i',
tag: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]', tag: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
tagValue: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"]', tagValue: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"]',
searchTagButton: 'vn-order-catalog-search-panel button[type=submit]', searchTagButton: 'vn-order-catalog-search-panel button[type=submit]',
@ -761,5 +787,22 @@ export default {
uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="cloud_upload"]', uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="cloud_upload"]',
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]'
},
zoneBasicData: {
name: 'vn-zone-basic-data vn-textfield[ng-model="$ctrl.zone.name"]',
agency: 'vn-zone-basic-data vn-autocomplete[ng-model="$ctrl.zone.agencyModeFk"]',
maxVolume: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.m3Max"]',
travelingDays: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.travelingDays"]',
closing: 'vn-zone-basic-data vn-input-time[ng-model="$ctrl.zone.hour"]',
price: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.price"]',
bonus: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.bonus"]',
inflation: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.inflation"]',
volumetric: 'vn-zone-basic-data vn-check[ng-model="$ctrl.zone.isVolumetric"]',
saveButton: 'vn-zone-basic-data vn-submit > button',
},
entrySummary: {
header: 'vn-entry-summary > vn-card > h5',
reference: 'vn-entry-summary vn-label-value[label="Reference"]',
confirmed: 'vn-entry-summary vn-check[label="Confirmed"]',
} }
}; };

View File

@ -0,0 +1,80 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Client summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'client');
await page.accessToSearchResult('Petter Parker');
});
afterAll(async() => {
await browser.close();
});
it('should reach the first route summary section', async() => {
let url = await page.expectURL('#!/client/102/summary');
expect(url).toBe(true);
});
it('should display details from the client on the header', async() => {
await page.waitForTextInElement(selectors.clientSummary.header, 'Petter Parker');
const result = await page.waitToGetProperty(selectors.clientSummary.header, 'innerText');
expect(result).toContain('Petter Parker');
});
it('should display some basic data', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.email, 'innerText');
expect(result).toContain('PetterParker@mydomain.com');
});
it('should display fiscal address details', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.street, 'innerText');
expect(result).toContain('20 Ingram Street');
});
it('should display some fiscal data', async() => {
await page.waitForClassPresent(selectors.clientSummary.verifiedData, 'checked');
const result = await page.waitToGetProperty(selectors.clientSummary.verifiedData, 'innerText');
expect(result).toContain('Verified data');
});
it('should display pay method details', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.payMethod, 'innerText');
expect(result).toContain('PayMethod five');
});
it('should display default address details', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.defaultAdressName, 'innerText');
expect(result).toContain('Petter Parker');
});
it('should display web access details', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.userName, 'innerText');
expect(result).toContain('PetterParker');
});
it('should display business data', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.rate, 'innerText');
expect(result).toContain('%');
});
it('should display financial information', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.credit, 'innerText');
expect(result).toContain('€300.00');
});
});

View File

@ -39,6 +39,26 @@ describe('Item Edit basic data path', () => {
expect(result).toEqual('Data saved!'); expect(result).toEqual('Data saved!');
}, 20000); }, 20000);
it(`should create a new intrastat`, async() => {
await page.waitToClick(selectors.itemBasicData.newIntrastatButton);
await page.write(selectors.itemBasicData.newIntrastatId, '588420239');
await page.write(selectors.itemBasicData.newIntrastatDescription, 'Tropical Flowers');
await page.waitToClick(selectors.itemBasicData.acceptIntrastatButton);
await page.waitForTextInField(selectors.itemBasicData.intrastat, 'Tropical Flowers');
let newcode = await page.waitToGetProperty(selectors.itemBasicData.intrastat, 'value');
expect(newcode).toEqual('588420239 Tropical Flowers');
});
it(`should save with the new intrastat`, async() => {
await page.waitFor(250);
await page.waitForTextInField(selectors.itemBasicData.intrastat, 'Tropical Flowers');
await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it(`should confirm the item name was edited`, async() => { it(`should confirm the item name was edited`, async() => {
await page.reloadSection('item.card.basicData'); await page.reloadSection('item.card.basicData');
const result = await page.waitToGetProperty(selectors.itemBasicData.name, 'value'); const result = await page.waitToGetProperty(selectors.itemBasicData.name, 'value');
@ -53,11 +73,11 @@ describe('Item Edit basic data path', () => {
expect(result).toEqual('Anthurium'); expect(result).toEqual('Anthurium');
}); });
it(`should confirm the item intrastad was edited`, async() => { it(`should confirm the item intrastat was edited`, async() => {
const result = await page const result = await page
.waitToGetProperty(selectors.itemBasicData.intrastat, 'value'); .waitToGetProperty(selectors.itemBasicData.intrastat, 'value');
expect(result).toEqual('5080000 Coral y materiales similares'); expect(result).toEqual('588420239 Tropical Flowers');
}); });
it(`should confirm the item relevancy was edited`, async() => { it(`should confirm the item relevancy was edited`, async() => {

View File

@ -0,0 +1,48 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Item request path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'item');
await page.accessToSection('item.request');
});
afterAll(async() => {
await browser.close();
});
it('should reach the item request section', async() => {
const result = await page.expectURL('/item/request');
expect(result).toBe(true);
});
it('should fill the id and quantity then check the concept was updated', async() => {
await page.writeOnEditableTD(selectors.itemRequest.firstRequestItemID, '4');
await page.writeOnEditableTD(selectors.itemRequest.firstRequestQuantity, '10');
await page.waitForTextInElement(selectors.itemRequest.firstRequestConcept, 'Melee weapon heavy shield 1x0.5m');
let filledConcept = await page.waitToGetProperty(selectors.itemRequest.firstRequestConcept, 'innerText');
expect(filledConcept).toContain('Melee weapon heavy shield 1x0.5m');
});
it('should the status of the request should now be accepted', async() => {
let status = await page.waitToGetProperty(selectors.itemRequest.firstRequestStatus, 'innerText');
expect(status).toContain('Aceptada');
});
it('should now click on the second declain request icon then type the reason', async() => {
await page.waitToClick(selectors.itemRequest.secondRequestDecline);
await page.write(selectors.itemRequest.declineReason, 'not quite as expected');
await page.waitToClick(selectors.itemRequest.acceptDeclineReason);
await page.waitForContentLoaded();
let status = await page.waitToGetProperty(selectors.itemRequest.firstRequestStatus, 'innerText');
expect(status).toContain('Denegada');
});
});

View File

@ -0,0 +1,103 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Zone basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('deliveryBoss', 'zone'); // turns up the zone module name and route aint the same lol
await page.accessToSearchResult('10');
await page.accessToSection('zone.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should reach the basic data section', async() => {
let url = await page.expectURL('#!/zone/10/basic-data');
expect(url).toBe(true);
});
it('should edit de form and then save', async() => {
await page.clearInput(selectors.zoneBasicData.name);
await page.write(selectors.zoneBasicData.name, 'Brimstone teleportation');
await page.autocompleteSearch(selectors.zoneBasicData.agency, 'Quantum break device');
await page.write(selectors.zoneBasicData.maxVolume, '10');
await page.clearInput(selectors.zoneBasicData.travelingDays);
await page.write(selectors.zoneBasicData.travelingDays, '1');
await page.clearInput(selectors.zoneBasicData.closing);
await page.type(selectors.zoneBasicData.closing, '2100');
await page.clearInput(selectors.zoneBasicData.price);
await page.write(selectors.zoneBasicData.price, '999');
await page.clearInput(selectors.zoneBasicData.bonus);
await page.write(selectors.zoneBasicData.bonus, '100');
await page.clearInput(selectors.zoneBasicData.inflation);
await page.write(selectors.zoneBasicData.inflation, '200');
await page.waitToClick(selectors.zoneBasicData.volumetric);
await page.waitToClick(selectors.zoneBasicData.saveButton);
});
it('should reload the section', async() => {
await page.reloadSection('zone.card.basicData');
let url = await page.expectURL('#!/zone/10/basic-data');
expect(url).toBe(true);
});
it('should confirm the name was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.name, 'value');
expect(result).toEqual('Brimstone teleportation');
});
it('should confirm the agency was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.agency, 'value');
expect(result).toEqual('Quantum break device');
});
it('should confirm the max volume was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.maxVolume, 'value');
expect(result).toEqual('10');
});
it('should confirm the traveling days were updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.travelingDays, 'value');
expect(result).toEqual('1');
});
it('should confirm the closing hour was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.closing, 'value');
expect(result).toEqual('21:00');
});
it('should confirm the price was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.price, 'value');
expect(result).toEqual('999');
});
it('should confirm the bonus was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.bonus, 'value');
expect(result).toEqual('100');
});
it('should confirm the inflation was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.inflation, 'value');
expect(result).toEqual('200');
});
it('should confirm the volumetric checkbox was checked', async() => {
await page.waitForClassPresent(selectors.zoneBasicData.volumetric, 'checked');
});
});

View File

@ -0,0 +1,43 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.waitToClick('vn-entry-index vn-tbody > a:nth-child(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 display details from the entry on the header`, async() => {
await page.waitForTextInElement(selectors.entrySummary.header, 'The king');
const result = await page.waitToGetProperty(selectors.entrySummary.header, 'innerText');
expect(result).toContain('The king');
});
it('should display some entry details like the reference', async() => {
const result = await page.waitToGetProperty(selectors.entrySummary.reference, 'innerText');
expect(result).toContain('Movement 2');
});
it('should display other entry details like the confirmed', async() => {
const result = await page.checkboxState(selectors.entrySummary.confirmed, 'innerText');
expect(result).toContain('unchecked');
});
});

View File

@ -29,8 +29,10 @@ export default class Button extends FormInput {
} }
onClick(event) { onClick(event) {
if (this.disabled) if (this.disabled) {
event.preventDefault();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
}
} }
} }
Button.$inject = ['$element', '$scope']; Button.$inject = ['$element', '$scope'];

View File

@ -3,6 +3,7 @@ import ArrayModel from '../array-model/array-model';
import CrudModel from '../crud-model/crud-model'; import CrudModel from '../crud-model/crud-model';
import {mergeWhere} from 'vn-loopback/util/filter'; import {mergeWhere} from 'vn-loopback/util/filter';
import Textfield from '../textfield/textfield'; import Textfield from '../textfield/textfield';
import './style.scss';
export default class Datalist extends Textfield { export default class Datalist extends Textfield {
constructor($element, $scope, $compile, $transclude) { constructor($element, $scope, $compile, $transclude) {
@ -12,7 +13,6 @@ export default class Datalist extends Textfield {
this._selection = null; this._selection = null;
this.buildInput('text'); this.buildInput('text');
this.input.setAttribute('autocomplete', 'off'); this.input.setAttribute('autocomplete', 'off');
} }
@ -157,8 +157,6 @@ export default class Datalist extends Textfield {
this.destroyList(); this.destroyList();
} else } else
this.buildList(); this.buildList();
this.emit('select', {selection});
}); });
} }

View File

@ -0,0 +1,7 @@
@import "effects";
vn-datalist {
input::-webkit-calendar-picker-indicator {
display: none
}
}

View File

@ -14,10 +14,13 @@ export function directive($parse) {
const cb = $parse($attrs.vnHttpClick); const cb = $parse($attrs.vnHttpClick);
const element = $element[0]; const element = $element[0];
$element.on('click', () => { $element.on('click', () => {
element.$ctrl.disabled = true; const controller = element.$ctrl;
controller.$oldDisabled = controller.disabled;
controller.disabled = true;
cb($scope).finally(() => { cb($scope).finally(() => {
element.$ctrl.disabled = false; if (!controller.$oldDisabled)
controller.disabled = false;
}); });
}); });
} }

View File

@ -19,12 +19,16 @@ export function directive($parse) {
const fields = angular.element(elements); const fields = angular.element(elements);
angular.forEach(fields, field => { angular.forEach(fields, field => {
field.$ctrl.disabled = true; const controller = field.$ctrl;
controller.$oldDisabled = controller.disabled;
controller.disabled = true;
}); });
cb($scope).finally(() => { cb($scope).finally(() => {
angular.forEach(fields, field => { angular.forEach(fields, field => {
field.$ctrl.disabled = false; const controller = field.$ctrl;
if (!controller.$oldDisabled)
controller.disabled = false;
}); });
}); });
}); });

View File

@ -23,6 +23,12 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-net:before {
content: "\e95b";
}
.icon-anonymous:before {
content: "\e95c";
}
.icon-buyrequest:before { .icon-buyrequest:before {
content: "\e914"; content: "\e914";
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -12,7 +12,7 @@ export default function moduleImport(moduleName) {
case 'ticket' : return import('ticket/front'); case 'ticket' : return import('ticket/front');
case 'order' : return import('order/front'); case 'order' : return import('order/front');
case 'claim' : return import('claim/front'); case 'claim' : return import('claim/front');
case 'agency' : return import('agency/front'); case 'zone' : return import('zone/front');
case 'travel' : return import('travel/front'); case 'travel' : return import('travel/front');
case 'worker' : return import('worker/front'); case 'worker' : return import('worker/front');
case 'invoiceOut' : return import('invoiceOut/front'); case 'invoiceOut' : return import('invoiceOut/front');

View File

@ -32,7 +32,7 @@ Remove: Quitar
# Modules # Modules
Agencies: Agencias Zones: Zonas
Claims: Reclamaciones Claims: Reclamaciones
Clients: Clientes Clients: Clientes
Items: Artículos Items: Artículos

View File

@ -2,7 +2,7 @@ import 'angular';
import 'angular-mocks'; import 'angular-mocks';
import core from './front/core/module.js'; import core from './front/core/module.js';
import './front/salix/components/app/app.js'; import './front/salix/components/app/app.js';
import './modules/agency/front/module.js'; import './modules/zone/front/module.js';
import './modules/claim/front/module.js'; import './modules/claim/front/module.js';
import './modules/client/front/module.js'; import './modules/client/front/module.js';
import './modules/invoiceOut/front/module.js'; import './modules/invoiceOut/front/module.js';

View File

@ -61,7 +61,7 @@
"MESSAGE_BOUGHT_UNITS": "Bought {{quantity}} units of {{concept}} (#{{itemId}}) for the ticket id [#{{ticketId}}]({{{url}}})", "MESSAGE_BOUGHT_UNITS": "Bought {{quantity}} units of {{concept}} (#{{itemId}}) for the ticket id [#{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} (#{{clientId}})]({{{url}}}) to *{{credit}} €*", "MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} (#{{clientId}})]({{{url}}}) to *{{credit}} €*",
"MESSAGE_CHANGED_PAYMETHOD": "I have changed the pay method for client [{{clientName}} (#{{clientId}})]({{{url}}})", "MESSAGE_CHANGED_PAYMETHOD": "I have changed the pay method for client [{{clientName}} (#{{clientId}})]({{{url}}})",
"MESSAGE_CLAIM_ITEM_REGULARIZE": "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}}"

View File

@ -124,7 +124,8 @@
"MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} (#{{itemId}}) para el ticket id [#{{ticketId}}]({{{url}}})", "MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} (#{{itemId}}) para el ticket id [#{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} (#{{clientId}})]({{{url}}}) a *{{credit}} €*", "MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} (#{{clientId}})]({{{url}}}) a *{{credit}} €*",
"MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} (#{{clientId}})]({{{url}}})", "MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} (#{{clientId}})]({{{url}}})",
"MESSAGE_CLAIM_ITEM_REGULARIZE": "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"
} }

View File

@ -1,3 +0,0 @@
import {ng} from 'core/vendor';
export default ng.module('agency', ['vnCore']);

View File

@ -37,27 +37,43 @@ module.exports = Self => {
for (let i = 0; i < claimEnds.length; i++) { for (let i = 0; i < claimEnds.length; i++) {
const claimEnd = claimEnds[i]; const claimEnd = claimEnds[i];
const destination = claimEnd.claimDestination(); const destination = claimEnd.claimDestination();
const addressFk = destination && destination.addressFk; const sale = await getSale(claimEnd.saleFk, options);
const addressId = destination && destination.addressFk;
if (!addressFk) continue; let address;
if (addressId)
address = await models.Address.findById(addressId, null, options);
const salesPerson = sale.ticket().client().salesPerson();
if (salesPerson) {
const nickname = address && address.nickname || destination.description;
const origin = ctx.req.headers.origin;
const message = $t('Sent units from ticket', {
quantity: sale.quantity,
concept: sale.concept,
itemId: sale.itemFk,
ticketId: sale.ticketFk,
nickname: nickname,
ticketUrl: `${origin}/#!/ticket/${sale.ticketFk}/summary`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
if (!address) continue;
let sale = await getSale(claimEnd.saleFk, options);
let ticketFk = await getTicketId({ let ticketFk = await getTicketId({
addressFk: addressFk, addressFk: addressId,
companyFk: sale.ticket().companyFk, companyFk: sale.ticket().companyFk,
warehouseFk: sale.ticket().warehouseFk warehouseFk: sale.ticket().warehouseFk
}, options); }, options);
let address = await models.Address.findOne({
where: {id: addressFk}
}, options);
if (!ticketFk) { if (!ticketFk) {
ticketFk = await createTicket(ctx, { ticketFk = await createTicket(ctx, {
clientId: address.clientFk, clientId: address.clientFk,
warehouseId: sale.ticket().warehouseFk, warehouseId: sale.ticket().warehouseFk,
companyId: sale.ticket().companyFk, companyId: sale.ticket().companyFk,
addressId: addressFk addressId: addressId
}, options); }, options);
} }
@ -69,21 +85,6 @@ module.exports = Self => {
price: sale.price, price: sale.price,
discount: 100 discount: 100
}, options); }, options);
const salesPerson = sale.ticket().client().salesPerson();
if (salesPerson) {
const origin = ctx.req.headers.origin;
const message = $t('MESSAGE_CLAIM_ITEM_REGULARIZE', {
quantity: sale.quantity,
concept: sale.concept,
itemId: sale.itemFk,
ticketId: sale.ticketFk,
nickname: address.nickname,
ticketUrl: `${origin}/#!/ticket/${sale.ticketFk}/summary`,
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
} }
let claim = await Self.findById(params.claimFk, null, options); let claim = await Self.findById(params.claimFk, null, options);

View File

@ -5,6 +5,7 @@ describe('regularizeClaim()', () => {
const pendentState = 1; const pendentState = 1;
const resolvedState = 3; const resolvedState = 3;
const trashDestination = 2; const trashDestination = 2;
const okDestination = 1;
const trashAddress = 12; const trashAddress = 12;
let claimEnds = []; let claimEnds = [];
let trashTicket; let trashTicket;
@ -21,15 +22,20 @@ describe('regularizeClaim()', () => {
done(); done();
}); });
it('should change claim state to resolved', async() => { it('should send a chat message with value "Trash" and then change claim state to resolved', async() => {
const ctx = {req: { const ctx = {
accessToken: {userId: 18}, req: {
headers: {origin: 'http://localhost'}} accessToken: {userId: 18},
headers: {origin: 'http://localhost'}
}
}; };
ctx.req.__ = value => { ctx.req.__ = (value, params) => {
return value; return params.nickname;
}; };
let params = {claimFk: claimFk}; let params = {claimFk: claimFk};
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, { claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
claimFk: claimFk, claimFk: claimFk,
@ -49,5 +55,32 @@ describe('regularizeClaim()', () => {
expect(trashTicket.addressFk).toEqual(trashAddress); expect(trashTicket.addressFk).toEqual(trashAddress);
expect(claimBefore.claimStateFk).toEqual(pendentState); expect(claimBefore.claimStateFk).toEqual(pendentState);
expect(claimAfter.claimStateFk).toEqual(resolvedState); expect(claimAfter.claimStateFk).toEqual(resolvedState);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Trash');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
});
it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => {
const ctx = {
req: {
accessToken: {userId: 18},
headers: {origin: 'http://localhost'}
}
};
ctx.req.__ = (value, params) => {
return params.nickname;
};
let params = {claimFk: claimFk};
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
claimEnds.forEach(async claimEnd => {
claimEnd.updateAttributes({claimDestinationFk: okDestination});
});
await app.models.Claim.regularizeClaim(ctx, params);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
}); });
}); });

View File

@ -5,60 +5,60 @@ module.exports = function(Self) {
description: 'Creates client address updating default address', description: 'Creates client address updating default address',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'Number', type: 'number',
description: 'The client id', description: 'The client id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'nickname', arg: 'nickname',
type: 'String', type: 'string',
required: true required: true
}, },
{ {
arg: 'city', arg: 'city',
type: 'String', type: 'string',
required: true required: true
}, },
{ {
arg: 'street', arg: 'street',
type: 'String', type: 'string',
required: true required: true
}, },
{ {
arg: 'phone', arg: 'phone',
type: 'String' type: 'string'
}, },
{ {
arg: 'mobile', arg: 'mobile',
type: 'String' type: 'string'
}, },
{ {
arg: 'postalCode', arg: 'postalCode',
type: 'String' type: 'string'
}, },
{ {
arg: 'provinceId', arg: 'provinceId',
type: 'Number' type: 'number'
}, },
{ {
arg: 'agencyModeId', arg: 'agencyModeId',
type: 'Number' type: 'number'
}, },
{ {
arg: 'incotermsId', arg: 'incotermsId',
type: 'String' type: 'string'
}, },
{ {
arg: 'customsAgentId', arg: 'customsAgentId',
type: 'Number' type: 'number'
}, },
{ {
arg: 'isActive', arg: 'isActive',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isDefaultAddress', arg: 'isDefaultAddress',
type: 'Boolean' type: 'boolean'
}], }],
returns: { returns: {
root: true, root: true,

View File

@ -10,63 +10,63 @@ module.exports = function(Self) {
}, },
{ {
arg: 'clientId', arg: 'clientId',
type: 'Number', type: 'number',
description: 'The client id', description: 'The client id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'addressId', arg: 'addressId',
type: 'Number', type: 'number',
description: 'The address id', description: 'The address id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'nickname', arg: 'nickname',
type: 'String' type: 'string'
}, },
{ {
arg: 'city', arg: 'city',
type: 'String' type: 'string'
}, },
{ {
arg: 'street', arg: 'street',
type: 'String' type: 'string'
}, },
{ {
arg: 'phone', arg: 'phone',
type: 'String' type: 'any'
}, },
{ {
arg: 'mobile', arg: 'mobile',
type: 'String' type: 'any'
}, },
{ {
arg: 'postalCode', arg: 'postalCode',
type: 'String' type: 'any'
}, },
{ {
arg: 'provinceFk', arg: 'provinceFk',
type: 'Number' type: 'any'
}, },
{ {
arg: 'agencyModeFk', arg: 'agencyModeFk',
type: 'Number' type: 'any'
}, },
{ {
arg: 'incotermsFk', arg: 'incotermsFk',
type: 'String' type: 'any'
}, },
{ {
arg: 'customsAgentFk', arg: 'customsAgentFk',
type: 'Number' type: 'any'
}, },
{ {
arg: 'isActive', arg: 'isActive',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isEqualizated', arg: 'isEqualizated',
type: 'Boolean' type: 'boolean'
}], }],
returns: { returns: {
root: true, root: true,

View File

@ -17,75 +17,75 @@ module.exports = Self => {
}, },
{ {
arg: 'socialName', arg: 'socialName',
type: 'String' type: 'string'
}, },
{ {
arg: 'fi', arg: 'fi',
type: 'String' type: 'string'
}, },
{ {
arg: 'street', arg: 'street',
type: 'String' type: 'string'
}, },
{ {
arg: 'postcode', arg: 'postcode',
type: 'String' type: 'string'
}, },
{ {
arg: 'city', arg: 'city',
type: 'String' type: 'string'
}, },
{ {
arg: 'countryFk', arg: 'countryFk',
type: 'Number' type: 'number'
}, },
{ {
arg: 'provinceFk', arg: 'provinceFk',
type: 'Number' type: 'number'
}, },
{ {
arg: 'hasToInvoiceByAddress', arg: 'hasToInvoiceByAddress',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'hasToInvoice', arg: 'hasToInvoice',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isActive', arg: 'isActive',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isFreezed', arg: 'isFreezed',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isVies', arg: 'isVies',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isToBeMailed', arg: 'isToBeMailed',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isEqualizated', arg: 'isEqualizated',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isTaxDataVerified', arg: 'isTaxDataVerified',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'isTaxDataChecked', arg: 'isTaxDataChecked',
type: 'Boolean' type: 'boolean'
}, },
{ {
arg: 'despiteOfClient', arg: 'despiteOfClient',
type: 'Number' type: 'number'
}], }],
returns: { returns: {
arg: 'res', arg: 'res',
type: 'String', type: 'string',
root: true root: true
}, },
http: { http: {

View File

@ -85,9 +85,9 @@
</span> </span>
</vn-td> </vn-td>
<vn-td number>{{::balance.bankFk}}</vn-td> <vn-td number>{{::balance.bankFk}}</vn-td>
<vn-td number>{{::balance.debit | currency: 'EUR':2}}</vn-td> <vn-td number expand>{{::balance.debit | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::balance.credit | currency: 'EUR':2}}</vn-td> <vn-td number expand>{{::balance.credit | currency: 'EUR':2}}</vn-td>
<vn-td number>{{balance.balance | currency: 'EUR':2}}</vn-td> <vn-td number expand>{{balance.balance | currency: 'EUR':2}}</vn-td>
<vn-td center shrink> <vn-td center shrink>
<vn-check <vn-check
ng-model="balance.isConciliate" ng-model="balance.isConciliate"

View File

@ -7,7 +7,6 @@ class Controller {
this.$ = $scope; this.$ = $scope;
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this.$translate = $translate; this.$translate = $translate;
this.accessToken = vnToken.token; this.accessToken = vnToken.token;
this.vnConfig = vnConfig; this.vnConfig = vnConfig;
this.filter = { this.filter = {
@ -33,6 +32,18 @@ class Controller {
if (value) this.getData(); if (value) this.getData();
} }
get balances() {
return this._balances;
}
set balances(value) {
this._balances = value;
const riskModel = this.$.riskModel;
if (value && riskModel.data)
this.getBalances();
}
getData() { getData() {
return this.$.model.applyFilter(null, { return this.$.model.applyFilter(null, {
clientId: this.$stateParams.id, clientId: this.$stateParams.id,

View File

@ -44,12 +44,6 @@ describe('Client', () => {
}); });
describe('company setter/getter', () => { describe('company setter/getter', () => {
it('should return the company', () => {
controller.companyId = null;
expect(controller._companyId).toEqual(jasmine.any(Object));
});
it('should return the company and then call getData()', () => { it('should return the company and then call getData()', () => {
spyOn(controller, 'getData'); spyOn(controller, 'getData');
controller.companyId = 442; controller.companyId = 442;
@ -97,5 +91,48 @@ describe('Client', () => {
expect(expectedBalances[2].balance).toEqual(213.24); expect(expectedBalances[2].balance).toEqual(213.24);
}); });
}); });
describe('balances() setter', () => {
it('should set the balances data and not call the getBalances() method', () => {
spyOn(controller, 'getBalances');
controller.$.riskModel.data = null;
controller.balances = [{
id: 1,
debit: 1000,
credit: null
}, {
id: 2,
debit: null,
credit: 500
}, {
id: 3,
debit: null,
credit: 300
}];
expect(controller.balances).toBeDefined();
expect(controller.getBalances).not.toHaveBeenCalledWith();
});
it('should set the balances data and then call the getBalances() method', () => {
spyOn(controller, 'getBalances');
controller.balances = [{
id: 1,
debit: 1000,
credit: null
}, {
id: 2,
debit: null,
credit: 500
}, {
id: 3,
debit: null,
credit: 300
}];
expect(controller.balances).toBeDefined();
expect(controller.getBalances).toHaveBeenCalledWith();
});
});
}); });
}); });

View File

@ -77,18 +77,15 @@
<tpl-body> <tpl-body>
<div> <div>
<h5 style="text-align: center"> <h5 style="text-align: center">
<span translate>From date</span> <span translate>Send consumer report</span>
</h5> </h5>
<vn-date-picker <vn-date-picker
vn-id="from" vn-id="from"
vn-one vn-one
ng-model="$ctrl.from" ng-model="$ctrl.from"
label="From hour" label="From date"
vn-focus> vn-focus>
</vn-date-picker> </vn-date-picker>
<h5 style="text-align: center">
<span translate>To date</span>
</h5>
<vn-date-picker <vn-date-picker
vn-id="to" vn-id="to"
vn-one vn-one

View File

@ -55,10 +55,11 @@ class Controller extends Component {
} }
showSMSDialog() { showSMSDialog() {
const phone = this.$params.phone || this.client.phone; const client = this.client;
const phone = this.$params.phone || client.mobile || client.phone;
const message = this.$params.message || ''; const message = this.$params.message || '';
this.newSMS = { this.newSMS = {
destinationFk: this.client.id, destinationFk: client.id,
destination: phone, destination: phone,
message: message message: message
}; };

View File

@ -1,2 +1,4 @@
Simple ticket: Ticket simple Simple ticket: Ticket simple
Send consumer report: Enviar informe de consumo Send consumer report: Enviar informe de consumo
From date: Fecha desde
To date: Fecha hasta

View File

@ -15,7 +15,8 @@
<vn-horizontal> <vn-horizontal>
<vn-textfield vn-one <vn-textfield vn-one
label="Recipient" label="Recipient"
ng-model="$ctrl.clientSample.recipient"> ng-model="$ctrl.clientSample.recipient"
info="Its only used when sample is sent">
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
@ -30,7 +31,7 @@
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
ng-model="$ctrl.companyId" ng-model="$ctrl.companyId"
model="ClientSample.companyFk" model="ClientSample.companyId"
data="companiesData" data="companiesData"
show-field="code" show-field="code"
value-field="id" value-field="id"

View File

@ -10,7 +10,7 @@ class Controller extends Component {
this.vnConfig = vnConfig; this.vnConfig = vnConfig;
this.clientSample = { this.clientSample = {
clientFk: this.$params.id, clientFk: this.$params.id,
companyFk: vnConfig.companyFk companyId: vnConfig.companyFk
}; };
} }
@ -26,44 +26,13 @@ class Controller extends Component {
} }
get companyId() { get companyId() {
if (!this.clientSample.companyFk) if (!this.clientSample.companyId)
this.clientSample.companyFk = this.vnConfig.companyFk; this.clientSample.companyId = this.vnConfig.companyFk;
return this.clientSample.companyFk; return this.clientSample.companyId;
} }
set companyId(value) { set companyId(value) {
this.clientSample.companyFk = value; this.clientSample.companyId = value;
}
showPreview() {
let sampleType = this.$.sampleType.selection;
if (!sampleType)
return this.vnApp.showError(this.$translate.instant('Choose a sample'));
if (sampleType.hasCompany && !this.clientSample.companyFk)
return this.vnApp.showError(this.$translate.instant('Choose a company'));
const params = {
clientId: this.$params.id,
recipient: this.clientSample.recipient,
isPreview: true
};
if (sampleType.hasCompany)
params.companyId = this.clientSample.companyFk;
const serializedParams = this.$httpParamSerializer(params);
const query = `email/${sampleType.code}?${serializedParams}`;
this.$http.get(query).then(res => {
this.$.showPreview.show();
let dialog = document.body.querySelector('div.vn-dialog');
let body = dialog.querySelector('tpl-body');
let scroll = dialog.querySelector('div:first-child');
body.innerHTML = res.data;
scroll.scrollTop = 0;
});
} }
onSubmit() { onSubmit() {
@ -73,28 +42,49 @@ class Controller extends Component {
); );
} }
showPreview() {
this.send(true, res => {
this.$.showPreview.show();
const dialog = document.body.querySelector('div.vn-dialog');
const body = dialog.querySelector('tpl-body');
const scroll = dialog.querySelector('div:first-child');
body.innerHTML = res.data;
scroll.scrollTop = 0;
});
}
sendSample() { sendSample() {
let sampleType = this.$.sampleType.selection; this.send(false, () => {
let params = { this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
this.$state.go('client.card.sample.index');
});
}
send(isPreview, cb) {
const sampleType = this.$.sampleType.selection;
const params = {
clientId: this.$params.id, clientId: this.$params.id,
recipient: this.clientSample.recipient recipient: this.clientSample.recipient
}; };
if (!params.recipient)
return this.vnApp.showError(this.$translate.instant('Email cannot be blank'));
if (!sampleType) if (!sampleType)
return this.vnApp.showError(this.$translate.instant('Choose a sample')); return this.vnApp.showError(this.$translate.instant('Choose a sample'));
if (sampleType.hasCompany && !this.clientSample.companyFk) if (sampleType.hasCompany && !this.clientSample.companyId)
return this.vnApp.showError(this.$translate.instant('Choose a company')); return this.vnApp.showError(this.$translate.instant('Choose a company'));
if (sampleType.hasCompany) if (sampleType.hasCompany)
params.companyId = this.clientSample.companyFk; params.companyId = this.clientSample.companyId;
if (isPreview) params.isPreview = true;
const serializedParams = this.$httpParamSerializer(params); const serializedParams = this.$httpParamSerializer(params);
const query = `email/${sampleType.code}?${serializedParams}`; const query = `email/${sampleType.code}?${serializedParams}`;
this.$http.get(query).then(res => { this.$http.get(query).then(cb);
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
this.$state.go('client.card.sample.index');
});
} }
} }
Controller.$inject = ['$element', '$scope', 'vnApp', '$httpParamSerializer', 'vnConfig']; Controller.$inject = ['$element', '$scope', 'vnApp', '$httpParamSerializer', 'vnConfig'];

View File

@ -40,84 +40,16 @@ describe('Client', () => {
$httpParamSerializer = _$httpParamSerializer_; $httpParamSerializer = _$httpParamSerializer_;
$element = angular.element('<vn-client-sample-create></vn-client-sample-create>'); $element = angular.element('<vn-client-sample-create></vn-client-sample-create>');
controller = $componentController('vnClientSampleCreate', {$element, $scope}); controller = $componentController('vnClientSampleCreate', {$element, $scope});
const element = document.createElement('div');
document.body.querySelector = () => {
return {
querySelector: () => {
return element;
}
};
};
})); }));
describe('showPreview()', () => {
it(`should perform a query (GET) and open a sample preview`, () => {
spyOn(controller.$.showPreview, 'show');
const element = document.createElement('div');
document.body.querySelector = () => {
return {
querySelector: () => {
return element;
}
};
};
controller.$.sampleType.selection = {
hasCompany: false,
code: 'MyReport'
};
controller.clientSample = {
clientFk: 101
};
let event = {preventDefault: () => {}};
const params = {
clientId: 101,
isPreview: true
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
controller.showPreview(event);
$httpBackend.flush();
expect(controller.$.showPreview.show).toHaveBeenCalledWith();
});
it(`should perform a query (GET) with companyFk param and open a sample preview`, () => {
spyOn(controller.$.showPreview, 'show');
const element = document.createElement('div');
document.body.querySelector = () => {
return {
querySelector: () => {
return element;
}
};
};
controller.$.sampleType.selection = {
hasCompany: true,
code: 'MyReport'
};
controller.clientSample = {
clientFk: 101,
companyFk: 442
};
let event = {preventDefault: () => {}};
const params = {
clientId: 101,
companyId: 442,
isPreview: true
};
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
controller.showPreview(event);
$httpBackend.flush();
expect(controller.$.showPreview.show).toHaveBeenCalledWith();
});
});
describe('onSubmit()', () => { describe('onSubmit()', () => {
it(`should call sendSample() method`, () => { it(`should call sendSample() method`, () => {
spyOn(controller, 'sendSample'); spyOn(controller, 'sendSample');
@ -127,55 +59,113 @@ describe('Client', () => {
}); });
}); });
describe('sendSample()', () => { describe('send()', () => {
it(`should perform a query (GET) and call go() method`, () => { it(`should not perform an HTTP query if no recipient is specified`, () => {
spyOn(controller.$state, 'go'); spyOn(controller.$http, 'get');
controller.$.sampleType.selection = { controller.$.sampleType.selection = {
hasCompany: false, hasCompany: false,
code: 'MyReport' code: 'MyReport'
}; };
controller.clientSample = { controller.clientSample = {
clientFk: 101
};
const params = {
clientId: 101 clientId: 101
}; };
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true); controller.send(false, () => {});
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
controller.sendSample();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('client.card.sample.index'); expect(controller.$http.get).not.toHaveBeenCalled();
}); });
it(`should perform a query (GET) with companyFk param and call go() method`, () => { it(`should not perform an HTTP query if no sample is specified`, () => {
spyOn(controller.$state, 'go'); spyOn(controller.$http, 'get');
controller.$.sampleType.selection = null;
controller.clientSample = {
clientId: 101,
recipient: 'client@email.com'
};
controller.send(false, () => {});
expect(controller.$http.get).not.toHaveBeenCalled();
});
it(`should not perform an HTTP query if company is required and not specified`, () => {
spyOn(controller.$http, 'get');
controller.$.sampleType.selection = { controller.$.sampleType.selection = {
hasCompany: true, hasCompany: true,
code: 'MyReport' code: 'MyReport'
}; };
controller.clientSample = { controller.clientSample = {
clientFk: 101, clientId: 101,
companyFk: 442 recipient: 'client@email.com'
}; };
const params = { controller.send(false, () => {});
expect(controller.$http.get).not.toHaveBeenCalled();
});
it(`should perform an HTTP query without passing companyId param`, () => {
controller.$.sampleType.selection = {
hasCompany: false,
code: 'MyReport'
};
controller.clientSample = {
clientId: 101, clientId: 101,
recipient: 'client@email.com'
};
const serializedParams = $httpParamSerializer(controller.clientSample);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`).respond(true);
controller.send(false, () => {});
$httpBackend.flush();
});
it(`should perform an HTTP query passing companyId param`, () => {
controller.$.sampleType.selection = {
hasCompany: true,
code: 'MyReport'
};
controller.clientSample = {
clientId: 101,
recipient: 'client@email.com',
companyId: 442 companyId: 442
}; };
const serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true); const serializedParams = $httpParamSerializer(controller.clientSample);
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`); $httpBackend.expect('GET', `email/MyReport?${serializedParams}`).respond(true);
controller.sendSample(); controller.send(false, () => {});
$httpBackend.flush(); $httpBackend.flush();
});
});
describe('showPreview()', () => {
it(`should open a sample preview`, () => {
spyOn(controller.$.showPreview, 'show');
controller.send = (isPreview, cb) => {
cb({
data: '<div></div>'
});
};
controller.showPreview();
expect(controller.$.showPreview.show).toHaveBeenCalledWith();
});
});
describe('sendSample()', () => {
it(`should perform a query (GET) and call go() method`, () => {
spyOn(controller.$state, 'go');
controller.send = (isPreview, cb) => {
cb({
data: true
});
};
controller.sendSample();
expect(controller.$state.go).toHaveBeenCalledWith('client.card.sample.index'); expect(controller.$state.go).toHaveBeenCalledWith('client.card.sample.index');
}); });

View File

@ -1,3 +1,5 @@
Choose a sample: Selecciona una plantilla Choose a sample: Selecciona una plantilla
Choose a company: Selecciona una empresa Choose a company: Selecciona una empresa
Email cannot be blank: Debes introducir un email
Recipient: Destinatario Recipient: Destinatario
Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla

View File

@ -13,20 +13,27 @@
rule> rule>
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal > <vn-horizontal>
<vn-textarea vn-one <vn-textarea vn-one
vn-id="message" vn-id="message"
label="Message" label="Message"
ng-model="$ctrl.sms.message" ng-model="$ctrl.sms.message"
info="Special characters like accents counts as a multiple"
rows="5" rows="5"
maxlength="160"
required="true" required="true"
rule> rule>
</vn-textarea> </vn-textarea>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<span> <span>
{{'Characters remaining' | translate}}: {{$ctrl.charactersRemaining()}} {{'Characters remaining' | translate}}:
<vn-chip translate-attr="{title: 'Packing'}" ng-class="{
'colored': $ctrl.charactersRemaining() > 25,
'warning': $ctrl.charactersRemaining() <= 25,
'alert': $ctrl.charactersRemaining() < 0,
}">
{{$ctrl.charactersRemaining()}}
</vn-chip>
</span> </span>
</vn-horizontal> </vn-horizontal>
</section> </section>

View File

@ -17,13 +17,12 @@ class Controller extends Component {
} }
charactersRemaining() { charactersRemaining() {
let elementMaxLength;
let textAreaLength;
const element = this.$scope.message; const element = this.$scope.message;
const value = element.input.value;
textAreaLength = element.input.textLength; const maxLength = 160;
elementMaxLength = element.maxlength; const textAreaLength = new Blob([value]).size;
return elementMaxLength - textAreaLength; return maxLength - textAreaLength;
} }
onResponse(response) { onResponse(response) {
@ -33,6 +32,8 @@ class Controller extends Component {
throw new Error(`The destination can't be empty`); throw new Error(`The destination can't be empty`);
if (!this.sms.message) if (!this.sms.message)
throw new Error(`The message can't be empty`); throw new Error(`The message can't be empty`);
if (this.charactersRemaining() < 0)
throw new Error(`The message it's too long`);
this.$http.post(`Clients/${this.$params.id}/sendSms`, this.sms).then(res => { this.$http.post(`Clients/${this.$params.id}/sendSms`, this.sms).then(res => {
this.vnApp.showMessage(this.$translate.instant('SMS sent!')); this.vnApp.showMessage(this.$translate.instant('SMS sent!'));

View File

@ -15,6 +15,11 @@ describe('Client', () => {
controller = $componentController('vnClientSms', {$element, $scope}); controller = $componentController('vnClientSms', {$element, $scope});
controller.client = {id: 101}; controller.client = {id: 101};
controller.$params = {id: 101}; controller.$params = {id: 101};
controller.$scope.message = {
input: {
value: 'My SMS'
}
};
})); }));
describe('onResponse()', () => { describe('onResponse()', () => {
@ -56,14 +61,13 @@ describe('Client', () => {
it('should return the characters remaining in a element', () => { it('should return the characters remaining in a element', () => {
controller.$scope.message = { controller.$scope.message = {
input: { input: {
textLength: 50 value: 'My message 0€'
}, }
maxlength: 150
}; };
let result = controller.charactersRemaining(); let result = controller.charactersRemaining();
expect(result).toEqual(100); expect(result).toEqual(145);
}); });
}); });
}); });

View File

@ -5,3 +5,5 @@ SMS sent!: ¡SMS enviado!
Characters remaining: Carácteres restantes Characters remaining: Carácteres restantes
The destination can't be empty: El destinatario no puede estar vacio The destination can't be empty: El destinatario no puede estar vacio
The message can't be empty: El mensaje no puede estar vacio The message can't be empty: El mensaje no puede estar vacio
The message it's too long: El mensaje es demasiado largo
Special characters like accents counts as a multiple: Carácteres especiales como los acentos cuentan como varios

View File

@ -68,6 +68,14 @@ module.exports = Self => {
type: 'Number', type: 'Number',
description: 'The currency id to filter', description: 'The currency id to filter',
http: {source: 'query'} http: {source: 'query'}
}, {
arg: 'from',
type: 'Date',
description: `The from date filter`
}, {
arg: 'to',
type: 'Date',
description: `The to date filter`
} }
], ],
returns: { returns: {
@ -91,6 +99,10 @@ module.exports = Self => {
return {[param]: {like: `%${value}%`}}; return {[param]: {like: `%${value}%`}};
case 'created': case 'created':
return {'e.created': {gte: value}}; return {'e.created': {gte: value}};
case 'from':
return {'t.landed': {gte: value}};
case 'to':
return {'t.landed': {lte: value}};
case 'id': case 'id':
case 'isBooked': case 'isBooked':
case 'isConfirmed': case 'isConfirmed':
@ -127,6 +139,7 @@ module.exports = Self => {
e.companyFk, e.companyFk,
e.gestDocFk, e.gestDocFk,
e.invoiceInFk, e.invoiceInFk,
t.landed,
s.name AS supplierName, s.name AS supplierName,
co.code AS companyCode, co.code AS companyCode,
cu.code AS currencyCode cu.code AS currencyCode

View File

@ -0,0 +1,76 @@
module.exports = Self => {
Self.remoteMethod('getEntry', {
description: 'Returns an entry',
accessType: 'READ',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The entry id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getEntry`,
verb: 'GET'
}
});
Self.getEntry = async id => {
let filter = {
where: {id: id},
include: [
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
},
{
relation: 'travel',
scope: {
fields: ['id', 'name', 'shipped', 'landed', 'agencyFk', 'warehouseOutFk', 'warehouseInFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
},
{
relation: 'currency',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
}
],
};
let entry = await Self.app.models.Entry.findOne(filter);
return entry;
};
};

View File

@ -23,7 +23,7 @@ describe('Entry filter()', () => {
let result = await app.models.Entry.filter(ctx); let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(7); expect(result.length).toEqual(8);
}); });
it('should return the entry matching the supplier', async() => { it('should return the entry matching the supplier', async() => {
@ -35,7 +35,7 @@ describe('Entry filter()', () => {
let result = await app.models.Entry.filter(ctx); let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(5); expect(result.length).toEqual(6);
}); });
it('should return the entry matching the company', async() => { it('should return the entry matching the company', async() => {
@ -47,7 +47,7 @@ describe('Entry filter()', () => {
let result = await app.models.Entry.filter(ctx); let result = await app.models.Entry.filter(ctx);
expect(result.length).toEqual(6); expect(result.length).toEqual(7);
}); });
it('should return the entries matching isBooked', async() => { it('should return the entries matching isBooked', async() => {

View File

@ -0,0 +1,31 @@
const app = require('vn-loopback/server/server');
describe('travel getEntry()', () => {
const entryId = 1;
it('should check the entry contains the id', async() => {
const entry = await app.models.Entry.getEntry(entryId);
expect(entry.id).toEqual(entryId);
});
it('should check the entry contains the supplier name', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const supplierName = entry.supplier().nickname;
expect(supplierName).toEqual('Plants nick');
});
it('should check the entry contains the receiver warehouse name', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const receiverWarehouseName = entry.travel().warehouseIn().name;
expect(receiverWarehouseName).toEqual('Warehouse One');
});
it('should check the entry contains the company code', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const companyCode = entry.company().code;
expect(companyCode).toEqual('VNL');
});
});

View File

@ -1,4 +1,5 @@
module.exports = Self => { module.exports = Self => {
require('../methods/entry/filter')(Self); require('../methods/entry/filter')(Self);
require('../methods/entry/getEntry')(Self);
}; };

View File

@ -39,6 +39,9 @@
"commission": { "commission": {
"type": "Number" "type": "Number"
}, },
"isOrdered": {
"type": "Boolean"
},
"created": { "created": {
"type": "date" "type": "date"
}, },

View File

@ -11,11 +11,34 @@ class Controller extends ModuleCard {
fields: ['id', 'code'] fields: ['id', 'code']
} }
}, { }, {
relation: 'travel' relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
}, { }, {
relation: 'supplier', relation: 'supplier',
scope: { scope: {
fields: ['id', 'name'] fields: ['id', 'nickname']
} }
}, { }, {
relation: 'currency' relation: 'currency'
@ -27,7 +50,7 @@ class Controller extends ModuleCard {
} }
} }
ngModule.component('vnEntry Card', { ngModule.component('vnEntryCard', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller
}); });

View File

@ -6,16 +6,37 @@
<a translate-attr="{title: 'Preview'}" ui-sref="entry.card.summary({id: $ctrl.entry.id})"> <a translate-attr="{title: 'Preview'}" ui-sref="entry.card.summary({id: $ctrl.entry.id})">
<vn-icon icon="desktop_windows"></vn-icon> <vn-icon icon="desktop_windows"></vn-icon>
</a> </a>
<span></span> <vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-icon-menu>
</div> </div>
<div class="body"> <div class="body">
<div class="attributes"> <div class="attributes">
<vn-label-value label="Id" <vn-label-value label="Id"
value="{{$ctrl.entry.id}}"> value="{{$ctrl.entry.id}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Reference" <vn-label-value label="Supplier"
value="{{$ctrl.entry.ref}}"> value="{{$ctrl.entry.supplier.nickname}}">
</vn-label-value>
<vn-label-value label="Agency "
value="{{$ctrl.entry.travel.agency.name}}">
</vn-label-value>
<vn-label-value label="Landed"
value="{{$ctrl.entry.travel.landed | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse Out"
value="{{$ctrl.entry.travel.warehouseOut.name}}">
</vn-label-value> </vn-label-value>
</div> </div>
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
</div> </div>
</div> </div>

View File

@ -1,17 +1,80 @@
import ngModule from '../module'; import ngModule from '../module';
import Component from 'core/lib/component';
class Controller { class Controller extends Component {
constructor($scope) { constructor($element, $, $httpParamSerializer, vnConfig) {
this.$ = $scope; super($element, $);
this.vnConfig = vnConfig;
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{name: 'Show entry report', callback: this.showEntryReport}
];
}
onMoreChange(callback) {
callback.call(this);
}
get entry() {
return this._entry;
}
set entry(value) {
this._entry = value;
if (!value) return;
const date = value.travel.landed;
let to = new Date(date);
let from = new Date(date);
to.setDate(to.getDate() + 10);
to.setHours(0, 0, 0, 0);
from.setDate(from.getDate() - 10);
from.setHours(0, 0, 0, 0);
let links = {
btnOne: {
icon: 'local_airport',
state: `travel.index({q: '{"agencyFk": ${value.travel.agencyFk}}'})`,
tooltip: 'All travels with current agency'
}};
links.btnTwo = {
icon: 'icon-entry',
state: `entry.index({q: '{"supplierFk": ${value.supplierFk}, "to": "${to}", "from": "${from}"}'})`,
tooltip: 'All entries with current supplier'
};
this._quicklinks = links;
}
get quicklinks() {
return this._quicklinks;
}
set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks);
}
showEntryReport() {
const params = {
clientId: this.vnConfig.storage.currentUserWorkerId,
entryId: this.entry.id
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/entry-order?${serializedParams}`;
window.open(url);
} }
} }
Controller.$inject = ['$scope']; Controller.$inject = ['$element', '$scope', '$httpParamSerializer', 'vnConfig'];
ngModule.component('vnEntryDescriptor', { ngModule.component('vnEntryDescriptor', {
template: require('./index.html'), template: require('./index.html'),
bindings: { bindings: {
entry: '<' entry: '<',
quicklinks: '<'
}, },
require: { require: {
card: '^?vnEntryCard' card: '^?vnEntryCard'

View File

@ -0,0 +1,35 @@
import './index.js';
describe('Entry Component vnEntryDescriptor', () => {
let $httpParamSerializer;
let controller;
let $element;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, _$httpParamSerializer_) => {
$httpParamSerializer = _$httpParamSerializer_;
$element = angular.element(`<vn-entry-descriptor></vn-entry-descriptor>`);
controller = $componentController('vnEntryDescriptor', {$element});
controller._entry = {id: 2};
controller.vnConfig.storage = {currentUserWorkerId: 9};
controller.cardReload = ()=> {
return true;
};
}));
describe('showEntryReport()', () => {
it('should open a new window showing a delivery note PDF document', () => {
const params = {
clientId: controller.vnConfig.storage.currentUserWorkerId,
entryId: controller.entry.id
};
const serializedParams = $httpParamSerializer(params);
let expectedPath = `api/report/entry-order?${serializedParams}`;
spyOn(window, 'open');
controller.showEntryReport();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
});

View File

@ -1 +1,4 @@
Reference: Referencia Reference: Referencia
All travels with current agency: Todos los envios con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual
Show entry report: Ver informe del pedido

View File

@ -5,7 +5,4 @@ import './index/';
import './search-panel'; import './search-panel';
import './descriptor'; import './descriptor';
import './card'; import './card';
// import './summary'; import './summary';
// import './basic-data';
// import './log';
// import './create';

View File

@ -16,45 +16,68 @@
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th shrink></vn-th>
<vn-th field="id" number>Id</vn-th> <vn-th field="id" number>Id</vn-th>
<vn-th field="created" center>Created</vn-th> <vn-th field="landed" center>Landed</vn-th>
<vn-th field="travelFk">Travel</vn-th>
<vn-th>Notes</vn-th>
<vn-th>Reference</vn-th> <vn-th>Reference</vn-th>
<vn-th field="isBooked" center>Booked</vn-th>
<vn-th field="isInventory" center>Is inventory</vn-th>
<vn-th field="isConfirmed" center>Confirmed</vn-th>
<vn-th field="isOrdered" center>Ordered</vn-th>
<vn-th field="isRaid" center>Is raid</vn-th>
<vn-th center>Commission</vn-th>
<vn-th field="supplierFk">Supplier</vn-th> <vn-th field="supplierFk">Supplier</vn-th>
<vn-th field="currencyFk" center>Currency</vn-th> <vn-th field="currencyFk" center>Currency</vn-th>
<vn-th field="companyFk" center>Company</vn-th> <vn-th field="companyFk" center>Company</vn-th>
<vn-th field="isBooked" center>Booked</vn-th>
<vn-th field="isConfirmed" center>Confirmed</vn-th>
<vn-th field="isOrdered" center>Ordered</vn-th>
<vn-th>Notes</vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<a ng-repeat="entry in entries" <a ng-repeat="entry in entries"
class="clickable vn-tr search-result" class="clickable vn-tr search-result"
ui-sref="entry.card.summary({id: {{::entry.id}}})"> ui-sref="entry.card.summary({id: {{::entry.id}}})">
<vn-td shrink>
<vn-icon
ng-show="entry.isInventory"
class="bright"
vn-tooltip="Inventory entry"
icon="icon-anonymous">
</vn-icon>
<vn-icon
ng-show="entry.isRaid"
class="bright"
vn-tooltip="Virtual entry"
icon="icon-net">
</vn-icon>
</vn-td>
<vn-td number>{{::entry.id}}</vn-td> <vn-td number>{{::entry.id}}</vn-td>
<vn-td center>{{::entry.created | date:'dd/MM/yyyy'}}</vn-td> <vn-td center>
<vn-td center expand>{{::entry.travelFk}}</vn-td> <span
<vn-td expand>{{::entry.notes}}</vn-td> class="link"
ng-click="$ctrl.showTravelDescriptor($event, entry.travelFk)">
{{::entry.landed | date:'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td expand>{{::entry.ref}}</vn-td> <vn-td expand>{{::entry.ref}}</vn-td>
<vn-td center><vn-check ng-model="entry.isBooked" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isInventory" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isConfirmed" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isOrdered" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isRaid" disabled="true"></vn-check></vn-td>
<vn-td center expand>{{::entry.commission}}</vn-td>
<vn-td expand>{{::entry.supplierName}}</vn-td> <vn-td expand>{{::entry.supplierName}}</vn-td>
<vn-td center expand>{{::entry.currencyCode}}</vn-td> <vn-td center expand>{{::entry.currencyCode}}</vn-td>
<vn-td center expand>{{::entry.companyCode}}</vn-td> <vn-td center expand>{{::entry.companyCode}}</vn-td>
<vn-td center><vn-check ng-model="entry.isBooked" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isConfirmed" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isOrdered" disabled="true"></vn-check></vn-td>
<vn-td shrink>
<vn-icon
ng-if="entry.notes.length"
vn-tooltip="{{::entry.notes}}"
icon="insert_drive_file">
</vn-icon>
</vn-td>
</a> </a>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
</vn-data-viewer> </vn-data-viewer>
</vn-card> </vn-card>
<vn-travel-descriptor-popover
vn-id="travelDescriptor"
travel-id="$ctrl.selectedTravel">
</vn-travel-descriptor-popover>
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-entry-summary <vn-entry-summary
entry="$ctrl.entrySelected"> entry="$ctrl.entrySelected">

View File

@ -1,5 +1,5 @@
import ngModule from '../module'; import ngModule from '../module';
import './style.scss';
export default class Controller { export default class Controller {
constructor($scope) { constructor($scope) {
this.$ = $scope; this.$ = $scope;
@ -11,6 +11,16 @@ export default class Controller {
else else
this.$.model.clear(); this.$.model.clear();
} }
showTravelDescriptor(event, travelFk) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
this.selectedTravel = travelFk;
this.$.travelDescriptor.parent = event.target;
this.$.travelDescriptor.show();
}
} }
Controller.$inject = ['$scope']; Controller.$inject = ['$scope'];

View File

@ -0,0 +1,15 @@
Inventory entry: Es inventario
Virtual entry: Es una redada
Supplier: Proveedor
Currency: Moneda
Company: Empresa
Confirmed: Confirmada
Ordered: Pedida
Is raid: Redada
Commission: Comisión
Landed: F. entrega
Reference: Referencia
Created: Creado
Booked: Facturado
Is inventory: Inventario
Notes: Notas

View File

@ -0,0 +1,5 @@
@import "variables";
vn-icon[icon=insert_drive_file]{
color: $color-font-secondary;
}

View File

@ -1,15 +1,3 @@
#Ordenar alfabeticamente #Ordenar alfabeticamente
Reference: Referencia
Created: Creado
Booked: Facturado
Is inventory: Inventario
Notes: Notas
Travel: Envío
Supplier: Proveedor
Currency: Moneda
Company: Empresa
Confirmed: Confirmada
Ordered: Pedida
Is raid: Redada
Commission: Comisión
# Sections # Sections

View File

@ -2,6 +2,7 @@
"module": "entry", "module": "entry",
"name": "Entries", "name": "Entries",
"icon": "icon-entry", "icon": "icon-entry",
"dependencies": ["travel"],
"validations": true, "validations": true,
"menus": { "menus": {
"main": [ "main": [
@ -27,6 +28,14 @@
"state": "entry.card", "state": "entry.card",
"abstract": true, "abstract": true,
"component": "vn-entry-card" "component": "vn-entry-card"
}, {
"url": "/summary",
"state": "entry.card.summary",
"component": "vn-entry-summary",
"description": "Summary",
"params": {
"entry": "$ctrl.entry"
}
} }
] ]
} }

View File

@ -54,6 +54,18 @@
ng-model="filter.created"> ng-model="filter.created">
</vn-date-picker> </vn-date-picker>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="From"
ng-model="filter.from">
</vn-date-picker>
<vn-date-picker
vn-one
label="To"
ng-model="filter.to">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-check <vn-check
vn-one vn-one

View File

@ -0,0 +1,68 @@
<vn-card class="summary">
<h5><span translate>Entry</span> #{{$ctrl.entryData.id}} - {{$ctrl.entryData.supplier.nickname}}</h5>
<vn-horizontal>
<vn-one>
<vn-label-value label="Commission"
value="{{$ctrl.entryData.commission}}">
</vn-label-value>
<vn-label-value label="Currency"
value="{{$ctrl.entryData.currency.name}}">
</vn-label-value>
<vn-label-value label="Company"
value="{{$ctrl.entryData.company.code}}">
</vn-label-value>
<vn-label-value label="Reference"
value="{{$ctrl.entryData.ref}}">
</vn-label-value>
<vn-label-value label="Notes"
value="{{$ctrl.entryData.notes}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-label-value label="Agency"
value="{{$ctrl.entryData.travel.agency.name}}">
</vn-label-value>
<vn-label-value label="Shipped"
value="{{$ctrl.entryData.travel.shipped | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse Out"
value="{{$ctrl.entryData.travel.warehouseOut.name}}">
</vn-label-value>
<vn-label-value label="Landed"
value="{{$ctrl.entryData.travel.landed | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse In"
value="{{$ctrl.entryData.travel.warehouseIn.name}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-vertical>
<vn-check
label="Ordered"
value="{{$ctrl.entryData.isOrdered}}"
disabled="true">
</vn-check>
<vn-check
label="Confirmed"
value="{{$ctrl.entryData.isConfirmed}}"
disabled="true">
</vn-check>
<vn-check
label="Booked"
value="{{$ctrl.entryData.isBooked}}"
disabled="true">
</vn-check>
<vn-check
label="Virtual"
value="{{$ctrl.entryData.isVirtual}}"
disabled="true">
</vn-check>
<vn-check
label="Inventory"
value="{{$ctrl.entryData.isInventory}}"
disabled="true">
</vn-check>
</vn-vertical>
</vn-one>
</vn-horizontal>
</vn-card>

View File

@ -0,0 +1,37 @@
import ngModule from '../module';
import './style.scss';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $, $httpParamSerializer) {
super($element, $);
this.$httpParamSerializer = $httpParamSerializer;
}
get entry() {
return this._entry;
}
set entry(value) {
this._entry = value;
if (value && value.id)
this.getEntryData();
}
getEntryData() {
return this.$http.get(`/api/Entries/${this.entry.id}/getEntry`).then(response => {
this.entryData = response.data;
});
}
}
Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnEntrySummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -0,0 +1,50 @@
import './index';
describe('component vnEntrySummary', () => {
let controller;
let $httpBackend;
let $scope;
let $element;
beforeEach(angular.mock.module('entry', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$element = angular.element(`<vn-entry-summary></vn-entry-summary>`);
controller = $componentController('vnEntrySummary', {$element, $scope});
}));
describe('entry setter/getter', () => {
it('should check if value.id is defined', () => {
spyOn(controller, 'getEntryData');
controller.entry = {id: 1};
expect(controller.getEntryData).toHaveBeenCalledWith();
});
it('should return the entry and then call getEntryData()', () => {
spyOn(controller, 'getEntryData');
controller.entry = {id: 99};
expect(controller._entry.id).toEqual(99);
expect(controller.getEntryData).toHaveBeenCalledWith();
});
});
describe('getEntryData()', () => {
it('should perform a get and then store data on the controller', () => {
controller._entry = {id: 999};
const query = `/api/Entries/${controller._entry.id}/getEntry`;
$httpBackend.expectGET(query).respond('I am the entryData');
controller.getEntryData();
$httpBackend.flush();
expect(controller.entryData).toEqual('I am the entryData');
});
});
});

View File

@ -0,0 +1,3 @@
Inventory: Inventario
Virtual: Redada
Entry: Entrada

View File

@ -0,0 +1,10 @@
@import "variables";
vn-entry-summary .summary {
max-width: $width-lg;
vn-icon[icon=insert_drive_file]{
color: $color-font-secondary;
}
}

View File

@ -0,0 +1,61 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('createIntrastat', {
description: 'Creates a new item intrastat',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
http: {source: 'path'}
},
{
arg: 'intrastatId',
type: 'number',
required: true
},
{
arg: 'description',
type: 'string',
required: true
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/:id/createIntrastat`,
verb: 'PATCH'
}
});
Self.createIntrastat = async(id, intrastatId, description) => {
const models = Self.app.models;
const country = await models.Country.findOne({
where: {code: 'ES'}
});
const itemTaxCountry = await models.ItemTaxCountry.findOne({
where: {
itemFk: id,
countryFk: country.id
},
order: 'effectived DESC'
});
const taxClassCode = await models.TaxClassCode.findOne({
where: {
taxClassFk: itemTaxCountry.taxClassFk
},
order: 'effectived DESC'
});
return models.Intrastat.create({
id: intrastatId,
description: description,
taxClassFk: itemTaxCountry.taxClassFk,
taxCodeFk: taxClassCode.taxCodeFk
});
};
};

View File

@ -22,6 +22,7 @@ module.exports = Self => {
let where = filter.where; let where = filter.where;
let query = `CALL vn.itemLastEntries(?, ?)`; let query = `CALL vn.itemLastEntries(?, ?)`;
let [lastEntries] = await Self.rawSql(query, [where.itemFk, where.date]); let [lastEntries] = await Self.rawSql(query, [where.itemFk, where.date]);
return lastEntries; return lastEntries;
}; };
}; };

View File

@ -0,0 +1,22 @@
const app = require('vn-loopback/server/server');
describe('createIntrastat()', () => {
let newIntrastat;
afterAll(async done => {
await app.models.Intrastat.destroyById(newIntrastat.id);
done();
});
it('should create a new intrastat', async() => {
const intrastatId = 588420239;
const description = 'Tropical Flowers';
const itemId = 9;
newIntrastat = await app.models.Item.createIntrastat(itemId, intrastatId, description);
expect(newIntrastat.description).toEqual(description);
expect(newIntrastat.taxClassFk).toEqual(1);
expect(newIntrastat.taxCodeFk).toEqual(21);
});
});

View File

@ -62,6 +62,9 @@
"TaxClass": { "TaxClass": {
"dataSource": "vn" "dataSource": "vn"
}, },
"TaxClassCode": {
"dataSource": "vn"
},
"TaxCode": { "TaxCode": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -12,6 +12,7 @@ module.exports = Self => {
require('../methods/item/getVisibleAvailable')(Self); require('../methods/item/getVisibleAvailable')(Self);
require('../methods/item/new')(Self); require('../methods/item/new')(Self);
require('../methods/item/getWasteDetail')(Self); require('../methods/item/getWasteDetail')(Self);
require('../methods/item/createIntrastat')(Self);
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'}); Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});

View File

@ -1,13 +1,13 @@
{ {
"name": "Item", "name": "Item",
"base": "Loggable", "base": "Loggable",
"log": { "log": {
"model":"ItemLog", "model": "ItemLog",
"showField": "id" "showField": "id"
}, },
"options": { "options": {
"mysql": { "mysql": {
"table": "item" "table": "item"
} }
}, },
"properties": { "properties": {
@ -125,55 +125,55 @@
} }
}, },
"relations": { "relations": {
"itemType": { "itemType": {
"type": "belongsTo", "type": "belongsTo",
"model": "ItemType", "model": "ItemType",
"foreignKey": "typeFk" "foreignKey": "typeFk"
}, },
"ink": { "ink": {
"type": "belongsTo", "type": "belongsTo",
"model": "Ink", "model": "Ink",
"foreignKey": "inkFk" "foreignKey": "inkFk"
}, },
"origin": { "origin": {
"type": "belongsTo", "type": "belongsTo",
"model": "Origin", "model": "Origin",
"foreignKey": "originFk" "foreignKey": "originFk"
}, },
"producer": { "producer": {
"type": "belongsTo", "type": "belongsTo",
"model": "Producer", "model": "Producer",
"foreignKey": "producerFk" "foreignKey": "producerFk"
}, },
"intrastat": { "intrastat": {
"type": "belongsTo", "type": "belongsTo",
"model": "Intrastat", "model": "Intrastat",
"foreignKey": "intrastatFk" "foreignKey": "intrastatFk"
}, },
"expense": { "expense": {
"type": "belongsTo", "type": "belongsTo",
"model": "Expense", "model": "Expense",
"foreignKey": "expenseFk" "foreignKey": "expenseFk"
}, },
"tags": { "tags": {
"type": "hasMany", "type": "hasMany",
"model": "ItemTag", "model": "ItemTag",
"foreignKey": "itemFk" "foreignKey": "itemFk"
}, },
"itemBarcode": { "itemBarcode": {
"type": "hasMany", "type": "hasMany",
"model": "ItemBarcode", "model": "ItemBarcode",
"foreignKey": "itemFk" "foreignKey": "itemFk"
}, },
"taxes": { "taxes": {
"type": "hasMany", "type": "hasMany",
"model": "ItemTaxCountry", "model": "ItemTaxCountry",
"foreignKey": "itemFk" "foreignKey": "itemFk"
}, },
"itemNiche": { "itemNiche": {
"type": "hasMany", "type": "hasMany",
"model": "ItemNiche", "model": "ItemNiche",
"foreignKey": "itemFk" "foreignKey": "itemFk"
} }
} }
} }

View File

@ -0,0 +1,46 @@
{
"name": "TaxClassCode",
"base": "VnModel",
"options": {
"mysql": {
"table": "taxClassCode"
}
},
"properties": {
"taxClassFk": {
"type": "number",
"required": true,
"id": 1
},
"taxCodeFk": {
"type": "number",
"required": true,
"id": 2
},
"effectived": {
"type": "date",
"required": true,
"id": 3
}
},
"relations": {
"taxClass": {
"type": "belongsTo",
"model": "TaxClass",
"foreignKey": "taxClassFk"
},
"taxCode": {
"type": "belongsTo",
"model": "TaxCode",
"foreignKey": "taxCodeFk"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`vnItemBasicData Component vnItemBasicData $onChanges() should pass the data to the watcher 1`] = `"the current value of an item"`;

View File

@ -55,6 +55,13 @@
<div style="width: 6em; text-align: right; padding-right: 1em;">{{::id}}</div> <div style="width: 6em; text-align: right; padding-right: 1em;">{{::id}}</div>
<div>{{::description}}</div> <div>{{::description}}</div>
</tpl-item> </tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New intrastat"
ng-click="$ctrl.showIntrastat($event)">
</vn-icon-button>
</append>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
url="Expenses" url="Expenses"
@ -69,7 +76,7 @@
value-field="id" value-field="id"
ng-model="$ctrl.item.originFk" ng-model="$ctrl.item.originFk"
initial-data="$ctrl.item.origin"> initial-data="$ctrl.item.origin">
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
@ -123,7 +130,7 @@
</vn-check> </vn-check>
<vn-textarea <vn-textarea
vn-one vn-one
label="description" label="Description"
ng-model="$ctrl.item.description" ng-model="$ctrl.item.description"
rule> rule>
</vn-textarea> </vn-textarea>
@ -134,3 +141,30 @@
<vn-button label="Undo changes" ng-if="$ctrl.$scope.form.$dirty" ng-click="watcher.loadOriginalData()"></vn-button> <vn-button label="Undo changes" ng-if="$ctrl.$scope.form.$dirty" ng-click="watcher.loadOriginalData()"></vn-button>
</vn-button-bar> </vn-button-bar>
</form> </form>
<!-- Create custom agent dialog -->
<vn-dialog class="edit"
vn-id="intrastat"
on-accept="$ctrl.onIntrastatAccept()">
<tpl-body>
<h5 class="vn-py-sm" translate>New intrastat</h5>
<vn-horizontal>
<vn-input-number vn-one vn-focus
label="Identifier"
ng-model="$ctrl.newIntrastat.intrastatId"
required="true">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one
label="Description"
ng-model="$ctrl.newIntrastat.description"
required="true">
</vn-textfield>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Create</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,20 +1,23 @@
import ngModule from '../module'; import ngModule from '../module';
import Component from 'core/lib/component';
class Controller extends Component {
showIntrastat(event) {
if (event.defaultPrevented) return;
event.preventDefault();
class Controller { this.newIntrastat = {
constructor($scope, $timeout) { taxClassFk: this.item.taxClassFk
this.$scope = $scope; };
this.$timeout = $timeout; this.$.intrastat.show();
} }
$onChanges(data) { onIntrastatAccept() {
this.$timeout(() => { const query = `Items/${this.$params.id}/createIntrastat`;
this.$scope.watcher.data = data.item.currentValue; return this.$http.patch(query, this.newIntrastat)
}); .then(res => this.item.intrastatFk = res.data.id);
} }
} }
Controller.$inject = ['$scope', '$timeout'];
ngModule.component('vnItemBasicData', { ngModule.component('vnItemBasicData', {
template: require('./index.html'), template: require('./index.html'),
bindings: { bindings: {

View File

@ -2,26 +2,31 @@ import './index.js';
describe('vnItemBasicData', () => { describe('vnItemBasicData', () => {
describe('Component vnItemBasicData', () => { describe('Component vnItemBasicData', () => {
let $httpBackend;
let $scope; let $scope;
let controller; let controller;
let $timeout; let $element;
beforeEach(ngModule('item')); beforeEach(ngModule('item'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$timeout_) => { beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$timeout = _$timeout_; $httpBackend = _$httpBackend_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
controller = $componentController('vnItemBasicData', {$scope, $timeout}); $element = angular.element('<vn-item-basic-data></vn-item-basic-data>');
controller.$scope.watcher = {}; controller = $componentController('vnItemBasicData', {$element, $scope});
controller.$.watcher = {};
controller.$params.id = 1;
controller.item = {id: 1, name: 'Rainbow Coral'};
})); }));
describe('$onChanges()', () => { describe('onIntrastatAccept()', () => {
it('should pass the data to the watcher', () => { it('should pass the data to the watcher', () => {
const data = {item: {currentValue: 'the current value of an item'}}; const newIntrastatId = 20;
controller.$onChanges(data); $httpBackend.expect('PATCH', 'Items/1/createIntrastat').respond({id: 20});
$timeout.flush(); controller.onIntrastatAccept();
$httpBackend.flush();
expect(controller.$scope.watcher.data).toMatchSnapshot(); expect(controller.item.intrastatFk).toEqual(newIntrastatId);
}); });
}); });
}); });

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