From 434bc592665f86071d079629ebcdd4c6d0eb8db4 Mon Sep 17 00:00:00 2001 From: SZCJW <792430652@qq.com> Date: Wed, 18 Mar 2026 00:07:53 +0800 Subject: [PATCH] x --- src/api/inventory/sales_order_routes.py | 61 ++++++++++++++--- src/api/inventory/schemas/__init__.py | 5 +- .../inventory/schemas/sales_order_schemas.py | 10 ++- static/vue-app.js | 66 ++++++++++++++----- 4 files changed, 115 insertions(+), 27 deletions(-) diff --git a/src/api/inventory/sales_order_routes.py b/src/api/inventory/sales_order_routes.py index 30816a6..a896ada 100644 --- a/src/api/inventory/sales_order_routes.py +++ b/src/api/inventory/sales_order_routes.py @@ -35,11 +35,13 @@ from .schemas import ( SalesOrderProductionPlanResponse, ProductionMaterialPlanItemResponse, SalesOrderIssueRequest, - SalesOrderIssueResponse + SalesOrderIssueResponse, + SalesOrderStatusUpdate ) from .utils import generate_order_no router = APIRouter(prefix="/sales-orders", tags=["销售订单"]) +VALID_ORDER_STATUSES = {"manufacturing", "delivered", "paid"} def _build_sales_order_response(order: SalesOrder, customer_name: str) -> SalesOrderResponse: @@ -317,17 +319,40 @@ async def _apply_order_items( ) -> float: total_amount = 0.0 for item_data in order_data.items: - product_result = await db_session.execute( - select(Product).where(Product.id == item_data.product_id, Product.is_active == True) - ) - product = product_result.scalar_one_or_none() - if not product: - raise HTTPException(status_code=400, detail=f"产品不存在: {item_data.product_id}") + product = None + if item_data.product_id is not None: + product_result = await db_session.execute( + select(Product).where(Product.id == item_data.product_id, Product.is_active == True) + ) + product = product_result.scalar_one_or_none() + if not product: + raise HTTPException(status_code=400, detail=f"产品不存在: {item_data.product_id}") + else: + if not item_data.product_sku or not item_data.product_name: + raise HTTPException(status_code=400, detail="请提供产品ID,或提供产品SKU与产品名称") + by_sku_result = await db_session.execute( + select(Product).where(Product.sku == item_data.product_sku, Product.is_active == True) + ) + product = by_sku_result.scalar_one_or_none() + if not product: + product = Product( + sku=item_data.product_sku, + name=item_data.product_name, + category=item_data.product_category, + unit=item_data.product_unit or "件", + item_type="finished", + cost_price=0, + sale_price=item_data.unit_price or 0, + min_stock=0, + max_stock=0 + ) + db_session.add(product) + await db_session.flush() if product.item_type != "finished": raise HTTPException(status_code=400, detail=f"销售单仅允许成品: {product.name}") item = SalesOrderItem( order_id=order.id, - product_id=item_data.product_id, + product_id=product.id, quantity=item_data.quantity, unit_price=item_data.unit_price, amount=item_data.quantity * item_data.unit_price, @@ -377,7 +402,7 @@ async def create_sales_order( delivery_date=order_data.delivery_date, remark=order_data.remark, operator_id=current_user.id, - status="draft" + status="manufacturing" ) db_session.add(order) await db_session.flush() @@ -421,6 +446,7 @@ async def update_sales_order( order.production_no = None order.planned_material_cost = 0 order.actual_material_cost = 0 + order.status = "manufacturing" order.total_amount = await _apply_order_items(db_session, order, order_data) await _issue_materials_for_order_creation(db_session, order, current_user) @@ -433,6 +459,23 @@ async def update_sales_order( return _build_sales_order_response(order, customer_name) +@router.patch("/{order_id}/status", response_model=SalesOrderResponse) +async def update_sales_order_status( + order_id: int, + payload: SalesOrderStatusUpdate, + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user) +): + if payload.status not in VALID_ORDER_STATUSES: + raise HTTPException(status_code=400, detail="订单状态必须为 manufacturing、delivered、paid") + + order, customer = await _get_sales_order_with_customer(db_session, order_id) + order.status = payload.status + await db_session.commit() + await db_session.refresh(order) + return _build_sales_order_response(order, customer.name) + + @router.delete("/{order_id}") async def delete_sales_order( order_id: int, diff --git a/src/api/inventory/schemas/__init__.py b/src/api/inventory/schemas/__init__.py index 4e75b40..6c9e210 100644 --- a/src/api/inventory/schemas/__init__.py +++ b/src/api/inventory/schemas/__init__.py @@ -29,7 +29,8 @@ from .sales_order_schemas import ( ProductionMaterialPlanItemResponse, SalesOrderProductionPlanResponse, SalesOrderIssueRequest, - SalesOrderIssueResponse + SalesOrderIssueResponse, + SalesOrderStatusUpdate ) from .finance_schemas import ( FinanceAllocationCreate, @@ -59,7 +60,7 @@ __all__ = [ "PurchaseOrderItemResponse", "PurchaseOrderDetailResponse", "PurchaseOrderReceiveItem", "PurchaseOrderReceiveRequest", "SalesOrderCreate", "SalesOrderResponse", "SalesOrderItemCreate", "SalesOrderItemResponse", "SalesOrderDetailResponse", "ProductionMaterialPlanItemResponse", "SalesOrderProductionPlanResponse", - "SalesOrderIssueRequest", "SalesOrderIssueResponse", + "SalesOrderIssueRequest", "SalesOrderIssueResponse", "SalesOrderStatusUpdate", "FinanceAllocationCreate", "FinanceTransactionCreate", "ReceiptCreate", "PaymentCreate", "FinanceAllocationResponse", "FinanceTransactionResponse", diff --git a/src/api/inventory/schemas/sales_order_schemas.py b/src/api/inventory/schemas/sales_order_schemas.py index 432f2c7..33952e2 100644 --- a/src/api/inventory/schemas/sales_order_schemas.py +++ b/src/api/inventory/schemas/sales_order_schemas.py @@ -4,7 +4,11 @@ from datetime import datetime class SalesOrderItemCreate(BaseModel): - product_id: int + product_id: Optional[int] = None + product_sku: Optional[str] = None + product_name: Optional[str] = None + product_category: Optional[str] = None + product_unit: Optional[str] = "件" quantity: int unit_price: float remark: Optional[str] = None @@ -88,3 +92,7 @@ class SalesOrderIssueResponse(BaseModel): cost_deviation: float cost_deviation_rate: float production_status: str + + +class SalesOrderStatusUpdate(BaseModel): + status: str diff --git a/static/vue-app.js b/static/vue-app.js index f562f94..6082978 100644 --- a/static/vue-app.js +++ b/static/vue-app.js @@ -1808,7 +1808,7 @@ const InventoryView = { if (item) { if (type === 'salesOrder') { await loadCustomers(); - await loadProducts(); + await loadFinishedProducts(); const detail = await apiRequest(`/api/sales-orders/${item.id}`); state.form = { customer_id: detail.customer_id, @@ -1816,6 +1816,8 @@ const InventoryView = { remark: detail.remark || '', items: (detail.items || []).map(line => ({ product_id: line.product_id, + product_sku: '', + product_name: '', quantity: line.quantity, unit_price: line.unit_price, remark: line.remark || '' @@ -1879,13 +1881,15 @@ const InventoryView = { } if (type === 'salesOrder') { await loadCustomers(); - await loadProducts(); + await loadFinishedProducts(); state.form = { customer_id: state.customers[0]?.id || null, delivery_date: '', remark: '', items: [{ - product_id: state.products.find(p => p.item_type === 'finished')?.id || null, + product_id: state.finishedProducts[0]?.id || null, + product_sku: '', + product_name: '', quantity: 1, unit_price: 0, remark: '' @@ -2105,7 +2109,9 @@ const InventoryView = { const addSalesOrderItem = () => { state.form.items = state.form.items || []; state.form.items.push({ - product_id: state.products.find(p => p.item_type === 'finished')?.id || null, + product_id: state.finishedProducts[0]?.id || null, + product_sku: '', + product_name: '', quantity: 1, unit_price: 0, remark: '' @@ -2126,7 +2132,16 @@ const InventoryView = { customer_id: state.form.customer_id, delivery_date: state.form.delivery_date ? new Date(state.form.delivery_date).toISOString() : null, remark: state.form.remark, - items: state.form.items + items: state.form.items.map(item => ({ + product_id: item.product_id || null, + product_sku: item.product_id ? null : (item.product_sku || null), + product_name: item.product_id ? null : (item.product_name || null), + product_category: null, + product_unit: '件', + quantity: item.quantity, + unit_price: item.unit_price, + remark: item.remark || '' + })) }; if (state.editingItem) { await apiRequest(`/api/sales-orders/${state.editingItem.id}`, { @@ -2143,6 +2158,7 @@ const InventoryView = { } closeModal(); loadProductionOrders(); + loadFinishedProducts(); loadInventory(); loadMovements(); } catch (e) { @@ -2150,6 +2166,19 @@ const InventoryView = { } }; + const updateSalesOrderStatus = async (order, targetStatus) => { + try { + await apiRequest(`/api/sales-orders/${order.id}/status`, { + method: 'PATCH', + body: JSON.stringify({ status: targetStatus }) + }); + addNotification('订单状态已更新', 'success'); + loadProductionOrders(); + } catch (e) { + handleApiError(e, '更新订单状态'); + } + }; + const deleteSalesOrder = async (orderId) => { if (!confirm('确定删除这个销售订单吗?系统会自动回补已扣减物料。')) return; try { @@ -2288,6 +2317,7 @@ const InventoryView = { removeSalesOrderItem, saveSalesOrder, deleteSalesOrder, + updateSalesOrderStatus, addPurchaseOrderItem, removePurchaseOrderItem, savePurchaseOrder, @@ -2623,10 +2653,9 @@ const InventoryView = {