crm_contract_sub_routes.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. """CRM 合同子模块路由 — 变更/模板/审批独立页"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Optional
  5. from fastapi import Depends, Query
  6. from sqlalchemy.orm import Session
  7. from app.core.database import get_db
  8. from app.core.menu_guard import require_admin_or_super_admin
  9. from app.core.response import error_response, success_response
  10. from app.core.tenant_context import get_tenant_no
  11. from app.crud.crm.data_scope_helpers import crm_user_info
  12. from app.crud.crm.contract import (
  13. create_contract_change,
  14. create_contract_template,
  15. export_contract_changes,
  16. export_contract_templates,
  17. execute_change,
  18. get_contract_change_detail,
  19. get_contract_template_detail,
  20. list_contract_changes,
  21. list_contract_templates,
  22. soft_delete_contract_changes,
  23. soft_delete_contract_templates,
  24. submit_change_approval,
  25. toggle_contract_template_active,
  26. total_pages,
  27. update_contract_change,
  28. update_contract_template,
  29. withdraw_change,
  30. )
  31. from app.crud.crm.contract_approval import (
  32. approve_pass,
  33. approve_reject,
  34. approve_transfer,
  35. export_contract_approvals,
  36. get_contract_approval_detail,
  37. list_contract_approvals_page,
  38. refresh_approver_names,
  39. )
  40. from app.schemas.crm.contract import (
  41. ContractApprovalExportBody,
  42. ContractApprovalListQuery,
  43. ContractApprovalPassBody,
  44. ContractApprovalRefreshNamesBody,
  45. ContractApprovalRejectBody,
  46. ContractApprovalTransferBody,
  47. ContractChangeCreateBody,
  48. ContractChangeExportBody,
  49. ContractChangeListQuery,
  50. ContractChangeSubmitBody,
  51. ContractChangeUpdateBody,
  52. ContractTemplateCreateBody,
  53. ContractTemplateExportBody,
  54. ContractTemplateListQuery,
  55. ContractTemplateUpdateBody,
  56. )
  57. from app.utils.crm_export_columns import (
  58. CONTRACT_APPROVAL_EXPORT_COLUMNS,
  59. CONTRACT_CHANGE_EXPORT_COLUMNS,
  60. CONTRACT_TEMPLATE_EXPORT_COLUMNS,
  61. )
  62. from app.utils.excel_export import (
  63. ExportValidationError,
  64. export_rows_to_xlsx_response,
  65. filter_export_columns,
  66. )
  67. from app.api.crm import _filter_export_rows_by_ids
  68. def register_contract_sub_routes(router):
  69. # --- 合同变更 ---
  70. @router.get("/contract/change/list")
  71. async def contract_change_list(
  72. pageNum: int = Query(1, ge=1),
  73. pageSize: int = Query(10, ge=1, le=500),
  74. contractId: Optional[str] = None,
  75. contractNo: Optional[str] = None,
  76. contractName: Optional[str] = None,
  77. changeType: Optional[str] = None,
  78. approvalStatus: Optional[str] = None,
  79. beginTime: Optional[str] = None,
  80. endTime: Optional[str] = None,
  81. _user: dict = Depends(require_admin_or_super_admin),
  82. db: Session = Depends(get_db),
  83. ):
  84. try:
  85. tenant_no = get_tenant_no(db, _user)
  86. user_info = crm_user_info(_user)
  87. query = ContractChangeListQuery(
  88. pageNum=pageNum,
  89. pageSize=pageSize,
  90. contractId=contractId,
  91. contractNo=contractNo,
  92. contractName=contractName,
  93. changeType=changeType,
  94. approvalStatus=approvalStatus,
  95. beginTime=beginTime,
  96. endTime=endTime,
  97. )
  98. rows, total = list_contract_changes(
  99. db,
  100. tenant_no,
  101. user_info=user_info,
  102. page=query.pageNum,
  103. page_size=query.pageSize,
  104. filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
  105. )
  106. return success_response(
  107. {
  108. "list": rows,
  109. "rows": rows,
  110. "total": total,
  111. "pageNum": query.pageNum,
  112. "pageSize": query.pageSize,
  113. "totalPages": total_pages(total, query.pageSize),
  114. }
  115. )
  116. except ValueError as e:
  117. return error_response(400, str(e))
  118. except Exception as e:
  119. return error_response(500, str(e))
  120. @router.get("/contract/change/{change_id}")
  121. async def contract_change_detail(
  122. change_id: str,
  123. _user: dict = Depends(require_admin_or_super_admin),
  124. db: Session = Depends(get_db),
  125. ):
  126. try:
  127. tenant_no = get_tenant_no(db, _user)
  128. user_info = crm_user_info(_user)
  129. detail = get_contract_change_detail(db, change_id, tenant_no=tenant_no, user_info=user_info)
  130. return success_response(detail)
  131. except ValueError as e:
  132. return error_response(400, str(e))
  133. except Exception as e:
  134. return error_response(500, str(e))
  135. @router.post("/contract/change")
  136. async def contract_change_add(
  137. body: ContractChangeCreateBody,
  138. _user: dict = Depends(require_admin_or_super_admin),
  139. db: Session = Depends(get_db),
  140. ):
  141. try:
  142. tenant_no = get_tenant_no(db, _user)
  143. user_info = crm_user_info(_user)
  144. user_id = _user.get("user_id", "") or _user.get("id", "")
  145. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  146. detail = create_contract_change(
  147. db,
  148. tenant_no=tenant_no,
  149. data=body.model_dump(by_alias=True),
  150. user_id=user_id,
  151. dept_id=dept_id,
  152. )
  153. return success_response(detail["id"], message="新增成功")
  154. except ValueError as e:
  155. return error_response(400, str(e))
  156. except Exception as e:
  157. return error_response(500, str(e))
  158. @router.put("/contract/change")
  159. async def contract_change_edit(
  160. body: ContractChangeUpdateBody,
  161. _user: dict = Depends(require_admin_or_super_admin),
  162. db: Session = Depends(get_db),
  163. ):
  164. try:
  165. tenant_no = get_tenant_no(db, _user)
  166. user_info = crm_user_info(_user)
  167. user_id = _user.get("user_id", "") or _user.get("id", "")
  168. data = body.model_dump(by_alias=True)
  169. change_id = data.pop("id")
  170. detail = update_contract_change(
  171. db,
  172. change_id,
  173. tenant_no=tenant_no,
  174. data=data,
  175. user_id=user_id,
  176. user_info=user_info,
  177. )
  178. return success_response(detail, message="修改成功")
  179. except ValueError as e:
  180. return error_response(400, str(e))
  181. except Exception as e:
  182. return error_response(500, str(e))
  183. @router.delete("/contract/change/{ids}")
  184. async def contract_change_remove(
  185. ids: str,
  186. _user: dict = Depends(require_admin_or_super_admin),
  187. db: Session = Depends(get_db),
  188. ):
  189. try:
  190. tenant_no = get_tenant_no(db, _user)
  191. user_info = crm_user_info(_user)
  192. id_list = [x.strip() for x in ids.split(",") if x.strip()]
  193. soft_delete_contract_changes(db, id_list, tenant_no=tenant_no, user_info=user_info)
  194. return success_response(None, message="删除成功")
  195. except ValueError as e:
  196. return error_response(400, str(e))
  197. except Exception as e:
  198. return error_response(500, str(e))
  199. @router.post("/contract/change/submitApproval")
  200. async def contract_change_submit_approval(
  201. body: ContractChangeSubmitBody,
  202. _user: dict = Depends(require_admin_or_super_admin),
  203. db: Session = Depends(get_db),
  204. ):
  205. try:
  206. tenant_no = get_tenant_no(db, _user)
  207. user_info = crm_user_info(_user)
  208. user_id = _user.get("user_id", "") or _user.get("id", "")
  209. submit_change_approval(
  210. db,
  211. body.id,
  212. tenant_no=tenant_no,
  213. approver_ids=body.approverIds,
  214. remark=body.remark or "",
  215. user_id=user_id,
  216. )
  217. return success_response(None, message="提交成功")
  218. except ValueError as e:
  219. return error_response(400, str(e))
  220. except Exception as e:
  221. return error_response(500, str(e))
  222. @router.put("/contract/change/withdraw/{change_id}")
  223. async def contract_change_withdraw(
  224. change_id: str,
  225. _user: dict = Depends(require_admin_or_super_admin),
  226. db: Session = Depends(get_db),
  227. ):
  228. try:
  229. tenant_no = get_tenant_no(db, _user)
  230. user_info = crm_user_info(_user)
  231. user_id = _user.get("user_id", "") or _user.get("id", "")
  232. withdraw_change(db, change_id, tenant_no=tenant_no, user_id=user_id)
  233. return success_response(None, message="撤回成功")
  234. except ValueError as e:
  235. return error_response(400, str(e))
  236. except Exception as e:
  237. return error_response(500, str(e))
  238. @router.put("/contract/change/execute/{change_id}")
  239. async def contract_change_execute(
  240. change_id: str,
  241. _user: dict = Depends(require_admin_or_super_admin),
  242. db: Session = Depends(get_db),
  243. ):
  244. try:
  245. tenant_no = get_tenant_no(db, _user)
  246. user_info = crm_user_info(_user)
  247. user_id = _user.get("user_id", "") or _user.get("id", "")
  248. execute_change(db, change_id, tenant_no=tenant_no, user_id=user_id)
  249. return success_response(None, message="执行成功")
  250. except ValueError as e:
  251. return error_response(400, str(e))
  252. except Exception as e:
  253. return error_response(500, str(e))
  254. @router.post("/contract/change/export")
  255. async def contract_change_export(
  256. body: ContractChangeExportBody = None,
  257. _user: dict = Depends(require_admin_or_super_admin),
  258. db: Session = Depends(get_db),
  259. ):
  260. try:
  261. tenant_no = get_tenant_no(db, _user)
  262. user_info = crm_user_info(_user)
  263. list_body = body or ContractChangeExportBody()
  264. filters = list_body.model_dump(
  265. by_alias=True,
  266. exclude={"pageNum", "pageSize"},
  267. exclude_none=True,
  268. )
  269. rows = export_contract_changes(db, tenant_no, filters=filters)
  270. rows = _filter_export_rows_by_ids(rows, getattr(list_body, "ids", None) or getattr(body, "ids", None))
  271. export_cols = filter_export_columns(CONTRACT_CHANGE_EXPORT_COLUMNS, getattr(list_body, "columns", None) or getattr(body, "columns", None))
  272. return export_rows_to_xlsx_response(
  273. db,
  274. tenant_no=tenant_no,
  275. sheet_title="合同变更记录",
  276. columns=export_cols,
  277. rows=rows,
  278. filename_stem=f"合同变更_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  279. include_legacy_filename=False,
  280. )
  281. except ValueError as e:
  282. return error_response(400, str(e))
  283. except Exception as e:
  284. return error_response(500, str(e))
  285. # --- 合同模板库 ---
  286. @router.get("/contract/template/list")
  287. async def contract_template_list(
  288. pageNum: int = Query(1, ge=1),
  289. pageSize: int = Query(10, ge=1, le=500),
  290. templateNo: Optional[str] = None,
  291. templateName: Optional[str] = None,
  292. templateType: Optional[str] = None,
  293. isActive: Optional[int] = None,
  294. beginTime: Optional[str] = None,
  295. endTime: Optional[str] = None,
  296. _user: dict = Depends(require_admin_or_super_admin),
  297. db: Session = Depends(get_db),
  298. ):
  299. try:
  300. tenant_no = get_tenant_no(db, _user)
  301. user_info = crm_user_info(_user)
  302. query = ContractTemplateListQuery(
  303. pageNum=pageNum,
  304. pageSize=pageSize,
  305. templateNo=templateNo,
  306. templateName=templateName,
  307. templateType=templateType,
  308. isActive=isActive,
  309. beginTime=beginTime,
  310. endTime=endTime,
  311. )
  312. rows, total = list_contract_templates(
  313. db,
  314. tenant_no,
  315. user_info=user_info,
  316. page=query.pageNum,
  317. page_size=query.pageSize,
  318. filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
  319. )
  320. return success_response(
  321. {
  322. "list": rows,
  323. "rows": rows,
  324. "total": total,
  325. "pageNum": query.pageNum,
  326. "pageSize": query.pageSize,
  327. "totalPages": total_pages(total, query.pageSize),
  328. }
  329. )
  330. except ValueError as e:
  331. return error_response(400, str(e))
  332. except Exception as e:
  333. return error_response(500, str(e))
  334. @router.get("/contract/template/{template_id}")
  335. async def contract_template_detail(
  336. template_id: str,
  337. _user: dict = Depends(require_admin_or_super_admin),
  338. db: Session = Depends(get_db),
  339. ):
  340. try:
  341. tenant_no = get_tenant_no(db, _user)
  342. user_info = crm_user_info(_user)
  343. detail = get_contract_template_detail(db, template_id, tenant_no=tenant_no)
  344. return success_response(detail)
  345. except ValueError as e:
  346. return error_response(400, str(e))
  347. except Exception as e:
  348. return error_response(500, str(e))
  349. @router.post("/contract/template")
  350. async def contract_template_add(
  351. body: ContractTemplateCreateBody,
  352. _user: dict = Depends(require_admin_or_super_admin),
  353. db: Session = Depends(get_db),
  354. ):
  355. try:
  356. tenant_no = get_tenant_no(db, _user)
  357. user_info = crm_user_info(_user)
  358. user_id = _user.get("user_id", "") or _user.get("id", "")
  359. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  360. detail = create_contract_template(
  361. db,
  362. tenant_no=tenant_no,
  363. data=body.model_dump(by_alias=True),
  364. user_id=user_id,
  365. dept_id=dept_id,
  366. )
  367. return success_response(detail["id"], message="新增成功")
  368. except ValueError as e:
  369. return error_response(400, str(e))
  370. except Exception as e:
  371. return error_response(500, str(e))
  372. @router.put("/contract/template")
  373. async def contract_template_edit(
  374. body: ContractTemplateUpdateBody,
  375. _user: dict = Depends(require_admin_or_super_admin),
  376. db: Session = Depends(get_db),
  377. ):
  378. try:
  379. tenant_no = get_tenant_no(db, _user)
  380. user_info = crm_user_info(_user)
  381. user_id = _user.get("user_id", "") or _user.get("id", "")
  382. data = body.model_dump(by_alias=True)
  383. template_id = data.pop("id")
  384. detail = update_contract_template(
  385. db,
  386. template_id,
  387. tenant_no=tenant_no,
  388. data=data,
  389. user_id=user_id,
  390. user_info=user_info,
  391. )
  392. return success_response(detail, message="修改成功")
  393. except ValueError as e:
  394. return error_response(400, str(e))
  395. except Exception as e:
  396. return error_response(500, str(e))
  397. @router.delete("/contract/template/{ids}")
  398. async def contract_template_remove(
  399. ids: str,
  400. _user: dict = Depends(require_admin_or_super_admin),
  401. db: Session = Depends(get_db),
  402. ):
  403. try:
  404. tenant_no = get_tenant_no(db, _user)
  405. user_info = crm_user_info(_user)
  406. id_list = [x.strip() for x in ids.split(",") if x.strip()]
  407. soft_delete_contract_templates(db, id_list, tenant_no=tenant_no, user_info=user_info)
  408. return success_response(None, message="删除成功")
  409. except ValueError as e:
  410. return error_response(400, str(e))
  411. except Exception as e:
  412. return error_response(500, str(e))
  413. @router.put("/contract/template/enable/{template_id}")
  414. async def contract_template_enable(
  415. template_id: str,
  416. _user: dict = Depends(require_admin_or_super_admin),
  417. db: Session = Depends(get_db),
  418. ):
  419. try:
  420. tenant_no = get_tenant_no(db, _user)
  421. user_info = crm_user_info(_user)
  422. user_id = _user.get("user_id", "") or _user.get("id", "")
  423. toggle_contract_template_active(
  424. db, template_id, tenant_no=tenant_no, is_active=1, user_id=user_id
  425. )
  426. return success_response(None, message="启用成功")
  427. except ValueError as e:
  428. return error_response(400, str(e))
  429. except Exception as e:
  430. return error_response(500, str(e))
  431. @router.put("/contract/template/disable/{template_id}")
  432. async def contract_template_disable(
  433. template_id: str,
  434. _user: dict = Depends(require_admin_or_super_admin),
  435. db: Session = Depends(get_db),
  436. ):
  437. try:
  438. tenant_no = get_tenant_no(db, _user)
  439. user_info = crm_user_info(_user)
  440. user_id = _user.get("user_id", "") or _user.get("id", "")
  441. toggle_contract_template_active(
  442. db, template_id, tenant_no=tenant_no, is_active=0, user_id=user_id
  443. )
  444. return success_response(None, message="停用成功")
  445. except ValueError as e:
  446. return error_response(400, str(e))
  447. except Exception as e:
  448. return error_response(500, str(e))
  449. @router.post("/contract/template/export")
  450. async def contract_template_export(
  451. body: ContractTemplateExportBody = None,
  452. _user: dict = Depends(require_admin_or_super_admin),
  453. db: Session = Depends(get_db),
  454. ):
  455. try:
  456. tenant_no = get_tenant_no(db, _user)
  457. user_info = crm_user_info(_user)
  458. list_body = body or ContractTemplateExportBody()
  459. filters = list_body.model_dump(
  460. by_alias=True,
  461. exclude={"pageNum", "pageSize"},
  462. exclude_none=True,
  463. )
  464. rows = export_contract_templates(db, tenant_no, filters=filters)
  465. rows = _filter_export_rows_by_ids(rows, getattr(list_body, "ids", None) or getattr(body, "ids", None))
  466. export_cols = filter_export_columns(CONTRACT_TEMPLATE_EXPORT_COLUMNS, getattr(list_body, "columns", None) or getattr(body, "columns", None))
  467. return export_rows_to_xlsx_response(
  468. db,
  469. tenant_no=tenant_no,
  470. sheet_title="合同模板",
  471. columns=export_cols,
  472. rows=rows,
  473. filename_stem=f"合同模板_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  474. include_legacy_filename=False,
  475. )
  476. except ValueError as e:
  477. return error_response(400, str(e))
  478. except Exception as e:
  479. return error_response(500, str(e))
  480. # --- 审批记录独立页 ---
  481. @router.get("/contract/approval/list")
  482. async def contract_approval_list(
  483. pageNum: int = Query(1, ge=1),
  484. pageSize: int = Query(10, ge=1, le=500),
  485. contractId: Optional[str] = None,
  486. contractNo: Optional[str] = None,
  487. contractName: Optional[str] = None,
  488. approverName: Optional[str] = None,
  489. approvalResult: Optional[str] = None,
  490. beginTime: Optional[str] = None,
  491. endTime: Optional[str] = None,
  492. _user: dict = Depends(require_admin_or_super_admin),
  493. db: Session = Depends(get_db),
  494. ):
  495. try:
  496. tenant_no = get_tenant_no(db, _user)
  497. user_info = crm_user_info(_user)
  498. query = ContractApprovalListQuery(
  499. pageNum=pageNum,
  500. pageSize=pageSize,
  501. contractId=contractId,
  502. contractNo=contractNo,
  503. contractName=contractName,
  504. approverName=approverName,
  505. approvalResult=approvalResult,
  506. beginTime=beginTime,
  507. endTime=endTime,
  508. )
  509. rows, total = list_contract_approvals_page(
  510. db,
  511. tenant_no,
  512. page=query.pageNum,
  513. page_size=query.pageSize,
  514. filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
  515. )
  516. return success_response(
  517. {
  518. "list": rows,
  519. "rows": rows,
  520. "total": total,
  521. "pageNum": query.pageNum,
  522. "pageSize": query.pageSize,
  523. "totalPages": total_pages(total, query.pageSize),
  524. }
  525. )
  526. except ValueError as e:
  527. return error_response(400, str(e))
  528. except Exception as e:
  529. return error_response(500, str(e))
  530. @router.get("/contract/approval/{approval_id}")
  531. async def contract_approval_detail(
  532. approval_id: str,
  533. _user: dict = Depends(require_admin_or_super_admin),
  534. db: Session = Depends(get_db),
  535. ):
  536. try:
  537. tenant_no = get_tenant_no(db, _user)
  538. user_info = crm_user_info(_user)
  539. detail = get_contract_approval_detail(db, approval_id, tenant_no=tenant_no)
  540. return success_response(detail)
  541. except ValueError as e:
  542. return error_response(400, str(e))
  543. except Exception as e:
  544. return error_response(500, str(e))
  545. @router.post("/contract/approval/pass")
  546. async def contract_approval_pass(
  547. body: ContractApprovalPassBody,
  548. _user: dict = Depends(require_admin_or_super_admin),
  549. db: Session = Depends(get_db),
  550. ):
  551. try:
  552. user_id = _user.get("user_id", "") or _user.get("id", "")
  553. approve_pass(db, body.id, approval_opinion=body.approvalOpinion or "", user_id=user_id)
  554. db.commit()
  555. return success_response(None, message="审批通过")
  556. except ValueError as e:
  557. return error_response(400, str(e))
  558. except Exception as e:
  559. return error_response(500, str(e))
  560. @router.post("/contract/approval/reject")
  561. async def contract_approval_reject(
  562. body: ContractApprovalRejectBody,
  563. _user: dict = Depends(require_admin_or_super_admin),
  564. db: Session = Depends(get_db),
  565. ):
  566. try:
  567. user_id = _user.get("user_id", "") or _user.get("id", "")
  568. approve_reject(db, body.id, approval_opinion=body.approvalOpinion, user_id=user_id)
  569. db.commit()
  570. return success_response(None, message="已驳回")
  571. except ValueError as e:
  572. return error_response(400, str(e))
  573. except Exception as e:
  574. return error_response(500, str(e))
  575. @router.post("/contract/approval/transfer")
  576. async def contract_approval_transfer(
  577. body: ContractApprovalTransferBody,
  578. _user: dict = Depends(require_admin_or_super_admin),
  579. db: Session = Depends(get_db),
  580. ):
  581. try:
  582. user_id = _user.get("user_id", "") or _user.get("id", "")
  583. approve_transfer(
  584. db,
  585. body.id,
  586. target_approver_id=body.targetApproverId,
  587. transfer_reason=body.transferReason,
  588. user_id=user_id,
  589. )
  590. db.commit()
  591. return success_response(None, message="转审成功")
  592. except ValueError as e:
  593. return error_response(400, str(e))
  594. except Exception as e:
  595. return error_response(500, str(e))
  596. @router.post("/contract/approval/export")
  597. async def contract_approval_export(
  598. body: ContractApprovalExportBody = None,
  599. _user: dict = Depends(require_admin_or_super_admin),
  600. db: Session = Depends(get_db),
  601. ):
  602. try:
  603. tenant_no = get_tenant_no(db, _user)
  604. user_info = crm_user_info(_user)
  605. list_body = body or ContractApprovalExportBody()
  606. filters = list_body.model_dump(
  607. by_alias=True,
  608. exclude={"pageNum", "pageSize"},
  609. exclude_none=True,
  610. )
  611. rows = export_contract_approvals(db, tenant_no, filters=filters)
  612. rows = _filter_export_rows_by_ids(rows, getattr(list_body, "ids", None) or getattr(body, "ids", None))
  613. export_cols = filter_export_columns(CONTRACT_APPROVAL_EXPORT_COLUMNS, getattr(list_body, "columns", None) or getattr(body, "columns", None))
  614. return export_rows_to_xlsx_response(
  615. db,
  616. tenant_no=tenant_no,
  617. sheet_title="合同审批记录",
  618. columns=export_cols,
  619. rows=rows,
  620. filename_stem=f"合同审批_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  621. include_legacy_filename=False,
  622. )
  623. except ValueError as e:
  624. return error_response(400, str(e))
  625. except Exception as e:
  626. return error_response(500, str(e))
  627. @router.post("/contract/approval/refreshNames")
  628. async def contract_approval_refresh_names(
  629. body: ContractApprovalRefreshNamesBody = None,
  630. _user: dict = Depends(require_admin_or_super_admin),
  631. db: Session = Depends(get_db),
  632. ):
  633. try:
  634. req = body or ContractApprovalRefreshNamesBody()
  635. count = refresh_approver_names(db, contract_id=req.contractId)
  636. db.commit()
  637. return success_response(count)
  638. except ValueError as e:
  639. return error_response(400, str(e))
  640. except Exception as e:
  641. return error_response(500, str(e))