[IMP] delivery_hibou: provide mechanisms for per-package carrier and tracking

This commit is contained in:
Jared Kipe
2021-09-20 17:41:42 -07:00
parent 514bdaab11
commit 5537f8a211
5 changed files with 209 additions and 5 deletions

View File

@@ -1,7 +1,7 @@
{
'name': 'Delivery Hibou',
'summary': 'Adds underlying pinnings for things like "RMA Return Labels"',
'version': '14.0.1.0.0',
'version': '14.0.1.1.0',
'author': "Hibou Corp.",
'category': 'Stock',
'license': 'AGPL-3',

View File

@@ -1,5 +1,6 @@
from odoo import fields, models
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
from odoo.exceptions import UserError
class DeliveryCarrier(models.Model):
@@ -161,11 +162,12 @@ class DeliveryCarrier(models.Model):
# -------------------------- #
# API for external providers #
# -------------------------- #
def rate_shipment_multi(self, order=None, picking=None):
def rate_shipment_multi(self, order=None, picking=None, packages=None):
''' Compute the price of the order shipment
:param order: record of sale.order or None
:param picking: record of stock.picking or None
:param packages: recordset of stock.quant.package or None (requires picking also set)
:return list: dict: {
'carrier': delivery.carrier(),
'success': boolean,
@@ -176,6 +178,7 @@ class DeliveryCarrier(models.Model):
'date_delivered': a datetime for when the shipment is supposed to arrive,
'transit_days': a Float for how many days it takes in transit,
'service_code': a string that represents the service level/agreement,
'package': stock.quant.package(),
}
e.g. self == delivery.carrier(5, 6)
@@ -190,12 +193,53 @@ class DeliveryCarrier(models.Model):
if picking:
self = self.with_context(date_planned=fields.Datetime.now())
if not packages:
packages = picking.package_ids
else:
if packages:
raise UserError('Cannot rate package without picking.')
self = self.with_context(date_planned=(order.date_planned or fields.Datetime.now()))
res = []
for carrier in self:
carrier_packages = packages.filtered(lambda p: not p.carrier_tracking_ref and
(not p.carrier_id or p.carrier_id == carrier) and
p.packaging_id.package_carrier_type in (False, '', 'none', carrier.delivery_type))
if packages and not carrier_packages:
continue
if hasattr(carrier, '%s_rate_shipment_multi' % self.delivery_type):
carrier_rates = getattr(carrier, '%s_rate_shipment_multi' % carrier.delivery_type)(order=order, picking=picking)
res += carrier_rates
try:
res += getattr(carrier, '%s_rate_shipment_multi' % carrier.delivery_type)(order=order,
picking=picking,
packages=carrier_packages)
except TypeError:
# TODO remove catch if after Odoo 14
# This is intended to find ones that don't support packages= kwarg
res += getattr(carrier, '%s_rate_shipment_multi' % carrier.delivery_type)(order=order,
picking=picking)
return res
def cancel_shipment(self, pickings, packages=None):
''' Cancel a shipment
:param pickings: A recordset of pickings
:param packages: Optional recordset of packages (should be for this carrier)
'''
self.ensure_one()
if hasattr(self, '%s_cancel_shipment' % self.delivery_type):
# No good way to tell if this method takes the kwarg for packages
if packages:
try:
return getattr(self, '%s_cancel_shipment' % self.delivery_type)(pickings, packages=packages)
except TypeError:
# we won't be able to cancel the packages properly
# here we will TRY to make a good call here where we put the package references into the picking
# and let the original mechanisms try to work here
tracking_ref = ','.join(packages.mapped('carrier_tracking_ref'))
pickings.write({
'carrier_id': self.id,
'carrier_tracking_ref': tracking_ref,
})
return getattr(self, '%s_cancel_shipment' % self.delivery_type)(pickings)

View File

@@ -1,4 +1,27 @@
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class StockQuantPackage(models.Model):
_inherit = 'stock.quant.package'
carrier_id = fields.Many2one('delivery.carrier', string='Carrier')
carrier_tracking_ref = fields.Char(string='Tracking Reference')
def _get_active_picking(self):
picking_id = self._context.get('active_id')
picking_model = self._context.get('active_model')
if not picking_id or picking_model != 'stock.picking':
raise UserError('Cannot cancel package other than through shipment/picking.')
return self.env['stock.picking'].browse(picking_id)
def send_to_shipper(self):
picking = self._get_active_picking()
picking.send_to_shipper(packages=self)
def cancel_shipment(self):
picking = self._get_active_picking()
picking.cancel_shipment(packages=self)
class StockPicking(models.Model):
@@ -11,6 +34,16 @@ class StockPicking(models.Model):
('no', 'No'),
], string='Require Insurance', default='auto',
help='If your carrier supports it, auto should be calculated off of the "Automatic Insurance Value" field.')
package_carrier_tracking_ref = fields.Char(string='Package Tracking Numbers', compute='_compute_package_carrier_tracking_ref')
@api.depends('package_ids.carrier_tracking_ref')
def _compute_package_carrier_tracking_ref(self):
for picking in self:
package_refs = picking.package_ids.filtered('carrier_tracking_ref').mapped('carrier_tracking_ref')
if package_refs:
picking.package_carrier_tracking_ref = ','.join(package_refs)
else:
picking.package_carrier_tracking_ref = False
@api.onchange('carrier_id')
def _onchange_carrier_id_for_priority(self):
@@ -41,3 +74,86 @@ class StockPicking(models.Model):
# 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
def clear_carrier_tracking_ref(self):
self.write({'carrier_tracking_ref': False})
def reset_carrier_tracking_ref(self):
for picking in self:
picking.carrier_tracking_ref = picking.package_carrier_tracking_ref
# Override to send to specific packaging carriers
def send_to_shipper(self, packages=None):
self.ensure_one()
if not packages:
packages = self.package_ids
package_carriers = packages.mapped('carrier_id')
if not package_carriers:
# Original behavior
return super().send_to_shipper()
tracking_numbers = []
carrier_prices = []
order_currency = self.sale_id.currency_id or self.company_id.currency_id
for carrier in package_carriers:
self.carrier_id = carrier
carrier_packages = packages.filtered(lambda p: p.carrier_id == carrier)
res = carrier.send_shipping(self)
if res:
res = res[0]
if carrier.free_over and self.sale_id and self.sale_id._compute_amount_total_without_delivery() >= carrier.amount:
res['exact_price'] = 0.0
carrier_price = res['exact_price'] * (1.0 + (self.carrier_id.margin / 100.0))
carrier_prices.append(carrier_price)
tracking_number = ''
if res['tracking_number']:
tracking_number = res['tracking_number']
tracking_numbers.append(tracking_number)
# Try to add tracking to the individual packages.
potential_tracking_numbers = tracking_number.split(',')
if len(potential_tracking_numbers) >= len(carrier_packages):
for t, p in zip(potential_tracking_numbers, carrier_packages):
p.carrier_tracking_ref = t
else:
carrier_packages.write({'carrier_tracking_ref': tracking_number})
msg = _(
"Shipment sent to carrier %(carrier_name)s for shipping with tracking number %(ref)s<br/>Cost: %(price).2f %(currency)s",
carrier_name=carrier.name,
ref=tracking_number,
price=carrier_price,
currency=order_currency.name
)
self.message_post(body=msg)
self.carrier_price = sum(carrier_prices or [0.0])
self.carrier_tracking_ref = ','.join(tracking_numbers or [''])
self._add_delivery_cost_to_so()
# Override to provide per-package versions...
def cancel_shipment(self, packages=None):
pickings_with_package_tracking = self.filtered(lambda p: p.package_carrier_tracking_ref)
for picking in pickings_with_package_tracking:
if packages:
current_packages = packages
else:
current_packages = picking.package_ids
# Packages without a carrier can just be cleared
packages_without_carrier = current_packages.filtered(lambda p: not p.carrier_id and p.carrier_tracking_ref)
packages_without_carrier.write({
'carrier_tracking_ref': False,
})
# Packages with carrier can use the carrier method
packages_with_carrier = current_packages.filtered(lambda p: p.carrier_id and p.carrier_tracking_ref)
carriers = packages_with_carrier.mapped('carrier_id')
for carrier in carriers:
carrier_packages = packages_with_carrier.filtered(lambda p: p.carrier_id == carrier)
carrier.cancel_shipment(self, packages=carrier_packages)
package_refs = ','.join(carrier_packages.mapped('carrier_tracking_ref'))
msg = "Shipment %s cancelled" % package_refs
picking.message_post(body=msg)
carrier_packages.write({'carrier_tracking_ref': False})
pickings_without_package_tracking = self - pickings_with_package_tracking
if pickings_without_package_tracking:
# use original on these
super(StockPicking, pickings_without_package_tracking).cancel_shipment()

View File

@@ -11,4 +11,16 @@
</xpath>
</field>
</record>
<record id="choose_delivery_package_view_form" model="ir.ui.view">
<field name="name">hibou.choose.delivery.package.form</field>
<field name="model">choose.delivery.package</field>
<field name="inherit_id" ref="delivery.choose_delivery_package_view_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='delivery_packaging_id']" position="attributes">
<attribute name="domain">[('package_carrier_type', '!=', False)]</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,14 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hibou_view_quant_package_form" model="ir.ui.view">
<field name="name">hibou.stock.quant.package.form</field>
<field name="model">stock.quant.package</field>
<field name="inherit_id" ref="stock.view_quant_package_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='location_id']" position="after">
<label for="carrier_id"/>
<div name="carrier">
<field name="carrier_id" class="oe_inline"/>
<button type="object" class="fa fa-arrow-right oe_link" name="send_to_shipper" string="Ship" attrs="{'invisible':['|',('carrier_tracking_ref','!=',False),('carrier_id','=', False)]}"/>
</div>
<label for="carrier_tracking_ref"/>
<div name="tracking">
<field name="carrier_tracking_ref" class="oe_inline" />
<button type="object" class="fa fa-arrow-right oe_link" name="cancel_shipment" string="Cancel" attrs="{'invisible':['|',('carrier_tracking_ref','=',False),('carrier_id','=', False)]}"/>
</div>
</xpath>
</field>
</record>
<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="priority" eval="200" />
<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'))]}"/>
<field name="package_carrier_tracking_ref" attrs="{'invisible': [('package_carrier_tracking_ref', '=', False)]}" />
<button name="clear_carrier_tracking_ref" type="object" string="Clear Tracking" attrs="{'invisible': [('carrier_tracking_ref', '!=', False)]}" />
<button name="reset_carrier_tracking_ref" type="object" string="Reset Tracking" attrs="{'invisible': [('package_carrier_tracking_ref', '!=', False)]}" />
<field name="package_ids" attrs="{'invisible': [('package_carrier_tracking_ref', '=', False)]}" context="{'active_id': id, 'active_model': 'stock.picking'}" nolabel="1" colspan="2">
<tree>
<field name="name" />
<field name="carrier_id" />
<field name="carrier_tracking_ref" />
<button type="object" name="cancel_shipment" string="Cancel" />
</tree>
</field>
</xpath>
</field>
</record>