1
0
forked from erp-dev/erp

feat: salesitem create api

This commit is contained in:
2026-03-10 19:30:48 +08:00
parent e2b91f977b
commit f0b017de52
6 changed files with 1469 additions and 583 deletions

View File

@@ -1,6 +1,7 @@
"""
Shipment API 序列化器
"""
from rest_framework import serializers
from shipment.models import ExternalFinishedProduct, SalesItem, Shipment
@@ -10,49 +11,66 @@ 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)
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_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)
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)
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'
"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']
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)
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)
employee = getattr(obj.cancelled_by, "employee", None)
if employee:
return employee.name
return obj.cancelled_by.username
@@ -66,7 +84,7 @@ class ShipmentSerializer(serializers.ModelSerializer):
出货单关联的销售品明细(无则返回空数组)。
"""
# 优先使用 prefetch 的 related manager兜底为 none()
rel = getattr(obj, 'items', None)
rel = getattr(obj, "items", None)
items = list(rel.all()) if rel is not None else []
return SalesItemSerializer(items, many=True).data
@@ -74,7 +92,7 @@ class ShipmentSerializer(serializers.ModelSerializer):
"""
出货单关联的外部成品表明细(无则返回空数组)。
"""
rel = getattr(obj, 'external_finished_products', None)
rel = getattr(obj, "external_finished_products", None)
products = list(rel.all()) if rel is not None else []
return ExternalFinishedProductSerializer(products, many=True).data
@@ -83,25 +101,28 @@ class ExternalFinishedProductSerializer(serializers.ModelSerializer):
"""
外部成品表序列化器(只读)
"""
created_by_id = serializers.IntegerField(source='created_by.id', read_only=True, allow_null=True)
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',
"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)
employee = getattr(obj.created_by, "employee", None)
if employee:
return employee.name
return obj.created_by.username
@@ -112,57 +133,79 @@ 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='备注')
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列表'
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='')
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='外部订单号(必填)')
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='外部成品表结构数组(必填)'
help_text="外部成品表结构数组(必填)",
)
def validate_external_id(self, value):
value = (value or '').strip()
value = (value or "").strip()
if not value:
raise serializers.ValidationError('external_id 不能为空')
raise serializers.ValidationError("external_id 不能为空")
return value
def validate_external_finished_products(self, value):
if not value:
raise serializers.ValidationError('external_finished_products 不能为空')
raise serializers.ValidationError("external_finished_products 不能为空")
return value
@@ -172,19 +215,31 @@ class ShipmentUpdateSerializer(serializers.Serializer):
说明:本次新增字段 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='外部订单号(可选)')
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)
@@ -194,19 +249,92 @@ class SalesItemSerializer(serializers.Serializer):
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)
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_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)
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", ""),
)