Repository URL to install this package:
|
Version:
0.4.139 ▾
|
lib-py-b2b
/
shipping.py
|
|---|
from decimal import Decimal
from enum import Enum, IntEnum
from typing import Optional
from boto3 import resource
from os import environ
import logging
from botocore.exceptions import ClientError
from lib_b2b.base import BaseClass
from lib_b2b.persistent import Persistable
from .errors import ShipRateNotFound, ShipZoneNotFound, ShippingRateSaveError
from .ship_request import ShipRequest
log_level = logging.getLevelName(environ['LOG_LEVEL']) if 'LOG_LEVEL' in environ else logging.INFO
logging.basicConfig(format='%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s',
datefmt='%d-%m-%Y:%H:%M:%S',
level=log_level)
logger = logging.getLogger('lib-b2b-shipping')
fedex_zone_table = environ['fedex_zone_table'] if 'zone_table' in environ else 'prod.b2b.FedexZoneLookup'
ship_rate_table = environ['ship_rate_table'] if 'ship_rate_table' in environ else 'prod.b2b.ShipRates'
class ShippingRateType(Enum):
FIXED_AMOUNT = 'fixed'
PERCENTAGE = 'percent'
class ShippingZone(IntEnum):
ZONE_TWO = 2
ZONE_THREE = 3
ZONE_FOUR = 4
ZONE_FIVE = 5
ZONE_SIX = 6
ZONE_SEVEN = 7
ZONE_EIGHT = 8
ZONE_NINE = 9
@staticmethod
def all() -> ['ShippingZone']:
return [z for z in ShippingZone]
@staticmethod
def for_(postal_code: str):
if postal_code:
dynamodb_resource = resource('dynamodb', region_name='us-east-1')
zone_lookup_table = dynamodb_resource.Table(fedex_zone_table)
_zip_code = postal_code[:5]
try:
response = zone_lookup_table.get_item(Key={'zip_code': _zip_code})
if 'Item' in response:
return ShippingZone(int(response['Item']['zone']))
else:
raise ShipZoneNotFound(f"Unable to find a ship zone for {_zip_code}.")
except Exception as e:
raise ShipZoneNotFound(f"Unable to find ship zone for zip: {_zip_code}.") from e
else:
return None
class ShippingRate(BaseClass, Persistable):
def __init__(self, customer_edi_id: str, item: str, zone: ShippingZone, rate: Decimal,
shipping_rate_id: str = None, rate_type: ShippingRateType = None) -> None:
super().__init__()
if not shipping_rate_id:
shipping_rate_id = ShippingRate.generate_id(customer_edi_id, item, zone)
if not rate_type:
rate_type = ShippingRateType.FIXED_AMOUNT
self.item = item
self.shipping_rate_id = shipping_rate_id
self.customer_edi_id = customer_edi_id
self.rate_type = rate_type
self.rate = rate
self.zone = zone
@staticmethod
def generate_id(customer_edi_id, item, zone):
return f"{customer_edi_id}-{item}-{str(zone.value)}"
@staticmethod
def from_dict(data: dict):
return ShippingRate(
shipping_rate_id=data.get('id'),
customer_edi_id=data.get('customer_edi_id'),
item=data.get('item'),
zone=ShippingZone(data.get('zone')),
rate=data.get('rate'),
rate_type=ShippingRateType.FIXED_AMOUNT
)
def as_dict(self) -> dict:
return {
'id': self.shipping_rate_id,
'customer_edi_id': self.customer_edi_id,
'item': self.item,
'zone': self.zone.value,
'rate': self.rate
}
@staticmethod
def fetch(customer_edi_id: str, zone: str, item: str) -> 'ShippingRate':
if zone is None:
return None
dynamodb_resource = resource('dynamodb', region_name='us-east-1')
rate_lookup_table = dynamodb_resource.Table(ship_rate_table)
try:
response = rate_lookup_table.get_item(Key={'id': f"{customer_edi_id}-{item}-{zone}"})
if 'Item' in response:
item = response['Item']['rate']
return ShippingRate.from_dict(item)
else:
raise ShipRateNotFound(f"Unable to find a ship rate for customer:{customer_edi_id}, zone:{zone}, item:{item}.")
except Exception as e:
raise ShipRateNotFound(f"Unable to find a ship rate for customer:{customer_edi_id}, zone:{zone}, item:{item}.") from e
@staticmethod
def fetch_all_rates():
dynamodb_resource = resource('dynamodb', region_name='us-east-1')
rate_lookup_table = dynamodb_resource.Table(ship_rate_table)
try:
response = rate_lookup_table.scan()
data = response.get('Items')
while 'LastEvaluatedKey' in response:
response = rate_lookup_table.scan(
ExclusiveStartKey=response['LastEvaluatedKey']
)
data.extend(response.get('Items'))
return [ShippingRate.from_dict(r) for r in data]
except ClientError as e:
raise ShipRateNotFound(e.response['Error']['Message'])
except Exception as e:
raise ShipRateNotFound(f"Unable to find a ship rates.") from e
@staticmethod
def fetch_rates(customer_edi_id: str, item: str = None):
dynamodb_resource = resource('dynamodb', region_name='us-east-1')
rate_lookup_table = dynamodb_resource.Table(ship_rate_table)
try:
if item:
response = rate_lookup_table.query(
IndexName='customer_edi_id-item-index',
KeyConditionExpression="customer_edi_id = :customer_edi_id and item = :item",
ExpressionAttributeValues={
':customer_edi_id': customer_edi_id,
':item': item
}
)
data = response.get('Items')
while 'LastEvaluatedKey' in response:
response = rate_lookup_table.query(
ExclusiveStartKey=response['LastEvaluatedKey'],
IndexName='customer_edi_id-index',
KeyConditionExpression="customer_edi_id = :customer_edi_id",
ExpressionAttributeValues={
':customer_edi_id': customer_edi_id
}
)
data.extend(response.get('Items'))
else:
response = rate_lookup_table.query(
IndexName='customer_edi_id-index',
KeyConditionExpression="customer_edi_id = :customer_edi_id",
ExpressionAttributeValues={
':customer_edi_id': customer_edi_id
}
)
data = response.get('Items')
while 'LastEvaluatedKey' in response:
response = rate_lookup_table.query(
ExclusiveStartKey=response['LastEvaluatedKey'],
IndexName='customer_edi_id-index',
KeyConditionExpression="customer_edi_id = :customer_edi_id",
ExpressionAttributeValues={
':customer_edi_id': customer_edi_id
}
)
data.extend(response.get('Items'))
if data:
return [ShippingRate.from_dict(r) for r in data]
else:
raise ShipRateNotFound(
f"Unable to find a ship rates for customer:{customer_edi_id}, item:{item}.")
except Exception as e:
raise ShipRateNotFound(
f"Unable to find a ship rates for customer:{customer_edi_id}, item:{item}.") from e
def save_or_update(self):
dynamodb_resource = resource('dynamodb', region_name='us-east-1')
rate_lookup_table = dynamodb_resource.Table(ship_rate_table)
try:
rate_lookup_table.put_item(Item=self.as_dict())
except ClientError as e:
raise ShippingRateSaveError(e.response['Error']['Message'])
class Shipping:
@staticmethod
def create_shipment(request: ShipRequest):
return request.ship()
@staticmethod
def clean_postal_code(postal_code: str):
new_postal_code = postal_code
if len(new_postal_code) > 9 and '-' in new_postal_code:
new_postal_code = new_postal_code.replace('-', '')
if len(new_postal_code) > 9:
new_postal_code = new_postal_code[0:5]
return new_postal_code
@staticmethod
def get_shipping_rate(customer_edi_id: str, postal_code: str, product_id: str) -> Optional[ShippingRate]:
ship_zone = Shipping.get_ship_zone(postal_code)
ship_rate = ShippingRate.fetch(customer_edi_id=customer_edi_id,
zone=str(ship_zone.value),
item=product_id)
if ship_rate:
return ship_rate
else:
return None
@staticmethod
def get_ship_zone(zip_code) -> Optional[ShippingZone]:
return ShippingZone.for_(zip_code)
@staticmethod
def get_ship_rates(customer_edi_id, item=None):
return ShippingRate.fetch_rates(customer_edi_id, item)
@staticmethod
def get_ship_rate(customer_edi_id, zone, item):
return ShippingRate.fetch(customer_edi_id, zone, item).as_dict()