Skip to content

Commit

Permalink
Merge pull request #20 from PurplShip/candidate_beta6
Browse files Browse the repository at this point in the history
Candidate beta6
  • Loading branch information
danh91 authored Nov 2, 2018
2 parents 7c459b6 + 186203e commit c7ee502
Show file tree
Hide file tree
Showing 24 changed files with 2,299 additions and 637 deletions.
2 changes: 1 addition & 1 deletion purplship/domain/entities/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .datatypes import *
from .definitions import *
from .factories import *

class Quote:
""" manage quotes operations """
Expand Down
50 changes: 37 additions & 13 deletions purplship/domain/entities/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,26 @@ class party(NamedTuple):

class package_type(NamedTuple):
weight: float
width: float = None
height: float = None
length: float = None
id: str = None
width: float = 1
height: float = 1
length: float = 1
packaging_type: str = None
description: str = None
quantity: int = 1
sku: str = None
value_amount: float = None
value_currency: str = None
origin_country: str = None
extra: Dict = {}

class customs_type(NamedTuple):
no_eei: str = None
aes: str = None
description: str = None
terms_of_trade: str = None
items: List[package_type] = []
commercial_invoice: bool = False
extra: Dict = {}

class commodity_type(NamedTuple):
Expand All @@ -44,17 +53,26 @@ class label_type(NamedTuple):
type: str = None
extra: Dict = {}

class invoice_type(NamedTuple):
date: str
identifier: str = None
type: str = None
copies: int = None
extra: Dict = {}

class quote_options(NamedTuple):
packages: List[package_type]
insured_amount: float = None
number_of_packages: int = None
packaging_type: str = None
is_document: bool = False
currency: str = None
total_weight: float = None
weight_unit: str = "LB"
dimension_unit: str = "IN"
currency: str = None
paid_by: str = None
declared_value: float = None
duty_paid_by: str = None
payment_country_code: str = None
payment_account_number: str = None
shipper_account_number: str = None
Expand All @@ -67,23 +85,28 @@ class shipment_options(NamedTuple):
number_of_packages: int = None
packaging_type: str = None
is_document: bool = False
currency: str = None
date: str = None
total_weight: float = None
weight_unit: str = "LB"
dimension_unit: str = "IN"

currency: str = None
paid_by: str = None
duty_paid_by: str = None
duty_payment_account: str = None
declared_value: float = None
payment_type: str = None
duty_paid_by: str = None
duty_payment_account: str = None
payment_country_code: str = None
payment_account_number: str = None
shipper_account_number: str = None
services: List[str] = []

ship_date: str = None
customs: customs_type = None
invoice: invoice_type = None

references: List[str] = []
services: List[str] = []
commodities: List[commodity_type] = []

label: label_type = None
extra: Dict = {}

Expand Down Expand Up @@ -203,15 +226,16 @@ def __init__(self, carrier: str, tracking_number: str, shipment_date: str = None
self.tracking_number = tracking_number

class ShipmentDetails:
def __init__(self, carrier: str, tracking_number: str, total_charge: ChargeDetails, shipment_date: str = None,
service: str = None, documents: List[str] = [], reference: ReferenceDetails = None):
def __init__(self, carrier: str, tracking_numbers: List[str], total_charge: ChargeDetails, charges: List[ChargeDetails], shipment_date: str = None,
services: List[str] = None, documents: List[str] = [], reference: ReferenceDetails = None):
self.carrier = carrier
self.tracking_number = tracking_number
self.tracking_numbers = tracking_numbers
self.shipment_date = shipment_date
self.documents = documents
self.service = service
self.services = services
self.reference = reference
self.total_charge = total_charge
self.charges = charges

class PickupDetails:
def __init__(self, carrier: str, confirmation_number: str, pickup_date: str = None,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
from collections import namedtuple
from typing import List, Dict
from purplship.domain.entities.datatypes import package_type, commodity_type, customs_type, label_type, party
from purplship.domain.entities.datatypes import package_type, commodity_type, label_type, party, invoice_type

''' customs Type definition '''
class customs_details_type(namedtuple("customs_details_type", "no_eei aes description terms_of_trade items commercial_invoice extra")):
def __new__(cls, no_eei: str = None, aes: str = None, description: str = None, terms_of_trade: str = None, items: List[dict] = [], commercial_invoice: bool = None, extra: dict = None):
return super(cls, customs_details_type).__new__(
cls,
no_eei,
aes,
description,
terms_of_trade,
[package_type(**i) for i in items],
commercial_invoice,
extra,
)


''' quote options Type definition '''
class quote_options_type(namedtuple("quote_options_type", "packages insured_amount number_of_packages packaging_type is_document currency total_weight weight_unit dimension_unit paid_by payment_country_code payment_account_number shipper_account_number services extra")):
def __new__(cls, packages: List[package_type], insured_amount: float = None, number_of_packages: int = None, packaging_type: str = None, is_document: bool = False, currency: str = None, total_weight: float = None, weight_unit: str = "LB", dimension_unit: str = "IN", paid_by: str = None, payment_country_code: str = None, payment_account_number: str = None, shipper_account_number: str = None, services: List[str] = [], extra: Dict = {}):
class quote_options_type(namedtuple("quote_options_type", "packages insured_amount number_of_packages packaging_type is_document currency total_weight weight_unit dimension_unit paid_by declared_value duty_paid_by payment_country_code payment_account_number shipper_account_number services extra")):
def __new__(cls, packages: List[package_type], insured_amount: float = None, number_of_packages: int = None, packaging_type: str = None, is_document: bool = False, currency: str = None, total_weight: float = None, weight_unit: str = "LB", dimension_unit: str = "IN", paid_by: str = None, declared_value: float = None, duty_paid_by: str = None, payment_country_code: str = None, payment_account_number: str = None, shipper_account_number: str = None, services: List[str] = [], extra: Dict = {}):
return super(cls, quote_options_type).__new__(
cls,
list(map(lambda p: package_type(**p), packages)),
[package_type(**p) for p in packages],
insured_amount,
number_of_packages,
packaging_type,
Expand All @@ -17,6 +32,8 @@ def __new__(cls, packages: List[package_type], insured_amount: float = None, num
weight_unit,
dimension_unit,
paid_by,
declared_value,
duty_paid_by,
payment_country_code,
payment_account_number,
shipper_account_number,
Expand All @@ -26,11 +43,11 @@ def __new__(cls, packages: List[package_type], insured_amount: float = None, num


''' shipment options Type definition '''
class shipment_options_type(namedtuple("shipment_options_type", "packages insured_amount number_of_packages packaging_type is_document currency date total_weight weight_unit dimension_unit paid_by duty_paid_by payment_type payment_country_code duty_payment_account declared_value payment_account_number shipper_account_number billing_account_number services customs references commodities label, extra")):
def __new__(cls, packages: List, insured_amount: float = None, number_of_packages: int = None, packaging_type: str = None, is_document: bool = False, currency: str = None, date: str = None, total_weight: float = None, weight_unit: str = "LB", dimension_unit: str = "IN", paid_by: str = None, duty_paid_by: str = None, payment_type: str = None, payment_country_code: str = None, duty_payment_account: str = None, declared_value: float = None, payment_account_number: str = None, shipper_account_number: str = None, billing_account_number: str = None, services: List[str] = [], customs: Dict = None, references: List[str] = [], commodities: List[Dict] = [], label: Dict = None, extra: Dict = {}):
class shipment_options_type(namedtuple("shipment_options_type", "packages insured_amount number_of_packages packaging_type is_document currency date total_weight weight_unit dimension_unit paid_by duty_paid_by payment_type payment_country_code duty_payment_account declared_value payment_account_number shipper_account_number billing_account_number services customs invoice references commodities label, extra")):
def __new__(cls, packages: List, insured_amount: float = None, number_of_packages: int = None, packaging_type: str = None, is_document: bool = False, currency: str = None, date: str = None, total_weight: float = None, weight_unit: str = "LB", dimension_unit: str = "IN", paid_by: str = None, duty_paid_by: str = None, payment_type: str = None, payment_country_code: str = None, duty_payment_account: str = None, declared_value: float = None, payment_account_number: str = None, shipper_account_number: str = None, billing_account_number: str = None, services: List[str] = [], customs: Dict = None, invoice: dict = None, references: List[str] = [], commodities: List[Dict] = [], label: Dict = None, extra: Dict = {}):
return super(cls, shipment_options_type).__new__(
cls,
list(map(lambda p: package_type(**p), packages)),
[package_type(**p) for p in packages],
insured_amount,
number_of_packages,
packaging_type,
Expand All @@ -50,9 +67,10 @@ def __new__(cls, packages: List, insured_amount: float = None, number_of_package
shipper_account_number,
billing_account_number,
services,
customs_type(**customs) if customs else None,
customs_details_type(**customs) if customs else None,
invoice_type(**invoice) if invoice else None,
references,
list(map(lambda c: commodity_type(**c), commodities)),
[commodity_type(**c) for c in commodities],
label_type(**label) if label else None,
extra
)
Expand All @@ -78,3 +96,4 @@ def __new__(cls, shipper: Dict, recipient: Dict, shipment: Dict):
party(**recipient),
shipment_options_type(**shipment)
)

129 changes: 110 additions & 19 deletions purplship/mappers/caps/caps_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,51 @@ def parse_error_response(self, response) -> List[E.Error]:

def create_quote_request(self, payload: E.quote_request) -> Rate.mailing_scenario:
package = payload.shipment.packages[0]
parcel = Rate.parcel_characteristicsType(
weight=payload.shipment.total_weight or package.weight,
dimensions=Rate.dimensionsType(
length=package.length,
width=package.width,
height=package.height
)
)
destinationPostalCode = Rate.domesticType(
postal_code=payload.recipient.postal_code)
destination = Rate.destinationType(
domestic=destinationPostalCode)

if len(payload.shipment.services) > 0:
services = Rate.servicesType()
for code in payload.shipment.services:
services.add_service_code(code)

if 'options' in payload.shipment.extra:
options = Rate.optionsType()
for option in payload.shipment.extra.get('options'):
options.add_option(Rate.optionType(
option_amount=option.get('option-amount'),
option_code=option.get('option-code')
))

return Rate.mailing_scenario(
customer_number=payload.shipment.shipper_account_number or payload.shipment.payment_account_number or self.client.customer_number,
parcel_characteristics=parcel,
contract_id=payload.shipment.extra.get('contract-id'),
promo_code=payload.shipment.extra.get('promo-code'),
quote_type=payload.shipment.extra.get('quote-type'),
expected_mailing_date=payload.shipment.extra.get('expected-mailing-date'),
options=options if ('options' in payload.shipment.extra) else None,
parcel_characteristics=Rate.parcel_characteristicsType(
weight=payload.shipment.total_weight or package.weight,
dimensions=Rate.dimensionsType(
length=package.length,
width=package.width,
height=package.height
),
unpackaged=payload.shipment.extra.get('unpackaged'),
mailing_tube=payload.shipment.extra.get('mailing-tube'),
oversized=payload.shipment.extra.get('oversized')
),
services=services if (len(payload.shipment.services) > 0) else None,
origin_postal_code=payload.shipper.postal_code,
destination=destination
destination=Rate.destinationType(
domestic=Rate.domesticType(
postal_code=payload.recipient.postal_code
) if (payload.recipient.country_code == 'CA') else None,
united_states=Rate.united_statesType(
zip_code=payload.recipient.postal_code
) if (payload.recipient.country_code == 'US') else None,
international=Rate.internationalType(
country_code=payload.shipment.country_code
) if (payload.recipient.country_code not in ['US', 'CA']) else None
)
)

def create_tracking_request(self, payload: E.tracking_request) -> List[str]:
Expand All @@ -53,28 +81,33 @@ def create_shipment_request(self, payload: E.shipment_request) -> Union[Shipment
return shipment


""" Mapper Interface parsing methods """

def parse_quote_response(self, response) -> Tuple[List[E.QuoteDetails], List[E.Error]]:
def parse_quote_response(self, response: 'XMLElement') -> Tuple[List[E.QuoteDetails], List[E.Error]]:
price_quotes = response.xpath('.//*[local-name() = $name]', name="price-quote")
quotes = reduce(self._extract_quote, price_quotes, [])
return (quotes, self.parse_error_response(response))

def parse_tracking_response(self, response) -> Tuple[List[E.TrackingDetails], List[E.Error]]:
def parse_tracking_response(self, response: 'XMLElement') -> Tuple[List[E.TrackingDetails], List[E.Error]]:
pin_summaries = response.xpath('.//*[local-name() = $name]', name="pin-summary")
trackings = reduce(self._extract_tracking, pin_summaries, [])
return (trackings, self.parse_error_response(response))

def parse_shipment_response(self, response: 'XMLElement') -> Tuple[E.ShipmentDetails, List[E.Error]]:
shipment = self._extract_shipment(response) if len(response.xpath('.//*[local-name() = $name]', name="shipment-id")) > 0 else None
return (shipment, self.parse_error_response(response))

""" Helpers functions """

def _extract_error(self, errors: List[E.Error], messageNode) -> List[E.Error]:
def _extract_error(self, errors: List[E.Error], messageNode: 'XMLElement') -> List[E.Error]:
message = Msg.messageType()
message.build(messageNode)
return errors + [
E.Error(code=message.code,
message=message.description, carrier=self.client.carrier_name)
]

def _extract_quote(self, quotes: List[E.QuoteDetails], price_quoteNode) -> List[E.QuoteDetails]:
def _extract_quote(self, quotes: List[E.QuoteDetails], price_quoteNode: 'XMLElement') -> List[E.QuoteDetails]:
price_quote = Rate.price_quoteType()
price_quote.build(price_quoteNode)
discounts = [E.ChargeDetails(name=d.adjustment_name, currency="CAD", amount=float(d.adjustment_cost or 0)) for d in price_quote.price_details.adjustments.adjustment]
Expand All @@ -97,7 +130,7 @@ def _extract_quote(self, quotes: List[E.QuoteDetails], price_quoteNode) -> List[
)
]

def _extract_tracking(self, trackings: List[E.TrackingDetails], pin_summaryNode) -> List[E.TrackingDetails]:
def _extract_tracking(self, trackings: List[E.TrackingDetails], pin_summaryNode: 'XMLElement') -> List[E.TrackingDetails]:
pin_summary = Track.pin_summary()
pin_summary.build(pin_summaryNode)
return trackings + [
Expand All @@ -115,6 +148,64 @@ def _extract_tracking(self, trackings: List[E.TrackingDetails], pin_summaryNode)
)
]

def _extract_shipment(self, response: 'XMLElement') -> E.ShipmentDetails:
is_non_contract = len(response.xpath('.//*[local-name() = $name]', name="non-contract-shipment-info")) > 0
info = NCShipment.NonContractShipmentInfoType() if is_non_contract else Shipment.ShipmentInfoType()
data = NCShipment.NonContractShipmentReceiptType() if is_non_contract else Shipment.ShipmentPriceType()

info.build(response.xpath(
'.//*[local-name() = $name]',
name=("non-contract-shipment-info" if is_non_contract else "shipment-info")
)[0])
data.build(response.xpath(
'.//*[local-name() = $name]',
name=("non-contract-shipment-receipt" if is_non_contract else "shipment-price")
)[0])
currency_ = data.cc_receipt_details.currency if is_non_contract else "CAD"

return E.ShipmentDetails(
carrier=self.client.carrier_name,
tracking_numbers=[info.tracking_pin],
total_charge=E.ChargeDetails(
name="Shipment charge",
amount=data.cc_receipt_details.charge_amount if is_non_contract else data.due_amount,
currency=currency_
),
charges=(
[
E.ChargeDetails(name="base-amount", amount=data.base_amount, currency=currency_),
E.ChargeDetails(name="gst-amount", amount=data.gst_amount, currency=currency_),
E.ChargeDetails(name="pst-amount", amount=data.pst_amount, currency=currency_),
E.ChargeDetails(name="hst-amount", amount=data.hst_amount, currency=currency_),
] + [
E.ChargeDetails(
name=adjustment.adjustment_code,
amount=adjustment.adjustment_amount,
currency=currency_
) for adjustment in data.adjustments.get_adjustment()
] + [
E.ChargeDetails(
name=option.option_code,
amount=option.option_price,
currency=currency_
) for option in data.priced_options.get_priced_option()
]
),
shipment_date=data.service_standard.expected_delivery_date,
services=(
[data.service_code] +
[option.option_code for option in data.priced_options.get_priced_option()]
),
documents=[
link.get('href') for link in response.xpath('.//*[local-name() = $name]', name="link") if link.get('rel') == 'label'
],
reference=E.ReferenceDetails(
value=info.shipment_id,
type="Shipment Id"
)
)


""" Private functions """

def _create_shipment(self, payload: E.shipment_request) -> Shipment.ShipmentType:
Expand Down
Loading

0 comments on commit c7ee502

Please sign in to comment.