Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 6870-modifyDefaulterNotification
gitea/salix/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Carlos Satorres 2024-04-29 12:14:38 +02:00
commit ace39b92ac
35 changed files with 487 additions and 116 deletions

View File

@ -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

View File

@ -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"
} }
} }

View File

@ -0,0 +1,9 @@
{
"name": "AgencyLog",
"base": "Log",
"options": {
"mysql": {
"table": "agencyLog"
}
}
}

View File

@ -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;
});
};

View File

@ -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"
}
}
}

View File

@ -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"
}] }]
} }

View File

@ -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"
}
]
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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 ;

View File

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

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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');

View File

@ -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;

View File

@ -0,0 +1,2 @@
-- Place your SQL code here
ALTER TABLE vn.claimBeginning MODIFY COLUMN quantity double DEFAULT 0 NULL;

View File

@ -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);

View File

@ -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"
} }

View File

@ -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);

View File

@ -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) {

View File

@ -16,8 +16,7 @@
"description": "Identifier" "description": "Identifier"
}, },
"quantity": { "quantity": {
"type": "number", "type": "number"
"required": true
} }
}, },
"relations": { "relations": {

View File

@ -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
});
}
}
}
}
}
}
}
};
};

View File

@ -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');
});
});

View File

@ -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'))

View File

@ -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);

View File

@ -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
);
}

View File

@ -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;

View File

@ -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"
} }
} }
} }

View File

@ -18,3 +18,8 @@ h2 {
.description strong { .description strong {
text-transform: uppercase; text-transform: uppercase;
} }
.black {
color: black;
}

View File

@ -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;

View File

@ -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>

View File

@ -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);
} }
} }