Merge branch 'mig/16.0/delivery_hibou' into '16.0-test'

mig/16.0/delivery_hibou into 16.0-test

See merge request hibou-io/hibou-odoo/suite!1585
This commit is contained in:
Hibou Bot
2022-10-28 21:04:40 +00:00
10 changed files with 942 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,25 @@
{
'name': 'Delivery Hibou',
'summary': 'Adds underlying pinnings for things like "RMA Return Labels"',
'version': '16.0.1.1.0',
'author': "Hibou Corp.",
'category': 'Stock',
'license': 'LGPL-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': [
'hibou_professional',
'delivery',
'delivery_partner',
],
'demo': [],
'data': [
'views/delivery_views.xml',
'views/stock_views.xml',
],
'auto_install': False,
'installable': True,
}

163
delivery_hibou/i18n/es.po Normal file
View File

@@ -0,0 +1,163 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * delivery_hibou
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-29 23:24+0000\n"
"PO-Revision-Date: 2021-10-29 23:24+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: delivery_hibou
#: model:ir.model.fields.selection,name:delivery_hibou.selection__stock_picking__require_insurance__auto
msgid "Automatic"
msgstr "Automático"
#. module: delivery_hibou
#: model:ir.model.fields,field_description:delivery_hibou.field_delivery_carrier__automatic_insurance_value
msgid "Automatic Insurance Value"
msgstr "Valor de Seguro Automático"
#. module: delivery_hibou
#: model_terms:ir.ui.view,arch_db:delivery_hibou.hibou_view_quant_package_form
#: model_terms:ir.ui.view,arch_db:delivery_hibou.view_picking_withcarrier_out_form
msgid "Cancel"
msgstr "Cancelar"
#. module: delivery_hibou
#: code:addons/delivery_hibou/models/stock.py:0
#, python-format
msgid "Cannot cancel package other than through shipment/picking."
msgstr ""
"No se puede cancelar el paquete de otra manera que no sea a través del "
"envío/picking."
#. module: delivery_hibou
#: code:addons/delivery_hibou/models/delivery.py:0
#, python-format
msgid "Cannot rate package without picking."
msgstr "No se puede calificar el paquete sin picking"
#. module: delivery_hibou
#: model:ir.model.fields,field_description:delivery_hibou.field_stock_quant_package__carrier_id
msgid "Carrier"
msgstr "Transportista"
#. module: delivery_hibou
#: model_terms:ir.ui.view,arch_db:delivery_hibou.view_picking_withcarrier_out_form
msgid "Clear Tracking"
msgstr "Vaciar Seguimiento"
#. module: delivery_hibou
#: model:ir.model.fields,help:delivery_hibou.field_stock_picking__require_insurance
msgid ""
"If your carrier supports it, auto should be calculated off of the "
"\"Automatic Insurance Value\" field."
msgstr ""
"Si su transportista lo permite, auto debe ser calculado en base del campo "
"\"Valor de Seguro Automático\""
#. module: delivery_hibou
#: model:ir.model.fields.selection,name:delivery_hibou.selection__stock_picking__require_insurance__no
msgid "No"
msgstr "No"
#. module: delivery_hibou
#: model:ir.model.fields.selection,name:delivery_hibou.selection__delivery_carrier__procurement_priority__0
msgid "Normal"
msgstr "Normal"
#. module: delivery_hibou
#: model:ir.model.fields,field_description:delivery_hibou.field_stock_picking__package_carrier_tracking_ref
msgid "Package Tracking Numbers"
msgstr "Números de Seguimiento del Paquete"
#. module: delivery_hibou
#: model:ir.model,name:delivery_hibou.model_stock_quant_package
msgid "Packages"
msgstr "Paquetes"
#. module: delivery_hibou
#: model:ir.model.fields,help:delivery_hibou.field_delivery_carrier__procurement_priority
msgid ""
"Priority for this carrier. Will affect pickings and procurements related to "
"this carrier."
msgstr ""
"Prioridad para este transportista. Afectará las operaciones de Picking y "
"adquisiciones del transportista"
#. module: delivery_hibou
#: model:ir.model.fields,field_description:delivery_hibou.field_delivery_carrier__procurement_priority
msgid "Procurement Priority"
msgstr "Prioridad de Adquisición"
#. module: delivery_hibou
#: model:ir.model.fields,field_description:delivery_hibou.field_stock_picking__require_insurance
msgid "Require Insurance"
msgstr "Requiere Seguro"
#. module: delivery_hibou
#: model_terms:ir.ui.view,arch_db:delivery_hibou.view_picking_withcarrier_out_form
msgid "Reset Tracking"
msgstr "Restablecer Seguimiento"
#. module: delivery_hibou
#: model_terms:ir.ui.view,arch_db:delivery_hibou.hibou_view_quant_package_form
msgid "Ship"
msgstr "Enviar"
#. module: delivery_hibou
#: code:addons/delivery_hibou/models/stock.py:0
#, python-format
msgid ""
"Shipment sent to carrier %(carrier_name)s for shipping with tracking number "
"%(ref)s<br/>Cost: %(price).2f %(currency)s"
msgstr ""
"Cargamento enviado al transportista %(carrier_name)s para enviar con número de seguimiento"
"%(ref)s<br/>Costo: %(price).2f %(currency)s
#. module: delivery_hibou
#: model:ir.model.fields,field_description:delivery_hibou.field_stock_picking__shipping_account_id
msgid "Shipping Account"
msgstr "Cuenta de Envío"
#. module: delivery_hibou
#: model:ir.model,name:delivery_hibou.model_delivery_carrier
msgid "Shipping Methods"
msgstr "Métodos de Envío"
#. module: delivery_hibou
#: model:ir.model.fields,field_description:delivery_hibou.field_stock_quant_package__carrier_tracking_ref
msgid "Tracking Reference"
msgstr "Referencia de Seguimiento"
#. module: delivery_hibou
#: model:ir.model,name:delivery_hibou.model_stock_picking
msgid "Transfer"
msgstr "Transferir"
#. module: delivery_hibou
#: model:ir.model.fields.selection,name:delivery_hibou.selection__delivery_carrier__procurement_priority__1
msgid "Urgent"
msgstr "Urgente"
#. module: delivery_hibou
#: model:ir.model.fields,help:delivery_hibou.field_delivery_carrier__automatic_insurance_value
msgid ""
"Will be used during shipping to determine if the picking's value warrants "
"insurance being added."
msgstr ""
"Será utilizado durante el envío para determinar si la operación de Picking "
"requiere agregar seguro"
#. module: delivery_hibou
#: model:ir.model.fields.selection,name:delivery_hibou.selection__stock_picking__require_insurance__yes
msgid "Yes"
msgstr "Sí"

View File

@@ -0,0 +1,2 @@
from . import delivery
from . import stock

View File

@@ -0,0 +1,337 @@
from odoo import api, fields, models, _
from odoo.tools.float_utils import float_compare
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
from odoo.exceptions import UserError
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.')
automatic_sig_req_value = fields.Float(string='Automatic Signature Required Value',
help='Will be used during shipping to determine if the '
'picking\'s value warrants signature required 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.')
# Utility
def get_insurance_value(self, order=None, picking=None, package=None):
value = 0.0
if order:
if order.order_line:
value = sum(order.order_line.filtered(lambda l: l.product_id.type != 'service').mapped('price_subtotal'))
else:
return value
if picking:
value = picking.declared_value(package=package)
if package and not package.require_insurance:
value = 0.0
else:
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_signature_required(self, order=None, picking=None, package=None):
value = 0.0
if order:
if order.order_line:
value = sum(order.order_line.filtered(lambda l: l.product_id.type != 'service').mapped('price_subtotal'))
else:
return False
if picking:
value = picking.declared_value(package=package)
if package:
return package.require_signature
else:
if picking.require_signature == 'no':
return False
elif picking.require_signature == 'yes':
return True
return self.automatic_sig_req_value and value >= self.automatic_sig_req_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'
def is_amazon(self, order=None, picking=None):
"""
Amazon MWS orders potentially need to be flagged for
clean up on the carrier's side.
Override to return based on criteria in your company.
:return:
"""
return False
# 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.partner_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
# -------------------------- #
# API for external providers #
# -------------------------- #
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,
'price': a float,
'error_message': a string containing an error message,
'warning_message': a string containing a warning message,
'date_planned': a datetime for when the shipment is supposed to leave,
'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)
then return might be:
[
{'carrier': delivery.carrier(5), 'price': 10.50, 'service_code': 'GROUND_HOME_DELIVERY', ...},
{'carrier': delivery.carrier(7), 'price': 12.99, 'service_code': 'FEDEX_EXPRESS_SAVER', ...}, # NEW!
{'carrier': delivery.carrier(6), 'price': 8.0, 'service_code': 'USPS_PRI', ...},
]
'''
self.ensure_one()
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 and packages.filtered(lambda p: not p.carrier_tracking_ref and
(not p.carrier_id or p.carrier_id == carrier) and
p.package_type_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):
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)
class ChooseDeliveryPackage(models.TransientModel):
_inherit = 'choose.delivery.package'
package_declared_value = fields.Float(string='Declared Value')
package_require_insurance = fields.Boolean(string='Require Insurance')
package_require_signature = fields.Boolean(string='Require Signature')
@api.model
def default_get(self, fields_list):
defaults = super().default_get(fields_list)
if 'package_declared_value' in fields_list:
picking = self.env['stock.picking'].browse(defaults.get('picking_id'))
move_line_ids = picking.move_line_ids.filtered(lambda m:
float_compare(m.qty_done, 0.0, precision_rounding=m.product_uom_id.rounding) > 0
and not m.result_package_id
)
total_value = 0.0
for ml in move_line_ids:
qty = ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
total_value += qty * ml.product_id.standard_price
defaults['package_declared_value'] = total_value
return defaults
@api.onchange('package_declared_value')
def _onchange_package_declared_value(self):
picking = self.picking_id
value = self.package_declared_value
if picking.require_insurance == 'auto':
self.package_require_insurance = value and picking.carrier_id.automatic_insurance_value and value >= picking.carrier_id.automatic_insurance_value
else:
self.package_require_insurance = picking.require_insurance == 'yes'
if picking.require_signature == 'auto':
self.package_require_signature = value and picking.carrier_id.automatic_sig_req_value and value >= picking.carrier_id.automatic_sig_req_value
else:
self.package_require_signature = picking.require_signature == 'yes'
def action_put_in_pack(self):
# Copied because `delivery_package` is not retained by reference or returned...
picking_move_lines = self.picking_id.move_line_ids
if not self.picking_id.picking_type_id.show_reserved and not self.env.context.get('barcode_view'):
picking_move_lines = self.picking_id.move_line_nosuggest_ids
move_line_ids = picking_move_lines.filtered(lambda ml:
float_compare(ml.qty_done, 0.0,
precision_rounding=ml.product_uom_id.rounding) > 0
and not ml.result_package_id
)
if not move_line_ids:
move_line_ids = picking_move_lines.filtered(lambda ml: float_compare(ml.reserved_uom_qty, 0.0,
precision_rounding=ml.product_uom_id.rounding) > 0 and float_compare(
ml.qty_done, 0.0,
precision_rounding=ml.product_uom_id.rounding) == 0)
delivery_package = self.picking_id._put_in_pack(move_line_ids)
# write shipping weight and package type on 'stock_quant_package' if needed
if self.delivery_package_type_id:
delivery_package.package_type_id = self.delivery_package_type_id
if self.shipping_weight:
delivery_package.shipping_weight = self.shipping_weight
# Hibou : Fill additional fields.
delivery_package.write({
'declared_value': self.package_declared_value,
'require_insurance': self.package_require_insurance,
'require_signature': self.package_require_signature,
})

View File

@@ -0,0 +1,176 @@
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')
require_insurance = fields.Boolean(string='Require Insurance')
require_signature = fields.Boolean(string='Require Signature')
declared_value = fields.Float(string='Declared Value')
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.with_context(packages=self).send_to_shipper()
def cancel_shipment(self):
picking = self._get_active_picking()
picking.with_context(packages=self).cancel_shipment()
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.')
require_signature = fields.Selection([
('auto', 'Automatic'),
('yes', 'Yes'),
('no', 'No'),
], string='Require Signature', default='auto',
help='If your carrier supports it, auto should be calculated off of the "Automatic Signature Required Value" field.')
package_carrier_tracking_ref = fields.Char(string='Package Tracking Numbers', compute='_compute_package_carrier_tracking_ref')
commercial_partner_id = fields.Many2one('res.partner', related='partner_id.commercial_partner_id')
@api.depends('move_line_ids.package_id.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):
for picking in self:
if picking.carrier_id and picking.carrier_id.procurement_priority:
picking.priority = picking.carrier_id.procurement_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
carrier_id = values.get('carrier_id')
if carrier_id:
carrier = self.env['delivery.carrier'].browse(carrier_id)
if carrier.procurement_priority:
values['priority'] = carrier.procurement_priority
res = super(StockPicking, self).create(values)
return res
def declared_value(self, package=None):
self.ensure_one()
if package:
return package.declared_value
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_ids] 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 = self._context.get('packages')
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) == 1:
potential_tracking_numbers = tracking_number.split('+') # UPS for example...
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 = self._context.get('packages')
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)
# Above cancel should also say which are cancelled in chatter.
# 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

@@ -0,0 +1 @@
from . import test_delivery_hibou

View File

@@ -0,0 +1,150 @@
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_ids = self.shipping_account
# Assign values to new Carrier
test_insurance_value = 600
test_procurement_priority = '1'
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,
'shipping_account_id': self.shipping_account.id,
'order_line': [(0, 0, {
'product_id': self.product.id,
})]
})
self.assertFalse(sale_order.carrier_id)
action = sale_order.action_open_delivery_wizard()
form = common.Form(self.env[action['res_model']].with_context(**action.get('context', {})))
form.carrier_id = self.carrier
wizard = form.save()
wizard.button_confirm()
#sale_order.set_delivery_line()
self.assertEqual(sale_order.carrier_id, self.carrier)
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 = 1000
self.carrier.automatic_insurance_value = test_insurance_value
picking_out = self.env.ref('stock.outgoing_shipment_main_warehouse')
picking_out.action_assign()
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('reserved_uom_qty'), [15.0])
self.assertEqual(picking_out.move_line_ids.mapped('product_id.standard_price'), [100.0])
test_whole_value = 15.0 * 100.0
test_one_value = 100.0
# The 'value' is assumed to be all of the product value from the initial demand.
self.assertEqual(picking_out.declared_value(), test_whole_value)
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(), 100.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), test_one_value)
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('reserved_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)

View File

@@ -0,0 +1,32 @@
<?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="automatic_sig_req_value"/>
<field name="procurement_priority"/>
</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_package_type_id']" position="attributes">
<attribute name="domain">[]</attribute>
</xpath>
<xpath expr="//field[@name='delivery_package_type_id']" position="after">
<field name="package_declared_value" />
<field name="package_require_insurance" />
<field name="package_require_signature" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,55 @@
<?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>
<field name="declared_value" />
<field name="require_insurance" />
<field name="require_signature" />
</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="commercial_partner_id" invisible="1" />
<field name="require_insurance" attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}"/>
<field name="require_signature" attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}"/>
<field name="shipping_account_id" attrs="{'readonly': [('state', 'in', ('done', 'cancel'))]}"
options="{'no_create': True, 'no_open': True}"
domain="[('partner_id', 'in', (False, partner_id, commercial_partner_id))]"/>
<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>
</odoo>