mirror of
https://github.com/bringout/oca-mrp.git
synced 2026-04-23 22:32:04 +02:00
Initial commit: OCA Mrp packages (117 packages)
This commit is contained in:
commit
277e84fd7a
4403 changed files with 395154 additions and 0 deletions
|
|
@ -0,0 +1,11 @@
|
|||
from . import mrp_area
|
||||
from . import stock_location
|
||||
from . import product_product
|
||||
from . import product_template
|
||||
from . import mrp_move
|
||||
from . import mrp_planned_order
|
||||
from . import mrp_inventory
|
||||
from . import product_mrp_area
|
||||
from . import stock_rule
|
||||
from . import mrp_production
|
||||
from . import stock_quant
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# Copyright 2016-21 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 odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpArea(models.Model):
|
||||
_name = "mrp.area"
|
||||
_description = "MRP Area"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
warehouse_id = fields.Many2one(
|
||||
comodel_name="stock.warehouse", string="Warehouse", required=True
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company", related="warehouse_id.company_id", store=True
|
||||
)
|
||||
location_id = fields.Many2one(
|
||||
comodel_name="stock.location", string="Location", required=True
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
calendar_id = fields.Many2one(
|
||||
comodel_name="resource.calendar",
|
||||
string="Working Hours",
|
||||
related="warehouse_id.calendar_id",
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _datetime_to_date_tz(self, dt_to_convert=None):
|
||||
"""Coverts a datetime to date considering the timezone of MRP Area.
|
||||
If no datetime is provided, it returns today's date in the timezone."""
|
||||
return fields.Date.context_today(
|
||||
self.with_context(tz=self.calendar_id.tz),
|
||||
timestamp=dt_to_convert,
|
||||
)
|
||||
|
||||
def _get_locations(self):
|
||||
self.ensure_one()
|
||||
return self.location_id
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# Copyright 2016-21 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 datetime import date, timedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class MrpInventory(models.Model):
|
||||
_name = "mrp.inventory"
|
||||
_order = "product_mrp_area_id, date"
|
||||
_description = "MRP inventory projections"
|
||||
_rec_name = "product_mrp_area_id"
|
||||
|
||||
# TODO: name to pass to procurements?
|
||||
# TODO: compute procurement_date to pass to the wizard? not needed for
|
||||
# PO at least. Check for MO and moves
|
||||
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name="mrp.area",
|
||||
string="MRP Area",
|
||||
related="product_mrp_area_id.mrp_area_id",
|
||||
store=True,
|
||||
)
|
||||
product_mrp_area_id = fields.Many2one(
|
||||
comodel_name="product.mrp.area",
|
||||
string="Product Parameters",
|
||||
index=True,
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
related="product_mrp_area_id.mrp_area_id.warehouse_id.company_id",
|
||||
store=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
related="product_mrp_area_id.product_id",
|
||||
store=True,
|
||||
)
|
||||
uom_id = fields.Many2one(
|
||||
comodel_name="uom.uom", string="Product UoM", compute="_compute_uom_id"
|
||||
)
|
||||
date = fields.Date()
|
||||
demand_qty = fields.Float(string="Demand")
|
||||
supply_qty = fields.Float(string="Supply")
|
||||
initial_on_hand_qty = fields.Float(
|
||||
string="Starting Inventory", group_operator="avg"
|
||||
)
|
||||
final_on_hand_qty = fields.Float(
|
||||
string="Forecasted Inventory", group_operator="avg"
|
||||
)
|
||||
to_procure = fields.Float(compute="_compute_to_procure", store=True)
|
||||
running_availability = fields.Float(
|
||||
string="Planned Availability",
|
||||
group_operator="avg",
|
||||
help="Theoretical inventory level if all planned orders were released.",
|
||||
)
|
||||
order_release_date = fields.Date(compute="_compute_order_release_date", store=True)
|
||||
planned_order_ids = fields.One2many(
|
||||
comodel_name="mrp.planned.order", inverse_name="mrp_inventory_id", readonly=True
|
||||
)
|
||||
supply_method = fields.Selection(
|
||||
string="Supply Method",
|
||||
related="product_mrp_area_id.supply_method",
|
||||
readonly=True,
|
||||
store=True,
|
||||
)
|
||||
main_supplier_id = fields.Many2one(
|
||||
string="Main Supplier",
|
||||
related="product_mrp_area_id.main_supplier_id",
|
||||
readonly=True,
|
||||
store=True,
|
||||
)
|
||||
mrp_planner_id = fields.Many2one(
|
||||
related="product_mrp_area_id.mrp_planner_id",
|
||||
readonly=True,
|
||||
store=True,
|
||||
)
|
||||
|
||||
def _compute_uom_id(self):
|
||||
for rec in self:
|
||||
rec.uom_id = rec.product_mrp_area_id.product_id.uom_id
|
||||
|
||||
@api.depends("planned_order_ids", "planned_order_ids.qty_released")
|
||||
def _compute_to_procure(self):
|
||||
for rec in self:
|
||||
rec.to_procure = (
|
||||
0.0
|
||||
if rec.supply_method == "phantom"
|
||||
else sum(rec.planned_order_ids.mapped("mrp_qty"))
|
||||
- sum(rec.planned_order_ids.mapped("qty_released"))
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"product_mrp_area_id",
|
||||
"product_mrp_area_id.main_supplierinfo_id",
|
||||
"product_mrp_area_id.mrp_lead_time",
|
||||
"product_mrp_area_id.mrp_area_id.calendar_id",
|
||||
)
|
||||
def _compute_order_release_date(self):
|
||||
today = date.today()
|
||||
for rec in self.filtered(lambda r: r.date):
|
||||
delay = rec.product_mrp_area_id.mrp_lead_time
|
||||
if delay and rec.mrp_area_id.calendar_id:
|
||||
dt_date = fields.Datetime.to_datetime(rec.date)
|
||||
# dt_date is at the beginning of the day (00:00),
|
||||
# so we can subtract the delay straight forward.
|
||||
order_release_date = rec.mrp_area_id.calendar_id.plan_days(
|
||||
-delay, dt_date
|
||||
).date()
|
||||
elif delay:
|
||||
order_release_date = fields.Date.from_string(rec.date) - timedelta(
|
||||
days=delay
|
||||
)
|
||||
else:
|
||||
order_release_date = rec.date
|
||||
if order_release_date < today:
|
||||
order_release_date = today
|
||||
rec.order_release_date = order_release_date
|
||||
|
||||
def action_open_planned_orders(self):
|
||||
planned_order_ids = []
|
||||
for rec in self:
|
||||
planned_order_ids += rec.planned_order_ids.ids
|
||||
|
||||
domain = [("id", "in", planned_order_ids)]
|
||||
|
||||
return {
|
||||
"name": _("Planned Orders"),
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "mrp.planned.order",
|
||||
"view_mode": "tree,form",
|
||||
"domain": domain,
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# Copyright 2016-19 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpMove(models.Model):
|
||||
_name = "mrp.move"
|
||||
_description = "MRP Move"
|
||||
_order = "product_mrp_area_id, mrp_date, mrp_type desc, id"
|
||||
|
||||
# TODO: too many indexes...
|
||||
|
||||
product_mrp_area_id = fields.Many2one(
|
||||
comodel_name="product.mrp.area",
|
||||
string="Product MRP Area",
|
||||
index=True,
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
)
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name="mrp.area",
|
||||
related="product_mrp_area_id.mrp_area_id",
|
||||
string="MRP Area",
|
||||
store=True,
|
||||
index=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
related="product_mrp_area_id.mrp_area_id.warehouse_id.company_id",
|
||||
store=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
related="product_mrp_area_id.product_id",
|
||||
store=True,
|
||||
)
|
||||
|
||||
current_date = fields.Date()
|
||||
current_qty = fields.Float()
|
||||
mrp_date = fields.Date(string="MRP Date")
|
||||
planned_order_up_ids = fields.Many2many(
|
||||
comodel_name="mrp.planned.order",
|
||||
relation="mrp_move_planned_order_rel",
|
||||
column1="move_down_id",
|
||||
column2="order_id",
|
||||
string="Planned Orders UP",
|
||||
)
|
||||
mrp_order_number = fields.Char(string="Order Number")
|
||||
mrp_origin = fields.Selection(
|
||||
selection=[
|
||||
("mo", "Manufacturing Order"),
|
||||
("po", "Purchase Order"),
|
||||
("mv", "Move"),
|
||||
("fc", "Forecast"),
|
||||
("mrp", "MRP"),
|
||||
],
|
||||
string="Origin",
|
||||
)
|
||||
mrp_qty = fields.Float(string="MRP Quantity")
|
||||
mrp_type = fields.Selection(
|
||||
selection=[("s", "Supply"), ("d", "Demand")], string="Type"
|
||||
)
|
||||
name = fields.Char(string="Description")
|
||||
origin = fields.Char(string="Source Document")
|
||||
parent_product_id = fields.Many2one(
|
||||
comodel_name="product.product", string="Parent Product", index=True
|
||||
)
|
||||
production_id = fields.Many2one(
|
||||
comodel_name="mrp.production", string="Manufacturing Order", index=True
|
||||
)
|
||||
purchase_line_id = fields.Many2one(
|
||||
comodel_name="purchase.order.line", string="Purchase Order Line", index=True
|
||||
)
|
||||
purchase_order_id = fields.Many2one(
|
||||
comodel_name="purchase.order", string="Purchase Order", index=True
|
||||
)
|
||||
state = fields.Selection(
|
||||
selection=[
|
||||
("draft", "Draft"),
|
||||
("assigned", "Assigned"),
|
||||
("confirmed", "Confirmed"),
|
||||
("waiting", "Waiting"),
|
||||
("partially_available", "Partially Available"),
|
||||
("ready", "Ready"),
|
||||
("sent", "Sent"),
|
||||
("to approve", "To Approve"),
|
||||
("approved", "Approved"),
|
||||
],
|
||||
)
|
||||
stock_move_id = fields.Many2one(
|
||||
comodel_name="stock.move", string="Stock Move", index=True
|
||||
)
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
# Copyright 2019 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# - Lois Rilo Antelo <lois.rilo@forgeflow.com>
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpPlannedOrder(models.Model):
|
||||
_name = "mrp.planned.order"
|
||||
_description = "Planned Order"
|
||||
_order = "due_date, id"
|
||||
|
||||
name = fields.Char(string="Description")
|
||||
origin = fields.Char(string="Source Document")
|
||||
product_mrp_area_id = fields.Many2one(
|
||||
comodel_name="product.mrp.area",
|
||||
string="Product MRP Area",
|
||||
index=True,
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
)
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name="mrp.area",
|
||||
related="product_mrp_area_id.mrp_area_id",
|
||||
string="MRP Area",
|
||||
store=True,
|
||||
index=True,
|
||||
readonly=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
related="product_mrp_area_id.mrp_area_id.warehouse_id.company_id",
|
||||
store=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
related="product_mrp_area_id.product_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
order_release_date = fields.Date(
|
||||
string="Release Date", help="Order release date planned by MRP.", required=True
|
||||
)
|
||||
due_date = fields.Date(
|
||||
help="Date in which the supply must have been completed.",
|
||||
required=True,
|
||||
)
|
||||
qty_released = fields.Float(readonly=True)
|
||||
fixed = fields.Boolean(default=True)
|
||||
mrp_qty = fields.Float(string="Quantity")
|
||||
mrp_move_down_ids = fields.Many2many(
|
||||
comodel_name="mrp.move",
|
||||
relation="mrp_move_planned_order_rel",
|
||||
column1="order_id",
|
||||
column2="move_down_id",
|
||||
string="MRP Move DOWN",
|
||||
)
|
||||
mrp_action = fields.Selection(
|
||||
selection=[
|
||||
("manufacture", "Manufacturing Order"),
|
||||
("phantom", "Kit"),
|
||||
("buy", "Purchase Order"),
|
||||
("pull", "Pull From"),
|
||||
("push", "Push To"),
|
||||
("pull_push", "Pull & Push"),
|
||||
("none", "None"),
|
||||
],
|
||||
string="Action",
|
||||
)
|
||||
mrp_inventory_id = fields.Many2one(
|
||||
string="Associated MRP Inventory",
|
||||
comodel_name="mrp.inventory",
|
||||
ondelete="set null",
|
||||
)
|
||||
mrp_production_ids = fields.One2many(
|
||||
"mrp.production", "planned_order_id", string="Manufacturing Orders"
|
||||
)
|
||||
mo_count = fields.Integer(compute="_compute_mrp_production_count")
|
||||
mrp_planner_id = fields.Many2one(
|
||||
related="product_mrp_area_id.mrp_planner_id",
|
||||
readonly=True,
|
||||
store=True,
|
||||
)
|
||||
|
||||
def _compute_mrp_production_count(self):
|
||||
for rec in self:
|
||||
rec.mo_count = len(rec.mrp_production_ids)
|
||||
|
||||
@api.onchange("due_date")
|
||||
def _onchange_due_date(self):
|
||||
if self.due_date:
|
||||
if self.product_mrp_area_id.mrp_lead_time:
|
||||
calendar = self.mrp_area_id.calendar_id
|
||||
if calendar:
|
||||
dt = fields.Datetime.from_string(self.due_date)
|
||||
res = calendar.plan_days(
|
||||
-1 * (self.product_mrp_area_id.mrp_lead_time + 1), dt
|
||||
)
|
||||
self.order_release_date = res.date()
|
||||
else:
|
||||
self.order_release_date = fields.Date.from_string(
|
||||
self.due_date
|
||||
) - timedelta(days=self.product_mrp_area_id.mrp_lead_time)
|
||||
|
||||
def action_toggle_fixed(self):
|
||||
for rec in self:
|
||||
rec.fixed = not rec.fixed
|
||||
|
||||
def action_open_linked_mrp_production(self):
|
||||
action = self.env.ref("mrp.mrp_production_action")
|
||||
result = action.read()[0]
|
||||
result["context"] = {}
|
||||
result["domain"] = "[('id','in',%s)]" % self.mrp_production_ids.ids
|
||||
return result
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# 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",
|
||||
index=True,
|
||||
)
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
# 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
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
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",
|
||||
ondelete="cascade",
|
||||
)
|
||||
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")
|
||||
distribution_lead_time = fields.Float()
|
||||
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"),
|
||||
("phantom", "Kit"),
|
||||
("pull", "Pull From"),
|
||||
("push", "Push To"),
|
||||
("pull_push", "Pull & Push"),
|
||||
],
|
||||
compute="_compute_supply_method",
|
||||
)
|
||||
supply_bom_id = fields.Many2one(
|
||||
comodel_name="mrp.bom",
|
||||
string="Supply BoM",
|
||||
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,
|
||||
)
|
||||
mrp_planner_id = fields.Many2one("res.users")
|
||||
|
||||
_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
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _name_search(
|
||||
self, name, args=None, operator="ilike", limit=100, name_get_uid=None
|
||||
):
|
||||
if operator in ("ilike", "like", "=", "=like", "=ilike"):
|
||||
args = expression.AND(
|
||||
[
|
||||
args or [],
|
||||
[
|
||||
"|",
|
||||
"|",
|
||||
("product_id.name", operator, name),
|
||||
("product_id.default_code", operator, name),
|
||||
("mrp_area_id.name", operator, name),
|
||||
],
|
||||
]
|
||||
)
|
||||
return super(ProductMRPArea, self)._name_search(
|
||||
name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid
|
||||
)
|
||||
|
||||
def _compute_mrp_lead_time(self):
|
||||
produced = self.filtered(lambda r: r.supply_method == "manufacture")
|
||||
purchased = self.filtered(lambda r: r.supply_method == "buy")
|
||||
distributed = self.filtered(
|
||||
lambda r: r.supply_method in ("pull", "push", "pull_push")
|
||||
)
|
||||
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
|
||||
for rec in distributed:
|
||||
rec.mrp_lead_time = rec.distribution_lead_time
|
||||
for rec in self - produced - purchased - distributed:
|
||||
rec.mrp_lead_time = 0
|
||||
|
||||
def _compute_qty_available(self):
|
||||
for rec in self:
|
||||
rec.qty_available = rec.product_id.with_context(
|
||||
location=rec._get_locations().ids
|
||||
).qty_available
|
||||
|
||||
def _get_rule(self):
|
||||
self.ensure_one()
|
||||
group_obj = self.env["procurement.group"]
|
||||
proc_loc = self.location_proc_id or self.location_id
|
||||
values = {
|
||||
"warehouse_id": self.mrp_area_id.warehouse_id,
|
||||
"company_id": self.company_id,
|
||||
}
|
||||
rule = group_obj._get_rule(self.product_id, proc_loc, values)
|
||||
if not rule:
|
||||
return False
|
||||
# Keep getting the rule for the product and the source location until the
|
||||
# action is "buy" or "manufacture". Or until the action is "Pull From" or
|
||||
# "Pull & Push" and the supply method is "Take from Stock".
|
||||
while rule.action not in ("buy", "manufacture") and rule.procure_method in (
|
||||
"make_to_order",
|
||||
"mts_else_mto",
|
||||
):
|
||||
new_rule = group_obj._get_rule(
|
||||
self.product_id, rule.location_src_id, values
|
||||
)
|
||||
if not new_rule:
|
||||
break
|
||||
rule = new_rule
|
||||
return rule
|
||||
|
||||
def _compute_supply_method(self):
|
||||
boms_by_product = self.env["mrp.bom"]._bom_find(self.mapped("product_id"))
|
||||
for rec in self:
|
||||
rule = rec._get_rule()
|
||||
bom = boms_by_product.get(rec.product_id, self.env["mrp.bom"])
|
||||
if bom.type == "phantom":
|
||||
rec.supply_method = "phantom"
|
||||
rec.supply_bom_id = bom
|
||||
elif not rule:
|
||||
rec.supply_method = "none"
|
||||
rec.supply_bom_id = False
|
||||
elif rule.action == "manufacture":
|
||||
rec.supply_method = rule.action
|
||||
rec.supply_bom_id = bom
|
||||
else:
|
||||
rec.supply_method = rule.action
|
||||
rec.supply_bom_id = False
|
||||
|
||||
@api.depends(
|
||||
"mrp_area_id",
|
||||
"product_id.route_ids",
|
||||
"product_id.seller_ids",
|
||||
"location_proc_id",
|
||||
)
|
||||
def _compute_main_supplier(self):
|
||||
"""Simplified and similar to procurement.rule logic."""
|
||||
for rec in self:
|
||||
if rec.supply_method != "buy":
|
||||
rec.main_supplierinfo_id = False
|
||||
rec.main_supplier_id = False
|
||||
continue
|
||||
suppliers = rec.product_id.seller_ids.filtered(
|
||||
lambda r: (not r.product_id or r.product_id == rec.product_id)
|
||||
and (not r.company_id or r.company_id == rec.company_id)
|
||||
).sorted(lambda s: (s.sequence, -s.min_qty, s.price, s.id))
|
||||
if not suppliers:
|
||||
rec.main_supplierinfo_id = False
|
||||
rec.main_supplier_id = False
|
||||
continue
|
||||
rec.main_supplierinfo_id = suppliers[0]
|
||||
rec.main_supplier_id = suppliers[0].partner_id
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def _in_stock_moves_domain(self):
|
||||
self.ensure_one()
|
||||
locations = self._get_locations()
|
||||
return [
|
||||
("product_id", "=", self.product_id.id),
|
||||
("state", "not in", ["done", "cancel"]),
|
||||
("product_qty", ">", 0.00),
|
||||
"!",
|
||||
("location_id", "child_of", locations.ids),
|
||||
("location_dest_id", "child_of", locations.ids),
|
||||
]
|
||||
|
||||
def _out_stock_moves_domain(self):
|
||||
self.ensure_one()
|
||||
locations = self._get_locations()
|
||||
return [
|
||||
("product_id", "=", self.product_id.id),
|
||||
("state", "not in", ["done", "cancel"]),
|
||||
("product_qty", ">", 0.00),
|
||||
("location_id", "child_of", locations.ids),
|
||||
"!",
|
||||
("location_dest_id", "child_of", locations.ids),
|
||||
]
|
||||
|
||||
def action_view_stock_moves(self, domain):
|
||||
self.ensure_one()
|
||||
action = self.env.ref("stock.stock_move_action").read()[0]
|
||||
action["domain"] = domain
|
||||
action["context"] = {}
|
||||
return action
|
||||
|
||||
def action_view_incoming_stock_moves(self):
|
||||
return self.action_view_stock_moves(self._in_stock_moves_domain())
|
||||
|
||||
def action_view_outgoing_stock_moves(self):
|
||||
return self.action_view_stock_moves(self._out_stock_moves_domain())
|
||||
|
||||
def _to_be_exploded(self):
|
||||
self.ensure_one()
|
||||
return self.supply_method in ["manufacture", "phantom"]
|
||||
|
||||
def _get_locations(self):
|
||||
self.ensure_one()
|
||||
return self.mrp_area_id._get_locations()
|
||||
|
||||
def _should_create_planned_order(self):
|
||||
self.ensure_one()
|
||||
return True
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
|
||||
# Copyright 2016-19 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
import ast
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
llc = fields.Integer(string="Low Level Code", default=0, index=True)
|
||||
manufacturing_order_ids = fields.One2many(
|
||||
comodel_name="mrp.production",
|
||||
inverse_name="product_id",
|
||||
string="Manufacturing Orders",
|
||||
domain=[("state", "=", "draft")],
|
||||
)
|
||||
purchase_order_line_ids = fields.One2many(
|
||||
comodel_name="purchase.order.line",
|
||||
inverse_name="product_id",
|
||||
string="Purchase Orders",
|
||||
)
|
||||
mrp_area_ids = fields.One2many(
|
||||
comodel_name="product.mrp.area",
|
||||
inverse_name="product_id",
|
||||
string="MRP Area parameters",
|
||||
)
|
||||
mrp_area_count = fields.Integer(
|
||||
string="MRP Area Parameter Count",
|
||||
readonly=True,
|
||||
compute="_compute_mrp_area_count",
|
||||
)
|
||||
|
||||
def _compute_mrp_area_count(self):
|
||||
for rec in self:
|
||||
rec.mrp_area_count = len(rec.mrp_area_ids)
|
||||
|
||||
def write(self, values):
|
||||
res = super().write(values)
|
||||
if values.get("active") is False:
|
||||
parameters = (
|
||||
self.env["product.mrp.area"]
|
||||
.sudo()
|
||||
.search([("product_id", "in", self.ids)])
|
||||
)
|
||||
parameters.write({"active": False})
|
||||
return res
|
||||
|
||||
def action_view_mrp_area_parameters(self):
|
||||
self.ensure_one()
|
||||
result = self.env["ir.actions.actions"]._for_xml_id(
|
||||
"mrp_multi_level.product_mrp_area_action"
|
||||
)
|
||||
ctx = ast.literal_eval(result.get("context"))
|
||||
if not ctx:
|
||||
ctx = {}
|
||||
mrp_areas = self.env["mrp.area"].search([])
|
||||
if len(mrp_areas) == 1:
|
||||
ctx.update({"default_mrp_area_id": mrp_areas[0].id})
|
||||
area_ids = self.mrp_area_ids.ids
|
||||
ctx.update({"default_product_id": self.id})
|
||||
if self.mrp_area_count != 1:
|
||||
result["domain"] = [("id", "in", area_ids)]
|
||||
else:
|
||||
res = self.env.ref("mrp_multi_level.product_mrp_area_form", False)
|
||||
result["views"] = [(res and res.id or False, "form")]
|
||||
result["res_id"] = area_ids[0]
|
||||
result["context"] = ctx
|
||||
return result
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright 2018-19 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
import ast
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
mrp_area_ids = fields.One2many(
|
||||
comodel_name="product.mrp.area",
|
||||
inverse_name="product_tmpl_id",
|
||||
string="MRP Area parameters",
|
||||
)
|
||||
mrp_area_count = fields.Integer(
|
||||
string="MRP Area Parameter Count",
|
||||
readonly=True,
|
||||
compute="_compute_mrp_area_count",
|
||||
)
|
||||
|
||||
def _compute_mrp_area_count(self):
|
||||
for rec in self:
|
||||
rec.mrp_area_count = len(rec.mrp_area_ids)
|
||||
|
||||
def action_view_mrp_area_parameters(self):
|
||||
self.ensure_one()
|
||||
result = self.env["ir.actions.actions"]._for_xml_id(
|
||||
"mrp_multi_level.product_mrp_area_action"
|
||||
)
|
||||
ctx = ast.literal_eval(result.get("context"))
|
||||
mrp_areas = self.env["mrp.area"].search([])
|
||||
if "context" not in result:
|
||||
result["context"] = {}
|
||||
if len(mrp_areas) == 1:
|
||||
ctx.update({"default_mrp_area_id": mrp_areas[0].id})
|
||||
mrp_area_ids = self.with_context(active_test=False).mrp_area_ids.ids
|
||||
if len(self.product_variant_ids) == 1:
|
||||
variant = self.product_variant_ids[0]
|
||||
ctx.update({"default_product_id": variant.id})
|
||||
if len(mrp_area_ids) != 1:
|
||||
result["domain"] = [("id", "in", mrp_area_ids)]
|
||||
else:
|
||||
res = self.env.ref("mrp_multi_level.product_mrp_area_form", False)
|
||||
result["views"] = [(res and res.id or False, "form")]
|
||||
result["res_id"] = mrp_area_ids[0]
|
||||
result["context"] = ctx
|
||||
return result
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# © 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>
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
import ast
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockLocation(models.Model):
|
||||
_inherit = "stock.location"
|
||||
|
||||
mrp_area_count = fields.Integer(
|
||||
string="MRP Area Parameter Count",
|
||||
readonly=True,
|
||||
compute="_compute_mrp_area_count",
|
||||
)
|
||||
|
||||
def _compute_mrp_area_count(self):
|
||||
for rec in self:
|
||||
areas = self.env["mrp.area"].search([("location_id", "=", rec.id)])
|
||||
rec.mrp_area_count = len(areas)
|
||||
|
||||
def action_view_mrp_area_location(self):
|
||||
self.ensure_one()
|
||||
result = self.env["ir.actions.actions"]._for_xml_id(
|
||||
"mrp_multi_level.mrp_area_action"
|
||||
)
|
||||
ctx = ast.literal_eval(result.get("context"))
|
||||
if not ctx:
|
||||
ctx = {}
|
||||
mrp_areas = self.env["mrp.area"].search([("location_id", "=", self.id)])
|
||||
if self.mrp_area_count != 1:
|
||||
result["domain"] = [("id", "in", mrp_areas.ids)]
|
||||
else:
|
||||
ctx.update({"default_mrp_area_id": mrp_areas[0].id})
|
||||
res = self.env.ref("mrp_multi_level.mrp_area_form", False)
|
||||
result["views"] = [(res and res.id or False, "form")]
|
||||
result["res_id"] = mrp_areas[0].id
|
||||
result["context"] = ctx
|
||||
return result
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com)
|
||||
# - Joan Sisquella Andrés <lois.rilo@forgeflow.com>
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class StockQuant(models.Model):
|
||||
_inherit = "stock.quant"
|
||||
|
||||
@api.model
|
||||
def _get_inventory_fields_write(self):
|
||||
"""
|
||||
Adding field product_uom_id, inventory_quantity
|
||||
"""
|
||||
fields = super(StockQuant, self)._get_inventory_fields_write()
|
||||
return fields + ["product_uom_id", "inventory_quantity"]
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue