Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 1835-e2e-item-request
This commit is contained in:
commit
081c742dcb
|
@ -32,6 +32,8 @@ module.exports = Self => {
|
|||
|
||||
if (sender.name != recipient)
|
||||
return sendMessage(sender, to, message);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
async function sendMessage(sender, channel, message) {
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = Self => {
|
|||
relation: 'department'
|
||||
}
|
||||
});
|
||||
const department = workerDepartment.department();
|
||||
const department = workerDepartment && workerDepartment.department();
|
||||
const channelName = department.chatName;
|
||||
|
||||
if (channelName)
|
||||
|
|
|
@ -9,10 +9,10 @@ describe('chat send()', () => {
|
|||
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 response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something');
|
||||
|
||||
expect(response).toBeUndefined();
|
||||
expect(response).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,9 +3,9 @@ ADD COLUMN `zonePrice` DECIMAL(10,2) NULL DEFAULT NULL AFTER `collectionFk`,
|
|||
ADD COLUMN `zoneBonus` DECIMAL(10,2) NULL DEFAULT NULL AFTER `zonePrice`,
|
||||
ADD COLUMN `zoneClosure` TIME NULL AFTER `zoneBonus`;
|
||||
|
||||
CREATE TABLE vn.`zoneCalcTicket` (
|
||||
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
|
||||
CONSTRAINT `zoneCalcTicketfk_1` FOREIGN KEY (`zoneFk`) REFERENCES `vn`.`zone` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
||||
|
||||
DROP EVENT IF EXISTS vn.`zone_doCalc`;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
USE `vn`;
|
|
@ -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
|
||||
*/
|
||||
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 OR zoneClosure 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, zoneClosure = zo.`hour`
|
||||
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, zoneClosure = z.`hour`
|
||||
WHERE t.zonePrice IS NULL AND z.id = vZoneFk
|
||||
AND landed >= '2019-01-01' AND shipped >= '2019-01-01';
|
||||
|
||||
END LOOP;
|
||||
|
||||
CLOSE cCur;
|
||||
|
||||
DELETE FROM zoneCalcTicket;
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
|
@ -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 ;
|
|
@ -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 ;
|
||||
|
|
@ -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`))));
|
|
@ -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`))));
|
|
@ -0,0 +1,41 @@
|
|||
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
|
||||
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 ;
|
|
@ -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 ;
|
||||
|
|
@ -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 ;
|
|
@ -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 ;
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES ('Intrastat', '*', '*', 'ALLOW', 'ROLE', 'buyer');
|
|
@ -684,8 +684,14 @@ INSERT INTO `vn`.`taxCode`(`id`, `dated`, `code`, `taxTypeFk`, `rate`, `equaliza
|
|||
|
||||
INSERT INTO `vn`.`taxClass`(`id`, `description`, `code`)
|
||||
VALUES
|
||||
(1, 'Reduced VAT','R'),
|
||||
(1, 'Reduced VAT', 'R'),
|
||||
(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`)
|
||||
VALUES
|
||||
|
@ -1123,17 +1129,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),
|
||||
(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),
|
||||
(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
|
||||
(1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 442, 'Movement 1', 'this is the note one', 'observation one'),
|
||||
(2, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 442, 'Movement 2', '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'),
|
||||
(4, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 69, 'Movement 4', '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'),
|
||||
(6, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 6, 442, 'Movement 6', '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');
|
||||
(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', 0, 0, 'this is the note two', 'observation two'),
|
||||
(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', 0, 0, 'this is the note four', 'observation four'),
|
||||
(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', 0, 0, 'this is the note six', 'observation six'),
|
||||
(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`)
|
||||
VALUES
|
||||
|
|
|
@ -272,6 +272,10 @@ export default {
|
|||
longName: 'vn-textfield[ng-model="$ctrl.item.longName"]',
|
||||
isActiveCheckbox: 'vn-check[label="Active"]',
|
||||
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]`
|
||||
},
|
||||
itemTags: {
|
||||
|
@ -627,8 +631,8 @@ export default {
|
|||
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
|
||||
type: 'vn-autocomplete[data="$ctrl.itemTypes"]',
|
||||
itemId: 'vn-order-catalog > vn-side-menu vn-textfield[ng-model="$ctrl.itemId"]',
|
||||
itemTagValue: 'vn-order-catalog > vn-side-menu vn-textfield[ng-model="$ctrl.value"]',
|
||||
openTagSearch: 'vn-order-catalog > vn-side-menu > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i',
|
||||
itemTagValue: 'vn-order-catalog > vn-side-menu vn-datalist[ng-model="$ctrl.value"]',
|
||||
openTagSearch: 'vn-order-catalog > vn-side-menu > div > vn-vertical > vn-datalist[ng-model="$ctrl.value"] .append i',
|
||||
tag: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
|
||||
tagValue: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"]',
|
||||
searchTagButton: 'vn-order-catalog-search-panel button[type=submit]',
|
||||
|
|
|
@ -39,6 +39,26 @@ describe('Item Edit basic data path', () => {
|
|||
expect(result).toEqual('Data saved!');
|
||||
}, 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() => {
|
||||
await page.reloadSection('item.card.basicData');
|
||||
const result = await page.waitToGetProperty(selectors.itemBasicData.name, 'value');
|
||||
|
@ -53,11 +73,11 @@ describe('Item Edit basic data path', () => {
|
|||
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
|
||||
.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() => {
|
||||
|
|
|
@ -3,6 +3,7 @@ import ArrayModel from '../array-model/array-model';
|
|||
import CrudModel from '../crud-model/crud-model';
|
||||
import {mergeWhere} from 'vn-loopback/util/filter';
|
||||
import Textfield from '../textfield/textfield';
|
||||
import './style.scss';
|
||||
|
||||
export default class Datalist extends Textfield {
|
||||
constructor($element, $scope, $compile, $transclude) {
|
||||
|
@ -12,7 +13,6 @@ export default class Datalist extends Textfield {
|
|||
this._selection = null;
|
||||
|
||||
this.buildInput('text');
|
||||
|
||||
this.input.setAttribute('autocomplete', 'off');
|
||||
}
|
||||
|
||||
|
@ -157,8 +157,6 @@ export default class Datalist extends Textfield {
|
|||
this.destroyList();
|
||||
} else
|
||||
this.buildList();
|
||||
|
||||
this.emit('select', {selection});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
@import "effects";
|
||||
|
||||
vn-datalist {
|
||||
input::-webkit-calendar-picker-indicator {
|
||||
display: none
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@
|
|||
"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_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",
|
||||
"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}}"
|
||||
|
|
|
@ -124,7 +124,8 @@
|
|||
"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_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}}",
|
||||
"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"
|
||||
}
|
|
@ -37,27 +37,43 @@ module.exports = Self => {
|
|||
for (let i = 0; i < claimEnds.length; i++) {
|
||||
const claimEnd = claimEnds[i];
|
||||
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({
|
||||
addressFk: addressFk,
|
||||
addressFk: addressId,
|
||||
companyFk: sale.ticket().companyFk,
|
||||
warehouseFk: sale.ticket().warehouseFk
|
||||
}, options);
|
||||
|
||||
let address = await models.Address.findOne({
|
||||
where: {id: addressFk}
|
||||
}, options);
|
||||
|
||||
if (!ticketFk) {
|
||||
ticketFk = await createTicket(ctx, {
|
||||
clientId: address.clientFk,
|
||||
warehouseId: sale.ticket().warehouseFk,
|
||||
companyId: sale.ticket().companyFk,
|
||||
addressId: addressFk
|
||||
addressId: addressId
|
||||
}, options);
|
||||
}
|
||||
|
||||
|
@ -69,21 +85,6 @@ module.exports = Self => {
|
|||
price: sale.price,
|
||||
discount: 100
|
||||
}, 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);
|
||||
|
|
|
@ -5,6 +5,7 @@ describe('regularizeClaim()', () => {
|
|||
const pendentState = 1;
|
||||
const resolvedState = 3;
|
||||
const trashDestination = 2;
|
||||
const okDestination = 1;
|
||||
const trashAddress = 12;
|
||||
let claimEnds = [];
|
||||
let trashTicket;
|
||||
|
@ -21,15 +22,20 @@ describe('regularizeClaim()', () => {
|
|||
done();
|
||||
});
|
||||
|
||||
it('should change claim state to resolved', async() => {
|
||||
const ctx = {req: {
|
||||
accessToken: {userId: 18},
|
||||
headers: {origin: 'http://localhost'}}
|
||||
it('should send a chat message with value "Trash" and then change claim state to resolved', async() => {
|
||||
const ctx = {
|
||||
req: {
|
||||
accessToken: {userId: 18},
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
};
|
||||
ctx.req.__ = value => {
|
||||
return value;
|
||||
ctx.req.__ = (value, params) => {
|
||||
return params.nickname;
|
||||
};
|
||||
|
||||
let params = {claimFk: claimFk};
|
||||
const chatModel = app.models.Chat;
|
||||
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
|
||||
|
||||
claimEnds = await app.models.ClaimEnd.importTicketSales(ctx, {
|
||||
claimFk: claimFk,
|
||||
|
@ -49,5 +55,32 @@ describe('regularizeClaim()', () => {
|
|||
expect(trashTicket.addressFk).toEqual(trashAddress);
|
||||
expect(claimBefore.claimStateFk).toEqual(pendentState);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -85,9 +85,9 @@
|
|||
</span>
|
||||
</vn-td>
|
||||
<vn-td number>{{::balance.bankFk}}</vn-td>
|
||||
<vn-td number>{{::balance.debit | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td number>{{::balance.credit | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td number>{{balance.balance | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td number expand>{{::balance.debit | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td number expand>{{::balance.credit | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td number expand>{{balance.balance | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td center shrink>
|
||||
<vn-check
|
||||
ng-model="balance.isConciliate"
|
||||
|
|
|
@ -7,7 +7,6 @@ class Controller {
|
|||
this.$ = $scope;
|
||||
this.$stateParams = $stateParams;
|
||||
this.$translate = $translate;
|
||||
|
||||
this.accessToken = vnToken.token;
|
||||
this.vnConfig = vnConfig;
|
||||
this.filter = {
|
||||
|
@ -33,6 +32,18 @@ class Controller {
|
|||
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() {
|
||||
return this.$.model.applyFilter(null, {
|
||||
clientId: this.$stateParams.id,
|
||||
|
|
|
@ -97,5 +97,48 @@ describe('Client', () => {
|
|||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
<vn-horizontal>
|
||||
<vn-textfield vn-one
|
||||
label="Recipient"
|
||||
ng-model="$ctrl.clientSample.recipient">
|
||||
ng-model="$ctrl.clientSample.recipient"
|
||||
info="Its only used when sample is sent">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
|
@ -30,7 +31,7 @@
|
|||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-one
|
||||
ng-model="$ctrl.companyId"
|
||||
model="ClientSample.companyFk"
|
||||
model="ClientSample.companyId"
|
||||
data="companiesData"
|
||||
show-field="code"
|
||||
value-field="id"
|
||||
|
|
|
@ -10,7 +10,7 @@ class Controller extends Component {
|
|||
this.vnConfig = vnConfig;
|
||||
this.clientSample = {
|
||||
clientFk: this.$params.id,
|
||||
companyFk: vnConfig.companyFk
|
||||
companyId: vnConfig.companyFk
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,44 +26,13 @@ class Controller extends Component {
|
|||
}
|
||||
|
||||
get companyId() {
|
||||
if (!this.clientSample.companyFk)
|
||||
this.clientSample.companyFk = this.vnConfig.companyFk;
|
||||
return this.clientSample.companyFk;
|
||||
if (!this.clientSample.companyId)
|
||||
this.clientSample.companyId = this.vnConfig.companyFk;
|
||||
return this.clientSample.companyId;
|
||||
}
|
||||
|
||||
set companyId(value) {
|
||||
this.clientSample.companyFk = 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;
|
||||
});
|
||||
this.clientSample.companyId = value;
|
||||
}
|
||||
|
||||
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() {
|
||||
let sampleType = this.$.sampleType.selection;
|
||||
let params = {
|
||||
this.send(false, () => {
|
||||
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,
|
||||
recipient: this.clientSample.recipient
|
||||
};
|
||||
|
||||
if (!params.recipient)
|
||||
return this.vnApp.showError(this.$translate.instant('Email cannot be blank'));
|
||||
|
||||
if (!sampleType)
|
||||
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'));
|
||||
|
||||
if (sampleType.hasCompany)
|
||||
params.companyId = this.clientSample.companyFk;
|
||||
params.companyId = this.clientSample.companyId;
|
||||
|
||||
if (isPreview) params.isPreview = true;
|
||||
|
||||
const serializedParams = this.$httpParamSerializer(params);
|
||||
const query = `email/${sampleType.code}?${serializedParams}`;
|
||||
this.$http.get(query).then(res => {
|
||||
this.vnApp.showSuccess(this.$translate.instant('Notification sent!'));
|
||||
this.$state.go('client.card.sample.index');
|
||||
});
|
||||
this.$http.get(query).then(cb);
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$element', '$scope', 'vnApp', '$httpParamSerializer', 'vnConfig'];
|
||||
|
|
|
@ -40,84 +40,16 @@ describe('Client', () => {
|
|||
$httpParamSerializer = _$httpParamSerializer_;
|
||||
$element = angular.element('<vn-client-sample-create></vn-client-sample-create>');
|
||||
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()', () => {
|
||||
it(`should call sendSample() method`, () => {
|
||||
spyOn(controller, 'sendSample');
|
||||
|
@ -127,55 +59,113 @@ describe('Client', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('sendSample()', () => {
|
||||
it(`should perform a query (GET) and call go() method`, () => {
|
||||
spyOn(controller.$state, 'go');
|
||||
describe('send()', () => {
|
||||
it(`should not perform an HTTP query if no recipient is specified`, () => {
|
||||
spyOn(controller.$http, 'get');
|
||||
|
||||
controller.$.sampleType.selection = {
|
||||
hasCompany: false,
|
||||
code: 'MyReport'
|
||||
};
|
||||
|
||||
controller.clientSample = {
|
||||
clientFk: 101
|
||||
};
|
||||
|
||||
const params = {
|
||||
clientId: 101
|
||||
};
|
||||
const serializedParams = $httpParamSerializer(params);
|
||||
|
||||
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
|
||||
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
|
||||
controller.sendSample();
|
||||
$httpBackend.flush();
|
||||
controller.send(false, () => {});
|
||||
|
||||
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`, () => {
|
||||
spyOn(controller.$state, 'go');
|
||||
it(`should not perform an HTTP query if no sample is specified`, () => {
|
||||
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 = {
|
||||
hasCompany: true,
|
||||
code: 'MyReport'
|
||||
};
|
||||
|
||||
controller.clientSample = {
|
||||
clientFk: 101,
|
||||
companyFk: 442
|
||||
clientId: 101,
|
||||
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,
|
||||
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
|
||||
};
|
||||
const serializedParams = $httpParamSerializer(params);
|
||||
|
||||
$httpBackend.when('GET', `email/MyReport?${serializedParams}`).respond(true);
|
||||
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`);
|
||||
controller.sendSample();
|
||||
const serializedParams = $httpParamSerializer(controller.clientSample);
|
||||
$httpBackend.expect('GET', `email/MyReport?${serializedParams}`).respond(true);
|
||||
controller.send(false, () => {});
|
||||
$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');
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
Choose a sample: Selecciona una plantilla
|
||||
Choose a company: Selecciona una empresa
|
||||
Recipient: Destinatario
|
||||
Email cannot be blank: Debes introducir un email
|
||||
Recipient: Destinatario
|
||||
Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla
|
|
@ -13,20 +13,27 @@
|
|||
rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal >
|
||||
<vn-horizontal>
|
||||
<vn-textarea vn-one
|
||||
vn-id="message"
|
||||
label="Message"
|
||||
ng-model="$ctrl.sms.message"
|
||||
info="Special characters like accents counts as a multiple"
|
||||
rows="5"
|
||||
maxlength="160"
|
||||
required="true"
|
||||
rule>
|
||||
</vn-textarea>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<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>
|
||||
</vn-horizontal>
|
||||
</section>
|
||||
|
|
|
@ -17,13 +17,12 @@ class Controller extends Component {
|
|||
}
|
||||
|
||||
charactersRemaining() {
|
||||
let elementMaxLength;
|
||||
let textAreaLength;
|
||||
const element = this.$scope.message;
|
||||
const value = element.input.value;
|
||||
|
||||
textAreaLength = element.input.textLength;
|
||||
elementMaxLength = element.maxlength;
|
||||
return elementMaxLength - textAreaLength;
|
||||
const maxLength = 160;
|
||||
const textAreaLength = new Blob([value]).size;
|
||||
return maxLength - textAreaLength;
|
||||
}
|
||||
|
||||
onResponse(response) {
|
||||
|
@ -33,6 +32,8 @@ class Controller extends Component {
|
|||
throw new Error(`The destination can't be empty`);
|
||||
if (!this.sms.message)
|
||||
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.vnApp.showMessage(this.$translate.instant('SMS sent!'));
|
||||
|
|
|
@ -15,6 +15,11 @@ describe('Client', () => {
|
|||
controller = $componentController('vnClientSms', {$element, $scope});
|
||||
controller.client = {id: 101};
|
||||
controller.$params = {id: 101};
|
||||
controller.$scope.message = {
|
||||
input: {
|
||||
value: 'My SMS'
|
||||
}
|
||||
};
|
||||
}));
|
||||
|
||||
describe('onResponse()', () => {
|
||||
|
@ -56,14 +61,13 @@ describe('Client', () => {
|
|||
it('should return the characters remaining in a element', () => {
|
||||
controller.$scope.message = {
|
||||
input: {
|
||||
textLength: 50
|
||||
},
|
||||
maxlength: 150
|
||||
value: 'My message 0€'
|
||||
}
|
||||
};
|
||||
|
||||
let result = controller.charactersRemaining();
|
||||
|
||||
expect(result).toEqual(100);
|
||||
expect(result).toEqual(145);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,4 +4,6 @@ Message: Mensaje
|
|||
SMS sent!: ¡SMS enviado!
|
||||
Characters remaining: Carácteres restantes
|
||||
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
|
|
@ -127,6 +127,7 @@ module.exports = Self => {
|
|||
e.companyFk,
|
||||
e.gestDocFk,
|
||||
e.invoiceInFk,
|
||||
t.landed,
|
||||
s.name AS supplierName,
|
||||
co.code AS companyCode,
|
||||
cu.code AS currencyCode
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('Entry filter()', () => {
|
|||
|
||||
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() => {
|
||||
|
@ -35,7 +35,7 @@ describe('Entry filter()', () => {
|
|||
|
||||
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() => {
|
||||
|
@ -47,7 +47,7 @@ describe('Entry filter()', () => {
|
|||
|
||||
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() => {
|
||||
|
|
|
@ -16,45 +16,68 @@
|
|||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th></vn-th>
|
||||
<vn-th field="id" number>Id</vn-th>
|
||||
<vn-th field="created" center>Created</vn-th>
|
||||
<vn-th field="travelFk">Travel</vn-th>
|
||||
<vn-th>Notes</vn-th>
|
||||
<vn-th field="landed" center>Landed</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="currencyFk" center>Currency</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-thead>
|
||||
<vn-tbody>
|
||||
<a ng-repeat="entry in entries"
|
||||
class="clickable vn-tr search-result"
|
||||
ui-sref="entry.card.summary({id: {{::entry.id}}})">
|
||||
<vn-td>
|
||||
<vn-icon
|
||||
ng-show="entry.isInventory"
|
||||
class="bright"
|
||||
vn-tooltip="Inventory entry"
|
||||
icon="icon-unavailable">
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
ng-show="entry.isRaid"
|
||||
class="bright"
|
||||
vn-tooltip="Virtual entry"
|
||||
icon="icon-100">
|
||||
</vn-icon>
|
||||
</vn-td>
|
||||
<vn-td number>{{::entry.id}}</vn-td>
|
||||
<vn-td center>{{::entry.created | date:'dd/MM/yyyy'}}</vn-td>
|
||||
<vn-td center expand>{{::entry.travelFk}}</vn-td>
|
||||
<vn-td expand>{{::entry.notes}}</vn-td>
|
||||
<vn-td center>
|
||||
<span
|
||||
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 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 center expand>{{::entry.currencyCode}}</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>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-data-viewer>
|
||||
</vn-card>
|
||||
<vn-travel-descriptor-popover
|
||||
vn-id="travelDescriptor"
|
||||
travel-id="$ctrl.selectedTravel">
|
||||
</vn-travel-descriptor-popover>
|
||||
<vn-popup vn-id="summary">
|
||||
<vn-entry-summary
|
||||
entry="$ctrl.entrySelected">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
import './style.scss';
|
||||
export default class Controller {
|
||||
constructor($scope) {
|
||||
this.$ = $scope;
|
||||
|
@ -11,6 +11,16 @@ export default class Controller {
|
|||
else
|
||||
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'];
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
@import "variables";
|
||||
|
||||
vn-icon[icon=insert_drive_file]{
|
||||
color: $color-font-secondary;
|
||||
}
|
|
@ -1,15 +1,3 @@
|
|||
#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
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"module": "entry",
|
||||
"name": "Entries",
|
||||
"icon": "icon-entry",
|
||||
"dependencies": ["travel"],
|
||||
"validations": true,
|
||||
"menus": {
|
||||
"main": [
|
||||
|
|
|
@ -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
|
||||
});
|
||||
};
|
||||
};
|
|
@ -22,6 +22,7 @@ module.exports = Self => {
|
|||
let where = filter.where;
|
||||
let query = `CALL vn.itemLastEntries(?, ?)`;
|
||||
let [lastEntries] = await Self.rawSql(query, [where.itemFk, where.date]);
|
||||
|
||||
return lastEntries;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -62,6 +62,9 @@
|
|||
"TaxClass": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"TaxClassCode": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"TaxCode": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = Self => {
|
|||
require('../methods/item/getVisibleAvailable')(Self);
|
||||
require('../methods/item/new')(Self);
|
||||
require('../methods/item/getWasteDetail')(Self);
|
||||
require('../methods/item/createIntrastat')(Self);
|
||||
|
||||
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"name": "Item",
|
||||
"base": "Loggable",
|
||||
"log": {
|
||||
"model":"ItemLog",
|
||||
"base": "Loggable",
|
||||
"log": {
|
||||
"model": "ItemLog",
|
||||
"showField": "id"
|
||||
},
|
||||
},
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "item"
|
||||
"table": "item"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
@ -125,55 +125,55 @@
|
|||
}
|
||||
},
|
||||
"relations": {
|
||||
"itemType": {
|
||||
"type": "belongsTo",
|
||||
"model": "ItemType",
|
||||
"foreignKey": "typeFk"
|
||||
},
|
||||
"ink": {
|
||||
"type": "belongsTo",
|
||||
"model": "Ink",
|
||||
"foreignKey": "inkFk"
|
||||
},
|
||||
"origin": {
|
||||
"type": "belongsTo",
|
||||
"model": "Origin",
|
||||
"foreignKey": "originFk"
|
||||
},
|
||||
"producer": {
|
||||
"type": "belongsTo",
|
||||
"model": "Producer",
|
||||
"foreignKey": "producerFk"
|
||||
},
|
||||
"intrastat": {
|
||||
"type": "belongsTo",
|
||||
"model": "Intrastat",
|
||||
"foreignKey": "intrastatFk"
|
||||
},
|
||||
"expense": {
|
||||
"type": "belongsTo",
|
||||
"model": "Expense",
|
||||
"foreignKey": "expenseFk"
|
||||
},
|
||||
"tags": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemTag",
|
||||
"foreignKey": "itemFk"
|
||||
},
|
||||
"itemBarcode": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemBarcode",
|
||||
"foreignKey": "itemFk"
|
||||
},
|
||||
"taxes": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemTaxCountry",
|
||||
"foreignKey": "itemFk"
|
||||
},
|
||||
"itemNiche": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemNiche",
|
||||
"foreignKey": "itemFk"
|
||||
}
|
||||
"itemType": {
|
||||
"type": "belongsTo",
|
||||
"model": "ItemType",
|
||||
"foreignKey": "typeFk"
|
||||
},
|
||||
"ink": {
|
||||
"type": "belongsTo",
|
||||
"model": "Ink",
|
||||
"foreignKey": "inkFk"
|
||||
},
|
||||
"origin": {
|
||||
"type": "belongsTo",
|
||||
"model": "Origin",
|
||||
"foreignKey": "originFk"
|
||||
},
|
||||
"producer": {
|
||||
"type": "belongsTo",
|
||||
"model": "Producer",
|
||||
"foreignKey": "producerFk"
|
||||
},
|
||||
"intrastat": {
|
||||
"type": "belongsTo",
|
||||
"model": "Intrastat",
|
||||
"foreignKey": "intrastatFk"
|
||||
},
|
||||
"expense": {
|
||||
"type": "belongsTo",
|
||||
"model": "Expense",
|
||||
"foreignKey": "expenseFk"
|
||||
},
|
||||
"tags": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemTag",
|
||||
"foreignKey": "itemFk"
|
||||
},
|
||||
"itemBarcode": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemBarcode",
|
||||
"foreignKey": "itemFk"
|
||||
},
|
||||
"taxes": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemTaxCountry",
|
||||
"foreignKey": "itemFk"
|
||||
},
|
||||
"itemNiche": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemNiche",
|
||||
"foreignKey": "itemFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"`;
|
|
@ -55,6 +55,13 @@
|
|||
<div style="width: 6em; text-align: right; padding-right: 1em;">{{::id}}</div>
|
||||
<div>{{::description}}</div>
|
||||
</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-one
|
||||
url="Expenses"
|
||||
|
@ -69,7 +76,7 @@
|
|||
value-field="id"
|
||||
ng-model="$ctrl.item.originFk"
|
||||
initial-data="$ctrl.item.origin">
|
||||
</vn-autocomplete>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
|
@ -123,7 +130,7 @@
|
|||
</vn-check>
|
||||
<vn-textarea
|
||||
vn-one
|
||||
label="description"
|
||||
label="Description"
|
||||
ng-model="$ctrl.item.description"
|
||||
rule>
|
||||
</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-bar>
|
||||
</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>
|
|
@ -1,20 +1,23 @@
|
|||
import ngModule from '../module';
|
||||
import Component from 'core/lib/component';
|
||||
class Controller extends Component {
|
||||
showIntrastat(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
event.preventDefault();
|
||||
|
||||
class Controller {
|
||||
constructor($scope, $timeout) {
|
||||
this.$scope = $scope;
|
||||
this.$timeout = $timeout;
|
||||
this.newIntrastat = {
|
||||
taxClassFk: this.item.taxClassFk
|
||||
};
|
||||
this.$.intrastat.show();
|
||||
}
|
||||
|
||||
$onChanges(data) {
|
||||
this.$timeout(() => {
|
||||
this.$scope.watcher.data = data.item.currentValue;
|
||||
});
|
||||
onIntrastatAccept() {
|
||||
const query = `Items/${this.$params.id}/createIntrastat`;
|
||||
return this.$http.patch(query, this.newIntrastat)
|
||||
.then(res => this.item.intrastatFk = res.data.id);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$scope', '$timeout'];
|
||||
|
||||
ngModule.component('vnItemBasicData', {
|
||||
template: require('./index.html'),
|
||||
bindings: {
|
||||
|
|
|
@ -2,26 +2,31 @@ import './index.js';
|
|||
|
||||
describe('vnItemBasicData', () => {
|
||||
describe('Component vnItemBasicData', () => {
|
||||
let $httpBackend;
|
||||
let $scope;
|
||||
let controller;
|
||||
let $timeout;
|
||||
let $element;
|
||||
|
||||
beforeEach(ngModule('item'));
|
||||
|
||||
beforeEach(angular.mock.inject(($componentController, $rootScope, _$timeout_) => {
|
||||
$timeout = _$timeout_;
|
||||
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$scope = $rootScope.$new();
|
||||
controller = $componentController('vnItemBasicData', {$scope, $timeout});
|
||||
controller.$scope.watcher = {};
|
||||
$element = angular.element('<vn-item-basic-data></vn-item-basic-data>');
|
||||
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', () => {
|
||||
const data = {item: {currentValue: 'the current value of an item'}};
|
||||
controller.$onChanges(data);
|
||||
$timeout.flush();
|
||||
const newIntrastatId = 20;
|
||||
$httpBackend.expect('PATCH', 'Items/1/createIntrastat').respond({id: 20});
|
||||
controller.onIntrastatAccept();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$scope.watcher.data).toMatchSnapshot();
|
||||
expect(controller.item.intrastatFk).toEqual(newIntrastatId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,4 +5,6 @@ Full name calculates based on tags 1-3. Is not recommended to change it manually
|
|||
No se recomienda cambiarlo manualmente
|
||||
Is active: Activo
|
||||
Expense: Gasto
|
||||
Price in kg: Precio en kg
|
||||
Price in kg: Precio en kg
|
||||
New intrastat: Nuevo intrastat
|
||||
Identifier: Identificador
|
|
@ -162,6 +162,7 @@ module.exports = Self => {
|
|||
`SELECT
|
||||
it.tagFk,
|
||||
it.itemFk,
|
||||
it.value,
|
||||
t.name
|
||||
FROM tmp.ticketCalculateItem tci
|
||||
JOIN vn.itemTag it ON it.itemFk = tci.itemFk
|
||||
|
|
|
@ -85,12 +85,15 @@
|
|||
<vn-icon icon="icon-item"></vn-icon>
|
||||
</prepend>
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
|
||||
<vn-datalist vn-one
|
||||
vn-id="search"
|
||||
data="$ctrl.tagValues"
|
||||
ng-model="$ctrl.value"
|
||||
ng-keyUp="$ctrl.onSearchByTag($event)"
|
||||
label="Search tag"
|
||||
ng-model="$ctrl.value">
|
||||
show-field="value"
|
||||
value-field="value"
|
||||
label="Search tag">
|
||||
<prepend>
|
||||
<vn-icon icon="search"></vn-icon>
|
||||
</prepend>
|
||||
|
@ -101,7 +104,7 @@
|
|||
style="cursor: pointer;">
|
||||
</vn-icon>
|
||||
</append>
|
||||
</vn-textfield>
|
||||
</vn-datalist>
|
||||
</vn-vertical>
|
||||
<vn-popover
|
||||
vn-id="popover"
|
||||
|
|
|
@ -19,7 +19,7 @@ class Controller {
|
|||
];
|
||||
this.defaultOrderFields = [
|
||||
{field: 'relevancy DESC, name', name: 'Relevancy'},
|
||||
{field: 'showOrder, price', name: 'Color'},
|
||||
{field: 'showOrder, price', name: 'Color and price'},
|
||||
{field: 'name', name: 'Name'},
|
||||
{field: 'price', name: 'Price'}
|
||||
];
|
||||
|
@ -69,37 +69,8 @@ class Controller {
|
|||
|
||||
if (!value) return;
|
||||
|
||||
const newFilterList = [];
|
||||
value.forEach(item => {
|
||||
// Add new tag filters
|
||||
item.tags.forEach(itemTag => {
|
||||
const alreadyAdded = newFilterList.findIndex(filter => {
|
||||
return filter.field == itemTag.tagFk;
|
||||
});
|
||||
|
||||
if (alreadyAdded == -1) {
|
||||
newFilterList.push({
|
||||
name: itemTag.name,
|
||||
field: itemTag.tagFk,
|
||||
isTag: true
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add default filters - Replaces tags with same name
|
||||
this.defaultOrderFields.forEach(orderField => {
|
||||
const index = newFilterList.findIndex(newfield => {
|
||||
return newfield.name == orderField.name;
|
||||
});
|
||||
|
||||
if (index > -1)
|
||||
newFilterList[index] = orderField;
|
||||
else
|
||||
newFilterList.push(orderField);
|
||||
});
|
||||
|
||||
this.orderFields = newFilterList;
|
||||
this.buildTagsFilter(value);
|
||||
this.buildOrderFilter(value);
|
||||
}
|
||||
|
||||
get categoryId() {
|
||||
|
@ -273,6 +244,43 @@ class Controller {
|
|||
|
||||
this.$state.go(this.$state.current.name, params);
|
||||
}
|
||||
|
||||
buildTagsFilter(items) {
|
||||
const tagValues = [];
|
||||
items.forEach(item => {
|
||||
item.tags.forEach(itemTag => {
|
||||
const alreadyAdded = tagValues.findIndex(tag => {
|
||||
return tag.value == itemTag.value;
|
||||
});
|
||||
|
||||
if (alreadyAdded == -1)
|
||||
tagValues.push(itemTag);
|
||||
});
|
||||
});
|
||||
this.tagValues = tagValues;
|
||||
}
|
||||
|
||||
buildOrderFilter(items) {
|
||||
const tags = [];
|
||||
items.forEach(item => {
|
||||
item.tags.forEach(itemTag => {
|
||||
const alreadyAdded = tags.findIndex(tag => {
|
||||
return tag.field == itemTag.tagFk;
|
||||
});
|
||||
|
||||
if (alreadyAdded == -1) {
|
||||
tags.push({
|
||||
name: itemTag.name,
|
||||
field: itemTag.tagFk,
|
||||
isTag: true
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
let newFilterList = [].concat(this.defaultOrderFields);
|
||||
newFilterList = newFilterList.concat(tags);
|
||||
this.orderFields = newFilterList;
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$http', '$scope', '$state', '$compile', '$transitions'];
|
||||
|
|
|
@ -37,16 +37,19 @@ describe('Order', () => {
|
|||
|
||||
describe('items() setter', () => {
|
||||
it(`should return an object with order params`, () => {
|
||||
let expectedResult = [{field: 'showOrder, price', name: 'Color'}];
|
||||
let unexpectedResult = [{tagFk: 5, name: 'Color'}];
|
||||
controller.items = [{id: 1, name: 'My Item', tags: [
|
||||
spyOn(controller, 'buildTagsFilter');
|
||||
spyOn(controller, 'buildOrderFilter').and.callThrough();
|
||||
const expectedResult = [{field: 'showOrder, price', name: 'Color and price'}];
|
||||
const items = [{id: 1, name: 'My Item', tags: [
|
||||
{tagFk: 4, name: 'Length'},
|
||||
{tagFk: 5, name: 'Color'}
|
||||
]}];
|
||||
controller.items = items;
|
||||
|
||||
expect(controller.orderFields.length).toEqual(5);
|
||||
expect(controller.orderFields.length).toEqual(6);
|
||||
expect(controller.orderFields).toEqual(jasmine.arrayContaining(expectedResult));
|
||||
expect(controller.orderFields).not.toEqual(jasmine.arrayContaining(unexpectedResult));
|
||||
expect(controller.buildTagsFilter).toHaveBeenCalledWith(items);
|
||||
expect(controller.buildOrderFilter).toHaveBeenCalledWith(items);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -222,6 +225,48 @@ describe('Order', () => {
|
|||
expect(controller.$.model.addFilter).toHaveBeenCalledWith(null, expectedOrder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildTagsFilter()', () => {
|
||||
it(`should create an array of non repeated tag values and then set the tagValues property`, () => {
|
||||
const items = [
|
||||
{
|
||||
id: 1, name: 'My Item 1', tags: [
|
||||
{tagFk: 4, name: 'Length', value: 1},
|
||||
{tagFk: 5, name: 'Color', value: 'red'}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2, name: 'My Item 2', tags: [
|
||||
{tagFk: 4, name: 'Length', value: 1},
|
||||
{tagFk: 5, name: 'Color', value: 'blue'}
|
||||
]
|
||||
}];
|
||||
controller.buildTagsFilter(items);
|
||||
|
||||
expect(controller.tagValues.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildOrderFilter()', () => {
|
||||
it(`should create an array of non repeated tags plus default filters and then set the orderFields property`, () => {
|
||||
const items = [
|
||||
{
|
||||
id: 1, name: 'My Item 1', tags: [
|
||||
{tagFk: 4, name: 'Length'},
|
||||
{tagFk: 5, name: 'Color'}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2, name: 'My Item 2', tags: [
|
||||
{tagFk: 5, name: 'Color'},
|
||||
{tagFk: 6, name: 'Relevancy'}
|
||||
]
|
||||
}];
|
||||
controller.buildOrderFilter(items);
|
||||
|
||||
expect(controller.orderFields.length).toEqual(7);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ Item id: Id de artículo
|
|||
Order by: Ordenar por
|
||||
Order: Orden
|
||||
Price: Precio
|
||||
Color and price: Color y precio
|
||||
Ascendant: Ascendente
|
||||
Descendant: Descendente
|
||||
Created from: Creado desde
|
||||
|
|
|
@ -5,4 +5,21 @@ module.exports = Self => {
|
|||
require('../methods/route/guessPriority')(Self);
|
||||
require('../methods/route/updateVolume')(Self);
|
||||
require('../methods/route/getDeliveryPoint')(Self);
|
||||
|
||||
Self.validate('kmStart', validateDistance, {
|
||||
message: 'Distance must be lesser than 1000'
|
||||
});
|
||||
|
||||
Self.validate('kmEnd', validateDistance, {
|
||||
message: 'Distance must be lesser than 1000'
|
||||
});
|
||||
|
||||
function validateDistance(err) {
|
||||
const routeTotalKm = this.kmEnd - this.kmStart;
|
||||
const routeMaxKm = 1000;
|
||||
if ( routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
|
||||
err();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
class Controller {
|
||||
constructor($scope) {
|
||||
constructor($scope, vnApp, $translate) {
|
||||
this.$ = $scope;
|
||||
this.vnApp = vnApp;
|
||||
this.$translate = $translate;
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
|
@ -11,7 +13,7 @@ class Controller {
|
|||
});
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$scope'];
|
||||
Controller.$inject = ['$scope', 'vnApp', '$translate'];
|
||||
|
||||
ngModule.component('vnRouteBasicData', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -2,4 +2,4 @@ Date finished: Fecha fin
|
|||
Date started: Fecha inicio
|
||||
Km start: Km de inicio
|
||||
Km end: Km de fin
|
||||
Description: Descripción
|
||||
Description: Descripción
|
||||
|
|
|
@ -14,7 +14,7 @@ class Controller {
|
|||
|
||||
$onInit() {
|
||||
if (this.$stateParams && this.$stateParams.clientFk)
|
||||
this.clientId = this.$stateParams.clientFk;
|
||||
this.clientId = parseInt(this.$stateParams.clientFk);
|
||||
|
||||
this.warehouseId = this.vnConfig.warehouseFk;
|
||||
}
|
||||
|
|
|
@ -18,15 +18,22 @@
|
|||
vn-id="message"
|
||||
label="Message"
|
||||
ng-model="$ctrl.sms.message"
|
||||
info="Special characters like accents counts as a multiple"
|
||||
rows="5"
|
||||
maxlength="160"
|
||||
required="true"
|
||||
rule>
|
||||
</vn-textarea>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<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>
|
||||
</vn-horizontal>
|
||||
</section>
|
||||
|
|
|
@ -17,13 +17,12 @@ class Controller extends Component {
|
|||
}
|
||||
|
||||
charactersRemaining() {
|
||||
let elementMaxLength;
|
||||
let textAreaLength;
|
||||
const element = this.$scope.message;
|
||||
const value = element.input.value;
|
||||
|
||||
textAreaLength = element.input.textLength;
|
||||
elementMaxLength = element.maxlength;
|
||||
return elementMaxLength - textAreaLength;
|
||||
const maxLength = 160;
|
||||
const textAreaLength = new Blob([value]).size;
|
||||
return maxLength - textAreaLength;
|
||||
}
|
||||
|
||||
onResponse(response) {
|
||||
|
@ -33,6 +32,8 @@ class Controller extends Component {
|
|||
throw new Error(`The destination can't be empty`);
|
||||
if (!this.sms.message)
|
||||
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(`Tickets/${this.$params.id}/sendSms`, this.sms).then(res => {
|
||||
this.vnApp.showMessage(this.$translate.instant('SMS sent!'));
|
||||
|
|
|
@ -14,6 +14,11 @@ describe('Ticket', () => {
|
|||
$element = angular.element('<vn-dialog></vn-dialog>');
|
||||
controller = $componentController('vnTicketSms', {$element, $scope});
|
||||
controller.$params = {id: 11};
|
||||
controller.$scope.message = {
|
||||
input: {
|
||||
value: 'My SMS'
|
||||
}
|
||||
};
|
||||
}));
|
||||
|
||||
describe('onResponse()', () => {
|
||||
|
@ -55,14 +60,13 @@ describe('Ticket', () => {
|
|||
it('should return the characters remaining in a element', () => {
|
||||
controller.$scope.message = {
|
||||
input: {
|
||||
textLength: 50
|
||||
},
|
||||
maxlength: 150
|
||||
value: 'My message 0€'
|
||||
}
|
||||
};
|
||||
|
||||
let result = controller.charactersRemaining();
|
||||
|
||||
expect(result).toEqual(100);
|
||||
expect(result).toEqual(145);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,4 +4,6 @@ Message: Mensaje
|
|||
SMS sent!: ¡SMS enviado!
|
||||
Characters remaining: Carácteres restantes
|
||||
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
|
|
@ -23,7 +23,7 @@ describe('Travel filter()', () => {
|
|||
|
||||
let result = await app.models.Travel.filter(ctx);
|
||||
|
||||
expect(result.length).toEqual(7);
|
||||
expect(result.length).toEqual(8);
|
||||
});
|
||||
|
||||
it('should return the travel matching "total entries"', async() => {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<vn-popover vn-id="popover">
|
||||
<vn-spinner
|
||||
ng-if="$ctrl.travel == null"
|
||||
style="padding: 1em;"
|
||||
enable="true">
|
||||
</vn-spinner>
|
||||
<vn-travel-descriptor
|
||||
ng-if="$ctrl.travel"
|
||||
travel="$ctrl.travel"
|
||||
quicklinks="$ctrl.quicklinks">
|
||||
</vn-travel-descriptor>
|
||||
</vn-popover>
|
|
@ -0,0 +1,88 @@
|
|||
import ngModule from '../module';
|
||||
import Component from 'core/lib/component';
|
||||
|
||||
class Controller extends Component {
|
||||
constructor($element, $scope, $http, $timeout, $q) {
|
||||
super($element, $scope);
|
||||
this.$timeout = $timeout;
|
||||
this.$http = $http;
|
||||
this.$q = $q;
|
||||
this.travel = null;
|
||||
this._quicklinks = {};
|
||||
}
|
||||
|
||||
set travelId(travelId) {
|
||||
if (travelId == this._travelId) return;
|
||||
|
||||
this._travelId = travelId;
|
||||
this.travel = null;
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
get travelId() {
|
||||
return this._travelId;
|
||||
}
|
||||
|
||||
get quicklinks() {
|
||||
return this._quicklinks;
|
||||
}
|
||||
|
||||
set quicklinks(value = {}) {
|
||||
Object.keys(value).forEach(key => {
|
||||
this._quicklinks[key] = value[key];
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$.popover.parent = this.parent;
|
||||
this.$.popover.show();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
let query = `Travels/findOne`;
|
||||
let filter = {
|
||||
fields: [
|
||||
'id',
|
||||
'ref',
|
||||
'shipped',
|
||||
'landed',
|
||||
'totalEntries',
|
||||
'warehouseInFk',
|
||||
'warehouseOutFk'
|
||||
],
|
||||
where: {
|
||||
id: this._travelId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
relation: 'warehouseIn',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}, {
|
||||
relation: 'warehouseOut',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.$http.get(query, {params: {filter}}).then(res => {
|
||||
this.travel = res.data;
|
||||
this.$.$applyAsync(() => {
|
||||
this.$.popover.relocate();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$element', '$scope', '$http', '$timeout', '$q'];
|
||||
|
||||
ngModule.component('vnTravelDescriptorPopover', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
travelId: '<',
|
||||
quicklinks: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
import './index.js';
|
||||
|
||||
describe('travel Component vnTravelDescriptorPopover', () => {
|
||||
let $httpBackend;
|
||||
let $httpParamSerializer;
|
||||
let $scope;
|
||||
let controller;
|
||||
let $element;
|
||||
|
||||
beforeEach(ngModule('travel'));
|
||||
|
||||
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpParamSerializer = _$httpParamSerializer_;
|
||||
$element = angular.element(`<div></div>`);
|
||||
$scope = $rootScope.$new();
|
||||
$scope.popover = {relocate: () => {}, show: () => {}};
|
||||
controller = $componentController('vnTravelDescriptorPopover', {$scope, $element});
|
||||
}));
|
||||
|
||||
describe('travelId()', () => {
|
||||
it(`should not apply any changes if the received id is the same stored in _travelId`, () => {
|
||||
controller.travel = 'I exist!';
|
||||
controller._travelId = 1;
|
||||
spyOn(controller, 'loadData');
|
||||
controller.travelId = 1;
|
||||
|
||||
expect(controller.travel).toEqual('I exist!');
|
||||
expect(controller._travelId).toEqual(1);
|
||||
expect(controller.loadData).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`should set the received id into _travelId, set the travel to null and then call loadData()`, () => {
|
||||
controller.travel = `Please don't`;
|
||||
controller._travelId = 1;
|
||||
spyOn(controller, 'loadData');
|
||||
controller.travelId = 999;
|
||||
|
||||
expect(controller.travel).toBeNull();
|
||||
expect(controller._travelId).toEqual(999);
|
||||
expect(controller.loadData).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('show()', () => {
|
||||
it(`should call the show()`, () => {
|
||||
spyOn(controller.$.popover, 'show');
|
||||
controller.show();
|
||||
|
||||
expect(controller.$.popover.show).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadData()', () => {
|
||||
it(`should perform a get query to store the worker data into the controller`, () => {
|
||||
controller.travelId = 1;
|
||||
controller.canceler = null;
|
||||
let response = {};
|
||||
|
||||
let config = {
|
||||
filter: {
|
||||
fields: [
|
||||
'id',
|
||||
'ref',
|
||||
'shipped',
|
||||
'landed',
|
||||
'totalEntries',
|
||||
'warehouseInFk',
|
||||
'warehouseOutFk'
|
||||
],
|
||||
where: {
|
||||
id: controller.travelId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
relation: 'warehouseIn',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}, {
|
||||
relation: 'warehouseOut',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let json = $httpParamSerializer(config);
|
||||
|
||||
$httpBackend.whenGET(`Travels/findOne?${json}`).respond(response);
|
||||
$httpBackend.expectGET(`Travels/findOne?${json}`);
|
||||
controller.loadData();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.travel).toEqual(response);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -11,4 +11,4 @@ import './log';
|
|||
import './create';
|
||||
import './thermograph/index/';
|
||||
import './thermograph/create/';
|
||||
|
||||
import './descriptor-popover';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import ngModule from '../module';
|
||||
import Component from 'core/lib/component';
|
||||
import './style.scss';
|
||||
|
||||
class Controller extends Component {
|
||||
constructor($element, $scope, $http, $timeout, $q) {
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
vn-ticket-descriptor-popover {
|
||||
vn-ticket-descriptor {
|
||||
display: block;
|
||||
width: 16em;
|
||||
max-height: 28em;
|
||||
|
||||
& > vn-card {
|
||||
margin: 0!important;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue