""" Printing API 序列化器 """ import json from rest_framework import serializers from rest_framework.fields import empty from api_v1.models import UploadedFile from api_v1.utils.media import build_public_media_url from api_v1.views.shipment.serializers import SalesItemSerializer 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) 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 PlateImageInputListSerializer(serializers.ListSerializer): """ 兼容 plate_image 的多种入参形态(尤其是 multipart/form-data 场景): - JSON:直接传 array - multipart:常见会把数组作为 JSON 字符串传入(例如 '[{"file_id": 1}]') """ def to_internal_value(self, data): # allow_null=True 时,None 会先到这里 if data is None: return [] # multipart/form-data 下,前端常把数组序列化成字符串 if isinstance(data, str): raw = data.strip() if not raw: return [] try: data = json.loads(raw) except json.JSONDecodeError: raise serializers.ValidationError('plate_image 必须是 JSON 数组或可解析为数组的 JSON 字符串') # 兼容单个对象 if isinstance(data, dict): data = [data] return super().to_internal_value(data) def get_value(self, dictionary): """ DRF 在 multipart/form-data 下会优先用“HTML list”解析(期望 plate_image[0][file_id] 这类键)。 但前端常见做法是直接传一个字段 plate_image='[{"file_id":1}]'(JSON 字符串)。 这里做一次兜底:若 HTML list 未解析到值,则回退读取原始键值。 """ value = super().get_value(dictionary) if value is empty and hasattr(dictionary, 'get'): raw = dictionary.get(self.field_name, empty) if raw is not empty: return raw return value 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 Meta: list_serializer_class = PlateImageInputListSerializer 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) jobs_status_summary = serializers.SerializerMethodField() jobs_last_status_summary = serializers.SerializerMethodField() created_by = serializers.IntegerField(source='created_by_id', read_only=True) created_by_name = serializers.SerializerMethodField() merchant_id = serializers.IntegerField(source='merchant.id', read_only=True, allow_null=True) external_order_id = serializers.CharField(read_only=True) external_customer_id = serializers.CharField(read_only=True) external_customer_name = serializers.CharField(read_only=True) external_employee_name = serializers.CharField(read_only=True) class Meta: model = models.PrintingOrder fields = [ 'id', 'human_id', 'merchant_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', 'jobs_status_summary', 'jobs_last_status_summary', 'external_order_id', 'external_customer_id', 'external_customer_name', 'external_employee_name', 'created_by', 'created_by_name', 'created_at', 'updated_at', ] read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress', 'print_count', 'merchant_id'] def get_jobs_status_summary(self, obj): """ 返回订单下所有 PrintingJob 按当前状态分组的数量汇总 返回格式: [{"state_name": "进度一", "state_id": 1, "count": 3}, ...] - state_name: 状态名称(已完成的返回"已完成") - state_id: 状态ID(已完成的返回 None) - count: 该状态下的 job 数量 """ # 使用预取的 printing_jobs(避免 N+1) jobs = getattr(obj, '_prefetched_objects_cache', {}).get('printing_jobs') or obj.printing_jobs.all() from collections import Counter status_counter = Counter() for job in jobs: # 获取 job 的当前状态显示名称 status_name = job.status # 调用 @property,返回状态名或"已完成" status_id = job.status_id # 状态ID,已完成时为 None status_counter[(status_name, status_id)] += 1 # 转换为列表格式 return [ {'state_name': name, 'state_id': sid, 'count': count} for (name, sid), count in status_counter.items() ] def get_jobs_last_status_summary(self, obj): """ 返回订单下所有 PrintingJob 按最后完成状态分组的数量汇总 返回格式: [{"state_name": "进度一", "count": 3}, ...] - state_name: 最后完成的状态名称 - count: 该状态下的 job 数量 注意: 未开始的 job(没有完成任何状态)不计入统计 """ # 使用预取的 printing_jobs(避免 N+1) jobs = getattr(obj, '_prefetched_objects_cache', {}).get('printing_jobs') or obj.printing_jobs.all() from collections import Counter status_counter = Counter() for job in jobs: # 获取 job 的最后完成状态名称 last_completed = job.last_completed_state # 调用 @property,未开始时返回空字符串 # 只统计有已完成状态的 job if last_completed: status_counter[last_completed] += 1 # 转换为列表格式 return [ {'state_name': name, 'count': count} for name, count in status_counter.items() ] def get_created_by_name(self, obj): """获取创建人名称(员工姓名)""" user = getattr(obj, 'created_by', None) if not user: return None emp = getattr(user, 'employee', None) return getattr(emp, 'name', None) 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) created_by_name = serializers.SerializerMethodField() progress = serializers.IntegerField(read_only=True) merchant_id = serializers.IntegerField(source='merchant.id', read_only=True, allow_null=True) external_order_id = serializers.CharField(read_only=True) external_customer_id = serializers.CharField(read_only=True) external_customer_name = serializers.CharField(read_only=True) external_employee_name = serializers.CharField(read_only=True) class Meta: model = models.PrintingOrder fields = [ 'id', 'human_id', 'merchant_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', 'created_by_name', 'printing_warn', 'rolling_warn', 'production_warn', 'external_order_id', 'external_customer_id', 'external_customer_name', 'external_employee_name', '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', 'merchant_id'] def get_created_by_name(self, obj): """获取创建人名称(员工姓名)""" user = getattr(obj, 'created_by', None) if not user: return None emp = getattr(user, 'employee', None) return getattr(emp, 'name', None) class PrintingOrderCreateUpdateSerializer(serializers.ModelSerializer): """印染订单创建/更新序列化器""" # 兼容前端只传日期(YYYY-MM-DD):会解析为 00:00:00 # 同时也接受 ISO8601 datetime(含时区)。 outgoing_date = serializers.DateTimeField( required=False, allow_null=True, input_formats=[ "iso-8601", "%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", ], help_text="出货日期时间(可仅传 YYYY-MM-DD,将自动视为 00:00:00)", ) 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) external_order_id = serializers.CharField(source='printing_order.external_order_id', read_only=True) product_name = serializers.CharField(source='product.name', read_only=True) product_image_url = serializers.SerializerMethodField() status = serializers.CharField(read_only=True) work_state_display = serializers.SerializerMethodField() has_started = serializers.BooleanField(read_only=True) is_completed = serializers.BooleanField(read_only=True) is_production_completed = serializers.BooleanField(read_only=True) progress_percentage = serializers.FloatField(read_only=True) last_completed_state = serializers.CharField(read_only=True) business_object_id = serializers.SerializerMethodField() batch_advance_records = serializers.SerializerMethodField() saleitems = serializers.SerializerMethodField() merchant_id = serializers.IntegerField(source='merchant.id', read_only=True, allow_null=True) class Meta: model = models.PrintingJob fields = [ 'id', 'original_id', 'merchant_id', 'printing_order', 'printing_order_id', 'external_order_id', 'product', 'product_name', 'product_image_url', 'has_started', 'quantity', 'unit', 'size', 'pieces', 'description', 'work_state', 'work_state_display', 'status', 'is_completed', 'is_production_completed', 'progress_percentage', 'last_completed_state', 'business_object_id', 'batch_advance_records', 'saleitems', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'created_at', 'updated_at', 'status', 'is_completed', 'is_production_completed', 'progress_percentage', 'last_completed_state', 'business_object_id', 'merchant_id' ] def get_business_object_id(self, obj): """安全地获取 business_object_id""" return obj.business_object.id if obj.business_object else None def get_product_image_url(self, obj): """ 获取产品图片 URL 优先级: 1. mdy_image_url(明道云图片) 2. description JSON 中的图片 URL 3. image 字段(本地上传的图片) """ if not obj.product: return None # 先尝试 get_primary_image_url(mdy_image_url 或 description JSON) primary_url = obj.product.get_primary_image_url() if primary_url: return primary_url # fallback 到本地上传的 image 字段 if obj.product.image: request = self.context.get('request') if request: return request.build_absolute_uri(obj.product.image.url) return obj.product.image.url return None def get_work_state_display(self, obj): return obj.get_work_state_display() def get_batch_advance_records(self, obj): """ 批量推进记录(稳定输出 key) - 如果没有批量记录:返回 [] - 仅返回与该 job 相关的批量推进审计记录(按 created_at 倒序) """ # 注意:这里不要链式调用 select_related/order_by,否则会绕开 viewset 的 prefetch 缓存,导致 N+1 查询。 records = list(obj.batch_advance_records.all()) return PrintingJobBatchAdvanceRecordSerializer(records, many=True).data def get_saleitems(self, obj): items = getattr(obj, '_saleitems_cache', None) if items is None: from shipment.services import get_active_sales_items_queryset items = list( get_active_sales_items_queryset().select_related('shipment', 'created_by') .filter(printing_job_id=obj.id) .order_by('id') ) return SalesItemSerializer(items, many=True).data class PrintingJobDetailSerializer(serializers.ModelSerializer): """印染款式明细详情序列化器""" printing_order_id = serializers.CharField(source='printing_order.human_id', read_only=True) external_order_id = serializers.CharField(source='printing_order.external_order_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) is_production_completed = serializers.BooleanField(read_only=True) has_started = serializers.BooleanField(read_only=True) progress_percentage = serializers.FloatField(read_only=True) last_completed_state = serializers.CharField(read_only=True) business_object_id = serializers.SerializerMethodField() batch_advance_records = serializers.SerializerMethodField() saleitems = serializers.SerializerMethodField() merchant_id = serializers.IntegerField(source='merchant.id', read_only=True, allow_null=True) class Meta: model = models.PrintingJob fields = [ 'id', 'original_id', 'merchant_id', 'printing_order', 'printing_order_id', 'external_order_id', 'product', 'product_name', 'product_code', 'quantity', 'unit', 'size', 'pieces', 'description', 'work_state', 'work_state_display', 'status', 'status_id', 'is_completed', 'is_production_completed', 'has_started', 'progress_percentage', 'last_completed_state', 'business_object_id', 'batch_advance_records', 'saleitems', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'created_at', 'updated_at', 'status', 'status_id', 'is_completed', 'is_production_completed', 'has_started', 'progress_percentage', 'last_completed_state', 'business_object_id', 'merchant_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() def get_batch_advance_records(self, obj): """见 list serializer,同样稳定输出 key""" records = list(obj.batch_advance_records.all()) return PrintingJobBatchAdvanceRecordSerializer(records, many=True).data def get_saleitems(self, obj): items = getattr(obj, '_saleitems_cache', None) if items is None: from shipment.services import get_active_sales_items_queryset items = list( get_active_sales_items_queryset().select_related('shipment', 'created_by') .filter(printing_job_id=obj.id) .order_by('id') ) return SalesItemSerializer(items, many=True).data class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer): """印染款式明细创建/更新序列化器""" batch_advance_records = serializers.SerializerMethodField(read_only=True) class Meta: model = models.PrintingJob fields = [ 'id', 'original_id', 'printing_order', 'product', 'quantity', 'unit', 'size', 'pieces', 'description', 'work_state', 'batch_advance_records', ] 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 层""" # 不允许修改 printing_order 绑定关系(避免业务对象流程不一致) if 'printing_order' in validated_data: new_order = validated_data.get('printing_order') if new_order and new_order.id != getattr(instance, 'printing_order_id', None): raise serializers.ValidationError({'printing_order': '不允许修改印染订单绑定关系'}) 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 def get_batch_advance_records(self, obj): """创建/更新接口也稳定输出该 key(通常为空)""" records = list(obj.batch_advance_records.all()) return PrintingJobBatchAdvanceRecordSerializer(records, many=True).data class PrintingJobBatchAdvanceRecordSerializer(serializers.ModelSerializer): """印染任务批量操作记录(用于嵌入 PrintingJob 的序列化结果)""" state_id = serializers.IntegerField(source='state.id', read_only=True) state_name = serializers.CharField(source='state.name', read_only=True) created_by_username = serializers.CharField(source='created_by.username', read_only=True) created_by_name = serializers.SerializerMethodField() class Meta: model = models.PrintingJobBatchAdvanceRecord fields = [ 'id', 'printing_order', 'state', 'state_id', 'state_name', 'created_by', 'created_by_username', 'created_by_name', 'parameters', 'only_parameters', 'created_at', ] read_only_fields = fields def get_created_by_name(self, obj): user = getattr(obj, 'created_by', None) if not user: return None emp = getattr(user, 'employee', None) return getattr(emp, 'name', None) 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) created_by = serializers.IntegerField(source='created_by_id', 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) content_type_id = serializers.SerializerMethodField() created_by_name = serializers.SerializerMethodField() merchant_id = serializers.IntegerField(source='merchant.id', read_only=True, allow_null=True) class Meta: model = models.PlateOrder fields = [ 'id', 'original_id', 'merchant_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', 'created_by_name', 'status', 'status_id', 'is_completed', 'has_started', 'last_completed_state', 'progress_percentage', 'business_object_id', 'content_type_id', 'created_by', 'created_at', 'updated_at' ] read_only_fields = [ 'id', 'status', 'progress_percentage', 'last_completed_state', 'created_at', 'updated_at', 'print_count', 'merchant_id' ] 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_created_by_name(self, obj): user = getattr(obj, 'created_by', None) if not user: return None emp = getattr(user, 'employee', None) return getattr(emp, 'name', None) 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 def get_content_type_id(self, obj): """ 返回关联流程实例(business_object)的 content_type_id。 - 无 business_object:返回 None - business_object 存在但未绑定关联对象:返回 None """ if not getattr(obj, 'business_object_id', None): return None return obj.business_object.content_type_id 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) created_by = serializers.IntegerField(source='created_by_id', 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) merchant_id = serializers.IntegerField(source='merchant.id', read_only=True, allow_null=True) class Meta: model = models.PlateOrder fields = [ 'id', 'original_id', 'merchant_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_by', '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', 'merchant_id' ] 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 字段" ) created_by = serializers.IntegerField(source='created_by_id', read_only=True) class Meta: model = models.PlateOrder fields = [ "id", "original_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", "created_by", ] read_only_fields = ["id", "created_by"] 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): # 若更新涉及 process 变更,需要重建并重新绑定 BusinessObject(若有未撤销进度则拒绝) new_process_id = validated_data.get('process', None) if new_process_id is not None and new_process_id != getattr(instance, 'process', None): from stateflow.models import Process from stateflow.services import relink_business_object_for_instance try: new_process = Process.objects.get(id=new_process_id) except Process.DoesNotExist: # 理论上 validate_process 已校验;这里兜底 raise serializers.ValidationError({'process': '流程不存在'}) relinked = relink_business_object_for_instance( instance=instance, new_process=new_process, default_name=f"PlateOrder-{instance.pk}", default_description=f"开版订单 {instance.pk} 的流程实例", ) if relinked is None: raise serializers.ValidationError({'process': '该订单流程已存在有效进度,无法修改流程'}) 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)