From f7a8d1df1106f204919ec3c3708f28e69c5dae16 Mon Sep 17 00:00:00 2001 From: Dan Kobina Date: Sun, 18 Nov 2018 07:05:12 -0500 Subject: [PATCH 1/5] fix DHL quote mappers missed changes for services --- purplship/mappers/dhl/dhl_mapper/partials/rate.py | 11 +++++++---- tests/dhl/quote.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/purplship/mappers/dhl/dhl_mapper/partials/rate.py b/purplship/mappers/dhl/dhl_mapper/partials/rate.py index 7f64868003..b08972b111 100644 --- a/purplship/mappers/dhl/dhl_mapper/partials/rate.py +++ b/purplship/mappers/dhl/dhl_mapper/partials/rate.py @@ -58,7 +58,10 @@ def _extract_quote(self, quotes: List[T.QuoteDetails], qtdshpNode: 'XMLElement') def create_dct_request(self, payload: T.shipment_request) -> Req.DCTRequest: default_product_code = Product.EXPRESS_WORLDWIDE_DOC if payload.shipment.is_document else Product.EXPRESS_WORLDWIDE - product_code = Product[payload.shipment.services] if payload.shipment.services != None else default_product_code + products = ( + [Product[svc] for svc in payload.shipment.services if svc in Product.__members__] + + [default_product_code] + ) is_dutiable = payload.shipment.declared_value != None default_packaging_type = DCTPackageType.SM if payload.shipment.is_document else DCTPackageType.BOX options = ( @@ -114,15 +117,15 @@ def create_dct_request(self, payload: T.shipment_request) -> Req.DCTRequest: AcctPickupCloseTime=payload.shipment.extra.get('AcctPickupCloseTime'), QtdShp=[ ReqType.QtdShpType( - GlobalProductCode=product_code.value, - LocalProductCode=product_code.value, + GlobalProductCode=product.value, + LocalProductCode=product.value, QtdShpExChrg=[ ReqType.QtdShpExChrgType( SpecialServiceType=svc.value, LocalSpecialServiceType=None ) for svc in options ] if len(options) > 0 else None - ) + ) for product in products ] ), Dutiable=ReqType.DCTDutiable( diff --git a/tests/dhl/quote.py b/tests/dhl/quote.py index b961fb0877..4387855d15 100644 --- a/tests/dhl/quote.py +++ b/tests/dhl/quote.py @@ -237,8 +237,8 @@ def test_parse_quote_vol_weight_higher_response(self): Y - D - D + P + P II From f617ea0d4db4529828808ce2ed978f23cb2a5d29 Mon Sep 17 00:00:00 2001 From: Dan Kobina Date: Sun, 18 Nov 2018 07:09:04 -0500 Subject: [PATCH 2/5] fix DHL shipment services definitions for product code identification --- purplship/mappers/dhl/dhl_mapper/partials/shipment.py | 9 ++++++--- tests/dhl/shipment.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/purplship/mappers/dhl/dhl_mapper/partials/shipment.py b/purplship/mappers/dhl/dhl_mapper/partials/shipment.py index e706d2e9c9..6308f8ee3f 100644 --- a/purplship/mappers/dhl/dhl_mapper/partials/shipment.py +++ b/purplship/mappers/dhl/dhl_mapper/partials/shipment.py @@ -59,7 +59,10 @@ def _extract_shipment(self, shipmentResponseNode) -> T.ShipmentDetails: def create_dhlshipment_request(self, payload: T.shipment_request) -> ShipReq.ShipmentRequest: is_dutiable = payload.shipment.declared_value != None default_product_code = Product.EXPRESS_WORLDWIDE_DOC if payload.shipment.is_document else Product.EXPRESS_WORLDWIDE - product_code = Product[payload.shipment.services] if payload.shipment.services != None else default_product_code + product = ( + [Product[svc] for svc in payload.shipment.services if svc in Product.__members__] + + [default_product_code] + )[0] default_packaging_type = PackageType.Document if payload.shipment.is_document else PackageType.Your_packaging options = ( [Service[svc] for svc in payload.shipment.options if svc in Service.__members__] + @@ -167,8 +170,8 @@ def create_dhlshipment_request(self, payload: T.shipment_request) -> ShipReq.Shi IsDutiable= "Y" if is_dutiable else "N", InsuredAmount=payload.shipment.insured_amount, DoorTo=payload.shipment.extra.get('DoorTo'), - GlobalProductCode=product_code.value, - LocalProductCode=product_code.value, + GlobalProductCode=product.value, + LocalProductCode=product.value, Contents=payload.shipment.extra.get('Contents') or "..." ), EProcShip=payload.shipment.extra.get('EProcShip'), diff --git a/tests/dhl/shipment.py b/tests/dhl/shipment.py index a5472da802..d7cc1cd015 100644 --- a/tests/dhl/shipment.py +++ b/tests/dhl/shipment.py @@ -469,7 +469,7 @@ def test_parse_shipment_response(self): "duty_payment_account": "123456789", "declared_value": 200.00, "options": ["Paperless_Trade"], - "services": "EXPRESS_WORLDWIDE", + "services": ["EXPRESS_WORLDWIDE"], "doc_images": [ { "type": "CIN", From 78d20448a3f1211276fe4a0d1380a5292e7f4ae7 Mon Sep 17 00:00:00 2001 From: Dan Kobina Date: Tue, 20 Nov 2018 06:20:58 -0500 Subject: [PATCH 3/5] implemented missing ups package rate response parser and fix Description code types --- .../mappers/ups/ups_mapper/partials/rate.py | 57 ++++++++++++------- purplship/mappers/ups/ups_proxy.py | 2 +- tests/ups/quote.py | 41 +++++++++++-- 3 files changed, 75 insertions(+), 25 deletions(-) diff --git a/purplship/mappers/ups/ups_mapper/partials/rate.py b/purplship/mappers/ups/ups_mapper/partials/rate.py index 2cc75c2ec6..aa89879cfd 100644 --- a/purplship/mappers/ups/ups_mapper/partials/rate.py +++ b/purplship/mappers/ups/ups_mapper/partials/rate.py @@ -44,29 +44,48 @@ def _extract_freight_rate(self, rates: List[T.QuoteDetails], detailNode: 'XMLEle ] def parse_package_rate_response(self, response: 'XMLElement') -> Tuple[List[T.QuoteDetails], List[T.Error]]: - rate_replys = response.xpath('.//*[local-name() = $name]', name="RateResponse") + rate_replys = response.xpath('.//*[local-name() = $name]', name="RatedShipment") rates = reduce(self._extract_package_rate, rate_replys, []) return (rates, self.parse_error_response(response)) def _extract_package_rate(self, rates: List[T.QuoteDetails], detailNode: 'XMLElement') -> List[T.QuoteDetails]: - detail = PRate.RateResponse() - detail.build(detailNode) + rate = PRate.RatedShipmentType() + rate.build(detailNode) - total_charge = [r for r in detail.Rate if r.Type.Code == 'AFTR_DSCNT'][0] - Discounts_ = [T.ChargeDetails(name=r.Type.Code, currency=r.Factor.UnitOfMeasurement.Code, amount=float(r.Factor.Value)) for r in detail.Rate if r.Type.Code == 'DSCNT'] - Surcharges_ = [T.ChargeDetails(name=r.Type.Code, currency=r.Factor.UnitOfMeasurement.Code, amount=float(r.Factor.Value)) for r in detail.Rate if r.Type.Code not in ['DSCNT', 'AFTR_DSCNT', 'DSCNT_RATE', 'LND_GROSS']] - extra_charges = Discounts_ + Surcharges_ + if rate.NegotiatedRateCharges != None: + total_charges = rate.NegotiatedRateCharges.TotalChargesWithTaxes or rate.NegotiatedRateCharges.TotalCharge + taxes = rate.NegotiatedRateCharges.TaxCharges + itemized_charges = rate.NegotiatedRateCharges.ItemizedCharges + taxes + else: + total_charges = rate.TotalChargesWithTaxes or rate.TotalCharges + taxes = rate.TaxCharges + itemized_charges = rate.ItemizedCharges + taxes + + extra_charges = itemized_charges + [ rate.ServiceOptionsCharges ] + return rates + [ T.QuoteDetails( carrier=self.client.carrier_name, - currency=detail.TotalShipmentCharge.CurrencyCode, - service_name=detail.Service.Description, - service_type=detail.Service.Code, - base_charge=float(detail.TotalShipmentCharge.MonetaryValue), - total_charge=float(total_charge.Factor.Value or 0), - duties_and_taxes=reduce(lambda r, c: r + c.amount, Surcharges_, 0), - discount=reduce(lambda r, c: r + c.amount, Discounts_, 0), - extra_charges=extra_charges + currency=rate.TransportationCharges.CurrencyCode, + service_name=rate.Service.Description, + service_type=rate.Service.Code, + base_charge=float(rate.TransportationCharges.MonetaryValue), + total_charge=float(total_charges.MonetaryValue), + duties_and_taxes=reduce( + lambda total, charge: total + float(charge.MonetaryValue), + taxes or [], + 0 + ), + discount=None, + extra_charges=reduce( + lambda total, charge: total + [T.ChargeDetails( + name=charge.Code, + amount=float(charge.MonetaryValue), + currency=charge.CurrencyCode + )], + [charge for charge in extra_charges if charge != None], + [] + ) ) ] @@ -254,18 +273,18 @@ def create_package_rate_request(self, payload: T.shipment_request) -> PRate.Rate FRSPaymentInformation=None, FreightShipmentInformation=None, GoodsNotInFreeCirculationIndicator=None, - Service=PRate.CodeDescriptionType(Code=service.value, Description=None), + Service=PRate.UOMCodeDescriptionType(Code=service.value, Description=None), NumOfPieces=payload.shipment.total_items, ShipmentTotalWeight=payload.shipment.total_weight, DocumentsOnlyIndicator="" if payload.shipment.is_document else None, Package=[ PRate.PackageType( - PackagingType=PRate.CodeDescriptionType( + PackagingType=PRate.UOMCodeDescriptionType( Code=RatingPackagingType[pkg.packaging_type or "BOX"].value, Description=None ), Dimensions=PRate.DimensionsType( - UnitOfMeasurement=PRate.CodeDescriptionType( + UnitOfMeasurement=PRate.UOMCodeDescriptionType( Code=DimensionUnit[payload.shipment.dimension_unit].value, Description=None ), @@ -275,7 +294,7 @@ def create_package_rate_request(self, payload: T.shipment_request) -> PRate.Rate ), DimWeight=pkg.extra.get('DimWeight'), PackageWeight=PRate.PackageWeightType( - UnitOfMeasurement=PRate.CodeDescriptionType( + UnitOfMeasurement=PRate.UOMCodeDescriptionType( Code=WeightUnit[payload.shipment.weight_unit].value, Description=None ), diff --git a/purplship/mappers/ups/ups_proxy.py b/purplship/mappers/ups/ups_proxy.py index 645cf09537..7b4de841c5 100644 --- a/purplship/mappers/ups/ups_proxy.py +++ b/purplship/mappers/ups/ups_proxy.py @@ -57,7 +57,7 @@ def get_quotes(self, RateRequest_: Union[RateRequest, FreightRateRequest]) -> "X header_child_name='UPSSecurity', body_child_name=body_child_name_, body_child_prefix=body_child_prefix_ - ) + ).replace('common:Code', 'rate:Code') result = http( url=url_, data=bytearray(xmlStr, "utf-8"), diff --git a/tests/ups/quote.py b/tests/ups/quote.py index fd362eecaa..2d84fb1783 100644 --- a/tests/ups/quote.py +++ b/tests/ups/quote.py @@ -20,7 +20,10 @@ def setUp(self): def test_create_quote_request(self): payload = Quote.create(**rate_req_data) RateRequest_ = proxy.mapper.create_quote_request(payload) - self.assertEqual(export(RateRequest_), export(self.RateRequest)) + self.assertEqual( + export(RateRequest_), + export(self.RateRequest).replace('common:Code', 'rate:Code') + ) def test_create_freight_quote_request(self): shipper = { @@ -53,6 +56,12 @@ def test_parse_quote_response(self): to_xml(FreightRateResponseXML)) self.assertEqual(jsonify(parsed_response), jsonify(ParsedFreightRateResponse)) + + def test_parse_package_quote_response(self): + parsed_response = proxy.mapper.parse_quote_response( + to_xml(RateResponseXML)) + self.assertEqual(jsonify(parsed_response), + jsonify(ParsedRateResponse)) def test_parse_quote_error(self): parsed_response = proxy.mapper.parse_quote_response( @@ -139,6 +148,28 @@ def test_parse_quote_missing_args_error(self): [] ] +ParsedRateResponse = [ + [ + { + 'base_charge': 9.86, + 'carrier': 'UPS', + 'currency': 'USD', + 'delivery_date': None, + 'discount': None, + 'duties_and_taxes': 0, + 'extra_charges': [ + {'amount': 0.0, 'currency': 'USD', 'name': None} + ], + 'pickup_date': None, + 'pickup_time': None, + 'service_name': '', + 'service_type': '03', + 'total_charge': 9.86 + } + ], + [] +] + QuoteParsingError = """ @@ -505,15 +536,15 @@ def test_parse_quote_missing_args_error(self): - 03 + 03 - 02 + 02 - IN + IN 10 3 @@ -521,7 +552,7 @@ def test_parse_quote_missing_args_error(self): - LBS + LBS 4.0 From 2bebf20aa8004a537d3b37d1d4c3ceed89f98633 Mon Sep 17 00:00:00 2001 From: Dan Kobina Date: Tue, 20 Nov 2018 07:03:12 -0500 Subject: [PATCH 4/5] compute if payment details is provided --- purplship/mappers/ups/ups_mapper/partials/rate.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/purplship/mappers/ups/ups_mapper/partials/rate.py b/purplship/mappers/ups/ups_mapper/partials/rate.py index aa89879cfd..0f4c951453 100644 --- a/purplship/mappers/ups/ups_mapper/partials/rate.py +++ b/purplship/mappers/ups/ups_mapper/partials/rate.py @@ -186,6 +186,11 @@ def create_package_rate_request(self, payload: T.shipment_request) -> PRate.Rate [RatingServiceCode[svc] for svc in payload.shipment.services if svc in RatingServiceCode.__members__] + [RatingServiceCode.UPS_Worldwide_Express] )[0] + payment_details_provided = ( + all((payload.shipment.paid_by, payload.shipment.payment_account_number)) or + (payload.shipment.paid_by == 'SENDER' and payload.shipper.account_number != None) or + (payload.shipment.paid_by == 'RECIPIENT' and payload.recipient.account_number != None) + ) return PRate.RateRequest( Request=Common.RequestType( RequestOption=payload.shipment.extra.get('RequestOption') or ["Rate"], @@ -254,7 +259,7 @@ def create_package_rate_request(self, payload: T.shipment_request) -> PRate.Rate AccountNumber=payload.shipment.payment_account_number or payload.shipper.account_number ) if payload.shipment.paid_by == 'SENDER' else None, BillReceiver=PRate.BillReceiverChargeType( - AccountNumber=payload.recipient.account_number, + AccountNumber=payload.shipment.payment_account_number or payload.recipient.account_number, Address=PRate.BillReceiverAddressType( PostalCode=payload.recipient.postal_code ) @@ -269,7 +274,7 @@ def create_package_rate_request(self, payload: T.shipment_request) -> PRate.Rate ) ], SplitDutyVATIndicator=None - ) if any((payload.shipment.paid_by, payload.shipment.payment_account_number)) else None, + ) if payment_details_provided else None, FRSPaymentInformation=None, FreightShipmentInformation=None, GoodsNotInFreeCirculationIndicator=None, From dfd1b08fa95806be9a0d0951758bf367bfdfa6ad Mon Sep 17 00:00:00 2001 From: Dan Kobina Date: Tue, 20 Nov 2018 07:30:00 -0500 Subject: [PATCH 5/5] handle negotiated rate request option --- purplship/mappers/ups/ups_mapper/partials/rate.py | 5 +++-- tests/ups/quote.py | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/purplship/mappers/ups/ups_mapper/partials/rate.py b/purplship/mappers/ups/ups_mapper/partials/rate.py index 0f4c951453..f063b71beb 100644 --- a/purplship/mappers/ups/ups_mapper/partials/rate.py +++ b/purplship/mappers/ups/ups_mapper/partials/rate.py @@ -191,6 +191,7 @@ def create_package_rate_request(self, payload: T.shipment_request) -> PRate.Rate (payload.shipment.paid_by == 'SENDER' and payload.shipper.account_number != None) or (payload.shipment.paid_by == 'RECIPIENT' and payload.recipient.account_number != None) ) + is_negotiated_rate = any((payload.shipment.payment_account_number, payload.shipper.account_number)) return PRate.RateRequest( Request=Common.RequestType( RequestOption=payload.shipment.extra.get('RequestOption') or ["Rate"], @@ -313,12 +314,12 @@ def create_package_rate_request(self, payload: T.shipment_request) -> PRate.Rate ShipmentServiceOptions=None, ShipmentRatingOptions=(lambda rating: PRate.ShipmentRatingOptionsType( - NegotiatedRatesIndicator="" if 'NegotiatedRatesIndicator' in rating else None, + NegotiatedRatesIndicator="" if is_negotiated_rate else None, FRSShipmentIndicator="" if 'FRSShipmentIndicator' in rating else None, RateChartIndicator="" if 'RateChartIndicator' in rating else None, UserLevelDiscountIndicator="" if 'UserLevelDiscountIndicator' in rating else None ) - )(payload.shipment.extra.get('ShipmentRatingOptions')) if 'ShipmentRatingOptions' in payload.shipment.extra else None, + )(payload.shipment.extra.get('ShipmentRatingOptions') or {}) if 'ShipmentRatingOptions' in payload.shipment.extra or is_negotiated_rate else None, InvoiceLineTotal=None, RatingMethodRequestedIndicator=None, TaxInformationIndicator=None, diff --git a/tests/ups/quote.py b/tests/ups/quote.py index 2d84fb1783..32578d2801 100644 --- a/tests/ups/quote.py +++ b/tests/ups/quote.py @@ -557,6 +557,9 @@ def test_parse_quote_missing_args_error(self): 4.0 + + +