Skip to content

[18.0][OU-FIX] account: correct payment state for migrated payments#5767

Open
akashr355 wants to merge 5 commits into
OCA:18.0from
akashr355:18.0-mig-account-payment-state
Open

[18.0][OU-FIX] account: correct payment state for migrated payments#5767
akashr355 wants to merge 5 commits into
OCA:18.0from
akashr355:18.0-mig-account-payment-state

Conversation

@akashr355

Copy link
Copy Markdown

[MIG] account: recompute payment states post-migration

During the 18.0 migration, fill_account_payment maps all <=17 payments to 'in_process' because a payment's own journal entry defaults to payment_state = 'not_paid'. Consequently, fully settled payments fail to migrate to the 'paid' state.

This introduces a post-migration script to re-trigger _compute_state() for in-process payments. This ensures fully reconciled payments are promoted to 'paid', accurately reflecting their true status.

[MIG] account: recompute payment states post-migration

During the 18.0 migration, `fill_account_payment` maps all <=17 payments
to 'in_process' because a payment's own journal entry defaults to
`payment_state = 'not_paid'`. Consequently, fully settled payments fail
to migrate to the 'paid' state.

This introduces a post-migration script to re-trigger `_compute_state()`
for in-process payments. This ensures fully reconciled payments are 
promoted to 'paid', accurately reflecting their true status.
@OCA-git-bot OCA-git-bot added mod:openupgrade_scripts Module openupgrade_scripts series:18.0 labels Jul 2, 2026

@pedrobaeza pedrobaeza left a comment

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.

This is not happening in my migrated instances, so something on your side (maybe a module like account_in_payment installed) is different, so you should fix it in that part, not here.

@pedrobaeza pedrobaeza added this to the 18.0 milestone Jul 2, 2026
@akashr355

akashr355 commented Jul 2, 2026

Copy link
Copy Markdown
Author

This is not happening in my migrated instances, so something on your side (maybe a module like account_in_payment installed) is different, so you should fix it in that part, not here.

fill_account_payment maps every posted payment to in_process, even ones that should be paid

Environment

  • OpenUpgrade 16.0 → 18.0 migration
  • account module at version 18.0.1.3
  • Source database is Odoo 16.0

Symptom

After migration, all customer/vendor payments show state = 'in_process' instead of 'paid'. In my database: 12,865 payments, 100% in_process, 0 paid. Their underlying moves are all state = 'posted', payment_state = 'not_paid'.

Root cause

The problem is in openupgrade_scripts/scripts/account/18.0.1.3/pre-migration.py, function fill_account_payment. It sets the new state with this CASE:

state = CASE WHEN am.state = 'cancel'       THEN 'canceled'
             WHEN am.payment_state = 'paid'  THEN 'paid'
             WHEN am.state = 'posted'        THEN 'in_process'
             ELSE am.state END

Here am is the payment's own journal entry (the join is ap.move_id = am.id). A payment move has move_type = 'entry'. In Odoo ≤ 17, account.move._compute_payment_state only computes a real payment_state for invoices; every non-invoice move is forced to 'not_paid' (see odoo/addons/account/models/account_move.py in v16, the for move in self - posted_invoices: move.payment_state = 'not_paid' branch).

So am.payment_state is always 'not_paid' for a payment. That makes the WHEN am.payment_state = 'paid' THEN 'paid' branch dead code for payments, and every posted payment falls through to 'in_process'.

That is wrong for payments that Odoo 18 would classify as paid. In Odoo 18 account.payment:

  • _compute_state sets 'paid' when the liquidity line is fully reconciled OR when none of the liquidity accounts are reconcilable: not any(liquidity.account_id.mapped('reconcile')).
  • action_post sets 'paid' immediately when outstanding_account_id.account_type == 'asset_cash'.

Why it does not reproduce on a standard setup

If payments post to a reconcilable Outstanding Receipts/Payments account (account_type = 'asset_current', reconcile = true) and have not been bank-reconciled yet, then in_process is genuinely correct — so nothing looks broken.

The bug is only visible when payments post directly to the bank/cash GL account (account_type = 'asset_cash', reconcile = false), i.e. there is no reconcilable outstanding account. In that case the correct Odoo 18 state is paid, but the migration produces in_process.

My journals use this direct-to-bank configuration: the journal's default account (and its payment-method-line payment account) is the bank/cash account itself (asset_cash, reconcile = false). That is why I hit the bug and you did not.

How to reproduce

On the Odoo 16 source: set a bank journal's inbound/outbound payment method line outstanding account (v16: res.company.account_journal_payment_debit_account_id / account_journal_payment_credit_account_id, or the journal-level override) to the bank/cash GL account itself (account_type = 'asset_cash', reconcile = false). Register and post a customer payment. Migrate to 18.0 → the payment ends up in_process; expected paid.

Proposed fix

After _handle_outstanding_accounts has populated the payment-method-line payment accounts, promote the mis-classified rows. This mirrors account.payment._compute_state exactly and is a single bulk SQL statement:

WITH liq AS (
    SELECT ap.id AS payment_id,
           SUM(aml.amount_residual) AS residual,
           BOOL_OR(aa.reconcile) AS any_reconcile,
           cur.rounding AS rounding
    FROM account_payment ap
    JOIN account_move am ON am.id = ap.move_id
    JOIN res_company comp ON comp.id = am.company_id
    JOIN res_currency cur ON cur.id = comp.currency_id
    JOIN account_journal aj ON aj.id = ap.journal_id
    JOIN account_move_line aml ON aml.move_id = am.id
    JOIN account_account aa ON aa.id = aml.account_id
    WHERE ap.state = 'in_process'
      AND (aml.account_id = aj.default_account_id
           OR aml.account_id IN (SELECT apml.payment_account_id
                                 FROM account_payment_method_line apml
                                 WHERE apml.journal_id = ap.journal_id
                                   AND apml.payment_account_id IS NOT NULL))
    GROUP BY ap.id, cur.rounding
)
UPDATE account_payment ap
SET state = 'paid', is_matched = TRUE
FROM liq
WHERE ap.id = liq.payment_id
  AND (NOT liq.any_reconcile OR ABS(liq.residual) < liq.rounding / 2.0);

The liquidity account set (journal default account + every payment-method-line payment account on that journal) is a complete cover of _get_valid_liquidity_accounts(), because in v18 outstanding_account_id is simply payment_method_line_id.payment_account_id. The residual comparison uses the company currency rounding to match float_is_zero. Payments whose liquidity account is reconcilable and not yet reconciled correctly stay in_process.

@akashr355 akashr355 requested a review from pedrobaeza July 3, 2026 04:22

@hbrunn hbrunn left a comment

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.

I would like to see tests here:

  1. a partial payment of one invoice
  2. a payment that pays multiple invoices partially
  3. a payment that pays an invoice fully
  4. two payments that pay one invoice fully

@akashr355 akashr355 requested a review from hbrunn July 8, 2026 07:18
@MiquelRForgeFlow

Copy link
Copy Markdown
Contributor

Please fix pre-commit.

@akashr355

Copy link
Copy Markdown
Author

I would like to see tests here:

  1. a partial payment of one invoice
  2. a payment that pays multiple invoices partially
  3. a payment that pays an invoice fully
  4. two payments that pay one invoice fully

Test added please review.

@akashr355

Copy link
Copy Markdown
Author

Please fix pre-commit.

pre-commit fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mod:openupgrade_scripts Module openupgrade_scripts series:18.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants