Skip to content

[19.0][MIG] stock_available_to_promise_release: Migration to 19.0 - #74

Open
vvrossem wants to merge 181 commits into
OCA:19.0from
camptocamp:19.0-mig-stock_available_to_promise_release
Open

vvrossem wants to merge 181 commits into
OCA:19.0from
camptocamp:19.0-mig-stock_available_to_promise_release

Conversation

@vvrossem

@vvrossem vvrossem commented Oct 19, 2026 •

Copy link
Copy Markdown

Migration of stock_available_to_promise_release to 19.0.

Depends on OCA/stock-logistics-warehouse#2677 (stock_warehouse_out_pull), pulled in by the [DO NOT MERGE] test-requirements.txt commit.

Most changes come from procurement.group being removed in 19.0.

Release policy

release_policy used to be taken from the procurement group's move_type when the picking was created. In 19.0, sale_stock computes the picking's move_type from the SO lines, so it is only known once the moves are linked. The module now sets release_policy for new pickings in _assign_picking_post_process. It stays a plain field, as in 18.0:

Picking release_policy
New picking (SO delivery, backorder of unreleased moves) the picking's move_type: SO shipping policy, else the operation type's
Backorder created at validation copied from the original picking
Changed by hand kept

The install hook fills existing pickings from move_type.

Other procurement.group changes

  • run_defer moves to stock.rule.
  • Tests use stock.reference instead of groups. The "fixed" group on the pick rule is gone, so tests that relied on it to gather the pick moves of two deliveries in one transfer use a _merge_pickings helper.

19.0 API

  • Search methods accept in/not in, which 19.0 passes instead of =/!= (boolean fields and ordered_available_to_promise_uom_qty), and return NotImplemented for other operators.
  • Unrelease posts one message per picking: grouped("picking_id") replaces itertools.groupby, which split moves of the same picking when they weren't consecutive.
  • Tests no longer rely on demo data.
  • Usual updates: _read_group, Domain, models.Constraint, group_ids, stock.package, fields.first, stock.move.name.

guewen and others added 30 commits October 19, 2026 15:03
When:
* using a 3 steps delivery
* creating and confirming a sale order
* opening the transfer using the "Deliveries" button on the sale order
* clicking on the "Release" button

The pickings generated by the release are incorrect.
This is because the smart button of the sale order view adds several
"default_" fields that are then interpreted during the creation of the
chained pickings.

Clean these fields from the context.
When stock moves have been released, their 'need_release' flag must be
set to False. Due to a coding mistake, they were not. The most visible
effect was the "Release" button still visible on transfers and moves,
but other vicious effects can't be excluded.

The issue come from an inversion in the order of the list comprehension,
added up to the fact that the 'procurement' variable name was recycled
from the code above: 'procurement' was always equal to the latest
procurement used in the loop before the list comprehension.
To reproduce

* Order 100x [E-COM10]
* Update quantities in stock to have 70 [E-COM10] in Stock/Shelf 1
* Release the moves

Actual result:

2 transfer, the backorder for the waiting 30 [E-COM10] has no backorder
link with the original transfer

Expected result:

2 transfer, the backorder for the waiting 30 [E-COM10] has a backorder
link with the original transfer
Current behavior:

When we release an OUT stock move, the OUT transfer is set to printed.
The origin transfers (e.g. Pick+Pack) are not.

Expected behavior:

When we release an OUT stock move, all the origin transfer (e.g.
Pick+Pack) are set to printed.

Reason:

The original reason for setting "printed" to True, is that when
"_assign_picking()" is called on a stock.move, the stock.move can be
added only in a stock.picking which is not printed yet. In other terms,
"printed" means the transfer has been started and shouldn't be modified.
When we "release" moves, we actually planify the work to be done, and if
we release again new moves, we don't expect transfers of the 2 waves of
releases to be merged together.

If we set the flag "printed" on the Out transfer only, we can end up
with one Packing transfer leading to 2 Out transfers, which generally
doesn't make sense.
* When a new picking was created in the "super" call, it would get the
carrier of the sale order (default implementation of
`_get_new_picking_values` in delivery).
* Modify the way to upgrade the groups and carriers during release:

  * filter moves without available quantity or which do not need a
  release, it prevents assigning a carrier on a picking with nothing
  available or for which we already set the carrier
  * as soon as a picking is assigned a new carrier, push it in a new
  group
  * the new group has the delivery method in the name, to prevent
  confusion with identical group names (default is the SO name)
  * after "super", new picking could have been created for the
  remaining, search them and assign them back to the old group and
  carrier
Previous behavior:

* If we use the "release" action on a move partially available, the
  remaining will be moved to a backorder picking
* When we "release" a move, it stays in the same transfer as the
  "unreleased" ones (wether they are available or not), so the
  "unreleased" ones are now in a picking with the "printed flag"

Examples of OUT transfer:

* 20 product A, 20 allocated
* 10 product B, 0 allocated

1. If I use the "release" on the 20 product A: both lines stays in the
   same transfer, which has the "printed" flag
2. If I use the "release" on both lines: a backorder is created for 10
   products B, the backorder has no "printed" flag

* 20 product A, 18 allocated
* 10 product B, 0 allocated

1. If I use the "release" on the 18 product A: both lines stays in the
   same transfer, which has the "printed" flag, a backorder is created for
   2 product A (without print flag)
2. If I use the "release" on both lines: a backorder is created for 10
   products B, the backorder has no "printed" flag

Expected behavior:

* If we use the "release" action on a move partially available, the
  remaining will be moved to a backorder picking
* When we "release" a move, the unreleased moves are put in a backorder
  (with the unavailable part of the released move if any) (and they are
  not "printed")
Add a module with tests to check a release of moves with dynamic routing
correctly sets the "printed" flag on all the chain.

When we release moves, we set the "printed" flag on the released
transfers, so we do not include any new moves (like a "wave" of
release).

As the dynamic routing is applied when we assign the moves, new moves
and transfers may be created after the "assign". We therefore have
to set the printed flag after it.
When we have thousands of moves, the screen becomes hardly usable.
Make the comptutation faster.

* Compute the available quantity directly from quants with a read group
  (the qty_available computed field is too slow as it computes other
  quantities, and even much slower because of "mrp" overrides)
* The computation of the quantity promised to previous moves is very
  slow because it runs one query per move. Use a SQL query with LATERAL
  JOIN in order to run a single query for *all* the moves.

As I changed the computation, I went through it and improved / fixed
some things:

* Show a "previous_promised_qty" field which contains the result of
  the new query that returns the promised qty of previous moves. It
  is shown only with debug mode.
* Do no longer filter on the "previous moves" of the same warehouse:
  moves do not always have a "warehouse_id" set. Instead, all the
  quantities are computed on the same basis: in the view locations of
  all the warehouses, assuming that if we have the quantity anywhere
  in the stock, we can release (then make internal transfers).
* Compute both uom and real "ordered available to promise" quantity in
  the same compute method, the uom one being displayed to the user,
  while the real one is used during the release
* When both the priority and the date_priority of previous moves were
  equal, the previous quantity could be indeterministic, in this case,
  order by id in last resort
* Add tests for search methods
jbaudoux and others added 19 commits October 19, 2026 15:27
split realease_ready and release_ready_count as the first is used for often and the second is more expensive to compute
…cation

The warehouse is used for computing available stock. Allow to view if
the warehouse is set.
Do not unrelease when 2 released moves are merged and do not send
pre-released move to a backorder.
…nsfers

This can happen with stock_dynamic_routing installed that split/cancel
pick transfers.
Move "Transfers Allocations" inside "Transfers" section
When a move partially available is released, a new move is created for
the remaining quantity. If the released move gets a new picking type, he
cannot loose it's priority in favor of the remaining quantity move.

Revert of OCA/wms#441
Add support to send released moves to a split order.

Always force a new picking on assignation. The condition is already
checked before calling action_assign.

Improve backorder link with origin and posted message.

Call hookable before_release before sending remaining moves to backorder
as the hook could manage the way backorders are created (see carrier
alternative).
Currently translated at 100.0% (87 of 87 strings)

Translation: stock-logistics-reservation-18.0/stock-logistics-reservation-18.0-stock_available_to_promise_release
Translate-URL: https://translation.odoo-community.org/projects/stock-logistics-reservation-18-0/stock-logistics-reservation-18-0-stock_available_to_promise_release/it/
Fix loop, do not override function argument on first iteration
Currently translated at 100.0% (90 of 90 strings)

Translation: stock-logistics-reservation-18.0/stock-logistics-reservation-18.0-stock_available_to_promise_release
Translate-URL: https://translation.odoo-community.org/projects/stock-logistics-reservation-18-0/stock-logistics-reservation-18-0-stock_available_to_promise_release/it/
When the rule is configured for a fixed procurement group, a procurement
group must be provided
- Fix query for horizon
- Fix split of move, ensure need_release is propagated
The procure_method is propagated since odoo v15.0. This is now obsolete.
Ensure released moves are not marked anymore as need_release before defer is called
On run_defer, _action_confirm already calls _action_assign, no need to run it a second time
Currently translated at 100.0% (90 of 90 strings)

Translation: stock-logistics-reservation-18.0/stock-logistics-reservation-18.0-stock_available_to_promise_release
Translate-URL: https://translation.odoo-community.org/projects/stock-logistics-reservation-18-0/stock-logistics-reservation-18-0-stock_available_to_promise_release/it/
@vvrossem
vvrossem force-pushed the 19.0-mig-stock_available_to_promise_release branch from 3f22ec8 to 2f3625f Compare October 19, 2026 13:27
@vvrossem
vvrossem force-pushed the 19.0-mig-stock_available_to_promise_release branch 3 times, most recently from 60c3f06 to 06d902f Compare October 20, 2026 10:39
@vvrossem
vvrossem marked this pull request as ready for review October 20, 2026 10:41
"=": py_operator.eq,
"!=": py_operator.ne,
"in": lambda qty, values: qty in values,
"not in": lambda qty, values: qty not in values,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why drop = and !=?

@vvrossem vvrossem Oct 26, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

In 19.0 the ORM turns =/!= into in/not in before calling a search method (fields.py#L145-L150). It only falls back to =/!= when the method returns NotImplemented for in/not in (domains.py#L1038-L1043), which this one doesn't.

In odoo shell:

Move = env.registry["stock.move"]
orig = Move._search_ordered_available_to_promise_uom_qty

def spy(self, operator, value):
    print(operator, value)
    return orig(self, operator, value)

Move._search_ordered_available_to_promise_uom_qty = spy
env["stock.move"].search([("ordered_available_to_promise_uom_qty", "=", 5)])
env["stock.move"].search([("ordered_available_to_promise_uom_qty", "!=", 5)])
in OrderedSet([5])
not in OrderedSet([5])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

cool, thanks!

"move_name": move.name,
"done_move_names": ", ".join(done_moves.mapped("name")),
"move_name": move.product_id.display_name,
"done_move_names": done_moves.mapped("reference"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

", ".join is lost from done_move_names, and it made sense to properly display multiple values.. no?

@vvrossem vvrossem Oct 26, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

env._ formats list arguments itself, with the language's separators (translate.py#L440-L448).

In odoo shell:

msg = "You cannot unrelease the move %(move_name)s because some origin moves %(done_move_names)s are done"
env._(msg, move_name="Desk", done_move_names=["WH/PICK/00001", "WH/PICK/00002"])
You cannot unrelease the move Desk because some origin moves WH/PICK/00001 and WH/PICK/00002 are done

In French the list becomes WH/PICK/00001 et WH/PICK/00002.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NICEEE!
I didn't know this one

Comment thread stock_available_to_promise_release/models/stock_move.py Outdated
@vvrossem
vvrossem force-pushed the 19.0-mig-stock_available_to_promise_release branch from 06d902f to f35aecd Compare October 26, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mod:stock_available_to_promise_release Module stock_available_to_promise_release series:19.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.