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:
@@ -28,6 +28,7 @@ on the specific method's characteristics. (e.g. Do they deliver on Saturday?)
|
||||
'base_geolocalize',
|
||||
'delivery',
|
||||
'resource',
|
||||
'stock',
|
||||
],
|
||||
'demo': [],
|
||||
'data': [
|
||||
@@ -35,6 +36,7 @@ on the specific method's characteristics. (e.g. Do they deliver on Saturday?)
|
||||
'views/sale.xml',
|
||||
'views/stock.xml',
|
||||
'views/delivery.xml',
|
||||
'views/product.xml',
|
||||
],
|
||||
'auto_install': False,
|
||||
'installable': True,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from . import sale
|
||||
from . import stock
|
||||
from . import delivery
|
||||
from . import product
|
||||
from . import planning
|
||||
from . import partner
|
||||
|
||||
@@ -36,7 +36,7 @@ class DeliveryCarrier(models.Model):
|
||||
while date_planned < date_delivered:
|
||||
if transit_days > 10:
|
||||
break
|
||||
interval = self.delivery_calendar_id.schedule_days(1, date_planned, compute_leaves=True)
|
||||
interval = self.delivery_calendar_id.plan_days(1, date_planned, compute_leaves=True)
|
||||
|
||||
if not interval:
|
||||
return self._calculate_transit_days_naive(date_planned, date_delivered)
|
||||
@@ -59,7 +59,7 @@ class DeliveryCarrier(models.Model):
|
||||
# date calculations needs an extra day
|
||||
effective_transit_days = transit_days + 1
|
||||
|
||||
interval = self.delivery_calendar_id.schedule_days(effective_transit_days, date_planned, compute_leaves=True)
|
||||
interval = self.delivery_calendar_id.plan_days(effective_transit_days, date_planned, compute_leaves=True)
|
||||
if not interval:
|
||||
return self._calculate_date_delivered_naive(date_planned, transit_days)
|
||||
|
||||
|
||||
29
sale_planner/models/partner.py
Normal file
29
sale_planner/models/partner.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
try:
|
||||
from uszipcode import ZipcodeSearchEngine
|
||||
except ImportError:
|
||||
ZipcodeSearchEngine = None
|
||||
|
||||
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
@api.multi
|
||||
def geo_localize(self):
|
||||
# We need country names in English below
|
||||
for partner in self.with_context(lang='en_US'):
|
||||
if ZipcodeSearchEngine and partner.zip:
|
||||
with ZipcodeSearchEngine() as search:
|
||||
zipcode = search.by_zipcode(partner.zip)
|
||||
if zipcode:
|
||||
partner.write({
|
||||
'partner_latitude': zipcode['Latitude'],
|
||||
'partner_longitude': zipcode['Longitude'],
|
||||
'date_localization': fields.Date.context_today(partner),
|
||||
})
|
||||
else:
|
||||
super(Partner, partner).geo_localize()
|
||||
else:
|
||||
super(Partner, partner).geo_localize()
|
||||
return True
|
||||
16
sale_planner/models/planning.py
Normal file
16
sale_planner/models/planning.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class PlanningPolicy(models.Model):
|
||||
_name = 'sale.order.planning.policy'
|
||||
|
||||
name = fields.Char(string='Name')
|
||||
carrier_filter_id = fields.Many2one(
|
||||
'ir.filters',
|
||||
string='Delivery Carrier Filter',
|
||||
)
|
||||
warehouse_filter_id = fields.Many2one(
|
||||
'ir.filters',
|
||||
string='Warehouse Filter',
|
||||
)
|
||||
always_closest_warehouse = fields.Boolean(string='Always Plan Closest Warehouse')
|
||||
22
sale_planner/models/product.py
Normal file
22
sale_planner/models/product.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
property_planning_policy_id = fields.Many2one('sale.order.planning.policy', company_dependent=True,
|
||||
string="Order Planner Policy",
|
||||
help="The Order Planner Policy to use when making a sale order planner.")
|
||||
|
||||
@api.multi
|
||||
def get_planning_policy(self):
|
||||
self.ensure_one()
|
||||
return self.property_planning_policy_id or self.categ_id.property_planning_policy_categ_id
|
||||
|
||||
|
||||
class ProductCategory(models.Model):
|
||||
_inherit = 'product.category'
|
||||
|
||||
property_planning_policy_categ_id = fields.Many2one('sale.order.planning.policy', company_dependent=True,
|
||||
string="Order Planner Policy",
|
||||
help="The Order Planner Policy to use when making a sale order planner.")
|
||||
@@ -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)
|
||||
|
||||
23
sale_planner/views/product.xml
Normal file
23
sale_planner/views/product.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="product_template_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">product.template.common.form.inherit</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='categ_id']" position="after">
|
||||
<field name="property_planning_policy_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="product_category_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">product.category.form.inherit</field>
|
||||
<field name="model">product.category</field>
|
||||
<field name="inherit_id" ref="product.product_category_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='parent_id']" position="after">
|
||||
<field name="property_planning_policy_categ_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -2,6 +2,7 @@ from math import sin, cos, sqrt, atan2, radians
|
||||
from json import dumps, loads
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
from logging import getLogger
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
@@ -255,59 +256,70 @@ class SaleOrderMakePlan(models.TransientModel):
|
||||
|
||||
return options
|
||||
|
||||
def get_warehouses(self, warehouse_id=None):
|
||||
def get_warehouses(self, warehouse_id=None, domain=None):
|
||||
warehouse = self.env['stock.warehouse'].sudo()
|
||||
if warehouse_id:
|
||||
return warehouse.search([('id', '=', warehouse_id)])
|
||||
return warehouse.browse(warehouse_id)
|
||||
|
||||
if domain:
|
||||
if not isinstance(domain, (list, tuple)):
|
||||
domain = tools.safe_eval(domain)
|
||||
else:
|
||||
domain = []
|
||||
|
||||
if self.env.context.get('warehouse_domain'):
|
||||
#potential bug here if this is textual
|
||||
return warehouse.search(self.env.context.get('warehouse_domain'))
|
||||
domain.extend(self.env.context.get('warehouse_domain'))
|
||||
|
||||
irconfig_parameter = self.env['ir.config_parameter'].sudo()
|
||||
if irconfig_parameter.get_param('sale.order.planner.warehouse_domain'):
|
||||
domain = tools.safe_eval(irconfig_parameter.get_param('sale.order.planner.warehouse_domain'))
|
||||
return warehouse.search(domain)
|
||||
domain.extend(tools.safe_eval(irconfig_parameter.get_param('sale.order.planner.warehouse_domain')))
|
||||
|
||||
return warehouse.search([])
|
||||
return warehouse.search(domain)
|
||||
|
||||
def get_shipping_carriers(self, carrier_id=None):
|
||||
def get_shipping_carriers(self, carrier_id=None, domain=None):
|
||||
Carrier = self.env['delivery.carrier'].sudo()
|
||||
if carrier_id:
|
||||
return Carrier.search([('id', '=', carrier_id)])
|
||||
return Carrier.browse(carrier_id)
|
||||
|
||||
if domain:
|
||||
if not isinstance(domain, (list, tuple)):
|
||||
domain = tools.safe_eval(domain)
|
||||
else:
|
||||
domain = []
|
||||
|
||||
if self.env.context.get('carrier_domain'):
|
||||
# potential bug here if this is textual
|
||||
return Carrier.search(self.env.context.get('carrier_domain'))
|
||||
domain.extend(self.env.context.get('carrier_domain'))
|
||||
|
||||
irconfig_parameter = self.env['ir.config_parameter'].sudo()
|
||||
if irconfig_parameter.get_param('sale.order.planner.carrier_domain'):
|
||||
domain = tools.safe_eval(irconfig_parameter.get_param('sale.order.planner.carrier_domain'))
|
||||
return Carrier.search(domain)
|
||||
domain.extend(tools.safe_eval(irconfig_parameter.get_param('sale.order.planner.carrier_domain')))
|
||||
|
||||
return Carrier.search([])
|
||||
return Carrier.search(domain)
|
||||
|
||||
def generate_base_option(self, order_fake):
|
||||
product_lines = list(filter(lambda line: line.product_id.type == 'product', order_fake.order_line))
|
||||
if not product_lines:
|
||||
return {}
|
||||
def _generate_base_option(self, order_fake, policy_group):
|
||||
policy = False
|
||||
flag_force_closest = False
|
||||
warehouse_domain = False
|
||||
if 'policy' in policy_group:
|
||||
policy = policy_group['policy']
|
||||
flag_force_closest = policy.always_closest_warehouse
|
||||
warehouse_domain = policy.warehouse_filter_id.domain
|
||||
# Need to look at warehouse filter.
|
||||
# Eventually need to look at shipping filter....
|
||||
|
||||
buy_qty = {line.product_id.id: line.product_uom_qty for line in product_lines}
|
||||
|
||||
products = self.env['product.product']
|
||||
for line in product_lines:
|
||||
products |= line.product_id
|
||||
|
||||
warehouses = self.get_warehouses()
|
||||
product_stock = self._fetch_product_stock(warehouses, products)
|
||||
warehouses = self.get_warehouses(domain=warehouse_domain)
|
||||
if flag_force_closest:
|
||||
warehouses = self._find_closest_warehouse_by_partner(warehouses, order_fake.partner_shipping_id)
|
||||
product_stock = self._fetch_product_stock(warehouses, policy_group['products'])
|
||||
sub_options = {}
|
||||
wh_date_planning = {}
|
||||
|
||||
p_len = len(products)
|
||||
p_len = len(policy_group['products'])
|
||||
full_candidates = set()
|
||||
partial_candidates = set()
|
||||
for wh_id, stock in product_stock.items():
|
||||
available = sum(1 for p_id, p_vals in stock.items() if self._is_in_stock(p_vals, buy_qty[p_id]))
|
||||
available = sum(1 for p_id, p_vals in stock.items() if self._is_in_stock(p_vals, policy_group['buy_qty'][p_id]))
|
||||
if available == p_len:
|
||||
full_candidates.add(wh_id)
|
||||
elif available > 0:
|
||||
@@ -316,32 +328,35 @@ class SaleOrderMakePlan(models.TransientModel):
|
||||
if full_candidates:
|
||||
if len(full_candidates) == 1:
|
||||
warehouse = warehouses.filtered(lambda wh: wh.id in full_candidates)
|
||||
date_planned = self._next_warehouse_shipping_date(warehouse)
|
||||
order_fake.warehouse_id = warehouse
|
||||
return {'warehouse_id': warehouse.id, 'date_planned': date_planned}
|
||||
|
||||
warehouse = self._find_closest_warehouse_by_partner(
|
||||
warehouses.filtered(lambda wh: wh.id in full_candidates), order_fake.partner_shipping_id)
|
||||
else:
|
||||
warehouse = self._find_closest_warehouse_by_partner(
|
||||
warehouses.filtered(lambda wh: wh.id in full_candidates), order_fake.partner_shipping_id)
|
||||
date_planned = self._next_warehouse_shipping_date(warehouse)
|
||||
order_fake.warehouse_id = warehouse
|
||||
#order_fake.warehouse_id = warehouse
|
||||
return {'warehouse_id': warehouse.id, 'date_planned': date_planned}
|
||||
|
||||
_logger.error(' partial_candidates: ' + str(partial_candidates))
|
||||
if partial_candidates:
|
||||
_logger.error(' using...')
|
||||
if len(partial_candidates) == 1:
|
||||
warehouse = warehouses.filtered(lambda wh: wh.id in partial_candidates)
|
||||
order_fake.warehouse_id = warehouse
|
||||
#order_fake.warehouse_id = warehouse
|
||||
return {'warehouse_id': warehouse.id}
|
||||
|
||||
sorted_warehouses = self._sort_warehouses_by_partner(warehouses.filtered(lambda wh: wh.id in partial_candidates), order_fake.partner_shipping_id)
|
||||
primary_wh = sorted_warehouses[0] #partial_candidates means there is at least one warehouse
|
||||
sorted_warehouses = self._sort_warehouses_by_partner(
|
||||
warehouses.filtered(lambda wh: wh.id in partial_candidates), order_fake.partner_shipping_id)
|
||||
_logger.error(' sorted_warehouses: ' + str(sorted_warehouses) + ' warehouses: ' + str(warehouses))
|
||||
primary_wh = sorted_warehouses[0] # partial_candidates means there is at least one warehouse
|
||||
primary_wh_date_planned = self._next_warehouse_shipping_date(primary_wh)
|
||||
wh_date_planning[primary_wh.id] = primary_wh_date_planned
|
||||
for wh in sorted_warehouses:
|
||||
if not buy_qty:
|
||||
_logger.error(' wh: ' + str(wh) + ' buy_qty: ' + str(policy_group['buy_qty']))
|
||||
if not policy_group['buy_qty']:
|
||||
continue
|
||||
stock = product_stock[wh.id]
|
||||
for p_id, p_vals in stock.items():
|
||||
if p_id in buy_qty and self._is_in_stock(p_vals, buy_qty[p_id]):
|
||||
_logger.error(' p_id: ' + str(p_id) + ' p_vals: ' + str(p_vals))
|
||||
if p_id in policy_group['buy_qty'] and self._is_in_stock(p_vals, policy_group['buy_qty'][p_id]):
|
||||
if wh.id not in sub_options:
|
||||
sub_options[wh.id] = {
|
||||
'date_planned': self._next_warehouse_shipping_date(wh),
|
||||
@@ -350,23 +365,287 @@ class SaleOrderMakePlan(models.TransientModel):
|
||||
}
|
||||
sub_options[wh.id]['product_ids'].append(p_id)
|
||||
sub_options[wh.id]['product_skus'].append(p_vals['sku'])
|
||||
del buy_qty[p_id]
|
||||
_logger.error(' removing: ' + str(p_id))
|
||||
del policy_group['buy_qty'][p_id]
|
||||
|
||||
if not buy_qty:
|
||||
if not policy_group['buy_qty']:
|
||||
# item_details can fulfil all items.
|
||||
# this is good!!
|
||||
order_fake.warehouse_id = primary_wh
|
||||
return {'warehouse_id': primary_wh.id, 'date_planned': primary_wh_date_planned, 'sub_options': sub_options}
|
||||
#order_fake.warehouse_id = primary_wh
|
||||
return {'warehouse_id': primary_wh.id, 'date_planned': primary_wh_date_planned,
|
||||
'sub_options': sub_options}
|
||||
|
||||
# warehouses cannot fulfil all requested items!!
|
||||
order_fake.warehouse_id = primary_wh
|
||||
#order_fake.warehouse_id = primary_wh
|
||||
return {'warehouse_id': primary_wh.id}
|
||||
|
||||
# nobody has stock!
|
||||
primary_wh = self._find_closest_warehouse_by_partner(warehouses, order_fake.partner_shipping_id)
|
||||
order_fake.warehouse_id = primary_wh
|
||||
#order_fake.warehouse_id = primary_wh
|
||||
return {'warehouse_id': primary_wh.id}
|
||||
|
||||
def generate_base_option(self, order_fake):
|
||||
_logger.error('generate_base_option:')
|
||||
product_lines = list(filter(lambda line: line.product_id.type == 'product', order_fake.order_line))
|
||||
if not product_lines:
|
||||
return {}
|
||||
|
||||
buy_qty = defaultdict(int)
|
||||
for line in product_lines:
|
||||
buy_qty[line.product_id.id] += line.product_uom_qty
|
||||
|
||||
products = self.env['product.product']
|
||||
for line in product_lines:
|
||||
products |= line.product_id
|
||||
|
||||
policy_groups = defaultdict(lambda: {'products': [], 'buy_qty': {}})
|
||||
for p in products:
|
||||
policy = p.product_tmpl_id.get_planning_policy()
|
||||
if policy:
|
||||
policy_groups[policy.id]['products'].append(p)
|
||||
policy_groups[policy.id]['buy_qty'][p.id] = buy_qty[p.id]
|
||||
policy_groups[policy.id]['policy'] = policy
|
||||
else:
|
||||
policy_groups[0]['products'].append(p)
|
||||
policy_groups[0]['buy_qty'][p.id] = buy_qty[p.id]
|
||||
|
||||
|
||||
for _, policy_group in policy_groups.items():
|
||||
product_set = self.env['product.product'].browse()
|
||||
for p in policy_group['products']:
|
||||
product_set += p
|
||||
policy_group['products'] = product_set
|
||||
policy_group['base_option'] = self._generate_base_option(order_fake, policy_group)
|
||||
|
||||
|
||||
option_policy_groups = defaultdict(lambda: {'products': self.env['product.product'].browse(), 'policies': self.env['sale.order.planning.policy'].browse(), 'date_planned': '1900', 'sub_options': [],})
|
||||
for policy_id, policy_group in policy_groups.items():
|
||||
base_option = policy_group['base_option']
|
||||
_logger.error(' base_option: ' + str(base_option))
|
||||
b_wh_id = base_option['warehouse_id']
|
||||
if 'policy' in policy_group:
|
||||
option_policy_groups[b_wh_id]['policies'] += policy_group['policy']
|
||||
if option_policy_groups[b_wh_id].get('date_planned'):
|
||||
# The first base_option without a date clears it
|
||||
if base_option.get('date_planned'):
|
||||
if base_option['date_planned'] > option_policy_groups[b_wh_id]['date_planned']:
|
||||
option_policy_groups[b_wh_id]['date_planned'] = base_option['date_planned']
|
||||
else:
|
||||
# One of our options has no plan date. Remove it.
|
||||
del option_policy_groups[b_wh_id]['date_planned']
|
||||
if 'sub_options' in base_option:
|
||||
option_policy_groups[b_wh_id]['sub_options'].append(base_option['sub_options'])
|
||||
option_policy_groups[b_wh_id]['products'] += policy_group['products']
|
||||
option_policy_groups[b_wh_id]['warehouse_id'] = b_wh_id
|
||||
|
||||
# clean up unused sub_options and collapse used ones
|
||||
for o_wh_id, option_group in option_policy_groups.items():
|
||||
if not option_group['sub_options']:
|
||||
del option_group['sub_options']
|
||||
else:
|
||||
sub_options = defaultdict(lambda: {'date_planned': '1900', 'product_ids': [], 'product_skus': []})
|
||||
remaining_products = option_group['products']
|
||||
for options in option_group['sub_options']:
|
||||
for wh_id, option in options.items():
|
||||
if sub_options[wh_id].get('date_planned'):
|
||||
# The first option without a date clears it
|
||||
if option.get('date_planned'):
|
||||
if option['date_planned'] > sub_options[wh_id]['date_planned']:
|
||||
sub_options[wh_id]['date_planned'] = option['date_planned']
|
||||
else:
|
||||
del sub_options[wh_id]['date_planned']
|
||||
sub_options[wh_id]['product_ids'] += option['product_ids']
|
||||
sub_options[wh_id]['product_skus'] += option['product_skus']
|
||||
remaining_products = remaining_products.filtered(lambda p: p.id not in sub_options[wh_id]['product_ids'])
|
||||
option_group['sub_options'] = sub_options
|
||||
if remaining_products:
|
||||
option_group['sub_options'][o_wh_id]['product_ids'] += remaining_products.ids
|
||||
option_group['sub_options'][o_wh_id]['product_skus'] += remaining_products.mapped('default_code')
|
||||
|
||||
|
||||
# At this point we should have all of the policy options collapsed.
|
||||
# Collapse warehouse options.
|
||||
base_option = {'date_planned': '1900', 'products': self.env['product.product'].browse()}
|
||||
for wh_id, intermediate_option in option_policy_groups.items():
|
||||
_logger.error(' base_option: ' + str(base_option))
|
||||
_logger.error(' intermediate_option: ' + str(intermediate_option))
|
||||
if 'warehouse_id' not in base_option:
|
||||
base_option['warehouse_id'] = wh_id
|
||||
b_wh_id = base_option['warehouse_id']
|
||||
|
||||
if base_option.get('date_planned'):
|
||||
if intermediate_option.get('date_planned'):
|
||||
if intermediate_option['date_planned'] > base_option['date_planned']:
|
||||
base_option['date_planned'] = intermediate_option['date_planned']
|
||||
else:
|
||||
del base_option['date_planned']
|
||||
if 'sub_options' in base_option:
|
||||
for _, option in base_option['sub_options'].items():
|
||||
del option['date_planned']
|
||||
if b_wh_id == wh_id:
|
||||
if 'sub_options' in intermediate_option and 'sub_options' not in base_option:
|
||||
# Base option will get new sub_options
|
||||
intermediate_option['sub_options'][wh_id]['product_ids'] += base_option['products'].ids
|
||||
intermediate_option['sub_options'][wh_id]['product_skus'] += base_option['products'].mapped('default_code')
|
||||
base_option['sub_options'] = intermediate_option['sub_options']
|
||||
elif 'sub_options' in intermediate_option and 'sub_options' in base_option:
|
||||
# Both have sub_options, merge
|
||||
for o_wh_id, option in intermediate_option['sub_options'].items():
|
||||
if o_wh_id not in base_option['sub_options']:
|
||||
base_option['sub_options'][o_wh_id] = option
|
||||
else:
|
||||
base_option['sub_options'][o_wh_id]['product_ids'] += option['product_ids']
|
||||
base_option['sub_options'][o_wh_id]['product_skus'] += option['product_skus']
|
||||
if base_option.get('date_planned'):
|
||||
if option['date_planned'] > base_option['sub_options'][o_wh_id]['date_planned']:
|
||||
base_option['sub_options'][o_wh_id]['date_planned'] = intermediate_option['date_planned']
|
||||
elif 'sub_options' in base_option:
|
||||
# merge products from intermediate into base_option's sub_options
|
||||
base_option['sub_options'][wh_id]['product_ids'] += intermediate_option['products'].ids
|
||||
base_option['sub_options'][wh_id]['product_skus'] += intermediate_option['products'].mapped('default_code')
|
||||
base_option['products'] += intermediate_option['products']
|
||||
else:
|
||||
# Promote
|
||||
if 'sub_options' not in intermediate_option and 'sub_options' not in base_option:
|
||||
base_option['sub_options'] = {
|
||||
wh_id: {
|
||||
'product_ids': intermediate_option['products'].ids,
|
||||
'product_skus': intermediate_option['products'].mapped('default_code'),
|
||||
},
|
||||
b_wh_id: {
|
||||
'product_ids': base_option['products'].ids,
|
||||
'product_skus': base_option['products'].mapped('default_code'),
|
||||
},
|
||||
}
|
||||
if base_option.get('date_planned'):
|
||||
base_option['sub_options'][wh_id]['date_planned'] = intermediate_option['date_planned']
|
||||
base_option['sub_options'][b_wh_id]['date_planned'] = base_option['date_planned']
|
||||
elif 'sub_options' in base_option and 'sub_options' not in intermediate_option:
|
||||
if wh_id not in base_option['sub_options']:
|
||||
base_option['sub_options'][wh_id] = {
|
||||
'product_ids': intermediate_option['products'].ids,
|
||||
'product_skus': intermediate_option['products'].mapped('default_code'),
|
||||
}
|
||||
if base_option.get('date_planned'):
|
||||
base_option['sub_options'][wh_id]['date_planned'] = intermediate_option['date_planned']
|
||||
else:
|
||||
base_option['sub_options'][wh_id]['product_ids'] += intermediate_option['products'].ids
|
||||
base_option['sub_options'][wh_id]['product_skus'] += intermediate_option['products'].mapped('default_code')
|
||||
if base_option.get('date_planned'):
|
||||
if intermediate_option['date_planned'] > base_option['sub_options'][wh_id]['date_planned']:
|
||||
base_option['sub_options'][wh_id]['date_planned'] = intermediate_option['date_planned']
|
||||
elif 'sub_options' in intermediate_option and 'sub_options' in base_option:
|
||||
# Both have sub_options, merge
|
||||
for o_wh_id, option in intermediate_option['sub_options'].items():
|
||||
if o_wh_id not in base_option['sub_options']:
|
||||
base_option['sub_options'][o_wh_id] = option
|
||||
else:
|
||||
base_option['sub_options'][o_wh_id]['product_ids'] += option['product_ids']
|
||||
base_option['sub_options'][o_wh_id]['product_skus'] += option['product_skus']
|
||||
if base_option.get('date_planned'):
|
||||
if option['date_planned'] > base_option['sub_options'][o_wh_id]['date_planned']:
|
||||
base_option['sub_options'][o_wh_id]['date_planned'] = intermediate_option['date_planned']
|
||||
else:
|
||||
# intermediate_option has sub_options but base_option doesn't
|
||||
base_option['sub_options'] = {
|
||||
b_wh_id: {
|
||||
'product_ids': base_option['products'].ids,
|
||||
'product_skus': base_option['products'].mapped('default_code'),
|
||||
}
|
||||
}
|
||||
if base_option.get('date_planned'):
|
||||
base_option['sub_options'][b_wh_id]['date_planned'] = base_option['date_planned']
|
||||
for o_wh_id, option in intermediate_option['sub_options'].items():
|
||||
if o_wh_id not in base_option['sub_options']:
|
||||
base_option['sub_options'][o_wh_id] = option
|
||||
else:
|
||||
base_option['sub_options'][o_wh_id]['product_ids'] += option['product_ids']
|
||||
base_option['sub_options'][o_wh_id]['product_skus'] += option['product_skus']
|
||||
if base_option.get('date_planned'):
|
||||
if option['date_planned'] > base_option['sub_options'][o_wh_id]['date_planned']:
|
||||
base_option['sub_options'][o_wh_id]['date_planned'] = intermediate_option['date_planned']
|
||||
|
||||
del base_option['products']
|
||||
_logger.error(' returning: ' + str(base_option))
|
||||
order_fake.warehouse_id = self.get_warehouses(warehouse_id=base_option['warehouse_id'])
|
||||
return base_option
|
||||
|
||||
|
||||
|
||||
# Collapse by warehouse_id
|
||||
|
||||
|
||||
|
||||
|
||||
# warehouses = self.get_warehouses()
|
||||
# product_stock = self._fetch_product_stock(warehouses, products)
|
||||
# sub_options = {}
|
||||
# wh_date_planning = {}
|
||||
#
|
||||
# p_len = len(products)
|
||||
# full_candidates = set()
|
||||
# partial_candidates = set()
|
||||
# for wh_id, stock in product_stock.items():
|
||||
# available = sum(1 for p_id, p_vals in stock.items() if self._is_in_stock(p_vals, buy_qty[p_id]))
|
||||
# if available == p_len:
|
||||
# full_candidates.add(wh_id)
|
||||
# elif available > 0:
|
||||
# partial_candidates.add(wh_id)
|
||||
#
|
||||
# if full_candidates:
|
||||
# if len(full_candidates) == 1:
|
||||
# warehouse = warehouses.filtered(lambda wh: wh.id in full_candidates)
|
||||
# date_planned = self._next_warehouse_shipping_date(warehouse)
|
||||
# order_fake.warehouse_id = warehouse
|
||||
# return {'warehouse_id': warehouse.id, 'date_planned': date_planned}
|
||||
#
|
||||
# warehouse = self._find_closest_warehouse_by_partner(
|
||||
# warehouses.filtered(lambda wh: wh.id in full_candidates), order_fake.partner_shipping_id)
|
||||
# date_planned = self._next_warehouse_shipping_date(warehouse)
|
||||
# order_fake.warehouse_id = warehouse
|
||||
# return {'warehouse_id': warehouse.id, 'date_planned': date_planned}
|
||||
#
|
||||
# if partial_candidates:
|
||||
# if len(partial_candidates) == 1:
|
||||
# warehouse = warehouses.filtered(lambda wh: wh.id in partial_candidates)
|
||||
# order_fake.warehouse_id = warehouse
|
||||
# return {'warehouse_id': warehouse.id}
|
||||
#
|
||||
# sorted_warehouses = self._sort_warehouses_by_partner(warehouses.filtered(lambda wh: wh.id in partial_candidates), order_fake.partner_shipping_id)
|
||||
# primary_wh = sorted_warehouses[0] #partial_candidates means there is at least one warehouse
|
||||
# primary_wh_date_planned = self._next_warehouse_shipping_date(primary_wh)
|
||||
# wh_date_planning[primary_wh.id] = primary_wh_date_planned
|
||||
# for wh in sorted_warehouses:
|
||||
# if not buy_qty:
|
||||
# continue
|
||||
# stock = product_stock[wh.id]
|
||||
# for p_id, p_vals in stock.items():
|
||||
# if p_id in buy_qty and self._is_in_stock(p_vals, buy_qty[p_id]):
|
||||
# if wh.id not in sub_options:
|
||||
# sub_options[wh.id] = {
|
||||
# 'date_planned': self._next_warehouse_shipping_date(wh),
|
||||
# 'product_ids': [],
|
||||
# 'product_skus': [],
|
||||
# }
|
||||
# sub_options[wh.id]['product_ids'].append(p_id)
|
||||
# sub_options[wh.id]['product_skus'].append(p_vals['sku'])
|
||||
# del buy_qty[p_id]
|
||||
#
|
||||
# if not buy_qty:
|
||||
# # item_details can fulfil all items.
|
||||
# # this is good!!
|
||||
# order_fake.warehouse_id = primary_wh
|
||||
# return {'warehouse_id': primary_wh.id, 'date_planned': primary_wh_date_planned, 'sub_options': sub_options}
|
||||
#
|
||||
# # warehouses cannot fulfil all requested items!!
|
||||
# order_fake.warehouse_id = primary_wh
|
||||
# return {'warehouse_id': primary_wh.id}
|
||||
#
|
||||
# # nobody has stock!
|
||||
# primary_wh = self._find_closest_warehouse_by_partner(warehouses, order_fake.partner_shipping_id)
|
||||
# order_fake.warehouse_id = primary_wh
|
||||
# return {'warehouse_id': primary_wh.id}
|
||||
|
||||
def _is_in_stock(self, p_stock, buy_qty):
|
||||
return p_stock['real_qty_available'] >= buy_qty
|
||||
|
||||
@@ -391,9 +670,9 @@ class SaleOrderMakePlan(models.TransientModel):
|
||||
return [warehouses.filtered(lambda wh: wh.id == distances[d]) for d in wh_distances]
|
||||
|
||||
def _next_warehouse_shipping_date(self, warehouse):
|
||||
return fields.Datetime.to_string(warehouse.shipping_calendar_id.plan_days(0.01,
|
||||
fields.Datetime.from_string(fields.Datetime.now()),
|
||||
compute_leaves=True))
|
||||
if warehouse.shipping_calendar_id:
|
||||
return fields.Datetime.to_string(warehouse.shipping_calendar_id.plan_days(0.01, fields.Datetime.from_string(fields.Datetime.now()), compute_leaves=True))
|
||||
return False
|
||||
|
||||
@api.model
|
||||
def _fetch_product_stock(self, warehouses, products):
|
||||
@@ -414,7 +693,16 @@ class SaleOrderMakePlan(models.TransientModel):
|
||||
def generate_shipping_options(self, base_option, order_fake):
|
||||
# generate a carrier_id, amount, requested_date (promise date)
|
||||
# if base_option['carrier_id'] then that is the only carrier we want to collect rates for.
|
||||
carriers = self.get_shipping_carriers(base_option.get('carrier_id'))
|
||||
product_lines = list(filter(lambda line: line.product_id.type == 'product', order_fake.order_line))
|
||||
domain = []
|
||||
for line in product_lines:
|
||||
policy = line.product_id.product_tmpl_id.get_planning_policy()
|
||||
if policy and policy.carrier_filter_id:
|
||||
domain.extend(tools.safe_eval(policy.carrier_filter_id.domain))
|
||||
carriers = self.get_shipping_carriers(base_option.get('carrier_id'), domain=domain)
|
||||
|
||||
if not carriers:
|
||||
return base_option
|
||||
|
||||
if not base_option.get('sub_options'):
|
||||
options = []
|
||||
|
||||
Reference in New Issue
Block a user