1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/delivery_dhl/models/delivery_dhl.py
2024-12-10 09:04:09 +07:00

378 lines
21 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from .dhl_request import DHLProvider
from markupsafe import Markup
from odoo.tools.zeep.helpers import serialize_object
from odoo import api, models, fields, _
from odoo.exceptions import UserError
from odoo.tools import float_repr
from odoo.tools.safe_eval import const_eval
class Providerdhl(models.Model):
_inherit = 'delivery.carrier'
delivery_type = fields.Selection(selection_add=[
('dhl', "DHL")
], ondelete={'dhl': lambda recs: recs.write({'delivery_type': 'fixed', 'fixed_price': 0})})
dhl_SiteID = fields.Char(string="DHL SiteID", groups="base.group_system")
dhl_password = fields.Char(string="DHL Password", groups="base.group_system")
dhl_account_number = fields.Char(string="DHL Account Number", groups="base.group_system")
dhl_package_dimension_unit = fields.Selection([('I', 'Inches'),
('C', 'Centimeters')],
default='C',
string='Package Dimension Unit')
dhl_package_weight_unit = fields.Selection([('L', 'Pounds'),
('K', 'Kilograms')],
default='K',
string="Package Weight Unit")
dhl_default_package_type_id = fields.Many2one('stock.package.type', string='DHL 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_custom_data_request = fields.Text(
'Custom data for DHL requests,',
help="""The custom data in DHL is organized like the inside of a json file.
There are 3 possible keys: 'rate', 'ship', 'return', to which you can add your custom data.
More info on https://xmlportal.dhl.com/"""
)
@api.ondelete(at_uninstall=False)
def _unlink_except_commercial_invoice_sequence(self):
if self.env.ref('delivery_dhl.dhl_commercial_invoice_seq').id in self.ids:
raise UserError(_('You cannot delete the commercial invoice sequence.'))
def _compute_can_generate_return(self):
super(Providerdhl, self)._compute_can_generate_return()
for carrier in self:
if carrier.delivery_type == 'dhl':
carrier.can_generate_return = True
def _compute_supports_shipping_insurance(self):
super(Providerdhl, self)._compute_supports_shipping_insurance()
for carrier in self:
if carrier.delivery_type == 'dhl':
carrier.supports_shipping_insurance = True
def dhl_rate_shipment(self, order):
res = self._rate_shipment_vals(order=order)
return res
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)
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
rating_request = {}
srm = DHLProvider(self.log_xml, request_type="rate", prod_environment=self.prod_environment)
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}
site_id = self.sudo().dhl_SiteID
password = self.sudo().dhl_password
rating_request['Request'] = srm._set_request(site_id, password)
rating_request['From'] = srm._set_dct_from(warehouse_partner_id)
if picking:
packages = self._get_packages_from_picking(picking, self.dhl_default_package_type_id)
else:
packages = self._get_packages_from_order(order, self.dhl_default_package_type_id)
rating_request['BkgDetails'] = srm._set_dct_bkg_details(self, packages)
rating_request['To'] = srm._set_dct_to(destination_partner_id)
if self.dhl_dutiable:
rating_request['Dutiable'] = srm._set_dct_dutiable(total_value, currency_id.name)
real_rating_request = {}
real_rating_request['GetQuote'] = rating_request
real_rating_request['schemaVersion'] = 2.0
self._dhl_add_custom_data_to_request(rating_request, 'rate')
response = srm._process_rating(real_rating_request)
available_product_code = []
shipping_charge = False
qtd_shp = response.findall('GetQuoteResponse/BkgDetails/QtdShp')
if qtd_shp:
for q in qtd_shp:
charge = q.find('ShippingCharge').text
global_product_code = q.find('GlobalProductCode').text
if global_product_code == self.dhl_product_code and charge:
shipping_charge = charge
shipping_currency = q.find('CurrencyCode')
shipping_currency = None if shipping_currency is None else shipping_currency.text
break
else:
available_product_code.append(global_product_code)
else:
condition = response.find('GetQuoteResponse/Note/Condition')
if condition:
condition_code = condition.find('ConditionCode').text
if condition_code == '410301':
return {
'success': False,
'price': 0.0,
'error_message': "%s.\n%s" % (condition.find('ConditionData').text, _("Hint: The destination may not require the dutiable option.")),
'warning_message': False,
}
elif condition_code in ['420504', '420505', '420506', '410304'] or\
response.find('GetQuoteResponse/Note/ActionStatus').text == "Failure":
return {
'success': False,
'price': 0.0,
'error_message': "%s." % (condition.find('ConditionData').text),
'warning_message': False,
}
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())
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_send_shipping(self, pickings):
res = []
for picking in pickings:
shipment_request = {}
srm = DHLProvider(self.log_xml, request_type="ship", prod_environment=self.prod_environment)
site_id = self.sudo().dhl_SiteID
password = self.sudo().dhl_password
account_number = self.sudo().dhl_account_number
shipment_request['Request'] = srm._set_request(site_id, password)
shipment_request['RegionCode'] = srm._set_region_code(self.dhl_region_code)
shipment_request['RequestedPickupTime'] = srm._set_requested_pickup_time(True)
shipment_request['Billing'] = srm._set_billing(account_number, "S", self.dhl_duty_payment, self.dhl_dutiable)
shipment_request['Consignee'] = srm._set_consignee(picking.partner_id)
shipment_request['Shipper'] = srm._set_shipper(account_number, picking.company_id.partner_id, picking.picking_type_id.warehouse_id.partner_id)
shipment_request['Reference'] = {
'ReferenceID': picking.sale_id.name if picking.sale_id else picking.name,
'ReferenceType': 'CU'
}
total_value, currency_name = self._dhl_calculate_value(picking)
if self.dhl_dutiable:
incoterm = picking.sale_id.incoterm or self.env.company.incoterm_id
shipment_request['Dutiable'] = srm._set_dutiable(total_value, currency_name, incoterm)
if picking._should_generate_commercial_invoice():
shipment_request['UseDHLInvoice'] = 'Y'
shipment_request['DHLInvoiceType'] = 'CMI'
shipment_request['ExportDeclaration'] = srm._set_export_declaration(self, picking)
shipment_request['ShipmentDetails'] = srm._set_shipment_details(picking)
shipment_request['LabelImageFormat'] = srm._set_label_image_format(self.dhl_label_image_format)
shipment_request['Label'] = srm._set_label(self.dhl_label_template)
shipment_request['schemaVersion'] = 10.0
shipment_request['LanguageCode'] = 'en'
if picking.carrier_id.shipping_insurance:
shipment_request['SpecialService'] = []
shipment_request['SpecialService'].append(srm._set_insurance(shipment_request['ShipmentDetails']))
self._dhl_add_custom_data_to_request(shipment_request, 'ship')
dhl_response = srm._process_shipment(shipment_request)
traking_number = dhl_response.AirwayBillNumber
logmessage = Markup(_("Shipment created into DHL <br/> <b>Tracking Number: </b>%s")) % (traking_number)
dhl_labels = [('%s-%s.%s' % (self._get_delivery_label_prefix(), traking_number, self.dhl_label_image_format), dhl_response.LabelImage[0].OutputImage)]
dhl_cmi = [('%s-%s.%s' % (self._get_delivery_doc_prefix(), mlabel.DocName, mlabel.DocFormat), mlabel.DocImageVal) for mlabel in dhl_response.LabelImage[0].MultiLabels.MultiLabel] if dhl_response.LabelImage[0].MultiLabels else None
lognote_pickings = picking
if picking.sale_id:
lognote_pickings |= picking.sale_id.picking_ids.filtered(lambda p: p.state not in ('done', 'cancel'))
for pick in lognote_pickings:
pick.message_post(body=logmessage, attachments=dhl_labels)
if dhl_cmi:
pick.message_post(body=_("DHL Documents"), attachments=dhl_cmi)
shipping_data = {
'exact_price': 0,
'tracking_number': traking_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_get_return_label(self, picking, tracking_number=None, origin_date=None):
shipment_request = {}
srm = DHLProvider(self.log_xml, request_type="ship", prod_environment=self.prod_environment)
site_id = self.sudo().dhl_SiteID
password = self.sudo().dhl_password
account_number = self.sudo().dhl_account_number
shipment_request['Request'] = srm._set_request(site_id, password)
shipment_request['RegionCode'] = srm._set_region_code(self.dhl_region_code)
shipment_request['RequestedPickupTime'] = srm._set_requested_pickup_time(True)
shipment_request['Billing'] = srm._set_billing(account_number, "S", "S", self.dhl_dutiable)
shipment_request['Consignee'] = srm._set_consignee(picking.picking_type_id.warehouse_id.partner_id)
shipment_request['Shipper'] = srm._set_shipper(account_number, picking.partner_id, picking.partner_id)
total_value, currency_name = self._dhl_calculate_value(picking)
if self.dhl_dutiable:
incoterm = picking.sale_id.incoterm or self.env.company.incoterm_id
shipment_request['Dutiable'] = srm._set_dutiable(total_value, currency_name, incoterm)
if picking._should_generate_commercial_invoice():
shipment_request['UseDHLInvoice'] = 'Y'
shipment_request['DHLInvoiceType'] = 'CMI'
shipment_request['ExportDeclaration'] = srm._set_export_declaration(self, picking, is_return=True)
shipment_request['ShipmentDetails'] = srm._set_shipment_details(picking)
shipment_request['LabelImageFormat'] = srm._set_label_image_format(self.dhl_label_image_format)
shipment_request['Label'] = srm._set_label(self.dhl_label_template)
shipment_request['SpecialService'] = []
shipment_request['SpecialService'].append(srm._set_return())
shipment_request['schemaVersion'] = 10.0
shipment_request['LanguageCode'] = 'en'
self._dhl_add_custom_data_to_request(shipment_request, 'return')
dhl_response = srm._process_shipment(shipment_request)
traking_number = dhl_response.AirwayBillNumber
logmessage = Markup(_("Shipment created into DHL <br/> <b>Tracking Number: </b>%s")) % (traking_number)
dhl_labels = [('%s-%s-%s.%s' % (self.get_return_label_prefix(), traking_number, 1, self.dhl_label_image_format), dhl_response.LabelImage[0].OutputImage)]
dhl_cmi = [('%s-Return-%s.%s' % (self._get_delivery_doc_prefix(), mlabel.DocName, mlabel.DocFormat), mlabel.DocImageVal) for mlabel in dhl_response.LabelImage[0].MultiLabels.MultiLabel] if dhl_response.LabelImage[0].MultiLabels else None
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 dhl_cmi:
pick.message_post(body=_("DHL Documents"), attachments=dhl_cmi)
shipping_data = {
'exact_price': 0,
'tracking_number': traking_number,
}
return shipping_data
def dhl_get_tracking_link(self, picking):
return 'http://www.dhl.com/en/express/tracking.html?AWB=%s' % picking.carrier_tracking_ref
def dhl_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=_(u"You can't cancel DHL shipping without pickup date."))
picking.write({'carrier_tracking_ref': '',
'carrier_price': 0.0})
def _dhl_convert_weight(self, weight, unit):
weight_uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter()
if unit == 'L':
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)
return float_repr(weight, 3)
def _dhl_add_custom_data_to_request(self, request, request_type):
"""Adds the custom data to the request.
When there are multiple items in a list, they will all be affected by
the change.
for example, with
{"ShipmentDetails": {"Pieces": {"Piece": {"AdditionalInformation": "custom info"}}}}
the AdditionalInformation of each piece will be updated.
"""
if not self.dhl_custom_data_request:
return
try:
custom_data = const_eval('{%s}' % self.dhl_custom_data_request).get(request_type, {})
except SyntaxError:
raise UserError(_('Invalid syntax for DHL custom data.'))
def extra_data_to_request(request, custom_data):
"""recursive function that adds custom data to the current request."""
for key, new_value in custom_data.items():
request[key] = current_value = serialize_object(request.get(key, {})) or None
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, custom_data)
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.filtered(
lambda l: l.product_id.type in ('consu', 'product') and not l.display_type))
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