Merge pull request '#7936 fineTunningInvoiceIn' (!3251) from 7936-fineTunningInvoiceIn into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #3251
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
Reviewed-by: Carlos Andrés <carlosap@verdnatura.es>
This commit is contained in:
Jorge Penadés 2024-12-18 09:22:02 +00:00
commit dd5d9bcb08
24 changed files with 409 additions and 39 deletions

View File

@ -4038,6 +4038,11 @@ INSERT IGNORE INTO vn.saySimpleConfig (url, defaultChannel)
INSERT INTO vn.workerIrpf (workerFk,spouseNif, geographicMobilityDate)
VALUES (1106,'26493101E','2019-09-20');
INSERT INTO vn.referenceRate (currencyFk, dated, value)
VALUES (2, '2000-12-01', 1.0495),
(2, '2001-01-01', 1.0531),
(2, '2001-02-01', 7.6347);
INSERT IGNORE INTO vn.osrmConfig (id,url,tolerance)
VALUES (1,'https://router.project-osrm.org', 0.002);

View File

@ -4,10 +4,10 @@ BEGIN
/**
* Traslada la info de contabilidad relacionada con las facturas recibidas
*
* @vInvoiceInFk Factura recibida
* @vXDiarioFk Id tabla XDiario
* @param vInvoiceInFk Factura recibida
* @param vXDiarioFk Id tabla XDiario
*/
DECLARE vInvoiceInOriginalFk INT;
DECLARE vInvoiceInOriginalFk INT;
DECLARE vDone BOOL DEFAULT FALSE;
DECLARE vBase DOUBLE;
DECLARE vVat DOUBLE;
@ -205,9 +205,9 @@ BEGIN
WHERE correctingFk = vInvoiceInFk;
IF vInvoiceInOriginalFk THEN
UPDATE movContaIVA mci
JOIN vn.invoiceInRefund iir ON iir.invoiceInRefundFk = vInvoiceInFk
JOIN vn.invoiceInCorrection iic ON iic.correctingFk = vInvoiceInFk
JOIN vn.siiTypeInvoiceIn st ON st.id = iic.siiTypeInvoiceInFk
JOIN (SELECT issued,
SUM(sub.taxableBase) taxableBase,
SUM(ROUND((sub.taxableBase * sub.PorcentajeIva) / 100 , 2)) vat
@ -216,7 +216,7 @@ BEGIN
ti.PorcentajeIva
FROM vn.invoiceIn i
JOIN vn.invoiceInTax iit ON iit.invoiceInFk = i.id
JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
JOIN TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
WHERE i.id = vInvoiceInOriginalFk
GROUP BY ti.CodigoIva)sub
)invoiceInOriginal
@ -229,7 +229,6 @@ BEGIN
mci.CuotaIvaOriginal = invoiceInOriginal.vat,
mci.ClaveOperacionFactura = co.ClaveOperacionFactura_
WHERE mci.id = vXDiarioFk;
END IF;
END$$
DELIMITER ;

View File

@ -169,6 +169,7 @@ BEGIN
UPDATE movContaIVA mci
JOIN vn.invoiceOut i ON i.id = vInvoiceOutCorrectedFk
JOIN vn.invoiceCorrection ic ON ic.correctedFk = vInvoiceOutCorrectedFk
JOIN vn.siiTypeInvoiceOut st ON st.id = ic.siiTypeInvoiceOutFk
JOIN (SELECT SUM(IF(IFNULL(e.vatFk, TRUE), iot.taxableBase, 0)) taxableBase,
SUM(IF(IFNULL(e.vatFk, TRUE), iot.vat, 0)) vat,
SUM(IF(IFNULL(e.vatFk, TRUE), 0, iot.vat)) equ
@ -177,8 +178,8 @@ BEGIN
WHERE iot.invoiceOutFk = vInvoiceOutCorrectedFk
) tax
JOIN ClavesOperacion co ON co.Descripcion = 'Factura rectificativa'
SET mci.TipoRectificativa = 2,
mci.ClaseAbonoRectificativas = 1,
SET mci.TipoRectificativa = ic.cplusRectificationTypeFk,
mci.ClaseAbonoRectificativas = REGEXP_REPLACE(st.`code`, '[^0-9]', ''),
mci.FechaFacturaOriginal = i.issued,
mci.FechaOperacion = i.issued,
mci.BaseImponibleOriginal = tax.taxableBase,

View File

@ -43,7 +43,7 @@ BEGIN
ii.cplusTaxBreakFk,
ii.cplusSubjectOpFk,
ii.siiTypeInvoiceInFk,
ii.cplusRectificationTypeFk,
ic.cplusRectificationTypeFk,
ii.booked,
IFNULL(a.isUeeMember, c.isUeeMember) isUeeMember,
(c.id = cc.id) isSameCountry,
@ -66,6 +66,7 @@ BEGIN
e.name expenseName
FROM invoiceIn ii
JOIN supplier s ON s.id = ii.supplierFk
LEFT JOIN invoiceInCorrection ic ON ic.correctingFk = ii.id
LEFT JOIN province p ON p.id = s.provinceFk
LEFT JOIN autonomy a ON a.id = p.autonomyFk
JOIN country c ON c.id = s.countryFk

View File

@ -3,24 +3,30 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`invoiceIn_afterUpdate`
AFTER UPDATE ON `invoiceIn`
FOR EACH ROW
BEGIN
IF NEW.issued != OLD.issued
OR NEW.currencyFk != OLD.currencyFk THEN
DECLARE vIsEuro BOOL;
SELECT `code` = 'EUR' INTO vIsEuro
FROM currency
WHERE id = NEW.currencyFk;
IF (NOT NEW.issued <=> OLD.issued
OR NEW.currencyFk <> OLD.currencyFk) THEN
UPDATE invoiceInTax iit
JOIN invoiceIn ii ON ii.id = iit.invoiceInFk
LEFT JOIN referenceRate rr ON rr.dated = ii.issued
AND rr.currencyFk = ii.currencyFk
SET iit.taxableBase = IF(iit.foreignValue IS NULL, iit.taxableBase, iit.foreignValue / rr.value)
SET iit.taxableBase = IF(vIsEuro, iit.taxableBase, iit.foreignValue / rr.value),
iit.foreignValue = IF(vIsEuro, NULL, iit.foreignValue)
WHERE ii.id = NEW.id;
UPDATE invoiceInDueDay iidd
JOIN invoiceIn ii ON ii.id = iidd.invoiceInFk
LEFT JOIN referenceRate rr ON rr.dated = ii.issued
AND rr.currencyFk = ii.currencyFk
SET iidd.amount = IF(iidd.foreignValue IS NULL, iidd.amount, iidd.foreignValue / rr.value)
SET iidd.amount = IF(vIsEuro, iidd.amount, iidd.foreignValue / rr.value),
iidd.foreignValue = IF(vIsEuro, NULL, iidd.foreignValue)
WHERE ii.id = NEW.id;
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,48 @@
USE vn;
DROP TRIGGER IF EXISTS invoiceIn_beforeUpdate;
UPDATE invoiceIn
SET cplusRectificationTypeFk = NULL
WHERE cplusRectificationTypeFk = 1;
DELETE IGNORE FROM cplusRectificationType WHERE id = 1;
UPDATE cplusRectificationType
SET id = 1
WHERE id = 3;
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`invoiceIn_beforeUpdate`
BEFORE UPDATE ON `invoiceIn`
FOR EACH ROW
BEGIN
DECLARE vWithholdingSageFk INT;
IF NOT (NEW.supplierRef <=> OLD.supplierRef) AND NOT util.checkPrintableChars(NEW.supplierRef) THEN
CALL util.throw('The invoiceIn reference contains invalid characters');
END IF;
SET NEW.editorFk = account.myUser_getId();
IF (SELECT COUNT(*) FROM invoiceIn
WHERE supplierRef = NEW.supplierRef
AND supplierFk = NEW.supplierFk
AND YEAR(issued) = YEAR(NEW.issued)
AND id <> NEW.id
) THEN
CALL util.throw('reference duplicated');
END IF;
IF NEW.supplierFk != OLD.supplierFk THEN
CALL supplier_checkIsActive(NEW.supplierFk);
SELECT withholdingSageFk INTO vWithholdingSageFk
FROM supplier
WHERE id = NEW.supplierFk;
SET NEW.withholdingSageFk = vWithholdingSageFk;
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,23 @@
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
VALUES('SiiTypeInvoiceIn', 'find', 'READ', 'ALLOW', 'ROLE', 'salesPerson');
DROP TABLE IF EXISTS vn.invoiceInCorrection;
CREATE TABLE `invoiceInCorrection` (
`correctingFk` mediumint(8) unsigned NOT NULL COMMENT 'Factura rectificativa',
`correctedFk` mediumint(8) unsigned NOT NULL COMMENT 'Factura rectificada',
`cplusRectificationTypeFk` int(10) unsigned NOT NULL,
`siiTypeInvoiceInFk` int(10) unsigned NOT NULL,
`invoiceCorrectionTypeFk` int(11) NOT NULL DEFAULT 3,
PRIMARY KEY (`correctingFk`),
KEY `invoiceInCorrection_correctedFk` (`correctedFk`),
KEY `invoiceInCorrection_cplusRectificationTypeFk` (`cplusRectificationTypeFk`),
KEY `invoiceInCorrection_siiTypeInvoiceIn` (`siiTypeInvoiceInFk`),
KEY `invoiceInCorrection_invoiceCorrectionTypeFk` (`invoiceCorrectionTypeFk`),
CONSTRAINT `invoiceInCorrection_correctedFk` FOREIGN KEY (`correctedFk`) REFERENCES `invoiceIn` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `invoiceInCorrection_correctingFk` FOREIGN KEY (`correctingFk`) REFERENCES `invoiceIn` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `invoiceInCorrection_cplusRectificationTypeFk` FOREIGN KEY (`cplusRectificationTypeFk`) REFERENCES `cplusRectificationType` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceInCorrection_invoiceCorrectionTypeFk` FOREIGN KEY (`invoiceCorrectionTypeFk`) REFERENCES `invoiceCorrectionType` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceInCorrection_siiTypeInvoiceIn` FOREIGN KEY (`siiTypeInvoiceInFk`) REFERENCES `siiTypeInvoiceIn` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;

View File

@ -44,7 +44,7 @@ module.exports = Self => {
correctingFk: clone.id,
correctedFk: id,
cplusRectificationTypeFk: invoiceType,
siiTypeInvoiceOutFk: invoiceClass,
siiTypeInvoiceInFk: invoiceClass,
invoiceCorrectionTypeFk: invoiceReason
}, myOptions);

View File

@ -91,6 +91,10 @@ module.exports = Self => {
{
arg: 'supplierActivityFk',
type: 'string',
},
{
arg: 'companyFk',
type: 'number',
}
],
returns: {
@ -161,8 +165,8 @@ module.exports = Self => {
: {'ii.id': {nin: correcteds.map(x => x.correctingFk)}};
case 'correctedFk':
return {'ii.id': {inq: correctings.map(x => x.correctingFk)}};
case 'supplierActivityFk':
return {'s.supplierActivityFk': value};
case 'companyFk':
return {'ii.companyFk': value};
}
});
@ -184,7 +188,9 @@ module.exports = Self => {
s.name supplierName,
s.account,
SUM(iid.amount) amount,
sub.code awbCode
sub.code awbCode,
c.code,
MIN(iid.dueDated) dueDated
FROM invoiceIn ii
JOIN supplier s ON s.id = ii.supplierFk
LEFT JOIN invoiceInDueDay iid ON iid.invoiceInFk = ii.id
@ -199,7 +205,8 @@ module.exports = Self => {
GROUP BY de.duaFk
) sub ON sub.duaFk = d.id
LEFT JOIN company co ON co.id = ii.companyFk
LEFT JOIN dms dm ON dm.id = ii.docFk`
LEFT JOIN dms dm ON dm.id = ii.docFk
JOIN company c ON c.id = ii.companyFk`,
);
const sqlWhere = conn.makeWhere(filter.where);

View File

@ -15,11 +15,11 @@ describe('invoiceIn corrective()', () => {
await tx.rollback();
});
it('La función corrective debería devolver un id cuando se ejecuta correctamente', async() => {
it('should return an id when executed correctly', async() => {
const originalId = 1;
const invoiceReason = 3;
const invoiceType = 2;
const invoiceClass = 1;
const invoiceClass = 8;
const cloneId = await models.InvoiceIn.corrective(ctx,
originalId, invoiceReason, invoiceType, invoiceClass, options);
@ -30,7 +30,7 @@ describe('invoiceIn corrective()', () => {
}, options);
expect(correction.cplusRectificationTypeFk).toEqual(invoiceType);
expect(correction.siiTypeInvoiceOutFk).toEqual(invoiceClass);
expect(correction.siiTypeInvoiceInFk).toEqual(invoiceClass);
expect(correction.invoiceCorrectionTypeFk).toEqual(invoiceReason);
});
});

View File

@ -166,4 +166,21 @@ describe('InvoiceIn filter()', () => {
throw e;
}
});
it('should return the invoice in matching companyFk', async() => {
const tx = await models.InvoiceIn.beginTransaction({});
const options = {transaction: tx};
try {
const company = await models.Company.findOne({}, options);
const invoicesByCompany = await models.InvoiceIn.find({where: {companyFk: company.id}}, options);
const filteredInvoices = await models.InvoiceIn.filter({args: {companyFk: company.id}}, {}, options);
expect(filteredInvoices.length).toEqual(invoicesByCompany.length);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,63 @@
const models = require('vn-loopback/server/server').models;
describe('invoiceIn', () => {
let options;
let tx;
const invoiceId = 1;
const supplierId = 791;
const currencyId = 1;
const companyId = 442;
beforeEach(async() => {
tx = await models.InvoiceIn.beginTransaction({});
options = {transaction: tx};
});
afterEach(async() => {
await tx.rollback();
});
it('should allow insert for new instance', async() => {
const newInvoice = {
supplierFk: supplierId,
issued: Date.vnNew(),
operated: Date.vnNew(),
currencyFk: currencyId,
companyFk: companyId,
isBooked: false
};
const createdInvoice = await models.InvoiceIn.create(newInvoice, options);
expect(createdInvoice).toBeDefined();
expect(createdInvoice.id).toBeDefined();
});
it('should throw an error if trying to update a booked invoice', async() => {
const invoice = await models.InvoiceIn.findById(invoiceId, null, options);
await invoice.updateAttribute('isBooked', true, options);
let error;
try {
await invoice.updateAttribute('supplierFk', supplierId, options);
} catch (err) {
error = err;
}
expect(error.message).toBe('InvoiceIn is already booked');
});
it('should throw an error if trying to delete a booked invoice', async() => {
const invoice = await models.InvoiceIn.findById(invoiceId, null, options);
await invoice.updateAttribute('isBooked', true, options);
let error;
try {
await models.InvoiceIn.deleteById(invoiceId, options);
} catch (err) {
error = err;
}
expect(error.message).toBe('InvoiceIn is already booked');
});
});

View File

@ -0,0 +1,74 @@
const models = require('vn-loopback/server/server').models;
describe('invoiceInTax', () => {
let options;
let tx;
const invoiceInId = 1;
const invoiceInTaxId = 1;
beforeEach(async() => {
tx = await models.InvoiceInTax.beginTransaction({});
options = {transaction: tx};
});
afterEach(async() => {
await tx.rollback();
});
it('should throw an error if trying to save a tax from a booked invoice', async() => {
const invoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options);
await invoiceIn.updateAttributes({isBooked: true}, options);
const invoiceInTax = await models.InvoiceInTax.findById(invoiceInTaxId, null, options);
let error;
try {
await invoiceInTax.updateAttribute('taxableBase', 100, options);
} catch (err) {
error = err;
}
expect(error.message).toBe('InvoiceIn is already booked');
});
it('should allow save if the invoice is not booked', async() => {
const invoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options);
await invoiceIn.updateAttribute('isBooked', false, options);
const invoiceInTax = await models.InvoiceInTax.findById(invoiceInTaxId, null, options);
await invoiceInTax.updateAttribute('taxableBase', 100, options);
const updatedInvoiceInTax = await models.InvoiceInTax.findById(invoiceInTaxId, null, options);
expect(updatedInvoiceInTax.taxableBase).toBe(100);
});
it('should throw an error if trying to delete a tax from a booked invoice', async() => {
const invoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options);
await invoiceIn.updateAttribute('isBooked', true, options);
let error;
try {
await models.InvoiceInTax.destroyById(invoiceInTaxId, options);
} catch (err) {
error = err;
}
expect(error).toBeDefined();
expect(error.message).toBe('InvoiceIn is already booked');
});
it('should allow delete if the invoice is not booked', async() => {
const invoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options);
await invoiceIn.updateAttribute('isBooked', false, options);
let error;
try {
await models.InvoiceInTax.destroyById(invoiceInTaxId, options);
} catch (err) {
error = err;
}
const deletedInvoiceInTax = await models.InvoiceInTax.findById(invoiceInTaxId, null, options);
expect(error).toBeUndefined();
expect(deletedInvoiceInTax).toBeNull();
});
});

View File

@ -0,0 +1,59 @@
const models = require('vn-loopback/server/server').models;
const invoiceInId = 1;
const supplierId = 791;
describe('invoiceIn updateInvoiceIn()', () => {
const ctx = beforeAll.getCtx();
let options;
let tx;
beforeEach(async() => {
options = {transaction: tx};
tx = await models.Sale.beginTransaction({});
options.transaction = tx;
});
afterEach(async() => {
await tx.rollback();
});
it('should update the invoice', async() => {
const invoiceBefore = await models.InvoiceIn.findById(invoiceInId, null, options);
await update(ctx, options);
const invoiceAfter = await models.InvoiceIn.findById(invoiceInId, null, options);
expect(invoiceAfter.supplierFk).not.toBe(invoiceBefore.supplierFk);
expect(invoiceAfter.supplierFk).toBe(supplierId);
});
it('should not update the invoice if is booked', async() => {
let error;
try {
await models.InvoiceIn.toBook(ctx, invoiceInId, options);
await update(ctx, options);
} catch (e) {
error = e;
}
expect(error.message).toBe('InvoiceIn is already booked');
});
});
async function update(ctx, opts) {
const supplierRef = 'mockRef';
const currencyId = 1;
await models.InvoiceIn.updateInvoiceIn(ctx,
invoiceInId,
supplierId,
supplierRef,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
currencyId,
undefined,
undefined,
opts);
}

View File

@ -37,7 +37,13 @@ module.exports = Self => {
{
relation: 'supplier',
scope: {
fields: ['id', 'name']
fields: ['id', 'name', 'isVies', 'countryFk'],
include: [{
relation: 'country',
scope: {
fields: ['id', 'code']
}
}]
}
},
{

View File

@ -52,7 +52,8 @@ module.exports = Self => {
accountingEntries = await models.Xdiario.count({ASIEN: asien}, myOptions);
await models.Xdiario.destroyAll({ASIEN: asien}, myOptions);
await Self.updateAll({id: invoiceInId}, {isBooked: false}, myOptions);
const invoiceIn = await Self.findById(invoiceInId, myOptions);
await invoiceIn.updateAttribute('isBooked', false, myOptions);
} else {
const linkedBookEntry = await models.Xdiario.findOne({
fields: ['ASIEN'],

View File

@ -82,7 +82,7 @@ module.exports = Self => {
try {
const invoiceIn = await Self.findById(id, null, myOptions);
invoiceIn.updateAttributes({supplierFk,
await invoiceIn.updateAttributes({supplierFk,
supplierRef,
issued,
operated,
@ -94,6 +94,7 @@ module.exports = Self => {
companyFk,
withholdingSageFk
}, myOptions);
if (tx) await tx.commit();
return invoiceIn;
} catch (e) {

View File

@ -28,11 +28,10 @@
"model": "InvoiceCorrectionType",
"foreignKey": "invoiceCorrectionTypeFk"
},
"siiTypeInvoiceOut": {
"siiTypeInvoiceIn": {
"type": "belongsTo",
"model": "SiiTypeInvoiceOut",
"foreignKey": "siiTypeInvoiceOutFk"
"model": "SiiTypeInvoiceIn",
"foreignKey": "siiTypeInvoiceInFk"
}
}
}

View File

@ -0,0 +1,18 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return;
const models = Self.app.models;
const invoiceIn = await models.InvoiceIn.findById(ctx.currentInstance.invoiceInFk, null, ctx.options);
if (invoiceIn.isBooked) throw new UserError('InvoiceIn is already booked');
});
Self.observe('before delete', async function(ctx) {
const models = Self.app.models;
const invoiceInTax = await Self.findById(ctx.where.id, null, ctx.options);
const invoiceIn = await models.InvoiceIn.findById(invoiceInTax.invoiceInFk, null, ctx.options);
if (invoiceIn.isBooked) throw new UserError('InvoiceIn is already booked');
});
};

View File

@ -22,12 +22,11 @@
"type": "number"
},
"expenseFk": {
"type": "number"
"type": "string"
},
"created": {
"type": "date"
}
},
"relations": {
"invoiceIn": {
@ -51,4 +50,4 @@
"foreignKey": "transactionTypeSageFk"
}
}
}
}

View File

@ -19,4 +19,25 @@ module.exports = Self => {
return new UserError(`This invoice has a linked vehicle.`);
return err;
});
Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return;
const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance;
let isNotEditable = orgData.isBooked || (!orgData.isBooked && changes.isBooked);
if (isNotEditable) {
for (const [key, value] of Object.entries(changes)) {
if (key !== 'isBooked' && value !== orgData[key])
throw new UserError('InvoiceIn is already booked');
}
}
});
Self.observe('before delete', async function(ctx) {
const invoiceIn = await Self.findById(ctx.where.id, null, ctx.options);
if (invoiceIn.isBooked) throw new UserError('InvoiceIn is already booked');
});
};

View File

@ -43,5 +43,8 @@
},
"SiiTypeInvoiceOut": {
"dataSource": "vn"
},
"SiiTypeInvoiceIn": {
"dataSource": "vn"
}
}
}

View File

@ -0,0 +1,22 @@
{
"name": "SiiTypeInvoiceIn",
"base": "VnModel",
"options": {
"mysql": {
"table": "siiTypeInvoiceIn"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"code": {
"type": "string"
},
"description": {
"type": "string"
}
}
}

View File

@ -17,9 +17,6 @@
},
"description": {
"type": "string"
},
"code": {
"type": "string"
}
}
}
}