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:
Jared Kipe
2018-07-07 12:48:54 -07:00
parent ee6bac5f67
commit eda3eee901
9 changed files with 700 additions and 59 deletions

View File

@@ -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)