From 7c6afe54ff6f3aa6a942663b163cad8bf187131b Mon Sep 17 00:00:00 2001 From: duongtq Date: Thu, 23 May 2024 17:11:17 +0700 Subject: [PATCH 1/4] [IMP] sale_order_import: better line error handling - Show errored lines in the chatter and ignore them on demand when importing. --- sale_order_import/README.rst | 6 +- sale_order_import/__manifest__.py | 6 +- .../static/description/index.html | 28 ++++----- .../templates/error_lines_chatter_msg.xml | 37 +++++++++++ sale_order_import/tests/test_order_import.py | 63 +++++++++++++++++++ sale_order_import/wizard/sale_order_import.py | 49 ++++++++++++--- .../wizard/sale_order_import_view.xml | 1 + 7 files changed, 158 insertions(+), 32 deletions(-) create mode 100644 sale_order_import/templates/error_lines_chatter_msg.xml diff --git a/sale_order_import/README.rst b/sale_order_import/README.rst index 0c6a45558a..dad9987beb 100644 --- a/sale_order_import/README.rst +++ b/sale_order_import/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ================= Sale Order Import ================= @@ -17,7 +13,7 @@ Sale Order Import .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github diff --git a/sale_order_import/__manifest__.py b/sale_order_import/__manifest__.py index b0db3566b2..54c0c5e51a 100644 --- a/sale_order_import/__manifest__.py +++ b/sale_order_import/__manifest__.py @@ -20,6 +20,10 @@ # and support for PDF import should be moved to a glue module "pdf_xml_attachment", ], - "data": ["security/ir.model.access.csv", "wizard/sale_order_import_view.xml"], + "data": [ + "security/ir.model.access.csv", + "wizard/sale_order_import_view.xml", + "templates/error_lines_chatter_msg.xml", + ], "installable": True, } diff --git a/sale_order_import/static/description/index.html b/sale_order_import/static/description/index.html index ff77b18907..e3a8a28e7c 100644 --- a/sale_order_import/static/description/index.html +++ b/sale_order_import/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Sale Order Import -
+
+

Sale Order Import

- - -Odoo Community Association - -
-

Sale Order Import

-

Beta License: AGPL-3 OCA/edi Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/edi Translate me on Weblate Try me on Runboat

This module adds support for the import of electronic RFQ or orders. This module provides the base methods to import electronic orders, and you can also plug additional formats by extending the wizard. It @@ -402,7 +397,7 @@

Sale Order Import

-

Usage

+

Usage

This module adds a wizard in the sale menu named Import RFQ or Order. This wizard will propose you to select the order or RFQ file. Depending on the format of the file (XML or PDF) and the type of file (RFQ or @@ -418,7 +413,7 @@

Usage

about the import.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -426,16 +421,16 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Akretion
  • Camptocamp
-

Contributors

+

Contributors

-

Other credits

+

Other credits

The migration of this module from 13.0 to 16.0 was financially supported by Camptocamp

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -463,6 +458,5 @@

Maintainers

-
diff --git a/sale_order_import/templates/error_lines_chatter_msg.xml b/sale_order_import/templates/error_lines_chatter_msg.xml new file mode 100644 index 0000000000..f453a094c3 --- /dev/null +++ b/sale_order_import/templates/error_lines_chatter_msg.xml @@ -0,0 +1,37 @@ + + + + + + diff --git a/sale_order_import/tests/test_order_import.py b/sale_order_import/tests/test_order_import.py index e23752680f..344a1da96c 100644 --- a/sale_order_import/tests/test_order_import.py +++ b/sale_order_import/tests/test_order_import.py @@ -195,3 +195,66 @@ def test_confirm_order(self): so = self.env["sale.order"].browse(action["res_id"]) # Check the state of the order self.assertEqual(so.state, "sale") + + def test_order_import_log_errored_line(self): + # Prepare test data + parsed_order = dict( + self.parsed_order, + partner={"email": "agrolait@yourcompany.example.com"}, + lines=[ + { + "product": {"code": "errored"}, # No product exists with this code + "qty": 3, + "uom": {"unece_code": "C62"}, + "price_unit": 12.42, + }, + { + "product": {"code": "FURN_9999"}, + "qty": 1, + "uom": {"unece_code": "C62"}, + "price_unit": 1.42, + }, + ], + ) + order_file_data = base64.b64encode( + b"baz" + ) + order_filename = "test_order.xml" + mock_parse_order = mock.patch.object(type(self.wiz_model), "parse_xml_order") + # Create a new form + with Form( + self.wiz_model.with_context( + default_order_filename=order_filename, + default_confirm_order=True, + default_skip_error_lines=True, + ) + ) as form: + with mock_parse_order as mocked: + # Return 'rfq' for doc_type + mocked.return_value = "rfq" + # Set values for the required fields + form.import_type = "xml" + form.order_file = order_file_data + # Test the button with the simulated values + mocked.return_value = parsed_order + action = form.save().import_order_button() + so = self.env["sale.order"].browse(action["res_id"]) + # Check the order + self.assertEqual(so.state, "sale") + self.assertEqual(so.client_order_ref, parsed_order["order_ref"]) + self.assertEqual(len(so.order_line), 1) + self.assertEqual( + so.order_line.product_id.default_code, + parsed_order["lines"][1]["product"]["code"], + ) + # Check the error lines message + messages = [ + "Errors lines on Import: 1 line(s)", + "Odoo couldn't find any product corresponding to the following" + " information extracted from the business document", + ] + self.assertTrue( + so.message_ids.filtered( + lambda m: messages[0] in m.body and messages[1] in m.body + ) + ) diff --git a/sale_order_import/wizard/sale_order_import.py b/sale_order_import/wizard/sale_order_import.py index 96eda0e699..2eca9ac60a 100644 --- a/sale_order_import/wizard/sale_order_import.py +++ b/sale_order_import/wizard/sale_order_import.py @@ -59,6 +59,9 @@ class SaleOrderImport(models.TransientModel): sale_id = fields.Many2one("sale.order", string="Quotation to Update") # Confirm order after creating Sale Order confirm_order = fields.Boolean(default=False) + skip_error_lines = fields.Boolean( + help="Ignore and push all error lines to the chatter when importing if enabled." + ) @api.onchange("order_file") def order_file_change(self): @@ -272,16 +275,29 @@ def _prepare_order(self, parsed_order, price_source): so_vals["partner_invoice_id"] = invoicing_partner.id if parsed_order.get("date"): so_vals["date_order"] = parsed_order["date"] + error_lines = [] for line in parsed_order["lines"]: - # partner=False because we don't want to use product.supplierinfo - product = bdio._match_product( - line["product"], parsed_order["chatter_msg"], seller=False - ) - uom = bdio._match_uom(line.get("uom"), parsed_order["chatter_msg"], product) - line_vals = self._prepare_create_order_line( - product, uom, so_vals, line, price_source - ) - so_vals["order_line"].append((0, 0, line_vals)) + try: + # partner=False because we don't want to use product.supplierinfo + product = bdio._match_product( + line["product"], parsed_order["chatter_msg"], seller=False + ) + uom = bdio._match_uom( + line.get("uom"), parsed_order["chatter_msg"], product + ) + line_vals = self._prepare_create_order_line( + product, uom, so_vals, line, price_source + ) + so_vals["order_line"].append((0, 0, line_vals)) + except UserError as exc: + if not self.skip_error_lines: + raise exc + error_line = dict(values=line, error_msg=exc.args[0]) + error_lines.append(error_line) + + # Push to the chatter all errored lines if any + if error_lines: + parsed_order["error_lines"] = error_lines defaults = self.env.context.get("sale_order_import__default_vals", {}).get( "order", {} @@ -422,6 +438,7 @@ def create_order_button(self): def create_order_return_action(self, parsed_order, order_filename): self.ensure_one() order = self.create_order(parsed_order, self.price_source, order_filename) + self._post_error_lines_message(parsed_order, order) order.message_post( body=self.env._("Created automatically via file import (%s).") % self.order_filename @@ -681,3 +698,17 @@ def update_order_button(self): } ) return action + + def _post_error_lines_message(self, parsed_order, order): + order.ensure_one() + if not self.skip_error_lines or not parsed_order.get("error_lines", False): + return + + error_lines = parsed_order.get("error_lines") + order.message_post_with_source( + "sale_order_import.skip_error_lines_message", + render_values={ + "lines": error_lines, + }, + subtype_id=self.env.ref("mail.mt_note").id, + ) diff --git a/sale_order_import/wizard/sale_order_import_view.xml b/sale_order_import/wizard/sale_order_import_view.xml index 582f6cd10b..d09e74f16d 100644 --- a/sale_order_import/wizard/sale_order_import_view.xml +++ b/sale_order_import/wizard/sale_order_import_view.xml @@ -59,6 +59,7 @@ invisible="doc_type != 'order'" required="doc_type == 'order'" /> +