Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
lib-py-b2b / order_request_handler.py
Size: Mime:
import abc
import json
from py_aws_util.logging import log_data
from lib_b2b.errors import OrderNotFoundError, OrderFetchError, BusinessError, TechnicalError, VersionConflictError, \
    PolicyViolation, ConditionCheckError, OrderSaveError
from lib_b2b.order_builder import OrderBuilder
from lib_b2b.order_change.cancel import OrderCancelChangeRequest
from lib_b2b.order_change.paid import OrderFinancialStatusChangeRequest
from lib_b2b.order_request import OrderRequest, OrderRequestType
from lib_b2b.orders import Orders
import logging
from aws_xray_sdk.core import xray_recorder

logger = logging.getLogger(__name__)


class OrderRequestHandler:
    @staticmethod
    def for_(request_type: OrderRequestType):
        if request_type is OrderRequestType.CANCEL:
            return CancelOrderRequestHandler()
        elif request_type is OrderRequestType.CREATE:
            return CreateOrderRequestHandler()
        elif request_type is OrderRequestType.FULFILLED:
            # TODO: - It may be worth evaulating whether we want to listen for this on the fulfillment side
            raise NotImplementedError
        elif request_type is OrderRequestType.PAID:
            return PaidOrderRequestHandler()
        elif request_type is OrderRequestType.PARTIALLY_FULFILLED:
            # TODO: - It may be worth evaluating whether we want to listen for this on the fulfillment side
            raise NotImplementedError
        elif request_type is OrderRequestType.CHANGED:
            return ChangedOrderRequestHandler()
        else:
            raise NotImplementedError

    @abc.abstractmethod
    def handle(self, request: OrderRequest):
        raise NotImplementedError


class CancelOrderRequestHandler(OrderRequestHandler):
    @xray_recorder.capture()
    def handle(self, request: OrderRequest):
        try:
            if not Orders.exists(request.order_id):
                try:
                    # go ahead and try to create the order in case this was received out of order
                    order_data = OrderBuilder.for_(request).build()
                    from lib_b2b.order import Order
                    Order.create(data=order_data, version=order_data.get('version'))
                except (VersionConflictError, ConditionCheckError) as ce:
                    logger.warning(f"Tried to create an order in support of a pre-mature cancellation request and the "
                                   f"order seems to already exist. {str(ce)}",
                                   extra=log_data(order_id=request.order_id,
                                                  request_data=request.request_data), exc_info=True)

            order = Orders.for_(request.order_id)
            data_provider = request.dialect.data_provider_for(request, OrderCancelChangeRequest)
            change_request = OrderCancelChangeRequest(on_behalf_of=request.principle_id,
                                                      change_data_provider=data_provider)
            if change_request.will_change(order):
                order.change([change_request])

        except OrderNotFoundError:
            logger.error(f"Received cancellation for order that is not in the system [{request.order_id}].",
                         extra=log_data(order_id=request.order_id, request_data=request.request_data), exc_info=True)
        except (OrderFetchError, OrderSaveError) as oe:
            logger.error(f"Unable to process cancellation request due to a problem with the DDB "
                         f"connection. [{str(oe)}] [order_id={request.order_id}].",
                         extra=log_data(order_id=request.order_id, request_data=request.request_data),
                         exc_info=True)
        except BusinessError as be:
            from lib_b2b.notification import Notifier, NotificationType, ErrorNotificationData
            Notifier.notify(
                notification_type=NotificationType.CUSTSVC,
                customer=request.dialect.customer['customer_edi_id'],
                subject="Unable to Cancel Order",
                data=ErrorNotificationData(order_id=request.order_id,
                                           message="Unable to automatically cancel order",
                                           errors=[{'message': be.sanitized_message}])
            )
            logger.error(be.sanitized_message,
                         extra=log_data(order_id=request.order_id,
                                        request_data=request.request_data),
                         exc_info=True)
        except TechnicalError as te:
            logger.error("Unable to process order cancel request.",
                         extra=log_data(order_id=request.order_id,
                                        errorMessage=te.message,
                                        request_data=request.request_data), exc_info=True)


class CreateOrderRequestHandler(OrderRequestHandler):
    @xray_recorder.capture()
    def handle(self, request: OrderRequest):
        try:
            if not Orders.exists(request.order_id):
                order_data = OrderBuilder.for_(request).build()
                from lib_b2b.order import Order
                Order.create(order_data)
            else:
                logger.info(f"Order {request.order_id} already exists. Disregarding new create request.")
        except BusinessError as be:
            from lib_b2b.notification import Notifier, NotificationType, ErrorNotificationData
            Notifier.notify(
                notification_type=NotificationType.CUSTSVC,
                customer=request.dialect.customer['customer_edi_id'],
                subject="Unable to Create Order",
                data=ErrorNotificationData(order_id=request.order_id,
                                           message="Unable to automatically create order",
                                           errors=[{'message': be.sanitized_message}])
            )
            logger.error(be.sanitized_message,
                         extra=log_data(order_id=request.order_id,
                                        request_data=request.request_data),
                         exc_info=True)
        except TechnicalError as te:
            logger.error("Unable to process order create request.",
                         extra=log_data(order_id=request.order_id,
                                        errorMessage=te.message,
                                        request_data=request.request_data), exc_info=True)


class PaidOrderRequestHandler(OrderRequestHandler):
    @xray_recorder.capture()
    def handle(self, request: OrderRequest):
        try:
            if not Orders.exists(request.order_id):
                try:
                    # go ahead and try to create the order in case this was received out of order
                    order_data = OrderBuilder.for_(request).build()
                    from lib_b2b.order import Order
                    Order.create(data=order_data, version=order_data.get('version'))
                except (VersionConflictError, ConditionCheckError) as ce:
                    logger.warning(f"Tried to create an order in support of a pre-mature paid event and the "
                                   f"order seems to already exist. {str(ce)}",
                                   extra=log_data(order_id=request.order_id,
                                                  request_data=request.request_data), exc_info=True)

            order = Orders.for_(request.order_id)
            # TODO: - fix principle id reference
            data_provider = request.dialect.data_provider_for(request, OrderFinancialStatusChangeRequest)
            change_request = OrderFinancialStatusChangeRequest(on_behalf_of=request.principle_id,
                                                               change_data_provider=data_provider)
            order.change([change_request])
        except OrderNotFoundError:
            logger.warning(f"Received paid event for order that is not in the system [{request.order_id}].",
                           exc_info=True, extra=request.request_data)
        except OrderFetchError:
            logger.error(f"Unable to process paid event due to a problem with the DDB "
                         f"connection [{request.order_id}].", exc_info=True, extra=request.request_data)
        except VersionConflictError as ve:
            logger.warning(f"Version conflict trying to process paid event. This is probably not a problem as "
                           f"events can come in out of order. {str(ve)}", extra=request.request_data)
        except PolicyViolation as pv:
            logger.warning(f"Policy violation processing paid event. This is probably not a problem as events "
                           f"can come in out of order. {str(pv)}", extra=request.request_data)
        except BusinessError as be:
            from lib_b2b.notification import Notifier, NotificationType, ErrorNotificationData
            order = Orders.for_(request.order_id)
            Notifier.notify(
                notification_type=NotificationType.CUSTSVC,
                customer=request.dialect.customer['customer_edi_id'],
                subject="Unable to Mark Order Paid",
                data=ErrorNotificationData(order_id=order.order_id,
                                           message="Unable to automatically mark order paid",
                                           errors=[{'message': be.sanitized_message}])
            )
            logger.error(be.sanitized_message,
                         extra=log_data(order_id=request.order_id,
                                        request_data=request.request_data),
                         exc_info=True)
        except TechnicalError as te:
            logger.error("Unable to process order paid request.",
                         extra=log_data(order_id=request.order_id,
                                        errorMessage=te.message,
                                        request_data=request.request_data), exc_info=True)
            print(json.dumps(request.request_data))


class ChangedOrderRequestHandler(OrderRequestHandler):
    @xray_recorder.capture()
    def handle(self, request: OrderRequest):
        try:
            if not Orders.exists(request.order_id):
                try:
                    # go ahead and try to create the order in case this was received out of order
                    order_data = OrderBuilder.for_(request).build()
                    from lib_b2b.order import Order
                    Order.create(data=order_data, version=order_data.get('version'))
                except (VersionConflictError, ConditionCheckError) as ce:
                    logger.warning(f"Tried to create an order in support of a pre-mature change event and the "
                                   f"order seems to already exist. {str(ce)}",
                                   extra=log_data(order_id=request.order_id,
                                                  request_data=request.request_data), exc_info=True)
            order = Orders.for_(request.order_id)
            change_types = order.supported_change_request_types()
            detected_changes = []
            for change_type in change_types:
                try:
                    logger.info(change_type)
                    data_provider = request.dialect.data_provider_for(request, change_type)
                    change_request = change_type(on_behalf_of=request.principle_id, change_data_provider=data_provider)
                    if change_request.will_change(order):
                        detected_changes.append(change_request)
                except NotImplementedError:
                    pass  # dialect doesn't support this change type no need to do anything
            if detected_changes:
                order.change(detected_changes)
            else:
                logger.warning("Did not apply change due to order being is status CANCELLED", extra=log_data(request.request.as_dict()))
        except OrderNotFoundError:
            logger.warning(f"Received change for order that is not in the system [{request.order_id}].",
                           exc_info=True, extra=request.request_data)
        except OrderFetchError:
            logger.error(f"Unable to process change request due to a problem with the DDB "
                         f"connection [{request.order_id}].", exc_info=True, extra=request.request_data)
        except VersionConflictError as ve:
            logger.warning(f"Version conflict trying to make change. This is probably not a problem as events can "
                           f"come in out of order. {str(ve)}", extra=request.request_data)
        except PolicyViolation as pv:
            logger.warning(f"Policy violation processing change. This is probably not a problem as events can come "
                           f"in out of order. {str(pv)}", extra=request.request_data)
        except BusinessError as be:
            from lib_b2b.notification import Notifier, NotificationType, ErrorNotificationData
            Notifier.notify(
                notification_type=NotificationType.CUSTSVC,
                customer=request.dialect.customer['customer_edi_id'],
                subject="Unable to Change Order",
                data=ErrorNotificationData(order_id=request.order_id,
                                           message="Unable to automatically change order",
                                           errors=[{'message': be.sanitized_message}])
            )
            logger.error(be.sanitized_message,
                         extra=log_data(order_id=request.order_id,
                                        request_data=request.request_data),
                         exc_info=True)
        except TechnicalError as te:
            logger.error("Unable to process order change request.",
                         extra=log_data(order_id=request.order_id,
                                        errorMessage=te.message,
                                        request_data=request.request_data), exc_info=True)