| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import mimetypes
- from typing import List, Optional, Set
- from fastapi import APIRouter, Depends, HTTPException, Request, status
- from sqlalchemy.orm import Session
- from starlette.datastructures import UploadFile as StarletteUploadFile
- from app.core.bx_auth import BxUserContext, get_bx_current_user
- from app.core.database import get_db
- from app.schemas.bx.common import ApiResponse, PagedData
- from app.schemas.bx.invoice import InvoiceOut, LedgerOut, WalletSummaryOut
- from app.services.bx.invoice import InvoiceService
- router = APIRouter(tags=["发票夹"])
- @router.get("/invoice-wallets/mine/summary", response_model=ApiResponse[WalletSummaryOut])
- def get_mine_wallet_summary(
- userId: Optional[str] = None,
- db: Session = Depends(get_db),
- user: BxUserContext = Depends(get_bx_current_user),
- ):
- data = InvoiceService.get_mine_summary(db, user=user, owner_user_id=userId)
- return ApiResponse(data=WalletSummaryOut.model_validate(data))
- @router.get("/invoice-wallets", response_model=ApiResponse[PagedData[WalletSummaryOut]])
- def list_invoice_wallets(
- page: int = 1,
- pageSize: int = 10,
- keyword: str = "",
- db: Session = Depends(get_db),
- user: BxUserContext = Depends(get_bx_current_user),
- ):
- rows, total = InvoiceService.list_wallets(
- db, user=user, page=page, page_size=pageSize, keyword=keyword
- )
- out_rows = [WalletSummaryOut.model_validate(InvoiceService._summary_to_dict(r)) for r in rows]
- return ApiResponse(data=PagedData(rows=out_rows, total=total, page=page, page_size=pageSize))
- @router.get("/invoices", response_model=ApiResponse[PagedData[InvoiceOut]])
- def list_invoices(
- page: int = 1,
- pageSize: int = 10,
- userId: Optional[str] = None,
- db: Session = Depends(get_db),
- user: BxUserContext = Depends(get_bx_current_user),
- ):
- tenant_no = InvoiceService._tenant_no(db, user)
- rows, total = InvoiceService.list_invoices(
- db, user=user, owner_user_id=userId, page=page, page_size=pageSize
- )
- out_rows = InvoiceService.out_rows_with_attachments(db, tenant_no, list(rows))
- return ApiResponse(
- data=PagedData(
- rows=[InvoiceOut.model_validate(r) for r in out_rows],
- total=total,
- page=page,
- page_size=pageSize,
- )
- )
- @router.post("/invoices", response_model=ApiResponse[InvoiceOut])
- async def create_invoice(
- request: Request,
- db: Session = Depends(get_db),
- user: BxUserContext = Depends(get_bx_current_user),
- ):
- """一次上传一条发票:多文件挂同一档案,金额为总额,只写一条额度流水。
- 使用 request.form().getlist,兼容只传 1 个文件时 FastAPI List[UploadFile] 的 422。
- """
- form = await request.form()
- amount_raw = form.get("amount")
- if amount_raw is None or str(amount_raw).strip() == "":
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请填写金额")
- try:
- amount = float(amount_raw)
- except (TypeError, ValueError) as exc:
- raise HTTPException(
- status_code=status.HTTP_400_BAD_REQUEST, detail="金额格式不正确"
- ) from exc
- remark = str(form.get("remark") or "")
- target_raw = form.get("targetUserId")
- target_user_id = str(target_raw).strip() if target_raw not in (None, "") else None
- upload_files: List[StarletteUploadFile] = []
- seen_ids: Set[int] = set()
- for key in ("files", "file"):
- for item in form.getlist(key):
- if not isinstance(item, StarletteUploadFile):
- continue
- obj_id = id(item)
- if obj_id in seen_ids:
- continue
- seen_ids.add(obj_id)
- upload_files.append(item)
- if not upload_files:
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请选择文件")
- file_payload = []
- for uf in upload_files:
- if not uf.filename:
- continue
- data = await uf.read()
- content_type = (
- uf.content_type
- or mimetypes.guess_type(uf.filename or "")[0]
- or "application/octet-stream"
- )
- file_payload.append((data, uf.filename or "unknown", content_type))
- if not file_payload:
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="请选择文件")
- try:
- invoice = InvoiceService.create(
- db,
- user=user,
- amount=amount,
- files=file_payload,
- remark=remark,
- target_user_id=target_user_id,
- )
- db.commit()
- db.refresh(invoice)
- except HTTPException:
- db.rollback()
- raise
- except Exception as exc:
- db.rollback()
- raise HTTPException(
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="上传发票失败"
- ) from exc
- tenant_no = InvoiceService._tenant_no(db, user)
- out = InvoiceService.out_rows_with_attachments(db, tenant_no, [invoice])[0]
- return ApiResponse(data=InvoiceOut.model_validate(out))
- @router.delete("/invoices/{invoice_id}", response_model=ApiResponse[bool])
- def delete_invoice(
- invoice_id: str,
- db: Session = Depends(get_db),
- user: BxUserContext = Depends(get_bx_current_user),
- ):
- try:
- success = InvoiceService.delete(db, user=user, invoice_id=invoice_id)
- db.commit()
- except HTTPException:
- db.rollback()
- raise
- return ApiResponse(data=success)
- @router.get("/invoice-ledgers", response_model=ApiResponse[List[LedgerOut]])
- def list_invoice_ledgers(
- userId: Optional[str] = None,
- db: Session = Depends(get_db),
- user: BxUserContext = Depends(get_bx_current_user),
- ):
- rows = InvoiceService.list_ledgers(db, user=user, owner_user_id=userId)
- return ApiResponse(data=[LedgerOut.model_validate(r) for r in rows])
|