mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Initial ideas of having product level 'Planning Policy' for grouping and limiting where items can be planned from and how.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
from odoo.tests import common
|
||||
from datetime import datetime, timedelta
|
||||
from json import loads as json_decode
|
||||
from logging import getLogger
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
|
||||
|
||||
class TestPlanner(common.TransactionCase):
|
||||
@@ -69,13 +73,13 @@ class TestPlanner(common.TransactionCase):
|
||||
self.warehouse_1 = self.env['stock.warehouse'].create({
|
||||
'name': 'Washington Warehouse',
|
||||
'partner_id': self.warehouse_partner_1.id,
|
||||
'code': 'WH1',
|
||||
'code': 'TWH1',
|
||||
'shipping_calendar_id': self.warehouse_calendar_1.id,
|
||||
})
|
||||
self.warehouse_2 = self.env['stock.warehouse'].create({
|
||||
'name': 'Colorado Warehouse',
|
||||
'partner_id': self.warehouse_partner_2.id,
|
||||
'code': 'WH2',
|
||||
'code': 'TWH2',
|
||||
'shipping_calendar_id': self.warehouse_calendar_2.id,
|
||||
})
|
||||
self.so = self.env['sale.order'].create({
|
||||
@@ -87,13 +91,25 @@ class TestPlanner(common.TransactionCase):
|
||||
'type': 'product',
|
||||
'standard_price': 1.0,
|
||||
})
|
||||
self.product_12 = self.env['product.template'].create({
|
||||
'name': 'Product for WH1 Second',
|
||||
'type': 'product',
|
||||
'standard_price': 1.0,
|
||||
})
|
||||
self.product_1 = self.product_1.product_variant_id
|
||||
self.product_12 = self.product_12.product_variant_id
|
||||
self.product_2 = self.env['product.template'].create({
|
||||
'name': 'Product for WH2',
|
||||
'type': 'product',
|
||||
'standard_price': 1.0,
|
||||
})
|
||||
self.product_22 = self.env['product.template'].create({
|
||||
'name': 'Product for WH2 Second',
|
||||
'type': 'product',
|
||||
'standard_price': 1.0,
|
||||
})
|
||||
self.product_2 = self.product_2.product_variant_id
|
||||
self.product_22 = self.product_22.product_variant_id
|
||||
self.product_both = self.env['product.template'].create({
|
||||
'name': 'Product for Both',
|
||||
'type': 'product',
|
||||
@@ -105,6 +121,11 @@ class TestPlanner(common.TransactionCase):
|
||||
'product_id': self.product_1.id,
|
||||
'new_quantity': 100,
|
||||
}).change_product_qty()
|
||||
self.env['stock.change.product.qty'].create({
|
||||
'location_id': self.warehouse_1.lot_stock_id.id,
|
||||
'product_id': self.product_12.id,
|
||||
'new_quantity': 100,
|
||||
}).change_product_qty()
|
||||
self.env['stock.change.product.qty'].create({
|
||||
'location_id': self.warehouse_1.lot_stock_id.id,
|
||||
'product_id': self.product_both.id,
|
||||
@@ -115,16 +136,45 @@ class TestPlanner(common.TransactionCase):
|
||||
'product_id': self.product_2.id,
|
||||
'new_quantity': 100,
|
||||
}).change_product_qty()
|
||||
self.env['stock.change.product.qty'].create({
|
||||
'location_id': self.warehouse_2.lot_stock_id.id,
|
||||
'product_id': self.product_22.id,
|
||||
'new_quantity': 100,
|
||||
}).change_product_qty()
|
||||
self.env['stock.change.product.qty'].create({
|
||||
'location_id': self.warehouse_2.lot_stock_id.id,
|
||||
'product_id': self.product_both.id,
|
||||
'new_quantity': 100,
|
||||
}).change_product_qty()
|
||||
|
||||
self.policy_closest = self.env['sale.order.planning.policy'].create({
|
||||
'always_closest_warehouse': True,
|
||||
})
|
||||
self.policy_other = self.env['sale.order.planning.policy'].create({})
|
||||
self.wh_filter_1 = self.env['ir.filters'].create({
|
||||
'name': 'TWH1 Only',
|
||||
'domain': "[('id', '=', %d)]" % (self.warehouse_1.id, ),
|
||||
'model_id': 'stock.warehouse',
|
||||
})
|
||||
self.wh_filter_2 = self.env['ir.filters'].create({
|
||||
'name': 'TWH2 Only',
|
||||
'domain': "[('id', '=', %d)]" % (self.warehouse_2.id,),
|
||||
'model_id': 'stock.warehouse',
|
||||
})
|
||||
self.policy_wh_1 = self.env['sale.order.planning.policy'].create({
|
||||
'warehouse_filter_id': self.wh_filter_1.id,
|
||||
})
|
||||
self.policy_wh_2 = self.env['sale.order.planning.policy'].create({
|
||||
'warehouse_filter_id': self.wh_filter_2.id,
|
||||
})
|
||||
|
||||
def both_wh_ids(self):
|
||||
return [self.warehouse_1.id, self.warehouse_2.id]
|
||||
|
||||
def test_planner_creation_internals(self):
|
||||
def test_10_planner_creation_internals(self):
|
||||
"""
|
||||
Tests certain internal representations and that we can create a basic plan.
|
||||
"""
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_1.id,
|
||||
@@ -138,7 +188,11 @@ class TestPlanner(common.TransactionCase):
|
||||
self.assertTrue(base_option, 'Must have base option.')
|
||||
self.assertEqual(self.warehouse_1.id, base_option['warehouse_id'])
|
||||
|
||||
def test_planner_creation(self):
|
||||
def test_21_planner_creation(self):
|
||||
"""
|
||||
Scenario where only one warehouse has inventory on the order line.
|
||||
This is "the closest" warehouse.
|
||||
"""
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_1.id,
|
||||
@@ -152,7 +206,11 @@ class TestPlanner(common.TransactionCase):
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_1)
|
||||
self.assertFalse(planner.planning_option_ids[0].sub_options)
|
||||
|
||||
def test_planner_creation_2(self):
|
||||
def test_22_planner_creation(self):
|
||||
"""
|
||||
Scenario where only one warehouse has inventory on the order line.
|
||||
This is "the further" warehouse.
|
||||
"""
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_2.id,
|
||||
@@ -166,7 +224,11 @@ class TestPlanner(common.TransactionCase):
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_2)
|
||||
self.assertFalse(planner.planning_option_ids[0].sub_options)
|
||||
|
||||
def test_planner_creation_split(self):
|
||||
def test_31_planner_creation_split(self):
|
||||
"""
|
||||
Scenario where only one warehouse has inventory on each of the order line.
|
||||
This will cause two pickings to be created, one for each warehouse.
|
||||
"""
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_1.id,
|
||||
@@ -175,7 +237,7 @@ class TestPlanner(common.TransactionCase):
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_2.id,
|
||||
'name': 'demo',
|
||||
'name': 'demo2',
|
||||
})
|
||||
self.assertEqual(self.product_1.with_context(warehouse=self.warehouse_1.id).qty_available, 100)
|
||||
self.assertEqual(self.product_2.with_context(warehouse=self.warehouse_2.id).qty_available, 100)
|
||||
@@ -184,7 +246,12 @@ class TestPlanner(common.TransactionCase):
|
||||
self.assertTrue(planner.planning_option_ids, 'Must have one or more plans.')
|
||||
self.assertTrue(planner.planning_option_ids[0].sub_options)
|
||||
|
||||
def test_planner_creation_no_split(self):
|
||||
def test_32_planner_creation_no_split(self):
|
||||
"""
|
||||
Scenario where only "the further" warehouse has inventory on whole order, but
|
||||
the "closest" warehouse only has inventory on one item.
|
||||
This will simply plan out of the "the further" warehouse.
|
||||
"""
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_both.id,
|
||||
@@ -202,3 +269,194 @@ class TestPlanner(common.TransactionCase):
|
||||
self.assertTrue(planner.planning_option_ids, 'Must have one or more plans.')
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_2)
|
||||
self.assertFalse(planner.planning_option_ids[0].sub_options)
|
||||
|
||||
def test_42_policy_force_closest(self):
|
||||
"""
|
||||
Scenario where an item may not be in stock at "the closest" warehouse, but an item is only allowed
|
||||
to come from "the closest" warehouse.
|
||||
"""
|
||||
self.product_2.property_planning_policy_id = self.policy_closest
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_both.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_2.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.assertEqual(self.product_both.with_context(warehouse=self.warehouse_1.id).qty_available, 100)
|
||||
# Close warehouse doesn't have product.
|
||||
self.assertEqual(self.product_2.with_context(warehouse=self.warehouse_1.id).qty_available, 0)
|
||||
both_wh_ids = self.both_wh_ids()
|
||||
planner = self.env['sale.order.make.plan'].with_context(warehouse_domain=[('id', 'in', both_wh_ids)],
|
||||
skip_plan_shipping=True).create({'order_id': self.so.id})
|
||||
self.assertTrue(planner.planning_option_ids, 'Must have one or more plans.')
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_1)
|
||||
self.assertFalse(planner.planning_option_ids[0].sub_options)
|
||||
|
||||
def test_43_policy_merge(self):
|
||||
"""
|
||||
Scenario that will make a complicated scenario specifically:
|
||||
- 3 policy groups
|
||||
- 2 base options with sub_options (all base options with same warehouse)
|
||||
"""
|
||||
self.product_both.property_planning_policy_id = self.policy_closest
|
||||
self.product_12.property_planning_policy_id = self.policy_other
|
||||
self.product_22.property_planning_policy_id = self.policy_other
|
||||
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_both.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_1.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_2.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_12.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_22.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
both_wh_ids = self.both_wh_ids()
|
||||
planner = self.env['sale.order.make.plan'].with_context(warehouse_domain=[('id', 'in', both_wh_ids)],
|
||||
skip_plan_shipping=True).create({'order_id': self.so.id})
|
||||
self.assertTrue(planner.planning_option_ids, 'Must have one or more plans.')
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_1)
|
||||
self.assertTrue(planner.planning_option_ids.sub_options)
|
||||
|
||||
sub_options = json_decode(planner.planning_option_ids.sub_options)
|
||||
_logger.error(sub_options)
|
||||
wh_1_ids = sorted([self.product_both.id, self.product_1.id, self.product_12.id])
|
||||
wh_2_ids = sorted([self.product_2.id, self.product_22.id])
|
||||
self.assertEqual(sorted(sub_options[str(self.warehouse_1.id)]['product_ids']), wh_1_ids)
|
||||
self.assertEqual(sorted(sub_options[str(self.warehouse_2.id)]['product_ids']), wh_2_ids)
|
||||
|
||||
def test_44_policy_merge_2(self):
|
||||
"""
|
||||
Scenario that will make a complicated scenario specifically:
|
||||
- 3 policy groups
|
||||
- 2 base options from different warehouses
|
||||
"""
|
||||
self.product_both.property_planning_policy_id = self.policy_other
|
||||
self.product_12.property_planning_policy_id = self.policy_closest
|
||||
self.product_22.property_planning_policy_id = self.policy_other
|
||||
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_both.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_1.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_2.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_12.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_22.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
both_wh_ids = self.both_wh_ids()
|
||||
planner = self.env['sale.order.make.plan'].with_context(warehouse_domain=[('id', 'in', both_wh_ids)],
|
||||
skip_plan_shipping=True).create({'order_id': self.so.id})
|
||||
self.assertTrue(planner.planning_option_ids, 'Must have one or more plans.')
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_2)
|
||||
self.assertTrue(planner.planning_option_ids.sub_options)
|
||||
|
||||
sub_options = json_decode(planner.planning_option_ids.sub_options)
|
||||
_logger.error(sub_options)
|
||||
wh_1_ids = sorted([self.product_1.id, self.product_12.id])
|
||||
wh_2_ids = sorted([self.product_both.id, self.product_2.id, self.product_22.id])
|
||||
self.assertEqual(sorted(sub_options[str(self.warehouse_1.id)]['product_ids']), wh_1_ids)
|
||||
self.assertEqual(sorted(sub_options[str(self.warehouse_2.id)]['product_ids']), wh_2_ids)
|
||||
|
||||
def test_45_policy_merge_3(self):
|
||||
"""
|
||||
Different order of products for test_44
|
||||
- 3 policy groups
|
||||
- 2 base options from different warehouses
|
||||
"""
|
||||
self.product_both.property_planning_policy_id = self.policy_other
|
||||
self.product_12.property_planning_policy_id = self.policy_closest
|
||||
self.product_22.property_planning_policy_id = self.policy_other
|
||||
|
||||
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_1.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_2.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_12.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_22.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_both.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
both_wh_ids = self.both_wh_ids()
|
||||
planner = self.env['sale.order.make.plan'].with_context(warehouse_domain=[('id', 'in', both_wh_ids)],
|
||||
skip_plan_shipping=True).create({'order_id': self.so.id})
|
||||
self.assertTrue(planner.planning_option_ids, 'Must have one or more plans.')
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_1)
|
||||
self.assertTrue(planner.planning_option_ids.sub_options)
|
||||
|
||||
sub_options = json_decode(planner.planning_option_ids.sub_options)
|
||||
_logger.error(sub_options)
|
||||
wh_1_ids = sorted([self.product_1.id, self.product_12.id])
|
||||
wh_2_ids = sorted([self.product_both.id, self.product_2.id, self.product_22.id])
|
||||
self.assertEqual(sorted(sub_options[str(self.warehouse_1.id)]['product_ids']), wh_1_ids)
|
||||
self.assertEqual(sorted(sub_options[str(self.warehouse_2.id)]['product_ids']), wh_2_ids)
|
||||
|
||||
def test_51_policy_specific_warehouse(self):
|
||||
"""
|
||||
Force one item to TWH2.
|
||||
"""
|
||||
self.product_both.property_planning_policy_id = self.policy_wh_2
|
||||
|
||||
self.env['sale.order.line'].create({
|
||||
'order_id': self.so.id,
|
||||
'product_id': self.product_both.id,
|
||||
'name': 'demo',
|
||||
})
|
||||
both_wh_ids = self.both_wh_ids()
|
||||
planner = self.env['sale.order.make.plan'].with_context(warehouse_domain=[('id', 'in', both_wh_ids)],
|
||||
skip_plan_shipping=True).create({'order_id': self.so.id})
|
||||
self.assertTrue(planner.planning_option_ids, 'Must have one or more plans.')
|
||||
self.assertEqual(planner.planning_option_ids.warehouse_id, self.warehouse_2)
|
||||
|
||||
Reference in New Issue
Block a user