Skip to content

[16.0][FIX] mrp_multi_level: Fix MRP wizard logic#1683

Open
danielalvaro1999 wants to merge 1 commit intoOCA:16.0from
factorlibre:16.0-fix-mrp_multi_level
Open

[16.0][FIX] mrp_multi_level: Fix MRP wizard logic#1683
danielalvaro1999 wants to merge 1 commit intoOCA:16.0from
factorlibre:16.0-fix-mrp_multi_level

Conversation

@danielalvaro1999
Copy link
Copy Markdown

@danielalvaro1999 danielalvaro1999 commented Dec 24, 2025

[16.0][FIX] mrp_multi_level: Fix MRP wizard logic (PR #1683)

Why

Previously, the MRP Multi Level procurement wizard did not reliably allow users to explicitly choose the procurement method (manufacture/buy/pull/push/pull_push): the actual rule/action used at execution time could be driven by routes/rules and end up different from what the user intended.

This change makes the resulting procurement behavior consistent with the user’s explicit selection.

What changed

1) Wizard: propagate the selected method + keep better context

In mrp_multi_level/wizards/mrp_inventory_procure.py:

  • Each wizard line now carries extra context to behave correctly depending on where the wizard was opened from:
    • source_context (planned order vs inventory)
    • original_qty (used to keep quantities stable when changing the UoM)
  • The selected procurement method is propagated through values["mrp_action"] so the downstream rule selection can use it.

2) New file: procurement group override (new flow)

Added mrp_multi_level/models/procurement_group.py:

  • Override procurement.group._get_rule() to respect mrp_action when it is provided in values.
  • The search is deterministic and safer for multi-company setups:
    • Filters by company_id in [company, False]
    • Uses order="sequence" to pick the highest priority rule
    • Tries destination location first, then falls back to warehouse

3) Multi-currency POs: avoid cross-currency draft reuse

In mrp_multi_level/models/stock_rule.py:

  • Include currency_id in the draft PO lookup domain when present in values.
  • Force the PO currency when currency_id is provided (supports both a single dict and grouped values).

4) UI: make the method clearly editable

In mrp_multi_level/wizards/mrp_inventory_procure_views.xml:

  • Keep wizard lines editable (editable="top").
  • Show vendor/currency columns only when the line action is buy.
  • Add an info message encouraging users to adjust the procurement method per line before executing.

5) Planned orders: clearer action + direct entrypoint

In mrp_multi_level/views/mrp_planned_order_views.xml:

  • Improve mrp_action visibility using a badge + decorations.
  • Add a “Create Procurement” button on the planned order form to open the wizard.

6) Change supply_method string:

  • Changed the string to “default supply method” instead of “supply method”, to avoid confusion between the mrp_action field and supply_method.ange stringo to Default supply method

7) Tests

In mrp_multi_level/tests/test_mrp_multi_level.py:

  • Added/updated tests covering the new wizard flows and the fact that _get_rule() respects mrp_action.

Modified files

  • mrp_multi_level/models/__init__.py
  • mrp_multi_level/models/procurement_group.py (new)
  • mrp_multi_level/models/mrp_inventory.py
  • mrp_multi_level/models/stock_rule.py
  • mrp_multi_level/models/product_mrp_area.py
  • mrp_multi_level/wizards/mrp_inventory_procure.py
  • mrp_multi_level/wizards/mrp_inventory_procure_views.xml
  • mrp_multi_level/views/mrp_planned_order_views.xml
  • mrp_multi_level/tests/test_mrp_multi_level.py

@OCA-git-bot
Copy link
Copy Markdown
Contributor

Hi @ChrisOForgeFlow, @LoisRForgeFlow, @JordiBForgeFlow,
some modules you are maintaining are being modified, check this out!

Copy link
Copy Markdown

@AdrianaSaiz AdrianaSaiz left a comment

Choose a reason for hiding this comment

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

Code Review Changes Summary:

  1. Editable mrp_action field: Users can now change the procurement method
    (buy, manufacture, pull, pull_push) before executing. The selected method
    is respected via ProcurementGroup._get_rule() override in stock_rule.py.

  2. Context-aware quantity handling: Added 'source_context' and 'original_qty'
    fields to prevent quantity reset when opening wizard from planned orders.

    • From inventory: recalculates qty from mrp_inventory.to_procure
    • From planned order: preserves original planned quantity
  3. Company and currency support: Added company_id and currency_id to
    procurement values to avoid errors during PO/MO creation and to support
    multi-currency purchases.

  4. Vendor pre-fill for purchases: When mrp_action='buy', the wizard now
    pre-fills supplier_id and currency_id from product's seller info.

  5. Tests added (test_mrp_multi_level.py):

    • test_26: Override to 'buy' creates PO
    • test_31: _get_rule respects mrp_action parameter
    • test_32: UoM onchange fallback to original_qty
    • test_33: Override to 'manufacture' creates MO
    • test_34: Override to 'pull' creates picking
    • test_35: Override to 'pull_push' creates picking
      Note: 'push' action not tested as it works on existing moves, not procurements.

Comment on lines +287 to +300
domain = [
("action", "=", self.mrp_action),
("location_dest_id", "=", location.id),
]

rule = self.env["stock.rule"].search(domain, limit=1)

if not rule:
domain2 = [
("action", "=", self.mrp_action),
("warehouse_id", "=", warehouse.id),
]
rule = self.env["stock.rule"].search(domain2, limit=1)
return rule
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
domain = [
("action", "=", self.mrp_action),
("location_dest_id", "=", location.id),
]
rule = self.env["stock.rule"].search(domain, limit=1)
if not rule:
domain2 = [
("action", "=", self.mrp_action),
("warehouse_id", "=", warehouse.id),
]
rule = self.env["stock.rule"].search(domain2, limit=1)
return rule
company = self.warehouse_id.company_id or self.env.company
domain = [
("action", "=", self.mrp_action),
("location_dest_id", "=", location.id),
("company_id", "in", [company.id, False]),
]
rule = self.env["stock.rule"].search(domain, order="sequence", limit=1)
if not rule:
domain2 = [
("action", "=", self.mrp_action),
("warehouse_id", "=", self.warehouse_id.id),
("company_id", "in", [company.id, False]),
]
rule = self.env["stock.rule"].search(domain2, order="sequence", limit=1)

Si hay varias reglas con el mismo action (ej: dos reglas "buy"), limit=1 sin order puede devolver cualquiera. Mejor añadir order="sequence" para que nos devuelva la de mayor prioridad.
En entorno multi-company podría devolver una regla de otra empresa. Por eso habría que añadir filtro por company_id.

Comment thread mrp_multi_level/models/stock_rule.py
Comment thread mrp_multi_level/wizards/mrp_inventory_procure.py
Comment on lines +303 to +346
company = self.warehouse_id.company_id or self.env.company
currency = company.currency_id

res = {
"date_planned": self.date_planned,
"warehouse_id": self.warehouse_id,
"group_id": group,
"planned_order_id": self.planned_order_id.id,
"company_id": company,
"currency_id": currency.id,
}
should_add_supplier = (self.mrp_action == "buy") or (
not self.mrp_action and self.supply_method == "buy"
)

if should_add_supplier:
qty = self.qty or 0.0
uom = self.uom_id or self.product_id.uom_id
supplier_info = self.product_id._select_seller(
quantity=qty,
date=self.date_planned,
uom_id=uom,
partner_id=self.supplier_id,
)
if not supplier_info:
supplier_info = self.product_id.seller_ids.filtered(
lambda s: (not s.company_id or s.company_id == company)
).sorted(lambda s: (s.sequence, s.min_qty, s.price))[:1]

if supplier_info:
res["supplier_id"] = supplier_info.partner_id.id
res["currency_id"] = (
supplier_info.currency_id or company.currency_id
).id
else:
res["currency_id"] = company.currency_id.id

if hasattr(self, "mrp_action") and self.mrp_action:
res["mrp_action"] = self.mrp_action

rule = self._get_procurement_rule()
if rule:
res["rule_id"] = rule.id
return res
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
company = self.warehouse_id.company_id or self.env.company
currency = company.currency_id
res = {
"date_planned": self.date_planned,
"warehouse_id": self.warehouse_id,
"group_id": group,
"planned_order_id": self.planned_order_id.id,
"company_id": company,
"currency_id": currency.id,
}
should_add_supplier = (self.mrp_action == "buy") or (
not self.mrp_action and self.supply_method == "buy"
)
if should_add_supplier:
qty = self.qty or 0.0
uom = self.uom_id or self.product_id.uom_id
supplier_info = self.product_id._select_seller(
quantity=qty,
date=self.date_planned,
uom_id=uom,
partner_id=self.supplier_id,
)
if not supplier_info:
supplier_info = self.product_id.seller_ids.filtered(
lambda s: (not s.company_id or s.company_id == company)
).sorted(lambda s: (s.sequence, s.min_qty, s.price))[:1]
if supplier_info:
res["supplier_id"] = supplier_info.partner_id.id
res["currency_id"] = (
supplier_info.currency_id or company.currency_id
).id
else:
res["currency_id"] = company.currency_id.id
if hasattr(self, "mrp_action") and self.mrp_action:
res["mrp_action"] = self.mrp_action
rule = self._get_procurement_rule()
if rule:
res["rule_id"] = rule.id
return res
company = self.warehouse_id.company_id or self.env.company
currency = company.currency_id
res = {
"date_planned": self.date_planned,
"warehouse_id": self.warehouse_id,
"group_id": group,
"planned_order_id": self.planned_order_id.id,
"company_id": company,
"currency_id": currency.id,
}
# Añadir mrp_action para que _get_rule lo use
if self.mrp_action:
res["mrp_action"] = self.mrp_action
should_add_supplier = (self.mrp_action == "buy") or (
not self.mrp_action and self.supply_method == "buy"
)
if should_add_supplier:
qty = self.qty or 0.0
uom = self.uom_id or self.product_id.uom_id
supplier_info = self.product_id._select_seller(
quantity=qty,
date=self.date_planned,
uom_id=uom,
partner_id=self.supplier_id,
)
if not supplier_info:
supplier_info = self.product_id.seller_ids.filtered(
lambda s: (not s.company_id or s.company_id == company)
).sorted(lambda s: (s.sequence, s.min_qty, s.price))[:1]
if supplier_info:
res["supplierinfo_id"] = supplier_info
res["currency_id"] = (
supplier_info.currency_id or company.currency_id
).id
else:
res["currency_id"] = company.currency_id.id
return res

En este punto la llamada a _get_procurement_rule() y la asignación de rule_id en los values no son necesarias. Solo necesitamots añadir mrp_action a los values para que _get_rule lo use.
Ya no necesitamos buscar la regla aquí ni pasarla en los values. Con la herencia de _get_rule, la regla correcta se selecciona automáticamente dentro del flujo de procurement cuando lee el mrp_action. Solo tenemos que pasarle el mrp_action y Odoo hace el resto.

Comment on lines +278 to +300
def _get_procurement_rule(self):
self.ensure_one()

if not self.mrp_action or self.mrp_action == "none":
return False

location = self.location_id
warehouse = self.warehouse_id

domain = [
("action", "=", self.mrp_action),
("location_dest_id", "=", location.id),
]

rule = self.env["stock.rule"].search(domain, limit=1)

if not rule:
domain2 = [
("action", "=", self.mrp_action),
("warehouse_id", "=", warehouse.id),
]
rule = self.env["stock.rule"].search(domain2, limit=1)
return rule
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
def _get_procurement_rule(self):
self.ensure_one()
if not self.mrp_action or self.mrp_action == "none":
return False
location = self.location_id
warehouse = self.warehouse_id
domain = [
("action", "=", self.mrp_action),
("location_dest_id", "=", location.id),
]
rule = self.env["stock.rule"].search(domain, limit=1)
if not rule:
domain2 = [
("action", "=", self.mrp_action),
("warehouse_id", "=", warehouse.id),
]
rule = self.env["stock.rule"].search(domain2, limit=1)
return rule

Esta método hay que eliminarlo, ya no es necesario porque la lógica va a estar en _get_rule del procurement group.

Comment on lines +1078 to +1153
def test_31_procure_wizard_forced_rule_pull_calls_run_pull(self):
"""Covers the branch where a forced rule triggers `_run_pull`."""
mrp_inv = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.fp_1.id)], limit=1
)
self.assertTrue(mrp_inv)

wiz = self.mrp_inventory_procure_wiz.with_context(
active_model="mrp.inventory",
active_ids=mrp_inv.ids,
active_id=mrp_inv.id,
).create({})
self.assertTrue(wiz.item_ids)
item = wiz.item_ids[0]
item.qty = 1.0

route = self.env["stock.route"].create(
{
"name": "Test route (mrp_multi_level)",
"product_selectable": False,
"warehouse_selectable": False,
"company_id": self.company.id,
}
)

picking_type = item.warehouse_id.in_type_id
self.assertTrue(picking_type)

rule = self.env["stock.rule"].create(
{
"name": "Test pull rule (mrp_multi_level)",
"route_id": route.id,
"action": "pull",
"location_src_id": item.location_id.id,
"location_dest_id": item.location_id.id,
"picking_type_id": picking_type.id,
"company_id": self.company.id,
}
)

calls = {"count": 0, "args": None}

ItemModel = type(item)
RuleModel = type(rule)
PGModel = type(self.env["procurement.group"])

original_prepare = ItemModel._prepare_procurement_values
original_run_pull = RuleModel._run_pull
original_pg_run = PGModel.run

def _fake_prepare_procurement_values(_self):
vals = original_prepare(_self)
vals["rule_id"] = rule.id
return vals

def _fake_run_pull(_self, procurements):
calls["count"] += 1
calls["args"] = procurements
return True

def _fake_pg_run(_self, procurements):
raise AssertionError("Shouldn't call procurement.group.run()")

try:
ItemModel._prepare_procurement_values = _fake_prepare_procurement_values
RuleModel._run_pull = _fake_run_pull
PGModel.run = _fake_pg_run
wiz.make_procurement()
finally:
ItemModel._prepare_procurement_values = original_prepare
RuleModel._run_pull = original_run_pull
PGModel.run = original_pg_run

self.assertEqual(calls["count"], 1)
self.assertTrue(calls["args"])
self.assertEqual(calls["args"][0][1].id, rule.id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
def test_31_procure_wizard_forced_rule_pull_calls_run_pull(self):
"""Covers the branch where a forced rule triggers `_run_pull`."""
mrp_inv = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.fp_1.id)], limit=1
)
self.assertTrue(mrp_inv)
wiz = self.mrp_inventory_procure_wiz.with_context(
active_model="mrp.inventory",
active_ids=mrp_inv.ids,
active_id=mrp_inv.id,
).create({})
self.assertTrue(wiz.item_ids)
item = wiz.item_ids[0]
item.qty = 1.0
route = self.env["stock.route"].create(
{
"name": "Test route (mrp_multi_level)",
"product_selectable": False,
"warehouse_selectable": False,
"company_id": self.company.id,
}
)
picking_type = item.warehouse_id.in_type_id
self.assertTrue(picking_type)
rule = self.env["stock.rule"].create(
{
"name": "Test pull rule (mrp_multi_level)",
"route_id": route.id,
"action": "pull",
"location_src_id": item.location_id.id,
"location_dest_id": item.location_id.id,
"picking_type_id": picking_type.id,
"company_id": self.company.id,
}
)
calls = {"count": 0, "args": None}
ItemModel = type(item)
RuleModel = type(rule)
PGModel = type(self.env["procurement.group"])
original_prepare = ItemModel._prepare_procurement_values
original_run_pull = RuleModel._run_pull
original_pg_run = PGModel.run
def _fake_prepare_procurement_values(_self):
vals = original_prepare(_self)
vals["rule_id"] = rule.id
return vals
def _fake_run_pull(_self, procurements):
calls["count"] += 1
calls["args"] = procurements
return True
def _fake_pg_run(_self, procurements):
raise AssertionError("Shouldn't call procurement.group.run()")
try:
ItemModel._prepare_procurement_values = _fake_prepare_procurement_values
RuleModel._run_pull = _fake_run_pull
PGModel.run = _fake_pg_run
wiz.make_procurement()
finally:
ItemModel._prepare_procurement_values = original_prepare
RuleModel._run_pull = original_run_pull
PGModel.run = original_pg_run
self.assertEqual(calls["count"], 1)
self.assertTrue(calls["args"])
self.assertEqual(calls["args"][0][1].id, rule.id)
def test_31_get_rule_respects_mrp_action(self):
"""Test that _get_rule returns rule matching mrp_action when provided."""
mrp_inv = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.fp_1.id)], limit=1
)
self.assertTrue(mrp_inv)
wiz = self.mrp_inventory_procure_wiz.with_context(
active_model="mrp.inventory",
active_ids=mrp_inv.ids,
active_id=mrp_inv.id,
).create({})
self.assertTrue(wiz.item_ids)
item = wiz.item_ids[0]
route = self.env["stock.route"].create(
{
"name": "Test route (mrp_multi_level)",
"product_selectable": False,
"warehouse_selectable": False,
"company_id": self.company.id,
}
)
picking_type = item.warehouse_id.in_type_id
pull_rule = self.env["stock.rule"].create(
{
"name": "Test pull rule (mrp_multi_level)",
"route_id": route.id,
"action": "pull",
"location_src_id": self.supplier_location.id,
"location_dest_id": item.location_id.id,
"picking_type_id": picking_type.id,
"company_id": self.company.id,
}
)
# Veriffy _get_rule returns pull rule when mrp_action='pull'
pg = self.env["procurement.group"]
values = {
"mrp_action": "pull",
"company_id": self.company,
"warehouse_id": item.warehouse_id,
}
found_rule = pg._get_rule(item.product_id, item.location_id, values)
self.assertEqual(found_rule.id, pull_rule.id)
self.assertEqual(found_rule.action, "pull")

Reescribir test para verificar el nuevo comportamiento: que _get_rule respeta el mrp_action pasado en los values y devuelve la regla correcta. El test anterior verificaba que se llamaba directamente a _run_pull, lo cual ya no aplica.

Comment thread mrp_multi_level/tests/test_mrp_multi_level.py
@danielalvaro1999 danielalvaro1999 marked this pull request as ready for review December 29, 2025 16:10
Comment on lines +8 to +43
def _get_rule(self, product_id, location_id, values):
"""Override to respect mrp_action from MRP Multi Level wizard."""
mrp_action = values.get("mrp_action")
if mrp_action and mrp_action not in ("none", False):
company = values.get("company_id", self.env.company)
if hasattr(company, "id"):
company_id = company.id
else:
company_id = company
rule = self.env["stock.rule"].search(
[
("action", "=", mrp_action),
("location_dest_id", "=", location_id.id),
("company_id", "in", [company_id, False]),
],
order="sequence",
limit=1,
)
if not rule:
warehouse = values.get("warehouse_id")
if warehouse:
warehouse_id = (
warehouse.id if hasattr(warehouse, "id") else warehouse
)
rule = self.env["stock.rule"].search(
[
("action", "=", mrp_action),
("warehouse_id", "=", warehouse_id),
("company_id", "in", [company_id, False]),
],
order="sequence",
limit=1,
)
if rule:
return rule
return super()._get_rule(product_id, location_id, 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.

Suggested change
def _get_rule(self, product_id, location_id, values):
"""Override to respect mrp_action from MRP Multi Level wizard."""
mrp_action = values.get("mrp_action")
if mrp_action and mrp_action not in ("none", False):
company = values.get("company_id", self.env.company)
if hasattr(company, "id"):
company_id = company.id
else:
company_id = company
rule = self.env["stock.rule"].search(
[
("action", "=", mrp_action),
("location_dest_id", "=", location_id.id),
("company_id", "in", [company_id, False]),
],
order="sequence",
limit=1,
)
if not rule:
warehouse = values.get("warehouse_id")
if warehouse:
warehouse_id = (
warehouse.id if hasattr(warehouse, "id") else warehouse
)
rule = self.env["stock.rule"].search(
[
("action", "=", mrp_action),
("warehouse_id", "=", warehouse_id),
("company_id", "in", [company_id, False]),
],
order="sequence",
limit=1,
)
if rule:
return rule
return super()._get_rule(product_id, location_id, values)
def _get_rule(self, product_id, location_id, values):
"""Override to respect mrp_action from MRP Multi Level wizard."""
mrp_action = values.get("mrp_action")
# If there is no valid mrp_action (None, False, 'none', ''), delegate to super
if not mrp_action or mrp_action in ("none", False):
return super()._get_rule(product_id, location_id, values)
# Normalize company (accept record or id)
company = values.get("company_id", self.env.company)
company_id = company.id if hasattr(company, "id") else company
domain = [
("action", "=", mrp_action),
("company_id", "in", [company_id, False]),
]
rule = self.env["stock.rule"].search(
domain + [("location_dest_id", "=", location_id.id)],
order="sequence",
limit=1,
)
if not rule:
warehouse = values.get("warehouse_id")
if warehouse:
warehouse_id = (
warehouse.id if hasattr(warehouse, "id") else warehouse
)
rule = self.env["stock.rule"].search(
domain + [("warehouse_id", "=", warehouse_id)],
order="sequence",
limit=1,
)
return rule or super()._get_rule(product_id, location_id, values)

@danielalvaro1999 danielalvaro1999 force-pushed the 16.0-fix-mrp_multi_level branch 8 times, most recently from 395fc70 to 204d148 Compare January 9, 2026 12:24
Copy link
Copy Markdown

@AdrianaSaiz AdrianaSaiz left a comment

Choose a reason for hiding this comment

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

LGTM

@AdrianaSaiz AdrianaSaiz force-pushed the 16.0-fix-mrp_multi_level branch 7 times, most recently from 8b8a419 to 4ea3cd3 Compare January 28, 2026 17:03
…antity reset, and missing company/currency data
@AdrianaSaiz AdrianaSaiz force-pushed the 16.0-fix-mrp_multi_level branch 7 times, most recently from 5024b75 to 697f2b8 Compare January 28, 2026 19:39
@AdrianaSaiz AdrianaSaiz force-pushed the 16.0-fix-mrp_multi_level branch from 697f2b8 to 5024b75 Compare January 28, 2026 19:48
@danielalvaro1999 danielalvaro1999 changed the title [FIX] mrp_multi_level : Fix MRP wizard logic [16.0][FIX] mrp_multi_level: Fix MRP wizard logic Jan 29, 2026
Copy link
Copy Markdown

@AdrianaSaiz AdrianaSaiz left a comment

Choose a reason for hiding this comment

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

LGTM

@alvaro-gmz
Copy link
Copy Markdown

LGTM

Copy link
Copy Markdown
Contributor

@LoisRForgeFlow LoisRForgeFlow left a comment

Choose a reason for hiding this comment

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

Hi @danielalvaro1999

First of all thanks for contributing. This is a big change for an stable version that is going to be hard for me to merge as is. Here there is a mix of technical fixes and functionality changes.

If you could split this in smaller PRs, particularly if you keep the group override and method selection aside for a moment, we could first focus on the "pure fixes" that are going to be easier to discuss.

Thanks!

@AdrianaSaiz
Copy link
Copy Markdown

Hi @LoisRForgeFlow

Thanks for the feedback!

The main issue here is that the wizard was ignoring the user's action selection entirely. Regardless of which action the user chose (buy, manufacture, etc.), the system would always follow the product's default route instead.

All changes in this PR work together to fix this behavior:

  • The group override passes the selected action through the procurement process
  • The method selection ensures that action is actually applied

These changes are interdependent - applying them separately would leave the wizard partially broken.

Happy to improve the commit messages or add documentation if that would help with the review.

Thanks!

@LoisRForgeFlow
Copy link
Copy Markdown
Contributor

Hi @AdrianaSaiz

In the PR description you enumerate the changes, based on that and without doing any code review yet:

  1. Wizard: propagate the selected method + keep better context
  2. New file: procurement group override (new flow)

These 2 are behavior changes. I would like a more functional description of what is the goal or need behind those changes.

  1. Multi-currency POs: avoid cross-currency draft reuse
    Looks like a pure fix of multi-currency RfQ handling.

  2. UI: make the method clearly editable
    This is a behavior change as well. I guess also related to 1 and 2.

  3. Planned orders: clearer action + direct entrypoint
    This is a change independent from the others, being able to open the procure wizard from the planned order. Right?

  4. Change supply_method string:
    This is completely residual and can be implied in 1 and 2 I guess.

A PR like this with the title "Fix MRP wizard logic" is a bit hard to digest. I miss a more functional description of the objectives and reasoning for the changes.

I hope you can help me a bit here, I'm trying to find a way to be able to review your changes without spending a lot of time which is going to be difficult for me if I don't understand the motivation clearly.

Thanks!

Copy link
Copy Markdown

@marcos-mendez marcos-mendez left a comment

Choose a reason for hiding this comment

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

Automated Review -- Tests Failed

1. Root Cause

The test failure is caused by a missing mrp_action field definition in the mrp.inventory.procure.item model, which is referenced in the procurement_group.py override of _get_rule(). This leads to an AttributeError when trying to access values.get("mrp_action") because the field is not defined in the model.

2. Suggested Fix

Add the mrp_action field to the MrpInventoryProcureItem model in mrp_multi_level/models/mrp_inventory_procure_item.py:

# File: mrp_multi_level/models/mrp_inventory_procure_item.py

from odoo import fields, models

class MrpInventoryProcureItem(models.TransientModel):
    _name = "mrp.inventory.procure.item"
    _description = "MRP Inventory Procure Item"

    # Existing fields...
    mrp_action = fields.Selection(
        selection=[
            ("none", "None"),
            ("buy", "Purchase Order"),
            ("manufacture", "Manufacturing Order"),
            ("pull", "Pull From"),
            ("push", "Push To"),
            ("pull_push", "Pull & Push"),
        ],
        string="Procurement Method",
        help="Method to use for procurement. Can be modified before executing.",
    )
    # ... rest of the model

3. Additional Code Issues

  • Missing model definition: The mrp_inventory_procure_item.py model file is not present in the diff, but the code assumes its existence. Ensure this file exists with proper field definitions.
  • Inconsistent use of hasattr: In procurement_group.py, hasattr(company, "id") is used to check for id attribute. This is safe, but the code can be simplified to isinstance(company, models.BaseModel) for better readability and robustness.

4. Test Improvements

Add a test case to verify the field is correctly defined and accessible:

def test_36_procure_item_mrp_action_field_exists(self):
    """Test that mrp_action field exists and is correctly defined."""
    item = self.env["mrp.inventory.procure.item"].create({})
    self.assertTrue(hasattr(item, 'mrp_action'))
    self.assertEqual(
        item._fields['mrp_action'].type,
        'selection',
        "mrp_action should be a selection field"
    )

Also, consider using SavepointCase for tests that modify data to ensure isolation and rollback:

from odoo.tests import SavepointCase

class TestMrpMultiLevelProcureAction(SavepointCase):
    ...

This ensures tests are more robust and don't interfere with each other.


⏰ This PR has been open for 81 days.
💤 Last activity was 45 days ago.

Every ignored PR is a contributor who might not come back. Review time matters. (OCA Aging Report)


Reciprocal Review Request

Hi everyone! I found some test failures on this PR and left detailed feedback above. I am happy to discuss or help debug. In the meantime, if any of you get a chance, I would appreciate a look at my open PR(s):

My open PRs across OCA:

Reviewing each other's work helps the whole community move forward. Thank you!


Environment via OCA Neural Reviewer: Minikube + K8s Job + oca-ci/py3.10-odoo16.0 | Odoo 16.0
Automated review by OCA Neural Reviewer + qwen3-coder:30b

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.

7 participants