Skip to content

[FIX] purchase_request: do not close allocations on locked orders - #3160

Merged
OCA-git-bot merged 1 commit into
OCA:18.0from
adhoc-dev:18.0-h-126544-les
Sep 1, 2026
Merged

[FIX] purchase_request: do not close allocations on locked orders#3160
OCA-git-bot merged 1 commit into
OCA:18.0from
adhoc-dev:18.0-h-126544-les

Conversation

@les-adhoc

Copy link
Copy Markdown
Contributor

Problem

purchase.request.allocation._compute_open_product_qty zeroes the open quantity when the purchase order line is in state done. In Odoo that state is Locked — a modification lock — not a completion: receipts still happen afterwards, and core itself creates and processes them for locked orders (_create_picking accepts ('purchase', 'done')).

When the company has Lock Confirmed Orders enabled (res.company.po_lock = 'lock'), button_approve moves the order to done at approval time. The allocation is therefore closed before any receipt can happen, and:

  • stock.move.line.allocate() never allocates the incoming quantity, so no receipt message is posted and the requester is never notified;
  • qty_done and qty_in_progress stay at 0 on the request line, which then shows nothing received and everything still pending, forever;
  • stock.move.copy_data() filters allocations on open_product_qty, so backorders are created without allocations.

For a database with that setting on, the allocation and notification feature is effectively dead. Nothing fails and nothing is logged.

This is a regression

The exact same one line change was already requested and merged once:

The forward port to 15.0 brought back the version including done:

Branch _compute_open_product_qty
14.0 if rec.purchase_state == "cancel":
15.0 → 19.0 if rec.purchase_state in ["cancel", "done"]:

So 15.0 up to 19.0 all carry the bug again. This PR restores the 14.0 behaviour on 18.0; happy to port it to the other affected branches if you want them in the same batch.

For the record on why the check existed at all: it was introduced in 3927305 for service lines only, where the purchase order line is the only available signal, and extended to stock backed allocations by 587c260 while splitting the models into separate files.

Fix

cancel is the only state that really means nothing else will be allocated. Once a receipt completes, allocated_product_qty reaches requested_product_uom_qty and the open quantity becomes 0 on its own, so no shortcut on done is needed. Keeping the open quantity positive on a locked order is also what copy_data needs in order to carry the remainder over to a backorder.

Test plan

The new test covers the locked order flow, which no existing test exercised because po_lock defaults to edit. Before the change it fails:

FAIL: TestPurchaseRequestToRfq.test_purchase_request_stock_allocation_locked_po
    self.assertEqual(allocation.open_product_qty, 10.0)
AssertionError: 0.0 != 10.0

After it, the whole module suite passes on 18.0:

odoo.tests.result: 0 failed, 0 error(s) of 30 tests

Out of scope

purchase_request_line._compute_purchase_state and _purchase_request_line_check read done the same way, which is what raises "The purchase has already been completed" when generating a second RFQ for a request line of a locked order. That reading is asserted by an existing test, so changing it is a separate discussion and is deliberately left untouched here.

_compute_open_product_qty treats a purchase order line in state done as a
finished purchase and zeroes the open quantity. In Odoo that state means
"Locked", a modification lock, not a completion: receipts still happen
afterwards, and core creates and processes them for locked orders
(_create_picking accepts both purchase and done).

With po_lock = 'lock' the order reaches done at approval time, so the
allocation is closed before any receipt can happen. As a consequence
stock.move.line.allocate() never allocates the incoming quantity, the
requester is never notified of the receipt, qty_done and qty_in_progress
stay at 0 on the request line, and stock.move.copy_data carries no
allocation over to backorders. Nothing fails and nothing is logged.

This is a regression: the same one line change was requested by a
maintainer in OCA#1419 and merged into 14.0 by OCA#1422, but the forward port
to 15.0 brought back the version including done, and 15.0 up to 19.0
still carry it.

cancel is the only state that really means nothing else will be
allocated: once a receipt completes, allocated_product_qty reaches
requested_product_uom_qty and the open quantity becomes 0 on its own.

Add a test covering the locked order flow, which no test exercised
because po_lock defaults to edit.
@les-adhoc

Copy link
Copy Markdown
Contributor Author

@StefanRijnhart you diagnosed this exact behaviour in #1419 and asked for the if rec.purchase_state == 'cancel': variant, which was merged into 14.0 by #1422. The forward port to 15.0 reverted it and 15.0 up to 19.0 still carry it, so this PR restores your fix on 18.0 with a test that pins it down.

@HviorForgeFlow could you take a look? You have context on the allocation code.

Thank you!

@StefanRijnhart StefanRijnhart 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.

Thanks

@HviorForgeFlow

Copy link
Copy Markdown
Member

/ocabot merge patch

@OCA-git-bot

Copy link
Copy Markdown
Contributor

On my way to merge this fine PR!
Prepared branch 18.0-ocabot-merge-pr-3160-by-HviorForgeFlow-bump-patch, awaiting test results.

@HviorForgeFlow

Copy link
Copy Markdown
Member

@les-adhoc do you mind moving the fix to all affected versions, thanks!

@OCA-git-bot
OCA-git-bot merged commit a3547c2 into OCA:18.0 Sep 1, 2026
9 checks passed
@OCA-git-bot

Copy link
Copy Markdown
Contributor

Congratulations, your PR was merged at e59468a. Thanks a lot for contributing to OCA. ❤️

@les-adhoc

les-adhoc commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@HviorForgeFlow forwarded to the other affected versions:

19.0 is not affected. There the done state was removed from
purchase.order.state and replaced by the locked boolean, so purchase_state
(related to purchase_line_id.state) can never be "done" and that branch of
the condition is unreachable. I checked it on a 19.0 database: with
po_lock = 'lock' the order stays in purchase with locked = True, the
allocation stays open, the incoming quantity is allocated and the requester is
notified, the current code already behaves correctly. I can open a cleanup PR
dropping the dead "done" from the condition there if you think it is worth it.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants