forked from erp-dev/erp
feat: salesitem create api
This commit is contained in:
325
api_v1/urls.py
325
api_v1/urls.py
@@ -22,7 +22,11 @@ from .views.business.statements import views as statement_views
|
||||
from .views.business.pre_sales import views as pre_sales_views
|
||||
from .views.business.pre_purchase import views as pre_purchase_views
|
||||
from .views.stock_change_views.snapshot import StockSnapshotListView
|
||||
from .views.printing.views import PrintingOrderViewSet, PrintingJobViewSet, PlateOrderViewSet
|
||||
from .views.printing.views import (
|
||||
PrintingOrderViewSet,
|
||||
PrintingJobViewSet,
|
||||
PlateOrderViewSet,
|
||||
)
|
||||
from .views.upload import UploadFileViewSet
|
||||
from .views.products import ProductQuickViewSet
|
||||
from .views.parameters import StateParameterViewSet
|
||||
@@ -30,6 +34,7 @@ from .views.users import CreateUserWithProfileView
|
||||
from .views.mingdaoyun import MDYPlateOrderStagingViewSet
|
||||
from .views.shipment import (
|
||||
SalesItemByPrintingOrderView,
|
||||
SalesItemCreateView,
|
||||
ShipmentListCreateView,
|
||||
ShipmentDetailView,
|
||||
ShipmentExternalCreateView,
|
||||
@@ -38,132 +43,282 @@ from .views.settlement.views import PlateOrderSummaryView
|
||||
|
||||
# 创建 DRF Router for Stateflow
|
||||
stateflow_router = DefaultRouter()
|
||||
stateflow_router.register(r'states', stateflow.StateViewSet, basename='state')
|
||||
stateflow_router.register(r'processes', stateflow.ProcessViewSet, basename='process')
|
||||
stateflow_router.register(r'business-objects', stateflow.BusinessObjectViewSet, basename='business-object')
|
||||
stateflow_router.register(r"states", stateflow.StateViewSet, basename="state")
|
||||
stateflow_router.register(r"processes", stateflow.ProcessViewSet, basename="process")
|
||||
stateflow_router.register(
|
||||
r"business-objects", stateflow.BusinessObjectViewSet, basename="business-object"
|
||||
)
|
||||
|
||||
# 创建主 Router
|
||||
main_router = DefaultRouter()
|
||||
main_router.register(r'printing-orders', PrintingOrderViewSet, basename='printing-order')
|
||||
main_router.register(r'printing-jobs', PrintingJobViewSet, basename='printing-job')
|
||||
main_router.register(r'plate-orders', PlateOrderViewSet, basename='plate-order')
|
||||
main_router.register(r'upload', UploadFileViewSet, basename='upload')
|
||||
main_router.register(r'products/quick', ProductQuickViewSet, basename='product-quick')
|
||||
main_router.register(r'parameters', StateParameterViewSet, basename='parameter')
|
||||
main_router.register(r'mdy-plate-order-staging', MDYPlateOrderStagingViewSet, basename='mdy-plate-order-staging')
|
||||
main_router.register(
|
||||
r"printing-orders", PrintingOrderViewSet, basename="printing-order"
|
||||
)
|
||||
main_router.register(r"printing-jobs", PrintingJobViewSet, basename="printing-job")
|
||||
main_router.register(r"plate-orders", PlateOrderViewSet, basename="plate-order")
|
||||
main_router.register(r"upload", UploadFileViewSet, basename="upload")
|
||||
main_router.register(r"products/quick", ProductQuickViewSet, basename="product-quick")
|
||||
main_router.register(r"parameters", StateParameterViewSet, basename="parameter")
|
||||
main_router.register(
|
||||
r"mdy-plate-order-staging",
|
||||
MDYPlateOrderStagingViewSet,
|
||||
basename="mdy-plate-order-staging",
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# 库存变动相关API
|
||||
path('stock-snapshots/', StockSnapshotListView.as_view(), name='list_stock_snapshots'),
|
||||
path('stock-changes/', stock_change_views.list_stock_changes, name='list_stock_changes'),
|
||||
path('stock-change-details/', stock_change_views.list_stock_change_details, name='list_stock_change_details'),
|
||||
path('stock-change/', stock_change_views.create_full_stock_change, name='create_full_stock_change'),
|
||||
path('stock-change/relaxed/', stock_change_views.create_relaxed_stock_change, name='create_relaxed_stock_change'),
|
||||
path('stock-change/restrict/', stock_change_views.create_restrict_stock_change, name='create_restrict_stock_change'),
|
||||
path('stock-change/transfer/', stock_change_views.transfer_stock_change, name='stock_transfer'),
|
||||
path('stock-change/<int:record_id>/', stock_change_views.get_stock_change, name='get_stock_change'),
|
||||
path('stock-change/<int:record_id>/finish/', stock_change_views.finish_stock_change, name='finish_stock_change'),
|
||||
path('stock-change/<int:record_id>/offset/', stock_change_views.offset_stock_change, name='offset_stock_change'),
|
||||
path(
|
||||
'set-merchant-auto-complete-stock-change/',
|
||||
stock_change_views.set_merchant_auto_complete_stock_change,
|
||||
name='set_merchant_auto_complete_stock_change',
|
||||
"stock-snapshots/", StockSnapshotListView.as_view(), name="list_stock_snapshots"
|
||||
),
|
||||
path(
|
||||
"stock-changes/",
|
||||
stock_change_views.list_stock_changes,
|
||||
name="list_stock_changes",
|
||||
),
|
||||
path(
|
||||
"stock-change-details/",
|
||||
stock_change_views.list_stock_change_details,
|
||||
name="list_stock_change_details",
|
||||
),
|
||||
path(
|
||||
"stock-change/",
|
||||
stock_change_views.create_full_stock_change,
|
||||
name="create_full_stock_change",
|
||||
),
|
||||
path(
|
||||
"stock-change/relaxed/",
|
||||
stock_change_views.create_relaxed_stock_change,
|
||||
name="create_relaxed_stock_change",
|
||||
),
|
||||
path(
|
||||
"stock-change/restrict/",
|
||||
stock_change_views.create_restrict_stock_change,
|
||||
name="create_restrict_stock_change",
|
||||
),
|
||||
path(
|
||||
"stock-change/transfer/",
|
||||
stock_change_views.transfer_stock_change,
|
||||
name="stock_transfer",
|
||||
),
|
||||
path(
|
||||
"stock-change/<int:record_id>/",
|
||||
stock_change_views.get_stock_change,
|
||||
name="get_stock_change",
|
||||
),
|
||||
path(
|
||||
"stock-change/<int:record_id>/finish/",
|
||||
stock_change_views.finish_stock_change,
|
||||
name="finish_stock_change",
|
||||
),
|
||||
path(
|
||||
"stock-change/<int:record_id>/offset/",
|
||||
stock_change_views.offset_stock_change,
|
||||
name="offset_stock_change",
|
||||
),
|
||||
path(
|
||||
"set-merchant-auto-complete-stock-change/",
|
||||
stock_change_views.set_merchant_auto_complete_stock_change,
|
||||
name="set_merchant_auto_complete_stock_change",
|
||||
),
|
||||
|
||||
# 用户信息 API
|
||||
path('user-info/', user_info.user_info, name='user_info'),
|
||||
|
||||
path("user-info/", user_info.user_info, name="user_info"),
|
||||
# 用户创建 API
|
||||
path('users/create/', CreateUserWithProfileView.as_view(), name='create_user_with_profile'),
|
||||
|
||||
path(
|
||||
"users/create/",
|
||||
CreateUserWithProfileView.as_view(),
|
||||
name="create_user_with_profile",
|
||||
),
|
||||
# 库存查询 API
|
||||
path('inventory/', inventory.InventoryAPIView.as_view(), name='inventory'),
|
||||
path('purchase-orders/', purchase_views.PurchaseOrderView.as_view(), name='purchase_orders'),
|
||||
path('purchase-orders/<int:pk>/', purchase_views.PurchaseOrderDetailView.as_view(), name='purchase_order_detail'),
|
||||
path('purchase-orders/<int:pk>/review/', purchase_views.PurchaseOrderReviewView.as_view(), name='purchase_order_review'),
|
||||
path("inventory/", inventory.InventoryAPIView.as_view(), name="inventory"),
|
||||
path(
|
||||
'purchase-orders/<int:pk>/bind-stock-change/',
|
||||
"purchase-orders/",
|
||||
purchase_views.PurchaseOrderView.as_view(),
|
||||
name="purchase_orders",
|
||||
),
|
||||
path(
|
||||
"purchase-orders/<int:pk>/",
|
||||
purchase_views.PurchaseOrderDetailView.as_view(),
|
||||
name="purchase_order_detail",
|
||||
),
|
||||
path(
|
||||
"purchase-orders/<int:pk>/review/",
|
||||
purchase_views.PurchaseOrderReviewView.as_view(),
|
||||
name="purchase_order_review",
|
||||
),
|
||||
path(
|
||||
"purchase-orders/<int:pk>/bind-stock-change/",
|
||||
purchase_views.PurchaseOrderBindStockChangeView.as_view(),
|
||||
name='purchase_order_bind_stock_change',
|
||||
name="purchase_order_bind_stock_change",
|
||||
),
|
||||
path('purchase-return-orders/', purchase_return_views.PurchaseReturnOrderView.as_view(), name='purchase_return_orders'),
|
||||
path('purchase-return-orders/<int:pk>/', purchase_return_views.PurchaseReturnOrderDetailView.as_view(), name='purchase_return_order_detail'),
|
||||
path('purchase-return-orders/<int:pk>/review/', purchase_return_views.PurchaseReturnOrderReviewView.as_view(), name='purchase_return_order_review'),
|
||||
path('sales-orders/', sales_views.SalesOrderView.as_view(), name='sales_orders'),
|
||||
path('sales-orders/<int:pk>/', sales_views.SalesOrderDetailView.as_view(), name='sales_order_detail'),
|
||||
path('sales-orders/<int:pk>/review/', sales_views.SalesOrderReviewView.as_view(), name='sales_order_review'),
|
||||
path('sales-return-orders/', sales_return_views.SalesReturnOrderView.as_view(), name='sales_return_orders'),
|
||||
path('sales-return-orders/<int:pk>/', sales_return_views.SalesReturnOrderDetailView.as_view(), name='sales_return_order_detail'),
|
||||
path('sales-return-orders/<int:pk>/review/', sales_return_views.SalesReturnOrderReviewView.as_view(), name='sales_return_order_review'),
|
||||
path('pre-sales-orders/', pre_sales_views.PreSalesOrderView.as_view(), name='pre_sales_orders'),
|
||||
path('pre-sales-orders/<int:pk>/', pre_sales_views.PreSalesOrderDetailView.as_view(), name='pre_sales_order_detail'),
|
||||
path(
|
||||
'pre-sales-orders/<int:pk>/convert-to-sales/',
|
||||
"purchase-return-orders/",
|
||||
purchase_return_views.PurchaseReturnOrderView.as_view(),
|
||||
name="purchase_return_orders",
|
||||
),
|
||||
path(
|
||||
"purchase-return-orders/<int:pk>/",
|
||||
purchase_return_views.PurchaseReturnOrderDetailView.as_view(),
|
||||
name="purchase_return_order_detail",
|
||||
),
|
||||
path(
|
||||
"purchase-return-orders/<int:pk>/review/",
|
||||
purchase_return_views.PurchaseReturnOrderReviewView.as_view(),
|
||||
name="purchase_return_order_review",
|
||||
),
|
||||
path("sales-orders/", sales_views.SalesOrderView.as_view(), name="sales_orders"),
|
||||
path(
|
||||
"sales-orders/<int:pk>/",
|
||||
sales_views.SalesOrderDetailView.as_view(),
|
||||
name="sales_order_detail",
|
||||
),
|
||||
path(
|
||||
"sales-orders/<int:pk>/review/",
|
||||
sales_views.SalesOrderReviewView.as_view(),
|
||||
name="sales_order_review",
|
||||
),
|
||||
path(
|
||||
"sales-return-orders/",
|
||||
sales_return_views.SalesReturnOrderView.as_view(),
|
||||
name="sales_return_orders",
|
||||
),
|
||||
path(
|
||||
"sales-return-orders/<int:pk>/",
|
||||
sales_return_views.SalesReturnOrderDetailView.as_view(),
|
||||
name="sales_return_order_detail",
|
||||
),
|
||||
path(
|
||||
"sales-return-orders/<int:pk>/review/",
|
||||
sales_return_views.SalesReturnOrderReviewView.as_view(),
|
||||
name="sales_return_order_review",
|
||||
),
|
||||
path(
|
||||
"pre-sales-orders/",
|
||||
pre_sales_views.PreSalesOrderView.as_view(),
|
||||
name="pre_sales_orders",
|
||||
),
|
||||
path(
|
||||
"pre-sales-orders/<int:pk>/",
|
||||
pre_sales_views.PreSalesOrderDetailView.as_view(),
|
||||
name="pre_sales_order_detail",
|
||||
),
|
||||
path(
|
||||
"pre-sales-orders/<int:pk>/convert-to-sales/",
|
||||
pre_sales_views.PreSalesOrderConvertToSalesOrderView.as_view(),
|
||||
name='pre_sales_order_convert_to_sales',
|
||||
name="pre_sales_order_convert_to_sales",
|
||||
),
|
||||
path(
|
||||
'pre-sales-order-items/<int:item_id>/allocations/',
|
||||
"pre-sales-order-items/<int:item_id>/allocations/",
|
||||
pre_sales_views.PreSalesOrderItemAllocationView.as_view(),
|
||||
name='pre_sales_order_item_allocations',
|
||||
name="pre_sales_order_item_allocations",
|
||||
),
|
||||
path(
|
||||
'pre-sales-order-items/<int:item_id>/allocations/<int:pk>/',
|
||||
"pre-sales-order-items/<int:item_id>/allocations/<int:pk>/",
|
||||
pre_sales_views.PreSalesOrderItemAllocationDetailView.as_view(),
|
||||
name='pre_sales_order_item_allocation_detail',
|
||||
name="pre_sales_order_item_allocation_detail",
|
||||
),
|
||||
path(
|
||||
"pre-purchase-orders/",
|
||||
pre_purchase_views.PrePurchaseOrderView.as_view(),
|
||||
name="pre_purchase_orders",
|
||||
),
|
||||
path(
|
||||
"pre-purchase-orders/<int:pk>/",
|
||||
pre_purchase_views.PrePurchaseOrderDetailView.as_view(),
|
||||
name="pre_purchase_order_detail",
|
||||
),
|
||||
path(
|
||||
"payment-orders/",
|
||||
payment_views.PaymentOrderView.as_view(),
|
||||
name="payment_orders",
|
||||
),
|
||||
path(
|
||||
"payment-orders/<int:pk>/review/",
|
||||
payment_views.PaymentOrderReviewView.as_view(),
|
||||
name="payment_order_review",
|
||||
),
|
||||
path(
|
||||
"receipt-orders/",
|
||||
receipt_views.ReceiptOrderView.as_view(),
|
||||
name="receipt_orders",
|
||||
),
|
||||
path(
|
||||
"receipt-orders/<int:pk>/review/",
|
||||
receipt_views.ReceiptOrderReviewView.as_view(),
|
||||
name="receipt_order_review",
|
||||
),
|
||||
path(
|
||||
"customers/<int:customer_id>/balance/",
|
||||
balance_views.CustomerBalanceView.as_view(),
|
||||
name="customer_balance",
|
||||
),
|
||||
path(
|
||||
"suppliers/<int:supplier_id>/balance/",
|
||||
balance_views.SupplierBalanceView.as_view(),
|
||||
name="supplier_balance",
|
||||
),
|
||||
path(
|
||||
"customers/<int:customer_id>/statements/",
|
||||
statement_views.CustomerStatementView.as_view(),
|
||||
name="customer_statements",
|
||||
),
|
||||
path(
|
||||
"suppliers/<int:supplier_id>/statements/",
|
||||
statement_views.SupplierStatementView.as_view(),
|
||||
name="supplier_statements",
|
||||
),
|
||||
path(
|
||||
"statements/record/",
|
||||
statement_views.StatementRecordView.as_view(),
|
||||
name="statement_record",
|
||||
),
|
||||
path("health/", healthy.HealthCheckView.as_view(), name="health_check"),
|
||||
path(
|
||||
"print-count/delta/", print_count.adjust_print_count, name="print_count_delta"
|
||||
),
|
||||
path('pre-purchase-orders/', pre_purchase_views.PrePurchaseOrderView.as_view(), name='pre_purchase_orders'),
|
||||
path('pre-purchase-orders/<int:pk>/', pre_purchase_views.PrePurchaseOrderDetailView.as_view(), name='pre_purchase_order_detail'),
|
||||
path('payment-orders/', payment_views.PaymentOrderView.as_view(), name='payment_orders'),
|
||||
path('payment-orders/<int:pk>/review/', payment_views.PaymentOrderReviewView.as_view(), name='payment_order_review'),
|
||||
path('receipt-orders/', receipt_views.ReceiptOrderView.as_view(), name='receipt_orders'),
|
||||
path('receipt-orders/<int:pk>/review/', receipt_views.ReceiptOrderReviewView.as_view(), name='receipt_order_review'),
|
||||
path('customers/<int:customer_id>/balance/', balance_views.CustomerBalanceView.as_view(), name='customer_balance'),
|
||||
path('suppliers/<int:supplier_id>/balance/', balance_views.SupplierBalanceView.as_view(), name='supplier_balance'),
|
||||
path('customers/<int:customer_id>/statements/', statement_views.CustomerStatementView.as_view(), name='customer_statements'),
|
||||
path('suppliers/<int:supplier_id>/statements/', statement_views.SupplierStatementView.as_view(), name='supplier_statements'),
|
||||
path('statements/record/', statement_views.StatementRecordView.as_view(), name='statement_record'),
|
||||
path('health/', healthy.HealthCheckView.as_view(), name='health_check'),
|
||||
path('print-count/delta/', print_count.adjust_print_count, name='print_count_delta'),
|
||||
|
||||
# 产品图片上传 API
|
||||
path('products/<int:product_id>/image/', product_image.ProductImageUploadView.as_view(), name='product_image_upload'),
|
||||
|
||||
path(
|
||||
"products/<int:product_id>/image/",
|
||||
product_image.ProductImageUploadView.as_view(),
|
||||
name="product_image_upload",
|
||||
),
|
||||
# Tencent Cloud (TIIA) API
|
||||
path('tiia/search-image/', tiia.TiiaSearchImageView.as_view(), name='tiia_search_image'),
|
||||
|
||||
path(
|
||||
"tiia/search-image/",
|
||||
tiia.TiiaSearchImageView.as_view(),
|
||||
name="tiia_search_image",
|
||||
),
|
||||
# Stateflow API (使用 Router)
|
||||
path('stateflow/', include(stateflow_router.urls)),
|
||||
|
||||
path("stateflow/", include(stateflow_router.urls)),
|
||||
# Shipment API
|
||||
path(
|
||||
'shipment/shipments/',
|
||||
"shipment/shipments/",
|
||||
ShipmentListCreateView.as_view(),
|
||||
name='shipment_list_create'
|
||||
name="shipment_list_create",
|
||||
),
|
||||
path(
|
||||
'shipment/shipments/<int:pk>/',
|
||||
"shipment/shipments/<int:pk>/",
|
||||
ShipmentDetailView.as_view(),
|
||||
name='shipment_detail'
|
||||
name="shipment_detail",
|
||||
),
|
||||
path(
|
||||
'shipment/shipments/external/',
|
||||
"shipment/shipments/external/",
|
||||
ShipmentExternalCreateView.as_view(),
|
||||
name='shipment_create_external'
|
||||
name="shipment_create_external",
|
||||
),
|
||||
path(
|
||||
'shipment/sales-items/by-printing-order/<int:printing_order_id>/',
|
||||
"shipment/sales-items/by-printing-order/<int:printing_order_id>/",
|
||||
SalesItemByPrintingOrderView.as_view(),
|
||||
name='sales_items_by_printing_order'
|
||||
name="sales_items_by_printing_order",
|
||||
),
|
||||
path(
|
||||
"shipment/sales-items/", SalesItemCreateView.as_view(), name="sales_item_create"
|
||||
),
|
||||
|
||||
# Settlement API
|
||||
path(
|
||||
'settlement/plate-orders/summary/',
|
||||
"settlement/plate-orders/summary/",
|
||||
PlateOrderSummaryView.as_view(),
|
||||
name='plate_order_summary'
|
||||
name="plate_order_summary",
|
||||
),
|
||||
|
||||
# 主 Router (printing-orders 等)
|
||||
path('', include(main_router.urls)),
|
||||
path("", include(main_router.urls)),
|
||||
]
|
||||
|
||||
@@ -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", ""),
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Shipment API ViewSet
|
||||
"""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
@@ -23,10 +24,10 @@ from .serializers import (
|
||||
class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
"""
|
||||
出货单:查询列表 / 创建
|
||||
|
||||
|
||||
- GET /api/v1/shipment/shipments/
|
||||
- POST /api/v1/shipment/shipments/
|
||||
|
||||
|
||||
请求体:
|
||||
{
|
||||
"customer": 1,
|
||||
@@ -34,7 +35,7 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
"remark": "备注信息(可选)",
|
||||
"sales_items": [1, 2, 3]
|
||||
}
|
||||
|
||||
|
||||
返回:
|
||||
{
|
||||
"id": 1,
|
||||
@@ -49,6 +50,7 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
"updated_at": "2026-01-14T10:00:00Z"
|
||||
}
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = ShipmentSerializer
|
||||
pagination_class = LimitedLimitOffsetPagination
|
||||
@@ -64,70 +66,73 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
- shipment_date_from: 出货日期起始(YYYY-MM-DD)
|
||||
- shipment_date_to: 出货日期结束(YYYY-MM-DD,包含整天)
|
||||
"""
|
||||
qs = Shipment.objects.all().select_related(
|
||||
'merchant', 'customer', 'created_by', 'cancelled_by'
|
||||
).prefetch_related(
|
||||
'items',
|
||||
'external_finished_products',
|
||||
qs = (
|
||||
Shipment.objects.all()
|
||||
.select_related("merchant", "customer", "created_by", "cancelled_by")
|
||||
.prefetch_related(
|
||||
"items",
|
||||
"external_finished_products",
|
||||
)
|
||||
)
|
||||
|
||||
user = self.request.user
|
||||
if not getattr(user, 'is_superuser', False):
|
||||
emp = getattr(user, 'employee', None)
|
||||
merchant = getattr(emp, 'merchant', None) if emp else None
|
||||
if not getattr(user, "is_superuser", False):
|
||||
emp = getattr(user, "employee", None)
|
||||
merchant = getattr(emp, "merchant", None) if emp else None
|
||||
if not merchant:
|
||||
return Shipment.objects.none()
|
||||
qs = qs.filter(merchant=merchant)
|
||||
|
||||
# optional filters
|
||||
customer_id = self.request.query_params.get('customer')
|
||||
customer_id = self.request.query_params.get("customer")
|
||||
if customer_id:
|
||||
qs = qs.filter(customer_id=customer_id)
|
||||
|
||||
status_val = self.request.query_params.get('status')
|
||||
status_val = self.request.query_params.get("status")
|
||||
if status_val:
|
||||
qs = qs.filter(status=status_val)
|
||||
|
||||
external_id = self.request.query_params.get('external_id')
|
||||
external_id = self.request.query_params.get("external_id")
|
||||
if external_id:
|
||||
qs = qs.filter(external_id=external_id)
|
||||
|
||||
date_from = self.request.query_params.get('shipment_date_from')
|
||||
date_from = self.request.query_params.get("shipment_date_from")
|
||||
if date_from:
|
||||
qs = qs.filter(shipment_date__gte=date_from)
|
||||
|
||||
date_to = self.request.query_params.get('shipment_date_to')
|
||||
date_to = self.request.query_params.get("shipment_date_to")
|
||||
if date_to:
|
||||
# 通过 <= 过滤日期
|
||||
qs = qs.filter(shipment_date__lte=date_to)
|
||||
|
||||
return qs.order_by('-created_at', '-id')
|
||||
return qs.order_by("-created_at", "-id")
|
||||
|
||||
def get(self, request):
|
||||
return self.list(request)
|
||||
|
||||
|
||||
def post(self, request):
|
||||
# 验证请求数据
|
||||
serializer = ShipmentCreateNormalSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
data = serializer.validated_data
|
||||
|
||||
|
||||
# 调用业务逻辑
|
||||
from shipment.services import create_shipment
|
||||
|
||||
try:
|
||||
shipment = create_shipment(
|
||||
customer_id=data['customer'],
|
||||
shipment_date=data['shipment_date'],
|
||||
sales_item_ids=data['sales_items'],
|
||||
customer_id=data["customer"],
|
||||
shipment_date=data["shipment_date"],
|
||||
sales_item_ids=data["sales_items"],
|
||||
created_by=request.user,
|
||||
remark=data.get('remark', ''),
|
||||
area=data.get('area', ''),
|
||||
remark=data.get("remark", ""),
|
||||
area=data.get("area", ""),
|
||||
)
|
||||
except ValueError as e:
|
||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 返回创建的出货单
|
||||
response_serializer = ShipmentSerializer(shipment)
|
||||
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
||||
@@ -139,23 +144,26 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView):
|
||||
|
||||
GET /api/v1/shipment/shipments/<id>/
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = ShipmentSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
qs = Shipment.objects.all().select_related(
|
||||
'merchant', 'customer', 'created_by', 'cancelled_by'
|
||||
).prefetch_related(
|
||||
'items',
|
||||
'external_finished_products',
|
||||
qs = (
|
||||
Shipment.objects.all()
|
||||
.select_related("merchant", "customer", "created_by", "cancelled_by")
|
||||
.prefetch_related(
|
||||
"items",
|
||||
"external_finished_products",
|
||||
)
|
||||
)
|
||||
|
||||
user = self.request.user
|
||||
if getattr(user, 'is_superuser', False):
|
||||
if getattr(user, "is_superuser", False):
|
||||
return qs
|
||||
|
||||
emp = getattr(user, 'employee', None)
|
||||
merchant = getattr(emp, 'merchant', None) if emp else None
|
||||
emp = getattr(user, "employee", None)
|
||||
merchant = getattr(emp, "merchant", None) if emp else None
|
||||
if not merchant:
|
||||
return Shipment.objects.none()
|
||||
return qs.filter(merchant=merchant)
|
||||
@@ -175,24 +183,30 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView):
|
||||
data = serializer.validated_data
|
||||
|
||||
# customer 变更需校验 merchant 一致
|
||||
if 'customer' in data:
|
||||
if "customer" in data:
|
||||
from basic_info.models import Customer
|
||||
|
||||
try:
|
||||
customer = Customer.objects.get(id=data['customer'])
|
||||
customer = Customer.objects.get(id=data["customer"])
|
||||
except Customer.DoesNotExist:
|
||||
return Response({'detail': f'客户 {data["customer"]} 不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{"detail": f"客户 {data['customer']} 不存在"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if customer.merchant_id != shipment.merchant_id:
|
||||
return Response({'detail': '无权限绑定该客户'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{"detail": "无权限绑定该客户"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
shipment.customer = customer
|
||||
|
||||
if 'shipment_date' in data:
|
||||
shipment.shipment_date = data['shipment_date']
|
||||
if 'area' in data:
|
||||
shipment.area = (data.get('area') or '').strip()
|
||||
if 'remark' in data:
|
||||
shipment.remark = data.get('remark') or ''
|
||||
if 'external_id' in data:
|
||||
shipment.external_id = (data.get('external_id') or '').strip() or None
|
||||
if "shipment_date" in data:
|
||||
shipment.shipment_date = data["shipment_date"]
|
||||
if "area" in data:
|
||||
shipment.area = (data.get("area") or "").strip()
|
||||
if "remark" in data:
|
||||
shipment.remark = data.get("remark") or ""
|
||||
if "external_id" in data:
|
||||
shipment.external_id = (data.get("external_id") or "").strip() or None
|
||||
|
||||
shipment.save()
|
||||
return Response(ShipmentSerializer(shipment).data, status=status.HTTP_200_OK)
|
||||
@@ -208,14 +222,15 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView):
|
||||
class ShipmentExternalCreateView(APIView):
|
||||
"""
|
||||
创建出货单(external 版)
|
||||
|
||||
|
||||
POST /api/v1/shipment/shipments/external/
|
||||
|
||||
|
||||
特点:
|
||||
- external_id 必填
|
||||
- external_finished_products 必填(数组)
|
||||
- 不绑定任何销售品
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
@@ -226,18 +241,19 @@ class ShipmentExternalCreateView(APIView):
|
||||
data = serializer.validated_data
|
||||
|
||||
from shipment.services import create_external_shipment
|
||||
|
||||
try:
|
||||
shipment = create_external_shipment(
|
||||
customer_id=data['customer'],
|
||||
shipment_date=data['shipment_date'],
|
||||
external_id=data['external_id'],
|
||||
external_finished_products=data['external_finished_products'],
|
||||
customer_id=data["customer"],
|
||||
shipment_date=data["shipment_date"],
|
||||
external_id=data["external_id"],
|
||||
external_finished_products=data["external_finished_products"],
|
||||
created_by=request.user,
|
||||
remark=data.get('remark', ''),
|
||||
area=data.get('area', ''),
|
||||
remark=data.get("remark", ""),
|
||||
area=data.get("area", ""),
|
||||
)
|
||||
except ValueError as e:
|
||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
response_serializer = ShipmentSerializer(shipment)
|
||||
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
||||
@@ -246,14 +262,14 @@ class ShipmentExternalCreateView(APIView):
|
||||
class SalesItemByPrintingOrderView(APIView):
|
||||
"""
|
||||
通过生产订单查询销售品
|
||||
|
||||
|
||||
GET /api/v1/shipment/sales-items/by-printing-order/<printing_order_id>/
|
||||
|
||||
|
||||
返回与该 PrintingOrder 下所有 PrintingJob 关联的 SalesItem 列表。
|
||||
|
||||
|
||||
查询参数:
|
||||
- include_already_has_shipment: 是否包含已关联出货单的销售品(true/false),默认 false
|
||||
|
||||
|
||||
返回:
|
||||
{
|
||||
"count": 5,
|
||||
@@ -278,35 +294,105 @@ class SalesItemByPrintingOrderView(APIView):
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
|
||||
def get(self, request, printing_order_id):
|
||||
# 验证生产订单是否存在
|
||||
from printing.models import PrintingOrder
|
||||
|
||||
try:
|
||||
printing_order = PrintingOrder.objects.get(id=printing_order_id)
|
||||
except PrintingOrder.DoesNotExist:
|
||||
return Response(
|
||||
{'detail': f'生产订单 {printing_order_id} 不存在'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
{"detail": f"生产订单 {printing_order_id} 不存在"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
|
||||
# 获取查询参数
|
||||
include_already_has_shipment = request.query_params.get(
|
||||
'include_already_has_shipment', 'false'
|
||||
).lower() == 'true'
|
||||
|
||||
include_already_has_shipment = (
|
||||
request.query_params.get("include_already_has_shipment", "false").lower()
|
||||
== "true"
|
||||
)
|
||||
|
||||
# 调用 shipment 业务逻辑
|
||||
from shipment.services import get_sales_items_by_printing_order
|
||||
|
||||
sales_items = get_sales_items_by_printing_order(
|
||||
printing_order_id=printing_order.id,
|
||||
include_already_has_shipment=include_already_has_shipment,
|
||||
)
|
||||
|
||||
|
||||
# 序列化返回
|
||||
serializer = SalesItemSerializer(sales_items, many=True)
|
||||
|
||||
return Response({
|
||||
'count': len(serializer.data),
|
||||
'results': serializer.data
|
||||
})
|
||||
|
||||
return Response({"count": len(serializer.data), "results": serializer.data})
|
||||
|
||||
|
||||
class SalesItemCreateView(APIView):
|
||||
"""
|
||||
手动创建销售品
|
||||
|
||||
POST /api/v1/shipment/sales-items/
|
||||
|
||||
当自动转化销售品开关关闭时,通过此接口手动创建销售品。
|
||||
|
||||
请求体:
|
||||
{
|
||||
"printing_job_id": 123,
|
||||
"name": "产品名称",
|
||||
"quantity": "100.50",
|
||||
"unit": 1,
|
||||
"customer_id": 456,
|
||||
"remark": "备注信息",
|
||||
"position": "A1-01"
|
||||
}
|
||||
|
||||
参数说明:
|
||||
- printing_job_id: 生产任务ID(必填)
|
||||
- name: 销售品名称(必填)
|
||||
- quantity: 数量(必填,支持小数)
|
||||
- unit: 单位(必填):1=米, 2=件, 3=码, 4=个
|
||||
- customer_id: 客户ID(可选,默认从生产订单获取)
|
||||
- remark: 备注(可选)
|
||||
- position: 货位(可选)
|
||||
|
||||
返回:
|
||||
{
|
||||
"id": 1,
|
||||
"name": "产品名称",
|
||||
"quantity": "100.50",
|
||||
"unit": 1,
|
||||
"unit_display": "米",
|
||||
"position": "A1-01",
|
||||
"remark": "备注信息",
|
||||
"printing_job_id": 123,
|
||||
"customer_id": 456,
|
||||
"shipment_id": null,
|
||||
"shipment_date": null,
|
||||
"created_at": "2026-01-14T10:00:00Z",
|
||||
"created_by_id": 1,
|
||||
"created_by_name": "张三"
|
||||
}
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
from .serializers import SalesItemCreateSerializer
|
||||
|
||||
serializer = SalesItemCreateSerializer(
|
||||
data=request.data, context={"request": request}
|
||||
)
|
||||
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
sales_item = serializer.save()
|
||||
except ValueError as e:
|
||||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 返回创建的销售品
|
||||
response_serializer = SalesItemSerializer(sales_item)
|
||||
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
Reference in New Issue
Block a user