Ejemplo n.º 1
0
class StaticPageStorage(object):
    __ENTITY_PROPERTY_DESCRIPTOR = '__descriptor'
    __ENTITY_PROPERTY_NAME = '__name'
    __ENTITY_PROPERTY_CONTENT = '__content'

    def __init__(self):
        # is better to use composition instead of inheritance
        self.__storage = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__storage.PARTITION_KEY = 'STATIC_PAGE'
        self.__reflector = Reflector()

    def save(self, entity: StaticPage) -> None:
        if not isinstance(entity, StaticPage):
            raise ArgumentTypeException(self.save, 'entity', entity)

        data = self.__reflector.extract(entity, (
            self.__class__.__ENTITY_PROPERTY_DESCRIPTOR,
            self.__class__.__ENTITY_PROPERTY_NAME,
            self.__class__.__ENTITY_PROPERTY_CONTENT,
        ))

        self.__storage.put_item(
            data[self.__class__.__ENTITY_PROPERTY_DESCRIPTOR], {
                'name': data[self.__class__.__ENTITY_PROPERTY_NAME],
                'content': data[self.__class__.__ENTITY_PROPERTY_CONTENT]
            })

    def __get_instance(self, data: dict) -> StaticPage:
        entity: StaticPage = self.__reflector.construct(
            StaticPage, {
                self.__class__.__ENTITY_PROPERTY_DESCRIPTOR: data['sk'],
                self.__class__.__ENTITY_PROPERTY_NAME: data['name'],
                self.__class__.__ENTITY_PROPERTY_CONTENT: data['content'],
            })
        return entity

    def get_by_descriptor(self, descriptor: str) -> Optional[StaticPage]:
        if not isinstance(descriptor, str):
            raise ArgumentTypeException(self.get_by_descriptor, 'descriptor',
                                        descriptor)
        elif not str(descriptor).strip():
            raise ArgumentCannotBeEmptyException(self.get_by_descriptor,
                                                 'descriptor')

        data = self.__storage.find_item(descriptor)
        return self.__get_instance(data) if data else None

    def remove(self, descriptor: str) -> None:
        if not isinstance(descriptor, str):
            raise ArgumentTypeException(self.get_by_descriptor, 'descriptor',
                                        descriptor)
        elif not str(descriptor).strip():
            raise ArgumentCannotBeEmptyException(self.get_by_descriptor,
                                                 'descriptor')

        self.__storage.delete_item(descriptor)
Ejemplo n.º 2
0
    def checkout_next_tier_indication():
        # @todo : this is a crutch
        # This should not be calculated on mpc side. This should be an api request to somewhere,
        # but this is impossible for now, so we have what we have.

        from chalicelib.settings import settings
        # from chalicelib.libs.core.elastic import Elastic
        from chalicelib.libs.models.mpc.base import DynamoModel
        from chalicelib.libs.purchase.customer.storage import CustomerStorageImplementation
        from chalicelib.libs.purchase.customer.storage import CustomerTierStorageImplementation

        try:
            user_id = __get_user().id

            customers_storage = CustomerStorageImplementation()
            tiers_storage = CustomerTierStorageImplementation()
            customer = customers_storage.get_by_id(Id(user_id))
            if customer.tier.is_neutral:
                return {
                    'currently_spent': 0,
                    'next_tier': None
                }

            # Spent amount for customer can not exist,
            # if customer spent nothing or sqs is delayed, for example.

            # We can use a tier's minimal amount to return a value close to real.
            # elastic = Elastic(
            #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT,
            #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT
            # )
            # row = elastic.get_data(customer.email.value)
            dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
            dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_SPENT_AMOUNT'
            row = dynamo_db.find_item(customer.email.value)

            customer_spent_amount = float(row['spent_amount'] or 0) if row else customer.tier.spent_amount_min

            current_tiers = list(tiers_storage.get_all())
            current_tiers.sort(key=lambda tier: tier.spent_amount_min)
            for i in range(0, len(current_tiers) - 1):
                if current_tiers[i].id == customer.tier.id:
                    next_tier = current_tiers[i+1]
                    break
            else:
                next_tier = current_tiers[-1]

            return {
                'currently_spent': customer_spent_amount,
                'next_tier': {
                    'name': next_tier.name.value,
                    'amount_min': next_tier.spent_amount_min,
                }
            }
        except BaseException as e:
            return http_response_exception_or_throw(e)
Ejemplo n.º 3
0
 def __init__(self):
     # """
     # curl -X DELETE localhost:9200/customer_tiers_customer_info_spent_amount
     # curl -X PUT localhost:9200/customer_tiers_customer_info_spent_amount -H "Content-Type: application/json" -d'{
     #     "mappings": {
     #         "customer_tiers_customer_info_spent_amount": {
     #             "properties": {
     #                 "spent_amount": {"type": "float"}
     #             }
     #         }
     #     }
     # }'
     # """
     # self.__elastic = Elastic(
     #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT,
     #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT
     # )
     self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
     self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_SPENT_AMOUNT'
Ejemplo n.º 4
0
    def __init__(self):
        # @todo : link to tier should be with other data. Will be moved to Nexus soon.
        # """
        # curl -X DELETE localhost:9200/customer_tiers_customer_tiers
        # curl -X PUT localhost:9200/customer_tiers_customer_tiers -H "Content-Type: application/json" -d'{
        #     "mappings": {
        #         "customer_tiers_customer_tiers": {
        #             "properties": {
        #                 "tier_id": {"type": "integer"}
        #             }
        #         }
        #     }
        # }'
        # """
        # self.__tier_elastic = Elastic(
        #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_TIERS,
        #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_TIERS
        # )
        self.__tiers_dynamo = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__tiers_dynamo.PARTITION_KEY = 'PURCHASE_CUSTOMER_TIERS_CUSTOMER_TIER'

        self.__tiers_storage = CustomerTierStorageImplementation()
Ejemplo n.º 5
0
class CrutchCustomerSpentAmountSqsHandler(SqsHandlerInterface):
    """
    @todo : this is a crutch
    This should be an api request, but it is impossible for now,
    so we use sqs-communication.
    This data is used to calculate next possible tier on mpc side.
    """
    def __init__(self):
        # """
        # curl -X DELETE localhost:9200/customer_tiers_customer_info_spent_amount
        # curl -X PUT localhost:9200/customer_tiers_customer_info_spent_amount -H "Content-Type: application/json" -d'{
        #     "mappings": {
        #         "customer_tiers_customer_info_spent_amount": {
        #             "properties": {
        #                 "spent_amount": {"type": "float"}
        #             }
        #         }
        #     }
        # }'
        # """
        # self.__elastic = Elastic(
        #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT,
        #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT
        # )
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_SPENT_AMOUNT'

    def handle(self, sqs_message: SqsMessage) -> None:
        for item in sqs_message.message_data.get('items'):
            customer_email = str(item['customer_email'])
            spent_amount = int(item['spent_amount'])

            # if self.__elastic.get_data(customer_email):
            #     self.__elastic.update_data(customer_email, {'doc': {'spent_amount': spent_amount}})
            # else:
            #     self.__elastic.create(customer_email, {'spent_amount': spent_amount})
            self.__dynamo_db.put_item(customer_email,
                                      {'spent_amount': spent_amount})
Ejemplo n.º 6
0
 def __init__(self):
     # is better to use composition instead of inheritance
     self.__storage = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
     self.__storage.PARTITION_KEY = 'EMAIL_SUBSCRIPTION'
     self.__reflector = Reflector()
Ejemplo n.º 7
0
class SubscriptionStorage:
    __ENTITY_PROPERTY_SUBSCRIPTION_ID = '__subscription_id'
    __ENTITY_PROPERTY_EMAIL = '__email'
    __ENTITY_PROPERTY_USER_ID = '__user_id'
    __ENTITY_PROPERTY_SUBSCRIBED_AT = '__subscribed_at'
    __ENTITY_PROPERTY_UNSUBSCRIBED_AT = '__unsubscribed_at'

    def __init__(self):
        # is better to use composition instead of inheritance
        self.__storage = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__storage.PARTITION_KEY = 'EMAIL_SUBSCRIPTION'
        self.__reflector = Reflector()

    def __get_instance(self, data: dict) -> Subscription:
        subscription_id = Id(data.get('sk'))
        email = Email(data.get('email'))
        user_id = Id(data.get('user_id')) if data.get('user_id') else None
        subscribed_at = datetime.strptime(data.get('subscribed_at'), '%Y-%m-%d %H:%M:%S')
        unsubscribed_at = data.get('unsubscribed_at') or None
        unsubscribed_at = datetime.strptime(unsubscribed_at, '%Y-%m-%d %H:%M:%S') if unsubscribed_at else None

        entity: Subscription = self.__reflector.construct(Subscription, {
            self.__class__.__ENTITY_PROPERTY_SUBSCRIPTION_ID: subscription_id,
            self.__class__.__ENTITY_PROPERTY_EMAIL: email,
            self.__class__.__ENTITY_PROPERTY_USER_ID: user_id,
            self.__class__.__ENTITY_PROPERTY_SUBSCRIBED_AT: subscribed_at,
            self.__class__.__ENTITY_PROPERTY_UNSUBSCRIBED_AT: unsubscribed_at,
        })
        return entity

    def get_by_id(self, subscription_id: Id) -> Optional[Subscription]:
        if not isinstance(subscription_id, Id):
            raise ArgumentTypeException(self.get_by_id, 'subscription_id', subscription_id)

        data = self.__storage.find_item(subscription_id.value)
        return self.__get_instance(data) if data else None

    def get_by_email(self, email: Email) -> Optional[Subscription]:
        if not isinstance(email, Email):
            raise ArgumentTypeException(self.get_by_email, 'email', email)

        data = (self.__storage.find_by_attribute('email', email.value) or [None])[0] or None
        return self.__get_instance(data) if data else None

    def save(self, entity: Subscription) -> None:
        if not isinstance(entity, Subscription):
            raise ArgumentTypeException(self.save, 'entity', entity)

        data = self.__reflector.extract(entity, (
            self.__class__.__ENTITY_PROPERTY_SUBSCRIPTION_ID,
            self.__class__.__ENTITY_PROPERTY_EMAIL,
            self.__class__.__ENTITY_PROPERTY_USER_ID,
            self.__class__.__ENTITY_PROPERTY_SUBSCRIBED_AT,
            self.__class__.__ENTITY_PROPERTY_UNSUBSCRIBED_AT,
        ))

        subscription_id: Id = data[self.__class__.__ENTITY_PROPERTY_SUBSCRIPTION_ID]
        email: Email = data[self.__class__.__ENTITY_PROPERTY_EMAIL]
        user_id: Optional[Id] = data[self.__class__.__ENTITY_PROPERTY_USER_ID]
        subscribed_at: datetime = data[self.__class__.__ENTITY_PROPERTY_SUBSCRIBED_AT]
        unsubscribed_at: Optional[datetime] = data[self.__class__.__ENTITY_PROPERTY_UNSUBSCRIBED_AT]

        self.__storage.put_item(subscription_id.value, {
            'email': email.value,
            'user_id': user_id.value if user_id else None,
            'subscribed_at': subscribed_at.strftime('%Y-%m-%d %H:%M:%S'),
            'unsubscribed_at': unsubscribed_at.strftime('%Y-%m-%d %H:%M:%S') if unsubscribed_at else None
        })
Ejemplo n.º 8
0
class _MessageStorageDynamoDb(MessageStorageInterface):
    __ENTITY_PROPERTY_MESSAGE_ID = '__id'
    __ENTITY_PROPERTY_CUSTOMER_EMAIL = '__customer_email'
    __ENTITY_PROPERTY_TITLE = '__title'
    __ENTITY_PROPERTY_TEXT = '__text'
    __ENTITY_PROPERTY_CREATED_AT = '__created_at'

    def __init__(self):
        # is better to use composition instead of inheritance
        self.__storage = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__storage.PARTITION_KEY = 'NOTIFICATION_SIMPLE'
        self.__reflector = Reflector()

    def save(self, entity: Message) -> None:
        if not isinstance(entity, Message):
            raise ArgumentTypeException(self.save, 'entity', entity)

        data = self.__reflector.extract(entity, (
            self.__class__.__ENTITY_PROPERTY_MESSAGE_ID,
            self.__class__.__ENTITY_PROPERTY_CUSTOMER_EMAIL,
            self.__class__.__ENTITY_PROPERTY_TITLE,
            self.__class__.__ENTITY_PROPERTY_TEXT,
            self.__class__.__ENTITY_PROPERTY_CREATED_AT,
        ))

        self.__storage.put_item(
            data[self.__class__.__ENTITY_PROPERTY_MESSAGE_ID], {
                'customer_email':
                data[self.__class__.__ENTITY_PROPERTY_CUSTOMER_EMAIL],
                'title':
                data[self.__class__.__ENTITY_PROPERTY_TITLE],
                'text':
                data[self.__class__.__ENTITY_PROPERTY_TEXT],
                'created_at':
                data[self.__class__.__ENTITY_PROPERTY_CREATED_AT].strftime(
                    '%Y-%m-%d %H:%M:%S')
            })

    def remove(self, message_id: str) -> None:
        if not isinstance(message_id, str):
            raise ArgumentTypeException(self.remove, 'message_id', message_id)
        elif not str(message_id).strip():
            raise ArgumentCannotBeEmptyException(self.remove, 'message_id')

        self.__storage.delete_item(message_id)

    def get_all_for_customer(self, customer_email: str) -> Tuple[Message]:
        if not isinstance(customer_email, str):
            raise ArgumentTypeException(self.get_all_for_customer,
                                        'customer_email', customer_email)
        elif not str(customer_email).strip():
            raise ArgumentCannotBeEmptyException(self.get_all_for_customer,
                                                 'customer_email')

        rows = self.__storage.filter_by_field_value('customer_email',
                                                    customer_email)
        return tuple([self.__get_instance(row) for row in rows])

    def __get_instance(self, data: dict) -> Message:
        entity: Message = self.__reflector.construct(
            Message, {
                self.__class__.__ENTITY_PROPERTY_MESSAGE_ID:
                data.get('sk'),
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_EMAIL:
                data.get('customer_email'),
                self.__class__.__ENTITY_PROPERTY_TITLE:
                data.get('title'),
                self.__class__.__ENTITY_PROPERTY_TEXT:
                data.get('text'),
                self.__class__.__ENTITY_PROPERTY_CREATED_AT:
                datetime.strptime(data.get('created_at'), '%Y-%m-%d %H:%M:%S')
            })
        return entity
Ejemplo n.º 9
0
 def __init__(self):
     self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
     self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CANCELLATION_REQUEST'
     self.__reflector = Reflector()
Ejemplo n.º 10
0
class _CancelRequestStorageDynamoDb(CancelRequestStorageInterface):
    __ENTITY_PROPERTY_REQUEST_NUMBER = '__number'
    __ENTITY_PROPERTY_ORDER_NUMBER = '__order_number'
    __ENTITY_PROPERTY_ITEMS = '__items'
    __ENTITY_PROPERTY_ITEMS_SIMPLE_SKU = '__simple_sku'
    __ENTITY_PROPERTY_ITEMS_QTY = '__qty'
    __ENTITY_PROPERTY_ITEMS_STATUS = '__status'
    __ENTITY_PROPERTY_ITEMS_PROCESSED_AT = '__processed_at'
    __ENTITY_PROPERTY_REFUND_METHOD = '__refund_method'
    __ENTITY_PROPERTY_ADDITIONAL_COMMENT = '__additional_comment'
    __ENTITY_PROPERTY_REQUESTED_AT = '__requested_at'

    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CANCELLATION_REQUEST'
        self.__reflector = Reflector()

    def save(self, cancel_request: CancelRequest) -> None:
        if not isinstance(cancel_request, CancelRequest):
            raise ArgumentTypeException(self.save, 'cancel_request',
                                        cancel_request)

        self.__dynamo_db.put_item(
            cancel_request.number.value, {
                "order_number":
                cancel_request.order_number.value,
                "requested_at":
                cancel_request.requested_at.strftime('%Y-%m-%dT%H:%M:%S.%f'),
                "request_items": [{
                    "simple_sku":
                    item.simple_sku.value,
                    "qty":
                    item.qty.value,
                    "status":
                    item.status.value,
                    "processed_at":
                    item.processed_at.strftime('%Y-%m-%dT%H:%M:%S.%f')
                    if item.processed_at else None
                } for item in cancel_request.items],
                'refund_method':
                cancel_request.refund_method.descriptor,
                'refund_method_extra_data_json':
                json.dumps(cancel_request.refund_method.extra_data),
                "additional_comment":
                cancel_request.additional_comment.value
                if cancel_request.additional_comment else None
            })

    def __restore(self, data: dict) -> CancelRequest:
        cancel_request = self.__reflector.construct(
            CancelRequest, {
                self.__class__.__ENTITY_PROPERTY_REQUEST_NUMBER:
                CancelRequest.Number(data['sk']),
                self.__class__.__ENTITY_PROPERTY_ORDER_NUMBER:
                OrderNumber(data['order_number']),
                self.__class__.__ENTITY_PROPERTY_ITEMS:
                tuple([
                    self.__reflector.construct(
                        CancelRequest.Item, {
                            self.__class__.__ENTITY_PROPERTY_ITEMS_SIMPLE_SKU:
                            SimpleSku(item_data['simple_sku']),
                            self.__class__.__ENTITY_PROPERTY_ITEMS_QTY:
                            Qty(item_data['qty']),
                            self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS:
                            CancelRequest.Item.Status(item_data['status']),
                            self.__class__.__ENTITY_PROPERTY_ITEMS_PROCESSED_AT:
                            (datetime.datetime.strptime(
                                item_data['processed_at'],
                                '%Y-%m-%dT%H:%M:%S.%f')
                             if item_data['processed_at'] else None),
                        }) for item_data in data['request_items']
                ]),
                self.__class__.__ENTITY_PROPERTY_REFUND_METHOD:
                _restore_refund_method(
                    data['refund_method'],
                    json.loads(data['refund_method_extra_data_json'])),
                self.__class__.__ENTITY_PROPERTY_ADDITIONAL_COMMENT:
                (CancelRequest.AdditionalComment(data['additional_comment'])
                 if data.get('additional_comment') or None else None),
                self.__class__.__ENTITY_PROPERTY_REQUESTED_AT:
                datetime.datetime.strptime(data['requested_at'],
                                           '%Y-%m-%dT%H:%M:%S.%f'),
            })

        return cancel_request

    def get_by_number(
            self,
            request_number: CancelRequest.Number) -> Optional[CancelRequest]:
        if not isinstance(request_number, CancelRequest.Number):
            raise ArgumentTypeException(self.get_by_number, 'request_number',
                                        request_number)

        data = self.__dynamo_db.find_item(request_number.value)
        return self.__restore(data) if data else None

    def get_all_by_order_number(
            self, order_number: OrderNumber) -> Tuple[CancelRequest]:
        if not isinstance(order_number, OrderNumber):
            raise ArgumentTypeException(self.get_all_by_order_number,
                                        'order_number', order_number)

        items = self.__dynamo_db.find_by_attribute('order_number',
                                                   order_number.value)
        result = [self.__restore(item) for item in items]
        return tuple(result)
Ejemplo n.º 11
0
class _OrderStorageDynamoDb(OrderStorageInterface):
    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_ORDERS'
        self.__reflector = Reflector()

    def save(self, order: Order) -> None:
        if not isinstance(order, Order):
            raise ArgumentTypeException(self.save, 'order', order)

        order_number = order.number
        delivery_address = order.delivery_address
        status_changes = order.status_history

        document_id = order_number.value
        document_data = {
            'customer_id':
            order.customer_id.value,
            'order_items': [{
                'event_code':
                item.event_code.value,
                'simple_sku':
                item.simple_sku.value,
                'product_original_price':
                item.product_original_price.value,
                'product_current_price':
                item.product_current_price.value,
                'dtd_occasion_name':
                item.dtd.occasion.name.value if item.dtd.occasion else None,
                'dtd_occasion_description':
                item.dtd.occasion.description.value
                if item.dtd.occasion else None,
                'dtd_date_from':
                item.dtd.date_from.strftime('%Y-%m-%d'),
                'dtd_date_to':
                item.dtd.date_to.strftime('%Y-%m-%d'),
                'dtd_working_days_from':
                item.dtd.working_days_from,
                'dtd_working_days_to':
                item.dtd.working_days_to,
                'qty_ordered':
                item.qty_ordered.value,
                'qty_cancelled_before_payment':
                item.qty_cancelled_before_payment.value,
                'qty_cancelled_after_payment_requested':
                item.qty_cancelled_after_payment_requested.value,
                'qty_cancelled_after_payment_cancelled':
                item.qty_cancelled_after_payment_cancelled.value,
                'qty_return_requested':
                item.qty_return_requested.value,
                'qty_return_returned':
                item.qty_return_returned.value,
                'qty_refunded':
                item.qty_refunded.value,
                'qty_modified_at':
                item.qty_modified_at.strftime('%Y-%m-%dT%H:%M:%S.%f'),
                'fbucks_earnings':
                item.fbucks_earnings.value,
            } for item in order.items],
            'delivery_address_recipient_name':
            delivery_address.recipient_name,
            'delivery_address_phone_number':
            delivery_address.phone_number,
            'delivery_address_street_address':
            delivery_address.street_address,
            'delivery_address_suburb':
            delivery_address.suburb,
            'delivery_address_city':
            delivery_address.city,
            'delivery_address_province':
            delivery_address.province,
            'delivery_address_complex_building':
            delivery_address.complex_building,
            'delivery_address_postal_code':
            delivery_address.postal_code,
            'delivery_address_business_name':
            delivery_address.business_name,
            'delivery_address_special_instructions':
            delivery_address.special_instructions,
            'delivery_cost':
            order.delivery_cost.value,
            'vat_percent':
            order.vat_percent.value,
            'credits_spent':
            order.credit_spent_amount.value,
            'payment_method':
            order.payment_method.descriptor if order.payment_method else None,
            'payment_method_extra_data_json':
            json.dumps(order.payment_method.extra_data if order.
                       payment_method else {}),
            'status_history': [{
                'status':
                status_change.status.value,
                'datetime':
                status_change.datetime.strftime('%Y-%m-%dT%H:%M:%S.%f'),
            } for status_change in status_changes],
        }

        # fix of "TypeError: Float types are not supported. Use Decimal types instead." error
        document_data = json.loads(json.dumps(document_data),
                                   parse_float=Decimal)

        self.__dynamo_db.put_item(document_id, document_data)

    def __restore(self, data: dict) -> Order:
        order_number = Order.Number(data.get('sk'))
        customer_id = Id(data.get('customer_id'))
        delivery_cost = Cost(float(data.get('delivery_cost')))
        vat_percent = Percentage(float(data.get('vat_percent')))
        credits_spent = Cost(float(data.get('credits_spent') or '0'))

        payment_method = self.__restore_payment_method(
            data.get('payment_method'),
            json.loads(data.get('payment_method_extra_data_json') or '{}')
            if data.get('payment_method') else None)

        delivery_address = DeliveryAddress(
            data.get('delivery_address_recipient_name'),
            data.get('delivery_address_phone_number'),
            data.get('delivery_address_street_address'),
            data.get('delivery_address_suburb'),
            data.get('delivery_address_city'),
            data.get('delivery_address_province'),
            data.get('delivery_address_complex_building'),
            data.get('delivery_address_postal_code'),
            data.get('delivery_address_business_name'),
            data.get('delivery_address_special_instructions'))

        status_changes = []
        for status_change_data in data.get('status_history'):
            status = Order.Status(status_change_data.get('status'))
            changed_at = datetime.datetime.strptime(
                status_change_data.get('datetime'), '%Y-%m-%dT%H:%M:%S.%f')
            status_change = self.__reflector.construct(
                Order.StatusChangesHistory.Change, {
                    '__status': status,
                    '__datetime': changed_at
                })
            status_changes.append(status_change)

        status_change_history = Order.StatusChangesHistory(
            tuple(status_changes))

        order_items = []
        for item_data in data.get('order_items'):
            event_code = EventCode(item_data.get('event_code'))
            simple_sku = SimpleSku(item_data.get('simple_sku'))
            product_original_price = Cost(
                item_data.get('product_original_price'))
            product_current_price = Cost(
                item_data.get('product_current_price'))
            fbucks_earnings = Cost(item_data.get('fbucks_earnings'))
            dtd = Dtd(
                Dtd.Occasion(
                    Name(item_data.get('dtd_occasion_name')),
                    Description(item_data.get('dtd_occasion_description')))
                if item_data.get('dtd_occasion_name') else None,
                datetime.date(
                    int(item_data.get('dtd_date_from').split('-')[0]),
                    int(item_data.get('dtd_date_from').split('-')[1]),
                    int(item_data.get('dtd_date_from').split('-')[2])),
                datetime.date(int(item_data.get('dtd_date_to').split('-')[0]),
                              int(item_data.get('dtd_date_to').split('-')[1]),
                              int(item_data.get('dtd_date_to').split('-')[2])),
                int(item_data.get('dtd_working_days_from')),
                int(item_data.get('dtd_working_days_to')))

            qty_ordered = Qty(int(item_data.get('qty_ordered')))
            qty_return_requested = Qty(
                int(item_data.get('qty_return_requested') or 0))
            qty_return_returned = Qty(
                int(item_data.get('qty_return_returned') or 0))
            qty_cancelled_before_payment = Qty(
                int(item_data.get('qty_cancelled_before_payment') or 0))
            qty_cancelled_after_payment_requested = Qty(
                int(
                    item_data.get('qty_cancelled_after_payment_requested')
                    or 0))
            qty_cancelled_after_payment_cancelled = Qty(
                int(
                    item_data.get('qty_cancelled_after_payment_cancelled')
                    or 0))
            qty_refunded = Qty(int(item_data.get('qty_refunded') or 0))
            qty_modified_at = datetime.datetime.strptime(
                item_data.get('qty_modified_at'), '%Y-%m-%dT%H:%M:%S.%f')

            order_item = self.__reflector.construct(
                Order.Item, {
                    '__event_code': event_code,
                    '__simple_sku': simple_sku,
                    '__product_original_price': product_original_price,
                    '__product_current_price': product_current_price,
                    '__dtd': dtd,
                    '__qty_ordered': qty_ordered,
                    '__qty_return_requested': qty_return_requested,
                    '__qty_return_returned': qty_return_returned,
                    '__qty_cancelled_before_payment':
                    qty_cancelled_before_payment,
                    '__qty_cancelled_after_payment_requested':
                    qty_cancelled_after_payment_requested,
                    '__qty_cancelled_after_payment_cancelled':
                    qty_cancelled_after_payment_cancelled,
                    '__qty_refunded': qty_refunded,
                    '__qty_modified_at': qty_modified_at,
                    '__fbucks_earnings': fbucks_earnings
                })
            order_items.append(order_item)

        order = self.__reflector.construct(
            Order, {
                '__order_number': order_number,
                '__customer_id': customer_id,
                '__items': order_items,
                '__delivery_address': delivery_address,
                '__delivery_cost': delivery_cost,
                '__vat_percent': vat_percent,
                '__payment_method': payment_method,
                '__status_history': status_change_history,
                '__credits_spent': credits_spent,
            })

        return order

    def __restore_payment_method(
        self,
        descriptor: Optional[str],
        extra_data: Optional[dict],
    ) -> Optional[Order.PaymentMethodAbstract]:
        if not descriptor:
            return None

        # @todo : refactoring !!!

        if descriptor == 'regular_eft':
            return RegularEftOrderPaymentMethod()
        elif descriptor == 'mobicred':
            return MobicredPaymentMethod(extra_data['payment_id'])
        elif descriptor == 'credit_card':
            return CreditCardOrderPaymentMethod(extra_data['payment_id'])
        elif descriptor == 'customer_credit':
            return CustomerCreditsOrderPaymentMethod()

        raise Exception(
            '{} does not know, how to restore {} payment method with data {}!'.
            format(self.__restore_payment_method, descriptor, extra_data))

    def load(self, order_number: Order.Number) -> Optional[Order]:
        if not isinstance(order_number, Order.Number):
            raise ArgumentTypeException(self.load, 'order_number',
                                        order_number)

        data = self.__dynamo_db.find_item(order_number.value)
        return self.__restore(data) if data else None

    def get_all_by_numbers(self,
                           order_numbers: Tuple[Order.Number]) -> Tuple[Order]:
        if any([
                not isinstance(order_number, Order.Number)
                for order_number in order_numbers
        ]):
            raise ArgumentTypeException(self.get_all_by_numbers,
                                        'order_numbers', order_numbers)

        result = [self.load(order_number) for order_number in order_numbers]
        result = [order for order in result if order is not None]
        return tuple(result)

    def get_all_for_customer(self, customer_id: Id) -> Tuple[Order]:
        if not isinstance(customer_id, Id):
            raise ArgumentTypeException(self.get_all_for_customer,
                                        'customer_id', customer_id)

        items = self.__dynamo_db.find_by_attribute('customer_id',
                                                   customer_id.value)
        result = [self.__restore(item) for item in items]
        return tuple(result)
Ejemplo n.º 12
0
    def __init__(self):
        self.__orders_storage = OrderStorageImplementation()
        self.__logger = Logger()

        # """
        # curl -X DELETE localhost:9200/fbucks_handled_orders
        # curl -X PUT localhost:9200/fbucks_handled_orders -H "Content-Type: application/json" -d'{
        #     "mappings": {
        #         "fbucks_handled_orders": {
        #             "properties": {
        #                 "handled_at": {"type": "date", "format": "yyyy-MM-dd HH:mm:ss"}
        #             }
        #         }
        #     }
        # }'
        # """
        # self.__fbucks_handled_orders_elastic = Elastic(
        #     settings.AWS_ELASTICSEARCH_FBUCKS_HANDLED_ORDERS,
        #     settings.AWS_ELASTICSEARCH_FBUCKS_HANDLED_ORDERS,
        # )
        self.__fbucks_handled_orders_dynamo_db = DynamoModel(
            settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__fbucks_handled_orders_dynamo_db.PARTITION_KEY = 'PURCHASE_FBUCKS_REWARD_HANDLED_ORDERS'

        # Attention!
        # We can get current customer's amount as a sum of all changes by customer_id
        # But theoretically elastic can not be in time with index update (1 second) between requests.
        # So there is another index to store amount value.
        """
        curl -X DELETE localhost:9200/fbucks_customer_amount
        curl -X PUT localhost:9200/fbucks_customer_amount -H "Content-Type: application/json" -d'{
            "mappings": {
                "fbucks_customer_amount": {
                    "properties": {
                        "amount": {"type": "integer"}
                    }
                }
            }
        }'
        curl -X DELETE localhost:9200/fbucks_customer_amount_changes
        curl -X PUT localhost:9200/fbucks_customer_amount_changes -H "Content-Type: application/json" -d'{
            "mappings": {
                "fbucks_customer_amount_changes": {
                    "properties": {
                        "customer_id": {"type": "keyword"},
                        "amount": {"type": "integer"},
                        "changed_at": {"type": "date", "format": "yyyy-MM-dd HH:mm:ss"},
                        "order_number": {"type": "keyword"}
                    }
                }
            }
        }'
        """
        self.__fbucks_customer_amount_elastic = Elastic(
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
        )
        self.__fbucks_customer_amount_changes_elastic = Elastic(
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
        )

        self.__customer_storage = CustomerStorageImplementation()
        self.__messages_storage = MessageStorageImplementation()
Ejemplo n.º 13
0
class _CustomerDynamoDbStorage(CustomerStorageInterface):
    def __init__(self):
        # @todo : link to tier should be with other data. Will be moved to Nexus soon.
        # """
        # curl -X DELETE localhost:9200/customer_tiers_customer_tiers
        # curl -X PUT localhost:9200/customer_tiers_customer_tiers -H "Content-Type: application/json" -d'{
        #     "mappings": {
        #         "customer_tiers_customer_tiers": {
        #             "properties": {
        #                 "tier_id": {"type": "integer"}
        #             }
        #         }
        #     }
        # }'
        # """
        # self.__tier_elastic = Elastic(
        #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_TIERS,
        #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_TIERS
        # )
        self.__tiers_dynamo = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__tiers_dynamo.PARTITION_KEY = 'PURCHASE_CUSTOMER_TIERS_CUSTOMER_TIER'

        self.__tiers_storage = CustomerTierStorageImplementation()

    def save(self, customer: CustomerInterface) -> None:
        if not isinstance(customer, CustomerInterface):
            raise ArgumentTypeException(self.save, 'customer', customer)

        information_model = InformationModel(customer.customer_id.value)

        # delete old addresses
        for info_address in information_model.get_information().addresses:
            information_model.delete_address(info_address.address_hash)

        # insert new
        information_model.add_addresses([{
            'address_nickname': delivery_address.address_nickname,
            'recipient_name': delivery_address.recipient_name,
            'mobile_number': delivery_address.phone_number,
            'business_name': delivery_address.business_name,
            'complex_building': delivery_address.complex_building,
            'street_address': delivery_address.street_address,
            'suburb': delivery_address.suburb,
            'postal_code': delivery_address.postal_code,
            'city': delivery_address.city,
            'province': delivery_address.province,
            'special_instructions': delivery_address.special_instructions,
            'business_type': delivery_address.address_type == CustomerInterface.DeliveryAddress.ADDRESS_TYPE_BUSINESS,
        } for delivery_address in customer.delivery_addresses])

        # @todo : link to tier should be with other data. Will be moved to Nexus soon.
        # update tier
        # if self.__tier_elastic.get_data(customer.email.value):
        #     self.__tier_elastic.update_data(customer.email.value, {'doc': {'tier_id': customer.tier.id.value}})
        # else:
        #     self.__tier_elastic.create(customer.email.value, {'tier_id': customer.tier.id.value})
        self.__tiers_dynamo.put_item(customer.email.value, {'tier_id': customer.tier.id.value})

    def get_by_id(self, customer_id: Id) -> Optional[CustomerInterface]:
        if not isinstance(customer_id, Id):
            raise ArgumentTypeException(self.get_by_id, 'customer_id', customer_id)

        information = InformationModel(customer_id.value).get_information()
        if not information.email:
            raise ValueError('User information is incorrect - {}'.format(information.to_dict()))

        # @todo : link to tier should be with other data. Will be moved to Nexus soon.
        # row = self.__tier_elastic.get_data(information.email)
        row = self.__tiers_dynamo.find_item(information.email)

        customer_tier = self.__tiers_storage.get_by_id(Id(str(row['tier_id']))) \
            if row else self.__tiers_storage.get_neutral()
        if not customer_tier:
            # something wrong with tiers set
            raise ValueError('Customer {} is assigned to unknown Customer Tier #{}'.format(
                customer_id.value,
                row['tier_id']
            ))

        return CustomerImplementation(customer_id, information, customer_tier)
Ejemplo n.º 14
0
class _CustomerTiersStorageDynamoDb(CustomerTierStorageInterface):
    __ENTITY_PROPERTY_ID = '__id'
    __ENTITY_PROPERTY_NAME = '__name'
    __ENTITY_PROPERTY_CREDIT_BACK_PERCENT = '__credit_back_percent'
    __ENTITY_PROPERTY_SPENT_AMOUNT_MIN = 'spent_amount_min'
    __ENTITY_PROPERTY_SPENT_AMOUNT_MAX = 'spent_amount_max'
    __ENTITY_PROPERTY_IS_DELETED = '__is_deleted'

    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_TIERS_TIER'
        self.__reflector = Reflector()

    def save(self, entity: CustomerTier) -> None:
        entity_data = self.__reflector.extract(entity, [
            self.__class__.__ENTITY_PROPERTY_ID,
            self.__class__.__ENTITY_PROPERTY_NAME,
            self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT,
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN,
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX,
            self.__class__.__ENTITY_PROPERTY_IS_DELETED
        ])

        document_id = entity_data[self.__class__.__ENTITY_PROPERTY_ID].value
        document_data = {
            'name': entity_data[self.__class__.__ENTITY_PROPERTY_NAME].value,
            'credit_back_percent': entity_data[self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT].value,
            'spent_amount_min': entity_data[self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN],
            'spent_amount_max': entity_data[self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX],
            'is_deleted': entity_data[self.__class__.__ENTITY_PROPERTY_IS_DELETED],
        }

        # fix of "TypeError: Float types are not supported. Use Decimal types instead." error
        document_data = json.loads(json.dumps(document_data), parse_float=Decimal)

        self.__dynamo_db.put_item(document_id, document_data)

    def __restore(self, row: dict) -> CustomerTier:
        entity = self.__reflector.construct(CustomerTier, {
            self.__class__.__ENTITY_PROPERTY_ID: Id(str(row['sk'])),
            self.__class__.__ENTITY_PROPERTY_NAME: Name(row['name']),
            self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT: Percentage(int(row['credit_back_percent'])),
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN: int(row['spent_amount_min']),
            self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX: int(row['spent_amount_max']),
            self.__class__.__ENTITY_PROPERTY_IS_DELETED: row['is_deleted'],
        })

        return entity

    def get_by_id(self, tier_id: Id) -> Optional[CustomerTier]:
        if not isinstance(tier_id, Id):
            raise ArgumentTypeException(self.get_by_id, 'tier_id', tier_id)

        row = self.__dynamo_db.find_item(tier_id.value)
        return self.__restore(row) if row else None

    def get_all(self) -> Tuple[CustomerTier]:
        rows = self.__dynamo_db.find_all()
        result = [self.__restore(row) for row in rows]
        result = [entity for entity in result if not entity.is_deleted]
        result = tuple(result)
        return result

    def get_neutral(self) -> CustomerTier:
        for tier in self.get_all():
            if tier.is_neutral:
                return tier
        else:
            raise ApplicationLogicException('Neutral Tier does not exist!')
Ejemplo n.º 15
0
class _ReturnRequestStorageDynamoDb(ReturnRequestStorageInterface):
    __ENTITY_PROPERTY_REQUEST_NUMBER = '__number'
    __ENTITY_PROPERTY_CUSTOMER_ID = '__customer_id'
    __ENTITY_PROPERTY_REFUND_METHOD = '__refund_method'
    __ENTITY_PROPERTY_DELIVERY_METHOD = '__delivery_method'
    __ENTITY_PROPERTY_ITEMS = '__items'
    __ENTITY_PROPERTY_ITEMS_ORDER_NUMBER = '__order_number'
    __ENTITY_PROPERTY_ITEMS_SIMPLE_SKU = '__simple_sku'
    __ENTITY_PROPERTY_ITEMS_QTY = '__qty'
    __ENTITY_PROPERTY_ITEMS_COST = '__cost'
    __ENTITY_PROPERTY_ITEMS_REASON = '__reason'
    __ENTITY_PROPERTY_ITEMS_ATTACHED_FILES = '__attached_files'
    __ENTITY_PROPERTY_ITEMS_ADDITIONAL_COMMENT = '__additional_comment'
    __ENTITY_PROPERTY_ITEMS_STATUS_HISTORY = '__status_history'

    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_RETURN_REQUESTS'

        self.__reflector = Reflector()

    def save(self, return_request: ReturnRequest) -> None:
        if not isinstance(return_request, ReturnRequest):
            raise ArgumentTypeException(self.save, 'return_request',
                                        return_request)

        items_data = []
        for item in return_request.items:
            status_history: ReturnRequest.Item.StatusChangesHistory = self.__reflector.extract(
                item,
                (self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY,
                 ))[self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY]

            items_data.append({
                'order_number':
                item.order_number.value,
                'simple_sku':
                item.simple_sku.value,
                'qty':
                item.qty.value,
                'cost':
                item.cost.value,
                'reason':
                item.reason.descriptor,
                'additional_comment':
                item.additional_comment.value
                if item.additional_comment else None,
                'attached_files_urls_json':
                json.dumps([file.url for file in item.attached_files]),
                'status_history': [{
                    'status':
                    status_change.status.value,
                    'datetime':
                    status_change.datetime.strftime('%Y-%m-%dT%H:%M:%S.%f'),
                } for status_change in status_history.get_all()]
            })

        document_id = return_request.number.value
        document_data = {
            'customer_id':
            return_request.customer_id.value,
            'request_items':
            items_data,
            'delivery_method':
            return_request.delivery_method.descriptor,
            'refund_method':
            return_request.refund_method.descriptor,
            'refund_method_extra_data_json':
            json.dumps(return_request.refund_method.extra_data),
        }

        # fix of "TypeError: Float types are not supported. Use Decimal types instead." error
        document_data = json.loads(json.dumps(document_data),
                                   parse_float=Decimal)

        self.__dynamo_db.put_item(document_id, document_data)

    def load(self,
             request_number: ReturnRequest.Number) -> Optional[ReturnRequest]:
        if not isinstance(request_number, ReturnRequest.Number):
            raise ArgumentTypeException(self.load, 'request_number',
                                        request_number)

        data = self.__dynamo_db.find_item(request_number.value)
        result = self.__restore(data) if data else None
        return result

    def __restore(self, data: dict) -> ReturnRequest:
        request_items = []
        for item_data in data['request_items']:
            attached_files = json.loads(item_data['attached_files_urls_json'])
            attached_files = tuple([
                ReturnRequest.Item.AttachedFile(url) for url in attached_files
            ])

            additional_comment = ReturnRequest.Item.AdditionalComment(
                item_data['additional_comment'])

            status_history = ReturnRequest.Item.StatusChangesHistory(
                tuple([
                    self.__reflector.construct(
                        ReturnRequest.Item.StatusChangesHistory.Change, {
                            '__status':
                            ReturnRequest.Item.Status(change['status']),
                            '__datetime':
                            datetime.datetime.strptime(change['datetime'],
                                                       '%Y-%m-%dT%H:%M:%S.%f'),
                        }) for change in item_data['status_history']
                ]))

            request_items.append(
                self.__reflector.construct(
                    ReturnRequest.Item, {
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ORDER_NUMBER:
                        OrderNumber(item_data['order_number']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_SIMPLE_SKU:
                        SimpleSku(item_data['simple_sku']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_QTY:
                        Qty(item_data['qty']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_COST:
                        Cost(item_data['cost']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_REASON:
                        ReturnRequest.Item.Reason(item_data['reason']),
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ATTACHED_FILES:
                        attached_files,
                        self.__class__.__ENTITY_PROPERTY_ITEMS_ADDITIONAL_COMMENT:
                        additional_comment,
                        self.__class__.__ENTITY_PROPERTY_ITEMS_STATUS_HISTORY:
                        status_history,
                    }))

        return_request = self.__reflector.construct(
            ReturnRequest, {
                self.__class__.__ENTITY_PROPERTY_REQUEST_NUMBER:
                ReturnRequest.Number(data['request_number']),
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID:
                Id(data['customer_id']),
                self.__class__.__ENTITY_PROPERTY_ITEMS:
                tuple(request_items),
                self.__class__.__ENTITY_PROPERTY_DELIVERY_METHOD:
                _restore_delivery_method(data['delivery_method']),
                self.__class__.__ENTITY_PROPERTY_REFUND_METHOD:
                _restore_refund_method(
                    data['refund_method'],
                    json.loads(data['refund_method_extra_data_json'])),
            })

        return return_request

    def get_all_for_customer(self, customer_id: Id) -> Tuple[ReturnRequest]:
        if not isinstance(customer_id, Id):
            raise ArgumentTypeException(self.get_all_for_customer,
                                        'customer_id', customer_id)

        items = self.__dynamo_db.find_by_attribute('customer_id',
                                                   customer_id.value)
        result = [self.__restore(item) for item in items]
        return tuple(result)
Ejemplo n.º 16
0
class _CreditCardsStorageDynamoDb(CreditCardsStorageInterface):
    __ENTITY_PROPERTY_TOKEN = '__token'
    __ENTITY_PROPERTY_CUSTOMER_ID = '__customer_id'
    __ENTITY_PROPERTY_BRAND = '__brand'
    __ENTITY_PROPERTY_NUMBER_HIDDEN = '__number_hidden'
    __ENTITY_PROPERTY_EXPIRES = '__expires'
    __ENTITY_PROPERTY_HOLDER_NAME = '__holder_name'
    __ENTITY_PROPERTY_IS_VERIFIED = '__is_verified'
    __ENTITY_PROPERTY_CREATED_AT = '__created_at'

    def __init__(self):
        self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_CREDIT_CARDS'
        self.__reflector = Reflector()

    def __restore(self, row: dict) -> CreditCard:
        card = self.__reflector.construct(
            CreditCard, {
                self.__class__.__ENTITY_PROPERTY_TOKEN:
                row['sk'],
                self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID:
                row['customer_id'],
                self.__class__.__ENTITY_PROPERTY_BRAND:
                row['brand'],
                self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN:
                row['number_hidden'],
                self.__class__.__ENTITY_PROPERTY_EXPIRES: (datetime.date(
                    year=int('20' + row['expires'][0:2]), month=12, day=31))
                if int(row['expires'][2:4]) == 12 else
                (datetime.date(year=int('20' + row['expires'][0:2]),
                               month=int(row['expires'][2:4]) + 1,
                               day=1) - datetime.timedelta(days=1)),
                self.__class__.__ENTITY_PROPERTY_HOLDER_NAME:
                row['holder_name'],
                self.__class__.__ENTITY_PROPERTY_IS_VERIFIED:
                row['is_verified'],
                self.__class__.__ENTITY_PROPERTY_CREATED_AT:
                datetime.datetime.strptime(row['created_at'],
                                           '%Y-%m-%d %H:%M:%S')
            })

        return card

    def get_by_token(self, token: str) -> Optional[CreditCard]:
        if not isinstance(token, str):
            raise ArgumentTypeException(self.get_by_token, 'token', token)
        elif not token.strip():
            raise ArgumentCannotBeEmptyException(self.get_by_token, 'token')

        row = self.__dynamo_db.find_item(token)
        return self.__restore(row) if row else None

    def get_all_by_customer(self, customer_id: str) -> Tuple[CreditCard]:
        if not isinstance(customer_id, str):
            raise ArgumentTypeException(self.get_all_by_customer,
                                        'customer_id', customer_id)
        elif not customer_id.strip():
            raise ArgumentCannotBeEmptyException(self.get_all_by_customer,
                                                 'customer_id')

        items = self.__dynamo_db.find_by_attribute('customer_id', customer_id)
        result = [self.__restore(item) for item in items]
        return tuple(result)

    def save(self, card: CreditCard) -> None:
        if not isinstance(card, CreditCard):
            raise ArgumentTypeException(self.save, 'card', card)

        data = self.__reflector.extract(card, [
            self.__class__.__ENTITY_PROPERTY_TOKEN,
            self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID,
            self.__class__.__ENTITY_PROPERTY_BRAND,
            self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN,
            self.__class__.__ENTITY_PROPERTY_EXPIRES,
            self.__class__.__ENTITY_PROPERTY_HOLDER_NAME,
            self.__class__.__ENTITY_PROPERTY_IS_VERIFIED,
            self.__class__.__ENTITY_PROPERTY_CREATED_AT,
        ])

        self.__dynamo_db.put_item(
            data[self.__class__.__ENTITY_PROPERTY_TOKEN], {
                'customer_id':
                data[self.__class__.__ENTITY_PROPERTY_CUSTOMER_ID],
                'brand':
                data[self.__class__.__ENTITY_PROPERTY_BRAND],
                'number_hidden':
                data[self.__class__.__ENTITY_PROPERTY_NUMBER_HIDDEN],
                'expires':
                data[self.__class__.__ENTITY_PROPERTY_EXPIRES].strftime(
                    '%y%m'),
                'holder_name':
                data[self.__class__.__ENTITY_PROPERTY_HOLDER_NAME],
                'is_verified':
                data[self.__class__.__ENTITY_PROPERTY_IS_VERIFIED],
                'created_at':
                data[self.__class__.__ENTITY_PROPERTY_CREATED_AT].strftime(
                    '%Y-%m-%d %H:%M:%S')
            })

    def remove(self, card: CreditCard) -> None:
        if not isinstance(card, CreditCard):
            raise ArgumentTypeException(self.remove, 'card', card)

        if not self.__dynamo_db.find_item(card.token):
            raise ArgumentValueException('Card #{} is already Removed!'.format(
                card.token))

        self.__dynamo_db.delete_item(card.token)
Ejemplo n.º 17
0
 def __init__(self):
     self.__dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
     self.__dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_CREDIT_CARDS'
     self.__reflector = Reflector()
Ejemplo n.º 18
0
class FbucksChargeSqsHandler(SqsHandlerInterface):
    # @TODO : REFACTORING !!! currently we are working with raw data

    def __init__(self):
        self.__orders_storage = OrderStorageImplementation()
        self.__logger = Logger()

        # """
        # curl -X DELETE localhost:9200/fbucks_handled_orders
        # curl -X PUT localhost:9200/fbucks_handled_orders -H "Content-Type: application/json" -d'{
        #     "mappings": {
        #         "fbucks_handled_orders": {
        #             "properties": {
        #                 "handled_at": {"type": "date", "format": "yyyy-MM-dd HH:mm:ss"}
        #             }
        #         }
        #     }
        # }'
        # """
        # self.__fbucks_handled_orders_elastic = Elastic(
        #     settings.AWS_ELASTICSEARCH_FBUCKS_HANDLED_ORDERS,
        #     settings.AWS_ELASTICSEARCH_FBUCKS_HANDLED_ORDERS,
        # )
        self.__fbucks_handled_orders_dynamo_db = DynamoModel(
            settings.AWS_DYNAMODB_CMS_TABLE_NAME)
        self.__fbucks_handled_orders_dynamo_db.PARTITION_KEY = 'PURCHASE_FBUCKS_REWARD_HANDLED_ORDERS'

        # Attention!
        # We can get current customer's amount as a sum of all changes by customer_id
        # But theoretically elastic can not be in time with index update (1 second) between requests.
        # So there is another index to store amount value.
        """
        curl -X DELETE localhost:9200/fbucks_customer_amount
        curl -X PUT localhost:9200/fbucks_customer_amount -H "Content-Type: application/json" -d'{
            "mappings": {
                "fbucks_customer_amount": {
                    "properties": {
                        "amount": {"type": "integer"}
                    }
                }
            }
        }'
        curl -X DELETE localhost:9200/fbucks_customer_amount_changes
        curl -X PUT localhost:9200/fbucks_customer_amount_changes -H "Content-Type: application/json" -d'{
            "mappings": {
                "fbucks_customer_amount_changes": {
                    "properties": {
                        "customer_id": {"type": "keyword"},
                        "amount": {"type": "integer"},
                        "changed_at": {"type": "date", "format": "yyyy-MM-dd HH:mm:ss"},
                        "order_number": {"type": "keyword"}
                    }
                }
            }
        }'
        """
        self.__fbucks_customer_amount_elastic = Elastic(
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
        )
        self.__fbucks_customer_amount_changes_elastic = Elastic(
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
            settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
        )

        self.__customer_storage = CustomerStorageImplementation()
        self.__messages_storage = MessageStorageImplementation()

    def handle(self, sqs_message: SqsMessage) -> None:
        import uuid
        import datetime
        from chalicelib.libs.purchase.core import Order

        order_number_values = sqs_message.message_data['order_numbers']
        for order_number_value in order_number_values:
            try:
                now_string = datetime.datetime.now().strftime(
                    "%Y-%m-%d %H:%M:%S")

                # skip duplicates
                # if self.__fbucks_handled_orders_elastic.get_data(order_number_value):
                if self.__fbucks_handled_orders_dynamo_db.find_item(
                        order_number_value):
                    self.__logger.log_simple(
                        '{}: Fbucks for order #{} already earned!'.format(
                            self.handle.__qualname__, order_number_value))
                    continue

                # ignore orders without fbucks amounts
                order = self.__orders_storage.load(
                    Order.Number(order_number_value))
                fbucks_amount = order.total_fbucks_earnings.value
                if fbucks_amount == 0:
                    # remember order as handled
                    # self.__fbucks_handled_orders_elastic.create(order_number_value, {'handled_at': now_string})
                    self.__fbucks_handled_orders_dynamo_db.put_item(
                        order_number_value, {'handled_at': now_string})
                    continue

                # earn fbucks
                self.__fbucks_customer_amount_elastic.update_data(
                    order.customer_id.value, {
                        'script':
                        'ctx._source.amount += ' + str(fbucks_amount),
                        'upsert': {
                            'amount': fbucks_amount,
                        }
                    })
                self.__fbucks_customer_amount_changes_elastic.create(
                    str(uuid.uuid4()) + str(order.customer_id.value), {
                        "customer_id": order.customer_id.value,
                        "amount": +fbucks_amount,
                        "changed_at": now_string,
                        "order_number": order_number_value,
                    })

                # remember order as handled
                # self.__fbucks_handled_orders_elastic.create(order_number_value, {'handled_at': now_string})
                self.__fbucks_handled_orders_dynamo_db.put_item(
                    order_number_value, {'handled_at': now_string})

                # notify (silently)
                try:
                    customer = self.__customer_storage.get_by_id(
                        order.customer_id)
                    self.__messages_storage.save(
                        Message(
                            str(uuid.uuid4()), customer.email.value,
                            'F-Bucks has been Earned!',
                            'You have earned {} F-Bucks by your Order #{}'.
                            format(fbucks_amount, order.number.value)))
                except BaseException as e:
                    self.__logger.log_exception(e)

            except BaseException as e:
                self.__logger.log_exception(e)