@@ -31,6 +31,7 @@ def __init__(self, carrier):
3131 self .client_secret = self .carrier .ups_client_secret
3232 self .token = self .carrier .ups_token
3333 self .token_expiration_date = self .carrier .ups_token_expiration_date
34+ self .negotiated_rates = self .carrier .ups_negotiated_rates
3435 self .url = "https://wwwcie.ups.com"
3536 if self .carrier .prod_environment :
3637 self .url = "https://onlinetools.ups.com"
@@ -266,12 +267,12 @@ def _add_insurance_to_package(
266267
267268 return package_item
268269
269- def _prepare_create_shipping (self , picking ):
270- """Return a dict that can be passed to the shipping endpoint of the UPS API"""
270+ def _prepare_packages_from_picking (self , picking ):
271+ """Prepare packages data from picking packages"""
272+ packages = []
271273
272274 if self .use_packages_from_picking and picking .package_ids :
273- # model: stock.quant.package
274- packages = []
275+ # Use actual packages from the picking
275276 for package in picking .package_ids :
276277 package_item = self ._quant_package_data_from_picking (
277278 package , picking , True
@@ -285,20 +286,22 @@ def _prepare_create_shipping(self, picking):
285286
286287 packages .append (package_item )
287288 else :
288- # model: stock.package.type
289- packages = []
289+ # Create packages based on default packaging
290290 package_info = self ._quant_package_data_from_picking (
291291 self .default_packaging_id , picking , False
292292 )
293- package_weight = 0
293+
294+ # Calculate package weight
294295 if picking .number_of_packages > 0 :
295296 package_weight = round (
296297 (picking .shipping_weight / picking .number_of_packages ), 2
297298 )
298299 else :
299300 package_weight = picking .shipping_weight
301+
300302 # Ensure at least one package is created
301303 num_packages = max (1 , picking .number_of_packages )
304+
302305 for i in range (0 , num_packages ):
303306 package_item = package_info .copy ()
304307 package_name = "%s (%s)" % (picking .name , i + 1 )
@@ -311,37 +314,11 @@ def _prepare_create_shipping(self, picking):
311314 package_item = self ._add_insurance_to_package (package_item , picking )
312315
313316 packages .append (package_item )
314- vals = {
315- "ShipmentRequest" : {
316- "Shipment" : {
317- "Description" : picking .name ,
318- "Shipper" : self ._partner_to_shipping_data (
319- partner = picking .company_id .partner_id ,
320- ShipperNumber = self .shipper_number ,
321- ),
322- "ShipTo" : self ._partner_to_shipping_data (picking .partner_id ),
323- "ShipFrom" : self ._partner_to_shipping_data (
324- picking .picking_type_id .warehouse_id .partner_id
325- or picking .company_id .partner_id
326- ),
327- "PaymentInformation" : {
328- "ShipmentCharge" : {
329- "Type" : "01" ,
330- "BillShipper" : {
331- # we ignore the alternatives paying per credit card or
332- # paypal for now
333- "AccountNumber" : self .shipper_number ,
334- },
335- }
336- },
337- "Service" : {"Code" : self .service_code },
338- "Package" : packages ,
339- },
340- "LabelSpecification" : self ._label_data (),
341- }
342- }
343317
344- # Add ShipmentServiceOptions if needed
318+ return packages
319+
320+ def _prepare_shipment_service_options (self , picking ):
321+ """Prepare shipment service options including COD and paperless invoice"""
345322 shipment_service_options = {}
346323
347324 # Add COD if enabled
@@ -375,11 +352,57 @@ def _prepare_create_shipping(self, picking):
375352 "UserCreatedForm" : {"DocumentID" : picking .document_id },
376353 }
377354
378- # Add ShipmentServiceOptions to the request if not empty
355+ return shipment_service_options
356+
357+ def _prepare_create_shipping (self , picking ):
358+ """Return a dict that can be passed to the shipping endpoint of the UPS API"""
359+ # Prepare packages
360+ packages = self ._prepare_packages_from_picking (picking )
361+
362+ # Build the base request structure
363+ vals = {
364+ "ShipmentRequest" : {
365+ "Shipment" : {
366+ "Description" : picking .name ,
367+ "Shipper" : self ._partner_to_shipping_data (
368+ partner = picking .company_id .partner_id ,
369+ ShipperNumber = self .shipper_number ,
370+ ),
371+ "ShipTo" : self ._partner_to_shipping_data (picking .partner_id ),
372+ "ShipFrom" : self ._partner_to_shipping_data (
373+ picking .picking_type_id .warehouse_id .partner_id
374+ or picking .company_id .partner_id
375+ ),
376+ "PaymentInformation" : {
377+ "ShipmentCharge" : {
378+ "Type" : "01" ,
379+ "BillShipper" : {
380+ # we ignore the alternatives paying per credit card or
381+ # paypal for now
382+ "AccountNumber" : self .shipper_number ,
383+ },
384+ }
385+ },
386+ "Service" : {"Code" : self .service_code },
387+ "Package" : packages ,
388+ },
389+ "LabelSpecification" : self ._label_data (),
390+ }
391+ }
392+
393+ # Add negotiated rates if enabled
394+ if self .negotiated_rates :
395+ vals ["ShipmentRequest" ]["Shipment" ]["ShipmentRatingOptions" ] = {
396+ "NegotiatedRatesIndicator" : "ABR"
397+ }
398+
399+ # Add shipment service options (COD, paperless invoice)
400+ shipment_service_options = self ._prepare_shipment_service_options (picking )
379401 if shipment_service_options :
380402 vals ["ShipmentRequest" ]["Shipment" ][
381403 "ShipmentServiceOptions"
382404 ] = shipment_service_options
405+
383406 return vals
384407
385408 def _send_shipping (self , picking ):
@@ -419,9 +442,14 @@ def _send_shipping(self, picking):
419442 "datas" : label ["ShippingLabel" ]["GraphicImage" ],
420443 }
421444 )
445+ # Use negotiated rates if available and enabled
446+ if self .negotiated_rates and "NegotiatedRates" in res :
447+ price = res ["NegotiatedRates" ]["TotalCharge" ]
448+ else :
449+ price = res ["ShipmentCharges" ]["TotalCharges" ]
422450
423451 return {
424- "price" : res [ "ShipmentCharges" ][ "TotalCharges" ] ,
452+ "price" : price ,
425453 "ShipmentIdentificationNumber" : res ["ShipmentIdentificationNumber" ],
426454 "labels" : labels ,
427455 }
@@ -448,7 +476,7 @@ def _quant_package_data_from_order(self, order):
448476
449477 def _prepare_rate_shipment (self , order ):
450478 packages = [self ._quant_package_data_from_order (order )]
451- return {
479+ rate_request = {
452480 "RateRequest" : {
453481 "Shipment" : {
454482 "Shipper" : self ._partner_to_shipping_data (
@@ -465,6 +493,14 @@ def _prepare_rate_shipment(self, order):
465493 }
466494 }
467495
496+ # Add negotiated rates if enabled
497+ if self .negotiated_rates :
498+ rate_request ["RateRequest" ]["Shipment" ]["ShipmentRatingOptions" ] = {
499+ "NegotiatedRatesIndicator" : "ABR"
500+ }
501+
502+ return rate_request
503+
468504 def _rate_shipment (self , order , skip_errors = False ):
469505 status = self ._process_reply (
470506 url = "%s/api/rating/v1/Rate" % self .url ,
@@ -475,7 +511,13 @@ def _rate_shipment(self, order, skip_errors=False):
475511
476512 def rate_shipment (self , order ):
477513 status = self ._rate_shipment (order )
478- return status ["RateResponse" ]["RatedShipment" ]["TotalCharges" ]
514+ rated_shipment = status ["RateResponse" ]["RatedShipment" ]
515+
516+ # Use negotiated rates if available and enabled
517+ if self .negotiated_rates and "NegotiatedRateCharges" in rated_shipment :
518+ return rated_shipment ["NegotiatedRateCharges" ]["TotalCharge" ]
519+
520+ return rated_shipment ["TotalCharges" ]
479521
480522 def _prepare_shipping_label (self , carrier_tracking_ref ):
481523 return {
0 commit comments