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 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().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().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