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 / shipnotice.py
Size: Mime:
from os import environ
import datetime
import logging
from boto3 import resource
from botocore.exceptions import ClientError
from dateutil.tz import UTC
from validators import url
from .errors import CapturePaymentFailedError, ShipNoticeFailedError, ShipNoticeFailedWarning

fulfillment_table = environ['fulfillment_table'] if 'fulfillment_table' in environ else 'test.b2b.Fulfillment'
log_level = logging.getLevelName(environ['LOG_LEVEL']) if 'LOG_LEVEL' in environ else logging.INFO
logging.basicConfig()
logger = logging.getLogger()
logger.setLevel(log_level)

MAX_RETRY_COUNT = 5


class ShipNotice:
    def __init__(self, fulfillment):
        self.fulfillment = fulfillment
        self.profile = Profile.profile_for(customer=self.fulfillment['customer_edi_id'])

    def __record_status(self, was_successful, msg=None):
        try:
            dynamodb_resource = resource('dynamodb', region_name='us-east-1')
            table = dynamodb_resource.Table(fulfillment_table)
            fid = self.fulfillment['id']
            sdt = self.fulfillment['ship_date']

            logger.debug(f"Updating ship notice status (success: {was_successful}) for fulfillment {fid}")
            if was_successful:
                response = table.update_item(
                    Key={'id': fid},
                    UpdateExpression="set #s = :s, sent_date = :d",
                    ExpressionAttributeValues={
                        ':s': 'SENT',
                        ':d': datetime.datetime.now().astimezone(UTC).isoformat()
                    },
                    ExpressionAttributeNames={
                        '#s': 'status'
                    },
                    ReturnValues="UPDATED_NEW"
                )
            else:
                retry_count = self.fulfillment['retry_count'] if 'retry_count' in self.fulfillment else 0
                retry_count = retry_count + 1

                response = table.update_item(
                    Key={'id': fid},
                    UpdateExpression="set #s = :s, last_error = :m, last_error_date = :d, retry_count = :r",
                    ExpressionAttributeValues={
                        ':s': 'FAILED',
                        ':m': msg,
                        ':r': retry_count,
                        ':d': datetime.datetime.now().astimezone(UTC).isoformat()
                    },
                    ExpressionAttributeNames={
                        '#s': 'status'
                    },
                    ReturnValues="UPDATED_NEW"
                )

            return response
        except ClientError as ce:
            logger.error(ce)
            if ce.response['Error']['Code'] != 'ConditionalCheckFailedException':
                raise ce
        except Exception as e:
            logger.error(e)

    def is_required(self):
        try:
            if self.profile.integration_config.send_notice_to_url_from_order:
                if 'notification_url' in self.fulfillment:
                    notification_url = self.fulfillment['notification_url']
                    if url(notification_url) is not True:
                        return False
                else:
                    return False

            retry_count = self.fulfillment['retry_count'] if 'retry_count' in self.fulfillment else 0
            if (FulfillmentStatus.for_(self.fulfillment['status']) in (FulfillmentStatus.NOT_SENT, FulfillmentStatus.FAILED)
                or 'status' not in self.fulfillment) \
                    and retry_count < MAX_RETRY_COUNT:
                return True
            return False
        except KeyError:
            return False

    def cancel(self):
        if self.profile.integration_type == 'shopify':
            try:
                if 'channel_fulfillment_id' in self.fulfillment:
                    from lib_b2b.shopify import CancelFullfillmentAction
                    CancelFullfillmentAction(self.fulfillment).send()
                    logging.info(f"Sent shopify fulfillment cancellation: [{self.fulfillment['id']}] "
                                 f"for order [{self.fulfillment['order_id']}]")
                    return self.fulfillment
                else:
                    logger.warning(f"Unable to send cancellation, fulfillment {self.fulfillment['id']} does not contain a channel_fulfillment_id.")
            except Exception as e:
                logging.warning(f"Failed to send shopify fulfillmment cancellation: "
                                f"[{self.fulfillment['id']}] for order [{self.fulfillment['order_id']}]", e)

    def notify(self):
        if self.is_required():
            if self.profile.integration_type == 'standard':
                if self.profile.integration_config.send_notice_to_url_from_order:
                    try:
                        StandardShipNoticeAction(self.fulfillment).send()
                        self.__record_status(was_successful=True)
                        logging.info(f"Sent ship notice for fulfillment: [{self.fulfillment['id']}] "
                                     f"with status [{self.fulfillment['status']}] "
                                     f"for order: [{self.fulfillment['order_id']}]")
                        return self.fulfillment
                    except Exception as e:
                        snf = ShipNoticeFailedWarning(e)
                        self.__record_status(was_successful=False, msg=str(snf))
                        logging.warning(f"Failed to send ship notice for order: [{self.fulfillment['id']}]. "
                                        f"Will retry on next scheduled interval. ", snf)
                        logging.warning(snf)

            if self.profile.integration_type == 'shopify':
                if self.profile.integration_config.capture_payment_on_ship:
                    try:
                        from lib_b2b.shopify import CapturePaymentAction
                        CapturePaymentAction(self.fulfillment).send()
                        logging.info(f"Successfully sent capture payment transaction to "
                                     f"shopify for order: [{self.fulfillment['order_id']}]")
                        return self.fulfillment
                    except Exception as e:
                        ce = CapturePaymentFailedError(e)
                        logging.error(f"Failed to send shopify capture payment order: [{self.fulfillment['order_id']}]", ce)
                        logging.error(ce)

                if self.profile.integration_config.send_fulfillment_on_ship:
                    try:
                        from lib_b2b.shopify import FullfillmentAction
                        FullfillmentAction(self.fulfillment).send()
                        self.__record_status(was_successful=True)
                        logging.info(f"Sent shopify fulfillment: [{self.fulfillment['id']}] "
                                     f"for order [{self.fulfillment['order_id']}]")
                        return self.fulfillment
                    except Exception as e:
                        snf = ShipNoticeFailedWarning(e)
                        self.__record_status(was_successful=False, msg=str(snf))
                        logging.warning(f"Failed to send shopify fulfillmment: "
                                        f"[{self.fulfillment['id']}] for order [{self.fulfillment['order_id']}]", snf)
                        logging.warning(snf)
        else:
            if 'status' not in self.fulfillment or FulfillmentStatus.for_(self.fulfillment['status']) != FulfillmentStatus.SENT:
                retry_count = self.fulfillment['retry_count'] if 'retry_count' in self.fulfillment else 0
                if retry_count >= MAX_RETRY_COUNT:
                    snf = ShipNoticeFailedError(f"Maximum retries exhausted trying to send ship notice "
                                                f"for fulfillmment: [{self.fulfillment['id']}] for order "
                                                f"[{self.fulfillment['order_id']}]. ")
                    logging.error(snf)
                else:
                    snf = ShipNoticeFailedError(f"Ship notice not required for fulfillment that is not sent. "
                                                f"fulfillmment: [{self.fulfillment['id']}], "
                                                f"order: [{self.fulfillment['order_id']}]")
                    logging.error(snf)


# Moved to bottom to avoid silly python circular import problems
from lib_b2b.fulfillment_status import FulfillmentStatus
from lib_b2b.standard import StandardShipNoticeAction
from lib_b2b.profile import Profile