crm_contract_sub_routes.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. """CRM 合同子模块路由 — 变更/模板/审批独立页"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Optional
  5. from fastapi import Depends, Query, Request
  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. request: Request,
  73. pageNum: int = Query(1, ge=1),
  74. pageSize: int = Query(10, ge=1, le=500),
  75. contractId: Optional[str] = None,
  76. contractNo: Optional[str] = None,
  77. contractName: Optional[str] = None,
  78. changeType: Optional[str] = None,
  79. approvalStatus: Optional[str] = None,
  80. beginTime: Optional[str] = None,
  81. endTime: Optional[str] = None,
  82. _user: dict = Depends(require_admin_or_super_admin),
  83. db: Session = Depends(get_db),
  84. ):
  85. try:
  86. tenant_no = get_tenant_no(db, _user)
  87. user_info = crm_user_info(_user)
  88. query = ContractChangeListQuery(
  89. pageNum=pageNum,
  90. pageSize=pageSize,
  91. contractId=contractId,
  92. contractNo=contractNo,
  93. contractName=contractName,
  94. changeType=changeType,
  95. approvalStatus=approvalStatus,
  96. beginTime=beginTime,
  97. endTime=endTime,
  98. )
  99. filters = query.model_dump(
  100. by_alias=True,
  101. exclude={"pageNum", "pageSize"},
  102. exclude_none=True,
  103. )
  104. if query.model_extra:
  105. filters.update(
  106. {
  107. k: v
  108. for k, v in query.model_extra.items()
  109. if v is not None and str(v).strip() != ""
  110. }
  111. )
  112. # 列头漏斗等未在签名中声明的 query 参数一并透传
  113. skip_keys = {"pageNum", "pageSize", *filters.keys()}
  114. for key, val in request.query_params.multi_items():
  115. if key in skip_keys or val is None or str(val).strip() == "":
  116. continue
  117. filters[key] = val
  118. rows, total = list_contract_changes(
  119. db,
  120. tenant_no,
  121. user_info=user_info,
  122. page=query.pageNum,
  123. page_size=query.pageSize,
  124. filters=filters,
  125. )
  126. return success_response(
  127. {
  128. "list": rows,
  129. "rows": rows,
  130. "total": total,
  131. "pageNum": query.pageNum,
  132. "pageSize": query.pageSize,
  133. "totalPages": total_pages(total, query.pageSize),
  134. }
  135. )
  136. except ValueError as e:
  137. return error_response(400, str(e))
  138. except Exception as e:
  139. return error_response(500, str(e))
  140. @router.get("/contract/change/{change_id}")
  141. async def contract_change_detail(
  142. change_id: str,
  143. _user: dict = Depends(require_admin_or_super_admin),
  144. db: Session = Depends(get_db),
  145. ):
  146. try:
  147. tenant_no = get_tenant_no(db, _user)
  148. user_info = crm_user_info(_user)
  149. detail = get_contract_change_detail(db, change_id, tenant_no=tenant_no, user_info=user_info)
  150. return success_response(detail)
  151. except ValueError as e:
  152. return error_response(400, str(e))
  153. except Exception as e:
  154. return error_response(500, str(e))
  155. @router.post("/contract/change")
  156. async def contract_change_add(
  157. body: ContractChangeCreateBody,
  158. _user: dict = Depends(require_admin_or_super_admin),
  159. db: Session = Depends(get_db),
  160. ):
  161. try:
  162. tenant_no = get_tenant_no(db, _user)
  163. user_info = crm_user_info(_user)
  164. user_id = _user.get("user_id", "") or _user.get("id", "")
  165. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  166. detail = create_contract_change(
  167. db,
  168. tenant_no=tenant_no,
  169. data=body.model_dump(by_alias=True),
  170. user_id=user_id,
  171. dept_id=dept_id,
  172. )
  173. return success_response(detail["id"], message="新增成功")
  174. except ValueError as e:
  175. return error_response(400, str(e))
  176. except Exception as e:
  177. return error_response(500, str(e))
  178. @router.put("/contract/change")
  179. async def contract_change_edit(
  180. body: ContractChangeUpdateBody,
  181. _user: dict = Depends(require_admin_or_super_admin),
  182. db: Session = Depends(get_db),
  183. ):
  184. try:
  185. tenant_no = get_tenant_no(db, _user)
  186. user_info = crm_user_info(_user)
  187. user_id = _user.get("user_id", "") or _user.get("id", "")
  188. data = body.model_dump(by_alias=True)
  189. change_id = data.pop("id")
  190. detail = update_contract_change(
  191. db,
  192. change_id,
  193. tenant_no=tenant_no,
  194. data=data,
  195. user_id=user_id,
  196. user_info=user_info,
  197. )
  198. return success_response(detail, message="修改成功")
  199. except ValueError as e:
  200. return error_response(400, str(e))
  201. except Exception as e:
  202. return error_response(500, str(e))
  203. @router.delete("/contract/change/{ids}")
  204. async def contract_change_remove(
  205. ids: str,
  206. _user: dict = Depends(require_admin_or_super_admin),
  207. db: Session = Depends(get_db),
  208. ):
  209. try:
  210. tenant_no = get_tenant_no(db, _user)
  211. user_info = crm_user_info(_user)
  212. id_list = [x.strip() for x in ids.split(",") if x.strip()]
  213. soft_delete_contract_changes(db, id_list, tenant_no=tenant_no, user_info=user_info)
  214. return success_response(None, message="删除成功")
  215. except ValueError as e:
  216. return error_response(400, str(e))
  217. except Exception as e:
  218. return error_response(500, str(e))
  219. @router.post("/contract/change/submitApproval")
  220. async def contract_change_submit_approval(
  221. body: ContractChangeSubmitBody,
  222. _user: dict = Depends(require_admin_or_super_admin),
  223. db: Session = Depends(get_db),
  224. ):
  225. try:
  226. tenant_no = get_tenant_no(db, _user)
  227. user_info = crm_user_info(_user)
  228. user_id = _user.get("user_id", "") or _user.get("id", "")
  229. submit_change_approval(
  230. db,
  231. body.id,
  232. tenant_no=tenant_no,
  233. approver_ids=body.approverIds,
  234. remark=body.remark or "",
  235. user_id=user_id,
  236. )
  237. return success_response(None, message="提交成功")
  238. except ValueError as e:
  239. return error_response(400, str(e))
  240. except Exception as e:
  241. return error_response(500, str(e))
  242. @router.put("/contract/change/withdraw/{change_id}")
  243. async def contract_change_withdraw(
  244. change_id: str,
  245. _user: dict = Depends(require_admin_or_super_admin),
  246. db: Session = Depends(get_db),
  247. ):
  248. try:
  249. tenant_no = get_tenant_no(db, _user)
  250. user_info = crm_user_info(_user)
  251. user_id = _user.get("user_id", "") or _user.get("id", "")
  252. withdraw_change(db, change_id, tenant_no=tenant_no, user_id=user_id)
  253. return success_response(None, message="撤回成功")
  254. except ValueError as e:
  255. return error_response(400, str(e))
  256. except Exception as e:
  257. return error_response(500, str(e))
  258. @router.put("/contract/change/execute/{change_id}")
  259. async def contract_change_execute(
  260. change_id: str,
  261. _user: dict = Depends(require_admin_or_super_admin),
  262. db: Session = Depends(get_db),
  263. ):
  264. try:
  265. tenant_no = get_tenant_no(db, _user)
  266. user_info = crm_user_info(_user)
  267. user_id = _user.get("user_id", "") or _user.get("id", "")
  268. execute_change(db, change_id, tenant_no=tenant_no, user_id=user_id)
  269. return success_response(None, message="执行成功")
  270. except ValueError as e:
  271. return error_response(400, str(e))
  272. except Exception as e:
  273. return error_response(500, str(e))
  274. @router.post("/contract/change/export")
  275. async def contract_change_export(
  276. body: ContractChangeExportBody = None,
  277. _user: dict = Depends(require_admin_or_super_admin),
  278. db: Session = Depends(get_db),
  279. ):
  280. try:
  281. tenant_no = get_tenant_no(db, _user)
  282. user_info = crm_user_info(_user)
  283. list_body = body or ContractChangeExportBody()
  284. filters = list_body.model_dump(
  285. by_alias=True,
  286. exclude={"pageNum", "pageSize"},
  287. exclude_none=True,
  288. )
  289. if list_body.model_extra:
  290. filters.update(
  291. {
  292. k: v
  293. for k, v in list_body.model_extra.items()
  294. if v is not None and str(v).strip() != ""
  295. }
  296. )
  297. rows = export_contract_changes(db, tenant_no, filters=filters)
  298. rows = _filter_export_rows_by_ids(rows, getattr(list_body, "ids", None) or getattr(body, "ids", None))
  299. export_cols = filter_export_columns(CONTRACT_CHANGE_EXPORT_COLUMNS, getattr(list_body, "columns", None) or getattr(body, "columns", None))
  300. return export_rows_to_xlsx_response(
  301. db,
  302. tenant_no=tenant_no,
  303. sheet_title="合同变更记录",
  304. columns=export_cols,
  305. rows=rows,
  306. filename_stem=f"合同变更_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  307. include_legacy_filename=False,
  308. )
  309. except ValueError as e:
  310. return error_response(400, str(e))
  311. except Exception as e:
  312. return error_response(500, str(e))
  313. # --- 合同模板库 ---
  314. @router.get("/contract/template/list")
  315. async def contract_template_list(
  316. request: Request,
  317. pageNum: int = Query(1, ge=1),
  318. pageSize: int = Query(10, ge=1, le=500),
  319. templateNo: Optional[str] = None,
  320. templateName: Optional[str] = None,
  321. templateType: Optional[str] = None,
  322. isActive: Optional[int] = None,
  323. beginTime: Optional[str] = None,
  324. endTime: Optional[str] = None,
  325. _user: dict = Depends(require_admin_or_super_admin),
  326. db: Session = Depends(get_db),
  327. ):
  328. try:
  329. tenant_no = get_tenant_no(db, _user)
  330. user_info = crm_user_info(_user)
  331. query = ContractTemplateListQuery(
  332. pageNum=pageNum,
  333. pageSize=pageSize,
  334. templateNo=templateNo,
  335. templateName=templateName,
  336. templateType=templateType,
  337. isActive=isActive,
  338. beginTime=beginTime,
  339. endTime=endTime,
  340. )
  341. filters = query.model_dump(
  342. by_alias=True,
  343. exclude={"pageNum", "pageSize"},
  344. exclude_none=True,
  345. )
  346. if query.model_extra:
  347. filters.update(
  348. {
  349. k: v
  350. for k, v in query.model_extra.items()
  351. if v is not None and str(v).strip() != ""
  352. }
  353. )
  354. skip_keys = {"pageNum", "pageSize", *filters.keys()}
  355. for key, val in request.query_params.multi_items():
  356. if key in skip_keys or val is None or str(val).strip() == "":
  357. continue
  358. filters[key] = val
  359. rows, total = list_contract_templates(
  360. db,
  361. tenant_no,
  362. user_info=user_info,
  363. page=query.pageNum,
  364. page_size=query.pageSize,
  365. filters=filters,
  366. )
  367. return success_response(
  368. {
  369. "list": rows,
  370. "rows": rows,
  371. "total": total,
  372. "pageNum": query.pageNum,
  373. "pageSize": query.pageSize,
  374. "totalPages": total_pages(total, query.pageSize),
  375. }
  376. )
  377. except ValueError as e:
  378. return error_response(400, str(e))
  379. except Exception as e:
  380. return error_response(500, str(e))
  381. @router.get("/contract/template/{template_id}")
  382. async def contract_template_detail(
  383. template_id: str,
  384. _user: dict = Depends(require_admin_or_super_admin),
  385. db: Session = Depends(get_db),
  386. ):
  387. try:
  388. tenant_no = get_tenant_no(db, _user)
  389. user_info = crm_user_info(_user)
  390. detail = get_contract_template_detail(db, template_id, tenant_no=tenant_no)
  391. return success_response(detail)
  392. except ValueError as e:
  393. return error_response(400, str(e))
  394. except Exception as e:
  395. return error_response(500, str(e))
  396. @router.post("/contract/template")
  397. async def contract_template_add(
  398. body: ContractTemplateCreateBody,
  399. _user: dict = Depends(require_admin_or_super_admin),
  400. db: Session = Depends(get_db),
  401. ):
  402. try:
  403. tenant_no = get_tenant_no(db, _user)
  404. user_info = crm_user_info(_user)
  405. user_id = _user.get("user_id", "") or _user.get("id", "")
  406. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  407. detail = create_contract_template(
  408. db,
  409. tenant_no=tenant_no,
  410. data=body.model_dump(by_alias=True),
  411. user_id=user_id,
  412. dept_id=dept_id,
  413. )
  414. return success_response(detail["id"], message="新增成功")
  415. except ValueError as e:
  416. return error_response(400, str(e))
  417. except Exception as e:
  418. return error_response(500, str(e))
  419. @router.put("/contract/template")
  420. async def contract_template_edit(
  421. body: ContractTemplateUpdateBody,
  422. _user: dict = Depends(require_admin_or_super_admin),
  423. db: Session = Depends(get_db),
  424. ):
  425. try:
  426. tenant_no = get_tenant_no(db, _user)
  427. user_info = crm_user_info(_user)
  428. user_id = _user.get("user_id", "") or _user.get("id", "")
  429. data = body.model_dump(by_alias=True)
  430. template_id = data.pop("id")
  431. detail = update_contract_template(
  432. db,
  433. template_id,
  434. tenant_no=tenant_no,
  435. data=data,
  436. user_id=user_id,
  437. user_info=user_info,
  438. )
  439. return success_response(detail, message="修改成功")
  440. except ValueError as e:
  441. return error_response(400, str(e))
  442. except Exception as e:
  443. return error_response(500, str(e))
  444. @router.delete("/contract/template/{ids}")
  445. async def contract_template_remove(
  446. ids: str,
  447. _user: dict = Depends(require_admin_or_super_admin),
  448. db: Session = Depends(get_db),
  449. ):
  450. try:
  451. tenant_no = get_tenant_no(db, _user)
  452. user_info = crm_user_info(_user)
  453. id_list = [x.strip() for x in ids.split(",") if x.strip()]
  454. soft_delete_contract_templates(db, id_list, tenant_no=tenant_no, user_info=user_info)
  455. return success_response(None, message="删除成功")
  456. except ValueError as e:
  457. return error_response(400, str(e))
  458. except Exception as e:
  459. return error_response(500, str(e))
  460. @router.put("/contract/template/enable/{template_id}")
  461. async def contract_template_enable(
  462. template_id: str,
  463. _user: dict = Depends(require_admin_or_super_admin),
  464. db: Session = Depends(get_db),
  465. ):
  466. try:
  467. tenant_no = get_tenant_no(db, _user)
  468. user_info = crm_user_info(_user)
  469. user_id = _user.get("user_id", "") or _user.get("id", "")
  470. toggle_contract_template_active(
  471. db, template_id, tenant_no=tenant_no, is_active=1, user_id=user_id
  472. )
  473. return success_response(None, message="启用成功")
  474. except ValueError as e:
  475. return error_response(400, str(e))
  476. except Exception as e:
  477. return error_response(500, str(e))
  478. @router.put("/contract/template/disable/{template_id}")
  479. async def contract_template_disable(
  480. template_id: str,
  481. _user: dict = Depends(require_admin_or_super_admin),
  482. db: Session = Depends(get_db),
  483. ):
  484. try:
  485. tenant_no = get_tenant_no(db, _user)
  486. user_info = crm_user_info(_user)
  487. user_id = _user.get("user_id", "") or _user.get("id", "")
  488. toggle_contract_template_active(
  489. db, template_id, tenant_no=tenant_no, is_active=0, user_id=user_id
  490. )
  491. return success_response(None, message="停用成功")
  492. except ValueError as e:
  493. return error_response(400, str(e))
  494. except Exception as e:
  495. return error_response(500, str(e))
  496. @router.post("/contract/template/export")
  497. async def contract_template_export(
  498. body: ContractTemplateExportBody = None,
  499. _user: dict = Depends(require_admin_or_super_admin),
  500. db: Session = Depends(get_db),
  501. ):
  502. try:
  503. tenant_no = get_tenant_no(db, _user)
  504. user_info = crm_user_info(_user)
  505. list_body = body or ContractTemplateExportBody()
  506. filters = list_body.model_dump(
  507. by_alias=True,
  508. exclude={"pageNum", "pageSize"},
  509. exclude_none=True,
  510. )
  511. if list_body.model_extra:
  512. filters.update(
  513. {
  514. k: v
  515. for k, v in list_body.model_extra.items()
  516. if v is not None and str(v).strip() != ""
  517. }
  518. )
  519. rows = export_contract_templates(db, tenant_no, filters=filters)
  520. rows = _filter_export_rows_by_ids(rows, getattr(list_body, "ids", None) or getattr(body, "ids", None))
  521. export_cols = filter_export_columns(CONTRACT_TEMPLATE_EXPORT_COLUMNS, getattr(list_body, "columns", None) or getattr(body, "columns", None))
  522. return export_rows_to_xlsx_response(
  523. db,
  524. tenant_no=tenant_no,
  525. sheet_title="合同模板",
  526. columns=export_cols,
  527. rows=rows,
  528. filename_stem=f"合同模板_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  529. include_legacy_filename=False,
  530. )
  531. except ValueError as e:
  532. return error_response(400, str(e))
  533. except Exception as e:
  534. return error_response(500, str(e))
  535. # --- 审批记录独立页 ---
  536. @router.get("/contract/approval/list")
  537. async def contract_approval_list(
  538. request: Request,
  539. pageNum: int = Query(1, ge=1),
  540. pageSize: int = Query(10, ge=1, le=500),
  541. contractId: Optional[str] = None,
  542. contractNo: Optional[str] = None,
  543. contractName: Optional[str] = None,
  544. approverName: Optional[str] = None,
  545. approvalResult: Optional[str] = None,
  546. beginTime: Optional[str] = None,
  547. endTime: Optional[str] = None,
  548. _user: dict = Depends(require_admin_or_super_admin),
  549. db: Session = Depends(get_db),
  550. ):
  551. try:
  552. tenant_no = get_tenant_no(db, _user)
  553. user_info = crm_user_info(_user)
  554. query = ContractApprovalListQuery(
  555. pageNum=pageNum,
  556. pageSize=pageSize,
  557. contractId=contractId,
  558. contractNo=contractNo,
  559. contractName=contractName,
  560. approverName=approverName,
  561. approvalResult=approvalResult,
  562. beginTime=beginTime,
  563. endTime=endTime,
  564. )
  565. filters = query.model_dump(
  566. by_alias=True,
  567. exclude={"pageNum", "pageSize"},
  568. exclude_none=True,
  569. )
  570. if query.model_extra:
  571. filters.update(
  572. {
  573. k: v
  574. for k, v in query.model_extra.items()
  575. if v is not None and str(v).strip() != ""
  576. }
  577. )
  578. skip_keys = {"pageNum", "pageSize", *filters.keys()}
  579. for key, val in request.query_params.multi_items():
  580. if key in skip_keys or val is None or str(val).strip() == "":
  581. continue
  582. filters[key] = val
  583. rows, total = list_contract_approvals_page(
  584. db,
  585. tenant_no,
  586. page=query.pageNum,
  587. page_size=query.pageSize,
  588. filters=filters,
  589. )
  590. return success_response(
  591. {
  592. "list": rows,
  593. "rows": rows,
  594. "total": total,
  595. "pageNum": query.pageNum,
  596. "pageSize": query.pageSize,
  597. "totalPages": total_pages(total, query.pageSize),
  598. }
  599. )
  600. except ValueError as e:
  601. return error_response(400, str(e))
  602. except Exception as e:
  603. return error_response(500, str(e))
  604. @router.get("/contract/approval/{approval_id}")
  605. async def contract_approval_detail(
  606. approval_id: str,
  607. _user: dict = Depends(require_admin_or_super_admin),
  608. db: Session = Depends(get_db),
  609. ):
  610. try:
  611. tenant_no = get_tenant_no(db, _user)
  612. user_info = crm_user_info(_user)
  613. detail = get_contract_approval_detail(db, approval_id, tenant_no=tenant_no)
  614. return success_response(detail)
  615. except ValueError as e:
  616. return error_response(400, str(e))
  617. except Exception as e:
  618. return error_response(500, str(e))
  619. @router.post("/contract/approval/pass")
  620. async def contract_approval_pass(
  621. body: ContractApprovalPassBody,
  622. _user: dict = Depends(require_admin_or_super_admin),
  623. db: Session = Depends(get_db),
  624. ):
  625. try:
  626. user_id = _user.get("user_id", "") or _user.get("id", "")
  627. approve_pass(db, body.id, approval_opinion=body.approvalOpinion or "", user_id=user_id)
  628. db.commit()
  629. return success_response(None, message="审批通过")
  630. except ValueError as e:
  631. return error_response(400, str(e))
  632. except Exception as e:
  633. return error_response(500, str(e))
  634. @router.post("/contract/approval/reject")
  635. async def contract_approval_reject(
  636. body: ContractApprovalRejectBody,
  637. _user: dict = Depends(require_admin_or_super_admin),
  638. db: Session = Depends(get_db),
  639. ):
  640. try:
  641. user_id = _user.get("user_id", "") or _user.get("id", "")
  642. approve_reject(db, body.id, approval_opinion=body.approvalOpinion, user_id=user_id)
  643. db.commit()
  644. return success_response(None, message="已驳回")
  645. except ValueError as e:
  646. return error_response(400, str(e))
  647. except Exception as e:
  648. return error_response(500, str(e))
  649. @router.post("/contract/approval/transfer")
  650. async def contract_approval_transfer(
  651. body: ContractApprovalTransferBody,
  652. _user: dict = Depends(require_admin_or_super_admin),
  653. db: Session = Depends(get_db),
  654. ):
  655. try:
  656. user_id = _user.get("user_id", "") or _user.get("id", "")
  657. approve_transfer(
  658. db,
  659. body.id,
  660. target_approver_id=body.targetApproverId,
  661. transfer_reason=body.transferReason,
  662. user_id=user_id,
  663. )
  664. db.commit()
  665. return success_response(None, message="转审成功")
  666. except ValueError as e:
  667. return error_response(400, str(e))
  668. except Exception as e:
  669. return error_response(500, str(e))
  670. @router.post("/contract/approval/export")
  671. async def contract_approval_export(
  672. body: ContractApprovalExportBody = None,
  673. _user: dict = Depends(require_admin_or_super_admin),
  674. db: Session = Depends(get_db),
  675. ):
  676. try:
  677. tenant_no = get_tenant_no(db, _user)
  678. user_info = crm_user_info(_user)
  679. list_body = body or ContractApprovalExportBody()
  680. filters = list_body.model_dump(
  681. by_alias=True,
  682. exclude={"pageNum", "pageSize"},
  683. exclude_none=True,
  684. )
  685. if list_body.model_extra:
  686. filters.update(
  687. {
  688. k: v
  689. for k, v in list_body.model_extra.items()
  690. if v is not None and str(v).strip() != ""
  691. }
  692. )
  693. rows = export_contract_approvals(db, tenant_no, filters=filters)
  694. rows = _filter_export_rows_by_ids(rows, getattr(list_body, "ids", None) or getattr(body, "ids", None))
  695. export_cols = filter_export_columns(CONTRACT_APPROVAL_EXPORT_COLUMNS, getattr(list_body, "columns", None) or getattr(body, "columns", None))
  696. return export_rows_to_xlsx_response(
  697. db,
  698. tenant_no=tenant_no,
  699. sheet_title="合同审批记录",
  700. columns=export_cols,
  701. rows=rows,
  702. filename_stem=f"合同审批_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  703. include_legacy_filename=False,
  704. )
  705. except ValueError as e:
  706. return error_response(400, str(e))
  707. except Exception as e:
  708. return error_response(500, str(e))
  709. @router.post("/contract/approval/refreshNames")
  710. async def contract_approval_refresh_names(
  711. body: ContractApprovalRefreshNamesBody = None,
  712. _user: dict = Depends(require_admin_or_super_admin),
  713. db: Session = Depends(get_db),
  714. ):
  715. try:
  716. req = body or ContractApprovalRefreshNamesBody()
  717. count = refresh_approver_names(db, contract_id=req.contractId)
  718. db.commit()
  719. return success_response(count)
  720. except ValueError as e:
  721. return error_response(400, str(e))
  722. except Exception as e:
  723. return error_response(500, str(e))