mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Initial commit of delivery_hibou and associated refactoring to sale_planner and delivery_stamps for 11.0
This commit is contained in:
1
delivery_hibou/__init__.py
Normal file
1
delivery_hibou/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
24
delivery_hibou/__manifest__.py
Normal file
24
delivery_hibou/__manifest__.py
Normal file
@@ -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,
|
||||
}
|
||||
2
delivery_hibou/models/__init__.py
Normal file
2
delivery_hibou/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import delivery
|
||||
from . import stock
|
||||
152
delivery_hibou/models/delivery.py
Normal file
152
delivery_hibou/models/delivery.py
Normal file
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
50
delivery_hibou/models/stock.py
Normal file
50
delivery_hibou/models/stock.py
Normal file
@@ -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
|
||||
1
delivery_hibou/tests/__init__.py
Normal file
1
delivery_hibou/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_delivery_hibou
|
||||
160
delivery_hibou/tests/test_delivery_hibou.py
Normal file
160
delivery_hibou/tests/test_delivery_hibou.py
Normal file
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
14
delivery_hibou/views/delivery_views.xml
Normal file
14
delivery_hibou/views/delivery_views.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="view_delivery_carrier_form" model="ir.ui.view">
|
||||
<field name="name">hibou.delivery.carrier.form</field>
|
||||
<field name="model">delivery.carrier</field>
|
||||
<field name="inherit_id" ref="delivery.view_delivery_carrier_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='integration_level']" position="after">
|
||||
<field name="automatic_insurance_value"/>
|
||||
<field name="procurement_priority"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
16
delivery_hibou/views/stock_views.xml
Normal file
16
delivery_hibou/views/stock_views.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_picking_withcarrier_out_form" model="ir.ui.view">
|
||||
<field name="name">hibou.delivery.stock.picking_withcarrier.form.view</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="delivery.view_picking_withcarrier_out_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='carrier_id']" position="before">
|
||||
<field name="require_insurance" attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}"/>
|
||||
<field name="shipping_account_id" attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user