From aa4761e3ce0c1df3490efd8a88f60f37fe5a1fbb Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Fri, 18 Jan 2019 16:10:35 +0100 Subject: [PATCH] [FIX] l10n_es_aeat_sii: float_round error in BaseImponible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extension of https://github.com/OCA/l10n-spain/pull/972/commits/8a650787f14fd40513e9161a0e78bf09b55fd220 En las lineas https://github.com/OCA/l10n-spain/blob/fabc3a39eda6c595045906a3a612a18a6d902f9f/l10n_es_aeat_sii/models/account_invoice.py#L491-L492 y https://github.com/OCA/l10n-spain/blob/fabc3a39eda6c595045906a3a612a18a6d902f9f/l10n_es_aeat_sii/models/account_invoice.py#L537-L538 la suma (+=) genera un error de coma flotante, para reproducir el error desde la consola Python: Python 3.5.2 (default, Nov 12 2018, 13:43:14) [GCC 5.4.0 20160609] on linux >>> 170.61 + 107.35 277.96000000000004 Example on customer exempt invoices: - "Invoice Lines" tab: * [ { quantity: 1, price_unit: 56.31, invoice_line_tax_ids: ['IVA Exento Repercutido'] }, { quantity: 58, price_unit: 0.88, invoice_line_tax_ids: ['IVA Exento Repercutido'] }, { quantity: 1, price_unit: 170.61, invoice_line_tax_ids: ['IVA Exento Repercutido'] }, ] - "SII" tab: - "General" tab: * Error de envio SII: 1100 | Valor o tipo incorrecto del campo: BaseImponible - "Technical" tab: * Último contenido enviado al SII { "FacturaExpedida": { "ClaveRegimenEspecialOTrascendencia": "01", "ImporteTotal": 277.96, "DescripcionOperacion": "/", "TipoDesglose": { "DesgloseTipoOperacion": { "PrestacionServicios": { "Sujeta": { "Exenta": { "DetalleExenta": [ { "BaseImponible": 277.96000000000004, "CausaExencion": "E1" } ] } } } } }, "TipoFactura": "F1", "Contraparte": { "NIF": "XXXXXXXX", "NombreRazon": "XXXXXXXXXXXXXXXXXXXXXXXX" } }, "IDFactura": { "NumSerieFacturaEmisor": "22902", "FechaExpedicionFacturaEmisor": "17-01-2019", "IDEmisorFactura": { "NIF": "XXXXXXXXXX" } }, "PeriodoLiquidacion": { "Periodo": "01", "Ejercicio": 2019 } } * Retorno SII { 'CSV': None, 'DatosPresentacion': None, 'Cabecera': { 'IDVersionSii': '1.1', 'Titular': { 'NombreRazon': 'XXXXXXXXXXXX', 'NIFRepresentante': None, 'NIF': 'XXXXXXXXXXXXX' }, 'TipoComunicacion': 'A0' }, 'EstadoEnvio': 'Incorrecto', 'RespuestaLinea': [ { 'IDFactura': { 'IDEmisorFactura': { 'NIF': 'XXXXXXXXXXXX' }, 'NumSerieFacturaEmisor': '22902', 'NumSerieFacturaEmisorResumenFin': None, 'FechaExpedicionFacturaEmisor': '17-01-2019' }, 'RefExterna': None, 'EstadoRegistro': 'Incorrecto', 'CodigoErrorRegistro': 1100, 'DescripcionErrorRegistro': 'Valor o tipo incorrecto del campo: BaseImponible', 'CSV': None, 'RegistroDuplicado': None } ] } --- l10n_es_aeat_sii/models/account_invoice.py | 58 ++++++++++++++-------- l10n_es_aeat_sii/readme/CONTRIBUTORS.rst | 1 + 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/l10n_es_aeat_sii/models/account_invoice.py b/l10n_es_aeat_sii/models/account_invoice.py index 3dc9fa57719..828ff5d093d 100644 --- a/l10n_es_aeat_sii/models/account_invoice.py +++ b/l10n_es_aeat_sii/models/account_invoice.py @@ -52,6 +52,22 @@ def empty_decorator_factory(*argv, **kwargs): SII_MACRODATA_LIMIT = 100000000.0 +def round_by_keys(elem, search_keys, prec=2): + """ This uses ``round`` method directly as if has been tested that Odoo's + ``float_round`` still returns incorrect amounts for certain values. Try + 3 units x 3,77 €/unit with 10% tax and you will be hit by the error + (on regular x86 architectures).""" + if isinstance(elem, dict): + for key, value in elem.items(): + if key in search_keys: + elem[key] = round(elem[key], prec) + else: + round_by_keys(value, search_keys) + elif isinstance(elem, list): + for value in elem: + round_by_keys(value, search_keys) + + class AccountInvoice(models.Model): _inherit = 'account.invoice' @@ -387,11 +403,6 @@ def _get_sii_tax_line_req(self, tax): def _get_sii_tax_dict(self, tax_line, sign): """Get the SII tax dictionary for the passed tax line. - This uses ``round`` method directly as if has been tested that Odoo's - ``float_round`` still returns incorrect amounts for certain values. Try - 3 units x 3,77 €/unit with 10% tax and you will be hit by the error - (on regular x86 architectures). - :param self: Single invoice record. :param tax_line: Tax line that is being analyzed. :param sign: Sign of the operation (only refund by differences is @@ -405,13 +416,13 @@ def _get_sii_tax_dict(self, tax_line, sign): tax_type = abs(tax.amount) tax_dict = { 'TipoImpositivo': str(tax_type), - 'BaseImponible': sign * abs(round(tax_line.base_company, 2)), + 'BaseImponible': sign * abs(tax_line.base_company), } if self.type in ['out_invoice', 'out_refund']: key = 'CuotaRepercutida' else: key = 'CuotaSoportada' - tax_dict[key] = sign * abs(round(tax_line.amount_company, 2)) + tax_dict[key] = sign * abs(tax_line.amount_company) # Recargo de equivalencia re_tax_line = self._get_sii_tax_line_req(tax) if re_tax_line: @@ -419,7 +430,7 @@ def _get_sii_tax_dict(self, tax_line, sign): abs(re_tax_line.tax_id.amount) ) tax_dict['CuotaRecargoEquivalencia'] = ( - sign * abs(round(re_tax_line.amount_company, 2)) + sign * abs(re_tax_line.amount_company) ) return tax_dict @@ -490,7 +501,7 @@ def _get_sii_out_taxes(self): if exempt_cause: det_dict['CausaExencion'] = exempt_cause det_dict['BaseImponible'] += ( - round(tax_line.base_company, 2) * sign) + tax_line.base_company * sign) else: sub_dict.setdefault('NoExenta', { 'TipoNoExenta': ( @@ -516,7 +527,7 @@ def _get_sii_out_taxes(self): 'NoSujeta', {default_no_taxable_cause: 0}, ) nsub_dict[default_no_taxable_cause] += ( - round(tax_line.base_company, 2) * sign) + tax_line.base_company * sign) if tax in (taxes_sfess + taxes_sfesse + taxes_sfesns): type_breakdown = taxes_dict.setdefault( 'DesgloseTipoOperacion', { @@ -536,7 +547,7 @@ def _get_sii_out_taxes(self): if exempt_cause: det_dict['CausaExencion'] = exempt_cause det_dict['BaseImponible'] += ( - round(tax_line.base_company, 2) * sign) + tax_line.base_company * sign) if tax in taxes_sfess: # TODO l10n_es_ no tiene impuesto ISP de servicios # if tax in taxes_sfesisps: @@ -568,6 +579,13 @@ def _get_sii_out_taxes(self): taxes_dict['DesgloseTipoOperacion']['Entrega'] = \ taxes_dict['DesgloseFactura'] del taxes_dict['DesgloseFactura'] + round_by_keys(taxes_dict, [ + 'BaseImponible', 'CuotaRepercutida', 'CuotaSoportada', + 'TipoRecargoEquivalencia', 'CuotaRecargoEquivalencia', + 'ImportePorArticulos7_14_Otros', 'ImporteTAIReglasLocalizacion', + 'ImporteTotal', 'BaseRectificada', 'CuotaRectificada', + 'CuotaDeducible' + ]) return taxes_dict @api.multi @@ -609,7 +627,7 @@ def _get_sii_in_taxes(self): 'DesgloseIVA', {'DetalleIVA': []}, ) sfrns_dict['DetalleIVA'].append({ - 'BaseImponible': sign * round(tax_line.base_company, 2), + 'BaseImponible': sign * tax_line.base_company, }) elif tax in taxes_sfrsa: sfrsa_dict = taxes_dict.setdefault( @@ -728,7 +746,7 @@ def _get_sii_invoice_dict_out(self, cancel=False): "DescripcionOperacion": self.sii_description, "TipoDesglose": self._get_sii_out_taxes(), "ImporteTotal": abs( - round(self.amount_total_company_signed, 2) + self.amount_total_company_signed ) * sign, } if self.sii_macrodata: @@ -762,14 +780,12 @@ def _get_sii_invoice_dict_out(self, cancel=False): if self.sii_refund_type == 'S': origin = self.refund_invoice_id exp_dict['ImporteRectificacion'] = { - 'BaseRectificada': round( - abs(origin.amount_untaxed_signed), 2, + 'BaseRectificada': abs( + origin.amount_untaxed_signed ), - 'CuotaRectificada': round( - abs( - origin.amount_total_company_signed - - origin.amount_untaxed_signed - ), 2, + 'CuotaRectificada': abs( + origin.amount_total_company_signed - + origin.amount_untaxed_signed ), } return inv_dict @@ -831,7 +847,7 @@ def _get_sii_invoice_dict_in(self, cancel=False): }, "FechaRegContable": reg_date, "ImporteTotal": abs(self.amount_total_company_signed) * sign, - "CuotaDeducible": round(tax_amount * sign, 2), + "CuotaDeducible": tax_amount * sign, } if self.sii_macrodata: inv_dict["FacturaRecibida"].update(Macrodato="S") diff --git a/l10n_es_aeat_sii/readme/CONTRIBUTORS.rst b/l10n_es_aeat_sii/readme/CONTRIBUTORS.rst index b96bb26d82a..a078e26d65c 100644 --- a/l10n_es_aeat_sii/readme/CONTRIBUTORS.rst +++ b/l10n_es_aeat_sii/readme/CONTRIBUTORS.rst @@ -10,3 +10,4 @@ * Javi Melendez * Santi Argüeso - Comunitea S.L. * Angel Moya - PESOL +* Eric Antonés - NuoBiT Solutions, S.L.