from rest_framework import serializers from .models import ESP32Config, Order, Salesperson, Service, VCCourse, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal, CommissionLog, CourseEnrollment from .utils import get_current_wechat_user class CommissionLogSerializer(serializers.ModelSerializer): """ 佣金记录序列化器 """ order_info = serializers.SerializerMethodField() class Meta: model = CommissionLog fields = ['id', 'amount', 'level', 'status', 'created_at', 'order_info'] read_only_fields = ['id', 'amount', 'level', 'status', 'created_at', 'order_info'] def get_order_info(self, obj): return { 'order_id': obj.order.id, 'total_price': obj.order.total_price, 'customer_name': obj.order.customer_name } class WeChatUserSerializer(serializers.ModelSerializer): is_admin = serializers.SerializerMethodField() has_web_account = serializers.SerializerMethodField() class Meta: model = WeChatUser fields = ['id', 'openid', 'nickname', 'avatar_url', 'gender', 'country', 'province', 'city', 'phone_number', 'is_star', 'title', 'skills', 'is_admin', 'has_web_account', 'has_web_badge'] read_only_fields = ['id', 'openid', 'phone_number', 'is_star', 'title', 'skills', 'is_admin', 'has_web_account', 'has_web_badge'] def get_is_admin(self, obj): # 检查是否关联了系统用户且具有管理员权限 return bool(obj.user and obj.user.is_staff) def get_has_web_account(self, obj): # 检查是否关联了系统用户(即网页账号) return obj.user is not None class DistributorSerializer(serializers.ModelSerializer): user_info = WeChatUserSerializer(source='user', read_only=True) class Meta: model = Distributor fields = ['id', 'user_info', 'level', 'commission_rate', 'total_earnings', 'withdrawable_balance', 'status', 'invite_code', 'qr_code_url'] read_only_fields = ['level', 'commission_rate', 'total_earnings', 'withdrawable_balance', 'status', 'invite_code', 'qr_code_url'] class WithdrawalSerializer(serializers.ModelSerializer): class Meta: model = Withdrawal fields = ['id', 'amount', 'status', 'remark', 'created_at'] read_only_fields = ['status', 'created_at', 'remark'] class ProductFeatureSerializer(serializers.ModelSerializer): """ 产品特性序列化器 """ display_icon = serializers.SerializerMethodField() class Meta: model = ProductFeature fields = ['title', 'description', 'icon_name', 'display_icon', 'order'] def get_display_icon(self, obj): if obj.icon_url: return obj.icon_url if obj.icon_image: return obj.icon_image.url return None class ServiceSerializer(serializers.ModelSerializer): """ AI服务序列化器 """ features_list = serializers.SerializerMethodField() display_icon = serializers.SerializerMethodField() display_detail_image = serializers.SerializerMethodField() class Meta: model = Service fields = '__all__' def get_features_list(self, obj): if obj.features: return [line.strip() for line in obj.features.split('\n') if line.strip()] return [] def get_display_icon(self, obj): if obj.icon_url: return obj.icon_url if obj.icon: return obj.icon.url return None def get_display_detail_image(self, obj): if obj.detail_image_url: return obj.detail_image_url if obj.detail_image: return obj.detail_image.url return None class CourseEnrollmentSerializer(serializers.ModelSerializer): """ 课程报名序列化器 """ course_title = serializers.CharField(source='course.title', read_only=True) ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True) class Meta: model = CourseEnrollment fields = ['id', 'course', 'course_title', 'customer_name', 'phone_number', 'email', 'wechat_id', 'message', 'status', 'created_at', 'ref_code'] read_only_fields = ['status', 'created_at'] def create(self, validated_data): ref_code = validated_data.pop('ref_code', None) # 尝试关联销售员或分销员 if ref_code: try: salesperson = Salesperson.objects.get(code=ref_code) validated_data['salesperson'] = salesperson except Salesperson.DoesNotExist: pass try: distributor = Distributor.objects.get(invite_code=ref_code) validated_data['distributor'] = distributor except Distributor.DoesNotExist: pass return super().create(validated_data) class ServiceOrderSerializer(serializers.ModelSerializer): """ AI服务订单序列化器 """ service_name = serializers.CharField(source='service.title', read_only=True) # 接收前端传来的 ref_code ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True) class Meta: model = ServiceOrder fields = ['id', 'service', 'service_name', 'customer_name', 'company_name', 'phone_number', 'email', 'requirements', 'total_price', 'status', 'created_at', 'ref_code'] read_only_fields = ['total_price', 'status', 'created_at'] def create(self, validated_data): ref_code = validated_data.pop('ref_code', None) service = validated_data.get('service') # 默认设置预估总价为服务起步价 if service: validated_data['total_price'] = service.price # 尝试关联销售员 if ref_code: try: salesperson = Salesperson.objects.get(code=ref_code) validated_data['salesperson'] = salesperson except Salesperson.DoesNotExist: pass try: distributor = Distributor.objects.get(invite_code=ref_code) validated_data['distributor'] = distributor except Distributor.DoesNotExist: pass return super().create(validated_data) class VCCourseSerializer(serializers.ModelSerializer): """ VC课程序列化器 """ display_cover_image = serializers.SerializerMethodField() display_detail_image = serializers.SerializerMethodField() course_type_display = serializers.CharField(source='get_course_type_display', read_only=True) video_url = serializers.SerializerMethodField() video_embed_code = serializers.SerializerMethodField() is_purchased = serializers.SerializerMethodField() class Meta: model = VCCourse fields = '__all__' def get_display_cover_image(self, obj): if obj.cover_image_url: return obj.cover_image_url if obj.cover_image: return obj.cover_image.url return None def get_display_detail_image(self, obj): if obj.detail_image_url: return obj.detail_image_url if obj.detail_image: return obj.detail_image.url return None def _check_purchased(self, obj): request = self.context.get('request') if not request: return False # 尝试获取当前用户 user = get_current_wechat_user(request) if not user: return False # 如果是管理员,视为已购买 if user.user and user.user.is_staff: return True # 检查是否已购买/报名 (通过已支付的订单) has_order = Order.objects.filter( wechat_user=user, course=obj, status__in=['paid', 'shipped', 'completed'] ).exists() return has_order def get_is_purchased(self, obj): return self._check_purchased(obj) def get_video_url(self, obj): """ 仅当用户已付费/报名时返回视频URL """ if not obj.is_video_course: return None if self._check_purchased(obj): return obj.video_url return None def get_video_embed_code(self, obj): """ 仅当用户已付费/报名时返回视频嵌入代码 """ if not obj.is_video_course: return None if self._check_purchased(obj): return obj.video_embed_code return None class ESP32ConfigSerializer(serializers.ModelSerializer): """ ESP32配置序列化器 """ display_detail_image = serializers.SerializerMethodField() features = ProductFeatureSerializer(many=True, read_only=True) class Meta: model = ESP32Config fields = '__all__' def get_display_detail_image(self, obj): if obj.detail_image_url: return obj.detail_image_url if obj.detail_image: return obj.detail_image.url return None class OrderSerializer(serializers.ModelSerializer): """ 订单序列化器 """ config_name = serializers.CharField(source='config.name', read_only=True) course_title = serializers.CharField(source='course.title', read_only=True) activity_title = serializers.CharField(source='activity.title', read_only=True) config_image = serializers.SerializerMethodField() salesperson_name = serializers.CharField(source='salesperson.name', read_only=True) salesperson_code = serializers.CharField(source='salesperson.code', read_only=True) # 接收前端传来的 ref_code,用于查找 Salesperson ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True) class Meta: model = Order fields = ['id', 'config', 'config_name', 'config_image', 'course', 'course_title', 'activity', 'activity_title', 'quantity', 'total_price', 'status', 'created_at', 'updated_at', 'wechat_trade_no', 'customer_name', 'phone_number', 'shipping_address', 'ref_code', 'salesperson_name', 'salesperson_code', 'courier_name', 'tracking_number'] read_only_fields = ['total_price', 'status', 'wechat_trade_no', 'created_at', 'updated_at'] extra_kwargs = { 'customer_name': {'required': True}, 'phone_number': {'required': True}, } def validate(self, data): # 如果是部分更新 (PATCH),可能不需要校验所有字段,但这里主要用于创建 if self.instance: return data config = data.get('config') course = data.get('course') activity = data.get('activity') if not config and not course and not activity: raise serializers.ValidationError("必须选择一种商品(硬件配置、课程或活动)") # Count how many types are selected selected_types = sum([bool(config), bool(course), bool(activity)]) if selected_types > 1: raise serializers.ValidationError("一次只能购买一种类型的商品") if config and not data.get('shipping_address'): raise serializers.ValidationError({"shipping_address": "购买硬件产品需要填写收货地址"}) return data def get_config_image(self, obj): if obj.config: if obj.config.static_image_url: return obj.config.static_image_url if obj.config.detail_image_url: return obj.config.detail_image_url if obj.config.detail_image: return obj.config.detail_image.url elif obj.course: if obj.course.cover_image_url: return obj.course.cover_image_url if obj.course.cover_image: return obj.course.cover_image.url elif obj.activity: # Use activity.display_banner_url logic if obj.activity.banner: return obj.activity.banner.url if obj.activity.banner_url: return obj.activity.banner_url return None def create(self, validated_data): """ 重写创建方法,自动计算总价并关联销售员/分销员 """ config = validated_data.get('config') course = validated_data.get('course') activity = validated_data.get('activity') quantity = validated_data.get('quantity', 1) ref_code = validated_data.pop('ref_code', None) if config: validated_data['total_price'] = config.price * quantity elif course: validated_data['total_price'] = course.price * quantity elif activity: validated_data['total_price'] = activity.price * quantity # 尝试关联销售员或分销员 if ref_code: # 1. 尝试查找旧版销售员 try: salesperson = Salesperson.objects.get(code=ref_code) validated_data['salesperson'] = salesperson except Salesperson.DoesNotExist: pass # 2. 尝试查找新版分销员 try: distributor = Distributor.objects.get(invite_code=ref_code) validated_data['distributor'] = distributor except Distributor.DoesNotExist: pass return super().create(validated_data)