Skip to content

[17.0][FIX] account_mass_reconcile: writeoff with currencies of opposite signs#831

Open
astirpe wants to merge 1 commit intoOCA:17.0from
astirpe:17_fix_account_mass_reconcile
Open

[17.0][FIX] account_mass_reconcile: writeoff with currencies of opposite signs#831
astirpe wants to merge 1 commit intoOCA:17.0from
astirpe:17_fix_account_mass_reconcile

Conversation

@astirpe
Copy link
Copy Markdown
Member

@astirpe astirpe commented Apr 15, 2025

It appeared to be a bug related to account_mass_reconcile on method create_write_off() when creating write off journal items with different currencies. An error is raised, caused by this line:
https://github.com/OCA/account-reconcile/blob/17.0/account_mass_reconcile/models/base_reconciliation.py#L163

which triggers this standard Odoo sql constraint:
https://github.com/odoo/odoo/blob/17.0/addons/account/models/account_move_line.py#L428

Here is the full error:

2025-04-15 08:13:39,661 249 INFO odoo odoo.addons.account_mass_reconcile.tests.test_scenario_reconcile: ====================================================================== 
2025-04-15 08:13:39,661 249 ERROR odoo odoo.addons.account_mass_reconcile.tests.test_scenario_reconcile: ERROR: TestScenarioReconcile.test_reconcile_with_writeoff_currency_opposite_signs
Traceback (most recent call last):
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/mass_reconcile.py", line 201, in run_reconcile
    ml_rec_ids = self.with_env(new_env)._run_reconcile_method(method)
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/mass_reconcile.py", line 150, in _run_reconcile_method
    return auto_rec_id.automatic_reconcile()
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/base_reconciliation.py", line 30, in automatic_reconcile
    return self._action_rec()
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/base_advanced_reconciliation.py", line 211, in _action_rec
    result = self._rec_auto_lines_advanced(credit_lines, debit_lines)
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/base_advanced_reconciliation.py", line 310, in _rec_auto_lines_advanced
    reconciled_ids = self._rec_group(reconcile_groups, lines_by_id)
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/base_advanced_reconciliation.py", line 232, in _rec_group
    reconciled, full = self._reconcile_lines(group_lines, allow_partial=True)
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/base_reconciliation.py", line 220, in _reconcile_lines
    writeoff_line = self.create_write_off(
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/base_reconciliation.py", line 178, in create_write_off
    move = self.env["account.move"].create(
  File "<decorator-gen-203>", line 2, in create
  File "/opt/odoo/odoo/api.py", line 430, in _model_create_multi
    return create(self, [arg])
  File "/opt/odoo/addons/account/models/account_move.py", line 2498, in create
    moves = super().create(vals_list)
  File "<decorator-gen-137>", line 2, in create
  File "/opt/odoo/odoo/api.py", line 431, in _model_create_multi
    return create(self, arg)
  File "/opt/odoo/addons/mail/models/mail_thread.py", line 259, in create
    threads = super(MailThread, self).create(vals_list)
  File "<decorator-gen-12>", line 2, in create
  File "/opt/odoo/odoo/api.py", line 431, in _model_create_multi
    return create(self, arg)
  File "/opt/odoo/odoo/models.py", line 4654, in create
    records = self._create(data_list)
  File "/opt/odoo/odoo/models.py", line 4901, in _create
    field.create([
  File "/opt/odoo/odoo/fields.py", line 4348, in create
    self.write_batch(record_values, True)
  File "/opt/odoo/odoo/fields.py", line 4374, in write_batch
    self.write_real(records_commands_list, create)
  File "/opt/odoo/odoo/fields.py", line 4564, in write_real
    flush()
  File "/opt/odoo/odoo/fields.py", line 4520, in flush
    comodel.create(to_create)
  File "<decorator-gen-204>", line 2, in create
  File "/opt/odoo/odoo/api.py", line 431, in _model_create_multi
    return create(self, arg)
  File "/opt/odoo/addons/account/models/account_move_line.py", line 1582, in create
    lines = super().create([self._sanitize_vals(vals) for vals in vals_list])
  File "<decorator-gen-158>", line 2, in create
  File "/opt/odoo/odoo/api.py", line 431, in _model_create_multi
    return create(self, arg)
  File "/opt/odoo/addons/analytic/models/analytic_mixin.py", line 126, in create
    return super().create(vals_list)
  File "<decorator-gen-12>", line 2, in create
  File "/opt/odoo/odoo/api.py", line 431, in _model_create_multi
    return create(self, arg)
  File "/opt/odoo/odoo/models.py", line 4654, in create
    records = self._create(data_list)
  File "/opt/odoo/odoo/models.py", line 4842, in _create
    cr.execute(SQL(
  File "/opt/odoo/odoo/sql_db.py", line 332, in execute
    res = self._obj.execute(query, params)
psycopg2.errors.CheckViolation: new row for relation "account_move_line" violates check constraint "account_move_line_check_amount_currency_balance_sign"
DETAIL:  Failing row contains (79, 30, 31, 6, 126, 100, 225, 1, 50, null, null, null, null, null, null, null, null, null, null, null, null, 9, 9, null, null, null, Automatic writeoff, null, product, null, null, null, null, null, 3.25, 0.00, 3.25, -0.10, null, null, null, 1.00, 0.00, null, null, 0.00, null, null, null, null, f, 2025-04-15 08:13:34.969427, 2025-04-15 08:13:34.969427).


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/tests/test_scenario_reconcile.py", line 546, in test_reconcile_with_writeoff_currency_opposite_signs
    mass_rec.run_reconcile()
  File "/__w/account-reconcile/account-reconcile/account_mass_reconcile/models/mass_reconcile.py", line 221, in run_reconcile
    rec.message_post(body=message)
  File "/opt/odoo/addons/sms/models/mail_thread.py", line 52, in message_post
    return super().message_post(*args, body=body, message_type=message_type, **kwargs)
  File "/opt/odoo/addons/mail/models/mail_thread.py", line 2206, in message_post
    'parent_id': self._message_compute_parent_id(parent_id),
  File "/opt/odoo/addons/mail/models/mail_thread.py", line 2863, in _message_compute_parent_id
    parent_message = MailMessage_sudo.search([('res_id', '=', self.id), ('model', '=', self._name), ('message_type', '!=', 'user_notification')], order="id ASC", limit=1)
  File "/opt/odoo/odoo/models.py", line [162](https://github.com/OCA/account-reconcile/actions/runs/14464536946/job/40563739331?pr=831#step:8:163)3, in search
    return self.search_fetch(domain, [], offset=offset, limit=limit, order=order)
  File "/opt/odoo/odoo/models.py", line 1654, in search_fetch
    return self._fetch_query(query, fields_to_fetch)
  File "/opt/odoo/odoo/models.py", line 3986, in _fetch_query
    fetched = self.browse(query)
  File "/opt/odoo/odoo/models.py", line 5861, in browse
    if not ids:
  File "/opt/odoo/odoo/tools/query.py", line 261, in __bool__
    return bool(self.get_result_ids())
  File "/opt/odoo/odoo/tools/query.py", line 224, in get_result_ids
    self._cr.execute(self.select())
  File "/opt/odoo/odoo/sql_db.py", line 332, in execute
    res = self._obj.execute(query, params)
psycopg2.errors.InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block

@astirpe astirpe marked this pull request as ready for review April 15, 2025 08:28
Copy link
Copy Markdown

@CasVissers-360ERP CasVissers-360ERP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional review

write_off_vals = {
"name": _("Automatic writeoff"),
"amount_currency": same_curr and amount_curr or amount,
"amount_currency": adjusted_amount_curr if same_curr else amount,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just take adjusted_amount_curr here because adjusted_amount_curr already contains the right value depending on the value of same_curr, making the condition here redundant (and confusing)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!

@StefanRijnhart
Copy link
Copy Markdown
Member

Please squash the two commits.

@astirpe astirpe force-pushed the 17_fix_account_mass_reconcile branch from 9c0bfd0 to cedb91d Compare October 6, 2025 08:56
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 8, 2026

There hasn't been any activity on this pull request in the past 4 months, so it has been marked as stale and it will be closed automatically if no further activity occurs in the next 30 days.
If you want this PR to never become stale, please ask a PSC member to apply the "no stale" label.

@github-actions github-actions Bot added the stale PR/Issue without recent activity, it'll be soon closed automatically. label Feb 8, 2026
Copy link
Copy Markdown
Contributor

@florian-dacosta florian-dacosta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this work.
In your test, you don't check if the invoice and the payment ends up fully reconciled (with the full_reconcile_id) and from what I see, it is not the case.

The problem presently, IMO, is that we try to force the amount_currency and the debit/credit of the writeoff line instead of letting Odoo compute it.
But you can't juste inverse the sign of the amount to bypass the constraint, as it changes everything.
If we take the case of your test as an example.
We have an invoice of 100 USD (amount_currency = +100) a payment of 100.1 USD (amount_currency = -100.1) The module compute that it needs a writeoff of amount_curency= 0.1 USD, so it everything is fully reconciliable.

If you inverse the sign as you need, the writeoff becomes -0.1 USD.
So the system tries then to reconcile 100 USD with 100.2 USD... and it does not fully reconcile even if we forced the company currency to be equal.

The solution I propose (you can see in v18, 263563a) is just to leave Odoo recompute the debit/credit from the amount currency.
During the reconciliation process, Odoo does generate an rate exchange currency difference entry to manage the company currency difference...
I am not sure I am clear, but have a look on the test on v18 please.

If you want to continue with this solution on v17, Add a check on the test to check the full_reconcile_id of the invoice/payment lines. (it should fail, as explained)

@github-actions github-actions Bot removed the stale PR/Issue without recent activity, it'll be soon closed automatically. label Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants