Files
manufacture/mrp_multi_level/models/product_mrp_area.py
2021-03-03 13:04:05 +01:00

194 lines
7.0 KiB
Python

# Copyright 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# Copyright 2016-19 ForgeFlow S.L. (https://www.forgeflow.com)
# - Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
# - Lois Rilo Antelo <lois.rilo@forgeflow.com>
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from math import ceil
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ProductMRPArea(models.Model):
_name = "product.mrp.area"
_description = "Product MRP Area"
active = fields.Boolean(default=True)
mrp_area_id = fields.Many2one(comodel_name="mrp.area", required=True)
company_id = fields.Many2one(
comodel_name="res.company",
related="mrp_area_id.warehouse_id.company_id",
store=True,
)
product_id = fields.Many2one(
comodel_name="product.product", required=True, string="Product"
)
product_tmpl_id = fields.Many2one(
comodel_name="product.template",
readonly=True,
related="product_id.product_tmpl_id",
store=True,
)
location_id = fields.Many2one(related="mrp_area_id.location_id")
location_proc_id = fields.Many2one(
string="Procure Location",
comodel_name="stock.location",
domain="[('location_id', 'child_of', location_id)]",
help="Set this if you need to procure from a different location"
"than area's location.",
)
# TODO: applicable and exclude... redundant??
mrp_applicable = fields.Boolean(string="MRP Applicable")
mrp_exclude = fields.Boolean(string="Exclude from MRP")
mrp_inspection_delay = fields.Integer(string="Inspection Delay")
mrp_maximum_order_qty = fields.Float(string="Maximum Order Qty", default=0.0)
mrp_minimum_order_qty = fields.Float(string="Minimum Order Qty", default=0.0)
mrp_minimum_stock = fields.Float(string="Safety Stock")
mrp_nbr_days = fields.Integer(
string="Nbr. Days",
default=0,
help="Number of days to group demand for this product during the "
"MRP run, in order to determine the quantity to order.",
)
mrp_qty_multiple = fields.Float(string="Qty Multiple", default=1.00)
mrp_transit_delay = fields.Integer(string="Transit Delay", default=0)
mrp_verified = fields.Boolean(
string="Verified for MRP",
help="Identifies that this product has been verified "
"to be valid for the MRP.",
)
mrp_lead_time = fields.Float(string="Lead Time", compute="_compute_mrp_lead_time")
main_supplier_id = fields.Many2one(
comodel_name="res.partner",
string="Main Supplier",
compute="_compute_main_supplier",
store=True,
index=True,
)
main_supplierinfo_id = fields.Many2one(
comodel_name="product.supplierinfo",
string="Supplier Info",
compute="_compute_main_supplier",
store=True,
)
supply_method = fields.Selection(
selection=[
("buy", "Buy"),
("none", "Undefined"),
("manufacture", "Produce"),
("pull", "Pull From"),
("push", "Push To"),
("pull_push", "Pull & Push"),
],
string="Supply Method",
compute="_compute_supply_method",
)
qty_available = fields.Float(
string="Quantity Available", compute="_compute_qty_available"
)
mrp_move_ids = fields.One2many(
comodel_name="mrp.move", inverse_name="product_mrp_area_id", readonly=True
)
planned_order_ids = fields.One2many(
comodel_name="mrp.planned.order",
inverse_name="product_mrp_area_id",
readonly=True,
)
_sql_constraints = [
(
"product_mrp_area_uniq",
"unique(product_id, mrp_area_id)",
"The product/MRP Area parameters combination must be unique.",
)
]
@api.constrains(
"mrp_minimum_order_qty",
"mrp_maximum_order_qty",
"mrp_qty_multiple",
"mrp_minimum_stock",
"mrp_nbr_days",
)
def _check_negatives(self):
values = self.read(
[
"mrp_minimum_order_qty",
"mrp_maximum_order_qty",
"mrp_qty_multiple",
"mrp_minimum_stock",
"mrp_nbr_days",
]
)
for rec in values:
if any(v < 0 for v in rec.values()):
raise ValidationError(_("You cannot use a negative number."))
def name_get(self):
return [
(
area.id,
"[{}] {}".format(area.mrp_area_id.name, area.product_id.display_name),
)
for area in self
]
def _compute_mrp_lead_time(self):
produced = self.filtered(lambda r: r.supply_method == "manufacture")
purchased = self.filtered(lambda r: r.supply_method == "buy")
for rec in produced:
rec.mrp_lead_time = rec.product_id.produce_delay
for rec in purchased:
rec.mrp_lead_time = rec.main_supplierinfo_id.delay
# TODO: 'move' supply method.
for rec in self - produced - purchased:
rec.mrp_lead_time = 0
def _compute_qty_available(self):
for rec in self:
rec.qty_available = rec.product_id.with_context(
{"location": rec.mrp_area_id.location_id.id}
).qty_available
def _compute_supply_method(self):
group_obj = self.env["procurement.group"]
for rec in self:
proc_loc = rec.location_proc_id or rec.mrp_area_id.location_id
values = {
"warehouse_id": rec.mrp_area_id.warehouse_id,
"company_id": self.env.user.company_id.id,
# TODO: better way to get company
}
rule = group_obj._get_rule(rec.product_id, proc_loc, values)
rec.supply_method = rule.action if rule else "none"
@api.depends("supply_method", "product_id.route_ids", "product_id.seller_ids")
def _compute_main_supplier(self):
"""Simplified and similar to procurement.rule logic."""
for rec in self.filtered(lambda r: r.supply_method == "buy"):
suppliers = rec.product_id.seller_ids.filtered(
lambda r: (not r.product_id or r.product_id == rec.product_id)
)
if not suppliers:
continue
rec.main_supplierinfo_id = suppliers[0]
rec.main_supplier_id = suppliers[0].name
def _adjust_qty_to_order(self, qty_to_order):
self.ensure_one()
if (
not self.mrp_maximum_order_qty
and not self.mrp_minimum_order_qty
and self.mrp_qty_multiple == 1.0
):
return qty_to_order
if qty_to_order < self.mrp_minimum_order_qty:
return self.mrp_minimum_order_qty
if self.mrp_qty_multiple:
multiplier = ceil(qty_to_order / self.mrp_qty_multiple)
qty_to_order = multiplier * self.mrp_qty_multiple
if self.mrp_maximum_order_qty and qty_to_order > self.mrp_maximum_order_qty:
return self.mrp_maximum_order_qty
return qty_to_order