Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6870-modifyDefaulterNotification
gitea/salix/pipeline/pr-dev This commit looks good
Details
gitea/salix/pipeline/pr-dev This commit looks good
Details
This commit is contained in:
commit
ace39b92ac
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [24.20.01] - 2024-05-14
|
## [24.20.01] - 2024-05-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- (Worker -> time-control) Corrección de errores
|
||||||
|
- (InvoiceOut -> Crear factura) Cuando falla al crear una factura, se devuelve un error
|
||||||
|
|
||||||
## [24.18.01] - 2024-05-07
|
## [24.18.01] - 2024-05-07
|
||||||
|
|
||||||
## [24.16.01] - 2024-04-18
|
## [24.16.01] - 2024-04-18
|
||||||
|
|
|
@ -124,6 +124,9 @@
|
||||||
"Postcode": {
|
"Postcode": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"ReferenceRate": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"SageWithholding": {
|
"SageWithholding": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
@ -177,5 +180,11 @@
|
||||||
},
|
},
|
||||||
"ProductionConfig": {
|
"ProductionConfig": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"AgencyLog": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"AgencyWorkCenter": {
|
||||||
|
"dataSource": "vn"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "AgencyLog",
|
||||||
|
"base": "Log",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "agencyLog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.rewriteDbError(function(err) {
|
||||||
|
if (err.code === 'ER_DUP_ENTRY')
|
||||||
|
return new UserError(`This workCenter is already assigned to this agency`);
|
||||||
|
return err;
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "AgencyWorkCenter",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "agencyWorkCenter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"id": true,
|
||||||
|
"type": "number",
|
||||||
|
"forceId": false
|
||||||
|
},
|
||||||
|
"agencyFk": {
|
||||||
|
"type": "number",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"workCenterFk": {
|
||||||
|
"type": "number",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"agency": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "WorkCenter",
|
||||||
|
"foreignKey": "agencyFk"
|
||||||
|
},
|
||||||
|
"workCenter": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "WorkCenter",
|
||||||
|
"foreignKey": "workCenterFk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"include":{
|
||||||
|
"relation": "workCenter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "Collection",
|
"name": "Collection",
|
||||||
"base": "VnModel",
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "collection"
|
||||||
|
}
|
||||||
|
},
|
||||||
"acls": [{
|
"acls": [{
|
||||||
"property": "validations",
|
"property": "validations",
|
||||||
"accessType": "EXECUTE",
|
"accessType": "EXECUTE",
|
||||||
|
@ -9,4 +14,3 @@
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "ReferenceRate",
|
||||||
|
"base": "PersistedModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "referenceRate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"id": true,
|
||||||
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"currencyFk": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"dated": {
|
||||||
|
"type": "date",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -160,7 +160,8 @@ INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
|
||||||
(1, 'EUR', 'Euro', 1),
|
(1, 'EUR', 'Euro', 1),
|
||||||
(2, 'USD', 'Dollar USA', 1.4),
|
(2, 'USD', 'Dollar USA', 1.4),
|
||||||
(3, 'GBP', 'Libra', 1),
|
(3, 'GBP', 'Libra', 1),
|
||||||
(4, 'JPY', 'Yen Japones', 1);
|
(4, 'JPY', 'Yen Japones', 1),
|
||||||
|
(5, 'CNY', 'Yuan Chino', 1.2);
|
||||||
|
|
||||||
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`)
|
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -3787,3 +3788,6 @@ INSERT INTO `vn`.`accountReconciliationConfig`(currencyFk, warehouseFk)
|
||||||
INSERT INTO vn.workerTeam(id, team, workerFk)
|
INSERT INTO vn.workerTeam(id, team, workerFk)
|
||||||
VALUES
|
VALUES
|
||||||
(8, 1, 19);
|
(8, 1, 19);
|
||||||
|
|
||||||
|
INSERT INTO vn.workCenter (id, name, payrollCenterFk, counter, warehouseFk, street, geoFk, deliveryManAdjustment)
|
||||||
|
VALUES(100, 'workCenterOne', 1, NULL, 1, 'gotham', NULL, NULL);
|
|
@ -155,27 +155,28 @@ BEGIN
|
||||||
SET @currentLineFk := 0;
|
SET @currentLineFk := 0;
|
||||||
SET @shipped := '';
|
SET @shipped := '';
|
||||||
|
|
||||||
SELECT DATE(@shipped:= shipped) shipped,
|
SELECT DATE(@shipped:= t.shipped) shipped,
|
||||||
alertLevel,
|
t.alertLevel,
|
||||||
stateName,
|
t.stateName,
|
||||||
origin,
|
t.origin,
|
||||||
reference,
|
t.reference,
|
||||||
clientFk,
|
t.clientFk,
|
||||||
name,
|
t.name,
|
||||||
`in` invalue,
|
t.`in` invalue,
|
||||||
`out`,
|
t.`out`,
|
||||||
@a := @a + IFNULL(`in`, 0) - IFNULL(`out`, 0) balance,
|
@a := @a + IFNULL(t.`in`, 0) - IFNULL(t.`out`, 0) balance,
|
||||||
@currentLineFk := IF (@shipped < util.VN_CURDATE()
|
@currentLineFk := IF (@shipped < util.VN_CURDATE()
|
||||||
OR (@shipped = util.VN_CURDATE() AND (isPicked OR a.`code` >= 'ON_PREPARATION')),
|
OR (@shipped = util.VN_CURDATE() AND (t.isPicked OR a.`code` >= 'ON_PREPARATION')),
|
||||||
lineFk,
|
t.lineFk,
|
||||||
@currentLineFk) lastPreparedLineFk,
|
@currentLineFk) lastPreparedLineFk,
|
||||||
isTicket,
|
t.isTicket,
|
||||||
lineFk,
|
t.lineFk,
|
||||||
isPicked,
|
t.isPicked,
|
||||||
clientType,
|
t.clientType,
|
||||||
claimFk
|
t.claimFk,
|
||||||
FROM tItemDiary
|
t.`order`
|
||||||
LEFT JOIN alertLevel a ON a.id = tItemDiary.alertLevel;
|
FROM tItemDiary t
|
||||||
|
LEFT JOIN alertLevel a ON a.id = t.alertLevel;
|
||||||
|
|
||||||
ELSE
|
ELSE
|
||||||
SELECT SUM(`in`) - SUM(`out`) INTO @a
|
SELECT SUM(`in`) - SUM(`out`) INTO @a
|
||||||
|
@ -197,7 +198,8 @@ BEGIN
|
||||||
0 lineFk,
|
0 lineFk,
|
||||||
0 isPicked,
|
0 isPicked,
|
||||||
0 clientType,
|
0 clientType,
|
||||||
0 claimFk
|
0 claimFk,
|
||||||
|
NULL `order`
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT shipped,
|
SELECT shipped,
|
||||||
alertlevel,
|
alertlevel,
|
||||||
|
@ -213,7 +215,8 @@ BEGIN
|
||||||
lineFk,
|
lineFk,
|
||||||
isPicked,
|
isPicked,
|
||||||
clientType,
|
clientType,
|
||||||
claimFk
|
claimFk,
|
||||||
|
`order`
|
||||||
FROM tItemDiary
|
FROM tItemDiary
|
||||||
WHERE shipped >= vDate;
|
WHERE shipped >= vDate;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
|
@ -29,7 +29,7 @@ BEGIN
|
||||||
FROM sale
|
FROM sale
|
||||||
WHERE ticketFk = vTicketNew AND price > 0;
|
WHERE ticketFk = vTicketNew AND price > 0;
|
||||||
|
|
||||||
CALL sale_recalcComponent('imbalance');
|
CALL sale_recalcComponent('buyerDiscount');
|
||||||
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales;
|
DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales;
|
||||||
|
|
||||||
|
|
|
@ -121,15 +121,17 @@ BEGIN
|
||||||
CALL util.throw(vErrorCode);
|
CALL util.throw(vErrorCode);
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
-- DIRECCION CORRECTA
|
-- DIRECCION CORRECTA
|
||||||
CALL workerTimeControl_direction(vWorkerFk, vTimed);
|
CALL workerTimeControl_direction(vWorkerFk, vTimed);
|
||||||
IF (SELECT
|
IF (SELECT
|
||||||
IF(IF(option1 IN ('inMiddle', 'outMiddle'),
|
IF((IF(option1 IN ('inMiddle', 'outMiddle'),
|
||||||
'middle',
|
'middle',
|
||||||
option1) <> vDirection
|
option1) <> vDirection
|
||||||
AND IF(option2 IN ('inMiddle', 'outMiddle'),
|
AND IF(option2 IN ('inMiddle', 'outMiddle'),
|
||||||
'middle',
|
'middle',
|
||||||
IFNULL(option2, '')) <> vDirection,
|
IFNULL(option2, '')) <> vDirection)
|
||||||
|
OR (option1 IS NULL AND option2 IS NULL),
|
||||||
TRUE ,
|
TRUE ,
|
||||||
FALSE)
|
FALSE)
|
||||||
FROM tmp.workerTimeControlDirection
|
FROM tmp.workerTimeControlDirection
|
||||||
|
@ -137,12 +139,17 @@ BEGIN
|
||||||
SET vIsError = TRUE;
|
SET vIsError = TRUE;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
DROP TEMPORARY TABLE tmp.workerTimeControlDirection;
|
|
||||||
IF vIsError THEN
|
IF vIsError THEN
|
||||||
SET vErrorCode = 'WRONG_DIRECTION';
|
SET vErrorCode = 'WRONG_DIRECTION';
|
||||||
|
IF(SELECT option1 IS NULL AND option2 IS NULL
|
||||||
|
FROM tmp.workerTimeControlDirection) THEN
|
||||||
|
|
||||||
|
SET vErrorCode = 'DAY_MAX_TIME';
|
||||||
|
END IF;
|
||||||
CALL util.throw(vErrorCode);
|
CALL util.throw(vErrorCode);
|
||||||
END IF;
|
END IF;
|
||||||
|
DROP TEMPORARY TABLE tmp.workerTimeControlDirection;
|
||||||
-- FICHADAS IMPARES
|
-- FICHADAS IMPARES
|
||||||
SELECT timed INTO vLastIn
|
SELECT timed INTO vLastIn
|
||||||
FROM workerTimeControl
|
FROM workerTimeControl
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`agency_beforeInsert`
|
||||||
|
BEFORE INSERT ON `agency`
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
SET NEW.editorFk = account.myUser_getId();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,2 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||||
|
VALUES ('InvoiceIn', 'exchangeRateUpdate', '*', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,4 @@
|
||||||
|
ALTER TABLE vn.referenceRate DROP INDEX `PRIMARY`;
|
||||||
|
ALTER TABLE vn.referenceRate ADD id INT auto_increment PRIMARY KEY;
|
||||||
|
ALTER TABLE vn.referenceRate CHANGE id id int(11) auto_increment NOT NULL FIRST;
|
||||||
|
CREATE UNIQUE INDEX referenceRate_currencyFk_IDX USING BTREE ON vn.referenceRate (currencyFk,dated);
|
|
@ -0,0 +1,24 @@
|
||||||
|
-- vn.agencyLog definition
|
||||||
|
ALTER TABLE vn.agency ADD IF NOT EXISTS editorFk int(10) unsigned DEFAULT NULL NULL;
|
||||||
|
|
||||||
|
ALTER TABLE vn.agency ADD CONSTRAINT agency_user_FK FOREIGN KEY (editorFk) REFERENCES `account`.`user`(id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `vn`.`agencyLog` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`originFk` smallint(5) unsigned 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('agency','agencyMode') NOT NULL DEFAULT 'agency',
|
||||||
|
`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,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `logAgencyUserFk` (`userFk`),
|
||||||
|
KEY `agencyLog_changedModel` (`changedModel`,`changedModelId`,`creationDate`),
|
||||||
|
KEY `agencyLog_originFk` (`originFk`,`creationDate`),
|
||||||
|
CONSTRAINT `agencyOriginFk` FOREIGN KEY (`originFk`) REFERENCES `agency` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `agencyUserFk` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
|
@ -0,0 +1,18 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS `vn`.`agencyWorkCenter` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`agencyFk` smallint(5) unsigned NOT NULL,
|
||||||
|
`workCenterFk` int(11) NOT NULL,
|
||||||
|
`editorFk` int(10) unsigned DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `agencyWorkCenter_unique` (`agencyFk`,`workCenterFk`),
|
||||||
|
KEY `agencyWorkCenter_workCenter_FK` (`workCenterFk`),
|
||||||
|
KEY `agencyWorkCenter_user_FK` (`editorFk`),
|
||||||
|
CONSTRAINT `agencyWorkCenter_agency_FK` FOREIGN KEY (`agencyFk`) REFERENCES `agency` (`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `agencyWorkCenter_user_FK` FOREIGN KEY (`editorFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `agencyWorkCenter_workCenter_FK` FOREIGN KEY (`workCenterFk`) REFERENCES `workCenter` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='refs #4988';
|
||||||
|
|
||||||
|
INSERT INTO vn.agencyWorkCenter (agencyFk, workCenterFk)
|
||||||
|
SELECT id, workCenterFk
|
||||||
|
FROM vn.agency
|
||||||
|
WHERE workCenterFk IS NOT NULL;
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- Place your SQL code here
|
||||||
|
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
|
||||||
|
VALUES ('AgencyLog','*','READ','ALLOW','ROLE','employee');
|
||||||
|
|
||||||
|
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
|
||||||
|
VALUES ('AgencyWorkCenter','*','READ','ALLOW','ROLE','employee');
|
||||||
|
|
||||||
|
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES('AgencyMode', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
|
||||||
|
|
||||||
|
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES('Agency', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
|
||||||
|
|
||||||
|
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
|
||||||
|
VALUES ('Agency','*','WRITE','ALLOW','ROLE','deliveryAssistant');
|
||||||
|
|
||||||
|
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
|
||||||
|
VALUES ('AgencyWorkCenter','*','WRITE','ALLOW','ROLE','deliveryAssistant');
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
CREATE OR REPLACE TABLE `vn`.`farmingDeliveryNote` (
|
CREATE OR REPLACE TABLE `vn`.`farmingDeliveryNote` (
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`farmingFk` int(10) unsigned NOT NULL,
|
`farmingFk` int(10) unsigned NOT NULL,
|
||||||
|
@ -10,3 +9,8 @@ CREATE OR REPLACE TABLE `vn`.`farmingDeliveryNote` (
|
||||||
CONSTRAINT `farmingDeliveryNoteFk_FK` FOREIGN KEY (`deliveryNoteFk`) REFERENCES `deliveryNote` (`id`),
|
CONSTRAINT `farmingDeliveryNoteFk_FK` FOREIGN KEY (`deliveryNoteFk`) REFERENCES `deliveryNote` (`id`),
|
||||||
CONSTRAINT `farmingDeliveryNoteFk_FK_1` FOREIGN KEY (`farmingFk`) REFERENCES `farming` (`id`)
|
CONSTRAINT `farmingDeliveryNoteFk_FK_1` FOREIGN KEY (`farmingFk`) REFERENCES `farming` (`id`)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `vn`.`farmingDeliveryNote` (farmingFk, deliveryNoteFk, amount)
|
||||||
|
SELECT farmingFk, id, amount
|
||||||
|
FROM vn.deliveryNote dn
|
||||||
|
WHERE farmingFk;
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- Place your SQL code here
|
||||||
|
ALTER TABLE vn.claimBeginning MODIFY COLUMN quantity double DEFAULT 0 NULL;
|
|
@ -40,7 +40,7 @@ describe('InvoiceOut manual invoice path', () => {
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
||||||
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
|
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
|
||||||
|
|
||||||
await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceClient, 'Max Eisenhardt');
|
await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceClient, 'Petter Parker');
|
||||||
await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceSerial, 'Global nacional');
|
await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceSerial, 'Global nacional');
|
||||||
await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceTaxArea, 'national');
|
await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceTaxArea, 'national');
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.saveInvoice);
|
await page.waitToClick(selectors.invoiceOutIndex.saveInvoice);
|
||||||
|
|
|
@ -353,5 +353,8 @@
|
||||||
"This password can only be changed by the user themselves": "Esta contraseña solo puede ser modificada por el propio usuario",
|
"This password can only be changed by the user themselves": "Esta contraseña solo puede ser modificada por el propio usuario",
|
||||||
"They're not your subordinate": "No es tu subordinado/a.",
|
"They're not your subordinate": "No es tu subordinado/a.",
|
||||||
"No results found": "No se han encontrado resultados",
|
"No results found": "No se han encontrado resultados",
|
||||||
"InvoiceIn is already booked": "La factura recibida está contabilizada"
|
"InvoiceIn is already booked": "La factura recibida está contabilizada",
|
||||||
|
"This workCenter is already assigned to this agency": "Este centro de trabajo ya está asignado a esta agencia",
|
||||||
|
"Select ticket or client": "Elija un ticket o un client",
|
||||||
|
"It was not able to create the invoice": "No se pudo crear la factura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,6 @@ module.exports = Self => {
|
||||||
const newClaimBeginning = models.ClaimBeginning.create({
|
const newClaimBeginning = models.ClaimBeginning.create({
|
||||||
saleFk: sale.id,
|
saleFk: sale.id,
|
||||||
claimFk: newClaim.id,
|
claimFk: newClaim.id,
|
||||||
quantity: sale.quantity
|
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
promises.push(newClaimBeginning);
|
promises.push(newClaimBeginning);
|
||||||
|
|
|
@ -37,7 +37,7 @@ describe('Claim createFromSales()', () => {
|
||||||
let claimBeginning = await models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
|
let claimBeginning = await models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
|
||||||
|
|
||||||
expect(claimBeginning.saleFk).toEqual(newSale[0].id);
|
expect(claimBeginning.saleFk).toEqual(newSale[0].id);
|
||||||
expect(claimBeginning.quantity).toEqual(newSale[0].quantity);
|
expect(claimBeginning.quantity).toEqual(0);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -67,7 +67,7 @@ describe('Claim createFromSales()', () => {
|
||||||
const claimBeginning = await models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
|
const claimBeginning = await models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
|
||||||
|
|
||||||
expect(claimBeginning.saleFk).toEqual(newSale[0].id);
|
expect(claimBeginning.saleFk).toEqual(newSale[0].id);
|
||||||
expect(claimBeginning.quantity).toEqual(newSale[0].quantity);
|
expect(claimBeginning.quantity).toEqual(0);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
"description": "Identifier"
|
"description": "Identifier"
|
||||||
},
|
},
|
||||||
"quantity": {
|
"quantity": {
|
||||||
"type": "number",
|
"type": "number"
|
||||||
"required": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
const axios = require('axios');
|
||||||
|
const {DOMParser} = require('xmldom');
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('exchangeRateUpdate', {
|
||||||
|
description: 'Updates the exchange rates from an XML feed',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [],
|
||||||
|
http: {
|
||||||
|
path: '/exchangeRateUpdate',
|
||||||
|
verb: 'post'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.exchangeRateUpdate = async() => {
|
||||||
|
const response = await axios.get('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml');
|
||||||
|
const xmlData = response.data;
|
||||||
|
|
||||||
|
const doc = new DOMParser({errorHandler: {warning: () => {}}})?.parseFromString(xmlData, 'text/xml');
|
||||||
|
const cubes = doc?.getElementsByTagName('Cube');
|
||||||
|
if (!cubes || cubes.length === 0)
|
||||||
|
throw new UserError('No cubes found. Exiting the method.');
|
||||||
|
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
const maxDateRecord = await models.ReferenceRate.findOne({order: 'dated DESC'});
|
||||||
|
|
||||||
|
const maxDate = maxDateRecord?.dated ? new Date(maxDateRecord.dated) : null;
|
||||||
|
|
||||||
|
for (const cube of Array.from(cubes)) {
|
||||||
|
if (cube.nodeType === doc.ELEMENT_NODE && cube.attributes.getNamedItem('time')) {
|
||||||
|
const xmlDate = new Date(cube.getAttribute('time'));
|
||||||
|
const xmlDateWithoutTime = new Date(xmlDate.getFullYear(), xmlDate.getMonth(), xmlDate.getDate());
|
||||||
|
if (!maxDate || maxDate < xmlDateWithoutTime) {
|
||||||
|
for (const rateCube of Array.from(cube.childNodes)) {
|
||||||
|
if (rateCube.nodeType === doc.ELEMENT_NODE) {
|
||||||
|
const currencyCode = rateCube.getAttribute('currency');
|
||||||
|
const rate = rateCube.getAttribute('rate');
|
||||||
|
if (['USD', 'CNY', 'GBP'].includes(currencyCode)) {
|
||||||
|
const currency = await models.Currency.findOne({where: {code: currencyCode}});
|
||||||
|
if (!currency) throw new UserError(`Currency not found for code: ${currencyCode}`);
|
||||||
|
const existingRate = await models.ReferenceRate.findOne({
|
||||||
|
where: {currencyFk: currency.id, dated: xmlDate}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingRate) {
|
||||||
|
if (existingRate.value !== rate)
|
||||||
|
await existingRate.updateAttributes({value: rate});
|
||||||
|
} else {
|
||||||
|
await models.ReferenceRate.create({
|
||||||
|
currencyFk: currency.id,
|
||||||
|
dated: xmlDate,
|
||||||
|
value: rate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,52 @@
|
||||||
|
describe('exchangeRateUpdate functionality', function() {
|
||||||
|
const axios = require('axios');
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
spyOn(axios, 'get').and.returnValue(Promise.resolve({
|
||||||
|
data: `<Cube>
|
||||||
|
<Cube time='2024-04-12'>
|
||||||
|
<Cube currency='USD' rate='1.1'/>
|
||||||
|
<Cube currency='CNY' rate='1.2'/>
|
||||||
|
</Cube>
|
||||||
|
</Cube>`
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process XML data and update or create rates in the database', async function() {
|
||||||
|
spyOn(models.ReferenceRate, 'findOne').and.returnValue(Promise.resolve(null));
|
||||||
|
spyOn(models.ReferenceRate, 'create').and.returnValue(Promise.resolve());
|
||||||
|
|
||||||
|
await models.InvoiceIn.exchangeRateUpdate();
|
||||||
|
|
||||||
|
expect(models.ReferenceRate.create).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create or update rates when no XML data is available', async function() {
|
||||||
|
axios.get.and.returnValue(Promise.resolve({}));
|
||||||
|
spyOn(models.ReferenceRate, 'create');
|
||||||
|
|
||||||
|
let thrownError = null;
|
||||||
|
try {
|
||||||
|
await models.InvoiceIn.exchangeRateUpdate();
|
||||||
|
} catch (error) {
|
||||||
|
thrownError = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(thrownError.message).toBe('No cubes found. Exiting the method.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors gracefully', async function() {
|
||||||
|
axios.get.and.returnValue(Promise.reject(new Error('Network error')));
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await models.InvoiceIn.exchangeRateUpdate();
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.message).toBe('Network error');
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,6 +10,7 @@ module.exports = Self => {
|
||||||
require('../methods/invoice-in/invoiceInEmail')(Self);
|
require('../methods/invoice-in/invoiceInEmail')(Self);
|
||||||
require('../methods/invoice-in/getSerial')(Self);
|
require('../methods/invoice-in/getSerial')(Self);
|
||||||
require('../methods/invoice-in/corrective')(Self);
|
require('../methods/invoice-in/corrective')(Self);
|
||||||
|
require('../methods/invoice-in/exchangeRateUpdate')(Self);
|
||||||
|
|
||||||
Self.rewriteDbError(function(err) {
|
Self.rewriteDbError(function(err) {
|
||||||
if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn'))
|
if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn'))
|
||||||
|
|
|
@ -46,12 +46,11 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.createManualInvoice = async(ctx, options) => {
|
Self.createManualInvoice = async(ctx, clientFk, ticketFk, maxShipped, serial, taxArea, reference, options) => {
|
||||||
|
if (!clientFk && !ticketFk) throw new UserError(`Select ticket or client`);
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const args = ctx.args;
|
|
||||||
|
|
||||||
let tx;
|
|
||||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||||
|
let tx;
|
||||||
|
|
||||||
if (typeof options == 'object')
|
if (typeof options == 'object')
|
||||||
Object.assign(myOptions, options);
|
Object.assign(myOptions, options);
|
||||||
|
@ -61,18 +60,15 @@ module.exports = Self => {
|
||||||
myOptions.transaction = tx;
|
myOptions.transaction = tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ticketId = args.ticketFk;
|
|
||||||
let clientId = args.clientFk;
|
|
||||||
let maxShipped = args.maxShipped;
|
|
||||||
let companyId;
|
let companyId;
|
||||||
let newInvoice;
|
let newInvoice;
|
||||||
let query;
|
let query;
|
||||||
try {
|
try {
|
||||||
if (ticketId) {
|
if (ticketFk) {
|
||||||
const ticket = await models.Ticket.findById(ticketId, null, myOptions);
|
const ticket = await models.Ticket.findById(ticketFk, null, myOptions);
|
||||||
const company = await models.Company.findById(ticket.companyFk, null, myOptions);
|
const company = await models.Company.findById(ticket.companyFk, null, myOptions);
|
||||||
|
|
||||||
clientId = ticket.clientFk;
|
clientFk = ticket.clientFk;
|
||||||
maxShipped = ticket.shipped;
|
maxShipped = ticket.shipped;
|
||||||
companyId = ticket.companyFk;
|
companyId = ticket.companyFk;
|
||||||
|
|
||||||
|
@ -85,7 +81,7 @@ module.exports = Self => {
|
||||||
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
|
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
|
||||||
|
|
||||||
// Validates ticket nagative base
|
// Validates ticket nagative base
|
||||||
const hasNegativeBase = await getNegativeBase(maxShipped, clientId, companyId, myOptions);
|
const hasNegativeBase = await getNegativeBase(maxShipped, clientFk, companyId, myOptions);
|
||||||
if (hasNegativeBase && company.code == 'VNL')
|
if (hasNegativeBase && company.code == 'VNL')
|
||||||
throw new UserError(`A ticket with a negative base can't be invoiced`);
|
throw new UserError(`A ticket with a negative base can't be invoiced`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,7 +91,7 @@ module.exports = Self => {
|
||||||
const company = await models.Ticket.findOne({
|
const company = await models.Ticket.findOne({
|
||||||
fields: ['companyFk'],
|
fields: ['companyFk'],
|
||||||
where: {
|
where: {
|
||||||
clientFk: clientId,
|
clientFk: clientFk,
|
||||||
shipped: {lte: maxShipped}
|
shipped: {lte: maxShipped}
|
||||||
}
|
}
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
@ -103,7 +99,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate invoiceable client
|
// Validate invoiceable client
|
||||||
const isClientInvoiceable = await isInvoiceable(clientId, myOptions);
|
const isClientInvoiceable = await isInvoiceable(clientFk, myOptions);
|
||||||
if (!isClientInvoiceable)
|
if (!isClientInvoiceable)
|
||||||
throw new UserError(`This client is not invoiceable`);
|
throw new UserError(`This client is not invoiceable`);
|
||||||
|
|
||||||
|
@ -114,27 +110,27 @@ module.exports = Self => {
|
||||||
if (maxShipped >= tomorrow)
|
if (maxShipped >= tomorrow)
|
||||||
throw new UserError(`Can't invoice to future`);
|
throw new UserError(`Can't invoice to future`);
|
||||||
|
|
||||||
const maxInvoiceDate = await getMaxIssued(args.serial, companyId, myOptions);
|
const maxInvoiceDate = await getMaxIssued(serial, companyId, myOptions);
|
||||||
if (Date.vnNew() < maxInvoiceDate)
|
if (Date.vnNew() < maxInvoiceDate)
|
||||||
throw new UserError(`Can't invoice to past`);
|
throw new UserError(`Can't invoice to past`);
|
||||||
|
|
||||||
if (ticketId) {
|
if (ticketFk) {
|
||||||
query = `CALL invoiceOut_newFromTicket(?, ?, ?, ?, @newInvoiceId)`;
|
query = `CALL invoiceOut_newFromTicket(?, ?, ?, ?, @newInvoiceId)`;
|
||||||
await Self.rawSql(query, [
|
await Self.rawSql(query, [
|
||||||
ticketId,
|
ticketFk,
|
||||||
args.serial,
|
serial,
|
||||||
args.taxArea,
|
taxArea,
|
||||||
args.reference
|
reference
|
||||||
], myOptions);
|
], myOptions);
|
||||||
} else {
|
} else {
|
||||||
query = `CALL invoiceOut_newFromClient(?, ?, ?, ?, ?, ?, @newInvoiceId)`;
|
query = `CALL invoiceOut_newFromClient(?, ?, ?, ?, ?, ?, @newInvoiceId)`;
|
||||||
await Self.rawSql(query, [
|
await Self.rawSql(query, [
|
||||||
clientId,
|
clientFk,
|
||||||
args.serial,
|
serial,
|
||||||
maxShipped,
|
maxShipped,
|
||||||
companyId,
|
companyId,
|
||||||
args.taxArea,
|
taxArea,
|
||||||
args.reference
|
reference
|
||||||
], myOptions);
|
], myOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,26 +142,27 @@ module.exports = Self => {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newInvoice.id)
|
if (!newInvoice.id) throw new UserError('It was not able to create the invoice');
|
||||||
|
|
||||||
await Self.createPdf(ctx, newInvoice.id);
|
await Self.createPdf(ctx, newInvoice.id);
|
||||||
|
|
||||||
return newInvoice;
|
return newInvoice;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function isInvoiceable(clientId, options) {
|
async function isInvoiceable(clientFk, options) {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const query = `SELECT (hasToInvoice AND isTaxDataChecked) AS invoiceable
|
const query = `SELECT (hasToInvoice AND isTaxDataChecked) AS invoiceable
|
||||||
FROM client
|
FROM client
|
||||||
WHERE id = ?`;
|
WHERE id = ?`;
|
||||||
const [result] = await models.InvoiceOut.rawSql(query, [clientId], options);
|
const [result] = await models.InvoiceOut.rawSql(query, [clientFk], options);
|
||||||
|
|
||||||
return result.invoiceable;
|
return result.invoiceable;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getNegativeBase(maxShipped, clientId, companyId, options) {
|
async function getNegativeBase(maxShipped, clientFk, companyId, options) {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
await models.InvoiceOut.rawSql('CALL invoiceOut_exportationFromClient(?,?,?)',
|
await models.InvoiceOut.rawSql('CALL invoiceOut_exportationFromClient(?,?,?)',
|
||||||
[maxShipped, clientId, companyId], options
|
[maxShipped, clientFk, companyId], options
|
||||||
);
|
);
|
||||||
const query = 'SELECT vn.hasAnyNegativeBase() AS base';
|
const query = 'SELECT vn.hasAnyNegativeBase() AS base';
|
||||||
const [result] = await models.InvoiceOut.rawSql(query, [], options);
|
const [result] = await models.InvoiceOut.rawSql(query, [], options);
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const {models} = require('vn-loopback/server/server');
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
describe('InvoiceOut createManualInvoice()', () => {
|
describe('InvoiceOut createManualInvoice()', () => {
|
||||||
const userId = 1;
|
|
||||||
const ticketId = 16;
|
const ticketId = 16;
|
||||||
const clientId = 1106;
|
const clientId = 1106;
|
||||||
const activeCtx = {
|
const activeCtx = {accessToken: {userId: 1}};
|
||||||
accessToken: {userId: userId},
|
|
||||||
};
|
|
||||||
const ctx = {req: activeCtx};
|
const ctx = {req: activeCtx};
|
||||||
|
|
||||||
it('should throw an error trying to invoice again', async() => {
|
it('should throw an error trying to invoice again', async() => {
|
||||||
|
@ -18,13 +15,8 @@ describe('InvoiceOut createManualInvoice()', () => {
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
ctx.args = {
|
await createInvoice(ctx, options, undefined, ticketId);
|
||||||
ticketFk: ticketId,
|
await createInvoice(ctx, options, undefined, ticketId);
|
||||||
serial: 'T',
|
|
||||||
taxArea: 'CEE'
|
|
||||||
};
|
|
||||||
await models.InvoiceOut.createManualInvoice(ctx, options);
|
|
||||||
await models.InvoiceOut.createManualInvoice(ctx, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -47,17 +39,9 @@ describe('InvoiceOut createManualInvoice()', () => {
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
const ticket = await models.Ticket.findById(ticketId, null, options);
|
const ticket = await models.Ticket.findById(ticketId, null, options);
|
||||||
await ticket.updateAttributes({
|
await ticket.updateAttributes({totalWithVat: 0}, options);
|
||||||
totalWithVat: 0
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
ctx.args = {
|
|
||||||
ticketFk: ticketId,
|
|
||||||
serial: 'T',
|
|
||||||
taxArea: 'CEE'
|
|
||||||
};
|
|
||||||
await models.InvoiceOut.createManualInvoice(ctx, options);
|
|
||||||
|
|
||||||
|
await createInvoice(ctx, options, undefined, ticketId);
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
|
@ -75,13 +59,7 @@ describe('InvoiceOut createManualInvoice()', () => {
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
ctx.args = {
|
await createInvoice(ctx, options, clientId);
|
||||||
clientFk: clientId,
|
|
||||||
serial: 'T',
|
|
||||||
taxArea: 'CEE'
|
|
||||||
};
|
|
||||||
await models.InvoiceOut.createManualInvoice(ctx, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
|
@ -103,16 +81,9 @@ describe('InvoiceOut createManualInvoice()', () => {
|
||||||
let error;
|
let error;
|
||||||
try {
|
try {
|
||||||
const client = await models.Client.findById(clientId, null, options);
|
const client = await models.Client.findById(clientId, null, options);
|
||||||
await client.updateAttributes({
|
await client.updateAttributes({isTaxDataChecked: false}, options);
|
||||||
isTaxDataChecked: false
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
ctx.args = {
|
await createInvoice(ctx, options, undefined, ticketId);
|
||||||
ticketFk: ticketId,
|
|
||||||
serial: 'T',
|
|
||||||
taxArea: 'CEE'
|
|
||||||
};
|
|
||||||
await models.InvoiceOut.createManualInvoice(ctx, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -130,12 +101,7 @@ describe('InvoiceOut createManualInvoice()', () => {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ctx.args = {
|
const result = await createInvoice(ctx, options, undefined, ticketId);
|
||||||
ticketFk: ticketId,
|
|
||||||
serial: 'T',
|
|
||||||
taxArea: 'CEE'
|
|
||||||
};
|
|
||||||
const result = await models.InvoiceOut.createManualInvoice(ctx, options);
|
|
||||||
|
|
||||||
expect(result.id).toEqual(jasmine.any(Number));
|
expect(result.id).toEqual(jasmine.any(Number));
|
||||||
|
|
||||||
|
@ -146,3 +112,18 @@ describe('InvoiceOut createManualInvoice()', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function createInvoice(
|
||||||
|
ctx,
|
||||||
|
options,
|
||||||
|
clientFk = undefined,
|
||||||
|
ticketFk = undefined,
|
||||||
|
maxShipped = undefined,
|
||||||
|
serial = 'T',
|
||||||
|
taxArea = 'CEE',
|
||||||
|
reference = undefined
|
||||||
|
) {
|
||||||
|
return models.InvoiceOut.createManualInvoice(
|
||||||
|
ctx, clientFk, ticketFk, maxShipped, serial, taxArea, reference, options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -99,6 +99,32 @@ describe('workerTimeControl clockIn()', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw an error trying to add an "in" entry if the last clockIn is not out', async() => {
|
||||||
|
activeCtx.accessToken.userId = HHRRId;
|
||||||
|
const workerId = teamBossId;
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
ctx.args = {timed: "2000-12-25T21:00:00.000Z", direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
ctx.args = {timed: "2000-12-25T22:00:00.000Z", direction: 'middle'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
ctx.args = {timed: "2000-12-25T22:30:00.000Z", direction: 'middle'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
ctx.args = {timed: "2000-12-26T01:00:00.000Z", direction: 'in'};
|
||||||
|
await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('Dirección incorrecta');
|
||||||
|
await tx.rollback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
describe('as Role errors', () => {
|
describe('as Role errors', () => {
|
||||||
it('should add if the current user is team boss and the target user is himself', async() => {
|
it('should add if the current user is team boss and the target user is himself', async() => {
|
||||||
activeCtx.accessToken.userId = teamBossId;
|
activeCtx.accessToken.userId = teamBossId;
|
||||||
|
|
|
@ -15,6 +15,22 @@
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": false
|
"required": false
|
||||||
|
},
|
||||||
|
"warehouseFk": {
|
||||||
|
"type": "number",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"isOwn": {
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"workCenterFk": {
|
||||||
|
"type": "number",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
"isAnyVolumeAllowed": {
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
@ -22,6 +38,16 @@
|
||||||
"type": "hasOne",
|
"type": "hasOne",
|
||||||
"model": "SupplierAgencyTerm",
|
"model": "SupplierAgencyTerm",
|
||||||
"foreignKey": "agencyFk"
|
"foreignKey": "agencyFk"
|
||||||
|
},
|
||||||
|
"warehouse": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Warehouse",
|
||||||
|
"foreignKey": "warehouseFk"
|
||||||
|
},
|
||||||
|
"workCenter": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "WorkCenter",
|
||||||
|
"foreignKey": "workCenterFk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,3 +18,8 @@ h2 {
|
||||||
.description strong {
|
.description strong {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.black {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,4 @@ SELECT
|
||||||
FROM vn.entry e
|
FROM vn.entry e
|
||||||
JOIN vn.travel t ON t.id = e.travelFk
|
JOIN vn.travel t ON t.id = e.travelFk
|
||||||
WHERE e.supplierFk = ? AND DATE(t.shipped) BETWEEN ? AND ?
|
WHERE e.supplierFk = ? AND DATE(t.shipped) BETWEEN ? AND ?
|
||||||
|
ORDER BY t.shipped DESC;
|
||||||
|
|
|
@ -37,7 +37,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-for="entry in entries" v-if="entry.buys">
|
<div v-for="entry in entries" v-if="entry.buys">
|
||||||
<h2>
|
<h2>
|
||||||
<span>{{$t('entry')}} {{entry.id}}</span>
|
<span>
|
||||||
|
<span>{{$t('entry')}}</span>
|
||||||
|
<span class="black"> {{entry.id}}</span>
|
||||||
|
</span>
|
||||||
<span>{{$t('dated')}} {{formatDate(entry.shipped, '%d-%m-%Y')}}</span>
|
<span>{{$t('dated')}} {{formatDate(entry.shipped, '%d-%m-%Y')}}</span>
|
||||||
<span class="pull-right">{{$t('reference')}} {{entry.reference}}</span>
|
<span class="pull-right">{{$t('reference')}} {{entry.reference}}</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -67,6 +70,13 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<table>
|
||||||
|
<tr class="font bold no-page-break">
|
||||||
|
<td>{{$t('total')}}</td>
|
||||||
|
<td class="number">{{total.price | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-slot:footer>
|
<template v-slot:footer>
|
||||||
|
|
|
@ -7,7 +7,7 @@ module.exports = {
|
||||||
this.supplier = await this.findOneFromDef('supplier', [this.id]);
|
this.supplier = await this.findOneFromDef('supplier', [this.id]);
|
||||||
this.checkMainEntity(this.supplier);
|
this.checkMainEntity(this.supplier);
|
||||||
let entries = await this.rawSqlFromDef('entries', [this.id, this.from, this.to]);
|
let entries = await this.rawSqlFromDef('entries', [this.id, this.from, this.to]);
|
||||||
|
this.total = {quantity: 0, price: 0};
|
||||||
const entriesId = [];
|
const entriesId = [];
|
||||||
|
|
||||||
for (let entry of entries)
|
for (let entry of entries)
|
||||||
|
@ -23,7 +23,8 @@ module.exports = {
|
||||||
const entry = entriesMap.get(buy.entryFk);
|
const entry = entriesMap.get(buy.entryFk);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
if (!entry.buys) entry.buys = [];
|
if (!entry.buys) entry.buys = [];
|
||||||
|
this.total.quantity = this.total.quantity + buy.quantity;
|
||||||
|
this.total.price = this.total.price + (buy.price * buy.quantity);
|
||||||
entry.buys.push(buy);
|
entry.buys.push(buy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue