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; return token;
// Schedule to remove current token // Schedule to remove current token
setTimeout(async() => { setTimeout(() => {
let exists; Self.logout(token.id);
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);
}
}, courtesyTime * 1000); }, courtesyTime * 1000);
// Get scopes // Get scopes
@ -53,14 +43,20 @@ module.exports = Self => {
return {id: accessToken.id, ttl: accessToken.ttl}; return {id: accessToken.id, ttl: accessToken.ttl};
} catch (error) { } catch (error) {
const body = {error: error.message, now: Date.now(), userId: token?.userId ?? null, createTokenOptions, isNotExceeded}; const body = {
await handleError(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); throw new Error(error);
} }
}; };
}; };
async function handleError(body, tag = 'renewToken') { async function handleError(body) {
body = JSON.stringify(body); await models.Application.rawSql('CALL util.debugAdd(?,?);', ['renewToken', body]);
await models.Application.rawSql('CALL util.debugAdd(?,?);', [tag, 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','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','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 ('','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 `tables_priv` ENABLE KEYS */;
/*!40000 ALTER TABLE `columns_priv` DISABLE 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 ('','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','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 ('','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_statementWithEntries','PROCEDURE','guillermo@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','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','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','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'); 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','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','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','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 */; /*!40000 ALTER TABLE `procs_priv` ENABLE KEYS */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!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 ('','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 ('','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 ('','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 ('','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 ('','hrBoss','{\"access\":0,\"version_id\":101106,\"is_role\":true}');
INSERT IGNORE INTO `global_priv` VALUES ('','invoicing','{\"access\":0,\"version_id\":100707,\"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`; 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` -- Current Database: `srt`
@ -9220,7 +9284,13 @@ BEGIN
SET NEW.editorFk = account.myUser_getId(); SET NEW.editorFk = account.myUser_getId();
IF NOT (NEW.routeFk <=> OLD.routeFk) THEN 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'); CALL util.throw('A signed ticket cannot be rerouted');
END IF; END IF;
INSERT IGNORE INTO routeRecalc(routeFk) INSERT IGNORE INTO routeRecalc(routeFk)
@ -11142,4 +11212,4 @@ USE `vn2008`;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!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)), (1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(1104, -30, 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 VALUES
(1, 'B2B'), (1, 'B2B'),
(2, 'CORE'), (2, 'CORE'),
@ -632,7 +632,7 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'), ('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'),
('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'), ('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'),
('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'), ('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), ('R', 'Rectificativa', 1, 'NATIONAL', 0, NULL),
('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick'); ('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick');
@ -3945,11 +3945,11 @@ VALUES
(35, 'ES12346B12345679', 3, 241); (35, 'ES12346B12345679', 3, 241);
INSERT INTO vn.accountDetailType INSERT INTO vn.accountDetailType
(id, description) (id, description, code)
VALUES VALUES
(1, 'IBAN'), (1, 'IBAN', 'iban'),
(2, 'SWIFT'), (2, 'SWIFT', 'swift'),
(3, 'Referencia Remesas'), (3, 'Referencia Remesas', 'remRef'),
(4, 'Referencia Transferencias'), (4, 'Referencia Transferencias', 'trnRef'),
(5, 'Referencia Nominas'), (5, 'Referencia Nominas', 'payRef'),
(6, 'ABA'); (6, 'ABA', 'aba');

View File

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

View File

@ -3,61 +3,69 @@ CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `srt`.`moving_clean`()
BEGIN BEGIN
/** /**
* Elimina movimientos por inactividad * Elimina movimientos por inactividad
*
*/ */
DECLARE vExpeditionFk INT; DECLARE vExpeditionFk INT;
DECLARE vBufferToFk INT;
DECLARE vBufferFromFk INT; DECLARE vBufferFromFk INT;
DECLARE done BOOL DEFAULT FALSE; DECLARE vStateOutFk INT
DEFAULT (SELECT id FROM expeditionState WHERE `description` = 'OUT');
DECLARE cur CURSOR FOR DECLARE vDone BOOL;
SELECT m.expeditionFk, m.bufferToFk, m.bufferFromFk DECLARE vSorter CURSOR FOR
FROM srt.moving m SELECT m.expeditionFk, m.bufferFromFk
JOIN srt.config c FROM moving m
JOIN (SELECT bufferFk, SUM(isActive) hasBox JOIN (
FROM srt.photocell SELECT bufferFk, SUM(isActive) hasBox
GROUP BY bufferFk) sub ON sub.bufferFk = m.bufferFromFk FROM photocell
WHERE m.created < TIMESTAMPADD(MINUTE, - c.movingMaxLife , util.VN_NOW()) 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; 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 vDone THEN
LEAVE l;
IF done THEN
LEAVE bucle;
END IF; END IF;
DELETE FROM srt.moving START TRANSACTION;
SELECT id
FROM moving
WHERE expeditionFk = vExpeditionFk
FOR UPDATE;
DELETE FROM moving
WHERE expeditionFk = vExpeditionFk; WHERE expeditionFk = vExpeditionFk;
UPDATE srt.expedition e SELECT id
JOIN srt.expeditionState es ON es.description = 'OUT' FROM expedition
SET WHERE id = vExpeditionFk
bufferFk = NULL, OR (bufferFk = vBufferFromFk AND `position` > 0)
FOR UPDATE;
UPDATE expedition
SET bufferFk = NULL,
`position` = NULL, `position` = NULL,
stateFk = es.id stateFk = vStateOutFk
WHERE e.id = vExpeditionFk; WHERE id = vExpeditionFk;
UPDATE srt.expedition e UPDATE expedition
SET e.`position` = e.`position` - 1 SET `position` = `position` - 1
WHERE e.bufferFk = vBufferFromFk WHERE bufferFk = vBufferFromFk
AND e.`position` > 0; AND `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;
COMMIT;
END LOOP l;
CLOSE vSorter;
END$$ END$$
DELIMITER ; 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 $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceSerial`(vClientFk INT, vCompanyFk INT, vType CHAR(1)) CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceSerial`(vClientFk INT, vCompanyFk INT, vType CHAR(15))
RETURNS char(1) CHARSET utf8mb3 COLLATE utf8mb3_general_ci RETURNS char(2) CHARSET utf8mb3 COLLATE utf8mb3_general_ci
DETERMINISTIC DETERMINISTIC
BEGIN BEGIN
/** /**
* Obtiene la serie de de una factura * Obtiene la serie de una factura
* dependiendo del area del cliente. * dependiendo del area del cliente.
* *
* @param vClientFk Id del cliente * @param vClientFk Id del cliente
* @param vCompanyFk Id de la empresa * @param vCompanyFk Id de la empresa
* @param vType Tipo de factura ["R", "M", "G"] * @param vType Tipo de factura ['global','multiple','quick']
* @return Serie de la factura * @return vSerie de la factura
*/ */
DECLARE vTaxArea VARCHAR(25); DECLARE vTaxArea VARCHAR(25) COLLATE utf8mb3_general_ci;
DECLARE vSerie CHAR(1); DECLARE vSerie CHAR(2);
IF (SELECT hasInvoiceSimplified FROM client WHERE id = vClientFk) THEN IF (SELECT hasInvoiceSimplified FROM client WHERE id = vClientFk) THEN
RETURN 'S'; RETURN 'S';
END IF; END IF;
SELECT clientTaxArea(vClientFk, vCompanyFk) INTO vTaxArea; SELECT addressTaxArea(defaultAddressFk, vCompanyFk) INTO vTaxArea
SELECT invoiceSerialArea(vType,vTaxArea) INTO vSerie; FROM client
WHERE id = vClientFk;
SELECT code INTO vSerie
FROM invoiceOutSerial
WHERE `type` = vType AND taxAreaFk = vTaxArea;
RETURN vSerie; RETURN vSerie;
END$$ END$$
DELIMITER ; 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()) AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase())
THEN 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( INSERT INTO invoiceOut(
ref, ref,
serial, serial,

View File

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

View File

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

View File

@ -14,16 +14,28 @@ BEGIN
DECLARE vTicketFk INT; DECLARE vTicketFk INT;
DECLARE cTickets CURSOR FOR DECLARE cTickets CURSOR FOR
SELECT id FROM ticket SELECT DISTINCT t.id
WHERE refFk IS NULL FROM ticket t
AND ((vScope = 'client' AND clientFk = vId) LEFT JOIN tItems ti ON ti.id = t.id
OR (vScope = 'address' AND addressFk = vId)); 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 DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
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; OPEN cTickets;
myLoop: LOOP myLoop: LOOP
SET vDone = FALSE; SET vDone = FALSE;
FETCH cTickets INTO vTicketFk; FETCH cTickets INTO vTicketFk;
@ -34,7 +46,8 @@ BEGIN
CALL ticket_recalc(vTicketFk, NULL); CALL ticket_recalc(vTicketFk, NULL);
END LOOP; END LOOP;
CLOSE cTickets; CLOSE cTickets;
DROP TEMPORARY TABLE tItems;
END$$ END$$
DELIMITER ; 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 SQL SECURITY DEFINER
VIEW `vn2008`.`mandato_tipo` VIEW `vn2008`.`mandato_tipo`
AS SELECT `m`.`id` AS `idmandato_tipo`, AS SELECT `m`.`id` AS `idmandato_tipo`,
`m`.`name` AS `Nombre` `m`.`code` AS `Nombre`
FROM `vn`.`mandateType` `m` 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) { async function notifyStateChange(ctx, workerId, claim, newState) {
const models = Self.app.models; const models = Self.app.models;
const url = await models.Url.getUrl(); const url = await models.Url.getUrl('lilium');
const $t = ctx.req.__; const $t = ctx.req.__;
const message = $t(`Claim state has changed to`, { const message = $t(`Claim state has changed to`, {
@ -122,7 +122,7 @@ module.exports = Self => {
async function notifyPickUp(ctx, workerId, claim) { async function notifyPickUp(ctx, workerId, claim) {
const models = Self.app.models; const models = Self.app.models;
const url = await models.Url.getUrl(); const url = await models.Url.getUrl('lilium');
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const message = $t('Claim will be picked', { const message = $t('Claim will be picked', {

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ class Controller extends Section {
{ {
relation: 'mandateType', relation: 'mandateType',
scope: { scope: {
fields: ['id', 'name'] fields: ['id', 'code']
} }
}, { }, {
relation: 'company', 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( const stmt = new ParameterizedSQL(
`SELECT i.serial, SUM(IF(i.isBooked, 0,1)) pending, COUNT(*) total `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.isTaxDataChecked
AND c.isActive AND c.isActive
AND NOT t.isDeleted 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;`; HAVING SUM(t.totalWithVat) > 0;`;
const addresses = await Self.rawSql(query, [ const addresses = await Self.rawSql(query, [

View File

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

View File

@ -43,7 +43,7 @@ module.exports = Self => {
const tickets = await models.Ticket.find(filter, myOptions); const tickets = await models.Ticket.find(filter, myOptions);
const ticketsIds = tickets.map(ticket => ticket.id); 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(); 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 models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('InvoiceOut invoiceClient()', () => { describe('InvoiceOut invoiceClient()', () => {
const userId = 1; const userId = 1;
const clientId = 1101; const clientId = 1101;
const addressId = 121; const addressFk = 121;
const companyFk = 442; const companyFk = 442;
const minShipped = Date.vnNew(); const minShipped = Date.vnNew();
minShipped.setFullYear(minShipped.getFullYear() - 1); minShipped.setFullYear(minShipped.getFullYear() - 1);
minShipped.setMonth(1); minShipped.setMonth(1);
minShipped.setDate(1); minShipped.setDate(1);
minShipped.setHours(0, 0, 0, 0); minShipped.setHours(0, 0, 0, 0);
const invoiceSerial = 'A';
const activeCtx = { const activeCtx = {
getLocale: () => { getLocale: () => {
return 'en'; return 'en';
@ -23,9 +23,14 @@ describe('InvoiceOut invoiceClient()', () => {
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should make a global invoicing', async() => { it('should make a global invoicing by address and verify billing status', async() => {
spyOn(models.InvoiceOut, 'makePdf').and.returnValue(new Promise(resolve => resolve(true))); spyOn(models.InvoiceOut, 'makePdf').and.returnValue(Promise.resolve(true));
spyOn(models.InvoiceOut, 'invoiceEmail'); spyOn(models.InvoiceOut, 'invoiceEmail');
const tx = await models.InvoiceOut.beginTransaction({}); const tx = await models.InvoiceOut.beginTransaction({});
@ -34,20 +39,96 @@ describe('InvoiceOut invoiceClient()', () => {
try { try {
ctx.args = { ctx.args = {
clientId: clientId, clientId: clientId,
addressId: addressId, addressId: addressFk,
invoiceDate: Date.vnNew(), invoiceDate: Date.vnNew(),
maxShipped: Date.vnNew(), maxShipped: Date.vnNew(),
companyFk: companyFk, companyFk: companyFk,
minShipped: minShipped serialType: 'global'
}; };
const invoiceOutId = await models.InvoiceOut.invoiceClient(ctx, options); const invoiceOutId = await models.InvoiceOut.invoiceClient(ctx, options);
const invoiceOut = await models.InvoiceOut.findById(invoiceOutId, null, 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} where: {refFk: invoiceOut.ref}
}, options); }, 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(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(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -82,20 +82,12 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
try { try {
const filterRef = {where: {refFk: refFk}}; const tickets = await models.Ticket.find({where: {refFk}}, myOptions);
const tickets = await models.Ticket.find(filterRef, myOptions);
const ticketsIds = tickets.map(ticket => ticket.id); 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 = []; const clonedTicketIds = [];
for (const clonedTicket of clonedTickets) { for (const clonedTicket of clonedTickets) {

View File

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

View File

@ -88,28 +88,7 @@
translate> translate>
Show CITES letter Show CITES letter
</vn-item> </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-list>
</vn-menu> </vn-menu>
<vn-confirm <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() { transferInvoice() {
const params = { const params = {
id: this.invoiceOut.id, id: this.invoiceOut.id,

View File

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

View File

@ -20,7 +20,7 @@ describe('Ticket cloning - clone function', () => {
const servicesIds = []; const servicesIds = [];
const withWarehouse = true; const withWarehouse = true;
const negative = false; 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).toBeDefined();
expect(newTickets.length).toBeGreaterThan(0); expect(newTickets.length).toBeGreaterThan(0);
@ -30,7 +30,7 @@ describe('Ticket cloning - clone function', () => {
const negative = true; const negative = true;
const salesIds = [7, 8]; const salesIds = [7, 8];
const servicesIds = []; 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) { for (const ticket of newTickets) {
const sales = await models.Sale.find({where: {ticketFk: ticket.id}}, options); 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() => { it('should create new components and services for cloned tickets', async() => {
const servicesIds = [2]; const servicesIds = [2];
const salesIds = [5]; 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) { for (const ticket of newTickets) {
const sale = await models.Sale.findOne({where: {ticketFk: ticket.id}}, options); 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() => { it('should create a ticket without sales', async() => {
const servicesIds = [4]; 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); const refundedTicket = await getTicketRefund(tickets[0].id, options);
expect(refundedTicket).toBeDefined(); 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 LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = su.countryFk AND itc.countryFk = su.countryFk
LEFT JOIN vn.invoiceOutSerial ios ON ios.taxAreaFk = 'WORLD' 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')) WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered'))
AND DATE(t.shipped) BETWEEN ? - INTERVAL 2 DAY AND util.dayEnd(?) AND DATE(t.shipped) BETWEEN ? - INTERVAL 2 DAY AND util.dayEnd(?)
AND t.refFk IS NULL AND t.refFk IS NULL

View File

@ -95,7 +95,7 @@ module.exports = function(Self) {
FROM vn.ticket FROM vn.ticket
WHERE id IN (?) WHERE id IN (?)
`, [ticketsIds], myOptions); `, [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()', () => { describe('ticket makeInvoice()', () => {
const userId = 19; const userId = 19;
const invoiceType = 'R'; const invoiceType = 'quick';
const companyFk = 442; const companyFk = 442;
const invoiceDate = Date.vnNew(); const invoiceDate = Date.vnNew();
const activeCtx = { const activeCtx = {

View File

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

View File

@ -287,15 +287,24 @@ class Controller extends Section {
} }
refund(withWarehouse) { refund(withWarehouse) {
const params = {ticketsIds: [this.id], withWarehouse: withWarehouse}; const params = {
const query = 'Tickets/refund'; ticketsIds: [this.id],
withWarehouse: withWarehouse,
negative: true // Asumimos que queremos cantidades negativas para reembolsos
};
const query = 'Tickets/cloneAll';
return this.$http.post(query, params) return this.$http.post(query, params)
.then(res => { .then(res => {
const [refundTicket] = res.data; 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 ticketId: refundTicket.id
})); }));
this.$state.go('ticket.card.sale', {id: 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()', () => { describe('sendChangesSms()', () => {
it('should make a query and open the sms dialog', () => { it('should make a query and open the sms dialog', () => {
controller.$.sms = {open: () => {}}; controller.$.sms = {open: () => {}};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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