mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[IMP] delivery_puralator: refactor multi to use full rating,
Additionally, use weight or volume package type finder Refactor single use API (purolator_rate_shipment) to use the multi API (_purolator_rate_shipment_multi_package) and find itself.
This commit is contained in:
@@ -93,48 +93,26 @@ class ProviderPurolator(models.Model):
|
|||||||
return weight_uom_id._compute_quantity(weight, self.env.ref('uom.product_uom_lb'), round=False)
|
return weight_uom_id._compute_quantity(weight, self.env.ref('uom.product_uom_lb'), round=False)
|
||||||
|
|
||||||
def purolator_convert_length(self, length):
|
def purolator_convert_length(self, length):
|
||||||
volume_uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter()
|
raise Exception('Not implemented. Need to do math on UOM to convert less dimensions')
|
||||||
return weight_uom_id._compute_quantity(weight, self.env.ref('uom.product_uom_lb'), round=False)
|
volume_uom_id = self.env['product.template']._get_volume_uom_id_from_ir_config_parameter()
|
||||||
|
return volume_uom_id._compute_quantity(weight, self.env.ref('uom.product_uom_lb'), round=False)
|
||||||
|
|
||||||
def purolator_rate_shipment(self, order):
|
def purolator_rate_shipment(self, order, downgrade_response=True):
|
||||||
# sudoself = self.sudo()
|
multi_res = self._purolator_rate_shipment_multi_package(order=order)
|
||||||
third_party = self.purolator_third_party(order=order)
|
for res in multi_res:
|
||||||
sender = self.get_shipper_warehouse(order=order)
|
if res.get('carrier') == self:
|
||||||
receiver = self.get_recipient(order=order)
|
if downgrade_response:
|
||||||
receiver_address = {
|
return {
|
||||||
'City': receiver.city,
|
'success': True,
|
||||||
'Province': receiver.state_id.code,
|
'price': res.get('price', 0.0),
|
||||||
'Country': receiver.country_id.code,
|
'error_message': False,
|
||||||
'PostalCode': receiver.zip,
|
'warning_message': False,
|
||||||
}
|
}
|
||||||
# TODO packaging volume/length/width/height
|
return res
|
||||||
weight = self.purolator_convert_weight(order._get_estimated_weight())
|
|
||||||
service = self._purolator_service()
|
|
||||||
res = service.get_quick_estimate(
|
|
||||||
sender.zip,
|
|
||||||
receiver_address,
|
|
||||||
self.purolator_default_package_type_id.shipper_package_code,
|
|
||||||
weight,
|
|
||||||
)
|
|
||||||
if res['error']:
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'price': 0.0,
|
|
||||||
'error_message': _(res['error']),
|
|
||||||
'warning_message': False,
|
|
||||||
}
|
|
||||||
shipment = list(filter(lambda s: s['ServiceID'] == self.purolator_service_type, res['shipments']))
|
|
||||||
if not shipment:
|
|
||||||
return {
|
|
||||||
'success': False,
|
|
||||||
'price': 0.0,
|
|
||||||
'error_message': _('No rate found matching service: %s') % self.purolator_service_type,
|
|
||||||
'warning_message': False,
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': False,
|
||||||
'price': shipment[0]['TotalPrice'] if not third_party else 0.0,
|
'price': 0.0,
|
||||||
'error_message': False,
|
'error_message': _('No rate found matching service: %s') % self.purolator_service_type,
|
||||||
'warning_message': False,
|
'warning_message': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,45 +125,71 @@ class ProviderPurolator(models.Model):
|
|||||||
rates += self._purolator_rate_shipment_multi_package(order=order, picking=picking, package=package)
|
rates += self._purolator_rate_shipment_multi_package(order=order, picking=picking, package=package)
|
||||||
return rates
|
return rates
|
||||||
|
|
||||||
|
def _purolator_format_errors(self, response_body, raise_class=None):
|
||||||
|
errors = response_body.ResponseInformation.Errors
|
||||||
|
if errors:
|
||||||
|
errors = errors.Error # unpack container node
|
||||||
|
puro_errors = '\n\n'.join(['%s - %s - %s' % (e.Code, e.AdditionalInformation, e.Description) for e in errors])
|
||||||
|
if raise_class:
|
||||||
|
raise raise_class(_('Error(s) during Purolator Request:\n%s') % (puro_errors, ))
|
||||||
|
return puro_errors
|
||||||
|
|
||||||
|
def _purolator_shipment_fill_payor(self, request, picking=None, order=None):
|
||||||
|
request.PaymentInformation.PaymentType = 'Sender'
|
||||||
|
request.PaymentInformation.RegisteredAccountNumber = self.purolator_account_number
|
||||||
|
request.PaymentInformation.BillingAccountNumber = self.purolator_account_number
|
||||||
|
third_party_account = self.purolator_third_party(picking=picking, order=order)
|
||||||
|
# when would it be 'Receiver' ?
|
||||||
|
if third_party_account:
|
||||||
|
request.PaymentInformation.PaymentType = 'ThirdParty'
|
||||||
|
request.PaymentInformation.BillingAccountNumber = third_party_account
|
||||||
|
|
||||||
def _purolator_rate_shipment_multi_package(self, order=None, picking=None, package=None):
|
def _purolator_rate_shipment_multi_package(self, order=None, picking=None, package=None):
|
||||||
|
service = self._purolator_service()
|
||||||
third_party = self.purolator_third_party(order=order, picking=picking)
|
third_party = self.purolator_third_party(order=order, picking=picking)
|
||||||
sender = self.get_shipper_warehouse(order=order, picking=picking)
|
sender = self.get_shipper_warehouse(order=order, picking=picking)
|
||||||
receiver = self.get_recipient(order=order, picking=picking)
|
receiver = self.get_recipient(order=order, picking=picking)
|
||||||
receiver_address = {
|
|
||||||
'City': receiver.city,
|
|
||||||
'Province': receiver.state_id.code,
|
|
||||||
'Country': receiver.country_id.code,
|
|
||||||
'PostalCode': receiver.zip,
|
|
||||||
}
|
|
||||||
weight_uom_id = self.env['product.template']._get_weight_uom_id_from_ir_config_parameter()
|
|
||||||
volume_uom_id = self.env['product.template']._get_volume_uom_id_from_ir_config_parameter()
|
|
||||||
|
|
||||||
date_planned = fields.Datetime.now()
|
date_planned = fields.Date.today()
|
||||||
if self.env.context.get('date_planned'):
|
if self.env.context.get('date_planned'):
|
||||||
date_planned = self.env.context.get('date_planned')
|
date_planned = self.env.context.get('date_planned')
|
||||||
|
if hasattr(date_planned, 'date'):
|
||||||
|
# this should be a datetime
|
||||||
|
date_planned = date_planned.date()
|
||||||
|
|
||||||
|
# create SOAP request to fill in
|
||||||
|
shipment = service.estimate_shipment_request()
|
||||||
|
# request getting more than one service back
|
||||||
|
shipment.ShowAlternativeServicesIndicator = "true"
|
||||||
|
# indicate when we will ship this for time in transit
|
||||||
|
shipment.ShipmentDate = str(date_planned)
|
||||||
|
|
||||||
|
# populate origin information
|
||||||
|
self._purolator_fill_address(shipment.SenderInformation.Address, sender)
|
||||||
|
# populate destination
|
||||||
|
self._purolator_fill_address(shipment.ReceiverInformation.Address, receiver)
|
||||||
|
|
||||||
# TODO need packaging volume/dimensions
|
|
||||||
package_code = self.purolator_default_package_type_id.shipper_package_code
|
|
||||||
if order:
|
if order:
|
||||||
weight = order._get_estimated_weight()
|
service.estimate_shipment_add_sale_order_packages(shipment, self, order)
|
||||||
else:
|
else:
|
||||||
if package:
|
service.estimate_shipment_add_picking_packages(shipment, self, picking, package)
|
||||||
weight = package.shipping_weight
|
|
||||||
package_code = package.package_type_id.shipper_package_code if package.package_type_id.package_carrier_type == 'purolator' else package_code
|
self._purolator_shipment_fill_payor(shipment, order=order, picking=picking)
|
||||||
else:
|
|
||||||
weight = picking.shipping_weight or picking.weight
|
shipment_res = service.get_full_estimate(shipment)
|
||||||
weight = self.purolator_convert_weight(weight)
|
|
||||||
service = self._purolator_service()
|
# _logger.info('_purolator_rate_shipment_multi_package called with shipment %s result %s' % (shipment, shipment_res))
|
||||||
res = service.get_quick_estimate(sender.zip, receiver_address, package_code, weight)
|
|
||||||
if res['error']:
|
errors = self._purolator_format_errors(shipment_res)
|
||||||
|
if errors:
|
||||||
return [{'carrier': self,
|
return [{'carrier': self,
|
||||||
'success': False,
|
'success': False,
|
||||||
'price': 0.0,
|
'price': 0.0,
|
||||||
'error_message': _('Error:\n%s') % res['error'],
|
'error_message': '\n'.join(errors),
|
||||||
'warning_message': False,
|
'warning_message': False,
|
||||||
}]
|
}]
|
||||||
rates = []
|
rates = []
|
||||||
for shipment in res['shipments']:
|
for shipment in shipment_res.ShipmentEstimates.ShipmentEstimate:
|
||||||
carrier = self.purolator_find_delivery_carrier_for_service(shipment['ServiceID'])
|
carrier = self.purolator_find_delivery_carrier_for_service(shipment['ServiceID'])
|
||||||
if carrier:
|
if carrier:
|
||||||
price = shipment['TotalPrice']
|
price = shipment['TotalPrice']
|
||||||
@@ -297,24 +301,14 @@ class ProviderPurolator(models.Model):
|
|||||||
# $request->Shipment->PackageInformation->OptionsInformation->Options->OptionIDValuePair->ID = "ResidentialSignatureDomestic";
|
# $request->Shipment->PackageInformation->OptionsInformation->Options->OptionIDValuePair->ID = "ResidentialSignatureDomestic";
|
||||||
# $request->Shipment->PackageInformation->OptionsInformation->Options->OptionIDValuePair->Value = "true";
|
# $request->Shipment->PackageInformation->OptionsInformation->Options->OptionIDValuePair->Value = "true";
|
||||||
|
|
||||||
shipment.PaymentInformation.PaymentType = 'Sender'
|
self._purolator_shipment_fill_payor(shipment, picking=picking)
|
||||||
shipment.PaymentInformation.RegisteredAccountNumber = self.purolator_account_number
|
|
||||||
shipment.PaymentInformation.BillingAccountNumber = self.purolator_account_number
|
|
||||||
third_party_account = self.purolator_third_party(picking=picking)
|
|
||||||
# when would it be 'Receiver' ?
|
|
||||||
if third_party_account:
|
|
||||||
shipment.PaymentInformation.PaymentType = 'ThirdParty'
|
|
||||||
shipment.PaymentInformation.BillingAccountNumber = third_party_account
|
|
||||||
|
|
||||||
shipment_res = service.shipment_create(shipment,
|
shipment_res = service.shipment_create(shipment,
|
||||||
printer_type=('Regular' if self.purolator_label_file_type == 'PDF' else 'Thermal'))
|
printer_type=('Regular' if self.purolator_label_file_type == 'PDF' else 'Thermal'))
|
||||||
# _logger.info('purolator service.shipment_create for shipment %s resulted in %s' % (shipment, shipment_res))
|
# _logger.info('purolator service.shipment_create for shipment %s resulted in %s' % (shipment, shipment_res))
|
||||||
|
|
||||||
errors = shipment_res.ResponseInformation.Errors
|
# this will raise an error alerting the user if there is an error, and no more
|
||||||
if errors:
|
self._purolator_format_errors(shipment_res, raise_class=UserError)
|
||||||
errors = errors.Error # unpack container node
|
|
||||||
puro_errors = '\n\n'.join(['%s - %s - %s' % (e.Code, e.AdditionalInformation, e.Description) for e in errors])
|
|
||||||
raise UserError(_('Error(s) during Purolator Shipment Request:\n%s') % (puro_errors, ))
|
|
||||||
|
|
||||||
document_blobs = []
|
document_blobs = []
|
||||||
shipment_pin = shipment_res.ShipmentPIN.Value
|
shipment_pin = shipment_res.ShipmentPIN.Value
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from math import ceil
|
||||||
from requests import Session
|
from requests import Session
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
from zeep import Client
|
from zeep import Client
|
||||||
@@ -81,7 +82,14 @@ class PurolatorClient(object):
|
|||||||
UserToken=self.activation_key,
|
UserToken=self.activation_key,
|
||||||
)
|
)
|
||||||
client.set_default_soapheaders([header_value])
|
client.set_default_soapheaders([header_value])
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
def get_full_estimate(self, shipment, show_alternative_services='true'):
|
||||||
|
response = self.estimating_client.service.GetFullEstimate(
|
||||||
|
Shipment=shipment,
|
||||||
|
ShowAlternativeServicesIndicator=show_alternative_services,
|
||||||
|
)
|
||||||
|
return response.body
|
||||||
|
|
||||||
def get_quick_estimate(self, sender_postal_code, receiver_address, package_type, total_weight):
|
def get_quick_estimate(self, sender_postal_code, receiver_address, package_type, total_weight):
|
||||||
""" Call GetQuickEstimate
|
""" Call GetQuickEstimate
|
||||||
@@ -123,26 +131,83 @@ class PurolatorClient(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def shipment_request(self):
|
def shipment_request(self):
|
||||||
shipment = self.shipping_factory.Shipment()
|
return self._shipment_request(self.shipping_factory)
|
||||||
shipment.SenderInformation = self.shipping_factory.SenderInformation()
|
|
||||||
shipment.SenderInformation.Address = self.shipping_factory.Address()
|
# just like above, but using estimate api
|
||||||
shipment.SenderInformation.Address.PhoneNumber = self.shipping_factory.PhoneNumber()
|
def estimate_shipment_request(self):
|
||||||
shipment.ReceiverInformation = self.shipping_factory.ReceiverInformation()
|
return self._shipment_request(self.estimating_factory)
|
||||||
shipment.ReceiverInformation.Address = self.shipping_factory.Address()
|
|
||||||
shipment.ReceiverInformation.Address.PhoneNumber = self.shipping_factory.PhoneNumber()
|
def _shipment_request(self, factory):
|
||||||
shipment.PackageInformation = self.shipping_factory.PackageInformation()
|
shipment = factory.Shipment()
|
||||||
shipment.PackageInformation.TotalWeight = self.shipping_factory.TotalWeight()
|
shipment.SenderInformation = factory.SenderInformation()
|
||||||
shipment.PackageInformation.PiecesInformation = self.shipping_factory.ArrayOfPiece()
|
shipment.SenderInformation.Address = factory.Address()
|
||||||
shipment.PaymentInformation = self.shipping_factory.PaymentInformation()
|
shipment.SenderInformation.Address.PhoneNumber = factory.PhoneNumber()
|
||||||
|
shipment.ReceiverInformation = factory.ReceiverInformation()
|
||||||
|
shipment.ReceiverInformation.Address = factory.Address()
|
||||||
|
shipment.ReceiverInformation.Address.PhoneNumber = factory.PhoneNumber()
|
||||||
|
shipment.PackageInformation = factory.PackageInformation()
|
||||||
|
shipment.PackageInformation.TotalWeight = factory.TotalWeight()
|
||||||
|
shipment.PackageInformation.PiecesInformation = factory.ArrayOfPiece()
|
||||||
|
shipment.PaymentInformation = factory.PaymentInformation()
|
||||||
return shipment
|
return shipment
|
||||||
|
|
||||||
def shipment_add_picking_packages(self, shipment, carrier, picking, packages):
|
def estimate_shipment_add_sale_order_packages(self, shipment, carrier, order):
|
||||||
|
# this could be a non-purolator package type as returned by the search functions
|
||||||
|
package_type = carrier.get_package_type_for_order(order)
|
||||||
|
shipment.PackageInformation.ServiceID = carrier.purolator_service_type
|
||||||
|
weight = carrier.purolator_convert_weight(order._get_estimated_weight())
|
||||||
|
package_type_max_weight = 0.0
|
||||||
|
if package_type.max_weight:
|
||||||
|
package_type_max_weight = carrier.purolator_convert_weight(package_type.max_weight)
|
||||||
|
|
||||||
|
if package_type_max_weight and weight > package_type_max_weight:
|
||||||
|
total_pieces = ceil(weight / package_type_max_weight)
|
||||||
|
package_weight = weight / total_pieces
|
||||||
|
else:
|
||||||
|
total_pieces = 1
|
||||||
|
package_weight = weight
|
||||||
|
|
||||||
|
if package_weight < 1.0:
|
||||||
|
package_weight = 1.0
|
||||||
|
|
||||||
|
total_weight_value = package_weight * total_pieces
|
||||||
|
for _i in range(total_pieces):
|
||||||
|
p = self.estimating_factory.Piece(
|
||||||
|
Weight={
|
||||||
|
'Value': str(package_weight),
|
||||||
|
'WeightUnit': 'lb',
|
||||||
|
},
|
||||||
|
Length={
|
||||||
|
'Value': str(package_type.packaging_length), # TODO need conversion
|
||||||
|
'DimensionUnit': 'in',
|
||||||
|
},
|
||||||
|
Width={
|
||||||
|
'Value': str(package_type.width), # TODO need conversion
|
||||||
|
'DimensionUnit': 'in',
|
||||||
|
},
|
||||||
|
Height={
|
||||||
|
'Value': str(package_type.height), # TODO need conversion
|
||||||
|
'DimensionUnit': 'in',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
shipment.PackageInformation.PiecesInformation.Piece.append(p)
|
||||||
|
shipment.PackageInformation.TotalWeight.Value = str(weight)
|
||||||
|
shipment.PackageInformation.TotalWeight.WeightUnit = 'lb'
|
||||||
|
shipment.PackageInformation.TotalPieces = str(total_pieces)
|
||||||
|
|
||||||
|
def estimate_shipment_add_picking_packages(self, shipment, carrier, picking, packages):
|
||||||
|
return self._shipment_add_picking_packages(self.estimating_factory, shipment, carrier, picking, packages)
|
||||||
|
|
||||||
|
def shipment_add_picking_packages(self, shipment, carrier, picking, packages):
|
||||||
|
return self._shipment_add_picking_packages(self.shipping_factory, shipment, carrier, picking, packages)
|
||||||
|
|
||||||
|
def _shipment_add_picking_packages(self, factory, shipment, carrier, picking, packages):
|
||||||
# note that no package can be less than 1lb, so we fix that here...
|
# note that no package can be less than 1lb, so we fix that here...
|
||||||
# for the package to be allowed, it must be the same service
|
# for the package to be allowed, it must be the same service
|
||||||
shipment.PackageInformation.ServiceID = carrier.purolator_service_type
|
shipment.PackageInformation.ServiceID = carrier.purolator_service_type
|
||||||
|
|
||||||
total_weight_value = 0.0
|
total_weight_value = 0.0
|
||||||
total_pieces = len(packages) or 1
|
total_pieces = len(packages or []) or 1
|
||||||
if not packages:
|
if not packages:
|
||||||
# setup default package
|
# setup default package
|
||||||
package_weight = carrier.purolator_convert_weight(picking.shipping_weight)
|
package_weight = carrier.purolator_convert_weight(picking.shipping_weight)
|
||||||
@@ -150,7 +215,7 @@ class PurolatorClient(object):
|
|||||||
package_weight = 1.0
|
package_weight = 1.0
|
||||||
total_weight_value += package_weight
|
total_weight_value += package_weight
|
||||||
package_type = carrier.purolator_default_package_type_id
|
package_type = carrier.purolator_default_package_type_id
|
||||||
p = self.shipping_factory.Piece(
|
p = factory.Piece(
|
||||||
Weight={
|
Weight={
|
||||||
'Value': str(package_weight),
|
'Value': str(package_weight),
|
||||||
'WeightUnit': 'lb',
|
'WeightUnit': 'lb',
|
||||||
@@ -176,7 +241,7 @@ class PurolatorClient(object):
|
|||||||
package_weight = 1.0
|
package_weight = 1.0
|
||||||
package_type = package.package_type_id
|
package_type = package.package_type_id
|
||||||
total_weight_value += package_weight
|
total_weight_value += package_weight
|
||||||
p = self.shipping_factory.Piece(
|
p = factory.Piece(
|
||||||
Weight={
|
Weight={
|
||||||
'Value': str(package_weight),
|
'Value': str(package_weight),
|
||||||
'WeightUnit': 'lb',
|
'WeightUnit': 'lb',
|
||||||
|
|||||||
Reference in New Issue
Block a user