export_common.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. """bx 列表 Excel 导出公共辅助。"""
  2. from __future__ import annotations
  3. from datetime import date, datetime
  4. from typing import Any, List, Mapping, Optional, Sequence
  5. from pydantic import BaseModel, ConfigDict, Field
  6. from sqlalchemy.orm import Session
  7. from app.utils.excel_export import (
  8. EXPORT_MAX_ROWS,
  9. ExportColumn,
  10. ExportValidationError,
  11. export_rows_to_xlsx_response,
  12. filter_export_columns,
  13. )
  14. BX_STATUS_LABELS = {
  15. "draft": "待提交",
  16. "pending": "审批中",
  17. "approved": "审批通过",
  18. "rejected": "审批驳回",
  19. "issued": "已发放",
  20. "paid": "已发放",
  21. "offset": "已冲抵",
  22. "processed": "已处理",
  23. }
  24. ISSUE_STATUS_LABELS = {
  25. "issued": "已发放",
  26. "pending": "待发放",
  27. }
  28. class BxExportBodyBase(BaseModel):
  29. model_config = ConfigDict(populate_by_name=True, extra="allow")
  30. ids: Optional[List[str]] = None
  31. columns: Optional[List[str]] = None
  32. class BxApplyListExportBody(BxExportBodyBase):
  33. model_config = ConfigDict(populate_by_name=True, extra="allow")
  34. status: Optional[str] = None
  35. keyword: str = ""
  36. applicant_id: Optional[str] = Field(default=None, alias="applicantId")
  37. apply_date: Optional[str] = Field(default=None, alias="applyDate")
  38. apply_date_from: Optional[str] = Field(default=None, alias="applyDateFrom")
  39. apply_date_to: Optional[str] = Field(default=None, alias="applyDateTo")
  40. expense_id: Optional[str] = Field(default=None, alias="expenseId")
  41. expense_name: Optional[str] = Field(default=None, alias="expenseName")
  42. apply_code: Optional[str] = Field(default=None, alias="applyCode")
  43. title: Optional[str] = None
  44. current_node: Optional[str] = Field(default=None, alias="currentNode")
  45. status_in: Optional[str] = Field(default=None, alias="statusIn")
  46. amount_op: Optional[str] = Field(default=None, alias="amountOp")
  47. amount: Optional[str] = None
  48. amount_min: Optional[str] = Field(default=None, alias="amountMin")
  49. amount_max: Optional[str] = Field(default=None, alias="amountMax")
  50. reason: Optional[str] = None
  51. applicant_name: Optional[str] = Field(default=None, alias="applicantName")
  52. actual_amount_op: Optional[str] = Field(default=None, alias="actualAmountOp")
  53. actual_amount: Optional[str] = Field(default=None, alias="actualAmount")
  54. actual_amount_min: Optional[str] = Field(default=None, alias="actualAmountMin")
  55. actual_amount_max: Optional[str] = Field(default=None, alias="actualAmountMax")
  56. offset_amount_op: Optional[str] = Field(default=None, alias="offsetAmountOp")
  57. offset_amount: Optional[str] = Field(default=None, alias="offsetAmount")
  58. offset_amount_min: Optional[str] = Field(default=None, alias="offsetAmountMin")
  59. offset_amount_max: Optional[str] = Field(default=None, alias="offsetAmountMax")
  60. issue_time_from: Optional[str] = Field(default=None, alias="issueTimeFrom")
  61. issue_time_to: Optional[str] = Field(default=None, alias="issueTimeTo")
  62. issue_user_id: Optional[str] = Field(default=None, alias="issueUserId")
  63. issue_user_name: Optional[str] = Field(default=None, alias="issueUserName")
  64. issue_by: Optional[str] = Field(default=None, alias="issueBy")
  65. sort_field: Optional[str] = Field(default=None, alias="sortField")
  66. sort_order: Optional[str] = Field(default=None, alias="sortOrder")
  67. def status_label(status: Any) -> str:
  68. if status is None:
  69. return ""
  70. if status in (0, "0"):
  71. return "禁用"
  72. if status in (1, "1"):
  73. return "启用"
  74. key = str(status)
  75. return BX_STATUS_LABELS.get(key, key)
  76. def issue_status_label(status: Any) -> str:
  77. if status == "issued":
  78. return "已发放"
  79. return "待发放"
  80. def dump_export_row(
  81. obj: Any,
  82. *,
  83. status_fields: Sequence[str] = ("status",),
  84. extra: Optional[Mapping[str, Any]] = None,
  85. ) -> dict:
  86. if hasattr(obj, "model_dump"):
  87. data = obj.model_dump(by_alias=True)
  88. elif isinstance(obj, Mapping):
  89. data = dict(obj)
  90. else:
  91. data = dict(obj)
  92. for key, value in list(data.items()):
  93. if isinstance(value, datetime):
  94. data[key] = value.strftime("%Y-%m-%d %H:%M:%S")
  95. elif isinstance(value, date):
  96. data[key] = value.isoformat()
  97. for field in status_fields:
  98. if field in data:
  99. data[field] = status_label(data.get(field))
  100. if extra:
  101. data.update(extra)
  102. return data
  103. def respond_xlsx(
  104. db: Session,
  105. *,
  106. tenant_no: str,
  107. sheet_title: str,
  108. columns: Sequence[ExportColumn],
  109. rows: Sequence[Mapping[str, Any]],
  110. filename_stem: str,
  111. requested_columns: Optional[Sequence[str]] = None,
  112. ):
  113. cols = filter_export_columns(columns, requested_columns)
  114. date_str = datetime.now().strftime("%Y%m%d")
  115. return export_rows_to_xlsx_response(
  116. db,
  117. tenant_no=tenant_no,
  118. sheet_title=sheet_title,
  119. columns=cols,
  120. rows=rows,
  121. filename_stem=f"{filename_stem}_{date_str}",
  122. )
  123. __all__ = [
  124. "EXPORT_MAX_ROWS",
  125. "ExportColumn",
  126. "ExportValidationError",
  127. "BxExportBodyBase",
  128. "BxApplyListExportBody",
  129. "status_label",
  130. "issue_status_label",
  131. "dump_export_row",
  132. "respond_xlsx",
  133. "ISSUE_STATUS_LABELS",
  134. ]