def _extract_details(shipment_node: Element, settings: Settings) -> TrackingDetails: track_detail = XP.to_object(ShipmentType, shipment_node) activities = [ XP.to_object(ActivityType, node) for node in shipment_node.xpath(".//*[local-name() = $name]", name="Activity") ] delivered = any(a.Status.Type == "D" for a in activities) return TrackingDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=track_detail.InquiryNumber.Value, events=list( map( lambda a: TrackingEvent( date=DF.fdate(a.Date, "%Y%m%d"), description=a.Status.Description if a.Status else None, location=(_format_location(a.ActivityLocation.Address) if a.ActivityLocation is not None and a. ActivityLocation.Address is not None else None), time=DF.ftime(a.Time, "%H%M%S"), code=a.Status.Code if a.Status else None, ), activities, )), delivered=delivered, )
def _create_label_request(shipment_response: str) -> Job: activity = XP.to_object(document, XP.to_xml(shipment_response)) fallback = shipment_response if activity is None else None data = (create_label_request(activity, payload, settings) if activity is None else None) return Job(id='create', data=data, fallback=fallback)
def _extract_tracking_details(node: Element, settings: Settings) -> TrackingDetails: result = XP.to_object(TrackingResult, node) is_en = settings.language == "en" events = [ TrackingEvent( date=DF.fdate(event.local_date_time, '%Y%m%d %H%M%S'), description=(event.code_description_en if is_en else event.code_description_fr), location=SF.concat_str(event.address.address_line_1, event.address.address_line_2, event.address.city, event.address.province, event.address.country, join=True, separator=", "), code=event.code, time=DF.ftime(event.local_date_time, '%Y%m%d %H%M%S'), ) for event in cast(List[CanparTrackingEvent], result.events) ] return TrackingDetails(carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=result.barcode, events=events, delivered=any(event.code == 'DEL' for event in events))
def _create_pickup(availability_response: str, payload: PickupRequest, settings: Settings): availability = XP.to_object(PickupAvailabilityReply, XP.to_xml(availability_response)) data = _pickup_request(payload, settings) if availability else None return Job(id="create_pickup", data=data, fallback="")
def _extract_details(node: Element, settings) -> TrackingDetails: info = XP.to_object(TrackInfoType, node) details: List[TrackDetailType] = [ *([info.TrackSummary] or []), *info.TrackDetail ] delivered = info.StatusCategory.lower() == "delivered" return TrackingDetails(carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=info.ID, events=[ TrackingEvent( code=str(event.EventCode), date=DF.fdate(event.EventDate, "%B %d, %Y"), time=DF.ftime(event.EventTime, "%H:%M %p"), description=event.Event, location=", ".join([ location for location in [ event.EventCity, event.EventState, event.EventCountry, str(event.EventZIPCode), ] if location is not None ]), ) for event in details ], delivered=delivered)
def parse_address_validation_response( response: Element, settings: Settings) -> Tuple[AddressValidationDetails, List[Message]]: errors = parse_error_response(response, settings) address_node = next( iter(response.xpath(".//*[local-name() = $name]", name="address")), None) address = XP.to_object(CanparAddress, address_node) success = len(errors) == 0 validation_details = AddressValidationDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, success=success, complete_address=Address(postal_code=address.postal_code, city=address.city, company_name=address.name, country_code=address.country, email=address.email, state_code=address.province, residential=address.residential, address_line1=address.address_line_1, address_line2=SF.concat_str( address.address_line_2, address.address_line_3, join=True))) if success else None return validation_details, errors
def _extract_details(postage_node: Element, settings: Settings) -> RateDetails: postage: ServiceType = XP.to_object(ServiceType, postage_node) service = ServiceClassID.map(str(postage.ID)) charges: List[ExtraServiceType] = postage.ExtraServices.ExtraService delivery_date = DF.date(postage.GuaranteeAvailability, "%m/%d/%Y") transit = ((delivery_date.date() - datetime.now().date()).days if delivery_date is not None else None) return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, service=service.name_or_key, base_charge=NF.decimal(postage.Postage), total_charge=NF.decimal(postage.Postage), currency=Currency.USD.name, transit_days=transit, extra_charges=[ ChargeDetails( name=charge.ServiceName, amount=NF.decimal(charge.Price), currency=Currency.USD.name, ) for charge in charges ], meta=dict(service_name=service.name or postage.SvcDescription) )
def _extract_quote(node: Element, settings: Settings) -> RateDetails: quote = XP.to_object(price_quoteType, node) service = ServiceType.map(quote.service_code) adjustments = getattr(quote.price_details.adjustments, 'adjustment', []) discount = sum(NF.decimal(d.adjustment_cost or 0) for d in adjustments) transit_days = quote.service_standard.expected_transit_time return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, currency=Currency.CAD.name, transit_days=transit_days, service=service.name_or_key, base_charge=NF.decimal(quote.price_details.base or 0), total_charge=NF.decimal(quote.price_details.due or 0), discount=NF.decimal(discount), duties_and_taxes=NF.decimal( float(quote.price_details.taxes.gst.valueOf_ or 0) + float(quote.price_details.taxes.pst.valueOf_ or 0) + float(quote.price_details.taxes.hst.valueOf_ or 0) ), extra_charges=[ ChargeDetails( name=a.adjustment_name, currency=Currency.CAD.name, amount=NF.decimal(a.adjustment_cost or 0), ) for a in adjustments ], meta=dict( service_name=(service.name or quote.service_name) ) )
def _extract_details(postage_node: Element, settings: Settings) -> RateDetails: postage: PostageType = XP.to_object(PostageType, postage_node) service = ServiceClassID.map(str(postage.CLASSID)) charges: List[SpecialServiceType] = getattr(postage.SpecialServices, 'SpecialService', []) rate = NF.decimal(XP.find('Rate', postage_node, first=True).text) estimated_date = DF.date( getattr(XP.find('CommitmentDate', postage_node, first=True), 'text', None)) transit = ((estimated_date.date() - datetime.now().date()).days if estimated_date is not None else None) return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, service=service.name_or_key, base_charge=rate, total_charge=rate, currency=Currency.USD.name, transit_days=transit, extra_charges=[ ChargeDetails( name=charge.ServiceName, amount=NF.decimal(charge.Price), currency=Currency.USD.name, ) for charge in charges ], meta=dict(service_name=(service.name or postage.MailService)))
def parse_address_validation_response( response: Element, settings: Settings) -> Tuple[AddressValidationDetails, List[Message]]: errors = parse_error_response(response, settings) reply = XP.to_object( ValidateCityPostalCodeZipResponse, next( iter( response.xpath(".//*[local-name() = $name]", name="ValidateCityPostalCodeZipResponse")), None)) address: ShortAddress = next( (result.Address for result in reply.SuggestedAddresses.SuggestedAddress), None) success = len(errors) == 0 validation_details = AddressValidationDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, success=success, complete_address=Address(city=address.City, state_code=address.Province, country_code=address.Country, postal_code=address.PostalCode) if address is not None else None) if success else None return validation_details, errors
def _extract_rate_details(node: Element, settings: Settings) -> RateDetails: shipment = XP.to_object(Shipment, node) service = Service.map(shipment.service_type) surcharges = [ ChargeDetails(name=charge.value, amount=NF.decimal(getattr(shipment, charge.name)), currency='CAD') for charge in list(Charges) if NF.decimal(getattr(shipment, charge.name)) > 0.0 ] taxes = [ ChargeDetails(name=f'{getattr(shipment, code)} Tax Charge', amount=NF.decimal(charge), currency='CAD') for code, charge in [( 'tax_code_1', shipment.tax_charge_1), ('tax_code_2', shipment.tax_charge_2)] if NF.decimal(charge) > 0.0 ] return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, currency="CAD", transit_days=shipment.transit_time, service=service.name_or_key, base_charge=NF.decimal(shipment.freight_charge), total_charge=sum([c.amount for c in (surcharges + taxes)], 0.0), duties_and_taxes=sum([t.amount for t in taxes], 0.0), extra_charges=(surcharges + taxes), meta=dict(service_name=(service.name or shipment.service_type)), )
def parse_shipment_response(response: Element, settings: Settings) -> Tuple[ShipmentDetails, List[Message]]: shipment = XP.to_object( Shipment, next(iter(response.xpath(".//*[local-name() = $name]", name="shipment")), None) ) success = (shipment is not None and shipment.id is not None) shipment_details = _extract_details(response, settings) if success else None return shipment_details, parse_error_response(response, settings)
def extract(rates: List[RateDetails], detail_node: Element) -> List[RateDetails]: rate = XP.to_object(RatedShipmentType, detail_node) if rate.NegotiatedRateCharges is not 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] estimated_arrival = (XP.find( "EstimatedArrival", detail_node, EstimatedArrivalType, first=True) or EstimatedArrivalType()) transit_days = (rate.GuaranteedDelivery.BusinessDaysInTransit if rate.GuaranteedDelivery is not None else estimated_arrival.BusinessDaysInTransit) currency = XP.find("CurrencyCode", detail_node, first=True).text service = ServiceCode.map(rate.Service.Code) return rates + [ RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, currency=currency, service=service.name_or_key, base_charge=NF.decimal( rate.TransportationCharges.MonetaryValue), total_charge=NF.decimal(total_charges.MonetaryValue), duties_and_taxes=reduce( lambda total, charge: total + NF.decimal(charge. MonetaryValue), taxes or [], 0.0, ), extra_charges=reduce( lambda total, charge: (total + [ ChargeDetails( name=charge.Code, amount=NF.decimal(charge.MonetaryValue), currency=charge.CurrencyCode, ) ]), [ charge for charge in extra_charges if charge is not None and charge.Code is not None ], [], ), transit_days=NF.integer(transit_days), meta=dict(service_name=service.name_or_key)) ]
def _extract_pickup_details(response: Element, settings: Settings) -> PickupDetails: header = next( ( XP.to_object(PickupRequestHeaderType, elt) for elt in response.xpath( ".//*[local-name() = $name]", name="pickup-request-header" ) ) ) price = next( ( XP.to_object(PickupRequestPriceType, elt) for elt in response.xpath( ".//*[local-name() = $name]", name="pickup-request-price" ) ), None, ) price_amount = ( sum( [ NF.decimal(price.hst_amount or 0.0), NF.decimal(price.gst_amount or 0.0), NF.decimal(price.due_amount or 0.0), ], 0.0, ) if price is not None else None ) return PickupDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, confirmation_number=header.request_id, pickup_date=DF.fdate(header.next_pickup_date), pickup_charge=ChargeDetails( name="Pickup fees", amount=NF.decimal(price_amount), currency="CAD" ) if price is not None else None, )
def _extract_faut(node: Element, settings: Settings) -> Message: error = XP.to_object(fault, node) return Message( # context info carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, # carrier error info code=error.key)
def _extract_error(node: Element, settings: Settings) -> Optional[Message]: notification = XP.to_object(Notification, node) if notification.Severity not in ("SUCCESS", "NOTE"): return Message( code=notification.Code, message=notification.Message, carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, ) return None
def _extract_details(response: Element, settings: Settings) -> ShipmentDetails: shipment = XP.to_object(eVSPriorityMailIntlResponse, response) return ShipmentDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, label=shipment.LabelImage, tracking_number=shipment.BarcodeNumber, shipment_identifier=shipment.BarcodeNumber, )
def parse_tracking_response(response, settings: Settings) -> Tuple[List[TrackingDetails], List[Message]]: non_existents = next( (XP.to_object(ArrayOfstring, n) for n in response.xpath(".//*[local-name() = $name]", name="NonExistingWaybills")), ArrayOfstring() ) results = response.xpath(".//*[local-name() = $name]", name="TrackingResult") tracking_details = [_extract_detail(node, settings) for node in results] errors = _extract_errors(non_existents, settings) + parse_error_response(response, settings) return tracking_details, errors
def _create_pickup( availability_response: str, payload: PickupRequest, settings: Settings ): availability = XP.to_object(pickup_availability, XP.to_xml(availability_response)) data = ( _create_pickup_request(payload, settings) if availability.on_demand_tour else None ) return Job(id="create_pickup", data=data, fallback="" if data is None else "")
def _extract_error(node: Element, settings: Settings) -> Message: error = XP.to_object(ERROR, node) return Message( # context info carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, # carrier error info code=error.CODE, message=error.DESCRIPTION, )
def _extract_details(response: Element, settings: Settings) -> ShipmentDetails: shipment = XP.to_object(eVSGXGGetLabelResponse, response) tracking_number = (shipment.USPSBarcodeNumber or shipment.FedExBarcodeNumber) return ShipmentDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, label=shipment.LabelImage, tracking_number=tracking_number, shipment_identifier=tracking_number, )
def _extract_broken_rules(node: Element, settings: Settings) -> Message: error = XP.to_object(brokenRules, node) return Message( # context info carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, # carrier error info code=error.errorCode, message=error.errorDescription, )
def _extract_broken_rule(node: Element, settings: Settings) -> Message: error = XP.to_object(brokenRule, node) return Message( # context info carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, # carrier error info code=error.code, message=error.description, details=dict(messageType=error.messageType))
def _extract_parse_error(node: Element, settings: Settings) -> Message: error = XP.to_object(parseError, node) return Message( # context info carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, # carrier error info code='parsing', message=error.errorReason, details=dict(srcText=error.errorSrcText))
def _get_label(shipment_response: str, settings: Settings) -> Job: response = XP.to_xml(shipment_response) shipment = XP.to_object( Shipment, next(iter(response.xpath(".//*[local-name() = $name]", name="shipment")), None) ) success = (shipment is not None and shipment.id is not None) data = ( get_label_request(LabelRequest(shipment_id=shipment.id), settings) if success else None ) return Job(id="get_label", data=data, fallback=("" if not success else None))
def parse_address_validation_response( response: Element, settings: Settings) -> Tuple[AddressValidationDetails, List[Message]]: notes = response.xpath(".//*[local-name() = $name]", name="Note") success = next((True for note in notes if XP.to_object(Note, note).ActionNote == "Success"), False) validation_details = AddressValidationDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, success=success) return validation_details, parse_error_response(response, settings)
def parse_error_response(response: Element, settings: Settings) -> List[Message]: error_nodes = ([response] if response.tag == 'Error' else response.xpath( ".//*[local-name() = $name]", name="Error")) errors = [XP.to_object(Error, node) for node in error_nodes] return [ Message( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, code=str(error.Number), message=error.Description, ) for error in errors ]
def _extract_details(response: Element, settings: Settings) -> ShipmentDetails: shipment_node = next(iter(response.xpath(".//*[local-name() = $name]", name="shipment")), None) label = next(iter(response.xpath(".//*[local-name() = $name]", name="labels")), None) shipment = XP.to_object(Shipment, shipment_node) tracking_number = next(iter(shipment.packages), Package()).barcode return ShipmentDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, label=str(label.text), tracking_number=tracking_number, shipment_identifier=str(shipment.id), selected_rate=_extract_rate_details(shipment_node, settings), )
def _extract_rate(detail_node: Element, settings: Settings) -> Optional[RateDetails]: rate: RateReplyDetail = XP.to_object(RateReplyDetail, detail_node) service = ServiceType.map(rate.ServiceType) rate_type = rate.ActualRateType shipment_rate, shipment_discount = cast( Tuple[ShipmentRateDetail, Money], next( ( (r.ShipmentRateDetail, r.EffectiveNetDiscount) for r in rate.RatedShipmentDetails if cast(ShipmentRateDetail, r.ShipmentRateDetail).RateType == rate_type ), (None, None), ), ) discount = ( NF.decimal(shipment_discount.Amount) if shipment_discount is not None else None ) currency = cast(Money, shipment_rate.TotalBaseCharge).Currency duties_and_taxes = ( shipment_rate.TotalTaxes.Amount + shipment_rate.TotalDutiesAndTaxes.Amount ) surcharges = [ ChargeDetails( name=cast(Surcharge, s).Description, amount=NF.decimal(cast(Surcharge, s).Amount.Amount), currency=currency, ) for s in shipment_rate.Surcharges + shipment_rate.Taxes ] estimated_delivery = DF.date(rate.DeliveryTimestamp) transit = ( ((estimated_delivery.date() - datetime.today().date()).days or None) if estimated_delivery is not None else None ) return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, service=service.name_or_key, currency=currency, base_charge=NF.decimal(shipment_rate.TotalBaseCharge.Amount), total_charge=NF.decimal(shipment_rate.TotalNetChargeWithDutiesAndTaxes.Amount), duties_and_taxes=NF.decimal(duties_and_taxes), discount=discount, transit_days=transit, extra_charges=surcharges, meta=dict(service_name=service.name_or_key) )
def parse_pickup_response( response: Element, settings: Settings) -> Tuple[PickupDetails, List[Message]]: reply = XP.to_object( CreatePickupReply, next( iter( response.xpath(".//*[local-name() = $name]", name="CreatePickupReply")), None, ), ) pickup = (_extract_pickup_details(reply, settings) if reply.HighestSeverity == NotificationSeverityType.SUCCESS.value else None) return pickup, parse_error_response(response, settings)