예제 #1
0
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,
    )
예제 #2
0
    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)
예제 #3
0
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))
예제 #4
0
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="")
예제 #5
0
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)
예제 #6
0
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
예제 #7
0
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)
    )
예제 #8
0
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)
        )
    )
예제 #9
0
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)))
예제 #10
0
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
예제 #11
0
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)),
    )
예제 #12
0
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)
예제 #13
0
    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))
        ]
예제 #14
0
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,
    )
예제 #15
0
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)
예제 #16
0
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
예제 #17
0
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,
    )
예제 #18
0
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
예제 #19
0
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 "")
예제 #20
0
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,
    )
예제 #22
0
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,
    )
예제 #23
0
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))
예제 #24
0
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))
예제 #25
0
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))
예제 #26
0
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)
예제 #27
0
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
    ]
예제 #28
0
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),
    )
예제 #29
0
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)
    )
예제 #30
0
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)