Exemplo n.º 1
0
 def __init__(self):
     self.__order_storage = OrderStorageImplementation()
     self.__sqs_sender = SqsSenderImplementation()
     self.__logger = Logger()
     self.__message_storage = MessageStorageImplementation()
     self.__customer_storage = CustomerStorageImplementation()
     self.__products_storage = ProductStorageImplementation()
    def __response_list(user_id: str) -> dict:
        customer_storage = CustomerStorageImplementation()
        customer_id = Id(user_id)
        customer = customer_storage.get_by_id(customer_id)

        delivery_addresses = [{
            'hash': delivery_address.address_hash,
            'address_type': delivery_address.address_type,
            'recipient_name': delivery_address.recipient_name,
            'phone_number': delivery_address.phone_number,
            'street_address': delivery_address.street_address,
            'suburb': delivery_address.suburb,
            'city': delivery_address.city,
            'province': delivery_address.province,
            'business_name': delivery_address.business_name,
            'complex_building': delivery_address.complex_building,
            'postal_code': delivery_address.postal_code,
            'special_instructions': delivery_address.special_instructions,
            'address_nickname': delivery_address.address_nickname,
            'is_billing': delivery_address.is_billing,
            'is_shipping': delivery_address.is_shipping,
        } for delivery_address in customer.delivery_addresses]

        return {
            'delivery_addresses': delivery_addresses,
        }
Exemplo n.º 3
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)
Exemplo n.º 4
0
 def __init__(self):
     # Need to be imported here, because orders storage depends from payment method class in this file.
     from chalicelib.libs.purchase.order.storage import OrderStorageImplementation
     from chalicelib.libs.purchase.product.storage import ProductStorageImplementation
     self.__order_storage = OrderStorageImplementation()
     self.__sqs_sender = SqsSenderImplementation()
     self.__logger = Logger()
     self.__message_storage = MessageStorageImplementation()
     self.__customer_storage = CustomerStorageImplementation()
     self.__products_storage = ProductStorageImplementation()
Exemplo n.º 5
0
 def __is_customer_rs_staff():
     # @todo : set customer in constructor, storage instead of id
     # @todo : customer.is_rs_staff and email domain to settings or so ?
     from chalicelib.libs.purchase.customer.storage import CustomerStorageImplementation
     customer = CustomerStorageImplementation().get_by_id(self.customer_id)
     customer_is_rs_staff = customer.email.value.split('@')[-1] == 'runwaysale.co.za'
     return self.__delivery_address.postal_code == '7777' or customer_is_rs_staff
Exemplo n.º 6
0
    def tier(self) -> dict:
        # customer tier - is a part of purchase module, not account information

        def __tier_to_dict(customer_tier: CustomerTier) -> dict:
            return {
                'name': customer_tier.name.value,
                'discount_rate': customer_tier.credit_back_percent.value,
                'is_neutral': customer_tier.is_neutral
            }

        # cache
        if self.__purchase_customer_tier_lazy_loading_cache:
            return __tier_to_dict(
                self.__purchase_customer_tier_lazy_loading_cache)

        # guests are in neutral tier
        if self.is_anonymous:
            self.__purchase_customer_tier_lazy_loading_cache = CustomerTierStorageImplementation(
            ).get_neutral()
            return __tier_to_dict(
                self.__purchase_customer_tier_lazy_loading_cache)

        # get assigned customer tier
        customer = CustomerStorageImplementation().get_by_id(
            Id(self.customer_id))
        self.__purchase_customer_tier_lazy_loading_cache = customer.tier
        return __tier_to_dict(self.__purchase_customer_tier_lazy_loading_cache)
    def customer_delivery_addresses_edit():
        customer_storage = CustomerStorageImplementation()
        delivery_address_service = CustomerDeliveryAddressAppService(
            customer_storage)

        try:
            request_data = blueprint.current_request.json_body
            address_type = str(request_data.get('address_type')).strip()
            address_hash = request_data.get('hash', None)
            address_hash = str(
                address_hash).strip() if address_hash is not None else None
            if not address_hash:
                raise HttpIncorrectInputDataException(
                    '"hash" parameter is incorrect!')

            user_id = __get_user_id()
            form = __create_delivery_address_form(address_type)

            form.load(request_data)
            if form.validate():
                delivery_address_service.edit_delivery_address(
                    user_id, address_hash, form)
                return __response_list(user_id)
            else:
                return {
                    'validation_errors': form.validation_errors,
                }
        except BaseException as e:
            return http_response_exception_or_throw(e)
Exemplo n.º 8
0
    def __init__(self, order: Order):
        if not isinstance(order, Order):
            raise ArgumentTypeException(self.__init__, 'order', order)

        self.__order = order
        self.__customer = CustomerStorageImplementation().get_by_id(
            order.customer_id)
Exemplo n.º 9
0
 def __init__(self):
     from chalicelib.libs.purchase.cart.storage import CartStorageImplementation
     from chalicelib.libs.purchase.customer.storage import CustomerStorageImplementation
     from chalicelib.libs.purchase.checkout.storage import CheckoutStorageImplementation
     super().__init__(CartStorageImplementation(),
                      CustomerStorageImplementation(),
                      CheckoutStorageImplementation())
Exemplo n.º 10
0
class OrderRefundSqsHandler(SqsHandlerInterface):
    def __init__(self) -> None:
        self.__messages_storage = MessageStorageImplementation()
        self.__order_storage = OrderStorageImplementation()
        self.__customer_storage = CustomerStorageImplementation()
        self.__product_storage = ProductStorageImplementation()
        self.__sqs_sender = SqsSenderImplementation()
        self.__logger = Logger()

    def handle(self, sqs_message: SqsMessage) -> None:
        def __log_flow(text: str) -> None:
            self.__logger.log_simple('{} : SQS Message #{} : {}'.format(
                self.__class__.__qualname__,
                sqs_message.id,
                text
            ))

        __log_flow('Start - {}'.format(sqs_message.message_data))

        order_number = Order.Number(sqs_message.message_data['order_number'])
        simple_sku = SimpleSku(sqs_message.message_data['simple_sku'])
        qty = Qty(sqs_message.message_data['qty'])

        __log_flow('Order Updating...')
        order = self.__order_storage.load(order_number)
        order.refund(simple_sku, qty)
        __log_flow('Order Updated!')

        __log_flow('Order Saving...')
        self.__order_storage.save(order)
        __log_flow('Order Saved!')

        __log_flow('Order SQS Sending...')
        self.__sqs_sender.send(OrderChangeSqsSenderEvent(order))
        __log_flow('Order SQS Sent!')

        # add message (silently)
        try:
            __log_flow('Notification popup: Adding...')
            customer = self.__customer_storage.get_by_id(order.customer_id)
            product = self.__product_storage.load(simple_sku)
            message = Message(
                str(uuid.uuid4()),
                customer.email.value,
                'Refund for Order #{}'.format(order.number.value),
                '"{}" has been Refunded in Qty {} for Order #{}'.format(
                    product.name.value,
                    qty.value,
                    order.number.value
                ),
            )
            self.__messages_storage.save(message)
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))

        __log_flow('End')
Exemplo n.º 11
0
class InformationsSqsHandler(SqsHandlerInterface):
    def __init__(self):
        self.__information_service = InformationService()
        self.__customers_storage = CustomerStorageImplementation()
        self.__tiers_storage = CustomerTierStorageImplementation()

    def handle(self, sqs_message: SqsMessage) -> None:
        message_type = sqs_message.message_type
        message_data = sqs_message.message_data

        if message_type != 'customer_info':
            raise ValueError('SQS Message type "' + message_type + '" is unknown for ' + self.__class__.__name__)

        customer_data = message_data.get('customer')
        if not customer_data:
            raise ValueError('SQS Message does not have customer field')

        email = str(customer_data.get('email', '')).strip()
        if not email:
            raise ValueError('SQS Message does not have email field')

        information_model = self.__information_service.get(email)
        information = information_model.get_information()
        information.first_name = customer_data.get('first_name')
        information.last_name = customer_data.get('last_name')
        information.gender = customer_data.get('gender')
        information_model.insert_item(information)

        # set tier
        tier_id = customer_data['mpc_tier_id']
        tier = self.__tiers_storage.get_by_id(Id(str(tier_id)))
        if not tier:
            raise ValueError('Unable to change Tier #{} for Customer #{} - tier does not exist!'.format(
                tier_id,
                information.customer_id
            ))

        customer = self.__customers_storage.get_by_id(Id(information.customer_id))
        customer.tier = tier
        self.__customers_storage.save(customer)
Exemplo n.º 12
0
 def __init__(self):
     from chalicelib.libs.purchase.product.storage import ProductStorageImplementation
     from chalicelib.libs.purchase.checkout.storage import CheckoutStorageImplementation
     from chalicelib.libs.purchase.customer.storage import CustomerStorageImplementation
     from chalicelib.libs.purchase.order.dtd_calculator import DtdCalculatorImplementation
     from chalicelib.libs.purchase.order.storage import OrderStorageImplementation
     from chalicelib.libs.core.sqs_sender import SqsSenderImplementation
     from chalicelib.libs.core.mailer import MailerImplementation
     from chalicelib.libs.core.logger import Logger
     super().__init__(ProductStorageImplementation(),
                      CheckoutStorageImplementation(),
                      CustomerStorageImplementation(),
                      DtdCalculatorImplementation(),
                      OrderStorageImplementation(),
                      SqsSenderImplementation(), MailerImplementation(),
                      Logger())
    def customer_delivery_addresses_edit():
        customer_storage = CustomerStorageImplementation()
        delivery_address_service = CustomerDeliveryAddressAppService(
            customer_storage)

        try:
            request_data = blueprint.current_request.json_body
            address_hash = request_data.get('hash', None)
            address_hash = str(
                address_hash).strip() if address_hash is not None else None
            if not address_hash:
                raise HttpIncorrectInputDataException(
                    '"hash" parameter is incorrect!')

            user_id = __get_user_id()
            delivery_address_service.remove_delivery_address(
                user_id, address_hash)
            return __response_list(user_id)
        except BaseException as e:
            return http_response_exception_or_throw(e)
    def customer_delivery_addresses_add():
        customer_storage = CustomerStorageImplementation()
        delivery_address_service = CustomerDeliveryAddressAppService(
            customer_storage)

        try:
            request_data = blueprint.current_request.json_body
            address_type = str(request_data.get('address_type')).strip()

            user_id = __get_user_id()
            form = __create_delivery_address_form(address_type)
            form.load(request_data)
            if form.validate():
                delivery_address_service.add_delivery_address(user_id, form)
                return __response_list(user_id)
            else:
                return {
                    'validation_errors': form.validation_errors,
                }
        except BaseException as e:
            return http_response_exception_or_throw(e)
Exemplo n.º 15
0
class CancelledOrderOnPortalSideSqsHandle(SqsHandlerInterface):
    def __init__(self):
        self.__orders_storage = OrderStorageImplementation()
        self.__products_storage = ProductStorageImplementation()
        self.__cancel_request_storage = CancelRequestStorageImplementation()
        self.__customer_storage = CustomerStorageImplementation()
        self.__messages_storage = MessageStorageImplementation()
        self.__sqs_sender = SqsSenderImplementation()
        self.__logger = Logger()

    def handle(self, sqs_message: SqsMessage) -> None:
        def __log_flow(text: str) -> None:
            self.__logger.log_simple('{} : {} : {}'.format(
                self.__class__.__qualname__,
                sqs_message.id,
                text
            ))

        __log_flow('Start - {}'.format(sqs_message.message_data))

        order_number = Order.Number(sqs_message.message_data['order_number'])
        simple_sku = SimpleSku(sqs_message.message_data['simple_sku'])
        qty = Qty(sqs_message.message_data['qty'])

        order = self.__orders_storage.load(order_number)
        product = self.__products_storage.load(simple_sku)

        if order.was_paid:
            order.request_cancellation_after_payment(simple_sku, qty)
            order.approve_cancellation_after_payment(simple_sku, qty)
        else:
            order.cancel_before_payment(simple_sku, qty)

        product.restore_qty(qty)
        order_change_event = OrderChangeSqsSenderEvent(order)

        __log_flow('Order Saving...')
        self.__orders_storage.save(order)
        __log_flow('Order Saved!')

        __log_flow('Product Saving...')
        self.__products_storage.update(product)
        __log_flow('Product Saved!')

        __log_flow('Order SQS Sending...')
        self.__sqs_sender.send(order_change_event)
        __log_flow('Order SQS Sent!')

        try:
            __log_flow('Notification popup: Adding...')
            customer = self.__customer_storage.get_by_id(order.customer_id)
            message = Message(
                str(uuid.uuid4()),
                customer.email.value,
                'Order #{} has been Updated!'.format(order.number.value),
                'Product "{}" for Order #{} has been Cancelled in Qty {}!'.format(
                    product.name.value,
                    order.number.value,
                    qty.value
                ),
            )
            self.__messages_storage.save(message)
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))

        __log_flow('End')
Exemplo n.º 16
0
    def _order_info(order: Order) -> dict:
        customer_storage = CustomerStorageImplementation()
        mpc_products = MpcProducts()

        customer = customer_storage.get_by_id(order.customer_id)
        delivery_address = order.delivery_address

        order_items_data = []
        for order_item in order.items:
            product = mpc_products.getRawDataBySimpleSku(
                order_item.simple_sku.value, False)
            size = tuple(
                filter(
                    lambda s: s['rs_simple_sku'] == order_item.simple_sku.
                    value, product['sizes']))[0]

            simple_id = int(size['portal_simple_id'])

            qty_ordered = order_item.qty_ordered.value
            qty_canceled_before_payment = order_item.qty_cancelled_before_payment.value
            qty_canceled_after_payment = order_item.qty_cancelled_after_payment_cancelled.value
            qty_invoiced = (
                qty_ordered -
                qty_canceled_before_payment) if order.was_paid else 0
            qty_shipped = (
                qty_ordered - qty_canceled_before_payment -
                qty_canceled_after_payment) if order.was_delivered else 0
            qty_returned = order_item.qty_return_returned.value
            qty_refunded = order_item.qty_refunded.value

            # @todo : not done yet
            base_discount_amount = 0

            # 1 - may mean "usual item", then 2 - "promo item", ...
            items_group_id = str(int('1%010d' % simple_id))

            order_items_data.append({
                'DTD':
                str(order_item.dtd.working_days_from) + '-' +
                str(order_item.dtd.working_days_to),
                'item_id':
                items_group_id,
                'sku':
                order_item.simple_sku.value,
                'rs_event_code':
                order_item.event_code.value,
                'qty_ordered':
                qty_ordered,
                'qty_canceled_before_payment':
                qty_canceled_before_payment,
                'qty_invoiced':
                qty_invoiced,
                'qty_canceled_after_payment':
                qty_canceled_after_payment,
                'qty_shipped':
                qty_shipped,
                'qty_returned':
                qty_returned,
                'qty_refunded':
                qty_refunded,
                'original_rsp':
                order_item.total_original_cost_ordered.value,
                'original_price':
                order_item.total_current_cost_ordered.value,
                'base_cost':
                order_item.product_current_price.value,
                'base_discount_amount':
                base_discount_amount,
                'base_price_incl_tax':
                order_item.product_current_price.value,
                'base_tax_amount':
                (order_item.total_current_cost_ordered.value /
                 (100 + order.vat_percent.value)) * order.vat_percent.value,
                'tax_percent':
                order.vat_percent.value,
                'base_row_total_incl_tax':
                order_item.product_current_price.value *
                order_item.qty_ordered.value,
                'item_selling_incl_marketing_discount':
                order_item.product_current_price.value,
                'order_tax_amount':
                order.subtotal_vat_amount.value,
                'order_total_inc_voucher_discounts':
                order.total_current_cost_ordered.value,
                'rs_store_type':
                'MPC',
                'product_id':
                simple_id,
                'in_time_for_what':
                '',
                'in_time_for_status':
                0,
            })

        if customer.name:
            customer_first_name = customer.name.first_name.value
            customer_last_name = customer.name.last_name.value
        else:
            customer_name_str = delivery_address.recipient_name
            customer_first_name = customer_name_str.split(' ')[0]
            customer_last_name = customer_name_str.replace(
                customer_first_name, '', 1).strip()

        customer_genders_map = {
            CustomerInterface.Gender.MALE: 'male',
            CustomerInterface.Gender.FEMALE: 'female',
        }

        data = {
            'increment_id':
            order.number.value,
            'status':
            order.status.value,
            'created_at':
            __to_utc(order.created_at).strftime('%Y-%m-%d %H:%M:%S'),
            'updated_at':
            __to_utc(order.updated_at).strftime('%Y-%m-%d %H:%M:%S'),
            'payment': {
                'entity_id':
                order.number.value,
                'method':
                order.payment_method.descriptor,
                'additional_information':
                order.payment_method.extra_data,
                'base_amount_paid':
                order.total_current_cost_paid.value -
                order.credit_spent_amount.value,
                'base_amount_refunded':
                order.total_refunded_cost.value,
            } if order.payment_method else {
                'base_amount_ordered':
                order.total_current_cost_ordered.value
                if order.was_paid else 0,
            },
            'status_history': [{
                'status':
                status_change.status.value,
                'created_at':
                __to_utc(status_change.datetime).strftime('%Y-%m-%d %H:%M:%S'),
            } for status_change in order.status_history],
            'invoice': [{
                'entity_id':
                order.number.value,
                'state':
                3 if order.was_closed or order.was_cancelled else
                2 if order.was_paid else 1,
                'base_grand_total':
                order.total_current_cost_paid.value -
                order.credit_spent_amount.value,
                'base_tax_amount':
                ((order.total_current_cost_paid.value -
                  order.credit_spent_amount.value) /
                 (100 + order.vat_percent.value)) * order.vat_percent.value,

                # @todo : not developed yet
                'base_discount_amount':
                0,
                'base_subtotal_incl_tax':
                sum([
                    item.product_current_price.value *
                    ((item.qty_ordered.value -
                      item.qty_cancelled_before_payment.value)
                     if order.was_paid else 0) for item in order.items
                ]),
                'base_shipping_amount':
                order.delivery_cost.value,
                'base_customer_balance_amount':
                order.credit_spent_amount.value,
            }] if order.was_paid else [],
            'customer_firstname':
            customer_first_name,
            'customer_lastname':
            customer_last_name,
            'customer_email':
            customer.email.value,
            'customer_gender':
            customer_genders_map.get(customer.gender.descriptor, None)
            if customer.gender else None,
            'ip_address':
            None,  # @TODO : add IP address. Not sure, that it should be inside Order. Create Elastic?
            'addresses': [{
                # magento address id. We use only one, so can set order number.
                'entity_id':
                order.number.value,
                # @todo : we have only one address in order, so let it be 'shipping'
                'address_type':
                'shipping',
                'country':
                'South Africa',
                'country_id':
                'ZA',
                'region':
                delivery_address.province,
                'city':
                delivery_address.city,
                'street':
                delivery_address.street_address,
                'suburb':
                delivery_address.suburb,
                'postcode':
                delivery_address.postal_code,
                'company':
                delivery_address.business_name,
                'firstname':
                delivery_address.recipient_name.split(' ')[0],
                'lastname':
                (lambda s: s.replace(s.split(' ')[0] + ' ', '', 1))(
                    delivery_address.recipient_name),
                'telephone':
                delivery_address.phone_number,
            }],
            'items':
            order_items_data,
            'base_subtotal_incl_tax':
            sum([
                item.product_current_price.value * item.qty_ordered.value
                for item in order.items
            ]),
            'base_customer_balance_invoiced':
            order.credit_spent_amount.value,
        }

        return data
Exemplo n.º 17
0
 def __init__(self):
     self.__information_service = InformationService()
     self.__customers_storage = CustomerStorageImplementation()
     self.__tiers_storage = CustomerTierStorageImplementation()
Exemplo n.º 18
0
 def __init__(self):
     from chalicelib.libs.purchase.customer.storage import CustomerStorageImplementation
     from chalicelib.libs.purchase.product.storage import ProductStorageImplementation
     self.__storage = _CheckoutDynamoDbStorage(
         CustomerStorageImplementation(), ProductStorageImplementation())
Exemplo n.º 19
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)
Exemplo n.º 20
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()
Exemplo n.º 21
0
class RegularEftPaymentSqsHandler(SqsHandlerInterface):
    def __init__(self):
        # Need to be imported here, because orders storage depends from payment method class in this file.
        from chalicelib.libs.purchase.order.storage import OrderStorageImplementation
        from chalicelib.libs.purchase.product.storage import ProductStorageImplementation
        self.__order_storage = OrderStorageImplementation()
        self.__sqs_sender = SqsSenderImplementation()
        self.__logger = Logger()
        self.__message_storage = MessageStorageImplementation()
        self.__customer_storage = CustomerStorageImplementation()
        self.__products_storage = ProductStorageImplementation()

    def handle(self, sqs_message: SqsMessage) -> None:
        def __log_flow(text: str) -> None:
            self.__logger.log_simple('{} : SQS Message #{} : {}'.format(
                self.__class__.__qualname__, sqs_message.id, text))

        __log_flow('Start')

        if sqs_message.message_type != 'regular_eft_proof_check_result':
            raise ValueError(
                '{} does not know how to handle {} sqs message! Message data: {}'
                .format(self.__class__.__qualname__, sqs_message.message_type,
                        sqs_message.message_data))

        order_number_value = sqs_message.message_data.get('order_number')
        is_proof_accepted = sqs_message.message_data.get('is_proof_accepted')

        __log_flow('Order #{} - Payment - {}'.format(
            order_number_value,
            'Accepted' if is_proof_accepted else 'Declined'))

        __log_flow('Updating...')
        order_number = Order.Number(order_number_value)
        order = self.__order_storage.load(order_number)
        if not order:
            raise ValueError(
                'Unable to handle {} sqs-message #{}: order does not exist. Message data: {}'
                .format(sqs_message.message_type, sqs_message.id,
                        sqs_message.message_data))

        if not isinstance(order.payment_method, RegularEftOrderPaymentMethod):
            raise ValueError(
                'Order #{} is not a Regular EFT payment order!'.format(
                    order.number.value))

        if is_proof_accepted:
            # accept order payment
            __log_flow('Order Updating...')
            order.status = Order.Status(Order.Status.PAYMENT_RECEIVED)
            self.__order_storage.save(order)
            __log_flow('Order Updated!')
        else:
            # Attention!
            # Order must be closed first to avoid multiple "restore-qty" actions!
            # @todo : refactoring ?

            # close order
            __log_flow('Order Closing...')
            order.status = Order.Status(Order.Status.CLOSED)
            self.__order_storage.save(order)
            __log_flow('Order Closed!')

            # restore products qty
            __log_flow('Product Qty Restoring - Start')
            for order_item in order.items:
                if order_item.qty_processable.value == 0:
                    __log_flow(
                        'Product Qty Restoring: {} skipped because of 0 qty'.
                        format(order_item.simple_sku.value))
                    continue

                try:
                    __log_flow('Product Qty Restoring {} / {} ...'.format(
                        order_item.simple_sku.value,
                        order_item.qty_processable.value))
                    product = self.__products_storage.load(
                        order_item.simple_sku)
                    product.restore_qty(order_item.qty_processable)
                    self.__products_storage.update(product)
                    __log_flow('Product Qty Restored {} / {}!'.format(
                        order_item.simple_sku.value,
                        order_item.qty_processable.value))
                except BaseException as e:
                    self.__logger.log_exception(e)
                    __log_flow(
                        'Product Qty NOT Restored {} / {} because of Error: '.
                        format(order_item.simple_sku.value,
                               order_item.qty_processable.value, str(e)))

            __log_flow('Product Qty Restoring - End')

        __log_flow('Updated!')

        # send to portal
        __log_flow('Order SQS: Sending...')
        self.__sqs_sender.send(OrderChangeSqsSenderEvent(order))
        __log_flow('Order SQS: Sent!')

        # silently add notification (silently)
        try:
            __log_flow('Notification popup: Adding...')
            customer = self.__customer_storage.get_by_id(order.customer_id)
            if not customer:
                raise ValueError(
                    '{} cant notify customer #{} about Regular-EFT payment updates for Order #{}'
                    .format(self.handle.__qualname__, order.customer_id.value,
                            order.number.value))

            self.__message_storage.save(
                Message(
                    str(uuid.uuid4()), customer.email.value,
                    'Regular EFT Payment has been checked!',
                    'Regular EFT Payment for Order #{} has been checked and {}!'
                    .format(order.number.value,
                            'Accepted' if is_proof_accepted else 'Declined')))
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow(
                'Notification popup: Not Added because of Error : {}'.format(
                    str(e)))

        __log_flow('End')
Exemplo n.º 22
0
class OrderPaymentOhHoldHandler(SqsHandlerInterface):
    def __init__(self):
        self.__order_storage = OrderStorageImplementation()
        self.__sqs_sender = SqsSenderImplementation()
        self.__logger = Logger()
        self.__message_storage = MessageStorageImplementation()
        self.__customer_storage = CustomerStorageImplementation()
        self.__products_storage = ProductStorageImplementation()

    def handle(self, sqs_message: SqsMessage) -> None:
        def __log_flow(text: str) -> None:
            self.__logger.log_simple('{} : SQS Message #{} : {}'.format(
                self.__class__.__qualname__,
                sqs_message.id,
                text
            ))

        __log_flow('Start : {}'.format(sqs_message.message_data))

        if sqs_message.message_type != 'fixel_order_on_hold_by_portal':
            raise ValueError('{} does not know how to handle {} sqs message! Message data: {}'.format(
                self.__class__.__qualname__,
                sqs_message.message_type,
                sqs_message.message_data
            ))

        order_number_value = sqs_message.message_data.get('order_number')
        on_hold_status = sqs_message.message_data.get('status')

        order_number = Order.Number(order_number_value)
        order = self.__order_storage.load(order_number)

        if on_hold_status == Order.Status.CLOSED:
            self.__close_order_on_hold(order, __log_flow)
        else:
            self.__on_hold_not_closed_status(order, on_hold_status, __log_flow)

        self.__send_order_change_to_portal(order, __log_flow)
        self.__notify_about_order_status_change_silently(order, __log_flow)

        __log_flow('End')

    def __on_hold_not_closed_status(self, order: Order, on_hold_status: str, __log_flow) -> None:
        __log_flow('Order Updating...')
        order.status = Order.Status(on_hold_status)
        __log_flow('Order Updated!')

        __log_flow('Order Saving...')
        self.__order_storage.save(order)
        __log_flow('Order Saved!')

    def __close_order_on_hold(self, order: Order, __log_flow) -> None:
        __log_flow('Updating...')

        # close order
        __log_flow('Order Updating...')
        order.status = Order.Status(Order.Status.CLOSED)
        __log_flow('Order Updated!')

        # restore products qty
        __log_flow('Product Qty Updating - Start')
        products_to_save = []
        for order_item in order.items:
            if order_item.qty_processable.value == 0:
                __log_flow('Product Qty Updating: {} skipped because of 0 qty'.format(order_item.simple_sku.value))
                continue

            __log_flow('Product Qty Updating {} / {} ...'.format(
                order_item.simple_sku.value,
                order_item.qty_processable.value
            ))

            product = self.__products_storage.load(order_item.simple_sku)
            product.restore_qty(order_item.qty_processable)
            products_to_save.append(product)

            __log_flow('Product Qty Updated {} / {}!'.format(
                order_item.simple_sku.value,
                order_item.qty_processable.value
            ))

        __log_flow('Product Qty Updating - End')

        __log_flow('Updated!')

        __log_flow('Saving...')

        __log_flow('Order Saving...')
        self.__order_storage.save(order)
        __log_flow('Order Saved!')

        __log_flow('Products Saving...')
        for product in products_to_save:
            __log_flow('Product {} Saving...'.format(product.simple_sku.value))
            self.__products_storage.update(product)
            __log_flow('Product {} Saved!'.format(product.simple_sku.value))
        __log_flow('Products Saved!')

        __log_flow('Saved!')

    def __send_order_change_to_portal(self, order: Order, __log_flow) -> None:
        __log_flow('Order SQS: Sending...')
        self.__sqs_sender.send(OrderChangeSqsSenderEvent(order))
        __log_flow('Order SQS: Sent!')

    def __notify_about_order_status_change_silently(self, order: Order, __log_flow) -> None:
        try:
            __log_flow('Notification popup: Adding...')
            customer = self.__customer_storage.get_by_id(order.customer_id)
            self.__message_storage.save(Message(
                str(uuid.uuid4()),
                customer.email.value,
                'Order #{} status is changed!',
                'Order #{} has been turned to "{}" status!'.format(order.number.value, order.status.label)
            ))
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))
Exemplo n.º 23
0
class CancelRequestPaidOrderAnswerSqsHandler(SqsHandlerInterface):
    def __init__(self):
        self.__orders_storage = OrderStorageImplementation()
        self.__products_storage = ProductStorageImplementation()
        self.__cancel_request_storage = CancelRequestStorageImplementation()
        self.__customer_storage = CustomerStorageImplementation()
        self.__messages_storage = MessageStorageImplementation()
        self.__sqs_sender = SqsSenderImplementation()
        self.__logger = Logger()

    def handle(self, sqs_message: SqsMessage) -> None:
        def __log_flow(text: str) -> None:
            self.__logger.log_simple('{} : {} : {}'.format(
                self.__class__.__qualname__,
                sqs_message.id,
                text
            ))

        __log_flow('Start - {}'.format(sqs_message.message_data))

        request_number = CancelRequest.Number(sqs_message.message_data['request_number'])
        order_number = Order.Number(sqs_message.message_data['order_number'])
        simple_sku = SimpleSku(sqs_message.message_data['simple_sku'])
        qty = Qty(sqs_message.message_data['qty'])
        status = sqs_message.message_data['status']

        actions_map = {
            'approved': self.__approve,
            'declined': self.__decline,
        }

        action = actions_map.get(status)
        if not action:
            raise Exception('{} can\'t handle SQS message {}:{}! Status is unknown!'.format(
                self.handle.__qualname__,
                sqs_message.message_type,
                sqs_message.message_data
            ))

        action(request_number, order_number, simple_sku, qty, __log_flow)

        __log_flow('End')

    def __approve(
        self,
        request_number: CancelRequest.Number,
        order_number: Order.Number,
        simple_sku: SimpleSku,
        qty: Qty,
        __log_flow
    ) -> None:
        __log_flow('Approving...')

        cancel_request = self.__cancel_request_storage.get_by_number(request_number)
        order = self.__orders_storage.load(order_number)
        product = self.__products_storage.load(simple_sku)

        cancel_request.approve_item(simple_sku)
        order.approve_cancellation_after_payment(simple_sku, qty)
        product.restore_qty(qty)
        order_change_event = OrderChangeSqsSenderEvent(order)

        __log_flow('Cancel Request Saving...')
        self.__cancel_request_storage.save(cancel_request)
        __log_flow('Cancel Request Saved!')

        __log_flow('Order Saving...')
        self.__orders_storage.save(order)
        __log_flow('Order Saved!')

        __log_flow('Product Saving...')
        self.__products_storage.update(product)
        __log_flow('Product Saved!')

        __log_flow('Order SQS Sending...')
        self.__sqs_sender.send(order_change_event)
        __log_flow('Order SQS Sent!')

        try:
            __log_flow('Notification popup: Adding...')
            self.__add_notification_message(cancel_request, order, product, 'Approved')
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))

        __log_flow('Approved!')

    def __decline(
        self,
        request_number: CancelRequest.Number,
        order_number: Order.Number,
        simple_sku: SimpleSku,
        qty: Qty,
        __log_flow
    ) -> None:
        __log_flow('Declining...')

        cancel_request = self.__cancel_request_storage.get_by_number(request_number)
        order = self.__orders_storage.load(order_number)
        product = self.__products_storage.load(simple_sku)

        cancel_request.decline_item(simple_sku)
        order.decline_cancellation_after_payment(simple_sku, qty)
        order_change_event = OrderChangeSqsSenderEvent(order)

        __log_flow('Cancel Request Saving...')
        self.__cancel_request_storage.save(cancel_request)
        __log_flow('Cancel Request Saved!')

        __log_flow('Order Saving...')
        self.__orders_storage.save(order)
        __log_flow('Order Saved!')

        __log_flow('Order SQS Sending...')
        self.__sqs_sender.send(order_change_event)
        __log_flow('Order SQS Sent!')

        try:
            __log_flow('Notification popup: Adding...')
            self.__add_notification_message(cancel_request, order, product, 'Declined')
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))

        __log_flow('Declined!')

    def __add_notification_message(
        self,
        cancel_request: CancelRequest,
        order: Order,
        product: ProductInterface,
        status_label: str
    ) -> None:
        customer = self.__customer_storage.get_by_id(order.customer_id)
        message = Message(
            str(uuid.uuid4()),
            customer.email.value,
            'Cancellation Request #{} has been Updated!'.format(cancel_request.number.value),
            'Cancellation Request for Product "{}" for Order #{} has been {}!'.format(
                product.name.value,
                order.number.value,
                status_label
            ),
        )
        self.__messages_storage.save(message)
Exemplo n.º 24
0
class ReturnRequestChangeSqsHandler(SqsHandlerInterface):
    def __init__(self):
        self.__returns_storage = ReturnRequestStorageImplementation()
        self.__order_storage = OrderStorageImplementation()
        self.__customer_storage = CustomerStorageImplementation()
        self.__product_storage = ProductStorageImplementation()
        self.__messages_storage = MessageStorageImplementation()
        self.__sqs_sender = SqsSenderImplementation()
        self.__logger = Logger()

    def handle(self, sqs_message: SqsMessage) -> None:
        def __log_flow(text: str) -> None:
            self.__logger.log_simple('{} : {} : {}'.format(
                self.__class__.__qualname__,
                sqs_message.id,
                text
            ))

        __log_flow('Start - {}'.format(sqs_message.message_data))

        request_number = sqs_message.message_data.get('return_request_number')
        order_number = sqs_message.message_data.get('order_number')
        simple_sku = sqs_message.message_data.get('simple_sku')
        status = sqs_message.message_data.get('status')

        __log_flow('Updating...')
        return_request = self.__returns_storage.load(ReturnRequest.Number(request_number))
        if not return_request:
            raise ValueError('{} can\'t handle SQS message {}:{}! Return Request does not exist!'.format(
                self.handle.__qualname__,
                sqs_message.message_type,
                sqs_message.message_data
            ))

        actions_map = {
            'approved': self.__approve,
            'cancelled': self.__decline,
            'closed': self.__close,
        }

        action = actions_map.get(status)
        if not action:
            raise Exception('{} can\'t handle SQS message {}:{}! Status is unknown!'.format(
                self.handle.__qualname__,
                sqs_message.message_type,
                sqs_message.message_data
            ))

        action(return_request, OrderNumber(order_number), SimpleSku(simple_sku), __log_flow)
        __log_flow('Updated!')

        __log_flow('End')

    def __approve(
        self,
        return_request: ReturnRequest,
        order_number: OrderNumber,
        simple_sku: SimpleSku,
        __log_flow
    ) -> None:
        __log_flow('Approving...')

        return_request.make_item_approved(order_number, simple_sku)

        __log_flow('Return Request Saving...')
        self.__returns_storage.save(return_request)
        __log_flow('Return Request Saved!')

        try:
            __log_flow('Notification popup: Adding...')
            self.__add_notification_message(return_request, order_number, simple_sku, 'Approved')
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))

        __log_flow('Approved!')

    def __decline(
        self,
        return_request: ReturnRequest,
        order_number: OrderNumber,
        simple_sku: SimpleSku,
        __log_flow
    ) -> None:
        __log_flow('Declining...')

        qty = return_request.get_item_qty(order_number, simple_sku)
        order = self.__order_storage.load(order_number)

        # updating
        return_request.make_item_declined(order_number, simple_sku)
        order.decline_return(simple_sku, qty)

        # saving
        __log_flow('Declining: Return Request Saving...')
        self.__returns_storage.save(return_request)
        __log_flow('Declining: Return Request Saved!')

        __log_flow('Declining: Order Saving...')
        self.__order_storage.save(order)
        __log_flow('Declining: Order Saved!')

        # add notification silently
        try:
            __log_flow('Notification popup: Adding...')
            self.__add_notification_message(return_request, order_number, simple_sku, 'Declined')
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))

        __log_flow('Declined!')

    def __close(
        self,
        return_request: ReturnRequest,
        order_number: OrderNumber,
        simple_sku: SimpleSku,
        __log_flow
    ) -> None:
        __log_flow('Closing...')

        qty = return_request.get_item_qty(order_number, simple_sku)
        product = self.__product_storage.load(simple_sku)
        order = self.__order_storage.load(order_number)

        product.restore_qty(qty)
        order.close_return(simple_sku, qty)
        return_request.make_item_closed(order_number, simple_sku)

        # update product
        __log_flow('Closing: Product Qty: Restoring...')
        self.__product_storage.update(product)
        __log_flow('Closing: Product Qty: Restored!')

        # update order
        __log_flow('Closing: Order Qty: Returning...')
        self.__order_storage.save(order)
        __log_flow('Closing: Order Qty: Returned!')

        # update request
        __log_flow('Closing: Return Request: Updating...')
        self.__returns_storage.save(return_request)
        __log_flow('Closing: Return Request: Updated!')

        __log_flow('Closing: Order SQS: Sending...')
        self.__sqs_sender.send(OrderChangeSqsSenderEvent(order))
        __log_flow('Closing: Order SQS: Sent!')

        # add notification silently
        try:
            __log_flow('Notification popup: Adding...')
            self.__add_notification_message(return_request, order_number, simple_sku, 'Closed')
            __log_flow('Notification popup: Added!')
        except BaseException as e:
            self.__logger.log_exception(e)
            __log_flow('Notification popup: Not Added because of Error : {}'.format(str(e)))

        __log_flow('Closed!')

    def __add_notification_message(
        self,
        return_request: ReturnRequest,
        order_number: OrderNumber,
        simple_sku: SimpleSku,
        status_label: str
    ) -> None:
        order = self.__order_storage.load(order_number)
        customer = self.__customer_storage.get_by_id(order.customer_id)
        product = self.__product_storage.load(simple_sku)
        message = Message(
            str(uuid.uuid4()),
            customer.email.value,
            'Return Request #{} has been Updated!'.format(return_request.number.value),
            'Return Request for Product "{}" for Order #{} has been {}!'.format(
                product.name.value,
                order_number.value,
                status_label
            ),
        )
        self.__messages_storage.save(message)