mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
MIG sale_planner to 12.0
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
'name': 'Sale Order Planner',
|
'name': 'Sale Order Planner',
|
||||||
'summary': 'Plans order dates and warehouses.',
|
'summary': 'Plans order dates and warehouses.',
|
||||||
'version': '11.0.1.0.0',
|
'version': '12.0.1.0.0',
|
||||||
'author': "Hibou Corp.",
|
'author': "Hibou Corp.",
|
||||||
'category': 'Sale',
|
'category': 'Sale',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
@@ -23,7 +23,6 @@ on the specific method's characteristics. (e.g. Do they deliver on Saturday?)
|
|||||||
|
|
||||||
""",
|
""",
|
||||||
'depends': [
|
'depends': [
|
||||||
'sale_order_dates',
|
|
||||||
'sale_sourced_by_line',
|
'sale_sourced_by_line',
|
||||||
'base_geolocalize',
|
'base_geolocalize',
|
||||||
'delivery',
|
'delivery',
|
||||||
@@ -32,6 +31,7 @@ on the specific method's characteristics. (e.g. Do they deliver on Saturday?)
|
|||||||
],
|
],
|
||||||
'demo': [],
|
'demo': [],
|
||||||
'data': [
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
'wizard/order_planner_views.xml',
|
'wizard/order_planner_views.xml',
|
||||||
'views/sale.xml',
|
'views/sale.xml',
|
||||||
'views/stock.xml',
|
'views/stock.xml',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
from . import delivery
|
||||||
|
from . import partner
|
||||||
|
from . import planning
|
||||||
|
from . import product
|
||||||
|
from . import resource
|
||||||
from . import sale
|
from . import sale
|
||||||
from . import stock
|
from . import stock
|
||||||
from . import delivery
|
|
||||||
from . import product
|
|
||||||
from . import planning
|
|
||||||
from . import partner
|
|
||||||
|
|||||||
47
sale_planner/models/resource.py
Normal file
47
sale_planner/models/resource.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from functools import partial
|
||||||
|
from datetime import timedelta
|
||||||
|
from odoo import api, models
|
||||||
|
from odoo.addons.resource.models.resource import make_aware
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceCalendar(models.Model):
|
||||||
|
_inherit = 'resource.calendar'
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def plan_days_end(self, days, day_dt, compute_leaves=False, domain=None):
|
||||||
|
"""
|
||||||
|
Override to `plan_days` that allows you to get the nearest 'end' including today.
|
||||||
|
"""
|
||||||
|
day_dt, revert = make_aware(day_dt)
|
||||||
|
|
||||||
|
# which method to use for retrieving intervals
|
||||||
|
if compute_leaves:
|
||||||
|
get_intervals = partial(self._work_intervals, domain=domain)
|
||||||
|
else:
|
||||||
|
get_intervals = self._attendance_intervals
|
||||||
|
|
||||||
|
if days >= 0:
|
||||||
|
found = set()
|
||||||
|
delta = timedelta(days=14)
|
||||||
|
for n in range(100):
|
||||||
|
dt = day_dt + delta * n
|
||||||
|
for start, stop, meta in get_intervals(dt, dt + delta):
|
||||||
|
found.add(start.date())
|
||||||
|
if len(found) >= days:
|
||||||
|
return revert(stop)
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif days < 0:
|
||||||
|
days = abs(days)
|
||||||
|
found = set()
|
||||||
|
delta = timedelta(days=14)
|
||||||
|
for n in range(100):
|
||||||
|
dt = day_dt - delta * n
|
||||||
|
for start, stop, meta in reversed(get_intervals(dt - delta, dt)):
|
||||||
|
found.add(start.date())
|
||||||
|
if len(found) == days:
|
||||||
|
return revert(start)
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
return revert(day_dt)
|
||||||
2
sale_planner/security/ir.model.access.csv
Normal file
2
sale_planner/security/ir.model.access.csv
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_sale_order_planning_policy,access_sale_order_planning_policy,model_sale_order_planning_policy,base.group_user,1,1,1,1
|
||||||
|
@@ -48,26 +48,38 @@ class TestPlanner(common.TransactionCase):
|
|||||||
'partner_latitude': 40.51525,
|
'partner_latitude': 40.51525,
|
||||||
'partner_longitude': -107.54645,
|
'partner_longitude': -107.54645,
|
||||||
})
|
})
|
||||||
|
hour_from = (self.today.hour - 1) % 24
|
||||||
|
hour_to = (self.today.hour + 1) % 24
|
||||||
|
if hour_to < hour_from:
|
||||||
|
hour_to, hour_from = hour_from, hour_to
|
||||||
|
|
||||||
self.warehouse_calendar_1 = self.env['resource.calendar'].create({
|
self.warehouse_calendar_1 = self.env['resource.calendar'].create({
|
||||||
'name': 'Washington Warehouse Hours',
|
'name': 'Washington Warehouse Hours',
|
||||||
|
'tz': 'UTC',
|
||||||
'attendance_ids': [
|
'attendance_ids': [
|
||||||
(0, 0, {'name': 'today',
|
(0, 0, {'name': 'today',
|
||||||
'dayofweek': str(self.today.weekday()),
|
'dayofweek': str(self.today.weekday()),
|
||||||
'hour_from': (self.today.hour - 1) % 24,
|
'hour_from': hour_from,
|
||||||
'hour_to': (self.today.hour + 1) % 24}),
|
'hour_to': hour_to,
|
||||||
|
'day_period': 'morning'}),
|
||||||
|
|
||||||
(0, 0, {'name': 'tomorrow',
|
(0, 0, {'name': 'tomorrow',
|
||||||
'dayofweek': str(self.tomorrow.weekday()),
|
'dayofweek': str(self.tomorrow.weekday()),
|
||||||
'hour_from': (self.tomorrow.hour - 1) % 24,
|
'hour_from': hour_from,
|
||||||
'hour_to': (self.tomorrow.hour + 1) % 24}),
|
'hour_to': hour_to,
|
||||||
|
'day_period': 'morning'}),
|
||||||
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
self.warehouse_calendar_2 = self.env['resource.calendar'].create({
|
self.warehouse_calendar_2 = self.env['resource.calendar'].create({
|
||||||
'name': 'Colorado Warehouse Hours',
|
'name': 'Colorado Warehouse Hours',
|
||||||
|
'tz': 'UTC',
|
||||||
'attendance_ids': [
|
'attendance_ids': [
|
||||||
(0, 0, {'name': 'tomorrow',
|
(0, 0, {'name': 'tomorrow',
|
||||||
'dayofweek': str(self.tomorrow.weekday()),
|
'dayofweek': str(self.tomorrow.weekday()),
|
||||||
'hour_from': (self.tomorrow.hour - 1) % 24,
|
'hour_from': hour_from,
|
||||||
'hour_to': (self.tomorrow.hour + 1) % 24}),
|
'hour_to': hour_to,
|
||||||
|
'day_period': 'morning'}),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
self.warehouse_1 = self.env['stock.warehouse'].create({
|
self.warehouse_1 = self.env['stock.warehouse'].create({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from math import sin, cos, sqrt, atan2, radians
|
from math import sin, cos, sqrt, atan2, radians
|
||||||
from json import dumps, loads
|
from json import dumps, loads
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
@@ -299,7 +299,6 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
return Carrier.search(domain)
|
return Carrier.search(domain)
|
||||||
|
|
||||||
def _generate_base_option(self, order_fake, policy_group):
|
def _generate_base_option(self, order_fake, policy_group):
|
||||||
policy = False
|
|
||||||
flag_force_closest = False
|
flag_force_closest = False
|
||||||
warehouse_domain = False
|
warehouse_domain = False
|
||||||
if 'policy' in policy_group:
|
if 'policy' in policy_group:
|
||||||
@@ -382,11 +381,11 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
|
|
||||||
# nobody has stock!
|
# nobody has stock!
|
||||||
primary_wh = self._find_closest_warehouse_by_partner(warehouses, order_fake.partner_shipping_id)
|
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}
|
return {'warehouse_id': primary_wh.id}
|
||||||
|
|
||||||
def generate_base_option(self, order_fake):
|
def generate_base_option(self, order_fake):
|
||||||
_logger.error('generate_base_option:')
|
_logger.error('generate_base_option:')
|
||||||
|
__start_date = datetime.now() - timedelta(days=-30)
|
||||||
product_lines = list(filter(lambda line: line.product_id.type == 'product', order_fake.order_line))
|
product_lines = list(filter(lambda line: line.product_id.type == 'product', order_fake.order_line))
|
||||||
if not product_lines:
|
if not product_lines:
|
||||||
return {}
|
return {}
|
||||||
@@ -410,7 +409,6 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
policy_groups[0]['products'].append(p)
|
policy_groups[0]['products'].append(p)
|
||||||
policy_groups[0]['buy_qty'][p.id] = buy_qty[p.id]
|
policy_groups[0]['buy_qty'][p.id] = buy_qty[p.id]
|
||||||
|
|
||||||
|
|
||||||
for _, policy_group in policy_groups.items():
|
for _, policy_group in policy_groups.items():
|
||||||
product_set = self.env['product.product'].browse()
|
product_set = self.env['product.product'].browse()
|
||||||
for p in policy_group['products']:
|
for p in policy_group['products']:
|
||||||
@@ -418,8 +416,7 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
policy_group['products'] = product_set
|
policy_group['products'] = product_set
|
||||||
policy_group['base_option'] = self._generate_base_option(order_fake, policy_group)
|
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': __start_date, 'sub_options': [],})
|
||||||
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():
|
for policy_id, policy_group in policy_groups.items():
|
||||||
base_option = policy_group['base_option']
|
base_option = policy_group['base_option']
|
||||||
_logger.error(' base_option: ' + str(base_option))
|
_logger.error(' base_option: ' + str(base_option))
|
||||||
@@ -444,7 +441,7 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
if not option_group['sub_options']:
|
if not option_group['sub_options']:
|
||||||
del option_group['sub_options']
|
del option_group['sub_options']
|
||||||
else:
|
else:
|
||||||
sub_options = defaultdict(lambda: {'date_planned': '1900', 'product_ids': [], 'product_skus': []})
|
sub_options = defaultdict(lambda: {'date_planned': __start_date, 'product_ids': [], 'product_skus': []})
|
||||||
remaining_products = option_group['products']
|
remaining_products = option_group['products']
|
||||||
for options in option_group['sub_options']:
|
for options in option_group['sub_options']:
|
||||||
for wh_id, option in options.items():
|
for wh_id, option in options.items():
|
||||||
@@ -466,7 +463,7 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
|
|
||||||
# At this point we should have all of the policy options collapsed.
|
# At this point we should have all of the policy options collapsed.
|
||||||
# Collapse warehouse options.
|
# Collapse warehouse options.
|
||||||
base_option = {'date_planned': '1900', 'products': self.env['product.product'].browse()}
|
base_option = {'date_planned': __start_date, 'products': self.env['product.product'].browse()}
|
||||||
for wh_id, intermediate_option in option_policy_groups.items():
|
for wh_id, intermediate_option in option_policy_groups.items():
|
||||||
_logger.error(' base_option: ' + str(base_option))
|
_logger.error(' base_option: ' + str(base_option))
|
||||||
_logger.error(' intermediate_option: ' + str(intermediate_option))
|
_logger.error(' intermediate_option: ' + str(intermediate_option))
|
||||||
@@ -596,7 +593,7 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
|
|
||||||
def _next_warehouse_shipping_date(self, warehouse):
|
def _next_warehouse_shipping_date(self, warehouse):
|
||||||
if warehouse.shipping_calendar_id:
|
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 warehouse.shipping_calendar_id.plan_days_end(0, fields.Datetime.now(), compute_leaves=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -683,15 +680,11 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
max_requested_date = None
|
max_requested_date = None
|
||||||
for option in sub_options.values():
|
for option in sub_options.values():
|
||||||
requested_date = option.get('requested_date')
|
requested_date = option.get('requested_date')
|
||||||
if requested_date and isinstance(requested_date, str):
|
|
||||||
requested_date = fields.Datetime.from_string(requested_date)
|
|
||||||
if requested_date and not max_requested_date:
|
if requested_date and not max_requested_date:
|
||||||
max_requested_date = requested_date
|
max_requested_date = requested_date
|
||||||
elif requested_date:
|
elif requested_date:
|
||||||
if requested_date > max_requested_date:
|
if requested_date > max_requested_date:
|
||||||
max_requested_date = requested_date
|
max_requested_date = requested_date
|
||||||
if max_requested_date:
|
|
||||||
return fields.Datetime.to_string(max_requested_date)
|
|
||||||
return max_requested_date
|
return max_requested_date
|
||||||
|
|
||||||
def _get_max_transit_days(self, sub_options):
|
def _get_max_transit_days(self, sub_options):
|
||||||
@@ -748,7 +741,7 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
option = deepcopy(base_option)
|
option = deepcopy(base_option)
|
||||||
option['carrier_id'] = carrier.id
|
option['carrier_id'] = carrier.id
|
||||||
option['shipping_price'] = final_price
|
option['shipping_price'] = final_price
|
||||||
option['requested_date'] = fields.Datetime.to_string(date_delivered) if (date_delivered and isinstance(date_delivered, datetime)) else date_delivered
|
option['requested_date'] = date_delivered
|
||||||
option['transit_days'] = transit_days
|
option['transit_days'] = transit_days
|
||||||
return option
|
return option
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -759,13 +752,15 @@ class SaleOrderMakePlan(models.TransientModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderPlanningOption(models.TransientModel):
|
class SaleOrderPlanningOption(models.TransientModel):
|
||||||
_name = 'sale.order.planning.option'
|
_name = 'sale.order.planning.option'
|
||||||
_description = 'Order Planning Option'
|
_description = 'Order Planning Option'
|
||||||
|
|
||||||
def create(self, values):
|
def create(self, values):
|
||||||
if 'sub_options' in values and not isinstance(values['sub_options'], str):
|
if 'sub_options' in values and not isinstance(values['sub_options'], str):
|
||||||
|
for wh_id, option in values['sub_options'].items():
|
||||||
|
if option.get('date_planned'):
|
||||||
|
option['date_planned'] = str(option['date_planned'])
|
||||||
values['sub_options'] = dumps(values['sub_options'])
|
values['sub_options'] = dumps(values['sub_options'])
|
||||||
return super(SaleOrderPlanningOption, self).create(values)
|
return super(SaleOrderPlanningOption, self).create(values)
|
||||||
|
|
||||||
@@ -782,13 +777,9 @@ class SaleOrderPlanningOption(models.TransientModel):
|
|||||||
line = ''
|
line = ''
|
||||||
for wh_id, wh_option in sub_options.items():
|
for wh_id, wh_option in sub_options.items():
|
||||||
product_skus = wh_option.get('product_skus', [])
|
product_skus = wh_option.get('product_skus', [])
|
||||||
|
date_planned = wh_option.get('date_planned')
|
||||||
product_skus = ', '.join(product_skus)
|
product_skus = ', '.join(product_skus)
|
||||||
requested_date = wh_option.get('requested_date', '')
|
requested_date = wh_option.get('requested_date', '')
|
||||||
if requested_date:
|
|
||||||
requested_date = self._context_datetime(requested_date)
|
|
||||||
date_planned = wh_option.get('date_planned', '')
|
|
||||||
if date_planned:
|
|
||||||
date_planned = self._context_datetime(date_planned)
|
|
||||||
shipping_price = float(wh_option.get('shipping_price', 0.0))
|
shipping_price = float(wh_option.get('shipping_price', 0.0))
|
||||||
transit_days = int(wh_option.get('transit_days', 0))
|
transit_days = int(wh_option.get('transit_days', 0))
|
||||||
|
|
||||||
@@ -817,8 +808,3 @@ class SaleOrderPlanningOption(models.TransientModel):
|
|||||||
for option in self:
|
for option in self:
|
||||||
option.plan_id.select_option(option)
|
option.plan_id.select_option(option)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _context_datetime(self, date):
|
|
||||||
date = fields.Datetime.from_string(date)
|
|
||||||
date = fields.Datetime.context_timestamp(self, date)
|
|
||||||
return fields.Datetime.to_string(date)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user