Repository URL to install this package:
|
Version:
0.4.200 ▾
|
lib-py-b2b
/
shopify_order_builder.py
|
|---|
from itertools import groupby
from dateutil.tz import UTC
from lib_b2b.additional_charge import AdditionalCharge
from lib_b2b.address import Address
from lib_b2b.customer import Customer
from lib_b2b.discount import Discount, DiscountType
from lib_b2b.financial_status import FinancialStatus
from lib_b2b.order import Order
from lib_b2b.order_dates import OrderDatesDelegate
from lib_b2b.order_line import OrderLine
from lib_b2b.order_builder import OrderBuilder
from lib_b2b.order_request import ShopifyOrderRequest
from datetime import datetime, timedelta, timezone
from dateutil.parser import parse
import logging
from os import environ
from lib_b2b.order_totals import OrderTotals
from lib_b2b.refund import Refund
from lib_b2b.tax_line import TaxLine
from lib_b2b.util import as_decimal
logger = logging.getLogger(__name__)
class ShopifyOrderBuilder(OrderBuilder):
def __init__(self, request: ShopifyOrderRequest):
super().__init__(request)
@property
def customer(self):
if not self._customer:
self._customer = Customer.fetch_by_shopify_domain(self.request.shopify_domain)
return self._customer
@property
def order_id(self):
return Order.generate_id(self.customer['customer_edi_id'], str(self.request.order_number))
def __is_recycle_fee(self, sku):
if sku.startswith('MRC-'):
return True
return False
def _build_identifiers(self):
_order = {}
_order['channel_order_id'] = self.request.id
_order['channel_order_name'] = self.request.name
_order['channel_name'] = "Shopify"
_order['customer_edi_id'] = self.customer['customer_edi_id']
_order['purchase_order'] = str(self.request.order_number)
_order['purchase_order_revision'] = 1
_order['version'] = self.request.dialect.updated_at.isoformat()
_order['carrier'] = "FDXG"
if self.request.note:
_order['note'] = self.request.note
_order['financial_status'] = FinancialStatus(self.request.financial_status).value if self.request.financial_status else None
if self.request.order_status_url:
_order['order_status_url'] = self.request.order_status_url
return _order
def _build_dates(self):
_order = {}
if self.request.created_at:
_order['order_date'] = parse(self.request.created_at).astimezone(tz=timezone.utc).isoformat()
else:
_order['order_date'] = datetime.now(tz=timezone.utc).isoformat()
date_delegate = OrderDatesDelegate(self.customer['customer_edi_id'], parse(_order['order_date']))
release_date = date_delegate.release_date.date
_order['release_on'] = f"{release_date.isoformat()}"
not_before_date = None
try:
if self.request.note_attributes:
delay_list = list(map(lambda y: y['value'], filter(lambda x: x['name'] == 'Delay Date',
self.request.note_attributes)))
if delay_list:
try:
if 'T' in str(delay_list[0]):
_nbd = parse(delay_list[0])
_nbd = _nbd.replace(hour=15, minute=0, second=0)
not_before_date = _nbd.astimezone(UTC).isoformat()
else:
not_before_date = parse(delay_list[0] + 'T15:00:00').astimezone(UTC).isoformat()
except (ValueError, OverflowError) as ve:
logger.exception(f"Unable to parse the date format for the delay date: "
f"'{delay_list[0]}' on order: {str(self.request.order_number)}. "
f"[{str(ve)}]")
except Exception as e:
logger.exception(f"Error attempting to parse Delay Date from Shopify order {self.request.order_id}", e)
required_date = not_before_date or (datetime.now().astimezone(UTC) + timedelta(days=2)).isoformat()
_order['required_date'] = required_date
_order['not_before_date'] = not_before_date
return _order
def _build_notification_urls(self):
_order = {}
_order['notification_urls'] = {}
return _order
def __build_recycling_additional_charge(self, data) -> AdditionalCharge:
qty = as_decimal(data['quantity'], 'order_line_qty')
price = as_decimal(data['price'], 'order_line_price')
amount = qty * price
sku = data['sku'] if 'sku' in data and data['sku'] else data['name']
_tax_lines = []
for tax in data['tax_lines']:
_tax_lines.append(
TaxLine(
amount=as_decimal(tax['price'], 'additional_charge_tax_price'),
rate=as_decimal(tax['rate'], 'additional_charge_tax_rate'),
tax_type=tax['title']
)
)
return AdditionalCharge(
amount=amount,
title=sku,
charge_type='Fees',
value=qty * price,
tax_lines=_tax_lines,
discounts=[],
channel_line_id=data.get('id')
)
def __build_line(self, line_count, order_line, discount_applications, order_id):
_line = {}
qty = as_decimal(order_line['quantity'], 'order_line_qty')
price = as_decimal(order_line['price'], 'order_line_price')
amount = qty * price
_tax_lines = []
_discounts = []
for tax in order_line['tax_lines']:
_tax_lines.append(
TaxLine(
amount=as_decimal(tax['price'], 'additional_charge_tax_price'),
rate=as_decimal(tax['rate'], 'additional_charge_tax_rate'),
tax_type=tax['title']
)
)
for discount in order_line['discount_allocations']:
_da = discount_applications[discount['discount_application_index']]
_discounts.append(
Discount(
amount=as_decimal(discount['amount'], 'line_discount_amt'),
code=_da['code'] if 'code' in _da and _da['code'] else _da['title'] if 'title' in _da and _da['title'] else _da['description'] if 'description' in _da and _da['description'] else None,
discount_type=DiscountType(_da['value_type']),
value=as_decimal(_da['value'], 'line_discount_value')
)
)
return OrderLine(
purchase_order_line=str(line_count).zfill(4),
product_external_id=order_line['sku'],
product_id=order_line.get('sku', order_line.get('name', None)),
quantity=qty,
note=order_line.get('note', None),
channel_line_id=order_line['id'],
channel_line_variant_id=order_line['variant_id'],
amount=amount,
unit_price=price,
tax_lines=_tax_lines,
discounts=_discounts,
order_id=order_id,
cancelled=False
)
def _build_lines(self, ship_to: Address) -> ([OrderLine], [AdditionalCharge]):
_lines = []
_addl_chrgs = []
# Orders Lines
line_count = 1
for i, order_line in enumerate(self.request.line_items, 1):
sku = order_line['sku'] if 'sku' in order_line and order_line['sku'] else order_line['name']
if self.__is_recycle_fee(sku):
_addl_chrgs.append(self.__build_recycling_additional_charge(order_line))
else:
_lines.append(self.__build_line(line_count=line_count, order_line=order_line,
discount_applications=self.request.discount_applications,
order_id=self.order_id))
line_count += 1
return _lines, _addl_chrgs
def _build_additional_charges(self) -> [AdditionalCharge]:
_charges = []
for line in self.request.shipping_lines:
_tax_lines = []
_discounts = []
for tax in line['tax_lines']:
_tax_lines.append(
TaxLine(
amount=as_decimal(tax['price'], 'additional_charge_tax_price'),
rate=as_decimal(tax['rate'], 'additional_charge_tax_rate'),
tax_type=tax['title']
)
)
for discount in line['discount_allocations']:
_da = self.request.discount_applications[discount['discount_application_index']]
_discounts.append(
Discount(
amount=as_decimal(discount['amount'], 'line_discount_amt'),
code=_da['code'] if 'code' in _da and _da['code'] else _da['title'] if 'title' in _da and _da[
'title'] else _da['description'] if 'description' in _da and _da['description'] else None,
discount_type=DiscountType(_da['value_type']),
value=as_decimal(_da['value'], 'line_discount_value')
)
)
_charges.append(
AdditionalCharge(
amount=as_decimal(line['price'], 'additional_charge_amount'),
title=line['title'],
charge_type=line['code'],
value=as_decimal(line['price'], 'additional_charge_amount'),
tax_lines=_tax_lines,
discounts=_discounts
)
)
return _charges
def _build_discounts(self) -> [Discount]:
_discounts = []
_discount_applications = self.request.discount_applications
_discount_applications_by_title = {
key: [app for app in group]
for key, group in groupby(_discount_applications,
lambda app: app['title'] if 'title' in app else app['code'] if 'code' in app else
app['description'] if 'description' in app else 'unknown')
}
_discount_codes = self.request.discount_codes
for code in _discount_codes:
_discount_app = _discount_applications_by_title[code['code']][0]
_discounts.append(
Discount(
amount=as_decimal(code['amount'], 'discount_code_amount'),
code=code['code'],
discount_type=DiscountType(code['type']),
value=as_decimal(_discount_app['value'], 'discount_app_value_for_code')
)
)
return _discounts
def _build_refunds(self) -> [Refund]:
return self.request.dialect.refunds
def _build_totals(self, lines: [OrderLine], discounts: [Discount],
additional_charges: [AdditionalCharge], refunds: [Refund]) -> OrderTotals:
return OrderTotals.create(order_lines=lines,
discounts=discounts,
additional_charges=additional_charges,
refunds=refunds,
total_amount=as_decimal(self.request.total_price, 'total_amount'),
total_line_amount=as_decimal(self.request.total_line_items_price, 'total_line_amount'),
total_tax=as_decimal(self.request.total_tax, 'total_tax'),
total_discount=as_decimal(self.request.total_discounts, 'total_discount'))
# _totals = {}
# _totals['total_amount'] = as_decimal(self.request.total_price, 'total_amount')
# _totals['total_line_amount'] = as_decimal(self.request.total_line_items_price, 'total_line_amount')
# _totals['total_tax'] = as_decimal(self.request.total_tax, 'total_tax')
# _totals['total_additional_charges'] = as_decimal(sum(addl_chg.amount for addl_chg in additional_charges), 'total_additional_charges')
# _totals['total_discount'] = as_decimal(self.request.total_discounts, 'total_discount')
# return _totals