Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2245-ticket_component
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Bernat Exposito Domenech 2020-09-08 07:17:23 +02:00
commit 174cea7bc3
163 changed files with 5182 additions and 1663 deletions

View File

@ -2,6 +2,9 @@
"Account": {
"dataSource": "vn"
},
"AccountingType": {
"dataSource": "vn"
},
"Bank": {
"dataSource": "vn"
},

View File

@ -0,0 +1,30 @@
{
"name": "AccountingType",
"base": "VnModel",
"options": {
"mysql": {
"table": "accountingType"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"description": {
"type": "String",
"required": true
},
"receiptDescription": {
"type": "String",
"required": true
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -2,39 +2,49 @@
"name": "Bank",
"base": "VnModel",
"options": {
"mysql": {
"table": "bank"
}
"mysql": {
"table": "bank"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"bank": {
"type": "string",
"required": true
},
"account": {
"type": "string",
"required": true
},
"cash": {
"type": "string",
"required": true
},
"entityFk": {
"type": "string",
"required": true
},
"isActive": {
"type": "string",
"required": true
},
"currencyFk": {
"type": "string",
"required": true
}
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"bank": {
"type": "String",
"required": true
},
"account": {
"type": "String",
"required": true
},
"accountingTypeFk": {
"type": "Number",
"required": true,
"mysql": {
"columnName": "cash"
}
},
"entityFk": {
"type": "Number",
"required": true
},
"isActive": {
"type": "Boolean",
"required": true
},
"currencyFk": {
"type": "Number",
"required": true
}
},
"relations": {
"accountingType": {
"type": "belongsTo",
"model": "AccountingType",
"foreignKey": "accountingTypeFk"
}
}
}
}

View File

@ -55,8 +55,9 @@ module.exports = Self => {
};
await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath)
await sharp(srcFilePath, {failOnError: false})
.resize(collection.maxWidth, collection.maxHeight, resizeOpts)
.png()
.toFile(dstFile);
const sizes = collection.sizes();
@ -69,8 +70,9 @@ module.exports = Self => {
};
await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath)
await sharp(srcFilePath, {failOnError: false})
.resize(size.width, size.height, resizeOpts)
.png()
.toFile(dstFile);
}
@ -88,7 +90,9 @@ module.exports = Self => {
);
}
await fs.unlink(srcFilePath);
if (fs.existsSync(srcFilePath))
await fs.unlink(srcFilePath);
await tx.commit();
return newImage;
} catch (e) {

View File

@ -0,0 +1,44 @@
USE `vn`;
DROP procedure IF EXISTS `vn`.`itemLastEntries`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `itemLastEntries__`(IN `vItem` INT, IN `vDays` DATE)
BEGIN
SELECT
w.id AS warehouseFk,
w.name AS warehouse,
tr.landed,
b.entryFk,
b.isIgnored,
b.price2,
b.price3,
b.stickers,
b.packing,
b.`grouping`,
b.groupingMode,
b.weight,
i.stems,
b.quantity,
b.buyingValue,
b.packageFk ,
s.id AS supplierFk,
s.name AS supplier
FROM itemType it
RIGHT JOIN (entry e
LEFT JOIN supplier s ON s.id = e.supplierFk
RIGHT JOIN buy b ON b.entryFk = e.id
LEFT JOIN item i ON i.id = b.itemFk
LEFT JOIN ink ON ink.id = i.inkFk
LEFT JOIN travel tr ON tr.id = e.travelFk
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
LEFT JOIN origin o ON o.id = i.originFk
) ON it.id = i.typeFk
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id
WHERE b.itemFk = vItem And tr.shipped BETWEEN vDays AND DATE_ADD(CURDATE(), INTERVAl + 10 DAY)
ORDER BY tr.landed DESC , b.id DESC;
END$$
DELIMITER ;
;

View File

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

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`accountingType`
ADD COLUMN `receiptDescription` VARCHAR(50) NULL COMMENT 'Descripción por defecto al crear nuevo recibo' AFTER `description`;

View File

@ -0,0 +1,119 @@
USE `vn`;
DROP procedure IF EXISTS `ticket_close`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticket_close`(vTicketFk INT)
BEGIN
/**
* Realiza el cierre de todos los
* tickets de la tabla ticketClosure.
*
* @param vTicketFk Id del ticket
*/
DECLARE vDone BOOL;
DECLARE vClientFk INT;
DECLARE vCurTicketFk INT;
DECLARE vIsTaxDataChecked BOOL;
DECLARE vCompanyFk INT;
DECLARE vShipped DATE;
DECLARE vNewInvoiceId INT;
DECLARE vHasDailyInvoice BOOL;
DECLARE vWithPackage BOOL;
DECLARE vHasToInvoice BOOL;
DECLARE cur CURSOR FOR
SELECT ticketFk FROM tmp.ticketClosure;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
RESIGNAL;
END;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure;
CREATE TEMPORARY TABLE tmp.ticketClosure
SELECT vTicketFk AS ticketFk;
INSERT INTO tmp.ticketClosure
SELECT id FROM stowaway s
WHERE s.shipFk = vTicketFk;
OPEN cur;
proc: LOOP
SET vDone = FALSE;
FETCH cur INTO vCurTicketFk;
IF vDone THEN
LEAVE proc;
END IF;
-- ticketClosure start
SELECT
c.id,
c.isTaxDataChecked,
t.companyFk,
t.shipped,
co.hasDailyInvoice,
w.isManaged,
c.hasToInvoice
INTO vClientFk,
vIsTaxDataChecked,
vCompanyFk,
vShipped,
vHasDailyInvoice,
vWithPackage,
vHasToInvoice
FROM ticket t
JOIN `client` c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
JOIN warehouse w ON w.id = t.warehouseFk
WHERE t.id = vCurTicketFk;
INSERT INTO ticketPackaging (ticketFk, packagingFk, quantity)
(SELECT vCurTicketFk, p.id, COUNT(*)
FROM expedition e
JOIN packaging p ON p.itemFk = e.itemFk
WHERE e.ticketFk = vCurTicketFk AND p.isPackageReturnable
AND vWithPackage
GROUP BY p.itemFk);
-- No retornables o no catalogados
INSERT INTO sale (itemFk, ticketFk, concept, quantity, price, isPriceFixed)
(SELECT e.itemFk, vCurTicketFk, i.name, COUNT(*) AS amount, getSpecialPrice(e.itemFk, vClientFk), 1
FROM expedition e
JOIN item i ON i.id = e.itemFk
LEFT JOIN packaging p ON p.itemFk = i.id
WHERE e.ticketFk = vCurTicketFk AND IFNULL(p.isPackageReturnable, 0) = 0
AND getSpecialPrice(e.itemFk, vClientFk) > 0
GROUP BY e.itemFk);
CALL vn.zonePromo_Make();
IF(vHasDailyInvoice) AND vHasToInvoice THEN
-- Facturacion rapida
CALL ticketTrackingAdd(vCurTicketFk, 'DELIVERED', NULL);
-- Facturar si está contabilizado
IF vIsTaxDataChecked THEN
CALL invoiceOut_newFromClient(
vClientFk,
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
vShipped,
vCompanyFk,
NULL,
vNewInvoiceId);
END IF;
ELSE
CALL ticketTrackingAdd(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk)), NULL);
END IF;
END LOOP;
CLOSE cur;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure;
END$$
DELIMITER ;

View File

@ -116,13 +116,13 @@ INSERT INTO `vn`.`shelving` (`code`, `parkingFk`, `isPrinted`, `priority`, `park
('GVC', '1', '0', '1', '0', '106'),
('HEJ', '2', '0', '1', '0', '106');
INSERT INTO `vn`.`accountingType`(`id`, `description`)
INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`)
VALUES
(1, 'Digital money'),
(2, 'Cash'),
(3, 'Card'),
(4, 'Stolen Money'),
(5, 'Miscellaneous');
(1, 'Digital money', ''),
(2, 'Cash', 'Cash'),
(3, 'Card', 'Pay on receipt'),
(4, 'Stolen Money', ''),
(5, 'Miscellaneous', '');
INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
VALUES
@ -133,8 +133,8 @@ INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
INSERT INTO `vn`.`bank`(`id`, `bank`, `account`, `cash`, `entityFk`, `isActive`, `currencyFk`)
VALUES
(1, 'Pay on receipt', '0000000000', 4, 0, 1, 1),
(2, 'Cash', '1111111111', 1, 0, 1, 1);
(1, 'Pay on receipt', '0000000000', 3, 0, 1, 1),
(2, 'Cash', '1111111111', 2, 0, 1, 1);
INSERT INTO `vn`.`deliveryMethod`(`id`, `code`, `description`)
VALUES
@ -586,7 +586,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
(18, 1, 4, 4, 4, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 108, 'Cerebro', 128, NULL, 0, 12, CURDATE()),
(19, 1, 5, 5, 3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, 13, CURDATE()),
(19, 1, 5, 5, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 1, 13, CURDATE()),
(20, 1, 5, 5, 3, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Thailand', 129, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(21, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Holland', 102, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
(22, NULL, 5, 5, 5, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(DATE_ADD(CURDATE(),INTERVAL +1 MONTH), INTERVAL +1 DAY), 109, 'Somewhere in Japan', 103, NULL, 0, 13, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
@ -627,11 +627,11 @@ INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `workerFk`, `created`)
(12, 3, 19, NOW()),
(13, 3, 19, NOW()),
(14, 3, 19, NOW()),
(15, 3, 19, NOW()),
(15, 2, 19, NOW()),
(16, 3, 19, NOW()),
(17, 3, 19, NOW()),
(18, 3, 19, NOW()),
(19, 3, 19, NOW()),
(17, 2, 19, NOW()),
(18, 2, 19, NOW()),
(19, 2, 19, NOW()),
(20, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
(21, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
(22, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
@ -759,25 +759,25 @@ INSERT INTO `vn`.`intrastat`(`id`, `description`, `taxClassFk`, `taxCodeFk`)
(05080000, 'Coral y materiales similares', 2, 2),
(06021010, 'Plantas vivas: Esqueje/injerto, Vid', 1, 1);
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`, `minPrice`)
VALUES
(1, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66540, 1, NULL),
(2, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 65540, 1, NULL),
(3, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 61692, 1, NULL),
(4, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 66090, 2, NULL),
(5, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(6, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(7, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(8, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66540, 1, NULL),
(9, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 65540, 1, NULL),
(10, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 61692, 1, NULL),
(11, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 66090, 2, NULL),
(12, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(13, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(14, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 67350, 2, NULL);
(1, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL, 0),
(2, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66, 1, NULL, 0),
(3, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 65, 1, NULL, 0),
(4, 1, 60, 'AMR', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, 69, 2, NULL, 0),
(5, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 74, 2, NULL, 0),
(6, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 62, 2, NULL, 0),
(7, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 64, 2, NULL, 0),
(8, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 75, 1, NULL, 0),
(9, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 76, 1, NULL, 0),
(10, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 77, 1, NULL, 0),
(11, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 78, 2, NULL, 0),
(12, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 82, 2, NULL, 0),
(13, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 83, 2, NULL, 0),
(14, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 84, 2, NULL, 0),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL, 0),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL, 0),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 88, 2, NULL, 0);
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `checked`, `workerFk`)
VALUES
@ -791,6 +791,26 @@ INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `create
(8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), 1, 1, 1, 18),
(9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 18),
(10, 7, 7, 71, CURDATE(), 1, 1, 1, 18);
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `taxClassFk`, `subName`)
VALUES
(1, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 67, 1, NULL),
(2, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 66, 1, NULL),
(3, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 65, 1, NULL),
(4, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 69, 2, NULL),
(5, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 74, 2, NULL),
(6, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 62, 2, NULL),
(7, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 64, 2, NULL),
(8, 2, 70, 'AMA', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 75, 1, NULL),
(9, 2, 70, 'AZL', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, 76, 1, NULL),
(10, 1, 60, 'AMR', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, 77, 1, NULL),
(11, 1, 60, 'AMR', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, 78, 2, NULL),
(12, 3, 30, 'GRE', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, 82, 2, NULL),
(13, 5, 30, 'GRE', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 83, 2, NULL),
(14, 5, 90, 'AZL', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 84, 2, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, 67350, 2, NULL),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, 88, 2, NULL);
INSERT INTO `vn`.`expeditionBoxVol`(`boxFk`, `m3`, `ratio`)
VALUES
@ -807,6 +827,19 @@ INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPack
('cc', 1640038.00, 56.00, 220.00, 128.00, 1, CURDATE(), 15, 90.00),
('pallet 100', 2745600.00, 100.00, 220.00, 120.00, 1, CURDATE(), 16, 0.00);
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `packagingFk`)
VALUES
(1, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 1),
(2, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 1),
(3, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 2, 3, 1, 1),
(4, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 4, 4, 1, 1),
(5, 1, 2, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 1),
(6, 7, 3, 71, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 1, 1, 1, 1),
(7, 2, 4, 71, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), 1, 1, 1, 1),
(8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), 1, 1, 1, 1),
(9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1, 1, 1),
(10, 7, 7, 71, CURDATE(), 1, 1, 1, 1);
INSERT INTO `vn`.`ticketPackaging`(`id`, `ticketFk`, `packagingFk`, `quantity`, `created`, `pvp`)
VALUES
(1, 1, 2, 2, CURDATE(), NULL),
@ -1263,23 +1296,23 @@ INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `saleTotal`, `saleWa
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', '186', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', '277', '0', '0.0');
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`producer`,`printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
VALUES
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, CURDATE()),
(4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE());
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 1, CURDATE()),
(4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, NULL, 0, 1, 0, 2.5, CURDATE()),
(6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 2.5, CURDATE()),
(7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 2.5, CURDATE()),
(9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE()),
(10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 4, CURDATE()),
(11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE()),
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, NULL, 0, 1, 0, 4, CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, NULL, 0, 1, 0, 4, CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, NULL, 0, 1, 0, 4, CURDATE());
INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_id`, `agency_id`, `address_id`, `company_id`, `note`, `source_app`, `confirmed`,`total`, `date_make`, `first_row_stamp`, `confirm_date`)
VALUES

View File

@ -189,8 +189,9 @@ export default {
newPaymentBank: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.receipt.bankFk"]',
newPaymentAmount: '.vn-dialog.shown vn-input-number[ng-model="$ctrl.receipt.amountPaid"]',
saveButton: '.vn-dialog.shown [response="accept"]',
firstBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)'
firstLineBalance: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)',
firstLineReference: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable',
firstLineReferenceInput: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable > div > field > vn-textfield'
},
webPayment: {
confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]',
@ -219,23 +220,23 @@ export default {
topbarSearch: 'vn-topbar',
searchButton: 'vn-searchbar vn-icon[icon="search"]',
closeItemSummaryPreview: '.vn-popup.shown',
fieldsToShowButton: 'vn-item-index vn-table > div > div > vn-icon-button[icon="menu"]',
fieldsToShowForm: '.vn-dialog.shown form',
fieldsToShowButton: 'vn-item-index vn-table > div > div > vn-icon-button[icon="more_vert"]',
fieldsToShowForm: '.vn-popover.shown .content',
firstItemImage: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > img',
firstItemImageTd: 'vn-item-index vn-table a:nth-child(1) vn-td:nth-child(1)',
firstItemId: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(2)',
idCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(2) > vn-check',
stemsCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(3) > vn-check',
sizeCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(4) > vn-check',
nicheCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(5) > vn-check',
typeCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(6) > vn-check',
categoryCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(7) > vn-check',
intrastadCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(8) > vn-check',
originCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(9) > vn-check',
buyerCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(10) > vn-check',
destinyCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(11) > vn-check',
taxClassCheckbox: '.vn-dialog.shown form vn-horizontal:nth-child(12) > vn-check',
saveFieldsButton: '.vn-dialog.shown vn-horizontal:nth-child(16) > vn-button > button'
idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(1) > vn-check',
stemsCheckbox: '.vn-popover.shown vn-horizontal:nth-child(2) > vn-check',
sizeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check',
nicheCheckbox: '.vn-popover.shown vn-horizontal:nth-child(4) > vn-check',
typeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(5) > vn-check',
categoryCheckbox: '.vn-popover.shown vn-horizontal:nth-child(6) > vn-check',
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(7) > vn-check',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(8) > vn-check',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(9) > vn-check',
destinyCheckbox: '.vn-popover.shown vn-horizontal:nth-child(10) > vn-check',
taxClassCheckbox: '.vn-popover.shown vn-horizontal:nth-child(11) > vn-check',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
},
itemCreateView: {
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
@ -371,7 +372,9 @@ export default {
ticketsIndex: {
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"]',
newTicketButton: 'vn-ticket-index a',
advancedSearchDaysOnward: 'vn-ticket-search-panel vn-input-number[ng-model="filter.scopeDays"]',
advancedSearchButton: 'vn-ticket-search-panel button[type=submit]',
newTicketButton: 'vn-ticket-index a[ui-sref="ticket.create"]',
searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
secondTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(2) > vn-td:nth-child(1) > vn-check',
thirdTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(3) > vn-td:nth-child(1) > vn-check',
@ -383,7 +386,6 @@ export default {
searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr',
searchResultDate: 'vn-ticket-summary [label=Landed] span',
topbarSearch: 'vn-searchbar',
advancedSearchButton: 'vn-ticket-search-panel button[type=submit]',
searchButton: 'vn-searchbar vn-icon[icon="search"]',
moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]',
sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6)',
@ -544,6 +546,7 @@ export default {
logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]',
changedBy: 'vn-ticket-log > vn-log vn-tr:nth-child(1) > vn-td:nth-child(2) > span',
actionTaken: 'vn-ticket-log > vn-log vn-td:nth-child(1) > div > div:nth-child(3) > span.value',
changes: 'vn-ticket-log vn-data-viewer vn-tbody > vn-tr > vn-td:nth-child(7)',
id: 'vn-ticket-log > vn-log vn-td.before > vn-one:nth-child(1) > div > span.value'
},
ticketService: {
@ -688,7 +691,7 @@ export default {
confirmButton: '.vn-confirm.shown button[response="accept"]',
},
routeIndex: {
addNewRouteButton: 'vn-route-index > a[ui-sref="route.create"]'
addNewRouteButton: 'vn-route-index a[ui-sref="route.create"]'
},
createRouteView: {
worker: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
@ -803,6 +806,22 @@ export default {
navigateBackToIndex: 'vn-worker-descriptor [name="goToModuleIndex"]',
acceptDeleteDialog: '.vn-confirm.shown button[response="accept"]'
},
workerCalendar: {
year: 'vn-worker-calendar vn-autocomplete[ng-model="$ctrl.year"]',
totalHolidaysUsed: 'vn-worker-calendar div.totalBox > div',
januaryThirtyFirst: 'vn-worker-calendar vn-calendar:nth-child(2) section:nth-child(33) > div',
marchTwentyThird: 'vn-worker-calendar vn-calendar:nth-child(4) section:nth-child(29) > div',
mayFourth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(8) > div',
mayEighth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(12) > div',
mayTwelfth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(16) > div',
mayThirteenth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(17) > div',
mayFourteenth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(18) > div',
holidays: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(1)',
absence: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(2)',
halfHoliday: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(3)',
furlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(4)',
halfFurlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(5)',
},
invoiceOutIndex: {
topbarSearch: 'vn-searchbar',
searchButton: 'vn-searchbar vn-icon[icon="search"]',
@ -852,6 +871,13 @@ export default {
createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr',
upload: 'vn-travel-thermograph-create button[type=submit]'
},
zoneIndex: {
searchResult: 'vn-zone-index a.vn-tr',
},
zoneDescriptor: {
menu: 'vn-zone-descriptor vn-icon-button[vn-popover="menu"]',
deleteZone: 'slot-menu vn-item[ng-click="$ctrl.onDelete()"]'
},
zoneBasicData: {
name: 'vn-zone-basic-data vn-textfield[ng-model="$ctrl.zone.name"]',
agency: 'vn-zone-basic-data vn-autocomplete[ng-model="$ctrl.zone.agencyModeFk"]',
@ -873,5 +899,22 @@ export default {
agency: 'vn-entry-descriptor div.body vn-label-value:nth-child(1) span',
travelsQuicklink: 'vn-entry-descriptor vn-quick-link[icon="local_airport"] > a',
entriesQuicklink: 'vn-entry-descriptor vn-quick-link[icon="icon-entry"] > a'
},
entryLatestBuys: {
firstBuy: 'vn-entry-latest-buys vn-tbody > a:nth-child(1)',
allBuysCheckBox: 'vn-entry-latest-buys vn-thead vn-check',
secondBuyCheckBox: 'vn-entry-latest-buys a:nth-child(2) vn-check[ng-model="buy.checked"]',
editBuysButton: 'vn-entry-latest-buys vn-button[icon="edit"]',
fieldAutocomplete: 'vn-autocomplete[ng-model="$ctrl.editedColumn.field"]',
newValueInput: 'vn-textfield[ng-model="$ctrl.editedColumn.newValue"]',
latestBuysSectionButton: 'a[ui-sref="entry.latestBuys"]',
acceptEditBuysDialog: 'button[response="accept"]'
},
entryIndex: {
createEntryButton: 'vn-entry-index vn-button[icon="add"]',
newEntrySupplier: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]',
newEntryTravel: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.travelFk"]',
newEntryCompany: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
saveNewEntry: 'vn-entry-create button[type="submit"]'
}
};

View File

@ -17,6 +17,7 @@ describe('Client Add address path', () => {
});
it(`should click on the add new address button to access to the new address form`, async() => {
await page.waitFor(500);
await page.waitToClick(selectors.clientAddresses.createAddress);
await page.waitForState('client.card.address.create');
});

View File

@ -53,15 +53,28 @@ describe('Client balance path', () => {
expect(message.type).toBe('success');
});
it('should check balance is now 0 and the company is now VNL becouse the user local settings were removed', async() => {
it('should edit the 1st line reference', async() => {
await page.waitToClick(selectors.clientBalance.firstLineReference);
await page.write(selectors.clientBalance.firstLineReferenceInput, 'Miscellaneous payment');
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should check balance is now 0, the reference was saved and the company is now VNL becouse the user local settings were removed', async() => {
await page.waitForSpinnerLoad();
let company = await page
.waitToGetProperty(selectors.clientBalance.company, 'value');
let reference = await page
.waitToGetProperty(selectors.clientBalance.firstLineReference, 'innerText');
let firstBalanceLine = await page
.waitToGetProperty(selectors.clientBalance.firstBalanceLine, 'innerText');
.waitToGetProperty(selectors.clientBalance.firstLineBalance, 'innerText');
expect(company).toEqual('VNL');
expect(reference).toEqual('Miscellaneous payment');
expect(firstBalanceLine).toContain('0.00');
});
@ -76,7 +89,7 @@ describe('Client balance path', () => {
it('should check balance is now -100', async() => {
let result = await page
.waitToGetProperty(selectors.clientBalance.firstBalanceLine, 'innerText');
.waitToGetProperty(selectors.clientBalance.firstLineBalance, 'innerText');
expect(result).toContain('-€100.00');
});
@ -92,7 +105,7 @@ describe('Client balance path', () => {
it('should check balance is now 50', async() => {
let result = await page
.waitToGetProperty(selectors.clientBalance.firstBalanceLine, 'innerText');
.waitToGetProperty(selectors.clientBalance.firstLineBalance, 'innerText');
expect(result).toEqual('€50.00');
});

View File

@ -0,0 +1,137 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker calendar path', () => {
let reasonableTimeBetweenClicks = 400;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSearchResult('Hank Pym');
await page.accessToSection('worker.card.calendar');
});
afterAll(async() => {
await browser.close();
});
describe('as hr', () => {
it('should check 5 total holidays have been used so far before testing anything', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 5 ');
});
it('should set two days as holidays on the calendar', async() => {
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.absence);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.marchTwentyThird);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfHoliday);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.mayFourth);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.furlough);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.mayTwelfth);
await page.waitToClick(selectors.workerCalendar.mayThirteenth);
await page.waitToClick(selectors.workerCalendar.mayFourteenth);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.mayEighth);
});
it('should check the total holidays increased by 1.5', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 6.5 ');
});
});
describe(`as salesBoss`, () => {
it(`should log in and get to Hank's calendar`, async() => {
await page.loginAndModule('salesBoss', 'worker');
await page.accessToSearchResult('Hank Pym');
await page.accessToSection('worker.card.calendar');
});
it('should undo what was done here', async() => {
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.absence);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.marchTwentyThird);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfHoliday);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.mayFourth);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.furlough);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.mayTwelfth);
await page.waitToClick(selectors.workerCalendar.mayThirteenth);
await page.waitToClick(selectors.workerCalendar.mayFourteenth);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.mayEighth);
});
it('should check the total holidays used are back to what it was', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 5 ');
});
});
describe(`as Hank`, () => {
it(`should log in and get to his calendar`, async() => {
await page.loginAndModule('HankPym', 'worker');
await page.accessToSearchResult('Hank Pym');
await page.accessToSection('worker.card.calendar');
});
it('should make a futile attempt to add holidays', async() => {
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitFor(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst);
});
it('should check the total holidays used are now the initial ones', async() => {
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 5 ');
});
it('should use the year selector to go to the previous year', async() => {
const date = new Date();
const lastYear = (date.getFullYear() - 1).toString();
await page.autocompleteSearch(selectors.workerCalendar.year, lastYear);
await page.waitFor(reasonableTimeBetweenClicks);
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 0 ');
});
});
});

View File

@ -55,7 +55,7 @@ describe('Item index path', () => {
});
it('should mark all unchecked boxes to leave the index as it was', async() => {
await page.waitFor(3000); // otherwise the snackbar doesnt appear some times.
await page.waitFor(500); // otherwise the snackbar doesnt appear some times.
await page.waitToClick(selectors.itemsIndex.fieldsToShowButton);
await page.waitToClick(selectors.itemsIndex.idCheckbox);
await page.waitToClick(selectors.itemsIndex.stemsCheckbox);

View File

@ -31,7 +31,7 @@ describe('Item request path', () => {
it('should the status of the request should now be accepted', async() => {
let status = await page.waitToGetProperty(selectors.itemRequest.firstRequestStatus, 'innerText');
expect(status).toContain('Aceptada');
expect(status).toContain('Accepted');
});
it('should now click on the second declain request icon then type the reason', async() => {
@ -40,6 +40,6 @@ describe('Item request path', () => {
await page.respondToDialog('accept');
let status = await page.waitToGetProperty(selectors.itemRequest.firstRequestStatus, 'innerText');
expect(status).toContain('Denegada');
expect(status).toContain('Denied');
});
});

View File

@ -18,7 +18,7 @@ describe('Ticket purchase request path', () => {
});
it('should add a new request', async() => {
await page.waitFor('.vn-popup', {hidden: true}),
await page.waitFor(500);
await page.waitToClick(selectors.ticketRequests.addRequestButton);
await page.write(selectors.ticketRequests.descriptionInput, 'New stuff');
await page.write(selectors.ticketRequests.quantity, '9');

View File

@ -19,7 +19,7 @@ describe('Ticket index payout path', () => {
await page.waitForState('ticket.index');
});
it('should check three tickets 2 of a clinet and 1 of another', async() => {
it('should check the second ticket from a client and 1 of another', async() => {
await page.keyboard.press('Enter');
await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.sixthTicketCheckbox);

View File

@ -17,6 +17,7 @@ describe('Route create path', () => {
describe('as employee', () => {
it('should click on the add new route button and open the creation form', async() => {
await page.waitFor(500);
await page.waitToClick(selectors.routeIndex.addNewRouteButton);
await page.waitForState('route.create');
});

View File

@ -18,6 +18,7 @@ describe('InvoiceOut descriptor path', () => {
describe('as Administrative', () => {
it('should search for tickets with an specific invoiceOut', async() => {
await page.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton);
await page.clearInput(selectors.ticketsIndex.advancedSearchDaysOnward);
await page.write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222');
await page.waitToClick(selectors.ticketsIndex.advancedSearchButton);
await page.waitForState('ticket.card.summary');
@ -63,7 +64,7 @@ describe('InvoiceOut descriptor path', () => {
await page.waitForState('ticket.index');
});
it('should search for tickets with an specific invoiceOut to find no results', async() => {
it('should search now for tickets with an specific invoiceOut to find no results', async() => {
await page.doSearch('T2222222');
const nResults = await page.countElement(selectors.ticketsIndex.searchResult);

View File

@ -0,0 +1,42 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Zone descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('deliveryBoss', 'zone');
await page.accessToSearchResult('13');
});
afterAll(async() => {
await browser.close();
});
it('should eliminate the zone using the descriptor option', async() => {
await page.waitToClick(selectors.zoneDescriptor.menu);
await page.waitToClick(selectors.zoneDescriptor.deleteZone);
await page.respondToDialog('accept');
await page.waitForState('zone.index');
});
it('should search for the deleted zone to find no results', async() => {
await page.doSearch('13');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(0);
});
it('should check the ticket whom lost the zone and see evidence on the logs', async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.selectModule('ticket');
await page.accessToSearchResult('20');
await page.accessToSection('ticket.card.log');
const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText');
expect(lastChanges).toContain('Arreglar');
});
});

View File

@ -0,0 +1,46 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry lastest buys path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
});
afterAll(async() => {
await browser.close();
});
it('should access the latest buys seccion and search not seeing the edit buys button yet', async() => {
await page.waitToClick(selectors.entryLatestBuys.latestBuysSectionButton);
await page.waitFor(250);
await page.keyboard.press('Enter');
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: false});
});
it('should select all lines but one and then check the edit buys button appears', async() => {
await page.waitToClick(selectors.entryLatestBuys.allBuysCheckBox);
await page.waitToClick(selectors.entryLatestBuys.secondBuyCheckBox);
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: true});
});
it('should open the edit dialog', async() => {
await page.waitToClick(selectors.entryLatestBuys.editBuysButton);
await page.waitForSelector(selectors.entryLatestBuys.fieldAutocomplete, {visible: true});
});
it('should search for the "Description" field and type a new description for the items in each selected buy', async() => {
await page.autocompleteSearch(selectors.entryLatestBuys.fieldAutocomplete, 'Description');
await page.write(selectors.entryLatestBuys.newValueInput, 'Crafted item');
await page.waitToClick(selectors.entryLatestBuys.acceptEditBuysDialog);
});
it('should navigate to the entry.buy section by clicking one of the buys', async() => {
await page.waitToClick(selectors.entryLatestBuys.firstBuy);
await page.waitForState('entry.card.buy');
});
});

View File

@ -0,0 +1,33 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry create path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
});
afterAll(async() => {
await browser.close();
});
it('should click the create entry button to open the form', async() => {
await page.waitToClick(selectors.entryIndex.createEntryButton);
await page.waitForState('entry.create');
});
it('should fill the form to create a valid entry', async() => {
await page.autocompleteSearch(selectors.entryIndex.newEntrySupplier, '2');
await page.autocompleteSearch(selectors.entryIndex.newEntryTravel, 'Warehouse Three');
await page.autocompleteSearch(selectors.entryIndex.newEntryCompany, 'ORN');
await page.waitToClick(selectors.entryIndex.saveNewEntry);
});
it('should be redirected to entry basic data', async() => {
await page.waitForState('entry.card.basicData');
});
});

View File

@ -67,13 +67,13 @@ export default class Contextmenu {
get cell() {
if (!this.target) return null;
return this.target.closest('vn-td, .vn-td');
return this.target.closest('vn-td, .vn-td, vn-td-editable');
}
get cellIndex() {
if (!this.row) return null;
const cells = this.row.querySelectorAll('vn-td, .vn-td');
const cells = this.row.querySelectorAll('vn-td, .vn-td, vn-td-editable');
return Array.from(cells).findIndex(
cellItem => cellItem == this.cell
);

View File

@ -1,5 +1,5 @@
<vn-check
ng-model="$ctrl.checked"
intermediate="$ctrl.isIntermediate"
indeterminate="$ctrl.isIndeterminate"
translate-attr="{title: 'Check all'}">
</vn-check>

View File

@ -15,6 +15,19 @@ export default class MultiCheck extends FormInput {
this._checked = false;
this.checkField = 'checked';
this.isIntermediate = false;
this.mayusEnabled = false;
this.window.addEventListener('keydown', event => {
if (event.key === 'Shift')
this.mayusEnabled = true;
});
this.window.addEventListener('keyup', event => {
if (event.key === 'Shift')
this.mayusEnabled = false;
});
this.window.addEventListener('selectstart', event => {
if (this.mayusEnabled)
event.preventDefault();
});
}
/**
@ -28,7 +41,7 @@ export default class MultiCheck extends FormInput {
/**
* Sets the array model instance
* Changes intermediate property for
* Changes indeterminate property for
* the check component
*
* @param {ArrayModel} value - Array model instance
@ -37,8 +50,17 @@ export default class MultiCheck extends FormInput {
this._model = value;
if (value) {
value.on('rowChange', () => {
this.isIntermediate = !this.areAllUnchecked() && !this.areAllChecked();
value.on('rowChange', row => {
this.isIndeterminate = !this.areAllUnchecked() && !this.areAllChecked();
if (this.mayusEnabled) {
this.currentSelection = row.obj.$orgIndex;
if (this.lastSelection != undefined && this.currentSelection != undefined)
this.setSelection(row.value, this.lastSelection, this.currentSelection);
}
this.lastSelection = row.obj.$orgIndex;
if (this.areAllChecked())
this._checked = true;
@ -52,6 +74,20 @@ export default class MultiCheck extends FormInput {
}
}
setSelection(value, from, to) {
let start = from;
let end = to;
if (from > to) {
start = to;
end = from;
}
const data = this.model.data;
for (let i = start; i <= end; i++)
data[i].checked = value;
}
/**
* Gets current check state
*/

View File

@ -62,7 +62,7 @@ describe('Component vnMultiCheck', () => {
});
describe('areAllChecked()', () => {
it(`should set return true if all elements are checked`, () => {
it(`should return true if all elements are checked`, () => {
const data = controller.model.data;
data[0].checked = true;
data[1].checked = true;
@ -71,7 +71,7 @@ describe('Component vnMultiCheck', () => {
expect(controller.areAllChecked()).toBeTruthy();
});
it(`should set return false if not all elements are checked`, () => {
it(`should return false if not all elements are checked`, () => {
const data = controller.model.data;
data[0].checked = true;
data[1].checked = false;
@ -82,7 +82,7 @@ describe('Component vnMultiCheck', () => {
});
describe('areAllUnchecked()', () => {
it(`should set return true if all elements are unchecked`, () => {
it(`should return true if all elements are unchecked`, () => {
const data = controller.model.data;
data[0].checked = false;
data[1].checked = false;
@ -91,7 +91,7 @@ describe('Component vnMultiCheck', () => {
expect(controller.areAllUnchecked()).toBeTruthy();
});
it(`should set return false if not all elements are unchecked`, () => {
it(`should return false if not all elements are unchecked`, () => {
const data = controller.model.data;
data[0].checked = false;
data[1].checked = true;
@ -100,4 +100,36 @@ describe('Component vnMultiCheck', () => {
expect(controller.areAllUnchecked()).toBeFalsy();
});
});
describe('setSelection()', () => {
it(`should check all elements between the index range`, () => {
controller.setSelection(true, 0, 2);
const data = controller.model.data;
const firstRow = data[0];
const secondRow = data[1];
const thirdRow = data[2];
expect(firstRow.checked).toBeTruthy();
expect(secondRow.checked).toBeTruthy();
expect(thirdRow.checked).toBeTruthy();
});
it(`should uncheck all elements between the index range`, () => {
const data = controller.model.data;
const firstRow = data[0];
const secondRow = data[1];
const thirdRow = data[2];
firstRow.checked = true;
secondRow.checked = true;
thirdRow.checked = true;
controller.setSelection(false, 0, 1);
expect(firstRow.checked).toBeFalsy();
expect(secondRow.checked).toBeFalsy();
expect(thirdRow.checked).toBeTruthy();
});
});
});

View File

@ -7,7 +7,6 @@ export default class Table {
this.table = $element[0];
this.field = null;
this.order = null;
this.autoLoad = true;
}
setOrder(field, order) {
@ -25,9 +24,7 @@ export default class Table {
}
$onChanges() {
// FIXME: The autoload property should be removed from vnTable
// because it's already implemented at vnModel
if (this.autoLoad && this.model && !this.model.data)
if (this.model && !this.model.data)
this.applyOrder();
}

View File

@ -11,7 +11,7 @@ import './visible-by';
import './bind';
import './repeat-last';
import './title';
import './uvc';
import './smart-table';
import './droppable';
import './http-click';
import './http-submit';

View File

@ -1,12 +1,13 @@
<div style="position: relative;">
<div style="position: absolute; top: 0; left: 0; padding: .3em; z-index: 1">
<div style="position: absolute; top: 0; left: 0; z-index: 1">
<vn-icon-button
icon="menu"
ng-click="$ctrl.showUvc($event)">
class="vn-pt-sm"
icon="more_vert"
ng-click="$ctrl.showSmartTable($event)">
</vn-icon-button>
<vn-dialog
<vn-popover
class="modal-form"
vn-id="uvc"
vn-id="smart-table"
message="Fields to show">
<tpl-body>
<div class="vn-pa-md">
@ -24,6 +25,6 @@
</vn-horizontal>
</div>
</tpl-body>
</vn-dialog>
</vn-popover>
</div>
</div>

View File

@ -1,23 +1,28 @@
import ngModule from '../module';
import template from './uvc.html';
import './uvc.scss';
import template from './smart-table.html';
import './smart-table.scss';
/**
* Directive to hide/show selected columns of a table, don't use with rowspan.
* Property smart-table-ignore ignores one or more vn-th with prop field.
*/
directive.$inject = ['$http', '$compile', 'vnApp', '$translate'];
export function directive($http, $compile, vnApp, $translate) {
function getHeaderList($element, $scope) {
let allHeaders = $element[0].querySelectorAll(`vn-th[field], vn-th[th-id]`);
let headerList = Array.from(allHeaders);
let filtrableHeaders = $element[0].querySelectorAll('vn-th[field]:not([smart-table-ignore])');
let headerList = Array.from(filtrableHeaders);
let ids = [];
let titles = {};
headerList.forEach(header => {
let id = header.getAttribute('th-id') || header.getAttribute('field');
let id = header.getAttribute('field');
ids.push(id);
titles[id] = header.innerText || id.charAt(0).toUpperCase() + id.slice(1);
});
$scope.fields = ids;
$scope.titles = titles;
$scope.allHeaders = Array.from($element[0].querySelectorAll('vn-th'));
return headerList;
}
@ -38,34 +43,33 @@ export function directive($http, $compile, vnApp, $translate) {
Object.keys(userConfig.configuration).forEach(key => {
let index;
if (userConfig.configuration[key] === false) {
index = headerList.findIndex(el => {
return (el.getAttribute('th-id') == key || el.getAttribute('field') == key);
index = $scope.allHeaders.findIndex(el => {
return el.getAttribute('field') == key;
});
let baseSelector = `vn-table[vn-uvc=${userConfig.tableCode}] > div`;
let baseSelector = `vn-table[vn-smart-table=${userConfig.tableCode}] > div`;
selectors.push(`${baseSelector} vn-thead > vn-tr > vn-th:nth-child(${index + 1})`);
selectors.push(`${baseSelector} vn-tbody > vn-tr > vn-td:nth-child(${index + 1})`);
selectors.push(`${baseSelector} vn-tbody > .vn-tr > vn-td:nth-child(${index + 1})`);
}
});
if (document.querySelector('style[id="uvc"]')) {
let child = document.querySelector('style[id="uvc"]');
if (document.querySelector('style[id="smart-table"]')) {
let child = document.querySelector('style[id="smart-table"]');
child.parentNode.removeChild(child);
}
if (selectors.length) {
let rule = selectors.join(', ') + '{display: none;}';
$scope.css = document.createElement('style');
$scope.css.setAttribute('id', 'uvc');
$scope.css.setAttribute('id', 'smart-table');
document.head.appendChild($scope.css);
$scope.css.appendChild(document.createTextNode(rule));
}
$scope.$on('$destroy', () => {
if ($scope.css && document.querySelector('style[id="uvc"]')) {
let child = document.querySelector('style[id="uvc"]');
if ($scope.css && document.querySelector('style[id="smart-table"]')) {
let child = document.querySelector('style[id="smart-table"]');
child.parentNode.removeChild(child);
}
});
@ -80,13 +84,13 @@ export function directive($http, $compile, vnApp, $translate) {
restrict: 'A',
link: async function($scope, $element, $attrs) {
let cTemplate = $compile(template)($scope)[0];
$scope.$ctrl.showUvc = event => {
$scope.$ctrl.showSmartTable = event => {
event.preventDefault();
event.stopImmediatePropagation();
$scope.uvc.show();
$scope.smartTable.show(event.target);
};
let tableCode = $attrs.vnUvc.trim();
let tableCode = $attrs.vnSmartTable.trim();
let headerList = getHeaderList($element, $scope);
let defaultConfigView = {tableCode: tableCode, configuration: {}};
@ -98,16 +102,16 @@ export function directive($http, $compile, vnApp, $translate) {
addHideClass($scope, headerList, config);
let table = document.querySelector(`vn-table[vn-uvc=${tableCode}]`);
let table = document.querySelector(`vn-table[vn-smart-table=${tableCode}]`);
table.insertBefore(cTemplate, table.firstChild);
$scope.$ctrl.saveConfiguration = async tableConfiguration => {
let newConfig = await saveConfiguration(tableConfiguration);
vnApp.showSuccess($translate.instant('Data saved!'));
addHideClass($scope, headerList, newConfig.data);
$scope.uvc.hide();
$scope.smartTable.hide();
};
}
};
}
ngModule.directive('vnUvc', directive);
ngModule.directive('vnSmartTable', directive);

View File

@ -1,4 +1,4 @@
vn-table vn-dialog[vn-id="uvc"]{
vn-table vn-popover[vn-id="smart-table"]{
& > div {
min-width: 288px;
align-items: center;

View File

@ -22,7 +22,9 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-latestBuys:before {
content: "\e95f";
}
.icon-zone:before {
content: "\e95d";
}

View File

@ -102,6 +102,7 @@
<glyph unicode="&#xe95c;" glyph-name="anonymous" d="M230.4 605.867c12.8 46.933 29.867 93.867 46.933 140.8 8.533 34.133 21.333 64 29.867 98.133 4.267 12.8 8.533 12.8 21.333 12.8 38.4-12.8 72.533-21.333 110.933-25.6 12.8-4.267 29.867 0 46.933 0 34.133 4.267 68.267 8.533 102.4 12.8s72.533 12.8 106.667 17.067c4.267 0 8.533 0 12.8 0s8.533 0 12.8-8.533c12.8-46.933 29.867-93.867 42.667-136.533 12.8-38.4 25.6-76.8 34.133-115.2-192 4.267-379.733 4.267-567.467 4.267zM456.533 247.467c34.133 8.533 64 12.8 98.133 4.267 8.533 0 12.8 0 12.8 8.533 17.067 34.133 42.667 59.733 76.8 72.533 38.4 17.067 76.8 21.333 115.2 8.533 34.133-8.533 59.733-29.867 81.067-55.467 25.6-34.133 38.4-72.533 34.133-119.467-8.533-51.2-34.133-89.6-76.8-115.2-51.2-34.133-132.267-29.867-179.2 12.8-42.667 34.133-59.733 76.8-64 128 0 8.533-4.267 12.8-12.8 17.067-21.333 4.267-42.667 4.267-64 0-8.533-4.267-8.533-4.267-8.533-12.8 0-21.333-4.267-46.933-12.8-68.267-12.8-29.867-34.133-55.467-64-72.533-55.467-38.4-136.533-34.133-183.467 8.533-42.667 34.133-64 76.8-59.733 128 0 59.733 29.867 106.667 85.333 136.533s115.2 25.6 170.667-12.8c12.8-8.533 21.333-17.067 34.133-29.867 0-8.533 8.533-21.333 17.067-38.4zM307.2 302.933c-64 0-115.2-51.2-115.2-110.933 0-64 46.933-115.2 110.933-115.2s115.2 46.933 115.2 110.933c4.267 64-46.933 115.2-110.933 115.2zM712.533 302.933c-64 0-115.2-51.2-115.2-110.933 0-64 46.933-115.2 110.933-115.2 59.733 0 115.2 46.933 115.2 110.933 0 59.733-46.933 115.2-110.933 115.2zM1024 409.6c-341.333 0-682.667 0-1024 0 0 0 0 0 0 0 17.067 8.533 29.867 17.067 46.933 21.333 42.667 12.8 85.333 29.867 132.267 42.667 34.133 8.533 68.267 17.067 102.4 21.333 89.6 17.067 183.467 21.333 277.333 17.067 55.467-4.267 110.933-8.533 166.4-17.067 38.4-4.267 72.533-12.8 110.933-21.333s72.533-21.333 110.933-34.133c21.333-4.267 51.2-17.067 76.8-29.867 0 0 0 0 0 0z" />
<glyph unicode="&#xe95d;" glyph-name="zone" d="M243.2 448c-12.8 17.067-25.6 34.133-38.4 51.2-34.133 46.933-68.267 98.133-89.6 153.6-17.067 34.133-25.6 72.533-17.067 110.933 8.533 51.2 38.4 89.6 85.333 110.933 59.733 25.6 132.267 8.533 174.933-34.133 34.133-38.4 42.667-81.067 34.133-132.267-8.533-46.933-29.867-85.333-51.2-123.733-29.867-46.933-59.733-89.6-89.6-132.267-4.267 0-4.267 0-8.533-4.267zM247.467 823.467c-46.933 0-89.6-38.4-89.6-89.6 0-46.933 38.4-89.6 85.333-89.6s89.6 38.4 89.6 85.333c0 55.467-38.4 93.867-85.333 93.867zM490.667 379.733l-17.067 25.6 12.8 8.533-34.133 183.467c0 0 0 8.533-8.533 8.533l-42.667 4.267c0 0-68.267-110.933-157.867-217.6 4.267 4.267-93.867 110.933-132.267 187.733l-110.933-51.2c0 0-4.267 0-4.267-8.533l25.6-145.067 34.133-21.333-8.533-21.333-17.067 8.533 59.733-332.8 213.333 102.4 238.933-21.333-51.2 290.133zM149.333 285.867c-12.8 4.267-29.867 12.8-42.667 17.067 4.267 8.533 4.267 17.067 8.533 21.333 17.067 0 29.867-4.267 42.667-12.8-4.267-8.533-4.267-17.067-8.533-25.6zM256 268.8c-17.067 0-34.133 4.267-46.933 4.267 0 8.533 4.267 17.067 4.267 25.6 12.8 0 29.867-4.267 42.667-4.267 0-8.533 0-17.067 0-25.6zM315.733 277.333c-4.267 8.533-4.267 12.8-8.533 21.333 17.067 8.533 29.867 17.067 42.667 21.333 4.267-8.533 8.533-12.8 8.533-21.333-12.8-8.533-25.6-12.8-42.667-21.333zM405.333 328.533c-4.267 8.533-8.533 12.8-12.8 21.333 12.8 8.533 25.6 17.067 38.4 25.6 4.267-4.267 8.533-12.8 12.8-21.333-8.533-8.533-21.333-17.067-38.4-25.6zM972.8 460.8l-29.867 25.6 12.8 21.333 12.8-8.533-34.133 187.733c0 0 0 8.533-8.533 8.533l-226.133 17.067-209.067-93.867c0 0-8.533-4.267-4.267-12.8l29.867-170.667 21.333-12.8-17.067-17.067 55.467-307.2 213.333 102.4 234.667-21.333-51.2 281.6zM580.267 465.067c-4.267 4.267-8.533 12.8-12.8 17.067 12.8 12.8 21.333 21.333 29.867 34.133 4.267-4.267 12.8-12.8 17.067-17.067-12.8-8.533-25.6-21.333-34.133-34.133zM657.067 541.867c-4.267 4.267-8.533 12.8-12.8 21.333 12.8 8.533 25.6 17.067 38.4 25.6 8.533-8.533 12.8-17.067 12.8-21.333-12.8-8.533-25.6-17.067-38.4-25.6zM797.867 571.733c-12.8 4.267-25.6 4.267-42.667 4.267 0 8.533 0 17.067 0 25.6 17.067 0 34.133 0 51.2-4.267-4.267-8.533-4.267-17.067-8.533-25.6zM891.733 520.533c-12.8 8.533-25.6 17.067-38.4 25.6 4.267 8.533 8.533 12.8 12.8 21.333 12.8-8.533 25.6-17.067 38.4-25.6-4.267-8.533-8.533-12.8-12.8-21.333z" />
<glyph unicode="&#xe95e;" glyph-name="inventory" d="M273.067 226.133c4.267 0 8.533 4.267 8.533 8.533v85.333h98.133v-221.867h-217.6v221.867h98.133v-81.067c0-8.533 8.533-12.8 12.8-12.8zM512 226.133c4.267 0 8.533 4.267 8.533 8.533v85.333h98.133v-221.867h-217.6v221.867h98.133v-81.067c0-8.533 8.533-12.8 12.8-12.8zM750.933 226.133c4.267 0 8.533 4.267 8.533 8.533v85.333h98.133v-221.867h-217.6v221.867h98.133v-81.067c4.267-8.533 8.533-12.8 12.8-12.8zM644.267 780.8h98.133v-81.067c0-4.267 4.267-8.533 8.533-8.533s8.533 4.267 8.533 8.533v81.067h98.133v-221.867h-217.6v221.867h4.267zM401.067 780.8h98.133v-81.067c0-4.267 4.267-8.533 8.533-8.533s8.533 4.267 8.533 8.533v81.067h98.133v-221.867h-213.333v221.867zM162.133 780.8h98.133v-81.067c0-4.267 4.267-8.533 8.533-8.533s8.533 4.267 8.533 8.533v81.067h98.133v-221.867h-213.333v221.867zM153.6 537.6h780.8v-38.4h-844.8v38.4zM68.267-42.667h-42.667v981.333h42.667v-908.8zM89.6 38.4v38.4h844.8v-38.4zM998.4-42.667h-42.667v981.333h42.667z" />
<glyph unicode="&#xe95f;" glyph-name="latestBuys" d="M183.467 750.933h712.533v-38.4h-768v38.4zM89.6 64c8.533 0 12.8 0 21.333-4.267v900.267h-42.667v-900.267c8.533 4.267 12.8 4.267 21.333 4.267zM955.733 512v448h-42.667v-413.867c17.067-12.8 29.867-21.333 42.667-34.133zM145.067-8.533c0-30.633-24.833-55.467-55.467-55.467s-55.467 24.833-55.467 55.467c0 30.633 24.833 55.467 55.467 55.467s55.467-24.833 55.467-55.467zM418.133 426.667h-290.133v-38.4h273.067c4.267 17.067 8.533 29.867 17.067 38.4zM392.533 106.667h-264.533v-38.4h281.6c-8.533 12.8-12.8 25.6-17.067 38.4zM725.333 247.467c-12.8 0-21.333-8.533-21.333-21.333s8.533-21.333 21.333-21.333c12.8 0 21.333 8.533 21.333 21.333 0 8.533-12.8 21.333-21.333 21.333zM721.067 541.867c-166.4 0-298.667-136.533-298.667-302.933s132.267-302.933 298.667-302.933c166.4 0 298.667 136.533 298.667 302.933 0 170.667-132.267 302.933-298.667 302.933zM725.333 34.133c-98.133 0-174.933 72.533-187.733 162.133h-34.133l51.2 64 59.733-64h-38.4c8.533-68.267 72.533-123.733 149.333-123.733 81.067 0 149.333 64 149.333 145.067s-68.267 145.067-149.333 145.067c-68.267 0-128-46.933-145.067-110.933l-21.333 29.867-17.067-12.8c8.533 29.867 25.6 55.467 46.933 76.8l-25.6 21.333c-4.267 4.267-4.267 12.8 0 17.067l12.8 12.8c4.267 4.267 12.8 4.267 17.067 0l25.6-25.6c21.333 12.8 51.2 25.6 76.8 25.6v29.867h-8.533c-8.533 0-12.8 4.267-12.8 12.8v17.067c0 8.533 4.267 12.8 12.8 12.8h59.733c8.533 0 12.8-4.267 12.8-12.8v-17.067c0-8.533-4.267-12.8-12.8-12.8h-8.533v-21.333c29.867-4.267 55.467-12.8 81.067-29.867l34.133 29.867c4.267 4.267 12.8 4.267 17.067 0l12.8-12.8c4.267-4.267 4.267-12.8 0-17.067l-25.6-25.6c29.867-34.133 51.2-76.8 51.2-128 4.267-102.4-81.067-187.733-183.467-187.733zM772.267 226.133c0-25.6-21.333-46.933-46.933-46.933s-46.933 21.333-46.933 46.933c0 25.6 21.333 46.933 46.933 46.933 8.533 0 17.067-4.267 21.333-4.267l46.933 46.933 21.333-21.333-46.933-46.933c4.267-4.267 4.267-12.8 4.267-21.333z" />
<glyph unicode="&#xe968;" glyph-name="wiki" d="M793.6 733.867c0 0 4.267 0 4.267 0l76.8 12.8v-42.667c0-34.133-21.333-68.267-46.933-72.533 0 0-4.267 0-4.267 0l-76.8-12.8v42.667c0 34.133 21.333 64 46.933 72.533zM742.4 597.333l38.4 4.267c12.8 0 25.6-12.8 25.6-29.867v-21.333l-38.4-4.267c-12.8 0-25.6 12.8-25.6 29.867v21.333zM618.667 699.733l68.267 8.533c25.6 4.267 42.667-21.333 42.667-55.467v-38.4l-68.267-8.533c-25.6-4.267-42.667 21.333-42.667 55.467v38.4zM665.6 588.8c4.267 0 4.267 0 0 0l59.733 4.267v-29.867c0-25.6-17.067-46.933-34.133-55.467 0 0-4.267 0-4.267 0l-55.467-8.533v29.867c4.267 29.867 17.067 51.2 34.133 59.733zM443.733 648.533c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267-4.267-8.533-4.267-12.8-4.267zM443.733 512c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267 0-8.533-4.267-12.8-4.267zM443.733 379.733c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267 0-8.533-4.267-12.8-4.267zM443.733 247.467c0 0-4.267 0-4.267 0-119.467 85.333-273.067 46.933-277.333 46.933s-8.533 0-12.8 8.533c0 4.267 0 8.533 8.533 12.8 0 0 42.667 12.8 98.133 8.533 51.2 0 128-12.8 196.267-59.733 4.267-4.267 4.267-8.533 4.267-12.8-4.267 0-8.533-4.267-12.8-4.267zM588.8 379.733c-4.267 0-4.267 0-8.533 4.267s0 8.533 4.267 12.8c68.267 46.933 140.8 59.733 196.267 59.733s93.867-8.533 98.133-8.533c4.267 0 8.533-8.533 8.533-12.8s-8.533-8.533-12.8-8.533v0c0 0-153.6 38.4-277.333-46.933-4.267 4.267-4.267 0-8.533 0zM588.8 247.467c-4.267 0-4.267 0-8.533 4.267s0 8.533 4.267 12.8c68.267 46.933 140.8 59.733 196.267 59.733s93.867-8.533 98.133-8.533c4.267 0 8.533-8.533 8.533-12.8s-8.533-8.533-12.8-8.533v0c0 0-153.6 38.4-277.333-46.933-4.267 4.267-4.267 0-8.533 0zM985.6 738.133v64l-8.533 4.267c-4.267 0-81.067 29.867-179.2 29.867-106.667 0-200.533-34.133-277.333-98.133-76.8 64-170.667 98.133-277.333 98.133-102.4 0-174.933-29.867-179.2-29.867l-12.8-4.267v-59.733c-34.133-4.267-51.2-17.067-51.2-34.133v-614.4h452.267c17.067-12.8 38.4-21.333 64-21.333s46.933 8.533 64 21.333h443.733v614.4c0 17.067-17.067 25.6-38.4 29.867v0zM512 145.067c-38.4 17.067-166.4 64-298.667 64-51.2 0-98.133-8.533-136.533-21.333v597.333c21.333 8.533 85.333 25.6 162.133 25.6 98.133 0 183.467-29.867 256-89.6v-358.4l17.067 17.067v-234.667zM955.733 183.467c-42.667 17.067-89.6 25.6-140.8 25.6-128 0-251.733-51.2-290.133-64v238.933l17.067-17.067v349.867c68.267 59.733 153.6 89.6 256 89.6 76.8 0 136.533-17.067 162.133-25.6v-597.333z" />
<glyph unicode="&#xe96c;" glyph-name="attach" d="M960 866.133c-42.667 42.667-98.133 64-157.867 64s-115.2-21.333-157.867-64l-593.067-593.067c-34.133-34.133-55.467-85.333-51.2-136.533 0-42.667 17.067-81.067 46.933-110.933 34.133-38.4 81.067-59.733 132.267-59.733 46.933 0 93.867 17.067 128 51.2l541.867 546.133c25.6 25.6 42.667 64 42.667 98.133s-12.8 68.267-38.4 93.867c-25.6 25.6-59.733 38.4-98.133 38.4-34.133 0-72.533-17.067-98.133-42.667l-354.133-354.133c-4.267 0-4.267-4.267-4.267-12.8s4.267-12.8 8.533-17.067 25.6-8.533 34.133 0l354.133 354.133c12.8 17.067 38.4 25.6 59.733 25.6 25.6 0 51.2-12.8 68.267-34.133 8.533-12.8 17.067-25.6 17.067-42.667 4.267-25.6-4.267-55.467-25.6-72.533l-541.867-541.867c-25.6-25.6-55.467-38.4-93.867-38.4-34.133 0-68.267 12.8-93.867 38.4s-38.4 59.733-38.4 93.867c0 34.133 12.8 68.267 38.4 93.867l588.8 584.533c34.133 34.133 76.8 51.2 123.733 51.2s89.6-17.067 123.733-51.2c34.133-34.133 51.2-76.8 51.2-123.733s-17.067-89.6-51.2-123.733l-401.067-401.067c-4.267-4.267-8.533-12.8-8.533-17.067 0-8.533 4.267-12.8 8.533-17.067 8.533-8.533 25.6-8.533 34.133 0l401.067 401.067c89.6 89.6 89.6 230.4 4.267 320z" />
<glyph unicode="&#xe96d;" glyph-name="zone2" d="M98.133 17.067c-4.267 29.867-12.8 64-17.067 93.867-17.067 98.133-34.133 192-51.2 290.133-12.8 46.933-21.333 98.133-29.867 149.333 0 4.267 0 8.533 4.267 8.533 42.667 21.333 85.333 42.667 128 59.733 0 0 0 0 4.267 0 4.267-8.533 8.533-12.8 12.8-21.333-21.333-8.533-42.667-21.333-64-29.867-17.067-8.533-34.133-17.067-51.2-21.333-4.267 0-4.267-4.267-4.267-8.533 8.533-42.667 17.067-85.333 25.6-132.267 0-4.267 4.267-4.267 4.267-8.533 8.533-4.267 17.067-8.533 25.6-17.067 0-4.267-4.267-12.8-8.533-17.067-4.267 4.267-12.8 4.267-17.067 8.533 17.067-102.4 38.4-209.067 55.467-311.467 17.067 8.533 29.867 12.8 42.667 21.333 51.2 25.6 102.4 51.2 153.6 72.533 4.267 0 8.533 0 17.067 0 68.267-4.267 136.533-12.8 204.8-17.067 0 0 4.267 0 8.533 0-4.267 17.067-4.267 34.133-8.533 51.2-12.8 68.267-25.6 136.533-38.4 204.8 0 8.533-4.267 17.067-12.8 25.6-4.267 4.267-4.267 8.533-8.533 12.8 12.8 4.267 8.533 17.067 8.533 25.6-8.533 51.2-17.067 106.667-29.867 157.867 0 4.267 0 4.267-8.533 4.267-17.067 0-38.4 4.267-55.467 4.267 4.267 8.533 8.533 17.067 12.8 21.333 0 0 4.267 4.267 8.533 4.267 17.067 0 34.133-4.267 46.933-4.267 4.267 0 8.533 0 12.8 0 68.267 29.867 132.267 64 200.533 93.867 4.267 4.267 8.533 8.533 12.8 8.533 68.267-4.267 136.533-8.533 204.8-17.067 8.533 0 17.067 0 29.867-4.267 4.267 0 8.533-4.267 8.533-8.533 12.8-64 25.6-132.267 34.133-196.267 17.067-102.4 38.4-204.8 55.467-311.467 0-8.533 4.267-17.067 4.267-25.6-17.067 0-34.133 4.267-51.2 4.267-42.667 4.267-89.6 8.533-132.267 12.8-17.067 0-38.4 4.267-55.467 4.267-4.267 0-12.8 0-17.067-4.267-68.267-29.867-132.267-64-200.533-93.867 0 0-4.267 0-8.533 0-76.8 8.533-149.333 12.8-226.133 21.333-4.267 0-8.533 0-12.8 0-72.533-34.133-140.8-68.267-213.333-102.4 0-4.267 0-8.533-4.267-8.533zM989.867 217.6c0 4.267 0 4.267 0 8.533-8.533 34.133-12.8 72.533-21.333 106.667-8.533 46.933-17.067 89.6-25.6 136.533 0 8.533-4.267 12.8-8.533 17.067-8.533 4.267-12.8 12.8-21.333 17.067 4.267 8.533 8.533 12.8 12.8 17.067 4.267-4.267 8.533-4.267 12.8-8.533-4.267 12.8-4.267 21.333-4.267 34.133-8.533 46.933-17.067 93.867-25.6 145.067 0 4.267-4.267 8.533-8.533 8.533-68.267 4.267-136.533 12.8-209.067 17.067-4.267 0-8.533 0-12.8-4.267-64-29.867-123.733-59.733-187.733-85.333-4.267-4.267-8.533-4.267-4.267-12.8 4.267-29.867 12.8-64 17.067-93.867 4.267-21.333 8.533-42.667 12.8-59.733 12.8-4.267 12.8-12.8 21.333-21.333-12.8-4.267-12.8-12.8-12.8-25.6 12.8-68.267 25.6-132.267 38.4-200.533 4.267-25.6 8.533-51.2 12.8-76.8 4.267 0 4.267 0 8.533 4.267 59.733 29.867 119.467 59.733 183.467 89.6-4.267 4.267 0 4.267 4.267 4.267 51.2-4.267 106.667-8.533 157.867-12.8 21.333 0 38.4-4.267 59.733-4.267zM260.267 469.333c-12.8 17.067-25.6 34.133-38.4 46.933-29.867 46.933-59.733 93.867-85.333 145.067-12.8 29.867-21.333 64-17.067 102.4 8.533 51.2 34.133 85.333 81.067 102.4 55.467 21.333 123.733 8.533 162.133-34.133 34.133-34.133 42.667-76.8 34.133-123.733-8.533-42.667-25.6-76.8-46.933-115.2-25.6-42.667-55.467-81.067-85.333-123.733 0 4.267-4.267 0-4.267 0zM260.267 819.2c-46.933 0-81.067-34.133-81.067-81.067s38.4-81.067 81.067-81.067c46.933 0 81.067 38.4 85.333 81.067 0 42.667-38.4 81.067-85.333 81.067zM358.4 349.867c4.267-8.533 4.267-12.8 8.533-21.333-12.8-4.267-25.6-12.8-38.4-17.067-4.267 8.533-4.267 12.8-8.533 21.333 12.8 4.267 25.6 12.8 38.4 17.067zM226.133 302.933c0 8.533 4.267 17.067 4.267 21.333 12.8 0 25.6-4.267 38.4-4.267 0-8.533 0-12.8 0-25.6-12.8 8.533-29.867 8.533-42.667 8.533zM413.867 354.133c-4.267 8.533-8.533 12.8-12.8 21.333 12.8 8.533 21.333 17.067 34.133 25.6 4.267-4.267 8.533-12.8 12.8-17.067-12.8-12.8-21.333-21.333-34.133-29.867zM179.2 341.333c-4.267-8.533-4.267-12.8-8.533-21.333-12.8 4.267-25.6 8.533-38.4 17.067 4.267 8.533 4.267 12.8 8.533 21.333 12.8-8.533 25.6-12.8 38.4-17.067zM682.667 580.267c-12.8-8.533-21.333-17.067-34.133-21.333-4.267 4.267-8.533 12.8-12.8 17.067 12.8 8.533 25.6 17.067 38.4 25.6 4.267-8.533 4.267-17.067 8.533-21.333zM878.933 558.933c-4.267-8.533-8.533-12.8-12.8-21.333-12.8 8.533-25.6 17.067-34.133 21.333 4.267 8.533 8.533 12.8 8.533 21.333 12.8-8.533 25.6-17.067 38.4-21.333zM571.733 486.4c-4.267 4.267-8.533 12.8-12.8 17.067 8.533 8.533 21.333 21.333 29.867 29.867 4.267-4.267 8.533-12.8 12.8-17.067-8.533-8.533-17.067-21.333-29.867-29.867zM785.067 610.133c-4.267-8.533-4.267-17.067-8.533-21.333-12.8 0-25.6 4.267-38.4 4.267 0 8.533 0 12.8 0 21.333 17.067 0 29.867-4.267 46.933-4.267z" />

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -403,8 +403,8 @@ module.exports = {
back,
backOnly,
backWatch,
backTestOnce,
backTest,
backTestOnce,
e2e,
i,
install,

View File

@ -70,7 +70,8 @@ module.exports = {
`front`,
`modules`,
`front/node_modules`,
`node_modules`
`node_modules`,
`print`
],
// An array of file extensions your modules use
@ -153,6 +154,7 @@ module.exports = {
// The glob patterns Jest uses to detect test files
testMatch: [
'**/front/**/*.spec.js',
'**/print/**/*.spec.js',
// 'loopback/**/*.spec.js',
// 'modules/*/back/**/*.spec.js'
// "**/__tests__/**/*.[jt]s?(x)",

View File

@ -134,5 +134,5 @@
"This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos",
"This thermograph id already exists": "La id del termógrafo ya existe",
"ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED"
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante"
}

View File

@ -36,7 +36,7 @@ module.exports = Self => {
relation: 'client',
scope: {
include: {
relation: 'salesPerson'
relation: 'salesPersonUser'
}
}
}
@ -53,7 +53,7 @@ module.exports = Self => {
const updatedClaim = await claim.updateAttributes(data);
// Get sales person from claim client
const salesPerson = claim.client().salesPerson();
const salesPerson = claim.client().salesPersonUser();
if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp) {
const origin = ctx.req.headers.origin;
const message = $t('Claim will be picked', {

View File

@ -34,17 +34,7 @@ class Controller extends ModuleCard {
scope: {
fields: ['salesPersonFk', 'name', 'email'],
include: {
relation: 'salesPerson',
scope: {
fields: ['userFk'],
include: {
relation: 'user',
scope: {
fields: ['nickname']
}
}
}
relation: 'salesPersonUser'
}
}
}

View File

@ -33,7 +33,7 @@
</vn-label-value>
<vn-label-value
label="Salesperson"
value="{{$ctrl.claim.client.salesPerson.user.nickname}}">
value="{{$ctrl.claim.client.salesPersonUser.nickname}}">
</vn-label-value>
<vn-label-value
label="Attended by"
@ -78,4 +78,4 @@
on-accept="$ctrl.deleteClaim()"
question="Delete claim"
message="Are you sure you want to delete this claim?">
</vn-confirm>
</vn-confirm>

View File

@ -42,11 +42,11 @@ module.exports = Self => {
r.workerFk,
c.code company,
r.created,
r.invoiceFk ref,
r.invoiceFk description,
NULL debit,
r.amountPaid credit,
r.bankFk,
u.nickname userNickname,
u.name userName,
r.clientFk,
FALSE hasPdf,
FALSE isInvoice

View File

@ -46,7 +46,11 @@
style="overflow: hidden; min-width: 14em;">
<div class="ellipsize"><b>{{::address.nickname}} - #{{::address.id}}</b></div>
<div class="ellipsize" name="street">{{::address.street}}</div>
<div class="ellipsize">{{::address.city}}, {{::address.province.name}}</div>
<div class="ellipsize">
<span ng-show="::address.postalCode">{{::address.postalCode}} -</span>
<span ng-show="::address.city">{{::address.city}},</span>
{{::address.province.name}}
</div>
<div class="ellipsize">
{{::address.phone}}<span ng-if="::address.mobile">, </span>
{{::address.mobile}}

View File

@ -16,7 +16,8 @@ class Controller extends Section {
'provinceFk',
'phone',
'mobile',
'isEqualizated'
'isEqualizated',
'postalCode'
],
order: [
'isDefaultAddress DESC',

View File

@ -17,11 +17,17 @@
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
url="Banks"
label="Bank"
url="Banks"
show-field="bank"
value-field="id"
ng-model="$ctrl.receipt.bankFk">
fields="['accountingTypeFk']"
include="{relation: 'accountingType'}"
order="id"
ng-model="$ctrl.receipt.bankFk"
search-function="{or: [{id: $search}, {bank: {like: '%'+ $search +'%'}}]}"
selection="$ctrl.bankSelection">
<tpl-item>{{id}}: {{bank}}</tpl-item>
</vn-autocomplete>
<vn-input-number
vn-focus
@ -31,6 +37,13 @@
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
label="Reference"
ng-model="$ctrl.receipt.description"
rule>
</vn-textfield>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>

View File

@ -50,6 +50,19 @@ class Controller extends Dialog {
return this.receipt.description;
}
get bankSelection() {
return this._bankSelection;
}
set bankSelection(value) {
this._bankSelection = value;
if (value) {
const accountingType = value.accountingType;
this.receipt.description = accountingType && accountingType.receiptDescription;
}
}
getAmountPaid() {
const filter = {
where: {

View File

@ -0,0 +1,76 @@
import './index';
describe('Client', () => {
describe('Component vnClientBalancCreate', () => {
let controller;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('client'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
let $scope = $rootScope.$new();
const $element = angular.element('<vn-client-balance-create></vn-client-balance-create>');
const $transclude = {
$$boundTransclude: {
$$slots: []
}
};
controller = $componentController('vnClientBalanceCreate', {$element, $scope, $transclude});
controller.receipt = {
clientFk: 101,
companyFk: 442
};
}));
describe('bankSelection() setter', () => {
it('should set the receipt description property', () => {
controller.bankSelection = {
id: 1,
bank: 'Cash',
accountingType: {
id: 2,
receiptDescription: 'Cash'
}
};
expect(controller.receipt.description).toEqual('Cash');
});
});
describe('getAmountPaid()', () => {
it('should make an http GET query and then set the receipt amountPaid property', () => {
controller.$params = {id: 101};
const receipt = controller.receipt;
const filter = {
where: {
clientFk: 101,
companyFk: 442
}
};
const serializedParams = $httpParamSerializer({filter});
$httpBackend.expect('GET', `ClientRisks?${serializedParams}`,).respond([{amount: 20}]);
controller.getAmountPaid();
$httpBackend.flush();
expect(receipt.amountPaid).toEqual(20);
});
});
describe('responseHandler()', () => {
it('should make an http POST query and then call to the parent responseHandler() method', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$params = {id: 101};
$httpBackend.expect('POST', `Receipts`,).respond({id: 1});
controller.responseHandler('accept');
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});
});

View File

@ -68,25 +68,33 @@
<span
vn-click-stop="workerDescriptor.show($event, balance.workerFk)"
class="link">
{{::balance.userNickname}}
{{::balance.userName}}
</span>
</vn-td>
<vn-td expand>
<div ng-show="::balance.ref">
<span
ng-if="balance.isInvoice"
title="{{'BILL' | translate: {ref: balance.ref} }}"
vn-click-stop="$ctrl.showInvoiceOutDescriptor($event, balance)"
class="link">
{{'BILL' | translate: {ref: balance.ref} }}
</span>
<span
ng-if="!balance.isInvoice"
title="{{::balance.ref}}">
{{::balance.ref}}
</span>
</div>
</vn-td>
<vn-td-editable disabled="balance.isInvoice || !$ctrl.isAdministrative" expand>
<text>
<div ng-show="::balance.description">
<span
ng-if="balance.isInvoice"
title="{{'BILL' | translate: {ref: balance.description} }}"
vn-click-stop="$ctrl.showInvoiceOutDescriptor($event, balance)"
class="link">
{{'BILL' | translate: {ref: balance.description} }}
</span>
<span
ng-if="!balance.isInvoice"
title="{{::balance.description}}">
{{balance.description}}
</span>
</div>
</text>
<field>
<vn-textfield vn-acl="administrative" class="dense" vn-focus
ng-model="balance.description"
on-change="$ctrl.changeDescription(balance)">
</vn-textfield>
</field>
</vn-td-editable>
<vn-td number>{{::balance.bankFk}}</vn-td>
<vn-td number expand>{{::balance.debit | currency: 'EUR':2}}</vn-td>
<vn-td number expand>{{::balance.credit | currency: 'EUR':2}}</vn-td>

View File

@ -39,6 +39,10 @@ class Controller extends Section {
this.getBalances();
}
get isAdministrative() {
return this.aclService.hasAny(['administrative']);
}
getData() {
return this.$.model.applyFilter(null, {
clientId: this.$params.id,
@ -58,7 +62,7 @@ class Controller extends Section {
return balance.companyFk === selectedCompany;
});
return currentBalance.amount;
return currentBalance && currentBalance.amount;
}
getBalances() {
@ -79,6 +83,13 @@ class Controller extends Section {
this.$.invoiceOutDescriptor.show(event.target, balance.id);
}
changeDescription(balance) {
const params = {description: balance.description};
const endpoint = `Receipts/${balance.id}`;
this.$http.patch(endpoint, params)
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
}
Controller.$inject = ['$element', '$scope'];

View File

@ -3,10 +3,12 @@ import './index';
describe('Client', () => {
describe('Component vnClientBalanceIndex', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('client'));
beforeEach(inject(($componentController, $rootScope) => {
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
let $scope = $rootScope.$new();
const $element = angular.element('<vn-client-balance-index></vn-client-balance-index>');
controller = $componentController('vnClientBalanceIndex', {$element, $scope});
@ -133,5 +135,21 @@ describe('Client', () => {
expect(controller.getBalances).toHaveBeenCalledWith();
});
});
describe('changeDescription()', () => {
it('should make an http PATCH query', () => {
const expectedParams = {description: 'Web'};
$httpBackend.expect('PATCH', `Receipts/1`, expectedParams).respond(200);
controller.changeDescription({
id: 1,
description: 'Web',
accountingType: {
description: 'Cash'
}
});
$httpBackend.flush();
});
});
});
});

View File

@ -0,0 +1,87 @@
module.exports = Self => {
Self.remoteMethod('editLatestBuys', {
description: 'Updates a column for one or more buys',
accessType: 'WRITE',
accepts: [{
arg: 'field',
type: 'String',
required: true,
description: `the column to edit`
},
{
arg: 'newValue',
type: 'Any',
required: true,
description: `The new value to save`
},
{
arg: 'lines',
type: ['Object'],
required: true,
description: `the buys which will be modified`
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/editLatestBuys`,
verb: 'POST'
}
});
Self.editLatestBuys = async(field, newValue, lines) => {
let modelName;
let identifier;
switch (field) {
case 'size':
case 'density':
case 'minPrice':
case 'description':
modelName = 'Item';
identifier = 'itemFk';
break;
case 'quantity':
case 'buyingValue':
case 'freightValue':
case 'packing':
case 'grouping':
case 'groupingMode':
case 'comissionValue':
case 'packageValue':
case 'price2':
case 'price3':
case 'weight':
modelName = 'Buy';
identifier = 'id';
}
const models = Self.app.models;
const model = models[modelName];
let tx = await model.beginTransaction({});
try {
let promises = [];
let options = {transaction: tx};
let targets = lines.map(line => {
return line[identifier];
});
let value = {};
value[field] = newValue;
// intentarlo con updateAll
for (let target of targets)
promises.push(model.upsertWithWhere({id: target}, value, options));
await Promise.all(promises);
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}
};
};

View File

@ -0,0 +1,171 @@
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.remoteMethodCtx('latestBuysFilter', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
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 name`,
}, {
arg: 'id',
type: 'Integer',
description: 'Item id',
}, {
arg: 'tags',
type: ['Object'],
description: 'List of tags to filter with',
http: {source: 'query'}
}, {
arg: 'description',
type: 'String',
description: 'The item description',
}, {
arg: 'salesPersonFk',
type: 'Integer',
description: 'The buyer of the item',
}, {
arg: 'active',
type: 'Boolean',
description: 'Whether the the item is or not active',
}, {
arg: 'visible',
type: 'Boolean',
description: 'Whether the the item is or not visible',
}, {
arg: 'typeFk',
type: 'Integer',
description: 'Type id',
}, {
arg: 'categoryFk',
type: 'Integer',
description: 'Category id',
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/latestBuysFilter`,
verb: 'GET'
}
});
Self.latestBuysFilter = async(ctx, filter) => {
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'i.id': value}
: {'i.name': {like: `%${value}%`}};
case 'id':
return {'i.id': value};
case 'description':
return {'i.description': {like: `%${value}%`}};
case 'categoryFk':
return {'ic.id': value};
case 'salesPersonFk':
return {'it.workerFk': value};
case 'typeFk':
return {'i.typeFk': value};
case 'active':
return {'i.isActive': value};
case 'visible':
if (value)
return {'v.visible': {gt: 0}};
else if (!value)
return {'v.visible': {lte: 0}};
}
});
filter = mergeFilters(ctx.args.filter, {where});
let stmts = [];
let stmt;
stmts.push('CALL cache.last_buy_refresh(FALSE)');
stmts.push('CALL cache.visible_refresh(@calc_id, FALSE, 1)');
stmt = new ParameterizedSQL(`
SELECT
i.image,
i.id AS itemFk,
i.size,
i.density,
i.typeFk,
i.family,
i.isActive,
i.minPrice,
i.description,
t.name AS type,
intr.description AS intrastat,
ori.code AS origin,
b.entryFk,
b.id,
b.quantity,
b.buyingValue,
b.freightValue,
b.isIgnored,
b.packing,
b.grouping,
b.groupingMode,
b.comissionValue,
b.packageValue,
b.price2,
b.price3,
b.ektFk,
b.weight
FROM cache.last_buy lb
LEFT JOIN cache.visible v ON v.item_id = lb.item_id
AND v.calc_id = @calc_id
JOIN item i ON i.id = lb.item_id
JOIN itemType it ON it.id = i.typeFk AND lb.warehouse_id = it.warehouseFk
JOIN buy b ON b.id = lb.buy_id
LEFT JOIN itemCategory ic ON ic.id = it.categoryFk
LEFT JOIN itemType t ON t.id = i.typeFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN origin ori ON ori.id = i.originFk`
);
if (ctx.args.tags) {
let i = 1;
for (const tag of ctx.args.tags) {
const tAlias = `it${i++}`;
if (tag.tagFk) {
stmt.merge({
sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id
AND ${tAlias}.tagFk = ?
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));
let buysIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql);
return buysIndex === 0 ? result : result[buysIndex];
};
};

View File

@ -0,0 +1,31 @@
const app = require('vn-loopback/server/server');
const model = app.models.Buy;
describe('Buy editLatestsBuys()', () => {
it('should change the value of a given column for the selected buys', async() => {
let ctx = {
args: {
search: 'Ranged weapon longbow 2m'
}
};
let [original] = await model.latestBuysFilter(ctx);
const field = 'size';
let newValue = 99;
const lines = [{itemFk: original.itemFk, id: original.id}];
await model.editLatestBuys(field, newValue, lines);
let [result] = await model.latestBuysFilter(ctx);
expect(result.size).toEqual(99);
newValue = original.size;
await model.editLatestBuys(field, newValue, lines);
let [restoredFixture] = await model.latestBuysFilter(ctx);
expect(restoredFixture.size).toEqual(original.size);
});
});

View File

@ -0,0 +1,139 @@
const app = require('vn-loopback/server/server');
describe('Buy latests buys filter()', () => {
it('should return the entry matching "search"', async() => {
let ctx = {
args: {
search: 'Ranged weapon longbow 2m'
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
let firstBuy = results[0];
expect(results.length).toEqual(1);
expect(firstBuy.size).toEqual(70);
});
it('should return the entry matching "id"', async() => {
let ctx = {
args: {
id: 1
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toEqual(1);
});
it('should return results matching "tags"', async() => {
let ctx = {
args: {
tags: [
{tagFk: 27, value: '2m'}
]
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(2);
});
it('should return results matching "categoryFk"', async() => {
let ctx = {
args: {
categoryFk: 1
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(4);
});
it('should return results matching "typeFk"', async() => {
let ctx = {
args: {
typeFk: 2
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(4);
});
it('should return results matching "active"', async() => {
let ctx = {
args: {
active: true
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(6);
});
it('should return results matching "not active"', async() => {
let ctx = {
args: {
active: false
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(0);
});
it('should return results matching "visible"', async() => {
let ctx = {
args: {
visible: true
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(5);
});
it('should return results matching "not visible"', async() => {
let ctx = {
args: {
visible: false
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(0);
});
it('should return results matching "salesPersonFk"', async() => {
let ctx = {
args: {
salesPersonFk: 35
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(6);
});
it('should return results matching "description"', async() => {
let ctx = {
args: {
description: 'Increases block'
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(1);
});
});

View File

@ -2,6 +2,9 @@
"Entry": {
"dataSource": "vn"
},
"Buy": {
"dataSource": "vn"
},
"EntryLog": {
"dataSource": "vn"
}

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/entry/editLatestBuys')(Self);
require('../methods/entry/latestBuysFilter')(Self);
};

View File

@ -0,0 +1,61 @@
{
"name": "Buy",
"base": "Loggable",
"log": {
"model": "EntryLog",
"relation": "entry"
},
"options": {
"mysql": {
"table": "buy"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"quantity": {
"type": "number"
},
"buyingValue": {
"type": "number"
},
"freightValue": {
"type": "number"
},
"packing": {
"type": "number"
},
"grouping": {
"type": "number"
},
"groupingMode": {
"type": "number"
},
"comissionValue": {
"type": "number"
},
"packageValue": {
"type": "number"
},
"price2": {
"type": "number"
},
"price3": {
"type": "number"
},
"weight": {
"type": "number"
}
},
"relations": {
"entry": {
"type": "belongsTo",
"model": "Entry",
"foreignKey": "entryFk",
"required": true
}
}
}

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "Entry",
"base": "VnModel",
"base": "Loggable",
"log": {
"model":"EntryLog"
},
@ -62,6 +62,18 @@
},
"loadPriority": {
"type": "number"
},
"supplierFk": {
"type": "number",
"required": true
},
"travelFk": {
"type": "number",
"required": true
},
"companyFk": {
"type": "number",
"required": true
}
},
"relations": {

View File

@ -0,0 +1,10 @@
import ngModule from '../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnEntryBasicData', {
template: require('./index.html'),
controller: Section,
bindings: {
entry: '<'
}
});

View File

@ -10,7 +10,8 @@ class Controller extends ModuleCard {
scope: {
fields: ['id', 'code']
}
}, {
},
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'],
@ -35,12 +36,14 @@ class Controller extends ModuleCard {
}
]
}
}, {
},
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
}, {
},
{
relation: 'currency'
}
]

View File

@ -0,0 +1,61 @@
<mg-ajax path="Entries" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.entry"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-icon color-marginal
icon="info"
vn-tooltip="Required fields (*)">
</vn-icon>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.entry.supplierFk"
url="Suppliers"
show-field="nickname"
search-function="{or: [{id: $search}, {nickname: {like: '%'+ $search +'%'}}]}"
value-field="id"
order="nickname"
label="Supplier"
required="true">
<tpl-item>
{{::id}} - {{::nickname}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.entry.travelFk"
url="Travels/filter"
search-function="$ctrl.searchFunction($search)"
value-field="id"
order="id"
label="Travel"
required="true">
<tpl-item>
{{::agencyModeName}} - {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}}) &#x2192;
{{::warehouseOutName}} ({{::landed | date: 'dd/MM/yyyy'}})
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
url="Companies"
label="Company"
show-field="code"
value-field="id"
ng-model="$ctrl.entry.companyFk"
required="true">
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Create"></vn-submit>
<vn-button ui-sref="entry.index" label="Cancel"></vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,43 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.entry = {
companyFk: this.vnConfig.companyFk
};
if (this.$params && this.$params.supplierFk)
this.entry.supplierFk = parseInt(this.$params.supplierFk);
if (this.$params && this.$params.travelFk)
this.entry.travelFk = parseInt(this.$params.travelFk);
if (this.$params && this.$params.companyFk)
this.entry.companyFk = parseInt(this.$params.companyFk);
}
onSubmit() {
this.$.watcher.submit().then(
res => this.$state.go('entry.card.basicData', {id: res.data.id})
);
}
searchFunction($search) {
return {or: [
{'agencyModeName': {like: `%${$search}%`}},
{'warehouseInName': {like: `%${$search}%`}},
{'warehouseOutName': {like: `%${$search}%`}},
{'shipped': new Date($search)},
{'landed': new Date($search)}
]};
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnEntryCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,2 @@
New entry: Nueva entrada
Required fields (*): Campos requeridos (*)

View File

@ -0,0 +1,10 @@
vn-entry-create {
vn-card {
position: relative
}
vn-icon[icon="info"] {
position: absolute;
top: 16px;
right: 16px
}
}

View File

@ -0,0 +1,3 @@
<slot-descriptor>
<vn-entry-descriptor></vn-entry-descriptor>
</slot-descriptor>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover {}
ngModule.vnComponent('vnEntryDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: Controller
});

View File

@ -11,15 +11,23 @@ class Controller extends Descriptor {
}
get travelFilter() {
return this.entry && JSON.stringify({
agencyFk: this.entry.travel.agencyFk
});
let travelFilter;
const entryTravel = this.entry && this.entry.travel;
if (entryTravel && entryTravel.agencyFk) {
travelFilter = this.entry && JSON.stringify({
agencyFk: entryTravel.agencyFk
});
}
return travelFilter;
}
get entryFilter() {
if (!this.entry) return null;
let entryTravel = this.entry && this.entry.travel;
const date = new Date(this.entry.travel.landed);
if (!entryTravel || !entryTravel.landed) return null;
const date = new Date(entryTravel.landed);
date.setHours(0, 0, 0, 0);
const from = new Date(date.getTime());
@ -35,6 +43,48 @@ class Controller extends Descriptor {
});
}
loadData() {
const filter = {
include: [
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
},
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
}
]
};
return this.getData(`Entries/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
showEntryReport() {
this.vnReport.show('entry-order', {
entryId: this.entry.id

View File

@ -1,12 +1,14 @@
import './index.js';
describe('Entry Component vnEntryDescriptor', () => {
let $httpBackend;
let controller;
const entry = {id: 2};
beforeEach(ngModule('entry'));
beforeEach(inject($componentController => {
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnEntryDescriptor', {$element: null}, {entry});
}));
@ -24,4 +26,18 @@ describe('Entry Component vnEntryDescriptor', () => {
expect(controller.vnReport.show).toHaveBeenCalledWith('entry-order', params);
});
});
describe('loadData()', () => {
it('should perform ask for the entry', () => {
let query = `Entries/${entry.id}`;
jest.spyOn(controller, 'getData');
$httpBackend.expectGET(query).respond();
controller.loadData();
$httpBackend.flush();
expect(controller.getData).toHaveBeenCalledTimes(1);
expect(controller.getData).toHaveBeenCalledWith(query, jasmine.any(Object));
});
});
});

View File

@ -2,8 +2,12 @@ export * from './module';
import './main';
import './index/';
import './create';
import './latest-buys';
import './search-panel';
import './latest-buys-search-panel';
import './descriptor';
import './descriptor-popover';
import './card';
import './summary';
import './log';

View File

@ -69,4 +69,16 @@
</vn-data-viewer>
<vn-travel-descriptor-popover
vn-id="travelDescriptor">
</vn-travel-descriptor-popover>
</vn-travel-descriptor-popover>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<a ui-sref="entry.create" vn-bind="+">
<vn-button class="round md vn-mb-sm"
icon="add"
vn-tooltip="New entry"
tooltip-position="left">
</vn-button>
</a>
</vn-vertical>
</div>

View File

@ -12,4 +12,6 @@ Reference: Referencia
Created: Creado
Booked: Facturado
Is inventory: Inventario
Notes: Notas
Notes: Notas
Status: Estado
Selection: Selección

View File

@ -0,0 +1,167 @@
<mg-ajax path="Tags" options="mgIndex as tags"></mg-ajax>
<div class="search-panel">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
label="General search"
ng-model="filter.search"
info="Search items by id, name or barcode"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-focus
url="ItemCategories"
label="Category"
show-field="name"
value-field="id"
ng-model="filter.categoryFk">
</vn-autocomplete>
<vn-autocomplete
url="ItemTypes"
label="Type"
where="{categoryFk: filter.categoryFk}"
show-field="name"
value-field="id"
ng-model="filter.typeFk"
fields="['categoryFk']"
include="'category'">
<tpl-item>
<div>{{name}}</div>
<div class="text-caption text-secondary">
{{category.name}}
</div>
</tpl-item>>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
disabled="false"
ng-model="filter.salesPersonFk"
url="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
label="Is active"
ng-model="filter.active"
triple-state="true">
</vn-check>
<vn-check
label="Is visible"
ng-model="filter.visible"
triple-state="true">
</vn-check>
</vn-horizontal>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
Tags
</vn-one>
<vn-icon-button
vn-none
vn-bind="+"
vn-tooltip="Add tag"
icon="add_circle"
ng-click="filter.tags.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal ng-repeat="itemTag in filter.tags">
<vn-autocomplete
vn-id="tag"
ng-model="itemTag.tagFk"
data="tags.model"
show-field="name"
label="Tag"
on-change="itemTag.value = null">
</vn-autocomplete>
<vn-textfield
ng-show="tag.selection.isFree !== false"
vn-id="text"
label="Value"
ng-model="itemTag.value">
</vn-textfield>
<vn-autocomplete
ng-show="tag.selection.isFree === false"
url="{{$ctrl.getSourceTable(tag.selection)}}"
label="Value"
ng-model="itemTag.value"
show-field="name"
value-field="name">
</vn-autocomplete>
<vn-icon-button
vn-none
vn-tooltip="Remove tag"
icon="delete"
ng-click="filter.tags.splice($index, 1)"
tabindex="-1">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
More fields
</vn-one>
<vn-icon-button
vn-none
vn-bind="+"
vn-tooltip="Add field"
icon="add_circle"
ng-click="$ctrl.fieldFilters.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal ng-repeat="fieldFilter in $ctrl.fieldFilters">
<vn-autocomplete
label="Field"
ng-model="fieldFilter.name"
data="$ctrl.moreFields"
value-field="name"
show-field="label"
show-filter="false"
translate-fields="['label']"
selection="info"
on-change="fieldFilter.value = null">
</vn-autocomplete>
<vn-one ng-switch="info.type">
<div ng-switch-when="Number">
<vn-input-number
label="Value"
ng-model="fieldFilter.value">
</vn-input-number>
</div>
<div ng-switch-when="Boolean">
<vn-check
label="Value"
ng-model="fieldFilter.value">
</vn-check>
</div>
<div ng-switch-when="Date">
<vn-date-picker
label="Value"
ng-model="fieldFilter.value">
</vn-date-picker>
</div>
<div ng-switch-default>
<vn-textfield
label="Value"
ng-model="fieldFilter.value">
</vn-textfield>
</div>
</vn-one>
<vn-icon-button
vn-none
vn-tooltip="Remove field"
icon="delete"
ng-click="$ctrl.removeField($index, fieldFilter.name)"
tabindex="-1">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,79 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
constructor($element, $) {
super($element, $);
let model = 'Item';
let moreFields = ['description', 'name'];
let properties;
let validations = window.validations;
if (validations && validations[model])
properties = validations[model].properties;
else
properties = {};
this.moreFields = [];
for (let field of moreFields) {
let prop = properties[field];
this.moreFields.push({
name: field,
label: prop ? prop.description : field,
type: prop ? prop.type : null
});
}
}
get filter() {
let filter = this.$.filter;
for (let fieldFilter of this.fieldFilters)
filter[fieldFilter.name] = fieldFilter.value;
return filter;
}
set filter(value) {
if (!value)
value = {};
if (!value.tags)
value.tags = [{}];
this.fieldFilters = [];
for (let field of this.moreFields) {
if (value[field.name] != undefined) {
this.fieldFilters.push({
name: field.name,
value: value[field.name],
info: field
});
}
}
this.$.filter = value;
}
getSourceTable(selection) {
if (!selection || selection.isFree === true)
return null;
if (selection.sourceTable) {
return ''
+ selection.sourceTable.charAt(0).toUpperCase()
+ selection.sourceTable.substring(1) + 's';
} else if (selection.sourceTable == null)
return `ItemTags/filterItemTags/${selection.id}`;
}
removeField(index, field) {
this.fieldFilters.splice(index, 1);
this.$.filter[field] = undefined;
}
}
ngModule.component('vnLatestBuysSearchPanel', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,45 @@
import './index.js';
describe('Entry', () => {
describe('Component vnLatestBuysSearchPanel', () => {
let $element;
let controller;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject($componentController => {
$element = angular.element(`<vn-latest-buys-search-panel></vn-latest-buys-search-panel>`);
controller = $componentController('vnLatestBuysSearchPanel', {$element});
}));
describe('getSourceTable()', () => {
it(`should return null if there's no selection`, () => {
let selection = null;
let result = controller.getSourceTable(selection);
expect(result).toBeNull();
});
it(`should return null if there's a selection but its isFree property is truthy`, () => {
let selection = {isFree: true};
let result = controller.getSourceTable(selection);
expect(result).toBeNull();
});
it(`should return the formated sourceTable concatenated to a path`, () => {
let selection = {sourceTable: 'hello guy'};
let result = controller.getSourceTable(selection);
expect(result).toEqual('Hello guys');
});
it(`should return a path if there's no sourceTable and the selection has an id`, () => {
let selection = {id: 99};
let result = controller.getSourceTable(selection);
expect(result).toEqual(`ItemTags/filterItemTags/${selection.id}`);
});
});
});
});

View File

@ -0,0 +1,177 @@
<vn-crud-model
vn-id="model"
url="Buys/latestBuysFilter"
limit="20"
data="$ctrl.buys">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
panel="vn-latest-buys-search-panel"
placeholder="Search by item id or name"
info="You can search by item id or name"
suggested-filter="{isActive: true}"
model="model"
auto-state="false">
</vn-searchbar>
</vn-portal>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl">
<vn-card>
<vn-table
model="model"
show-fields="$ctrl.showFields"
vn-smart-table="latestBuys">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th field="Image">Picture</vn-th>
<vn-th smart-table-ignore field="id">Id</vn-th>
<vn-th field="packing">Packing</vn-th>
<vn-th field="grouping">Grouping</vn-th>
<vn-th field="description" style="text-align: center">Description</vn-th>
<vn-th field="size">Size</vn-th>
<vn-th field="type">Type</vn-th>
<vn-th field="intrastat">Intrastat</vn-th>
<vn-th field="origin">Origin</vn-th>
<vn-th field="density">Density</vn-th>
<vn-th field="isActive">Active</vn-th>
<vn-th field="family">Family</vn-th>
<vn-th field="entryFk">Entry</vn-th>
<vn-th field="quantity">Quantity</vn-th>
<vn-th field="buyingValue">Buying value</vn-th>
<vn-th field="freightValue">Freight value</vn-th>
<vn-th field="comissionValue" expand>Commission value</vn-th>
<vn-th field="packageValue" expand>Package value</vn-th>
<vn-th field="isIgnored">Is ignored</vn-th>
<vn-th expand field="price2">Grouping price</vn-th>
<vn-th expand field="price3">Packing price</vn-th>
<vn-th field="minPrice">Min price</vn-th>
<vn-th field="ektFk">Ekt</vn-th>
<vn-th field="weight">Weight</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="buy in $ctrl.buys"
class="clickable vn-tr search-result"
ui-sref="entry.card.buy({id: {{::buy.entryFk}}})">
<vn-td shrink>
<vn-check
ng-model="buy.checked"
vn-click-stop>
</vn-check>
</vn-td>
<vn-td shrink >
<img
ng-src="{{::$root.imagePath}}/catalog/50x50/{{::buy.image}}"
zoom-image="{{::$root.imagePath}}/catalog/1600x900/{{::buy.image}}"
vn-click-stop
on-error-src/>
</vn-td>
<vn-td shrink>
<span
vn-click-stop="itemDescriptor.show($event, buy.itemFk)"
class="link">
{{::buy.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>
<vn-chip translate-attr="buy.groupingMode == 2 ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': buy.groupingMode == 2}">
<span translate>{{::buy.packing | dashIfEmpty}}</span>
</vn-chip>
</vn-td>
<vn-td number>
<vn-chip translate-attr="buy.groupingMode == 1 ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': buy.groupingMode == 1}">
<span translate>{{::buy.grouping | dashIfEmpty}}</span>
</vn-chip>
</vn-td>
<vn-td vn-two title="{{::buy.description}}">
{{::buy.description | dashIfEmpty}}
</vn-td>
<vn-td number>{{::buy.size}}</vn-td>
<vn-td shrink title="{{::buy.type}}">
{{::buy.type}}
</vn-td>
<vn-td shrink title="{{::item.intrastat}}">
{{::buy.intrastat}}
</vn-td>
<vn-td shrink>{{::buy.origin}}</vn-td>
<vn-td shrink>{{::buy.density}}</vn-td>
<vn-td shrink>
<vn-check
disabled="true"
ng-model="::buy.isActive">
</vn-check>
</vn-td>
<vn-td shrink>{{::buy.family}}</vn-td>
<vn-td shrink>
<span
vn-click-stop="entryDescriptor.show($event, buy.entryFk)"
class="link">
{{::buy.entryFk}}
</span>
</vn-td>
<vn-td number>{{::buy.quantity}}</vn-td>
<vn-td number>{{::buy.buyingValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.freightValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.comissionValue | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.packageValue | currency: 'EUR':2}}</vn-td>
<vn-td shrink>{{::buy.isIgnored}}</vn-td>
<vn-td number>{{::buy.price2 | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.price3 | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.minPrice | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::buy.ektFk | dashIfEmpty}}</vn-td>
<vn-td number>{{::buy.weight}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<vn-button class="round sm vn-mb-sm"
icon="edit"
ng-show="$ctrl.totalChecked > 0"
ng-click="edit.show($event)"
vn-tooltip="Edit buy(s)"
tooltip-position="left">
</vn-button>
</vn-vertical>
</div>
<vn-dialog class="edit"
vn-id="edit"
on-accept="$ctrl.onEditAccept()"
on-close="$ctrl.editedColumn = null"
message="Edit buy(s)">
<tpl-body>
<vn-horizontal>
<vn-autocomplete
vn-two
ng-model="$ctrl.editedColumn.field"
data="$ctrl.columns"
value-field="field"
show-field="displayName"
label="Field to edit">
</vn-autocomplete>
<vn-textfield
vn-one
label="Value"
ng-model="$ctrl.editedColumn.newValue">
</vn-textfield>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Create</button>
</tpl-buttons>
</vn-dialog>
<vn-item-descriptor-popover
vn-id="itemDescriptor">
</vn-item-descriptor-popover>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>

View File

@ -0,0 +1,78 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.showFields = {
id: false,
actions: false
};
this.editedColumn;
}
get columns() {
if (this._columns) return this._columns;
this._columns = [
{field: 'quantity', displayName: this.$t('Quantity')},
{field: 'buyingValue', displayName: this.$t('Buying value')},
{field: 'freightValue', displayName: this.$t('Freight value')},
{field: 'packing', displayName: this.$t('Packing')},
{field: 'grouping', displayName: this.$t('Grouping')},
{field: 'comissionValue', displayName: this.$t('Commission value')},
{field: 'packageValue', displayName: this.$t('Package value')},
{field: 'price2', displayName: this.$t('Grouping price')},
{field: 'price3', displayName: this.$t('Packing price')},
{field: 'weight', displayName: this.$t('Weight')},
{field: 'description', displayName: this.$t('Description')},
{field: 'minPrice', displayName: this.$t('Min price')},
{field: 'size', displayName: this.$t('Size')},
{field: 'density', displayName: this.$t('Density')}
];
return this._columns;
}
get checked() {
const buys = this.$.model.data || [];
const checkedBuys = [];
for (let buy of buys) {
if (buy.checked)
checkedBuys.push(buy);
}
return checkedBuys;
}
uncheck() {
const lines = this.checked;
for (let line of lines) {
if (line.checked)
line.checked = false;
}
}
get totalChecked() {
return this.checked.length;
}
onEditAccept() {
let data = {
field: this.editedColumn.field,
newValue: this.editedColumn.newValue,
lines: this.checked
};
return this.$http.post('Buys/editLatestBuys', data)
.then(() => {
this.uncheck();
this.$.model.refresh();
});
}
}
ngModule.component('vnEntryLatestBuys', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,82 @@
import './index.js';
describe('Entry', () => {
describe('Component vnEntryLatestBuys', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, $compile, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
let $element = $compile('<vn-entry-latest-buys></vn-entry-latest-buys')($rootScope);
controller = $componentController('vnEntryLatestBuys', {$element});
controller.$ = {
model: {refresh: () => {}},
edit: {hide: () => {}}
};
}));
describe('get columns', () => {
it(`should return a set of columns`, () => {
let result = controller.columns;
let length = result.length;
let anyColumn = Object.keys(result[Math.floor(Math.random() * Math.floor(length))]);
expect(anyColumn).toContain('field', 'displayName');
});
});
describe('get checked', () => {
it(`should return a set of checked lines`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
];
let result = controller.checked;
expect(result.length).toEqual(3);
});
});
describe('uncheck()', () => {
it(`should clear the selection of lines on the controller`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
];
let result = controller.checked;
expect(result.length).toEqual(3);
controller.uncheck();
result = controller.checked;
expect(result.length).toEqual(0);
});
});
describe('onEditAccept()', () => {
it(`should perform a query to update columns`, () => {
controller.editedColumn = {field: 'my field', newValue: 'the new value'};
let query = 'Buys/editLatestBuys';
$httpBackend.expectPOST(query).respond();
controller.onEditAccept();
$httpBackend.flush();
const result = controller.checked;
expect(result.length).toEqual(0);
});
});
});
});

View File

@ -0,0 +1 @@
Minimun amount: Minimun purchase quantity

View File

@ -0,0 +1,13 @@
Edit buy(s): Editar compra(s)
Buying value: Precio
Freight value: Porte
Commission value: Comisión
Package value: Embalaje
Is ignored: Ignorado
Grouping price: Precio grouping
Packing price: Precio packing
Min price: Precio min
Ekt: Ekt
Weight: Peso
Minimun amount: Cantidad mínima de compra
Field to edit: Campo a editar

View File

@ -1,5 +1,6 @@
#Ordenar alfabeticamente
entry: entrada
Latest buys: Últimas compras
# Sections

View File

@ -2,13 +2,15 @@
"module": "entry",
"name": "Entries",
"icon": "icon-entry",
"dependencies": ["travel"],
"dependencies": ["travel", "item"],
"validations": true,
"menus": {
"main": [
{"state": "entry.index", "icon": "icon-entry"}
{"state": "entry.index", "icon": "icon-entry"},
{"state": "entry.latestBuys", "icon": "icon-latestBuys"}
],
"card": [
{"state": "entry.card.basicData", "icon": "settings"},
{"state": "entry.card.buy", "icon": "icon-lines"},
{"state": "entry.card.log", "icon": "history"}
]
@ -24,7 +26,20 @@
"url": "/index?q",
"state": "entry.index",
"component": "vn-entry-index",
"description": "Entries"
"description": "Entries",
"acl": ["buyer"]
}, {
"url": "/latest-buys?q",
"state": "entry.latestBuys",
"component": "vn-entry-latest-buys",
"description": "Latest buys",
"acl": ["buyer"]
}, {
"url": "/create?supplierFk&travelFk&companyFk",
"state": "entry.create",
"component": "vn-entry-create",
"description": "New entry",
"acl": ["buyer"]
}, {
"url": "/:id",
"state": "entry.card",
@ -38,6 +53,14 @@
"params": {
"entry": "$ctrl.entry"
}
}, {
"url": "/basic-data",
"state": "entry.card.basicData",
"component": "vn-entry-basic-data",
"description": "Basic data",
"params": {
"entry": "$ctrl.entry"
}
}, {
"url" : "/log",
"state": "entry.card.log",

View File

@ -20,37 +20,66 @@ module.exports = Self => {
const models = Self.app.models;
try {
const imageQueue = await Self.find({limit: 25});
const rootPath = models.Image.getPath();
const tempPath = path.join(rootPath, 'temp');
const tempPath = path.join('/tmp/salix-image');
// Create temporary path
await fs.mkdir(tempPath, {recursive: true});
for (let image of imageQueue) {
const timer = setInterval(async() => {
const image = await Self.findOne({where: {error: null}});
// Exit loop
if (!image) return clearInterval(timer);
const fileName = `${image.itemFk}.png`;
const filePath = path.join(tempPath, fileName);
const file = fs.createWriteStream(filePath);
https.get(image.url, async response => {
response.pipe(file);
const writeStream = fs.createWriteStream(filePath);
writeStream.on('open', () => {
https.get(image.url, async response => {
if (response.statusCode != 200) {
const error = new Error(`Could not download the image. Status code ${response.statusCode}`);
return await errorHandler(image.itemFk, error, filePath);
}
response.pipe(writeStream);
}).on('error', async error => {
await errorHandler(image.itemFk, error, filePath);
});
});
file.on('finish', async function() {
await models.Image.registerImage('catalog', fileName, filePath);
await image.destroy();
writeStream.on('error', async error => {
await errorHandler(image.itemFk, error, filePath);
});
file.on('error', err => {
fs.unlink(filePath);
throw err;
writeStream.on('finish', async function() {
try {
await models.Image.registerImage('catalog', fileName, filePath);
await image.destroy();
} catch (error) {
await errorHandler(image.itemFk, error, filePath);
}
});
}, 1000);
} catch (error) {
throw new Error('Try-catch error: ', error);
}
async function errorHandler(rowId, error, filePath) {
try {
const row = await Self.findById(rowId);
if (!row)
throw new Error(`Could not update due error ${error}`);
await row.updateAttribute('error', error);
if (filePath && fs.existsSync(filePath))
await fs.unlink(filePath);
} catch (err) {
throw new Error(`ErrorHandler error: ${err}`);
}
return imageQueue;
} catch (e) {
throw e;
}
};
};

View File

@ -12,42 +12,38 @@ module.exports = Self => {
arg: 'filter',
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}, {
arg: 'tags',
type: ['Object'],
description: 'List of tags to filter with',
http: {source: 'query'}
}, {
arg: 'search',
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',
description: 'Category id',
http: {source: 'query'}
}, {
arg: 'typeFk',
type: 'Integer',
description: 'Type id',
http: {source: 'query'}
}, {
arg: 'isActive',
type: 'Boolean',
description: 'Whether the the item is o not active',
http: {source: 'query'}
description: 'Whether the the item is or not active',
}, {
arg: 'salesPersonFk',
type: 'Integer',
description: 'The buyer of the item',
http: {source: 'query'}
}, {
arg: 'description',
type: 'String',
description: 'The item description',
}
],
returns: {

View File

@ -1,28 +0,0 @@
module.exports = Self => {
Self.remoteMethod('getLastEntries', {
description: 'Returns last entries',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object',
required: true,
description: 'itemFk, id'
}],
returns: {
type: 'Array',
root: true
},
http: {
path: `/getLastEntries`,
verb: 'GET'
}
});
Self.getLastEntries = async filter => {
let where = filter.where;
let query = `CALL vn.itemLastEntries(?, ?)`;
let [lastEntries] = await Self.rawSql(query, [where.itemFk, where.date]);
return lastEntries;
};
};

View File

@ -0,0 +1,62 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('lastEntriesFilter', {
description: 'Returns last entries',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object',
required: true,
description: 'itemFk, id'
}],
returns: {
type: 'Array',
root: true
},
http: {
path: `/lastEntriesFilter`,
verb: 'GET'
}
});
Self.lastEntriesFilter = async filter => {
const conn = Self.dataSource.connector;
const stmt = new ParameterizedSQL(
`SELECT
w.id AS warehouseFk,
w.name AS warehouse,
tr.landed,
b.id AS buyFk,
b.entryFk,
b.isIgnored,
b.price2,
b.price3,
b.stickers,
b.packing,
b.grouping,
b.groupingMode,
b.weight,
i.stems,
b.quantity,
b.buyingValue,
b.packageFk ,
s.id AS supplierFk,
s.name AS supplier
FROM itemType it
RIGHT JOIN (entry e
LEFT JOIN supplier s ON s.id = e.supplierFk
RIGHT JOIN buy b ON b.entryFk = e.id
LEFT JOIN item i ON i.id = b.itemFk
LEFT JOIN ink ON ink.id = i.inkFk
LEFT JOIN travel tr ON tr.id = e.travelFk
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
LEFT JOIN origin o ON o.id = i.originFk
) ON it.id = i.typeFk
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id`);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -1,22 +0,0 @@
const app = require('vn-loopback/server/server');
describe('item getLastEntries()', () => {
it('should return one entry for a given item', async() => {
let date = new Date();
let filter = {where: {itemFk: 1, date: date}};
let result = await app.models.Item.getLastEntries(filter);
expect(result.length).toEqual(1);
});
it('should return five entries for a given item', async() => {
let date = new Date();
date.setMonth(date.getMonth() - 2, 1);
let filter = {where: {itemFk: 1, date: date}};
let result = await app.models.Item.getLastEntries(filter);
expect(result.length).toEqual(5);
});
});

View File

@ -0,0 +1,24 @@
const app = require('vn-loopback/server/server');
describe('item lastEntriesFilter()', () => {
const minDate = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxDate = new Date(value);
maxHour.setHours(23, 59, 59, 59);
it('should return one entry for a given item', async() => {
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
const result = await app.models.Item.lastEntriesFilter(filter);
expect(result.length).toEqual(1);
});
it('should return five entries for a given item', async() => {
minDate.setMonth(minDate.getMonth() - 2, 1);
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
const result = await app.models.Item.lastEntriesFilter(filter);
expect(result.length).toEqual(5);
});
});

View File

@ -16,6 +16,10 @@
"url": {
"type": "String",
"required": true
},
"error": {
"type": "String",
"required": true
}
},
"relations": {

View File

@ -5,7 +5,7 @@ module.exports = Self => {
require('../methods/item/clone')(Self);
require('../methods/item/updateTaxes')(Self);
require('../methods/item/getBalance')(Self);
require('../methods/item/getLastEntries')(Self);
require('../methods/item/lastEntriesFilter')(Self);
require('../methods/item/getSummary')(Self);
require('../methods/item/getCard')(Self);
require('../methods/item/regularize')(Self);

View File

@ -122,6 +122,9 @@
"mysql": {
"columnName": "expenceFk"
}
},
"minPrice": {
"type": "number"
}
},
"relations": {

View File

@ -33,13 +33,13 @@
"retAccount": {
"type": "Number"
},
"commision": {
"commission": {
"type": "Boolean"
},
"created": {
"type": "Date"
},
"poscodeFk": {
"postcodeFk": {
"type": "Number"
},
"isActive": {

View File

@ -109,6 +109,14 @@
ng-model="$ctrl.item.density"
rule>
</vn-input-number>
<vn-input-number
vn-one
min="0"
step="0.01"
label="Compression"
ng-model="$ctrl.item.compression"
rule>
</vn-input-number>
<vn-input-number
vn-one
min="0"

View File

@ -8,24 +8,24 @@
<vn-table
model="model"
show-fields="$ctrl.showFields"
vn-uvc="itemIndex">
vn-smart-table="itemIndex">
<vn-thead>
<vn-tr>
<vn-th th-id="picture" shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th field="id" shrink>Id</vn-th>
<vn-th th-id="grouping" shrink>Grouping</vn-th>
<vn-th th-id="packing" shrink>Packing</vn-th>
<vn-th th-id="description" style="text-align: center">Description</vn-th>
<vn-th th-id="stems" shrink>Stems</vn-th>
<vn-th th-id="size" shrink>Size</vn-th>
<vn-th th-id="niche" shrink>Niche</vn-th>
<vn-th th-id="type" shrink>Type</vn-th>
<vn-th th-id="category" shrink>Category</vn-th>
<vn-th th-id="intrastat" shrink>Intrastat</vn-th>
<vn-th th-id="origin" shrink>Origin</vn-th>
<vn-th th-id="salesperson" shrink>Buyer</vn-th>
<vn-th th-id="density" shrink>Density</vn-th>
<vn-th th-id="active" shrink>Active</vn-th>
<vn-th field="grouping" shrink>Grouping</vn-th>
<vn-th field="packing" shrink>Packing</vn-th>
<vn-th field="description" style="text-align: center">Description</vn-th>
<vn-th field="stems" shrink>Stems</vn-th>
<vn-th field="size" shrink>Size</vn-th>
<vn-th field="niche" shrink>Niche</vn-th>
<vn-th field="type" shrink>Type</vn-th>
<vn-th field="category" shrink>Category</vn-th>
<vn-th field="intrastat" shrink>Intrastat</vn-th>
<vn-th field="origin" shrink>Origin</vn-th>
<vn-th field="salesperson" shrink>Buyer</vn-th>
<vn-th field="density" shrink>Density</vn-th>
<vn-th field="active" shrink>Active</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>

View File

@ -1,11 +1,15 @@
<vn-crud-model
vn-id="model"
url="Items/getLastEntries"
url="Items/lastEntriesFilter"
filter="::$ctrl.filter"
data="entries"
auto-load="false">
auto-load="true"
order="landed DESC, buyFk DESC"
limit="20">
</vn-crud-model>
<vn-vertical>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal>
@ -20,8 +24,8 @@
<vn-thead>
<vn-tr>
<vn-th vn-tooltip="Ignored" center>Ig</vn-th>
<vn-th>Warehouse</vn-th>
<vn-th>Landed</vn-th>
<vn-th field="warehouseFk">Warehouse</vn-th>
<vn-th field="landed">Landed</vn-th>
<vn-th number>Entry</vn-th>
<vn-th number vn-tooltip="Price Per Unit">P.P.U</vn-th>
<vn-th number vn-tooltip="Price Per Package">P.P.P</vn-th>
@ -29,11 +33,11 @@
<vn-th number>Packing</vn-th>
<vn-th number>Grouping</vn-th>
<vn-th number class="expendable">Stems</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th number class="expendable">Cost</vn-th>
<vn-th number>Kg.</vn-th>
<vn-th number>Cube</vn-th>
<vn-th class="expendable">Provider</vn-th>
<vn-th field="packageFk" number>Cube</vn-th>
<vn-th field="supplierFk" class="expendable">Provider</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
@ -70,6 +74,30 @@
</vn-tbody>
</vn-table>
</vn-vertical>
<vn-pagination model="model"></vn-pagination>
</vn-card>
</vn-vertical>
</vn-data-viewer>
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()" >
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()" >
Remove all filters
</vn-item>
</slot-menu>
</vn-contextmenu>

View File

@ -6,17 +6,23 @@ class Controller extends Section {
constructor($element, $) {
super($element, $);
let defaultDate = new Date();
defaultDate.setDate(defaultDate.getDate() - 75);
defaultDate.setHours(0, 0, 0, 0);
const from = new Date();
from.setDate(from.getDate() - 75);
from.setHours(0, 0, 0, 0);
const to = new Date();
to.setDate(to.getDate() + 10);
to.setHours(23, 59, 59, 59);
this.filter = {
where: {
itemFk: this.$params.id,
date: defaultDate
shipped: {
between: [from, to]
}
}
};
this._date = defaultDate;
this._date = from;
}
set date(value) {
@ -31,6 +37,32 @@ class Controller extends Section {
get date() {
return this._date;
}
exprBuilder(param, value) {
switch (param) {
case 'id':
case 'quantity':
case 'packageFk':
return {[`b.${param}`]: value};
case 'supplierFk':
return {[`s.id`]: value};
case 'warehouseFk':
return {'tr.warehouseInFk': value};
case 'landed':
return {'tr.landed': {
between: this.dateRange(value)}
};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
}
Controller.$inject = ['$element', '$scope'];

View File

@ -56,12 +56,14 @@
label="For me"
ng-model="filter.mine">
</vn-check>
<vn-check
vn-one
triple-state="true"
label="Confirmed"
ng-model="filter.isOk">
</vn-check>
<vn-autocomplete
vn-one
ng-model="filter.state"
data="$ctrl.states"
value-field="code"
label="State">
<tpl-item>{{name}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>

View File

@ -1,7 +1,19 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
constructor($element, $) {
super($element, $);
this.states = [
{code: 'pending', name: this.$t('Pending')},
{code: 'accepted', name: this.$t('Accepted')},
{code: 'denied', name: this.$t('Denied')}
];
}
}
ngModule.vnComponent('vnRequestSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
controller: Controller
});

View File

@ -24,14 +24,15 @@
<vn-tr>
<vn-th field="ticketFk" number>Ticket ID</vn-th>
<vn-th field="shipped" expand>Shipped</vn-th>
<vn-th field="description" expand>Description</vn-th>
<vn-th field="description" filter-enabled="false" expand>Description</vn-th>
<vn-th field="quantity" number editable>Requested</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th field="atenderNickname">Atender</vn-th>
<vn-th field="itemFk">Item</vn-th>
<vn-th field="attenderName">Atender</vn-th>
<vn-th>Item</vn-th>
<vn-th field="saleQuantity">Achieved</vn-th>
<vn-th field="description">Concept</vn-th>
<vn-th field="description" filter-enabled="false">Concept</vn-th>
<vn-th field="isOk">State</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
@ -83,7 +84,7 @@
{{request.itemDescription}}
</span>
</vn-td>
<vn-td>{{$ctrl.getState(request.isOk)}}</vn-td>
<vn-td translate>{{$ctrl.getState(request.isOk)}}</vn-td>
<vn-td>
<vn-icon
ng-if="request.response.length"
@ -128,4 +129,29 @@
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
</vn-dialog>
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()" >
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()" >
Remove all filters
</vn-item>
</slot-menu>
</vn-contextmenu>

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