""" Printing API 序列化器 """ from rest_framework import serializers from api_v1.models import UploadedFile from api_v1.utils.media import build_public_media_url from printing import models from .services import PrintingOrderService, PrintingJobService from basic_info.models import Customer, Employee def _build_absolute_media_url(url: str | None, request): return build_public_media_url(url, request=request) def _serialize_plate_images(raw_value, request): if not raw_value: return [] serialized = [] if isinstance(raw_value, list): iterable = raw_value elif isinstance(raw_value, dict): iterable = [raw_value] elif isinstance(raw_value, str): iterable = [{'url': raw_value}] else: iterable = [] for entry in iterable: if isinstance(entry, str): data = {'url': entry} elif isinstance(entry, dict): data = dict(entry) else: continue url = data.get('url') or data.get('path') data['url'] = _build_absolute_media_url(url, request) if 'path' not in data and isinstance(url, str): data['path'] = url serialized.append(data) return serialized def _build_plate_image_payload(items, request_user): if items is None: return None if not items: return [] file_ids = [item['file_id'] for item in items if item.get('file_id') is not None] if not file_ids: return [] queryset = UploadedFile.objects.filter(id__in=file_ids, is_deleted=False) if request_user and request_user.is_authenticated: queryset = queryset.filter(owner=request_user) files_map = {file.id: file for file in queryset} missing = [str(fid) for fid in file_ids if fid not in files_map] if missing: raise serializers.ValidationError({'plate_image': f'以下文件不存在或已删除: {", ".join(missing)}'}) payload = [] for item in items: file = files_map[item['file_id']] name = item.get('name') or file.original_filename or file.path.name payload.append({ 'file_id': file.id, 'name': name, 'path': file.path.name, 'url': file.file_url, 'size': file.file_size, 'content_type': file.content_type, 'uploaded_at': file.created_at.isoformat(), }) return payload class PlateImageInputSerializer(serializers.Serializer): file_id = serializers.IntegerField(min_value=1, help_text='上传文件的 ID') name = serializers.CharField( required=False, allow_blank=True, allow_null=True, help_text='可选的图片名称,默认使用文件原始名称' ) class PrintingOrderListSerializer(serializers.ModelSerializer): """印染订单列表序列化器""" customer_name = serializers.CharField(source='customer.name', read_only=True) customer_phone = serializers.CharField(source='customer.mobile', read_only=True) process_name = serializers.CharField(source='process.name', read_only=True) progress = serializers.IntegerField(read_only=True) class Meta: model = models.PrintingOrder fields = [ 'id', 'human_id', 'customer', 'customer_name', 'customer_phone', 'fabric', 'width', 'is_urgent', 'area', 'address', 'curve', 'is_fabric_received', 'outgoing_date', 'is_invalid', 'new_curve', 'process', 'process_name', 'progress', 'position', 'print_count', 'created_at', 'updated_at', ] read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress', 'print_count'] class PrintingOrderDetailSerializer(serializers.ModelSerializer): """印染订单详情序列化器""" customer_name = serializers.CharField(source='customer.name', read_only=True) customer_phone = serializers.CharField(source='customer.mobile', read_only=True) customer_area = serializers.CharField(source='customer.area', read_only=True) process_name = serializers.CharField(source='process.name', read_only=True) progress = serializers.IntegerField(read_only=True) class Meta: model = models.PrintingOrder fields = [ 'id', 'human_id', 'customer', 'customer_name', 'customer_phone', 'customer_area', 'fabric', 'width', 'is_urgent', 'area', 'address', 'fabric_source', 'is_fabric_received', 'craft', 'description', 'outgoing_date', 'curve', 'new_curve', 'position', 'printing_warn', 'rolling_warn', 'production_warn', 'is_invalid', 'process', 'process_name', 'progress', 'print_count', 'created_at', 'updated_at' ] read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress', 'print_count'] class PrintingOrderCreateUpdateSerializer(serializers.ModelSerializer): """印染订单创建/更新序列化器""" class Meta: model = models.PrintingOrder fields = [ 'id', 'customer', 'fabric', 'width', 'is_urgent', 'area', 'address', 'fabric_source', 'is_fabric_received', 'craft', 'description', 'outgoing_date', 'curve', 'new_curve', 'position', 'printing_warn', 'rolling_warn', 'production_warn', 'is_invalid', 'process' ] read_only_fields = ['id'] def validate_customer(self, value): """验证客户是否存在""" if not value: raise serializers.ValidationError("客户不能为空") return value def validate(self, attrs): """验证流程修改权限""" # 更新时验证 process 修改权限 if self.instance and 'process' in attrs: new_process = attrs['process'] old_process = self.instance.process if new_process != old_process: if not PrintingOrderService.can_change_process(self.instance): raise serializers.ValidationError({ 'process': '存在已开始的印染任务,无法修改流程' }) return attrs def create(self, validated_data): """创建订单,使用 service 层""" user = self.context['request'].user return PrintingOrderService.create_printing_order(validated_data, user) def update(self, instance, validated_data): """更新订单,使用 service 层""" user = self.context['request'].user success, message, updated_instance = PrintingOrderService.update_printing_order( instance, validated_data, user ) if not success: raise serializers.ValidationError(message) return updated_instance class PrintingJobListSerializer(serializers.ModelSerializer): """印染款式明细列表序列化器""" printing_order_id = serializers.CharField(source='printing_order.human_id', read_only=True) product_name = serializers.CharField(source='product.name', read_only=True) status = serializers.CharField(read_only=True) work_state_display = serializers.SerializerMethodField() is_completed = serializers.BooleanField(read_only=True) business_object_id = serializers.SerializerMethodField() class Meta: model = models.PrintingJob fields = [ 'id', 'printing_order', 'printing_order_id', 'product', 'product_name', 'quantity', 'unit', 'size', 'pieces', 'description', 'work_state', 'work_state_display', 'status', 'is_completed', 'business_object_id', 'created_at', 'updated_at' ] read_only_fields = ['id', 'created_at', 'updated_at', 'status', 'is_completed', 'business_object_id'] def get_business_object_id(self, obj): """安全地获取 business_object_id""" return obj.business_object.id if obj.business_object else None def get_work_state_display(self, obj): return obj.get_work_state_display() class PrintingJobDetailSerializer(serializers.ModelSerializer): """印染款式明细详情序列化器""" printing_order_id = serializers.CharField(source='printing_order.human_id', read_only=True) product_name = serializers.CharField(source='product.name', read_only=True) product_code = serializers.CharField(source='product.human_id', read_only=True) status = serializers.CharField(read_only=True) status_id = serializers.IntegerField(read_only=True) work_state_display = serializers.SerializerMethodField() is_completed = serializers.BooleanField(read_only=True) has_started = serializers.BooleanField(read_only=True) business_object_id = serializers.SerializerMethodField() class Meta: model = models.PrintingJob fields = [ 'id', 'printing_order', 'printing_order_id', 'product', 'product_name', 'product_code', 'quantity', 'unit', 'size', 'pieces', 'description', 'work_state', 'work_state_display', 'status', 'status_id', 'is_completed', 'has_started', 'business_object_id', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'created_at', 'updated_at', 'status', 'status_id', 'is_completed', 'has_started', 'business_object_id' ] def get_business_object_id(self, obj): """安全地获取 business_object_id""" return obj.business_object.id if obj.business_object else None def get_work_state_display(self, obj): return obj.get_work_state_display() class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer): """印染款式明细创建/更新序列化器""" class Meta: model = models.PrintingJob fields = [ 'id', 'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description', 'work_state', ] read_only_fields = ['id'] extra_kwargs = { 'size': {'required': False, 'allow_null': True, 'allow_blank': True}, 'pieces': {'required': False, 'allow_null': True}, 'description': {'required': False, 'allow_null': True, 'allow_blank': True}, 'work_state': {'required': False}, } def validate_printing_order(self, value): """验证印染订单是否存在""" if not value: raise serializers.ValidationError("印染订单不能为空") return value def validate_product(self, value): """验证产品是否存在""" if not value: raise serializers.ValidationError("产品不能为空") return value def validate_quantity(self, value): """验证数量""" if value <= 0: raise serializers.ValidationError("数量必须大于0") return value def create(self, validated_data): """创建任务,使用 service 层""" user = self.context['request'].user return PrintingJobService.create_printing_job(validated_data, user) def update(self, instance, validated_data): """更新任务,使用 service 层""" user = self.context['request'].user success, message, updated_instance = PrintingJobService.update_printing_job( instance, validated_data, user ) if not success: raise serializers.ValidationError(message) return updated_instance class PlateOrderDesignCodeMixin: """确保 design_code 为空时使用主键""" @staticmethod def _normalize_design_code(design_code: str | None, instance_id: int | None) -> str | None: if design_code: return design_code if instance_id: return str(instance_id) return design_code def to_representation(self, instance): data = super().to_representation(instance) data['design_code'] = self._normalize_design_code(data.get('design_code'), instance.id) data['plate_image'] = _serialize_plate_images(getattr(instance, 'plate_image', None), self.context.get('request')) return data class PlateOrderListSerializer(PlateOrderDesignCodeMixin, serializers.ModelSerializer): """开版订单列表序列化器""" customer_name = serializers.CharField(source="customer.name", read_only=True) salesperson_name = serializers.CharField(source="salesperson.name", read_only=True) merchandiser_name = serializers.CharField(source="merchandiser.name", read_only=True) designer_name = serializers.CharField(source="designer.name", read_only=True) status = serializers.CharField(read_only=True) progress_percentage = serializers.IntegerField(read_only=True) process_name = serializers.SerializerMethodField() plate_image_url = serializers.SerializerMethodField() last_completed_state = serializers.CharField(read_only=True) class Meta: model = models.PlateOrder fields = [ 'id', 'design_code', 'plate_type', 'plate_date', 'plate_method', 'plate_image', 'plate_image_url', 'image_name', 'plate_notes', 'reprint_reason', 'urgency_level', 'is_invalid', 'customer', 'customer_name', 'area', 'default_address', 'salesperson', 'salesperson_name', 'merchandiser', 'merchandiser_name', 'designer', 'designer_name', 'style_name', 'fabric', 'fabric_source', 'width', 'production_method', 'is_mark_frame', 'drawing_rating', 'color_matching_rating', 'sample_rating', 'difficulty_rating', 'sample_meter', 'required_sample_meters', 'required_completion_date', 'completion_date', 'approval_result', 'is_ordered', 'customer_feedback', 'print_count', 'process', 'process_name', 'status', 'status_id', 'is_completed', 'has_started', 'last_completed_state', 'progress_percentage', 'business_object_id', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'status', 'progress_percentage', 'last_completed_state', 'created_at', 'updated_at', 'print_count' ] def get_plate_image_url(self, obj): images = _serialize_plate_images(getattr(obj, 'plate_image', None), self.context.get('request')) return [entry.get('url') for entry in images if entry.get('url')] def get_process_name(self, obj) ->str | None: """获取流程名称""" if obj.process: try: from stateflow.models import Process process = Process.objects.get(id=obj.process) return process.name except Process.DoesNotExist: return None return None class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSerializer): """开版订单详情序列化器""" customer_name = serializers.CharField(source="customer.name", read_only=True) customer_phone = serializers.CharField(source="customer.mobile", read_only=True) salesperson_name = serializers.CharField(source="salesperson.name", read_only=True) merchandiser_name = serializers.CharField(source="merchandiser.name", read_only=True) designer_name = serializers.CharField(source="designer.name", read_only=True) status = serializers.CharField(read_only=True) status_id = serializers.IntegerField(read_only=True) is_completed = serializers.BooleanField(read_only=True) has_started = serializers.BooleanField(read_only=True) progress_percentage = serializers.IntegerField(read_only=True) business_object_id = serializers.SerializerMethodField() plate_image_url = serializers.SerializerMethodField() process_name = serializers.SerializerMethodField() last_completed_state = serializers.CharField(read_only=True) class Meta: model = models.PlateOrder fields = [ 'id', 'design_code', 'plate_type', 'plate_date', 'plate_method', 'plate_image', 'plate_image_url', 'image_name', 'plate_notes', 'reprint_reason', 'urgency_level', 'is_invalid', 'customer', 'customer_name', 'customer_phone', 'area', 'default_address', 'salesperson', 'salesperson_name', 'merchandiser', 'merchandiser_name', 'designer', 'designer_name', 'style_name', 'fabric', 'fabric_source', 'width', 'production_method', 'is_mark_frame', 'drawing_rating', 'color_matching_rating', 'sample_rating', 'difficulty_rating', 'sample_meter', 'required_sample_meters', 'required_completion_date', 'completion_date', 'approval_result', 'is_ordered', 'customer_feedback', 'print_count', 'process', 'process_name', 'status', 'status_id', 'is_completed', 'has_started', 'progress_percentage', 'business_object_id', 'last_completed_state', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'status', 'status_id', 'is_completed', 'has_started', 'progress_percentage', 'business_object_id', 'last_completed_state', 'created_at', 'updated_at', 'print_count' ] def get_business_object_id(self, obj): """安全地获取 business_object_id""" return obj.business_object.id if obj.business_object else None def get_plate_image_url(self, obj): images = _serialize_plate_images(getattr(obj, 'plate_image', None), self.context.get('request')) return [entry.get('url') for entry in images if entry.get('url')] def get_process_name(self, obj): """获取流程名称""" if obj.process: try: from stateflow.models import Process process = Process.objects.get(id=obj.process) return process.name except Process.DoesNotExist: return None return None class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer): """开版订单创建/更新序列化器""" plate_image = PlateImageInputSerializer( many=True, required=False, allow_null=True, help_text="开版图片列表,需提供已上传的 file_id,可选 name 字段" ) class Meta: model = models.PlateOrder fields = [ "id", "design_code", "plate_type", "plate_date", "plate_method", "plate_image", "image_name", "plate_notes", "reprint_reason", "urgency_level", "is_invalid", "customer", "area", "default_address", "salesperson", "merchandiser", "designer", "style_name", "fabric", "fabric_source", "width", "production_method", "is_mark_frame", "drawing_rating", "color_matching_rating", "sample_rating", "difficulty_rating", "sample_meter", "required_sample_meters", "required_completion_date", "completion_date", "approval_result", "is_ordered", "customer_feedback", "process" ] read_only_fields = ["id"] def validate_plate_image(self, value): return value or [] def validate_customer(self, value): """验证客户是否存在""" if not value: raise serializers.ValidationError("客户不能为空") return value def validate_salesperson(self, value): """验证业务员是否存在""" if value and not Employee.objects.filter(id=value.id).exists(): raise serializers.ValidationError("业务员不存在") return value def validate_merchandiser(self, value): """验证跟单员是否存在""" if value and not Employee.objects.filter(id=value.id).exists(): raise serializers.ValidationError("跟单员不存在") return value def validate_designer(self, value): """验证设计师是否存在""" if value and not Employee.objects.filter(id=value.id).exists(): raise serializers.ValidationError("设计师不存在") return value def validate_sample_meter(self, value): """验证样品米数""" if value is not None and value < 0: raise serializers.ValidationError("样品米数不能为负数") return value def validate_required_sample_meters(self, value): """验证所需样品米数""" if value is not None and value < 0: raise serializers.ValidationError("所需样品米数不能为负数") return value def validate_process(self, value): """验证流程是否存在""" if value: from stateflow.models import Process if not Process.objects.filter(id=value).exists(): raise serializers.ValidationError("流程不存在") return value def validate(self, attrs): """交叉验证""" return attrs def create(self, validated_data): plate_images = validated_data.pop('plate_image', None) if plate_images is not None: validated_data['plate_image'] = _build_plate_image_payload(plate_images, self._request_user) return super().create(validated_data) def update(self, instance, validated_data): plate_images = validated_data.pop('plate_image', None) if plate_images is not None: validated_data['plate_image'] = _build_plate_image_payload(plate_images, self._request_user) return super().update(instance, validated_data) @property def _request_user(self): request = self.context.get('request') return getattr(request, 'user', None)