Compare commits

...

11 Commits

Author SHA1 Message Date
Carlos Andrés 1989375368 Merge branch 'dev'
gitea/salix/pipeline/head There was a failure building this commit Details
2023-06-21 10:37:46 +02:00
Alexandre Riera efdf62438f refs #4466 moved sql
gitea/salix/pipeline/head This commit looks good Details
2023-05-23 10:38:20 +02:00
Alexandre Riera 4ee50991d6 Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4466-facturas-rectificativas 2023-05-23 10:38:05 +02:00
Alexandre Riera ca9e0a0f2a refs #4466 rename e2e
gitea/salix/pipeline/head This commit looks good Details
2023-05-02 11:35:13 +02:00
Alexandre Riera 179822583f refs #4466 moved sql
gitea/salix/pipeline/head Build queued... Details
2023-05-02 11:34:06 +02:00
Alexandre Riera e9487dee7b Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4466-facturas-rectificativas
gitea/salix/pipeline/head This commit looks good Details
2023-05-02 11:32:47 +02:00
Alexandre Riera 08e6c435c6 refs #4466 changelog
gitea/salix/pipeline/head There was a failure building this commit Details
2023-03-08 11:41:28 +01:00
Alexandre Riera 0b55e20dbe Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4466-facturas-rectificativas
gitea/salix/pipeline/head There was a failure building this commit Details
2023-03-08 11:37:32 +01:00
Alexandre Riera ba3e961942 refs #4466 fix e2e
gitea/salix/pipeline/head This commit looks good Details
2023-03-08 11:35:09 +01:00
Alexandre Riera 7568a5f47f refs #4466 tests added
gitea/salix/pipeline/head There was a failure building this commit Details
2023-03-08 10:30:57 +01:00
Alexandre Riera a6894b88c1 refs #4466 correction section added, missing tests
gitea/salix/pipeline/head There was a failure building this commit Details
2023-03-08 08:58:55 +01:00
25 changed files with 800 additions and 20 deletions

View File

@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2326.01] - 2023-06-29
### Added
- (Tickets -> Crear Factura) Al facturar se envia automáticamente el pdf al cliente
- (Facturas Recibidas -> Rectificativa) Nueva sección
- (Entradas -> Correo) Al cambiar el tipo de cambio enviará un correo a las personas designadas
### Changed

View File

@ -0,0 +1,251 @@
CREATE TABLE `vn`.`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,
`cplusInvoiceType477Fk` 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_cplusInvoiceType477Fk` (`cplusInvoiceType477Fk`),
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_cplusInvoiceType477Fk` FOREIGN KEY (`cplusInvoiceType477Fk`) REFERENCES `cplusInvoiceType477` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceInCorrection_invoiceCorrectionTypeFk` FOREIGN KEY (`invoiceCorrectionTypeFk`) REFERENCES `invoiceCorrectionType` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceInCorrection_cplusRectificationTypeFk` FOREIGN KEY (`cplusRectificationTypeFk`) REFERENCES `cplusRectificationType` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Relacion entre las facturas rectificativas y las rectificadas.';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('InvoiceInCorrection', '*', '*', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceCorrectionType', '*', '*', 'ALLOW', 'ROLE', 'administrative'),
('CplusInvoiceType477', '*', '*', 'ALLOW', 'ROLE', 'administrative'),
('CplusRectificationType', '*', '*', 'ALLOW', 'ROLE', 'administrative');
DROP PROCEDURE IF EXISTS sage.invoiceIn_add;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `sage`.`invoiceIn_add`(vInvoiceInFk INT, vXDiarioFk INT)
BEGIN
/**
* Traslada la info de contabilidad relacionada con las facturas recibidas
*
* @vInvoiceInFk Factura recibida
* @vXDiarioFk Id tabla XDiario
*/
DECLARE vDone BOOL DEFAULT FALSE;
DECLARE vBase DOUBLE;
DECLARE vVat DOUBLE;
DECLARE vRate DOUBLE;
DECLARE vTransactionCode INT;
DECLARE vCounter INT DEFAULT 0;
DECLARE vTransactionCodeOld INT;
DECLARE vTaxCode INT;
DECLARE vTaxCodeOld INT;
DECLARE vOperationCode VARCHAR(1);
DECLARE vIsIntracommunity BOOL DEFAULT FALSE;
DECLARE vSerialDua VARCHAR(1) DEFAULT 'D';
DECLARE vInvoiceTypeReceived VARCHAR(1);
DECLARE vInvoiceTypeInformative VARCHAR(1);
DECLARE vInvoiceInOriginalFk INT;
DECLARE vCursor CURSOR FOR
SELECT it.taxableBase,
CAST((( it.taxableBase / 100) * t.PorcentajeIva) AS DECIMAL (10,2)),
t.PorcentajeIva,
it.transactionTypeSageFk,
it.taxTypeSageFk,
t.isIntracommunity,
tt.ClaveOperacionDefecto
FROM vn.invoiceIn i
JOIN vn.invoiceInTax it ON it.InvoiceInFk = i.id
JOIN TiposIva t ON t.CodigoIva = it.taxTypeSageFk
JOIN TiposTransacciones tt ON tt.CodigoTransaccion = it.transactionTypeSageFk
LEFT JOIN vn.dua d ON d.id = vInvoiceInFk
WHERE i.id = vInvoiceInFk
AND d.id IS NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DELETE FROM movContaIVA
WHERE id = vXDiarioFk;
SELECT codeSage INTO vInvoiceTypeReceived
FROM invoiceType WHERE code ='received';
SELECT codeSage INTO vInvoiceTypeInformative
FROM invoiceType WHERE code ='informative';
INSERT INTO movContaIVA(id, LibreA1)
VALUES (vXDiarioFk, vInvoiceInFk);
SELECT invoiceInOriginalFk INTO vInvoiceInOriginalFk
FROM vn.invoiceInRefund
WHERE invoiceInRefundFk = vInvoiceInFk;
IF vInvoiceInOriginalFk THEN
UPDATE movContaIVA mci
JOIN vn.invoiceInRefund iir ON iir.invoiceInRefundFk = vInvoiceInFk
JOIN (SELECT issued,
SUM(sub.taxableBase) taxableBase,
SUM(ROUND((sub.taxableBase * sub.PorcentajeIva) / 100 , 2)) vat
FROM (SELECT issued,
SUM(iit.taxableBase) taxableBase,
ti.PorcentajeIva
FROM vn.invoiceIn i
JOIN vn.invoiceInTax iit ON iit.invoiceInFk = i.id
JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
WHERE i.id = vInvoiceInOriginalFk
GROUP BY ti.CodigoIva)sub
) invoiceInOriginal
JOIN ClavesOperacion co ON co.Descripcion = 'Factura rectificativa'
SET mci.TipoRectificativa = iir.refundCategoryFk,
mci.ClaseAbonoRectificativas = iir.refundType,
mci.FechaFacturaOriginal = invoiceInOriginal.issued,
mci.FechaOperacion = invoiceInOriginal.issued,
mci.BaseImponibleOriginal = invoiceInOriginal.taxableBase,
mci.CuotaIvaOriginal = invoiceInOriginal.vat,
mci.ClaveOperacionFactura = co.ClaveOperacionFactura_
WHERE mci.id = vXDiarioFk;
END IF;
OPEN vCursor;
l: LOOP
FETCH vCursor INTO vBase,
vVat,
vRate,
vTransactionCode,
vTaxCode,
vIsIntracommunity,
vOperationCode;
IF vDone THEN
LEAVE l;
END IF;
SET vTransactionCodeOld = vTransactionCode;
SET vTaxCodeOld = vTaxCode;
IF vOperationCode IS NOT NULL THEN
UPDATE movContaIVA
SET ClaveOperacionFactura = vOperationCode
WHERE id = vXDiarioFk;
END IF;
SET vCounter = vCounter + 1;
CASE vCounter
WHEN 1 THEN
UPDATE movContaIVA
SET BaseIva1 = vBase,
PorIva1 = vRate,
CuotaIva1 = vVat,
CodigoTransaccion1 = vTransactionCode,
CodigoIva1 = vTaxCode
WHERE id = vXDiarioFk;
WHEN 2 THEN
UPDATE movContaIVA
SET BaseIva2 = vBase,
PorIva2 = vRate,
CuotaIva2 = vVat,
CodigoTransaccion2 = vTransactionCode,
CodigoIva2 = vTaxCode
WHERE id = vXDiarioFk;
WHEN 3 THEN
UPDATE movContaIVA
SET BaseIva3 = vBase,
PorIva3 = vRate,
CuotaIva3 = vVat,
CodigoTransaccion3 = vTransactionCode,
CodigoIva3 = vTaxCode
WHERE id = vXDiarioFk;
WHEN 4 THEN
UPDATE movContaIVA
SET BaseIva4 = vBase,
PorIva4 = vRate,
CuotaIva4 = vVat,
CodigoTransaccion4 = vTransactionCode,
CodigoIva4 = vTaxCode
WHERE id = vXDiarioFk;
ELSE
SELECT vXDiarioFk INTO vXDiarioFk;
END CASE;
IF vIsIntracommunity THEN
UPDATE movContaIVA
SET Intracomunitaria = TRUE
WHERE id = vXDiarioFk;
END IF;
SET vTransactionCodeOld = vTransactionCode;
SET vTaxCodeOld = vTaxCode;
END LOOP;
CLOSE vCursor;
UPDATE movContaIVA mci
JOIN tmp.invoiceIn ii ON ii.id = vInvoiceInFk
JOIN vn.XDiario x ON x.id = mci.id
LEFT JOIN tmp.invoiceDua id ON id.id = mci.id
JOIN vn.supplier s ON s.id = ii.supplierFk
JOIN Naciones n ON n.countryFk = s.countryFk
SET mci.CodigoDivisa = ii.currencyFk,
mci.Año = YEAR(ii.issued),
mci.Serie = ii.serial,
mci.Factura = ii.serialNumber,
mci.FechaFactura = ii.issued,
mci.ImporteFactura = IFNULL(mci.BaseIva1, 0) + IFNULL(mci.CuotaIva1, 0) +
IFNULL(mci.BaseIva2, 0) + IFNULL(mci.CuotaIva2, 0) +
IFNULL(mci.BaseIva3, 0) + IFNULL(mci.CuotaIva3, 0) +
IFNULL(mci.BaseIva4, 0) + IFNULL(mci.CuotaIva4, 0),
mci.TipoFactura = IF(id.id, IF( ii.serial = vSerialDua COLLATE utf8mb3_unicode_ci, vInvoiceTypeReceived, vInvoiceTypeInformative), vInvoiceTypeReceived),
mci.CodigoCuentaFactura = x.SUBCTA,
mci.CifDni = IF(LEFT(TRIM(s.nif), 2) = n.SiglaNacion, SUBSTRING(TRIM(s.nif), 3), s.nif),
mci.Nombre = s.name,
mci.SiglaNacion = n.SiglaNacion,
mci.EjercicioFactura = YEAR(ii.issued),
mci.FechaOperacion = ii.issued,
mci.MantenerAsiento = TRUE,
mci.SuFacturaNo = ii.supplierRef,
mci.IvaDeducible1 = IF(id.id, FALSE, IF(IFNULL(mci.BaseIva1, FALSE) = FALSE, FALSE, ii.isVatDeductible)),
mci.IvaDeducible2 = IF(id.id, FALSE, IF(IFNULL(mci.BaseIva2, FALSE) = FALSE, FALSE, ii.isVatDeductible)),
mci.IvaDeducible3 = IF(id.id, FALSE, IF(IFNULL(mci.BaseIva3, FALSE) = FALSE, FALSE, ii.isVatDeductible)),
mci.IvaDeducible4 = IF(id.id, FALSE, IF(IFNULL(mci.BaseIva4, FALSE) = FALSE, FALSE, ii.isVatDeductible)),
mci.FechaFacturaOriginal = x.FECHA_EX
WHERE mci.id = vXDiarioFk;
-- RETENCIONES
UPDATE movContaIVA mci
JOIN vn.invoiceIn ii ON ii.id = vInvoiceInFk
JOIN vn.XDiario x ON x.id = mci.id
JOIN vn.supplier s ON s.id = supplierFk
JOIN vn.invoiceInTax iit ON iit.invoiceInFk = ii.id
JOIN vn.expence e ON e.id = iit.expenceFk
JOIN TiposRetencion t ON t.CodigoRetencion = ii.withholdingSageFk
LEFT JOIN tmp.invoiceDua id ON id.id = mci.id
JOIN (SELECT SUM(x2.BASEEURO) taxableBase, SUM(x2.EURODEBE) taxBase
FROM vn.XDiario x1
JOIN vn.XDiario x2 ON x1.ASIEN = x2.ASIEN
WHERE x2.BASEEURO <> 0
AND x1.id = vXDiarioFk
)sub
JOIN ClavesOperacion co ON co.Descripcion = 'Arrendamiento de locales de negocio'
SET mci.CodigoRetencion = t.CodigoRetencion,
mci.ClaveOperacionFactura = IF( t.Retencion = 'ARRENDAMIENTO Y SUBARRENDAMIENTO', co.ClaveOperacionFactura_, mci.ClaveOperacionFactura),
mci.BaseRetencion = IF (t.Retencion = 'ACTIVIDADES AGRICOLAS O GANADERAS', sub.taxableBase + sub.taxBase, sub.taxableBase),
mci.PorRetencion = t.PorcentajeRetencion,
mci.ImporteRetencion = iit.taxableBase * - 1
WHERE mci.id = vXDiarioFk
AND e.name = 'Retenciones'
AND id.id IS NULL;
END$$
DELIMITER ;

View File

@ -2867,6 +2867,12 @@ INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `create
(1, 1, util.VN_NOW()),
(3, 3, util.VN_NOW());
INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`)
VALUES
(1, 'Error en los datos del cliente'),
(2, 'Error en el cálculo del IVA'),
(3, 'Error en el detalle de las ventas');
INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `state`, `updated`, `sendedCounter`, `reason`)
VALUES
(1, 9, 2000, 49, 'REVISE', util.VN_NOW(), 1, 'test2'),
@ -2895,6 +2901,16 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`)
(2, 1, 50, 2),
(3, 1, 0, 3);
INSERT INTO `vn`.`cplusRectificationType` (`id`, `description`)
VALUES
(1, 'Campo vacio'),
(2, 'I - Por diferencias'),
(3, 'S - Por sustitución');
INSERT INTO `vn`.`invoiceInCorrection` (`correctingFk`, `correctedFk`, `cplusRectificationTypeFk`, `cplusInvoiceType477Fk`, `invoiceCorrectionTypeFk`)
VALUES
(2, 3, 1, 1, 1);
INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`)
VALUES
(1, 21600, 300);

View File

@ -1017,14 +1017,27 @@ export default {
},
invoiceInSummary: {
totalTaxableBase: 'vn-invoice-in-summary > vn-card > vn-horizontal > vn-auto > vn-horizontal > vn-one.taxes > span',
supplierRef: 'vn-invoice-in-summary vn-label-value:nth-child(2) > section > span'
supplierRef: 'vn-invoice-in-summary vn-label-value:nth-child(2) > section > span',
correction: 'vn-invoice-in-summary > vn-card > vn-horizontal > vn-one[ng-if="$ctrl.summary.invoiceInCorrecting"]'
},
invoiceInCorrection: {
add: 'vn-invoice-in-correction vn-icon-button[vn-tooltip="Add correction"]',
originInvoice: 'vn-invoice-in-correction vn-autocomplete[vn-id="originInvoice"]',
type: 'vn-invoice-in-correction vn-autocomplete[vn-id="type"]',
class: 'vn-invoice-in-correction vn-autocomplete[vn-id="class"]',
reason: 'vn-invoice-in-correction vn-autocomplete[vn-id="reason"]',
deleteButton: 'vn-invoice-in-correction vn-icon-button[icon="delete"]',
saveButton: 'vn-invoice-in-correction vn-submit',
},
invoiceInDescriptor: {
summaryIcon: 'vn-invoice-in-descriptor a[title="Go to module summary"]',
moreMenu: 'vn-invoice-in-descriptor vn-icon-button[icon=more_vert]',
moreMenuDeleteInvoiceIn: '.vn-menu [name="deleteInvoice"]',
moreMenuCloneInvoiceIn: '.vn-menu [name="cloneInvoice"]',
acceptButton: '.vn-confirm.shown button[response="accept"]'
acceptButton: '.vn-confirm.shown button[response="accept"]',
supplierRef: 'vn-invoice-in-descriptor h5',
correctedInvoice: 'vn-invoice-in-descriptor a[vn-tooltip="Corrected invoice"]',
originInvoice: 'vn-invoice-in-descriptor a[vn-tooltip="Origin invoice"]'
},
invoiceInBasicData: {
issued: 'vn-invoice-in-basic-data vn-date-picker[ng-model="$ctrl.invoiceIn.issued"]',

View File

@ -0,0 +1,56 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer.js';
describe('InvoiceIn correction path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'invoiceIn');
await page.accessToSearchResult('2');
});
afterAll(async() => {
await browser.close();
});
it('should reach the summary section and check that contain a correction', async() => {
await page.waitForSelector(selectors.invoiceInSummary.correction);
await page.waitToClick(selectors.invoiceInDescriptor.correctedInvoice);
const result = await page.waitToGetProperty(selectors.invoiceInDescriptor.supplierRef, 'innerText');
expect(result).toContain('1236');
await page.waitToClick(selectors.invoiceInDescriptor.originInvoice);
});
it('should go to correction section, delete the correction and save', async() => {
await page.accessToSection('invoiceIn.card.correction');
await page.waitToClick(selectors.invoiceInCorrection.deleteButton);
await page.waitToClick(selectors.invoiceInCorrection.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should create a new correction', async() => {
await page.reloadSection('invoiceIn.card.correction');
await page.waitToClick(selectors.invoiceInCorrection.add);
await page.autocompleteSearch(selectors.invoiceInCorrection.originInvoice, '1238');
await page.autocompleteSearch(selectors.invoiceInCorrection.type, 'Sustitución');
await page.autocompleteSearch(selectors.invoiceInCorrection.class, 'Ticket');
await page.autocompleteSearch(selectors.invoiceInCorrection.reason, 'IVA');
await page.waitToClick(selectors.invoiceInCorrection.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should go to the new correction summary and check it has changed', async() => {
await page.waitToClick(selectors.invoiceInDescriptor.correctedInvoice);
const result = await page.waitToGetProperty(selectors.invoiceInDescriptor.supplierRef, 'innerText');
expect(result).toContain('1238');
});
});

View File

@ -151,10 +151,12 @@
"It is not possible to modify tracked sales": "It is not possible to modify tracked sales",
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
"Valid priorities: 1,2,3": "Valid priorities: 1,2,3",
"Origin invoice cannot be empty": "Origin invoice cannot be empty",
"Warehouse inventory not set": "Almacén inventario no está establecido",
"Component cost not set": "Componente coste no está estabecido",
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2",
"Type cannot be empty": "Type cannot be empty",
"Class cannot be empty": "Class cannot be empty",
"Reason cannot be empty": "Reason cannot be empty",
"Description cannot be blank": "Description cannot be blank",
"company": "Company",
"country": "Country",

View File

@ -290,8 +290,12 @@
"isTaxDataChecked": "Datos comprobados",
"comercialId": "Id comercial",
"comercialName": "Comercial",
"Pass expired": "La contraseña ha caducado, cambiela desde Salix",
"Invalid NIF for VIES": "Invalid NIF for VIES",
"Origin invoice cannot be empty": "La factura origen no puede estar vacía",
"Type cannot be empty": "El tipo no puede estar vacío",
"Class cannot be empty": "La clase no puede estar vacía",
"Reason cannot be empty": "El motivo no puede estar vacío",
"Pass expired": "La contraseña ha caducado, cambiela desde Salix",
"Ticket does not exist": "Este ticket no existe",
"Ticket is already signed": "Este ticket ya ha sido firmado",
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado"

View File

@ -128,6 +128,37 @@ module.exports = Self => {
scope: {
fields: ['id', 'code']
}
},
{
relation: 'invoiceInCorrecting',
scope: {
fields: ['correctedFk',
'cplusInvoiceType477Fk',
'invoiceCorrectionTypeFk',
'cplusRectificationTypeFk'],
include: [{
relation: 'corrected',
scope: {
fields: ['supplierRef']
}
}, {
relation: 'cplusInvoiceType477',
scope: {
fields: ['description']
}
}, {
relation: 'invoiceCorrectionType',
scope: {
fields: ['description']
}
}, {
relation: 'cplusRectificationType',
scope: {
fields: ['description']
}
}]
}
}
]

View File

@ -16,5 +16,17 @@
},
"InvoiceInTax": {
"dataSource": "vn"
},
"InvoiceInCorrection": {
"dataSource": "vn"
},
"InvoiceCorrectionType": {
"dataSource": "vn"
},
"CplusInvoiceType477": {
"dataSource": "vn"
},
"CplusRectificationType": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,21 @@
{
"name": "CplusInvoiceType477",
"base": "VnModel",
"options": {
"mysql": {
"table": "cplusInvoiceType477"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"description": {
"type": "string"
}
}
}

View File

@ -0,0 +1,21 @@
{
"name": "CplusRectificationType",
"base": "VnModel",
"options": {
"mysql": {
"table": "cplusRectificationType"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"description": {
"type": "string"
}
}
}

View File

@ -0,0 +1,21 @@
{
"name": "InvoiceCorrectionType",
"base": "VnModel",
"options": {
"mysql": {
"table": "invoiceCorrectionType"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"description": {
"type": "string"
}
}
}

View File

@ -0,0 +1,14 @@
module.exports = Self => {
Self.validatesPresenceOf('correctedFk', {
message: 'Origin invoice cannot be empty'
});
Self.validatesPresenceOf('cplusRectificationTypeFk', {
message: 'Type cannot be empty'
});
Self.validatesPresenceOf('cplusInvoiceType477Fk', {
message: 'Class cannot be empty'
});
Self.validatesPresenceOf('invoiceCorrectionTypeFk', {
message: 'Reason cannot be empty'
});
};

View File

@ -0,0 +1,57 @@
{
"name": "InvoiceInCorrection",
"base": "VnModel",
"options": {
"mysql": {
"table": "invoiceInCorrection"
}
},
"properties": {
"correctingFk": {
"id": true,
"type": "number",
"description": "Identifier"
},
"correctedFk": {
"type": "number"
},
"cplusRectificationTypeFk": {
"type": "number"
},
"cplusInvoiceType477Fk": {
"type": "number"
},
"invoiceCorrectionTypeFk": {
"type": "number"
}
},
"relations": {
"correcting": {
"type": "belongsTo",
"model": "InvoiceIn",
"foreignKey": "correctingFk"
},
"corrected": {
"type": "belongsTo",
"model": "InvoiceIn",
"foreignKey": "correctedFk"
},
"cplusInvoiceType477": {
"type": "belongsTo",
"model": "CplusInvoiceType477",
"foreignKey": "cplusInvoiceType477Fk"
},
"invoiceCorrectionType": {
"type": "belongsTo",
"model": "InvoiceCorrectionType",
"foreignKey": "invoiceCorrectionTypeFk"
},
"cplusRectificationType": {
"type": "belongsTo",
"model": "CplusRectificationType",
"foreignKey": "cplusRectificationTypeFk"
}
}
}

View File

@ -61,6 +61,16 @@
"model": "InvoiceInDueDay",
"foreignKey": "invoiceInFk"
},
"invoiceInCorrecting": {
"type": "hasOne",
"model": "InvoiceInCorrection",
"foreignKey": "correctingFk"
},
"invoiceInCorrected": {
"type": "hasOne",
"model": "InvoiceInCorrection",
"foreignKey": "correctedFk"
},
"invoiceInIntrastat": {
"type": "hasMany",
"model": "InvoiceInIntrastat",

View File

@ -26,7 +26,14 @@ class Controller extends ModuleCard {
},
{
relation: 'currency'
},
{
relation: 'invoiceInCorrecting'
},
{
relation: 'invoiceInCorrected'
}
]};
this.$http.get(`InvoiceIns/${this.$params.id}`, {filter})

View File

@ -0,0 +1,75 @@
<vn-crud-model
vn-id="model"
url="InvoiceInCorrections"
where="{correctingFk: $ctrl.$params.id}"
data="$ctrl.corrections"
auto-load="true"
primary-key="correctingFk">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="$ctrl.corrections"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()">
<vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="invoiceInCorrection in $ctrl.corrections">
<vn-autocomplete vn-one vn-id="originInvoice"
label="Origin invoice"
ng-model="invoiceInCorrection.correctedFk"
url="InvoiceIns"
where="{supplierFk: $ctrl.invoiceIn.supplierFk, id: {neq: $ctrl.invoiceIn.id}}"
show-field="supplierRef"
value-field="id"
rule>
</vn-autocomplete>
<vn-autocomplete vn-one vn-id="type"
label="Type"
ng-model="invoiceInCorrection.cplusRectificationTypeFk"
url="CplusRectificationTypes"
show-field="description"
value-field="id"
rule>
</vn-autocomplete>
<vn-autocomplete vn-one vn-id="class"
label="Class"
ng-model="invoiceInCorrection.cplusInvoiceType477Fk"
url="CplusInvoiceType477s"
show-field="description"
value-field="id"
rule>
</vn-autocomplete>
<vn-autocomplete vn-one vn-id="reason"
label="Reason"
ng-model="invoiceInCorrection.invoiceCorrectionTypeFk"
url="InvoiceCorrectionTypes"
show-field="description"
value-field="id"
rule>
</vn-autocomplete>
<vn-none>
<vn-icon-button
vn-tooltip="Remove correction"
icon="delete"
ng-click="model.remove($index)"
tabindex="-1">
</vn-icon-button>
</vn-none>
</vn-horizontal>
<vn-one>
<vn-icon-button
ng-if="$ctrl.corrections.length == 0"
vn-bind="+"
vn-tooltip="Add correction"
icon="add_circle"
ng-click="$ctrl.add()">
</vn-icon-button>
</vn-one>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,34 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
}
add() {
this.$.model.insert({
correctingFk: this.$params.id
});
}
onSubmit() {
this.$.watcher.check();
this.$.model.save().then(() => {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
this.card.reload();
});
}
}
ngModule.vnComponent('vnInvoiceInCorrection', {
template: require('./index.html'),
controller: Controller,
require: {
card: '^vnInvoiceInCard'
},
bindings: {
invoiceIn: '<'
}
});

View File

@ -0,0 +1,46 @@
import './index.js';
import watcher from 'core/mocks/watcher';
import crudModel from 'core/mocks/crud-model';
describe('InvoiceIn', () => {
describe('Component correction', () => {
let controller;
let $scope;
let vnApp;
beforeEach(ngModule('invoiceIn'));
beforeEach(inject(($componentController, $rootScope, _vnApp_,) => {
vnApp = _vnApp_;
jest.spyOn(vnApp, 'showError');
$scope = $rootScope.$new();
$scope.model = crudModel;
$scope.watcher = watcher;
const $element = angular.element('<vn-invoice-in-correction></vn-invoice-in-correction>');
controller = $componentController('vnInvoiceInCorrection', {$element, $scope});
controller.$.model = crudModel;
controller.invoiceIn = {id: 1};
}));
describe('onSubmit()', () => {
it('should make HTTP POST request to save correction values', () => {
controller.card = {reload: () => {}};
jest.spyOn($scope.watcher, 'check');
jest.spyOn($scope.watcher, 'notifySaved');
jest.spyOn($scope.watcher, 'updateOriginalData');
jest.spyOn(controller.card, 'reload');
jest.spyOn($scope.model, 'save');
controller.onSubmit();
expect($scope.model.save).toHaveBeenCalledWith();
expect($scope.watcher.updateOriginalData).toHaveBeenCalledWith();
expect($scope.watcher.check).toHaveBeenCalledWith();
expect($scope.watcher.notifySaved).toHaveBeenCalledWith();
expect(controller.card.reload).toHaveBeenCalledWith();
});
});
});
});

View File

@ -61,21 +61,31 @@
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link tooltip="Supplier" state="['supplier.card.summary', {id: $ctrl.invoiceIn.supplier.id}]"
icon="icon-supplier">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link tooltip="Entries list" state="['entry.index', {q: $ctrl.entryFilter}]"
icon="icon-entry">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
<div ng-transclude="btnTwo">
<vn-quick-link tooltip="Invoice list" state="['invoiceIn.index', {q: $ctrl.invoiceInFilter}]"
icon="icon-invoice-in">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
<vn-quick-link
ng-if="$ctrl.invoiceIn.invoiceInCorrecting"
tooltip="Corrected invoice"
state="['invoiceIn.card.summary', {id: $ctrl.invoiceIn.invoiceInCorrecting.correctedFk}]"
icon="link">
</vn-quick-link>
</div>
<div ng-transclude="btnFour">
<vn-quick-link
ng-if="$ctrl.invoiceIn.invoiceInCorrected"
tooltip="Origin invoice"
state="['invoiceIn.card.summary', {id: $ctrl.invoiceIn.invoiceInCorrected.correctingFk}]"
icon="link">
</vn-quick-link>
</div>
</div>
</slot-body>

View File

@ -114,6 +114,24 @@ class Controller extends Descriptor {
isAgricultural() {
return this.invoiceIn.supplier.sageWithholdingFk == this.config[0].sageWithholdingFk;
}
get invoiceInCorrection() {
if (this.invoiceIn) {
if (this.invoiceIn.invoiceInCorrecting) {
return {
tooltip: this.$t('Corrected invoice'),
id: this.invoiceIn.invoiceInCorrecting.correctedFk
};
} else if (this.invoiceIn.invoiceInCorrected) {
return {
tooltip: this.$t('Origin invoice'),
id: this.invoiceIn.invoiceInCorrected.correctingFk
};
}
}
return null;
}
}
ngModule.vnComponent('vnInvoiceInDescriptor', {

View File

@ -15,3 +15,5 @@ import './create';
import './log';
import './serial';
import './serial-search-panel';
import './correction';

View File

@ -25,3 +25,8 @@ Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
New InvoiceIn: Nueva Factura
Days ago: Últimos días
Negative bases: Bases negativas
Correction: Rectificativa
Add correction: Añadir rectificativa
Remove correction: Borrar rectificativa
Origin invoice: Factura origen
Corrected invoice: Factura rectificativa

View File

@ -29,6 +29,10 @@
"state": "invoiceIn.card.intrastat",
"icon": "icon-lines"
},
{
"state": "invoiceIn.card.correction",
"icon": "link"
},
{
"state": "invoiceIn.card.log",
"icon": "history"
@ -110,6 +114,16 @@
},
"acl": ["administrative"]
},
{
"url": "/correction",
"state": "invoiceIn.card.correction",
"component": "vn-invoice-in-correction",
"description": "Correction",
"params": {
"invoice-in": "$ctrl.invoiceIn"
},
"acl": ["administrative"]
},
{
"url": "/dueDay",
"state": "invoiceIn.card.dueDay",

View File

@ -49,10 +49,10 @@
<span td class="chip"><vn-label translate>Taxable base</vn-label> {{$ctrl.summary.totals.totalTaxableBase | currency: 'EUR':2 | dashIfEmpty}}</span>
<p><vn-label translate>Total</vn-label> {{$ctrl.summary.totals.totalVat | currency: 'EUR':2 | dashIfEmpty}}</p>
<vn-label translate>Due day</vn-label>
<vn-chip
class="transparent"
<vn-chip
class="transparent"
ng-class="{'alert': $ctrl.amountsNotMatch}"
translate-attr="{title: $ctrl.amountsNotMatch ? 'Do not match' : 'Due day'}"
translate-attr="{title: $ctrl.amountsNotMatch ? 'Do not match' : 'Due day'}"
>{{$ctrl.summary.totals.totalDueDay | currency: 'EUR':2 | dashIfEmpty}}
</vn-chip>
</vn-one>
@ -61,7 +61,7 @@
<vn-one ng-if="$ctrl.summary.invoiceInTax.length != 0">
<h4>
<a
<a
ui-sref="invoiceIn.card.tax({id:$ctrl.invoiceIn.id})"
target="_self">
<span translate vn-tooltip="Go to">Vat</span>
@ -94,7 +94,7 @@
<vn-horizontal>
<vn-one ng-if="$ctrl.summary.invoiceInDueDay.length != 0">
<h4>
<a
<a
ui-sref="invoiceIn.card.dueDay({id:$ctrl.invoiceIn.id})"
target="_self">
<span translate vn-tooltip="Go to">Due day</span>
@ -123,7 +123,7 @@
<vn-horizontal>
<vn-one ng-if="$ctrl.summary.invoiceInIntrastat.length != 0">
<h4>
<a
<a
ui-sref="invoiceIn.card.intrastat({id:$ctrl.invoiceIn.id})"
target="_self">
<span translate vn-tooltip="Go to">Intrastat</span>
@ -141,7 +141,7 @@
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="intrastat in $ctrl.summary.invoiceInIntrastat">
<vn-td>{{::intrastat.intrastatFk}}: {{::intrastat.intrastat.description}}</vn-td>
<vn-td>{{::intrastat.intrastatFk}}: {{::intrastat.intrastat.description}}</vn-td>
<vn-td>{{::intrastat.amount | currency: 'EUR':2}}</vn-td>
<vn-td>{{::intrastat.net}}</vn-td>
<vn-td>{{::intrastat.stems}}</vn-td>
@ -151,7 +151,45 @@
</vn-table>
</vn-one>
</vn-horizontal>
<vn-horizontal>
<vn-one ng-if="$ctrl.summary.invoiceInCorrecting">
<h4>
<a
ui-sref="invoiceIn.card.correction({id:$ctrl.invoiceIn.id})"
target="_self">
<span translate vn-tooltip="Go to">Correction</span>
</a>
</h4>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th>Origin invoice</vn-th>
<vn-th>Type</vn-th>
<vn-th>Class</vn-th>
<vn-th>Reason</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr>
<vn-td>
<span
vn-click-stop="invoiceInDescriptor.show($event, $ctrl.summary.invoiceInCorrecting.correctedFk)"
class="link">
{{$ctrl.summary.invoiceInCorrecting.corrected.supplierRef}}
</span>
</vn-td>
<vn-td>{{$ctrl.summary.invoiceInCorrecting.cplusRectificationType.description}}</vn-td>
<vn-td>{{$ctrl.summary.invoiceInCorrecting.cplusInvoiceType477.description}}</vn-td>
<vn-td>{{$ctrl.summary.invoiceInCorrecting.invoiceCorrectionType.description}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>
</vn-supplier-descriptor-popover>
<vn-invoice-in-descriptor-popover
vn-id="invoiceInDescriptor">
</vn-invoice-in-descriptor-popover>