Initial commit: OCA Mrp packages (117 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:05 +02:00
commit 277e84fd7a
4403 changed files with 395154 additions and 0 deletions

View file

@ -0,0 +1 @@
from . import test_mrp_multi_level

View file

@ -0,0 +1,659 @@
# Copyright 2018-19 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from datetime import datetime, timedelta
from odoo.tests import Form
from odoo.tests.common import TransactionCase
class TestMrpMultiLevelCommon(TransactionCase):
@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.quant_obj = cls.env["stock.quant"]
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.lot_obj = cls.env["stock.lot"]
cls.mrp_bom_obj = cls.env["mrp.bom"]
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.fp_3 = cls.env.ref("mrp_multi_level.product_product_fp_3")
cls.fp_4 = cls.env.ref("mrp_multi_level.product_product_fp_4")
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.sf_3 = cls.env.ref("mrp_multi_level.product_product_sf_3")
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.pp_3 = cls.env.ref("mrp_multi_level.product_product_pp_3")
cls.pp_4 = cls.env.ref("mrp_multi_level.product_product_pp_4")
cls.product_4b = cls.env.ref("product.product_product_4b")
cls.product_4c = cls.env.ref("product.product_product_4c")
cls.av_11 = cls.env.ref("mrp_multi_level.product_product_av_11")
cls.av_12 = cls.env.ref("mrp_multi_level.product_product_av_12")
cls.av_21 = cls.env.ref("mrp_multi_level.product_product_av_21")
cls.av_22 = cls.env.ref("mrp_multi_level.product_product_av_22")
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")
# Add calendar to WH:
cls.wh.calendar_id = cls.calendar
# Partner:
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")
cls.mrp_manager = cls._create_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}
)
# 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,
}
)
# 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, {"partner_id": 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, {"partner_id": 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, {"partner_id": 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, {"partner_id": 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, {"partner_id": 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,
}
)
# Another product:
cls.product_tz = cls.product_obj.create(
{
"name": "Product Timezone",
"type": "product",
"list_price": 100.0,
"route_ids": [(6, 0, [route_buy])],
"seller_ids": [(0, 0, {"partner_id": vendor1.id, "price": 20.0})],
}
)
cls.product_mrp_area_obj.create(
{"product_id": cls.product_tz.id, "mrp_area_id": cls.cases_area.id}
)
# Product to test special case with Purchase Uom:
cls.prod_uom_test = cls.product_obj.create(
{
"name": "Product Uom Test",
"type": "product",
"uom_id": cls.env.ref("uom.product_uom_unit").id,
"uom_po_id": cls.env.ref("uom.product_uom_dozen").id,
"list_price": 150.0,
"produce_delay": 5.0,
"route_ids": [(6, 0, [route_buy])],
"seller_ids": [(0, 0, {"partner_id": vendor1.id, "price": 20.0})],
}
)
cls.product_mrp_area_obj.create(
{"product_id": cls.prod_uom_test.id, "mrp_area_id": cls.mrp_area.id}
)
# Product to test lots
cls.product_lots = cls.product_obj.create(
{
"name": "Product Tracked by Lots",
"type": "product",
"tracking": "lot",
"uom_id": cls.env.ref("uom.product_uom_unit").id,
"list_price": 100.0,
"produce_delay": 5.0,
"route_ids": [(6, 0, [route_buy])],
"seller_ids": [(0, 0, {"partner_id": vendor1.id, "price": 25.0})],
}
)
cls.product_mrp_area_obj.create(
{"product_id": cls.product_lots.id, "mrp_area_id": cls.mrp_area.id}
)
cls.lot_1 = cls.lot_obj.create(
{
"product_id": cls.product_lots.id,
"name": "Lot 1",
"company_id": cls.company.id,
}
)
cls.lot_2 = cls.lot_obj.create(
{
"product_id": cls.product_lots.id,
"name": "Lot 2",
"company_id": cls.company.id,
}
)
cls.quant_obj.sudo().create(
{
"product_id": cls.product_lots.id,
"lot_id": cls.lot_1.id,
"quantity": 100.0,
"location_id": cls.stock_location.id,
}
)
cls.quant_obj.sudo().create(
{
"product_id": cls.product_lots.id,
"lot_id": cls.lot_2.id,
"quantity": 110.0,
"location_id": cls.stock_location.id,
}
)
# Product MRP Parameter to test supply method computation
cls.env.ref("stock.route_warehouse0_mto").active = True
cls.env["stock.rule"].create(
{
"name": "WH2: Main Area → Secondary Area (MTO)",
"action": "pull",
"picking_type_id": cls.env.ref("stock.picking_type_in").id,
"location_src_id": cls.env.ref("stock.stock_location_stock").id,
"location_dest_id": cls.sec_loc.id,
"route_id": cls.env.ref("stock.route_warehouse0_mto").id,
"procure_method": "mts_else_mto",
}
)
cls.product_mrp_area_obj.create(
{"product_id": cls.fp_4.id, "mrp_area_id": cls.secondary_area.id}
)
# 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
)
dt_bit_later = dt_base + timedelta(hours=1)
cls._create_picking_out(
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
)
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
)
# product_4b will use the template bom (sequence 5)
# (11, 22) = ("steel", "black")
# create variant bom for product_4c (sequence 1)
# (12, 21) = ("aluminum", "white")
cls.mrp_bom_obj.create(
{
"product_tmpl_id": cls.product_4c.product_tmpl_id.id,
"product_id": cls.product_4c.id,
"type": "normal",
"sequence": 1,
"bom_line_ids": [
(
0,
0,
{
"product_id": cls.av_12.id,
"product_qty": 1.0,
},
),
(
0,
0,
{
"product_id": cls.av_21.id,
"product_qty": 1.0,
},
),
],
}
)
# Create test picking for FP-1, FP-2, Desk(steel, black), Desk(aluminum, white)
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,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move fp-1",
"product_id": cls.fp_1.id,
"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": 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,
},
),
(
0,
0,
{
"name": "Test move fp-3",
"product_id": cls.fp_3.id,
"date": date_move,
"product_uom": cls.fp_3.uom_id.id,
"product_uom_qty": 5,
"location_id": cls.stock_location.id,
"location_dest_id": cls.customer_location.id,
},
),
(
0,
0,
{
"name": "Test move product-4b",
"product_id": cls.product_4b.id,
"date": date_move,
"product_uom": cls.product_4b.uom_id.id,
"product_uom_qty": 150,
"location_id": cls.stock_location.id,
"location_dest_id": cls.customer_location.id,
},
),
(
0,
0,
{
"name": "Test move product-4c",
"product_id": cls.product_4c.id,
"date": date_move,
"product_uom": cls.product_4c.uom_id.id,
"product_uom_qty": 56,
"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,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move prod_min",
"product_id": cls.prod_min.id,
"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": 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": 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,
},
)
],
}
)
# Create Test PO for special case Puchase uom:
# Remember that prod_uom_test had a UoM of units but it is purchased in dozens.
# For this reason buying 1 quantity of it, means to have 12 units in stock.
date_po = cls.calendar.plan_days(1 + 1, datetime.today().replace(hour=0)).date()
cls.po_uom = cls.po_obj.create(
{
"name": "Test PO-002",
"partner_id": cls.vendor.id,
"order_line": [
(
0,
0,
{
"name": "Product Uom Test line",
"product_id": cls.prod_uom_test.id,
"date_planned": date_po,
"product_qty": 1.0,
"product_uom": cls.prod_uom_test.uom_po_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._create_mo(cls.fp_2, bom_fp_2, date_mo, qty=12.0)
# Dates:
today = datetime.today().replace(hour=0)
cls.date_3 = cls.calendar.plan_days(3 + 1, today).date()
cls.date_5 = cls.calendar.plan_days(5 + 1, today).date()
cls.date_6 = cls.calendar.plan_days(6 + 1, today).date()
cls.date_7 = cls.calendar.plan_days(7 + 1, today).date()
cls.date_8 = cls.calendar.plan_days(8 + 1, today).date()
cls.date_9 = cls.calendar.plan_days(9 + 1, today).date()
cls.date_10 = cls.calendar.plan_days(10 + 1, today).date()
cls.date_20 = cls.calendar.plan_days(20 + 1, today).date()
cls.date_22 = cls.calendar.plan_days(22 + 1, today).date()
# Create movements in secondary area:
cls.create_demand_sec_loc(cls.date_8, 80.0)
cls.create_demand_sec_loc(cls.date_9, 50.0)
cls.create_demand_sec_loc(cls.date_10, 70.0)
cls.create_demand_sec_loc(cls.date_20, 46.0)
cls.create_demand_sec_loc(cls.date_22, 33.0)
# Create pickings:
cls._create_picking_out(cls.product_lots, 25, today)
cls.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
@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,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test move",
"product_id": cls.prod_test.id,
"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])],
}
)
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,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test Move",
"product_id": product.id,
"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
@classmethod
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,
"scheduled_date": date_move,
"move_ids": [
(
0,
0,
{
"name": "Test Move",
"product_id": product.id,
"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
@classmethod
def _create_mo(cls, product, bom, date, qty=10.0):
mo_form = Form(cls.mo_obj)
mo_form.product_id = product
mo_form.bom_id = bom
mo_form.product_qty = qty
mo_form.date_planned_start = date
mo = mo_form.save()
# Confirm the MO to generate stock moves:
mo.action_confirm()
return mo

View file

@ -0,0 +1,918 @@
# Copyright 2018-19 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from datetime import date, datetime, timedelta
from odoo import fields
from .common import TestMrpMultiLevelCommon
class TestMrpMultiLevel(TestMrpMultiLevelCommon):
def test_01_mrp_levels(self):
"""Tests computation of MRP levels."""
self.assertEqual(self.fp_1.llc, 0)
self.assertEqual(self.fp_2.llc, 0)
self.assertEqual(self.sf_1.llc, 1)
self.assertEqual(self.sf_2.llc, 1)
self.assertEqual(self.pp_1.llc, 2)
self.assertEqual(self.pp_2.llc, 2)
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")
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")
self.assertFalse(product_mrp_area.main_supplier_id)
self.assertFalse(product_mrp_area.main_supplierinfo_id)
# Archiving the product should archive parameters:
self.assertTrue(product_mrp_area.active)
self.sf_1.active = False
self.assertFalse(product_mrp_area.active)
def test_03_mrp_moves(self):
"""Tests for mrp moves generated."""
moves = self.mrp_move_obj.search([("product_id", "=", self.pp_1.id)])
self.assertEqual(len(moves), 3)
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:
# Demand coming from FP-1
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:
# Demand coming from FP-2 -> SF-1
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)]
)
self.assertEqual(len(planned_orders), 3)
for plan in planned_orders:
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")]
)
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)
def test_04_mrp_multi_level(self):
"""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)]
)
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),
]
)
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)
fp_2_line_2 = self.mrp_inventory_obj.search(
[
("product_mrp_area_id.product_id", "=", self.fp_2.id),
("date", "=", self.date_10),
]
)
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),
]
)
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),
]
)
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),
]
)
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),
]
)
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),
]
)
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),
]
)
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),
]
)
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),
]
)
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),
]
)
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),
]
)
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)
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"
)
self.assertEqual(len(invs), 2)
expected = [0.0, 0.0] # No grouping, lot size nor safety stock.
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)])
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)])
self.assertTrue(mos)
self.assertEqual(mos.product_qty, 100.0)
mo_date_start = fields.Date.to_date(mos.date_planned_start)
self.assertEqual(mo_date_start, self.date_5)
def test_07_adjust_qty_to_order(self):
"""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)]
)
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)]
)
self.assertEqual(mrp_inv_max.to_procure, 150)
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"))
# quantity multiple:
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),
]
)
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")
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"))
self.assertIn(abs(week_1_expected), quantities)
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.with_user(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
)
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)]
)
self.assertEqual(len(moves), 4)
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)
# Net needs = 18, available on-hand = 3 -> 15
self.assertEqual(mrp_invs[1].to_procure, 15)
def test_12_bom_line_attribute_value_skip(self):
"""Check for the correct demand on components of a product with
multiple variants"""
product_4b_demand = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.product_4b.id)]
)
self.assertTrue(product_4b_demand)
self.assertEqual(product_4b_demand.to_procure, 100)
product_4c_demand = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.product_4c.id)]
)
self.assertTrue(product_4c_demand)
self.assertEqual(product_4c_demand.to_procure, 1)
# Testing variant BoM
# Supply of one unit for AV-12 or AV-21
av_12_supply = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.av_12.id)]
)
self.assertEqual(av_12_supply.to_procure, 1.0)
av_21_supply = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.av_21.id)]
)
self.assertEqual(av_21_supply.to_procure, 1.0)
# Testing template BoM
# Supply of 150 units for AV-11 and AV-22
av_11_supply = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.av_11.id)]
)
self.assertEqual(av_11_supply.to_procure, 100.0)
av_22_supply = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.av_22.id)]
)
self.assertTrue(av_22_supply.to_procure, 100.0)
def test_13_timezone_handling(self):
self.calendar.tz = "Australia/Sydney" # Oct-Apr/Apr-Oct: UTC+11/UTC+10
date_move = datetime(2090, 4, 19, 20, 00) # Apr 20 6/7 am in Sidney
sidney_date = date(2090, 4, 20)
self._create_picking_in(
self.product_tz, 10.0, date_move, location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[
("mrp_area_id", "=", self.cases_area.id),
("product_id", "=", self.product_tz.id),
]
)
self.assertEqual(len(inventory), 1)
self.assertEqual(inventory.date, sidney_date)
def test_14_timezone_not_set(self):
self.wh.calendar_id = False
date_move = datetime(2090, 4, 19, 20, 00)
self._create_picking_in(
self.product_tz, 10.0, date_move, location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[
("mrp_area_id", "=", self.cases_area.id),
("product_id", "=", self.product_tz.id),
]
)
self.assertEqual(len(inventory), 1)
self.assertEqual(inventory.date, date_move.date())
def test_15_units_case(self):
"""When a product has a different purchase unit of measure than
the general unit of measure and the supply is coming from an RFQ"""
prod_uom_test_inventory_lines = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.prod_uom_test.id)]
)
self.assertEqual(len(prod_uom_test_inventory_lines), 1)
self.assertEqual(prod_uom_test_inventory_lines.supply_qty, 12.0)
# Supply qty has to be 12 has a dozen of units are in a RFQ.
def test_16_phantom_comp_planning(self):
"""
Phantom components will not appear in MRP Inventory or Planned Orders.
MRP Parameter will have 'phantom' supply method.
"""
# SF-3
sf_3_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.sf_3.id)]
)
self.assertEqual(len(sf_3_line_1), 0)
sf_3_planned_order_1 = self.planned_order_obj.search(
[("product_mrp_area_id.product_id", "=", self.sf_3.id)]
)
self.assertEqual(sf_3_planned_order_1.mrp_action, "phantom")
self.assertEqual(sf_3_planned_order_1.mrp_qty, 10.0)
# PP-3
pp_3_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_3.id)]
)
self.assertEqual(len(pp_3_line_1), 1)
self.assertEqual(pp_3_line_1.demand_qty, 20.0)
pp_3_planned_orders = self.planned_order_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_3.id)]
)
self.assertEqual(len(pp_3_planned_orders), 2)
# PP-4
pp_4_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_4.id)]
)
self.assertEqual(len(pp_4_line_1), 1)
self.assertEqual(pp_4_line_1.demand_qty, 30.0)
pp_4_planned_orders = self.planned_order_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_4.id)]
)
self.assertEqual(len(pp_4_planned_orders), 1)
def test_17_supply_method(self):
"""Test supply method computation."""
self.fp_4.route_ids = [(5, 0, 0)]
product_mrp_area = self.product_mrp_area_obj.search(
[("product_id", "=", self.fp_4.id)]
)
self.assertEqual(product_mrp_area.supply_method, "none")
self.fp_4.route_ids = [(4, self.env.ref("stock.route_warehouse0_mto").id)]
product_mrp_area._compute_supply_method()
self.assertEqual(product_mrp_area.supply_method, "pull")
self.fp_4.route_ids = [(4, self.env.ref("mrp.route_warehouse0_manufacture").id)]
product_mrp_area._compute_supply_method()
self.assertEqual(product_mrp_area.supply_method, "manufacture")
self.fp_4.route_ids = [
(4, self.env.ref("purchase_stock.route_warehouse0_buy").id)
]
product_mrp_area._compute_supply_method()
self.assertEqual(product_mrp_area.supply_method, "buy")
kit_bom = self.mrp_bom_obj.create(
{
"product_tmpl_id": self.fp_4.product_tmpl_id.id,
"product_id": self.fp_4.id,
"type": "phantom",
}
)
product_mrp_area._compute_supply_method()
self.assertEqual(product_mrp_area.supply_method, "phantom")
self.assertEqual(product_mrp_area.supply_bom_id, kit_bom)
def test_18_priorize_safety_stock(self):
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_applicable": True, # needed?
}
)
self._create_picking_out(
product, 6.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_in(
product, 10.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=14), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[
("mrp_area_id", "=", self.cases_area.id),
("product_id", "=", product.id),
]
)
expected = [
{
"date": now.date(),
"demand_qty": 0.0,
"final_on_hand_qty": 5.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 10.0,
},
{
"date": now.date() + timedelta(days=3),
"demand_qty": 6.0,
"final_on_hand_qty": -1.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 6.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 0.0,
"final_on_hand_qty": 9.0,
"initial_on_hand_qty": -1.0,
"running_availability": 25.0,
"supply_qty": 10.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=14),
"demand_qty": 12.0,
"final_on_hand_qty": -3.0,
"initial_on_hand_qty": 9.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 2.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_19_on_hand_with_lots(self):
"""Check that on-hand is correctly computed when tracking by lots."""
lots_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.product_lots.id)]
)
self.assertEqual(len(lots_line_1), 1)
self.assertEqual(lots_line_1.initial_on_hand_qty, 210)
self.assertEqual(lots_line_1.final_on_hand_qty, 185)
def test_20_prioritize_safety_stock_grouped_1(self):
"""Test grouped demand MRP but with a short nbr days.
Safety stock should be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 2,
}
)
self._create_picking_out(
product, 6.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_in(
product, 10.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=14), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[
("mrp_area_id", "=", self.cases_area.id),
("product_id", "=", product.id),
]
)
expected = [
{
"date": now.date(),
"demand_qty": 0.0,
"final_on_hand_qty": 5.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 10.0,
},
{
"date": now.date() + timedelta(days=3),
"demand_qty": 6.0,
"final_on_hand_qty": -1.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 6.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 0.0,
"final_on_hand_qty": 9.0,
"initial_on_hand_qty": -1.0,
"running_availability": 25.0,
"supply_qty": 10.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=14),
"demand_qty": 12.0,
"final_on_hand_qty": -3.0,
"initial_on_hand_qty": 9.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 2.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_21_prioritize_safety_stock_grouped_2(self):
"""Test grouped demand MRP but with a longer nbr days.
Safety stock should be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 7,
}
)
self._create_picking_out(
product, 6.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_in(
product, 10.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=12), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[
("mrp_area_id", "=", self.cases_area.id),
("product_id", "=", product.id),
]
)
expected = [
{
"date": now.date(),
"demand_qty": 0.0,
"final_on_hand_qty": 5.0,
"initial_on_hand_qty": 5.0,
"running_availability": 21.0,
"supply_qty": 0.0,
"to_procure": 16.0,
},
{
"date": now.date() + timedelta(days=3),
"demand_qty": 6.0,
"final_on_hand_qty": -1.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 0.0,
"final_on_hand_qty": 9.0,
"initial_on_hand_qty": -1.0,
"running_availability": 27.0,
"supply_qty": 10.0,
"to_procure": 2.0,
},
{
"date": now.date() + timedelta(days=12),
"demand_qty": 12.0,
"final_on_hand_qty": -3.0,
"initial_on_hand_qty": 9.0,
"running_availability": 15.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_22_prioritize_safety_stock_grouped_3(self):
"""Test grouped demand MRP but with an existing incoming supply
Safety stock should NOT be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 7,
}
)
self._create_picking_in(
product, 30.0, now + timedelta(days=3), location=self.cases_loc
)
self._create_picking_out(
product, 6.0, now + timedelta(days=7), location=self.cases_loc
)
self._create_picking_out(
product, 12.0, now + timedelta(days=12), location=self.cases_loc
)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[
("mrp_area_id", "=", self.cases_area.id),
("product_id", "=", product.id),
]
)
expected = [
{
"date": now.date() + timedelta(days=3),
"demand_qty": 0.0,
"initial_on_hand_qty": 5.0,
"final_on_hand_qty": 35.0,
"running_availability": 35.0,
"supply_qty": 30.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=7),
"demand_qty": 6.0,
"initial_on_hand_qty": 35.0,
"final_on_hand_qty": 29.0,
"running_availability": 29.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
{
"date": now.date() + timedelta(days=12),
"demand_qty": 12.0,
"initial_on_hand_qty": 29.0,
"final_on_hand_qty": 17.0,
"running_availability": 17.0,
"supply_qty": 0.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_23_prioritize_safety_stock_with_mrp_moves_today(self):
"""Test MRP but with moves today. Safety stock should not be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
}
)
self._create_picking_out(product, 10.0, now, location=self.cases_loc)
self._create_picking_in(product, 20.0, now, location=self.cases_loc)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date(),
"demand_qty": 10.0,
"final_on_hand_qty": 15.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 20.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_24_prioritize_safety_stock_with_mrp_moves_today_grouped(self):
"""Test grouped demand MRP but with moves today. Safety stock should not be ordered."""
now = datetime.now()
product = self.prod_test # has Buy route
product.seller_ids[0].delay = 2 # set a purchase lead time
self.quant_obj._update_available_quantity(product, self.cases_loc, 5)
self.product_mrp_area_obj.create(
{
"product_id": product.id,
"mrp_area_id": self.cases_area.id,
"mrp_minimum_stock": 15,
"mrp_nbr_days": 2,
}
)
self._create_picking_out(product, 10.0, now, location=self.cases_loc)
self._create_picking_in(product, 20.0, now, location=self.cases_loc)
self.mrp_multi_level_wiz.create(
{"mrp_area_ids": [(6, 0, self.cases_area.ids)]}
).run_mrp_multi_level()
inventory = self.mrp_inventory_obj.search(
[("mrp_area_id", "=", self.cases_area.id), ("product_id", "=", product.id)]
)
expected = [
{
"date": now.date(),
"demand_qty": 10.0,
"final_on_hand_qty": 15.0,
"initial_on_hand_qty": 5.0,
"running_availability": 15.0,
"supply_qty": 20.0,
"to_procure": 0.0,
},
]
self.assertEqual(len(expected), len(inventory))
for test_vals, inv in zip(expected, inventory):
for key in test_vals:
self.assertEqual(
test_vals[key],
inv[key],
f"unexpected value for {key}: {inv[key]} "
f"(expected {test_vals[key]} on {inv.date})",
)
def test_25_phantom_comp_on_hand(self):
"""
A phantom product with positive qty_available (which is computed from the
availability of its components) should not satisfy demand, because this leads
to double counting qty_available of its component products.
"""
quant = self.quant_obj.sudo().create(
{
"product_id": self.pp_3.id,
"inventory_quantity": 10.0,
"location_id": self.stock_location.id,
}
)
quant.action_apply_inventory()
quant = self.quant_obj.sudo().create(
{
"product_id": self.pp_4.id,
"inventory_quantity": 30.0,
"location_id": self.stock_location.id,
}
)
quant.action_apply_inventory()
self.assertEqual(self.sf_3.qty_available, 10.0)
self.mrp_multi_level_wiz.create({}).run_mrp_multi_level()
# PP-3
pp_3_line_1 = self.mrp_inventory_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_3.id)]
)
self.assertEqual(len(pp_3_line_1), 1)
self.assertEqual(pp_3_line_1.demand_qty, 20.0)
self.assertEqual(pp_3_line_1.to_procure, 10.0)
pp_3_planned_orders = self.planned_order_obj.search(
[("product_mrp_area_id.product_id", "=", self.pp_3.id)]
)
self.assertEqual(len(pp_3_planned_orders), 1)
self.assertEqual(pp_3_planned_orders.mrp_qty, 10)
sf3_planned_orders = self.env["mrp.planned.order"].search(
[("product_id", "=", self.sf_3.id)]
)
self.assertEqual(len(sf3_planned_orders), 1)
# Trying to procure a kit planned order will have no effect.
procure_wizard = (
self.env["mrp.inventory.procure"]
.with_context(
active_model="mrp.planned.order", active_ids=sf3_planned_orders.ids
)
.create({})
)
self.assertEqual(len(procure_wizard.item_ids), 0)