forked from erp-dev/erp
feat: shipment_delivery
This commit is contained in:
@@ -4,7 +4,61 @@ Shipment API 序列化器
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from shipment.models import ExternalFinishedProduct, SalesItem, Shipment
|
||||
from shipment.models import (
|
||||
ExternalFinishedProduct,
|
||||
SalesItem,
|
||||
Shipment,
|
||||
ShipmentDelivery,
|
||||
ShipmentDeliveryStatus,
|
||||
)
|
||||
|
||||
|
||||
def _build_nested_sales_item_context(items):
|
||||
customer_ids = {item.customer_id for item in items if item.customer_id}
|
||||
printing_job_ids = {item.printing_job_id for item in items if item.printing_job_id}
|
||||
|
||||
customer_name_map = {}
|
||||
if customer_ids:
|
||||
from basic_info.models import Customer
|
||||
|
||||
customer_name_map = dict(
|
||||
Customer.objects.filter(id__in=customer_ids).values_list("id", "name")
|
||||
)
|
||||
|
||||
printing_order_map = {}
|
||||
external_order_id_map = {}
|
||||
product_image_map = {}
|
||||
if printing_job_ids:
|
||||
from printing.models import PrintingJob
|
||||
|
||||
printing_jobs = list(
|
||||
PrintingJob.objects.filter(id__in=printing_job_ids).select_related(
|
||||
"printing_order", "product"
|
||||
)
|
||||
)
|
||||
printing_order_map = {job.id: job.printing_order_id for job in printing_jobs}
|
||||
external_order_id_map = {
|
||||
job.id: getattr(job.printing_order, "external_order_id", None)
|
||||
for job in printing_jobs
|
||||
}
|
||||
for job in printing_jobs:
|
||||
if not getattr(job, "product", None):
|
||||
product_image_map[job.id] = None
|
||||
continue
|
||||
primary_url = job.product.get_primary_image_url()
|
||||
if primary_url:
|
||||
product_image_map[job.id] = primary_url
|
||||
elif job.product.image:
|
||||
product_image_map[job.id] = job.product.image.url
|
||||
else:
|
||||
product_image_map[job.id] = None
|
||||
|
||||
return {
|
||||
"customer_name_map": customer_name_map,
|
||||
"printing_order_map": printing_order_map,
|
||||
"external_order_id_map": external_order_id_map,
|
||||
"product_image_map": product_image_map,
|
||||
}
|
||||
|
||||
|
||||
class ShipmentSerializer(serializers.ModelSerializer):
|
||||
@@ -42,6 +96,9 @@ class ShipmentSerializer(serializers.ModelSerializer):
|
||||
"customer",
|
||||
"customer_name",
|
||||
"shipment_date",
|
||||
"address",
|
||||
"contact_name",
|
||||
"contact_phone",
|
||||
"area",
|
||||
"remark",
|
||||
"status",
|
||||
@@ -100,7 +157,13 @@ class ShipmentSerializer(serializers.ModelSerializer):
|
||||
# 优先使用 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
|
||||
serializer_context = dict(self.context)
|
||||
serializer_context.update(_build_nested_sales_item_context(items))
|
||||
return SalesItemDetailSerializer(
|
||||
items,
|
||||
many=True,
|
||||
context=serializer_context,
|
||||
).data
|
||||
|
||||
def get_external_finished_products(self, obj):
|
||||
"""
|
||||
@@ -150,6 +213,27 @@ class ShipmentCreateNormalSerializer(serializers.Serializer):
|
||||
|
||||
customer = serializers.IntegerField(help_text="客户ID")
|
||||
shipment_date = serializers.DateField(help_text="出货日期")
|
||||
address = serializers.CharField(
|
||||
max_length=255,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="地址(可选)",
|
||||
)
|
||||
contact_name = serializers.CharField(
|
||||
max_length=100,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="联系人(可选)",
|
||||
)
|
||||
contact_phone = serializers.CharField(
|
||||
max_length=50,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="联系电话(可选)",
|
||||
)
|
||||
area = serializers.CharField(
|
||||
max_length=30,
|
||||
required=False,
|
||||
@@ -194,6 +278,27 @@ class ShipmentCreateExternalSerializer(serializers.Serializer):
|
||||
|
||||
customer = serializers.IntegerField(help_text="客户ID")
|
||||
shipment_date = serializers.DateField(help_text="出货日期")
|
||||
address = serializers.CharField(
|
||||
max_length=255,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="地址(可选)",
|
||||
)
|
||||
contact_name = serializers.CharField(
|
||||
max_length=100,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="联系人(可选)",
|
||||
)
|
||||
contact_phone = serializers.CharField(
|
||||
max_length=50,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="联系电话(可选)",
|
||||
)
|
||||
area = serializers.CharField(
|
||||
max_length=30,
|
||||
required=False,
|
||||
@@ -232,6 +337,15 @@ class ShipmentUpdateSerializer(serializers.Serializer):
|
||||
|
||||
customer = serializers.IntegerField(required=False, help_text="客户ID(可选)")
|
||||
shipment_date = serializers.DateField(required=False, help_text="出货日期(可选)")
|
||||
address = serializers.CharField(
|
||||
max_length=255, required=False, allow_blank=True, help_text="地址(可选)"
|
||||
)
|
||||
contact_name = serializers.CharField(
|
||||
max_length=100, required=False, allow_blank=True, help_text="联系人(可选)"
|
||||
)
|
||||
contact_phone = serializers.CharField(
|
||||
max_length=50, required=False, allow_blank=True, help_text="联系电话(可选)"
|
||||
)
|
||||
area = serializers.CharField(
|
||||
max_length=30, required=False, allow_blank=True, help_text="出货地区(可选)"
|
||||
)
|
||||
@@ -360,6 +474,194 @@ class ShipmentSalesItemCustomerSerializer(serializers.Serializer):
|
||||
unshipped_sales_items_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
||||
class ShipmentDeliveryShipmentSummarySerializer(serializers.ModelSerializer):
|
||||
customer_name = serializers.CharField(source="customer.name", read_only=True)
|
||||
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Shipment
|
||||
fields = [
|
||||
"id",
|
||||
"customer",
|
||||
"customer_name",
|
||||
"shipment_date",
|
||||
"status",
|
||||
"status_display",
|
||||
"external_id",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class ShipmentDeliverySerializer(serializers.ModelSerializer):
|
||||
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
||||
merchant_id = serializers.IntegerField(source="merchant.id", read_only=True)
|
||||
merchant_name = serializers.CharField(source="merchant.name", read_only=True)
|
||||
created_by_id = serializers.IntegerField(
|
||||
source="created_by.id", read_only=True, allow_null=True
|
||||
)
|
||||
created_by_name = serializers.SerializerMethodField()
|
||||
operator_id = serializers.IntegerField(
|
||||
source="operator.id", read_only=True, allow_null=True
|
||||
)
|
||||
operator_name = serializers.CharField(source="operator.name", read_only=True, allow_null=True)
|
||||
cancelled_by_id = serializers.IntegerField(
|
||||
source="cancelled_by.id", read_only=True, allow_null=True
|
||||
)
|
||||
cancelled_by_name = serializers.SerializerMethodField()
|
||||
shipments_count = serializers.SerializerMethodField()
|
||||
shipments = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = ShipmentDelivery
|
||||
fields = [
|
||||
"id",
|
||||
"merchant_id",
|
||||
"merchant_name",
|
||||
"driver_name",
|
||||
"vehicle_trip",
|
||||
"contact_phone",
|
||||
"vehicle_capacity",
|
||||
"remark",
|
||||
"internal_remark",
|
||||
"status",
|
||||
"status_display",
|
||||
"started_at",
|
||||
"delivered_at",
|
||||
"cancelled_at",
|
||||
"shipments_count",
|
||||
"shipments",
|
||||
"created_by_id",
|
||||
"created_by_name",
|
||||
"operator_id",
|
||||
"operator_name",
|
||||
"cancelled_by_id",
|
||||
"cancelled_by_name",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
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
|
||||
|
||||
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_shipments_count(self, obj):
|
||||
return obj.shipments.count()
|
||||
|
||||
def get_shipments(self, obj):
|
||||
return ShipmentDeliveryShipmentSummarySerializer(
|
||||
obj.shipments.order_by("id"),
|
||||
many=True,
|
||||
).data
|
||||
|
||||
|
||||
class ShipmentDeliveryCreateSerializer(serializers.Serializer):
|
||||
driver_name = serializers.CharField(max_length=100, help_text="司机名")
|
||||
vehicle_trip = serializers.CharField(max_length=100, help_text="车次")
|
||||
contact_phone = serializers.CharField(
|
||||
max_length=50,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="联系电话(可选)",
|
||||
)
|
||||
vehicle_capacity = serializers.CharField(
|
||||
max_length=100,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="车辆容量(可选)",
|
||||
)
|
||||
remark = serializers.CharField(
|
||||
max_length=200,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="备注(可选)",
|
||||
)
|
||||
internal_remark = serializers.CharField(
|
||||
max_length=200,
|
||||
required=False,
|
||||
default="",
|
||||
allow_blank=True,
|
||||
help_text="内部备注(可选)",
|
||||
)
|
||||
shipments = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
required=False,
|
||||
default=list,
|
||||
help_text="要关联的出货单ID列表",
|
||||
)
|
||||
|
||||
def validate_shipments(self, value):
|
||||
return list(dict.fromkeys(value)) if value else []
|
||||
|
||||
|
||||
class ShipmentDeliveryUpdateSerializer(serializers.Serializer):
|
||||
driver_name = serializers.CharField(
|
||||
max_length=100, required=False, allow_blank=False, help_text="司机名(可选)"
|
||||
)
|
||||
vehicle_trip = serializers.CharField(
|
||||
max_length=100, required=False, allow_blank=False, help_text="车次(可选)"
|
||||
)
|
||||
contact_phone = serializers.CharField(
|
||||
max_length=50, required=False, allow_blank=True, help_text="联系电话(可选)"
|
||||
)
|
||||
vehicle_capacity = serializers.CharField(
|
||||
max_length=100, required=False, allow_blank=True, help_text="车辆容量(可选)"
|
||||
)
|
||||
remark = serializers.CharField(
|
||||
max_length=200, required=False, allow_blank=True, help_text="备注(可选)"
|
||||
)
|
||||
internal_remark = serializers.CharField(
|
||||
max_length=200, required=False, allow_blank=True, help_text="内部备注(可选)"
|
||||
)
|
||||
shipments = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
required=False,
|
||||
help_text="要绑定的出货单ID列表(可选,传入即视为替换)",
|
||||
)
|
||||
|
||||
def validate_shipments(self, value):
|
||||
return list(dict.fromkeys(value)) if value else []
|
||||
|
||||
|
||||
class ShipmentDeliveryStatusUpdateSerializer(serializers.Serializer):
|
||||
status = serializers.ChoiceField(
|
||||
choices=[
|
||||
(ShipmentDeliveryStatus.PENDING, ShipmentDeliveryStatus.PENDING.label),
|
||||
(ShipmentDeliveryStatus.IN_TRANSIT, ShipmentDeliveryStatus.IN_TRANSIT.label),
|
||||
(ShipmentDeliveryStatus.DELIVERED, ShipmentDeliveryStatus.DELIVERED.label),
|
||||
],
|
||||
help_text="送货单状态(1=待送货, 2=送货中, 3=已送达)",
|
||||
)
|
||||
|
||||
|
||||
class ShipmentDeliveryBindShipmentsSerializer(serializers.Serializer):
|
||||
shipments = serializers.ListField(
|
||||
child=serializers.IntegerField(),
|
||||
required=True,
|
||||
help_text="要追加绑定到当前送货单的出货单ID列表",
|
||||
)
|
||||
|
||||
def validate_shipments(self, value):
|
||||
if not value:
|
||||
raise serializers.ValidationError("shipments 不能为空")
|
||||
return list(dict.fromkeys(value))
|
||||
|
||||
|
||||
class SalesItemCreateSerializer(serializers.Serializer):
|
||||
"""
|
||||
销售品创建序列化器
|
||||
|
||||
Reference in New Issue
Block a user