import re from rest_framework import serializers from django.contrib.auth.models import User from .models import SupplierProfile, Product, Order, ReturnRequest, Wallet, WalletTransaction, Category, SellerKYC 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 SellerKYCSerializer(serializers.ModelSerializer): class Meta: model = SellerKYC fields = ('aadhaar_number', 'name', 'dob', 'gender', 'address', 'created_at') 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() kyc = serializers.SerializerMethodField() class Meta: model = SupplierProfile exclude = ('user',) def get_kyc(self, obj): if hasattr(obj.user, 'kyc'): return SellerKYCSerializer(obj.user.kyc).data return None 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')