From 48853a988d6c5ba0fb07d1a66595202b95c90cec Mon Sep 17 00:00:00 2001 From: cjw <792430652@qq.com> Date: Sun, 15 Mar 2026 15:47:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B4=A2=E5=8A=A1=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/inventory/__init__.py | 2 + src/api/inventory/finance_routes.py | 361 +++++++++++++++++++ src/api/inventory/schemas/__init__.py | 15 + src/api/inventory/schemas/finance_schemas.py | 94 +++++ src/database/init_db.py | 10 +- src/models/database.py | 39 ++ static/vue-app.js | 107 +++++- 7 files changed, 614 insertions(+), 14 deletions(-) create mode 100644 src/api/inventory/finance_routes.py create mode 100644 src/api/inventory/schemas/finance_schemas.py diff --git a/src/api/inventory/__init__.py b/src/api/inventory/__init__.py index 61fb145..42429a8 100644 --- a/src/api/inventory/__init__.py +++ b/src/api/inventory/__init__.py @@ -24,6 +24,7 @@ from .stock_movement_routes import router as stock_movement_router from .purchase_order_routes import router as purchase_order_router from .sales_order_routes import router as sales_order_router from .dashboard_routes import router as dashboard_router +from .finance_routes import router as finance_router inventory_router = APIRouter(prefix="/api", tags=["进销存"]) @@ -36,5 +37,6 @@ inventory_router.include_router(stock_movement_router) inventory_router.include_router(purchase_order_router) inventory_router.include_router(sales_order_router) inventory_router.include_router(dashboard_router) +inventory_router.include_router(finance_router) __all__ = ["inventory_router"] diff --git a/src/api/inventory/finance_routes.py b/src/api/inventory/finance_routes.py new file mode 100644 index 0000000..e150186 --- /dev/null +++ b/src/api/inventory/finance_routes.py @@ -0,0 +1,361 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, func +from sqlalchemy.orm import selectinload +from typing import Optional, List +from datetime import datetime + +from database.database import get_db_session +from services.auth_service import get_current_active_user +from models.database import ( + User, + Customer, + Supplier, + SalesOrder, + PurchaseOrder, + FinanceTransaction, + FinanceAllocation, +) +from .schemas import ( + ReceiptCreate, + PaymentCreate, + FinanceTransactionResponse, + FinanceSummaryResponse, + ReceivableItemResponse, + PayableItemResponse, +) +from .utils import generate_order_no + +router = APIRouter(prefix="/finance", tags=["财务管理"]) + + +def _build_transaction_response(txn: FinanceTransaction) -> FinanceTransactionResponse: + allocations = [ + { + "id": item.id, + "order_type": item.order_type, + "order_id": item.order_id, + "allocated_amount": item.allocated_amount, + } + for item in txn.allocations + ] + return FinanceTransactionResponse( + id=txn.id, + txn_no=txn.txn_no, + txn_type=txn.txn_type, + partner_type=txn.partner_type, + partner_id=txn.partner_id, + amount=txn.amount, + txn_date=txn.txn_date, + method=txn.method, + account_name=txn.account_name, + status=txn.status, + remark=txn.remark, + created_at=txn.created_at, + allocations=allocations, + ) + + +def _validate_allocation_total(transaction_amount: float, allocation_amounts: List[float]): + allocated_total = sum(allocation_amounts) + if allocated_total - transaction_amount > 1e-6: + raise HTTPException(status_code=400, detail="核销总额不能大于单据金额") + + +@router.post("/receipts", response_model=FinanceTransactionResponse, status_code=201) +async def create_receipt( + payload: ReceiptCreate, + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user), +): + customer_result = await db_session.execute( + select(Customer).where(Customer.id == payload.customer_id, Customer.is_active == True) + ) + customer = customer_result.scalar_one_or_none() + if not customer: + raise HTTPException(status_code=404, detail="客户不存在") + + _validate_allocation_total(payload.amount, [item.allocated_amount for item in payload.allocations]) + + txn = FinanceTransaction( + txn_no=generate_order_no("RC"), + txn_type="receipt", + partner_type="customer", + partner_id=payload.customer_id, + amount=payload.amount, + txn_date=payload.txn_date or datetime.now(), + method=payload.method, + account_name=payload.account_name, + status="confirmed", + remark=payload.remark, + operator_id=current_user.id, + ) + db_session.add(txn) + await db_session.flush() + + for allocation in payload.allocations: + if allocation.order_type != "sales": + raise HTTPException(status_code=400, detail="收款单只允许核销销售订单") + + order_result = await db_session.execute( + select(SalesOrder).where(SalesOrder.id == allocation.order_id, SalesOrder.customer_id == payload.customer_id) + ) + sales_order = order_result.scalar_one_or_none() + if not sales_order: + raise HTTPException(status_code=404, detail=f"销售订单不存在: {allocation.order_id}") + + remaining = (sales_order.total_amount or 0) - (sales_order.received_amount or 0) + if allocation.allocated_amount - remaining > 1e-6: + raise HTTPException(status_code=400, detail=f"销售订单核销超额: {sales_order.order_no}") + + db_session.add( + FinanceAllocation( + transaction_id=txn.id, + order_type="sales", + order_id=sales_order.id, + allocated_amount=allocation.allocated_amount, + ) + ) + sales_order.received_amount = (sales_order.received_amount or 0) + allocation.allocated_amount + + await db_session.commit() + result = await db_session.execute( + select(FinanceTransaction) + .options(selectinload(FinanceTransaction.allocations)) + .where(FinanceTransaction.id == txn.id) + ) + created = result.scalar_one() + return _build_transaction_response(created) + + +@router.post("/payments", response_model=FinanceTransactionResponse, status_code=201) +async def create_payment( + payload: PaymentCreate, + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user), +): + supplier_result = await db_session.execute( + select(Supplier).where(Supplier.id == payload.supplier_id, Supplier.is_active == True) + ) + supplier = supplier_result.scalar_one_or_none() + if not supplier: + raise HTTPException(status_code=404, detail="供应商不存在") + + _validate_allocation_total(payload.amount, [item.allocated_amount for item in payload.allocations]) + + txn = FinanceTransaction( + txn_no=generate_order_no("PY"), + txn_type="payment", + partner_type="supplier", + partner_id=payload.supplier_id, + amount=payload.amount, + txn_date=payload.txn_date or datetime.now(), + method=payload.method, + account_name=payload.account_name, + status="confirmed", + remark=payload.remark, + operator_id=current_user.id, + ) + db_session.add(txn) + await db_session.flush() + + for allocation in payload.allocations: + if allocation.order_type != "purchase": + raise HTTPException(status_code=400, detail="付款单只允许核销采购订单") + + order_result = await db_session.execute( + select(PurchaseOrder).where(PurchaseOrder.id == allocation.order_id, PurchaseOrder.supplier_id == payload.supplier_id) + ) + purchase_order = order_result.scalar_one_or_none() + if not purchase_order: + raise HTTPException(status_code=404, detail=f"采购订单不存在: {allocation.order_id}") + + remaining = (purchase_order.total_amount or 0) - (purchase_order.paid_amount or 0) + if allocation.allocated_amount - remaining > 1e-6: + raise HTTPException(status_code=400, detail=f"采购订单核销超额: {purchase_order.order_no}") + + db_session.add( + FinanceAllocation( + transaction_id=txn.id, + order_type="purchase", + order_id=purchase_order.id, + allocated_amount=allocation.allocated_amount, + ) + ) + purchase_order.paid_amount = (purchase_order.paid_amount or 0) + allocation.allocated_amount + + await db_session.commit() + result = await db_session.execute( + select(FinanceTransaction) + .options(selectinload(FinanceTransaction.allocations)) + .where(FinanceTransaction.id == txn.id) + ) + created = result.scalar_one() + return _build_transaction_response(created) + + +@router.get("/transactions", response_model=List[FinanceTransactionResponse]) +async def list_transactions( + txn_type: Optional[str] = None, + status: Optional[str] = "confirmed", + skip: int = Query(0, ge=0), + limit: int = Query(20, ge=1, le=100), + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user), +): + query = ( + select(FinanceTransaction) + .options(selectinload(FinanceTransaction.allocations)) + .order_by(FinanceTransaction.created_at.desc()) + ) + if txn_type: + query = query.where(FinanceTransaction.txn_type == txn_type) + if status: + query = query.where(FinanceTransaction.status == status) + query = query.offset(skip).limit(limit) + result = await db_session.execute(query) + rows = result.scalars().all() + return [_build_transaction_response(item) for item in rows] + + +@router.post("/transactions/{transaction_id}/void") +async def void_transaction( + transaction_id: int, + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user), +): + result = await db_session.execute( + select(FinanceTransaction) + .options(selectinload(FinanceTransaction.allocations)) + .where(FinanceTransaction.id == transaction_id) + ) + txn = result.scalar_one_or_none() + if not txn: + raise HTTPException(status_code=404, detail="财务单据不存在") + if txn.status == "voided": + return {"message": "单据已作废"} + + for allocation in txn.allocations: + if allocation.order_type == "sales": + sales_result = await db_session.execute(select(SalesOrder).where(SalesOrder.id == allocation.order_id)) + sales_order = sales_result.scalar_one_or_none() + if sales_order: + sales_order.received_amount = max((sales_order.received_amount or 0) - allocation.allocated_amount, 0) + elif allocation.order_type == "purchase": + purchase_result = await db_session.execute(select(PurchaseOrder).where(PurchaseOrder.id == allocation.order_id)) + purchase_order = purchase_result.scalar_one_or_none() + if purchase_order: + purchase_order.paid_amount = max((purchase_order.paid_amount or 0) - allocation.allocated_amount, 0) + + txn.status = "voided" + await db_session.commit() + return {"message": "单据已作废"} + + +@router.get("/receivables", response_model=List[ReceivableItemResponse]) +async def list_receivables( + skip: int = Query(0, ge=0), + limit: int = Query(50, ge=1, le=200), + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user), +): + result = await db_session.execute( + select(SalesOrder, Customer) + .join(Customer, SalesOrder.customer_id == Customer.id) + .where((SalesOrder.total_amount - SalesOrder.received_amount) > 0) + .order_by(SalesOrder.created_at.desc()) + .offset(skip) + .limit(limit) + ) + rows = [] + for order, customer in result.all(): + receivable_amount = (order.total_amount or 0) - (order.received_amount or 0) + rows.append( + ReceivableItemResponse( + order_id=order.id, + order_no=order.order_no, + customer_id=customer.id, + customer_name=customer.name, + order_date=order.order_date, + total_amount=order.total_amount or 0, + received_amount=order.received_amount or 0, + receivable_amount=receivable_amount, + status=order.status, + ) + ) + return rows + + +@router.get("/payables", response_model=List[PayableItemResponse]) +async def list_payables( + skip: int = Query(0, ge=0), + limit: int = Query(50, ge=1, le=200), + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user), +): + result = await db_session.execute( + select(PurchaseOrder, Supplier) + .join(Supplier, PurchaseOrder.supplier_id == Supplier.id) + .where((PurchaseOrder.total_amount - PurchaseOrder.paid_amount) > 0) + .order_by(PurchaseOrder.created_at.desc()) + .offset(skip) + .limit(limit) + ) + rows = [] + for order, supplier in result.all(): + payable_amount = (order.total_amount or 0) - (order.paid_amount or 0) + rows.append( + PayableItemResponse( + order_id=order.id, + order_no=order.order_no, + supplier_id=supplier.id, + supplier_name=supplier.name, + order_date=order.order_date, + total_amount=order.total_amount or 0, + paid_amount=order.paid_amount or 0, + payable_amount=payable_amount, + status=order.status, + ) + ) + return rows + + +@router.get("/summary", response_model=FinanceSummaryResponse) +async def get_finance_summary( + db_session: AsyncSession = Depends(get_db_session), + current_user: User = Depends(get_current_active_user), +): + receivable_total = await db_session.scalar( + select(func.coalesce(func.sum(SalesOrder.total_amount - SalesOrder.received_amount), 0)) + .where((SalesOrder.total_amount - SalesOrder.received_amount) > 0) + ) or 0 + payable_total = await db_session.scalar( + select(func.coalesce(func.sum(PurchaseOrder.total_amount - PurchaseOrder.paid_amount), 0)) + .where((PurchaseOrder.total_amount - PurchaseOrder.paid_amount) > 0) + ) or 0 + + now = datetime.now() + month_start = datetime(now.year, now.month, 1) + + monthly_receipt_total = await db_session.scalar( + select(func.coalesce(func.sum(FinanceTransaction.amount), 0)) + .where(FinanceTransaction.txn_type == "receipt") + .where(FinanceTransaction.status == "confirmed") + .where(FinanceTransaction.txn_date >= month_start) + ) or 0 + monthly_payment_total = await db_session.scalar( + select(func.coalesce(func.sum(FinanceTransaction.amount), 0)) + .where(FinanceTransaction.txn_type == "payment") + .where(FinanceTransaction.status == "confirmed") + .where(FinanceTransaction.txn_date >= month_start) + ) or 0 + + return FinanceSummaryResponse( + receivable_total=round(float(receivable_total), 2), + payable_total=round(float(payable_total), 2), + monthly_receipt_total=round(float(monthly_receipt_total), 2), + monthly_payment_total=round(float(monthly_payment_total), 2), + overdue_receivable_count=0, + overdue_payable_count=0, + ) + diff --git a/src/api/inventory/schemas/__init__.py b/src/api/inventory/schemas/__init__.py index 1ea672e..7636e02 100644 --- a/src/api/inventory/schemas/__init__.py +++ b/src/api/inventory/schemas/__init__.py @@ -14,6 +14,17 @@ from .sales_order_schemas import ( SalesOrderResponse, SalesOrderItemCreate ) +from .finance_schemas import ( + FinanceAllocationCreate, + FinanceTransactionCreate, + ReceiptCreate, + PaymentCreate, + FinanceAllocationResponse, + FinanceTransactionResponse, + FinanceSummaryResponse, + ReceivableItemResponse, + PayableItemResponse +) __all__ = [ "ProductCreate", "ProductResponse", @@ -24,4 +35,8 @@ __all__ = [ "StockMovementCreate", "StockMovementResponse", "PurchaseOrderCreate", "PurchaseOrderResponse", "PurchaseOrderItemCreate", "SalesOrderCreate", "SalesOrderResponse", "SalesOrderItemCreate", + "FinanceAllocationCreate", "FinanceTransactionCreate", + "ReceiptCreate", "PaymentCreate", + "FinanceAllocationResponse", "FinanceTransactionResponse", + "FinanceSummaryResponse", "ReceivableItemResponse", "PayableItemResponse", ] diff --git a/src/api/inventory/schemas/finance_schemas.py b/src/api/inventory/schemas/finance_schemas.py new file mode 100644 index 0000000..96058c0 --- /dev/null +++ b/src/api/inventory/schemas/finance_schemas.py @@ -0,0 +1,94 @@ +from pydantic import BaseModel, Field +from typing import Optional, List, Literal +from datetime import datetime + + +TxnType = Literal["receipt", "payment"] +PartnerType = Literal["customer", "supplier"] +OrderType = Literal["sales", "purchase"] +TxnStatus = Literal["confirmed", "voided"] + + +class FinanceAllocationCreate(BaseModel): + order_type: OrderType + order_id: int + allocated_amount: float = Field(gt=0) + + +class FinanceTransactionCreate(BaseModel): + amount: float = Field(gt=0) + txn_date: Optional[datetime] = None + method: str = "bank" + account_name: Optional[str] = None + remark: Optional[str] = None + allocations: List[FinanceAllocationCreate] + + +class ReceiptCreate(FinanceTransactionCreate): + customer_id: int + + +class PaymentCreate(FinanceTransactionCreate): + supplier_id: int + + +class FinanceAllocationResponse(BaseModel): + id: int + order_type: str + order_id: int + allocated_amount: float + + class Config: + from_attributes = True + + +class FinanceTransactionResponse(BaseModel): + id: int + txn_no: str + txn_type: TxnType + partner_type: PartnerType + partner_id: int + amount: float + txn_date: datetime + method: str + account_name: Optional[str] + status: TxnStatus + remark: Optional[str] + created_at: datetime + allocations: List[FinanceAllocationResponse] = [] + + class Config: + from_attributes = True + + +class FinanceSummaryResponse(BaseModel): + receivable_total: float + payable_total: float + monthly_receipt_total: float + monthly_payment_total: float + overdue_receivable_count: int = 0 + overdue_payable_count: int = 0 + + +class ReceivableItemResponse(BaseModel): + order_id: int + order_no: str + customer_id: int + customer_name: str + order_date: datetime + total_amount: float + received_amount: float + receivable_amount: float + status: str + + +class PayableItemResponse(BaseModel): + order_id: int + order_no: str + supplier_id: int + supplier_name: str + order_date: datetime + total_amount: float + paid_amount: float + payable_amount: float + status: str diff --git a/src/database/init_db.py b/src/database/init_db.py index d492287..2ce414e 100644 --- a/src/database/init_db.py +++ b/src/database/init_db.py @@ -29,15 +29,19 @@ DEFAULT_PERMISSIONS = [ {"code": "manage_suppliers", "name": "管理供应商", "module": "inventory"}, {"code": "view_customers", "name": "查看客户", "module": "inventory"}, {"code": "manage_customers", "name": "管理客户", "module": "inventory"}, + {"code": "view_finance", "name": "查看财务", "module": "finance"}, + {"code": "manage_receipts", "name": "管理收款", "module": "finance"}, + {"code": "manage_payments", "name": "管理付款", "module": "finance"}, + {"code": "void_finance_transaction", "name": "作废财务单据", "module": "finance"}, {"code": "view_users", "name": "查看用户", "module": "admin"}, {"code": "manage_users", "name": "管理用户", "module": "admin"}, {"code": "manage_roles", "name": "管理角色", "module": "admin"}, ] DEFAULT_ROLES = [ - {"code": "admin", "name": "管理员", "description": "系统管理员,拥有所有权限", "is_system": True, "permissions": ["view_dashboard", "view_moldinsight", "upload_file", "view_history", "view_inventory", "manage_inventory", "view_products", "manage_products", "view_suppliers", "manage_suppliers", "view_customers", "manage_customers", "view_users", "manage_users", "manage_roles"]}, - {"code": "user", "name": "普通用户", "description": "普通用户,可使用模具分析和查看库存", "is_system": False, "permissions": ["view_dashboard", "view_moldinsight", "upload_file", "view_history", "view_inventory", "view_products", "view_suppliers", "view_customers"]}, - {"code": "viewer", "name": "只读用户", "description": "只读用户,只能查看数据", "is_system": False, "permissions": ["view_dashboard", "view_moldinsight", "view_history", "view_inventory", "view_products", "view_suppliers", "view_customers"]}, + {"code": "admin", "name": "管理员", "description": "系统管理员,拥有所有权限", "is_system": True, "permissions": ["view_dashboard", "view_moldinsight", "upload_file", "view_history", "view_inventory", "manage_inventory", "view_products", "manage_products", "view_suppliers", "manage_suppliers", "view_customers", "manage_customers", "view_finance", "manage_receipts", "manage_payments", "void_finance_transaction", "view_users", "manage_users", "manage_roles"]}, + {"code": "user", "name": "普通用户", "description": "普通用户,可使用模具分析和查看库存", "is_system": False, "permissions": ["view_dashboard", "view_moldinsight", "upload_file", "view_history", "view_inventory", "view_products", "view_suppliers", "view_customers", "view_finance", "manage_receipts", "manage_payments"]}, + {"code": "viewer", "name": "只读用户", "description": "只读用户,只能查看数据", "is_system": False, "permissions": ["view_dashboard", "view_moldinsight", "view_history", "view_inventory", "view_products", "view_suppliers", "view_customers", "view_finance"]}, ] diff --git a/src/models/database.py b/src/models/database.py index ccdfee8..9e95636 100644 --- a/src/models/database.py +++ b/src/models/database.py @@ -681,6 +681,45 @@ class SalesOrder(Base): return f"" +class FinanceTransaction(Base): + __tablename__ = "finance_transactions" + + id = Column(Integer, primary_key=True, index=True) + txn_no = Column(String(50), unique=True, index=True, nullable=False) + txn_type = Column(String(20), nullable=False, index=True) + partner_type = Column(String(20), nullable=False, index=True) + partner_id = Column(Integer, nullable=False, index=True) + amount = Column(Float, nullable=False) + txn_date = Column(DateTime, default=func.now(), index=True) + method = Column(String(30), default="bank") + account_name = Column(String(100), nullable=True) + status = Column(String(20), default="confirmed", index=True) + remark = Column(Text, nullable=True) + operator_id = Column(Integer, ForeignKey("users.id"), nullable=True) + created_at = Column(DateTime, default=func.now(), index=True) + + allocations = relationship("FinanceAllocation", back_populates="transaction", cascade="all, delete-orphan") + + def __repr__(self): + return f"" + + +class FinanceAllocation(Base): + __tablename__ = "finance_allocations" + + id = Column(Integer, primary_key=True, index=True) + transaction_id = Column(Integer, ForeignKey("finance_transactions.id"), nullable=False, index=True) + order_type = Column(String(20), nullable=False, index=True) + order_id = Column(Integer, nullable=False, index=True) + allocated_amount = Column(Float, nullable=False) + created_at = Column(DateTime, default=func.now(), index=True) + + transaction = relationship("FinanceTransaction", back_populates="allocations") + + def __repr__(self): + return f"" + + class AnalysisMetrics(Base): """分析指标表""" __tablename__ = "analysis_metrics" diff --git a/static/vue-app.js b/static/vue-app.js index 22e30e2..4e489ae 100644 --- a/static/vue-app.js +++ b/static/vue-app.js @@ -1473,6 +1473,10 @@ const InventoryView = { const state = reactive({ activeTab: 'dashboard', dashboard: null, + financeSummary: null, + financeTransactions: [], + receivables: [], + payables: [], products: [], suppliers: [], customers: [], @@ -1552,6 +1556,26 @@ const InventoryView = { } }; + const loadFinance = async () => { + state.loading = true; + try { + const [summary, transactions, receivables, payables] = await Promise.all([ + apiRequest('/api/finance/summary'), + apiRequest('/api/finance/transactions?status=confirmed&limit=20'), + apiRequest('/api/finance/receivables?limit=20'), + apiRequest('/api/finance/payables?limit=20') + ]); + state.financeSummary = summary; + state.financeTransactions = transactions; + state.receivables = receivables; + state.payables = payables; + } catch (e) { + handleApiError(e, '加载财务数据'); + } finally { + state.loading = false; + } + }; + const switchTab = (tab) => { state.activeTab = tab; switch (tab) { @@ -1561,6 +1585,7 @@ const InventoryView = { case 'customers': loadCustomers(); break; case 'inventory': loadInventory(); break; case 'movements': loadMovements(); break; + case 'finance': loadFinance(); break; } }; @@ -1589,13 +1614,13 @@ const InventoryView = { method: 'PUT', body: JSON.stringify(state.form) }); - showNotification('产品更新成功', 'success'); + addNotification('产品更新成功', 'success'); } else { await apiRequest('/api/products', { method: 'POST', body: JSON.stringify(state.form) }); - showNotification('产品创建成功', 'success'); + addNotification('产品创建成功', 'success'); } closeModal(); loadProducts(); @@ -1608,7 +1633,7 @@ const InventoryView = { if (!confirm('确定要删除这个产品吗?')) return; try { await apiRequest(`/api/products/${id}`, { method: 'DELETE' }); - showNotification('产品已删除', 'success'); + addNotification('产品已删除', 'success'); loadProducts(); } catch (e) { handleApiError(e, '删除产品'); @@ -1622,13 +1647,13 @@ const InventoryView = { method: 'PUT', body: JSON.stringify(state.form) }); - showNotification('供应商更新成功', 'success'); + addNotification('供应商更新成功', 'success'); } else { await apiRequest('/api/suppliers', { method: 'POST', body: JSON.stringify(state.form) }); - showNotification('供应商创建成功', 'success'); + addNotification('供应商创建成功', 'success'); } closeModal(); loadSuppliers(); @@ -1641,7 +1666,7 @@ const InventoryView = { if (!confirm('确定要删除这个供应商吗?')) return; try { await apiRequest(`/api/suppliers/${id}`, { method: 'DELETE' }); - showNotification('供应商已删除', 'success'); + addNotification('供应商已删除', 'success'); loadSuppliers(); } catch (e) { handleApiError(e, '删除供应商'); @@ -1655,13 +1680,13 @@ const InventoryView = { method: 'PUT', body: JSON.stringify(state.form) }); - showNotification('客户更新成功', 'success'); + addNotification('客户更新成功', 'success'); } else { await apiRequest('/api/customers', { method: 'POST', body: JSON.stringify(state.form) }); - showNotification('客户创建成功', 'success'); + addNotification('客户创建成功', 'success'); } closeModal(); loadCustomers(); @@ -1674,7 +1699,7 @@ const InventoryView = { if (!confirm('确定要删除这个客户吗?')) return; try { await apiRequest(`/api/customers/${id}`, { method: 'DELETE' }); - showNotification('客户已删除', 'success'); + addNotification('客户已删除', 'success'); loadCustomers(); } catch (e) { handleApiError(e, '删除客户'); @@ -1687,7 +1712,7 @@ const InventoryView = { method: 'POST', body: JSON.stringify({ ...state.form, movement_type: 'in' }) }); - showNotification('入库成功', 'success'); + addNotification('入库成功', 'success'); closeModal(); loadInventory(); loadMovements(); @@ -1702,7 +1727,7 @@ const InventoryView = { method: 'POST', body: JSON.stringify({ ...state.form, movement_type: 'out' }) }); - showNotification('出库成功', 'success'); + addNotification('出库成功', 'success'); closeModal(); loadInventory(); loadMovements(); @@ -1751,6 +1776,7 @@ const InventoryView = { +
@@ -1940,6 +1966,65 @@ const InventoryView = {
+
+
+
+
🧾
+
+
{{ formatCurrency(state.financeSummary?.receivable_total || 0) }}
+
应收总额
+
+
+
+
💸
+
+
{{ formatCurrency(state.financeSummary?.payable_total || 0) }}
+
应付总额
+
+
+
+
💵
+
+
{{ formatCurrency(state.financeSummary?.monthly_receipt_total || 0) }}
+
本月收款
+
+
+
+
🏦
+
+
{{ formatCurrency(state.financeSummary?.monthly_payment_total || 0) }}
+
本月付款
+
+
+
+ +
+

最近财务流水

+ + + + + + + + + + + + + + + + + + + + + +
单号类型往来方金额状态日期
{{ txn.txn_no }}{{ txn.txn_type === 'receipt' ? '收款' : '付款' }}{{ txn.partner_type === 'customer' ? '客户' : '供应商' }}#{{ txn.partner_id }}{{ formatCurrency(txn.amount) }}{{ txn.status === 'confirmed' ? '已确认' : '已作废' }}{{ formatDateTime(txn.txn_date) }}
+
+
+