7953-devToTest_2438 #2942

Merged
alexm merged 199 commits from 7953-devToTest_2438 into test 2024-09-10 07:22:31 +00:00
73 changed files with 2118 additions and 1895 deletions
Showing only changes of commit 1e91d689c5 - Show all commits

View File

@ -29,18 +29,8 @@ module.exports = Self => {
return token;
// Schedule to remove current token
setTimeout(async() => {
let exists;
try {
exists = await models.AccessToken.findById(token.id);
exists && await Self.logout(token.id);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
const body = {error: error.message, now: Date.now(), userId: token?.userId ?? null, exists};
await handleError(body);
throw new Error(error);
}
setTimeout(() => {
Self.logout(token.id);
}, courtesyTime * 1000);
// Get scopes
@ -53,14 +43,20 @@ module.exports = Self => {
return {id: accessToken.id, ttl: accessToken.ttl};
} catch (error) {
const body = {error: error.message, now: Date.now(), userId: token?.userId ?? null, createTokenOptions, isNotExceeded};
await handleError(body);
const body = {
error: error.message,
userId: token?.userId ?? null,
token: token?.id,
scopes: token?.scopes,
createTokenOptions,
isNotExceeded
};
await handleError(JSON.stringify(body));
throw new Error(error);
}
};
};
async function handleError(body, tag = 'renewToken') {
body = JSON.stringify(body);
await models.Application.rawSql('CALL util.debugAdd(?,?);', [tag, body]);
async function handleError(body) {
await models.Application.rawSql('CALL util.debugAdd(?,?);', ['renewToken', body]);
}

File diff suppressed because it is too large Load Diff

View File

@ -1455,6 +1455,11 @@ INSERT IGNORE INTO `tables_priv` VALUES ('','vn','grafana','accountingType','gu
INSERT IGNORE INTO `tables_priv` VALUES ('','vn','grafana','bankPolicyDetail','guillermo@db-proxy2.servers.dc.verdnatura.es','0000-00-00 00:00:00','Select','');
INSERT IGNORE INTO `tables_priv` VALUES ('','vn','grafana','bankPolicyReview','guillermo@db-proxy2.servers.dc.verdnatura.es','0000-00-00 00:00:00','Select','');
INSERT IGNORE INTO `tables_priv` VALUES ('','vn','grafana','bankPolicy','guillermo@db-proxy2.servers.dc.verdnatura.es','0000-00-00 00:00:00','Select','');
INSERT IGNORE INTO `tables_priv` VALUES ('','edi','hedera-web','imapMultiConfig','guillermo@db-proxy2.servers.dc.verdnatura.es','0000-00-00 00:00:00','Select','');
INSERT IGNORE INTO `tables_priv` VALUES ('','hedera','salesAssistant','orderConfig','root@localhost','0000-00-00 00:00:00','Select','');
INSERT IGNORE INTO `tables_priv` VALUES ('','vn','grafana','itemEntryOut','guillermo@db-proxy2.servers.dc.verdnatura.es','0000-00-00 00:00:00','Select','');
INSERT IGNORE INTO `tables_priv` VALUES ('','vn','grafana','itemEntryIn','guillermo@db-proxy2.servers.dc.verdnatura.es','0000-00-00 00:00:00','Select','');
INSERT IGNORE INTO `tables_priv` VALUES ('','vn','grafana','itemShelvingSale','guillermo@db-proxy1.servers.dc.verdnatura.es','0000-00-00 00:00:00','Select','');
/*!40000 ALTER TABLE `tables_priv` ENABLE KEYS */;
/*!40000 ALTER TABLE `columns_priv` DISABLE KEYS */;
@ -2160,9 +2165,7 @@ INSERT IGNORE INTO `procs_priv` VALUES ('','vn','buyer','buy_recalcPricesByAwb'
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','buyer','buy_recalcPricesByEntry','PROCEDURE','jenkins@db-proxy2.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','util','hr','accountNumberToIban','FUNCTION','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','util','financial','accountNumberToIban','FUNCTION','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','administrative','supplier_statement','PROCEDURE','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','buyer','supplier_statement','PROCEDURE','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','hrBoss','supplier_statement','PROCEDURE','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','buyer','supplier_statementWithEntries','PROCEDURE','guillermo@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','adminBoss','XDiario_check','PROCEDURE','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','buyer','travel_getDetailFromContinent','PROCEDURE','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','claimManager','entry_getTransfer','PROCEDURE','jenkins@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
@ -2193,6 +2196,8 @@ INSERT IGNORE INTO `procs_priv` VALUES ('','vn','production','itemShelvingSale_
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','production','sectorCollection_getMyPartial','PROCEDURE','carlosap@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','grafana-write','item_ValuateInventory','PROCEDURE','guillermo@db-proxy1.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','guest','ticketCalculatePurge','PROCEDURE','jenkins@db-proxy2.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','buyer','buy_getUltimate','PROCEDURE','guillermo@db-proxy2.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
INSERT IGNORE INTO `procs_priv` VALUES ('','vn','cooler','buy_getUltimate','PROCEDURE','guillermo@db-proxy2.servers.dc.verdnatura.es','Execute','0000-00-00 00:00:00');
/*!40000 ALTER TABLE `procs_priv` ENABLE KEYS */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
@ -2237,7 +2242,7 @@ INSERT IGNORE INTO `global_priv` VALUES ('','grafana-write','{\"access\":0,\"ve
INSERT IGNORE INTO `global_priv` VALUES ('','greenhouseBoss','{\"access\":0,\"version_id\":101106,\"is_role\":true}');
INSERT IGNORE INTO `global_priv` VALUES ('','guest','{\"access\": 0, \"max_questions\": 40000, \"max_updates\": 1000, \"max_connections\": 150000, \"max_user_connections\": 200, \"max_statement_time\": 0.000000, \"is_role\": true, \"version_id\": 101106}');
INSERT IGNORE INTO `global_priv` VALUES ('','handmadeBoss','{\"access\":0,\"version_id\":101106,\"is_role\":true}');
INSERT IGNORE INTO `global_priv` VALUES ('','hedera-web','{\"access\":0,\"version_id\":100707,\"is_role\":true}');
INSERT IGNORE INTO `global_priv` VALUES ('','hedera-web','{\"access\":0,\"version_id\":101106,\"is_role\":true}');
INSERT IGNORE INTO `global_priv` VALUES ('','hr','{\"access\": 0, \"is_role\": true, \"version_id\": 101106}');
INSERT IGNORE INTO `global_priv` VALUES ('','hrBoss','{\"access\":0,\"version_id\":101106,\"is_role\":true}');
INSERT IGNORE INTO `global_priv` VALUES ('','invoicing','{\"access\":0,\"version_id\":100707,\"is_role\":true}');

File diff suppressed because it is too large Load Diff

View File

@ -1423,6 +1423,70 @@ DELIMITER ;
--
USE `salix`;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER `salix`.`ACL_beforeInsert`
BEFORE INSERT ON `ACL`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER `salix`.`ACL_beforeUpdate`
BEFORE UPDATE ON `ACL`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER `salix`.`ACL_afterDelete`
AFTER DELETE ON `ACL`
FOR EACH ROW
BEGIN
INSERT INTO ACLLog
SET `action` = 'delete',
`changedModel` = 'Acl',
`changedModelId` = OLD.id,
`userFk` = account.myUser_getId();
END */;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
--
-- Current Database: `srt`
@ -9220,7 +9284,13 @@ BEGIN
SET NEW.editorFk = account.myUser_getId();
IF NOT (NEW.routeFk <=> OLD.routeFk) THEN
IF NEW.isSigned THEN
IF NEW.isSigned AND NOT (
SELECT (COUNT(s.id) = COUNT(cb.saleFk)
AND SUM(s.quantity) = SUM(cb.quantity))
FROM sale s
LEFT JOIN claimBeginning cb ON cb.saleFk = s.id
WHERE s.ticketFk = NEW.id
) THEN
CALL util.throw('A signed ticket cannot be rerouted');
END IF;
INSERT IGNORE INTO routeRecalc(routeFk)
@ -11142,4 +11212,4 @@ USE `vn2008`;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2024-08-06 6:03:19
-- Dump completed on 2024-08-20 7:45:23

View File

@ -412,7 +412,7 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
(1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(1104, -30, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH));
INSERT INTO `vn`.`mandateType`(`id`, `name`)
INSERT INTO `vn`.`mandateType`(`id`, `code`)
VALUES
(1, 'B2B'),
(2, 'CORE'),
@ -632,7 +632,7 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'),
('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'),
('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'),
('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'),
('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'multiple'),
('R', 'Rectificativa', 1, 'NATIONAL', 0, NULL),
('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick');
@ -3945,11 +3945,11 @@ VALUES
(35, 'ES12346B12345679', 3, 241);
INSERT INTO vn.accountDetailType
(id, description)
(id, description, code)
VALUES
(1, 'IBAN'),
(2, 'SWIFT'),
(3, 'Referencia Remesas'),
(4, 'Referencia Transferencias'),
(5, 'Referencia Nominas'),
(6, 'ABA');
(1, 'IBAN', 'iban'),
(2, 'SWIFT', 'swift'),
(3, 'Referencia Remesas', 'remRef'),
(4, 'Referencia Transferencias', 'trnRef'),
(5, 'Referencia Nominas', 'payRef'),
(6, 'ABA', 'aba');

View File

@ -1,13 +1,9 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` EVENT `srt`.`moving_clean`
ON SCHEDULE EVERY 5 MINUTE
ON SCHEDULE EVERY 15 MINUTE
STARTS '2022-01-21 00:00:00.000'
ON COMPLETION PRESERVE
ENABLE
COMMENT 'Llama a srt.moving_clean para que elimine y notifique de registr'
DO BEGIN
CALL srt.moving_clean();
END$$
DO CALL srt.moving_clean()$$
DELIMITER ;

View File

@ -3,61 +3,69 @@ CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `srt`.`moving_clean`()
BEGIN
/**
* Elimina movimientos por inactividad
*
*/
DECLARE vExpeditionFk INT;
DECLARE vBufferToFk INT;
DECLARE vBufferFromFk INT;
DECLARE done BOOL DEFAULT FALSE;
DECLARE cur CURSOR FOR
SELECT m.expeditionFk, m.bufferToFk, m.bufferFromFk
FROM srt.moving m
JOIN srt.config c
JOIN (SELECT bufferFk, SUM(isActive) hasBox
FROM srt.photocell
GROUP BY bufferFk) sub ON sub.bufferFk = m.bufferFromFk
WHERE m.created < TIMESTAMPADD(MINUTE, - c.movingMaxLife , util.VN_NOW())
DECLARE vStateOutFk INT
DEFAULT (SELECT id FROM expeditionState WHERE `description` = 'OUT');
DECLARE vDone BOOL;
DECLARE vSorter CURSOR FOR
SELECT m.expeditionFk, m.bufferFromFk
FROM moving m
JOIN (
SELECT bufferFk, SUM(isActive) hasBox
FROM photocell
GROUP BY bufferFk
) sub ON sub.bufferFk = m.bufferFromFk
WHERE m.created < (util.VN_NOW() - INTERVAL (SELECT movingMaxLife FROM config) MINUTE)
AND NOT sub.hasBox;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
OPEN cur;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
bucle: LOOP
OPEN vSorter;
l: LOOP
SET vDone = FALSE;
FETCH vSorter INTO vExpeditionFk, vBufferFromFk;
FETCH cur INTO vExpeditionFk, vBufferToFk, vBufferFromFk;
IF done THEN
LEAVE bucle;
IF vDone THEN
LEAVE l;
END IF;
DELETE FROM srt.moving
START TRANSACTION;
SELECT id
FROM moving
WHERE expeditionFk = vExpeditionFk
FOR UPDATE;
DELETE FROM moving
WHERE expeditionFk = vExpeditionFk;
UPDATE srt.expedition e
JOIN srt.expeditionState es ON es.description = 'OUT'
SET
bufferFk = NULL,
SELECT id
FROM expedition
WHERE id = vExpeditionFk
OR (bufferFk = vBufferFromFk AND `position` > 0)
FOR UPDATE;
UPDATE expedition
SET bufferFk = NULL,
`position` = NULL,
stateFk = es.id
WHERE e.id = vExpeditionFk;
stateFk = vStateOutFk
WHERE id = vExpeditionFk;
UPDATE srt.expedition e
SET e.`position` = e.`position` - 1
WHERE e.bufferFk = vBufferFromFk
AND e.`position` > 0;
CALL vn.mail_insert(
'pako@verdnatura.es, carles@verdnatura.es',
NULL,
CONCAT('Moving_clean. Expedition: ', vExpeditionFk, ' estaba parada'),
CONCAT('Expedition: ', vExpeditionFk,' vBufferToFk: ', vBufferToFk)
);
END LOOP bucle;
CLOSE cur;
UPDATE expedition
SET `position` = `position` - 1
WHERE bufferFk = vBufferFromFk
AND `position` > 0;
COMMIT;
END LOOP l;
CLOSE vSorter;
END$$
DELIMITER ;

View File

@ -0,0 +1,12 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `srt`.`buffer_afterDelete`
AFTER DELETE ON `buffer`
FOR EACH ROW
BEGIN
INSERT INTO buffer
SET `action` = 'delete',
`changedModel` = 'Buffer',
`changedModelId` = OLD.id,
`userFk` = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `srt`.`buffer_beforeInsert`
BEFORE INSERT ON `buffer`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `srt`.`buffer_beforeUpdate`
BEFORE UPDATE ON `buffer`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,12 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `srt`.`config_afterDelete`
AFTER DELETE ON `config`
FOR EACH ROW
BEGIN
INSERT INTO config
SET `action` = 'delete',
`changedModel` = 'Config',
`changedModelId` = OLD.id,
`userFk` = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `srt`.`config_beforeInsert`
BEFORE INSERT ON `config`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `srt`.`config_beforeUpdate`
BEFORE UPDATE ON `config`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -1,26 +1,32 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceSerial`(vClientFk INT, vCompanyFk INT, vType CHAR(1))
RETURNS char(1) CHARSET utf8mb3 COLLATE utf8mb3_general_ci
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceSerial`(vClientFk INT, vCompanyFk INT, vType CHAR(15))
RETURNS char(2) CHARSET utf8mb3 COLLATE utf8mb3_general_ci
DETERMINISTIC
BEGIN
/**
* Obtiene la serie de de una factura
* Obtiene la serie de una factura
* dependiendo del area del cliente.
*
* @param vClientFk Id del cliente
* @param vCompanyFk Id de la empresa
* @param vType Tipo de factura ["R", "M", "G"]
* @return Serie de la factura
* @param vType Tipo de factura ['global','multiple','quick']
* @return vSerie de la factura
*/
DECLARE vTaxArea VARCHAR(25);
DECLARE vSerie CHAR(1);
DECLARE vTaxArea VARCHAR(25) COLLATE utf8mb3_general_ci;
DECLARE vSerie CHAR(2);
IF (SELECT hasInvoiceSimplified FROM client WHERE id = vClientFk) THEN
RETURN 'S';
END IF;
SELECT clientTaxArea(vClientFk, vCompanyFk) INTO vTaxArea;
SELECT invoiceSerialArea(vType,vTaxArea) INTO vSerie;
SELECT addressTaxArea(defaultAddressFk, vCompanyFk) INTO vTaxArea
FROM client
WHERE id = vClientFk;
SELECT code INTO vSerie
FROM invoiceOutSerial
WHERE `type` = vType AND taxAreaFk = vTaxArea;
RETURN vSerie;
END$$
DELIMITER ;

View File

@ -1,34 +0,0 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceSerialArea`(vType CHAR(1), vTaxArea VARCHAR(25))
RETURNS char(1) CHARSET utf8mb3 COLLATE utf8mb3_unicode_ci
DETERMINISTIC
BEGIN
DECLARE vSerie CHAR(1);
IF vType = 'R' THEN
SELECT
CASE vTaxArea
WHEN 'CEE' THEN 'H'
WHEN 'WORLD' THEN 'E'
ELSE 'T'
END INTO vSerie;
-- Factura multiple
ELSEIF vType = 'M' THEN
SELECT
CASE vTaxArea
WHEN 'CEE' THEN 'H'
WHEN 'WORLD' THEN 'E'
ELSE 'M'
END INTO vSerie;
-- Factura global
ELSEIF vType = 'G' THEN
SELECT
CASE vTaxArea
WHEN 'CEE' THEN 'V'
WHEN 'WORLD' THEN 'X'
ELSE 'A'
END INTO vSerie;
END IF;
RETURN vSerie;
END$$
DELIMITER ;

View File

@ -97,7 +97,7 @@ BEGIN
AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase())
THEN
-- el trigger añade el siguiente Id_Factura correspondiente a la vSerial
-- el trigger añade el siguiente ref correspondiente a la vSerial
INSERT INTO invoiceOut(
ref,
serial,

View File

@ -189,7 +189,7 @@ BEGIN
SELECT * FROM sales
UNION ALL
SELECT * FROM orders
ORDER BY shipped DESC,
ORDER BY shipped,
(inventorySupplierFk = entityId) DESC,
alertLevel DESC,
isTicket,

View File

@ -90,7 +90,7 @@ BEGIN
IF vIsTaxDataChecked THEN
CALL invoiceOut_newFromClient(
vClientFk,
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'multiple')),
vShipped,
vCompanyFk,
NULL,

View File

@ -14,16 +14,28 @@ BEGIN
DECLARE vTicketFk INT;
DECLARE cTickets CURSOR FOR
SELECT id FROM ticket
WHERE refFk IS NULL
AND ((vScope = 'client' AND clientFk = vId)
OR (vScope = 'address' AND addressFk = vId));
SELECT DISTINCT t.id
FROM ticket t
LEFT JOIN tItems ti ON ti.id = t.id
WHERE t.refFk IS NULL
AND ((vScope = 'client' AND t.clientFk = vId)
OR (vScope = 'address' AND t.addressFk = vId)
OR (vScope = 'item' AND ti.id)
);
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET vDone = TRUE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
CREATE OR REPLACE TEMPORARY TABLE tItems
(PRIMARY KEY (id))
ENGINE = MEMORY
SELECT DISTINCT t.id
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN itemTaxCountry itc ON itc.itemFk = s.itemFk
WHERE t.refFk IS NULL
AND (vScope = 'item' AND itc.itemFk = vId);
OPEN cTickets;
myLoop: LOOP
SET vDone = FALSE;
FETCH cTickets INTO vTicketFk;
@ -34,7 +46,8 @@ BEGIN
CALL ticket_recalc(vTicketFk, NULL);
END LOOP;
CLOSE cTickets;
DROP TEMPORARY TABLE tItems;
END$$
DELIMITER ;

View File

@ -0,0 +1,12 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`roadmap_beforeInsert`
BEFORE INSERT ON `roadmap`
FOR EACH ROW
BEGIN
IF NEW.driver1Fk IS NOT NULL THEN
SET NEW.driverName = (SELECT firstName FROM worker WHERE id = NEW.driver1Fk);
ELSE
SET NEW.driverName = NULL;
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,12 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`roadmap_beforeUpdate`
BEFORE UPDATE ON `roadmap`
FOR EACH ROW
BEGIN
IF NEW.driver1Fk IS NOT NULL THEN
SET NEW.driverName = (SELECT firstName FROM worker WHERE id = NEW.driver1Fk);
ELSE
SET NEW.driverName = NULL;
END IF;
END$$
DELIMITER ;

View File

@ -2,5 +2,5 @@ CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn2008`.`mandato_tipo`
AS SELECT `m`.`id` AS `idmandato_tipo`,
`m`.`name` AS `Nombre`
`m`.`code` AS `Nombre`
FROM `vn`.`mandateType` `m`

View File

@ -0,0 +1 @@
ALTER TABLE srt.moving DROP INDEX moving_fk1_idx;

View File

@ -0,0 +1,4 @@
ALTER TABLE vn.invoiceOutSerial
MODIFY COLUMN `type` enum('global','quick','multiple') CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL;
CREATE UNIQUE INDEX invoiceOutSerial_taxAreaFk_IDX USING BTREE ON vn.invoiceOutSerial (taxAreaFk,`type`);

View File

@ -0,0 +1,3 @@
UPDATE vn.invoiceOutSerial
SET `type`='multiple'
WHERE `description` LIKE '%Múltiple%';

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.mandateType
CHANGE name code VARCHAR(45) NOT NULL,
ADD UNIQUE (code);

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.accountDetailType
ADD COLUMN code VARCHAR(45),
ADD UNIQUE (code);

View File

@ -0,0 +1,9 @@
UPDATE vn.accountDetailType
SET code = CASE description
WHEN 'IBAN' THEN 'iban'
WHEN 'SWIFT' THEN 'swift'
WHEN 'Referencia Remesas' THEN 'remRef'
WHEN 'Referencia Transferencias' THEN 'trnRef'
WHEN 'Referencia Nominas' THEN 'payRef'
WHEN 'ABA' THEN 'aba'
END;

View File

@ -0,0 +1,3 @@
ALTER TABLE hedera.tpvMerchantEnable
MODIFY COLUMN companyFk int(10) unsigned NOT NULL,
ADD CONSTRAINT tpvMerchantEnable_company_FK FOREIGN KEY (companyFk) REFERENCES vn.company(id) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,19 @@
CREATE OR REPLACE TABLE `srt`.`bufferLog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`originFk` int(11) DEFAULT NULL,
`userFk` int(10) unsigned DEFAULT NULL,
`action` set('insert','update','delete','select') NOT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`description` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
`changedModel` enum('Buffer', 'Config') NOT NULL DEFAULT 'Buffer',
`oldInstance` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`oldInstance`)),
`newInstance` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`newInstance`)),
`changedModelId` int(11) NOT NULL,
`changedModelValue` varchar(45) DEFAULT NULL,
`summaryId` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `logBufferUserFk` (`userFk`),
KEY `bufferLog_changedModel` (`changedModel`,`changedModelId`,`creationDate`),
KEY `bufferLog_originFk` (`originFk`,`creationDate`),
CONSTRAINT `bufferUserFk` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;

View File

@ -0,0 +1 @@
ALTER TABLE srt.buffer ADD editorFk int(10) unsigned DEFAULT NULL NULL;

View File

@ -0,0 +1 @@
ALTER TABLE srt.config ADD editorFk int(10) unsigned DEFAULT NULL NULL;

View File

@ -0,0 +1,9 @@
CREATE TABLE vn.quadMindsApiConfig (
id int(10) unsigned NULL PRIMARY KEY,
`url` varchar(255) DEFAULT NULL NULL,
`key` varchar(255) DEFAULT NULL NULL,
CONSTRAINT quadMindsConfig_check CHECK (id = 1)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb3
COLLATE=utf8mb3_unicode_ci;

View File

@ -0,0 +1,6 @@
ALTER TABLE vn.roadmap
ADD COLUMN m3 INT UNSIGNED NULL,
ADD COLUMN driver2Fk INT UNSIGNED NULL,
ADD COLUMN driver1Fk INT UNSIGNED NULL,
ADD CONSTRAINT roadmap_worker_FK FOREIGN KEY (driver1Fk) REFERENCES vn.worker(id) ON DELETE RESTRICT ON UPDATE CASCADE,
ADD CONSTRAINT roadmap_worker_FK_2 FOREIGN KEY (driver2Fk) REFERENCES vn.worker(id) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,24 @@
DELETE FROM `salix`.`ACL`
WHERE `model` = 'Ticket'
AND `property` = 'refund'
AND `accessType` = 'WRITE'
AND `permission` = 'ALLOW'
AND `principalType` = 'ROLE'
AND `principalId` = 'salesAssistant';
UPDATE `salix`.`ACL`
SET `property` = 'cloneAll'
WHERE `model` = 'Ticket'
AND `property` = 'refund'
AND `accessType` = 'WRITE'
AND `permission` = 'ALLOW'
AND `principalType` = 'ROLE'
AND `principalId` IN ('invoicing', 'claimManager', 'logistic');
DELETE FROM `salix`.`ACL`
WHERE `model` = 'Ticket'
AND `property` = 'clone'
AND `accessType` = 'WRITE'
AND `permission` = 'ALLOW'
AND `principalType` = 'ROLE'
AND `principalId` = 'administrative';

View File

@ -108,7 +108,7 @@ module.exports = Self => {
async function notifyStateChange(ctx, workerId, claim, newState) {
const models = Self.app.models;
const url = await models.Url.getUrl();
const url = await models.Url.getUrl('lilium');
const $t = ctx.req.__;
const message = $t(`Claim state has changed to`, {
@ -122,7 +122,7 @@ module.exports = Self => {
async function notifyPickUp(ctx, workerId, claim) {
const models = Self.app.models;
const url = await models.Url.getUrl();
const url = await models.Url.getUrl('lilium');
const $t = ctx.req.__; // $translate
const message = $t('Claim will be picked', {

View File

@ -27,7 +27,7 @@ module.exports = Self => {
// Renew mandate
if (mandate) {
const mandateType = await models.MandateType.findOne({
where: {name: mandate.type}
where: {code: mandate.type}
});
const oldMandate = await models.Mandate.findOne({

View File

@ -12,7 +12,7 @@
"type": "number",
"description": "Identifier"
},
"name": {
"code": {
"type": "string"
}
}

View File

@ -26,7 +26,7 @@
<vn-tr ng-repeat="mandate in mandates">
<vn-td number>{{::mandate.id}}</vn-td>
<vn-td>{{::mandate.company.code}}</vn-td>
<vn-td>{{::mandate.mandateType.name}}</vn-td>
<vn-td>{{::mandate.mandateType.code}}</vn-td>
<vn-td shrink-datetime>{{::mandate.created | date:'dd/MM/yyyy HH:mm' | dashIfEmpty}}</vn-td>
<vn-td shrink-datetime>{{::mandate.finished | date:'dd/MM/yyyy HH:mm' | dashIfEmpty}}</vn-td>
</vn-tr>

View File

@ -9,7 +9,7 @@ class Controller extends Section {
{
relation: 'mandateType',
scope: {
fields: ['id', 'name']
fields: ['id', 'code']
}
}, {
relation: 'company',

View File

@ -46,7 +46,7 @@ module.exports = Self => {
}
});
filter = mergeFilters(args.filter, {where});
const filter = mergeFilters(args.filter, {where});
const stmt = new ParameterizedSQL(
`SELECT i.serial, SUM(IF(i.isBooked, 0,1)) pending, COUNT(*) total

View File

@ -75,7 +75,7 @@ module.exports = Self => {
AND c.isTaxDataChecked
AND c.isActive
AND NOT t.isDeleted
GROUP BY c.id, IF(c.hasToInvoiceByAddress, a.id, TRUE)
GROUP BY IF(c.hasToInvoiceByAddress, a.id, c.id)
HAVING SUM(t.totalWithVat) > 0;`;
const addresses = await Self.rawSql(query, [

View File

@ -28,6 +28,11 @@ module.exports = Self => {
type: 'number',
description: 'The company id to invoice',
required: true
}, {
arg: 'serialType',
type: 'string',
description: 'Invoice serial number type (see vn.invoiceOutSerial.type enum)',
required: true
}
],
returns: {
@ -39,12 +44,10 @@ module.exports = Self => {
verb: 'POST'
}
});
Self.invoiceClient = async(ctx, options) => {
const args = ctx.args;
const models = Self.app.models;
options = typeof options == 'object'
? Object.assign({}, options) : {};
options = typeof options === 'object' ? {...options} : {};
options.userId = ctx.req.accessToken.userId;
let tx;
@ -74,10 +77,9 @@ module.exports = Self => {
], options);
}
const invoiceType = 'G';
const invoiceId = await models.Ticket.makeInvoice(
ctx,
invoiceType,
args.serialType,
args.companyFk,
args.invoiceDate,
null,

View File

@ -43,7 +43,7 @@ module.exports = Self => {
const tickets = await models.Ticket.find(filter, myOptions);
const ticketsIds = tickets.map(ticket => ticket.id);
const refundedTickets = await models.Ticket.refund(ctx, ticketsIds, withWarehouse, myOptions);
const refundedTickets = await models.Ticket.cloneAll(ctx, ticketsIds, withWarehouse, true, myOptions);
if (tx) await tx.commit();

View File

@ -0,0 +1,75 @@
const models = require('vn-loopback/server/server').models;
describe('InvoiceOut clientsToInvoice()', () => {
const userId = 1;
const clientId = 1101;
const companyFk = 442;
const maxShipped = new Date();
maxShipped.setMonth(11);
maxShipped.setDate(31);
maxShipped.setHours(23, 59, 59, 999);
const invoiceDate = new Date();
const activeCtx = {
getLocale: () => {
return 'en';
},
accessToken: {userId: userId},
__: value => {
return value;
},
headers: {origin: 'http://localhost'}
};
const ctx = {req: activeCtx};
it('should return a list of clients to invoice', async() => {
spyOn(models.InvoiceOut, 'rawSql').and.callFake(query => {
if (query.includes('ticketPackaging_add'))
return Promise.resolve(true);
else if (query.includes('SELECT c.id clientId')) {
return Promise.resolve([
{
clientId: clientId,
clientName: 'Test Client',
id: 1,
nickname: 'Address 1'
}
]);
}
});
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
const addresses = await models.InvoiceOut.clientsToInvoice(
ctx, clientId, invoiceDate, maxShipped, companyFk, options);
expect(addresses.length).toBeGreaterThan(0);
expect(addresses[0].clientId).toBe(clientId);
expect(addresses[0].clientName).toBe('Test Client');
expect(addresses[0].id).toBe(1);
expect(addresses[0].nickname).toBe('Address 1');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should handle errors and rollback transaction', async() => {
spyOn(models.InvoiceOut, 'rawSql').and.callFake(() => {
return Promise.reject(new Error('Test Error'));
});
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceOut.clientsToInvoice(ctx, clientId, invoiceDate, maxShipped, companyFk, options);
} catch (e) {
expect(e.message).toBe('Test Error');
await tx.rollback();
}
});
});

View File

@ -1,16 +1,16 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('InvoiceOut invoiceClient()', () => {
const userId = 1;
const clientId = 1101;
const addressId = 121;
const addressFk = 121;
const companyFk = 442;
const minShipped = Date.vnNew();
minShipped.setFullYear(minShipped.getFullYear() - 1);
minShipped.setMonth(1);
minShipped.setDate(1);
minShipped.setHours(0, 0, 0, 0);
const invoiceSerial = 'A';
const activeCtx = {
getLocale: () => {
return 'en';
@ -23,9 +23,14 @@ describe('InvoiceOut invoiceClient()', () => {
};
const ctx = {req: activeCtx};
beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should make a global invoicing', async() => {
spyOn(models.InvoiceOut, 'makePdf').and.returnValue(new Promise(resolve => resolve(true)));
it('should make a global invoicing by address and verify billing status', async() => {
spyOn(models.InvoiceOut, 'makePdf').and.returnValue(Promise.resolve(true));
spyOn(models.InvoiceOut, 'invoiceEmail');
const tx = await models.InvoiceOut.beginTransaction({});
@ -34,20 +39,96 @@ describe('InvoiceOut invoiceClient()', () => {
try {
ctx.args = {
clientId: clientId,
addressId: addressId,
addressId: addressFk,
invoiceDate: Date.vnNew(),
maxShipped: Date.vnNew(),
companyFk: companyFk,
minShipped: minShipped
serialType: 'global'
};
const invoiceOutId = await models.InvoiceOut.invoiceClient(ctx, options);
const invoiceOut = await models.InvoiceOut.findById(invoiceOutId, null, options);
const [firstTicket] = await models.Ticket.find({
expect(invoiceOutId).toBeGreaterThan(0);
const allClientTickets = await models.Ticket.find({
where: {
clientFk: clientId,
or: [
{refFk: null},
{refFk: invoiceOut.ref}
]
}
}, options);
const billedTickets = await models.Ticket.find({
where: {refFk: invoiceOut.ref}
}, options);
const allBilledTicketsHaveCorrectAddress = billedTickets.every(ticket => ticket.addressFk === addressFk);
expect(allBilledTicketsHaveCorrectAddress).toBe(true);
const addressTickets = allClientTickets.filter(ticket => ticket.addressFk === addressFk);
const allAddressTicketsBilled = addressTickets.every(ticket => ticket.refFk === invoiceOut.ref);
expect(allAddressTicketsBilled).toBe(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should invoice all tickets regardless of address when hasToInvoiceByAddress is false', async() => {
spyOn(models.InvoiceOut, 'makePdf').and.returnValue(Promise.resolve(true));
spyOn(models.InvoiceOut, 'invoiceEmail');
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('hasToInvoiceByAddress', false, options);
ctx.args = {
clientId: clientId,
invoiceDate: Date.vnNew(),
maxShipped: Date.vnNew(),
companyFk: companyFk,
serialType: 'global'
};
const invoiceOutId = await models.InvoiceOut.invoiceClient(ctx, options);
const invoiceOut = await models.InvoiceOut.findById(invoiceOutId, null, options);
expect(invoiceOutId).toBeGreaterThan(0);
expect(firstTicket.refFk).toContain(invoiceSerial);
const allClientTickets = await models.Ticket.find({
where: {
clientFk: clientId,
or: [
{refFk: null},
{refFk: invoiceOut.ref}
]
}
}, options);
const billedTickets = await models.Ticket.find({
where: {refFk: invoiceOut.ref}
}, options);
const allTicketsBilled = allClientTickets.every(ticket => ticket.refFk === invoiceOut.ref);
expect(allTicketsBilled).toBe(true);
const billedAddresses = new Set(billedTickets.map(ticket => ticket.addressFk));
expect(billedAddresses.size).toBeGreaterThan(1);
await tx.rollback();
} catch (e) {

View File

@ -82,20 +82,12 @@ module.exports = Self => {
myOptions.transaction = tx;
}
try {
const filterRef = {where: {refFk: refFk}};
const tickets = await models.Ticket.find(filterRef, myOptions);
const tickets = await models.Ticket.find({where: {refFk}}, myOptions);
const ticketsIds = tickets.map(ticket => ticket.id);
const refundTickets = await models.Ticket.refund(ctx, ticketsIds, null, myOptions);
const refundTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, true, myOptions);
const filterTicket = {where: {ticketFk: {inq: ticketsIds}}};
const clonedTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, false, myOptions);
const services = await models.TicketService.find(filterTicket, myOptions);
const servicesIds = services.map(service => service.id);
const sales = await models.Sale.find(filterTicket, myOptions);
const salesIds = sales.map(sale => sale.id);
const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, myOptions);
const clonedTicketIds = [];
for (const clonedTicket of clonedTickets) {

View File

@ -20,6 +20,9 @@
},
"isCEE": {
"type": "boolean"
},
"type": {
"type": "string"
}
},
"relations": {

View File

@ -88,28 +88,7 @@
translate>
Show CITES letter
</vn-item>
<vn-item class="dropdown"
vn-click-stop="refundMenu.show($event, 'left')"
vn-tooltip="Create a refund ticket for each ticket on the current invoice"
vn-acl="invoicing, claimManager, salesAssistant"
vn-acl-action="remove"
translate>
Refund...
<vn-menu vn-id="refundMenu">
<vn-list>
<vn-item
ng-click="$ctrl.refundInvoiceOut(true)"
translate>
with warehouse
</vn-item>
<vn-item
ng-click="$ctrl.refundInvoiceOut(false)"
translate>
without warehouse
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
</vn-list>
</vn-menu>
<vn-confirm

View File

@ -135,21 +135,6 @@ class Controller extends Section {
});
}
refundInvoiceOut(withWarehouse) {
const query = 'InvoiceOuts/refund';
const params = {ref: this.invoiceOut.ref, withWarehouse: withWarehouse};
this.$http.post(query, params).then(res => {
const tickets = res.data;
const refundTickets = tickets.map(ticket => ticket.id);
this.vnApp.showSuccess(this.$t('The following refund tickets have been created', {
ticketId: refundTickets.join(',')
}));
if (refundTickets.length == 1)
this.$state.go('ticket.card.sale', {id: refundTickets[0]});
});
}
transferInvoice() {
const params = {
id: this.invoiceOut.id,

View File

@ -105,17 +105,4 @@ describe('vnInvoiceOutDescriptorMenu', () => {
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
describe('refundInvoiceOut()', () => {
it('should make a query and show a success message', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
const params = {ref: controller.invoiceOut.ref};
$httpBackend.expectPOST(`InvoiceOuts/refund`, params).respond([{id: 1}, {id: 2}]);
controller.refundInvoiceOut();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});

View File

@ -1,40 +1,25 @@
module.exports = Self => {
Self.remoteMethodCtx('clone', {
description: 'Clone sales and services provided',
description: 'Clone sales, services, and ticket packaging provided',
accessType: 'WRITE',
accepts: [
{
arg: 'salesIds',
type: ['number'],
}, {
arg: 'servicesIds',
type: ['number']
}, {
arg: 'withWarehouse',
type: 'boolean',
required: true
}, {
arg: 'negative',
type: 'boolean'
}
{arg: 'salesIds', type: ['number']},
{arg: 'servicesIds', type: ['number']},
{arg: 'ticketPackagingIds', type: ['number']},
{arg: 'withWarehouse', type: 'boolean', required: true},
{arg: 'negative', type: 'boolean'}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/clone`,
verb: 'POST'
}
returns: {type: ['object'], root: true},
http: {path: `/clone`, verb: 'POST'}
});
Self.clone = async(ctx, salesIds, servicesIds, withWarehouse, negative, options) => {
Self.clone = async(ctx, salesIds, servicesIds, ticketPackagingIds, withWarehouse, negative, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
const newTickets = [];
if (typeof options == 'object')
Object.assign(myOptions, options);
if (typeof options === 'object') Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
@ -44,8 +29,9 @@ module.exports = Self => {
try {
let sales;
let services;
let ticketPackaging;
if (salesIds && salesIds.length) {
if (salesIds?.length) {
sales = await models.Sale.find({
where: {id: {inq: salesIds}},
include: {
@ -57,12 +43,18 @@ module.exports = Self => {
}, myOptions);
}
if (servicesIds && servicesIds.length) {
if (servicesIds?.length) {
services = await models.TicketService.find({
where: {id: {inq: servicesIds}}
}, myOptions);
}
if (ticketPackagingIds?.length) {
ticketPackaging = await models.TicketPackaging.find({
where: {id: {inq: ticketPackagingIds}}
}, myOptions);
}
let ticketsIds = sales ?
[...new Set(sales.map(sale => sale.ticketFk))] :
[...new Set(services.map(service => service.ticketFk))];
@ -74,12 +66,12 @@ module.exports = Self => {
ctx,
ticketId,
withWarehouse,
negative,
myOptions
);
newTickets.push(newTicket);
mappedTickets.set(ticketId, newTicket.id);
}
if (sales) {
for (const sale of sales) {
const newTicketId = mappedTickets.get(sale.ticketFk);
@ -107,7 +99,7 @@ module.exports = Self => {
await models.TicketService.create({
description: service.description,
quantity: negative ? - service.quantity : service.quantity,
quantity: negative ? -service.quantity : service.quantity,
price: service.price,
taxClassFk: service.taxClassFk,
ticketFk: newTicketId,
@ -116,6 +108,18 @@ module.exports = Self => {
}
}
if (ticketPackaging) {
for (const packaging of ticketPackaging) {
const newTicketId = mappedTickets.get(packaging.ticketFk);
await models.TicketPackaging.create({
ticketFk: newTicketId,
packagingFk: packaging.packagingFk,
quantity: negative ? -packaging.quantity : packaging.quantity
}, myOptions);
}
}
if (tx) await tx.commit();
return newTickets;
@ -124,13 +128,7 @@ module.exports = Self => {
throw e;
}
async function createTicket(
ctx,
ticketId,
withWarehouse,
negative,
myOptions
) {
async function createTicket(ctx, ticketId, withWarehouse, myOptions) {
const models = Self.app.models;
const now = Date.vnNew();

View File

@ -20,7 +20,7 @@ describe('Ticket cloning - clone function', () => {
const servicesIds = [];
const withWarehouse = true;
const negative = false;
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, withWarehouse, negative, options);
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, withWarehouse, negative, options);
expect(newTickets).toBeDefined();
expect(newTickets.length).toBeGreaterThan(0);
@ -30,7 +30,7 @@ describe('Ticket cloning - clone function', () => {
const negative = true;
const salesIds = [7, 8];
const servicesIds = [];
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, false, negative, options);
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, negative, options);
for (const ticket of newTickets) {
const sales = await models.Sale.find({where: {ticketFk: ticket.id}}, options);
@ -43,7 +43,7 @@ describe('Ticket cloning - clone function', () => {
it('should create new components and services for cloned tickets', async() => {
const servicesIds = [2];
const salesIds = [5];
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, false, false, options);
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, false, options);
for (const ticket of newTickets) {
const sale = await models.Sale.findOne({where: {ticketFk: ticket.id}}, options);
@ -58,7 +58,7 @@ describe('Ticket cloning - clone function', () => {
it('should create a ticket without sales', async() => {
const servicesIds = [4];
const tickets = await models.Sale.clone(ctx, null, servicesIds, false, false, options);
const tickets = await models.Sale.clone(ctx, null, servicesIds, null, false, false, options);
const refundedTicket = await getTicketRefund(tickets[0].id, options);
expect(refundedTicket).toBeDefined();

View File

@ -1,54 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('clone', {
description: 'clone a ticket and return the new ticket id',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
}, {
arg: 'shipped',
type: 'date',
}, {
arg: 'withWarehouse',
type: 'boolean',
}
],
returns: {
type: 'number',
root: true
},
http: {
path: `/:id/clone`,
verb: 'POST'
}
});
Self.clone = async(ctx, id, shipped, withWarehouse, options) => {
const myOptions = {userId: ctx.req.accessToken.userId};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const [, [{clonedTicketId}]] = await Self.rawSql(`
CALL vn.ticket_cloneAll(?, ?, ?, @clonedTicketId);
SELECT @clonedTicketId clonedTicketId;`,
[id, shipped, withWarehouse], myOptions);
if (tx) await tx.commit();
return clonedTicketId;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,77 @@
module.exports = Self => {
Self.remoteMethodCtx('cloneAll', {
description: 'Clone tickets, sales, services and packages',
accessType: 'WRITE',
accepts: [
{
arg: 'ticketsIds',
type: ['number'],
required: true,
description: 'IDs of the tickets to clone'
},
{
arg: 'withWarehouse',
type: 'boolean',
required: true,
description: 'true: keep original warehouse; false: set to null'
},
{
arg: 'negative',
type: 'boolean',
required: true,
description: 'true: invert quantities; false: keep as is.'
}
],
returns: {
type: ['object'],
root: true,
description: 'The cloned tickets with associated data'
},
http: {
path: `/cloneAll`,
verb: 'POST'
}
});
Self.cloneAll = async(ctx, ticketsIds, withWarehouse, negative, options) => {
const models = Self.app.models;
const myOptions = typeof options == 'object' ? {...options} : {};
let tx;
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const filter = {where: {ticketFk: {inq: ticketsIds}}};
const [sales, services, ticketPackaging] = await Promise.all([
models.Sale.find(filter, myOptions),
models.TicketService.find(filter, myOptions),
models.TicketPackaging.find(filter, myOptions)
]);
const salesIds = sales.map(({id}) => id);
const servicesIds = services.map(({id}) => id);
const ticketPackagingIds = ticketPackaging.map(({id}) => id);
const clonedTickets = await models.Sale.clone(
ctx,
salesIds,
servicesIds,
ticketPackagingIds,
withWarehouse,
negative,
myOptions
);
if (tx) await tx.commit();
return clonedTickets;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -114,7 +114,7 @@ module.exports = Self => {
LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = su.countryFk
LEFT JOIN vn.invoiceOutSerial ios ON ios.taxAreaFk = 'WORLD'
AND ios.code = invoiceSerial(t.clientFk, t.companyFk, 'M')
AND ios.code = invoiceSerial(t.clientFk, t.companyFk, 'multiple')
WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered'))
AND DATE(t.shipped) BETWEEN ? - INTERVAL 2 DAY AND util.dayEnd(?)
AND t.refFk IS NULL

View File

@ -95,7 +95,7 @@ module.exports = function(Self) {
FROM vn.ticket
WHERE id IN (?)
`, [ticketsIds], myOptions);
return models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), invoiceCorrection, myOptions);
return models.Ticket.makeInvoice(ctx, 'quick', companyId, Date.vnNew(), invoiceCorrection, myOptions);
}
};

View File

@ -1,58 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('refund', {
description: 'Create refund tickets with all their sales and services',
accessType: 'WRITE',
accepts: [
{
arg: 'ticketsIds',
type: ['number'],
required: true
},
{
arg: 'withWarehouse',
type: 'boolean',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/refund`,
verb: 'POST'
}
});
Self.refund = async(ctx, ticketsIds, withWarehouse, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const filter = {where: {ticketFk: {inq: ticketsIds}}};
const sales = await models.Sale.find(filter, myOptions);
const salesIds = sales.map(sale => sale.id);
const services = await models.TicketService.find(filter, myOptions);
const servicesIds = services.map(service => service.id);
const refundedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, withWarehouse, true, myOptions);
if (tx) await tx.commit();
return refundedTickets;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,43 +0,0 @@
const models = require('vn-loopback/server/server').models;
describe('Ticket cloning - clone function', () => {
const ctx = beforeAll.getCtx();
let options;
let tx;
const ticketId = 1;
const shipped = Date.vnNew();
beforeEach(async() => {
options = {transaction: tx};
tx = await models.Ticket.beginTransaction({});
options.transaction = tx;
});
afterEach(async() => {
await tx.rollback();
});
it('should clone a new ticket without warehouse', async() => {
const originalTicket = await models.Ticket.findById(ticketId, null, options);
const newTicketId = await models.Ticket.clone(ctx, ticketId, shipped, false, options);
const newTicket = await models.Ticket.findById(newTicketId, null, options);
expect(newTicket.clientFk).toEqual(originalTicket.clientFk);
expect(newTicket.companyFk).toEqual(originalTicket.companyFk);
expect(newTicket.addressFk).toEqual(originalTicket.addressFk);
expect(newTicket.warehouseFk).toBeFalsy();
});
it('should clone a new ticket with warehouse', async() => {
const originalTicket = await models.Ticket.findById(ticketId, null, options);
const newTicketId = await models.Ticket.clone(ctx, ticketId, shipped, true, options);
const newTicket = await models.Ticket.findById(newTicketId, null, options);
expect(newTicket.clientFk).toEqual(originalTicket.clientFk);
expect(newTicket.companyFk).toEqual(originalTicket.companyFk);
expect(newTicket.addressFk).toEqual(originalTicket.addressFk);
expect(newTicket.warehouseFk).toEqual(originalTicket.warehouseFk);
});
});

View File

@ -0,0 +1,53 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Ticket cloning - cloneAll function', () => {
const activeCtx = {
accessToken: {userId: 1},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
let options;
let tx;
const ticketIds = [1, 2];
const withWarehouse = true;
const negative = false;
beforeEach(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req});
tx = await models.Ticket.beginTransaction({});
options = {transaction: tx};
});
afterEach(async() => {
if (tx)
await tx.rollback();
});
it('should clone all provided tickets with their associated sales, services, and packages', async() => {
const originalTickets = await models.Ticket.find({where: {id: {inq: ticketIds}}}, options);
const originalSales = await models.Sale.find({where: {ticketFk: {inq: ticketIds}}}, options);
const originalServices = await models.TicketService.find({where: {ticketFk: {inq: ticketIds}}}, options);
const originalTicketPackaging =
await models.TicketPackaging.find({where: {ticketFk: {inq: ticketIds}}}, options);
// Pass the ctx correctly to the cloneAll function
const clonedTickets = await models.Ticket.cloneAll(ctx, ticketIds, withWarehouse, negative, options);
expect(clonedTickets.length).toEqual(originalTickets.length);
const clonedSales = await models.Sale.find({where: {ticketFk: {inq: clonedTickets.map(t => t.id)}}}, options);
const clonedServices =
await models.TicketService.find({where: {ticketFk: {inq: clonedTickets.map(t => t.id)}}}, options);
const clonedTicketPackaging =
await models.TicketPackaging.find({where: {ticketFk: {inq: clonedTickets.map(t => t.id)}}}, options);
expect(clonedSales.length).toEqual(originalSales.length);
expect(clonedServices.length).toEqual(originalServices.length);
expect(clonedTicketPackaging.length).toEqual(originalTicketPackaging.length);
});
});

View File

@ -3,7 +3,7 @@ const LoopBackContext = require('loopback-context');
describe('ticket makeInvoice()', () => {
const userId = 19;
const invoiceType = 'R';
const invoiceType = 'quick';
const companyFk = 442;
const invoiceDate = Date.vnNew();
const activeCtx = {

View File

@ -26,7 +26,7 @@ module.exports = function(Self) {
require('../methods/ticket/isLocked')(Self);
require('../methods/ticket/freightCost')(Self);
require('../methods/ticket/getComponentsSum')(Self);
require('../methods/ticket/refund')(Self);
require('../methods/ticket/cloneAll')(Self);
require('../methods/ticket/deliveryNotePdf')(Self);
require('../methods/ticket/deliveryNoteEmail')(Self);
require('../methods/ticket/deliveryNoteCsv')(Self);
@ -46,5 +46,4 @@ module.exports = function(Self) {
require('../methods/ticket/invoiceTicketsAndPdf')(Self);
require('../methods/ticket/docuwareDownload')(Self);
require('../methods/ticket/myLastModified')(Self);
require('../methods/ticket/clone')(Self);
};

View File

@ -287,15 +287,24 @@ class Controller extends Section {
}
refund(withWarehouse) {
const params = {ticketsIds: [this.id], withWarehouse: withWarehouse};
const query = 'Tickets/refund';
const params = {
ticketsIds: [this.id],
withWarehouse: withWarehouse,
negative: true // Asumimos que queremos cantidades negativas para reembolsos
};
const query = 'Tickets/cloneAll';
return this.$http.post(query, params)
.then(res => {
const [refundTicket] = res.data;
this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {
this.vnApp.showSuccess(this.$t('The following refund ticket has been created', {
ticketId: refundTicket.id
}));
this.$state.go('ticket.card.sale', {id: refundTicket.id});
})
.catch(error => {
this.vnApp.showError(this.$t('Error creating refund ticket', {
error: error.data?.error?.message || 'Unknown error'
}));
});
}

View File

@ -217,24 +217,6 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
});
});
describe('refund()', () => {
it('should make a query and go to ticket.card.sale', () => {
controller.$state.go = jest.fn();
controller._id = ticket.id;
const params = {
ticketsIds: [16]
};
const response = {id: 99};
$httpBackend.expectPOST('Tickets/refund', params).respond([response]);
controller.refund();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', response);
});
});
describe('sendChangesSms()', () => {
it('should make a query and open the sms dialog', () => {
controller.$.sms = {open: () => {}};

View File

@ -22,7 +22,7 @@ labels AS (
b.id,
b.itemFk,
p.name producer,
IF(i2.id, i2.comment, i.comment) comment
IFNULL(i2.comment, i.comment) comment
FROM buy b
JOIN item i ON i.id = b.itemFk
LEFT JOIN producer p ON p.id = i.producerFk

View File

@ -1,14 +1,20 @@
h3 {
font-weight: 100;
color: #555
}
.report-info {
font-size: 20px
}
.description strong {
text-transform: uppercase;
}
.nowrap {
white-space: nowrap;
}
.padding {
padding: 16px;
}
.tags {
font-size: 10px;
margin: 0;
}

View File

@ -4,23 +4,23 @@
</template>
<div class="grid-row">
<div class="grid-block">
<div class="columns">
<h1 class="title uppercase">{{$t('title')}}</h1>
<div class="columns header-tables">
<div class="size50">
<div class="body">
<h1 class="title uppercase">{{$t('title')}}</h1>
<table class="row-oriented report-info">
<tbody>
<tr>
<td class="font gray uppercase">{{$t('entryId')}}</td>
<td class="font gray uppercase padding nowrap">{{$t('entryId')}}</td>
<th>{{entry.id}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('date')}}</td>
<td class="font gray uppercase padding nowrap">{{$t('date')}}</td>
<th>{{formatDate(entry.landed,'%d-%m-%Y')}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('ref')}}</td>
<th>{{entry.invoiceNumber}}</th>
<td class="font gray uppercase padding nowrap">{{$t('ref')}}</td>
<th>{{entry.invoiceNumber | dashIfEmpty}}</th>
</tr>
</tbody>
</table>
@ -38,42 +38,56 @@
</div>
</div>
</div>
<table class="column-oriented vn-mt-ml">
<table class="column-oriented vn-mt-ml border-collapse">
<thead>
<tr>
<th class="number">{{$t('boxes')}}</th>
<th width="5%" class="number"></th>
<th class="number">{{$t('packing')}}</th>
<th width="50%">{{$t('concept')}}</th>
<th width="40%">{{$t('concept')}}</th>
<th width="10%">{{$t('reference')}}</th>
<th width="10%">{{$t('tags')}}</th>
<th width="10%" class="number">{{$t('quantity')}}</th>
<th width="5%" class="number"></th>
<th width="15%" class="number">{{$t('price')}}</th>
<th width="5%" class="number"></th>
<th width="15%" class="number">{{$t('amount')}}</th>
</tr>
</thead>
<tbody v-for="buy in buys">
<tr>
<td class="number">{{buy.box}}</td>
<td class="number">{{buy.stickers}}</td>
<td width="5%" class="number">x</td>
<td class="number">{{buy.packing}}</td>
<td width="50%">{{buy.itemName}}</td>
<td width="40%" class="nowrap">{{buy.name}}</td>
<td width="10%">{{buy.comment}}</td>
<td width="10%" class="font light-gray tags">
<span v-if="buy.value5" class="nowrap"><strong>{{buy.tag5}} → </strong>{{buy.value5}} </span>
<span v-if="buy.value6" class="nowrap"><strong>{{buy.tag6}} → </strong>{{buy.value6}} </span>
<span v-if="buy.value7" class="nowrap"><strong>{{buy.tag7}} → </strong>{{buy.value7}} </span>
</td>
<td width="10%" class="number">{{buy.quantity | number($i18n.locale)}}</td>
<td width="5%" class="number">x</td>
<td width="15%" class="number">{{buy.buyingValue | currency('EUR', $i18n.locale)}}</td>
<td width="5%" class="number">=</td>
<td width="15%" class="number">
{{buy.buyingValue * buy.quantity | currency('EUR', $i18n.locale)}}
</td>
</tr>
<tr class="description font light-gray">
<td colspan="7">
<span v-if="buy.value5"> <strong>{{buy.tag5}}</strong> {{buy.value5}} </span>
<span v-if="buy.value6"> <strong>{{buy.tag6}}</strong> {{buy.value6}} </span>
<span v-if="buy.value7"> <strong>{{buy.tag7}}</strong> {{buy.value7}} </span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" class="font bold">
<span class="pull-right">{{$t('total')}}</span>
</td>
<td class="number">{{getTotal() | currency('EUR', $i18n.locale)}}</td>
<tr class="font bold">
<td class="number">{{getTotalBy('stickers')}}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="number">{{getTotalBy('quantity') | number($i18n.locale)}}</td>
<td></td>
<td></td>
<td></td>
<td class="number">{{getTotalBy('amount') | currency('EUR', $i18n.locale)}}</td>
</tr>
</tfoot>
</table>

View File

@ -13,13 +13,17 @@ module.exports = {
return {totalBalance: 0.00};
},
methods: {
getTotal() {
let total = 0.00;
this.buys.forEach(buy => {
total += buy.quantity * buy.buyingValue;
});
return total;
getTotalBy(property) {
return this.buys.reduce((total, buy) => {
switch (property) {
case 'amount':
return total + buy.quantity * buy.buyingValue;
case 'quantity':
return total + buy.quantity;
case 'stickers':
return total + buy.stickers;
}
}, 0);
}
},
props: {

View File

@ -2,7 +2,7 @@ reportName: pedido-de-entrada
title: Pedido
supplierName: Proveedor
supplierStreet: Dirección
entryId: Referencia interna
entryId: Nº Entrada
date: Fecha
ref: Nº Factura
boxes: Cajas
@ -15,3 +15,5 @@ total: Total
entry: Entrada {0}
supplierData: Datos del proveedor
notes: Notas
reference: Referencia
tags: Tags

View File

@ -1,16 +1,17 @@
SELECT
b.itemFk,
b.quantity,
b.buyingValue,
b.stickers box,
b.packing,
i.name itemName,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7
FROM buy b
JOIN item i ON i.id = b.itemFk
WHERE b.entryFk = ?
SELECT b.itemFk,
b.quantity,
b.buyingValue,
b.stickers,
b.packing,
i.name,
IFNULL(i2.comment, i.comment) comment,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7
FROM buy b
JOIN item i ON i.id = b.itemFk
LEFT JOIN item i2 ON i2.id = b.itemOriginalFk
WHERE b.entryFk = ?

View File

@ -1,9 +1,8 @@
SELECT
e.id,
e.invoiceNumber,
c.code companyCode,
t.landed
FROM entry e
JOIN travel t ON t.id = e.travelFk
JOIN company c ON c.id = e.companyFk
WHERE e.id = ?
SELECT e.id,
e.invoiceNumber,
c.code companyCode,
t.landed
FROM entry e
JOIN travel t ON t.id = e.travelFk
JOIN company c ON c.id = e.companyFk
WHERE e.id = ?

View File

@ -1,11 +1,10 @@
SELECT
s.name,
s.street,
s.nif,
s.postCode,
s.city,
p.name province
FROM supplier s
JOIN entry e ON e.supplierFk = s.id
LEFT JOIN province p ON p.id = s.provinceFk
WHERE e.id = ?
SELECT s.name,
s.street,
s.nif,
s.postCode,
s.city,
p.name province
FROM supplier s
JOIN entry e ON e.supplierFk = s.id
LEFT JOIN province p ON p.id = s.provinceFk
WHERE e.id = ?