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

343 lines
17 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml.etree import fromstring
from datetime import datetime, date, timedelta
from lxml import etree
from odoo.tools.zeep import Client, Plugin
from odoo.tools.zeep.wsdl.utils import etree_to_string
from odoo import _
from odoo import release
from odoo.exceptions import UserError
from odoo.tools import float_repr, float_round
from odoo.tools.misc import file_path
class DHLProvider():
def __init__(self, debug_logger, request_type='ship', prod_environment=False):
self.debug_logger = debug_logger
if not prod_environment:
self.url = 'https://xmlpitest-ea.dhl.com/XMLShippingServlet?isUTF8Support=true'
else:
self.url = 'https://xmlpi-ea.dhl.com/XMLShippingServlet?isUTF8Support=true'
if request_type == "ship":
self.client = self._set_client('ship-10.0.wsdl', 'Ship')
self.factory = self.client.type_factory('ns1')
elif request_type =="rate":
self.client = self._set_client('rate.wsdl', 'Rate')
self.factory = self.client.type_factory('ns1')
self.factory_dct_request = self.client.type_factory('ns2')
self.factory_dct_response = self.client.type_factory('ns3')
def _set_client(self, wsdl_filename, api):
wsdl_path = file_path(f'delivery_dhl/api/{wsdl_filename}')
client = Client(wsdl_path)
return client
def _set_request(self, site_id, password):
request = self.factory.Request()
service_header = self.factory.ServiceHeader()
service_header.MessageTime = datetime.now()
service_header.MessageReference = 'ref:' + datetime.now().isoformat() #CHANGEME
service_header.SiteID = site_id
service_header.Password = password
request.ServiceHeader = service_header
metadata = self.factory.MetaData()
metadata.SoftwareName = release.product_name
metadata.SoftwareVersion = release.series
request.MetaData = metadata
return request
def _set_region_code(self, region_code):
return region_code
def _set_requested_pickup_time(self, requested_pickup):
if requested_pickup:
return "Y"
else:
return "N"
def _set_billing(self, shipper_account, payment_type, duty_payment_type, is_dutiable):
billing = self.factory.Billing()
billing.ShipperAccountNumber = shipper_account
billing.ShippingPaymentType = payment_type
if is_dutiable:
billing.DutyPaymentType = duty_payment_type
return billing
def _set_consignee(self, partner_id):
consignee = self.factory.Consignee()
consignee.CompanyName = partner_id.commercial_company_name or partner_id.name
consignee.AddressLine1 = partner_id.street or partner_id.street2
consignee.AddressLine2 = partner_id.street and partner_id.street2 or None
consignee.City = partner_id.city
if partner_id.state_id:
consignee.Division = partner_id.state_id.name
consignee.DivisionCode = partner_id.state_id.code
consignee.PostalCode = partner_id.zip
consignee.CountryCode = partner_id.country_id.code
consignee.CountryName = partner_id.country_id.name
contact = self.factory.Contact()
contact.PersonName = partner_id.name
contact.PhoneNumber = partner_id.phone
if partner_id.email:
contact.Email = partner_id.email
consignee.Contact = contact
return consignee
def _set_dct_to(self, partner_id):
to = self.factory_dct_request.DCTTo()
country_code = partner_id.country_id.code
zip_code = partner_id.zip or ''
if country_code == 'ES' and (zip_code.startswith('35') or zip_code.startswith('38')):
country_code = 'IC'
to.CountryCode = country_code
to.Postalcode = zip_code
to.City = partner_id.city
return to
def _set_shipper(self, account_number, company_partner_id, warehouse_partner_id):
shipper = self.factory.Shipper()
shipper.ShipperID = account_number
shipper.CompanyName = company_partner_id.name
shipper.AddressLine1 = warehouse_partner_id.street or warehouse_partner_id.street2
shipper.AddressLine2 = warehouse_partner_id.street and warehouse_partner_id.street2 or None
shipper.City = warehouse_partner_id.city
if warehouse_partner_id.state_id:
shipper.Division = warehouse_partner_id.state_id.name
shipper.DivisionCode = warehouse_partner_id.state_id.code
shipper.PostalCode = warehouse_partner_id.zip
shipper.CountryCode = warehouse_partner_id.country_id.code
shipper.CountryName = warehouse_partner_id.country_id.name
contact = self.factory.Contact()
contact.PersonName = warehouse_partner_id.name
contact.PhoneNumber = warehouse_partner_id.phone
if warehouse_partner_id.email:
contact.Email = warehouse_partner_id.email
shipper.Contact = contact
return shipper
def _set_dct_from(self, warehouse_partner_id):
dct_from = self.factory_dct_request.DCTFrom()
dct_from.CountryCode = warehouse_partner_id.country_id.code
dct_from.Postalcode = warehouse_partner_id.zip
dct_from.City = warehouse_partner_id.city
return dct_from
def _set_dutiable(self, total_value, currency_name, incoterm):
dutiable = self.factory.Dutiable()
dutiable.DeclaredValue = float_repr(total_value, 2)
dutiable.DeclaredCurrency = currency_name
if not incoterm:
raise UserError(_("Please define an incoterm in the associated sale order or set a default incoterm for the company in the accounting's settings."))
dutiable.TermsOfTrade = incoterm.code
return dutiable
def _set_dct_dutiable(self, total_value, currency_name):
dct_dutiable = self.factory_dct_request.DCTDutiable()
dct_dutiable.DeclaredCurrency = currency_name
dct_dutiable.DeclaredValue = total_value
return dct_dutiable
def _set_dct_bkg_details(self, carrier, packages):
bkg_details = self.factory_dct_request.BkgDetailsType()
bkg_details.PaymentCountryCode = packages[0].company_id.partner_id.country_id.code
bkg_details.Date = date.today()
bkg_details.ReadyTime = timedelta(hours=1,minutes=2)
bkg_details.DimensionUnit = "CM" if carrier.dhl_package_dimension_unit == "C" else "IN"
bkg_details.WeightUnit = "KG" if carrier.dhl_package_weight_unit == "K" else "LB"
bkg_details.InsuredValue = float_repr(sum(pkg.total_cost for pkg in packages) * carrier.shipping_insurance / 100, precision_digits=3)
bkg_details.InsuredCurrency = packages[0].currency_id.name
pieces = []
for sequence, package in enumerate(packages):
piece = self.factory_dct_request.PieceType()
piece.PieceID = sequence
piece.PackageTypeCode = package.packaging_type
piece.Height = package.dimension['height']
piece.Depth = package.dimension['length']
piece.Width = package.dimension['width']
piece.Weight = carrier._dhl_convert_weight(package.weight, carrier.dhl_package_weight_unit)
pieces.append(piece)
bkg_details.Pieces = {'Piece': pieces}
bkg_details.PaymentAccountNumber = carrier.dhl_account_number
if carrier.dhl_dutiable:
bkg_details.IsDutiable = "Y"
else:
bkg_details.IsDutiable = "N"
bkg_details.NetworkTypeCode = "AL"
return bkg_details
def _set_shipment_details(self, picking):
shipment_details = self.factory.ShipmentDetails()
pieces = []
packages = picking.carrier_id._get_packages_from_picking(picking, picking.carrier_id.dhl_default_package_type_id)
for sequence, package in enumerate(packages):
piece = self.factory.Piece()
piece.PieceID = sequence
piece.Height = package.dimension['height']
piece.Depth = package.dimension['length']
piece.Width = package.dimension['width']
piece.Weight = picking.carrier_id._dhl_convert_weight(package.weight, picking.carrier_id.dhl_package_weight_unit)
piece.PieceContents = package.name
pieces.append(piece)
shipment_details.Pieces = self.factory.Pieces(pieces)
shipment_details.WeightUnit = picking.carrier_id.dhl_package_weight_unit
shipment_details.GlobalProductCode = picking.carrier_id.dhl_product_code
shipment_details.LocalProductCode = picking.carrier_id.dhl_product_code
shipment_details.Date = date.today()
shipment_details.Contents = "MY DESCRIPTION"
shipment_details.DimensionUnit = picking.carrier_id.dhl_package_dimension_unit
shipment_details.InsuredAmount = float_repr(sum(pkg.total_cost for pkg in packages) * picking.carrier_id.shipping_insurance / 100, precision_digits=2)
if picking.carrier_id.dhl_dutiable:
shipment_details.IsDutiable = "Y"
currency = picking.group_id.sale_id.currency_id or picking.company_id.currency_id
shipment_details.CurrencyCode = currency.name
return shipment_details
def _set_label_image_format(self, label_image_format):
return label_image_format
def _set_label(self, label):
label_obj = self.factory.Label()
label_obj.LabelTemplate = label
return label_obj
def _set_return(self):
return_service = self.factory.SpecialService()
return_service.SpecialServiceType = "PV"
return return_service
def _set_insurance(self, shipment_details):
insurance_service = self.factory.SpecialService()
insurance_service.SpecialServiceType = "II"
insurance_service.ChargeValue = shipment_details.InsuredAmount
insurance_service.CurrencyCode = shipment_details.CurrencyCode
return insurance_service
def _process_shipment(self, shipment_request):
ShipmentRequest = self.client._Client__obj.get_element('ns0:ShipmentRequest')
document = etree.Element('root')
ShipmentRequest.render(document, shipment_request)
request_to_send = etree_to_string(list(document)[0])
headers = {'Content-Type': 'text/xml'}
response = self.client._Client__obj.transport.post(self.url, request_to_send, headers=headers)
if self.debug_logger:
self.debug_logger(request_to_send, 'dhl_shipment_request')
self.debug_logger(response.content, 'dhl_shipment_response')
response_element_xml = fromstring(response.content)
Response = self.client._Client__obj.get_element(response_element_xml.tag)
response_zeep = Response.type.parse_xmlelement(response_element_xml)
dict_response = {'tracking_number': 0.0,
'price': 0.0,
'currency': False}
# This condition handle both 'ShipmentValidateErrorResponse' and
# 'ErrorResponse', we could handle them differently if needed as
# the 'ShipmentValidateErrorResponse' is something you cannot do,
# and 'ErrorResponse' are bad values given in the request.
if hasattr(response_zeep.Response, 'Status'):
condition = response_zeep.Response.Status.Condition[0]
error_msg = "%s: %s" % (condition.ConditionCode, condition.ConditionData)
raise UserError(error_msg)
return response_zeep
def _process_rating(self, rating_request):
DCTRequest = self.client._Client__obj.get_element('ns0:DCTRequest')
document = etree.Element('root')
DCTRequest.render(document, rating_request)
request_to_send = etree_to_string(list(document)[0])
headers = {'Content-Type': 'text/xml'}
response = self.client._Client__obj.transport.post(self.url, request_to_send, headers=headers)
if self.debug_logger:
self.debug_logger(request_to_send, 'dhl_rating_request')
self.debug_logger(response.content, 'dhl_rating_response')
response_element_xml = fromstring(response.content)
dict_response = {'tracking_number': 0.0,
'price': 0.0,
'currency': False}
# This condition handle both 'ShipmentValidateErrorResponse' and
# 'ErrorResponse', we could handle them differently if needed as
# the 'ShipmentValidateErrorResponse' is something you cannot do,
# and 'ErrorResponse' are bad values given in the request.
if response_element_xml.find('GetQuoteResponse') is not None:
return response_element_xml
else:
condition = response_element_xml.find('Response/Status/Condition')
error_msg = "%s: %s" % (condition.find('ConditionCode').text, condition.find('ConditionData').text)
raise UserError(error_msg)
def check_required_value(self, carrier, recipient, shipper, order=False, picking=False):
carrier = carrier.sudo()
recipient_required_field = ['city', 'zip', 'country_id']
if not carrier.dhl_SiteID:
return _("DHL Site ID is missing, please modify your delivery method settings.")
if not carrier.dhl_password:
return _("DHL password is missing, please modify your delivery method settings.")
if not carrier.dhl_account_number:
return _("DHL account number is missing, please modify your delivery method settings.")
# The street isn't required if we compute the rate with a partial delivery address in the
# express checkout flow.
if not recipient.street and not recipient.street2 and not recipient._context.get(
'express_checkout_partial_delivery_address', False
):
recipient_required_field.append('street')
res = [field for field in recipient_required_field if not recipient[field]]
if res:
return _("The address of the customer is missing or wrong (Missing field(s) :\n %s)", ", ".join(res).replace("_id", ""))
shipper_required_field = ['city', 'zip', 'phone', 'country_id']
if not shipper.street and not shipper.street2:
shipper_required_field.append('street')
res = [field for field in shipper_required_field if not shipper[field]]
if res:
return _("The address of your company warehouse is missing or wrong (Missing field(s) :\n %s)", ", ".join(res).replace("_id", ""))
if order:
if not order.order_line:
return _("Please provide at least one item to ship.")
error_lines = order.order_line.filtered(lambda line: not line.product_id.weight and not line.is_delivery and line.product_id.type != 'service' and not line.display_type)
if error_lines:
return _("The estimated shipping price cannot be computed because the weight is missing for the following product(s): \n %s", ", ".join(error_lines.product_id.mapped('name')))
return False
def _set_export_declaration(self, carrier, picking, is_return=False):
export_lines = []
move_lines = picking.move_line_ids.filtered(lambda line: line.product_id.type in ['product', 'consu'])
currency_id = picking.sale_id and picking.sale_id.currency_id or picking.company_id.currency_id
for sequence, line in enumerate(move_lines, start=1):
if line.move_id.sale_line_id:
unit_quantity = line.product_uom_id._compute_quantity(line.quantity, line.move_id.sale_line_id.product_uom)
else:
unit_quantity = line.product_uom_id._compute_quantity(line.quantity, line.product_id.uom_id)
rounded_qty = max(1, float_round(unit_quantity, precision_digits=0, rounding_method='HALF-UP'))
item = self.factory.ExportLineItem()
item.LineNumber = sequence
item.Quantity = int(rounded_qty)
item.QuantityUnit = 'PCS' # Pieces - very generic
if len(line.product_id.name) > 75:
raise UserError(_("DHL doesn't support products with name greater than 75 characters."))
item.Description = line.product_id.name
item.Value = float_repr(line.sale_price / rounded_qty, currency_id.decimal_places)
item.Weight = item.GrossWeight = {
"Weight": carrier._dhl_convert_weight(line.product_id.weight, carrier.dhl_package_weight_unit),
"WeightUnit": carrier.dhl_package_weight_unit,
}
item.ManufactureCountryCode = line.product_id.country_of_origin.code or line.picking_id.picking_type_id.warehouse_id.partner_id.country_id.code
if line.product_id.hs_code:
item.ImportCommodityCode = line.product_id.hs_code
item.CommodityCode = line.product_id.hs_code
export_lines.append(item)
export_declaration = self.factory.ExportDeclaration()
export_declaration.InvoiceDate = datetime.today()
export_declaration.InvoiceNumber = carrier.env['ir.sequence'].sudo().next_by_code('delivery_dhl.commercial_invoice')
if is_return:
export_declaration.ExportReason = 'RETURN'
export_declaration.ExportLineItem = export_lines
if picking.sale_id.client_order_ref:
export_declaration.ReceiverReference = picking.sale_id.client_order_ref
return export_declaration