def _create_label_request(shipment_response: str) -> Job: activity = XP.build(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 _create_pickup(availability_response: str, payload: PickupRequest, settings: Settings): availability = XP.build(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_pickup_details( response: PickupCreationResponse, settings: Settings ) -> PickupDetails: pickup = XP.build( PickupCreationResponse, next( iter( response.xpath( ".//*[local-name() = $name]", name="PickupCreationResponse" ) ), None, ), ) rate = XP.build( RateResultType, next( iter(response.xpath(".//*[local-name() = $name]", name="RateResult")), None ), ) return PickupDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, confirmation_number=pickup.PRN, pickup_charge=ChargeDetails( name=rate.RateType, currency=rate.CurrencyCode, amount=NF.decimal(rate.GrandTotalOfAllCharge), ), )
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 _extract_pickup_details(response: Element, settings: Settings) -> PickupDetails: header = next((XP.build(PickupRequestHeaderType, elt) for elt in response.xpath(".//*[local-name() = $name]", name="pickup-request-header"))) price = next((XP.build(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_details(shipment_node: Element, settings: Settings) -> TrackingDetails: track_detail = XP.build(ShipmentType, shipment_node) activities = [ XP.build(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 extract(rates: List[RateDetails], detail_node: Element) -> List[RateDetails]: rate = XP.build(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 = next( (XP.build(EstimatedArrivalType, n) for n in detail_node.xpath(".//*[local-name() = $name]", name="EstimatedArrival")), EstimatedArrivalType(), ) transit_days = (rate.GuaranteedDelivery.BusinessDaysInTransit if rate.GuaranteedDelivery is not None else estimated_arrival.BusinessDaysInTransit) currency_ = next( str(c.text) for c in detail_node.xpath(".//*[local-name() = $name]", name="CurrencyCode")) service = ShippingServiceCode(rate.Service.Code).name return rates + [ RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, currency=currency_, service=service, 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], [], ), transit_days=NF.integer(transit_days), ) ]
def parse_shipment_response(response: Element, settings: Settings) -> Tuple[ShipmentDetails, List[Message]]: package = XP.find("PackageID", response, ArrayOfString, first=True) label = XP.find("label", response, first=True) details = ( _extract_details((package.string[0], str(label.text)), settings) if getattr(package, 'string', [None])[0] is not None else None ) return 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 _create_pickup(availability_response: str, payload: PickupRequest, settings: Settings): availability = XP.build(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 parse_tracking_response( response: Element, settings: Settings ) -> Tuple[List[TrackingDetails], List[Message]]: details = XP.find("tracking-detail", response) tracking_details: List[TrackingDetails] = [ _extract_tracking(node, settings) for node in details if len(XP.find("occurrence", node)) > 0 ] return tracking_details, parse_error_response(response, settings)
def _extract_structure_error(node: Element, settings: Settings) -> Message: return Message( # context info carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, # carrier error info code=XP.find("Code", node, first=True).text, message=XP.find("Message", node, first=True).text, )
def _get_shipment_label(create_response: str, payload: ShipmentRequest, settings: Settings) -> Job: response = XP.to_xml(create_response) valid = len(parse_error_response(response, settings)) == 0 shipment_pin = (getattr(XP.find("ShipmentPIN", response, PIN, first=True), 'Value', None) if valid else None) data = (get_shipping_documents_request(shipment_pin, payload, settings) if valid else None) return Job(id="document", data=data, fallback="")
def _get_pickup(update_response: str, payload: PickupUpdateRequest, settings: Settings) -> Job: errors = parse_error_response(XP.to_xml(XP.bundle_xml([update_response])), settings) data = None if any( errors ) else f"/enab/{settings.customer_number}/pickuprequest/{payload.confirmation_number}/details" return Job(id="get_pickup", data=Serializable(data), fallback="" if data is None else "")
def _extract_shipment(response: Element, settings: Settings) -> ShipmentDetails: info = XP.find("shipment-info", response, ShipmentInfoType, first=True) label = XP.find("label", response, first=True) return ShipmentDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=info.tracking_pin, shipment_identifier=info.tracking_pin, label=getattr(label, "text", None), )
def _extract_shipment(response: Element, settings: Settings) -> ShipmentDetails: pin: PIN = XP.find("ShipmentPIN", response, PIN, first=True) document = XP.find("DocumentDetail", response, DocumentDetail, first=True) or DocumentDetail() return ShipmentDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, tracking_number=pin.Value, shipment_identifier=pin.Value, label=document.Data, )
def parse_error_response(response: Element, settings: Settings) -> List[Message]: errors = XP.find("Error", response, ErrorType) carrier_errors = XP.find("CarrierErrorMessage", response, CarrierErrorMessageType) return [ *[_extract_error(er, settings) for er in errors if er.Message != ""], *[ _extract_carrier_error(er, settings) for er in carrier_errors if er.errorMessage0 != "" ] ]
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_error_response(response, settings: Settings) -> List[Message]: structure_errors = XP.find("ErrorStructure", response) broken_rules_nodes = XP.find("brokenRules", response) broken_rule_nodes = XP.find("brokenRule", response) runtime_errors = XP.find("runtime_error", response) parse_errors = XP.find("parse_error", response) ERRORS = XP.find("ERROR", response) errors = XP.find("Error", response) faults = XP.find("fault", response) return [ *[ _extract_structure_error(node, settings) for node in structure_errors ], *[ _extract_broken_rules(node, settings) for node in broken_rules_nodes ], *[_extract_broken_rule(node, settings) for node in broken_rule_nodes], *[_extract_runtime_error(node, settings) for node in runtime_errors], *[_extract_parse_error(node, settings) for node in parse_errors], *[_extract_structure_error(node, settings) for node in errors], *[_extract_error(node, settings) for node in ERRORS], *[_extract_faut(node, settings) for node in faults], ]
def parse_rate_response( response: Element, settings: Settings) -> Tuple[List[RateDetails], List[Message]]: estimate = XP.find("GetEstimatedChargesResult", response, ResponseGetCharges, first=True) product = XP.find("product", response, first=True) details: List[RateDetails] = [ _extract_rate_details((product, estimate), settings) ] return details, parse_error_response(response, settings)
def _refund_if_submitted(shipment_details: str): shipment = XP.build(ShipmentInfoType, XP.to_xml(shipment_details)) transmitted = shipment.shipment_status == 'transmitted' data = dict( id=payload.shipment_identifier, payload=Serializable( ShipmentRefundRequestType( email=payload.options.get('email')), lambda request: XP. export(request, name_='shipment-refund-request', namespacedef_= 'xmlns="http://www.canadapost.ca/ws/shipment-v8"')) ) if transmitted else None return Job(id="refund", data=data, fallback=shipment_details)
def _cancel_pickup_request(response: str, payload: PickupUpdateRequest, settings: Settings): reply = next( iter( XP.to_xml(response).xpath(".//*[local-name() = $name]", name="PickupCreationResponse")), None, ) new_pickup = XP.to_object(PickupCreationResponse, reply) data = (pickup_cancel_request( PickupCancelRequest(confirmation_number=payload.confirmation_number), settings, ) if new_pickup is not None and new_pickup.PRN is not None else None) return Job(id="cancel_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 parse_rate_response( response: Element, settings: Settings) -> Tuple[List[RateDetails], List[Message]]: rate_reply = XP.find("RatedShipment", response) rates: List[RateDetails] = reduce(_extract_package_rate(settings), rate_reply, []) return rates, parse_error_response(response, settings)
def _modify_pickup( validation_response: str, payload: PickupUpdateRequest, settings: Settings ): errors = parse_error_response(XP.to_xml(validation_response), settings) data = _modify_pickup_request(payload, settings) if len(errors) == 0 else None return Job(id="modify", data=data, fallback="")
def _extract_pickup_details(response: Element, settings: Settings) -> PickupDetails: pickup = XP.find( "PickupCreationResponse", response, PickupCreationResponse, first=True ) rate = XP.find("RateResult", response, RateResultType, first=True) return PickupDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, confirmation_number=pickup.PRN, pickup_charge=ChargeDetails( name=rate.RateType, currency=rate.CurrencyCode, amount=NF.decimal(rate.GrandTotalOfAllCharge), ), )
def _extract_rate_details(node: Element, settings: Settings) -> RateDetails: shipment = XP.build(Shipment, node) 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(shipment.service_type).name, 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), )
def _extract_details(service_node: Element, settings: Settings) -> RateDetails: service: ServiceType = XP.build(ServiceType, service_node) charges: List[ExtraServiceType] = service.ExtraServices.ExtraService delivery_date = DF.date(service.GuaranteeAvailability, "%m/%d/%Y") transit = ( (delivery_date - datetime.now()).days if delivery_date is not None else None ) return RateDetails( carrier_name=settings.carrier_name, carrier_id=settings.carrier_id, service=ServiceClassID(str(service.ID)), base_charge=NF.decimal(service.Postage), total_charge=NF.decimal(service.Postage), currency=Currency.USD.name, transit_days=transit, extra_charges=[ ChargeDetails( name=charge.ServiceID, amount=NF.decimal(charge.Price), currency=Currency.USD.name, ) for charge in charges ], )
def _extract_tracking_details(node: Element, settings: Settings) -> TrackingDetails: result = XP.build(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))