All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 33s
262 lines
9.4 KiB
Python
262 lines
9.4 KiB
Python
import re
|
|
from rest_framework import serializers
|
|
from django.contrib.auth.models import User
|
|
from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction, Category
|
|
|
|
class CategoryListField(serializers.RelatedField):
|
|
def to_representation(self, value):
|
|
return value.name
|
|
|
|
def to_internal_value(self, data):
|
|
category, _ = Category.objects.get_or_create(name=data)
|
|
return category
|
|
|
|
class SupplierProfileSerializer(serializers.ModelSerializer):
|
|
onboarding_step = serializers.SerializerMethodField()
|
|
categories = CategoryListField(many=True, queryset=Category.objects.all(), required=False)
|
|
aadhar_url = serializers.SerializerMethodField()
|
|
pan_url = serializers.SerializerMethodField()
|
|
logo_url = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = SupplierProfile
|
|
exclude = ('user',)
|
|
|
|
def get_aadhar_url(self, obj):
|
|
from .views import get_document_url
|
|
request = self.context.get('request')
|
|
return get_document_url(obj.aadhar_s3_key, request)
|
|
|
|
def get_pan_url(self, obj):
|
|
from .views import get_document_url
|
|
request = self.context.get('request')
|
|
return get_document_url(obj.pan_s3_key, request)
|
|
|
|
def get_logo_url(self, obj):
|
|
from .views import get_document_url
|
|
request = self.context.get('request')
|
|
return get_document_url(obj.logo_s3_key, request)
|
|
|
|
def get_onboarding_step(self, obj):
|
|
if obj.is_profile_complete:
|
|
return 7
|
|
if not obj.business_type or not obj.gstin or not obj.is_gstin_verified:
|
|
return 2
|
|
if not obj.aadhar_s3_key or not obj.pan_s3_key:
|
|
return 3
|
|
if not obj.phone_verified or not obj.email_verified:
|
|
return 4
|
|
if obj.categories.count() == 0:
|
|
return 5
|
|
if not obj.store_name or not obj.store_slug or not obj.logo_s3_key or not obj.street:
|
|
return 6
|
|
return 7
|
|
|
|
def validate_pincode(self, value):
|
|
if value and (not value.isdigit() or len(value) != 6):
|
|
raise serializers.ValidationError("Pincode must be a 6-digit number.")
|
|
return value
|
|
|
|
def validate_state(self, value):
|
|
return value
|
|
|
|
def validate_logo_s3_key(self, value):
|
|
user = self.context['request'].user
|
|
if value and not value.startswith(f"suppliers/{user.id}/") and not value.startswith(f"supplierdocument/{user.id}/"):
|
|
raise serializers.ValidationError("logo_s3_key must belong to the logged-in supplier.")
|
|
return value
|
|
|
|
class UserSerializer(serializers.ModelSerializer):
|
|
profile = SupplierProfileSerializer(read_only=True)
|
|
|
|
class Meta:
|
|
model = User
|
|
fields = ('id', 'username', 'email', 'profile')
|
|
|
|
class RegisterSerializer(serializers.Serializer):
|
|
username = serializers.CharField(required=True)
|
|
email = serializers.EmailField(required=True)
|
|
phone = serializers.CharField(required=True)
|
|
password = serializers.CharField(required=True, write_only=True)
|
|
confirm_password = serializers.CharField(required=False, write_only=True)
|
|
policy_accepted = serializers.BooleanField(required=False, default=True)
|
|
|
|
def validate_email(self, value):
|
|
if User.objects.filter(email=value).exists():
|
|
raise serializers.ValidationError("Email already registered.")
|
|
return value
|
|
|
|
def validate_phone(self, value):
|
|
if SupplierProfile.objects.filter(phone=value).exists():
|
|
raise serializers.ValidationError("Phone already registered.")
|
|
return value
|
|
|
|
def validate_password(self, value):
|
|
if len(value) < 8:
|
|
raise serializers.ValidationError("Password must be at least 8 characters.")
|
|
|
|
return value
|
|
|
|
def validate(self, data):
|
|
if 'confirm_password' in data and data['password'] != data['confirm_password']:
|
|
raise serializers.ValidationError({"confirm_password": "Passwords do not match."})
|
|
if not data.get('policy_accepted', True):
|
|
raise serializers.ValidationError({"policy_accepted": "Policy must be accepted."})
|
|
return data
|
|
|
|
def create(self, validated_data):
|
|
validated_data.pop('confirm_password', None)
|
|
validated_data.pop('policy_accepted', None)
|
|
user = User.objects.create_user(
|
|
username=validated_data['username'],
|
|
email=validated_data['email'],
|
|
password=validated_data['password']
|
|
)
|
|
SupplierProfile.objects.update_or_create(
|
|
user=user,
|
|
defaults={
|
|
'phone': validated_data['phone'],
|
|
'status': 'unverified'
|
|
}
|
|
)
|
|
|
|
is_test_user = validated_data['email'].startswith('test_seller_') or validated_data['username'] == 'demo_seller'
|
|
|
|
wallet, _ = Wallet.objects.update_or_create(
|
|
supplier=user,
|
|
defaults={
|
|
'outstanding': 850.00 if is_test_user else 0.00,
|
|
'withdrawn': 1250.00 if is_test_user else 0.00
|
|
}
|
|
)
|
|
|
|
if is_test_user:
|
|
# Seed default products
|
|
Product.objects.update_or_create(
|
|
sku=f'APP-SILK-{user.id}',
|
|
defaults={
|
|
'supplier': user,
|
|
'title': 'Silk Scarf',
|
|
'category': 'Apparel',
|
|
'price': 45.00,
|
|
'stock': 25,
|
|
'image': 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100'
|
|
}
|
|
)
|
|
|
|
# Seed default orders
|
|
Order.objects.update_or_create(
|
|
order_id=f'OR-8721-{user.id}',
|
|
defaults={
|
|
'supplier': user,
|
|
'date': '2026-08-05',
|
|
'item': 'Handmade Wool Blanket',
|
|
'quantity': 1,
|
|
'customer': 'Alice Smith',
|
|
'total': 85.00,
|
|
'status': 'Pending Acceptance'
|
|
}
|
|
)
|
|
Order.objects.update_or_create(
|
|
order_id=f'ORD-8501-{user.id}',
|
|
defaults={
|
|
'supplier': user,
|
|
'date': '2026-08-08',
|
|
'item': 'Brass Elephant Figurine',
|
|
'quantity': 1,
|
|
'customer': 'Bruce Wayne',
|
|
'total': 120.00,
|
|
'status': 'Pending Acceptance'
|
|
}
|
|
)
|
|
|
|
# Seed default returns
|
|
ReturnRequest.objects.update_or_create(
|
|
return_id=f'RET-5510-{user.id}',
|
|
defaults={
|
|
'supplier': user,
|
|
'order_id': f'OR-3920-{user.id}',
|
|
'customer': 'Bob Johnson',
|
|
'item': 'Silk Scarf',
|
|
'reason': 'Ordered wrong color variant.',
|
|
'status': 'Pending Approval'
|
|
}
|
|
)
|
|
|
|
# Seed default wallet transactions
|
|
WalletTransaction.objects.update_or_create(
|
|
tx_id=f'TX-9031-{user.id}',
|
|
defaults={
|
|
'wallet': wallet,
|
|
'date': '2026-08-01',
|
|
'amount': 500.00,
|
|
'status': 'Transferred'
|
|
}
|
|
)
|
|
WalletTransaction.objects.update_or_create(
|
|
tx_id=f'TX-9022-{user.id}',
|
|
defaults={
|
|
'wallet': wallet,
|
|
'date': '2026-07-15',
|
|
'amount': 750.00,
|
|
'status': 'Transferred'
|
|
}
|
|
)
|
|
|
|
return user
|
|
|
|
class ProductSerializer(serializers.ModelSerializer):
|
|
is_low_stock = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = Product
|
|
fields = '__all__'
|
|
read_only_fields = ('supplier',)
|
|
|
|
def get_is_low_stock(self, obj):
|
|
return obj.stock <= 5
|
|
|
|
def validate_price(self, value):
|
|
if value <= 0:
|
|
raise serializers.ValidationError("Price must be greater than 0.")
|
|
return value
|
|
|
|
def validate_stock(self, value):
|
|
if value < 0:
|
|
raise serializers.ValidationError("Stock cannot be negative.")
|
|
return value
|
|
|
|
def validate_sku(self, value):
|
|
if not re.match(r'^[A-Z0-9][A-Z0-9\-]{2,49}$', value):
|
|
raise serializers.ValidationError("Invalid SKU format. Must match ^[A-Z0-9][A-Z0-9\\-]{2,49}$")
|
|
return value
|
|
|
|
def validate_category(self, value):
|
|
allowed = ["Apparel", "Home Decor", "Sculptures", "Kitchenware", "Jewellery", "Textiles", "Other"]
|
|
if value not in allowed:
|
|
raise serializers.ValidationError(f"Category must be one of: {', '.join(allowed)}")
|
|
return value
|
|
|
|
class OrderSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Order
|
|
fields = '__all__'
|
|
read_only_fields = ('supplier',)
|
|
|
|
class ReturnRequestSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = ReturnRequest
|
|
fields = '__all__'
|
|
read_only_fields = ('supplier',)
|
|
|
|
class WalletTransactionSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = WalletTransaction
|
|
fields = '__all__'
|
|
|
|
class WalletSerializer(serializers.ModelSerializer):
|
|
transactions = WalletTransactionSerializer(many=True, read_only=True)
|
|
|
|
class Meta:
|
|
model = Wallet
|
|
fields = ('outstanding', 'withdrawn', 'transactions')
|