document_export.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. from __future__ import annotations
  2. from datetime import datetime
  3. from io import BytesIO
  4. from typing import Any
  5. from openpyxl import Workbook
  6. from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
  7. from openpyxl.utils import get_column_letter
  8. from app.db.models.document import BusinessDocument
  9. PARSE_STATUS_LABELS = {
  10. "pending": "等待解析",
  11. "running": "处理中",
  12. "parsing": "解析中",
  13. "extracting": "抽取中",
  14. "completed": "抽取完成",
  15. "failed": "处理失败",
  16. }
  17. def _safe_excel_value(value: Any) -> Any:
  18. if isinstance(value, str) and value.startswith(("=", "+", "-", "@")):
  19. return f"'{value}"
  20. return value
  21. def build_document_list_workbook(
  22. documents: list[BusinessDocument],
  23. extraction_counts: dict[str, int],
  24. *,
  25. mine_name: str,
  26. category_label: str = "全部资料",
  27. keyword: str = "",
  28. document_type_labels: dict[str, str] | None = None,
  29. ) -> BytesIO:
  30. type_labels = document_type_labels or {}
  31. workbook = Workbook()
  32. sheet = workbook.active
  33. sheet.title = "资料清单"
  34. headers = [
  35. "序号",
  36. "资料名称",
  37. "文档编号",
  38. "资料分组",
  39. "资料子类",
  40. "资料类型",
  41. "文件名",
  42. "扩展名",
  43. "文件大小(MB)",
  44. "页数",
  45. "行数",
  46. "资料责任人",
  47. "部门",
  48. "版本",
  49. "解析状态",
  50. "解析进度",
  51. "抽取字段数",
  52. "入库日期",
  53. "生效日期",
  54. "到期日期",
  55. "摘要",
  56. ]
  57. last_column = get_column_letter(len(headers))
  58. sheet.merge_cells(f"A1:{last_column}1")
  59. sheet["A1"] = f"{mine_name}业务资料清单"
  60. sheet["A1"].font = Font(size=16, bold=True, color="16222F")
  61. sheet["A1"].alignment = Alignment(horizontal="center", vertical="center")
  62. sheet.row_dimensions[1].height = 30
  63. sheet.merge_cells(f"A2:{last_column}2")
  64. filter_text = f"导出时间:{datetime.now().strftime('%Y-%m-%d %H:%M')} 范围:{category_label}"
  65. if keyword:
  66. filter_text += f" 搜索:{keyword}"
  67. sheet["A2"] = filter_text
  68. sheet["A2"].font = Font(size=10, color="6F7F8E")
  69. sheet["A2"].alignment = Alignment(horizontal="left", vertical="center")
  70. for column_index, header in enumerate(headers, start=1):
  71. cell = sheet.cell(row=3, column=column_index, value=header)
  72. cell.fill = PatternFill("solid", fgColor="0F9F8F")
  73. cell.font = Font(color="FFFFFF", bold=True)
  74. cell.alignment = Alignment(horizontal="center", vertical="center")
  75. cell.border = Border(
  76. bottom=Side(style="thin", color="D5E2E6"),
  77. )
  78. sheet.row_dimensions[3].height = 24
  79. date_format = "yyyy-mm-dd"
  80. thin_border = Border(
  81. bottom=Side(style="hair", color="E2E9EC"),
  82. )
  83. for row_index, document in enumerate(documents, start=4):
  84. values = [
  85. row_index - 3,
  86. document.title,
  87. document.code or "",
  88. document.category,
  89. document.subcategory or "未填写",
  90. type_labels.get(document.document_type, document.document_type or "未分类"),
  91. document.file_name,
  92. document.file_ext,
  93. round(float(document.file_size or 0) / (1024 * 1024), 2),
  94. document.page_count,
  95. document.row_count,
  96. document.owner_name,
  97. document.department,
  98. document.version,
  99. PARSE_STATUS_LABELS.get(document.parse_status, document.parse_status),
  100. f"{int(document.parse_progress or 0)}%",
  101. int(extraction_counts.get(document.id, 0)),
  102. document.created_at,
  103. document.effective_date,
  104. document.expire_date,
  105. document.summary,
  106. ]
  107. for column_index, value in enumerate(values, start=1):
  108. cell = sheet.cell(
  109. row=row_index,
  110. column=column_index,
  111. value=_safe_excel_value(value),
  112. )
  113. cell.alignment = Alignment(
  114. vertical="top",
  115. wrap_text=column_index in {2, 6, 7, 21},
  116. )
  117. cell.border = thin_border
  118. if column_index in {18, 19, 20} and value is not None:
  119. cell.number_format = date_format
  120. if column_index in {9, 10, 11, 16, 17}:
  121. cell.alignment = Alignment(horizontal="center", vertical="top")
  122. sheet.freeze_panes = "A4"
  123. sheet.auto_filter.ref = f"A3:{last_column}{max(sheet.max_row, 3)}"
  124. widths = {
  125. 1: 7,
  126. 2: 38,
  127. 3: 20,
  128. 4: 14,
  129. 5: 16,
  130. 6: 24,
  131. 7: 34,
  132. 8: 10,
  133. 9: 14,
  134. 10: 9,
  135. 11: 9,
  136. 12: 14,
  137. 13: 14,
  138. 14: 10,
  139. 15: 13,
  140. 16: 11,
  141. 17: 13,
  142. 18: 13,
  143. 19: 13,
  144. 20: 13,
  145. 21: 48,
  146. }
  147. for column_index, width in widths.items():
  148. sheet.column_dimensions[get_column_letter(column_index)].width = width
  149. workbook.properties.title = f"{mine_name}业务资料清单"
  150. workbook.properties.creator = "矿安通防智能管控平台"
  151. output = BytesIO()
  152. workbook.save(output)
  153. workbook.close()
  154. output.seek(0)
  155. return output