mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
463 lines
21 KiB
Python
463 lines
21 KiB
Python
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):
|
|
# @todo Test date planning!
|
|
|
|
def setUp(self):
|
|
super(TestPlanner, self).setUp()
|
|
self.today = datetime.today()
|
|
self.tomorrow = datetime.today() + timedelta(days=1)
|
|
# This partner has a parent
|
|
self.country_usa = self.env['res.country'].search([('name', '=', 'United States')], limit=1)
|
|
self.state_wa = self.env['res.country.state'].search([('name', '=', 'Washington')], limit=1)
|
|
self.state_co = self.env['res.country.state'].search([('name', '=', 'Colorado')], limit=1)
|
|
self.partner_wa = self.env['res.partner'].create({
|
|
'name': 'Jared',
|
|
'street': '1234 Test Street',
|
|
'city': 'Marysville',
|
|
'state_id': self.state_wa.id,
|
|
'zip': '98270',
|
|
'country_id': self.country_usa.id,
|
|
'partner_latitude': 48.05636,
|
|
'partner_longitude': -122.14922,
|
|
})
|
|
self.warehouse_partner_1 = self.env['res.partner'].create({
|
|
'name': 'WH1',
|
|
'street': '1234 Test Street',
|
|
'city': 'Lynnwood',
|
|
'state_id': self.state_wa.id,
|
|
'zip': '98036',
|
|
'country_id': self.country_usa.id,
|
|
'partner_latitude': 47.82093,
|
|
'partner_longitude': -122.31513,
|
|
})
|
|
|
|
self.warehouse_partner_2 = self.env['res.partner'].create({
|
|
'name': 'WH2',
|
|
'street': '1234 Test Street',
|
|
'city': 'Craig',
|
|
'state_id': self.state_co.id,
|
|
'zip': '81625',
|
|
'country_id': self.country_usa.id,
|
|
'partner_latitude': 40.51525,
|
|
'partner_longitude': -107.54645,
|
|
})
|
|
self.warehouse_calendar_1 = self.env['resource.calendar'].create({
|
|
'name': 'Washington Warehouse Hours',
|
|
'attendance_ids': [
|
|
(0, 0, {'name': 'today',
|
|
'dayofweek': str(self.today.weekday()),
|
|
'hour_from': (self.today.hour - 1) % 24,
|
|
'hour_to': (self.today.hour + 1) % 24}),
|
|
(0, 0, {'name': 'tomorrow',
|
|
'dayofweek': str(self.tomorrow.weekday()),
|
|
'hour_from': (self.tomorrow.hour - 1) % 24,
|
|
'hour_to': (self.tomorrow.hour + 1) % 24}),
|
|
]
|
|
})
|
|
self.warehouse_calendar_2 = self.env['resource.calendar'].create({
|
|
'name': 'Colorado Warehouse Hours',
|
|
'attendance_ids': [
|
|
(0, 0, {'name': 'tomorrow',
|
|
'dayofweek': str(self.tomorrow.weekday()),
|
|
'hour_from': (self.tomorrow.hour - 1) % 24,
|
|
'hour_to': (self.tomorrow.hour + 1) % 24}),
|
|
]
|
|
})
|
|
self.warehouse_1 = self.env['stock.warehouse'].create({
|
|
'name': 'Washington Warehouse',
|
|
'partner_id': self.warehouse_partner_1.id,
|
|
'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': 'TWH2',
|
|
'shipping_calendar_id': self.warehouse_calendar_2.id,
|
|
})
|
|
self.so = self.env['sale.order'].create({
|
|
'partner_id': self.partner_wa.id,
|
|
'warehouse_id': self.warehouse_1.id,
|
|
})
|
|
self.product_1 = self.env['product.template'].create({
|
|
'name': 'Product for WH1',
|
|
'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',
|
|
'standard_price': 1.0,
|
|
})
|
|
self.product_both = self.product_both.product_variant_id
|
|
self.env['stock.change.product.qty'].create({
|
|
'location_id': self.warehouse_1.lot_stock_id.id,
|
|
'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,
|
|
'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_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_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,
|
|
'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.assertEqual(set(both_wh_ids), set(planner.get_warehouses().ids))
|
|
fake_order = planner._fake_order(self.so)
|
|
base_option = planner.generate_base_option(fake_order)
|
|
self.assertTrue(base_option, 'Must have base option.')
|
|
self.assertEqual(self.warehouse_1.id, base_option['warehouse_id'])
|
|
|
|
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,
|
|
'name': 'demo',
|
|
})
|
|
self.assertEqual(self.product_1.with_context(warehouse=self.warehouse_1.id).qty_available, 100)
|
|
self.assertEqual(self.product_1.with_context(warehouse=self.warehouse_2.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_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,
|
|
'name': 'demo',
|
|
})
|
|
self.assertEqual(self.product_2.with_context(warehouse=self.warehouse_1.id).qty_available, 0)
|
|
self.assertEqual(self.product_2.with_context(warehouse=self.warehouse_2.id).qty_available, 100)
|
|
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.assertFalse(planner.planning_option_ids[0].sub_options)
|
|
|
|
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,
|
|
'name': 'demo',
|
|
})
|
|
self.env['sale.order.line'].create({
|
|
'order_id': self.so.id,
|
|
'product_id': self.product_2.id,
|
|
'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)
|
|
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.assertTrue(planner.planning_option_ids[0].sub_options)
|
|
|
|
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,
|
|
'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_2.id).qty_available, 100)
|
|
self.assertEqual(self.product_2.with_context(warehouse=self.warehouse_2.id).qty_available, 100)
|
|
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.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_1, 'If this fails, it will probably pass next time.')
|
|
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)
|