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_change / cancel_line.py
Size: Mime:
import abc
import logging
from datetime import datetime

from lib_b2b.change import ChangeRecord, ChangeDataProvider
from lib_b2b.errors import LineCancelError, ErrorRecord, VersionConflictError
from lib_b2b.order import Order
from lib_b2b.order_change import OrderChangeRequest, SimpleOrderPredicate
from lib_b2b.order_line import OrderLineCancellation, OrderLine
from lib_b2b.order_status import OrderStatus
from lib_b2b.policy import Policy

logger = logging.getLogger(__name__)


class OrderCancelLineChangeDataProvider(ChangeDataProvider, metaclass=abc.ABCMeta):
    @property
    @abc.abstractmethod
    def cancellations(self) -> [OrderLineCancellation]:
        raise NotImplementedError


class OrderCancelLineChangeRequest(OrderChangeRequest):
    # TODO: - Move these policies to be data driven
    _policy = Policy(
        name='cancel_line_change_policy',
        subject=Policy.SUBJECT_ALL,
        predicates=[
            SimpleOrderPredicate(
                lambda o: o.status < OrderStatus.SHIPPED,
                'Line cancellation is only allowed prior to shipping.')
        ]
    )

    def __init__(self, on_behalf_of: str, change_data_provider: OrderCancelLineChangeDataProvider):
        super().__init__(on_behalf_of, OrderCancelLineChangeRequest._policy)
        self.change_data_provider = change_data_provider

    def will_change(self, order: Order) -> bool:
        if order.status is OrderStatus.CANCELLED:
            return False
        if self.change_data_provider.cancellations:
            for cancellation in self.change_data_provider.cancellations:
                line = order.line_matching(purchase_order_line=cancellation.purchase_order_line,
                                           channel_line_id=cancellation.channel_line_id)
                if line and not line.cancellation_exists(cancellation) and not line.cancelled:
                    return True
        return False

    def __cancel_line(self, line: OrderLine, cancellation: OrderLineCancellation):
        if line.cancellation_exists(cancellation):
            logger.info(f'Line cancellation request {cancellation.cancellation_id} has already '
                        f'been processed for this line.')
            return
        if line.fulfilled:
            raise LineCancelError(line_id=line.order_line_id,
                                  message=f"Unable to cancel a line that is already fulfilled "
                                          f"[line_id: {line.order_line_id}, "
                                          f"cancellation_id: {cancellation.cancellation_id}]")
        if line.cancelled:
            logger.warning(f"Attempted to cancel line {line.order_line_id} that was already cancelled.")
            return

        from lib_b2b.orders import Orders
        from lib_b2b.order_status import OrderStatus
        order = Orders.for_(cancellation.order_id)
        if order.status >= OrderStatus.SHIPPED:
            raise LineCancelError(line_id=line.order_line_id,
                                  message=f"Unable to cancel a line on an order that is already shipped or cancelled"
                                          f"[line_id: {line.order_line_id}, "
                                          f"cancellation_id: {cancellation.cancellation_id}]")

        # Can we cancel this amount on this line??
        if cancellation.cancel_qty > line.remaining_qty:
            raise LineCancelError(line.order_line_id, f"Requested quantity {cancellation.cancel_qty} "
                                                      f"exceeds the remaining quantity "
                                                      f"{line.remaining_qty} for the line "
                                                      f"{line.order_line_id}")
        else:
            line.cancelled_qty = line.cancelled_qty + cancellation.cancel_qty
            if line.cancelled_qty >= line.quantity:
                line.cancelled = True
            line.cancellations.append(cancellation)
            order._modify_line(line, version=self.change_data_provider.version())
            if order.status >= OrderStatus.ENTERED:
                from lib_b2b.erp import ERP
                ERP.for_(order.customer_edi_id).cancel_line(line.order_id,
                                                            line.order_line_id,
                                                            quantity=cancellation.cancel_qty)
            else:
                logger.info(f"Order not yet in ERP, cancellation there of order "
                            f"line {line.order_line_id} is unnecessary.")

    def apply(self, order: Order):
        self.changeable_object = order
        if super().permitted(order):
            if self.will_change(order):
                for cancellation in self.change_data_provider.cancellations:
                    try:
                        line = order.line_matching(purchase_order_line=cancellation.purchase_order_line,
                                                   channel_line_id=cancellation.channel_line_id)
                        before_cancellations = list(map(lambda x: x.as_dict(), line.cancellations))
                        self.__cancel_line(line, cancellation)
                        after_cancellations = list(map(lambda x: x.as_dict(), line.cancellations))
                        order.record(ChangeRecord(before=before_cancellations, after=after_cancellations,
                                                  description=f"Qty of {cancellation.cancel_qty} cancelled on line {line.order_line_id}",
                                                  when=datetime.now(), who=self.on_behalf_of)
                                     )

                    except LineCancelError as lce:
                        order.error(ErrorRecord(code="ORDER-64",
                                                msg=lce.sanitized_message))
                        raise lce
                    except ValueError as ve:
                        raise VersionConflictError(str(ve))