Repository URL to install this package:
| 
      
     
      
        
        
        Version: 
        
         
  
        
    
          
          0.4.184  ▾
        
         
  
      
        
      
  
      
  
     | 
    
    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