feat: add profit and special discount fields to ProductVariant model
- Updated ProductVariant model to include 'profit' and 'special_discount_percent' fields. - Added corresponding fields in the admin interface for ProductVariant. - Created migration to add new fields to the database. feat: implement special discount code functionality in cart - Added composables for submitting and deleting special discount codes. - Updated CartSummary and CartItem components to handle special discount codes. - Enhanced API endpoints to support special discount operations. - Updated global types to include special discount code details in the cart.
This commit is contained in:
@@ -15,7 +15,11 @@ from django.template.loader import render_to_string
|
|||||||
from folium import Map, Marker
|
from folium import Map, Marker
|
||||||
from unfold.decorators import action, display
|
from unfold.decorators import action, display
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
|
from account.models import SpecialDiscountCode
|
||||||
|
|
||||||
|
@admin.register(SpecialDiscountCode)
|
||||||
|
class SpecialDiscountCodeAdmin(ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
class UserAddressInLine(TabularInline):
|
class UserAddressInLine(TabularInline):
|
||||||
model = UserAddressModel
|
model = UserAddressModel
|
||||||
|
|||||||
@@ -61,6 +61,15 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||||||
# def groups(self):
|
# def groups(self):
|
||||||
# return None
|
# return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_special_code(self):
|
||||||
|
"""Generate and save a unique special code for the user if missing."""
|
||||||
|
# simple deterministic code based on phone and timestamp hash
|
||||||
|
base = f"{self.phone}-{timezone.now().timestamp()}"
|
||||||
|
code = hashlib.sha256(base.encode()).hexdigest()[:12].upper()
|
||||||
|
|
||||||
|
return code
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
if self.first_name and self.last_name:
|
if self.first_name and self.last_name:
|
||||||
@@ -120,6 +129,14 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|||||||
return self.phone
|
return self.phone
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialDiscountCode(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.PROTECT, related_name='spital_code')
|
||||||
|
code = models.CharField(max_length=12, unique=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.user} - {self.code}'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ShopModel(models.Model):
|
class ShopModel(models.Model):
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='shop', verbose_name='کاربر')
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='shop', verbose_name='کاربر')
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class SendOTPView(APIView):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return Response({'detail': 'user not found'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'detail': 'user not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response({'detail': f'error: {e} مشتی فعلا برو تو غمت نباشه تا بعدا یه کاریش بکنم', 'otp_code': otp}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
return Response({'detail': f'error: {e} مشتی فعلا برو تو غمت نباشه تا بعدا یه کاریش بکنم', 'otp_code': otp}, status=status.HTTP_200_OK)
|
||||||
# return Response({'detail': f'An error occurred: {e}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
# return Response({'detail': f'An error occurred: {e}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
@extend_schema_view(
|
@extend_schema_view(
|
||||||
post=extend_schema(tags=['authentication'])
|
post=extend_schema(tags=['authentication'])
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-11-14 14:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0032_specialdiscountcode'),
|
||||||
|
('order', '0037_ordermodel_is_stock_rolled_back'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cart',
|
||||||
|
name='special_discount_code',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='account.specialdiscountcode', verbose_name='کدتخفیف خاص'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cartitem',
|
||||||
|
name='special_discount_amount',
|
||||||
|
field=models.BigIntegerField(default=0, help_text='تخفیف محاسبه شده از سود تنوع', verbose_name='مقدار تخفیف ویژه'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='orderitemmodel',
|
||||||
|
name='special_discount_amount',
|
||||||
|
field=models.BigIntegerField(default=0, verbose_name='مقدار تخفیف ویژه'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ordermodel',
|
||||||
|
name='special_discount_total',
|
||||||
|
field=models.BigIntegerField(blank=True, null=True, verbose_name='مجموع تخفیف ویژه'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-11-14 15:19
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0038_cart_special_discount_code_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='cartitem',
|
||||||
|
name='special_discount_amount',
|
||||||
|
),
|
||||||
|
]
|
||||||
+36
-3
@@ -1,3 +1,4 @@
|
|||||||
|
from account.models import SpecialDiscountCode
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from account.models import User, UserAddressModel, PushSubscription
|
from account.models import User, UserAddressModel, PushSubscription
|
||||||
from product.models import ProductModel, ProductVariant, ProductImageModel
|
from product.models import ProductModel, ProductVariant, ProductImageModel
|
||||||
@@ -47,6 +48,8 @@ class Cart(models.Model):
|
|||||||
related_name='carts', null=True, verbose_name='ادرس')
|
related_name='carts', null=True, verbose_name='ادرس')
|
||||||
discount_code = models.ForeignKey(
|
discount_code = models.ForeignKey(
|
||||||
DiscountCode, on_delete=models.PROTECT, null=True, blank=True, verbose_name="کدتخفیف")
|
DiscountCode, on_delete=models.PROTECT, null=True, blank=True, verbose_name="کدتخفیف")
|
||||||
|
special_discount_code = models.ForeignKey(
|
||||||
|
SpecialDiscountCode, on_delete=models.PROTECT, null=True, blank=True, verbose_name="کدتخفیف خاص")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'سبد خرید'
|
verbose_name = 'سبد خرید'
|
||||||
@@ -58,6 +61,7 @@ class Cart(models.Model):
|
|||||||
def clear_cart(self):
|
def clear_cart(self):
|
||||||
self.items.all().delete()
|
self.items.all().delete()
|
||||||
self.discount_code = None
|
self.discount_code = None
|
||||||
|
self.special_discount_code = None
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -71,9 +75,16 @@ class Cart(models.Model):
|
|||||||
def items_discount_amount(self):
|
def items_discount_amount(self):
|
||||||
return int(sum(item.item_discount_amount for item in self.items.all()))
|
return int(sum(item.item_discount_amount for item in self.items.all()))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def special_discount_total(self):
|
||||||
|
"""Sum of all special discounts from cart items when special_discount_code is applied."""
|
||||||
|
if self.special_discount_code:
|
||||||
|
return int(sum(item.item_special_discount_amount for item in self.items.all()))
|
||||||
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_before_tax(self):
|
def total_before_tax(self):
|
||||||
return self.cart_total - (self.discount_code_amount + self.items_discount_amount)
|
return self.cart_total - (self.discount_code_amount + self.items_discount_amount + self.special_discount_total)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tax_amount(self):
|
def tax_amount(self):
|
||||||
@@ -102,6 +113,7 @@ class CartItem(models.Model):
|
|||||||
quantity = models.PositiveIntegerField(default=1)
|
quantity = models.PositiveIntegerField(default=1)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
# special_discount_amount = models.BigIntegerField(default=0, verbose_name='مقدار تخفیف ویژه', help_text='تخفیف محاسبه شده از سود تنوع')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'ایتم سبد خرید'
|
verbose_name = 'ایتم سبد خرید'
|
||||||
@@ -111,6 +123,13 @@ class CartItem(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.quantity} x {self.product_variant.product.name} in cart {self.cart.id}"
|
return f"{self.quantity} x {self.product_variant.product.name} in cart {self.cart.id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def special_discount_amount(self):
|
||||||
|
"""Calculate special discount for this cart item based on variant profit and special_discount_percent."""
|
||||||
|
if hasattr(self.cart, 'special_discount_code') and self.cart.special_discount_code:
|
||||||
|
return self.product_variant.special_discount_amount_per_unit * self.quantity
|
||||||
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def price_before_discount(self):
|
def price_before_discount(self):
|
||||||
return self.quantity * self.product_variant.price_before_discount
|
return self.quantity * self.product_variant.price_before_discount
|
||||||
@@ -119,14 +138,22 @@ class CartItem(models.Model):
|
|||||||
def item_discount_amount(self):
|
def item_discount_amount(self):
|
||||||
return self.product_variant.discount_amount * self.quantity
|
return self.product_variant.discount_amount * self.quantity
|
||||||
|
|
||||||
|
@property
|
||||||
|
def item_special_discount_amount(self):
|
||||||
|
"""Calculate special discount for this cart item based on variant profit and special_discount_percent."""
|
||||||
|
if hasattr(self.cart, 'special_discount_code') and self.cart.special_discount_code:
|
||||||
|
return self.product_variant.special_discount_amount_per_unit * self.quantity
|
||||||
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def price_after_discount(self):
|
def price_after_discount(self):
|
||||||
return self.price_before_discount - self.item_discount_amount
|
return self.price_before_discount - self.item_discount_amount - self.item_special_discount_amount
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discount(self):
|
def discount(self):
|
||||||
return self.product_variant.discount
|
return self.product_variant.discount
|
||||||
|
|
||||||
|
|
||||||
class OrderModel(models.Model):
|
class OrderModel(models.Model):
|
||||||
objects = jmodels.jManager()
|
objects = jmodels.jManager()
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
@@ -155,8 +182,11 @@ class OrderModel(models.Model):
|
|||||||
null=True, blank=True, verbose_name='قیمت نهایی')
|
null=True, blank=True, verbose_name='قیمت نهایی')
|
||||||
cart_total = models.BigIntegerField(
|
cart_total = models.BigIntegerField(
|
||||||
null=True, blank=True, verbose_name='کل سبد خرید')
|
null=True, blank=True, verbose_name='کل سبد خرید')
|
||||||
|
special_discount_total = models.BigIntegerField(
|
||||||
|
null=True, blank=True, verbose_name='مجموع تخفیف ویژه')
|
||||||
|
|
||||||
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, null=True, blank=True)
|
cart = models.ForeignKey(
|
||||||
|
Cart, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
is_stock_rolled_back = models.BooleanField(
|
is_stock_rolled_back = models.BooleanField(
|
||||||
default=False, verbose_name="موجودی برگردانده شده")
|
default=False, verbose_name="موجودی برگردانده شده")
|
||||||
@@ -200,6 +230,7 @@ class OrderModel(models.Model):
|
|||||||
# logger.error(f"Failed to rollback stock for order {self.pk}: {e}")
|
# logger.error(f"Failed to rollback stock for order {self.pk}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class OrderItemModel(models.Model):
|
class OrderItemModel(models.Model):
|
||||||
order = models.ForeignKey(
|
order = models.ForeignKey(
|
||||||
OrderModel, on_delete=models.CASCADE, related_name='items', verbose_name='سفارش')
|
OrderModel, on_delete=models.CASCADE, related_name='items', verbose_name='سفارش')
|
||||||
@@ -208,6 +239,8 @@ class OrderItemModel(models.Model):
|
|||||||
product = models.ForeignKey(
|
product = models.ForeignKey(
|
||||||
ProductVariant, on_delete=models.PROTECT, verbose_name="محصول")
|
ProductVariant, on_delete=models.PROTECT, verbose_name="محصول")
|
||||||
discount_percent = models.SmallIntegerField(verbose_name='درصد تخفیف')
|
discount_percent = models.SmallIntegerField(verbose_name='درصد تخفیف')
|
||||||
|
special_discount_amount = models.BigIntegerField(
|
||||||
|
default=0, verbose_name='مقدار تخفیف ویژه')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'ایتم سبد خرید'
|
verbose_name = 'ایتم سبد خرید'
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
from django.conf import settings
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import OrderItemModel, OrderModel, DiscountCode, Cart, CartItem
|
from .models import OrderItemModel, OrderModel, DiscountCode, Cart, CartItem
|
||||||
from product.serializers import ProductVariantSerialzier, AttributeValueSerialzier, ProductImageSerailizer
|
from product.serializers import ProductVariantSerialzier, AttributeValueSerialzier, ProductImageSerailizer
|
||||||
from account.serializers import UserAddressSerializer
|
from account.serializers import UserAddressSerializer
|
||||||
from product.models import ProductVariant
|
from product.models import ProductVariant
|
||||||
|
|
||||||
|
|
||||||
class ProductVariantSerialzier(serializers.ModelSerializer):
|
class ProductVariantSerialzier(serializers.ModelSerializer):
|
||||||
product_attributes = AttributeValueSerialzier(many=True)
|
product_attributes = AttributeValueSerialzier(many=True)
|
||||||
image = serializers.SerializerMethodField()
|
image = serializers.SerializerMethodField()
|
||||||
@@ -13,9 +15,11 @@ class ProductVariantSerialzier(serializers.ModelSerializer):
|
|||||||
final_price = serializers.SerializerMethodField()
|
final_price = serializers.SerializerMethodField()
|
||||||
category = serializers.SerializerMethodField()
|
category = serializers.SerializerMethodField()
|
||||||
slug = serializers.CharField(source='product.slug')
|
slug = serializers.CharField(source='product.slug')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProductVariant
|
model = ProductVariant
|
||||||
fields = ['id', 'slug', 'title', 'product_attributes', 'in_stock', 'price', 'discount', 'color', 'image', 'discount_amount', 'category', 'final_price']
|
fields = ['id', 'slug', 'title', 'product_attributes', 'in_stock', 'price',
|
||||||
|
'discount', 'color', 'image', 'discount_amount', 'category', 'final_price']
|
||||||
|
|
||||||
def get_discount_amount(self, obj):
|
def get_discount_amount(self, obj):
|
||||||
discount_amount = int(obj.price * (obj.discount / 100))
|
discount_amount = int(obj.price * (obj.discount / 100))
|
||||||
@@ -51,10 +55,13 @@ class OrderItemSerailzier(serializers.ModelSerializer):
|
|||||||
price = serializers.SerializerMethodField()
|
price = serializers.SerializerMethodField()
|
||||||
final_price = serializers.SerializerMethodField()
|
final_price = serializers.SerializerMethodField()
|
||||||
discount = serializers.SerializerMethodField()
|
discount = serializers.SerializerMethodField()
|
||||||
|
special_discount_amount = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CartItem
|
model = CartItem
|
||||||
exclude = ('cart',)
|
exclude = ('cart',)
|
||||||
read_only_fields = ('cart', 'product', 'discount_percent')
|
read_only_fields = ('cart', 'product', 'discount_percent')
|
||||||
|
|
||||||
def get_product(self, obj):
|
def get_product(self, obj):
|
||||||
return ProductVariantSerialzier(instance=obj.product_variant, context={'request': self.context.get('request')}).data
|
return ProductVariantSerialzier(instance=obj.product_variant, context={'request': self.context.get('request')}).data
|
||||||
|
|
||||||
@@ -70,7 +77,20 @@ class OrderItemSerailzier(serializers.ModelSerializer):
|
|||||||
def get_discount(self, obj):
|
def get_discount(self, obj):
|
||||||
return obj.product_variant.discount
|
return obj.product_variant.discount
|
||||||
|
|
||||||
from django.conf import settings
|
def get_special_discount_amount(self, obj):
|
||||||
|
# For cart items
|
||||||
|
print('in here asdfasfd')
|
||||||
|
amount = getattr(obj, 'special_discount_amount', None)
|
||||||
|
print(amount)
|
||||||
|
if amount is None:
|
||||||
|
print('in here')
|
||||||
|
# If it's an order item, check item_special_discount_amount property
|
||||||
|
amount = getattr(obj, 'item_special_discount_amount', 0)
|
||||||
|
if amount is None:
|
||||||
|
amount = 0
|
||||||
|
print('in here ')
|
||||||
|
return f'{int(amount):,.0f} تومان'
|
||||||
|
|
||||||
|
|
||||||
class CartSerializer(serializers.ModelSerializer):
|
class CartSerializer(serializers.ModelSerializer):
|
||||||
items = OrderItemSerailzier(many=True)
|
items = OrderItemSerailzier(many=True)
|
||||||
@@ -79,11 +99,23 @@ class CartSerializer(serializers.ModelSerializer):
|
|||||||
final_price = serializers.SerializerMethodField()
|
final_price = serializers.SerializerMethodField()
|
||||||
discount_code = serializers.SerializerMethodField()
|
discount_code = serializers.SerializerMethodField()
|
||||||
items_discount_amount = serializers.SerializerMethodField()
|
items_discount_amount = serializers.SerializerMethodField()
|
||||||
|
special_discount_total = serializers.SerializerMethodField()
|
||||||
|
special_discount_code = serializers.SerializerMethodField()
|
||||||
address = UserAddressSerializer()
|
address = UserAddressSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cart
|
model = Cart
|
||||||
fields = ['items_discount_amount', 'discount_code', 'items', 'cart_total', 'tax_amount', 'final_price', 'address']
|
fields = ['items_discount_amount', 'discount_code', 'items', 'cart_total', 'tax_amount',
|
||||||
|
'final_price', 'address', 'special_discount_total', 'special_discount_code']
|
||||||
|
|
||||||
|
def get_special_discount_code(self, obj):
|
||||||
|
if obj.special_discount_code:
|
||||||
|
return {
|
||||||
|
'code': f'{obj.special_discount_code.code}',
|
||||||
|
'user': f'{obj.special_discount_code.user.phone}'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def get_discount_code(self, obj):
|
def get_discount_code(self, obj):
|
||||||
if obj.discount_code:
|
if obj.discount_code:
|
||||||
@@ -95,7 +127,6 @@ class CartSerializer(serializers.ModelSerializer):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_tax_amount(self, obj):
|
def get_tax_amount(self, obj):
|
||||||
return f'{obj.tax_amount:,.0f} تومان'
|
return f'{obj.tax_amount:,.0f} تومان'
|
||||||
|
|
||||||
@@ -108,16 +139,25 @@ class CartSerializer(serializers.ModelSerializer):
|
|||||||
def get_final_price(self, obj):
|
def get_final_price(self, obj):
|
||||||
return f'{obj.final_price:,.0f} تومان'
|
return f'{obj.final_price:,.0f} تومان'
|
||||||
|
|
||||||
|
def get_special_discount_total(self, obj):
|
||||||
|
# sum of special discounts on cart items
|
||||||
|
total = obj.special_discount_total if hasattr(
|
||||||
|
obj, 'special_discount_total') else 0
|
||||||
|
return f'{int(total):,.0f} تومان'
|
||||||
|
|
||||||
|
|
||||||
class OrderListSerializer(serializers.ModelSerializer):
|
class OrderListSerializer(serializers.ModelSerializer):
|
||||||
count = serializers.SerializerMethodField()
|
count = serializers.SerializerMethodField()
|
||||||
images = serializers.SerializerMethodField()
|
images = serializers.SerializerMethodField()
|
||||||
verbose_status = serializers.SerializerMethodField()
|
verbose_status = serializers.SerializerMethodField()
|
||||||
order_id = serializers.SerializerMethodField()
|
order_id = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OrderModel
|
model = OrderModel
|
||||||
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status']
|
fields = ['created_at', 'status', "images", "count",
|
||||||
|
"id", 'final_price', 'order_id', 'verbose_status']
|
||||||
read_only_fields = ['count', 'images', 'order_id', 'verbose_status']
|
read_only_fields = ['count', 'images', 'order_id', 'verbose_status']
|
||||||
|
|
||||||
def get_verbose_status(self, obj):
|
def get_verbose_status(self, obj):
|
||||||
return obj.get_status_display()
|
return obj.get_status_display()
|
||||||
|
|
||||||
@@ -126,6 +166,7 @@ class OrderListSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def get_order_id(self, obj):
|
def get_order_id(self, obj):
|
||||||
return obj.pk + 1000
|
return obj.pk + 1000
|
||||||
|
|
||||||
def get_images(self, obj):
|
def get_images(self, obj):
|
||||||
image_list = [
|
image_list = [
|
||||||
self.context.get('request').build_absolute_uri(image.image.url)
|
self.context.get('request').build_absolute_uri(image.image.url)
|
||||||
@@ -143,9 +184,11 @@ class OrderGetSerializer(serializers.ModelSerializer):
|
|||||||
items = OrderItemSerailzier(many=True)
|
items = OrderItemSerailzier(many=True)
|
||||||
address = UserAddressSerializer()
|
address = UserAddressSerializer()
|
||||||
discount_code = DiscountCodeSerializer()
|
discount_code = DiscountCodeSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OrderModel
|
model = OrderModel
|
||||||
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status', 'address', 'items', 'tax' , 'cart_total', 'discount_code', 'discount_amount']
|
fields = ['created_at', 'status', "images", "count", "id", 'final_price', 'order_id', 'verbose_status',
|
||||||
|
'address', 'items', 'tax', 'cart_total', 'discount_code', 'discount_amount', 'special_discount_total']
|
||||||
|
|
||||||
def get_verbose_status(self, obj):
|
def get_verbose_status(self, obj):
|
||||||
return obj.get_status_display()
|
return obj.get_status_display()
|
||||||
@@ -160,5 +203,6 @@ class OrderGetSerializer(serializers.ModelSerializer):
|
|||||||
for item in obj.items.all()[:3]
|
for item in obj.items.all()[:3]
|
||||||
]
|
]
|
||||||
return filter(lambda x: x is not None, image_list)
|
return filter(lambda x: x is not None, image_list)
|
||||||
|
|
||||||
def get_order_id(self, obj):
|
def get_order_id(self, obj):
|
||||||
return obj.id + 1000
|
return obj.id + 1000
|
||||||
@@ -7,7 +7,7 @@ from azbankgateways import (
|
|||||||
from .models import OrderModel
|
from .models import OrderModel
|
||||||
from account.models import PushSubscription
|
from account.models import PushSubscription
|
||||||
import ghasedak_sms
|
import ghasedak_sms
|
||||||
|
from product.models import ProductImageModel
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
@@ -24,6 +24,8 @@ def udpate_bank_status():
|
|||||||
bank_record = bank_models.Bank.objects.get(tracking_code=item.tracking_code)
|
bank_record = bank_models.Bank.objects.get(tracking_code=item.tracking_code)
|
||||||
if bank_record.is_success:
|
if bank_record.is_success:
|
||||||
bank_record.order.cart.clear_cart()
|
bank_record.order.cart.clear_cart()
|
||||||
|
bank_record.order.is_paid = True
|
||||||
|
bank_record.order.save()
|
||||||
logging.debug("This record is verify now.", extra={"pk": bank_record.pk})
|
logging.debug("This record is verify now.", extra={"pk": bank_record.pk})
|
||||||
else:
|
else:
|
||||||
order = bank_record.order
|
order = bank_record.order
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ urlpatterns = [
|
|||||||
path('cart', CartView.as_view()),
|
path('cart', CartView.as_view()),
|
||||||
path('cart/set-address', SetAddressForCartView.as_view()),
|
path('cart/set-address', SetAddressForCartView.as_view()),
|
||||||
path('cart/discount', ApplyDiscountView.as_view()),
|
path('cart/discount', ApplyDiscountView.as_view()),
|
||||||
|
path('cart/special-discount', ApplySpecialDiscountView.as_view()),
|
||||||
path('cart/all', CartItemClear.as_view()),
|
path('cart/all', CartItemClear.as_view()),
|
||||||
path('cart/item/<int:pk>', CartItemViews.as_view(), name='change-item-cart'),
|
path('cart/item/<int:pk>', CartItemViews.as_view(), name='change-item-cart'),
|
||||||
path('cart/payment', PaymentView.as_view(), name='payment'),
|
path('cart/payment', PaymentView.as_view(), name='payment'),
|
||||||
path('transaction/<int:tracking_code>', CallbackView.as_view(), name='callback-gateway'),
|
path('transaction/<int:tracking_code>',
|
||||||
|
CallbackView.as_view(), name='callback-gateway'),
|
||||||
path('<int:pk>', OrderGetView.as_view(), name='order-get'),
|
path('<int:pk>', OrderGetView.as_view(), name='order-get'),
|
||||||
]
|
]
|
||||||
|
|||||||
+99
-41
@@ -1,3 +1,12 @@
|
|||||||
|
from django.db import transaction
|
||||||
|
from order.models import Cart
|
||||||
|
from django.utils.translation import override
|
||||||
|
from .permissons import PaymentCallBackPermissions
|
||||||
|
from azbankgateways.models.enum import PaymentStatus
|
||||||
|
from azbankgateways.models import Bank
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from django.utils import timezone
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from rest_framework.views import APIView, Response
|
from rest_framework.views import APIView, Response
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
@@ -30,11 +39,13 @@ from account.models import UserAddressModel
|
|||||||
class ApplyDiscountView(APIView):
|
class ApplyDiscountView(APIView):
|
||||||
serializer_class = DiscountCodeSerializer
|
serializer_class = DiscountCodeSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
cart_order, created = Cart.objects.get_or_create(
|
cart_order, created = Cart.objects.get_or_create(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
)
|
)
|
||||||
discount_code = get_object_or_404(DiscountCode, code=request.data.get('code'))
|
discount_code = get_object_or_404(
|
||||||
|
DiscountCode, code=request.data.get('code'))
|
||||||
|
|
||||||
if not discount_code.is_valid():
|
if not discount_code.is_valid():
|
||||||
return Response({'detail': discount_code.not_valid_reason()}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': discount_code.not_valid_reason()}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@@ -50,9 +61,45 @@ class ApplyDiscountView(APIView):
|
|||||||
cart_order.save()
|
cart_order.save()
|
||||||
return Response({'detail': 'کد تخفیف با موفقیت حذف شد'}, status=status.HTTP_204_NO_CONTENT)
|
return Response({'detail': 'کد تخفیف با موفقیت حذف شد'}, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
post=extend_schema(tags=["cart special discount code"]),
|
||||||
|
delete=extend_schema(tags=["cart special discount code"]),
|
||||||
|
)
|
||||||
|
class ApplySpecialDiscountView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
from account.models import SpecialDiscountCode
|
||||||
|
|
||||||
|
cart, created = Cart.objects.get_or_create(user=request.user)
|
||||||
|
code = request.data.get('code')
|
||||||
|
|
||||||
|
if not code:
|
||||||
|
return Response({'detail': 'کد تخفیف ویژه را وارد کنید'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
try:
|
||||||
|
special_discount_code = SpecialDiscountCode.objects.get(code=code)
|
||||||
|
except SpecialDiscountCode.DoesNotExist:
|
||||||
|
return Response({'detail': 'کد تخفیف ویژه معتبر نیست'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
# Apply the special discount code to cart
|
||||||
|
cart.special_discount_code = special_discount_code
|
||||||
|
cart.save()
|
||||||
|
|
||||||
|
return Response({'detail': 'کد تخفیف ویژه با موفقیت اعمال شد'}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def delete(self, request):
|
||||||
|
cart, created = Cart.objects.get_or_create(user=request.user)
|
||||||
|
cart.special_discount_code = None
|
||||||
|
cart.save()
|
||||||
|
return Response({'detail': 'کد تخفیف ویژه با موفقیت حذف شد'}, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class CartItemClear(APIView):
|
class CartItemClear(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = OrderItemSerailzier
|
serializer_class = OrderItemSerailzier
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["order cart"]
|
tags=["order cart"]
|
||||||
)
|
)
|
||||||
@@ -63,6 +110,7 @@ class CartItemClear(APIView):
|
|||||||
cart_order.items.all().delete()
|
cart_order.items.all().delete()
|
||||||
return Response({'detail': f'سبد خرید با موفقیت خالی شد'}, status=status.HTTP_204_NO_CONTENT)
|
return Response({'detail': f'سبد خرید با موفقیت خالی شد'}, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(
|
||||||
post=extend_schema(tags=["order cart"]),
|
post=extend_schema(tags=["order cart"]),
|
||||||
delete=extend_schema(tags=["order cart"]),
|
delete=extend_schema(tags=["order cart"]),
|
||||||
@@ -70,6 +118,7 @@ class CartItemClear(APIView):
|
|||||||
class CartItemViews(APIView):
|
class CartItemViews(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = OrderItemSerailzier
|
serializer_class = OrderItemSerailzier
|
||||||
|
|
||||||
def post(self, request, pk):
|
def post(self, request, pk):
|
||||||
product_variant = get_object_or_404(ProductVariant, pk=pk)
|
product_variant = get_object_or_404(ProductVariant, pk=pk)
|
||||||
response = 'محصول با موفقیت به سبد خرید اضافه شد'
|
response = 'محصول با موفقیت به سبد خرید اضافه شد'
|
||||||
@@ -80,7 +129,8 @@ class CartItemViews(APIView):
|
|||||||
response = 'تعداد درخواستی بیشتر از موجودی محصول میباشد'
|
response = 'تعداد درخواستی بیشتر از موجودی محصول میباشد'
|
||||||
|
|
||||||
cart_order, created = Cart.objects.get_or_create(user=request.user)
|
cart_order, created = Cart.objects.get_or_create(user=request.user)
|
||||||
order_item, created = CartItem.objects.get_or_create(cart=cart_order, product_variant=product_variant, defaults={'quantity': quantity})
|
order_item, created = CartItem.objects.get_or_create(
|
||||||
|
cart=cart_order, product_variant=product_variant, defaults={'quantity': quantity})
|
||||||
if not created and order_item.quantity:
|
if not created and order_item.quantity:
|
||||||
order_item.quantity = quantity
|
order_item.quantity = quantity
|
||||||
order_item.save()
|
order_item.save()
|
||||||
@@ -88,7 +138,6 @@ class CartItemViews(APIView):
|
|||||||
order_item.delete()
|
order_item.delete()
|
||||||
return Response({'detail': response, 'count': quantity}, status=status.HTTP_202_ACCEPTED)
|
return Response({'detail': response, 'count': quantity}, status=status.HTTP_202_ACCEPTED)
|
||||||
|
|
||||||
|
|
||||||
def delete(self, request, pk):
|
def delete(self, request, pk):
|
||||||
order_item = get_object_or_404(OrderItemModel, pk=pk)
|
order_item = get_object_or_404(OrderItemModel, pk=pk)
|
||||||
permission = CanDeleteCartItemPermissions()
|
permission = CanDeleteCartItemPermissions()
|
||||||
@@ -102,17 +151,19 @@ class CartItemViews(APIView):
|
|||||||
status=status.HTTP_204_NO_CONTENT,
|
status=status.HTTP_204_NO_CONTENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
from order.models import Cart
|
|
||||||
class CartView(APIView):
|
class CartView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = CartSerializer
|
serializer_class = CartSerializer
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["order cart"]
|
tags=["order cart"]
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
user = request.user
|
user = request.user
|
||||||
cart_instance, created = Cart.objects.get_or_create(user=user)
|
cart_instance, created = Cart.objects.get_or_create(user=user)
|
||||||
cart_ser = self.serializer_class(instance=cart_instance, context={'request': request})
|
cart_ser = self.serializer_class(
|
||||||
|
instance=cart_instance, context={'request': request})
|
||||||
return Response(cart_ser.data, status=status.HTTP_200_OK)
|
return Response(cart_ser.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
@@ -120,6 +171,7 @@ class OrderlistView(APIView):
|
|||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = OrderListSerializer
|
serializer_class = OrderListSerializer
|
||||||
pagination_class = StructurePagination
|
pagination_class = StructurePagination
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
parameters=[
|
parameters=[
|
||||||
OpenApiParameter(
|
OpenApiParameter(
|
||||||
@@ -168,45 +220,42 @@ class OrderlistView(APIView):
|
|||||||
orders = orders.order_by(sort)
|
orders = orders.order_by(sort)
|
||||||
paginator = self.pagination_class()
|
paginator = self.pagination_class()
|
||||||
paginated_orders = paginator.paginate_queryset(orders, request)
|
paginated_orders = paginator.paginate_queryset(orders, request)
|
||||||
orders_ser = self.serializer_class(instance=paginated_orders, many=True, context={'request': request})
|
orders_ser = self.serializer_class(
|
||||||
|
instance=paginated_orders, many=True, context={'request': request})
|
||||||
return paginator.get_paginated_response(orders_ser.data)
|
return paginator.get_paginated_response(orders_ser.data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class OrderGetView(APIView):
|
class OrderGetView(APIView):
|
||||||
permission_classes = [IsAuthenticated, GetOrderPermission]
|
permission_classes = [IsAuthenticated, GetOrderPermission]
|
||||||
serializer_class = OrderGetSerializer
|
serializer_class = OrderGetSerializer
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
order_object = get_object_or_404(OrderModel, pk=pk)
|
order_object = get_object_or_404(OrderModel, pk=pk)
|
||||||
|
|
||||||
|
|
||||||
permission = GetOrderPermission()
|
permission = GetOrderPermission()
|
||||||
if not permission.has_object_permission(request, self, order_object):
|
if not permission.has_object_permission(request, self, order_object):
|
||||||
return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
order_ser = self.serializer_class(
|
||||||
order_ser = self.serializer_class(order_object, context={'request': request})
|
order_object, context={'request': request})
|
||||||
return Response(order_ser.data, status=status.HTTP_200_OK)
|
return Response(order_ser.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
class BankTypeSerializer(serializers.Serializer):
|
class BankTypeSerializer(serializers.Serializer):
|
||||||
gateway_type = serializers.ChoiceField(choices=['ZIBAL', 'BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'BAHAMTA', 'MELLAT', 'PAYV1'])
|
gateway_type = serializers.ChoiceField(
|
||||||
|
choices=['ZIBAL', 'BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'BAHAMTA', 'MELLAT', 'PAYV1'])
|
||||||
|
|
||||||
|
|
||||||
from django.db import transaction
|
|
||||||
from django.utils import timezone
|
|
||||||
class PaymentView(APIView):
|
class PaymentView(APIView):
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
serializer_class = BankTypeSerializer
|
serializer_class = BankTypeSerializer
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
description="choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']",
|
description="choices=['BMI', 'SEP', 'ZARINPAL', 'IDPAY', 'ZIBAL', 'BAHAMTA', 'MELLAT', 'PAYV1']",
|
||||||
tags=['order payment']
|
tags=['order payment']
|
||||||
)
|
)
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
|
||||||
|
|
||||||
# Get user's cart
|
# Get user's cart
|
||||||
cart = get_object_or_404(Cart, user=request.user)
|
cart = get_object_or_404(Cart, user=request.user)
|
||||||
|
|
||||||
@@ -264,7 +313,8 @@ class PaymentView(APIView):
|
|||||||
|
|
||||||
if insufficient_stock_items:
|
if insufficient_stock_items:
|
||||||
# Create error message with product names
|
# Create error message with product names
|
||||||
product_names = [item['product'] for item in insufficient_stock_items]
|
product_names = [item['product']
|
||||||
|
for item in insufficient_stock_items]
|
||||||
product_list = '، '.join(product_names)
|
product_list = '، '.join(product_names)
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
@@ -274,10 +324,14 @@ class PaymentView(APIView):
|
|||||||
'adjusted_items': adjusted_items
|
'adjusted_items': adjusted_items
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
# Compute special discounts for cart items if special_discount_code is applied
|
||||||
|
special_total = 0
|
||||||
|
for cart_item in cart.items.select_related('product_variant').all():
|
||||||
|
if cart.special_discount_code:
|
||||||
|
special_total += cart_item.special_discount_amount
|
||||||
|
|
||||||
# Create order
|
# Create order
|
||||||
order = OrderModel.objects.create(
|
order = OrderModel.objects.create(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
@@ -285,6 +339,7 @@ class PaymentView(APIView):
|
|||||||
created_at=timezone.now().date(),
|
created_at=timezone.now().date(),
|
||||||
discount_code=cart.discount_code,
|
discount_code=cart.discount_code,
|
||||||
discount_amount=cart.discount_code_amount,
|
discount_amount=cart.discount_code_amount,
|
||||||
|
special_discount_total=special_total,
|
||||||
tax=cart.tax_amount,
|
tax=cart.tax_amount,
|
||||||
final_price=cart.final_price,
|
final_price=cart.final_price,
|
||||||
cart_total=cart.cart_total,
|
cart_total=cart.cart_total,
|
||||||
@@ -299,7 +354,8 @@ class PaymentView(APIView):
|
|||||||
quantity=cart_item.quantity,
|
quantity=cart_item.quantity,
|
||||||
price=cart_item.product_variant.price,
|
price=cart_item.product_variant.price,
|
||||||
product=cart_item.product_variant,
|
product=cart_item.product_variant,
|
||||||
discount_percent=cart_item.discount
|
discount_percent=cart_item.discount,
|
||||||
|
special_discount_amount=cart_item.special_discount_amount
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reduce product variant quantity
|
# Reduce product variant quantity
|
||||||
@@ -317,8 +373,10 @@ class PaymentView(APIView):
|
|||||||
|
|
||||||
bank = factory.create(bank_models.BankType.ZIBAL)
|
bank = factory.create(bank_models.BankType.ZIBAL)
|
||||||
bank.set_request(request)
|
bank.set_request(request)
|
||||||
bank.set_amount(cart.final_price) # Use final_price instead of hardcoded amount
|
# Use final_price instead of hardcoded amount
|
||||||
bank.set_client_callback_url('http://localhost:3000/transaction')
|
bank.set_amount(cart.final_price)
|
||||||
|
bank.set_client_callback_url(
|
||||||
|
'http://localhost:3000/transaction')
|
||||||
bank.set_mobile_number(user_mobile_number)
|
bank.set_mobile_number(user_mobile_number)
|
||||||
|
|
||||||
bank_record = bank.ready()
|
bank_record = bank.ready()
|
||||||
@@ -326,7 +384,6 @@ class PaymentView(APIView):
|
|||||||
bank_record.order = order
|
bank_record.order = order
|
||||||
bank_record.save()
|
bank_record.save()
|
||||||
|
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
'url': bank.get_gateway()['url'],
|
'url': bank.get_gateway()['url'],
|
||||||
})
|
})
|
||||||
@@ -344,26 +401,17 @@ class PaymentView(APIView):
|
|||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from azbankgateways import bankfactories, models as bank_models
|
|
||||||
from rest_framework import serializers
|
|
||||||
from azbankgateways.models import Bank
|
|
||||||
from azbankgateways.models.enum import PaymentStatus
|
|
||||||
from .permissons import PaymentCallBackPermissions
|
|
||||||
from django.utils.translation import override
|
|
||||||
|
|
||||||
class BankCallbackSerializer(serializers.ModelSerializer):
|
class BankCallbackSerializer(serializers.ModelSerializer):
|
||||||
status_detail = serializers.SerializerMethodField()
|
status_detail = serializers.SerializerMethodField()
|
||||||
bank_type = serializers.SerializerMethodField()
|
bank_type = serializers.SerializerMethodField()
|
||||||
amount = serializers.SerializerMethodField()
|
amount = serializers.SerializerMethodField()
|
||||||
status = serializers.SerializerMethodField()
|
status = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Bank
|
model = Bank
|
||||||
fields = ['status', 'bank_type', 'tracking_code', 'amount', 'created_at', 'response_result', 'reference_number', 'status_detail']
|
fields = ['status', 'bank_type', 'tracking_code', 'amount',
|
||||||
|
'created_at', 'response_result', 'reference_number', 'status_detail']
|
||||||
|
|
||||||
def get_status_detail(self, obj):
|
def get_status_detail(self, obj):
|
||||||
with override('fa'):
|
with override('fa'):
|
||||||
return obj.get_status_display()
|
return obj.get_status_display()
|
||||||
@@ -371,8 +419,10 @@ class BankCallbackSerializer(serializers.ModelSerializer):
|
|||||||
def get_bank_type(self, obj):
|
def get_bank_type(self, obj):
|
||||||
with override('fa'):
|
with override('fa'):
|
||||||
return obj.get_bank_type_display()
|
return obj.get_bank_type_display()
|
||||||
|
|
||||||
def get_amount(self, obj):
|
def get_amount(self, obj):
|
||||||
return f'{int(obj.amount):,.0f} تومان'
|
return f'{int(obj.amount):,.0f} تومان'
|
||||||
|
|
||||||
def get_status(self, obj):
|
def get_status(self, obj):
|
||||||
if obj.status in {
|
if obj.status in {
|
||||||
PaymentStatus.WAITING,
|
PaymentStatus.WAITING,
|
||||||
@@ -395,24 +445,30 @@ class BankCallbackSerializer(serializers.ModelSerializer):
|
|||||||
class CallbackView(APIView):
|
class CallbackView(APIView):
|
||||||
serializer_class = BankCallbackSerializer
|
serializer_class = BankCallbackSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
def get(self, request, tracking_code):
|
def get(self, request, tracking_code):
|
||||||
if not tracking_code:
|
if not tracking_code:
|
||||||
return Response({'detail': 'تریسکد خالی است.'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': 'تریسکد خالی است.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bank_record = bank_models.Bank.objects.get(tracking_code=tracking_code)
|
bank_record = bank_models.Bank.objects.get(
|
||||||
|
tracking_code=tracking_code)
|
||||||
|
|
||||||
permission = PaymentCallBackPermissions()
|
permission = PaymentCallBackPermissions()
|
||||||
if not permission.has_object_permission(request, self, bank_record):
|
if not permission.has_object_permission(request, self, bank_record):
|
||||||
return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN)
|
return Response({"detail": permission.message}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
bank_record_ser = self.serializer_class(instance=bank_record, context={'request': request})
|
bank_record_ser = self.serializer_class(
|
||||||
|
instance=bank_record, context={'request': request})
|
||||||
|
|
||||||
except bank_models.Bank.DoesNotExist:
|
except bank_models.Bank.DoesNotExist:
|
||||||
return Response({'detail': 'کد تریسکد معتبر نمیباشد.'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'detail': 'کد تریسکد معتبر نمیباشد.'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if bank_record.is_success:
|
if bank_record.is_success:
|
||||||
bank_record.order.cart.clear_cart()
|
order = bank_record.order
|
||||||
|
order.cart.clear_cart()
|
||||||
|
order.is_paid = True
|
||||||
|
order.save()
|
||||||
return Response({"detail": "پرداخت با موفقیت انجام شد.", "bank_result": bank_record_ser.data}, status=status.HTTP_200_OK)
|
return Response({"detail": "پرداخت با موفقیت انجام شد.", "bank_result": bank_record_ser.data}, status=status.HTTP_200_OK)
|
||||||
else:
|
else:
|
||||||
order = bank_record.order
|
order = bank_record.order
|
||||||
@@ -428,10 +484,12 @@ class CallbackView(APIView):
|
|||||||
|
|
||||||
class SetAddressSerilizer(serializers.Serializer):
|
class SetAddressSerilizer(serializers.Serializer):
|
||||||
address_id = serializers.IntegerField()
|
address_id = serializers.IntegerField()
|
||||||
from order.models import Cart
|
|
||||||
|
|
||||||
class SetAddressForCartView(APIView):
|
class SetAddressForCartView(APIView):
|
||||||
serializer_class = SetAddressSerilizer
|
serializer_class = SetAddressSerilizer
|
||||||
permission_classes = [IsAuthenticated, SetAddressPermissions]
|
permission_classes = [IsAuthenticated, SetAddressPermissions]
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["order cart"]
|
tags=["order cart"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ class ProductVariantInLine(StackedInline):
|
|||||||
readonly_fields = ['price']
|
readonly_fields = ['price']
|
||||||
# inlines = [DetailModelInLine]
|
# inlines = [DetailModelInLine]
|
||||||
autocomplete_fields = ['product_attributes', 'in_pack_items', 'images', 'details']
|
autocomplete_fields = ['product_attributes', 'in_pack_items', 'images', 'details']
|
||||||
fields = ['images', 'video','input_price', 'min_price', 'currency', 'price', 'discount','in_stock', 'color', 'product_attributes', 'in_pack_items', 'details', 'sell', 'slider_category']
|
fields = ['images', 'video','input_price', 'min_price', 'currency', 'price', 'discount','in_stock', 'color', 'product_attributes', 'in_pack_items', 'details', 'sell', 'slider_category', 'profit', 'special_discount_percent']
|
||||||
# search_fields = ['']
|
# search_fields = ['']
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-11-14 14:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('product', '0056_productmodel_image'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='productvariant',
|
||||||
|
name='profit',
|
||||||
|
field=models.BigIntegerField(default=0, help_text='مقدار سود به ازای هر واحد به تومان', verbose_name='سود (تومان)'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='productvariant',
|
||||||
|
name='special_discount_percent',
|
||||||
|
field=models.SmallIntegerField(default=0, help_text='درصدی که از سود برای محاسبه تخفیف ویژه استفاده می\u200cشود', verbose_name='درصد تخفیف ویژه'),
|
||||||
|
),
|
||||||
|
]
|
||||||
+155
-71
@@ -6,14 +6,23 @@ import requests
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from home.models import ShowCaseSlider
|
from home.models import ShowCaseSlider
|
||||||
|
|
||||||
|
|
||||||
class MainCategoryModel(models.Model):
|
class MainCategoryModel(models.Model):
|
||||||
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
|
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
|
||||||
slug = models.SlugField(max_length=50, unique=True, help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید")
|
slug = models.SlugField(max_length=50, unique=True,
|
||||||
icon = models.ImageField(upload_to='category_model/',verbose_name='آیکون', blank=True, null=True)
|
help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید")
|
||||||
image = models.ImageField(upload_to='category_model/',verbose_name='عکس', blank=True, null=True)
|
icon = models.ImageField(upload_to='category_model/',
|
||||||
meta_title = models.CharField(max_length=60, verbose_name="عنوان متا", help_text="عنوان متا برای SEO", blank=True, null=True)
|
verbose_name='آیکون', blank=True, null=True)
|
||||||
meta_description = models.TextField(max_length=160, verbose_name="توضیحات متا", help_text="توضیحات متا برای SEO", blank=True, null=True)
|
image = models.ImageField(
|
||||||
video = models.FileField(upload_to='category_videos/', blank=True, null=True, verbose_name='ویدیو')
|
upload_to='category_model/', verbose_name='عکس', blank=True, null=True)
|
||||||
|
meta_title = models.CharField(
|
||||||
|
max_length=60, verbose_name="عنوان متا", help_text="عنوان متا برای SEO", blank=True, null=True)
|
||||||
|
meta_description = models.TextField(
|
||||||
|
max_length=160, verbose_name="توضیحات متا", help_text="توضیحات متا برای SEO", blank=True, null=True)
|
||||||
|
video = models.FileField(upload_to='category_videos/',
|
||||||
|
blank=True, null=True, verbose_name='ویدیو')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "دستهبندی اصلی"
|
verbose_name = "دستهبندی اصلی"
|
||||||
verbose_name_plural = "دستهبندیهااصلی"
|
verbose_name_plural = "دستهبندیهااصلی"
|
||||||
@@ -32,12 +41,18 @@ class MainCategoryModel(models.Model):
|
|||||||
|
|
||||||
class SubCategoryModel(models.Model):
|
class SubCategoryModel(models.Model):
|
||||||
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
|
name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
|
||||||
slug = models.SlugField(max_length=50, unique=True, help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید")
|
slug = models.SlugField(max_length=50, unique=True,
|
||||||
image = models.ImageField(upload_to='category_model/',verbose_name='عکس', blank=True, null=True)
|
help_text="اسم دسته را برای مسیر به انگلیسی و بدون فاصله وارد کنید")
|
||||||
icon = models.ImageField(upload_to='category_model/',verbose_name='آیکون', blank=True, null=True)
|
image = models.ImageField(
|
||||||
meta_title = models.CharField(max_length=60, verbose_name="عنوان متا", help_text="عنوان متا برای SEO", blank=True, null=True)
|
upload_to='category_model/', verbose_name='عکس', blank=True, null=True)
|
||||||
meta_description = models.TextField(max_length=160, verbose_name="توضیحات متا", help_text="توضیحات متا برای SEO", blank=True, null=True)
|
icon = models.ImageField(upload_to='category_model/',
|
||||||
parent = models.ForeignKey(MainCategoryModel, on_delete=models.CASCADE, related_name='subcategorys', verbose_name='دستهبندی والد')
|
verbose_name='آیکون', blank=True, null=True)
|
||||||
|
meta_title = models.CharField(
|
||||||
|
max_length=60, verbose_name="عنوان متا", help_text="عنوان متا برای SEO", blank=True, null=True)
|
||||||
|
meta_description = models.TextField(
|
||||||
|
max_length=160, verbose_name="توضیحات متا", help_text="توضیحات متا برای SEO", blank=True, null=True)
|
||||||
|
parent = models.ForeignKey(MainCategoryModel, on_delete=models.CASCADE,
|
||||||
|
related_name='subcategorys', verbose_name='دستهبندی والد')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "زیر دستهبندی"
|
verbose_name = "زیر دستهبندی"
|
||||||
@@ -49,19 +64,25 @@ class SubCategoryModel(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
self.slug = slugify(self.name, allow_unicode=True)
|
self.slug = slugify(self.name, allow_unicode=True)
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DollorModel(models.Model):
|
class DollorModel(models.Model):
|
||||||
price = models.FloatField(null=True, blank=True, verbose_name='قیمت دلار')
|
price = models.FloatField(null=True, blank=True, verbose_name='قیمت دلار')
|
||||||
defualt_price = models.FloatField(null=True, blank=True, default=80000.0, verbose_name='قیمت دستی')
|
defualt_price = models.FloatField(
|
||||||
|
null=True, blank=True, default=80000.0, verbose_name='قیمت دستی')
|
||||||
# these fields will avoid dublicate of this model
|
# these fields will avoid dublicate of this model
|
||||||
unique = (('unique', 'unique'),)
|
unique = (('unique', 'unique'),)
|
||||||
unique_filed = models.CharField(max_length=20, choices=unique, unique=True, default='unique')
|
unique_filed = models.CharField(
|
||||||
|
max_length=20, choices=unique, unique=True, default='unique')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.price)
|
return str(self.price)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.price:
|
if not self.price:
|
||||||
self.update_price()
|
self.update_price()
|
||||||
@@ -92,13 +113,15 @@ class DollorModel(models.Model):
|
|||||||
verbose_name = 'مدل دلار'
|
verbose_name = 'مدل دلار'
|
||||||
verbose_name_plural = 'مدل دلار'
|
verbose_name_plural = 'مدل دلار'
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['unique_filed'], name='dollor_unique_field_idx'),
|
models.Index(fields=['unique_filed'],
|
||||||
|
name='dollor_unique_field_idx'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class InPackItems(models.Model):
|
class InPackItems(models.Model):
|
||||||
item_title = models.CharField(max_length=50)
|
item_title = models.CharField(max_length=50)
|
||||||
cover = models.ImageField(upload_to='product_items/', verbose_name='کاور ایتم')
|
cover = models.ImageField(
|
||||||
|
upload_to='product_items/', verbose_name='کاور ایتم')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'ایتم داخل پک'
|
verbose_name = 'ایتم داخل پک'
|
||||||
@@ -117,15 +140,24 @@ class ProductModel(models.Model):
|
|||||||
view = models.IntegerField(default=0, verbose_name='بازدید')
|
view = models.IntegerField(default=0, verbose_name='بازدید')
|
||||||
slug = models.SlugField(max_length=255, unique=True, blank=True, null=True, allow_unicode=True,
|
slug = models.SlugField(max_length=255, unique=True, blank=True, null=True, allow_unicode=True,
|
||||||
verbose_name='نام یکتا', help_text="این فیلد را خالی بگذارید")
|
verbose_name='نام یکتا', help_text="این فیلد را خالی بگذارید")
|
||||||
meta_description = models.CharField(max_length=300, blank=True, null=True, help_text='این فیلد را حتما پر کنید', verbose_name='متا دیسکریپشن')
|
meta_description = models.CharField(
|
||||||
meta_keywords = models.CharField(max_length=300, blank=True, null=True, help_text='این فیلد را حتما پر کنید', verbose_name='متا کیورد')
|
max_length=300, blank=True, null=True, help_text='این فیلد را حتما پر کنید', verbose_name='متا دیسکریپشن')
|
||||||
meta_rating = models.FloatField(default=5, help_text='امتیاز محصول', verbose_name='متا ریتینگ')
|
meta_keywords = models.CharField(max_length=300, blank=True, null=True,
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='زمان ثبت محصول')
|
help_text='این فیلد را حتما پر کنید', verbose_name='متا کیورد')
|
||||||
category = models.ForeignKey(SubCategoryModel, null=True, on_delete=models.SET_NULL, related_name='products', verbose_name='دسته بندی محصول')
|
meta_rating = models.FloatField(
|
||||||
related_products = models.ManyToManyField('self', blank=True, verbose_name='محصولات مرتبط')
|
default=5, help_text='امتیاز محصول', verbose_name='متا ریتینگ')
|
||||||
shop = models.ForeignKey('account.ShopModel', on_delete=models.CASCADE, related_name='products', verbose_name='فروشگاه', blank=True, null=True)
|
created_at = models.DateTimeField(
|
||||||
show_in_bot = models.BooleanField(default=False, verbose_name='نمایش در ربات')
|
auto_now_add=True, verbose_name='زمان ثبت محصول')
|
||||||
bot_banner = models.TextField(null=True, blank=True, verbose_name='بنر ربات')
|
category = models.ForeignKey(SubCategoryModel, null=True, on_delete=models.SET_NULL,
|
||||||
|
related_name='products', verbose_name='دسته بندی محصول')
|
||||||
|
related_products = models.ManyToManyField(
|
||||||
|
'self', blank=True, verbose_name='محصولات مرتبط')
|
||||||
|
shop = models.ForeignKey('account.ShopModel', on_delete=models.CASCADE,
|
||||||
|
related_name='products', verbose_name='فروشگاه', blank=True, null=True)
|
||||||
|
show_in_bot = models.BooleanField(
|
||||||
|
default=False, verbose_name='نمایش در ربات')
|
||||||
|
bot_banner = models.TextField(
|
||||||
|
null=True, blank=True, verbose_name='بنر ربات')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -145,14 +177,13 @@ class ProductModel(models.Model):
|
|||||||
models.Index(fields=['name'], name='product_name_idx'),
|
models.Index(fields=['name'], name='product_name_idx'),
|
||||||
models.Index(fields=['created_at'], name='product_created_at_idx'),
|
models.Index(fields=['created_at'], name='product_created_at_idx'),
|
||||||
models.Index(fields=['show'], name='product_show_idx'),
|
models.Index(fields=['show'], name='product_show_idx'),
|
||||||
models.Index(fields=['category', 'created_at'], name='product_category_created_idx'),
|
models.Index(fields=['category', 'created_at'],
|
||||||
models.Index(fields=['category', 'name'], name='product_category_name_idx'),
|
name='product_category_created_idx'),
|
||||||
|
models.Index(fields=['category', 'name'],
|
||||||
|
name='product_category_name_idx'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ProductDetailCategory(models.Model):
|
class ProductDetailCategory(models.Model):
|
||||||
title = models.CharField(max_length=40, verbose_name='عنوان')
|
title = models.CharField(max_length=40, verbose_name='عنوان')
|
||||||
|
|
||||||
@@ -167,35 +198,40 @@ class ProductDetailCategory(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CommentModel(models.Model):
|
class CommentModel(models.Model):
|
||||||
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE, related_name='comments', verbose_name='محصول')
|
product = models.ForeignKey(
|
||||||
|
ProductModel, on_delete=models.CASCADE, related_name='comments', verbose_name='محصول')
|
||||||
title = models.CharField(max_length=50)
|
title = models.CharField(max_length=50)
|
||||||
content = models.TextField(verbose_name='محتوای نظر')
|
content = models.TextField(verbose_name='محتوای نظر')
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='کاربر')
|
user = models.ForeignKey(
|
||||||
timestamp = models.DateTimeField(auto_now_add=True, verbose_name='زمان ثبت کامنت')
|
User, on_delete=models.CASCADE, verbose_name='کاربر')
|
||||||
|
timestamp = models.DateTimeField(
|
||||||
|
auto_now_add=True, verbose_name='زمان ثبت کامنت')
|
||||||
status_types = (
|
status_types = (
|
||||||
('reviewed_and_confirmed', 'بررسی و تایید شده'),
|
('reviewed_and_confirmed', 'بررسی و تایید شده'),
|
||||||
('reviewed_and_rejected', 'بررسی شده و رد شده'),
|
('reviewed_and_rejected', 'بررسی شده و رد شده'),
|
||||||
('not_reviwed', 'بررسی نشده'),
|
('not_reviwed', 'بررسی نشده'),
|
||||||
)
|
)
|
||||||
review_status = models.CharField(default='not_reviwed', verbose_name='نشان دادن کامنت', max_length=30, choices=status_types)
|
review_status = models.CharField(
|
||||||
|
default='not_reviwed', verbose_name='نشان دادن کامنت', max_length=30, choices=status_types)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'نظر'
|
verbose_name = 'نظر'
|
||||||
verbose_name_plural = 'نظرات'
|
verbose_name_plural = 'نظرات'
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['product'], name='comment_product_idx'),
|
models.Index(fields=['product'], name='comment_product_idx'),
|
||||||
models.Index(fields=['review_status'], name='comment_review_status_idx'),
|
models.Index(fields=['review_status'],
|
||||||
models.Index(fields=['product', 'review_status'], name='comment_product_status_idx'),
|
name='comment_review_status_idx'),
|
||||||
|
models.Index(fields=['product', 'review_status'],
|
||||||
|
name='comment_product_status_idx'),
|
||||||
models.Index(fields=['user'], name='comment_user_idx'),
|
models.Index(fields=['user'], name='comment_user_idx'),
|
||||||
models.Index(fields=['timestamp'], name='comment_timestamp_idx'),
|
models.Index(fields=['timestamp'], name='comment_timestamp_idx'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.user}-{self.content[:30]}"
|
return f"{self.user}-{self.content[:30]}"
|
||||||
|
|
||||||
|
|
||||||
class AttributeType(models.Model):
|
class AttributeType(models.Model):
|
||||||
name = models.CharField(verbose_name='نام نوع متغییر', max_length=100)
|
name = models.CharField(verbose_name='نام نوع متغییر', max_length=100)
|
||||||
|
|
||||||
@@ -206,9 +242,13 @@ class AttributeType(models.Model):
|
|||||||
verbose_name = 'نوع متغییر محصول'
|
verbose_name = 'نوع متغییر محصول'
|
||||||
verbose_name_plural = 'نوع های متغییر محصول'
|
verbose_name_plural = 'نوع های متغییر محصول'
|
||||||
|
|
||||||
|
|
||||||
class AttributeValue(models.Model):
|
class AttributeValue(models.Model):
|
||||||
attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE, blank=True, null=True)
|
attribute_type = models.ForeignKey(
|
||||||
value = models.CharField(verbose_name='مقدار نوع اتربیوت', max_length=100, blank=True, null=True)
|
AttributeType, on_delete=models.CASCADE, blank=True, null=True)
|
||||||
|
value = models.CharField(
|
||||||
|
verbose_name='مقدار نوع اتربیوت', max_length=100, blank=True, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('attribute_type', 'value')
|
unique_together = ('attribute_type', 'value')
|
||||||
verbose_name = 'مقدار متغییر محصول'
|
verbose_name = 'مقدار متغییر محصول'
|
||||||
@@ -217,6 +257,7 @@ class AttributeValue(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.attribute_type}: {self.value}"
|
return f"{self.attribute_type}: {self.value}"
|
||||||
|
|
||||||
|
|
||||||
class ProductImageModel(models.Model):
|
class ProductImageModel(models.Model):
|
||||||
name = models.CharField(max_length=30, verbose_name='نام عکس')
|
name = models.CharField(max_length=30, verbose_name='نام عکس')
|
||||||
image = models.ImageField(upload_to='product_images/')
|
image = models.ImageField(upload_to='product_images/')
|
||||||
@@ -230,15 +271,19 @@ class ProductImageModel(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ProductDetailModel(models.Model):
|
class ProductDetailModel(models.Model):
|
||||||
name = models.CharField(max_length=50, verbose_name='نام جزيیات', help_text='این متن فقط برای راحتی در استفاده از پنل ادمین میباشد')
|
name = models.CharField(max_length=50, verbose_name='نام جزيیات',
|
||||||
detail_category = models.ForeignKey(ProductDetailCategory, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات')
|
help_text='این متن فقط برای راحتی در استفاده از پنل ادمین میباشد')
|
||||||
|
detail_category = models.ForeignKey(
|
||||||
|
ProductDetailCategory, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'جزیات محصول'
|
verbose_name = 'جزیات محصول'
|
||||||
verbose_name_plural = 'جزیات محصول ها'
|
verbose_name_plural = 'جزیات محصول ها'
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['detail_category'], name='product_detail_category_idx'),
|
models.Index(fields=['detail_category'],
|
||||||
|
name='product_detail_category_idx'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'جزيیات محصول {self.detail_category.title} - {self.name}'
|
return f'جزيیات محصول {self.detail_category.title} - {self.name}'
|
||||||
|
|
||||||
@@ -246,10 +291,15 @@ class ProductDetailModel(models.Model):
|
|||||||
class DetailModel(models.Model):
|
class DetailModel(models.Model):
|
||||||
title = models.CharField(max_length=50, verbose_name='عنوان')
|
title = models.CharField(max_length=50, verbose_name='عنوان')
|
||||||
detail_text1 = models.CharField(max_length=150, verbose_name='متن جزیات ۱')
|
detail_text1 = models.CharField(max_length=150, verbose_name='متن جزیات ۱')
|
||||||
detail_text2 = models.CharField(max_length=150 , verbose_name='متن جزیات ۲', blank=True, null=True)
|
detail_text2 = models.CharField(
|
||||||
detail_text3 = models.CharField(max_length=150 , verbose_name='متن جزیات ۳', blank=True, null=True)
|
max_length=150, verbose_name='متن جزیات ۲', blank=True, null=True)
|
||||||
detail_text4 = models.CharField(max_length=150 , verbose_name='متن جزیات ۴', blank=True, null=True)
|
detail_text3 = models.CharField(
|
||||||
detail_model = models.ForeignKey(ProductDetailModel, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات', related_name='details')
|
max_length=150, verbose_name='متن جزیات ۳', blank=True, null=True)
|
||||||
|
detail_text4 = models.CharField(
|
||||||
|
max_length=150, verbose_name='متن جزیات ۴', blank=True, null=True)
|
||||||
|
detail_model = models.ForeignKey(
|
||||||
|
ProductDetailModel, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات', related_name='details')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.title}'
|
return f'{self.title}'
|
||||||
|
|
||||||
@@ -263,41 +313,65 @@ class DetailModel(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ProductVariant(models.Model):
|
class ProductVariant(models.Model):
|
||||||
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE, related_name='variants', verbose_name='محصول')
|
product = models.ForeignKey(
|
||||||
product_attributes = models.ManyToManyField(AttributeValue, verbose_name='ویژگیها', related_name='variant')
|
ProductModel, on_delete=models.CASCADE, related_name='variants', verbose_name='محصول')
|
||||||
in_stock = models.PositiveIntegerField(default=0, verbose_name='تعداد موجود')
|
product_attributes = models.ManyToManyField(
|
||||||
price = models.PositiveIntegerField(verbose_name='قیمت محاسبه شده', blank=True, null=True)
|
AttributeValue, verbose_name='ویژگیها', related_name='variant')
|
||||||
input_price = models.PositiveIntegerField(default=0, verbose_name='قیمت ورودی')
|
in_stock = models.PositiveIntegerField(
|
||||||
min_price = models.PositiveIntegerField(verbose_name='قیمت کف', help_text='این قیمت برای کف قیمتی محصول در نظر گرفته میشود')
|
default=0, verbose_name='تعداد موجود')
|
||||||
|
price = models.PositiveIntegerField(
|
||||||
|
verbose_name='قیمت محاسبه شده', blank=True, null=True)
|
||||||
|
input_price = models.PositiveIntegerField(
|
||||||
|
default=0, verbose_name='قیمت ورودی')
|
||||||
|
min_price = models.PositiveIntegerField(
|
||||||
|
verbose_name='قیمت کف', help_text='این قیمت برای کف قیمتی محصول در نظر گرفته میشود')
|
||||||
|
profit = models.BigIntegerField(
|
||||||
|
default=0, verbose_name='سود (تومان)', help_text='مقدار سود به ازای هر واحد به تومان')
|
||||||
|
special_discount_percent = models.SmallIntegerField(
|
||||||
|
default=0, verbose_name='درصد تخفیف ویژه', help_text='درصدی که از سود برای محاسبه تخفیف ویژه استفاده میشود')
|
||||||
currency_type = (
|
currency_type = (
|
||||||
('dollor', 'دلار'),
|
('dollor', 'دلار'),
|
||||||
('toman', 'تومان'),
|
('toman', 'تومان'),
|
||||||
('derham', 'درهم')
|
('derham', 'درهم')
|
||||||
)
|
)
|
||||||
in_pack_items = models.ManyToManyField(InPackItems, blank=True, verbose_name='ایتم های داخل پک')
|
in_pack_items = models.ManyToManyField(
|
||||||
|
InPackItems, blank=True, verbose_name='ایتم های داخل پک')
|
||||||
sell = models.IntegerField(default=0, verbose_name='فروش')
|
sell = models.IntegerField(default=0, verbose_name='فروش')
|
||||||
currency = models.CharField(verbose_name='نوع ارز', max_length=20, choices=currency_type)
|
currency = models.CharField(
|
||||||
|
verbose_name='نوع ارز', max_length=20, choices=currency_type)
|
||||||
discount = models.SmallIntegerField(default=0, verbose_name='تخفیف')
|
discount = models.SmallIntegerField(default=0, verbose_name='تخفیف')
|
||||||
color = models.CharField(verbose_name='رنگ', max_length=7, blank=True, null=True)
|
color = models.CharField(
|
||||||
|
verbose_name='رنگ', max_length=7, blank=True, null=True)
|
||||||
images = models.ManyToManyField(ProductImageModel, verbose_name='عکس ها')
|
images = models.ManyToManyField(ProductImageModel, verbose_name='عکس ها')
|
||||||
video = models.FileField(upload_to='product_videos/', blank=True, null=True, verbose_name='ویدیو')
|
video = models.FileField(upload_to='product_videos/',
|
||||||
details = models.ManyToManyField(ProductDetailModel, verbose_name='جزییات محصول', related_name='product')
|
blank=True, null=True, verbose_name='ویدیو')
|
||||||
slider_category = models.ForeignKey(ShowCaseSlider, verbose_name='دسته بندی پورسانتی', blank=True, null=True, on_delete=models.CASCADE)
|
details = models.ManyToManyField(
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='زمان ثبت محصول')
|
ProductDetailModel, verbose_name='جزییات محصول', related_name='product')
|
||||||
|
slider_category = models.ForeignKey(
|
||||||
|
ShowCaseSlider, verbose_name='دسته بندی پورسانتی', blank=True, null=True, on_delete=models.CASCADE)
|
||||||
|
created_at = models.DateTimeField(
|
||||||
|
auto_now_add=True, verbose_name='زمان ثبت محصول')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'تنوع محصول'
|
verbose_name = 'تنوع محصول'
|
||||||
verbose_name_plural = 'تنوعهای محصول'
|
verbose_name_plural = 'تنوعهای محصول'
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['product', 'price', 'created_at'], name='idx_product_price_created'),
|
models.Index(fields=['product', 'price', 'created_at'],
|
||||||
models.Index(fields=['in_stock', 'discount'], name='idx_stock_discount'),
|
name='idx_product_price_created'),
|
||||||
|
models.Index(fields=['in_stock', 'discount'],
|
||||||
|
name='idx_stock_discount'),
|
||||||
models.Index(fields=['created_at'], name='idx_created'),
|
models.Index(fields=['created_at'], name='idx_created'),
|
||||||
models.Index(fields=['price'], name='idx_price'),
|
models.Index(fields=['price'], name='idx_price'),
|
||||||
models.Index(fields=['discount'], name='product_variant_discount_idx'),
|
models.Index(fields=['discount'],
|
||||||
models.Index(fields=['in_stock'], name='product_variant_in_stock_idx'),
|
name='product_variant_discount_idx'),
|
||||||
models.Index(fields=['product'], name='product_variant_product_idx'),
|
models.Index(fields=['in_stock'],
|
||||||
models.Index(fields=['product', 'in_stock'], name='variant_product_stock_idx'),
|
name='product_variant_in_stock_idx'),
|
||||||
models.Index(fields=['product', 'discount'], name='variant_product_discount_idx'),
|
models.Index(fields=['product'],
|
||||||
|
name='product_variant_product_idx'),
|
||||||
|
models.Index(fields=['product', 'in_stock'],
|
||||||
|
name='variant_product_stock_idx'),
|
||||||
|
models.Index(fields=['product', 'discount'],
|
||||||
|
name='variant_product_discount_idx'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -315,13 +389,23 @@ class ProductVariant(models.Model):
|
|||||||
def discount_amount(self):
|
def discount_amount(self):
|
||||||
return self.price * (self.discount / 100)
|
return self.price * (self.discount / 100)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def special_discount_amount_per_unit(self):
|
||||||
|
"""Calculate special discount amount per unit as profit * special_discount_percent / 100."""
|
||||||
|
try:
|
||||||
|
return int(self.profit * (self.special_discount_percent / 100))
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
def set_or_update_price(self, dollor_price=None):
|
def set_or_update_price(self, dollor_price=None):
|
||||||
if not dollor_price:
|
if not dollor_price:
|
||||||
dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
|
dollor_object, _ = DollorModel.objects.get_or_create(
|
||||||
|
unique_filed='unique')
|
||||||
dollor_price = dollor_object.price
|
dollor_price = dollor_object.price
|
||||||
|
|
||||||
if dollor_price is None:
|
if dollor_price is None:
|
||||||
raise ValidationError({"dollor_price": "The 'dollor_price' must be provided in the context for dollar pricing."})
|
raise ValidationError(
|
||||||
|
{"dollor_price": "The 'dollor_price' must be provided in the context for dollar pricing."})
|
||||||
|
|
||||||
dollar_to_dirham = 0.27
|
dollar_to_dirham = 0.27
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
// imports
|
// imports
|
||||||
|
|
||||||
import useDeleteDiscountCode from "~/composables/api/orders/useDeleteDiscountCode";
|
import useDeleteDiscountCode from "~/composables/api/orders/useDeleteDiscountCode";
|
||||||
|
import useDeleteSpecialDiscountCode from "~/composables/api/orders/useDeleteSpecialDiscountCode";
|
||||||
import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
|
import useGetCartOrders from "~/composables/api/orders/useGetCartOrders";
|
||||||
import usePayOrder from "~/composables/api/orders/usePayOrder";
|
import usePayOrder from "~/composables/api/orders/usePayOrder";
|
||||||
import useSubmitDiscountCode from "~/composables/api/orders/useSubmitDiscountCode";
|
import useSubmitDiscountCode from "~/composables/api/orders/useSubmitDiscountCode";
|
||||||
|
import useSubmitSpecialDiscountCode from "~/composables/api/orders/useSubmitSpecialDiscountCode";
|
||||||
import { useToast } from "~/composables/global/useToast";
|
import { useToast } from "~/composables/global/useToast";
|
||||||
import { QUERY_KEYS } from "~/constants";
|
import { QUERY_KEYS } from "~/constants";
|
||||||
|
|
||||||
@@ -19,11 +21,16 @@ const { addToast } = useToast();
|
|||||||
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
|
||||||
|
|
||||||
const discountCode = ref(cart.value?.discount_code?.code || "");
|
const discountCode = ref(cart.value?.discount_code?.code || "");
|
||||||
|
const specialDiscountCode = ref(cart.value?.special_discount_code?.code || "");
|
||||||
|
|
||||||
const { mutateAsync: submitDiscountCode, isPending: submitDiscountCodeIsPending } = useSubmitDiscountCode();
|
const { mutateAsync: submitDiscountCode, isPending: submitDiscountCodeIsPending } = useSubmitDiscountCode();
|
||||||
|
|
||||||
const { mutateAsync: deleteDiscountCode, isPending: deleteDiscountCodeIsPending } = useDeleteDiscountCode();
|
const { mutateAsync: deleteDiscountCode, isPending: deleteDiscountCodeIsPending } = useDeleteDiscountCode();
|
||||||
|
|
||||||
|
const { mutateAsync: submitSpecialDiscountCode, isPending: submitSpecialDiscountCodeIsPending } = useSubmitSpecialDiscountCode();
|
||||||
|
|
||||||
|
const { mutateAsync: deleteSpecialDiscountCode, isPending: deleteSpecialDiscountCodeIsPending } = useDeleteSpecialDiscountCode();
|
||||||
|
|
||||||
const { mutateAsync: pay, isPending: paymentIsPending } = usePayOrder();
|
const { mutateAsync: pay, isPending: paymentIsPending } = usePayOrder();
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
@@ -32,6 +39,8 @@ const nextPage = computed(() => route.meta.nextPage as { name: string; label: st
|
|||||||
|
|
||||||
const hasSubmittedDiscountCode = computed(() => !!cart.value?.discount_code);
|
const hasSubmittedDiscountCode = computed(() => !!cart.value?.discount_code);
|
||||||
|
|
||||||
|
const hasSubmittedSpecialDiscountCode = computed(() => !!cart.value?.special_discount_code);
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
|
|
||||||
const handleSubmitDiscountCode = () => {
|
const handleSubmitDiscountCode = () => {
|
||||||
@@ -72,6 +81,44 @@ const handleDeleteDiscountCode = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmitSpecialDiscountCode = () => {
|
||||||
|
submitSpecialDiscountCode(
|
||||||
|
{ code: specialDiscountCode.value },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.cart] });
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
addToast({
|
||||||
|
message: "خطایی در ثبت کد تخفیف ویژه رخ داد",
|
||||||
|
options: {
|
||||||
|
status: "error",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
specialDiscountCode.value = "";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteSpecialDiscountCode = () => {
|
||||||
|
deleteSpecialDiscountCode(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.cart] });
|
||||||
|
specialDiscountCode.value = "";
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
addToast({
|
||||||
|
message: "خطایی در حذف کد تخفیف ویژه رخ داد",
|
||||||
|
options: {
|
||||||
|
status: "error",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
specialDiscountCode.value = "";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handlePayment = () => {
|
const handlePayment = () => {
|
||||||
pay(
|
pay(
|
||||||
{
|
{
|
||||||
@@ -95,6 +142,30 @@ const handlePayment = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// watch
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => cart.value?.discount_code,
|
||||||
|
(newCode) => {
|
||||||
|
if (newCode) {
|
||||||
|
discountCode.value = newCode.code;
|
||||||
|
} else {
|
||||||
|
discountCode.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => cart.value?.special_discount_code,
|
||||||
|
(newCode) => {
|
||||||
|
if (newCode) {
|
||||||
|
specialDiscountCode.value = newCode.code;
|
||||||
|
} else {
|
||||||
|
specialDiscountCode.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -154,6 +225,17 @@ const handlePayment = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="cart?.special_discount_total && cart.special_discount_total !== '0 تومان'"
|
||||||
|
class="flex items-center justify-between w-full text-green-700"
|
||||||
|
>
|
||||||
|
<span class="max-w-1/2 text-sm"> تخفیف ویژه: </span>
|
||||||
|
|
||||||
|
<span class="max-w-1/2 text-sm">
|
||||||
|
{{ cart?.special_discount_total }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between w-full text-slate-800">
|
<div class="flex items-center justify-between w-full text-slate-800">
|
||||||
<span class="max-w-1/2 text-sm"> مالیات ارزش افزوده: </span>
|
<span class="max-w-1/2 text-sm"> مالیات ارزش افزوده: </span>
|
||||||
|
|
||||||
@@ -193,6 +275,30 @@ const handlePayment = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex justify-between">
|
||||||
|
<Input
|
||||||
|
v-model="specialDiscountCode"
|
||||||
|
placeholder="کد تخفیف ویژه"
|
||||||
|
class="!py-3 !pe-2 ps-2.5 w-full !rounded-none !border-e-[0px] !rounded-s-100"
|
||||||
|
:disabled="hasSubmittedSpecialDiscountCode"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
@click="hasSubmittedSpecialDiscountCode ? handleDeleteSpecialDiscountCode() : handleSubmitSpecialDiscountCode()"
|
||||||
|
class="text-xs px-5 rounded-e-100 py-1.5 text-white bg-green-600 hover:bg-transparent hover:text-green-600 border-[1.5px] border-green-600 hover:border-green-600 transition-all disabled:cursor-not-allowed"
|
||||||
|
:disabled="!specialDiscountCode.length || submitSpecialDiscountCodeIsPending || deleteSpecialDiscountCodeIsPending"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
v-if="submitSpecialDiscountCodeIsPending || deleteSpecialDiscountCodeIsPending"
|
||||||
|
name="svg-spinners:180-ring-with-bg"
|
||||||
|
size="20"
|
||||||
|
class="**:fill-white"
|
||||||
|
/>
|
||||||
|
<span v-else>
|
||||||
|
{{ hasSubmittedSpecialDiscountCode ? "حذف" : "ثبت" }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
v-if="nextPage?.name == 'payment'"
|
v-if="nextPage?.name == 'payment'"
|
||||||
start-icon="ci:arrow-right"
|
start-icon="ci:arrow-right"
|
||||||
|
|||||||
@@ -232,13 +232,19 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col items-end">
|
||||||
<span
|
<span
|
||||||
v-if="data.discount > 0"
|
v-if="data.discount > 0"
|
||||||
class="typo-p-sm relative flex-center w-fit line-through"
|
class="typo-p-sm relative flex-center w-fit line-through text-slate-400"
|
||||||
>
|
>
|
||||||
{{ data.price }}
|
{{ data.price }}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="data.special_discount_amount"
|
||||||
|
class="typo-p-xs text-green-600 font-medium"
|
||||||
|
>
|
||||||
|
تخفیف ویژه: {{ data.special_discount_amount }}
|
||||||
|
</span>
|
||||||
<span class="typo-p-xl relative flex-center w-fit font-medium">
|
<span class="typo-p-xl relative flex-center w-fit font-medium">
|
||||||
{{ data.final_price }}
|
{{ data.final_price }}
|
||||||
</span>
|
</span>
|
||||||
@@ -296,13 +302,19 @@ watch(
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col items-end">
|
||||||
<span
|
<span
|
||||||
v-if="data.discount > 0"
|
v-if="data.discount > 0"
|
||||||
class="typo-p-xs relative flex-center w-fit line-through"
|
class="typo-p-xs relative flex-center w-fit line-through text-slate-400"
|
||||||
>
|
>
|
||||||
{{ data.price }}
|
{{ data.price }}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="data.special_discount_amount && data.special_discount_amount !== '0 تومان'"
|
||||||
|
class="text-[10px] text-green-600 font-medium"
|
||||||
|
>
|
||||||
|
تخفیف ویژه: {{ data.special_discount_amount }}
|
||||||
|
</span>
|
||||||
<span class="typo-p-md relative flex-center w-fit font-medium">
|
<span class="typo-p-md relative flex-center w-fit font-medium">
|
||||||
{{ data.final_price }}
|
{{ data.final_price }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// imports
|
||||||
|
|
||||||
|
import { useMutation } from "@tanstack/vue-query";
|
||||||
|
import { API_ENDPOINTS } from "~/constants";
|
||||||
|
|
||||||
|
const useDeleteSpecialDiscountCode = () => {
|
||||||
|
// state
|
||||||
|
|
||||||
|
const { $axios: axios } = useNuxtApp();
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
const handleDeleteSpecialDiscountCode = async () => {
|
||||||
|
const { data } = await axios.delete(
|
||||||
|
API_ENDPOINTS.orders.cart.delete_special_discount
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: () => handleDeleteSpecialDiscountCode(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDeleteSpecialDiscountCode;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// imports
|
||||||
|
|
||||||
|
import { useMutation } from "@tanstack/vue-query";
|
||||||
|
import { API_ENDPOINTS } from "~/constants";
|
||||||
|
|
||||||
|
// types
|
||||||
|
|
||||||
|
export type SubmitSpecialDiscountCodeRequest = {
|
||||||
|
code: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSubmitSpecialDiscountCode = () => {
|
||||||
|
// state
|
||||||
|
|
||||||
|
const { $axios: axios } = useNuxtApp();
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
const handleSubmitSpecialDiscountCode = async (
|
||||||
|
params: SubmitSpecialDiscountCodeRequest
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.post(
|
||||||
|
API_ENDPOINTS.orders.cart.add_special_discount,
|
||||||
|
{
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (discountData: SubmitSpecialDiscountCodeRequest) =>
|
||||||
|
handleSubmitSpecialDiscountCode(discountData),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSubmitSpecialDiscountCode;
|
||||||
@@ -61,6 +61,8 @@ export const API_ENDPOINTS = {
|
|||||||
add_one: "/order/cart/item",
|
add_one: "/order/cart/item",
|
||||||
add_discount: "/order/cart/discount",
|
add_discount: "/order/cart/discount",
|
||||||
delete_discount: "/order/cart/discount",
|
delete_discount: "/order/cart/discount",
|
||||||
|
add_special_discount: "/order/cart/special-discount",
|
||||||
|
delete_special_discount: "/order/cart/special-discount",
|
||||||
},
|
},
|
||||||
delivery: {
|
delivery: {
|
||||||
set_address: "/order/cart/set-address",
|
set_address: "/order/cart/set-address",
|
||||||
|
|||||||
Vendored
+9
@@ -195,6 +195,7 @@ declare global {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
final_price: string;
|
final_price: string;
|
||||||
order_id: number;
|
order_id: number;
|
||||||
|
special_discount_total?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DiscountCode = {
|
type DiscountCode = {
|
||||||
@@ -203,6 +204,11 @@ declare global {
|
|||||||
amount: string;
|
amount: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SpecialDiscountCode = {
|
||||||
|
code: string;
|
||||||
|
user: number;
|
||||||
|
};
|
||||||
|
|
||||||
type CartItem = {
|
type CartItem = {
|
||||||
id: number;
|
id: number;
|
||||||
product: {
|
product: {
|
||||||
@@ -227,6 +233,7 @@ declare global {
|
|||||||
};
|
};
|
||||||
discount: number;
|
discount: number;
|
||||||
discount_amount: string;
|
discount_amount: string;
|
||||||
|
special_discount_amount: string;
|
||||||
price: string;
|
price: string;
|
||||||
final_price: string;
|
final_price: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -234,9 +241,11 @@ declare global {
|
|||||||
|
|
||||||
type Cart = {
|
type Cart = {
|
||||||
discount_code: DiscountCode;
|
discount_code: DiscountCode;
|
||||||
|
special_discount_code?: SpecialDiscountCode;
|
||||||
items: CartItem[];
|
items: CartItem[];
|
||||||
cart_total: string;
|
cart_total: string;
|
||||||
items_discount_amount: string;
|
items_discount_amount: string;
|
||||||
|
special_discount_total: string;
|
||||||
tax_amount: string;
|
tax_amount: string;
|
||||||
final_price: string;
|
final_price: string;
|
||||||
address: Address;
|
address: Address;
|
||||||
|
|||||||
Reference in New Issue
Block a user