mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[IMP] mrp_multi_level:
* Planned Order release and due date become required. * Add button to Product MRP Area to update MOQ from Supplier Info. * Link Manufacturing Orders with Planned Orders. * Allow Mrp Inventory Procure Wizard to be used from other models. * Make MRP Inventory creation more extensible. * Main Supplier computation (v13 requires explicit False definitions).
This commit is contained in:
@@ -6,3 +6,5 @@ from . import mrp_move
|
|||||||
from . import mrp_planned_order
|
from . import mrp_planned_order
|
||||||
from . import mrp_inventory
|
from . import mrp_inventory
|
||||||
from . import product_mrp_area
|
from . import product_mrp_area
|
||||||
|
from . import stock_rule
|
||||||
|
from . import mrp_production
|
||||||
|
|||||||
@@ -37,10 +37,12 @@ class MrpPlannedOrder(models.Model):
|
|||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
order_release_date = fields.Date(
|
order_release_date = fields.Date(
|
||||||
string="Release Date", help="Order release date planned by MRP."
|
string="Release Date", help="Order release date planned by MRP.", required=True
|
||||||
)
|
)
|
||||||
due_date = fields.Date(
|
due_date = fields.Date(
|
||||||
string="Due Date", help="Date in which the supply must have been completed."
|
string="Due Date",
|
||||||
|
help="Date in which the supply must have been completed.",
|
||||||
|
required=True,
|
||||||
)
|
)
|
||||||
qty_released = fields.Float(readonly=True)
|
qty_released = fields.Float(readonly=True)
|
||||||
fixed = fields.Boolean(default=True)
|
fixed = fields.Boolean(default=True)
|
||||||
|
|||||||
12
mrp_multi_level/models/mrp_production.py
Normal file
12
mrp_multi_level/models/mrp_production.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||||
|
# - Héctor Villarreal <hector.villarreal@forgeflow.com>
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class MrpProduction(models.Model):
|
||||||
|
""" Manufacturing Orders """
|
||||||
|
|
||||||
|
_inherit = "mrp.production"
|
||||||
|
|
||||||
|
planned_order_id = fields.Many2one(comodel_name="mrp.planned.order")
|
||||||
@@ -171,9 +171,14 @@ class ProductMRPArea(models.Model):
|
|||||||
and (not r.company_id or r.company_id == rec.company_id)
|
and (not r.company_id or r.company_id == rec.company_id)
|
||||||
)
|
)
|
||||||
if not suppliers:
|
if not suppliers:
|
||||||
|
rec.main_supplierinfo_id = False
|
||||||
|
rec.main_supplier_id = False
|
||||||
continue
|
continue
|
||||||
rec.main_supplierinfo_id = suppliers[0]
|
rec.main_supplierinfo_id = suppliers[0]
|
||||||
rec.main_supplier_id = suppliers[0].name
|
rec.main_supplier_id = suppliers[0].name
|
||||||
|
for rec in self.filtered(lambda r: r.supply_method != "buy"):
|
||||||
|
rec.main_supplierinfo_id = False
|
||||||
|
rec.main_supplier_id = False
|
||||||
|
|
||||||
def _adjust_qty_to_order(self, qty_to_order):
|
def _adjust_qty_to_order(self, qty_to_order):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -191,3 +196,9 @@ class ProductMRPArea(models.Model):
|
|||||||
if self.mrp_maximum_order_qty and qty_to_order > self.mrp_maximum_order_qty:
|
if self.mrp_maximum_order_qty and qty_to_order > self.mrp_maximum_order_qty:
|
||||||
return self.mrp_maximum_order_qty
|
return self.mrp_maximum_order_qty
|
||||||
return qty_to_order
|
return qty_to_order
|
||||||
|
|
||||||
|
def update_min_qty_from_main_supplier(self):
|
||||||
|
for rec in self.filtered(
|
||||||
|
lambda r: r.main_supplierinfo_id and r.supply_method == "buy"
|
||||||
|
):
|
||||||
|
rec.mrp_minimum_order_qty = rec.main_supplierinfo_id.min_qty
|
||||||
|
|||||||
35
mrp_multi_level/models/stock_rule.py
Normal file
35
mrp_multi_level/models/stock_rule.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||||
|
# - Héctor Villarreal <hector.villarreal@forgeflow.com>
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class StockRule(models.Model):
|
||||||
|
_inherit = "stock.rule"
|
||||||
|
|
||||||
|
def _prepare_mo_vals(
|
||||||
|
self,
|
||||||
|
product_id,
|
||||||
|
product_qty,
|
||||||
|
product_uom,
|
||||||
|
location_id,
|
||||||
|
name,
|
||||||
|
origin,
|
||||||
|
company_id,
|
||||||
|
values,
|
||||||
|
bom,
|
||||||
|
):
|
||||||
|
res = super()._prepare_mo_vals(
|
||||||
|
product_id,
|
||||||
|
product_qty,
|
||||||
|
product_uom,
|
||||||
|
location_id,
|
||||||
|
name,
|
||||||
|
origin,
|
||||||
|
company_id,
|
||||||
|
values,
|
||||||
|
bom,
|
||||||
|
)
|
||||||
|
if "planned_order_id" in values:
|
||||||
|
res["planned_order_id"] = values["planned_order_id"]
|
||||||
|
return res
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
13.0.1.3.0 (2020-03-02)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* [IMP] Minor changes"
|
||||||
|
(`#470 <https://github.com/OCA/manufacture/pull/470>`_).
|
||||||
|
|
||||||
|
* Planned Order release and due date become required.
|
||||||
|
* Add button to Product MRP Area to update MOQ from Supplier Info.
|
||||||
|
* Link Manufacturing Orders with Planned Orders.
|
||||||
|
* Allow Mrp Inventory Procure Wizard to be used from other models.
|
||||||
|
* Make MRP Inventory creation more extensible.
|
||||||
|
* Main Supplier computation (v13 requires explicit False definitions)
|
||||||
|
|
||||||
13.0.1.2.0 (2020-02-20)
|
13.0.1.2.0 (2020-02-20)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
|||||||
[("product_id", "=", self.sf_1.id)]
|
[("product_id", "=", self.sf_1.id)]
|
||||||
)
|
)
|
||||||
self.assertEqual(product_mrp_area.supply_method, "manufacture")
|
self.assertEqual(product_mrp_area.supply_method, "manufacture")
|
||||||
|
self.assertFalse(product_mrp_area.main_supplier_id)
|
||||||
|
self.assertFalse(product_mrp_area.main_supplierinfo_id)
|
||||||
|
|
||||||
def test_03_mrp_moves(self):
|
def test_03_mrp_moves(self):
|
||||||
"""Tests for mrp moves generated."""
|
"""Tests for mrp moves generated."""
|
||||||
|
|||||||
@@ -53,7 +53,17 @@
|
|||||||
<field name="mrp_transit_delay" invisible="1"/>
|
<field name="mrp_transit_delay" invisible="1"/>
|
||||||
<field name="mrp_inspection_delay" invisible="1"/>
|
<field name="mrp_inspection_delay" invisible="1"/>
|
||||||
<field name="mrp_minimum_stock"/>
|
<field name="mrp_minimum_stock"/>
|
||||||
<field name="mrp_minimum_order_qty"/>
|
<label for="mrp_minimum_order_qty"/>
|
||||||
|
<div name="mrp_minimum_order_qty" class="o_row">
|
||||||
|
<field name="mrp_minimum_order_qty"/>
|
||||||
|
<span name="update_min_qty">
|
||||||
|
<button string="Get from main supplier" type="object"
|
||||||
|
name="update_min_qty_from_main_supplier"
|
||||||
|
attrs="{'invisible':[('supply_method', '!=', 'buy'), ('main_supplierinfo_id', '=', False)]}"
|
||||||
|
class="oe_link pt-0 oe_inline"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<field name="mrp_maximum_order_qty"/>
|
<field name="mrp_maximum_order_qty"/>
|
||||||
<field name="mrp_qty_multiple"/>
|
<field name="mrp_qty_multiple"/>
|
||||||
<field name="supply_method"/>
|
<field name="supply_method"/>
|
||||||
|
|||||||
@@ -47,21 +47,19 @@ class MrpInventoryProcure(models.TransientModel):
|
|||||||
@api.model
|
@api.model
|
||||||
def default_get(self, fields):
|
def default_get(self, fields):
|
||||||
res = super(MrpInventoryProcure, self).default_get(fields)
|
res = super(MrpInventoryProcure, self).default_get(fields)
|
||||||
mrp_inventory_obj = self.env["mrp.inventory"]
|
active_ids = self.env.context["active_ids"] or []
|
||||||
mrp_inventory_ids = self.env.context["active_ids"] or []
|
|
||||||
active_model = self.env.context["active_model"]
|
active_model = self.env.context["active_model"]
|
||||||
if not mrp_inventory_ids or "item_ids" not in fields:
|
if not active_ids or "item_ids" not in fields:
|
||||||
return res
|
return res
|
||||||
|
if active_model == "mrp.inventory":
|
||||||
assert active_model == "mrp.inventory", "Bad context propagation"
|
items = item_obj = self.env["mrp.inventory.procure.item"]
|
||||||
|
mrp_inventory_obj = self.env["mrp.inventory"]
|
||||||
items = item_obj = self.env["mrp.inventory.procure.item"]
|
for line in mrp_inventory_obj.browse(active_ids).mapped(
|
||||||
for line in mrp_inventory_obj.browse(mrp_inventory_ids).mapped(
|
"planned_order_ids"
|
||||||
"planned_order_ids"
|
):
|
||||||
):
|
if line.qty_released < line.mrp_qty:
|
||||||
if line.qty_released < line.mrp_qty:
|
items += item_obj.create(self._prepare_item(line))
|
||||||
items += item_obj.create(self._prepare_item(line))
|
res["item_ids"] = [(6, 0, items.ids)]
|
||||||
res["item_ids"] = [(6, 0, items.ids)]
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def make_procurement(self):
|
def make_procurement(self):
|
||||||
@@ -109,7 +107,7 @@ class MrpInventoryProcureItem(models.TransientModel):
|
|||||||
)
|
)
|
||||||
qty = fields.Float(string="Quantity")
|
qty = fields.Float(string="Quantity")
|
||||||
uom_id = fields.Many2one(string="Unit of Measure", comodel_name="uom.uom")
|
uom_id = fields.Many2one(string="Unit of Measure", comodel_name="uom.uom")
|
||||||
date_planned = fields.Date(string="Planned Date", required=False)
|
date_planned = fields.Date(string="Planned Date", required=True)
|
||||||
mrp_inventory_id = fields.Many2one(
|
mrp_inventory_id = fields.Many2one(
|
||||||
string="Mrp Inventory", comodel_name="mrp.inventory"
|
string="Mrp Inventory", comodel_name="mrp.inventory"
|
||||||
)
|
)
|
||||||
@@ -127,6 +125,7 @@ class MrpInventoryProcureItem(models.TransientModel):
|
|||||||
("push", "Push To"),
|
("push", "Push To"),
|
||||||
("pull_push", "Pull & Push"),
|
("pull_push", "Pull & Push"),
|
||||||
],
|
],
|
||||||
|
readonly=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _prepare_procurement_values(self, group=False):
|
def _prepare_procurement_values(self, group=False):
|
||||||
@@ -135,8 +134,8 @@ class MrpInventoryProcureItem(models.TransientModel):
|
|||||||
fields.Date.from_string(self.date_planned)
|
fields.Date.from_string(self.date_planned)
|
||||||
),
|
),
|
||||||
"warehouse_id": self.warehouse_id,
|
"warehouse_id": self.warehouse_id,
|
||||||
# 'company_id': self.company_id, # TODO: consider company
|
|
||||||
"group_id": group,
|
"group_id": group,
|
||||||
|
"planned_order_id": self.planned_order_id.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api.onchange("uom_id")
|
@api.onchange("uom_id")
|
||||||
|
|||||||
@@ -648,6 +648,33 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
params = {"mrp_product": product_mrp_area.id}
|
params = {"mrp_product": product_mrp_area.id}
|
||||||
return query, params
|
return query, params
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_mrp_inventory_data(
|
||||||
|
self,
|
||||||
|
product_mrp_area,
|
||||||
|
mdt,
|
||||||
|
on_hand_qty,
|
||||||
|
running_availability,
|
||||||
|
demand_qty_by_date,
|
||||||
|
supply_qty_by_date,
|
||||||
|
planned_qty_by_date,
|
||||||
|
):
|
||||||
|
"""Return dict to create mrp.inventory records on MRP Multi Level Scheduler"""
|
||||||
|
mrp_inventory_data = {"product_mrp_area_id": product_mrp_area.id, "date": mdt}
|
||||||
|
demand_qty = demand_qty_by_date.get(mdt, 0.0)
|
||||||
|
mrp_inventory_data["demand_qty"] = abs(demand_qty)
|
||||||
|
supply_qty = supply_qty_by_date.get(mdt, 0.0)
|
||||||
|
mrp_inventory_data["supply_qty"] = abs(supply_qty)
|
||||||
|
mrp_inventory_data["initial_on_hand_qty"] = on_hand_qty
|
||||||
|
on_hand_qty += supply_qty + demand_qty
|
||||||
|
mrp_inventory_data["final_on_hand_qty"] = on_hand_qty
|
||||||
|
# Consider that MRP plan is followed exactly:
|
||||||
|
running_availability += (
|
||||||
|
supply_qty + demand_qty + planned_qty_by_date.get(mdt, 0.0)
|
||||||
|
)
|
||||||
|
mrp_inventory_data["running_availability"] = running_availability
|
||||||
|
return mrp_inventory_data, running_availability
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _init_mrp_inventory(self, product_mrp_area):
|
def _init_mrp_inventory(self, product_mrp_area):
|
||||||
mrp_move_obj = self.env["mrp.move"]
|
mrp_move_obj = self.env["mrp.move"]
|
||||||
@@ -683,23 +710,15 @@ class MultiLevelMrp(models.TransientModel):
|
|||||||
)._product_available()[product_mrp_area.product_id.id]["qty_available"]
|
)._product_available()[product_mrp_area.product_id.id]["qty_available"]
|
||||||
running_availability = on_hand_qty
|
running_availability = on_hand_qty
|
||||||
for mdt in sorted(mrp_dates):
|
for mdt in sorted(mrp_dates):
|
||||||
mrp_inventory_data = {
|
mrp_inventory_data, running_availability = self._prepare_mrp_inventory_data(
|
||||||
"product_mrp_area_id": product_mrp_area.id,
|
product_mrp_area,
|
||||||
"date": mdt,
|
mdt,
|
||||||
}
|
on_hand_qty,
|
||||||
demand_qty = demand_qty_by_date.get(mdt, 0.0)
|
running_availability,
|
||||||
mrp_inventory_data["demand_qty"] = abs(demand_qty)
|
demand_qty_by_date,
|
||||||
supply_qty = supply_qty_by_date.get(mdt, 0.0)
|
supply_qty_by_date,
|
||||||
mrp_inventory_data["supply_qty"] = abs(supply_qty)
|
planned_qty_by_date,
|
||||||
mrp_inventory_data["initial_on_hand_qty"] = on_hand_qty
|
|
||||||
on_hand_qty += supply_qty + demand_qty
|
|
||||||
mrp_inventory_data["final_on_hand_qty"] = on_hand_qty
|
|
||||||
# Consider that MRP plan is followed exactly:
|
|
||||||
running_availability += (
|
|
||||||
supply_qty + demand_qty + planned_qty_by_date.get(mdt, 0.0)
|
|
||||||
)
|
)
|
||||||
mrp_inventory_data["running_availability"] = running_availability
|
|
||||||
|
|
||||||
inv_id = self.env["mrp.inventory"].create(mrp_inventory_data)
|
inv_id = self.env["mrp.inventory"].create(mrp_inventory_data)
|
||||||
# attach planned orders to inventory
|
# attach planned orders to inventory
|
||||||
planned_order_obj.search(
|
planned_order_obj.search(
|
||||||
|
|||||||
Reference in New Issue
Block a user