Odoo18-Base/enterprise-17.0/mrp_mps/tests/test_mrp_mps.py
2025-01-06 10:57:38 +07:00

729 lines
32 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import date, datetime, timedelta
from odoo.tests import common, Form
from odoo import Command
from odoo.tools.date_utils import start_of
class TestMpsMps(common.TransactionCase):
@classmethod
def setUpClass(cls):
""" Define a multi level BoM and generate a production schedule with
default value for each of the products.
BoM 1:
Table
|
------------------------------------
1 Drawer 2 Table Legs
| |
---------------- -------------------
4 Screw 2 Table Legs 4 Screw 4 Bolt
|
-------------------
4 Screw 4 Bolt
BoM 2 and 3:
Wardrobe Chair
| |
3 Drawer 4 Table Legs
"""
super().setUpClass()
cls.table = cls.env['product.product'].create({
'name': 'Table',
'type': 'product',
})
cls.drawer = cls.env['product.product'].create({
'name': 'Drawer',
'type': 'product',
})
cls.table_leg = cls.env['product.product'].create({
'name': 'Table Leg',
'type': 'product',
})
cls.screw = cls.env['product.product'].create({
'name': 'Screw',
'type': 'product',
})
cls.bolt = cls.env['product.product'].create({
'name': 'Bolt',
'type': 'product',
})
bom_form_table = Form(cls.env['mrp.bom'])
bom_form_table.product_tmpl_id = cls.table.product_tmpl_id
bom_form_table.product_qty = 1
cls.bom_table = bom_form_table.save()
with Form(cls.bom_table) as bom:
with bom.bom_line_ids.new() as line:
line.product_id = cls.drawer
line.product_qty = 1
with bom.bom_line_ids.new() as line:
line.product_id = cls.table_leg
line.product_qty = 2
bom_form_drawer = Form(cls.env['mrp.bom'])
bom_form_drawer.product_tmpl_id = cls.drawer.product_tmpl_id
bom_form_drawer.product_qty = 1
cls.bom_drawer = bom_form_drawer.save()
with Form(cls.bom_drawer) as bom:
with bom.bom_line_ids.new() as line:
line.product_id = cls.table_leg
line.product_qty = 2
with bom.bom_line_ids.new() as line:
line.product_id = cls.screw
line.product_qty = 4
bom_form_table_leg = Form(cls.env['mrp.bom'])
bom_form_table_leg.product_tmpl_id = cls.table_leg.product_tmpl_id
bom_form_table_leg.product_qty = 1
cls.bom_table_leg = bom_form_table_leg.save()
with Form(cls.bom_table_leg) as bom:
with bom.bom_line_ids.new() as line:
line.product_id = cls.bolt
line.product_qty = 4
with bom.bom_line_ids.new() as line:
line.product_id = cls.screw
line.product_qty = 4
cls.wardrobe = cls.env['product.product'].create({
'name': 'Wardrobe',
'type': 'product',
})
bom_form_wardrobe = Form(cls.env['mrp.bom'])
bom_form_wardrobe.product_tmpl_id = cls.wardrobe.product_tmpl_id
bom_form_wardrobe.product_qty = 1
cls.bom_wardrobe = bom_form_wardrobe.save()
with Form(cls.bom_wardrobe) as bom:
with bom.bom_line_ids.new() as line:
line.product_id = cls.drawer
# because pim-odoo said '3 drawers because 4 is too much'
line.product_qty = 3
cls.chair = cls.env['product.product'].create({
'name': 'Chair',
'type': 'product',
})
bom_form_chair = Form(cls.env['mrp.bom'])
bom_form_chair.product_tmpl_id = cls.chair.product_tmpl_id
bom_form_chair.product_qty = 1
cls.bom_chair = bom_form_chair.save()
with Form(cls.bom_chair) as bom:
with bom.bom_line_ids.new() as line:
line.product_id = cls.table_leg
line.product_qty = 4
cls.warehouse = cls.env['stock.warehouse'].search([], limit=1)
cls.mps_table = cls.env['mrp.production.schedule'].create({
'product_id': cls.table.id,
'warehouse_id': cls.warehouse.id,
})
cls.mps_wardrobe = cls.env['mrp.production.schedule'].create({
'product_id': cls.wardrobe.id,
'warehouse_id': cls.warehouse.id,
})
cls.mps_chair = cls.env['mrp.production.schedule'].create({
'product_id': cls.chair.id,
'warehouse_id': cls.warehouse.id,
})
cls.mps_drawer = cls.env['mrp.production.schedule'].create({
'product_id': cls.drawer.id,
'warehouse_id': cls.warehouse.id,
})
cls.mps_table_leg = cls.env['mrp.production.schedule'].create({
'product_id': cls.table_leg.id,
'warehouse_id': cls.warehouse.id,
})
cls.mps_screw = cls.env['mrp.production.schedule'].create({
'product_id': cls.screw.id,
'warehouse_id': cls.warehouse.id,
})
cls.mps = cls.mps_table | cls.mps_wardrobe | cls.mps_chair |\
cls.mps_drawer | cls.mps_table_leg | cls.mps_screw
def test_basic_state(self):
""" Testing master product scheduling default values for client
action rendering.
"""
mps_state = self.mps.get_mps_view_state()
self.assertTrue(len(mps_state['manufacturing_period']), 12)
# Remove demo data
production_schedule_ids = [s for s in mps_state['production_schedule_ids'] if s['id'] in self.mps.ids]
# Check that 6 states are returned (one by production schedule)
self.assertEqual(len(production_schedule_ids), 6)
self.assertEqual(mps_state['company_id'], self.env.user.company_id.id)
company_groups = mps_state['groups'][0]
self.assertTrue(company_groups['mrp_mps_show_starting_inventory'])
self.assertTrue(company_groups['mrp_mps_show_demand_forecast'])
self.assertTrue(company_groups['mrp_mps_show_indirect_demand'])
self.assertTrue(company_groups['mrp_mps_show_to_replenish'])
self.assertTrue(company_groups['mrp_mps_show_safety_stock'])
self.assertFalse(company_groups['mrp_mps_show_actual_demand'])
self.assertFalse(company_groups['mrp_mps_show_actual_replenishment'])
self.assertFalse(company_groups['mrp_mps_show_available_to_promise'])
# Check that quantity on forecast are empty
self.assertTrue(all([not forecast['starting_inventory_qty'] for forecast in production_schedule_ids[0]['forecast_ids']]))
self.assertTrue(all([not forecast['forecast_qty'] for forecast in production_schedule_ids[0]['forecast_ids']]))
self.assertTrue(all([not forecast['replenish_qty'] for forecast in production_schedule_ids[0]['forecast_ids']]))
self.assertTrue(all([not forecast['safety_stock_qty'] for forecast in production_schedule_ids[0]['forecast_ids']]))
self.assertTrue(all([not forecast['indirect_demand_qty'] for forecast in production_schedule_ids[0]['forecast_ids']]))
# Check that there is 12 periods for each forecast
self.assertTrue(all([len(production_schedule_id['forecast_ids']) == 12 for production_schedule_id in production_schedule_ids]))
def test_forecast_1(self):
""" Testing master product scheduling """
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_screw.id,
'date': date.today(),
'forecast_qty': 100
})
screw_mps_state = self.mps_screw.get_production_schedule_view_state()[0]
forecast_at_first_period = screw_mps_state['forecast_ids'][0]
self.assertEqual(forecast_at_first_period['forecast_qty'], 100)
self.assertEqual(forecast_at_first_period['replenish_qty'], 100)
self.assertEqual(forecast_at_first_period['safety_stock_qty'], 0)
self.env['stock.quant']._update_available_quantity(self.mps_screw.product_id, self.warehouse.lot_stock_id, 50)
# Invalidate qty_available on product.product
self.env.invalidate_all()
screw_mps_state = self.mps_screw.get_production_schedule_view_state()[0]
forecast_at_first_period = screw_mps_state['forecast_ids'][0]
self.assertEqual(forecast_at_first_period['forecast_qty'], 100)
self.assertEqual(forecast_at_first_period['replenish_qty'], 50)
self.assertEqual(forecast_at_first_period['safety_stock_qty'], 0)
self.mps_screw.max_to_replenish_qty = 20
screw_mps_state = self.mps_screw.get_production_schedule_view_state()[0]
forecast_at_first_period = screw_mps_state['forecast_ids'][0]
self.assertEqual(forecast_at_first_period['forecast_qty'], 100)
self.assertEqual(forecast_at_first_period['replenish_qty'], 20)
self.assertEqual(forecast_at_first_period['safety_stock_qty'], -30)
forecast_at_second_period = screw_mps_state['forecast_ids'][1]
self.assertEqual(forecast_at_second_period['forecast_qty'], 0)
self.assertEqual(forecast_at_second_period['replenish_qty'], 20)
self.assertEqual(forecast_at_second_period['safety_stock_qty'], -10)
forecast_at_third_period = screw_mps_state['forecast_ids'][2]
self.assertEqual(forecast_at_third_period['forecast_qty'], 0)
self.assertEqual(forecast_at_third_period['replenish_qty'], 10)
self.assertEqual(forecast_at_third_period['safety_stock_qty'], 0)
def test_replenish(self):
""" Test to run procurement for forecasts. Check that replenish for
different periods will not merger purchase order line and create
multiple docurements. Also modify the existing quantity replenished on
a forecast and run the replenishment again, ensure the purchase order
line is updated.
"""
mps_dates = self.env.company._get_date_range()
forecast_screw = self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_screw.id,
'date': mps_dates[0][0],
'forecast_qty': 100
})
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_screw.id,
'date': mps_dates[1][0],
'forecast_qty': 100
})
partner = self.env['res.partner'].create({
'name': 'Jhon'
})
seller = self.env['product.supplierinfo'].create({
'partner_id': partner.id,
'price': 12.0,
'delay': 0
})
self.screw.seller_ids = [(6, 0, [seller.id])]
self.mps_screw.action_replenish()
purchase_order_line = self.env['purchase.order.line'].search([('product_id', '=', self.screw.id)])
self.assertTrue(purchase_order_line)
# It should not create a procurement since it exists already one for the
# current period and the sum of lead time should be 0.
self.mps_screw.action_replenish(based_on_lead_time=True)
purchase_order_line = self.env['purchase.order.line'].search([('product_id', '=', self.screw.id)])
self.assertEqual(len(purchase_order_line), 1)
self.mps_screw.action_replenish()
purchase_order_lines = self.env['purchase.order.line'].search([('product_id', '=', self.screw.id)])
self.assertEqual(len(purchase_order_lines), 2)
self.assertEqual(len(purchase_order_lines.mapped('order_id')), 2)
# This replenish should be withtout effect since everything is already
# plannified.
self.mps_screw.action_replenish()
purchase_order_lines = self.env['purchase.order.line'].search([('product_id', '=', self.screw.id)])
self.assertEqual(len(purchase_order_lines), 2)
# Replenish an existing forecast with a procurement in progress
forecast_screw.forecast_qty = 150
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
self.assertEqual(screw_forecast_1['state'], 'to_relaunch')
self.assertTrue(screw_forecast_1['to_replenish'])
self.assertTrue(screw_forecast_1['forced_replenish'])
self.mps_screw.action_replenish(based_on_lead_time=True)
purchase_order_lines = self.env['purchase.order.line'].search([('product_id', '=', self.screw.id)])
self.assertEqual(len(purchase_order_lines), 2)
purchase_order_line = self.env['purchase.order.line'].search([('product_id', '=', self.screw.id)], order='date_planned', limit=1)
self.assertEqual(purchase_order_line.product_qty, 150)
forecast_screw.forecast_qty = 50
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
self.assertEqual(screw_forecast_1['state'], 'to_correct')
self.assertFalse(screw_forecast_1['to_replenish'])
self.assertFalse(screw_forecast_1['forced_replenish'])
def test_lead_times(self):
""" Manufacture, supplier and rules uses delay. The forecasts to
replenish are impacted by those delay. Ensure that the MPS state and
the period to replenish are correct.
"""
self.env.company.manufacturing_period = 'week'
partner = self.env['res.partner'].create({
'name': 'Jhon'
})
seller = self.env['product.supplierinfo'].create({
'partner_id': partner.id,
'price': 12.0,
'delay': 7,
})
self.screw.seller_ids = [(6, 0, [seller.id])]
mps_dates = self.env.company._get_date_range()
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_screw.id,
'date': mps_dates[0][0],
'forecast_qty': 100
})
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
screw_forecast_2 = mps_screw['forecast_ids'][1]
screw_forecast_3 = mps_screw['forecast_ids'][2]
# Check screw forecasts state
self.assertEqual(screw_forecast_1['state'], 'to_launch')
# launched because it's in lead time frame but it do not require a
# replenishment. The state launched is used in order to render the cell
# with a grey background.
self.assertEqual(screw_forecast_2['state'], 'launched')
self.assertEqual(screw_forecast_3['state'], 'to_launch')
self.assertTrue(screw_forecast_1['to_replenish'])
self.assertFalse(screw_forecast_2['to_replenish'])
self.assertFalse(screw_forecast_3['to_replenish'])
self.assertTrue(screw_forecast_1['forced_replenish'])
self.assertFalse(screw_forecast_2['forced_replenish'])
self.assertFalse(screw_forecast_3['forced_replenish'])
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_screw.id,
'date': mps_dates[1][0],
'forecast_qty': 100
})
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
screw_forecast_2 = mps_screw['forecast_ids'][1]
screw_forecast_3 = mps_screw['forecast_ids'][2]
self.assertEqual(screw_forecast_1['state'], 'to_launch')
self.assertEqual(screw_forecast_2['state'], 'to_launch')
self.assertEqual(screw_forecast_3['state'], 'to_launch')
self.assertTrue(screw_forecast_1['to_replenish'])
self.assertTrue(screw_forecast_2['to_replenish'])
self.assertFalse(screw_forecast_3['to_replenish'])
self.assertTrue(screw_forecast_1['forced_replenish'])
self.assertFalse(screw_forecast_2['forced_replenish'])
self.assertFalse(screw_forecast_3['forced_replenish'])
seller.delay = 14
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
screw_forecast_2 = mps_screw['forecast_ids'][1]
screw_forecast_3 = mps_screw['forecast_ids'][2]
self.assertEqual(screw_forecast_1['state'], 'to_launch')
self.assertEqual(screw_forecast_2['state'], 'to_launch')
self.assertEqual(screw_forecast_3['state'], 'launched')
self.assertTrue(screw_forecast_1['to_replenish'])
self.assertTrue(screw_forecast_2['to_replenish'])
self.assertFalse(screw_forecast_3['to_replenish'])
self.assertTrue(screw_forecast_1['forced_replenish'])
self.assertFalse(screw_forecast_2['forced_replenish'])
self.assertFalse(screw_forecast_3['forced_replenish'])
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_screw.id,
'date': mps_dates[2][0],
'forecast_qty': 100
})
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
screw_forecast_2 = mps_screw['forecast_ids'][1]
screw_forecast_3 = mps_screw['forecast_ids'][2]
self.assertEqual(screw_forecast_1['state'], 'to_launch')
self.assertEqual(screw_forecast_2['state'], 'to_launch')
self.assertEqual(screw_forecast_3['state'], 'to_launch')
self.assertTrue(screw_forecast_1['to_replenish'])
self.assertTrue(screw_forecast_2['to_replenish'])
self.assertTrue(screw_forecast_3['to_replenish'])
self.assertTrue(screw_forecast_1['forced_replenish'])
self.assertFalse(screw_forecast_2['forced_replenish'])
self.assertFalse(screw_forecast_3['forced_replenish'])
self.mps_screw.action_replenish(based_on_lead_time=True)
purchase_order_line = self.env['purchase.order.line'].search([('product_id', '=', self.screw.id)])
self.assertEqual(len(purchase_order_line.mapped('order_id')), 3)
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
screw_forecast_2 = mps_screw['forecast_ids'][1]
screw_forecast_3 = mps_screw['forecast_ids'][2]
self.assertEqual(screw_forecast_1['state'], 'launched')
self.assertEqual(screw_forecast_2['state'], 'launched')
self.assertEqual(screw_forecast_3['state'], 'launched')
self.assertFalse(screw_forecast_1['to_replenish'])
self.assertFalse(screw_forecast_2['to_replenish'])
self.assertFalse(screw_forecast_3['to_replenish'])
self.assertFalse(screw_forecast_1['forced_replenish'])
self.assertFalse(screw_forecast_2['forced_replenish'])
self.assertFalse(screw_forecast_3['forced_replenish'])
def test_indirect_demand(self):
""" On a multiple BoM relation, ensure that the replenish quantity on
a production schedule impact the indirect demand on other production
that have a component as product.
"""
mps_dates = self.env.company._get_date_range()
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_table.id,
'date': mps_dates[0][0],
'forecast_qty': 2
})
# 2 drawer from table
mps_drawer = self.mps_drawer.get_production_schedule_view_state()[0]
drawer_forecast_1 = mps_drawer['forecast_ids'][0]
self.assertEqual(drawer_forecast_1['indirect_demand_qty'], 2)
# Screw for 2 tables:
# 2 * 2 legs * 4 screw = 16
# 1 drawer = 4 + 2 * legs * 4 = 12
# 16 + 2 drawers = 16 + 24 = 40
mps_screw = self.mps_screw.get_production_schedule_view_state()[0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
self.assertEqual(screw_forecast_1['indirect_demand_qty'], 40)
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_wardrobe.id,
'date': mps_dates[0][0],
'forecast_qty': 3
})
# 2 drawer from table + 9 from wardrobe (3 x 3)
mps_drawer, mps_screw = (self.mps_drawer | self.mps_screw).get_production_schedule_view_state()
drawer_forecast_1 = mps_drawer['forecast_ids'][0]
self.assertEqual(drawer_forecast_1['indirect_demand_qty'], 11)
# Screw for 2 tables + 3 wardrobe:
# 11 drawer = 11 * 12 = 132
# + 2 * 2 legs * 4 = 16
screw_forecast_1 = mps_screw['forecast_ids'][0]
self.assertEqual(screw_forecast_1['indirect_demand_qty'], 148)
# Ensure that a forecast on another period will not impact the forecast
# for current period.
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_table.id,
'date': mps_dates[1][0],
'forecast_qty': 2
})
mps_drawer, mps_screw = (self.mps_drawer | self.mps_screw).get_production_schedule_view_state()
drawer_forecast_1 = mps_drawer['forecast_ids'][0]
screw_forecast_1 = mps_screw['forecast_ids'][0]
self.assertEqual(drawer_forecast_1['indirect_demand_qty'], 11)
self.assertEqual(screw_forecast_1['indirect_demand_qty'], 148)
def test_indirect_demand_kit(self):
""" On changing demand of a product whose BOM contains kit with a
component, ensure that the replenish quantity on a production schedule
impacts the indirect demand of kit's component.
"""
cabinet = self.env['product.product'].create({
'name': 'Cabinet',
'type': 'product',
})
wood_kit = self.env['product.product'].create({
'name': 'Wood Kit',
'type': 'product',
})
wood = self.env['product.product'].create({
'name': 'Wood',
'type': 'product',
})
self.env['mrp.bom'].create({
'product_tmpl_id': cabinet.product_tmpl_id.id,
'product_qty': 1,
'bom_line_ids': [
Command.create({'product_id': wood_kit.id, 'product_qty': 1}),
],
})
self.env['mrp.bom'].create({
'product_tmpl_id': wood_kit.product_tmpl_id.id,
'product_qty': 1,
'bom_line_ids': [
Command.create({'product_id': wood.id, 'product_qty': 2}),
],
})
mps_cabinet = self.env['mrp.production.schedule'].create({
'product_id': cabinet.id,
'warehouse_id': self.warehouse.id,
})
mps_wood = self.env['mrp.production.schedule'].create({
'product_id': wood.id,
'warehouse_id': self.warehouse.id,
})
self.mps |= mps_cabinet | mps_wood
mps_dates = self.env.company._get_date_range()
self.env['mrp.product.forecast'].create({
'production_schedule_id': mps_cabinet.id,
'date': mps_dates[0][0],
'forecast_qty': 2
})
# 4 wood from cabinet
mps_wood = mps_wood.get_production_schedule_view_state()[0]
wood_forecast_1 = mps_wood['forecast_ids'][0]
self.assertEqual(wood_forecast_1['indirect_demand_qty'], 4)
def test_impacted_schedule(self):
impacted_schedules = self.mps_screw.get_impacted_schedule()
self.assertEqual(sorted(impacted_schedules), sorted((self.mps - self.mps_screw).ids))
impacted_schedules = self.mps_drawer.get_impacted_schedule()
self.assertEqual(sorted(impacted_schedules), sorted((self.mps_table |
self.mps_wardrobe | self.mps_table_leg | self.mps_screw).ids))
def test_3_steps(self):
self.warehouse.manufacture_steps = 'pbm_sam'
self.table_leg.write({
'route_ids': [(6, 0, [self.ref('mrp.route_warehouse0_manufacture')])]
})
self.env['mrp.product.forecast'].create({
'production_schedule_id': self.mps_table_leg.id,
'date': date.today(),
'forecast_qty': 25
})
self.mps_table_leg.action_replenish()
mps_table_leg = self.mps_table_leg.get_production_schedule_view_state()[0]
self.assertEqual(mps_table_leg['forecast_ids'][0]['forecast_qty'], 25.0, "Wrong resulting value of to_supply")
self.assertEqual(mps_table_leg['forecast_ids'][0]['incoming_qty'], 25.0, "Wrong resulting value of incoming quantity")
def test_interwh_delay(self):
"""
Suppose an interwarehouse configuration. The user adds some delays on
each rule of the interwh route. Then, the user defines a replenishment
qty on the MPS view and calls the replenishment action. This test
ensures that the MPS view includes the delays for the incoming quantity
"""
main_warehouse = self.warehouse
second_warehouse = self.env['stock.warehouse'].create({
'name': 'Second Warehouse',
'code': 'WH02',
})
main_warehouse.write({
'resupply_wh_ids': [(6, 0, second_warehouse.ids)]
})
interwh_route = self.env['stock.route'].search([('supplied_wh_id', '=', main_warehouse.id), ('supplier_wh_id', '=', second_warehouse.id)])
interwh_route.rule_ids.delay = 1
product = self.env['product.product'].create({
'name': 'SuperProduct',
'type': 'product',
'route_ids': [(6, 0, interwh_route.ids)],
})
mps = self.env['mrp.production.schedule'].create({
'product_id': product.id,
'warehouse_id': main_warehouse.id,
})
interval_index = 3
mps.set_replenish_qty(interval_index, 1)
mps.action_replenish()
state = mps.get_production_schedule_view_state()[0]
for index, forecast in enumerate(state['forecast_ids']):
self.assertEqual(forecast['incoming_qty'], 1 if index == interval_index else 0, 'Incoming qty is incorrect for index %s' % index)
def test_outgoing_sm_and_lead_time_out_of_date_range(self):
"""
Set a lead time on delivery rule. Then generate an outgoing SM based on
that rule with:
- its date in dates range of MPS
- its date + rule's lead time outside the dates range of MPS
As a result, for the product mps, each outgoing quantity should be zero
"""
self.env.company.manufacturing_period = 'day'
self.env.company.manufacturing_period_to_display = 10
customer_location = self.env.ref('stock.stock_location_customers')
stock_location = self.warehouse.lot_stock_id
delivery_rule = self.env['stock.rule'].search([
('warehouse_id', '=', self.warehouse.id),
('location_src_id', '=', stock_location.id),
('location_dest_id', '=', customer_location.id),
('action', '=', 'pull')
], limit=1)
delivery_rule.delay = 15
product = self.env['product.product'].create({'name': 'SuperProduct', 'type': 'product'})
procurement = self.env["procurement.group"].Procurement(
product, 1, product.uom_id,
customer_location,
product.name,
"/",
self.env.company,
{
"warehouse_id": self.warehouse,
"date_planned": date.today() + timedelta(days=16),
}
)
self.env["procurement.group"].run([procurement])
tomorrow = start_of(datetime.now() + timedelta(days=1), 'day')
move = self.env['stock.move'].search([('product_id', '=', product.id)], limit=1)
self.assertEqual(move.date, tomorrow)
mps = self.env['mrp.production.schedule'].create({
'product_id': product.id,
'warehouse_id': self.warehouse.id,
})
state = mps.get_production_schedule_view_state()[0]
self.assertTrue(all(forecast['outgoing_qty'] == 0 for forecast in state['forecast_ids']))
def test_incoming_sm_and_lead_time_out_of_date_range(self):
"""
Set a lead time on sam rule. Then generate an outgoing SM based on that
rule with:
- its date in dates range of MPS
- its date + rule's lead time outside the dates range of MPS
As a result, for the product mps, each incoming quantity should be zero
"""
self.env.company.manufacturing_period = 'day'
self.env.company.manufacturing_period_to_display = 10
warehouse = self.warehouse
warehouse.manufacture_steps = 'pbm_sam'
post_production_location = warehouse.sam_loc_id
stock_location = self.warehouse.lot_stock_id
pull_sam = self.env['stock.rule'].search([
('warehouse_id', '=', self.warehouse.id),
('location_src_id', '=', post_production_location.id),
('location_dest_id', '=', stock_location.id),
('action', '=', 'pull')
], limit=1)
pull_sam.delay = 15
template = self.bom_wardrobe.product_tmpl_id
template.route_ids = [(6, 0, pull_sam.route_id.ids)]
product = template.product_variant_id
procurement = self.env["procurement.group"].Procurement(
product, 1, product.uom_id,
stock_location,
product.name,
"/",
self.env.company,
{
"warehouse_id": self.warehouse,
"date_planned": date.today() + timedelta(days=16),
}
)
self.env["procurement.group"].run([procurement])
tomorrow = start_of(datetime.now() + timedelta(days=1), 'day')
move = self.env['stock.move'].search([('product_id', '=', product.id)], limit=1)
self.assertEqual(move.date, tomorrow)
state = self.mps_wardrobe.get_production_schedule_view_state()[0]
self.assertTrue(all(forecast['incoming_qty'] == 0 for forecast in state['forecast_ids']))
def test_product_variants_in_mps(self):
"""
Test that only the impacted components are updated when the forecast demand of a product is changed.
"""
# create the attribute size with two values ('M', 'L')
size_attribute = self.env['product.attribute'].create({'name': 'Size', 'sequence': 4})
self.env['product.attribute.value'].create([{
'name': name,
'attribute_id': size_attribute.id,
'sequence': 1,
} for name in ('M', 'L')])
product, c1, c2 = self.env['product.product'].create([{
'name': i,
'type': 'product',
} for i in range(3)])
product_template = product.product_tmpl_id
size_attribute_line = self.env['product.template.attribute.line'].create([{
'product_tmpl_id': product_template.id,
'attribute_id': size_attribute.id,
'value_ids': [(6, 0, size_attribute.value_ids.ids)]
}])
# Check that two product variant are created
self.assertEqual(product_template.product_variant_count, 2)
# Create a BoM with two components ('c1 applied only in m' and 'c2 applied only in L')
self.env['mrp.bom'].create({
'product_tmpl_id': product_template.id,
'product_uom_id': product_template.uom_id.id,
'product_qty': 1.0,
'type': 'normal',
'bom_line_ids': [
Command.create({
'product_id': c1.id,
'product_qty': 1,
'bom_product_template_attribute_value_ids': [(4, size_attribute_line.product_template_value_ids[0].id)]}), # M size
Command.create({
'product_id': c2.id,
'product_qty': 1,
'bom_product_template_attribute_value_ids': [(4, size_attribute_line.product_template_value_ids[1].id)]}), # L size
]
})
mps_p_m, mps_p_l, mps_c1, mps_c2 = self.env['mrp.production.schedule'].create([{
'product_id': product,
'warehouse_id': self.warehouse.id,
} for product in (product_template.product_variant_ids[0] | product_template.product_variant_ids[1] | c1 | c2).ids])
# check the mps of the product variant M
mps_impacted = mps_p_m[0].get_impacted_schedule()
self.assertEqual(len(mps_impacted), 1)
self.assertEqual(mps_impacted[0], mps_c1.id)
# check the mps of the product variant L
mps_impacted = mps_p_l[0].get_impacted_schedule()
self.assertEqual(len(mps_impacted), 1)
self.assertEqual(mps_impacted[0], mps_c2.id)