Exemplo n.º 1
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__()
Exemplo n.º 2
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.',
    )
Exemplo n.º 3
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',
    )
Exemplo n.º 4
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)
Exemplo n.º 5
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)
Exemplo n.º 6
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