odoo/odoo · error · UserError

%(move)s reached an invalid state regarding its related stat

Error message

%(move)s reached an invalid state regarding its related statement line.\nTo be consistent, the journal entry must always have exactly one suspense line.

What it means

Same synchronization method, next invariant: after classifying the move's lines, at most one line may hit the journal's suspense account (len(suspense_lines) > 1 raises). The suspense account is a temporary balancing account; multiple suspense lines on one statement-line move are an invalid intermediate state.

Source

Thrown at addons/account/models/account_bank_statement_line.py:751

                st_line_vals_to_write.update({
                    'payment_ref': liquidity_lines.name,
                    'partner_id': liquidity_lines.partner_id.id,
                })

                # Update 'amount' according to the liquidity line.

                if journal_currency:
                    st_line_vals_to_write.update({
                        'amount': liquidity_lines.amount_currency,
                    })
                else:
                    st_line_vals_to_write.update({
                        'amount': liquidity_lines.balance,
                    })

                if len(suspense_lines) > 1:
                    raise UserError(_(
                        "%(move)s reached an invalid state regarding its related statement line.\n"
                        "To be consistent, the journal entry must always have exactly one suspense line.",
                        move=st_line.move_id.display_name,
                    ))
                elif len(suspense_lines) == 1:
                    if journal_currency and suspense_lines.currency_id == journal_currency:

                        # The suspense line is expressed in the journal's currency meaning the foreign currency
                        # set on the statement line is no longer needed.

                        st_line_vals_to_write.update({
                            'amount_currency': 0.0,
                            'foreign_currency_id': False,
                        })

                    elif not journal_currency and suspense_lines.currency_id == company_currency:

                        # Don't set a specific foreign currency on the statement line.

View on GitHub (pinned to 1e661df964)

Solutions

  1. Reduce to a single suspense line: reconcile/replace the extra suspense lines with real counterpart accounts.
  2. Edit the statement line itself (amount, partner) instead of hand-editing the generated move lines.
  3. Cancel and re-create the statement line to regenerate a clean move if the entry is heavily modified.

Example fix

# before
env['account.move.line'].create({
    'move_id': st_line.move_id.id,
    'account_id': journal.suspense_account_id.id, ...})  # 2nd suspense line
# -> UserError

# after (single counterpart: edit the statement line / reconcile the suspense line)
st_line.write({'amount': corrected_amount})
Defensive patterns

Strategy: validation

Validate before calling

def move_has_at_most_one_suspense_line(st_line):
    liquidity, suspense, other = st_line._seek_for_lines()
    return len(suspense) <= 1

Try / catch

from odoo.exceptions import UserError
try:
    move.write({'line_ids': line_commands})
except UserError as e:
    if 'exactly one suspense line' in str(e):
        raise  # remove/replace extra suspense lines, then retry
    raise

Prevention

When it happens

Trigger: Editing the move lines of a statement line so that two or more lines use the journal's suspense_account_id — e.g. manually adding a second suspense-account line or duplicating the counterpart line before reconciliation.

Common situations: Manual journal-entry edits that add lines on the suspense account; custom code writing suspense lines; partial reconciliation tooling leaving duplicate suspense counterparts.

Related errors


AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15). Data as JSON: /api/errors/cc51ff1a8c5535be. Report an issue: GitHub.