mrp_p_auto_validate: split qty on create

- split values for create in dedicated function
- post messages on MO with modified qty
- split MOs on create only on procurements
This commit is contained in:
Akim Juillerat
2023-01-10 18:16:09 +01:00
committed by Sébastien Alix
parent bf7e47056a
commit a8e4fa8ddb
4 changed files with 169 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
from . import mrp_bom
from . import mrp_production
from . import stock_picking
from . import stock_rule

View File

@@ -21,6 +21,7 @@ class MrpProduction(models.Model):
@api.constrains("bom_id", "auto_validate", "product_qty")
def check_bom_auto_validate(self):
for mo in self:
# FIXME: Handle different UOM between BOM and MO
qty_ok = (
tools.float_compare(
mo.product_qty,
@@ -91,3 +92,101 @@ class MrpProduction(models.Model):
)
wiz = wiz_model.create({})
return wiz.action_backorder()
@api.model_create_multi
def create(self, values_list):
new_values_list, messages_to_post = self.adapt_values_qty_for_auto_validation(
values_list
)
res = super().create(new_values_list)
if messages_to_post:
for pos, msg in messages_to_post.items():
prod = res[pos]
prod.message_post(body=msg)
return res
@api.model
def adapt_values_qty_for_auto_validation(self, values_list):
"""Adapt create values according to qty with auto validated BOM
If MOs are to be created with a BOM having auto validation, we must ensure
the quantity of the MO is equal to the quantity of the BOM.
However when MOs are created through procurements, the requested quantity
is based on the procurement quantity, so we should either
* increase the quantity to match the BOM if procurement value is lower
* split the values to create one MO into multiple values to create multiple
MOs matching the BOM quantity if procurement value is bigger
"""
messages_to_post = {}
if not self.env.context.get("_split_create_values_for_auto_validation"):
return values_list, messages_to_post
new_values_list = []
for values in values_list:
bom_id = values.get("bom_id")
if not bom_id:
new_values_list.append(values)
continue
bom = self.env["mrp.bom"].browse(bom_id)
if not bom.mo_auto_validation:
new_values_list.append(values)
continue
create_qty = values.get("product_qty")
create_uom = self.env["uom.uom"].browse(values.get("product_uom_id"))
bom_qty = bom.product_qty
bom_uom = bom.product_uom_id
if create_uom != bom_uom:
create_qty = create_uom._compute_quantity(create_qty, bom_uom)
if (
tools.float_compare(
create_qty, bom_qty, precision_rounding=bom_uom.rounding
)
== 0
):
new_values_list.append(values)
continue
elif (
tools.float_compare(
create_qty, bom_qty, precision_rounding=bom_uom.rounding
)
< 0
):
procure_qty = values.get("product_qty")
values["product_qty"] = bom_qty
values["product_uom_id"] = bom_uom.id
msg = _(
"Quantity in procurement (%s %s) was increased to %s %s due to auto "
"validation feature preventing to create an MO with a different "
"qty than defined on the BOM."
) % (
procure_qty,
create_uom.display_name,
bom_qty,
bom_uom.display_name,
)
messages_to_post[len(new_values_list)] = msg
new_values_list.append(values)
continue
# If we get here we need to split the prepared MO values
# into multiple MO values to respect BOM qty
while (
tools.float_compare(create_qty, 0, precision_rounding=bom_uom.rounding)
> 0
):
new_values = values.copy()
new_values["product_qty"] = bom_qty
new_values["product_uom_id"] = bom_uom.id
msg = _(
"Quantity in procurement (%s %s) was split to multiple production "
"orders of %s %s due to auto validation feature preventing to "
"set a quantity to produce different than the quantity defined "
"on the Bill of Materials."
) % (
values.get("product_qty"),
create_uom.display_name,
bom_qty,
bom_uom.display_name,
)
messages_to_post[len(new_values_list)] = msg
new_values_list.append(new_values)
create_qty -= bom_qty
return new_values_list, messages_to_post

View File

@@ -0,0 +1,13 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import api, models
class StockRule(models.Model):
_inherit = "stock.rule"
@api.model
def _run_manufacture(self, procurements):
return super(
StockRule, self.with_context(_split_create_values_for_auto_validation=True)
)._run_manufacture(procurements)

View File

@@ -14,10 +14,33 @@ class TestManufacturingOrderAutoValidate(SavepointCase):
# "pick components" transfer operation
cls.wh = cls.env.ref("stock.warehouse0")
cls.wh.manufacture_steps = "pbm"
# Configure the product to be replenished through manufacture route
cls.product_template = cls.env.ref(
"mrp.product_product_computer_desk_head_product_template"
)
cls.manufacture_route = cls.env.ref("mrp.route_warehouse0_manufacture")
cls.product_template.route_ids = [(6, 0, [cls.manufacture_route.id])]
# Configure the BoM to auto-validate manufacturing orders
# NOTE: to ease tests we take a BoM with only one component
cls.bom = cls.env.ref("mrp.mrp_bom_table_top") # Tracked by S/N
cls.bom.mo_auto_validation = True
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
@classmethod
def _replenish_product(cls, product, product_qty=1, product_uom=None):
if product_uom is None:
product_uom = cls.uom_unit
wiz = (
cls.env["product.replenish"]
.with_context(default_product_id=product.id)
.create(
{
"quantity": product_qty,
"product_uom_id": product_uom.id,
}
)
)
wiz.launch_replenishment()
@classmethod
def _create_manufacturing_order(cls, bom, product_qty=1):
@@ -139,3 +162,36 @@ class TestManufacturingOrderAutoValidate(SavepointCase):
self.assertFalse(order_done)
self.assertEqual(order.state, "confirmed")
self.assertEqual(order.product_qty, 2)
def test_keep_qty_on_replenishment(self):
existing_mos = self.env["mrp.production"].search([])
self._replenish_product(self.product_template.product_variant_id, product_qty=1)
created_mos = self.env["mrp.production"].search(
[("id", "not in", existing_mos.ids)]
)
self.assertEqual(len(created_mos), 1)
self.assertEqual(created_mos.product_qty, self.bom.product_qty)
self.assertFalse(any("split" in m.body for m in created_mos.message_ids))
self.assertFalse(any("increased" in m.body for m in created_mos.message_ids))
def test_split_qty_on_replenishment(self):
existing_mos = self.env["mrp.production"].search([])
self._replenish_product(self.product_template.product_variant_id, product_qty=3)
created_mos = self.env["mrp.production"].search(
[("id", "not in", existing_mos.ids)]
)
self.assertEqual(len(created_mos), 3)
for mo in created_mos:
self.assertEqual(mo.product_qty, self.bom.product_qty)
self.assertTrue(any("split" in m.body for m in mo.message_ids))
def test_raise_qty_on_replenishment(self):
existing_mos = self.env["mrp.production"].search([])
self.bom.product_qty = 5
self._replenish_product(self.product_template.product_variant_id, product_qty=3)
created_mos = self.env["mrp.production"].search(
[("id", "not in", existing_mos.ids)]
)
self.assertEqual(len(created_mos), 1)
self.assertEqual(created_mos.product_qty, self.bom.product_qty)
self.assertTrue(any("increased" in m.body for m in created_mos.message_ids))