Skip to content

Commit f5a58d8

Browse files
[IMP] Added Negotiated Rates & Unit Test Cases
1 parent 482f71e commit f5a58d8

File tree

8 files changed

+499
-41
lines changed

8 files changed

+499
-41
lines changed

delivery_ups_oca/README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ To configure this module, you need to:
6262
``ups`` delivery type and check the "Cash on Delivery" checkbox under the "UPS" tab.
6363
It is required to select the "UPS COD Funds Code" when the "Cash on Delivery" option
6464
is selected.
65+
#. The "Negotiated Rates" checkbox is enabled by default. When checked, UPS will use your
66+
account's negotiated rates for shipping cost calculations. If your UPS account does not
67+
have negotiated rates, you should uncheck this option.
6568
#. To enable insurance for your shipments, set the "Declared Value (%)" field in the
6669
"Insurance" section of the UPS tab. This percentage will be applied to the total
6770
value of the picking to determine the insurance amount. Set to 0 to disable insurance.

delivery_ups_oca/models/delivery_carrier.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ class DeliveryCarrier(models.Model):
103103
help="If the destination country is in one of these country groups, "
104104
"the paperless invoice option will be automatically enabled.",
105105
)
106+
ups_negotiated_rates = fields.Boolean(
107+
string="Negotiated Rates",
108+
default=True,
109+
help="If checked, UPS will use the account's negotiated rates for shipping.",
110+
)
106111

107112
def _ups_get_response_price(self, total_charges, currency, company):
108113
"""We need to convert the price if the currency is different."""

delivery_ups_oca/models/ups_request.py

Lines changed: 83 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {

delivery_ups_oca/readme/CONFIGURE.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ To configure this module, you need to:
1111
``ups`` delivery type and check the "Cash on Delivery" checkbox under the "UPS" tab.
1212
It is required to select the "UPS COD Funds Code" when the "Cash on Delivery" option
1313
is selected.
14+
#. The "Negotiated Rates" checkbox is enabled by default. When checked, UPS will use your
15+
account's negotiated rates for shipping cost calculations. If your UPS account does not
16+
have negotiated rates, you should uncheck this option.
1417
#. To enable insurance for your shipments, set the "Declared Value (%)" field in the
1518
"Insurance" section of the UPS tab. This percentage will be applied to the total
1619
value of the picking to determine the insurance amount. Set to 0 to disable insurance.

delivery_ups_oca/static/description/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ <h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
410410
<tt class="docutils literal">ups</tt> delivery type and check the “Cash on Delivery” checkbox under the “UPS” tab.
411411
It is required to select the “UPS COD Funds Code” when the “Cash on Delivery” option
412412
is selected.</li>
413+
<li>The “Negotiated Rates” checkbox is enabled by default. When checked, UPS will use your
414+
account’s negotiated rates for shipping cost calculations. If your UPS account does not
415+
have negotiated rates, you should uncheck this option.</li>
413416
<li>To enable insurance for your shipments, set the “Declared Value (%)” field in the
414417
“Insurance” section of the UPS tab. This percentage will be applied to the total
415418
value of the picking to determine the insurance amount. Set to 0 to disable insurance.</li>

delivery_ups_oca/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
from . import test_ups_insurance
55
from . import test_send_paperless_invoice
66
from . import test_residential_address
7+
from . import test_negotiated_rates

0 commit comments

Comments
 (0)