This commit is contained in:
2026-06-09 13:44:53 +08:00
parent 5d73a7d036
commit fa2178fbd8
18 changed files with 168 additions and 40 deletions
+35 -5
View File
@@ -39,6 +39,24 @@ from .utils import generate_order_no
router = APIRouter(prefix="/purchase-orders", tags=["采购订单"])
def _compute_receipt_status(order: PurchaseOrder) -> str:
"""收货状态:已下单 / 部分收货 / 已收货 / 已作废"""
if order.status == "cancelled":
return "cancelled"
if order.status in ("received", "paid"):
return "received"
if order.status == "partial_received":
return "partial_received"
return "pending"
def _compute_payment_status(order: PurchaseOrder) -> str:
"""付款状态:未付款 / 已付款"""
if order.status == "paid" or order.paid_date:
return "paid"
return "unpaid"
def _build_purchase_order_response(order: PurchaseOrder, supplier_name: str) -> PurchaseOrderResponse:
return PurchaseOrderResponse(
id=order.id,
@@ -47,6 +65,8 @@ def _build_purchase_order_response(order: PurchaseOrder, supplier_name: str) ->
order_date=order.order_date,
expected_date=order.expected_date,
status=order.status,
receipt_status=_compute_receipt_status(order),
payment_status=_compute_payment_status(order),
total_amount=order.total_amount,
paid_amount=order.paid_amount,
remark=order.remark,
@@ -92,6 +112,8 @@ async def _build_purchase_order_detail(
order_date=order.order_date,
expected_date=order.expected_date,
status=order.status,
receipt_status=_compute_receipt_status(order),
payment_status=_compute_payment_status(order),
total_amount=order.total_amount,
paid_amount=order.paid_amount,
remark=order.remark,
@@ -319,19 +341,27 @@ async def update_purchase_order_status(
if not new_status:
raise HTTPException(status_code=400, detail="状态不能为空")
valid_statuses = ["pending", "partial_received", "received", "paid"]
valid_statuses = ["pending", "partial_received", "received", "paid", "cancelled"]
if new_status not in valid_statuses:
raise HTTPException(status_code=400, detail=f"无效的状态值,有效值为: {valid_statuses}")
# 状态转换逻辑
if order.status == "paid":
raise HTTPException(status_code=400, detail="已付款的采购订单禁止修改状态")
if order.status == "cancelled":
raise HTTPException(status_code=400, detail="已作废的采购订单禁止修改状态")
if order.status == "received" and new_status != "paid":
raise HTTPException(status_code=400, detail="已收货的采购订单只能标记为已付款")
if order.status == "received" and new_status not in ("paid", "cancelled"):
raise HTTPException(status_code=400, detail="已收货的采购订单只能标记为已付款或已作废")
if order.status == "partial_received" and new_status not in ("received", "paid"):
raise HTTPException(status_code=400, detail="部分收货的采购订单只能标记为已收货或已付款")
if order.status == "partial_received" and new_status not in ("received", "paid", "cancelled"):
raise HTTPException(status_code=400, detail="部分收货的采购订单只能标记为已收货、已付款或已作废")
if order.status == "pending" and new_status not in ("received", "cancelled"):
raise HTTPException(status_code=400, detail="待收货的采购订单只能标记为已收货或已作废")
if new_status == "cancelled" and order.status == "paid":
raise HTTPException(status_code=400, detail="已付款的订单不能作废")
# 更新状态和对应时间
order.status = new_status
+36 -5
View File
@@ -44,10 +44,26 @@ from .schemas import (
from .utils import generate_order_no
router = APIRouter(prefix="/sales-orders", tags=["销售订单"])
VALID_ORDER_STATUSES = {"draft", "manufacturing", "delivered", "paid"}
VALID_ORDER_STATUSES = {"manufacturing", "delivered", "paid", "cancelled"}
PRODUCTION_STATUSES = {"not_started", "bom_missing", "material_issued", "completed"}
def _compute_delivery_status(order: SalesOrder) -> str:
"""物流状态:制造中 / 已交付 / 已作废"""
if order.status == "cancelled":
return "cancelled"
if order.status in ("delivered", "paid") or order.actual_delivery_date:
return "delivered"
return "manufacturing"
def _compute_payment_status(order: SalesOrder) -> str:
"""收款状态:未收款 / 已收款"""
if order.status == "paid" or order.actual_payment_date:
return "paid"
return "unpaid"
def _build_sales_order_response(order: SalesOrder, customer_name: str) -> SalesOrderResponse:
return SalesOrderResponse(
id=order.id,
@@ -59,6 +75,8 @@ def _build_sales_order_response(order: SalesOrder, customer_name: str) -> SalesO
actual_delivery_date=order.actual_delivery_date,
actual_payment_date=order.actual_payment_date,
status=order.status,
delivery_status=_compute_delivery_status(order),
payment_status=_compute_payment_status(order),
production_status=order.production_status or "not_started",
production_no=order.production_no,
planned_material_cost=Decimal(str(order.planned_material_cost or 0)),
@@ -90,6 +108,8 @@ async def _build_sales_order_detail_response(
actual_delivery_date=order.actual_delivery_date,
actual_payment_date=order.actual_payment_date,
status=order.status,
delivery_status=_compute_delivery_status(order),
payment_status=_compute_payment_status(order),
production_status=order.production_status or "not_started",
production_no=order.production_no,
planned_material_cost=Decimal(str(order.planned_material_cost or 0)),
@@ -508,16 +528,27 @@ async def update_sales_order_status(
raise HTTPException(status_code=400, detail="订单状态必须为 manufacturing、delivered、paid")
order, customer = await _get_sales_order_with_customer(db_session, order_id)
# 只禁止从已交付状态改为非已收款状态
if order.status == "delivered" and payload.status != "paid":
raise HTTPException(status_code=400, detail="已交付的销售订单只能修改为已收款状态")
# 状态转换守卫
if order.status == "paid":
raise HTTPException(status_code=400, detail="已收款的销售订单禁止修改状态")
if order.status == "cancelled":
raise HTTPException(status_code=400, detail="已作废的销售订单禁止修改状态")
if order.status == "manufacturing" and payload.status == "paid":
raise HTTPException(status_code=400, detail="制造中的订单不能直接标记为已收款,请先标记为已交付")
if order.status == "delivered" and payload.status not in ("paid", "cancelled"):
raise HTTPException(status_code=400, detail="已交付的销售订单只能修改为已收款或已作废")
if payload.status == "cancelled" and order.status == "paid":
raise HTTPException(status_code=400, detail="已收款的订单不能作废,请联系管理员")
# 根据状态更新相应的日期字段
from datetime import datetime
if payload.status == "delivered" and not order.actual_delivery_date:
order.actual_delivery_date = datetime.now()
elif payload.status == "paid" and not order.actual_payment_date:
order.actual_payment_date = datetime.now()
elif payload.status == "cancelled" and not order.actual_delivery_date:
order.actual_delivery_date = datetime.now()
order.status = payload.status
await db_session.commit()
@@ -25,6 +25,8 @@ class PurchaseOrderResponse(BaseModel):
order_date: datetime
expected_date: Optional[date]
status: str
receipt_status: str # "pending" | "partial_received" | "received" | "cancelled"
payment_status: str # "unpaid" | "paid"
total_amount: Decimal
paid_amount: Decimal
remark: Optional[str]
@@ -32,6 +32,8 @@ class SalesOrderResponse(BaseModel):
actual_delivery_date: Optional[datetime]
actual_payment_date: Optional[datetime]
status: str
delivery_status: str # "manufacturing" | "delivered" | "cancelled"
payment_status: str # "unpaid" | "paid"
production_status: str
production_no: Optional[str]
planned_material_cost: Decimal