diff --git a/l10n_it_fatturapa_in/__init__.py b/l10n_it_fatturapa_in/__init__.py index 9b4296142f47..3a1fd6e5d147 100644 --- a/l10n_it_fatturapa_in/__init__.py +++ b/l10n_it_fatturapa_in/__init__.py @@ -1,2 +1,3 @@ +from . import fields from . import models from . import wizard diff --git a/l10n_it_fatturapa_in/fields.py b/l10n_it_fatturapa_in/fields.py new file mode 100644 index 000000000000..e9ffa8f66855 --- /dev/null +++ b/l10n_it_fatturapa_in/fields.py @@ -0,0 +1,47 @@ +# Copyright 2024 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.fields import Float + +orig_convert_to_cache = Float.convert_to_cache +orig_get_digits = Float.get_digits + +E_INVOICE_PRECISION_TO_FIELD = { + "Discount": "discount_decimal_digits", + "Product Price": "price_decimal_digits", + "Product Unit of Measure": "quantity_decimal_digits", +} +# Map a `decimal.precision` to the name of the field in `fatturapa.attachment` +# that stores the value used during import + + +def get_digits(self, env): + digits = orig_get_digits(self, env) + if e_invoice_precision := env.context.get("l10n_it_fatturapa_in_precision"): + digits = digits[0], e_invoice_precision + return digits + + +def convert_to_cache(self, value, record, validate=True): + if record._name in ("account.move", "account.move.line"): + e_invoice = record.fatturapa_attachment_in_id + if e_invoice: + # The invoice [line] has been created by importing an e-invoice. + # If a different precision has been used, + # keep using that precision to read values that have it. + field_precision = self._digits + if isinstance(field_precision, str): + e_invoice_precision_field = E_INVOICE_PRECISION_TO_FIELD.get( + field_precision + ) + if e_invoice_precision_field: + if e_invoice_precision := e_invoice[e_invoice_precision_field]: + record = record.with_context( + l10n_it_fatturapa_in_precision=e_invoice_precision + ) + + return orig_convert_to_cache(self, value, record, validate=validate) + + +Float.convert_to_cache = convert_to_cache +Float.get_digits = get_digits diff --git a/l10n_it_fatturapa_in/models/attachment.py b/l10n_it_fatturapa_in/models/attachment.py index 734f7745f977..b16fca5505e1 100644 --- a/l10n_it_fatturapa_in/models/attachment.py +++ b/l10n_it_fatturapa_in/models/attachment.py @@ -77,6 +77,24 @@ class FatturaPAAttachmentIn(models.Model): compute="_compute_linked_invoice_id_xml", store=True, ) + price_decimal_digits = fields.Integer( + string="Prices decimal digits", + help="Value used during import of this e-invoice " + 'to override "Product Price" precision.', + readonly=True, + ) + quantity_decimal_digits = fields.Integer( + string="Quantities decimal digits", + help="Value used during import of this e-invoice " + 'to override "Product Unit of Measure" precision.', + readonly=True, + ) + discount_decimal_digits = fields.Integer( + string="Discounts decimal digits", + help="Value used during import of this e-invoice " + 'to override "Discount" precision.', + readonly=True, + ) _sql_constraints = [ ( diff --git a/l10n_it_fatturapa_in/tests/data/IT01234567890_FPR16.xml b/l10n_it_fatturapa_in/tests/data/IT01234567890_FPR16.xml new file mode 100644 index 000000000000..55ca5dbe6b49 --- /dev/null +++ b/l10n_it_fatturapa_in/tests/data/IT01234567890_FPR16.xml @@ -0,0 +1,87 @@ + + + + + + IT + 02780790107 + + FPR14 + FPR12 + 0000000 + + 06543534343 + info@yourcompany.example.com + + test@pec.it + + + + + IT + 02780790107 + + + YourCompany + + RF01 + + + Via Milano, 1 + 00100 + Roma + AK + IT + + + 06543534343 + info@yourcompany.example.com + + + + + + IT + 07973780013 + + 07973780013 + + B2B Customer + + + + Via Roma, 1 + 16100 + Genova + AK + IT + + + + + + + TD01 + EUR + 2020-09-30 + 14481 + 81.49 + + + + + 1 + Test precisione decimale + 69.00 + 0.968 + 66.792 + 22.00 + + + 22.00 + 66.79 + 14.69 + + + + diff --git a/l10n_it_fatturapa_in/tests/fatturapa_common.py b/l10n_it_fatturapa_in/tests/fatturapa_common.py index 197081db5e10..e7a52f45e235 100644 --- a/l10n_it_fatturapa_in/tests/fatturapa_common.py +++ b/l10n_it_fatturapa_in/tests/fatturapa_common.py @@ -288,6 +288,8 @@ def run_wizard( ): if module_name is None: module_name = "l10n_it_fatturapa_in" + if wiz_values is None: + wiz_values = dict() attach = self.create_attachment(name, file_name, module_name=module_name) attach.e_invoice_received_date = fields.Datetime.now() attach_id = attach.id @@ -297,6 +299,8 @@ def run_wizard( active_ids=[attach_id], active_model="fatturapa.attachment.in" ) ) + for wiz_field, wiz_value in wiz_values.items(): + setattr(wizard_form, wiz_field, wiz_value) wizard = wizard_form.save() return wizard.importFatturaPA() if mode == "link": diff --git a/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py b/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py index be8f6ddc2ba7..9f2d36ccde2c 100644 --- a/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py +++ b/l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py @@ -1150,6 +1150,65 @@ def test_ignore_global_discount(self): self.assertEqual(invoice.amount_tax, 5.12) self.assertEqual(invoice.amount_total, 28.39) + def test_increased_decimal_precision(self): + """ + Increase price decimal precision during import: + computation of line's price is more accurate. + """ + res = self.run_wizard( + "increased_decimal_precision", + "IT01234567890_FPR16.xml", + wiz_values={ + "price_decimal_digits": 3, + }, + ) + + # The new precision allows to compute the correct amount + invoice = self.invoice_model.search(res["domain"]) + expected_invoice_values = { + "amount_untaxed": 66.79, + "amount_tax": 14.69, + "amount_total": 81.48, + } + self.assertRecordValues( + invoice, + [ + expected_invoice_values, + ], + ) + invoice_line = invoice.invoice_line_ids + expected_invoice_line_values = { + "price_subtotal": 66.79, + "price_total": 81.48, + } + self.assertRecordValues( + invoice_line, + [ + expected_invoice_line_values, + ], + ) + + # Trigger amounts recomputation because: + # date triggers an update on date_due + # date_due triggers an update on needed_terms + # needed_terms needs amount_total_signed + with Form(invoice) as invoice_form: + invoice_form.date = fields.Date.today() + + # The correct amount is kept + self.assertRecordValues( + invoice, + [ + expected_invoice_values, + ], + ) + self.assertRecordValues( + invoice_line, + [ + expected_invoice_line_values, + ], + ) + class TestFatturaPAEnasarco(FatturapaCommon): def setUp(self): diff --git a/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py b/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py index 2578faae1923..c765d94878ac 100644 --- a/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py +++ b/l10n_it_fatturapa_in/wizard/wizard_import_fatturapa.py @@ -6,7 +6,7 @@ import re from datetime import datetime -from odoo import api, fields, models, registry +from odoo import api, fields, models from odoo.exceptions import UserError from odoo.fields import first from odoo.osv import expression @@ -1786,34 +1786,25 @@ def create_and_get_line_id(self, invoice_line_ids, invoice_line_model, upd_vals) ) invoice_line_ids.append(invoice_line_id) - def _set_decimal_precision(self, precision_name, field_name): + def _set_decimal_precision(self, precision_name, field_name, attachments): precision = self.env["decimal.precision"].search( [("name", "=", precision_name)], limit=1 ) different_precisions = original_precision = None if precision: - precision_id = precision.id original_precision = precision.digits different_precisions = self[field_name] != original_precision if different_precisions: - with registry(self.env.cr.dbname).cursor() as new_cr: - # We need a new env (and cursor) because 'digits' property of Float - # fields is retrieved with a new LazyCursor, - # see class Float at odoo.fields, - # so we need to write (commit) to DB in order to make the new - # precision available - new_env = api.Environment(new_cr, self.env.uid, self.env.context) - new_precision = new_env["decimal.precision"].browse(precision_id) - new_precision.sudo().write({"digits": self[field_name]}) - new_cr.commit() + precision.sudo().digits = self[field_name] + attachments.update( + { + field_name: self[field_name], + } + ) return precision, different_precisions, original_precision def _restore_original_precision(self, precision, original_precision): - with registry(self.env.cr.dbname).cursor() as new_cr: - new_env = api.Environment(new_cr, self.env.uid, self.env.context) - new_price_precision = new_env["decimal.precision"].browse(precision.id) - new_price_precision.sudo().write({"digits": original_precision}) - new_cr.commit() + precision.sudo().digits = original_precision def _get_invoice_partner_id(self, fatt): cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore @@ -1822,28 +1813,34 @@ def _get_invoice_partner_id(self, fatt): def importFatturaPA(self): self.ensure_one() + fatturapa_attachments = self._get_selected_records() ( price_precision, different_price_precisions, original_price_precision, - ) = self._set_decimal_precision("Product Price", "price_decimal_digits") + ) = self._set_decimal_precision( + "Product Price", "price_decimal_digits", attachments=fatturapa_attachments + ) ( qty_precision, different_qty_precisions, original_qty_precision, ) = self._set_decimal_precision( - "Product Unit of Measure", "quantity_decimal_digits" + "Product Unit of Measure", + "quantity_decimal_digits", + attachments=fatturapa_attachments, ) ( discount_precision, different_discount_precisions, original_discount_precision, - ) = self._set_decimal_precision("Discount", "discount_decimal_digits") + ) = self._set_decimal_precision( + "Discount", "discount_decimal_digits", attachments=fatturapa_attachments + ) new_invoices = [] # convert to dict in order to be able to modify context - fatturapa_attachments = self._get_selected_records() self.env.context = dict(self.env.context) for fatturapa_attachment in fatturapa_attachments: self.reset_inconsistencies() diff --git a/l10n_it_fatturapa_pec/models/mail_thread.py b/l10n_it_fatturapa_pec/models/mail_thread.py index da7be41312f5..6b321cdc3e43 100644 --- a/l10n_it_fatturapa_pec/models/mail_thread.py +++ b/l10n_it_fatturapa_pec/models/mail_thread.py @@ -45,6 +45,7 @@ def clean_message_dict(self, message_dict): del message_dict["bounced_partner"] del message_dict["bounced_msg_id"] del message_dict["bounced_message"] + del message_dict["x_odoo_message_id"] @api.model def message_route(