expense_item.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. from typing import List, Optional
  2. from fastapi import APIRouter, Depends, HTTPException, status
  3. from pydantic import BaseModel, Field
  4. from sqlalchemy.orm import Session
  5. from app.api.bx.export_common import (
  6. ExportColumn,
  7. ExportValidationError,
  8. BxExportBodyBase,
  9. dump_export_row,
  10. respond_xlsx,
  11. status_label,
  12. )
  13. from app.core.bx_auth import BxUserContext, get_bx_current_user
  14. from app.core.database import get_db
  15. from app.core.tenant_context import get_tenant_no
  16. from app.schemas.bx.common import ApiResponse
  17. from app.schemas.bx.expense_item import ExpenseItemCreate, ExpenseItemOut, ExpenseItemUpdate
  18. from app.services.bx.expense_item import ExpenseItemService
  19. router = APIRouter(prefix="/expense-items", tags=["费用项目管理"])
  20. @router.get("/tree", response_model=ApiResponse[List[ExpenseItemOut]])
  21. def get_expense_item_tree(
  22. db: Session = Depends(get_db),
  23. user: BxUserContext = Depends(get_bx_current_user),
  24. ):
  25. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  26. tree = ExpenseItemService.get_item_tree(db, tenant_no, user=user)
  27. return ApiResponse(data=[ExpenseItemOut.model_validate(i) for i in tree])
  28. EXPENSE_ITEM_EXPORT_COLUMNS = [
  29. ExportColumn(title="项目名称", field="itemName"),
  30. ExportColumn(title="项目编码", field="itemCode"),
  31. ExportColumn(title="层级", field="level"),
  32. ExportColumn(title="子项数", field="childCount"),
  33. ExportColumn(title="排序", field="sortOrder"),
  34. ExportColumn(title="状态", field="status"),
  35. ]
  36. class ExpenseItemExportBody(BxExportBodyBase):
  37. name: Optional[str] = None
  38. status: Optional[str] = None
  39. def _flatten_expense_tree(nodes: list, out: list) -> None:
  40. for n in nodes:
  41. out.append(n)
  42. children = getattr(n, "children", None) or (n.get("children") if isinstance(n, dict) else None) or []
  43. if children:
  44. _flatten_expense_tree(list(children), out)
  45. @router.post("/export")
  46. def export_expense_items(
  47. body: ExpenseItemExportBody,
  48. db: Session = Depends(get_db),
  49. user: BxUserContext = Depends(get_bx_current_user),
  50. ):
  51. try:
  52. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  53. tree = ExpenseItemService.get_item_tree(db, tenant_no, user=user)
  54. flat: list = []
  55. _flatten_expense_tree(list(tree), flat)
  56. outs = [ExpenseItemOut.model_validate(i) for i in flat]
  57. if body.ids:
  58. id_set = {str(i) for i in body.ids}
  59. outs = [o for o in outs if str(o.id) in id_set]
  60. if body.name:
  61. kw = body.name.strip()
  62. outs = [o for o in outs if kw in (o.item_name or "")]
  63. if body.status not in (None, ""):
  64. outs = [o for o in outs if str(o.status) == str(body.status)]
  65. rows = []
  66. for o in outs:
  67. data = dump_export_row(o, status_fields=())
  68. data["status"] = status_label(data.get("status"))
  69. # childCount may be computed
  70. if "childCount" not in data:
  71. data["childCount"] = len(getattr(o, "children", None) or [])
  72. rows.append(data)
  73. return respond_xlsx(
  74. db, tenant_no=tenant_no, sheet_title="费用科目", columns=EXPENSE_ITEM_EXPORT_COLUMNS,
  75. rows=rows, filename_stem="费用科目", requested_columns=body.columns,
  76. )
  77. except ExportValidationError as e:
  78. raise HTTPException(status_code=400, detail=str(e)) from e
  79. @router.get("/{item_id}", response_model=ApiResponse[ExpenseItemOut])
  80. def get_expense_item(
  81. item_id: str,
  82. db: Session = Depends(get_db),
  83. user: BxUserContext = Depends(get_bx_current_user),
  84. ):
  85. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  86. item = ExpenseItemService.get_by_id(db, item_id, tenant_no, user=user)
  87. if not item:
  88. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="费用项目不存在")
  89. return ApiResponse(data=ExpenseItemOut.model_validate(item))
  90. @router.post("/", response_model=ApiResponse[ExpenseItemOut])
  91. def create_expense_item(
  92. body: ExpenseItemCreate,
  93. db: Session = Depends(get_db),
  94. user: BxUserContext = Depends(get_bx_current_user),
  95. ):
  96. try:
  97. item = ExpenseItemService.create(db, body.model_dump(exclude_unset=True), user=user)
  98. db.commit()
  99. db.refresh(item)
  100. except ValueError as exc:
  101. db.rollback()
  102. raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
  103. return ApiResponse(data=ExpenseItemOut.model_validate(item))
  104. @router.put("/{item_id}", response_model=ApiResponse[ExpenseItemOut])
  105. def update_expense_item(
  106. item_id: str,
  107. body: ExpenseItemUpdate,
  108. db: Session = Depends(get_db),
  109. user: BxUserContext = Depends(get_bx_current_user),
  110. ):
  111. try:
  112. item = ExpenseItemService.update(
  113. db, item_id, body.model_dump(exclude_unset=True), user=user
  114. )
  115. if not item:
  116. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="费用项目不存在")
  117. db.commit()
  118. db.refresh(item)
  119. except ValueError as exc:
  120. db.rollback()
  121. raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
  122. return ApiResponse(data=ExpenseItemOut.model_validate(item))
  123. @router.delete("/{item_id}", response_model=ApiResponse[None])
  124. def delete_expense_item(
  125. item_id: str,
  126. db: Session = Depends(get_db),
  127. user: BxUserContext = Depends(get_bx_current_user),
  128. ):
  129. tenant_no = get_tenant_no(db, user.to_tenant_user_dict())
  130. item = ExpenseItemService.get_by_id(db, item_id, tenant_no, user=user)
  131. if not item:
  132. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="费用项目不存在")
  133. ExpenseItemService.soft_delete(db, item_id, user=user)
  134. db.commit()
  135. return ApiResponse(data=None)