diff --git a/delivery_hibou/__init__.py b/delivery_hibou/__init__.py
new file mode 100644
index 00000000..0650744f
--- /dev/null
+++ b/delivery_hibou/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/delivery_hibou/__manifest__.py b/delivery_hibou/__manifest__.py
new file mode 100644
index 00000000..e53e01b9
--- /dev/null
+++ b/delivery_hibou/__manifest__.py
@@ -0,0 +1,24 @@
+{
+ 'name': 'Delivery Hibou',
+ 'summary': 'Adds underlying pinnings for things like "RMA Return Labels"',
+ 'version': '11.0.1.0.0',
+ 'author': "Hibou Corp.",
+ 'category': 'Stock',
+ 'license': 'AGPL-3',
+ 'images': [],
+ 'website': "https://hibou.io",
+ 'description': """
+This is a collection of "typical" carrier needs, and a bridge into Hibou modules like `delivery_partner` and `sale_planner`.
+""",
+ 'depends': [
+ 'delivery',
+ 'delivery_partner',
+ ],
+ 'demo': [],
+ 'data': [
+ 'views/delivery_views.xml',
+ 'views/stock_views.xml',
+ ],
+ 'auto_install': False,
+ 'installable': True,
+}
diff --git a/delivery_hibou/models/__init__.py b/delivery_hibou/models/__init__.py
new file mode 100644
index 00000000..29ce1386
--- /dev/null
+++ b/delivery_hibou/models/__init__.py
@@ -0,0 +1,2 @@
+from . import delivery
+from . import stock
diff --git a/delivery_hibou/models/delivery.py b/delivery_hibou/models/delivery.py
new file mode 100644
index 00000000..d8b6d41b
--- /dev/null
+++ b/delivery_hibou/models/delivery.py
@@ -0,0 +1,152 @@
+from odoo import fields, models
+from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
+
+
+class DeliveryCarrier(models.Model):
+ _inherit = 'delivery.carrier'
+
+ automatic_insurance_value = fields.Float(string='Automatic Insurance Value',
+ help='Will be used during shipping to determine if the '
+ 'picking\'s value warrants insurance being added.')
+ procurement_priority = fields.Selection(PROCUREMENT_PRIORITIES,
+ string='Procurement Priority',
+ help='Priority for this carrier. Will affect pickings '
+ 'and procurements related to this carrier.')
+
+ def get_insurance_value(self, order=None, picking=None):
+ value = 0.0
+ if order:
+ if order.order_line:
+ value = sum(order.order_line.filtered(lambda l: l.type != 'service').mapped('price_subtotal'))
+ else:
+ return value
+ if picking:
+ value = picking.declared_value()
+ if picking.require_insurance == 'no':
+ value = 0.0
+ elif picking.require_insurance == 'auto' and self.automatic_insurance_value and self.automatic_insurance_value > value:
+ value = 0.0
+ return value
+
+ def get_third_party_account(self, order=None, picking=None):
+ if order and order.shipping_account_id:
+ return order.shipping_account_id
+ if picking and picking.shipping_account_id:
+ return picking.shipping_account_id
+ return None
+
+ def get_order_name(self, order=None, picking=None):
+ if order:
+ return order.name
+ if picking:
+ if picking.sale_id:
+ return picking.sale_id.name # + ' - ' + picking.name
+ return picking.name
+ return ''
+
+ def get_attn(self, order=None, picking=None):
+ if order:
+ return order.client_order_ref
+ if picking and picking.sale_id:
+ return picking.sale_id.client_order_ref
+ # If Picking has a reference, decide what it is.
+ return False
+
+ def _classify_picking(self, picking):
+ if picking.picking_type_id.code == 'incoming' and picking.location_id.usage == 'supplier' and picking.location_dest_id.usage == 'customer':
+ return 'dropship'
+ elif picking.picking_type_id.code == 'incoming' and picking.location_id.usage == 'customer' and picking.location_dest_id.usage == 'supplier':
+ return 'dropship_in'
+ elif picking.picking_type_id.code == 'incoming':
+ return 'in'
+ return 'out'
+
+ # Shipper Company
+
+ def get_shipper_company(self, order=None, picking=None):
+ """
+ Shipper Company: The `res.partner` that provides the name of where the shipment is coming from.
+ """
+ if order:
+ return order.company_id.partner_id
+ if picking:
+ return getattr(self, ('_get_shipper_company_%s' % (self._classify_picking(picking),)),
+ self._get_shipper_company_out)(picking)
+ return None
+
+ def _get_shipper_company_dropship(self, picking):
+ return picking.company_id.partner_id
+
+ def _get_shipper_company_dropship_in(self, picking):
+ return picking.company_id.partner_id
+
+ def _get_shipper_company_in(self, picking):
+ return picking.company_id.partner_id
+
+ def _get_shipper_company_out(self, picking):
+ return picking.company_id.partner_id
+
+ # Shipper Warehouse
+
+ def get_shipper_warehouse(self, order=None, picking=None):
+ """
+ Shipper Warehouse: The `res.partner` that is basically the physical address a shipment is coming from.
+ """
+ if order:
+ return order.warehouse_id.partner_id
+ if picking:
+ return getattr(self, ('_get_shipper_warehouse_%s' % (self._classify_picking(picking),)),
+ self._get_shipper_warehouse_out)(picking)
+ return None
+
+ def _get_shipper_warehouse_dropship(self, picking):
+ return picking.partner_id
+
+ def _get_shipper_warehouse_dropship_in(self, picking):
+ if picking.sale_id:
+ picking.sale_id.partner_shipping_id
+ return self._get_shipper_warehouse_dropship_in_no_sale(picking)
+
+ def _get_shipper_warehouse_dropship_in_no_sale(self, picking):
+ return picking.company_id.partner_id
+
+ def _get_shipper_warehouse_in(self, picking):
+ return picking.partner_id
+
+ def _get_shipper_warehouse_out(self, picking):
+ return picking.picking_type_id.warehouse_id.partner_id
+
+ # Recipient
+
+ def get_recipient(self, order=None, picking=None):
+ """
+ Recipient: The `res.partner` receiving the shipment.
+ """
+ if order:
+ return order.partner_shipping_id
+ if picking:
+ return getattr(self, ('_get_recipient_%s' % (self._classify_picking(picking),)),
+ self._get_recipient_out)(picking)
+ return None
+
+ def _get_recipient_dropship(self, picking):
+ if picking.sale_id:
+ return picking.sale_id.partner_shipping_id
+ return picking.sale_id.partner_shipping_id
+
+ def _get_recipient_dropship_no_sale(self, picking):
+ return picking.company_id.partner_id
+
+ def _get_recipient_dropship_in(self, picking):
+ return picking.picking_type_id.warehouse_id.partner_id
+
+ def _get_recipient_in(self, picking):
+ return picking.picking_type_id.warehouse_id.partner_id
+
+ def _get_recipient_out(self, picking):
+ return picking.partner_id
+
+
+
+
+
diff --git a/delivery_hibou/models/stock.py b/delivery_hibou/models/stock.py
new file mode 100644
index 00000000..c1a6a792
--- /dev/null
+++ b/delivery_hibou/models/stock.py
@@ -0,0 +1,50 @@
+from odoo import api, fields, models
+
+
+class StockPicking(models.Model):
+ _inherit = 'stock.picking'
+
+ shipping_account_id = fields.Many2one('partner.shipping.account', string='Shipping Account')
+ require_insurance = fields.Selection([
+ ('auto', 'Automatic'),
+ ('yes', 'Yes'),
+ ('no', 'No'),
+ ], string='Require Insurance', default='auto',
+ help='If your carrier supports it, auto should be calculated off of the "Automatic Insurance Value" field.')
+
+ @api.one
+ @api.depends('move_lines.priority', 'carrier_id')
+ def _compute_priority(self):
+ if self.carrier_id.procurement_priority:
+ self.priority = self.carrier_id.procurement_priority
+ else:
+ super(StockPicking, self)._compute_priority()
+
+ @api.model
+ def create(self, values):
+ origin = values.get('origin')
+ if origin and not values.get('shipping_account_id'):
+ so = self.env['sale.order'].search([('name', '=', str(origin))], limit=1)
+ if so and so.shipping_account_id:
+ values['shipping_account_id'] = so.shipping_account_id.id
+
+ res = super(StockPicking, self).create(values)
+ return res
+
+ def declared_value(self):
+ self.ensure_one()
+ cost = sum([(l.product_id.standard_price * l.qty_done) for l in self.move_line_ids] or [0.0])
+ if not cost:
+ # Assume Full Value
+ cost = sum([(l.product_id.standard_price * l.product_uom_qty) for l in self.move_lines] or [0.0])
+ return cost
+
+
+
+class StockMove(models.Model):
+ _inherit = 'stock.move'
+
+ def _prepare_procurement_values(self):
+ res = super(StockMove, self)._prepare_procurement_values()
+ res['priority'] = self.picking_id.priority or self.priority
+ return res
diff --git a/delivery_hibou/tests/__init__.py b/delivery_hibou/tests/__init__.py
new file mode 100644
index 00000000..d4e6373e
--- /dev/null
+++ b/delivery_hibou/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_delivery_hibou
diff --git a/delivery_hibou/tests/test_delivery_hibou.py b/delivery_hibou/tests/test_delivery_hibou.py
new file mode 100644
index 00000000..68866f45
--- /dev/null
+++ b/delivery_hibou/tests/test_delivery_hibou.py
@@ -0,0 +1,160 @@
+from odoo.tests import common
+
+
+class TestDeliveryHibou(common.TransactionCase):
+
+ def setUp(self):
+ super(TestDeliveryHibou, self).setUp()
+ self.partner = self.env.ref('base.res_partner_address_13')
+ self.product = self.env.ref('product.product_product_7')
+ # Create Shipping Account
+ self.shipping_account = self.env['partner.shipping.account'].create({
+ 'name': '123123',
+ 'delivery_type': 'other',
+ })
+ # Create Carrier
+ self.delivery_product = self.env['product.product'].create({
+ 'name': 'Test Carrier1 Delivery',
+ 'type': 'service',
+ })
+ self.carrier = self.env['delivery.carrier'].create({
+ 'name': 'Test Carrier1',
+ 'product_id': self.delivery_product.id,
+ })
+
+ def test_delivery_hibou(self):
+ # Assign a new shipping account
+ self.partner.shipping_account_id = self.shipping_account
+
+ # Assign values to new Carrier
+ test_insurance_value = 600
+ test_procurement_priority = '2'
+ self.carrier.automatic_insurance_value = test_insurance_value
+ self.carrier.procurement_priority = test_procurement_priority
+
+
+ sale_order = self.env['sale.order'].create({
+ 'partner_id': self.partner.id,
+ 'partner_shipping_id': self.partner.id,
+ 'partner_invoice_id': self.partner.id,
+ 'carrier_id': self.carrier.id,
+ 'shipping_account_id': self.shipping_account.id,
+ 'order_line': [(0, 0, {
+ 'product_id': self.product.id,
+ })]
+ })
+ sale_order.get_delivery_price()
+ sale_order.set_delivery_line()
+ sale_order.action_confirm()
+ # Make sure 3rd party Shipping Account is set.
+ self.assertEqual(sale_order.shipping_account_id, self.shipping_account)
+
+ self.assertTrue(sale_order.picking_ids)
+ # Priority coming from Carrier procurement_priority
+ self.assertEqual(sale_order.picking_ids.priority, test_procurement_priority)
+ # 3rd party Shipping Account copied from Sale Order
+ self.assertEqual(sale_order.picking_ids.shipping_account_id, self.shipping_account)
+ self.assertEqual(sale_order.carrier_id.get_third_party_account(order=sale_order), self.shipping_account)
+
+ # Test attn
+ test_ref = 'TEST100'
+ self.assertEqual(sale_order.carrier_id.get_attn(order=sale_order), False)
+ sale_order.client_order_ref = test_ref
+ self.assertEqual(sale_order.carrier_id.get_attn(order=sale_order), test_ref)
+ # The picking should get this ref as well
+ self.assertEqual(sale_order.picking_ids.carrier_id.get_attn(picking=sale_order.picking_ids), test_ref)
+
+ # Test order_name
+ self.assertEqual(sale_order.carrier_id.get_order_name(order=sale_order), sale_order.name)
+ # The picking should get the same 'order_name'
+ self.assertEqual(sale_order.picking_ids.carrier_id.get_order_name(picking=sale_order.picking_ids), sale_order.name)
+
+ def test_carrier_hibou_out(self):
+ test_insurance_value = 4000
+ self.carrier.automatic_insurance_value = test_insurance_value
+
+ picking_out = self.env.ref('stock.outgoing_shipment_main_warehouse')
+ self.assertEqual(picking_out.state, 'assigned')
+
+ picking_out.carrier_id = self.carrier
+
+ # This relies heavily on the 'stock' demo data.
+ # Should only have a single move_line_ids and it should not be done at all.
+ self.assertEqual(picking_out.move_line_ids.mapped('qty_done'), [0.0])
+ self.assertEqual(picking_out.move_line_ids.mapped('product_uom_qty'), [15.0])
+ self.assertEqual(picking_out.move_line_ids.mapped('product_id.standard_price'), [3300.0])
+
+ # The 'value' is assumed to be all of the product value from the initial demand.
+ self.assertEqual(picking_out.declared_value(), 15.0 * 3300.0)
+ self.assertEqual(picking_out.carrier_id.get_insurance_value(picking=picking_out), picking_out.declared_value())
+
+ # Workflow where user explicitly opts out of insurance on the picking level.
+ picking_out.require_insurance = 'no'
+ self.assertEqual(picking_out.carrier_id.get_insurance_value(picking=picking_out), 0.0)
+ picking_out.require_insurance = 'auto'
+
+ # Lets choose to only delivery one piece at the moment.
+ # This does not meet the minimum on the carrier to have insurance value.
+ picking_out.move_line_ids.qty_done = 1.0
+ self.assertEqual(picking_out.declared_value(), 3300.0)
+ self.assertEqual(picking_out.carrier_id.get_insurance_value(picking=picking_out), 0.0)
+ # Workflow where user opts in to insurance.
+ picking_out.require_insurance = 'yes'
+ self.assertEqual(picking_out.carrier_id.get_insurance_value(picking=picking_out), 3300.0)
+ picking_out.require_insurance = 'auto'
+
+ # Test with picking having 3rd party account.
+ self.assertEqual(picking_out.carrier_id.get_third_party_account(picking=picking_out), None)
+ picking_out.shipping_account_id = self.shipping_account
+ self.assertEqual(picking_out.carrier_id.get_third_party_account(picking=picking_out), self.shipping_account)
+
+ # Shipment Time Methods!
+ self.assertEqual(picking_out.carrier_id._classify_picking(picking=picking_out), 'out')
+ self.assertEqual(picking_out.carrier_id.get_shipper_company(picking=picking_out),
+ picking_out.company_id.partner_id)
+ self.assertEqual(picking_out.carrier_id.get_shipper_warehouse(picking=picking_out),
+ picking_out.picking_type_id.warehouse_id.partner_id)
+ self.assertEqual(picking_out.carrier_id.get_recipient(picking=picking_out),
+ picking_out.partner_id)
+ # This picking has no `sale_id`
+ # Right now ATTN requires a sale_id, which this picking doesn't have (none of the stock ones do)
+ self.assertEqual(picking_out.carrier_id.get_attn(picking=picking_out), False)
+ self.assertEqual(picking_out.carrier_id.get_order_name(picking=picking_out), picking_out.name)
+
+ def test_carrier_hibou_in(self):
+ picking_in = self.env.ref('stock.incomming_shipment1')
+ self.assertEqual(picking_in.state, 'assigned')
+
+ picking_in.carrier_id = self.carrier
+ # This relies heavily on the 'stock' demo data.
+ # Should only have a single move_line_ids and it should not be done at all.
+ self.assertEqual(picking_in.move_line_ids.mapped('qty_done'), [0.0])
+ self.assertEqual(picking_in.move_line_ids.mapped('product_uom_qty'), [35.0])
+ self.assertEqual(picking_in.move_line_ids.mapped('product_id.standard_price'), [55.0])
+
+ self.assertEqual(picking_in.carrier_id._classify_picking(picking=picking_in), 'in')
+ self.assertEqual(picking_in.carrier_id.get_shipper_company(picking=picking_in),
+ picking_in.company_id.partner_id)
+ self.assertEqual(picking_in.carrier_id.get_shipper_warehouse(picking=picking_in),
+ picking_in.partner_id)
+ self.assertEqual(picking_in.carrier_id.get_recipient(picking=picking_in),
+ picking_in.picking_type_id.warehouse_id.partner_id)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/delivery_hibou/views/delivery_views.xml b/delivery_hibou/views/delivery_views.xml
new file mode 100644
index 00000000..01208bbd
--- /dev/null
+++ b/delivery_hibou/views/delivery_views.xml
@@ -0,0 +1,14 @@
+
+
+
+ hibou.delivery.carrier.form
+ delivery.carrier
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/delivery_hibou/views/stock_views.xml b/delivery_hibou/views/stock_views.xml
new file mode 100644
index 00000000..78067b01
--- /dev/null
+++ b/delivery_hibou/views/stock_views.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ hibou.delivery.stock.picking_withcarrier.form.view
+ stock.picking
+
+
+
+
+
+
+
+
+
+
diff --git a/delivery_stamps/models/delivery_stamps.py b/delivery_stamps/models/delivery_stamps.py
index 29cd80c7..e653e294 100644
--- a/delivery_stamps/models/delivery_stamps.py
+++ b/delivery_stamps/models/delivery_stamps.py
@@ -193,6 +193,30 @@ class ProviderStamps(models.Model):
res = res + [(0.0, 0, None)]
return res
+ def stamps_rate_shipment(self, order):
+ self.ensure_one()
+ result = {
+ 'success': False,
+ 'price': 0.0,
+ 'error_message': 'Error Retrieving Response from Stamps.com',
+ 'warning_message': False
+ }
+ date_planned = None
+ if self.env.context.get('date_planned'):
+ date_planned = self.env.context.get('date_planned')
+ rate = self.stamps_get_shipping_price_for_plan(order, date_planned)
+ if rate:
+ price, transit_time, date_delivered = rate[0]
+ result.update({
+ 'success': True,
+ 'price': price,
+ 'error_message': False,
+ 'transit_time': transit_time,
+ 'date_delivered': date_delivered,
+ })
+ return result
+ return result
+
def stamps_send_shipping(self, pickings):
res = []
service = self._get_stamps_service()
diff --git a/sale_planner/models/delivery.py b/sale_planner/models/delivery.py
index 47219b76..17a5eb25 100644
--- a/sale_planner/models/delivery.py
+++ b/sale_planner/models/delivery.py
@@ -25,6 +25,36 @@ class DeliveryCarrier(models.Model):
if hasattr(self, '%s_get_shipping_price_for_plan' % self.delivery_type):
return getattr(self, '%s_get_shipping_price_for_plan' % self.delivery_type)(orders, date_planned)
+ def rate_shipment_date_planned(self, order, date_planned=None):
+ """
+ For every sale order, compute the price of the shipment and potentially when it will arrive.
+ :param order: `sale.order`
+ :param date_planned: The date the shipment is expected to leave/be picked up by carrier.
+ :return: rate in the same form the normal `rate_shipment` method does BUT
+ -- Additional keys
+ - transit_days: int
+ - date_delivered: string
+ """
+ self.ensure_one()
+ if hasattr(self, '%s_rate_shipment_date_planned' % self.delivery_type):
+ # New API Odoo 11 - Carrier specific override.
+ return getattr(self, '%s_rate_shipment_date_planned' % self.delivery_type)(order, date_planned)
+
+ rate = self.with_context(date_planned=date_planned).rate_shipment(order)
+ if rate and date_planned:
+ if rate.get('date_delivered'):
+ date_delivered = rate['date_delivered']
+ transit_days = self.calculate_transit_days(date_planned, date_delivered)
+ if not rate.get('transit_days') or transit_days < rate.get('transit_days'):
+ rate['transit_days'] = transit_days
+ elif rate.get('transit_days'):
+ rate['date_delivered'] = self.calculate_date_delivered(date_planned, rate.get('transit_days'))
+ elif rate:
+ if rate.get('date_delivered'):
+ rate.pop('date_delivered')
+
+ return rate
+
def calculate_transit_days(self, date_planned, date_delivered):
self.ensure_one()
if isinstance(date_planned, str):
@@ -36,11 +66,14 @@ class DeliveryCarrier(models.Model):
while date_planned < date_delivered:
if transit_days > 10:
break
- interval = self.delivery_calendar_id.plan_days(1, date_planned, compute_leaves=True)
- if not interval:
+ current_date_planned = self.delivery_calendar_id.plan_days(1, date_planned, compute_leaves=True)
+ if not current_date_planned:
return self._calculate_transit_days_naive(date_planned, date_delivered)
- date_planned = interval[0][1]
+ if current_date_planned == date_planned:
+ date_planned += timedelta(days=1)
+ else:
+ date_planned = current_date_planned
transit_days += 1
if transit_days > 1:
@@ -59,11 +92,11 @@ class DeliveryCarrier(models.Model):
# date calculations needs an extra day
effective_transit_days = transit_days + 1
- interval = self.delivery_calendar_id.plan_days(effective_transit_days, date_planned, compute_leaves=True)
- if not interval:
+ last_day = self.delivery_calendar_id.plan_days(effective_transit_days, date_planned, compute_leaves=True)
+ if not last_day:
return self._calculate_date_delivered_naive(date_planned, transit_days)
- return fields.Datetime.to_string(interval[-1][1])
+ return fields.Datetime.to_string(last_day)
def _calculate_date_delivered_naive(self, date_planned, transit_days):
return fields.Datetime.to_string(date_planned + timedelta(days=transit_days))
diff --git a/sale_planner/tests/test_planner.py b/sale_planner/tests/test_planner.py
index 49696bc0..35dad49c 100644
--- a/sale_planner/tests/test_planner.py
+++ b/sale_planner/tests/test_planner.py
@@ -384,7 +384,7 @@ class TestPlanner(common.TransactionCase):
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.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)
diff --git a/sale_planner/wizard/order_planner.py b/sale_planner/wizard/order_planner.py
index 6d9fe64d..470f1135 100644
--- a/sale_planner/wizard/order_planner.py
+++ b/sale_planner/wizard/order_planner.py
@@ -51,7 +51,6 @@ class FakePartner():
self.partner_longitude = 0.0
self.is_company = False
for attr, value in kwargs.items():
- _logger.warn(' ' + str(attr) + ': ' + str(value))
setattr(self, attr, value)
@property
@@ -220,6 +219,8 @@ class SaleOrderMakePlan(models.TransientModel):
planner = super(SaleOrderMakePlan, self).create(values)
for option_vals in self.generate_order_options(planner.order_id):
+ if type(option_vals) != dict:
+ continue
option_vals['plan_id'] = planner.id
planner.planning_option_ids |= self.env['sale.order.planning.option'].create(option_vals)
@@ -570,82 +571,6 @@ class SaleOrderMakePlan(models.TransientModel):
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
@@ -700,6 +625,7 @@ class SaleOrderMakePlan(models.TransientModel):
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)
+ _logger.info('generate_shipping_options:: base_optoin: ' + str(base_option) + ' order_fake: ' + str(order_fake) + ' carriers: ' + str(carriers))
if not carriers:
return base_option
@@ -711,8 +637,9 @@ class SaleOrderMakePlan(models.TransientModel):
option = self._generate_shipping_carrier_option(base_option, order_fake, carrier)
if option:
options.append(option)
-
- return options
+ if options:
+ return options
+ return [base_option]
else:
warehouses = self.get_warehouses()
original_order_fake_warehouse_id = order_fake.warehouse_id
@@ -776,14 +703,33 @@ class SaleOrderMakePlan(models.TransientModel):
# this logic comes from "delivery.models.sale_order.SaleOrder"
try:
+ result = None
date_delivered = None
transit_days = 0
if carrier.delivery_type not in ['fixed', 'base_on_rule']:
- result = carrier.get_shipping_price_for_plan(order_fake, base_option.get('date_planned'))
- if result:
+ if hasattr(carrier, 'rate_shipment_date_planned'):
+ # New API
+ result = carrier.rate_shipment_date_planned(order_fake, base_option.get('date_planned'))
+ if result:
+ if not result.get('success'):
+ return None
+ price_unit, transit_days, date_delivered = result['price'], result.get('transit_days'), result.get('date_delivered')
+ elif hasattr(carrier, 'get_shipping_price_for_plan'):
+ # Old API
+ result = carrier.get_shipping_price_for_plan(order_fake, base_option.get('date_planned'))
+ if result and isinstance(result, list):
price_unit, transit_days, date_delivered = result[0]
- else:
- price_unit = carrier.get_shipping_price_from_so(order_fake)[0]
+ elif not result:
+ rate = carrier.rate_shipment(order_fake)
+ if rate and rate.get('success'):
+ price_unit = rate['price']
+ if rate.get('transit_days'):
+ transit_days = rate.get('transit_days')
+ if rate.get('date_delivered'):
+ date_delivered = rate.get('date_delivered')
+ else:
+ _logger.warn('returning None because carrier: ' + str(carrier))
+ return None
else:
carrier = carrier.verify_carrier(order_fake.partner_shipping_id)
if not carrier:
@@ -802,13 +748,13 @@ class SaleOrderMakePlan(models.TransientModel):
option = deepcopy(base_option)
option['carrier_id'] = carrier.id
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'] = fields.Datetime.to_string(date_delivered) if (date_delivered and isinstance(date_delivered, datetime)) else date_delivered
option['transit_days'] = transit_days
return option
except Exception as e:
- # _logger.warn("Exception collecting carrier rates: " + str(e))
+ _logger.info("Exception collecting carrier rates: " + str(e))
+ # Want to see more?
# _logger.exception(e)
- pass
return None