""" Shipment API 序列化器 """ from rest_framework import serializers from shipment.models import ExternalFinishedProduct, SalesItem, Shipment class ShipmentSerializer(serializers.ModelSerializer): """ 出货单序列化器(只读,用于返回数据) """ customer_name = serializers.CharField(source="customer.name", read_only=True) created_by_id = serializers.IntegerField( source="created_by.id", read_only=True, allow_null=True ) created_by_name = serializers.SerializerMethodField() items_count = serializers.SerializerMethodField() cancelled_by_id = serializers.IntegerField( source="cancelled_by.id", read_only=True, allow_null=True ) cancelled_by_name = serializers.SerializerMethodField() status_display = serializers.CharField(source="get_status_display", read_only=True) external_finished_products_count = serializers.SerializerMethodField() merchant_id = serializers.IntegerField(source="merchant.id", read_only=True) merchant_name = serializers.CharField(source="merchant.name", read_only=True) sales_items = serializers.SerializerMethodField() external_finished_products = serializers.SerializerMethodField() class Meta: model = Shipment fields = [ "id", "merchant_id", "merchant_name", "customer", "customer_name", "shipment_date", "area", "remark", "status", "status_display", "external_id", "cancelled_at", "cancelled_by_id", "cancelled_by_name", "items_count", "created_by_id", "created_by_name", "external_finished_products_count", "sales_items", "external_finished_products", "created_at", "updated_at", ] read_only_fields = ["id", "created_at", "updated_at"] def get_created_by_name(self, obj): if obj.created_by: employee = getattr(obj.created_by, "employee", None) if employee: return employee.name return obj.created_by.username return None def get_items_count(self, obj): return obj.items.count() def get_cancelled_by_name(self, obj): if obj.cancelled_by: employee = getattr(obj.cancelled_by, "employee", None) if employee: return employee.name return obj.cancelled_by.username return None def get_external_finished_products_count(self, obj): return obj.external_finished_products.count() def get_sales_items(self, obj): """ 出货单关联的销售品明细(无则返回空数组)。 """ # 优先使用 prefetch 的 related manager;兜底为 none() rel = getattr(obj, "items", None) items = list(rel.all()) if rel is not None else [] return SalesItemSerializer(items, many=True).data def get_external_finished_products(self, obj): """ 出货单关联的外部成品表明细(无则返回空数组)。 """ rel = getattr(obj, "external_finished_products", None) products = list(rel.all()) if rel is not None else [] return ExternalFinishedProductSerializer(products, many=True).data class ExternalFinishedProductSerializer(serializers.ModelSerializer): """ 外部成品表序列化器(只读) """ created_by_id = serializers.IntegerField( source="created_by.id", read_only=True, allow_null=True ) created_by_name = serializers.SerializerMethodField() class Meta: model = ExternalFinishedProduct fields = [ "id", "style_name", "num_of_rolls", "remark", "created_at", "created_by_id", "created_by_name", ] read_only_fields = fields def get_created_by_name(self, obj): if obj.created_by: employee = getattr(obj.created_by, "employee", None) if employee: return employee.name return obj.created_by.username return None class ShipmentCreateNormalSerializer(serializers.Serializer): """ 出货单创建序列化器(普通版) """ customer = serializers.IntegerField(help_text="客户ID") shipment_date = serializers.DateField(help_text="出货日期") area = serializers.CharField( max_length=30, required=False, default="", allow_blank=True, help_text="出货地区(可选)", ) remark = serializers.CharField( required=False, default="", allow_blank=True, help_text="备注" ) sales_items = serializers.ListField( child=serializers.IntegerField(), required=False, default=list, help_text="要关联的销售品ID列表", ) def validate_sales_items(self, value): # 去重 return list(set(value)) if value else [] class ExternalFinishedProductInputSerializer(serializers.Serializer): """外部成品表写入结构(external create 专用)""" style_name = serializers.CharField(max_length=200) num_of_rolls = serializers.IntegerField(min_value=0) remark = serializers.CharField( max_length=200, required=False, allow_blank=True, allow_null=True, default="" ) class ShipmentCreateExternalSerializer(serializers.Serializer): """ 出货单创建序列化器(external 版) 特点: - 不绑定任何 SalesItem - 必须提供 external_id - 同时写入 ExternalFinishedProduct 列表并关联到 Shipment """ customer = serializers.IntegerField(help_text="客户ID") shipment_date = serializers.DateField(help_text="出货日期") area = serializers.CharField( max_length=30, required=False, default="", allow_blank=True, help_text="出货地区(可选)", ) remark = serializers.CharField( required=False, default="", allow_blank=True, help_text="备注" ) external_id = serializers.CharField(max_length=120, help_text="外部订单号(必填)") external_finished_products = serializers.ListField( child=ExternalFinishedProductInputSerializer(), required=True, help_text="外部成品表结构数组(必填)", ) def validate_external_id(self, value): value = (value or "").strip() if not value: raise serializers.ValidationError("external_id 不能为空") return value def validate_external_finished_products(self, value): if not value: raise serializers.ValidationError("external_finished_products 不能为空") return value class ShipmentUpdateSerializer(serializers.Serializer): """ 出货单更新序列化器(PATCH/PUT) 说明:本次新增字段 area,需要保证更新入口可写入并回显。 """ customer = serializers.IntegerField(required=False, help_text="客户ID(可选)") shipment_date = serializers.DateField(required=False, help_text="出货日期(可选)") area = serializers.CharField( max_length=30, required=False, allow_blank=True, help_text="出货地区(可选)" ) remark = serializers.CharField( required=False, allow_blank=True, help_text="备注(可选)" ) external_id = serializers.CharField( max_length=120, required=False, allow_blank=True, allow_null=True, help_text="外部订单号(可选)", ) class SalesItemSerializer(serializers.Serializer): """ 销售品序列化器(只读) 用于返回销售品数据 """ id = serializers.IntegerField(read_only=True) name = serializers.CharField(read_only=True) quantity = serializers.DecimalField(max_digits=12, decimal_places=2, read_only=True) unit = serializers.IntegerField(read_only=True) unit_display = serializers.SerializerMethodField() position = serializers.CharField(read_only=True) remark = serializers.CharField(read_only=True) printing_job_id = serializers.IntegerField(read_only=True) customer_id = serializers.IntegerField(read_only=True) shipment_id = serializers.IntegerField( source="shipment.id", read_only=True, allow_null=True ) shipment_date = serializers.DateField( source="shipment.shipment_date", read_only=True, allow_null=True ) created_at = serializers.DateTimeField(read_only=True) created_by_id = serializers.IntegerField( source="created_by.id", read_only=True, allow_null=True ) created_by_name = serializers.SerializerMethodField() def get_unit_display(self, obj): return obj.get_unit_display() def get_created_by_name(self, obj): if obj.created_by: employee = getattr(obj.created_by, "employee", None) if employee: return employee.name return obj.created_by.username return None class SalesItemCreateSerializer(serializers.Serializer): """ 销售品创建序列化器 用于手动创建销售品(当自动转化开关关闭时使用) """ printing_job_id = serializers.IntegerField( required=True, min_value=1, help_text="生产任务ID(必填)" ) name = serializers.CharField( max_length=200, required=True, help_text="销售品名称(必填)" ) quantity = serializers.CharField( max_length=20, required=True, help_text="数量(必填,支持小数,如:100.50)" ) unit = serializers.IntegerField( required=True, help_text="单位(必填):1=米, 2=件, 3=码, 4=个" ) customer_id = serializers.IntegerField( required=False, allow_null=True, help_text="客户ID(可选,默认从生产订单获取)" ) remark = serializers.CharField( max_length=200, required=False, allow_blank=True, default="", help_text="备注(可选)", ) position = serializers.CharField( max_length=200, required=False, allow_blank=True, default="", help_text="货位(可选)", ) def validate_unit(self, value): """验证单位值是否在允许范围内""" from shipment.models import UnitChoices valid_units = [choice[0] for choice in UnitChoices.choices] if value not in valid_units: raise serializers.ValidationError( f"单位值无效。可选值:{dict(UnitChoices.choices)}" ) return value def create(self, validated_data): """创建销售品""" from shipment.services import create_sales_item # 获取当前用户(从context传入) created_by = self.context["request"].user return create_sales_item( printing_job_id=validated_data["printing_job_id"], name=validated_data["name"], quantity=validated_data["quantity"], unit=validated_data["unit"], created_by=created_by, customer_id=validated_data.get("customer_id"), remark=validated_data.get("remark", ""), position=validated_data.get("position", ""), )