236 lines
8.3 KiB
Python
236 lines
8.3 KiB
Python
import logging
|
|
from decimal import Decimal
|
|
|
|
from measurement.measures import Weight
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.shortcuts import redirect, reverse
|
|
from django.urls import reverse_lazy
|
|
|
|
from core.models import Product, OrderLine, Coupon
|
|
from core.usps import USPSApiWithRate
|
|
from core import (
|
|
DiscountValueType,
|
|
VoucherType,
|
|
TransactionStatus,
|
|
OrderStatus,
|
|
ShippingMethodType,
|
|
ShippingService,
|
|
ShippingContainer,
|
|
CoffeeGrind
|
|
)
|
|
|
|
from .payments import CreateOrder
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class Cart:
|
|
def __init__(self, request):
|
|
self.request = request
|
|
self.session = request.session
|
|
self.coupon_code = self.session.get('coupon_code')
|
|
cart = self.session.get(settings.CART_SESSION_ID)
|
|
if not cart:
|
|
cart = self.session[settings.CART_SESSION_ID] = {}
|
|
self.cart = cart
|
|
|
|
def add(self, request, product, quantity=1, grind='', update_quantity=False):
|
|
product_id = str(product.id)
|
|
if product_id not in self.cart:
|
|
self.cart[product_id] = {
|
|
'variations': {},
|
|
'price': str(product.price)
|
|
}
|
|
self.cart[product_id]['variations'][grind] = {'quantity': 0}
|
|
|
|
if update_quantity:
|
|
self.cart[product_id]['variations'][grind]['quantity'] = quantity
|
|
else:
|
|
if not grind in self.cart[product_id]['variations']:
|
|
# create it
|
|
self.cart[product_id]['variations'][grind] = {'quantity': quantity}
|
|
else:
|
|
# add to it
|
|
self.cart[product_id]['variations'][grind]['quantity'] += quantity
|
|
if len(self) <= 20:
|
|
self.save()
|
|
else:
|
|
messages.warning(request, "Cart is full: 20 items or less.")
|
|
|
|
def save(self):
|
|
self.session[settings.CART_SESSION_ID] = self.cart
|
|
self.session.modified = True
|
|
logger.info(f'\nCart:\n{self.cart}\n')
|
|
|
|
def remove(self, product, grind):
|
|
product_id = str(product.id)
|
|
if product_id in self.cart:
|
|
del self.cart[product_id]['variations'][grind]
|
|
if not self.cart[product_id]['variations']:
|
|
del self.cart[product_id]
|
|
self.save()
|
|
|
|
def __iter__(self):
|
|
product_ids = self.cart.keys()
|
|
products = Product.objects.filter(id__in=product_ids)
|
|
for product in products:
|
|
self.cart[str(product.id)]['product'] = product
|
|
|
|
for item in self.cart.values():
|
|
item['price'] = Decimal(item['price'])
|
|
item['total_price'] = Decimal(sum(self.get_item_prices()))
|
|
item['quantity'] = self.get_single_item_total_quantity(item)
|
|
yield item
|
|
|
|
def __len__(self):
|
|
return sum(self.get_all_item_quantities())
|
|
|
|
def get_all_item_quantities(self):
|
|
for item in self.cart.values():
|
|
yield sum([value['quantity'] for value in item['variations'].values()])
|
|
|
|
def get_single_item_total_quantity(self, item):
|
|
return sum([value['quantity'] for value in item['variations'].values()])
|
|
|
|
def get_item_prices(self):
|
|
for item in self.cart.values():
|
|
yield Decimal(item['price']) * sum([value['quantity'] for value in item['variations'].values()])
|
|
|
|
def get_total_price(self):
|
|
return sum(self.get_item_prices())
|
|
|
|
def get_total_weight(self):
|
|
if len(self) > 0:
|
|
for item in self:
|
|
return item['product'].weight.value * sum(self.get_all_item_quantities())
|
|
else:
|
|
return 0
|
|
|
|
def get_shipping_box(self):
|
|
if len(self) > 6 and len(self) <= 10:
|
|
return ShippingContainer.LG_FLAT_RATE_BOX
|
|
elif len(self) > 2 and len(self) <= 6:
|
|
return ShippingContainer.REGIONAL_RATE_BOX_B
|
|
elif len(self) <= 2:
|
|
return ShippingContainer.REGIONAL_RATE_BOX_A
|
|
else:
|
|
return ShippingContainer.VARIABLE
|
|
|
|
def get_shipping_cost(self):
|
|
if len(self) > 0 and self.session.get("shipping_address"):
|
|
try:
|
|
usps_rate_request = self.build_usps_rate_request()
|
|
except TypeError as e:
|
|
return Decimal('0.00')
|
|
usps = USPSApiWithRate(settings.USPS_USER_ID, test=True)
|
|
|
|
try:
|
|
validation = usps.get_rate(usps_rate_request)
|
|
except ConnectionError:
|
|
raise ValidationError(
|
|
'Could not connect to USPS, try again.'
|
|
)
|
|
|
|
logger.info(validation.result)
|
|
if not 'Error' in validation.result['RateV4Response']['Package']:
|
|
rate = validation.result['RateV4Response']['Package']['Postage']['CommercialRate']
|
|
else:
|
|
logger.error("USPS Rate error")
|
|
rate = '0.00'
|
|
return Decimal(rate)
|
|
else:
|
|
return Decimal('0.00')
|
|
|
|
def clear(self):
|
|
del self.session[settings.CART_SESSION_ID]
|
|
try:
|
|
del self.session['coupon_code']
|
|
except KeyError:
|
|
pass
|
|
self.session.modified = True
|
|
|
|
def build_usps_rate_request(self):
|
|
return \
|
|
{
|
|
'service': ShippingService.PRIORITY_COMMERCIAL,
|
|
'zip_origination': settings.DEFAULT_ZIP_ORIGINATION,
|
|
'zip_destination': f'{self.session.get("shipping_address")["postal_code"]}',
|
|
'pounds': '0',
|
|
'ounces': f'{self.get_total_weight()}',
|
|
'container': f'{self.get_shipping_box()}',
|
|
'width': '',
|
|
'length': '',
|
|
'height': '',
|
|
'girth': '',
|
|
'machinable': 'TRUE'
|
|
}
|
|
|
|
def build_order_params(self):
|
|
return \
|
|
{
|
|
'items': self,
|
|
'total_price': f'{self.get_total_price_after_discount()}',
|
|
'item_total': f'{self.get_total_price()}',
|
|
'discount': f'{self.get_discount()}',
|
|
'shipping_price': f'{self.get_shipping_cost()}',
|
|
'tax_total': '0',
|
|
'shipping_method': 'US POSTAL SERVICE',
|
|
'shipping_address': self.build_shipping_address(self.session.get('shipping_address')),
|
|
}
|
|
|
|
def create_order(self):
|
|
params = self.build_order_params()
|
|
logger.info(f'\nParams: {params}\n')
|
|
if settings.DEBUG:
|
|
response = CreateOrder().create_order(params, debug=True)
|
|
else:
|
|
response = CreateOrder().create_order(params)
|
|
return response
|
|
|
|
def build_bulk_list(self, order):
|
|
bulk_list = []
|
|
|
|
for item in self:
|
|
for key, value in item['variations'].items():
|
|
bulk_list.append(OrderLine(
|
|
order=order,
|
|
product=item['product'],
|
|
customer_note=next((v[1] for i, v in enumerate(CoffeeGrind.GRIND_CHOICES) if v[0] == key), None),
|
|
unit_price=item['price'],
|
|
quantity=value['quantity'],
|
|
tax_rate=2,
|
|
))
|
|
|
|
return bulk_list
|
|
|
|
def build_shipping_address(self, address):
|
|
return \
|
|
{
|
|
'address_line_1': f'{address["street_address_1"]}',
|
|
'address_line_2': f'{address["street_address_2"]}',
|
|
'admin_area_2': f'{address["city"]}',
|
|
'admin_area_1': f'{address["state"]}',
|
|
'postal_code': f'{address["postal_code"]}',
|
|
'country_code': 'US'
|
|
}
|
|
|
|
@property
|
|
def coupon(self):
|
|
if self.coupon_code:
|
|
return Coupon.objects.get(code=self.coupon_code)
|
|
return None
|
|
|
|
def get_discount(self):
|
|
if self.coupon:
|
|
if self.coupon.discount_value_type == DiscountValueType.FIXED:
|
|
return round(self.coupon.discount_value, 2)
|
|
elif self.coupon.discount_value_type == DiscountValueType.PERCENTAGE:
|
|
return round((self.coupon.discount_value / Decimal('100')) * self.get_total_price(), 2)
|
|
return Decimal('0')
|
|
|
|
def get_subtotal_price_after_discount(self):
|
|
return round(self.get_total_price() - self.get_discount(), 2)
|
|
|
|
def get_total_price_after_discount(self):
|
|
return round(self.get_total_price() - self.get_discount() + self.get_shipping_cost(), 2)
|