mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[IMP] mrp_multi_level: black, isort
This commit is contained in:
committed by
JasminSForgeFlow
parent
b84e05a9c2
commit
6de26bb868
@@ -3,46 +3,40 @@
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
{
|
||||
'name': 'MRP Multi Level',
|
||||
'version': '12.0.1.0.1',
|
||||
'development_status': 'Beta',
|
||||
'license': 'LGPL-3',
|
||||
'author': 'Ucamco, '
|
||||
'Eficent, '
|
||||
'Odoo Community Association (OCA)',
|
||||
'maintainers': ['jbeficent', 'lreficent'],
|
||||
'summary': 'Adds an MRP Scheduler',
|
||||
'website': 'https://github.com/OCA/manufacture',
|
||||
'category': 'Manufacturing',
|
||||
'depends': [
|
||||
'mrp',
|
||||
'purchase_stock',
|
||||
'mrp_warehouse_calendar',
|
||||
"name": "MRP Multi Level",
|
||||
"version": "12.0.1.0.1",
|
||||
"development_status": "Beta",
|
||||
"license": "LGPL-3",
|
||||
"author": "Ucamco, " "Eficent, " "Odoo Community Association (OCA)",
|
||||
"maintainers": ["jbeficent", "lreficent"],
|
||||
"summary": "Adds an MRP Scheduler",
|
||||
"website": "https://github.com/OCA/manufacture",
|
||||
"category": "Manufacturing",
|
||||
"depends": ["mrp", "purchase_stock", "mrp_warehouse_calendar"],
|
||||
"data": [
|
||||
"security/mrp_multi_level_security.xml",
|
||||
"security/ir.model.access.csv",
|
||||
"views/mrp_area_views.xml",
|
||||
"views/product_product_views.xml",
|
||||
"views/product_template_views.xml",
|
||||
"views/product_mrp_area_views.xml",
|
||||
"views/stock_location_views.xml",
|
||||
"wizards/mrp_inventory_procure_views.xml",
|
||||
"views/mrp_inventory_views.xml",
|
||||
"wizards/mrp_multi_level_views.xml",
|
||||
"views/mrp_menuitem.xml",
|
||||
"data/mrp_multi_level_cron.xml",
|
||||
"data/mrp_area_data.xml",
|
||||
],
|
||||
'data': [
|
||||
'security/mrp_multi_level_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/mrp_area_views.xml',
|
||||
'views/product_product_views.xml',
|
||||
'views/product_template_views.xml',
|
||||
'views/product_mrp_area_views.xml',
|
||||
'views/stock_location_views.xml',
|
||||
'wizards/mrp_inventory_procure_views.xml',
|
||||
'views/mrp_inventory_views.xml',
|
||||
'wizards/mrp_multi_level_views.xml',
|
||||
'views/mrp_menuitem.xml',
|
||||
'data/mrp_multi_level_cron.xml',
|
||||
'data/mrp_area_data.xml',
|
||||
"demo": [
|
||||
"demo/product_category_demo.xml",
|
||||
"demo/product_product_demo.xml",
|
||||
"demo/res_partner_demo.xml",
|
||||
"demo/product_supplierinfo_demo.xml",
|
||||
"demo/product_mrp_area_demo.xml",
|
||||
"demo/mrp_bom_demo.xml",
|
||||
"demo/initial_on_hand_demo.xml",
|
||||
],
|
||||
'demo': [
|
||||
'demo/product_category_demo.xml',
|
||||
'demo/product_product_demo.xml',
|
||||
'demo/res_partner_demo.xml',
|
||||
'demo/product_supplierinfo_demo.xml',
|
||||
'demo/product_mrp_area_demo.xml',
|
||||
'demo/mrp_bom_demo.xml',
|
||||
'demo/initial_on_hand_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
"installable": True,
|
||||
"application": True,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Copyright 2019 Eficent Business and IT Consulting Services, S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
import logging
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
from odoo import SUPERUSER_ID, api
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -10,7 +11,8 @@ __name__ = "Upgrade to 11.0.2.0.0"
|
||||
|
||||
def _migrate_product_to_product_mrp_area(env):
|
||||
_logger.info("Migrating product parameters to Product MRP Areas")
|
||||
env.cr.execute("""
|
||||
env.cr.execute(
|
||||
"""
|
||||
SELECT DISTINCT mrp_area.id, pr.id, pr.mrp_applicable, pr.mrp_exclude,
|
||||
pr.mrp_inspection_delay, pr.mrp_maximum_order_qty,
|
||||
pr.mrp_minimum_order_qty, pr.mrp_minimum_stock, pr.mrp_nbr_days,
|
||||
@@ -21,27 +23,41 @@ def _migrate_product_to_product_mrp_area(env):
|
||||
ON pt.id = pr.product_tmpl_id
|
||||
WHERE pr.mrp_exclude = False
|
||||
AND pt.type = 'product'
|
||||
""")
|
||||
product_mrp_area_model = env['product.mrp.area']
|
||||
for mrp_area_id, product_id, mrp_applicable, mrp_exclude,\
|
||||
mrp_inspection_delay, mrp_maximum_order_qty, mrp_minimum_order_qty, \
|
||||
mrp_minimum_stock, mrp_nbr_days, mrp_qty_multiple, mrp_transit_delay,\
|
||||
mrp_verified, active in env.cr.fetchall():
|
||||
product_mrp_area_model.create({
|
||||
'mrp_area_id': mrp_area_id,
|
||||
'product_id': product_id,
|
||||
'mrp_applicable': mrp_applicable,
|
||||
'mrp_exclude': mrp_exclude,
|
||||
'mrp_inspection_delay': mrp_inspection_delay,
|
||||
'mrp_maximum_order_qty': mrp_maximum_order_qty,
|
||||
'mrp_minimum_order_qty': mrp_minimum_order_qty,
|
||||
'mrp_minimum_stock': mrp_minimum_stock,
|
||||
'mrp_nbr_days': mrp_nbr_days,
|
||||
'mrp_qty_multiple': mrp_qty_multiple,
|
||||
'mrp_transit_delay': mrp_transit_delay,
|
||||
'mrp_verified': mrp_verified,
|
||||
'active': active,
|
||||
})
|
||||
"""
|
||||
)
|
||||
product_mrp_area_model = env["product.mrp.area"]
|
||||
for (
|
||||
mrp_area_id,
|
||||
product_id,
|
||||
mrp_applicable,
|
||||
mrp_exclude,
|
||||
mrp_inspection_delay,
|
||||
mrp_maximum_order_qty,
|
||||
mrp_minimum_order_qty,
|
||||
mrp_minimum_stock,
|
||||
mrp_nbr_days,
|
||||
mrp_qty_multiple,
|
||||
mrp_transit_delay,
|
||||
mrp_verified,
|
||||
active,
|
||||
) in env.cr.fetchall():
|
||||
product_mrp_area_model.create(
|
||||
{
|
||||
"mrp_area_id": mrp_area_id,
|
||||
"product_id": product_id,
|
||||
"mrp_applicable": mrp_applicable,
|
||||
"mrp_exclude": mrp_exclude,
|
||||
"mrp_inspection_delay": mrp_inspection_delay,
|
||||
"mrp_maximum_order_qty": mrp_maximum_order_qty,
|
||||
"mrp_minimum_order_qty": mrp_minimum_order_qty,
|
||||
"mrp_minimum_stock": mrp_minimum_stock,
|
||||
"mrp_nbr_days": mrp_nbr_days,
|
||||
"mrp_qty_multiple": mrp_qty_multiple,
|
||||
"mrp_transit_delay": mrp_transit_delay,
|
||||
"mrp_verified": mrp_verified,
|
||||
"active": active,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
|
||||
@@ -8,32 +8,29 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class MrpArea(models.Model):
|
||||
_name = 'mrp.area'
|
||||
_name = "mrp.area"
|
||||
_description = "MRP Area"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
warehouse_id = fields.Many2one(
|
||||
comodel_name='stock.warehouse', string='Warehouse',
|
||||
required=True,
|
||||
comodel_name="stock.warehouse", string="Warehouse", required=True
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company',
|
||||
related='warehouse_id.company_id',
|
||||
store=True,
|
||||
comodel_name="res.company", related="warehouse_id.company_id", store=True
|
||||
)
|
||||
location_id = fields.Many2one(
|
||||
comodel_name='stock.location', string='Location',
|
||||
required=True,
|
||||
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',
|
||||
comodel_name="resource.calendar",
|
||||
string="Working Hours",
|
||||
related="warehouse_id.calendar_id",
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def _get_locations(self):
|
||||
self.ensure_one()
|
||||
return self.env['stock.location'].search([
|
||||
('id', 'child_of', self.location_id.id)])
|
||||
return self.env["stock.location"].search(
|
||||
[("id", "child_of", self.location_id.id)]
|
||||
)
|
||||
|
||||
@@ -4,68 +4,63 @@
|
||||
# - Lois Rilo Antelo <lois.rilo@eficent.com>
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import api, fields, models
|
||||
from datetime import date, timedelta
|
||||
|
||||
from datetime import timedelta, date
|
||||
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'
|
||||
_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,
|
||||
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',
|
||||
comodel_name="product.mrp.area",
|
||||
string="Product Parameters",
|
||||
index=True,
|
||||
required=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company',
|
||||
related='product_mrp_area_id.mrp_area_id.warehouse_id.company_id',
|
||||
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',
|
||||
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',
|
||||
comodel_name="uom.uom", string="Product UoM", compute="_compute_uom_id"
|
||||
)
|
||||
date = fields.Date(string='Date')
|
||||
demand_qty = fields.Float(string='Demand')
|
||||
supply_qty = fields.Float(string='Supply')
|
||||
initial_on_hand_qty = fields.Float(string='Starting Inventory')
|
||||
final_on_hand_qty = fields.Float(string='Forecasted Inventory')
|
||||
date = fields.Date(string="Date")
|
||||
demand_qty = fields.Float(string="Demand")
|
||||
supply_qty = fields.Float(string="Supply")
|
||||
initial_on_hand_qty = fields.Float(string="Starting Inventory")
|
||||
final_on_hand_qty = fields.Float(string="Forecasted Inventory")
|
||||
to_procure = fields.Float(
|
||||
string="To procure",
|
||||
compute="_compute_to_procure",
|
||||
store=True,
|
||||
string="To procure", compute="_compute_to_procure", store=True
|
||||
)
|
||||
running_availability = fields.Float(
|
||||
string="Planned Availability",
|
||||
help="Theoretical inventory level if all planned orders"
|
||||
"were released.",
|
||||
help="Theoretical inventory level if all planned orders" "were released.",
|
||||
)
|
||||
order_release_date = fields.Date(
|
||||
string="Order Release Date",
|
||||
compute="_compute_order_release_date",
|
||||
store=True,
|
||||
string="Order Release 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,
|
||||
comodel_name="mrp.planned.order", inverse_name="mrp_inventory_id", readonly=True
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@@ -76,14 +71,17 @@ class MrpInventory(models.Model):
|
||||
@api.depends("planned_order_ids", "planned_order_ids.qty_released")
|
||||
def _compute_to_procure(self):
|
||||
for rec in self:
|
||||
rec.to_procure = sum(rec.planned_order_ids.mapped('mrp_qty')) - \
|
||||
sum(rec.planned_order_ids.mapped('qty_released'))
|
||||
rec.to_procure = sum(rec.planned_order_ids.mapped("mrp_qty")) - sum(
|
||||
rec.planned_order_ids.mapped("qty_released")
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@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')
|
||||
@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):
|
||||
@@ -93,10 +91,12 @@ class MrpInventory(models.Model):
|
||||
# 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()
|
||||
-delay, dt_date
|
||||
).date()
|
||||
else:
|
||||
order_release_date = fields.Date.from_string(
|
||||
rec.date) - timedelta(days=delay)
|
||||
order_release_date = fields.Date.from_string(rec.date) - timedelta(
|
||||
days=delay
|
||||
)
|
||||
if order_release_date < today:
|
||||
order_release_date = today
|
||||
rec.order_release_date = order_release_date
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
# © 2016-18 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import models, fields
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpMove(models.Model):
|
||||
_name = 'mrp.move'
|
||||
_name = "mrp.move"
|
||||
_description = "MRP Move"
|
||||
_order = 'product_mrp_area_id, mrp_date, mrp_type desc, id'
|
||||
_order = "product_mrp_area_id, mrp_date, mrp_type desc, id"
|
||||
|
||||
# TODO: too many indexes...
|
||||
|
||||
@@ -26,19 +26,19 @@ class MrpMove(models.Model):
|
||||
index=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company',
|
||||
related='product_mrp_area_id.mrp_area_id.warehouse_id.company_id',
|
||||
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',
|
||||
comodel_name="product.product",
|
||||
related="product_mrp_area_id.product_id",
|
||||
store=True,
|
||||
)
|
||||
|
||||
current_date = fields.Date(string='Current Date')
|
||||
current_qty = fields.Float(string='Current Qty')
|
||||
mrp_date = fields.Date(string='MRP Date')
|
||||
current_date = fields.Date(string="Current Date")
|
||||
current_qty = fields.Float(string="Current Qty")
|
||||
mrp_date = fields.Date(string="MRP Date")
|
||||
planned_order_up_ids = fields.Many2many(
|
||||
comodel_name="mrp.planned.order",
|
||||
relation="mrp_move_planned_order_rel",
|
||||
@@ -46,49 +46,48 @@ class MrpMove(models.Model):
|
||||
column2="order_id",
|
||||
string="Planned Orders UP",
|
||||
)
|
||||
mrp_order_number = fields.Char(string='Order Number')
|
||||
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',
|
||||
selection=[
|
||||
("mo", "Manufacturing Order"),
|
||||
("po", "Purchase Order"),
|
||||
("mv", "Move"),
|
||||
("fc", "Forecast"),
|
||||
("mrp", "MRP"),
|
||||
],
|
||||
string="Origin",
|
||||
)
|
||||
name = fields.Char(string='Description')
|
||||
mrp_qty = fields.Float(string="MRP Quantity")
|
||||
mrp_type = fields.Selection(
|
||||
selection=[("s", "Supply"), ("d", "Demand")], string="Type"
|
||||
)
|
||||
name = fields.Char(string="Description")
|
||||
parent_product_id = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
string="Parent Product", index=True,
|
||||
comodel_name="product.product", string="Parent Product", index=True
|
||||
)
|
||||
production_id = fields.Many2one(
|
||||
comodel_name='mrp.production',
|
||||
string='Manufacturing Order', index=True,
|
||||
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,
|
||||
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,
|
||||
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')],
|
||||
string='State',
|
||||
selection=[
|
||||
("draft", "Draft"),
|
||||
("assigned", "Assigned"),
|
||||
("confirmed", "Confirmed"),
|
||||
("waiting", "Waiting"),
|
||||
("partially_available", "Partially Available"),
|
||||
("ready", "Ready"),
|
||||
("sent", "Sent"),
|
||||
("to approve", "To Approve"),
|
||||
("approved", "Approved"),
|
||||
],
|
||||
string="State",
|
||||
)
|
||||
stock_move_id = fields.Many2one(
|
||||
comodel_name='stock.move',
|
||||
string='Stock Move', index=True,
|
||||
comodel_name="stock.move", string="Stock Move", index=True
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# - Lois Rilo Antelo <lois.rilo@eficent.com>
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import models, fields
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class MrpPlannedOrder(models.Model):
|
||||
@@ -26,8 +26,8 @@ class MrpPlannedOrder(models.Model):
|
||||
readonly=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company',
|
||||
related='product_mrp_area_id.mrp_area_id.warehouse_id.company_id',
|
||||
comodel_name="res.company",
|
||||
related="product_mrp_area_id.mrp_area_id.warehouse_id.company_id",
|
||||
store=True,
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
@@ -37,12 +37,10 @@ class MrpPlannedOrder(models.Model):
|
||||
readonly=True,
|
||||
)
|
||||
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."
|
||||
)
|
||||
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."
|
||||
)
|
||||
qty_released = fields.Float()
|
||||
fixed = fields.Boolean()
|
||||
@@ -55,12 +53,14 @@ class MrpPlannedOrder(models.Model):
|
||||
string="MRP Move DOWN",
|
||||
)
|
||||
mrp_action = fields.Selection(
|
||||
selection=[("manufacture", "Manufacturing Order"),
|
||||
("buy", "Purchase Order"),
|
||||
('pull', 'Pull From'),
|
||||
('push', 'Push To'),
|
||||
('pull_push', 'Pull & Push'),
|
||||
("none", "None")],
|
||||
selection=[
|
||||
("manufacture", "Manufacturing Order"),
|
||||
("buy", "Purchase Order"),
|
||||
("pull", "Pull From"),
|
||||
("push", "Push To"),
|
||||
("pull_push", "Pull & Push"),
|
||||
("none", "None"),
|
||||
],
|
||||
string="Action",
|
||||
)
|
||||
mrp_inventory_id = fields.Many2one(
|
||||
|
||||
@@ -6,99 +6,89 @@
|
||||
|
||||
from math import ceil
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ProductMRPArea(models.Model):
|
||||
_name = 'product.mrp.area'
|
||||
_description = 'Product MRP Area'
|
||||
_name = "product.mrp.area"
|
||||
_description = "Product MRP Area"
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name='mrp.area',
|
||||
required=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',
|
||||
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',
|
||||
comodel_name="product.product", required=True, string="Product"
|
||||
)
|
||||
product_tmpl_id = fields.Many2one(
|
||||
comodel_name='product.template',
|
||||
comodel_name="product.template",
|
||||
readonly=True,
|
||||
related='product_id.product_tmpl_id',
|
||||
related="product_id.product_tmpl_id",
|
||||
store=True,
|
||||
)
|
||||
location_id = fields.Many2one(
|
||||
related="mrp_area_id.location_id",
|
||||
)
|
||||
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.",
|
||||
"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_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,
|
||||
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 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_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',
|
||||
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',
|
||||
"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,
|
||||
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,
|
||||
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',
|
||||
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",
|
||||
string="Quantity Available", compute="_compute_qty_available"
|
||||
)
|
||||
mrp_move_ids = fields.One2many(
|
||||
comodel_name='mrp.move',
|
||||
inverse_name='product_mrp_area_id',
|
||||
readonly=True,
|
||||
comodel_name="mrp.move", inverse_name="product_mrp_area_id", readonly=True
|
||||
)
|
||||
planned_order_ids = fields.One2many(
|
||||
comodel_name="mrp.planned.order",
|
||||
@@ -107,29 +97,41 @@ class ProductMRPArea(models.Model):
|
||||
)
|
||||
|
||||
_sql_constraints = [
|
||||
('product_mrp_area_uniq', 'unique(product_id, mrp_area_id)',
|
||||
'The product/MRP Area parameters combination must be unique.'),
|
||||
(
|
||||
"product_mrp_area_uniq",
|
||||
"unique(product_id, mrp_area_id)",
|
||||
"The product/MRP Area parameters combination must be unique.",
|
||||
)
|
||||
]
|
||||
|
||||
@api.multi
|
||||
@api.constrains(
|
||||
"mrp_minimum_order_qty", "mrp_maximum_order_qty", "mrp_qty_multiple",
|
||||
"mrp_minimum_stock", "mrp_nbr_days",
|
||||
"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",
|
||||
])
|
||||
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."))
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
return [(area.id, '[%s] %s' % (
|
||||
area.mrp_area_id.name,
|
||||
area.product_id.display_name)) for area in self]
|
||||
return [
|
||||
(area.id, "[{}] {}".format(area.mrp_area_id.name, area.product_id.display_name))
|
||||
for area in self
|
||||
]
|
||||
|
||||
@api.multi
|
||||
def _compute_mrp_lead_time(self):
|
||||
@@ -140,36 +142,37 @@ class ProductMRPArea(models.Model):
|
||||
for rec in purchased:
|
||||
rec.mrp_lead_time = rec.main_supplierinfo_id.delay
|
||||
# TODO: 'move' supply method.
|
||||
for rec in (self - produced - purchased):
|
||||
for rec in self - produced - purchased:
|
||||
rec.mrp_lead_time = 0
|
||||
|
||||
@api.multi
|
||||
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
|
||||
{"location": rec.mrp_area_id.location_id.id}
|
||||
).qty_available
|
||||
|
||||
@api.multi
|
||||
def _compute_supply_method(self):
|
||||
group_obj = self.env['procurement.group']
|
||||
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,
|
||||
"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'
|
||||
rec.supply_method = rule.action if rule else "none"
|
||||
|
||||
@api.multi
|
||||
@api.depends('supply_method', 'product_id.route_ids',
|
||||
'product_id.seller_ids')
|
||||
@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'):
|
||||
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))
|
||||
lambda r: (not r.product_id or r.product_id == rec.product_id)
|
||||
)
|
||||
if not suppliers:
|
||||
continue
|
||||
rec.main_supplierinfo_id = suppliers[0]
|
||||
@@ -178,15 +181,17 @@ class ProductMRPArea(models.Model):
|
||||
@api.multi
|
||||
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):
|
||||
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:
|
||||
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
|
||||
|
||||
@@ -2,33 +2,34 @@
|
||||
# Copyright 2016-18 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
import ast
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
_inherit = 'product.product'
|
||||
_inherit = "product.product"
|
||||
|
||||
llc = fields.Integer(string='Low Level Code', default=0)
|
||||
llc = fields.Integer(string="Low Level Code", default=0)
|
||||
manufacturing_order_ids = fields.One2many(
|
||||
comodel_name='mrp.production',
|
||||
inverse_name='product_id',
|
||||
string='Manufacturing Orders',
|
||||
domain=[('state', '=', 'draft')],
|
||||
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',
|
||||
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'
|
||||
comodel_name="product.mrp.area",
|
||||
inverse_name="product_id",
|
||||
string="MRP Area parameters",
|
||||
)
|
||||
mrp_area_count = fields.Integer(
|
||||
string='MRP Area Parameter Count',
|
||||
string="MRP Area Parameter Count",
|
||||
readonly=True,
|
||||
compute='_compute_mrp_area_count',
|
||||
compute="_compute_mrp_area_count",
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@@ -39,21 +40,21 @@ class Product(models.Model):
|
||||
@api.multi
|
||||
def action_view_mrp_area_parameters(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('mrp_multi_level.product_mrp_area_action')
|
||||
action = self.env.ref("mrp_multi_level.product_mrp_area_action")
|
||||
result = action.read()[0]
|
||||
ctx = ast.literal_eval(result.get('context'))
|
||||
ctx = ast.literal_eval(result.get("context"))
|
||||
if not ctx:
|
||||
ctx = {}
|
||||
mrp_areas = self.env['mrp.area'].search([])
|
||||
mrp_areas = self.env["mrp.area"].search([])
|
||||
if len(mrp_areas) == 1:
|
||||
ctx.update({'default_mrp_area_id': mrp_areas[0].id})
|
||||
ctx.update({"default_mrp_area_id": mrp_areas[0].id})
|
||||
area_ids = self.mrp_area_ids.ids
|
||||
ctx.update({'default_product_id': self.id})
|
||||
ctx.update({"default_product_id": self.id})
|
||||
if self.mrp_area_count != 1:
|
||||
result['domain'] = [('id', 'in', area_ids)]
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
# Copyright 2018 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
import ast
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
_inherit = "product.template"
|
||||
|
||||
mrp_area_ids = fields.One2many(
|
||||
comodel_name='product.mrp.area',
|
||||
inverse_name='product_tmpl_id',
|
||||
string='MRP Area parameters',
|
||||
comodel_name="product.mrp.area",
|
||||
inverse_name="product_tmpl_id",
|
||||
string="MRP Area parameters",
|
||||
)
|
||||
mrp_area_count = fields.Integer(
|
||||
string='MRP Area Parameter Count',
|
||||
string="MRP Area Parameter Count",
|
||||
readonly=True,
|
||||
compute='_compute_mrp_area_count',
|
||||
compute="_compute_mrp_area_count",
|
||||
)
|
||||
|
||||
@api.multi
|
||||
@@ -26,24 +27,23 @@ class ProductTemplate(models.Model):
|
||||
@api.multi
|
||||
def action_view_mrp_area_parameters(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('mrp_multi_level.product_mrp_area_action')
|
||||
action = self.env.ref("mrp_multi_level.product_mrp_area_action")
|
||||
result = action.read()[0]
|
||||
ctx = ast.literal_eval(result.get('context'))
|
||||
mrp_areas = self.env['mrp.area'].search([])
|
||||
if 'context' not in result:
|
||||
result['context'] = {}
|
||||
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
|
||||
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})
|
||||
ctx.update({"default_product_id": variant.id})
|
||||
if len(mrp_area_ids) != 1:
|
||||
result['domain'] = [('id', 'in', mrp_area_ids)]
|
||||
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
|
||||
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
|
||||
|
||||
@@ -7,10 +7,11 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class StockLocation(models.Model):
|
||||
_inherit = 'stock.location'
|
||||
_inherit = "stock.location"
|
||||
|
||||
mrp_area_id = fields.Many2one(
|
||||
comodel_name='mrp.area', string='MRP Area',
|
||||
comodel_name="mrp.area",
|
||||
string="MRP Area",
|
||||
help="Requirements for a particular MRP area are combined for the "
|
||||
"purposes of procurement by the MRP.",
|
||||
"purposes of procurement by the MRP.",
|
||||
)
|
||||
|
||||
@@ -8,272 +8,330 @@ from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class TestMrpMultiLevelCommon(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.mo_obj = cls.env['mrp.production']
|
||||
cls.po_obj = cls.env['purchase.order']
|
||||
cls.product_obj = cls.env['product.product']
|
||||
cls.loc_obj = cls.env['stock.location']
|
||||
cls.mrp_area_obj = cls.env['mrp.area']
|
||||
cls.product_mrp_area_obj = cls.env['product.mrp.area']
|
||||
cls.partner_obj = cls.env['res.partner']
|
||||
cls.res_users = cls.env['res.users']
|
||||
cls.stock_picking_obj = cls.env['stock.picking']
|
||||
cls.mrp_multi_level_wiz = cls.env['mrp.multi.level']
|
||||
cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure']
|
||||
cls.mrp_inventory_obj = cls.env['mrp.inventory']
|
||||
cls.mrp_move_obj = cls.env['mrp.move']
|
||||
cls.planned_order_obj = cls.env['mrp.planned.order']
|
||||
cls.mo_obj = cls.env["mrp.production"]
|
||||
cls.po_obj = cls.env["purchase.order"]
|
||||
cls.product_obj = cls.env["product.product"]
|
||||
cls.loc_obj = cls.env["stock.location"]
|
||||
cls.mrp_area_obj = cls.env["mrp.area"]
|
||||
cls.product_mrp_area_obj = cls.env["product.mrp.area"]
|
||||
cls.partner_obj = cls.env["res.partner"]
|
||||
cls.res_users = cls.env["res.users"]
|
||||
cls.stock_picking_obj = cls.env["stock.picking"]
|
||||
cls.mrp_multi_level_wiz = cls.env["mrp.multi.level"]
|
||||
cls.mrp_inventory_procure_wiz = cls.env["mrp.inventory.procure"]
|
||||
cls.mrp_inventory_obj = cls.env["mrp.inventory"]
|
||||
cls.mrp_move_obj = cls.env["mrp.move"]
|
||||
cls.planned_order_obj = cls.env["mrp.planned.order"]
|
||||
|
||||
cls.fp_1 = cls.env.ref('mrp_multi_level.product_product_fp_1')
|
||||
cls.fp_2 = cls.env.ref('mrp_multi_level.product_product_fp_2')
|
||||
cls.sf_1 = cls.env.ref('mrp_multi_level.product_product_sf_1')
|
||||
cls.sf_2 = cls.env.ref('mrp_multi_level.product_product_sf_2')
|
||||
cls.pp_1 = cls.env.ref('mrp_multi_level.product_product_pp_1')
|
||||
cls.pp_2 = cls.env.ref('mrp_multi_level.product_product_pp_2')
|
||||
cls.company = cls.env.ref('base.main_company')
|
||||
cls.mrp_area = cls.env.ref('mrp_multi_level.mrp_area_stock_wh0')
|
||||
cls.vendor = cls.env.ref('mrp_multi_level.res_partner_lazer_tech')
|
||||
cls.wh = cls.env.ref('stock.warehouse0')
|
||||
cls.fp_1 = cls.env.ref("mrp_multi_level.product_product_fp_1")
|
||||
cls.fp_2 = cls.env.ref("mrp_multi_level.product_product_fp_2")
|
||||
cls.sf_1 = cls.env.ref("mrp_multi_level.product_product_sf_1")
|
||||
cls.sf_2 = cls.env.ref("mrp_multi_level.product_product_sf_2")
|
||||
cls.pp_1 = cls.env.ref("mrp_multi_level.product_product_pp_1")
|
||||
cls.pp_2 = cls.env.ref("mrp_multi_level.product_product_pp_2")
|
||||
cls.company = cls.env.ref("base.main_company")
|
||||
cls.mrp_area = cls.env.ref("mrp_multi_level.mrp_area_stock_wh0")
|
||||
cls.vendor = cls.env.ref("mrp_multi_level.res_partner_lazer_tech")
|
||||
cls.wh = cls.env.ref("stock.warehouse0")
|
||||
cls.stock_location = cls.wh.lot_stock_id
|
||||
cls.customer_location = cls.env.ref(
|
||||
'stock.stock_location_customers')
|
||||
cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
|
||||
cls.calendar = cls.env.ref('resource.resource_calendar_std')
|
||||
cls.customer_location = cls.env.ref("stock.stock_location_customers")
|
||||
cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
|
||||
cls.calendar = cls.env.ref("resource.resource_calendar_std")
|
||||
# Add calendar to WH:
|
||||
cls.wh.calendar_id = cls.calendar
|
||||
|
||||
# Partner:
|
||||
vendor1 = cls.partner_obj.create({'name': 'Vendor 1'})
|
||||
vendor1 = cls.partner_obj.create({"name": "Vendor 1"})
|
||||
|
||||
# Create user:
|
||||
group_mrp_manager = cls.env.ref('mrp.group_mrp_manager')
|
||||
group_user = cls.env.ref('base.group_user')
|
||||
group_stock_manager = cls.env.ref('stock.group_stock_manager')
|
||||
group_mrp_manager = cls.env.ref("mrp.group_mrp_manager")
|
||||
group_user = cls.env.ref("base.group_user")
|
||||
group_stock_manager = cls.env.ref("stock.group_stock_manager")
|
||||
cls.mrp_manager = cls._create_user(
|
||||
'Test User',
|
||||
"Test User",
|
||||
[group_mrp_manager, group_user, group_stock_manager],
|
||||
cls.company,
|
||||
)
|
||||
|
||||
# Create secondary location and MRP Area:
|
||||
cls.sec_loc = cls.loc_obj.create({
|
||||
'name': 'Test location',
|
||||
'usage': 'internal',
|
||||
'location_id': cls.wh.view_location_id.id,
|
||||
})
|
||||
cls.secondary_area = cls.mrp_area_obj.create({
|
||||
'name': 'Test',
|
||||
'warehouse_id': cls.wh.id,
|
||||
'location_id': cls.sec_loc.id,
|
||||
})
|
||||
cls.sec_loc = cls.loc_obj.create(
|
||||
{
|
||||
"name": "Test location",
|
||||
"usage": "internal",
|
||||
"location_id": cls.wh.view_location_id.id,
|
||||
}
|
||||
)
|
||||
cls.secondary_area = cls.mrp_area_obj.create(
|
||||
{"name": "Test", "warehouse_id": cls.wh.id, "location_id": cls.sec_loc.id}
|
||||
)
|
||||
# Create an area for design special cases and test them, different
|
||||
# cases will be expected to not share products, this way each case
|
||||
# can be isolated.
|
||||
cls.cases_loc = cls.loc_obj.create({
|
||||
'name': 'Special Cases location',
|
||||
'usage': 'internal',
|
||||
'location_id': cls.wh.view_location_id.id,
|
||||
})
|
||||
cls.cases_area = cls.mrp_area_obj.create({
|
||||
'name': 'Special Cases Tests',
|
||||
'warehouse_id': cls.wh.id,
|
||||
'location_id': cls.cases_loc.id,
|
||||
})
|
||||
cls.cases_loc = cls.loc_obj.create(
|
||||
{
|
||||
"name": "Special Cases location",
|
||||
"usage": "internal",
|
||||
"location_id": cls.wh.view_location_id.id,
|
||||
}
|
||||
)
|
||||
cls.cases_area = cls.mrp_area_obj.create(
|
||||
{
|
||||
"name": "Special Cases Tests",
|
||||
"warehouse_id": cls.wh.id,
|
||||
"location_id": cls.cases_loc.id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create products:
|
||||
route_buy = cls.env.ref('purchase_stock.route_warehouse0_buy').id
|
||||
cls.prod_test = cls.product_obj.create({
|
||||
'name': 'Test Top Seller',
|
||||
'type': 'product',
|
||||
'list_price': 150.0,
|
||||
'produce_delay': 5.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_test.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
})
|
||||
route_buy = cls.env.ref("purchase_stock.route_warehouse0_buy").id
|
||||
cls.prod_test = cls.product_obj.create(
|
||||
{
|
||||
"name": "Test Top Seller",
|
||||
"type": "product",
|
||||
"list_price": 150.0,
|
||||
"produce_delay": 5.0,
|
||||
"route_ids": [(6, 0, [route_buy])],
|
||||
"seller_ids": [(0, 0, {"name": vendor1.id, "price": 20.0})],
|
||||
}
|
||||
)
|
||||
cls.product_mrp_area_obj.create(
|
||||
{"product_id": cls.prod_test.id, "mrp_area_id": cls.mrp_area.id}
|
||||
)
|
||||
# Parameters in secondary area with nbr_days set.
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_test.id,
|
||||
'mrp_area_id': cls.secondary_area.id,
|
||||
'mrp_nbr_days': 7,
|
||||
})
|
||||
cls.prod_min = cls.product_obj.create({
|
||||
'name': 'Product with minimum order qty',
|
||||
'type': 'product',
|
||||
'list_price': 50.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_min.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
'mrp_minimum_order_qty': 50.0,
|
||||
'mrp_maximum_order_qty': 0.0,
|
||||
'mrp_qty_multiple': 1.0,
|
||||
})
|
||||
cls.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": cls.prod_test.id,
|
||||
"mrp_area_id": cls.secondary_area.id,
|
||||
"mrp_nbr_days": 7,
|
||||
}
|
||||
)
|
||||
cls.prod_min = cls.product_obj.create(
|
||||
{
|
||||
"name": "Product with minimum order qty",
|
||||
"type": "product",
|
||||
"list_price": 50.0,
|
||||
"route_ids": [(6, 0, [route_buy])],
|
||||
"seller_ids": [(0, 0, {"name": vendor1.id, "price": 10.0})],
|
||||
}
|
||||
)
|
||||
cls.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": cls.prod_min.id,
|
||||
"mrp_area_id": cls.mrp_area.id,
|
||||
"mrp_minimum_order_qty": 50.0,
|
||||
"mrp_maximum_order_qty": 0.0,
|
||||
"mrp_qty_multiple": 1.0,
|
||||
}
|
||||
)
|
||||
|
||||
cls.prod_max = cls.product_obj.create({
|
||||
'name': 'Product with maximum order qty',
|
||||
'type': 'product',
|
||||
'list_price': 50.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_max.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
'mrp_minimum_order_qty': 50.0,
|
||||
'mrp_maximum_order_qty': 100.0,
|
||||
'mrp_qty_multiple': 1.0,
|
||||
})
|
||||
cls.prod_multiple = cls.product_obj.create({
|
||||
'name': 'Product with qty multiple',
|
||||
'type': 'product',
|
||||
'list_price': 50.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.prod_multiple.id,
|
||||
'mrp_area_id': cls.mrp_area.id,
|
||||
'mrp_minimum_order_qty': 50.0,
|
||||
'mrp_maximum_order_qty': 500.0,
|
||||
'mrp_qty_multiple': 25.0,
|
||||
})
|
||||
cls.prod_max = cls.product_obj.create(
|
||||
{
|
||||
"name": "Product with maximum order qty",
|
||||
"type": "product",
|
||||
"list_price": 50.0,
|
||||
"route_ids": [(6, 0, [route_buy])],
|
||||
"seller_ids": [(0, 0, {"name": vendor1.id, "price": 10.0})],
|
||||
}
|
||||
)
|
||||
cls.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": cls.prod_max.id,
|
||||
"mrp_area_id": cls.mrp_area.id,
|
||||
"mrp_minimum_order_qty": 50.0,
|
||||
"mrp_maximum_order_qty": 100.0,
|
||||
"mrp_qty_multiple": 1.0,
|
||||
}
|
||||
)
|
||||
cls.prod_multiple = cls.product_obj.create(
|
||||
{
|
||||
"name": "Product with qty multiple",
|
||||
"type": "product",
|
||||
"list_price": 50.0,
|
||||
"route_ids": [(6, 0, [route_buy])],
|
||||
"seller_ids": [(0, 0, {"name": vendor1.id, "price": 10.0})],
|
||||
}
|
||||
)
|
||||
cls.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": cls.prod_multiple.id,
|
||||
"mrp_area_id": cls.mrp_area.id,
|
||||
"mrp_minimum_order_qty": 50.0,
|
||||
"mrp_maximum_order_qty": 500.0,
|
||||
"mrp_qty_multiple": 25.0,
|
||||
}
|
||||
)
|
||||
# Create more products to test special corner case scenarios:
|
||||
cls.product_scenario_1 = cls.product_obj.create({
|
||||
'name': 'Product Special Scenario 1',
|
||||
'type': 'product',
|
||||
'list_price': 100.0,
|
||||
'route_ids': [(6, 0, [route_buy])],
|
||||
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})],
|
||||
})
|
||||
cls.product_mrp_area_obj.create({
|
||||
'product_id': cls.product_scenario_1.id,
|
||||
'mrp_area_id': cls.cases_area.id,
|
||||
'mrp_nbr_days': 7,
|
||||
'mrp_qty_multiple': 5.0,
|
||||
})
|
||||
cls.product_scenario_1 = cls.product_obj.create(
|
||||
{
|
||||
"name": "Product Special Scenario 1",
|
||||
"type": "product",
|
||||
"list_price": 100.0,
|
||||
"route_ids": [(6, 0, [route_buy])],
|
||||
"seller_ids": [(0, 0, {"name": vendor1.id, "price": 20.0})],
|
||||
}
|
||||
)
|
||||
cls.product_mrp_area_obj.create(
|
||||
{
|
||||
"product_id": cls.product_scenario_1.id,
|
||||
"mrp_area_id": cls.cases_area.id,
|
||||
"mrp_nbr_days": 7,
|
||||
"mrp_qty_multiple": 5.0,
|
||||
}
|
||||
)
|
||||
|
||||
# Create pickings for Scenario 1:
|
||||
dt_base = cls.calendar.plan_days(3 + 1, datetime.today())
|
||||
cls._create_picking_in(
|
||||
cls.product_scenario_1, 87, dt_base, location=cls.cases_loc)
|
||||
cls.product_scenario_1, 87, dt_base, location=cls.cases_loc
|
||||
)
|
||||
dt_bit_later = dt_base + timedelta(hours=1)
|
||||
cls._create_picking_out(
|
||||
cls.product_scenario_1, 124, dt_bit_later, location=cls.cases_loc)
|
||||
cls.product_scenario_1, 124, dt_bit_later, location=cls.cases_loc
|
||||
)
|
||||
dt_base_2 = cls.calendar.plan_days(3 + 1, datetime.today())
|
||||
cls._create_picking_out(
|
||||
cls.product_scenario_1, 90, dt_base_2, location=cls.cases_loc)
|
||||
cls.product_scenario_1, 90, dt_base_2, location=cls.cases_loc
|
||||
)
|
||||
|
||||
dt_next_group = cls.calendar.plan_days(10 + 1, datetime.today())
|
||||
cls._create_picking_out(
|
||||
cls.product_scenario_1, 18, dt_next_group, location=cls.cases_loc)
|
||||
cls.product_scenario_1, 18, dt_next_group, location=cls.cases_loc
|
||||
)
|
||||
|
||||
# Create test picking for FP-1 and FP-2:
|
||||
res = cls.calendar.plan_days(7 + 1, datetime.today().replace(hour=0))
|
||||
date_move = res.date()
|
||||
cls.picking_1 = cls.stock_picking_obj.create({
|
||||
'picking_type_id': cls.env.ref('stock.picking_type_out').id,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customer_location.id,
|
||||
'move_lines': [
|
||||
(0, 0, {
|
||||
'name': 'Test move fp-1',
|
||||
'product_id': cls.fp_1.id,
|
||||
'date_expected': date_move,
|
||||
'date': date_move,
|
||||
'product_uom': cls.fp_1.uom_id.id,
|
||||
'product_uom_qty': 100,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customer_location.id
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'Test move fp-2',
|
||||
'product_id': cls.fp_2.id,
|
||||
'date_expected': date_move,
|
||||
'date': date_move,
|
||||
'product_uom': cls.fp_2.uom_id.id,
|
||||
'product_uom_qty': 15,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customer_location.id
|
||||
})]
|
||||
})
|
||||
cls.picking_1 = cls.stock_picking_obj.create(
|
||||
{
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_out").id,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
"move_lines": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test move fp-1",
|
||||
"product_id": cls.fp_1.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": cls.fp_1.uom_id.id,
|
||||
"product_uom_qty": 100,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test move fp-2",
|
||||
"product_id": cls.fp_2.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": cls.fp_2.uom_id.id,
|
||||
"product_uom_qty": 15,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
cls.picking_1.action_confirm()
|
||||
|
||||
# Create test picking for procure qty adjustment tests:
|
||||
cls.picking_2 = cls.stock_picking_obj.create({
|
||||
'picking_type_id': cls.env.ref('stock.picking_type_out').id,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customer_location.id,
|
||||
'move_lines': [
|
||||
(0, 0, {
|
||||
'name': 'Test move prod_min',
|
||||
'product_id': cls.prod_min.id,
|
||||
'date_expected': date_move,
|
||||
'date': date_move,
|
||||
'product_uom': cls.prod_min.uom_id.id,
|
||||
'product_uom_qty': 16,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customer_location.id
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'Test move prod_max',
|
||||
'product_id': cls.prod_max.id,
|
||||
'date_expected': date_move,
|
||||
'date': date_move,
|
||||
'product_uom': cls.prod_max.uom_id.id,
|
||||
'product_uom_qty': 140,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customer_location.id
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'Test move prod_multiple',
|
||||
'product_id': cls.prod_multiple.id,
|
||||
'date_expected': date_move,
|
||||
'date': date_move,
|
||||
'product_uom': cls.prod_multiple.uom_id.id,
|
||||
'product_uom_qty': 112,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customer_location.id
|
||||
})]
|
||||
})
|
||||
cls.picking_2 = cls.stock_picking_obj.create(
|
||||
{
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_out").id,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
"move_lines": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test move prod_min",
|
||||
"product_id": cls.prod_min.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": cls.prod_min.uom_id.id,
|
||||
"product_uom_qty": 16,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test move prod_max",
|
||||
"product_id": cls.prod_max.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": cls.prod_max.uom_id.id,
|
||||
"product_uom_qty": 140,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test move prod_multiple",
|
||||
"product_id": cls.prod_multiple.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": cls.prod_multiple.uom_id.id,
|
||||
"product_uom_qty": 112,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
cls.picking_2.action_confirm()
|
||||
|
||||
# Create Test PO:
|
||||
date_po = cls.calendar.plan_days(
|
||||
1+1, datetime.today().replace(hour=0)).date()
|
||||
cls.po = cls.po_obj.create({
|
||||
'name': 'Test PO-001',
|
||||
'partner_id': cls.vendor.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': 'Test PP-2 line',
|
||||
'product_id': cls.pp_2.id,
|
||||
'date_planned': date_po,
|
||||
'product_qty': 5.0,
|
||||
'product_uom': cls.pp_2.uom_id.id,
|
||||
'price_unit': 25.0,
|
||||
})],
|
||||
})
|
||||
date_po = cls.calendar.plan_days(1 + 1, datetime.today().replace(hour=0)).date()
|
||||
cls.po = cls.po_obj.create(
|
||||
{
|
||||
"name": "Test PO-001",
|
||||
"partner_id": cls.vendor.id,
|
||||
"order_line": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test PP-2 line",
|
||||
"product_id": cls.pp_2.id,
|
||||
"date_planned": date_po,
|
||||
"product_qty": 5.0,
|
||||
"product_uom": cls.pp_2.uom_id.id,
|
||||
"price_unit": 25.0,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
# Create test MO:
|
||||
date_mo = cls.calendar.plan_days(
|
||||
9 + 1, datetime.today().replace(hour=0)).date()
|
||||
bom_fp_2 = cls.env.ref('mrp_multi_level.mrp_bom_fp_2')
|
||||
cls.mo = cls.mo_obj.create({
|
||||
'product_id': cls.fp_2.id,
|
||||
'bom_id': bom_fp_2.id,
|
||||
'product_qty': 12.0,
|
||||
'product_uom_id': cls.fp_2.uom_id.id,
|
||||
'date_planned_start': date_mo,
|
||||
})
|
||||
date_mo = cls.calendar.plan_days(9 + 1, datetime.today().replace(hour=0)).date()
|
||||
bom_fp_2 = cls.env.ref("mrp_multi_level.mrp_bom_fp_2")
|
||||
cls.mo = cls.mo_obj.create(
|
||||
{
|
||||
"product_id": cls.fp_2.id,
|
||||
"bom_id": bom_fp_2.id,
|
||||
"product_qty": 12.0,
|
||||
"product_uom_id": cls.fp_2.uom_id.id,
|
||||
"date_planned_start": date_mo,
|
||||
}
|
||||
)
|
||||
|
||||
# Dates:
|
||||
today = datetime.today().replace(hour=0)
|
||||
@@ -298,55 +356,71 @@ class TestMrpMultiLevelCommon(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def create_demand_sec_loc(cls, date_move, qty):
|
||||
return cls.stock_picking_obj.create({
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_out").id,
|
||||
"location_id": cls.sec_loc.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
"move_lines": [
|
||||
(0, 0, {
|
||||
"name": "Test move",
|
||||
"product_id": cls.prod_test.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": cls.prod_test.uom_id.id,
|
||||
"product_uom_qty": qty,
|
||||
"location_id": cls.sec_loc.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
})],
|
||||
})
|
||||
return cls.stock_picking_obj.create(
|
||||
{
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_out").id,
|
||||
"location_id": cls.sec_loc.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
"move_lines": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test move",
|
||||
"product_id": cls.prod_test.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": cls.prod_test.uom_id.id,
|
||||
"product_uom_qty": qty,
|
||||
"location_id": cls.sec_loc.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_user(cls, login, groups, company):
|
||||
user = cls.res_users.create({
|
||||
'name': login,
|
||||
'login': login,
|
||||
'password': 'demo',
|
||||
'email': 'example@yourcompany.com',
|
||||
'company_id': company.id,
|
||||
'groups_id': [(6, 0, [group.id for group in groups])]
|
||||
})
|
||||
user = cls.res_users.create(
|
||||
{
|
||||
"name": login,
|
||||
"login": login,
|
||||
"password": "demo",
|
||||
"email": "example@yourcompany.com",
|
||||
"company_id": company.id,
|
||||
"groups_id": [(6, 0, [group.id for group in groups])],
|
||||
}
|
||||
)
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def _create_picking_in(cls, product, qty, date_move, location=None):
|
||||
if not location:
|
||||
location = cls.stock_location
|
||||
picking = cls.stock_picking_obj.create({
|
||||
'picking_type_id': cls.env.ref('stock.picking_type_in').id,
|
||||
'location_id': cls.supplier_location.id,
|
||||
'location_dest_id': location.id,
|
||||
'move_lines': [
|
||||
(0, 0, {
|
||||
'name': 'Test Move',
|
||||
'product_id': product.id,
|
||||
'date_expected': date_move,
|
||||
'date': date_move,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': qty,
|
||||
'location_id': cls.supplier_location.id,
|
||||
'location_dest_id': location.id,
|
||||
})],
|
||||
})
|
||||
picking = cls.stock_picking_obj.create(
|
||||
{
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_in").id,
|
||||
"location_id": cls.supplier_location.id,
|
||||
"location_dest_id": location.id,
|
||||
"move_lines": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test Move",
|
||||
"product_id": product.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": product.uom_id.id,
|
||||
"product_uom_qty": qty,
|
||||
"location_id": cls.supplier_location.id,
|
||||
"location_dest_id": location.id,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
picking.action_confirm()
|
||||
return picking
|
||||
|
||||
@@ -354,21 +428,28 @@ class TestMrpMultiLevelCommon(SavepointCase):
|
||||
def _create_picking_out(cls, product, qty, date_move, location=None):
|
||||
if not location:
|
||||
location = cls.stock_location
|
||||
picking = cls.stock_picking_obj.create({
|
||||
'picking_type_id': cls.env.ref('stock.picking_type_out').id,
|
||||
'location_id': location.id,
|
||||
'location_dest_id': cls.customer_location.id,
|
||||
'move_lines': [
|
||||
(0, 0, {
|
||||
'name': 'Test Move',
|
||||
'product_id': product.id,
|
||||
'date_expected': date_move,
|
||||
'date': date_move,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': qty,
|
||||
'location_id': location.id,
|
||||
'location_dest_id': cls.customer_location.id,
|
||||
})],
|
||||
})
|
||||
picking = cls.stock_picking_obj.create(
|
||||
{
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_out").id,
|
||||
"location_id": location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
"move_lines": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": "Test Move",
|
||||
"product_id": product.id,
|
||||
"date_expected": date_move,
|
||||
"date": date_move,
|
||||
"product_uom": product.uom_id.id,
|
||||
"product_uom_qty": qty,
|
||||
"location_id": location.id,
|
||||
"location_dest_id": cls.customer_location.id,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
picking.action_confirm()
|
||||
return picking
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# (http://www.eficent.com)
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo.addons.mrp_multi_level.tests.common import TestMrpMultiLevelCommon
|
||||
from odoo import fields
|
||||
|
||||
from odoo.addons.mrp_multi_level.tests.common import TestMrpMultiLevelCommon
|
||||
|
||||
|
||||
class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
||||
|
||||
def test_01_mrp_levels(self):
|
||||
"""Tests computation of MRP levels."""
|
||||
self.assertEqual(self.fp_1.llc, 0)
|
||||
@@ -19,51 +19,46 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
||||
|
||||
def test_02_product_mrp_area(self):
|
||||
"""Tests that mrp products are generated correctly."""
|
||||
product_mrp_area = self.product_mrp_area_obj.search([
|
||||
('product_id', '=', self.pp_1.id)])
|
||||
self.assertEqual(product_mrp_area.supply_method, 'buy')
|
||||
product_mrp_area = self.product_mrp_area_obj.search(
|
||||
[("product_id", "=", self.pp_1.id)]
|
||||
)
|
||||
self.assertEqual(product_mrp_area.supply_method, "buy")
|
||||
self.assertEqual(product_mrp_area.main_supplier_id, self.vendor)
|
||||
self.assertEqual(product_mrp_area.qty_available, 10.0)
|
||||
product_mrp_area = self.product_mrp_area_obj.search([
|
||||
('product_id', '=', self.sf_1.id)])
|
||||
self.assertEqual(product_mrp_area.supply_method, 'manufacture')
|
||||
product_mrp_area = self.product_mrp_area_obj.search(
|
||||
[("product_id", "=", self.sf_1.id)]
|
||||
)
|
||||
self.assertEqual(product_mrp_area.supply_method, "manufacture")
|
||||
|
||||
def test_03_mrp_moves(self):
|
||||
"""Tests for mrp moves generated."""
|
||||
moves = self.mrp_move_obj.search([
|
||||
('product_id', '=', self.pp_1.id),
|
||||
])
|
||||
moves = self.mrp_move_obj.search([("product_id", "=", self.pp_1.id)])
|
||||
self.assertEqual(len(moves), 3)
|
||||
self.assertNotIn('s', moves.mapped('mrp_type'))
|
||||
self.assertNotIn("s", moves.mapped("mrp_type"))
|
||||
for move in moves:
|
||||
self.assertTrue(move.planned_order_up_ids)
|
||||
if move.planned_order_up_ids.product_mrp_area_id.product_id == \
|
||||
self.fp_1:
|
||||
if move.planned_order_up_ids.product_mrp_area_id.product_id == self.fp_1:
|
||||
# Demand coming from FP-1
|
||||
self.assertEqual(
|
||||
move.planned_order_up_ids.mrp_action, "manufacture")
|
||||
self.assertEqual(move.planned_order_up_ids.mrp_action, "manufacture")
|
||||
self.assertEqual(move.mrp_qty, -200.0)
|
||||
elif move.planned_order_up_ids.product_mrp_area_id.product_id == \
|
||||
self.sf_1:
|
||||
elif move.planned_order_up_ids.product_mrp_area_id.product_id == self.sf_1:
|
||||
# Demand coming from FP-2 -> SF-1
|
||||
self.assertEqual(
|
||||
move.planned_order_up_ids.mrp_action, "manufacture")
|
||||
self.assertEqual(move.planned_order_up_ids.mrp_action, "manufacture")
|
||||
if move.mrp_date == self.date_5:
|
||||
self.assertEqual(move.mrp_qty, -90.0)
|
||||
elif move.mrp_date == self.date_8:
|
||||
self.assertEqual(move.mrp_qty, -72.0)
|
||||
# Check actions:
|
||||
planned_orders = self.planned_order_obj.search([
|
||||
('product_id', '=', self.pp_1.id),
|
||||
])
|
||||
planned_orders = self.planned_order_obj.search(
|
||||
[("product_id", "=", self.pp_1.id)]
|
||||
)
|
||||
self.assertEqual(len(planned_orders), 3)
|
||||
for plan in planned_orders:
|
||||
self.assertEqual(plan.mrp_action, 'buy')
|
||||
self.assertEqual(plan.mrp_action, "buy")
|
||||
# Check PP-2 PO being accounted:
|
||||
po_move = self.mrp_move_obj.search([
|
||||
('product_id', '=', self.pp_2.id),
|
||||
('mrp_type', '=', 's'),
|
||||
])
|
||||
po_move = self.mrp_move_obj.search(
|
||||
[("product_id", "=", self.pp_2.id), ("mrp_type", "=", "s")]
|
||||
)
|
||||
self.assertEqual(len(po_move), 1)
|
||||
self.assertEqual(po_move.purchase_order_id, self.po)
|
||||
self.assertEqual(po_move.purchase_line_id, self.po.order_line)
|
||||
@@ -72,90 +67,127 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
||||
"""Tests MRP inventories created."""
|
||||
# FP-1
|
||||
fp_1_inventory_lines = self.mrp_inventory_obj.search(
|
||||
[('product_mrp_area_id.product_id', '=', self.fp_1.id)])
|
||||
[("product_mrp_area_id.product_id", "=", self.fp_1.id)]
|
||||
)
|
||||
self.assertEqual(len(fp_1_inventory_lines), 1)
|
||||
self.assertEqual(fp_1_inventory_lines.date, self.date_7)
|
||||
self.assertEqual(fp_1_inventory_lines.demand_qty, 100.0)
|
||||
self.assertEqual(fp_1_inventory_lines.to_procure, 100.0)
|
||||
# FP-2
|
||||
fp_2_line_1 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.fp_2.id),
|
||||
('date', '=', self.date_7)])
|
||||
fp_2_line_1 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.fp_2.id),
|
||||
("date", "=", self.date_7),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(fp_2_line_1), 1)
|
||||
self.assertEqual(fp_2_line_1.demand_qty, 15.0)
|
||||
self.assertEqual(fp_2_line_1.to_procure, 15.0)
|
||||
# TODO: ask odoo to fix it... should be date10
|
||||
fp_2_line_2 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.fp_2.id),
|
||||
('date', '=', self.date_9)])
|
||||
fp_2_line_2 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.fp_2.id),
|
||||
("date", "=", self.date_9),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(fp_2_line_2), 1)
|
||||
self.assertEqual(fp_2_line_2.demand_qty, 0.0)
|
||||
self.assertEqual(fp_2_line_2.to_procure, 0.0)
|
||||
self.assertEqual(fp_2_line_2.supply_qty, 12.0)
|
||||
|
||||
# SF-1
|
||||
sf_1_line_1 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.sf_1.id),
|
||||
('date', '=', self.date_6)])
|
||||
sf_1_line_1 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.sf_1.id),
|
||||
("date", "=", self.date_6),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(sf_1_line_1), 1)
|
||||
self.assertEqual(sf_1_line_1.demand_qty, 30.0)
|
||||
self.assertEqual(sf_1_line_1.to_procure, 30.0)
|
||||
sf_1_line_2 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.sf_1.id),
|
||||
('date', '=', self.date_9)])
|
||||
sf_1_line_2 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.sf_1.id),
|
||||
("date", "=", self.date_9),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(sf_1_line_2), 1)
|
||||
self.assertEqual(sf_1_line_2.demand_qty, 24.0)
|
||||
self.assertEqual(sf_1_line_2.to_procure, 24.0)
|
||||
# SF-2
|
||||
sf_2_line_1 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.sf_2.id),
|
||||
('date', '=', self.date_6)])
|
||||
sf_2_line_1 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.sf_2.id),
|
||||
("date", "=", self.date_6),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(sf_2_line_1), 1)
|
||||
self.assertEqual(sf_2_line_1.demand_qty, 45.0)
|
||||
self.assertEqual(sf_2_line_1.to_procure, 30.0)
|
||||
sf_2_line_2 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.sf_2.id),
|
||||
('date', '=', self.date_9)])
|
||||
sf_2_line_2 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.sf_2.id),
|
||||
("date", "=", self.date_9),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(sf_2_line_2), 1)
|
||||
self.assertEqual(sf_2_line_2.demand_qty, 36.0)
|
||||
self.assertEqual(sf_2_line_2.to_procure, 36.0)
|
||||
|
||||
# PP-1
|
||||
pp_1_line_1 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.pp_1.id),
|
||||
('date', '=', self.date_5)])
|
||||
pp_1_line_1 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.pp_1.id),
|
||||
("date", "=", self.date_5),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(pp_1_line_1), 1)
|
||||
self.assertEqual(pp_1_line_1.demand_qty, 290.0)
|
||||
self.assertEqual(pp_1_line_1.to_procure, 280.0)
|
||||
pp_1_line_2 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.pp_1.id),
|
||||
('date', '=', self.date_8)])
|
||||
pp_1_line_2 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.pp_1.id),
|
||||
("date", "=", self.date_8),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(pp_1_line_2), 1)
|
||||
self.assertEqual(pp_1_line_2.demand_qty, 72.0)
|
||||
self.assertEqual(pp_1_line_2.to_procure, 72.0)
|
||||
# PP-2
|
||||
pp_2_line_1 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_3)])
|
||||
pp_2_line_1 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.pp_2.id),
|
||||
("date", "=", self.date_3),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(pp_2_line_1), 1)
|
||||
self.assertEqual(pp_2_line_1.demand_qty, 90.0)
|
||||
# 90.0 demand - 20.0 on hand - 5.0 on PO = 65.0
|
||||
self.assertEqual(pp_2_line_1.to_procure, 65.0)
|
||||
pp_2_line_2 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_5)])
|
||||
pp_2_line_2 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.pp_2.id),
|
||||
("date", "=", self.date_5),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(pp_2_line_2), 1)
|
||||
self.assertEqual(pp_2_line_2.demand_qty, 360.0)
|
||||
self.assertEqual(pp_2_line_2.to_procure, 360.0)
|
||||
pp_2_line_3 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_6)])
|
||||
pp_2_line_3 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.pp_2.id),
|
||||
("date", "=", self.date_6),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(pp_2_line_3), 1)
|
||||
self.assertEqual(pp_2_line_3.demand_qty, 108.0)
|
||||
self.assertEqual(pp_2_line_3.to_procure, 108.0)
|
||||
pp_2_line_4 = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.pp_2.id),
|
||||
('date', '=', self.date_8)])
|
||||
pp_2_line_4 = self.mrp_inventory_obj.search(
|
||||
[
|
||||
("product_mrp_area_id.product_id", "=", self.pp_2.id),
|
||||
("date", "=", self.date_8),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(pp_2_line_4), 1)
|
||||
self.assertEqual(pp_2_line_4.demand_qty, 48.0)
|
||||
self.assertEqual(pp_2_line_4.to_procure, 48.0)
|
||||
@@ -163,27 +195,28 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
||||
def test_05_planned_availability(self):
|
||||
"""Test planned availability computation."""
|
||||
# Running availability for PP-1:
|
||||
invs = self.mrp_inventory_obj.search([
|
||||
('product_id', '=', self.pp_1.id)],
|
||||
order='date')
|
||||
invs = self.mrp_inventory_obj.search(
|
||||
[("product_id", "=", self.pp_1.id)], order="date"
|
||||
)
|
||||
self.assertEqual(len(invs), 2)
|
||||
expected = [0.0, 0.0] # No grouping, lot size nor safety stock.
|
||||
self.assertEqual(invs.mapped('running_availability'), expected)
|
||||
self.assertEqual(invs.mapped("running_availability"), expected)
|
||||
|
||||
def test_06_procure_mo(self):
|
||||
"""Test procurement wizard with MOs."""
|
||||
mos = self.mo_obj.search([
|
||||
('product_id', '=', self.fp_1.id)])
|
||||
mos = self.mo_obj.search([("product_id", "=", self.fp_1.id)])
|
||||
self.assertFalse(mos)
|
||||
mrp_inv = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.fp_1.id)])
|
||||
self.mrp_inventory_procure_wiz.with_context({
|
||||
'active_model': 'mrp.inventory',
|
||||
'active_ids': mrp_inv.ids,
|
||||
'active_id': mrp_inv.id,
|
||||
}).create({}).make_procurement()
|
||||
mos = self.mo_obj.search([
|
||||
('product_id', '=', self.fp_1.id)])
|
||||
mrp_inv = self.mrp_inventory_obj.search(
|
||||
[("product_mrp_area_id.product_id", "=", self.fp_1.id)]
|
||||
)
|
||||
self.mrp_inventory_procure_wiz.with_context(
|
||||
{
|
||||
"active_model": "mrp.inventory",
|
||||
"active_ids": mrp_inv.ids,
|
||||
"active_id": mrp_inv.id,
|
||||
}
|
||||
).create({}).make_procurement()
|
||||
mos = self.mo_obj.search([("product_id", "=", self.fp_1.id)])
|
||||
self.assertTrue(mos)
|
||||
self.assertEqual(mos.product_qty, 100.0)
|
||||
mo_date_start = fields.Date.to_date(mos.date_planned_start)
|
||||
@@ -193,72 +226,84 @@ class TestMrpMultiLevel(TestMrpMultiLevelCommon):
|
||||
"""Test the adjustments made to the qty to procure when minimum,
|
||||
maximum order quantities and quantity multiple are set."""
|
||||
# minimum order quantity:
|
||||
mrp_inv_min = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.prod_min.id)])
|
||||
mrp_inv_min = self.mrp_inventory_obj.search(
|
||||
[("product_mrp_area_id.product_id", "=", self.prod_min.id)]
|
||||
)
|
||||
self.assertEqual(mrp_inv_min.to_procure, 50.0)
|
||||
# maximum order quantity:
|
||||
mrp_inv_max = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.prod_max.id)])
|
||||
mrp_inv_max = self.mrp_inventory_obj.search(
|
||||
[("product_mrp_area_id.product_id", "=", self.prod_max.id)]
|
||||
)
|
||||
self.assertEqual(mrp_inv_max.to_procure, 150)
|
||||
plans = self.planned_order_obj.search([
|
||||
('product_id', '=', self.prod_max.id),
|
||||
])
|
||||
plans = self.planned_order_obj.search([("product_id", "=", self.prod_max.id)])
|
||||
self.assertEqual(len(plans), 2)
|
||||
self.assertIn(100.0, plans.mapped('mrp_qty'))
|
||||
self.assertIn(50.0, plans.mapped('mrp_qty'))
|
||||
self.assertIn(100.0, plans.mapped("mrp_qty"))
|
||||
self.assertIn(50.0, plans.mapped("mrp_qty"))
|
||||
# quantity multiple:
|
||||
mrp_inv_multiple = self.mrp_inventory_obj.search([
|
||||
('product_mrp_area_id.product_id', '=', self.prod_multiple.id)])
|
||||
mrp_inv_multiple = self.mrp_inventory_obj.search(
|
||||
[("product_mrp_area_id.product_id", "=", self.prod_multiple.id)]
|
||||
)
|
||||
self.assertEqual(mrp_inv_multiple.to_procure, 125)
|
||||
|
||||
def test_08_group_demand(self):
|
||||
"""Test demand grouping functionality, `nbr_days`."""
|
||||
pickings = self.stock_picking_obj.search([
|
||||
('product_id', '=', self.prod_test.id),
|
||||
('location_id', '=', self.sec_loc.id)])
|
||||
pickings = self.stock_picking_obj.search(
|
||||
[
|
||||
("product_id", "=", self.prod_test.id),
|
||||
("location_id", "=", self.sec_loc.id),
|
||||
]
|
||||
)
|
||||
self.assertEqual(len(pickings), 5)
|
||||
moves = self.mrp_move_obj.search([
|
||||
('product_id', '=', self.prod_test.id),
|
||||
('mrp_area_id', '=', self.secondary_area.id),
|
||||
])
|
||||
supply_plans = self.planned_order_obj.search([
|
||||
('product_id', '=', self.prod_test.id),
|
||||
('mrp_area_id', '=', self.secondary_area.id),
|
||||
])
|
||||
moves_demand = moves.filtered(lambda m: m.mrp_type == 'd')
|
||||
moves = self.mrp_move_obj.search(
|
||||
[
|
||||
("product_id", "=", self.prod_test.id),
|
||||
("mrp_area_id", "=", self.secondary_area.id),
|
||||
]
|
||||
)
|
||||
supply_plans = self.planned_order_obj.search(
|
||||
[
|
||||
("product_id", "=", self.prod_test.id),
|
||||
("mrp_area_id", "=", self.secondary_area.id),
|
||||
]
|
||||
)
|
||||
moves_demand = moves.filtered(lambda m: m.mrp_type == "d")
|
||||
self.assertEqual(len(moves_demand), 5)
|
||||
# two groups expected:
|
||||
# 1. days 8, 9 and 10.
|
||||
# 2. days 20, and 22.
|
||||
self.assertEqual(len(supply_plans), 2)
|
||||
quantities = supply_plans.mapped('mrp_qty')
|
||||
week_1_expected = sum(moves_demand[0:3].mapped('mrp_qty'))
|
||||
quantities = supply_plans.mapped("mrp_qty")
|
||||
week_1_expected = sum(moves_demand[0:3].mapped("mrp_qty"))
|
||||
self.assertIn(abs(week_1_expected), quantities)
|
||||
week_2_expected = sum(moves_demand[3:].mapped('mrp_qty'))
|
||||
week_2_expected = sum(moves_demand[3:].mapped("mrp_qty"))
|
||||
self.assertIn(abs(week_2_expected), quantities)
|
||||
|
||||
def test_09_isolated_mrp_area_run(self):
|
||||
"""Test running MRP for just one area."""
|
||||
self.mrp_multi_level_wiz.sudo(self.mrp_manager).create({
|
||||
'mrp_area_ids': [(6, 0, self.secondary_area.ids)],
|
||||
}).run_mrp_multi_level()
|
||||
this = self.mrp_inventory_obj.search([
|
||||
('mrp_area_id', '=', self.secondary_area.id)], limit=1)
|
||||
self.mrp_multi_level_wiz.sudo(self.mrp_manager).create(
|
||||
{"mrp_area_ids": [(6, 0, self.secondary_area.ids)]}
|
||||
).run_mrp_multi_level()
|
||||
this = self.mrp_inventory_obj.search(
|
||||
[("mrp_area_id", "=", self.secondary_area.id)], limit=1
|
||||
)
|
||||
self.assertTrue(this)
|
||||
# Only recently exectued areas should have been created by test user:
|
||||
self.assertEqual(this.create_uid, self.mrp_manager)
|
||||
prev = self.mrp_inventory_obj.search([
|
||||
('mrp_area_id', '!=', self.secondary_area.id)], limit=1)
|
||||
prev = self.mrp_inventory_obj.search(
|
||||
[("mrp_area_id", "!=", self.secondary_area.id)], limit=1
|
||||
)
|
||||
self.assertNotEqual(this.create_uid, prev.create_uid)
|
||||
|
||||
def test_11_special_scenario_1(self):
|
||||
"""When grouping demand supply and demand are in the same day but
|
||||
supply goes first."""
|
||||
moves = self.mrp_move_obj.search([
|
||||
('product_id', '=', self.product_scenario_1.id)])
|
||||
moves = self.mrp_move_obj.search(
|
||||
[("product_id", "=", self.product_scenario_1.id)]
|
||||
)
|
||||
self.assertEqual(len(moves), 4)
|
||||
mrp_invs = self.mrp_inventory_obj.search([
|
||||
('product_id', '=', self.product_scenario_1.id)])
|
||||
mrp_invs = self.mrp_inventory_obj.search(
|
||||
[("product_id", "=", self.product_scenario_1.id)]
|
||||
)
|
||||
self.assertEqual(len(mrp_invs), 2)
|
||||
# Net needs = 124 + 90 - 87 = 127 -> 130 (because of qty multiple)
|
||||
self.assertEqual(mrp_invs[0].to_procure, 130)
|
||||
|
||||
@@ -2,68 +2,66 @@
|
||||
# (http://www.eficent.com)
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
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'
|
||||
_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',
|
||||
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,
|
||||
"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,
|
||||
}
|
||||
|
||||
@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"):
|
||||
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
|
||||
"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
|
||||
"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)
|
||||
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)
|
||||
mrp_inventory_obj = self.env['mrp.inventory']
|
||||
mrp_inventory_ids = self.env.context['active_ids'] or []
|
||||
active_model = self.env.context['active_model']
|
||||
if not mrp_inventory_ids or 'item_ids' not in fields:
|
||||
mrp_inventory_obj = self.env["mrp.inventory"]
|
||||
mrp_inventory_ids = self.env.context["active_ids"] or []
|
||||
active_model = self.env.context["active_model"]
|
||||
if not mrp_inventory_ids or "item_ids" not in fields:
|
||||
return res
|
||||
|
||||
assert active_model == 'mrp.inventory', 'Bad context propagation'
|
||||
assert active_model == "mrp.inventory", "Bad context propagation"
|
||||
|
||||
items = item_obj = self.env['mrp.inventory.procure.item']
|
||||
items = item_obj = self.env["mrp.inventory.procure.item"]
|
||||
for line in mrp_inventory_obj.browse(mrp_inventory_ids).mapped(
|
||||
'planned_order_ids'):
|
||||
"planned_order_ids"
|
||||
):
|
||||
if line.qty_released < line.mrp_qty:
|
||||
items += item_obj.create(self._prepare_item(line))
|
||||
res['item_ids'] = [(6, 0, items.ids)]
|
||||
res["item_ids"] = [(6, 0, items.ids)]
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@@ -76,69 +74,58 @@ class MrpInventoryProcure(models.TransientModel):
|
||||
values = item._prepare_procurement_values()
|
||||
# Run procurement
|
||||
try:
|
||||
self.env['procurement.group'].run(
|
||||
self.env["procurement.group"].run(
|
||||
item.product_id,
|
||||
item.qty,
|
||||
item.uom_id,
|
||||
item.location_id,
|
||||
'INT: ' + str(self.env.user.login), # name?
|
||||
'INT: ' + str(self.env.user.login), # origin?
|
||||
values
|
||||
"INT: " + str(self.env.user.login), # name?
|
||||
"INT: " + str(self.env.user.login), # origin?
|
||||
values,
|
||||
)
|
||||
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'}
|
||||
raise UserError("\n".join(errors))
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
|
||||
|
||||
class MrpInventoryProcureItem(models.TransientModel):
|
||||
_name = 'mrp.inventory.procure.item'
|
||||
_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,
|
||||
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=False)
|
||||
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=False)
|
||||
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',
|
||||
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")
|
||||
|
||||
def _prepare_procurement_values(self, group=False):
|
||||
return {
|
||||
'date_planned': fields.Datetime.to_string(
|
||||
fields.Date.from_string(self.date_planned)),
|
||||
'warehouse_id': self.warehouse_id,
|
||||
"date_planned": fields.Datetime.to_string(
|
||||
fields.Date.from_string(self.date_planned)
|
||||
),
|
||||
"warehouse_id": self.warehouse_id,
|
||||
# 'company_id': self.company_id, # TODO: consider company
|
||||
'group_id': group,
|
||||
"group_id": group,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
@api.onchange('uom_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)
|
||||
rec.mrp_inventory_id.to_procure, rec.uom_id
|
||||
)
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
# - Lois Rilo <lois.rilo@eficent.com>
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
from odoo import api, fields, models, exceptions, _
|
||||
from datetime import date, timedelta
|
||||
import logging
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo import _, api, exceptions, fields, models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MultiLevelMrp(models.TransientModel):
|
||||
_name = 'mrp.multi.level'
|
||||
_name = "mrp.multi.level"
|
||||
_description = "Multi Level MRP"
|
||||
|
||||
mrp_area_ids = fields.Many2many(
|
||||
@@ -25,39 +27,40 @@ class MultiLevelMrp(models.TransientModel):
|
||||
@api.model
|
||||
def _prepare_product_mrp_area_data(self, product_mrp_area):
|
||||
qty_available = 0.0
|
||||
product_obj = self.env['product.product']
|
||||
product_obj = self.env["product.product"]
|
||||
location_ids = product_mrp_area.mrp_area_id._get_locations()
|
||||
for location in location_ids:
|
||||
product_l = product_obj.with_context(
|
||||
{'location': location.id}).browse(
|
||||
product_mrp_area.product_id.id)
|
||||
product_l = product_obj.with_context({"location": location.id}).browse(
|
||||
product_mrp_area.product_id.id
|
||||
)
|
||||
qty_available += product_l.qty_available
|
||||
|
||||
return {
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'mrp_qty_available': qty_available,
|
||||
'mrp_llc': product_mrp_area.product_id.llc,
|
||||
"product_mrp_area_id": product_mrp_area.id,
|
||||
"mrp_qty_available": qty_available,
|
||||
"mrp_llc": product_mrp_area.product_id.llc,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_stock_move(
|
||||
self, product_mrp_area, move, direction='in'):
|
||||
if direction == 'out':
|
||||
mrp_type = 'd'
|
||||
self, product_mrp_area, move, direction="in"
|
||||
):
|
||||
if direction == "out":
|
||||
mrp_type = "d"
|
||||
product_qty = -move.product_qty
|
||||
else:
|
||||
mrp_type = 's'
|
||||
mrp_type = "s"
|
||||
product_qty = move.product_qty
|
||||
po = po_line = None
|
||||
mo = origin = order_number = parent_product_id = None
|
||||
if move.purchase_line_id:
|
||||
order_number = move.purchase_line_id.order_id.name
|
||||
origin = 'po'
|
||||
origin = "po"
|
||||
po = move.purchase_line_id.order_id.id
|
||||
po_line = move.purchase_line_id.id
|
||||
if move.production_id:
|
||||
order_number = move.production_id.name
|
||||
origin = 'mo'
|
||||
origin = "mo"
|
||||
mo = move.production_id.id
|
||||
else:
|
||||
if move.move_dest_ids:
|
||||
@@ -65,11 +68,10 @@ class MultiLevelMrp(models.TransientModel):
|
||||
for move_dest_id in move.move_dest_ids:
|
||||
if move_dest_id.production_id:
|
||||
order_number = move_dest_id.production_id.name
|
||||
origin = 'mo'
|
||||
origin = "mo"
|
||||
mo = move_dest_id.production_id.id
|
||||
if move_dest_id.production_id.product_id:
|
||||
parent_product_id = \
|
||||
move_dest_id.production_id.product_id.id
|
||||
parent_product_id = move_dest_id.production_id.product_id.id
|
||||
else:
|
||||
parent_product_id = move_dest_id.product_id.id
|
||||
if order_number is None:
|
||||
@@ -78,69 +80,68 @@ class MultiLevelMrp(models.TransientModel):
|
||||
if move.date_expected.date() > date.today():
|
||||
mrp_date = move.date_expected.date()
|
||||
return {
|
||||
'product_id': move.product_id.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': mo,
|
||||
'purchase_order_id': po,
|
||||
'purchase_line_id': po_line,
|
||||
'stock_move_id': move.id,
|
||||
'mrp_qty': product_qty,
|
||||
'current_qty': product_qty,
|
||||
'mrp_date': mrp_date,
|
||||
'current_date': move.date_expected,
|
||||
'mrp_type': mrp_type,
|
||||
'mrp_origin': origin,
|
||||
'mrp_order_number': order_number,
|
||||
'parent_product_id': parent_product_id,
|
||||
'name': order_number,
|
||||
'state': move.state,
|
||||
"product_id": move.product_id.id,
|
||||
"product_mrp_area_id": product_mrp_area.id,
|
||||
"production_id": mo,
|
||||
"purchase_order_id": po,
|
||||
"purchase_line_id": po_line,
|
||||
"stock_move_id": move.id,
|
||||
"mrp_qty": product_qty,
|
||||
"current_qty": product_qty,
|
||||
"mrp_date": mrp_date,
|
||||
"current_date": move.date_expected,
|
||||
"mrp_type": mrp_type,
|
||||
"mrp_origin": origin,
|
||||
"mrp_order_number": order_number,
|
||||
"parent_product_id": parent_product_id,
|
||||
"name": order_number,
|
||||
"state": move.state,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_planned_order_data(
|
||||
self, product_mrp_area, qty, mrp_date_supply,
|
||||
mrp_action_date, name
|
||||
self, product_mrp_area, qty, mrp_date_supply, mrp_action_date, name
|
||||
):
|
||||
return {
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'mrp_qty': qty,
|
||||
'due_date': mrp_date_supply,
|
||||
'order_release_date': mrp_action_date,
|
||||
'mrp_action': product_mrp_area.supply_method,
|
||||
'qty_released': 0.0,
|
||||
'name': 'Supply: ' + name,
|
||||
"product_mrp_area_id": product_mrp_area.id,
|
||||
"mrp_qty": qty,
|
||||
"due_date": mrp_date_supply,
|
||||
"order_release_date": mrp_action_date,
|
||||
"mrp_action": product_mrp_area.supply_method,
|
||||
"qty_released": 0.0,
|
||||
"name": "Supply: " + name,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_bom_explosion(
|
||||
self, product, bomline, qty, mrp_date_demand_2, bom, name):
|
||||
self, product, bomline, qty, mrp_date_demand_2, bom, name
|
||||
):
|
||||
product_mrp_area = self._get_product_mrp_area_from_product_and_area(
|
||||
bomline.product_id, product.mrp_area_id)
|
||||
bomline.product_id, product.mrp_area_id
|
||||
)
|
||||
if not product_mrp_area:
|
||||
raise exceptions.Warning(
|
||||
_("No MRP product found"))
|
||||
raise exceptions.Warning(_("No MRP product found"))
|
||||
|
||||
return {
|
||||
'mrp_area_id': product.mrp_area_id.id,
|
||||
'product_id': bomline.product_id.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': None,
|
||||
'purchase_line_id': None,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': -(qty * bomline.product_qty), # TODO: review with UoM
|
||||
'current_qty': None,
|
||||
'mrp_date': mrp_date_demand_2,
|
||||
'current_date': None,
|
||||
'mrp_type': 'd',
|
||||
'mrp_origin': 'mrp',
|
||||
'mrp_order_number': None,
|
||||
'parent_product_id': bom.product_id.id,
|
||||
'name':
|
||||
('Demand Bom Explosion: ' + name).replace(
|
||||
'Demand Bom Explosion: Demand Bom '
|
||||
'Explosion: ',
|
||||
'Demand Bom Explosion: '),
|
||||
"mrp_area_id": product.mrp_area_id.id,
|
||||
"product_id": bomline.product_id.id,
|
||||
"product_mrp_area_id": product_mrp_area.id,
|
||||
"production_id": None,
|
||||
"purchase_order_id": None,
|
||||
"purchase_line_id": None,
|
||||
"stock_move_id": None,
|
||||
"mrp_qty": -(qty * bomline.product_qty), # TODO: review with UoM
|
||||
"current_qty": None,
|
||||
"mrp_date": mrp_date_demand_2,
|
||||
"current_date": None,
|
||||
"mrp_type": "d",
|
||||
"mrp_origin": "mrp",
|
||||
"mrp_order_number": None,
|
||||
"parent_product_id": bom.product_id.id,
|
||||
"name": ("Demand Bom Explosion: " + name).replace(
|
||||
"Demand Bom Explosion: Demand Bom " "Explosion: ",
|
||||
"Demand Bom Explosion: ",
|
||||
),
|
||||
}
|
||||
|
||||
@api.model
|
||||
@@ -158,18 +159,14 @@ class MultiLevelMrp(models.TransientModel):
|
||||
date_str = fields.Date.to_string(mrp_date)
|
||||
dt = fields.Datetime.from_string(date_str)
|
||||
# dt is at the beginning of the day (00:00)
|
||||
res = calendar.plan_days(
|
||||
-1 * product_mrp_area.mrp_lead_time, dt)
|
||||
res = calendar.plan_days(-1 * product_mrp_area.mrp_lead_time, dt)
|
||||
mrp_action_date = res.date()
|
||||
else:
|
||||
mrp_action_date = mrp_date - timedelta(
|
||||
days=product_mrp_area.mrp_lead_time)
|
||||
mrp_action_date = mrp_date - timedelta(days=product_mrp_area.mrp_lead_time)
|
||||
return mrp_action_date, mrp_date_supply
|
||||
|
||||
@api.model
|
||||
def explode_action(
|
||||
self, product_mrp_area_id, mrp_action_date, name, qty, action
|
||||
):
|
||||
def explode_action(self, product_mrp_area_id, mrp_action_date, name, qty, action):
|
||||
"""Explode requirements."""
|
||||
mrp_date_demand = mrp_action_date
|
||||
if mrp_date_demand < date.today():
|
||||
@@ -184,52 +181,56 @@ class MultiLevelMrp(models.TransientModel):
|
||||
if bomcount != 1:
|
||||
continue
|
||||
for bomline in bom.bom_line_ids:
|
||||
if bomline.product_qty <= 0.00 or \
|
||||
bomline.product_id.type != 'product':
|
||||
if bomline.product_qty <= 0.00 or bomline.product_id.type != "product":
|
||||
continue
|
||||
if self.with_context(mrp_explosion=True)._exclude_from_mrp(
|
||||
bomline.product_id,
|
||||
product_mrp_area_id.mrp_area_id):
|
||||
bomline.product_id, product_mrp_area_id.mrp_area_id
|
||||
):
|
||||
# Stop explosion.
|
||||
continue
|
||||
# TODO: review: mrp_transit_delay, mrp_inspection_delay
|
||||
mrp_date_demand_2 = mrp_date_demand - timedelta(
|
||||
days=(product_mrp_area_id.mrp_transit_delay +
|
||||
product_mrp_area_id.mrp_inspection_delay))
|
||||
move_data = \
|
||||
self._prepare_mrp_move_data_bom_explosion(
|
||||
product_mrp_area_id, bomline, qty,
|
||||
mrp_date_demand_2,
|
||||
bom, name)
|
||||
mrpmove_id2 = self.env['mrp.move'].create(move_data)
|
||||
days=(
|
||||
product_mrp_area_id.mrp_transit_delay
|
||||
+ product_mrp_area_id.mrp_inspection_delay
|
||||
)
|
||||
)
|
||||
move_data = self._prepare_mrp_move_data_bom_explosion(
|
||||
product_mrp_area_id, bomline, qty, mrp_date_demand_2, bom, name
|
||||
)
|
||||
mrpmove_id2 = self.env["mrp.move"].create(move_data)
|
||||
if hasattr(action, "mrp_move_down_ids"):
|
||||
action.mrp_move_down_ids = [(4, mrpmove_id2.id)]
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def create_action(
|
||||
self, product_mrp_area_id, mrp_date, mrp_qty, name, values=None,
|
||||
):
|
||||
def create_action(self, product_mrp_area_id, mrp_date, mrp_qty, name, values=None):
|
||||
if not values:
|
||||
values = {}
|
||||
if not isinstance(mrp_date, date):
|
||||
mrp_date = fields.Date.from_string(mrp_date)
|
||||
action_date, date_supply = \
|
||||
self._get_action_and_supply_dates(product_mrp_area_id, mrp_date)
|
||||
action_date, date_supply = self._get_action_and_supply_dates(
|
||||
product_mrp_area_id, mrp_date
|
||||
)
|
||||
return self.create_planned_order(
|
||||
product_mrp_area_id, mrp_qty, name, date_supply,
|
||||
action_date, values=values)
|
||||
product_mrp_area_id, mrp_qty, name, date_supply, action_date, values=values
|
||||
)
|
||||
|
||||
@api.model
|
||||
def create_planned_order(
|
||||
self, product_mrp_area_id, mrp_qty,
|
||||
name, mrp_date_supply, mrp_action_date, values=None,
|
||||
self,
|
||||
product_mrp_area_id,
|
||||
mrp_qty,
|
||||
name,
|
||||
mrp_date_supply,
|
||||
mrp_action_date,
|
||||
values=None,
|
||||
):
|
||||
self = self.with_context(auditlog_disabled=True)
|
||||
if self._exclude_from_mrp(
|
||||
product_mrp_area_id.product_id,
|
||||
product_mrp_area_id.mrp_area_id):
|
||||
values['qty_ordered'] = 0.0
|
||||
product_mrp_area_id.product_id, product_mrp_area_id.mrp_area_id
|
||||
):
|
||||
values["qty_ordered"] = 0.0
|
||||
return values
|
||||
|
||||
qty_ordered = values.get("qty_ordered", 0.0) if values else 0.0
|
||||
@@ -238,21 +239,21 @@ class MultiLevelMrp(models.TransientModel):
|
||||
qty = product_mrp_area_id._adjust_qty_to_order(qty_to_order)
|
||||
qty_to_order -= qty
|
||||
order_data = self._prepare_planned_order_data(
|
||||
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date,
|
||||
name)
|
||||
planned_order = self.env['mrp.planned.order'].create(order_data)
|
||||
product_mrp_area_id, qty, mrp_date_supply, mrp_action_date, name
|
||||
)
|
||||
planned_order = self.env["mrp.planned.order"].create(order_data)
|
||||
qty_ordered = qty_ordered + qty
|
||||
|
||||
if product_mrp_area_id.supply_method == 'manufacture':
|
||||
if product_mrp_area_id.supply_method == "manufacture":
|
||||
self.explode_action(
|
||||
product_mrp_area_id, mrp_action_date,
|
||||
name, qty, planned_order)
|
||||
product_mrp_area_id, mrp_action_date, name, qty, planned_order
|
||||
)
|
||||
|
||||
values['qty_ordered'] = qty_ordered
|
||||
log_msg = '[%s] %s: qty_ordered = %s' % (
|
||||
values["qty_ordered"] = qty_ordered
|
||||
log_msg = "[{}] {}: qty_ordered = {}".format(
|
||||
product_mrp_area_id.mrp_area_id.name,
|
||||
product_mrp_area_id.product_id.default_code or
|
||||
product_mrp_area_id.product_id.name,
|
||||
product_mrp_area_id.product_id.default_code
|
||||
or product_mrp_area_id.product_id.name,
|
||||
qty_ordered,
|
||||
)
|
||||
logger.debug(log_msg)
|
||||
@@ -260,47 +261,48 @@ class MultiLevelMrp(models.TransientModel):
|
||||
|
||||
@api.model
|
||||
def _mrp_cleanup(self, mrp_areas):
|
||||
logger.info('Start MRP Cleanup')
|
||||
logger.info("Start MRP Cleanup")
|
||||
domain = []
|
||||
if mrp_areas:
|
||||
domain += [('mrp_area_id', 'in', mrp_areas.ids)]
|
||||
self.env['mrp.move'].search(domain).unlink()
|
||||
self.env['mrp.inventory'].search(domain).unlink()
|
||||
domain += [('fixed', '=', False)]
|
||||
self.env['mrp.planned.order'].search(domain).unlink()
|
||||
logger.info('End MRP Cleanup')
|
||||
domain += [("mrp_area_id", "in", mrp_areas.ids)]
|
||||
self.env["mrp.move"].search(domain).unlink()
|
||||
self.env["mrp.inventory"].search(domain).unlink()
|
||||
domain += [("fixed", "=", False)]
|
||||
self.env["mrp.planned.order"].search(domain).unlink()
|
||||
logger.info("End MRP Cleanup")
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _low_level_code_calculation(self):
|
||||
logger.info('Start low level code calculation')
|
||||
logger.info("Start low level code calculation")
|
||||
counter = 999999
|
||||
llc = 0
|
||||
self.env['product.product'].search([]).write({'llc': llc})
|
||||
products = self.env['product.product'].search([('llc', '=', llc)])
|
||||
self.env["product.product"].search([]).write({"llc": llc})
|
||||
products = self.env["product.product"].search([("llc", "=", llc)])
|
||||
if products:
|
||||
counter = len(products)
|
||||
log_msg = 'Low level code 0 finished - Nbr. products: %s' % counter
|
||||
log_msg = "Low level code 0 finished - Nbr. products: %s" % counter
|
||||
logger.info(log_msg)
|
||||
|
||||
while counter:
|
||||
llc += 1
|
||||
products = self.env['product.product'].search(
|
||||
[('llc', '=', llc - 1)])
|
||||
p_templates = products.mapped('product_tmpl_id')
|
||||
bom_lines = self.env['mrp.bom.line'].search(
|
||||
[('product_id.llc', '=', llc - 1),
|
||||
('bom_id.product_tmpl_id', 'in', p_templates.ids)])
|
||||
products = bom_lines.mapped('product_id')
|
||||
products.write({'llc': llc})
|
||||
products = self.env['product.product'].search([('llc', '=', llc)])
|
||||
products = self.env["product.product"].search([("llc", "=", llc - 1)])
|
||||
p_templates = products.mapped("product_tmpl_id")
|
||||
bom_lines = self.env["mrp.bom.line"].search(
|
||||
[
|
||||
("product_id.llc", "=", llc - 1),
|
||||
("bom_id.product_tmpl_id", "in", p_templates.ids),
|
||||
]
|
||||
)
|
||||
products = bom_lines.mapped("product_id")
|
||||
products.write({"llc": llc})
|
||||
products = self.env["product.product"].search([("llc", "=", llc)])
|
||||
counter = len(products)
|
||||
log_msg = 'Low level code %s finished - Nbr. products: %s' % (
|
||||
llc, counter)
|
||||
log_msg = "Low level code {} finished - Nbr. products: {}".format(llc, counter)
|
||||
logger.info(log_msg)
|
||||
|
||||
mrp_lowest_llc = llc
|
||||
logger.info('End low level code calculation')
|
||||
logger.info("End low level code calculation")
|
||||
return mrp_lowest_llc
|
||||
|
||||
@api.model
|
||||
@@ -312,21 +314,19 @@ class MultiLevelMrp(models.TransientModel):
|
||||
|
||||
@api.model
|
||||
def _calculate_mrp_applicable(self, mrp_areas):
|
||||
logger.info('Start Calculate MRP Applicable')
|
||||
logger.info("Start Calculate MRP Applicable")
|
||||
domain = []
|
||||
if mrp_areas:
|
||||
domain += [('mrp_area_id', 'in', mrp_areas.ids)]
|
||||
self.env['product.mrp.area'].search(domain).write(
|
||||
{'mrp_applicable': False})
|
||||
domain += [('product_id.type', '=', 'product')]
|
||||
self.env['product.mrp.area'].search(domain).write(
|
||||
{'mrp_applicable': True})
|
||||
domain += [("mrp_area_id", "in", mrp_areas.ids)]
|
||||
self.env["product.mrp.area"].search(domain).write({"mrp_applicable": False})
|
||||
domain += [("product_id.type", "=", "product")]
|
||||
self.env["product.mrp.area"].search(domain).write({"mrp_applicable": True})
|
||||
self._adjust_mrp_applicable(mrp_areas)
|
||||
count_domain = [('mrp_applicable', '=', True)]
|
||||
count_domain = [("mrp_applicable", "=", True)]
|
||||
if mrp_areas:
|
||||
count_domain += [('mrp_area_id', 'in', mrp_areas.ids)]
|
||||
counter = self.env['product.mrp.area'].search(count_domain, count=True)
|
||||
log_msg = 'End Calculate MRP Applicable: %s' % counter
|
||||
count_domain += [("mrp_area_id", "in", mrp_areas.ids)]
|
||||
counter = self.env["product.mrp.area"].search(count_domain, count=True)
|
||||
log_msg = "End Calculate MRP Applicable: %s" % counter
|
||||
logger.info(log_msg)
|
||||
return True
|
||||
|
||||
@@ -341,28 +341,28 @@ class MultiLevelMrp(models.TransientModel):
|
||||
def _in_stock_moves_domain(self, product_mrp_area):
|
||||
locations = product_mrp_area.mrp_area_id._get_locations()
|
||||
return [
|
||||
('product_id', '=', product_mrp_area.product_id.id),
|
||||
('state', 'not in', ['done', 'cancel']),
|
||||
('product_qty', '>', 0.00),
|
||||
('location_id', 'not in', locations.ids),
|
||||
('location_dest_id', 'in', locations.ids),
|
||||
("product_id", "=", product_mrp_area.product_id.id),
|
||||
("state", "not in", ["done", "cancel"]),
|
||||
("product_qty", ">", 0.00),
|
||||
("location_id", "not in", locations.ids),
|
||||
("location_dest_id", "in", locations.ids),
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _out_stock_moves_domain(self, product_mrp_area):
|
||||
locations = product_mrp_area.mrp_area_id._get_locations()
|
||||
return [
|
||||
('product_id', '=', product_mrp_area.product_id.id),
|
||||
('state', 'not in', ['done', 'cancel']),
|
||||
('product_qty', '>', 0.00),
|
||||
('location_id', 'in', locations.ids),
|
||||
('location_dest_id', 'not in', locations.ids),
|
||||
("product_id", "=", product_mrp_area.product_id.id),
|
||||
("state", "not in", ["done", "cancel"]),
|
||||
("product_qty", ">", 0.00),
|
||||
("location_id", "in", locations.ids),
|
||||
("location_dest_id", "not in", locations.ids),
|
||||
]
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_stock_move(self, product_mrp_area):
|
||||
move_obj = self.env['stock.move']
|
||||
mrp_move_obj = self.env['mrp.move']
|
||||
move_obj = self.env["stock.move"]
|
||||
mrp_move_obj = self.env["mrp.move"]
|
||||
in_domain = self._in_stock_moves_domain(product_mrp_area)
|
||||
in_moves = move_obj.search(in_domain)
|
||||
out_domain = self._out_stock_moves_domain(product_mrp_area)
|
||||
@@ -370,67 +370,74 @@ class MultiLevelMrp(models.TransientModel):
|
||||
if in_moves:
|
||||
for move in in_moves:
|
||||
move_data = self._prepare_mrp_move_data_from_stock_move(
|
||||
product_mrp_area, move, direction='in')
|
||||
product_mrp_area, move, direction="in"
|
||||
)
|
||||
mrp_move_obj.create(move_data)
|
||||
if out_moves:
|
||||
for move in out_moves:
|
||||
move_data = self._prepare_mrp_move_data_from_stock_move(
|
||||
product_mrp_area, move, direction='out')
|
||||
product_mrp_area, move, direction="out"
|
||||
)
|
||||
mrp_move_obj.create(move_data)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _prepare_mrp_move_data_from_purchase_order(
|
||||
self, poline, product_mrp_area):
|
||||
def _prepare_mrp_move_data_from_purchase_order(self, poline, product_mrp_area):
|
||||
mrp_date = date.today()
|
||||
if fields.Date.from_string(poline.date_planned) > date.today():
|
||||
mrp_date = fields.Date.from_string(poline.date_planned)
|
||||
return {
|
||||
'product_id': poline.product_id.id,
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'production_id': None,
|
||||
'purchase_order_id': poline.order_id.id,
|
||||
'purchase_line_id': poline.id,
|
||||
'stock_move_id': None,
|
||||
'mrp_qty': poline.product_qty,
|
||||
'current_qty': poline.product_qty,
|
||||
'mrp_date': mrp_date,
|
||||
'current_date': poline.date_planned,
|
||||
'mrp_type': 's',
|
||||
'mrp_origin': 'po',
|
||||
'mrp_order_number': poline.order_id.name,
|
||||
'parent_product_id': None,
|
||||
'name': poline.order_id.name,
|
||||
'state': poline.order_id.state,
|
||||
"product_id": poline.product_id.id,
|
||||
"product_mrp_area_id": product_mrp_area.id,
|
||||
"production_id": None,
|
||||
"purchase_order_id": poline.order_id.id,
|
||||
"purchase_line_id": poline.id,
|
||||
"stock_move_id": None,
|
||||
"mrp_qty": poline.product_qty,
|
||||
"current_qty": poline.product_qty,
|
||||
"mrp_date": mrp_date,
|
||||
"current_date": poline.date_planned,
|
||||
"mrp_type": "s",
|
||||
"mrp_origin": "po",
|
||||
"mrp_order_number": poline.order_id.name,
|
||||
"parent_product_id": None,
|
||||
"name": poline.order_id.name,
|
||||
"state": poline.order_id.state,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_from_purchase_order(self, product_mrp_area):
|
||||
location_ids = product_mrp_area.mrp_area_id._get_locations()
|
||||
picking_types = self.env['stock.picking.type'].search(
|
||||
[('default_location_dest_id', 'in',
|
||||
location_ids.ids)])
|
||||
picking_types = self.env["stock.picking.type"].search(
|
||||
[("default_location_dest_id", "in", location_ids.ids)]
|
||||
)
|
||||
picking_type_ids = [ptype.id for ptype in picking_types]
|
||||
orders = self.env['purchase.order'].search(
|
||||
[('picking_type_id', 'in', picking_type_ids),
|
||||
('state', 'in', ['draft', 'sent', 'to approve'])])
|
||||
po_lines = self.env['purchase.order.line'].search(
|
||||
[('order_id', 'in', orders.ids),
|
||||
('product_qty', '>', 0.0),
|
||||
('product_id', '=', product_mrp_area.product_id.id)])
|
||||
orders = self.env["purchase.order"].search(
|
||||
[
|
||||
("picking_type_id", "in", picking_type_ids),
|
||||
("state", "in", ["draft", "sent", "to approve"]),
|
||||
]
|
||||
)
|
||||
po_lines = self.env["purchase.order.line"].search(
|
||||
[
|
||||
("order_id", "in", orders.ids),
|
||||
("product_qty", ">", 0.0),
|
||||
("product_id", "=", product_mrp_area.product_id.id),
|
||||
]
|
||||
)
|
||||
|
||||
for line in po_lines:
|
||||
mrp_move_data = \
|
||||
self._prepare_mrp_move_data_from_purchase_order(
|
||||
line, product_mrp_area)
|
||||
self.env['mrp.move'].create(mrp_move_data)
|
||||
mrp_move_data = self._prepare_mrp_move_data_from_purchase_order(
|
||||
line, product_mrp_area
|
||||
)
|
||||
self.env["mrp.move"].create(mrp_move_data)
|
||||
|
||||
@api.model
|
||||
def _get_product_mrp_area_from_product_and_area(self, product, mrp_area):
|
||||
return self.env['product.mrp.area'].search([
|
||||
('product_id', '=', product.id),
|
||||
('mrp_area_id', '=', mrp_area.id),
|
||||
], limit=1)
|
||||
return self.env["product.mrp.area"].search(
|
||||
[("product_id", "=", product.id), ("mrp_area_id", "=", mrp_area.id)],
|
||||
limit=1,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move(self, product_mrp_area):
|
||||
@@ -441,35 +448,37 @@ class MultiLevelMrp(models.TransientModel):
|
||||
@api.model
|
||||
def _exclude_from_mrp(self, product, mrp_area):
|
||||
""" To extend with various logic where needed. """
|
||||
product_mrp_area = self.env['product.mrp.area'].search(
|
||||
[('product_id', '=', product.id),
|
||||
('mrp_area_id', '=', mrp_area.id)], limit=1)
|
||||
product_mrp_area = self.env["product.mrp.area"].search(
|
||||
[("product_id", "=", product.id), ("mrp_area_id", "=", mrp_area.id)],
|
||||
limit=1,
|
||||
)
|
||||
if not product_mrp_area:
|
||||
return True
|
||||
return product_mrp_area.mrp_exclude
|
||||
|
||||
@api.model
|
||||
def _mrp_initialisation(self, mrp_areas):
|
||||
logger.info('Start MRP initialisation')
|
||||
logger.info("Start MRP initialisation")
|
||||
if not mrp_areas:
|
||||
mrp_areas = self.env['mrp.area'].search([])
|
||||
product_mrp_areas = self.env['product.mrp.area'].search([
|
||||
('mrp_area_id', 'in', mrp_areas.ids),
|
||||
('mrp_applicable', '=', True),
|
||||
])
|
||||
mrp_areas = self.env["mrp.area"].search([])
|
||||
product_mrp_areas = self.env["product.mrp.area"].search(
|
||||
[("mrp_area_id", "in", mrp_areas.ids), ("mrp_applicable", "=", True)]
|
||||
)
|
||||
init_counter = 0
|
||||
for mrp_area in mrp_areas:
|
||||
for product_mrp_area in product_mrp_areas.filtered(
|
||||
lambda a: a.mrp_area_id == mrp_area):
|
||||
if self._exclude_from_mrp(
|
||||
product_mrp_area.product_id, mrp_area):
|
||||
lambda a: a.mrp_area_id == mrp_area
|
||||
):
|
||||
if self._exclude_from_mrp(product_mrp_area.product_id, mrp_area):
|
||||
continue
|
||||
init_counter += 1
|
||||
log_msg = 'MRP Init: %s - %s ' % (
|
||||
init_counter, product_mrp_area.display_name)
|
||||
log_msg = "MRP Init: {} - {} ".format(
|
||||
init_counter,
|
||||
product_mrp_area.display_name,
|
||||
)
|
||||
logger.info(log_msg)
|
||||
self._init_mrp_move(product_mrp_area)
|
||||
logger.info('End MRP initialisation')
|
||||
logger.info("End MRP initialisation")
|
||||
|
||||
@api.model
|
||||
def _init_mrp_move_grouped_demand(self, nbr_create, product_mrp_area):
|
||||
@@ -480,30 +489,35 @@ class MultiLevelMrp(models.TransientModel):
|
||||
for move in product_mrp_area.mrp_move_ids:
|
||||
if self._exclude_move(move):
|
||||
continue
|
||||
if last_date and (
|
||||
if (
|
||||
last_date
|
||||
and (
|
||||
fields.Date.from_string(move.mrp_date)
|
||||
>= last_date + timedelta(days=grouping_delta)) and (
|
||||
(onhand + last_qty + move.mrp_qty)
|
||||
< product_mrp_area.mrp_minimum_stock
|
||||
or (onhand + last_qty)
|
||||
< product_mrp_area.mrp_minimum_stock):
|
||||
name = 'Grouped Demand for %d Days' % grouping_delta
|
||||
qtytoorder = product_mrp_area.mrp_minimum_stock - \
|
||||
onhand - last_qty
|
||||
>= last_date + timedelta(days=grouping_delta)
|
||||
)
|
||||
and (
|
||||
(onhand + last_qty + move.mrp_qty)
|
||||
< product_mrp_area.mrp_minimum_stock
|
||||
or (onhand + last_qty) < product_mrp_area.mrp_minimum_stock
|
||||
)
|
||||
):
|
||||
name = "Grouped Demand for %d Days" % grouping_delta
|
||||
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
|
||||
cm = self.create_action(
|
||||
product_mrp_area_id=product_mrp_area,
|
||||
mrp_date=last_date,
|
||||
mrp_qty=qtytoorder,
|
||||
name=name)
|
||||
qty_ordered = cm.get('qty_ordered', 0.0)
|
||||
name=name,
|
||||
)
|
||||
qty_ordered = cm.get("qty_ordered", 0.0)
|
||||
onhand = onhand + last_qty + qty_ordered
|
||||
last_date = None
|
||||
last_qty = 0.00
|
||||
nbr_create += 1
|
||||
if (onhand + last_qty + move.mrp_qty) < \
|
||||
product_mrp_area.mrp_minimum_stock or \
|
||||
(onhand + last_qty) < \
|
||||
product_mrp_area.mrp_minimum_stock:
|
||||
if (
|
||||
(onhand + last_qty + move.mrp_qty) < product_mrp_area.mrp_minimum_stock
|
||||
or (onhand + last_qty) < product_mrp_area.mrp_minimum_stock
|
||||
):
|
||||
if not last_date or last_qty == 0.0:
|
||||
last_date = fields.Date.from_string(move.mrp_date)
|
||||
last_qty = move.mrp_qty
|
||||
@@ -514,12 +528,15 @@ class MultiLevelMrp(models.TransientModel):
|
||||
onhand += move.mrp_qty
|
||||
|
||||
if last_date and last_qty != 0.00:
|
||||
name = 'Grouped Demand for %d Days' % grouping_delta
|
||||
name = "Grouped Demand for %d Days" % grouping_delta
|
||||
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand - last_qty
|
||||
cm = self.create_action(
|
||||
product_mrp_area_id=product_mrp_area, mrp_date=last_date,
|
||||
mrp_qty=qtytoorder, name=name)
|
||||
qty_ordered = cm.get('qty_ordered', 0.0)
|
||||
product_mrp_area_id=product_mrp_area,
|
||||
mrp_date=last_date,
|
||||
mrp_qty=qtytoorder,
|
||||
name=name,
|
||||
)
|
||||
qty_ordered = cm.get("qty_ordered", 0.0)
|
||||
onhand += qty_ordered
|
||||
nbr_create += 1
|
||||
return nbr_create
|
||||
@@ -531,17 +548,17 @@ class MultiLevelMrp(models.TransientModel):
|
||||
|
||||
@api.model
|
||||
def _mrp_calculation(self, mrp_lowest_llc, mrp_areas):
|
||||
logger.info('Start MRP calculation')
|
||||
product_mrp_area_obj = self.env['product.mrp.area']
|
||||
logger.info("Start MRP calculation")
|
||||
product_mrp_area_obj = self.env["product.mrp.area"]
|
||||
counter = 0
|
||||
if not mrp_areas:
|
||||
mrp_areas = self.env['mrp.area'].search([])
|
||||
mrp_areas = self.env["mrp.area"].search([])
|
||||
for mrp_area in mrp_areas:
|
||||
llc = 0
|
||||
while mrp_lowest_llc > llc:
|
||||
product_mrp_areas = product_mrp_area_obj.search(
|
||||
[('product_id.llc', '=', llc),
|
||||
('mrp_area_id', '=', mrp_area.id)])
|
||||
[("product_id.llc", "=", llc), ("mrp_area_id", "=", mrp_area.id)]
|
||||
)
|
||||
llc += 1
|
||||
|
||||
for product_mrp_area in product_mrp_areas:
|
||||
@@ -551,40 +568,47 @@ class MultiLevelMrp(models.TransientModel):
|
||||
for move in product_mrp_area.mrp_move_ids:
|
||||
if self._exclude_move(move):
|
||||
continue
|
||||
qtytoorder = product_mrp_area.mrp_minimum_stock - \
|
||||
onhand - move.mrp_qty
|
||||
qtytoorder = (
|
||||
product_mrp_area.mrp_minimum_stock
|
||||
- onhand
|
||||
- move.mrp_qty
|
||||
)
|
||||
if qtytoorder > 0.0:
|
||||
cm = self.create_action(
|
||||
product_mrp_area_id=product_mrp_area,
|
||||
mrp_date=move.mrp_date,
|
||||
mrp_qty=qtytoorder, name=move.name)
|
||||
qty_ordered = cm['qty_ordered']
|
||||
mrp_qty=qtytoorder,
|
||||
name=move.name,
|
||||
)
|
||||
qty_ordered = cm["qty_ordered"]
|
||||
onhand += move.mrp_qty + qty_ordered
|
||||
nbr_create += 1
|
||||
else:
|
||||
onhand += move.mrp_qty
|
||||
else:
|
||||
nbr_create = self._init_mrp_move_grouped_demand(
|
||||
nbr_create, product_mrp_area)
|
||||
nbr_create, product_mrp_area
|
||||
)
|
||||
|
||||
if onhand < product_mrp_area.mrp_minimum_stock and \
|
||||
nbr_create == 0:
|
||||
qtytoorder = \
|
||||
product_mrp_area.mrp_minimum_stock - onhand
|
||||
if onhand < product_mrp_area.mrp_minimum_stock and nbr_create == 0:
|
||||
qtytoorder = product_mrp_area.mrp_minimum_stock - onhand
|
||||
cm = self.create_action(
|
||||
product_mrp_area_id=product_mrp_area,
|
||||
mrp_date=date.today(),
|
||||
mrp_qty=qtytoorder,
|
||||
name='Minimum Stock')
|
||||
qty_ordered = cm['qty_ordered']
|
||||
name="Minimum Stock",
|
||||
)
|
||||
qty_ordered = cm["qty_ordered"]
|
||||
onhand += qty_ordered
|
||||
counter += 1
|
||||
|
||||
log_msg = 'MRP Calculation LLC %s Finished - Nbr. products: %s' % (
|
||||
llc - 1, counter)
|
||||
log_msg = "MRP Calculation LLC {} Finished - Nbr. products: {}".format(
|
||||
llc - 1,
|
||||
counter,
|
||||
)
|
||||
logger.info(log_msg)
|
||||
|
||||
logger.info('Enb MRP calculation')
|
||||
logger.info("Enb MRP calculation")
|
||||
|
||||
@api.model
|
||||
def _get_demand_groups(self, product_mrp_area):
|
||||
@@ -595,9 +619,7 @@ class MultiLevelMrp(models.TransientModel):
|
||||
AND mrp_type = 'd'
|
||||
GROUP BY mrp_date
|
||||
"""
|
||||
params = {
|
||||
'mrp_product': product_mrp_area.id
|
||||
}
|
||||
params = {"mrp_product": product_mrp_area.id}
|
||||
return query, params
|
||||
|
||||
@api.model
|
||||
@@ -609,9 +631,7 @@ class MultiLevelMrp(models.TransientModel):
|
||||
AND mrp_type = 's'
|
||||
GROUP BY mrp_date
|
||||
"""
|
||||
params = {
|
||||
'mrp_product': product_mrp_area.id,
|
||||
}
|
||||
params = {"mrp_product": product_mrp_area.id}
|
||||
return query, params
|
||||
|
||||
@api.model
|
||||
@@ -622,15 +642,13 @@ class MultiLevelMrp(models.TransientModel):
|
||||
WHERE product_mrp_area_id = %(mrp_product)s
|
||||
GROUP BY due_date
|
||||
"""
|
||||
params = {
|
||||
'mrp_product': product_mrp_area.id
|
||||
}
|
||||
params = {"mrp_product": product_mrp_area.id}
|
||||
return query, params
|
||||
|
||||
@api.model
|
||||
def _init_mrp_inventory(self, product_mrp_area):
|
||||
mrp_move_obj = self.env['mrp.move']
|
||||
planned_order_obj = self.env['mrp.planned.order']
|
||||
mrp_move_obj = self.env["mrp.move"]
|
||||
planned_order_obj = self.env["mrp.planned.order"]
|
||||
# Read Demand
|
||||
demand_qty_by_date = {}
|
||||
query, params = self._get_demand_groups(product_mrp_area)
|
||||
@@ -650,59 +668,60 @@ class MultiLevelMrp(models.TransientModel):
|
||||
for mrp_date, qty in self.env.cr.fetchall():
|
||||
planned_qty_by_date[mrp_date] = qty
|
||||
# Dates
|
||||
moves_dates = mrp_move_obj.search([
|
||||
('product_mrp_area_id', '=', product_mrp_area.id)],
|
||||
order='mrp_date').mapped('mrp_date')
|
||||
action_dates = planned_order_obj.search([
|
||||
('product_mrp_area_id', '=', product_mrp_area.id)],
|
||||
order='due_date').mapped('due_date')
|
||||
moves_dates = mrp_move_obj.search(
|
||||
[("product_mrp_area_id", "=", product_mrp_area.id)], order="mrp_date"
|
||||
).mapped("mrp_date")
|
||||
action_dates = planned_order_obj.search(
|
||||
[("product_mrp_area_id", "=", product_mrp_area.id)], order="due_date"
|
||||
).mapped("due_date")
|
||||
mrp_dates = set(moves_dates + action_dates)
|
||||
on_hand_qty = product_mrp_area.product_id.with_context(
|
||||
location=product_mrp_area.mrp_area_id.location_id.id
|
||||
)._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
|
||||
for mdt in sorted(mrp_dates):
|
||||
mrp_inventory_data = {
|
||||
'product_mrp_area_id': product_mrp_area.id,
|
||||
'date': mdt,
|
||||
"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)
|
||||
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
|
||||
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
|
||||
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
|
||||
planned_order_obj.search([
|
||||
('due_date', '=', mdt),
|
||||
('product_mrp_area_id', '=', product_mrp_area.id),
|
||||
]).write(
|
||||
{'mrp_inventory_id': inv_id.id})
|
||||
planned_order_obj.search(
|
||||
[
|
||||
("due_date", "=", mdt),
|
||||
("product_mrp_area_id", "=", product_mrp_area.id),
|
||||
]
|
||||
).write({"mrp_inventory_id": inv_id.id})
|
||||
|
||||
@api.model
|
||||
def _mrp_final_process(self, mrp_areas):
|
||||
logger.info('Start MRP final process')
|
||||
domain = [('product_id.llc', '<', 9999)]
|
||||
logger.info("Start MRP final process")
|
||||
domain = [("product_id.llc", "<", 9999)]
|
||||
if mrp_areas:
|
||||
domain += [('mrp_area_id', 'in', mrp_areas.ids)]
|
||||
product_mrp_area_ids = self.env['product.mrp.area'].search(domain)
|
||||
domain += [("mrp_area_id", "in", mrp_areas.ids)]
|
||||
product_mrp_area_ids = self.env["product.mrp.area"].search(domain)
|
||||
|
||||
for product_mrp_area in product_mrp_area_ids:
|
||||
# Build the time-phased inventory
|
||||
if self._exclude_from_mrp(
|
||||
product_mrp_area.product_id,
|
||||
product_mrp_area.mrp_area_id):
|
||||
product_mrp_area.product_id, product_mrp_area.mrp_area_id
|
||||
):
|
||||
continue
|
||||
self._init_mrp_inventory(product_mrp_area)
|
||||
logger.info('End MRP final process')
|
||||
logger.info("End MRP final process")
|
||||
|
||||
@api.multi
|
||||
def run_mrp_multi_level(self):
|
||||
|
||||
Reference in New Issue
Block a user