forked from erp-dev/erp
428 lines
14 KiB
Python
428 lines
14 KiB
Python
"""
|
||
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()
|
||
approved_by_id = serializers.IntegerField(
|
||
source="approved_by.id", read_only=True, allow_null=True
|
||
)
|
||
approved_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",
|
||
"status_modified_at",
|
||
"cancelled_by_id",
|
||
"cancelled_by_name",
|
||
"approved_by_id",
|
||
"approved_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_approved_by_name(self, obj):
|
||
if obj.approved_by:
|
||
employee = getattr(obj.approved_by, "employee", None)
|
||
if employee:
|
||
return employee.name
|
||
return obj.approved_by.username
|
||
return None
|
||
|
||
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)
|
||
printing_order_id = serializers.SerializerMethodField()
|
||
external_order_id = serializers.SerializerMethodField()
|
||
customer_id = serializers.IntegerField(read_only=True)
|
||
customer_name = serializers.SerializerMethodField()
|
||
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_printing_order_id(self, obj):
|
||
printing_order_map = self.context.get("printing_order_map", {})
|
||
if obj.printing_job_id in printing_order_map:
|
||
return printing_order_map[obj.printing_job_id]
|
||
|
||
printing_job = obj.get_printing_job()
|
||
if printing_job and getattr(printing_job, "printing_order_id", None):
|
||
return printing_job.printing_order_id
|
||
return None
|
||
|
||
def get_external_order_id(self, obj):
|
||
external_order_id_map = self.context.get("external_order_id_map", {})
|
||
if obj.printing_job_id in external_order_id_map:
|
||
return external_order_id_map[obj.printing_job_id]
|
||
|
||
printing_job = obj.get_printing_job()
|
||
if printing_job and getattr(printing_job.printing_order, "external_order_id", None):
|
||
return printing_job.printing_order.external_order_id
|
||
return None
|
||
|
||
def get_customer_name(self, obj):
|
||
customer_name_map = self.context.get("customer_name_map", {})
|
||
if obj.customer_id in customer_name_map:
|
||
return customer_name_map[obj.customer_id]
|
||
|
||
customer = obj.get_customer()
|
||
return customer.name if customer else None
|
||
|
||
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 SalesItemDetailSerializer(SalesItemSerializer):
|
||
"""
|
||
销售品详情序列化器。
|
||
|
||
在列表字段基础上补充关联印染任务的产品图片。
|
||
"""
|
||
|
||
product_image_url = serializers.SerializerMethodField()
|
||
|
||
def get_product_image_url(self, obj):
|
||
product_image_map = self.context.get("product_image_map", {})
|
||
if obj.printing_job_id in product_image_map:
|
||
return product_image_map[obj.printing_job_id]
|
||
|
||
printing_job = obj.get_printing_job()
|
||
if not printing_job or not getattr(printing_job, "product", None):
|
||
return None
|
||
|
||
primary_url = printing_job.product.get_primary_image_url()
|
||
if primary_url:
|
||
return primary_url
|
||
|
||
if printing_job.product.image:
|
||
request = self.context.get("request")
|
||
if request:
|
||
return request.build_absolute_uri(printing_job.product.image.url)
|
||
return printing_job.product.image.url
|
||
return None
|
||
|
||
|
||
class ShipmentSalesItemCustomerSerializer(serializers.Serializer):
|
||
"""
|
||
由未出货销售品反推的客户摘要序列化器。
|
||
"""
|
||
|
||
customer_id = serializers.IntegerField(source="id", read_only=True)
|
||
customer_name = serializers.CharField(source="name", read_only=True)
|
||
mobile = serializers.CharField(read_only=True, allow_null=True)
|
||
area = serializers.CharField(read_only=True, allow_null=True)
|
||
unshipped_sales_items_count = serializers.IntegerField(read_only=True)
|
||
|
||
|
||
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", ""),
|
||
)
|