forked from erp-dev/erp
feat: salesitem create api
This commit is contained in:
@@ -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", ""),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user