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) => {
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 sender = await models.Account.findById(userId, transaction);
const recipient = await models.Account.findById(data.recipientFk, transaction);

View File

@ -55,13 +55,15 @@
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}, {
},
{
"property": "logout",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}, {
},
{
"property": "validateToken",
"accessType": "EXECUTE",
"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
#IMPORTANT Any changes in this file are to applyed to mirror file export-data.cmd
echo "USE \`account\`;" > 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

View File

@ -10,7 +10,7 @@ echo "[INFO] -> Imported ./dump/mysqlPlugins.sql"
mysql -u root -f < ./dump/mysqlPlugins.sql
# Import changes
for file in changes/*/*.sql; do
for file in changes/*.sql; do
echo "[INFO] -> Imported ./$file"
mysql -u root -fc < $file
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 `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')
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
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'),
(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
('LGN', 106, 'David Charles', 'Haller', 106),
('ANT', 107, 'Hank' , 'Pym' , 107),
@ -209,38 +211,65 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
( 103, 0, 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
(101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0),
(103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0),
(104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, 3333333333, 444444444, 1, 0, 109, 2, NULL, NULL, 0),
(105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, 5555555555, 666666666, 1, 0, 109, 2, NULL, NULL, 0),
(109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(118, 'address 18', 'Somewhere over the rainbow', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 109, 2, NULL, NULL, 0),
(121, 'address 21', 'the bat cave', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 101, 2, NULL, NULL, 0),
(122, 'address 22', 'NY roofs', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 102, 2, NULL, NULL, 0),
(123, 'address 23', 'The phone box', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 103, 2, NULL, NULL, 0),
(124, 'address 24', 'Stark tower', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 104, 2, NULL, NULL, 0),
(125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 105, 2, NULL, NULL, 0),
(126, 'address 26', 'Many places', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 106, 2, NULL, NULL, 0),
(127, 'address 27', 'Your pocket', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 107, 2, NULL, NULL, 0),
(128, 'address 28', 'Cerebro', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 108, 2, NULL, NULL, 0),
(129, 'address 29', 'Luke Cages Bar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 0, 110, 2, NULL, NULL, 0),
(130, 'address 30', 'Non valid address', 'Silla', 46460, 1, 1111111111, 222222222, 0, 0, 101, 2, NULL, NULL, 0);
(1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 1),
(2, 'Petter Parker', '20 Ingram Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 1),
(3, 'Clark Kent', '344 Clinton Street', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 1),
(4, 'Tony Stark', '10880 Malibu Point', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 1),
(5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 1),
(6, 'DavidCharlesHaller', 'Evil hideout', 'Silla', 46460, 1, 1111111111, 222222222, 1, 106, 2, NULL, NULL, 0, 1),
(7, 'Hank Pym', 'Anthill', 'Silla', 46460, 1, 1111111111, 222222222, 1, 107, 2, NULL, NULL, 0, 1),
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Silla', 46460, 1, 1111111111, 222222222, 1, 108, 2, NULL, NULL, 0, 1),
(9, 'Bruce Banner', 'Somewhere in New York', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 1),
(10, 'Jessica Jones', 'NYCC 2015 Poster', 'Silla', 46460, 1, 1111111111, 222222222, 1, 110, 2, NULL, NULL, 0, 1),
(11, 'Missing', 'The space', 'Silla', 46460, 1, 1111111111, 222222222, 1, 200, 2, NULL, NULL, 0, 1),
(12, 'Trash', 'New York city', 'Silla', 46460, 1, 1111111111, 222222222, 1, 400, 2, NULL, NULL, 0, 1),
(101, 'address 01', 'Somewhere in Thailand', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(102, 'address 02', 'Somewhere in Poland', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(103, 'address 03', 'Somewhere in Japan', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(104, 'address 04', 'Somewhere in Spain', 'Silla', 46460, 1, 3333333333, 444444444, 1, 109, 2, NULL, NULL, 0, 0),
(105, 'address 05', 'Somewhere in Potugal', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(106, 'address 06', 'Somewhere in UK', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(107, 'address 07', 'Somewhere in Valencia', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(108, 'address 08', 'Somewhere in Silla', 'Silla', 46460, 1, 5555555555, 666666666, 1, 109, 2, NULL, NULL, 0, 0),
(109, 'address 09', 'Somewhere in London', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(110, 'address 10', 'Somewhere in Algemesi', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(111, 'address 11', 'Somewhere in Carlet', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(112, 'address 12', 'Somewhere in Campanar', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(113, 'address 13', 'Somewhere in Malilla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(114, 'address 14', 'Somewhere in France', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(115, 'address 15', 'Somewhere in Birmingham', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(116, 'address 16', 'Somewhere in Scotland', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
(117, 'address 17', 'Somewhere in nowhere', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 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`)
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)),
(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()),
(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()),
(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()),
@ -593,11 +622,11 @@ INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
(5, 29, -18.72),
(5, 39, 0.02),
(6, 23, 6.5),
(7, 15, 0.29),
(7, 15, 0.2899),
(7, 28, 5.6),
(7, 29, -4.6),
(7, 39, 0.01),
(8, 15, 0.044),
(8, 15, 0.0435),
(8, 21, -0.004),
(8, 28, 20.72),
(8, 29, -19.72),
@ -624,7 +653,7 @@ INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
(12, 29, -19.72),
(12, 37, 2),
(12, 39, 0.01),
(13, 15, 0.29),
(13, 15, 0.2899),
(13, 28, 5.6),
(13, 29, -4.6),
(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),
(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),
(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`)
VALUES
( 1, 1, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 1, 442, 'Movimiento 1'),
( 2, 2, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 2, 442, 'Movimiento 2'),
( 3, 1, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 3, 442, 'Movimiento 3'),
( 4, 2, DATE_ADD(CURDATE(), INTERVAL -30 DAY), 4, 69, 'Movimiento 4');
( 1, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 442, 'Movimiento 1'),
( 2, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 442, 'Movimiento 2'),
( 3, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 3, 442, 'Movimiento 3'),
( 4, 2, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 69, 'Movimiento 4');
INSERT INTO `vn`.`agencyProvince`(`provinceFk`, `agencyFk`, `zone`, `warehouseFk`)
VALUES
@ -927,7 +956,7 @@ INSERT INTO `hedera`.`orderRowComponent`(`rowFk`, `componentFk`, `price`)
(5, 29, -18.72),
(5, 39, 0.02),
(6, 23, 6.5),
(7, 15, 0.29),
(7, 15, 0.2899),
(7, 28, 5.6),
(7, 29, -4.6),
(7, 39, 0.01),
@ -1126,5 +1155,3 @@ INSERT INTO `postgresql`.`business_labour`(`business_id`, `notes`, `department_i
VALUES
(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);

View File

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

View File

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

View File

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

View File

@ -31,6 +31,8 @@ export default {
name: `${components.vnTextfield}[name="name"]`,
taxNumber: `${components.vnTextfield}[name="fi"]`,
socialName: `${components.vnTextfield}[name="socialName"]`,
street: `${components.vnTextfield}[name="street"]`,
city: `${components.vnTextfield}[name="city"]`,
userName: `${components.vnTextfield}[name="userName"]`,
email: `${components.vnTextfield}[name="email"]`,
salesPersonAutocomplete: `vn-autocomplete[field="$ctrl.client.salesPersonFk"]`,
@ -149,6 +151,7 @@ export default {
},
clientLog: {
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',
lastModificationCurrentValue: 'vn-client-log vn-table vn-td.after'
@ -165,7 +168,7 @@ export default {
},
webPayment: {
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: {
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',
firstSaleZoomedImage: 'body > div > div > img',
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`,
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',
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',
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)',
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)`,
@ -468,6 +472,17 @@ export default {
secondClaimRedeliveryAutocomplete: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[field="claimDevelopment.claimRedeliveryFk"]',
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: {
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)`,

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
.write(selectors.createClientView.name, 'Carol Danvers')
.write(selectors.createClientView.socialName, 'AVG tax')
.write(selectors.createClientView.street, 'Many places')
.write(selectors.createClientView.city, 'Silla')
.clearInput(selectors.createClientView.email)
.write(selectors.createClientView.email, 'incorrect email format')
.waitToClick(selectors.createClientView.createButton)

View File

@ -58,7 +58,7 @@ describe('Client Edit fiscalData path', () => {
.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
.wait(selectors.clientFiscalData.socialNameInput)
.clearInput(selectors.clientFiscalData.socialNameInput)
@ -84,9 +84,17 @@ describe('Client Edit fiscalData path', () => {
.waitToClick(selectors.clientFiscalData.saveButton)
.waitForLastSnackbar();
expect(result).toEqual('Cannot check VIES and Equalization Tax');
expect(result).toEqual('Data saved!');
}, 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() => {
const result = await nightmare
.waitToClick(selectors.clientFiscalData.viesCheckbox)
@ -108,14 +116,6 @@ describe('Client Edit fiscalData path', () => {
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
it(`should click on the addresses button to access to the client's addresses`, async() => {
const url = await nightmare

View File

@ -4,6 +4,8 @@ import createNightmare from '../../helpers/nightmare';
describe('Client log path', () => {
const nightmare = createNightmare();
let date;
beforeAll(() => {
nightmare
.loginAndModule('employee', 'client')
@ -17,6 +19,7 @@ describe('Client log path', () => {
.write(selectors.clientBasicData.nameInput, 'this is a test')
.waitToClick(selectors.clientBasicData.saveButton)
.waitForLastSnackbar();
date = new Date();
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() => {
let lastModificationDate = await nightmare
.waitToGetProperty(selectors.clientLog.lastModificationDate, 'innerText');
let lastModificationPreviousValue = await nightmare
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
let lastModificationCurrentValue = await nightmare
.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 createNightmare from '../../helpers/nightmare';
// #860 E2E client/client/src/web-payment/index.js missing fixtures for another client
xdescribe('Client web Payment', () => {
describe('Client web Payment', () => {
const nightmare = createNightmare();
describe('as employee', () => {
beforeAll(() => {
nightmare
.loginAndModule('employee', 'client')
.accessToSearchResult('Bruce Wayne')
.accessToSearchResult('Tony Stark')
.accessToSection('client.card.webPayment');
});
@ -25,13 +24,14 @@ xdescribe('Client web Payment', () => {
beforeAll(() => {
nightmare
.loginAndModule('administrative', 'client')
.accessToSearchResult('Bruce Wayne')
.accessToSearchResult('Tony Stark')
.accessToSection('client.card.webPayment');
});
it('should be able to confirm payments', async() => {
let exists = await nightmare
.waitToClick(selectors.webPayment.confirmFirstPaymentButton)
.wait(selectors.webPayment.firstPaymentConfirmed)
.exists(selectors.webPayment.firstPaymentConfirmed);
expect(exists).toBeTruthy();

View File

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

View File

@ -15,21 +15,21 @@ describe('Ticket List sale path', () => {
const value = await nightmare
.waitToGetProperty(selectors.ticketSales.firstSaleColour, 'innerText');
expect(value).toContain('Red');
expect(value).toContain('Yellow');
});
it('should confirm the first ticket sale contains the lenght', async() => {
const value = await nightmare
.waitToGetProperty(selectors.ticketSales.firstSaleText, 'innerText');
expect(value).toContain('3');
expect(value).toContain('5');
});
it('should confirm the first ticket sale contains the price', async() => {
const value = await nightmare
.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() => {
@ -43,7 +43,7 @@ describe('Ticket List sale path', () => {
const value = await nightmare
.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() => {

View File

@ -73,7 +73,7 @@ describe('Ticket Edit basic data path', () => {
.wait(1900)
.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() => {

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
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', () => {
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() => {
const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleQuantityClearInput)
.waitToFocus(selectors.ticketSales.firstSaleQuantityCell)
.write(selectors.ticketSales.firstSaleQuantity, '9\u000d')
.waitForLastSnackbar();
@ -147,7 +147,7 @@ xdescribe('Ticket Edit sale path', () => {
it('should remove 1 from quantity', async() => {
const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleQuantityClearInput)
.waitToFocus(selectors.ticketSales.firstSaleQuantityCell)
.write(selectors.ticketSales.firstSaleQuantity, '4\u000d')
.waitForLastSnackbar();
@ -180,7 +180,6 @@ xdescribe('Ticket Edit sale path', () => {
it('should update the discount', async() => {
const result = await nightmare
.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('body', '\u000d') // simulates enter
.waitForLastSnackbar();
@ -467,7 +466,7 @@ xdescribe('Ticket Edit sale path', () => {
.waitToClick(selectors.ticketSales.moreMenuButton)
.waitToClick(selectors.ticketSales.moreMenuUpdateDiscount)
.write(selectors.ticketSales.moreMenuUpdateDiscountInput, 100)
.write('body', '\u000d') // simulates enter
.write('body', '\u000d')
.waitForTextInElement(selectors.ticketSales.totalImport, '0.00')
.waitToGetProperty(selectors.ticketSales.totalImport, 'innerText');
@ -597,16 +596,16 @@ xdescribe('Ticket Edit sale path', () => {
describe('when state is preparation and loged as salesPerson', () => {
it(`shouldnt be able to edit the sale price`, async() => {
const result = await nightmare
.waitToClick(selectors.ticketSales.firstSalePrice)
.exists(selectors.ticketSales.firstSalePriceInput);
.waitToClick(selectors.ticketSales.firstSaleID)
.exists(selectors.ticketSales.firstSalePrice);
expect(result).toBeFalsy();
});
it(`shouldnt be able to edit the sale discount`, async() => {
const result = await nightmare
.waitToClick(selectors.ticketSales.firstSaleDiscount)
.exists(selectors.ticketSales.firstSaleDiscountInput);
.waitToClick(selectors.ticketSales.firstSaleID)
.exists(selectors.ticketSales.firstSaleDiscount);
expect(result).toBeFalsy();
});

View File

@ -15,11 +15,10 @@ export default class Button extends Input {
}
$onInit() {
if (!this.type) {
if (!this.type)
this.type = 'button';
}
}
}
Button.$inject = ['$element'];
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>
<span class="text" ng-transclude="text"></span>

View File

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

View File

@ -1,16 +1,54 @@
@import "variables";
vn-td-editable {
text {
cursor: pointer;
& > div.text-container{
width: 100%;
display: block
}
&.selected {
& > .text-container{
outline: none;
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;
}
}
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
align-items: center;
padding: .6em;
overflow: visible;
vn-icon {
font-size: 1em;
& > field {
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)
this.saveOldValue();
});
this.input.addEventListener('keyup', e => {
if (e.key == 'Escape') {
if (e.defaultPrevented || e.key != 'Escape')
return;
this.value = this.oldValue;
this.cancelled = true;
e.stopPropagation();
}
if (e.key == 'Escape' || e.key == 'Enter')
this.input.blur();
e.preventDefault();
});
this.input.addEventListener('blur', () => {
this.input.addEventListener('change', e => {
if (this.onChange && !this.cancelled && (this.oldValue != this.value)) {
this.onChange();
this.saveOldValue();

View File

@ -1,16 +1,6 @@
import ngModule from '../module';
/**
* 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];
export function focus(input) {
let selector = 'input, textarea, button, submit';
if (!input.matches(selector))
@ -25,7 +15,18 @@ export function directive() {
if (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', () => {
it(`should set depth property and call activateItem()`, () => {
spyOn(controller, 'activateItem');

View File

@ -355,7 +355,7 @@ async function docker() {
let d = new Date();
let pad = v => v < 10 ? '0' + v : v;
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}`;

View File

@ -12,6 +12,10 @@ module.exports = function(Self) {
});
Self.observe('before save', async function(ctx) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let oldInstance;
let oldInstanceFk;
let newInstance;
@ -22,7 +26,7 @@ module.exports = function(Self) {
oldInstance = await fkToValue(oldInstanceFk, ctx);
if (ctx.where && !ctx.currentInstance) {
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)
@ -33,10 +37,14 @@ module.exports = function(Self) {
});
Self.observe('before delete', async function(ctx) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
if (ctx.where) {
let affectedModel = ctx.Model.definition.name;
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;
if (relation) {
@ -61,6 +69,10 @@ module.exports = function(Self) {
});
async function logDeletedInstances(ctx, loopBackContext) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
ctx.hookState.oldInstance.forEach(async instance => {
let userFk;
if (loopBackContext)
@ -80,16 +92,16 @@ module.exports = function(Self) {
newInstance: {}
};
let transaction = {};
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
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) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let cleanInstance = JSON.parse(JSON.stringify(instance));
let result = {};
for (let key in cleanInstance) {
@ -98,8 +110,11 @@ module.exports = function(Self) {
for (let key1 in ctx.Model.relations) {
let val1 = ctx.Model.relations[key1];
if (val1.keyFrom == key && key != 'id') {
let recordSet = await val1.modelTo.findById(val);
val = recordSet.name; // FIXME preparar todos los modelos con campo name
let recordSet = await ctx.Model.app.models[val1.modelTo.modelName].findById(val, options);
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;
}
}
@ -109,6 +124,10 @@ module.exports = function(Self) {
}
async function logInModel(ctx, loopBackContext) {
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let definition = ctx.Model.definition;
let primaryKey;
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
let changedModelValue = definition.settings.log.changedModelValue;
let where;
if (changedModelValue && (!ctx.instance || !ctx.instance[changedModelValue])) {
var where = [];
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 => {
where.push(element[changedModelValue]);
changedModelId.push(element.id);
@ -185,11 +205,7 @@ module.exports = function(Self) {
let logModel = definition.settings.log.model;
let transaction = {};
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
await ctx.Model.app.models[logModel].create(logsToSave, transaction);
await ctx.Model.app.models[logModel].create(logsToSave, options);
}
// 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",
"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",
"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",
"ORDER_EMPTY": "Cesta vacía",
"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()', () => {
let claimEnds;
afterAll(async () => {
afterAll(async done => {
claimEnds.forEach(async line => {
await line.destroy();
});
done();
});
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 createdClaimFk;
afterAll(async () => {
afterAll(async done => {
await app.models.Claim.destroyById(createdClaimFk);
done();
});
let newClaim = {

View File

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

View File

@ -14,12 +14,16 @@ describe('Update Claim', () => {
observation: 'observation'
};
beforeAll(async() => {
beforeAll(async done => {
newInstance = await app.models.Claim.create(original);
done();
});
afterAll(async() => {
afterAll(async done => {
await app.models.Claim.destroyById(newInstance.id);
done();
});
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,
password: parseInt(Math.random() * 100000000000000)
};
let Account = Self.app.models.Account;
let transaction = await Account.beginTransaction({});
const Account = Self.app.models.Account;
const Address = Self.app.models.Address;
const transaction = await Account.beginTransaction({});
try {
let account = await Account.create(user, {transaction});
let client = {
let client = await Self.create({
id: account.id,
name: data.name,
fi: data.fi,
@ -42,10 +42,26 @@ module.exports = function(Self) {
provinceFk: data.provinceFk,
countryFk: data.countryFk,
isEqualizated: data.isEqualizated
};
newClient = await Self.create(client);
}, {transaction});
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();
return newClient;
return client;
} catch (e) {
await transaction.rollback();
throw e;

View File

@ -8,9 +8,11 @@ describe('Client addressesPropagateRe', () => {
await client.updateAttributes({hasToInvoiceByAddress: true});
});
afterAll(async() => {
afterAll(async done => {
await app.models.Address.update({clientFk: 101}, {isEqualizated: false});
await client.updateAttributes({hasToInvoiceByAddress: true});
done();
});
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', () => {
const transactionId = 2;
afterAll(async() => {
afterAll(async done => {
await app.models.Client.rawSql(`
CALL hedera.tpvTransactionUndo(?)`, [transactionId]);
done();
});
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', () => {
const clientName = 'Wade';
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 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);
done();
});
let newAccount = {
@ -26,7 +20,9 @@ describe('Client Create', () => {
email: 'Deadpool@marvel.com',
fi: '16195279J',
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() => {

View File

@ -1,12 +1,14 @@
const app = require('vn-loopback/server/server');
describe('Client updateFiscalData', () => {
afterAll(async() => {
afterAll(async done => {
let ctxOfAdmin = {req: {accessToken: {userId: 5}}};
let validparams = {postcode: 46460};
let idWithDataChecked = 101;
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() => {

View File

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

View File

@ -3,8 +3,10 @@ const app = require('vn-loopback/server/server');
describe('Client createWithInsurance', () => {
let classificationId;
afterAll(async() => {
afterAll(async done => {
await app.models.CreditClassification.destroyById(classificationId);
done();
});
it('should verify the classifications and insurances are untainted', async() => {
@ -24,7 +26,7 @@ describe('Client createWithInsurance', () => {
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 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;
module.exports = Self => {
Self.validate('isDefaultAddress', isActive,
{message: 'Unable to default a disabled consignee'}
);
function isActive(err) {
if (!this.isActive && this.isDefaultAddress) err();
}
// Methods
require('../methods/address/createDefaultAddress')(Self);
Self.validateAsync('isEqualizated', cannotHaveET, {
message: 'Cannot check Equalization Tax in this NIF/CIF'
@ -50,31 +45,26 @@ module.exports = Self => {
// Helpers
Self.observe('before save', async function(ctx) {
const Client = Self.app.models.Client;
if (isMultiple(ctx)) return;
let transaction = {};
if (ctx.options && ctx.options.transaction)
transaction = ctx.options.transaction;
let changes = ctx.data || ctx.instance;
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');
if (changes.isDefaultAddress == true && finalState.isActive != false) {
let filter = {
clientFk: finalState.clientFk,
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;
}
// Propagate client isEqualizated to all addresses
if (ctx.isNewInstance == true)
ctx.instance.isEqualizated = client.isEqualizated;
});
};

View File

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

View File

@ -1,3 +1,4 @@
let request = require('request-promise-native');
let UserError = require('vn-loopback/util/user-error');
let getFinalState = require('vn-loopback/util/hook').getFinalState;
let isMultiple = require('vn-loopback/util/hook').isMultiple;
@ -21,6 +22,14 @@ module.exports = Self => {
// Validations
Self.validatesPresenceOf('street', {
message: 'Street cannot be empty'
});
Self.validatesPresenceOf('city', {
message: 'City cannot be empty'
});
Self.validatesUniquenessOf('fi', {
message: 'TIN must be unique'
});
@ -90,15 +99,6 @@ module.exports = Self => {
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, {
message: 'Cannot check Equalization Tax in this NIF/CIF'
});
@ -133,6 +133,19 @@ module.exports = Self => {
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) {
let changes = ctx.data || ctx.instance;
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) {
let models = Self.app.models;
let userId = ctx.options.accessToken.userId;

View File

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

View File

@ -3,14 +3,13 @@ const app = require('vn-loopback/server/server');
describe('loopback model address', () => {
let createdAddressId;
afterAll(async() => {
let address = await app.models.Address.findById(1);
afterAll(async done => {
let client = await app.models.Client.findById(101);
await address.updateAttribute('isDefaultAddress', true);
await app.models.Address.destroyById(createdAddressId);
await client.updateAttribute('isEqualizated', false);
done();
});
describe('observe()', () => {
@ -28,24 +27,6 @@ describe('loopback model address', () => {
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() => {
let client = await app.models.Client.findById(101);
@ -58,7 +39,6 @@ describe('loopback model address', () => {
agencyModeFk: 5,
city: 'here',
isActive: true,
isDefaultAddress: false,
mobile: '555555555',
nickname: 'Test address',
phone: '555555555',

View File

@ -1,15 +1,15 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Addresses"
url="/client/api/Addresses/createDefaultAddress"
id-field="id"
data="$ctrl.address"
data="$ctrl.data"
save="post"
form="form">
</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-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-textfield vn-one label="Consignee" field="$ctrl.address.nickname" vn-focus></vn-textfield>

View File

@ -1,16 +1,35 @@
import ngModule from '../../module';
export default class Controller {
constructor($state) {
this.address = {
constructor($scope, $state) {
this.$scope = $scope;
this.$state = $state;
this.data = {
address: {
clientFk: parseInt($state.params.id),
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', {
template: require('./index.html'),
controller: Controller
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -1,4 +1,5 @@
import './index';
import watcher from 'core/mocks/watcher';
describe('Client', () => {
describe('Component vnClientAddressCreate', () => {
@ -13,11 +14,40 @@ describe('Client', () => {
$state = _$state_;
$state.params.id = '1234';
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', () => {
expect(controller.address.clientFk).toBe(1234);
expect(controller.address.isActive).toBe(true);
expect(controller.data.address.clientFk).toBe(1234);
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"
filter="::$ctrl.filter"
link="{clientFk: $ctrl.$stateParams.id}"
data="addresses"
data="$ctrl.addresses"
auto-load="true">
</vn-crud-model>
<div compact>
<vn-card pad-large>
<vn-horizontal
ng-repeat="address in addresses"
ng-repeat="address in $ctrl.addresses"
class="pad-medium-top"
style="align-items: center;">
<vn-one
border-radius
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-none pad-medium-h>
<vn-icon-button
icon="star"
ng-if="address.isDefaultAddress">
ng-if="$ctrl.isDefaultAddress(address)">
</vn-icon-button>
<vn-icon-button
ng-if="!address.isActive"
@ -28,7 +31,7 @@
vn-tooltip="Active first to set as default">
</vn-icon-button>
<vn-icon-button
ng-if="address.isActive && !address.isDefaultAddress"
ng-if="address.isActive && !$ctrl.isDefaultAddress(address)"
icon="star_border"
vn-tooltip="Set as default"
ng-click="$ctrl.setDefault(address)">

View File

@ -9,22 +9,60 @@ class Controller {
include: {
observations: 'observationType'
},
order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC']
order: ['isActive DESC', 'nickname ASC']
};
}
setDefault(address) {
if (address.isActive) {
let params = {isDefaultAddress: true};
this.$http.patch(`/client/api/Addresses/${address.id}`, params).then(
() => this.$scope.model.refresh()
);
get client() {
return this._client;
}
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'];
ngModule.component('vnClientAddressIndex', {
template: require('./index.html'),
controller: Controller
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -1,36 +1,69 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('Client', () => {
describe('Component vnClientAddressIndex', () => {
let controller;
let $scope;
let $state;
let $stateParams;
let $httpBackend;
beforeEach(ngModule('client'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$state_, _$httpBackend_) => {
$state = _$state_;
beforeEach(angular.mock.inject(($componentController, $rootScope, _$stateParams_, _$httpBackend_) => {
$stateParams = _$stateParams_;
$stateParams.id = 1;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$scope.model = {
refresh: () => {}
};
controller = $componentController('vnClientAddressIndex', {$state, $scope});
controller = $componentController('vnClientAddressIndex', {$stateParams, $scope});
controller.client = {id: 101, defaultAddressFk: 121};
controller.$scope.model = crudModel;
}));
describe('setDefault()', () => {
it('should perform a PATCH if the address is active and call the refresh method', () => {
spyOn($scope.model, 'refresh');
it('should perform a PATCH and set a value to defaultAddressFk property', () => {
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/Addresses/1`).respond(200);
$httpBackend.expect('PATCH', `/client/api/Addresses/1`);
$httpBackend.when('PATCH', `/client/api/Clients/1`, data).respond(200, expectedResult);
$httpBackend.expect('PATCH', `/client/api/Clients/1`, data);
controller.setDefault(address);
$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(() => {
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() {
let orgData = this.$scope.watcher.orgData;

View File

@ -21,26 +21,6 @@ describe('Client', () => {
$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()', () => {
it(`should return true if there are changes on payMethod data`, () => {
controller.client.payMethodFk = 5;

View File

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

View File

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

View File

@ -23,6 +23,11 @@ module.exports = Self => {
type: 'String',
description: `If it's and integer searchs by id, otherwise it searchs by name`,
http: {source: 'query'}
}, {
arg: 'id',
type: 'Integer',
description: 'Item id',
http: {source: 'query'}
}, {
arg: 'categoryFk',
type: 'Integer',
@ -33,6 +38,21 @@ module.exports = Self => {
type: 'Integer',
description: 'Type id',
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: {
@ -45,7 +65,7 @@ module.exports = Self => {
}
});
Self.filter = async(ctx, filter, tags) => {
Self.filter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
@ -60,47 +80,82 @@ module.exports = Self => {
return {'i.description': {like: `%${value}%`}};
case 'categoryFk':
return {'ic.id': value};
case 'salesPersonFk':
return {'t.workerFk': value};
case 'typeFk':
return {'t.id': value};
return {'i.typeFk': value};
case 'isActive':
return {'i.isActive': value};
}
});
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,
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.id userId,
intr.description AS intrastat, i.stems,
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
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
JOIN worker w ON w.id = t.workerFk
JOIN account.user u ON u.id = w.userFk
LEFT JOIN worker w ON w.id = t.workerFk
LEFT JOIN account.user u ON u.id = w.userFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN producer pr ON pr.id = i.producerFk
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) {
let i = 1;
for (let tag of ctx.args.tags) {
if (tag.value == null) continue;
let tAlias = `it${i++}`;
for (const tag of ctx.args.tags) {
const tAlias = `it${i++}`;
if (tag.tagFk) {
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}.value = ?`,
params: [tag.tagFk, tag.value]
AND ${tAlias}.value LIKE ?`,
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));
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()', () => {
let item;
afterAll(async() => {
afterAll(async done => {
await app.models.Item.destroyById(item.id);
done();
});
it('should create a new item, adding the name as a tag', async() => {

View File

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

View File

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

View File

@ -57,6 +57,13 @@
value="{{tag.value}}">
</vn-label-value>
</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">
<a ng-if="$ctrl.quicklinks.btnOne"
vn-tooltip="{{::$ctrl.quicklinks.btnOne.tooltip}}"

View File

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

View File

@ -3,10 +3,39 @@
<vn-auto>
<section
class="inline-tag ellipsize"
ng-class="::{empty: !fetchedTag.value}"
ng-repeat="fetchedTag in $ctrl.tags track by $index"
title="{{::fetchedTag.name}}: {{::fetchedTag.value}}">
{{::fetchedTag.value}}
ng-class="::{empty: !$ctrl.item.value5}"
title="{{::$ctrl.item.tag5}}: {{::$ctrl.item.value5}}">
{{::$ctrl.item.value5}}
</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>
</vn-auto>
</vn-horizontal>

View File

@ -1,52 +1,8 @@
import ngModule from '../module';
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', {
template: require('./index.html'),
controller: FetchedTags,
bindings: {
maxLength: '<',
item: '<',

View File

@ -22,13 +22,17 @@
<vn-tr>
<vn-th th-id="picture"></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="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="category">Category</vn-th>
<vn-th th-id="intrastat">Intrastat</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="taxClass">Tax class</vn-th>
<vn-th th-id="active" shrink>Active</vn-th>
@ -53,6 +57,8 @@
{{::item.id | zeroFill:6}}
</span>
</vn-td>
<vn-td number>{{::item.grouping | dashIfEmpty}}</vn-td>
<vn-td number>{{::item.packing | dashIfEmpty}}</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
@ -61,6 +67,8 @@
</vn-fetched-tags>
</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.category}}</vn-td>
<vn-td>{{::item.intrastat}}</vn-td>

View File

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

View File

@ -46,6 +46,30 @@
label="Description"
model="filter.description">
</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 ng-repeat="itemTag in filter.tags">
<vn-autocomplete

View File

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

View File

@ -4,10 +4,12 @@ describe('order removes()', () => {
let row;
let newRow;
beforeAll(async() => {
beforeAll(async done => {
row = await app.models.OrderRow.findOne({where: {id: 12}});
row.id = null;
newRow = await app.models.OrderRow.create(row);
done();
});
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