Esempio n. 1
0
class ContributorModel(TimeStampedModel):
    """
    This model contains the contributor information about who is owner, who is
    editor.
    """
    created_by = fields.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name='Created by',
        blank=True,
        null=True,
        on_delete=SET_NULL,
        related_name='%(class)s_created_by',
    )

    last_modified_by = fields.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name='Last modified by',
        blank=True,
        null=True,
        on_delete=SET_NULL,
        related_name='%(class)s_modified_by',
    )

    class Meta:
        abstract = True
Esempio n. 2
0
class RedeemingProduct(ContributorModel):
    """
    This model store products which allow to redeem by point or coupon.
    """

    objects = RedeemingProductQuerySet.as_manager()

    product = fields.ForeignKey(
        Product,
        on_delete=models.CASCADE,
    )
    kind = fields.IntegerField(
        choices=RedeemKindsEnum.to_tuple(),
        default=RedeemKindsEnum.NA.value,
    )
    start_date = fields.DateTimeField(
        null=True,
        blank=True,
    )
    end_date = fields.DateTimeField(
        null=True,
        blank=True,
    )

    @property
    def is_expired(self):
        import datetime
        now = datetime.datetime.now().replace(tzinfo=pytz.UTC)

        if not self.end_date:
            return False

        return self.end_date.replace(tzinfo=pytz.UTC) > now
Esempio n. 3
0
class MembershipLevel(ContributorModel):
    """
    MembershipLevel model
    """
    objects = MembershipLevelQuerySet.as_manager()

    previous = fields.ForeignKey(
        'self',
        on_delete=models.SET_NULL,
        related_name='level_previous',
        null=True,
        blank=True,
    )
    next = fields.ForeignKey(
        'self',
        on_delete=models.SET_NULL,
        related_name='level_next',
        null=True,
        blank=True,
    )
    name = fields.ShortNameField(
        null=False,
        blank=False,
        unique=True,
    )
    require_point = fields.IntegerField(
        default=0,
        validators=[MinValueValidator(0)],
        help_text='Require point to reach the membership level.')
    earning_point_rate = fields.FloatField(
        default=1,
        validators=[MinValueValidator(0),
                    MaxValueValidator(1)],
        help_text='The rate to calculate the earning point for customer based'
        'on the total amount on the bill.',
    )
    burning_point_rate = fields.FloatField(
        default=1,
        validators=[MinValueValidator(0),
                    MaxValueValidator(1)],
        help_text='The rate to calculate amount when customer uses point to'
        'redeem products.',
    )
Esempio n. 4
0
class Transaction(ContributorModel):
    """
    Transaction model
    """
    objects = TransactionQuerySet.as_manager()

    order = fields.OneToOneField(
        Order,
        on_delete=models.CASCADE,
        help_text='The order which transaction belong to',
    )
    user = fields.ForeignKey(
        'accounts.User',
        on_delete=models.CASCADE,
        help_text='The user who own this transaction',
    )
    coupon = fields.ForeignKey(
        Coupon,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
    )
    total_amount = fields.FloatField(
        default=0,
        validators=[MinValueValidator(0)],
        help_text='Total amount of an order',
    )
    total_pay_amount = fields.FloatField(
        default=0,
        validators=[MinValueValidator(0)],
        help_text='The actual amount that customer need to pay',
    )
    earning_point = fields.IntegerField(
        validators=[MinValueValidator(0)],
        default=0,
        help_text='Number of point of an order that customer earns',
    )
    burning_point = fields.IntegerField(
        validators=[MinValueValidator(0)],
        default=0,
        help_text='Number of point that customer spend to redeem product',
    )
Esempio n. 5
0
class Category(ProductCommonInfoModel):

    objects = CategoryQuerySet.as_manager()

    parent = fields.ForeignKey(
        'self',
        verbose_name='Parent category',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )

    def __str__(self):
        return super().__str__()
Esempio n. 6
0
class Product(ProductCommonInfoModel):

    objects = ProductQuerySet.as_manager()

    price = fields.FloatField(validators=[MinValueValidator(0)], )
    quantity = fields.IntegerField(validators=[MinValueValidator(0)], )
    sold_quantity = fields.IntegerField(validators=[MinValueValidator(0)], )
    category = fields.ForeignKey(
        Category,
        verbose_name='Product category',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    gender = fields.ForeignKey(
        Gender,
        verbose_name='Product gender',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    base_colour = fields.ForeignKey(
        BaseColour,
        verbose_name='Product base colour',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    season = fields.ForeignKey(
        Season,
        verbose_name='Season',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    usage = fields.ForeignKey(
        Usage,
        verbose_name='Product usage',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    article_type = fields.ForeignKey(
        ArticleType,
        verbose_name='Article type',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    is_published = fields.BooleanField(
        default=False,
        help_text='This is a flag which is used to determine that product is'
        'published or not. `False`: not published; otherwise, `True`.')

    def __str__(self):
        return super().__str__()
Esempio n. 7
0
class AssigningPoint(ContributorModel):
    """
    AssigningPoint model.
    """

    objects = AssigningPointQuerySet.as_manager()

    customer = fields.ForeignKey(
        Customer,
        on_delete=models.CASCADE,
    )
    point = fields.IntegerField(
        default=0,
        validators=[MinValueValidator(0)],
        help_text='Assigning point which assigned to customer.')
    description = fields.LongDescField()
Esempio n. 8
0
class AddressBook(ContributorModel):
    """
    AddressBook model.
    """

    objects = AddressBookQuerySet.as_manager()

    customer = fields.ForeignKey(
        Customer,
        on_delete=models.CASCADE,
    )
    address = fields.LongNameField()
    is_default = fields.BooleanField(
        default=False,
        help_text='This flag is used to mark which address is default address.',
    )
Esempio n. 9
0
class GiftItem(ContributorModel):
    """
    Gift Item model
    """
    objects = GiftItemQuerySet.as_manager()

    product = fields.OneToOneField(
        Product,
        on_delete=models.CASCADE,
    )
    gift = fields.ForeignKey(
        Gift,
        on_delete=models.CASCADE,
    )
    quantity = fields.IntegerField(
        verbose_name='Number of item',
        default=0,
        validators=[MinValueValidator(0)],
    )
Esempio n. 10
0
class Coupon(ContributorModel):
    """
    Coupon model.
    """

    objects = CouponQuerySet.as_manager()

    reward = fields.ForeignKey(
        'Reward',
        on_delete=models.CASCADE,
        help_text='The reward that coupon belong to',
    )
    kind = fields.IntegerField(
        verbose_name='Coupon kind',
        choices=CouponKindsEnum.to_tuple(),
        default=CouponKindsEnum.PERCENTAGE.value,
    )
    code = fields.CharField(
        verbose_name='Reward unique coupon code',
        max_length=10,
        unique=True,
    )
    start_date = models.DateTimeField(
        verbose_name='Valid date time',
        null=True,
        blank=True,
    )
    end_date = models.DateTimeField(
        verbose_name='Expire date time',
        null=True,
        blank=True,
    )
    amount = fields.FloatField(
        help_text='Depend on coupon kind. If coupon kind is percentage coupon, '
        'its value must be between 0 to 100. If coupon kind is money,'
        'its value must be greater than 0',
        validators=[MinValueValidator(0)],
    )
    target_amount = fields.FloatField(
        help_text='How much need to be bought to use coupon.',
        validators=[MinValueValidator(0)],
    )
    is_minimum_purchase = fields.BooleanField(
        default=True,
        help_text='This flag is used to determine when customer can use coupon.'
        '`True`: Coupon can be used when total amount on bill is '
        'greater than or equal `target amount`.'
        '`False`: Coupon can be used when total amount on bill is'
        'less than target amount.',
    )
    is_one_time_using = fields.BooleanField(
        default=True,
        help_text='True if coupon is allowed one time using, '
        '`False` if it can be used all times.',
    )
    can_by_any_product = fields.BooleanField(
        default=False,
        help_text='`True`: coupon can buy any product. '
        '`False`: coupon can buy products in the allowed list only.',
    )
    is_active = fields.BooleanField(
        default=True,
        help_text='`True` if coupon is active; otherwise, `False`.',
    )
    is_expired = fields.BooleanField(
        default=False,
        help_text='`True` if coupon is expired; otherwise, `False`.',
    )

    def __str__(self):
        return f'coupon:{self.id}'

    def save(self, **kwargs):
        if not self.code:
            self.code = uuid.uuid4()

        super().save(**kwargs)
Esempio n. 11
0
class Order(ContributorModel):
    """
    Order model
    """
    objects = OrderQuerySet.as_manager()
    user = fields.ForeignKey(
        'accounts.User',
        on_delete=models.CASCADE,
        help_text='The owner of the order',
    )
    status = fields.IntegerField(
        choices=OrderStatusesEnum.to_tuple(),
        default=OrderStatusesEnum.SHOPPING.value,
        help_text='The order status',
    )
    total_amount = fields.FloatField(
        default=0,
        validators=[MinValueValidator(0)],
        help_text='Total amount before applying coupon and points',
    )
    shipping_address = fields.ForeignKey(
        AddressBook,
        on_delete=models.SET_NULL,
        null=True,
        default=None,
        help_text='The shipping address',
    )
    coupon = fields.ForeignKey(
        Coupon,
        on_delete=models.SET_NULL,
        null=True,
        default=None,
        help_text='The coupon',
    )
    burning_point = fields.IntegerField(
        default=0,
        help_text='Number of points which used to redeem products',
    )

    @property
    def total_pay_amount(self) -> float:
        """
        Calculate the total payment amount

        @return: Total payment amount
        """
        if self.burning_point:
            customer_obj = Customer.objects.get(account=self.user)
            burning_point_rate = customer_obj.membership.level.\
                burning_point_rate

            return self.total_amount - burning_point_rate * self.burning_point

        else:
            return self.total_amount

    @property
    def num_of_items(self) -> int:
        """
        Number of items in the cart

        @return: The number of order/cart items
        """
        return OrderItem.objects.non_archived_only().\
            filter(order=self.id).count()

    def __str__(self):
        return f'order-{self.id}'

    def clean(self):
        """
        Don't allow `total_pay_amount` greater than `total_amount`
        """
        if self.total_amount < self.total_pay_amount:
            total_amount_msg = '`total_amount` must be greater ' \
                               'than or equal `total_pay_amount`'

            total_pay_amount_msg = '`total_pay_amount` must be less ' \
                                   'than or equal `total_amount`'

            raise ValidationError({
                'total_pay_amount':
                ValidationError(_(total_pay_amount_msg)),
                'total_amount':
                ValidationError(_(total_amount_msg))
            })

    def save(self, **kwargs):
        """
        Save an order and auto create transaction based on the order status.
        If order is paid, transaction is created if not existed.
        Otherwise, just save order only

        TODO: Need to calculate total pay amount when applying coupon.
            Temporary skip this task.
        """
        try:
            # If order status is not paid status. Just save and by pass.
            if self.status is not OrderStatusesEnum.PAID.value:
                super(Order, self).save(**kwargs)
                return

            self._create_transaction(**kwargs)
        except Exception as e:
            raise e

    def _create_transaction(self, **kwargs):
        """
        Auto create a transaction if the order status is paid
        """
        # Get customer info who has this order
        membership_level = None
        customer_obj = Customer.objects.get(account=self.user)
        if customer_obj and customer_obj.membership:
            membership_level = customer_obj.membership.level

        earning_point_rate = membership_level.earning_point_rate \
            if membership_level else 0

        earning_point = int(self.total_pay_amount * earning_point_rate)

        transaction_data = {
            'order': self,
            'user': self.user,
            'total_amount': self.total_amount,
            'total_pay_amount': self.total_pay_amount,
            'earning_point': earning_point,
            'created_by': self.last_modified_by,
            'last_modified_by': self.last_modified_by,
        }

        # Update earning point for customer.
        customer_obj.total_earned_point += earning_point
        customer_obj.available_point += earning_point
        customer_obj.save()

        # If order status is paid status, check and create transaction.
        if self.pk is None:
            super(Order, self).save(**kwargs)
            Transaction.objects.create(**transaction_data)
        else:
            old_instance = Order.objects.get(pk=self.pk)
            super(Order, self).save(**kwargs)

            transaction = Transaction.objects.filter(order=self)

            if old_instance.status is not OrderStatusesEnum.PAID.value \
                    and len(transaction) == 0:
                Transaction.objects.create(**transaction_data)
Esempio n. 12
0
class OrderItem(ContributorModel):
    """
    Order Item model
    """
    objects = OrderItemQuerySet.as_manager()
    order = fields.ForeignKey(
        Order,
        on_delete=models.CASCADE,
    )
    product = fields.ForeignKey(
        Product,
        on_delete=models.CASCADE,
    )
    quantity = fields.IntegerField(validators=[MinValueValidator(1)], )
    copy_price = fields.FloatField(
        default=0,
        null=True,
    )

    @property
    def price(self):
        if not self.copy_price:
            self.copy_price = self.product.price

        return self.copy_price

    @property
    def amount(self):
        return self.price * self.quantity

    def __str__(self):
        return f'order-item-{self.id}'

    def delete(self, using=None, keep_parents=False):
        """
        Delete an order item and update the order information.
        """
        try:
            super(OrderItem, self).delete(using, keep_parents)

            self.order.total_amount = models.F('total_amount') - self.amount
            self.order.save()
        except Exception as e:
            raise e

    def save(self, **kwargs):
        """
        Save an order item and update the order information.
        """
        try:
            if self.pk is None:
                super(OrderItem, self).save(**kwargs)
                self.order.total_amount += self.amount
                self.order.save()

            else:
                old_order_item = OrderItem.objects.get(pk=self.pk)
                super(OrderItem, self).save(**kwargs)
                if old_order_item.amount is not self.amount:
                    self.order.total_amount = self.order.total_amount - \
                                              old_order_item.amount + \
                                              self.amount
                    self.order.save()
        except Exception as e:
            raise e
Esempio n. 13
0
class Customer(ContributorModel):
    """
    Customer model.
    """

    objects = CustomerQuerySet.as_manager()

    name = fields.ShortNameField(verbose_name='Customer name', )
    account = fields.OneToOneField(
        'accounts.User',
        on_delete=models.CASCADE,
    )
    membership = fields.ForeignKey(
        Membership,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
    )
    email = models.OneToOneField(
        Email,
        on_delete=models.CASCADE,
    )
    phone_number = fields.PhoneNumberField()
    available_point = fields.IntegerField(
        help_text='Current point',
        default=0,
        null=False,
        blank=False,
        validators=[MinValueValidator(0)],
    )
    total_earned_point = fields.IntegerField(
        help_text='Total earned point from the beginning',
        default=0,
        null=False,
        blank=False,
        validators=[MinValueValidator(0)],
    )
    is_active = models.BooleanField(
        help_text='Indicate that customer is active or not. '
        'True if active; otherwise, False',
        default=True,
    )

    def __str__(self):
        return f'customer: {self.id}'

    def clean(self):
        """
        Don't allow `total_earned_point` is less than `available_point`
        """
        if self.total_earned_point < self.available_point:
            total_earned_point_msg = '`total_earned_point` must be greater ' \
                                     'than or equal `available_point`'

            available_point_msg = '`available_point` must be less ' \
                                  'than or equal `total_earned_point`'

            raise ValidationError({
                'total_earned_point':
                ValidationError(_(total_earned_point_msg)),
                'available_point':
                ValidationError(_(available_point_msg))
            })

    def save(self, **kwargs):
        """
        Save the customer information.
        Check and set membership level for customer based on the total earned
        point.
        """
        try:
            level_obj = MembershipLevel.objects.filter(
                require_point__lte=self.total_earned_point).order_by(
                    '-require_point').first()

            if level_obj:
                if self.membership:
                    self.membership.level = level_obj
                    self.membership.save()
                else:
                    level_filtered = Membership.objects.filter(level=level_obj)\
                                                       .first()
                    self.membership = level_filtered if level_filtered else \
                        Membership.objects.create(level=level_obj)

            super(Customer, self).save(**kwargs)
        except Exception as e:
            raise e