Merge branch 'dev' into test
gitea/salix/test This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-03-05 11:12:44 +01:00
commit e7ca55fea3
157 changed files with 3846 additions and 2664 deletions

View File

@ -26,7 +26,7 @@ module.exports = Self => {
}); });
Self.send = async(ctx, data, transaction) => { Self.send = async(ctx, data, transaction) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.options && ctx.options.accessToken.userId || ctx.req && ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const sender = await models.Account.findById(userId, transaction); const sender = await models.Account.findById(userId, transaction);
const recipient = await models.Account.findById(data.recipientFk, transaction); const recipient = await models.Account.findById(data.recipientFk, transaction);

View File

@ -55,13 +55,15 @@
"principalType": "ROLE", "principalType": "ROLE",
"principalId": "$everyone", "principalId": "$everyone",
"permission": "ALLOW" "permission": "ALLOW"
}, { },
{
"property": "logout", "property": "logout",
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",
"principalId": "$authenticated", "principalId": "$authenticated",
"permission": "ALLOW" "permission": "ALLOW"
}, { },
{
"property": "validateToken", "property": "validateToken",
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",

7
db/connect.tpl.ini Normal file
View File

@ -0,0 +1,7 @@
[client]
enable_cleartext_plugin = ON
host = localhost
port = 3306
user = root
password = password
ssl-mode = DISABLED

View File

@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
#IMPORTANT Any changes in this file are to applyed to mirror file export-data.cmd
echo "USE \`account\`;" > install/dump/dumpedFixtures.sql echo "USE \`account\`;" > install/dump/dumpedFixtures.sql
mysqldump --defaults-file=connect.ini --no-create-info account role roleRole roleInherit >> install/dump/dumpedFixtures.sql mysqldump --defaults-file=connect.ini --no-create-info account role roleRole roleInherit >> install/dump/dumpedFixtures.sql
echo "USE \`salix\`;" >> install/dump/dumpedFixtures.sql echo "USE \`salix\`;" >> install/dump/dumpedFixtures.sql

View File

@ -10,7 +10,7 @@ echo "[INFO] -> Imported ./dump/mysqlPlugins.sql"
mysql -u root -f < ./dump/mysqlPlugins.sql mysql -u root -f < ./dump/mysqlPlugins.sql
# Import changes # Import changes
for file in changes/*/*.sql; do for file in changes/*.sql; do
echo "[INFO] -> Imported ./$file" echo "[INFO] -> Imported ./$file"
mysql -u root -fc < $file mysql -u root -fc < $file
done done

View File

@ -0,0 +1,29 @@
USE `hedera`;
DROP procedure IF EXISTS `orderGetTotal`;
DELIMITER $$
USE `hedera`$$
CREATE DEFINER=`root`@`%` PROCEDURE `orderGetTotal`()
BEGIN
/**
* Calcula el total con IVA para un conjunto de orders.
*
* @table tmp.order(orderFk) Identificadores de las ordenes a calcular
* @return tmp.orderTotal Total para cada order
*/
CALL orderGetTax;
DROP TEMPORARY TABLE IF EXISTS tmp.orderTotal;
CREATE TEMPORARY TABLE tmp.orderTotal
(INDEX (orderFk))
ENGINE = MEMORY
SELECT o.orderFk, IFNULL(SUM(ot.taxableBase + ot.tax), 0.0) AS total
FROM tmp.order o
LEFT JOIN tmp.orderAmount ot ON o.orderFk = ot.orderFk
GROUP BY orderFk;
DROP TEMPORARY TABLE IF EXISTS tmp.orderTax;
END$$
DELIMITER ;

View File

@ -0,0 +1,36 @@
DROP PROCEDURE IF EXISTS vn.ticketGetVisibleAvailable;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`ticketGetVisibleAvailable`(
vTicket INT)
BEGIN
DECLARE vVisibleCalc INT;
DECLARE vAvailableCalc INT;
DECLARE vShipped DATE;
DECLARE vWarehouse TINYINT;
DECLARE vAlertLevel INT;
SELECT t.warehouseFk, t.shipped, ts.alertLevel INTO vWarehouse, vShipped, vAlertLevel
FROM ticket t
LEFT JOIN ticketState ts ON ts.ticketFk = vTicket
WHERE t.id = vTicket;
IF vAlertLevel IS NULL OR vAlertLevel = 0 THEN
IF vShipped >= CURDATE() THEN
CALL cache.available_refresh(vAvailableCalc, FALSE, vWarehouse, vShipped);
END IF;
IF vShipped = CURDATE() THEN
CALL cache.visible_refresh(vVisibleCalc, FALSE, vWarehouse);
END IF;
END IF;
SELECT s.id, s.itemFk, s.quantity, s.concept, s.price, s.reserved, s.discount, v.visible, av.available, it.image
FROM sale s
LEFT JOIN cache.visible v ON v.item_id = s.itemFk AND v.calc_id = vVisibleCalc
LEFT JOIN cache.available av ON av.item_id = s.itemFk AND av.calc_id = vAvailableCalc
LEFT JOIN item it ON it.id = s.itemFk
WHERE s.ticketFk = vTicket
ORDER BY s.concept;
END$$
DELIMITER ;

View File

@ -0,0 +1,36 @@
USE `hedera`;
DROP FUNCTION IF EXISTS `orderGetTotal`;
DELIMITER $$
USE `hedera`$$
CREATE DEFINER=`root`@`%` FUNCTION `orderGetTotal`(vOrder INT) RETURNS decimal(10,2)
READS SQL DATA
DETERMINISTIC
BEGIN
/**
* Obtiene el total de un pedido con el IVA y el recargo de
* equivalencia incluidos.
*
* @param vOrder El identificador del pedido
* @return El total del pedido
*/
DECLARE vTotal DECIMAL(10,2);
DROP TEMPORARY TABLE IF EXISTS tmp.order;
CREATE TEMPORARY TABLE tmp.order
ENGINE = MEMORY
SELECT vOrder orderFk;
CALL orderGetTotal;
SELECT total INTO vTotal FROM tmp.orderTotal;
DROP TEMPORARY TABLE
tmp.order,
tmp.orderTotal;
RETURN vTotal;
END$$
DELIMITER ;

View File

@ -0,0 +1,65 @@
USE `hedera`;
DROP procedure IF EXISTS `orderGetTax`;
DELIMITER $$
USE `hedera`$$
CREATE DEFINER=`root`@`%` PROCEDURE `orderGetTax`()
READS SQL DATA
BEGIN
/**
* Calcula el IVA, y el recargo de equivalencia de un pedido
* desglosados por tipos.
*
* @param vOrder El identificador del pedido
* @treturn tmp.orderTax Bases imponibles, IVA y recargo de equivalencia
*/
DROP TEMPORARY TABLE IF EXISTS tmp.addressCompany;
CREATE TEMPORARY TABLE tmp.addressCompany
(INDEX (addressFk, companyFk))
ENGINE = MEMORY
SELECT DISTINCT o.address_id addressFk, o.company_id companyFk
FROM tmp.order tmpOrder
JOIN hedera.order o ON o.id = tmpOrder.orderFk;
CALL vn.addressTaxArea ();
-- Calcula el IVA y el recargo desglosado.
DROP TEMPORARY TABLE IF EXISTS tmp.orderTax;
CREATE TEMPORARY TABLE tmp.orderTax
(INDEX (orderFk))
ENGINE = MEMORY
SELECT o.id orderFk,
tc.code,
SUM(m.amount * m.price) taxableBase,
pgc.rate
FROM tmp.order tmpOrder
JOIN `order` o ON o.id = tmpOrder.orderFk
JOIN orderRow m ON m.orderFk = o.id
JOIN vn.item i ON i.id = m.itemFk
JOIN vn.client c ON c.id = o.customer_id
JOIN vn.supplier s ON s.id = o.company_id
JOIN tmp.addressTaxArea ata
ON ata.addressFk = o.address_id AND ata.companyFk = o.company_id
JOIN vn.itemTaxCountry itc
ON itc.itemFk = i.id AND itc.countryFk = s.countryFk
JOIN vn.bookingPlanner bp
ON bp.countryFk = s.countryFk
AND bp.taxAreaFk = ata.areaFk
AND bp.taxClassFk = itc.taxClassFk
JOIN vn.pgc ON pgc.code = bp.pgcFk
JOIN vn.taxClass tc ON tc.id = bp.taxClassFk
GROUP BY tmpOrder.orderFk, pgc.code,pgc.rate
HAVING taxableBase != 0;
DROP TEMPORARY TABLE IF EXISTS tmp.orderAmount;
CREATE TEMPORARY TABLE tmp.orderAmount
(INDEX (orderFk))
ENGINE = MEMORY
SELECT orderFk, taxableBase, SUM(CAST(taxableBase * rate / 100 AS DECIMAL(10, 2))) tax,code
FROM tmp.orderTax
GROUP BY orderFk, code;
END$$
DELIMITER ;

View File

@ -0,0 +1,34 @@
/* Añadir a producción cuando se suba salix */
DROP TRIGGER IF EXISTS vn2008.ConsignatariosAfterUpdate;
USE vn2008;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` TRIGGER `vn2008`.`ConsignatariosAfterUpdate` AFTER UPDATE ON `Consignatarios` FOR EACH ROW
BEGIN
-- Recargos de equivalencia distintos implican facturacion por consignatario
IF NEW.isEqualizated != OLD.isEqualizated THEN
IF
(SELECT COUNT(*) FROM
(
SELECT DISTINCT (isEqualizated = FALSE) as Equ
FROM Consignatarios
WHERE Id_Cliente = NEW.Id_Cliente
) t1
) > 1
THEN
UPDATE Clientes
SET invoiceByAddress = TRUE
WHERE Id_Cliente = NEW.Id_Cliente;
END IF;
END IF;
END$$
DELIMITER ;
USE vn;

View File

@ -0,0 +1,27 @@
/* Añadir a producción cuando se suba salix */
DROP TRIGGER IF EXISTS vn2008.ConsignatariosBeforeInsert;
USE vn2008;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` TRIGGER `vn2008`.`ConsignatariosBeforeInsert`
BEFORE INSERT ON `vn2008`.`Consignatarios`
FOR EACH ROW
BEGIN
DECLARE vIsEqualizated BOOLEAN;
CALL pbx.phoneIsValid (NEW.telefono);
CALL pbx.phoneIsValid (NEW.movil);
IF NEW.isEqualizated IS NULL THEN
SELECT RE
INTO vIsEqualizated
FROM Clientes
WHERE Id_Cliente = NEW.Id_Cliente;
SET NEW.isEqualizated = vIsEqualizated;
END IF;
END$$
DELIMITER ;
USE vn;

View File

@ -0,0 +1,16 @@
/* Añadir a producción cuando se suba salix */
DROP TRIGGER IF EXISTS vn2008.ConsignatariosBeforeUpdate;
USE vn2008;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` TRIGGER `vn2008`.`ConsignatariosBeforeUpdate`
BEFORE UPDATE ON `vn2008`.`Consignatarios`
FOR EACH ROW
BEGIN
CALL pbx.phoneIsValid (NEW.telefono);
CALL pbx.phoneIsValid (NEW.movil);
END$$
DELIMITER ;
USE vn;

View File

@ -0,0 +1,3 @@
/* Añadir a producción cuando se suba salix */
DROP TRIGGER vn2008.ClientesAfterInsert;

View File

@ -0,0 +1,19 @@
/* Añadir a producción cuando se suba salix */
DROP TRIGGER IF EXISTS vn2008.ClientesAfterUpdate;
USE vn2008;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` TRIGGER `vn2008`.`ClientesAfterUpdate`
AFTER UPDATE ON `Clientes`
FOR EACH ROW
BEGIN
IF NEW.default_address AND (NEW.default_address != OLD.default_address) THEN
UPDATE Consignatarios SET predeterminada = FALSE
WHERE Id_cliente = NEW.Id_cliente;
END IF;
END$$
DELIMITER ;
USE vn;

View File

@ -0,0 +1,9 @@
/* Script de migración consignatarios.
Añadir a producción cuando se suba salix */
/* USE vn;
UPDATE vn.client c
JOIN vn.address a ON a.clientFk = c.id AND a.isDefaultAddress
SET c.defaultAddressFk = a.id
WHERE c.defaultAddressFk IS NULL */

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,5 @@
ALTER TABLE `vn`.`itemTaxCountry` AUTO_INCREMENT = 1; ALTER TABLE `vn`.`itemTaxCountry` AUTO_INCREMENT = 1;
ALTER TABLE `vn2008`.`Consignatarios` AUTO_INCREMENT = 1; ALTER TABLE `vn2008`.`Consignatarios` AUTO_INCREMENT = 1;
@ -8,7 +10,7 @@ INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@verdnatura.es') SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@verdnatura.es')
FROM `account`.`role`; FROM `account`.`role`;
INSERT INTO `vn`.`worker`(`id`,`workerCode`, `firstName`, `name`, `userFk`) INSERT INTO `vn2008`.`Trabajadores`(`Id_Trabajador`,`CodigoTrabajador`, `Nombre`, `Apellidos`, `user_id`)
SELECT id,UPPER(LPAD(role, 3, '0')), name, name, id SELECT id,UPPER(LPAD(role, 3, '0')), name, name, id
FROM `vn`.`user`; FROM `vn`.`user`;
@ -29,7 +31,7 @@ INSERT INTO `account`.`user`(`id`,`name`,`password`,`role`,`active`,`email`,`lan
(111, 'Missing', 'ac754a330530832ba1bf7687f577da91', 2 , 0, NULL, 'es'), (111, 'Missing', 'ac754a330530832ba1bf7687f577da91', 2 , 0, NULL, 'es'),
(112, 'Trash', 'ac754a330530832ba1bf7687f577da91', 2 , 0, NULL, 'es'); (112, 'Trash', 'ac754a330530832ba1bf7687f577da91', 2 , 0, NULL, 'es');
INSERT INTO `vn`.`worker`(`workerCode`, `id`, `firstName`, `name`, `userFk`) INSERT INTO `vn2008`.`Trabajadores`(`CodigoTrabajador`, `Id_Trabajador`, `Nombre`, `Apellidos`, `user_id`)
VALUES VALUES
('LGN', 106, 'David Charles', 'Haller', 106), ('LGN', 106, 'David Charles', 'Haller', 106),
('ANT', 107, 'Hank' , 'Pym' , 107), ('ANT', 107, 'Hank' , 'Pym' , 107),
@ -209,38 +211,65 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
( 103, 0, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)), ( 103, 0, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
( 104, -30, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)); ( 104, -30, DATE_ADD(CURDATE(), INTERVAL -1 MONTH));
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `isDefaultAddress`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`) INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
VALUES VALUES
(101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 1),
(102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0), (2, 'Petter Parker', '20 Ingram Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 1),
(103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0), (3, 'Clark Kent', '344 Clinton Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 1),
(104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0), (4, 'Tony Stark', '10880 Malibu Point', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 1),
(105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0), (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 1),
(106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0), (6, 'DavidCharlesHaller', 'Evil hideout', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 1),
(107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0), (7, 'Hank Pym', 'Anthill', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 1),
(108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0), (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 1),
(109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (9, 'Bruce Banner', 'Somewhere in New York', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 1),
(110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 1),
(111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (11, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 200, 2, NULL, NULL, 0, 1),
(112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (12, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 400, 2, NULL, NULL, 0, 1),
(113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(118, 'address 18', 'Somewhere over the rainbow', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0), (108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(121, 'address 21', 'the bat cave', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 101, 2, NULL, NULL, 0), (109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(122, 'address 22', 'NY roofs', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 102, 2, NULL, NULL, 0), (110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(123, 'address 23', 'The phone box', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 103, 2, NULL, NULL, 0), (111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(124, 'address 24', 'Stark tower', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 104, 2, NULL, NULL, 0), (112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 105, 2, NULL, NULL, 0), (113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(126, 'address 26', 'Many places', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 106, 2, NULL, NULL, 0), (114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 107, 2, NULL, NULL, 0), (115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 108, 2, NULL, NULL, 0), (116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 110, 2, NULL, NULL, 0), (117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(130, 'address 30', 'Non valid address', 'Silla', 46460, 1, 1111111111, 222222222, 0, 0, 101, 2, NULL, NULL, 0); (118, 'address 18', 'Somewhere over the rainbow', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(121, 'address 21', 'the bat cave', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 0),
(122, 'address 22', 'NY roofs', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 0),
(123, 'address 23', 'The phone box', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 0),
(124, 'address 24', 'Stark tower', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 0),
(125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 0),
(126, 'address 26', 'Many places', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 0),
(127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 0),
(128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 0),
(129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 0),
(130, 'address 30', 'Non valid address', 'Silla', 46460, 1, 1111111111, 222222222, 0, 101, 2, NULL, NULL, 0, 0),
(131, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 200, 2, NULL, NULL, 0, 0),
(132, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 400, 2, NULL, NULL, 0, 0);
UPDATE `vn`.`client` SET defaultAddressFk = 1 WHERE id = 101;
UPDATE `vn`.`client` SET defaultAddressFk = 2 WHERE id = 102;
UPDATE `vn`.`client` SET defaultAddressFk = 3 WHERE id = 103;
UPDATE `vn`.`client` SET defaultAddressFk = 4 WHERE id = 104;
UPDATE `vn`.`client` SET defaultAddressFk = 5 WHERE id = 105;
UPDATE `vn`.`client` SET defaultAddressFk = 6 WHERE id = 106;
UPDATE `vn`.`client` SET defaultAddressFk = 7 WHERE id = 107;
UPDATE `vn`.`client` SET defaultAddressFk = 8 WHERE id = 108;
UPDATE `vn`.`client` SET defaultAddressFk = 9 WHERE id = 109;
UPDATE `vn`.`client` SET defaultAddressFk = 10 WHERE id = 110;
UPDATE `vn`.`client` SET defaultAddressFk = 11 WHERE id = 200;
UPDATE `vn`.`client` SET defaultAddressFk = 12 WHERE id = 400;
INSERT INTO `vn`.`clientCredit`(`id`, `clientFk`, `workerFk`, `amount`, `created`) INSERT INTO `vn`.`clientCredit`(`id`, `clientFk`, `workerFk`, `amount`, `created`)
VALUES VALUES
@ -547,7 +576,7 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
(5, 1, 2 , 'Object1 Gem1 5', 10, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY)), (5, 1, 2 , 'Object1 Gem1 5', 10, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY)),
(6, 1, 3 , 'Object1 Gem1 5', 15, 6.50, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY)), (6, 1, 3 , 'Object1 Gem1 5', 15, 6.50, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY)),
(7, 2, 11, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE()), (7, 2, 11, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE()),
(8, 4, 11, 'Object4 Armor2 2', 10, 3.26, 0, 0, 0, CURDATE()), (8, 4, 11, 'Object4 Armor2 2', 10, 3.05, 0, 0, 0, CURDATE()),
(9, 1, 16, 'Object1 Gem1 5', 5, 9.10, 0, 0, 0, CURDATE()), (9, 1, 16, 'Object1 Gem1 5', 5, 9.10, 0, 0, 0, CURDATE()),
(10, 2, 16, 'Object2 Gem2 3', 10, 1.07, 0, 0, 0, CURDATE()), (10, 2, 16, 'Object2 Gem2 3', 10, 1.07, 0, 0, 0, CURDATE()),
(11, 1, 16, 'Object1 Gem1 5', 2, 9.10, 0, 0, 0, CURDATE()), (11, 1, 16, 'Object1 Gem1 5', 2, 9.10, 0, 0, 0, CURDATE()),
@ -593,11 +622,11 @@ INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
(5, 29, -18.72), (5, 29, -18.72),
(5, 39, 0.02), (5, 39, 0.02),
(6, 23, 6.5), (6, 23, 6.5),
(7, 15, 0.29), (7, 15, 0.2899),
(7, 28, 5.6), (7, 28, 5.6),
(7, 29, -4.6), (7, 29, -4.6),
(7, 39, 0.01), (7, 39, 0.01),
(8, 15, 0.044), (8, 15, 0.0435),
(8, 21, -0.004), (8, 21, -0.004),
(8, 28, 20.72), (8, 28, 20.72),
(8, 29, -19.72), (8, 29, -19.72),
@ -624,7 +653,7 @@ INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
(12, 29, -19.72), (12, 29, -19.72),
(12, 37, 2), (12, 37, 2),
(12, 39, 0.01), (12, 39, 0.01),
(13, 15, 0.29), (13, 15, 0.2899),
(13, 28, 5.6), (13, 28, 5.6),
(13, 29, -4.6), (13, 29, -4.6),
(13, 39, 0.01), (13, 39, 0.01),
@ -793,14 +822,14 @@ INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseO
(1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 2, 1, 100.00, 1000), (1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 2, 1, 100.00, 1000),
(2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 150, 2000), (2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 150, 2000),
(3, CURDATE(), CURDATE(), 1, 2, 1, 0.00, 0.00), (3, CURDATE(), CURDATE(), 1, 2, 1, 0.00, 0.00),
(4, DATE_ADD(CURDATE(), INTERVAL -30 DAY), DATE_ADD(CURDATE(), INTERVAL -30 DAY), 1, 2, 1, 50.00, 500); (4, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 50.00, 500);
INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `companyFk`,`ref`) INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `companyFk`,`ref`)
VALUES VALUES
( 1, 1, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 1, 442, 'Movimiento 1'), ( 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 442, 'Movimiento 1'),
( 2, 2, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 2, 442, 'Movimiento 2'), ( 2, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 442, 'Movimiento 2'),
( 3, 1, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 3, 442, 'Movimiento 3'), ( 3, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 442, 'Movimiento 3'),
( 4, 2, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 4, 69, 'Movimiento 4'); ( 4, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 69, 'Movimiento 4');
INSERT INTO `vn`.`agencyProvince`(`provinceFk`, `agencyFk`, `zone`, `warehouseFk`) INSERT INTO `vn`.`agencyProvince`(`provinceFk`, `agencyFk`, `zone`, `warehouseFk`)
VALUES VALUES
@ -927,7 +956,7 @@ INSERT INTO `hedera`.`orderRowComponent`(`rowFk`, `componentFk`, `price`)
(5, 29, -18.72), (5, 29, -18.72),
(5, 39, 0.02), (5, 39, 0.02),
(6, 23, 6.5), (6, 23, 6.5),
(7, 15, 0.29), (7, 15, 0.2899),
(7, 28, 5.6), (7, 28, 5.6),
(7, 29, -4.6), (7, 29, -4.6),
(7, 39, 0.01), (7, 39, 0.01),
@ -1126,5 +1155,3 @@ INSERT INTO `postgresql`.`business_labour`(`business_id`, `notes`, `department_i
VALUES VALUES
(1, NULL, 22, 4, 0, 1, 1, 1, 1), (1, NULL, 22, 4, 0, 1, 1, 1, 1),
(2, 'From las friday worker ownes the company 1 hour', 23, 1, 0, 1, 0, 1, 1); (2, 'From las friday worker ownes the company 1 hour', 23, 1, 0, 1, 0, 1, 1);

View File

@ -18,11 +18,11 @@ let jasmine = new Jasmine();
let SpecReporter = require('jasmine-spec-reporter').SpecReporter; let SpecReporter = require('jasmine-spec-reporter').SpecReporter;
let serviceSpecs = [ let serviceSpecs = [
'./db/tests/**/*[sS]pec.js' './tests/**/*[sS]pec.js'
]; ];
jasmine.loadConfig({ jasmine.loadConfig({
spec_dir: 'services', spec_dir: 'db',
spec_files: serviceSpecs, spec_files: serviceSpecs,
helpers: [] helpers: []
}); });

View File

@ -183,6 +183,7 @@ describe('ticket ticketCreateWithUser()', () => {
params.landed, params.landed,
params.userId params.userId
]); ]);
stmts.push(stmt); stmts.push(stmt);
let ticketAddressIndex = stmts.push(`SELECT addressFk FROM vn.ticket WHERE id = @newTicketId`) - 1; let ticketAddressIndex = stmts.push(`SELECT addressFk FROM vn.ticket WHERE id = @newTicketId`) - 1;
@ -191,12 +192,9 @@ describe('ticket ticketCreateWithUser()', () => {
params.clientFk, params.clientFk,
]); ]);
let clientDefaultAddressIndex = stmts.push(stmt) - 1; let clientDefaultAddressIndex = stmts.push(stmt) - 1;
stmts.push('ROLLBACK'); stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';'); let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql); let result = await app.models.Ticket.rawStmt(sql);
let ticketAddress = result[ticketAddressIndex][0]; let ticketAddress = result[ticketAddressIndex][0];
let clientDefaultAddress = result[clientDefaultAddressIndex][0]; let clientDefaultAddress = result[clientDefaultAddressIndex][0];

View File

@ -160,6 +160,16 @@ let actions = {
.catch(done); .catch(done);
}, },
waitToFocus: function(selector, done) {
this.wait(selector)
.evaluate_now(selector => {
let element = document.querySelector(selector);
element.focus();
}, done, selector)
.then(done)
.catch(done);
},
isVisible: function(selector, done) { isVisible: function(selector, done) {
this.wait(selector) this.wait(selector)
.evaluate_now(elementSelector => { .evaluate_now(elementSelector => {
@ -351,6 +361,7 @@ let actions = {
accessToSearchResult: function(searchValue, done) { accessToSearchResult: function(searchValue, done) {
this.write(`vn-searchbar input`, searchValue) this.write(`vn-searchbar input`, searchValue)
.click(`vn-searchbar vn-icon[icon="search"]`) .click(`vn-searchbar vn-icon[icon="search"]`)
.wait(100)
.waitForNumberOfElements('.searchResult', 1) .waitForNumberOfElements('.searchResult', 1)
.evaluate(() => { .evaluate(() => {
return document.querySelector('ui-view vn-card vn-table') != null; return document.querySelector('ui-view vn-card vn-table') != null;

View File

@ -31,6 +31,8 @@ export default {
name: `${components.vnTextfield}[name="name"]`, name: `${components.vnTextfield}[name="name"]`,
taxNumber: `${components.vnTextfield}[name="fi"]`, taxNumber: `${components.vnTextfield}[name="fi"]`,
socialName: `${components.vnTextfield}[name="socialName"]`, socialName: `${components.vnTextfield}[name="socialName"]`,
street: `${components.vnTextfield}[name="street"]`,
city: `${components.vnTextfield}[name="city"]`,
userName: `${components.vnTextfield}[name="userName"]`, userName: `${components.vnTextfield}[name="userName"]`,
email: `${components.vnTextfield}[name="email"]`, email: `${components.vnTextfield}[name="email"]`,
salesPersonAutocomplete: `vn-autocomplete[field="$ctrl.client.salesPersonFk"]`, salesPersonAutocomplete: `vn-autocomplete[field="$ctrl.client.salesPersonFk"]`,
@ -149,6 +151,7 @@ export default {
}, },
clientLog: { clientLog: {
logButton: `vn-left-menu a[ui-sref="client.card.log"]`, logButton: `vn-left-menu a[ui-sref="client.card.log"]`,
lastModificationDate: 'vn-client-log > vn-log vn-table vn-tbody > vn-tr > vn-td:nth-child(1)',
lastModificationPreviousValue: 'vn-client-log vn-table vn-td.before', lastModificationPreviousValue: 'vn-client-log vn-table vn-td.before',
lastModificationCurrentValue: 'vn-client-log vn-table vn-td.after' lastModificationCurrentValue: 'vn-client-log vn-table vn-td.after'
@ -165,7 +168,7 @@ export default {
}, },
webPayment: { webPayment: {
confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]', confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]',
firstPaymentConfirmed: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon[icon="check"]' firstPaymentConfirmed: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon[icon="check"][aria-hidden="false"]'
}, },
itemsIndex: { itemsIndex: {
goBackToModuleIndexButton: `vn-ticket-descriptor a[href="#!/ticket/index"]`, goBackToModuleIndexButton: `vn-ticket-descriptor a[href="#!/ticket/index"]`,
@ -339,12 +342,13 @@ export default {
firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img', firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img',
firstSaleZoomedImage: 'body > div > div > img', firstSaleZoomedImage: 'body > div > div > img',
firstSaleQuantity: `vn-textfield[model="sale.quantity"]:nth-child(1) input`, firstSaleQuantity: `vn-textfield[model="sale.quantity"]:nth-child(1) input`,
firstSaleQuantityCell: `vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable`,
firstSaleQuantityClearInput: `vn-textfield[model="sale.quantity"] div.suffix > i`, firstSaleQuantityClearInput: `vn-textfield[model="sale.quantity"] div.suffix > i`,
firstSaleID: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(4) > span', firstSaleID: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(4) > span',
firstSalePrice: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) > vn-td:nth-child(7) > span', firstSalePrice: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) > vn-td:nth-child(7) > span',
firstSalePriceInput: 'vn-ticket-sale:nth-child(1) > vn-vertical > vn-popover.edit.dialog-summary.ng-isolate-scope.vn-popover.shown > div > div.content > div > vn-textfield', firstSalePriceInput: 'vn-ticket-sale:nth-child(1) vn-popover.edit.dialog-summary.ng-isolate-scope.vn-popover.shown vn-textfield input',
firstSaleDiscount: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(8) > span', firstSaleDiscount: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(8) > span',
firstSaleDiscountInput: 'vn-ticket-sale:nth-child(1) vn-ticket-sale-edit-discount > div > vn-textfield > div > div > div.infix > input.ng-not-empty', firstSaleDiscountInput: 'vn-ticket-sale:nth-child(1) vn-ticket-sale-edit-discount vn-textfield input',
firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(9)', firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(9)',
firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)', firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)',
firstSaleColour: `vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(6) section:nth-child(1)`, firstSaleColour: `vn-ticket-sale vn-tr:nth-child(1) vn-td:nth-child(6) section:nth-child(1)`,
@ -468,6 +472,17 @@ export default {
secondClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimRedeliveryFk"]', secondClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimRedeliveryFk"]',
saveDevelopmentButton: `${components.vnSubmit}` saveDevelopmentButton: `${components.vnSubmit}`
}, },
claimAction: {
importClaimButton: 'vn-claim-action vn-button[label="Import claim"]',
importTicketButton: 'vn-claim-action vn-button[label="Import ticket"]',
secondImportableTicket: 'vn-claim-action > vn-vertical > vn-popover > div > div.content > div > vn-table > div > vn-tbody > vn-tr:nth-child(2)',
firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[field="saleClaimed.claimDestinationFk"]',
thirdLineDestination: 'vn-claim-action vn-tr:nth-child(3) vn-autocomplete[field="saleClaimed.claimDestinationFk"]',
firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
secondDeleteLine: 'vn-claim-action vn-tr:nth-child(2) vn-icon-button[icon="delete"]',
thirdDeleteLine: 'vn-claim-action vn-tr:nth-child(3) vn-icon-button[icon="delete"]'
},
ordersIndex: { ordersIndex: {
searchResult: `vn-order-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr`, searchResult: `vn-order-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr`,
searchResultDate: `vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)`, searchResultDate: `vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)`,

View File

@ -0,0 +1,63 @@
import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare';
describe('Claim edit basic data path', () => {
const nightmare = createNightmare();
beforeAll(() => {
nightmare
.loginAndModule('administrative', 'claim')
.accessToSearchResult('4')
.accessToSection('claim.card.action');
});
it('should import the claim', async() => {
const result = await nightmare
.waitToClick(selectors.claimAction.importClaimButton)
.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it('should import the eighth ticket', async() => {
const result = await nightmare
.waitToClick(selectors.claimAction.importTicketButton)
.waitToClick(selectors.claimAction.secondImportableTicket)
.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it('should edit the fourth line destination field', async() => {
const result = await nightmare
.autocompleteSearch(selectors.claimAction.thirdLineDestination, 'Bueno')
.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it('should delete two first lines', async() => {
const result = await nightmare
.waitToClick(selectors.claimAction.secondDeleteLine)
.waitToClick(selectors.claimAction.firstDeleteLine)
.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it('should refresh the view to check the remaining line is the expected one', async() => {
const result = await nightmare
.reloadSection('claim.card.action')
.waitToGetProperty(`${selectors.claimAction.firstLineDestination} input`, 'value');
expect(result).toEqual('Bueno');
});
it('should delete the current first line', async() => {
const result = await nightmare
.waitToClick(selectors.claimAction.firstDeleteLine)
.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
});

View File

@ -52,6 +52,8 @@ describe('Client create path', () => {
const result = await nightmare const result = await nightmare
.write(selectors.createClientView.name, 'Carol Danvers') .write(selectors.createClientView.name, 'Carol Danvers')
.write(selectors.createClientView.socialName, 'AVG tax') .write(selectors.createClientView.socialName, 'AVG tax')
.write(selectors.createClientView.street, 'Many places')
.write(selectors.createClientView.city, 'Silla')
.clearInput(selectors.createClientView.email) .clearInput(selectors.createClientView.email)
.write(selectors.createClientView.email, 'incorrect email format') .write(selectors.createClientView.email, 'incorrect email format')
.waitToClick(selectors.createClientView.createButton) .waitToClick(selectors.createClientView.createButton)

View File

@ -58,7 +58,7 @@ describe('Client Edit fiscalData path', () => {
.accessToSection('client.card.fiscalData'); .accessToSection('client.card.fiscalData');
}); });
it('should receive an error if VIES and EQtax are being ticked together', async() => { it('should edit the fiscal data', async() => {
const result = await nightmare const result = await nightmare
.wait(selectors.clientFiscalData.socialNameInput) .wait(selectors.clientFiscalData.socialNameInput)
.clearInput(selectors.clientFiscalData.socialNameInput) .clearInput(selectors.clientFiscalData.socialNameInput)
@ -84,9 +84,17 @@ describe('Client Edit fiscalData path', () => {
.waitToClick(selectors.clientFiscalData.saveButton) .waitToClick(selectors.clientFiscalData.saveButton)
.waitForLastSnackbar(); .waitForLastSnackbar();
expect(result).toEqual('Cannot check VIES and Equalization Tax'); expect(result).toEqual('Data saved!');
}, 15000); }, 15000);
it('should propagate the Equalization tax', async() => {
const result = await nightmare
.waitToClick(selectors.clientFiscalData.acceptPropagationButton)
.waitForLastSnackbar();
expect(result).toEqual('Equivalent tax spreaded');
});
it('should receive an error if the fiscal id contains A or B at the beginning', async() => { it('should receive an error if the fiscal id contains A or B at the beginning', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.clientFiscalData.viesCheckbox) .waitToClick(selectors.clientFiscalData.viesCheckbox)
@ -108,14 +116,6 @@ describe('Client Edit fiscalData path', () => {
expect(result).toEqual('Data saved!'); expect(result).toEqual('Data saved!');
}); });
it('should propagate the Equalization tax', async() => {
const result = await nightmare
.waitToClick(selectors.clientFiscalData.acceptPropagationButton)
.waitForLastSnackbar();
expect(result).toEqual('Equivalent tax spreaded');
});
// confirm all addresses have now EQtax checked step 1 // confirm all addresses have now EQtax checked step 1
it(`should click on the addresses button to access to the client's addresses`, async() => { it(`should click on the addresses button to access to the client's addresses`, async() => {
const url = await nightmare const url = await nightmare

View File

@ -4,6 +4,8 @@ import createNightmare from '../../helpers/nightmare';
describe('Client log path', () => { describe('Client log path', () => {
const nightmare = createNightmare(); const nightmare = createNightmare();
let date;
beforeAll(() => { beforeAll(() => {
nightmare nightmare
.loginAndModule('employee', 'client') .loginAndModule('employee', 'client')
@ -17,6 +19,7 @@ describe('Client log path', () => {
.write(selectors.clientBasicData.nameInput, 'this is a test') .write(selectors.clientBasicData.nameInput, 'this is a test')
.waitToClick(selectors.clientBasicData.saveButton) .waitToClick(selectors.clientBasicData.saveButton)
.waitForLastSnackbar(); .waitForLastSnackbar();
date = new Date();
expect(result).toEqual('Data saved!'); expect(result).toEqual('Data saved!');
}); });
@ -38,9 +41,33 @@ describe('Client log path', () => {
}); });
it('should check the current value of the last logged change', async() => { it('should check the current value of the last logged change', async() => {
let lastModificationDate = await nightmare
.waitToGetProperty(selectors.clientLog.lastModificationDate, 'innerText');
let lastModificationPreviousValue = await nightmare
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
let lastModificationCurrentValue = await nightmare let lastModificationCurrentValue = await nightmare
.waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText'); .waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
expect(lastModificationCurrentValue).toContain('this is a test'); let month = date.getMonth() + 1;
let day = date.getDate();
let year = date.getFullYear();
let hours = date.getHours();
let minutes = date.getMinutes();
if (month.toString().length < 2) month = '0' + month;
if (day.toString().length < 2) day = `0${day}`;
if (hours.toString().length < 2) hours = '0' + hours;
if (minutes.toString().length < 2) minutes = `0${minutes}`;
let today = [day, month, year].join('/');
let time = [hours, minutes].join(':');
let now = [today, time].join(' ');
expect(lastModificationDate).toContain(now);
expect(lastModificationPreviousValue).toEqual('name: DavidCharlesHaller');
expect(lastModificationCurrentValue).toEqual('name: this is a test');
}); });
}); });

View File

@ -1,15 +1,14 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare'; import createNightmare from '../../helpers/nightmare';
// #860 E2E client/client/src/web-payment/index.js missing fixtures for another client describe('Client web Payment', () => {
xdescribe('Client web Payment', () => {
const nightmare = createNightmare(); const nightmare = createNightmare();
describe('as employee', () => { describe('as employee', () => {
beforeAll(() => { beforeAll(() => {
nightmare nightmare
.loginAndModule('employee', 'client') .loginAndModule('employee', 'client')
.accessToSearchResult('Bruce Wayne') .accessToSearchResult('Tony Stark')
.accessToSection('client.card.webPayment'); .accessToSection('client.card.webPayment');
}); });
@ -25,13 +24,14 @@ xdescribe('Client web Payment', () => {
beforeAll(() => { beforeAll(() => {
nightmare nightmare
.loginAndModule('administrative', 'client') .loginAndModule('administrative', 'client')
.accessToSearchResult('Bruce Wayne') .accessToSearchResult('Tony Stark')
.accessToSection('client.card.webPayment'); .accessToSection('client.card.webPayment');
}); });
it('should be able to confirm payments', async() => { it('should be able to confirm payments', async() => {
let exists = await nightmare let exists = await nightmare
.waitToClick(selectors.webPayment.confirmFirstPaymentButton) .waitToClick(selectors.webPayment.confirmFirstPaymentButton)
.wait(selectors.webPayment.firstPaymentConfirmed)
.exists(selectors.webPayment.firstPaymentConfirmed); .exists(selectors.webPayment.firstPaymentConfirmed);
expect(exists).toBeTruthy(); expect(exists).toBeTruthy();

View File

@ -1,7 +1,8 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare'; import createNightmare from '../../helpers/nightmare';
describe('Item regularize path', () => { // #1186 repearar e2e ticket.sale, item.regularize.
xdescribe('Item regularize path', () => {
const nightmare = createNightmare(); const nightmare = createNightmare();
beforeAll(() => { beforeAll(() => {
nightmare nightmare
@ -176,7 +177,7 @@ describe('Item regularize path', () => {
it('should search for the ticket with id 23 once again', async() => { it('should search for the ticket with id 23 once again', async() => {
const result = await nightmare const result = await nightmare
.write(selectors.ticketsIndex.searchTicketInput, 'id:23') .write(selectors.ticketsIndex.searchTicketInput, 'id:24')
.waitToClick(selectors.ticketsIndex.searchButton) .waitToClick(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countElement(selectors.ticketsIndex.searchResult); .countElement(selectors.ticketsIndex.searchResult);
@ -186,7 +187,7 @@ describe('Item regularize path', () => {
it(`should now click on the search result to access to the ticket summary`, async() => { it(`should now click on the search result to access to the ticket summary`, async() => {
const url = await nightmare const url = await nightmare
.waitForTextInElement(selectors.ticketsIndex.searchResult, '23') .waitForTextInElement(selectors.ticketsIndex.searchResult, '24')
.waitToClick(selectors.ticketsIndex.searchResult) .waitToClick(selectors.ticketsIndex.searchResult)
.waitForURL('/summary') .waitForURL('/summary')
.parsedUrl(); .parsedUrl();

View File

@ -15,21 +15,21 @@ describe('Ticket List sale path', () => {
const value = await nightmare const value = await nightmare
.waitToGetProperty(selectors.ticketSales.firstSaleColour, 'innerText'); .waitToGetProperty(selectors.ticketSales.firstSaleColour, 'innerText');
expect(value).toContain('Red'); expect(value).toContain('Yellow');
}); });
it('should confirm the first ticket sale contains the lenght', async() => { it('should confirm the first ticket sale contains the lenght', async() => {
const value = await nightmare const value = await nightmare
.waitToGetProperty(selectors.ticketSales.firstSaleText, 'innerText'); .waitToGetProperty(selectors.ticketSales.firstSaleText, 'innerText');
expect(value).toContain('3'); expect(value).toContain('5');
}); });
it('should confirm the first ticket sale contains the price', async() => { it('should confirm the first ticket sale contains the price', async() => {
const value = await nightmare const value = await nightmare
.waitToGetProperty(selectors.ticketSales.firstSalePrice, 'innerText'); .waitToGetProperty(selectors.ticketSales.firstSalePrice, 'innerText');
expect(value).toContain('1.30'); expect(value).toContain('2.30');
}); });
it('should confirm the first ticket sale contains the discount', async() => { it('should confirm the first ticket sale contains the discount', async() => {
@ -43,7 +43,7 @@ describe('Ticket List sale path', () => {
const value = await nightmare const value = await nightmare
.waitToGetProperty(selectors.ticketSales.firstSaleImport, 'innerText'); .waitToGetProperty(selectors.ticketSales.firstSaleImport, 'innerText');
expect(value).toContain('19.50'); expect(value).toContain('23');
}); });
it('should navigate to the catalog by pressing the new item button', async() => { it('should navigate to the catalog by pressing the new item button', async() => {

View File

@ -73,7 +73,7 @@ describe('Ticket Edit basic data path', () => {
.wait(1900) .wait(1900)
.waitToGetProperty(selectors.ticketBasicData.stepTwoTotalPriceDif, 'innerText'); .waitToGetProperty(selectors.ticketBasicData.stepTwoTotalPriceDif, 'innerText');
expect(result).toContain('-€187.75'); expect(result).toContain('-€208.75');
}); });
it(`should then click next to move on to step three`, async() => { it(`should then click next to move on to step three`, async() => {

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import createNightmare from '../../helpers/nightmare'; import createNightmare from '../../helpers/nightmare';
// #1152 refactor ticket.sale, update price no funciona correctamente. // #1186 repearar e2e ticket.sale, item.regularize.
xdescribe('Ticket Edit sale path', () => { xdescribe('Ticket Edit sale path', () => {
const nightmare = createNightmare(); const nightmare = createNightmare();
@ -138,7 +138,7 @@ xdescribe('Ticket Edit sale path', () => {
it('should try to add a higher quantity value and then receive an error', async() => { it('should try to add a higher quantity value and then receive an error', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleQuantityClearInput) .waitToFocus(selectors.ticketSales.firstSaleQuantityCell)
.write(selectors.ticketSales.firstSaleQuantity, '9\u000d') .write(selectors.ticketSales.firstSaleQuantity, '9\u000d')
.waitForLastSnackbar(); .waitForLastSnackbar();
@ -147,7 +147,7 @@ xdescribe('Ticket Edit sale path', () => {
it('should remove 1 from quantity', async() => { it('should remove 1 from quantity', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleQuantityClearInput) .waitToFocus(selectors.ticketSales.firstSaleQuantityCell)
.write(selectors.ticketSales.firstSaleQuantity, '4\u000d') .write(selectors.ticketSales.firstSaleQuantity, '4\u000d')
.waitForLastSnackbar(); .waitForLastSnackbar();
@ -180,7 +180,6 @@ xdescribe('Ticket Edit sale path', () => {
it('should update the discount', async() => { it('should update the discount', async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleDiscount) .waitToClick(selectors.ticketSales.firstSaleDiscount)
.wait('vn-textfield[label="Discount"] > div[class="container selected"]') // a function selects the text after it's loaded
.write(selectors.ticketSales.firstSaleDiscountInput, '50\u000d') .write(selectors.ticketSales.firstSaleDiscountInput, '50\u000d')
// .write('body', '\u000d') // simulates enter // .write('body', '\u000d') // simulates enter
.waitForLastSnackbar(); .waitForLastSnackbar();
@ -467,7 +466,7 @@ xdescribe('Ticket Edit sale path', () => {
.waitToClick(selectors.ticketSales.moreMenuButton) .waitToClick(selectors.ticketSales.moreMenuButton)
.waitToClick(selectors.ticketSales.moreMenuUpdateDiscount) .waitToClick(selectors.ticketSales.moreMenuUpdateDiscount)
.write(selectors.ticketSales.moreMenuUpdateDiscountInput, 100) .write(selectors.ticketSales.moreMenuUpdateDiscountInput, 100)
.write('body', '\u000d') // simulates enter .write('body', '\u000d')
.waitForTextInElement(selectors.ticketSales.totalImport, '0.00') .waitForTextInElement(selectors.ticketSales.totalImport, '0.00')
.waitToGetProperty(selectors.ticketSales.totalImport, 'innerText'); .waitToGetProperty(selectors.ticketSales.totalImport, 'innerText');
@ -597,16 +596,16 @@ xdescribe('Ticket Edit sale path', () => {
describe('when state is preparation and loged as salesPerson', () => { describe('when state is preparation and loged as salesPerson', () => {
it(`shouldnt be able to edit the sale price`, async() => { it(`shouldnt be able to edit the sale price`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketSales.firstSalePrice) .waitToClick(selectors.ticketSales.firstSaleID)
.exists(selectors.ticketSales.firstSalePriceInput); .exists(selectors.ticketSales.firstSalePrice);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
it(`shouldnt be able to edit the sale discount`, async() => { it(`shouldnt be able to edit the sale discount`, async() => {
const result = await nightmare const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleDiscount) .waitToClick(selectors.ticketSales.firstSaleID)
.exists(selectors.ticketSales.firstSaleDiscountInput); .exists(selectors.ticketSales.firstSaleDiscount);
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });

View File

@ -15,11 +15,10 @@ export default class Button extends Input {
} }
$onInit() { $onInit() {
if (!this.type) { if (!this.type)
this.type = 'button'; this.type = 'button';
} }
} }
}
Button.$inject = ['$element']; Button.$inject = ['$element'];
ngModule.component('vnButton', { ngModule.component('vnButton', {

View File

@ -0,0 +1,84 @@
import './searchbar.js';
describe('Component vnSearchbar', () => {
let controller;
let $element;
let $state;
beforeEach(ngModule('vnCore'));
beforeEach(angular.mock.inject(($componentController, _$state_) => {
$state = _$state_;
$element = angular.element(`<div></div>`);
controller = $componentController('vnSearchbar', {$element, $state});
controller.panel = 'vn-client-search-panel';
}));
describe('$postLink()', () => {
it(`should not call onStateChange() if filter is defined`, () => {
spyOn(controller, 'onStateChange');
controller.filter = {};
controller.$postLink();
expect(controller.onStateChange).not.toHaveBeenCalledWith();
});
it(`should call onStateChange() if filter is null`, () => {
spyOn(controller, 'onStateChange');
controller.filter = null;
controller.$postLink();
expect(controller.onStateChange).toHaveBeenCalledWith();
});
});
describe('filter() setter', () => {
it(`should call $state.go()`, () => {
controller._filter = {};
spyOn(controller.$state, 'go');
controller.filter = {expected: 'filter'};
expect(controller._filter).toEqual(controller.filter);
expect(controller.$state.go).toHaveBeenCalledWith('.', Object({q: '{"expected":"filter"}'}));
});
});
describe('openPanel()', () => {
it(`should do nothing if the event is prevented`, () => {
let event = {
defaultPrevented: true,
preventDefault: jasmine.createSpy('preventDefault')
};
controller.openPanel(event);
expect(event.preventDefault).not.toHaveBeenCalledWith();
});
});
describe('getObjectFromString()', () => {
it(`should return a formated object based on the string received for basic search`, () => {
let result = controller.getObjectFromString('Bruce Wayne');
expect(result).toEqual({search: 'Bruce Wayne'});
});
it(`should return a formated object based on the string received for advanced search`, () => {
let result = controller.getObjectFromString('id:101 name:(Bruce Wayne)');
expect(result).toEqual({id: '101', name: 'Bruce Wayne'});
});
it(`should format the object grouping any unmatched part of the instring of the string to the search property`, () => {
let string = 'I am the search id:101 name:(Bruce Wayne) concatenated value';
let result = controller.getObjectFromString(string);
expect(result).toEqual({
id: '101',
name: 'Bruce Wayne',
search: 'I am the search concatenated value'
});
});
});
});

View File

@ -1,4 +1,2 @@
<vn-horizontal class="text-container">
<span class="text" ng-transclude="text" vn-one></span>
</vn-horizontal>
<div class="field"></div> <div class="field"></div>
<span class="text" ng-transclude="text"></span>

View File

@ -1,42 +1,67 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from '../../lib/component'; import Component from '../../lib/component';
import {focus} from '../../directives/focus';
import './style.scss'; import './style.scss';
export default class Controller extends Component { export default class Controller extends Component {
constructor($element, $scope, $transclude) { constructor($element, $scope, $transclude, $timeout) {
super($element, $scope); super($element, $scope);
this.$timeout = $timeout;
let element = $element[0]; let element = $element[0];
element.tabIndex = 0; element.tabIndex = 0;
element.addEventListener('focus', () => { element.addEventListener('focus', () => {
if (this.field || this.disabled) return;
$transclude((tClone, tScope) => { $transclude((tClone, tScope) => {
this.field = tClone; this.field = tClone;
this.tScope = tScope; this.tScope = tScope;
this.element.querySelector('.field').appendChild(this.field[0]); this.element.querySelector('.field').appendChild(this.field[0]);
element.tabIndex = -1;
this.timer = $timeout(() => {
this.timer = null;
focus(this.field[0]);
});
}, null, 'field'); }, null, 'field');
element.classList.add('selected'); element.classList.add('selected');
}); });
element.addEventListener('focusout', event => { element.addEventListener('focusout', event => {
if (!this.field || this.disabled) return;
// this.destroyTimer();
this.lastEvent = event; this.lastEvent = event;
let target = event.relatedTarget; let target = event.relatedTarget;
while (target && target.parentNode != element) while (target && target != element)
target = target.parentNode; target = target.parentNode;
if (!target) { if (!target) {
this.tScope.$destroy(); this.tScope.$destroy();
this.field.remove(); this.field.remove();
this.field = null;
element.classList.remove('selected'); element.classList.remove('selected');
element.tabIndex = 0;
} }
}); });
} }
destroyTimer() {
if (this.timer) {
this.$timeout.cancel(this.timer);
this.timer = null;
}
} }
Controller.$inject = ['$element', '$scope', '$transclude']; $onDestroy() {
this.destroyTimer();
}
}
Controller.$inject = ['$element', '$scope', '$transclude', '$timeout'];
ngModule.component('vnTdEditable', { ngModule.component('vnTdEditable', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: {
disabled: '<?'
},
transclude: { transclude: {
text: 'text', text: 'text',
field: '?field' field: '?field'

View File

@ -1,16 +1,54 @@
@import "variables";
vn-td-editable { vn-td-editable {
text {
cursor: pointer; cursor: pointer;
& > div.text-container{ display: block
width: 100%;
} }
&.selected { outline: none;
& > .text-container{ position: relative;
&:not([disabled="true"]) {
cursor: initial;
text:hover::after {
font-family: 'salixfont';
float: right;
content: '\e900';
display: block
}
}
&.selected > .text {
visibility: hidden;
}
& > .field {
display: none; display: none;
} position: absolute;
} top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
align-items: center;
padding: .6em;
overflow: visible;
vn-icon { & > field {
font-size: 1em; flex: 1;
background-color: $color-bg-panel;
padding: .5em;
box-shadow: 0 0 .4em rgba(0, 0, 0, .2);
border-radius: .1em;
min-width: 6em;
& > * {
width: 100%;
max-width: initial;
}
}
}
&.selected > .field {
display: flex;
} }
} }

View File

@ -18,18 +18,15 @@ export default class Textfield extends Input {
if (!this.oldValue) if (!this.oldValue)
this.saveOldValue(); this.saveOldValue();
}); });
this.input.addEventListener('keyup', e => { this.input.addEventListener('keyup', e => {
if (e.key == 'Escape') { if (e.defaultPrevented || e.key != 'Escape')
return;
this.value = this.oldValue; this.value = this.oldValue;
this.cancelled = true; this.cancelled = true;
e.stopPropagation(); e.preventDefault();
}
if (e.key == 'Escape' || e.key == 'Enter')
this.input.blur();
}); });
this.input.addEventListener('change', e => {
this.input.addEventListener('blur', () => {
if (this.onChange && !this.cancelled && (this.oldValue != this.value)) { if (this.onChange && !this.cancelled && (this.oldValue != this.value)) {
this.onChange(); this.onChange();
this.saveOldValue(); this.saveOldValue();

View File

@ -1,16 +1,6 @@
import ngModule from '../module'; import ngModule from '../module';
/** export function focus(input) {
* Sets the focus and selects the text on the input.
*
* @return {Object} The directive
*/
export function directive() {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
$scope.$watch('', function() {
let input = $element[0];
let selector = 'input, textarea, button, submit'; let selector = 'input, textarea, button, submit';
if (!input.matches(selector)) if (!input.matches(selector))
@ -25,7 +15,18 @@ export function directive() {
if (input.select) if (input.select)
input.select(); input.select();
}); }
/**
* Sets the focus and selects the text on the input.
*
* @return {Object} The directive
*/
export function directive() {
return {
restrict: 'A',
link: function($scope, $element) {
$scope.$watch('', () => focus($element[0]));
} }
}; };
} }

Binary file not shown.

View File

@ -17,6 +17,11 @@ describe('Component vnLeftMenu', () => {
]; ];
})); }));
it(`should set items in the controller for the left menu`, () => {
expect(controller.items.length).toEqual(3);
expect(controller.items[2].state).toEqual('client.card.summary');
});
describe('depth() setter', () => { describe('depth() setter', () => {
it(`should set depth property and call activateItem()`, () => { it(`should set depth property and call activateItem()`, () => {
spyOn(controller, 'activateItem'); spyOn(controller, 'activateItem');

View File

@ -355,7 +355,7 @@ async function docker() {
let d = new Date(); let d = new Date();
let pad = v => v < 10 ? '0' + v : v; let pad = v => v < 10 ? '0' + v : v;
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
await execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./services/db`); await execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`);
let dockerArgs = `--name ${containerId} -p 3306:${dbConf.port}`; let dockerArgs = `--name ${containerId} -p 3306:${dbConf.port}`;

View File

@ -12,6 +12,10 @@ module.exports = function(Self) {
}); });
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let oldInstance; let oldInstance;
let oldInstanceFk; let oldInstanceFk;
let newInstance; let newInstance;
@ -22,7 +26,7 @@ module.exports = function(Self) {
oldInstance = await fkToValue(oldInstanceFk, ctx); oldInstance = await fkToValue(oldInstanceFk, ctx);
if (ctx.where && !ctx.currentInstance) { if (ctx.where && !ctx.currentInstance) {
let fields = Object.keys(ctx.data); let fields = Object.keys(ctx.data);
ctx.oldInstances = await ctx.Model.app.models[ctx.Model.definition.name].find({where: ctx.where, fields: fields}); ctx.oldInstances = await ctx.Model.app.models[ctx.Model.definition.name].find({where: ctx.where, fields: fields}, options);
} }
} }
if (ctx.isNewInstance) if (ctx.isNewInstance)
@ -33,10 +37,14 @@ module.exports = function(Self) {
}); });
Self.observe('before delete', async function(ctx) { Self.observe('before delete', async function(ctx) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
if (ctx.where) { if (ctx.where) {
let affectedModel = ctx.Model.definition.name; let affectedModel = ctx.Model.definition.name;
let definition = ctx.Model.definition; let definition = ctx.Model.definition;
let deletedInstances = await ctx.Model.app.models[affectedModel].find({where: ctx.where}); let deletedInstances = await ctx.Model.app.models[affectedModel].find({where: ctx.where}, options);
let relation = definition.settings.log.relation; let relation = definition.settings.log.relation;
if (relation) { if (relation) {
@ -61,6 +69,10 @@ module.exports = function(Self) {
}); });
async function logDeletedInstances(ctx, loopBackContext) { async function logDeletedInstances(ctx, loopBackContext) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
ctx.hookState.oldInstance.forEach(async instance => { ctx.hookState.oldInstance.forEach(async instance => {
let userFk; let userFk;
if (loopBackContext) if (loopBackContext)
@ -80,16 +92,16 @@ module.exports = function(Self) {
newInstance: {} newInstance: {}
}; };
let transaction = {};
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
let logModel = definition.settings.log.model; let logModel = definition.settings.log.model;
await ctx.Model.app.models[logModel].create(logRecord, transaction); await ctx.Model.app.models[logModel].create(logRecord, options);
}); });
} }
async function fkToValue(instance, ctx) { async function fkToValue(instance, ctx) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let cleanInstance = JSON.parse(JSON.stringify(instance)); let cleanInstance = JSON.parse(JSON.stringify(instance));
let result = {}; let result = {};
for (let key in cleanInstance) { for (let key in cleanInstance) {
@ -98,8 +110,11 @@ module.exports = function(Self) {
for (let key1 in ctx.Model.relations) { for (let key1 in ctx.Model.relations) {
let val1 = ctx.Model.relations[key1]; let val1 = ctx.Model.relations[key1];
if (val1.keyFrom == key && key != 'id') { if (val1.keyFrom == key && key != 'id') {
let recordSet = await val1.modelTo.findById(val); let recordSet = await ctx.Model.app.models[val1.modelTo.modelName].findById(val, options);
val = recordSet.name; // FIXME preparar todos los modelos con campo name let definition = val1.modelTo.definition;
let changedModelValue = definition.settings.log && definition.settings.log.changedModelValue;
val = (changedModelValue && recordSet && recordSet[changedModelValue]) || (recordSet && recordSet.id) || val; // FIXME preparar todos los modelos con campo name
break; break;
} }
} }
@ -109,6 +124,10 @@ module.exports = function(Self) {
} }
async function logInModel(ctx, loopBackContext) { async function logInModel(ctx, loopBackContext) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let definition = ctx.Model.definition; let definition = ctx.Model.definition;
let primaryKey; let primaryKey;
for (let property in definition.properties) { for (let property in definition.properties) {
@ -143,10 +162,11 @@ module.exports = function(Self) {
// Sets the changedModelValue to save and the instances changed in case its an updateAll // Sets the changedModelValue to save and the instances changed in case its an updateAll
let changedModelValue = definition.settings.log.changedModelValue; let changedModelValue = definition.settings.log.changedModelValue;
let where;
if (changedModelValue && (!ctx.instance || !ctx.instance[changedModelValue])) { if (changedModelValue && (!ctx.instance || !ctx.instance[changedModelValue])) {
var where = [];
changedModelId = []; changedModelId = [];
let changedInstances = await ctx.Model.app.models[definition.name].find({where: ctx.where, fields: ['id', changedModelValue]}); where = [];
let changedInstances = await ctx.Model.app.models[definition.name].find({where: ctx.where, fields: ['id', changedModelValue]}, options);
changedInstances.forEach(element => { changedInstances.forEach(element => {
where.push(element[changedModelValue]); where.push(element[changedModelValue]);
changedModelId.push(element.id); changedModelId.push(element.id);
@ -185,11 +205,7 @@ module.exports = function(Self) {
let logModel = definition.settings.log.model; let logModel = definition.settings.log.model;
let transaction = {}; await ctx.Model.app.models[logModel].create(logsToSave, options);
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
await ctx.Model.app.models[logModel].create(logsToSave, transaction);
} }
// this function retuns all the instances changed in case this is an updateAll // this function retuns all the instances changed in case this is an updateAll

View File

@ -37,5 +37,7 @@
"Barcode must be unique": "Barcode must be unique", "Barcode must be unique": "Barcode must be unique",
"You don't have enough privileges to do that": "You don't have enough privileges to do that", "You don't have enough privileges to do that": "You don't have enough privileges to do that",
"You can't create a ticket for a frozen client": "You can't create a ticket for a frozen client", "You can't create a ticket for a frozen client": "You can't create a ticket for a frozen client",
"can't be blank": "can't be blank" "can't be blank": "can't be blank",
"Street cannot be empty": "Street cannot be empty",
"City cannot be empty": "City cannot be empty"
} }

View File

@ -68,5 +68,9 @@
"Tag value cannot be blank": "El valor del tag no puede quedar en blanco", "Tag value cannot be blank": "El valor del tag no puede quedar en blanco",
"ORDER_EMPTY": "Cesta vacía", "ORDER_EMPTY": "Cesta vacía",
"You don't have enough privileges to do that": "No tienes permisos para cambiar esto", "You don't have enough privileges to do that": "No tienes permisos para cambiar esto",
"You can't create a ticket for a client that has a debt": "No puedes crear un ticket para un client con deuda" "You can't create a ticket for a client that has a debt": "No puedes crear un ticket para un client con deuda",
"NO SE PUEDE DESACTIVAR EL CONSIGNAT": "NO SE PUEDE DESACTIVAR EL CONSIGNAT",
"Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido",
"Street cannot be empty": "Street cannot be empty",
"City cannot be empty": "City cannot be empty"
} }

View File

@ -3,10 +3,12 @@ const app = require('vn-loopback/server/server');
describe('Claim importTicketSales()', () => { describe('Claim importTicketSales()', () => {
let claimEnds; let claimEnds;
afterAll(async () => { afterAll(async done => {
claimEnds.forEach(async line => { claimEnds.forEach(async line => {
await line.destroy(); await line.destroy();
}); });
done();
}); });
it('should import sales to a claim actions from an specific ticket', async() => { it('should import sales to a claim actions from an specific ticket', async() => {

View File

@ -4,8 +4,10 @@ describe('Claim Create', () => {
let newDate = new Date(); let newDate = new Date();
let createdClaimFk; let createdClaimFk;
afterAll(async () => { afterAll(async done => {
await app.models.Claim.destroyById(createdClaimFk); await app.models.Claim.destroyById(createdClaimFk);
done();
}); });
let newClaim = { let newClaim = {

View File

@ -9,7 +9,7 @@ xdescribe('regularizeClaim()', () => {
let claimEnds = []; let claimEnds = [];
let trashTicket; let trashTicket;
afterAll(async () => { afterAll(async done => {
let claim = await app.models.Claim.findById(claimFk); let claim = await app.models.Claim.findById(claimFk);
await claim.updateAttributes({claimStateFk: pendentState}); await claim.updateAttributes({claimStateFk: pendentState});
await app.models.Ticket.destroyById(trashTicket.id); await app.models.Ticket.destroyById(trashTicket.id);
@ -17,6 +17,8 @@ xdescribe('regularizeClaim()', () => {
claimEnds.forEach(async line => { claimEnds.forEach(async line => {
await line.destroy(); await line.destroy();
}); });
done();
}); });
it('should change claim state to resolved', async() => { it('should change claim state to resolved', async() => {

View File

@ -14,12 +14,16 @@ describe('Update Claim', () => {
observation: 'observation' observation: 'observation'
}; };
beforeAll(async() => { beforeAll(async done => {
newInstance = await app.models.Claim.create(original); newInstance = await app.models.Claim.create(original);
done();
}); });
afterAll(async() => { afterAll(async done => {
await app.models.Claim.destroyById(newInstance.id); await app.models.Claim.destroyById(newInstance.id);
done();
}); });
it('should throw error if isSaleAssistant is false and try to modify a forbidden field', async() => { it('should throw error if isSaleAssistant is false and try to modify a forbidden field', async() => {

View File

@ -0,0 +1,42 @@
module.exports = function(Self) {
Self.remoteMethod('createDefaultAddress', {
description: 'Creates both client and its web account',
accepts: {
arg: 'data',
type: 'object',
http: {source: 'body'}
},
returns: {
root: true,
type: 'Object'
},
http: {
verb: 'post',
path: '/createDefaultAddress'
}
});
Self.createDefaultAddress = async data => {
const Address = Self.app.models.Address;
const Client = Self.app.models.Client;
const transaction = await Address.beginTransaction({});
try {
let address = data.address;
let newAddress = await Address.create(address, {transaction});
let client = await Client.findById(address.clientFk, {transaction});
if (data.isDefaultAddress) {
await client.updateAttributes({
defaultAddressFk: newAddress.id
}, {transaction});
}
await transaction.commit();
return newAddress;
} catch (e) {
await transaction.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,37 @@
const app = require('vn-loopback/server/server');
describe('Address createDefaultAddress', () => {
let address;
let client;
afterAll(async done => {
await client.updateAttributes({defaultAddressFk: 1});
await address.destroy();
done();
});
it('should verify that client defaultAddressFk is untainted', async() => {
client = await app.models.Client.findById(101);
expect(client.defaultAddressFk).toEqual(1);
});
it('should create a new address and set as a client default address', async() => {
let data = {
address: {
clientFk: 101,
nickname: 'My address',
street: 'Wall Street',
city: 'New York',
},
isDefaultAddress: true
};
address = await app.models.Address.createDefaultAddress(data);
client = await app.models.Client.findById(101);
expect(client.defaultAddressFk).toEqual(address.id);
});
});

View File

@ -23,13 +23,13 @@ module.exports = function(Self) {
email: firstEmail, email: firstEmail,
password: parseInt(Math.random() * 100000000000000) password: parseInt(Math.random() * 100000000000000)
}; };
let Account = Self.app.models.Account; const Account = Self.app.models.Account;
const Address = Self.app.models.Address;
let transaction = await Account.beginTransaction({}); const transaction = await Account.beginTransaction({});
try { try {
let account = await Account.create(user, {transaction}); let account = await Account.create(user, {transaction});
let client = { let client = await Self.create({
id: account.id, id: account.id,
name: data.name, name: data.name,
fi: data.fi, fi: data.fi,
@ -42,10 +42,26 @@ module.exports = function(Self) {
provinceFk: data.provinceFk, provinceFk: data.provinceFk,
countryFk: data.countryFk, countryFk: data.countryFk,
isEqualizated: data.isEqualizated isEqualizated: data.isEqualizated
}; }, {transaction});
newClient = await Self.create(client);
let address = await Address.create({
clientFk: client.id,
nickname: client.name,
city: client.city,
street: client.street,
postalCode: client.postcode,
provinceFk: client.provinceFk,
isEqualizated: client.isEqualizated,
isActive: true
}, {transaction});
await client.updateAttributes({
defaultAddressFk: address.id
}, {transaction});
await transaction.commit(); await transaction.commit();
return newClient; return client;
} catch (e) { } catch (e) {
await transaction.rollback(); await transaction.rollback();
throw e; throw e;

View File

@ -8,9 +8,11 @@ describe('Client addressesPropagateRe', () => {
await client.updateAttributes({hasToInvoiceByAddress: true}); await client.updateAttributes({hasToInvoiceByAddress: true});
}); });
afterAll(async() => { afterAll(async done => {
await app.models.Address.update({clientFk: 101}, {isEqualizated: false}); await app.models.Address.update({clientFk: 101}, {isEqualizated: false});
await client.updateAttributes({hasToInvoiceByAddress: true}); await client.updateAttributes({hasToInvoiceByAddress: true});
done();
}); });
it('should propagate the isEqualizated on both addresses of Mr Wayne and set hasToInvoiceByAddress to false', async() => { it('should propagate the isEqualizated on both addresses of Mr Wayne and set hasToInvoiceByAddress to false', async() => {

View File

@ -3,9 +3,11 @@ const app = require('vn-loopback/server/server');
describe('Client confirmTransaction', () => { describe('Client confirmTransaction', () => {
const transactionId = 2; const transactionId = 2;
afterAll(async() => { afterAll(async done => {
await app.models.Client.rawSql(` await app.models.Client.rawSql(`
CALL hedera.tpvTransactionUndo(?)`, [transactionId]); CALL hedera.tpvTransactionUndo(?)`, [transactionId]);
done();
}); });
it('should call confirmTransaction() method to mark transaction as confirmed', async() => { it('should call confirmTransaction() method to mark transaction as confirmed', async() => {

View File

@ -3,22 +3,16 @@ const app = require('vn-loopback/server/server');
describe('Client Create', () => { describe('Client Create', () => {
const clientName = 'Wade'; const clientName = 'Wade';
const AccountName = 'Deadpool'; const AccountName = 'Deadpool';
/* beforeAll(async() => {
let address = await app.models.Address.findOne({where: {nickname: clientName}});
let client = await app.models.Client.findOne({where: {name: clientName}});
let account = await app.models.Account.findOne({where: {name: AccountName}});
await app.models.Address.destroyById(address.id);
await app.models.Client.destroyById(client.id);
await app.models.Account.destroyById(account.id);
}); */
afterAll(async() => { afterAll(async done => {
let address = await app.models.Address.findOne({where: {nickname: clientName}}); let address = await app.models.Address.findOne({where: {nickname: clientName}});
let client = await app.models.Client.findOne({where: {name: clientName}}); let client = await app.models.Client.findOne({where: {name: clientName}});
let account = await app.models.Account.findOne({where: {name: AccountName}}); let account = await app.models.Account.findOne({where: {name: AccountName}});
await app.models.Address.destroyById(address.id); await app.models.Address.destroyById(address.id);
await app.models.Client.destroyById(client.id); await app.models.Client.destroyById(client.id);
await app.models.Account.destroyById(account.id); await app.models.Account.destroyById(account.id);
done();
}); });
let newAccount = { let newAccount = {
@ -26,7 +20,9 @@ describe('Client Create', () => {
email: 'Deadpool@marvel.com', email: 'Deadpool@marvel.com',
fi: '16195279J', fi: '16195279J',
name: 'Wade', name: 'Wade',
socialName: 'Deadpool Marvel' socialName: 'Deadpool Marvel',
street: 'Wall Street',
city: 'New York'
}; };
it(`should not find Deadpool as he's not created yet`, async() => { it(`should not find Deadpool as he's not created yet`, async() => {

View File

@ -1,12 +1,14 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Client updateFiscalData', () => { describe('Client updateFiscalData', () => {
afterAll(async() => { afterAll(async done => {
let ctxOfAdmin = {req: {accessToken: {userId: 5}}}; let ctxOfAdmin = {req: {accessToken: {userId: 5}}};
let validparams = {postcode: 46460}; let validparams = {postcode: 46460};
let idWithDataChecked = 101; let idWithDataChecked = 101;
await app.models.Client.updateFiscalData(ctxOfAdmin, validparams, idWithDataChecked); await app.models.Client.updateFiscalData(ctxOfAdmin, validparams, idWithDataChecked);
done();
}); });
it('should return an error if the user is not administrative and the isTaxDataChecked value is true', async() => { it('should return an error if the user is not administrative and the isTaxDataChecked value is true', async() => {

View File

@ -78,9 +78,8 @@ module.exports = Self => {
} }
}, },
{ {
relation: 'addresses', relation: 'defaultAddress',
scope: { scope: {
where: {isDefaultAddress: true},
fields: ['nickname', 'street', 'city', 'postalCode'] fields: ['nickname', 'street', 'city', 'postalCode']
} }
}, },

View File

@ -3,8 +3,10 @@ const app = require('vn-loopback/server/server');
describe('Client createWithInsurance', () => { describe('Client createWithInsurance', () => {
let classificationId; let classificationId;
afterAll(async() => { afterAll(async done => {
await app.models.CreditClassification.destroyById(classificationId); await app.models.CreditClassification.destroyById(classificationId);
done();
}); });
it('should verify the classifications and insurances are untainted', async() => { it('should verify the classifications and insurances are untainted', async() => {
@ -24,7 +26,7 @@ describe('Client createWithInsurance', () => {
error = e; error = e;
}); });
expect(error.toString()).toBe("Error: ER_BAD_NULL_ERROR: Column 'client' cannot be null"); expect(error.toString()).toBe('Error: ER_BAD_NULL_ERROR: Column \'client\' cannot be null');
let classifications = await app.models.CreditClassification.find(); let classifications = await app.models.CreditClassification.find();
let insurances = await app.models.CreditInsurance.find(); let insurances = await app.models.CreditInsurance.find();

View File

@ -3,13 +3,8 @@ let getFinalState = require('vn-loopback/util/hook').getFinalState;
let isMultiple = require('vn-loopback/util/hook').isMultiple; let isMultiple = require('vn-loopback/util/hook').isMultiple;
module.exports = Self => { module.exports = Self => {
Self.validate('isDefaultAddress', isActive, // Methods
{message: 'Unable to default a disabled consignee'} require('../methods/address/createDefaultAddress')(Self);
);
function isActive(err) {
if (!this.isActive && this.isDefaultAddress) err();
}
Self.validateAsync('isEqualizated', cannotHaveET, { Self.validateAsync('isEqualizated', cannotHaveET, {
message: 'Cannot check Equalization Tax in this NIF/CIF' message: 'Cannot check Equalization Tax in this NIF/CIF'
@ -50,31 +45,26 @@ module.exports = Self => {
// Helpers // Helpers
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
const Client = Self.app.models.Client;
if (isMultiple(ctx)) return; if (isMultiple(ctx)) return;
let transaction = {};
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
let changes = ctx.data || ctx.instance; let changes = ctx.data || ctx.instance;
let finalState = getFinalState(ctx); let finalState = getFinalState(ctx);
if (changes.isActive == false && finalState.isDefaultAddress) const client = await Client.findById(finalState.clientFk, {
fields: ['isEqualizated', 'defaultAddressFk']
}, {transaction});
if (changes.isActive == false && client.defaultAddressFk === finalState.id)
throw new UserError('The default consignee can not be unchecked'); throw new UserError('The default consignee can not be unchecked');
if (changes.isDefaultAddress == true && finalState.isActive != false) { // Propagate client isEqualizated to all addresses
let filter = { if (ctx.isNewInstance == true)
clientFk: finalState.clientFk, ctx.instance.isEqualizated = client.isEqualizated;
isDefaultAddress: {neq: false}
};
await Self.updateAll(filter, {isDefaultAddress: false});
}
if (ctx.isNewInstance == true) {
let filter = {
where: {
id: ctx.instance.clientFk
},
fields: ['isEqualizated']
};
let findOneResponse = await Self.app.models.Client.findOne(filter);
ctx.instance.isEqualizated = findOneResponse.isEqualizated;
}
}); });
}; };

View File

@ -42,9 +42,6 @@
"isActive": { "isActive": {
"type": "boolean" "type": "boolean"
}, },
"isDefaultAddress": {
"type": "boolean"
},
"longitude": { "longitude": {
"type": "Number" "type": "Number"
}, },

View File

@ -1,3 +1,4 @@
let request = require('request-promise-native');
let UserError = require('vn-loopback/util/user-error'); let UserError = require('vn-loopback/util/user-error');
let getFinalState = require('vn-loopback/util/hook').getFinalState; let getFinalState = require('vn-loopback/util/hook').getFinalState;
let isMultiple = require('vn-loopback/util/hook').isMultiple; let isMultiple = require('vn-loopback/util/hook').isMultiple;
@ -21,6 +22,14 @@ module.exports = Self => {
// Validations // Validations
Self.validatesPresenceOf('street', {
message: 'Street cannot be empty'
});
Self.validatesPresenceOf('city', {
message: 'City cannot be empty'
});
Self.validatesUniquenessOf('fi', { Self.validatesUniquenessOf('fi', {
message: 'TIN must be unique' message: 'TIN must be unique'
}); });
@ -90,15 +99,6 @@ module.exports = Self => {
err(); err();
} }
Self.validate('isVies', hasEquTax, {
message: 'Cannot check VIES and Equalization Tax'
});
function hasEquTax(err) {
if (this.isVies && this.isEqualizated)
err();
}
Self.validate('isEqualizated', cannotHaveET, { Self.validate('isEqualizated', cannotHaveET, {
message: 'Cannot check Equalization Tax in this NIF/CIF' message: 'Cannot check Equalization Tax in this NIF/CIF'
}); });
@ -133,6 +133,19 @@ module.exports = Self => {
done(); done();
} }
Self.validateAsync('defaultAddressFk', isActive,
{message: 'Unable to default a disabled consignee'}
);
async function isActive(err, done) {
if (!this.defaultAddressFk)
return done();
const address = await Self.app.models.Address.findById(this.defaultAddressFk);
if (address && !address.isActive) err();
done();
}
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
let changes = ctx.data || ctx.instance; let changes = ctx.data || ctx.instance;
let orgData = ctx.currentInstance; let orgData = ctx.currentInstance;
@ -167,6 +180,41 @@ module.exports = Self => {
} }
}); });
Self.observe('after save', async ctx => {
if (ctx.isNewInstance) return;
const hookState = ctx.hookState;
const newInstance = hookState.newInstance;
const oldInstance = hookState.oldInstance;
const instance = ctx.instance;
const payMethodChanged = oldInstance.payMethodFk != newInstance.payMethodFk;
const ibanChanged = oldInstance.iban != newInstance.iban;
const dueDayChanged = oldInstance.dueDay != newInstance.dueDay;
if (payMethodChanged || ibanChanged || dueDayChanged) {
const message = `La forma de pago del cliente con id ${instance.id} ha cambiado`;
const salesPersonFk = instance.salesPersonFk;
if (salesPersonFk) {
await Self.app.models.Message.send(ctx, {
recipientFk: salesPersonFk,
message: message
});
}
const options = {
method: 'POST',
uri: 'http://127.0.0.1:3000/api/email/payment-update',
body: {
clientFk: instance.id
},
json: true
};
await request(options);
}
});
async function validateCreditChange(ctx, finalState) { async function validateCreditChange(ctx, finalState) {
let models = Self.app.models; let models = Self.app.models;
let userId = ctx.options.accessToken.userId; let userId = ctx.options.accessToken.userId;

View File

@ -31,10 +31,12 @@
"type": "string" "type": "string"
}, },
"street": { "street": {
"type": "string" "type": "string",
"required": true
}, },
"city": { "city": {
"type": "string" "type": "string",
"required": true
}, },
"postcode": { "postcode": {
"type": "string" "type": "string"
@ -114,7 +116,7 @@
"type": "string" "type": "string"
}, },
"created": { "created": {
"type": "date" "type": "Date"
} }
}, },
"relations": { "relations": {

View File

@ -3,14 +3,13 @@ const app = require('vn-loopback/server/server');
describe('loopback model address', () => { describe('loopback model address', () => {
let createdAddressId; let createdAddressId;
afterAll(async() => { afterAll(async done => {
let address = await app.models.Address.findById(1);
let client = await app.models.Client.findById(101); let client = await app.models.Client.findById(101);
await address.updateAttribute('isDefaultAddress', true);
await app.models.Address.destroyById(createdAddressId); await app.models.Address.destroyById(createdAddressId);
await client.updateAttribute('isEqualizated', false); await client.updateAttribute('isEqualizated', false);
done();
}); });
describe('observe()', () => { describe('observe()', () => {
@ -28,24 +27,6 @@ describe('loopback model address', () => {
expect(error).toBeDefined(); expect(error).toBeDefined();
}); });
it('should set isDefaultAddress to false of all the addresses for a given client', async() => {
let previousDefaultAddress = await app.models.Address.findById(1);
expect(previousDefaultAddress.isDefaultAddress).toBeTruthy();
let address = await app.models.Address.findById(121);
expect(address.isDefaultAddress).toBeFalsy();
let defaultAddress = await address.updateAttribute('isDefaultAddress', true);
expect(defaultAddress.isDefaultAddress).toBeTruthy();
let noLongerDefaultAddress = await app.models.Address.findById(1);
expect(noLongerDefaultAddress.isDefaultAddress).toBeFalsy();
});
it('should set isEqualizated to true of a given Client to trigger any new address to have it', async() => { it('should set isEqualizated to true of a given Client to trigger any new address to have it', async() => {
let client = await app.models.Client.findById(101); let client = await app.models.Client.findById(101);
@ -58,7 +39,6 @@ describe('loopback model address', () => {
agencyModeFk: 5, agencyModeFk: 5,
city: 'here', city: 'here',
isActive: true, isActive: true,
isDefaultAddress: false,
mobile: '555555555', mobile: '555555555',
nickname: 'Test address', nickname: 'Test address',
phone: '555555555', phone: '555555555',

View File

@ -1,15 +1,15 @@
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
url="/client/api/Addresses" url="/client/api/Addresses/createDefaultAddress"
id-field="id" id-field="id"
data="$ctrl.address" data="$ctrl.data"
save="post" save="post"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="watcher.submitGo('client.card.address.index')" compact> <form name="form" ng-submit="$ctrl.onSubmit()" compact>
<vn-card pad-large> <vn-card pad-large>
<vn-horizontal pad-small-v> <vn-horizontal pad-small-v>
<vn-check vn-one label="Default" field="$ctrl.address.isDefaultAddress"></vn-check> <vn-check vn-one label="Default" field="$ctrl.data.isDefaultAddress"></vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.nickname" vn-focus></vn-textfield> <vn-textfield vn-one label="Consignee" field="$ctrl.address.nickname" vn-focus></vn-textfield>

View File

@ -1,16 +1,35 @@
import ngModule from '../../module'; import ngModule from '../../module';
export default class Controller { export default class Controller {
constructor($state) { constructor($scope, $state) {
this.address = { this.$scope = $scope;
this.$state = $state;
this.data = {
address: {
clientFk: parseInt($state.params.id), clientFk: parseInt($state.params.id),
isActive: true isActive: true
},
isDefaultAddress: false
}; };
this.address = this.data.address;
}
onSubmit() {
this.$scope.watcher.submit().then(res => {
if (res.data && this.data.isDefaultAddress)
this.client.defaultAddressFk = res.data.id;
this.$state.go('client.card.address.index');
});
} }
} }
Controller.$inject = ['$state'];
Controller.$inject = ['$scope', '$state'];
ngModule.component('vnClientAddressCreate', { ngModule.component('vnClientAddressCreate', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller,
bindings: {
client: '<'
}
}); });

View File

@ -1,4 +1,5 @@
import './index'; import './index';
import watcher from 'core/mocks/watcher';
describe('Client', () => { describe('Client', () => {
describe('Component vnClientAddressCreate', () => { describe('Component vnClientAddressCreate', () => {
@ -13,11 +14,40 @@ describe('Client', () => {
$state = _$state_; $state = _$state_;
$state.params.id = '1234'; $state.params.id = '1234';
controller = $componentController('vnClientAddressCreate', {$state}); controller = $componentController('vnClientAddressCreate', {$state});
controller.$scope.watcher = watcher;
controller.$scope.watcher.submit = () => {
return {
then: callback => {
callback({data: {id: 124}});
}
};
};
controller.client = {id: 101, defaultAddressFk: 121};
})); }));
it('should define and set address property', () => { it('should define and set address property', () => {
expect(controller.address.clientFk).toBe(1234); expect(controller.data.address.clientFk).toBe(1234);
expect(controller.address.isActive).toBe(true); expect(controller.data.address.isActive).toBe(true);
});
describe('onSubmit()', () => {
it('should perform a PATCH and not set value to defaultAddressFk property', () => {
spyOn(controller.$state, 'go');
controller.data.isDefaultAddress = false;
controller.onSubmit();
expect(controller.client.defaultAddressFk).toEqual(121);
expect(controller.$state.go).toHaveBeenCalledWith('client.card.address.index');
});
it('should perform a PATCH and set a value to defaultAddressFk property', () => {
spyOn(controller.$state, 'go');
controller.data.isDefaultAddress = true;
controller.onSubmit();
expect(controller.client.defaultAddressFk).toEqual(124);
expect(controller.$state.go).toHaveBeenCalledWith('client.card.address.index');
});
}); });
}); });
}); });

View File

@ -3,24 +3,27 @@
url="/client/api/Addresses" url="/client/api/Addresses"
filter="::$ctrl.filter" filter="::$ctrl.filter"
link="{clientFk: $ctrl.$stateParams.id}" link="{clientFk: $ctrl.$stateParams.id}"
data="addresses" data="$ctrl.addresses"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<div compact> <div compact>
<vn-card pad-large> <vn-card pad-large>
<vn-horizontal <vn-horizontal
ng-repeat="address in addresses" ng-repeat="address in $ctrl.addresses"
class="pad-medium-top" class="pad-medium-top"
style="align-items: center;"> style="align-items: center;">
<vn-one <vn-one
border-radius border-radius
class="pad-small border-solid" class="pad-small border-solid"
ng-class="{'item-hightlight': address.isDefaultAddress, 'item-disabled': !address.isActive && !address.isDefaultAddress}"> ng-class="{
'item-hightlight': $ctrl.isDefaultAddress(address),
'item-disabled': !address.isActive && !$ctrl.isDefaultAddress(address)
}">
<vn-horizontal style="align-items: center;"> <vn-horizontal style="align-items: center;">
<vn-none pad-medium-h> <vn-none pad-medium-h>
<vn-icon-button <vn-icon-button
icon="star" icon="star"
ng-if="address.isDefaultAddress"> ng-if="$ctrl.isDefaultAddress(address)">
</vn-icon-button> </vn-icon-button>
<vn-icon-button <vn-icon-button
ng-if="!address.isActive" ng-if="!address.isActive"
@ -28,7 +31,7 @@
vn-tooltip="Active first to set as default"> vn-tooltip="Active first to set as default">
</vn-icon-button> </vn-icon-button>
<vn-icon-button <vn-icon-button
ng-if="address.isActive && !address.isDefaultAddress" ng-if="address.isActive && !$ctrl.isDefaultAddress(address)"
icon="star_border" icon="star_border"
vn-tooltip="Set as default" vn-tooltip="Set as default"
ng-click="$ctrl.setDefault(address)"> ng-click="$ctrl.setDefault(address)">

View File

@ -9,22 +9,60 @@ class Controller {
include: { include: {
observations: 'observationType' observations: 'observationType'
}, },
order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'] order: ['isActive DESC', 'nickname ASC']
}; };
} }
setDefault(address) { get client() {
if (address.isActive) { return this._client;
let params = {isDefaultAddress: true};
this.$http.patch(`/client/api/Addresses/${address.id}`, params).then(
() => this.$scope.model.refresh()
);
} }
set client(value) {
this._client = value;
this.sortAddresses();
}
get addresses() {
return this._addresses;
}
set addresses(value) {
this._addresses = value;
this.sortAddresses();
}
setDefault(address) {
let query = `/client/api/Clients/${this.$stateParams.id}`;
let params = {defaultAddressFk: address.id};
this.$http.patch(query, params).then(res => {
if (res.data) {
this.client.defaultAddressFk = res.data.defaultAddressFk;
this.sortAddresses();
}
});
}
isDefaultAddress(address) {
if (!this.client) return;
return this.client.defaultAddressFk === address.id;
}
/**
* Sort address by default address
*/
sortAddresses() {
if (!this.client || !this.addresses) return;
this.$scope.model.data = this.addresses.sort((a, b) => {
return this.isDefaultAddress(b) - this.isDefaultAddress(a);
});
} }
} }
Controller.$inject = ['$http', '$scope', '$stateParams']; Controller.$inject = ['$http', '$scope', '$stateParams'];
ngModule.component('vnClientAddressIndex', { ngModule.component('vnClientAddressIndex', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller,
bindings: {
client: '<'
}
}); });

View File

@ -1,36 +1,69 @@
import './index'; import './index';
import crudModel from 'core/mocks/crud-model';
describe('Client', () => { describe('Client', () => {
describe('Component vnClientAddressIndex', () => { describe('Component vnClientAddressIndex', () => {
let controller; let controller;
let $scope; let $scope;
let $state; let $stateParams;
let $httpBackend; let $httpBackend;
beforeEach(ngModule('client')); beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_, _$httpBackend_) => { beforeEach(angular.mock.inject(($componentController, $rootScope, _$stateParams_, _$httpBackend_) => {
$state = _$state_; $stateParams = _$stateParams_;
$stateParams.id = 1;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$scope.model = { controller = $componentController('vnClientAddressIndex', {$stateParams, $scope});
refresh: () => {} controller.client = {id: 101, defaultAddressFk: 121};
}; controller.$scope.model = crudModel;
controller = $componentController('vnClientAddressIndex', {$state, $scope});
})); }));
describe('setDefault()', () => { describe('setDefault()', () => {
it('should perform a PATCH if the address is active and call the refresh method', () => { it('should perform a PATCH and set a value to defaultAddressFk property', () => {
spyOn($scope.model, 'refresh'); spyOn(controller, 'sortAddresses');
let address = {id: 1};
let data = {defaultAddressFk: address.id};
let expectedResult = {defaultAddressFk: address.id};
let address = {id: 1, isActive: true}; $httpBackend.when('PATCH', `/client/api/Clients/1`, data).respond(200, expectedResult);
$httpBackend.expect('PATCH', `/client/api/Clients/1`, data);
$httpBackend.when('PATCH', `/client/api/Addresses/1`).respond(200);
$httpBackend.expect('PATCH', `/client/api/Addresses/1`);
controller.setDefault(address); controller.setDefault(address);
$httpBackend.flush(); $httpBackend.flush();
expect($scope.model.refresh).toHaveBeenCalledWith(); expect(controller.client.defaultAddressFk).toEqual(1);
expect(controller.sortAddresses).toHaveBeenCalledWith();
});
});
describe('isDefaultAddress()', () => {
it('should return true if a passed address is the current default one', () => {
let address = {id: 121};
let result = controller.isDefaultAddress(address);
expect(result).toBeTruthy();
});
it('should return false if a passed address is the current default one', () => {
let address = {id: 1};
let result = controller.isDefaultAddress(address);
expect(result).toBeFalsy();
});
});
describe('sortAddresses()', () => {
it('should return an array of addresses sorted by client defaultAddressFk', () => {
controller.client.defaultAddressFk = 123;
controller.addresses = [
{id: 121, nickname: 'My address one'},
{id: 122, nickname: 'My address two'},
{id: 123, nickname: 'My address three'}];
controller.sortAddresses();
expect(controller.addresses[0].id).toEqual(123);
}); });
}); });
}); });

View File

@ -33,16 +33,10 @@ export default class Controller {
this.$scope.watcher.submit().then(() => { this.$scope.watcher.submit().then(() => {
if (shouldNotify) if (shouldNotify)
this.notifyChanges(); this.vnApp.showMessage(this.$translate.instant('Notification sent!'));
}); });
} }
notifyChanges() {
this.$http.post(`/api/email/payment-update`, {clientFk: this.client.id}).then(
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
);
}
hasPaymethodChanges() { hasPaymethodChanges() {
let orgData = this.$scope.watcher.orgData; let orgData = this.$scope.watcher.orgData;

View File

@ -21,26 +21,6 @@ describe('Client', () => {
$scope.watcher.orgData = {id: 101, name: 'Client name', payMethodFk: 4}; $scope.watcher.orgData = {id: 101, name: 'Client name', payMethodFk: 4};
})); }));
describe('onSubmit()', () => {
it(`should call notifyChanges() if there are changes on payMethod data`, () => {
spyOn(controller, 'notifyChanges');
controller.client.payMethodFk = 5;
controller.onSubmit();
expect(controller.notifyChanges).toHaveBeenCalledWith();
});
});
describe('notifyChanges()', () => {
it(`should perform a GET query`, () => {
let params = {clientFk: 101};
$httpBackend.when('POST', `/api/email/payment-update`, params).respond(true);
$httpBackend.expect('POST', `/api/email/payment-update`, params);
controller.notifyChanges();
$httpBackend.flush();
});
});
describe('hasPaymethodChanges()', () => { describe('hasPaymethodChanges()', () => {
it(`should return true if there are changes on payMethod data`, () => { it(`should return true if there are changes on payMethod data`, () => {
controller.client.payMethodFk = 5; controller.client.payMethodFk = 5;

View File

@ -102,7 +102,10 @@
"url": "/create", "url": "/create",
"state": "client.card.address.create", "state": "client.card.address.create",
"component": "vn-client-address-create", "component": "vn-client-address-create",
"description": "New address" "description": "New address",
"params": {
"client": "$ctrl.client"
}
}, { }, {
"url": "/:addressId/edit", "url": "/:addressId/edit",
"state": "client.card.address.edit", "state": "client.card.address.edit",

View File

@ -124,13 +124,13 @@
<vn-one> <vn-one>
<h4 translate>Default address</h4> <h4 translate>Default address</h4>
<vn-label-value label="Name" <vn-label-value label="Name"
value="{{$ctrl.summary.addresses[0].nickname}}"> value="{{$ctrl.summary.defaultAddress.nickname}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Street" ellipsize="false" <vn-label-value label="Street" ellipsize="false"
value="{{$ctrl.summary.addresses[0].street}}"> value="{{$ctrl.summary.defaultAddress.street}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="City" <vn-label-value label="City"
value="{{$ctrl.summary.addresses[0].city}}"> value="{{$ctrl.summary.defaultAddress.city}}">
</vn-label-value> </vn-label-value>
</vn-one> </vn-one>
<vn-one> <vn-one>

View File

@ -23,6 +23,11 @@ module.exports = Self => {
type: 'String', type: 'String',
description: `If it's and integer searchs by id, otherwise it searchs by name`, description: `If it's and integer searchs by id, otherwise it searchs by name`,
http: {source: 'query'} http: {source: 'query'}
}, {
arg: 'id',
type: 'Integer',
description: 'Item id',
http: {source: 'query'}
}, { }, {
arg: 'categoryFk', arg: 'categoryFk',
type: 'Integer', type: 'Integer',
@ -33,6 +38,21 @@ module.exports = Self => {
type: 'Integer', type: 'Integer',
description: 'Type id', description: 'Type id',
http: {source: 'query'} http: {source: 'query'}
}, {
arg: 'hasVisible',
type: 'Boolean',
description: 'Whether the the item has o not visible',
http: {source: 'query'}
}, {
arg: 'isActive',
type: 'Boolean',
description: 'Whether the the item is o not active',
http: {source: 'query'}
}, {
arg: 'salesPersonFk',
type: 'Integer',
description: 'The buyer of the item',
http: {source: 'query'}
} }
], ],
returns: { returns: {
@ -45,7 +65,7 @@ module.exports = Self => {
} }
}); });
Self.filter = async(ctx, filter, tags) => { Self.filter = async(ctx, filter) => {
let conn = Self.dataSource.connector; let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => { let where = buildFilter(ctx.args, (param, value) => {
@ -60,47 +80,82 @@ module.exports = Self => {
return {'i.description': {like: `%${value}%`}}; return {'i.description': {like: `%${value}%`}};
case 'categoryFk': case 'categoryFk':
return {'ic.id': value}; return {'ic.id': value};
case 'salesPersonFk':
return {'t.workerFk': value};
case 'typeFk': case 'typeFk':
return {'t.id': value}; return {'i.typeFk': value};
case 'isActive':
return {'i.isActive': value};
} }
}); });
filter = mergeFilters(ctx.args.filter, {where}); filter = mergeFilters(ctx.args.filter, {where});
let stmt = new ParameterizedSQL( let stmts = [];
let stmt;
if (ctx.args.hasVisible === true)
stmts.push('CALL cache.visible_refresh(@visibleCalc, true, 1)');
stmt = new ParameterizedSQL(
`SELECT i.id, i.image, i.name, i.description, `SELECT i.id, i.image, i.name, i.description,
i.size, i.tag5, i.value5, i.tag6, i.value6, i.size, i.tag5, i.value5, i.tag6, i.value6,
i.tag7, i.value7, i.tag8, i.value8, i.isActive, i.tag7, i.value7, i.tag8, i.value8,
i.tag9, i.value9, i.tag10, i.value10, i.isActive,
t.name type, u.nickname userNickname, t.name type, u.nickname userNickname,
t.name type, u.id userId, t.name type, u.id userId,
intr.description AS intrastat, i.stems, intr.description AS intrastat, i.stems,
ori.code AS origin, t.name AS type, ori.code AS origin, t.name AS type,
ic.name AS category, i.density, tc.description AS taxClass ic.name AS category, i.density, tc.description AS taxClass,
b.grouping, b.packing, itn.code AS niche
FROM item i FROM item i
JOIN itemType t ON t.id = i.typeFk LEFT JOIN itemType t ON t.id = i.typeFk
LEFT JOIN itemCategory ic ON ic.id = t.categoryFk LEFT JOIN itemCategory ic ON ic.id = t.categoryFk
JOIN worker w ON w.id = t.workerFk LEFT JOIN worker w ON w.id = t.workerFk
JOIN account.user u ON u.id = w.userFk LEFT JOIN account.user u ON u.id = w.userFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN producer pr ON pr.id = i.producerFk LEFT JOIN producer pr ON pr.id = i.producerFk
LEFT JOIN origin ori ON ori.id = i.originFk LEFT JOIN origin ori ON ori.id = i.originFk
LEFT JOIN taxClass tc ON tc.id = i.taxClassFk` LEFT JOIN taxClass tc ON tc.id = i.taxClassFk
LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = t.warehouseFk
LEFT JOIN vn.buy b ON b.id = lb.buy_id
LEFT JOIN itemPlacement itn ON itn.itemFk = i.id AND itn.warehouseFk = t.warehouseFk`
); );
if (ctx.args.hasVisible === true) {
let joinAvailable = new ParameterizedSQL(
`JOIN cache.visible v
ON v.item_id = i.id AND v.calc_id = @visibleCalc`
);
stmt.merge(joinAvailable);
}
if (ctx.args.tags) { if (ctx.args.tags) {
let i = 1; let i = 1;
for (let tag of ctx.args.tags) { for (const tag of ctx.args.tags) {
if (tag.value == null) continue; const tAlias = `it${i++}`;
let tAlias = `it${i++}`;
if (tag.tagFk) {
stmt.merge({ stmt.merge({
sql: `JOIN itemTag ${tAlias} ON ${tAlias}.itemFk = i.id sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id
AND ${tAlias}.tagFk = ? AND ${tAlias}.tagFk = ?
AND ${tAlias}.value = ?`, AND ${tAlias}.value LIKE ?`,
params: [tag.tagFk, tag.value] params: [tag.tagFk, `%${tag.value}%`],
});
} else {
stmt.merge({
sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id
AND ${tAlias}.value LIKE ?`,
params: [`%${tag.value}%`],
}); });
} }
} }
}
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));
return Self.rawStmt(stmt); let itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
return itemsIndex === 0 ? result : result[itemsIndex];
}; };
}; };

View File

@ -3,8 +3,10 @@ const app = require('vn-loopback/server/server');
describe('item new()', () => { describe('item new()', () => {
let item; let item;
afterAll(async() => { afterAll(async done => {
await app.models.Item.destroyById(item.id); await app.models.Item.destroyById(item.id);
done();
}); });
it('should create a new item, adding the name as a tag', async() => { it('should create a new item, adding the name as a tag', async() => {

View File

@ -6,8 +6,10 @@ describe('regularize()', () => {
const trashAddress = 11; const trashAddress = 11;
let trashTicket; let trashTicket;
afterAll(async () => { afterAll(async done => {
await app.models.Ticket.destroyById(trashTicket.id); await app.models.Ticket.destroyById(trashTicket.id);
done();
}); });
it('should create a new ticket and add a line', async() => { it('should create a new ticket and add a line', async() => {

View File

@ -1,10 +1,12 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('item updateTaxes()', () => { describe('item updateTaxes()', () => {
afterAll(async() => { afterAll(async done => {
let taxesInFixtures = [{id: 3, taxClassFk: 1}]; let taxesInFixtures = [{id: 3, taxClassFk: 1}];
await app.models.Item.updateTaxes(taxesInFixtures); await app.models.Item.updateTaxes(taxesInFixtures);
done();
}); });
it('should throw an error if the taxClassFk is blank', async() => { it('should throw an error if the taxClassFk is blank', async() => {

View File

@ -57,6 +57,13 @@
value="{{tag.value}}"> value="{{tag.value}}">
</vn-label-value> </vn-label-value>
</div> </div>
<div class="icons">
<vn-icon
vn-tooltip="Item inactive"
icon="icon-unavailable"
ng-class="{bright: $ctrl.item.isActive == false}">
</vn-icon>
</div>
<div class="quicklinks"> <div class="quicklinks">
<a ng-if="$ctrl.quicklinks.btnOne" <a ng-if="$ctrl.quicklinks.btnOne"
vn-tooltip="{{::$ctrl.quicklinks.btnOne.tooltip}}" vn-tooltip="{{::$ctrl.quicklinks.btnOne.tooltip}}"

View File

@ -1,2 +1,3 @@
Regularize stock: Regularizar stock Regularize stock: Regularizar stock
Type the visible quantity: Introduce la cantidad visible Type the visible quantity: Introduce la cantidad visible
Item inactive: Artículo inactivo

View File

@ -3,10 +3,39 @@
<vn-auto> <vn-auto>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !fetchedTag.value}" ng-class="::{empty: !$ctrl.item.value5}"
ng-repeat="fetchedTag in $ctrl.tags track by $index" title="{{::$ctrl.item.tag5}}: {{::$ctrl.item.value5}}">
title="{{::fetchedTag.name}}: {{::fetchedTag.value}}"> {{::$ctrl.item.value5}}
{{::fetchedTag.value}} </section>
<section
class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value6}"
title="{{::$ctrl.item.tag6}}: {{::$ctrl.item.value6}}">
{{::$ctrl.item.value6}}
</section>
<section
class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value7}"
title="{{::$ctrl.item.tag7}}: {{::$ctrl.item.value7}}">
{{::$ctrl.item.value7}}
</section>
<section
class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value8}"
title="{{::$ctrl.item.tag8}}: {{::$ctrl.item.value8}}">
{{::$ctrl.item.value8}}
</section>
<section
class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value9}"
title="{{::$ctrl.item.tag9}}: {{::$ctrl.item.value9}}">
{{::$ctrl.item.value9}}
</section>
<section
class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value10}"
title="{{::$ctrl.item.tag10}}: {{::$ctrl.item.value10}}">
{{::$ctrl.item.value10}}
</section> </section>
</vn-auto> </vn-auto>
</vn-horizontal> </vn-horizontal>

View File

@ -1,52 +1,8 @@
import ngModule from '../module'; import ngModule from '../module';
import './style.scss'; import './style.scss';
export default class FetchedTags {
set item(value) {
if (value) {
let tags = [];
for (let i = 5; i < 5 + this.maxLength; i++) {
if (value['tag' + i]) {
let tagValue = value['value' + i];
let tagKey = value['tag' + i];
tags.push({name: tagKey, value: tagValue, position: i - 5});
}
}
this.tags = tags;
}
this._item = value;
}
get item() {
return this._item;
}
set tags(value) {
if (value) {
let organizedTags = new Array(parseInt(this.maxLength));
for (let i = 0; i < this.maxLength; i++) {
let organizedTag = {};
for (let j = 0; j < value.length; j++) {
if (value[j].position === i) {
organizedTag.name = value[j].name;
organizedTag.value = value[j].value;
}
organizedTags[i] = JSON.parse(JSON.stringify(organizedTag));
}
}
this._tags = organizedTags;
}
}
get tags() {
return this._tags;
}
}
ngModule.component('vnFetchedTags', { ngModule.component('vnFetchedTags', {
template: require('./index.html'), template: require('./index.html'),
controller: FetchedTags,
bindings: { bindings: {
maxLength: '<', maxLength: '<',
item: '<', item: '<',

View File

@ -22,13 +22,17 @@
<vn-tr> <vn-tr>
<vn-th th-id="picture"></vn-th> <vn-th th-id="picture"></vn-th>
<vn-th field="id" number>Id</vn-th> <vn-th field="id" number>Id</vn-th>
<vn-th th-id="grouping" number>Grouping</vn-th>
<vn-th th-id="packing" number>Packing</vn-th>
<vn-th th-id="description" style="text-align: center">Description</vn-th> <vn-th th-id="description" style="text-align: center">Description</vn-th>
<vn-th th-id="stems" number>Stems</vn-th> <vn-th th-id="stems" number>Stems</vn-th>
<vn-th th-id="size"number>Size</vn-th>
<vn-th th-id="niche"number>Niche</vn-th>
<vn-th th-id="type">Type</vn-th> <vn-th th-id="type">Type</vn-th>
<vn-th th-id="category">Category</vn-th> <vn-th th-id="category">Category</vn-th>
<vn-th th-id="intrastat">Intrastat</vn-th> <vn-th th-id="intrastat">Intrastat</vn-th>
<vn-th th-id="origin">Origin</vn-th> <vn-th th-id="origin">Origin</vn-th>
<vn-th th-id="salesperson">Sales person</vn-th> <vn-th th-id="salesperson">Buyer</vn-th>
<vn-th th-id="density" number>Density</vn-th> <vn-th th-id="density" number>Density</vn-th>
<vn-th th-id="taxClass">Tax class</vn-th> <vn-th th-id="taxClass">Tax class</vn-th>
<vn-th th-id="active" shrink>Active</vn-th> <vn-th th-id="active" shrink>Active</vn-th>
@ -53,6 +57,8 @@
{{::item.id | zeroFill:6}} {{::item.id | zeroFill:6}}
</span> </span>
</vn-td> </vn-td>
<vn-td number>{{::item.grouping | dashIfEmpty}}</vn-td>
<vn-td number>{{::item.packing | dashIfEmpty}}</vn-td>
<vn-td expand> <vn-td expand>
<vn-fetched-tags <vn-fetched-tags
max-length="6" max-length="6"
@ -61,6 +67,8 @@
</vn-fetched-tags> </vn-fetched-tags>
</vn-td> </vn-td>
<vn-td number>{{::item.stems}}</vn-td> <vn-td number>{{::item.stems}}</vn-td>
<vn-td number>{{::item.size}}</vn-td>
<vn-td number>{{::item.niche}}</vn-td>
<vn-td>{{::item.type}}</vn-td> <vn-td>{{::item.type}}</vn-td>
<vn-td>{{::item.category}}</vn-td> <vn-td>{{::item.category}}</vn-td>
<vn-td>{{::item.intrastat}}</vn-td> <vn-td>{{::item.intrastat}}</vn-td>

View File

@ -2,7 +2,7 @@ import ngModule from '../module';
import './style.scss'; import './style.scss';
class Controller { class Controller {
constructor($http, $state, $scope) { constructor($http, $state, $scope, $stateParams) {
this.$http = $http; this.$http = $http;
this.$state = $state; this.$state = $state;
this.$ = $scope; this.$ = $scope;
@ -13,6 +13,14 @@ class Controller {
id: false, id: false,
actions: false actions: false
}; };
if (!$stateParams.q)
this.filter = {hasVisible: true, isActive: true};
}
$postLink() {
if (this.filter)
this.onSearch(this.filter);
} }
stopEvent(event) { stopEvent(event) {
@ -83,7 +91,7 @@ class Controller {
this.$.preview.show(); this.$.preview.show();
} }
} }
Controller.$inject = ['$http', '$state', '$scope']; Controller.$inject = ['$http', '$state', '$scope', '$stateParams'];
ngModule.component('vnItemIndex', { ngModule.component('vnItemIndex', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -46,6 +46,30 @@
label="Description" label="Description"
model="filter.description"> model="filter.description">
</vn-textfield> </vn-textfield>
<vn-autocomplete
vn-one
disabled="false"
field="filter.salesPersonFk"
url="/client/api/Clients/activeWorkersWithRole"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'employee'}"
label="Buyer">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-check
vn-one
label="With visible"
field="filter.hasVisible">
</vn-check>
<vn-check
vn-one
label="Active"
field="filter.isActive"
triple-state="true">
</vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal ng-repeat="itemTag in filter.tags"> <vn-horizontal ng-repeat="itemTag in filter.tags">
<vn-autocomplete <vn-autocomplete

View File

@ -2,8 +2,10 @@ const app = require('vn-loopback/server/server');
describe('order addToOrder()', () => { describe('order addToOrder()', () => {
let rowToDelete; let rowToDelete;
afterAll(async() => { afterAll(async done => {
await app.models.OrderRow.removes({rows: [rowToDelete], actualOrderId: 20}); await app.models.OrderRow.removes({rows: [rowToDelete], actualOrderId: 20});
done();
}); });
it('should add a row to a given order', async() => { it('should add a row to a given order', async() => {

View File

@ -4,10 +4,12 @@ describe('order removes()', () => {
let row; let row;
let newRow; let newRow;
beforeAll(async() => { beforeAll(async done => {
row = await app.models.OrderRow.findOne({where: {id: 12}}); row = await app.models.OrderRow.findOne({where: {id: 12}});
row.id = null; row.id = null;
newRow = await app.models.OrderRow.create(row); newRow = await app.models.OrderRow.create(row);
done();
}); });
it('should throw an error if rows property is empty', async() => { it('should throw an error if rows property is empty', async() => {

View File

@ -0,0 +1,164 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Find all instances of the model matched by filter from the data source.',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}, {
arg: 'filter',
type: 'Object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}, {
arg: 'search',
type: 'String',
description: `If it's and integer searchs by id, otherwise it searchs by nickname`
}, {
arg: 'from',
type: 'Date',
description: `The from date`
}, {
arg: 'to',
type: 'Date',
description: `The to date`
}, {
arg: 'id',
type: 'Integer',
description: `The ticket id`
}, {
arg: 'clientFk',
type: 'Integer',
description: `The client id`
}, {
arg: 'ticketFk',
type: 'Integer',
description: `The ticket id`
}, {
arg: 'agencyModeFk',
type: 'Integer',
description: `The agency mode id`
}, {
arg: 'workerFk',
type: 'Integer',
description: `The salesperson id`
}, {
arg: 'isConfirmed',
type: 'Boolean',
description: `Order is confirmed`
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: '/filter',
verb: 'GET'
}
});
Self.filter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return {'o.id': value};
case 'from':
return {'o.date_send': {gte: value}};
case 'to':
return {'o.date_send': {lte: value}};
case 'workerFk':
return {'c.salesPersonFk': value};
case 'clientFk':
return {'o.customer_id': value};
case 'agencyModeFk':
return {'o.agency_id': value};
case 'sourceApp':
return {'o.source_app': value};
case 'ticketFk':
return {'ort.ticketFk': value};
case 'isConfirmed':
return {'o.confirmed': value ? 1 : 0};
case 'id':
param = `o.${param}`;
return {[param]: value};
}
});
filter = mergeFilters(filter, {where});
let stmts = [];
let stmt;
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.filter');
stmt = new ParameterizedSQL(
`CREATE TEMPORARY TABLE tmp.filter
(INDEX (id))
ENGINE = MEMORY
SELECT
o.id,
o.date_send landed,
o.date_make created,
o.customer_id clientFk,
o.agency_id agencyModeFk,
o.address_id addressFk,
o.company_id companyFk,
o.source_app sourceApp,
o.confirmed isConfirmed,
c.name clientName,
u.nickname workerNickname,
u.id userId,
co.code companyCode
FROM hedera.order o
LEFT JOIN address a ON a.id = o.address_id
LEFT JOIN agencyMode am ON am.id = o.agency_id
LEFT JOIN client c ON c.id = o.customer_id
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.userFk
LEFT JOIN company co ON co.id = o.company_id`);
if (ctx.args && ctx.args.ticketFk) {
stmt.merge({
sql: `LEFT JOIN orderTicket ort ON ort.orderFk = o.id`
});
}
stmt.merge(conn.makeSuffix(filter));
stmts.push(stmt);
stmts.push(`
CREATE TEMPORARY TABLE tmp.order
(INDEX (orderFk))
ENGINE = MEMORY
SELECT id AS orderFk
FROM tmp.filter`);
stmts.push('CALL hedera.orderGetTotal()');
let orderIndex = stmts.push(`
SELECT f.*, ot.*
FROM tmp.filter f
LEFT JOIN tmp.orderTotal ot ON ot.orderFk = f.id`) - 1;
stmts.push(`
DROP TEMPORARY TABLE
tmp.order,
tmp.orderTotal`);
stmts.push(
`DROP TEMPORARY TABLE
tmp.filter`);
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
return result[orderIndex];
};
};

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