diff --git a/sale_planner_delivery_route/README.rst b/sale_planner_delivery_route/README.rst new file mode 100644 index 00000000..5d6c252c --- /dev/null +++ b/sale_planner_delivery_route/README.rst @@ -0,0 +1,15 @@ +************************************************** +Hibou - Sale Planner with Warehouse Delivery Route +************************************************** + +Calculates the closest delivery route during order planning. + +For more information and add-ons, visit `Hibou.io `_. + + +======= +License +======= + +Please see `LICENSE `_. +Copyright Hibou Corp. 2018 \ No newline at end of file diff --git a/sale_planner_delivery_route/__init__.py b/sale_planner_delivery_route/__init__.py new file mode 100644 index 00000000..134df274 --- /dev/null +++ b/sale_planner_delivery_route/__init__.py @@ -0,0 +1,2 @@ +from . import wizard +from . import models diff --git a/sale_planner_delivery_route/__manifest__.py b/sale_planner_delivery_route/__manifest__.py new file mode 100644 index 00000000..41a57804 --- /dev/null +++ b/sale_planner_delivery_route/__manifest__.py @@ -0,0 +1,29 @@ +{ + 'name': 'Sale Order Planner - Delivery Route', + 'summary': 'Plans to the closest delivery route.', + 'version': '11.0.1.0.0', + 'author': "Hibou Corp.", + 'category': 'Sale', + 'license': 'AGPL-3', + 'complexity': 'expert', + 'images': [], + 'website': "https://hibou.io", + 'description': """ +Sale Order Planner - Delivery Route +=================================== + +Plans to the closest delivery route. + +""", + 'depends': [ + 'stock_delivery_route', + 'sale_planner', + ], + 'demo': [], + 'data': [ + 'wizard/order_planner_views.xml', + 'views/stock_views.xml', + ], + 'auto_install': True, + 'installable': True, +} diff --git a/sale_planner_delivery_route/models/__init__.py b/sale_planner_delivery_route/models/__init__.py new file mode 100644 index 00000000..12bab770 --- /dev/null +++ b/sale_planner_delivery_route/models/__init__.py @@ -0,0 +1 @@ +from . import stock diff --git a/sale_planner_delivery_route/models/stock.py b/sale_planner_delivery_route/models/stock.py new file mode 100644 index 00000000..50d6212c --- /dev/null +++ b/sale_planner_delivery_route/models/stock.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class DeliveryRoute(models.Model): + _inherit = 'stock.warehouse.delivery.route' + + latitude = fields.Float(string='Latitude', digits=(12, 6)) + longitude = fields.Float(string='Longitude', digits=(12, 6)) diff --git a/sale_planner_delivery_route/tests/__init__.py b/sale_planner_delivery_route/tests/__init__.py new file mode 100644 index 00000000..25366b57 --- /dev/null +++ b/sale_planner_delivery_route/tests/__init__.py @@ -0,0 +1 @@ +from . import test_planner diff --git a/sale_planner_delivery_route/tests/test_planner.py b/sale_planner_delivery_route/tests/test_planner.py new file mode 100644 index 00000000..246f865a --- /dev/null +++ b/sale_planner_delivery_route/tests/test_planner.py @@ -0,0 +1,34 @@ +from odoo.addons.sale_planner.tests.test_planner import TestPlanner + + +class TestPlannerRoute(TestPlanner): + def setUp(self): + super(TestPlannerRoute, self).setUp() + self.route_near = self.env['stock.warehouse.delivery.route'].create({ + 'name': 'Route 1', + 'warehouse_id': self.warehouse_1.id, + 'latitude': 48.02995, + 'longitude': -122.14771, + }) + self.route_far = self.env['stock.warehouse.delivery.route'].create({ + 'name': 'Route Far', + 'warehouse_id': self.warehouse_1.id, + 'latitude': 47.82093, + 'longitude': -122.31513, + }) + + def test_planner_creation(self): + 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.assertTrue(planner.planning_option_ids, 'Must have one or more plans.') + self.assertEqual(planner.planning_option_ids.delivery_route_id, self.route_near) + self.so.partner_id.partner_latitude = 47.82093 + 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(planner.planning_option_ids.delivery_route_id, self.route_far) diff --git a/sale_planner_delivery_route/views/stock_views.xml b/sale_planner_delivery_route/views/stock_views.xml new file mode 100644 index 00000000..8fe6a528 --- /dev/null +++ b/sale_planner_delivery_route/views/stock_views.xml @@ -0,0 +1,18 @@ + + + + stock.warehouse.inherit + stock.warehouse + + + + + + + + + + + + + \ No newline at end of file diff --git a/sale_planner_delivery_route/wizard/__init__.py b/sale_planner_delivery_route/wizard/__init__.py new file mode 100644 index 00000000..235b12a8 --- /dev/null +++ b/sale_planner_delivery_route/wizard/__init__.py @@ -0,0 +1 @@ +from . import order_planner diff --git a/sale_planner_delivery_route/wizard/order_planner.py b/sale_planner_delivery_route/wizard/order_planner.py new file mode 100644 index 00000000..9bd34833 --- /dev/null +++ b/sale_planner_delivery_route/wizard/order_planner.py @@ -0,0 +1,38 @@ +from odoo import api, fields, models +from odoo.addons.sale_planner.wizard.order_planner import distance + + +class SaleOrderMakePlan(models.TransientModel): + _inherit = 'sale.order.make.plan' + + def generate_base_option(self, order_fake): + option = super(SaleOrderMakePlan, self).generate_base_option(order_fake) + option['delivery_route_id'] = self.find_closest_route(option, order_fake) + return option + + def find_closest_route(self, option, order_fake): + warehouse = self.env['stock.warehouse'].browse(option['warehouse_id']) + if warehouse.delivery_route_ids: + partner = order_fake.partner_shipping_id + if not partner.date_localization: + partner.geo_localize() + return self._find_closest_route_id(warehouse.delivery_route_ids, + partner.partner_latitude, + partner.partner_longitude) + return False + + def _find_closest_route_id(self, routes, latitude, longitude): + distances = {distance(latitude, longitude, route.latitude, route.longitude): route.id for route in routes} + route_id = distances[min(distances)] + return route_id + + def _order_fields_for_option(self, option): + vals = super(SaleOrderMakePlan, self)._order_fields_for_option(option) + vals['delivery_route_id'] = option.delivery_route_id.id + return vals + + +class SaleOrderPlanningOption(models.TransientModel): + _inherit = 'sale.order.planning.option' + + delivery_route_id = fields.Many2one('stock.warehouse.delivery.route', string='Delivery Route') diff --git a/sale_planner_delivery_route/wizard/order_planner_views.xml b/sale_planner_delivery_route/wizard/order_planner_views.xml new file mode 100644 index 00000000..e394a02e --- /dev/null +++ b/sale_planner_delivery_route/wizard/order_planner_views.xml @@ -0,0 +1,13 @@ + + + + view.plan.sale.order.inherit + sale.order.make.plan + + + + + + + + \ No newline at end of file