Files
manufacture/mrp_multi_level/wizards/mrp_inventory_procure.py
Matt Taylor 33cf4af1ac [FIX] mrp_multi_level: fix kit/phantom planning
fixes #1362

Ignoring qty_available for phantom products prevents double counting the
qty_available of components.

Creating planned orders for phantom products is simpler than recursively
exploding phantom BOMs. This also makes it easier to analyze the planning data
generated by the MRP calculation.
2024-10-29 07:53:58 -06:00

152 lines
5.8 KiB
Python

# Copyright 2018-21 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class MrpInventoryProcure(models.TransientModel):
_name = "mrp.inventory.procure"
_description = "Make Procurements from MRP inventory projections"
item_ids = fields.One2many(
comodel_name="mrp.inventory.procure.item", inverse_name="wiz_id", string="Items"
)
@api.model
def _prepare_item(self, planned_order):
return {
"planned_order_id": planned_order.id,
"qty": planned_order.mrp_qty - planned_order.qty_released,
"uom_id": planned_order.mrp_inventory_id.uom_id.id,
"date_planned": planned_order.due_date,
"mrp_inventory_id": planned_order.mrp_inventory_id.id,
"product_id": planned_order.product_id.id,
"warehouse_id": planned_order.mrp_area_id.warehouse_id.id,
"location_id": planned_order.product_mrp_area_id.location_proc_id.id
or planned_order.mrp_area_id.location_id.id,
"supply_method": planned_order.product_mrp_area_id.supply_method,
}
@api.model
def fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
):
if self.user_has_groups("mrp_multi_level.group_change_mrp_procure_qty"):
view_id = self.env.ref(
"mrp_multi_level.view_mrp_inventory_procure_wizard"
).id
else:
view_id = self.env.ref(
"mrp_multi_level.view_mrp_inventory_procure_without_security"
).id
return super(MrpInventoryProcure, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
)
@api.model
def default_get(self, fields):
res = super(MrpInventoryProcure, self).default_get(fields)
active_ids = self.env.context["active_ids"] or []
active_model = self.env.context["active_model"]
if not active_ids or "item_ids" not in fields:
return res
items = item_obj = self.env["mrp.inventory.procure.item"]
if active_model == "mrp.inventory":
mrp_inventory_obj = self.env[active_model]
for line in mrp_inventory_obj.browse(active_ids).mapped(
"planned_order_ids"
):
if line.qty_released < line.mrp_qty:
items += item_obj.create(self._prepare_item(line))
elif active_model == "mrp.planned.order":
mrp_planned_order_obj = self.env[active_model]
for line in mrp_planned_order_obj.browse(active_ids):
if line.mrp_action == "phantom":
continue
if line.qty_released < line.mrp_qty:
items += item_obj.create(self._prepare_item(line))
if items:
res["item_ids"] = [(6, 0, items.ids)]
return res
def make_procurement(self):
self.ensure_one()
errors = []
pg = self.env["procurement.group"]
procurements = []
for item in self.item_ids:
if not item.qty:
raise ValidationError(_("Quantity must be positive."))
values = item._prepare_procurement_values()
procurements.append(
pg.Procurement(
item.product_id,
item.qty,
item.uom_id,
item.location_id,
"MRP: " + (item.planned_order_id.name or self.env.user.login),
"MRP: " + (item.planned_order_id.origin or self.env.user.login),
item.mrp_inventory_id.company_id,
values,
)
)
# Run procurements
try:
pg.run(procurements)
for item in self.item_ids:
item.planned_order_id.qty_released += item.qty
except UserError as error:
errors.append(error.name)
if errors:
raise UserError("\n".join(errors))
return {"type": "ir.actions.act_window_close"}
class MrpInventoryProcureItem(models.TransientModel):
_name = "mrp.inventory.procure.item"
_description = "MRP Inventory procure item"
wiz_id = fields.Many2one(
comodel_name="mrp.inventory.procure",
string="Wizard",
ondelete="cascade",
readonly=True,
)
qty = fields.Float(string="Quantity")
uom_id = fields.Many2one(string="Unit of Measure", comodel_name="uom.uom")
date_planned = fields.Date(string="Planned Date", required=True)
mrp_inventory_id = fields.Many2one(
string="Mrp Inventory", comodel_name="mrp.inventory"
)
planned_order_id = fields.Many2one(comodel_name="mrp.planned.order")
product_id = fields.Many2one(string="Product", comodel_name="product.product")
warehouse_id = fields.Many2one(string="Warehouse", comodel_name="stock.warehouse")
location_id = fields.Many2one(string="Location", comodel_name="stock.location")
supply_method = fields.Selection(
selection=[
("buy", "Buy"),
("none", "Undefined"),
("manufacture", "Produce"),
("pull", "Pull From"),
("push", "Push To"),
("pull_push", "Pull & Push"),
],
readonly=True,
)
def _prepare_procurement_values(self, group=False):
return {
"date_planned": self.date_planned,
"warehouse_id": self.warehouse_id,
"group_id": group,
"planned_order_id": self.planned_order_id.id,
}
@api.onchange("uom_id")
def onchange_uom_id(self):
for rec in self:
rec.qty = rec.mrp_inventory_id.uom_id._compute_quantity(
rec.mrp_inventory_id.to_procure, rec.uom_id
)