diff --git a/account_reconcile_sale_order/README.rst b/account_reconcile_sale_order/README.rst new file mode 100644 index 0000000000..bb767dffa6 --- /dev/null +++ b/account_reconcile_sale_order/README.rst @@ -0,0 +1,119 @@ +===================== +Reconcile sale orders +===================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:56395133d4d660c38418fe1fcfd4a84ba5403d7023e50c6531a16f25d947928a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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%2Faccount--reconcile-lightgray.png?logo=github + :target: https://github.com/OCA/account-reconcile/tree/15.0/account_reconcile_sale_order + :alt: OCA/account-reconcile +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-reconcile-15-0/account-reconcile-15-0-account_reconcile_sale_order + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/account-reconcile&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows a workflow where you don't invoice sale orders until +you've received a payment. + +That's useful ie for webshops with non-instant payment like wire +transfer, where you might have a lot of customers not doing the payment +after all, which results in extra work for cancellation of the +invoices/orders involved. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +1. Go to Invoicing/Configuration/Reconciliation Models +2. Create a model of type *Rule to match sale orders* + +Usage +===== + +To use this module, you need to: + +1. Have a payment on a bank statement matching the amount of an + *invoicable* sale order +2. Enter the reconciliation screen +3. Observe that the sale order is offered as reconciliation counterpart + +Note the reconciliation only works if fully invoicing the sale order +yields an invoice over the order's total amount. Usually this means that +all products in the sale order must have invoicing policy *Ordered +quantities*. + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Hunki Enterprises BV + +Contributors +------------ + +- Holger Brunn + (https://hunki-enterprises.com) + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-hbrunn| image:: https://github.com/hbrunn.png?size=40px + :target: https://github.com/hbrunn + :alt: hbrunn + +Current `maintainer `__: + +|maintainer-hbrunn| + +This module is part of the `OCA/account-reconcile `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_reconcile_sale_order/__init__.py b/account_reconcile_sale_order/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/account_reconcile_sale_order/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_reconcile_sale_order/__manifest__.py b/account_reconcile_sale_order/__manifest__.py new file mode 100644 index 0000000000..16261e2b35 --- /dev/null +++ b/account_reconcile_sale_order/__manifest__.py @@ -0,0 +1,29 @@ +# Copyright 2024 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) + +{ + "name": "Reconcile sale orders", + "summary": "Invoice and reconcile sale orders", + "version": "15.0.1.0.0", + "development_status": "Alpha", + "category": "Accounting", + "website": "https://github.com/OCA/account-reconcile", + "author": "Hunki Enterprises BV, Odoo Community Association (OCA)", + "maintainers": ["hbrunn"], + "license": "AGPL-3", + "depends": [ + "sale", + "account_reconciliation_widget", + ], + "data": [ + "views/account_reconcile_model.xml", + ], + "demo": [ + "demo/account_reconcile_model.xml", + ], + "assets": { + "web.assets_backend": [ + "account_reconcile_sale_order/static/src/js/*.js", + ], + }, +} diff --git a/account_reconcile_sale_order/demo/account_reconcile_model.xml b/account_reconcile_sale_order/demo/account_reconcile_model.xml new file mode 100644 index 0000000000..1694ca4fa3 --- /dev/null +++ b/account_reconcile_sale_order/demo/account_reconcile_model.xml @@ -0,0 +1,6 @@ + + + Match sale orders + sale_order_matching + + diff --git a/account_reconcile_sale_order/models/__init__.py b/account_reconcile_sale_order/models/__init__.py new file mode 100644 index 0000000000..21b26a683b --- /dev/null +++ b/account_reconcile_sale_order/models/__init__.py @@ -0,0 +1,3 @@ +from . import account_bank_statement_line +from . import account_reconciliation_widget +from . import account_reconcile_model diff --git a/account_reconcile_sale_order/models/account_bank_statement_line.py b/account_reconcile_sale_order/models/account_bank_statement_line.py new file mode 100644 index 0000000000..5dbe941578 --- /dev/null +++ b/account_reconcile_sale_order/models/account_bank_statement_line.py @@ -0,0 +1,64 @@ +# Copyright 2024 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) + +from odoo import models + + +class AccountBankStatementLine(models.Model): + _inherit = "account.bank.statement.line" + + def process_reconciliation( + self, counterpart_aml_dicts=None, payment_aml_rec=None, new_aml_dicts=None + ): + """ + Invoice selected sale orders and use resulting move lines + """ + new_aml_dicts2 = [] + counterpart_aml_dicts = (counterpart_aml_dicts or [])[:] + for new_aml_dict in new_aml_dicts or []: + sale_order_id = new_aml_dict.get("sale_order_id") + if sale_order_id: + order = self.env["sale.order"].browse(sale_order_id) + self._process_reconciliation_sale_order_invoice(order) + counterpart_aml_dicts += ( + self._process_reconciliation_sale_order_counterparts(order) + ) + else: + new_aml_dicts2.append(new_aml_dict) + + return super().process_reconciliation( + counterpart_aml_dicts=counterpart_aml_dicts, + payment_aml_rec=payment_aml_rec, + new_aml_dicts=new_aml_dicts2, + ) + + def _process_reconciliation_sale_order_invoice(self, order): + """ + Invoice selected sale orders and post the invoices + """ + clean_context = { + key: value + for key, value in self.env.context.items() + if key != "force_price_include" + } + order = order.with_context(clean_context) # pylint: disable=context-overridden + if order.state in ("draft", "sent"): + order.action_confirm() + invoices = order._create_invoices() + invoices.action_post() + + def _process_reconciliation_sale_order_counterparts(self, order): + """ + Return counterpart aml dicts for sale order + """ + return [ + { + "name": line.name, + "move_line": line, + "debit": line.credit, + "credit": line.debit, + "analytic_tag_ids": [(6, 0, line.analytic_tag_ids.ids)], + } + for line in order.mapped("invoice_ids.line_ids") + if line.account_id.user_type_id.type == "receivable" + ] diff --git a/account_reconcile_sale_order/models/account_reconcile_model.py b/account_reconcile_sale_order/models/account_reconcile_model.py new file mode 100644 index 0000000000..ae650bcb5e --- /dev/null +++ b/account_reconcile_sale_order/models/account_reconcile_model.py @@ -0,0 +1,124 @@ +# Copyright 2024 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) + + +from odoo import fields, models + + +class AccountReconcileModel(models.AbstractModel): + _inherit = "account.reconcile.model" + + rule_type = fields.Selection( + selection_add=[("sale_order_matching", "Rule to match sale orders")], + ondelete={"sale_order_matching": "cascade"}, + ) + sale_order_matching_token_match = fields.Boolean( + string="Match tokens", + help="When this is activated, the statement line's label is split into words " + "and if one of those words match a sale order, it is considered a match. So " + "if the statement line's label is 'hello world', sale orders with names 'hello', " + "'world', 'some name containing hello', 'some name containing world' will be " + "considered matches, in that order", + ) + sale_order_matching_token_length = fields.Integer( + string="Minimum token length", + default=3, + help="Set the minimum word length to search for. If you set this to 4, and the " + "statement line's label is 'hello you', it will only search for 'hello', not " + "for 'you'", + ) + + def _get_candidates(self, st_lines_with_partner, excluded_ids): + if self.rule_type == "sale_order_matching": + return self._get_candidates_sale_order(st_lines_with_partner, excluded_ids) + else: + return super()._get_candidates(st_lines_with_partner, excluded_ids) + + def _get_rule_result( + self, st_line, candidates, aml_ids_to_exclude, reconciled_amls_ids, partner_map + ): + if self.rule_type == "sale_order_matching": + return self._get_rule_result_sale_order( + st_line, + candidates, + aml_ids_to_exclude, + reconciled_amls_ids, + partner_map, + ) + else: + return super()._get_rule_result( + st_line, + candidates, + aml_ids_to_exclude, + reconciled_amls_ids, + partner_map, + ) + + def _get_candidates_sale_order(self, st_lines_with_partner, excluded_ids): + """Return candidates for matching sale orders""" + return { + line.id: self._get_candidates_sale_order_best_match( + line, partner, excluded_ids + ) + for line, partner in st_lines_with_partner + } + + def _get_candidates_sale_order_best_match( + self, bank_statement_line, partner, excluded_ids + ): + """Return one sale order that is considered the best match for some line and partner""" + + def domain(extra_domain): + widget = self.env["account.reconciliation.widget"] + return widget._get_sale_orders_for_bank_statement_line_domain( + bank_statement_line.id, + partner.id, + amount=bank_statement_line.amount, + excluded_ids=excluded_ids, + extra_domain=extra_domain, + ) + + def search(domain): + return self.env["sale.order"].search(domain, limit=1) + + def first(field, operator, tokens): + return sum( + (search([(field, operator, token)]) for token in tokens), + self.env["sale.order"], + )[:1] + + ref = bank_statement_line.payment_ref + tokens = list( + filter( + lambda x: len(x) >= self.sale_order_matching_token_length, ref.split() + ) + ) + + return ( + search(domain([("name", "=ilike", ref)])) + or search(domain([("partner_id", "=ilike", ref)])) + or ( + (first("name", "=ilike", tokens) or first("name", "ilike", tokens)) + if self.sale_order_matching_token_match + else self.env["sale.order"] + ) + ) + + def _get_rule_result_sale_order( + self, st_line, candidates, aml_ids_to_exclude, reconciled_amls_ids, partner_map + ): + return ( + { + "model": self, + "status": "sale_order_matching", + "aml_ids": candidates, + "write_off_vals": [ + self.env[ + "account.reconciliation.widget" + ]._reconciliation_proposition_from_sale_order(order) + for order in candidates + ], + }, + set(), + set(), + ) diff --git a/account_reconcile_sale_order/models/account_reconciliation_widget.py b/account_reconcile_sale_order/models/account_reconciliation_widget.py new file mode 100644 index 0000000000..741fc8d52a --- /dev/null +++ b/account_reconcile_sale_order/models/account_reconciliation_widget.py @@ -0,0 +1,155 @@ +# Copyright 2024 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) + +from odoo import api, models +from odoo.tools.misc import format_date, formatLang + + +class AccountReconciliationWidget(models.AbstractModel): + _inherit = "account.reconciliation.widget" + + @api.model + def _prepare_move_lines( + self, move_lines, target_currency=False, target_date=False, recs_count=0 + ): + if ( + move_lines.ids + and isinstance(move_lines.ids[0], models.Model) + and move_lines.ids[0]._name == "sale.order" + ): + return [ + self._reconciliation_proposition_from_sale_order(order) + for order in move_lines.ids + ] + return super()._prepare_move_lines( + move_lines, + target_currency=target_currency, + target_date=target_date, + recs_count=recs_count, + ) + + @api.model + def get_move_lines_for_bank_statement_line( + self, + st_line_id, + partner_id=None, + excluded_ids=None, + search_str=False, + offset=0, + limit=None, + mode=None, + ): + """ + Prepend matching sale orders to move line propositions + """ + result = super().get_move_lines_for_bank_statement_line( + st_line_id, + partner_id=partner_id, + excluded_ids=excluded_ids, + search_str=search_str, + offset=offset, + limit=limit, + mode=mode, + ) + sale_orders = [] + if mode == "rp": + sale_orders = self._get_sale_orders_for_bank_statement_line( + st_line_id, + partner_id=partner_id, + excluded_ids=excluded_ids, + search_str=search_str, + offset=offset, + limit=limit, + ) + return sale_orders + result + + @api.model + def _get_sale_orders_for_bank_statement_line( + self, + st_line_id, + partner_id=None, + excluded_ids=None, + search_str=False, + offset=0, + limit=None, + ): + result = [] + for order in self.env["sale.order"].search( + self._get_sale_orders_for_bank_statement_line_domain( + st_line_id, + partner_id=partner_id, + excluded_ids=excluded_ids, + extra_domain=[ + "|", + ("name", "ilike", search_str), + ("partner_id", "ilike", search_str), + ] + if search_str + else None, + ), + limit=limit, + ): + result.append(self._reconciliation_proposition_from_sale_order(order)) + return result + + @api.model + def _get_sale_orders_for_bank_statement_line_domain( + self, + st_line_id, + partner_id=None, + excluded_ids=None, + amount=None, + extra_domain=None, + ): + return ( + [ + ("state", "not in", ("done", "cancel")), + ("partner_id", "=?", partner_id), + ("amount_total", "=?", amount), + ("invoice_status", "not in", ("upselling", "invoiced")), + ] + + ([("id", "not in", excluded_ids)] if excluded_ids else []) + + (extra_domain or []) + ) + + @api.model + def _reconciliation_proposition_from_sale_order(self, order): + journal = ( + self.env["account.move"] + .with_context(default_move_type="out_invoice") + ._get_default_journal() + ) + account = order.partner_id.property_account_receivable_id + return { + "id": "so_%d" % order.id, + "name": order.name, + "ref": order.reference or order.client_order_ref or order.origin or "", + "already_paid": False, + "account_type": "receivable", + "date_maturity": format_date(self.env, order.date_order), + "date": format_date(self.env, order.date_order), + "partner_id": order.partner_id.id, + "partner_name": order.partner_id.name, + "currency_id": order.currency_id.id, + "debit": order.amount_total, + "credit": 0, + "amount_str": formatLang( + self.env, + order.amount_total, + currency_obj=order.currency_id, + ), + "total_amount_str": formatLang( + self.env, + order.amount_total, + currency_obj=order.currency_id, + ), + "sale_order_id": order.id, + "recs_count": 1, + "amount_currency": "", + "amount_currency_str": "", + "total_amount_currency_str": "", + "account_id": [account.id, account.name], + "account_code": account.code, + "account_name": account.name, + "journal_id": [journal.id, journal.name], + } diff --git a/account_reconcile_sale_order/readme/CONFIGURE.md b/account_reconcile_sale_order/readme/CONFIGURE.md new file mode 100644 index 0000000000..5c3bf6f54a --- /dev/null +++ b/account_reconcile_sale_order/readme/CONFIGURE.md @@ -0,0 +1,4 @@ +To configure this module, you need to: + +1. Go to Invoicing/Configuration/Reconciliation Models +2. Create a model of type *Rule to match sale orders* diff --git a/account_reconcile_sale_order/readme/CONTRIBUTORS.md b/account_reconcile_sale_order/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..b28199e1f4 --- /dev/null +++ b/account_reconcile_sale_order/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Holger Brunn \ (https://hunki-enterprises.com) diff --git a/account_reconcile_sale_order/readme/DESCRIPTION.md b/account_reconcile_sale_order/readme/DESCRIPTION.md new file mode 100644 index 0000000000..f2748c8fe5 --- /dev/null +++ b/account_reconcile_sale_order/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module allows a workflow where you don't invoice sale orders until you've received a payment. + +That's useful ie for webshops with non-instant payment like wire transfer, where you might have a lot of customers not doing the payment after all, which results in extra work for cancellation of the invoices/orders involved. diff --git a/account_reconcile_sale_order/readme/USAGE.md b/account_reconcile_sale_order/readme/USAGE.md new file mode 100644 index 0000000000..78975322ea --- /dev/null +++ b/account_reconcile_sale_order/readme/USAGE.md @@ -0,0 +1,7 @@ +To use this module, you need to: + +1. Have a payment on a bank statement matching the amount of an *invoicable* sale order +2. Enter the reconciliation screen +3. Observe that the sale order is offered as reconciliation counterpart + +Note the reconciliation only works if fully invoicing the sale order yields an invoice over the order's total amount. Usually this means that all products in the sale order must have invoicing policy *Ordered quantities*. diff --git a/account_reconcile_sale_order/static/description/icon.png b/account_reconcile_sale_order/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/account_reconcile_sale_order/static/description/icon.png differ diff --git a/account_reconcile_sale_order/static/description/index.html b/account_reconcile_sale_order/static/description/index.html new file mode 100644 index 0000000000..e9e5029354 --- /dev/null +++ b/account_reconcile_sale_order/static/description/index.html @@ -0,0 +1,459 @@ + + + + + + +Reconcile sale orders + + + +
+

Reconcile sale orders

+ + +

Alpha License: AGPL-3 OCA/account-reconcile Translate me on Weblate Try me on Runboat

+

This module allows a workflow where you don’t invoice sale orders until +you’ve received a payment.

+

That’s useful ie for webshops with non-instant payment like wire +transfer, where you might have a lot of customers not doing the payment +after all, which results in extra work for cancellation of the +invoices/orders involved.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Go to Invoicing/Configuration/Reconciliation Models
  2. +
  3. Create a model of type Rule to match sale orders
  4. +
+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Have a payment on a bank statement matching the amount of an +invoicable sale order
  2. +
  3. Enter the reconciliation screen
  4. +
  5. Observe that the sale order is offered as reconciliation counterpart
  6. +
+

Note the reconciliation only works if fully invoicing the sale order +yields an invoice over the order’s total amount. Usually this means that +all products in the sale order must have invoicing policy Ordered +quantities.

+
+
+

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 +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Hunki Enterprises BV
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

hbrunn

+

This module is part of the OCA/account-reconcile project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/account_reconcile_sale_order/static/src/js/reconciliation_model.js b/account_reconcile_sale_order/static/src/js/reconciliation_model.js new file mode 100644 index 0000000000..924b563fa2 --- /dev/null +++ b/account_reconcile_sale_order/static/src/js/reconciliation_model.js @@ -0,0 +1,18 @@ +odoo.define("account_reconcile_sale_order.ReconciliationModel", function (require) { + "use strict"; + var StatementModel = require("account.ReconciliationModel").StatementModel; + + StatementModel.include({ + _formatToProcessReconciliation: function (line, prop) { + if (prop.sale_order_id) { + return this._formatToProcessReconciliationSaleOrder(line, prop); + } + return this._super.apply(this, arguments); + }, + _formatToProcessReconciliationSaleOrder: function (line, prop) { + return { + sale_order_id: prop.sale_order_id, + }; + }, + }); +}); diff --git a/account_reconcile_sale_order/tests/__init__.py b/account_reconcile_sale_order/tests/__init__.py new file mode 100644 index 0000000000..18312e8980 --- /dev/null +++ b/account_reconcile_sale_order/tests/__init__.py @@ -0,0 +1 @@ +from . import test_account_reconcile_sale_order diff --git a/account_reconcile_sale_order/tests/test_account_reconcile_sale_order.py b/account_reconcile_sale_order/tests/test_account_reconcile_sale_order.py new file mode 100644 index 0000000000..ccd65bdc5d --- /dev/null +++ b/account_reconcile_sale_order/tests/test_account_reconcile_sale_order.py @@ -0,0 +1,94 @@ +# Copyright 2024 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0) + +from odoo.tests import tagged +from odoo.tools.misc import mute_logger + +from odoo.addons.account.tests.common import TestAccountReconciliationCommon + + +@tagged("post_install", "-at_install") +class TestAccountReconcileSaleOrder(TestAccountReconciliationCommon): + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref=chart_template_ref) + partner = cls.env.ref("base.res_partner_12") # Azure Interior + cls.model = cls.env.ref( + "account_reconcile_sale_order.reconcile_model_sale_order" + ) + cls.sale_order = cls.env["sale.order"].create( + { + "partner_id": partner.id, + "order_line": [ + ( + 0, + 0, + { + "name": "Order line", + "price_unit": 4242, + "product_id": cls.env.ref("product.consu_delivery_01").id, + }, + ) + ], + } + ) + cls.bank_statement = cls.env["account.bank.statement"].create( + { + "line_ids": [ + ( + 0, + 0, + { + "name": "bank payment", + "amount": 4242, + "payment_ref": "/", + "partner_id": partner.id, + }, + ) + ], + "journal_id": cls.bank_journal_euro.id, + } + ) + + def test_reconcile_sale_order(self): + """Test that we find a sale order via reconciliation rules""" + self.bank_statement.line_ids.payment_ref = self.sale_order.name + self.assertEqual(self.sale_order.invoice_status, "no") + rule_result = self.model.sudo()._apply_rules(self.bank_statement.line_ids) + line_result = rule_result[self.bank_statement.line_ids.id] + self.assertTrue(line_result, "No order found") + self.assertEqual(line_result["status"], "sale_order_matching") + self.bank_statement.line_ids.process_reconciliation( + new_aml_dicts=line_result["write_off_vals"], + ) + self.assertEqual(self.sale_order.invoice_status, "invoiced") + + def test_token_matching(self): + """Test that we find orders by substrings of statement label""" + self.model.sudo().sale_order_matching_token_match = True + self.bank_statement.line_ids.payment_ref = ( + "payment for %s" % self.sale_order.name + ) + rule_result = self.model.sudo()._apply_rules(self.bank_statement.line_ids) + line_result = rule_result[self.bank_statement.line_ids.id] + self.assertTrue(line_result, "No order found") + self.assertEqual(line_result["status"], "sale_order_matching") + + @mute_logger( + "odoo.addons.account_reconciliation_widget.models.reconciliation_widget" + ) + # the base module logs a warning if search_str is not a number + def test_manual_match(self): + """Test that we find orders when users fill in a name of a partner""" + widget = self.env["account.reconciliation.widget"] + propositions = widget.get_move_lines_for_bank_statement_line( + self.bank_statement.line_ids.id, + search_str=self.sale_order.partner_id.email, + mode="rp", + ) + self.assertTrue(propositions, "Sale order not found") + self.assertEqual( + propositions[0].get("sale_order_id"), + self.sale_order.id, + "Sale order not found", + ) diff --git a/account_reconcile_sale_order/views/account_reconcile_model.xml b/account_reconcile_sale_order/views/account_reconcile_model.xml new file mode 100644 index 0000000000..7d6ad8c390 --- /dev/null +++ b/account_reconcile_sale_order/views/account_reconcile_model.xml @@ -0,0 +1,25 @@ + + + account.reconcile.model + + + + {'invisible': [('rule_type', '=', 'sale_order_matching')]} + + + + + + + + + + diff --git a/setup/account_reconcile_sale_order/odoo/addons/account_reconcile_sale_order b/setup/account_reconcile_sale_order/odoo/addons/account_reconcile_sale_order new file mode 120000 index 0000000000..7ab579399c --- /dev/null +++ b/setup/account_reconcile_sale_order/odoo/addons/account_reconcile_sale_order @@ -0,0 +1 @@ +../../../../account_reconcile_sale_order \ No newline at end of file diff --git a/setup/account_reconcile_sale_order/setup.py b/setup/account_reconcile_sale_order/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/account_reconcile_sale_order/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)