562 lines
30 KiB
Python
562 lines
30 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import base64
|
|
from markupsafe import Markup
|
|
import json
|
|
from json import JSONDecodeError
|
|
import pytz
|
|
from datetime import timedelta
|
|
|
|
from odoo import fields, models, _
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools.float_utils import float_round, json_float_round
|
|
from odoo.tools.misc import groupby
|
|
|
|
from .dhl_request import DHLProvider
|
|
|
|
HELP_EXTRA_DATA = """Extra data to be sent in the request. It should be JSON-formatted.
|
|
This functionality is advanced/technical and should only be used if you know what you are doing.
|
|
|
|
Example of a valid value: ```
|
|
"content": {"packages": {"description": "amazing package"}}
|
|
```
|
|
|
|
With the above example, the description of each package will be updated.
|
|
For more information, please refer to the DHL API documentation: https://developer.dhl.com/api-reference/dhl-express-mydhl-api.
|
|
"""
|
|
|
|
LABEL_FORMAT_MAPPING = {
|
|
'8X4_A4_PDF': 'ECOM26_84_A4_001',
|
|
'8X4_thermal': 'ECOM26_84_001',
|
|
'8X4_A4_TC_PDF': 'ECOM_TC_A4',
|
|
'6X4_thermal': 'ECOM26_A6_002',
|
|
'6X4_A4_PDF': 'ECOM26_A6_002',
|
|
'8X4_CI_PDF': 'ECOM26_84CI_001',
|
|
'8X4_CI_thermal': 'ECOM26_84CI_001',
|
|
'8X4_RU_A4_PDF': 'ECOM_A4_RU_002',
|
|
'6X4_PDF': 'ECOM26_A6_002',
|
|
'8X4_PDF': 'ECOM26_84_001',
|
|
}
|
|
|
|
|
|
class ProviderDHL(models.Model):
|
|
_inherit = 'delivery.carrier'
|
|
|
|
delivery_type = fields.Selection(selection_add=[
|
|
('dhl_rest', "DHL")
|
|
], ondelete={'dhl_rest': lambda recs: recs.write({'delivery_type': 'fixed', 'fixed_price': 0})})
|
|
dhl_api_key = fields.Char(string="DHL API Key", groups="base.group_system")
|
|
dhl_api_secret = fields.Char(string="DHL API Secret", groups="base.group_system")
|
|
dhl_account_number = fields.Char(string="DHL Account Number", groups="base.group_system")
|
|
dhl_unit_system = fields.Selection([('imperial', 'Imperial'),
|
|
('metric', 'Metric')],
|
|
default='metric',
|
|
string='Unit System')
|
|
dhl_default_package_type_id = fields.Many2one('stock.package.type', string='DHLxw Package Type')
|
|
dhl_region_code = fields.Selection([('AP', 'Asia Pacific'),
|
|
('AM', 'America'),
|
|
('EU', 'Europe')],
|
|
default='AM',
|
|
string='Region')
|
|
# Nowadays hidden, by default it's the D, couldn't find any documentation on other services
|
|
dhl_product_code = fields.Selection([('0', '0 - Logistics Services'),
|
|
('1', '1 - Domestic Express 12:00'),
|
|
('2', '2 - B2C'),
|
|
('3', '3 - B2C'),
|
|
('4', '4 - Jetline'),
|
|
('5', '5 - Sprintline'),
|
|
('6', '6 - Secureline'),
|
|
('7', '7 - Express Easy'),
|
|
('8', '8 - Express Easy'),
|
|
('9', '9 - Europack'),
|
|
('A', 'A - Auto Reversals'),
|
|
('B', 'B - Break Bulk Express'),
|
|
('C', 'C - Medical Express'),
|
|
('D', 'D - Express Worldwide'),
|
|
('E', 'E - Express 9:00'),
|
|
('F', 'F - Freight Worldwide'),
|
|
('G', 'G - Domestic Economy Select'),
|
|
('H', 'H - Economy Select'),
|
|
('I', 'I - Break Bulk Economy'),
|
|
('J', 'J - Jumbo Box'),
|
|
('K', 'K - Express 9:00'),
|
|
('L', 'L - Express 10:30'),
|
|
('M', 'M - Express 10:30'),
|
|
('N', 'N - Domestic Express'),
|
|
('O', 'O - DOM Express 10:30'),
|
|
('P', 'P - Express Worldwide'),
|
|
('Q', 'Q - Medical Express'),
|
|
('R', 'R - GlobalMail Business'),
|
|
('S', 'S - Same Day'),
|
|
('T', 'T - Express 12:00'),
|
|
('U', 'U - Express Worldwide'),
|
|
('V', 'V - Europack'),
|
|
('W', 'W - Economy Select'),
|
|
('X', 'X - Express Envelope'),
|
|
('Y', 'Y - Express 12:00'),
|
|
('Z', 'Z - Destination Charges'),
|
|
],
|
|
default='D',
|
|
string='DHL Product')
|
|
dhl_dutiable = fields.Boolean(string="Dutiable Material", help="Check this if your package is dutiable.")
|
|
dhl_duty_payment = fields.Selection([('S', 'Sender'), ('R', 'Recipient')], required=True, default="S")
|
|
dhl_label_image_format = fields.Selection([('EPL2', 'EPL2'),
|
|
('PDF', 'PDF'),
|
|
('ZPL2', 'ZPL2')], string="Label Image Format", default='PDF')
|
|
dhl_label_template = fields.Selection([('8X4_A4_PDF', '8X4_A4_PDF'),
|
|
('8X4_thermal', '8X4_thermal'),
|
|
('8X4_A4_TC_PDF', '8X4_A4_TC_PDF'),
|
|
('6X4_thermal', '6X4_thermal'),
|
|
('6X4_A4_PDF', '6X4_A4_PDF'),
|
|
('8X4_CI_PDF', '8X4_CI_PDF'),
|
|
('8X4_CI_thermal', '8X4_CI_thermal'),
|
|
('8X4_RU_A4_PDF', '8X4_RU_A4_PDF'),
|
|
('6X4_PDF', '6X4_PDF'),
|
|
('8X4_PDF', '8X4_PDF')], string="Label Template", default='8X4_A4_PDF')
|
|
dhl_extra_data_rate_request = fields.Text('Extra data for rate requests', help=HELP_EXTRA_DATA)
|
|
dhl_extra_data_ship_request = fields.Text('Extra data for ship requests', help=HELP_EXTRA_DATA)
|
|
dhl_extra_data_return_request = fields.Text('Extra data for return requests', help=HELP_EXTRA_DATA)
|
|
|
|
def _compute_can_generate_return(self):
|
|
super()._compute_can_generate_return()
|
|
for carrier in self:
|
|
if carrier.delivery_type == 'dhl_rest':
|
|
carrier.can_generate_return = True
|
|
|
|
def _compute_supports_shipping_insurance(self):
|
|
super()._compute_supports_shipping_insurance()
|
|
for carrier in self:
|
|
if carrier.delivery_type == 'dhl_rest':
|
|
carrier.supports_shipping_insurance = True
|
|
|
|
def dhl_rest_rate_shipment(self, order):
|
|
res = self._rate_shipment_vals(order=order)
|
|
return res
|
|
|
|
def _dhl_rest_get_order_packages(self, order):
|
|
self.ensure_one()
|
|
total_weight = order._get_estimated_weight()
|
|
total_weight = self._dhl_rest_convert_weight(total_weight)
|
|
if total_weight == 0.0:
|
|
weight_uom_name = self.env['product.template']._get_weight_uom_name_from_ir_config_parameter()
|
|
raise UserError(_("The package cannot be created because the total weight of the products in the picking is 0.0 %s", weight_uom_name))
|
|
|
|
max_weight = self.dhl_default_package_type_id.max_weight or total_weight + 1
|
|
number_of_packages = int(total_weight / max_weight)
|
|
last_package_weight = total_weight % max_weight
|
|
|
|
weights = [max_weight] * number_of_packages + ([last_package_weight] if last_package_weight else [])
|
|
return [{
|
|
'weight': weight,
|
|
'dimension': {
|
|
'length': self.dhl_default_package_type_id.packaging_length,
|
|
'width': self.dhl_default_package_type_id.width,
|
|
'height': self.dhl_default_package_type_id.height,
|
|
},
|
|
} for weight in weights]
|
|
|
|
def _dhl_rest_get_commodities_from_stock_move_lines(self, move_lines):
|
|
commodities = []
|
|
|
|
product_lines = move_lines.filtered(lambda line: line.product_id.type in ['product', 'consu'])
|
|
for product, lines in groupby(product_lines, lambda x: x.product_id):
|
|
unit_quantity = sum(
|
|
line.product_uom_id._compute_quantity(
|
|
line.quantity,
|
|
product.uom_id)
|
|
for line in lines)
|
|
rounded_qty = max(1, float_round(unit_quantity, precision_digits=0))
|
|
country_of_origin = lines[0].picking_id.picking_type_id.warehouse_id.partner_id.country_id.code
|
|
unit_price = sum(line.sale_price for line in lines) / rounded_qty
|
|
commodities.append({
|
|
'product_id': product,
|
|
'qty': rounded_qty,
|
|
'monetary_value': unit_price,
|
|
'country_of_origin': country_of_origin
|
|
})
|
|
|
|
return commodities
|
|
|
|
def _dhl_rest_get_picking_packages(self, picking):
|
|
self.ensure_one()
|
|
packages = []
|
|
|
|
if picking.is_return_picking:
|
|
commodities = self._dhl_rest_get_commodities_from_stock_move_lines(picking.move_line_ids)
|
|
weight = picking._get_estimated_weight()
|
|
packages.append({
|
|
'commodities': commodities,
|
|
'weight': weight,
|
|
'dimension': {
|
|
'length': self.dhl_default_package_type_id.packaging_length,
|
|
'width': self.dhl_default_package_type_id.width,
|
|
'height': self.dhl_default_package_type_id.height,
|
|
},
|
|
})
|
|
return packages
|
|
|
|
# Create all packages.
|
|
for package in picking.move_line_ids.result_package_id:
|
|
move_lines = picking.move_line_ids.filtered(lambda ml: ml.result_package_id == package)
|
|
commodities = self._dhl_rest_get_commodities_from_stock_move_lines(move_lines)
|
|
packages.append({
|
|
'commodities': commodities,
|
|
'weight': package.shipping_weight or package.weight,
|
|
'dimension': {
|
|
'length': package.package_type_id.packaging_length,
|
|
'width': package.package_type_id.width,
|
|
'height': package.package_type_id.height,
|
|
},
|
|
'name': package.name,
|
|
})
|
|
|
|
# Create one package: either everything is in pack or nothing is.
|
|
if picking.weight_bulk:
|
|
commodities = self._dhl_rest_get_commodities_from_stock_move_lines(picking.move_line_ids)
|
|
packages.append({
|
|
'commodities': commodities,
|
|
'weight': picking.weight_bulk,
|
|
'dimension': {
|
|
'length': self.dhl_default_package_type_id.packaging_length,
|
|
'width': self.dhl_default_package_type_id.width,
|
|
'height': self.dhl_default_package_type_id.height,
|
|
},
|
|
'name': 'Bulk Content'
|
|
})
|
|
elif not packages:
|
|
raise UserError(_(
|
|
"The package cannot be created because the total weight of the "
|
|
"products in the picking is 0.0 %s",
|
|
picking.weight_uom_name
|
|
))
|
|
return packages
|
|
|
|
def _convert_to_utc_string(self, datetime_object):
|
|
return datetime_object.astimezone(tz=pytz.utc).strftime('%Y-%m-%dT%H:%M:%S GMT+00:00')
|
|
|
|
def _get_dhl_label_format(self):
|
|
match self.dhl_label_image_format:
|
|
case 'ZPL2':
|
|
return 'zpl'
|
|
case 'EPL2':
|
|
return 'epl'
|
|
case _:
|
|
return 'pdf'
|
|
|
|
def _rate_shipment_vals(self, order=False, picking=False):
|
|
if picking:
|
|
warehouse_partner_id = picking.picking_type_id.warehouse_id.partner_id
|
|
currency_id = picking.sale_id.currency_id or picking.company_id.currency_id
|
|
destination_partner_id = picking.partner_id
|
|
total_value = sum(sml.sale_price for sml in picking.move_line_ids)
|
|
planned_date = picking.scheduled_date + timedelta(hours=1)
|
|
else:
|
|
warehouse_partner_id = order.warehouse_id.partner_id
|
|
currency_id = order.currency_id or order.company_id.currency_id
|
|
total_value = sum(line.price_reduce_taxinc * line.product_uom_qty for line in order.order_line.filtered(lambda l: l.product_id.type in ('consu', 'product') and not l.display_type))
|
|
destination_partner_id = order.partner_shipping_id
|
|
if hasattr(order, 'website_id') and order.website_id:
|
|
planned_date = fields.Datetime.now() + timedelta(hours=1)
|
|
else:
|
|
planned_date = order.date_order + timedelta(hours=1)
|
|
|
|
rating_request = {}
|
|
account_number = self.sudo().dhl_account_number
|
|
srm = DHLProvider(self)
|
|
check_value = srm._check_required_value(self, destination_partner_id, warehouse_partner_id, order=order, picking=picking)
|
|
if check_value:
|
|
return {'success': False,
|
|
'price': 0.0,
|
|
'error_message': check_value,
|
|
'warning_message': False}
|
|
rating_request['customerDetails'] = {
|
|
'shipperDetails': srm._get_from_vals(warehouse_partner_id),
|
|
'receiverDetails': srm._get_to_vals(destination_partner_id)
|
|
}
|
|
if picking:
|
|
packages = self._dhl_rest_get_picking_packages(picking)
|
|
else:
|
|
packages = self._dhl_rest_get_order_packages(order)
|
|
rating_request['packages'] = srm._get_package_vals(self, packages)
|
|
rating_request['isCustomsDeclarable'] = self.dhl_dutiable
|
|
if self.dhl_dutiable:
|
|
rating_request['monetaryAmount'] = srm._get_dutiable_vals(total_value, currency_id.name)
|
|
rating_request['unitOfMeasurement'] = self.dhl_unit_system
|
|
if planned_date <= fields.Datetime.now():
|
|
raise UserError(_("The planned date for the shipment must be in the future."))
|
|
rating_request['plannedShippingDateAndTime'] = self._convert_to_utc_string(planned_date)
|
|
rating_request['nextBusinessDay'] = True
|
|
rating_request['accounts'] = srm._get_billing_vals(account_number, "shipper")
|
|
self._dhl_add_extra_data_to_request(rating_request, 'rate')
|
|
rating_request['productsAndServices'] = [{
|
|
'productCode': self.dhl_product_code,
|
|
'valueAddedServices': [],
|
|
}]
|
|
if self.supports_shipping_insurance and self.shipping_insurance:
|
|
rating_request['productsAndServices'][0]['valueAddedServices'].append(srm._get_insurance_vals(self.shipping_insurance, total_value, currency_id.name))
|
|
|
|
response = srm._get_rates(rating_request)
|
|
|
|
available_product_code = []
|
|
shipping_charge = False
|
|
products = response['products']
|
|
for product in products:
|
|
charge = [price for price in product['totalPrice'] if price['currencyType'] == 'BILLC'] # get the price in the billing currency
|
|
global_product_code = product['productCode']
|
|
if global_product_code == self.dhl_product_code and charge:
|
|
shipping_charge = charge[0]['price']
|
|
shipping_currency = charge[0]['priceCurrency']
|
|
break
|
|
else:
|
|
available_product_code.append(global_product_code)
|
|
if shipping_charge:
|
|
if order:
|
|
order_currency = order.currency_id
|
|
else:
|
|
order_currency = picking.sale_id.currency_id or picking.company_id.currency_id
|
|
if shipping_currency is None or order_currency.name == shipping_currency:
|
|
price = float(shipping_charge)
|
|
else:
|
|
quote_currency = self.env['res.currency'].search([('name', '=', shipping_currency)], limit=1)
|
|
price = quote_currency._convert(float(shipping_charge), order_currency, (order or picking).company_id, order.date_order if order else fields.Date.today())
|
|
if self.supports_shipping_insurance and self.shipping_insurance:
|
|
for product in products:
|
|
services = []
|
|
for price_breakdown in product['detailedPriceBreakdown']:
|
|
services.extend([service['serviceCode'] for service in price_breakdown['breakdown'] if 'serviceCode' in service])
|
|
if 'II' not in services:
|
|
return {'success': False,
|
|
'price': 0.0,
|
|
'error_message': _("Shipment insurance is not available between the origin and destination. You should try with another DHL product, or select a delivery method with no insurance."),
|
|
'warning_message': False}
|
|
|
|
return {'success': True,
|
|
'price': price,
|
|
'error_message': False,
|
|
'warning_message': False}
|
|
|
|
if available_product_code:
|
|
return {'success': False,
|
|
'price': 0.0,
|
|
'error_message': _(
|
|
"There is no price available for this shipping, you should rather try with the DHL product %s",
|
|
available_product_code[0]),
|
|
'warning_message': False}
|
|
|
|
def dhl_rest_send_shipping(self, pickings):
|
|
res = []
|
|
for picking in pickings:
|
|
shipment_request = {}
|
|
srm = DHLProvider(self)
|
|
account_number = self.sudo().dhl_account_number
|
|
planned_date = picking.scheduled_date
|
|
scheduled_date_warning = False
|
|
if not planned_date or planned_date <= fields.Datetime.now():
|
|
# DHL requires a planned date in the future => +1 hr to now
|
|
planned_date = fields.Datetime.now() + timedelta(hours=1)
|
|
scheduled_date_warning = _(" Extra Info: The scheduled time for the shipment was either not set or was in the past, so DHL has been sent a planned shipping time of one hour from now")
|
|
shipment_request['plannedShippingDateAndTime'] = self._convert_to_utc_string(planned_date)
|
|
shipment_request['pickup'] = {'isRequested': True}
|
|
shipment_request['accounts'] = srm._get_billing_vals(account_number, "shipper")
|
|
shipment_request['customerDetails'] = {}
|
|
shipment_request['customerDetails']['receiverDetails'] = srm._get_consignee_vals(picking.partner_id)
|
|
shipment_request['customerDetails']['shipperDetails'] = srm._get_shipper_vals(picking.company_id.partner_id, picking.picking_type_id.warehouse_id.partner_id)
|
|
shipment_request['productCode'] = self.dhl_product_code
|
|
shipment_request['customerReferences'] = [{
|
|
'value': picking.sale_id.name if picking.sale_id else picking.name,
|
|
'typeCode': 'CU'
|
|
}]
|
|
shipment_request['content'] = {}
|
|
shipment_request['content']['description'] = picking.sale_id.name if picking.sale_id else picking.name
|
|
shipment_request['content']['unitOfMeasurement'] = self.dhl_unit_system
|
|
incoterm = picking.sale_id.incoterm or self.env.company.incoterm_id
|
|
shipment_request['content']['incoterm'] = incoterm.code or 'EXW'
|
|
total_value, currency_name = self._dhl_calculate_value(picking)
|
|
shipment_request['content']['isCustomsDeclarable'] = self.dhl_dutiable
|
|
if self.dhl_dutiable:
|
|
shipment_request['content']['declaredValue'] = total_value
|
|
shipment_request['content']['declaredValueCurrency'] = currency_name
|
|
if picking._should_generate_commercial_invoice():
|
|
shipment_request['content']['exportDeclaration'] = srm._get_export_declaration_vals(self, picking)
|
|
shipment_request['content']['declaredValueCurrency'] = currency_name
|
|
shipment_request['content']['packages'] = srm._get_shipment_vals(picking)
|
|
shipment_request['outputImageProperties'] = {
|
|
'encodingFormat': self._get_dhl_label_format(),
|
|
}
|
|
shipment_request['outputImageProperties']['imageOptions'] = [{
|
|
'typeCode': 'label',
|
|
'templateName': LABEL_FORMAT_MAPPING.get(self.dhl_label_template, 'ECOM26_84_001'),
|
|
'fitLabelsToA4': self.dhl_label_image_format == 'PDF' and 'A4' in self.dhl_label_template,
|
|
}]
|
|
if self.supports_shipping_insurance and self.shipping_insurance:
|
|
shipment_request['valueAddedServices'] = [srm._get_insurance_vals(self.shipping_insurance, total_value, currency_name)]
|
|
self._dhl_add_extra_data_to_request(shipment_request, 'ship')
|
|
dhl_response = srm._send_shipment(shipment_request)
|
|
tracking_number = dhl_response['shipmentTrackingNumber']
|
|
logmessage = Markup("%s<br/><b>%s:</b> %s") % (_("Shipment created into DHL"), _("Tracking Number"), tracking_number)
|
|
if scheduled_date_warning:
|
|
logmessage += scheduled_date_warning
|
|
dhl_labels = [
|
|
(
|
|
'LabelShipping-DHL-{}.{}'.format(tracking_number, document['imageFormat']),
|
|
base64.b64decode(document['content'])
|
|
)
|
|
for document in dhl_response['documents'] if document['typeCode'] == 'label'
|
|
]
|
|
other_documents = [
|
|
(
|
|
'ShippingDoc-DHL-{}.{}'.format(document['packageReferenceNumber'], document['imageFormat']),
|
|
base64.b64decode(document['content'])
|
|
)
|
|
for document in dhl_response['documents'] if document['typeCode'] != 'label'
|
|
]
|
|
lognote_pickings = picking.sale_id.picking_ids if picking.sale_id else picking
|
|
for pick in lognote_pickings:
|
|
pick.message_post(body=logmessage, attachments=dhl_labels)
|
|
if other_documents:
|
|
pick.message_post(body=_("DHL Documents"), attachments=other_documents)
|
|
shipping_data = {
|
|
'exact_price': 0,
|
|
'tracking_number': tracking_number,
|
|
}
|
|
rate = self._rate_shipment_vals(picking=picking)
|
|
shipping_data['exact_price'] = rate['price']
|
|
if self.return_label_on_delivery:
|
|
self.get_return_label(picking)
|
|
res = res + [shipping_data]
|
|
|
|
return res
|
|
|
|
def dhl_rest_get_return_label(self, picking, tracking_number=None, origin_date=None):
|
|
shipment_request = {}
|
|
srm = DHLProvider(self)
|
|
account_number = self.sudo().dhl_account_number
|
|
planned_date = picking.scheduled_date
|
|
scheduled_date_warning = False
|
|
if not planned_date or planned_date <= fields.Datetime.now():
|
|
# DHL requires a planned date in the future => +1 hr to now
|
|
planned_date = fields.Datetime.now() + timedelta(hours=1)
|
|
scheduled_date_warning = _(" Extra Info: The scheduled time for the shipment was either not set or was in the past, so DHL has been sent a planned shipping time of one hour from now")
|
|
shipment_request['plannedShippingDateAndTime'] = self._convert_to_utc_string(planned_date)
|
|
shipment_request['pickup'] = {'isRequested': False}
|
|
shipment_request['accounts'] = srm._get_billing_vals(account_number, "shipper")
|
|
shipment_request['customerDetails'] = {
|
|
'shipperDetails': srm._get_shipper_vals(picking.partner_id, picking.partner_id),
|
|
'receiverDetails': srm._get_consignee_vals(picking.picking_type_id.warehouse_id.partner_id)
|
|
}
|
|
shipment_request['productCode'] = self.dhl_product_code
|
|
shipment_request['content'] = {
|
|
'description': picking.sale_id.name if picking.sale_id else picking.name,
|
|
'unitOfMeasurement': self.dhl_unit_system,
|
|
'incoterm': picking.sale_id.incoterm.code or self.env.company.incoterm_id.code or 'EXW',
|
|
'isCustomsDeclarable': self.dhl_dutiable,
|
|
'packages': srm._get_shipment_vals(picking)
|
|
}
|
|
total_value, currency_name = self._dhl_calculate_value(picking)
|
|
if self.dhl_dutiable:
|
|
shipment_request['content']['declaredValue'] = total_value
|
|
shipment_request['content']['declaredValueCurrency'] = currency_name
|
|
if picking._should_generate_commercial_invoice():
|
|
shipment_request['content']['exportDeclaration'] = srm._get_export_declaration_vals(self, picking)
|
|
shipment_request['content']['declaredValueCurrency'] = currency_name
|
|
shipment_request['outputImageProperties'] = {
|
|
'imageOptions': [{
|
|
'typeCode': 'label',
|
|
'templateName': self.dhl_label_template,
|
|
}]
|
|
}
|
|
shipment_request['valueAddedServices'] = [{'serviceCode': 'PV'}]
|
|
self._dhl_add_extra_data_to_request(shipment_request, 'return')
|
|
dhl_response = srm._send_shipment(shipment_request)
|
|
tracking_number = dhl_response['shipmentTrackingNumber']
|
|
logmessage = Markup("%s<br/><b>%s:</b> %s") % (_("Shipment created into DHL"), _("Tracking Number"), tracking_number)
|
|
if scheduled_date_warning:
|
|
logmessage += scheduled_date_warning
|
|
dhl_labels = [
|
|
(
|
|
'LabelReturn-DHL-{}.{}'.format(tracking_number, document['imageFormat']),
|
|
base64.b64decode(document['content'])
|
|
)
|
|
for document in dhl_response['documents'] if document['typeCode'] == 'label'
|
|
]
|
|
other_documents = [
|
|
(
|
|
'ShippingDoc-DHL-{}.{}'.format(document['packageReferenceNumber'], document['imageFormat']),
|
|
base64.b64decode(document['content'])
|
|
)
|
|
for document in dhl_response['documents'] if document['typeCode'] != 'label'
|
|
]
|
|
lognote_pickings = picking.sale_id.picking_ids if picking.sale_id else picking
|
|
for pick in lognote_pickings:
|
|
pick.message_post(body=logmessage, attachments=dhl_labels)
|
|
if other_documents:
|
|
pick.message_post(body=_("DHL Documents"), attachments=other_documents)
|
|
shipping_data = {
|
|
'exact_price': 0,
|
|
'tracking_number': tracking_number,
|
|
}
|
|
return shipping_data
|
|
|
|
def dhl_rest_get_tracking_link(self, picking):
|
|
return f'http://www.dhl.com/en/express/tracking.html?AWB={picking.carrier_tracking_ref}'
|
|
|
|
def dhl_rest_cancel_shipment(self, picking):
|
|
# Obviously you need a pick up date to delete SHIPMENT by DHL. So you can't do it if you didn't schedule a pick-up.
|
|
picking.message_post(body=_("You can't cancel DHL shipping without pickup date."))
|
|
picking.write({'carrier_tracking_ref': '', 'carrier_price': 0.0})
|
|
|
|
def _dhl_rest_convert_weight(self, weight):
|
|
weight_uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter()
|
|
unit = self.dhl_unit_system
|
|
if unit == 'imperial':
|
|
weight = weight_uom_id._compute_quantity(weight, self.env.ref('uom.product_uom_lb'), round=False)
|
|
else:
|
|
weight = weight_uom_id._compute_quantity(weight, self.env.ref('uom.product_uom_kgm'), round=False)
|
|
# float_round doesn't work here, for example float_round(0.7000000000000001, 3) = 0.7000000000000001
|
|
return json_float_round(weight, 3)
|
|
|
|
def _dhl_calculate_value(self, picking):
|
|
sale_order = picking.sale_id
|
|
if sale_order:
|
|
total_value = sum(
|
|
line.price_reduce_taxinc * line.product_uom_qty
|
|
for line in sale_order.order_line
|
|
if not line.display_type
|
|
if line.product_id.type in ['product', 'consu']
|
|
)
|
|
currency_name = picking.sale_id.currency_id.name
|
|
else:
|
|
total_value = sum(line.product_id.lst_price * line.product_qty for line in picking.move_ids)
|
|
currency_name = picking.company_id.currency_id.name
|
|
return total_value, currency_name
|
|
|
|
def _dhl_add_extra_data_to_request(self, request, request_type):
|
|
"""Adds the extra data to the request.
|
|
When there are multiple items in a list, they will all be affected by
|
|
the change.
|
|
for example, with
|
|
{"content": {"packages": {"description": "amazon package"}}}
|
|
the description of each piece will be updated."""
|
|
extra_data_input = {
|
|
'rate': self.dhl_extra_data_rate_request,
|
|
'ship': self.dhl_extra_data_ship_request,
|
|
'return': self.dhl_extra_data_return_request,
|
|
}.get(request_type) or ''
|
|
try:
|
|
extra_data = json.loads('{' + extra_data_input + '}')
|
|
except JSONDecodeError:
|
|
raise UserError(_("Invalid syntax for DHL extra data."))
|
|
|
|
def extra_data_to_request(request, extra_data):
|
|
"""recursive function that adds extra data to the current request"""
|
|
for key, new_value in extra_data.items():
|
|
current_value = request.get(key)
|
|
if isinstance(current_value, list):
|
|
for item in current_value:
|
|
extra_data_to_request(item, new_value)
|
|
elif isinstance(new_value, dict) and isinstance(current_value, dict):
|
|
extra_data_to_request(current_value, new_value)
|
|
else:
|
|
request[key] = new_value
|
|
|
|
extra_data_to_request(request, extra_data)
|