From 47108f791cf8a453509783b690f7afb3da1f4d04 Mon Sep 17 00:00:00 2001
From: Florian da Costa
Date: Thu, 1 Apr 2021 15:04:39 +0200
Subject: [PATCH] Manage write off, some refactore to manage better currency,
partial reconciliation, and add some tests Also add an advanced
reconciliation method based on name. Could be very useful to actually match
account move line based on payment_reference field on invoices, as it is
passed to account move line on name field
---
account_mass_reconcile/README.rst | 10 +-
.../i18n/account_mass_reconcile.pot | 73 ++--
.../models/advanced_reconciliation.py | 104 ++++++
.../models/base_advanced_reconciliation.py | 93 +++--
.../models/base_reconciliation.py | 126 ++++---
.../models/mass_reconcile.py | 40 ++-
.../models/simple_reconciliation.py | 15 +-
.../security/ir.model.access.csv | 2 +
.../static/description/index.html | 6 +-
account_mass_reconcile/tests/__init__.py | 1 -
.../tests/test_onchange_company.py | 11 -
.../tests/test_reconcile.py | 37 +-
.../tests/test_reconcile_history.py | 37 --
.../tests/test_scenario_reconcile.py | 318 +++++++++---------
.../views/mass_reconcile.xml | 8 -
15 files changed, 507 insertions(+), 374 deletions(-)
delete mode 100644 account_mass_reconcile/tests/test_reconcile_history.py
diff --git a/account_mass_reconcile/README.rst b/account_mass_reconcile/README.rst
index 5fe88688..db77d557 100644
--- a/account_mass_reconcile/README.rst
+++ b/account_mass_reconcile/README.rst
@@ -14,13 +14,13 @@ Account Mass Reconcile
: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/13.0/account_mass_reconcile
+ :target: https://github.com/OCA/account-reconcile/tree/14.0/account_mass_reconcile
: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-13-0/account-reconcile-13-0-account_mass_reconcile
+ :target: https://translation.odoo-community.org/projects/account-reconcile-14-0/account-reconcile-14-0-account_mass_reconcile
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/98/13.0
+ :target: https://runbot.odoo-community.org/runbot/98/14.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -62,7 +62,7 @@ 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 smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -111,6 +111,6 @@ 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.
-This module is part of the `OCA/account-reconcile `_ project on GitHub.
+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_mass_reconcile/i18n/account_mass_reconcile.pot b/account_mass_reconcile/i18n/account_mass_reconcile.pot
index a80c243d..00c43e2e 100644
--- a/account_mass_reconcile/i18n/account_mass_reconcile.pot
+++ b/account_mass_reconcile/i18n/account_mass_reconcile.pot
@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 13.0\n"
+"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -13,6 +13,14 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
+#. module: account_mass_reconcile
+#: code:addons/account_mass_reconcile/models/mass_reconcile.py:0
+#, python-format
+msgid ""
+"A mass reconcile is already ongoing for this account, please try again "
+"later."
+msgstr ""
+
#. module: account_mass_reconcile
#: code:addons/account_mass_reconcile/models/base_advanced_reconciliation.py:0
#, python-format
@@ -33,6 +41,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__account
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__account_id
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__account_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__account_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__account_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple__account_id
@@ -45,6 +54,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__account_lost_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__account_lost_id
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__account_lost_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__account_lost_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__account_lost_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options__account_lost_id
@@ -63,6 +73,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__account_profit_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__account_profit_id
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__account_profit_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__account_profit_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__account_profit_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options__account_profit_id
@@ -113,6 +124,12 @@ msgstr ""
msgid "Automatic Write Off"
msgstr ""
+#. module: account_mass_reconcile
+#: code:addons/account_mass_reconcile/models/base_reconciliation.py:0
+#, python-format
+msgid "Automatic writeoff"
+msgstr ""
+
#. module: account_mass_reconcile
#: model_terms:ir.actions.act_window,help:account_mass_reconcile.action_account_mass_reconcile
msgid "Click to add a reconciliation profile."
@@ -148,6 +165,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__create_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__create_uid
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__create_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__create_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_history__create_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__create_uid
@@ -159,6 +177,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__create_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__create_date
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__create_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__create_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_history__create_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__create_date
@@ -170,6 +189,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__date_base_on
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__date_base_on
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__date_base_on
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__date_base_on
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__date_base_on
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options__date_base_on
@@ -184,6 +204,7 @@ msgstr ""
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__display_name
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__display_name
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__display_name
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__display_name
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__display_name
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__display_name
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_history__display_name
@@ -192,6 +213,8 @@ msgstr ""
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__display_name
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_partner__display_name
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_reference__display_name
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_company__display_name
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_config_settings__display_name
msgid "Display Name"
msgstr ""
@@ -211,6 +234,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method___filter
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced___filter
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name___filter
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref___filter
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base___filter
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options___filter
@@ -241,19 +265,6 @@ msgstr ""
msgid "Full Reconciliations"
msgstr ""
-#. module: account_mass_reconcile
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_partner__income_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_reference__income_exchange_account_id
-msgid "Gain Exchange Rate Account"
-msgstr ""
-
#. module: account_mass_reconcile
#: model_terms:ir.ui.view,arch_db:account_mass_reconcile.account_mass_reconcile_form
#: model_terms:ir.ui.view,arch_db:account_mass_reconcile.mass_reconcile_history_form
@@ -277,11 +288,6 @@ msgstr ""
msgid "History"
msgstr ""
-#. module: account_mass_reconcile
-#: model:ir.actions.act_window,name:account_mass_reconcile.act_mass_reconcile_to_history
-msgid "History Details"
-msgstr ""
-
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_company__reconciliation_commit_every
#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_config_settings__reconciliation_commit_every
@@ -299,6 +305,7 @@ msgstr ""
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__id
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_history__id
@@ -307,6 +314,8 @@ msgstr ""
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_partner__id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_reference__id
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_company__id
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_config_settings__id
msgid "ID"
msgstr ""
@@ -335,6 +344,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__journal_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__journal_id
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__journal_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__journal_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__journal_id
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options__journal_id
@@ -349,6 +359,7 @@ msgstr ""
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile____last_update
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method____last_update
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced____last_update
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name____last_update
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref____last_update
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base____last_update
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_history____last_update
@@ -357,12 +368,15 @@ msgstr ""
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name____last_update
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_partner____last_update
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_reference____last_update
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_company____last_update
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_res_config_settings____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__write_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__write_uid
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__write_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__write_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_history__write_uid
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__write_uid
@@ -374,6 +388,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__write_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__write_date
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__write_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__write_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_history__write_date
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__write_date
@@ -393,19 +408,6 @@ msgstr ""
msgid "Leave zero to commit only at the end of the process."
msgstr ""
-#. module: account_mass_reconcile
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_name__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_partner__expense_exchange_account_id
-#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple_reference__expense_exchange_account_id
-msgid "Loss Exchange Rate Account"
-msgstr ""
-
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile__message_main_attachment_id
msgid "Main Attachment"
@@ -427,6 +429,11 @@ msgstr ""
msgid "Mass Reconcile Advanced"
msgstr ""
+#. module: account_mass_reconcile
+#: model:ir.model,name:account_mass_reconcile.model_mass_reconcile_advanced_name
+msgid "Mass Reconcile Advanced Name"
+msgstr ""
+
#. module: account_mass_reconcile
#: model:ir.model,name:account_mass_reconcile.model_mass_reconcile_advanced_ref
msgid "Mass Reconcile Advanced Ref"
@@ -578,6 +585,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__partner_ids
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__partner_ids
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__partner_ids
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__partner_ids
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_simple__partner_ids
@@ -698,6 +706,7 @@ msgstr ""
#. module: account_mass_reconcile
#: model:ir.model.fields,field_description:account_mass_reconcile.field_account_mass_reconcile_method__write_off
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced__write_off
+#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_name__write_off
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_advanced_ref__write_off
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_base__write_off
#: model:ir.model.fields,field_description:account_mass_reconcile.field_mass_reconcile_options__write_off
diff --git a/account_mass_reconcile/models/advanced_reconciliation.py b/account_mass_reconcile/models/advanced_reconciliation.py
index af0a4459..2e81337d 100644
--- a/account_mass_reconcile/models/advanced_reconciliation.py
+++ b/account_mass_reconcile/models/advanced_reconciliation.py
@@ -110,3 +110,107 @@ class MassReconcileAdvancedRef(models.TransientModel):
move_line["name"].lower().strip(),
),
)
+
+
+class MassReconcileAdvancedName(models.TransientModel):
+
+ _name = "mass.reconcile.advanced.name"
+ _inherit = "mass.reconcile.advanced"
+ _description = "Mass Reconcile Advanced Name"
+
+ @staticmethod
+ def _skip_line(move_line):
+ """
+ When True is returned on some conditions, the credit move line
+ will be skipped for reconciliation. Can be inherited to
+ skip on some conditions. ie: ref or partner_id is empty.
+ """
+ return not (move_line.get("name", "/") != "/" and move_line.get("partner_id"))
+
+ @staticmethod
+ def _matchers(move_line):
+ """
+ Return the values used as matchers to find the opposite lines
+
+ All the matcher keys in the dict must have their equivalent in
+ the `_opposite_matchers`.
+
+ The values of each matcher key will be searched in the
+ one returned by the `_opposite_matchers`
+
+ Must be inherited to implement the matchers for one method
+
+ For instance, it can return:
+ return ('ref', move_line['rec'])
+
+ or
+ return (('partner_id', move_line['partner_id']),
+ ('ref', "prefix_%s" % move_line['rec']))
+
+ All the matchers have to be found in the opposite lines
+ to consider them as "opposite"
+
+ The matchers will be evaluated in the same order as declared
+ vs the the opposite matchers, so you can gain performance by
+ declaring first the partners with the less computation.
+
+ All matchers should match with their opposite to be considered
+ as "matching".
+ So with the previous example, partner_id and ref have to be
+ equals on the opposite line matchers.
+
+ :return: tuple of tuples (key, value) where the keys are
+ the matchers keys
+ (must be the same than `_opposite_matchers` returns,
+ and their values to match in the opposite lines.
+ A matching key can have multiples values.
+ """
+ return (
+ ("partner_id", move_line["partner_id"]),
+ ("name", move_line["name"].lower().strip()),
+ )
+
+ @staticmethod
+ def _opposite_matchers(move_line):
+ """
+ Return the values of the opposite line used as matchers
+ so the line is matched
+
+ Must be inherited to implement the matchers for one method
+ It can be inherited to apply some formatting of fields
+ (strip(), lower() and so on)
+
+ This method is the counterpart of the `_matchers()` method.
+
+ Each matcher has to yield its value respecting the order
+ of the `_matchers()`.
+
+ When a matcher does not correspond, the next matchers won't
+ be evaluated so the ones which need the less computation
+ have to be executed first.
+
+ If the `_matchers()` returns:
+ (('partner_id', move_line['partner_id']),
+ ('ref', move_line['ref']))
+
+ Here, you should yield :
+ yield ('partner_id', move_line['partner_id'])
+ yield ('ref', move_line['ref'])
+
+ Note that a matcher can contain multiple values, as instance,
+ if for a move line, you want to search from its `ref` in the
+ `ref` or `name` fields of the opposite move lines, you have to
+ yield ('partner_id', move_line['partner_id'])
+ yield ('ref', (move_line['ref'], move_line['name'])
+
+ An OR is used between the values for the same key.
+ An AND is used between the differents keys.
+
+ :param dict move_line: values of the move_line
+ :yield: matchers as tuple ('matcher key', value(s))
+ """
+ yield ("partner_id", move_line["partner_id"])
+ yield (
+ "name",
+ (move_line["name"].lower().strip(),),
+ )
diff --git a/account_mass_reconcile/models/base_advanced_reconciliation.py b/account_mass_reconcile/models/base_advanced_reconciliation.py
index 2d8064e7..57e80321 100644
--- a/account_mass_reconcile/models/base_advanced_reconciliation.py
+++ b/account_mass_reconcile/models/base_advanced_reconciliation.py
@@ -205,6 +205,7 @@ class MassReconcileAdvanced(models.AbstractModel):
]
def _action_rec(self):
+ self.flush()
credit_lines = self._query_credit()
debit_lines = self._query_debit()
result = self._rec_auto_lines_advanced(credit_lines, debit_lines)
@@ -222,57 +223,51 @@ class MassReconcileAdvanced(models.AbstractModel):
""" Advanced reconciliation main loop """
# pylint: disable=invalid-commit
reconciled_ids = []
- for rec in self:
- reconcile_groups = []
- ctx = self.env.context.copy()
- ctx["commit_every"] = rec.account_id.company_id.reconciliation_commit_every
- _logger.info("%d credit lines to reconcile", len(credit_lines))
- for idx, credit_line in enumerate(credit_lines, start=1):
- if idx % 50 == 0:
- _logger.info(
- "... %d/%d credit lines inspected ...", idx, len(credit_lines)
+ reconcile_groups = []
+ _logger.info("%d credit lines to reconcile", len(credit_lines))
+ for idx, credit_line in enumerate(credit_lines, start=1):
+ if idx % 50 == 0:
+ _logger.info(
+ "... %d/%d credit lines inspected ...", idx, len(credit_lines)
+ )
+ if self._skip_line(credit_line):
+ continue
+ opposite_lines = self._search_opposites(credit_line, debit_lines)
+ if not opposite_lines:
+ continue
+ opposite_ids = [line["id"] for line in opposite_lines]
+ line_ids = opposite_ids + [credit_line["id"]]
+ for group in reconcile_groups:
+ if any([lid in group for lid in opposite_ids]):
+ _logger.debug(
+ "New lines %s matched with an existing " "group %s",
+ line_ids,
+ group,
)
- if self._skip_line(credit_line):
- continue
- opposite_lines = self._search_opposites(credit_line, debit_lines)
- if not opposite_lines:
- continue
- opposite_ids = [l["id"] for l in opposite_lines]
- line_ids = opposite_ids + [credit_line["id"]]
- for group in reconcile_groups:
- if any([lid in group for lid in opposite_ids]):
- _logger.debug(
- "New lines %s matched with an existing " "group %s",
- line_ids,
- group,
- )
- group.update(line_ids)
- break
- else:
- _logger.debug("New group of lines matched %s", line_ids)
- reconcile_groups.append(set(line_ids))
- lines_by_id = {l["id"]: l for l in credit_lines + debit_lines}
+ group.update(line_ids)
+ break
+ else:
+ _logger.debug("New group of lines matched %s", line_ids)
+ reconcile_groups.append(set(line_ids))
+ lines_by_id = {line["id"]: line for line in credit_lines + debit_lines}
_logger.info("Found %d groups to reconcile", len(reconcile_groups))
- for group_count, reconcile_group_ids in enumerate(
- reconcile_groups, start=1
- ):
- _logger.debug(
- "Reconciling group %d/%d with ids %s",
- group_count,
- len(reconcile_groups),
- reconcile_group_ids,
- )
- group_lines = [lines_by_id[lid] for lid in reconcile_group_ids]
- reconciled, full = self._reconcile_lines(
- group_lines, allow_partial=True
- )
- if reconciled and full:
- reconciled_ids += reconcile_group_ids
+ for group_count, reconcile_group_ids in enumerate(reconcile_groups, start=1):
+ _logger.debug(
+ "Reconciling group %d/%d with ids %s",
+ group_count,
+ len(reconcile_groups),
+ reconcile_group_ids,
+ )
+ group_lines = [lines_by_id[lid] for lid in reconcile_group_ids]
+ reconciled, full = self._reconcile_lines(group_lines, allow_partial=True)
+ if reconciled and full:
+ reconciled_ids += reconcile_group_ids
- if ctx["commit_every"] and group_count % ctx["commit_every"] == 0:
- self.env.cr.commit()
- _logger.info(
- "Commit the reconciliations after %d groups", group_count
- )
+ if (
+ self.env.context.get("commit_every", 0)
+ and group_count % self.env.context["commit_every"] == 0
+ ):
+ self.env.cr.commit()
+ _logger.info("Commit the reconciliations after %d groups", group_count)
_logger.info("Reconciliation is over")
return reconciled_ids
diff --git a/account_mass_reconcile/models/base_reconciliation.py b/account_mass_reconcile/models/base_reconciliation.py
index 9c90017f..079d4fa7 100644
--- a/account_mass_reconcile/models/base_reconciliation.py
+++ b/account_mass_reconcile/models/base_reconciliation.py
@@ -2,7 +2,6 @@
# Copyright 2010 Sébastien Beau
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from functools import reduce
from operator import itemgetter
from odoo import _, fields, models
@@ -46,6 +45,9 @@ class MassReconcileBase(models.AbstractModel):
"id",
"debit",
"credit",
+ "currency_id",
+ "amount_residual",
+ "amount_residual_currency",
"date",
"ref",
"name",
@@ -69,7 +71,8 @@ class MassReconcileBase(models.AbstractModel):
self.ensure_one()
where = (
"WHERE account_move_line.account_id = %s "
- "AND NOT account_move_line.reconciled"
+ "AND NOT account_move_line.reconciled "
+ "AND parent_state = 'posted'"
)
# it would be great to use dict for params
# but as we use _where_calc in _get_filter
@@ -78,7 +81,7 @@ class MassReconcileBase(models.AbstractModel):
params = [self.account_id.id]
if self.partner_ids:
where += " AND account_move_line.partner_id IN %s"
- params.append(tuple([l.id for l in self.partner_ids]))
+ params.append(tuple([line.id for line in self.partner_ids]))
return where, params
def _get_filter(self):
@@ -95,16 +98,29 @@ class MassReconcileBase(models.AbstractModel):
def _below_writeoff_limit(self, lines, writeoff_limit):
self.ensure_one()
precision = self.env["decimal.precision"].precision_get("Account")
- keys = ("debit", "credit")
- sums = reduce(
- lambda line, memo: {
- key: value + memo[key] for key, value in line.items() if key in keys
- },
- lines,
+
+ writeoff_amount = round(
+ sum([line["amount_residual"] for line in lines]), precision
+ )
+ writeoff_amount_curr = round(
+ sum([line["amount_residual_currency"] for line in lines]), precision
+ )
+
+ first_currency = lines[0]["currency_id"]
+ if all([line["currency_id"] == first_currency for line in lines]):
+ ref_amount = writeoff_amount_curr
+ same_curr = True
+ # TODO if currency != company currency compute writeoff_limit in currency
+ else:
+ ref_amount = writeoff_amount
+ same_curr = False
+
+ return (
+ bool(writeoff_limit >= abs(ref_amount)),
+ writeoff_amount,
+ writeoff_amount_curr,
+ same_curr,
)
- debit, credit = sums["debit"], sums["credit"]
- writeoff_amount = round(debit - credit, precision)
- return bool(writeoff_limit >= abs(writeoff_amount)), debit, credit
def _get_rec_date(self, lines, based_on="end_period_last_credit"):
self.ensure_one()
@@ -113,10 +129,10 @@ class MassReconcileBase(models.AbstractModel):
return max(mlines, key=itemgetter("date"))
def credit(mlines):
- return [l for l in mlines if l["credit"] > 0]
+ return [line for line in mlines if line["credit"] > 0]
def debit(mlines):
- return [l for l in mlines if l["debit"] > 0]
+ return [line for line in mlines if line["debit"] > 0]
if based_on == "newest":
return last_date(lines)["date"]
@@ -128,11 +144,51 @@ class MassReconcileBase(models.AbstractModel):
# when date is None
return None
+ def create_write_off(self, lines, amount, amount_curr, same_curr):
+ self.ensure_one()
+ if amount < 0:
+ account = self.account_profit_id
+ else:
+ account = self.account_lost_id
+ currency = same_curr and lines[0].currency_id or lines[0].company_id.currency_id
+ journal = self.journal_id
+ partners = lines.mapped("partner_id")
+ write_off_vals = {
+ "name": _("Automatic writeoff"),
+ "amount_currency": same_curr and amount_curr or amount,
+ "debit": amount > 0.0 and amount or 0.0,
+ "credit": amount < 0.0 and -amount or 0.0,
+ "partner_id": len(partners) == 1 and partners.id or False,
+ "account_id": account.id,
+ "journal_id": journal.id,
+ "currency_id": currency.id,
+ }
+ counterpart_account = lines.mapped("account_id")
+ counter_part = write_off_vals.copy()
+ counter_part["debit"] = write_off_vals["credit"]
+ counter_part["credit"] = write_off_vals["debit"]
+ counter_part["amount_currency"] = -write_off_vals["amount_currency"]
+ counter_part["account_id"] = (counterpart_account.id,)
+
+ move = self.env["account.move"].create(
+ {
+ "date": lines.env.context.get("date_p"),
+ "journal_id": journal.id,
+ "currency_id": currency.id,
+ "line_ids": [(0, 0, write_off_vals), (0, 0, counter_part)],
+ }
+ )
+ move.action_post()
+ return move.line_ids.filtered(
+ lambda l: l.account_id.id == counterpart_account.id
+ )
+
def _reconcile_lines(self, lines, allow_partial=False):
"""Try to reconcile given lines
:param list lines: list of dict of move lines, they must at least
- contain values for : id, debit, credit
+ contain values for : id, debit, credit, amount_residual and
+ amount_residual_currency
:param boolean allow_partial: if True, partial reconciliation will be
created, otherwise only Full
reconciliation will be created
@@ -143,36 +199,26 @@ class MassReconcileBase(models.AbstractModel):
"""
self.ensure_one()
ml_obj = self.env["account.move.line"]
- below_writeoff, sum_debit, sum_credit = self._below_writeoff_limit(
- lines, self.write_off
- )
+ (
+ below_writeoff,
+ amount_writeoff,
+ amount_writeoff_curr,
+ same_curr,
+ ) = self._below_writeoff_limit(lines, self.write_off)
rec_date = self._get_rec_date(lines, self.date_base_on)
- line_rs = ml_obj.browse([l["id"] for l in lines]).with_context(
+ line_rs = ml_obj.browse([line["id"] for line in lines]).with_context(
date_p=rec_date, comment=_("Automatic Write Off")
)
if below_writeoff:
- if sum_credit > sum_debit:
- writeoff_account = self.account_profit_id
- else:
- writeoff_account = self.account_lost_id
- line_rs.reconcile(
- writeoff_acc_id=writeoff_account, writeoff_journal_id=self.journal_id
- )
+ balance = amount_writeoff_curr if same_curr else amount_writeoff
+ if abs(balance) != 0.0:
+ writeoff_line = self.create_write_off(
+ line_rs, amount_writeoff, amount_writeoff_curr, same_curr
+ )
+ line_rs |= writeoff_line
+ line_rs.reconcile()
return True, True
elif allow_partial:
- # We need to give a writeoff_acc_id
- # in case we have a multi currency lines
- # to reconcile.
- # If amount in currency is equal between
- # lines to reconcile
- # it will do a full reconcile instead of a partial reconcile
- # and make a write-off for exchange
- if sum_credit > sum_debit:
- writeoff_account = self.income_exchange_account_id
- else:
- writeoff_account = self.expense_exchange_account_id
- line_rs.reconcile(
- writeoff_acc_id=writeoff_account, writeoff_journal_id=self.journal_id
- )
+ line_rs.reconcile()
return True, False
return False, False
diff --git a/account_mass_reconcile/models/mass_reconcile.py b/account_mass_reconcile/models/mass_reconcile.py
index a5ec4606..519bcb5e 100644
--- a/account_mass_reconcile/models/mass_reconcile.py
+++ b/account_mass_reconcile/models/mass_reconcile.py
@@ -5,9 +5,10 @@
import logging
from datetime import datetime
+import psycopg2
from psycopg2.extensions import AsIs
-from odoo import _, api, fields, models, sql_db
+from odoo import _, api, exceptions, fields, models, sql_db
from odoo.exceptions import Warning as UserError
_logger = logging.getLogger(__name__)
@@ -40,12 +41,6 @@ class MassReconcileOptions(models.AbstractModel):
default="newest",
)
_filter = fields.Char(string="Filter")
- income_exchange_account_id = fields.Many2one(
- "account.account", string="Gain Exchange Rate Account"
- )
- expense_exchange_account_id = fields.Many2one(
- "account.account", string="Loss Exchange Rate Account"
- )
class AccountMassReconcileMethod(models.Model):
@@ -61,6 +56,7 @@ class AccountMassReconcileMethod(models.Model):
("mass.reconcile.simple.partner", "Simple. Amount and Partner"),
("mass.reconcile.simple.reference", "Simple. Amount and Reference"),
("mass.reconcile.advanced.ref", "Advanced. Partner and Ref."),
+ ("mass.reconcile.advanced.name", "Advanced. Partner and Name."),
]
def _selection_name(self):
@@ -136,13 +132,16 @@ class AccountMassReconcile(models.Model):
"write_off": rec_method.write_off,
"account_lost_id": (rec_method.account_lost_id.id),
"account_profit_id": (rec_method.account_profit_id.id),
- "income_exchange_account_id": (rec_method.income_exchange_account_id.id),
- "expense_exchange_account_id": (rec_method.income_exchange_account_id.id),
"journal_id": (rec_method.journal_id.id),
"date_base_on": rec_method.date_base_on,
"_filter": rec_method._filter,
}
+ def _run_reconcile_method(self, reconcile_method):
+ rec_model = self.env[reconcile_method.name]
+ auto_rec_id = rec_model.create(self._prepare_run_transient(reconcile_method))
+ return auto_rec_id.automatic_reconcile()
+
def run_reconcile(self):
def find_reconcile_ids(fieldname, move_line_ids):
if not move_line_ids:
@@ -163,21 +162,36 @@ class AccountMassReconcile(models.Model):
# does not.
for rec in self:
+ # SELECT FOR UPDATE the mass reconcile row ; this is done in order
+ # to avoid 2 processes on the same mass reconcile method.
+ try:
+ self.env.cr.execute(
+ "SELECT id FROM account_mass_reconcile"
+ " WHERE id = %s"
+ " FOR UPDATE NOWAIT",
+ (rec.id,),
+ )
+ except psycopg2.OperationalError:
+ raise exceptions.UserError(
+ _(
+ "A mass reconcile is already ongoing for this account, "
+ "please try again later."
+ )
+ )
ctx = self.env.context.copy()
ctx["commit_every"] = rec.account.company_id.reconciliation_commit_every
if ctx["commit_every"]:
new_cr = sql_db.db_connect(self.env.cr.dbname).cursor()
+ new_env = api.Environment(new_cr, self.env.uid, ctx)
else:
new_cr = self.env.cr
+ new_env = self.env
try:
all_ml_rec_ids = []
for method in rec.reconcile_method:
- rec_model = self.env[method.name]
- auto_rec_id = rec_model.create(self._prepare_run_transient(method))
-
- ml_rec_ids = auto_rec_id.automatic_reconcile()
+ ml_rec_ids = self.with_env(new_env)._run_reconcile_method(method)
all_ml_rec_ids += ml_rec_ids
diff --git a/account_mass_reconcile/models/simple_reconciliation.py b/account_mass_reconcile/models/simple_reconciliation.py
index f55c4801..7ce6ff1d 100644
--- a/account_mass_reconcile/models/simple_reconciliation.py
+++ b/account_mass_reconcile/models/simple_reconciliation.py
@@ -2,8 +2,12 @@
# Copyright 2010 Sébastien Beau
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+import logging
+
from odoo import models
+_logger = logging.getLogger(__name__)
+
class MassReconcileSimple(models.AbstractModel):
_name = "mass.reconcile.simple"
@@ -40,6 +44,15 @@ class MassReconcileSimple(models.AbstractModel):
if reconciled:
res += [credit_line["id"], debit_line["id"]]
del lines[i]
+ if (
+ self.env.context.get("commit_every", 0)
+ and len(res) % self.env.context["commit_every"] == 0
+ ):
+ # new cursor is already open in cron
+ self.env.cr.commit() # pylint: disable=invalid-commit
+ _logger.info(
+ "Commit the reconciliations after %d groups", len(res)
+ )
break
count += 1
return res
@@ -58,7 +71,7 @@ class MassReconcileSimple(models.AbstractModel):
query = " ".join(
(select, self._from_query(), where, where2, self._simple_order())
)
-
+ self.flush()
self.env.cr.execute(query, params + params2)
lines = self.env.cr.dictfetchall()
return self.rec_auto_lines_simple(lines)
diff --git a/account_mass_reconcile/security/ir.model.access.csv b/account_mass_reconcile/security/ir.model.access.csv
index dc2e3435..786158c8 100644
--- a/account_mass_reconcile/security/ir.model.access.csv
+++ b/account_mass_reconcile/security/ir.model.access.csv
@@ -10,3 +10,5 @@ access_mass_reconcile_history_acc_mgr,mass.reconcile.history,model_mass_reconcil
access_mass_reconcile_simple_name,mass.reconcile.simple.name,model_mass_reconcile_simple_name,account.group_account_user,1,1,1,1
access_mass_reconcile_simple_partner,mass.reconcile.simple.partner,model_mass_reconcile_simple_partner,account.group_account_user,1,1,1,1
access_mass_reconcile_simple_reference,mass.reconcile.simple.reference,model_mass_reconcile_simple_reference,account.group_account_user,1,1,1,1
+access_mass_reconcile_advanced_ref_acc_user,mass.reconcile.advanced.ref,model_mass_reconcile_advanced_ref,account.group_account_user,1,1,1,1
+access_mass_reconcile_advanced_name_acc_user,mass.reconcile.advanced.name,model_mass_reconcile_advanced_name,account.group_account_user,1,1,1,1
diff --git a/account_mass_reconcile/static/description/index.html b/account_mass_reconcile/static/description/index.html
index 2f8792f4..9ae6a471 100644
--- a/account_mass_reconcile/static/description/index.html
+++ b/account_mass_reconcile/static/description/index.html
@@ -367,7 +367,7 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

This is a shared work between Akretion and Camptocamp
in order to provide:
@@ -410,7 +410,7 @@ reconcile.
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 smashing it by providing a detailed and welcomed
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
@@ -453,7 +453,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
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.
-
This module is part of the OCA/account-reconcile project on GitHub.
+
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_mass_reconcile/tests/__init__.py b/account_mass_reconcile/tests/__init__.py
index fe1df9fe..89f15015 100644
--- a/account_mass_reconcile/tests/__init__.py
+++ b/account_mass_reconcile/tests/__init__.py
@@ -2,6 +2,5 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_onchange_company
-from . import test_reconcile_history
from . import test_reconcile
from . import test_scenario_reconcile
diff --git a/account_mass_reconcile/tests/test_onchange_company.py b/account_mass_reconcile/tests/test_onchange_company.py
index 9db6eef4..1730bb86 100644
--- a/account_mass_reconcile/tests/test_onchange_company.py
+++ b/account_mass_reconcile/tests/test_onchange_company.py
@@ -1,8 +1,6 @@
# © 2014-2016 Camptocamp SA (Damien Crier)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import tools
-from odoo.modules import get_module_resource
from odoo.tests import common
@@ -10,15 +8,6 @@ class TestOnChange(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestOnChange, cls).setUpClass()
- tools.convert_file(
- cls.cr,
- "account",
- get_module_resource("account", "test", "account_minimal_test.xml"),
- {},
- "init",
- False,
- "test",
- )
acc_setting = cls.env["res.config.settings"]
cls.acc_setting_obj = acc_setting.create({})
cls.company_obj = cls.env["res.company"]
diff --git a/account_mass_reconcile/tests/test_reconcile.py b/account_mass_reconcile/tests/test_reconcile.py
index 06901e8d..3c176e12 100644
--- a/account_mass_reconcile/tests/test_reconcile.py
+++ b/account_mass_reconcile/tests/test_reconcile.py
@@ -1,29 +1,24 @@
# © 2014-2016 Camptocamp SA (Damien Crier)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import exceptions, fields, tools
-from odoo.modules import get_module_resource
-from odoo.tests import common
+import odoo.tests
+from odoo import exceptions, fields
+
+from odoo.addons.account.tests.common import TestAccountReconciliationCommon
-class TestReconcile(common.SavepointCase):
+@odoo.tests.tagged("post_install", "-at_install")
+class TestReconcile(TestAccountReconciliationCommon):
@classmethod
def setUpClass(cls):
super(TestReconcile, cls).setUpClass()
- tools.convert_file(
- cls.cr,
- "account",
- get_module_resource("account", "test", "account_minimal_test.xml"),
- {},
- "init",
- False,
- "test",
- )
cls.rec_history_obj = cls.env["mass.reconcile.history"]
cls.mass_rec_obj = cls.env["account.mass.reconcile"]
cls.mass_rec_method_obj = cls.env["account.mass.reconcile.method"]
+
+ cls.sale_journal = cls.company_data["default_journal_sale"]
cls.mass_rec = cls.mass_rec_obj.create(
- {"name": "AER2", "account": cls.env.ref("account.a_salary_expense").id}
+ {"name": "Sale Account", "account": cls.sale_journal.default_account_id.id}
)
cls.mass_rec_method = cls.mass_rec_method_obj.create(
{
@@ -33,7 +28,7 @@ class TestReconcile(common.SavepointCase):
}
)
cls.mass_rec_no_history = cls.mass_rec_obj.create(
- {"name": "AER3", "account": cls.env.ref("account.a_salary_expense").id}
+ {"name": "AER3", "account": cls.sale_journal.default_account_id.id}
)
cls.rec_history = cls.rec_history_obj.create(
{"mass_reconcile_id": cls.mass_rec.id, "date": fields.Datetime.now()}
@@ -57,4 +52,14 @@ class TestReconcile(common.SavepointCase):
def test_prepare_run_transient(self):
res = self.mass_rec._prepare_run_transient(self.mass_rec_method)
- self.assertEqual(self.ref("account.a_salary_expense"), res.get("account_id", 0))
+ self.assertEqual(
+ self.sale_journal.default_account_id.id, res.get("account_id", 0)
+ )
+
+ def test_open_full_empty(self):
+ res = self.rec_history._open_move_lines()
+ self.assertEqual([("id", "in", [])], res.get("domain", []))
+
+ def test_open_full_empty_from_method(self):
+ res = self.rec_history.open_reconcile()
+ self.assertEqual([("id", "in", [])], res.get("domain", []))
diff --git a/account_mass_reconcile/tests/test_reconcile_history.py b/account_mass_reconcile/tests/test_reconcile_history.py
deleted file mode 100644
index 89ea2647..00000000
--- a/account_mass_reconcile/tests/test_reconcile_history.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# © 2014-2016 Camptocamp SA (Damien Crier)
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo import fields, tools
-from odoo.modules import get_module_resource
-from odoo.tests import common
-
-
-class TestReconcileHistory(common.SavepointCase):
- @classmethod
- def setUpClass(cls):
- super(TestReconcileHistory, cls).setUpClass()
- tools.convert_file(
- cls.cr,
- "account",
- get_module_resource("account", "test", "account_minimal_test.xml"),
- {},
- "init",
- False,
- "test",
- )
- cls.rec_history_obj = cls.env["mass.reconcile.history"]
- cls.mass_rec_obj = cls.env["account.mass.reconcile"]
- cls.mass_rec = cls.mass_rec_obj.create(
- {"name": "AER1", "account": cls.env.ref("account.a_expense").id}
- )
- cls.rec_history = cls.rec_history_obj.create(
- {"mass_reconcile_id": cls.mass_rec.id, "date": fields.Datetime.now()}
- )
-
- def test_open_full_empty(self):
- res = self.rec_history._open_move_lines()
- self.assertEqual([("id", "in", [])], res.get("domain", []))
-
- def test_open_full_empty_from_method(self):
- res = self.rec_history.open_reconcile()
- self.assertEqual([("id", "in", [])], res.get("domain", []))
diff --git a/account_mass_reconcile/tests/test_scenario_reconcile.py b/account_mass_reconcile/tests/test_scenario_reconcile.py
index 9ef536f5..09fba673 100644
--- a/account_mass_reconcile/tests/test_scenario_reconcile.py
+++ b/account_mass_reconcile/tests/test_scenario_reconcile.py
@@ -1,24 +1,19 @@
# © 2014-2016 Camptocamp SA (Damien Crier)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import fields, tools
-from odoo.modules import get_module_resource
-from odoo.tests import common
+from datetime import timedelta
+
+import odoo.tests
+from odoo import fields
+
+from odoo.addons.account.tests.common import TestAccountReconciliationCommon
-class TestScenarioReconcile(common.SavepointCase):
+@odoo.tests.tagged("post_install", "-at_install")
+class TestScenarioReconcile(TestAccountReconciliationCommon):
@classmethod
def setUpClass(cls):
super(TestScenarioReconcile, cls).setUpClass()
- tools.convert_file(
- cls.cr,
- "account",
- get_module_resource("account", "test", "account_minimal_test.xml"),
- {},
- "init",
- False,
- "test",
- )
cls.rec_history_obj = cls.env["mass.reconcile.history"]
cls.mass_rec_obj = cls.env["account.mass.reconcile"]
cls.invoice_obj = cls.env["account.move"]
@@ -26,13 +21,12 @@ class TestScenarioReconcile(common.SavepointCase):
cls.bk_stmt_line_obj = cls.env["account.bank.statement.line"]
cls.acc_move_line_obj = cls.env["account.move.line"]
cls.mass_rec_method_obj = cls.env["account.mass.reconcile.method"]
- cls.account_fx_income_id = cls.env.ref("account.income_fx_income").id
- cls.account_fx_expense_id = cls.env.ref("account.income_fx_expense").id
cls.acs_model = cls.env["res.config.settings"]
- acs_ids = cls.acs_model.search(
- [("company_id", "=", cls.env.ref("base.main_company").id)]
- )
+ cls.company = cls.company_data["company"]
+ cls.bank_journal = cls.company_data["default_journal_bank"]
+ cls.sale_journal = cls.company_data["default_journal_sale"]
+ acs_ids = cls.acs_model.search([("company_id", "=", cls.company.id)])
values = {"group_multi_currency": True}
@@ -44,185 +38,193 @@ class TestScenarioReconcile(common.SavepointCase):
acs_ids = cls.acs_model.create(default_vals)
def test_scenario_reconcile(self):
- # create invoice
- invoice = self.invoice_obj.with_context(default_type="out_invoice").create(
- {
- "type": "out_invoice",
- "company_id": self.ref("base.main_company"),
- "journal_id": self.ref("account.sales_journal"),
- "partner_id": self.ref("base.res_partner_12"),
- "invoice_line_ids": [
- (
- 0,
- 0,
- {
- "name": "[FURN_7800] Desk Combination",
- "account_id": self.ref("account.a_sale"),
- "price_unit": 1000.0,
- "quantity": 1.0,
- "product_id": self.ref("product.product_product_3"),
- },
- )
- ],
- }
- )
- # validate invoice
- invoice.post()
+ invoice = self.create_invoice()
self.assertEqual("posted", invoice.state)
- # create bank_statement
- statement = self.bk_stmt_obj.create(
+ receivalble_account_id = invoice.partner_id.property_account_receivable_id.id
+ # create payment
+ payment = self.env["account.payment"].create(
{
- "balance_end_real": 0.0,
- "balance_start": 0.0,
- "date": fields.Date.today(),
- "journal_id": self.ref("account.bank_journal"),
- "line_ids": [
- (
- 0,
- 0,
- {
- "amount": 1000.0,
- "partner_id": self.ref("base.res_partner_12"),
- "name": invoice.name,
- "ref": invoice.name,
- },
- )
- ],
+ "partner_type": "customer",
+ "payment_type": "inbound",
+ "partner_id": invoice.partner_id.id,
+ "destination_account_id": receivalble_account_id,
+ "amount": 50.0,
+ "journal_id": self.bank_journal.id,
}
)
-
- # reconcile
- line_id = None
- for l in invoice.line_ids:
- if l.account_id.internal_type == "receivable":
- line_id = l
- break
-
- for statement_line in statement.line_ids:
- statement_line.process_reconciliation(
- [
- {
- "move_line": line_id,
- "credit": 1000.0,
- "debit": 0.0,
- "name": invoice.name,
- }
- ]
- )
-
- # unreconcile journal item created by previous reconciliation
- lines_to_unreconcile = self.acc_move_line_obj.search(
- [("reconciled", "=", True), ("statement_id", "=", statement.id)]
- )
- lines_to_unreconcile.remove_move_reconcile()
+ payment.action_post()
# create the mass reconcile record
mass_rec = self.mass_rec_obj.create(
{
"name": "mass_reconcile_1",
- "account": line_id.account_id.id,
+ "account": invoice.partner_id.property_account_receivable_id.id,
"reconcile_method": [(0, 0, {"name": "mass.reconcile.simple.partner"})],
}
)
# call the automatic reconcilation method
mass_rec.run_reconcile()
- invoice.invalidate_cache()
- self.assertEqual("paid", invoice.invoice_payment_state)
+ self.assertEqual("paid", invoice.payment_state)
def test_scenario_reconcile_currency(self):
# create currency rate
self.env["res.currency.rate"].create(
{
- "name": fields.Date.today().strftime("%Y-%m-%d") + " 00:00:00",
+ "name": fields.Date.today(),
"currency_id": self.ref("base.USD"),
- "rate": 1.5,
+ "rate": 1.25,
}
)
# create invoice
- invoice = self.invoice_obj.with_context(default_type="out_invoice").create(
- {
- "type": "out_invoice",
- "company_id": self.ref("base.main_company"),
- "currency_id": self.ref("base.USD"),
- "journal_id": self.ref("account.sales_journal"),
- "partner_id": self.ref("base.res_partner_12"),
- "invoice_line_ids": [
- (
- 0,
- 0,
- {
- "name": "[FURN_7800] Desk Combination",
- "account_id": self.ref("account.a_sale"),
- "price_unit": 1000.0,
- "quantity": 1.0,
- "product_id": self.ref("product.product_product_3"),
- },
- )
- ],
- }
+ invoice = self._create_invoice(
+ currency_id=self.ref("base.USD"),
+ date_invoice=fields.Date.today(),
+ auto_validate=True,
)
- # validate invoice
- invoice.post()
self.assertEqual("posted", invoice.state)
- # create bank_statement
- statement = self.bk_stmt_obj.create(
+ self.env["res.currency.rate"].create(
{
- "balance_end_real": 0.0,
- "balance_start": 0.0,
- "date": fields.Date.today(),
- "journal_id": self.ref("account.bank_journal_usd"),
+ "name": fields.Date.today() - timedelta(days=3),
"currency_id": self.ref("base.USD"),
- "line_ids": [
- (
- 0,
- 0,
- {
- "amount": 1000.0,
- "amount_currency": 1500.0,
- "partner_id": self.ref("base.res_partner_12"),
- "name": invoice.name,
- "ref": invoice.name,
- },
- )
- ],
+ "rate": 2,
}
)
-
- # reconcile
- line_id = None
- for l in invoice.line_ids:
- if l.account_id.internal_type == "receivable":
- line_id = l
- break
-
- for statement_line in statement.line_ids:
- statement_line.process_reconciliation(
- [
- {
- "move_line": line_id,
- "credit": 1000.0,
- "debit": 0.0,
- "name": invoice.name,
- }
- ]
- )
- # unreconcile journal item created by previous reconciliation
- lines_to_unreconcile = self.acc_move_line_obj.search(
- [("reconciled", "=", True), ("statement_id", "=", statement.id)]
+ receivable_account_id = invoice.partner_id.property_account_receivable_id.id
+ # create payment
+ payment = self.env["account.payment"].create(
+ {
+ "partner_type": "customer",
+ "payment_type": "inbound",
+ "partner_id": invoice.partner_id.id,
+ "destination_account_id": receivable_account_id,
+ "amount": 50.0,
+ "currency_id": self.ref("base.USD"),
+ "journal_id": self.bank_journal.id,
+ "date": fields.Date.today() - timedelta(days=2),
+ }
)
- lines_to_unreconcile.remove_move_reconcile()
+ payment.action_post()
# create the mass reconcile record
mass_rec = self.mass_rec_obj.create(
{
"name": "mass_reconcile_1",
- "account": line_id.account_id.id,
+ "account": invoice.partner_id.property_account_receivable_id.id,
"reconcile_method": [(0, 0, {"name": "mass.reconcile.simple.partner"})],
}
)
# call the automatic reconcilation method
mass_rec.run_reconcile()
- invoice.invalidate_cache()
- self.assertEqual("paid", invoice.invoice_payment_state)
+ self.assertEqual("paid", invoice.payment_state)
+
+ def test_scenario_reconcile_partial(self):
+ invoice1 = self.create_invoice()
+ invoice1.ref = "test ref"
+ # create payment
+ receivable_account_id = invoice1.partner_id.property_account_receivable_id.id
+ payment = self.env["account.payment"].create(
+ {
+ "partner_type": "customer",
+ "payment_type": "inbound",
+ "partner_id": invoice1.partner_id.id,
+ "destination_account_id": receivable_account_id,
+ "amount": 500.0,
+ "journal_id": self.bank_journal.id,
+ "ref": "test ref",
+ }
+ )
+ payment.action_post()
+ line_payment = payment.line_ids.filtered(
+ lambda l: l.account_id.id == receivable_account_id
+ )
+ self.assertEqual(line_payment.reconciled, False)
+ invoice1_line = invoice1.line_ids.filtered(
+ lambda l: l.account_id.id == receivable_account_id
+ )
+ self.assertEqual(invoice1_line.reconciled, False)
+
+ # Create the mass reconcile record
+ reconcile_method_vals = {
+ "name": "mass.reconcile.advanced.ref",
+ "write_off": 0.1,
+ }
+ mass_rec = self.mass_rec_obj.create(
+ {
+ "name": "mass_reconcile_1",
+ "account": receivable_account_id,
+ "reconcile_method": [(0, 0, reconcile_method_vals)],
+ }
+ )
+ mass_rec.run_reconcile()
+
+ self.assertEqual(line_payment.amount_residual, -450.0)
+ self.assertEqual(invoice1_line.reconciled, True)
+ invoice2 = self._create_invoice(invoice_amount=500, auto_validate=True)
+ invoice2.ref = "test ref"
+ invoice2_line = invoice2.line_ids.filtered(
+ lambda l: l.account_id.id == receivable_account_id
+ )
+ mass_rec.run_reconcile()
+ self.assertEqual(line_payment.reconciled, True)
+ self.assertEqual(invoice2_line.reconciled, False)
+
+ self.assertEqual(invoice2_line.amount_residual, 50.0)
+
+ def test_reconcile_with_writeoff(self):
+ invoice = self.create_invoice()
+
+ receivable_account_id = invoice.partner_id.property_account_receivable_id.id
+ # create payment
+ payment = self.env["account.payment"].create(
+ {
+ "partner_type": "customer",
+ "payment_type": "inbound",
+ "partner_id": invoice.partner_id.id,
+ "destination_account_id": receivable_account_id,
+ "amount": 50.1,
+ "journal_id": self.bank_journal.id,
+ }
+ )
+ payment.action_post()
+
+ # create the mass reconcile record
+ mass_rec = self.mass_rec_obj.create(
+ {
+ "name": "mass_reconcile_1",
+ "account": invoice.partner_id.property_account_receivable_id.id,
+ "reconcile_method": [
+ (
+ 0,
+ 0,
+ {
+ "name": "mass.reconcile.simple.partner",
+ "account_lost_id": self.company_data[
+ "default_account_expense"
+ ].id,
+ "account_profit_id": self.company_data[
+ "default_account_revenue"
+ ].id,
+ "journal_id": self.company_data["default_journal_misc"].id,
+ "write_off": 0.05,
+ },
+ )
+ ],
+ }
+ )
+ # call the automatic reconcilation method
+ mass_rec.run_reconcile()
+ self.assertEqual("not_paid", invoice.payment_state)
+ mass_rec.reconcile_method.write_off = 0.11
+ mass_rec.run_reconcile()
+ self.assertEqual("paid", invoice.payment_state)
+ full_reconcile = invoice.line_ids.mapped("full_reconcile_id")
+ writeoff_line = full_reconcile.reconciled_line_ids.filtered(
+ lambda l: l.debit == 0.1
+ )
+ self.assertEqual(len(writeoff_line), 1)
+ self.assertEqual(
+ writeoff_line.move_id.journal_id.id,
+ self.company_data["default_journal_misc"].id,
+ )
diff --git a/account_mass_reconcile/views/mass_reconcile.xml b/account_mass_reconcile/views/mass_reconcile.xml
index ce59569a..7a39e161 100644
--- a/account_mass_reconcile/views/mass_reconcile.xml
+++ b/account_mass_reconcile/views/mass_reconcile.xml
@@ -173,14 +173,6 @@ The lines should have the same partner, and the credit entry ref. is matched wit
name="account_profit_id"
attrs="{'required':[('write_off','>',0)]}"
/>
-
-