crm_contract_routes.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. """CRM 合同 API 路由 — GET/PUT/DELETE/POST 对齐原 Java 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.oper_log_decorator import oper_log
  10. from app.core.response import error_response, success_response
  11. from app.core.tenant_context import get_tenant_no
  12. from app.crud.crm.data_scope_helpers import crm_user_info
  13. from app.crud.crm.contract import (
  14. batch_assign_owner,
  15. batch_submit_contract_approval,
  16. create_contract,
  17. create_from_opportunity,
  18. create_from_quote,
  19. download_contract_pdf,
  20. export_contracts,
  21. finish_contract,
  22. get_contract_detail,
  23. get_template,
  24. invalid_contract,
  25. list_contracts,
  26. query_expiring,
  27. renew_contract,
  28. save_template,
  29. sign_contract,
  30. soft_delete_contracts,
  31. start_execute,
  32. statistics_by_status,
  33. statistics_by_type,
  34. statistics_overview,
  35. statistics_trend,
  36. submit_contract_approval,
  37. total_pages,
  38. update_contract,
  39. upload_attachments,
  40. upload_attachments_with_category,
  41. upload_file,
  42. withdraw_contract_approval,
  43. )
  44. from app.crud.crm.contract_approval import (
  45. approve_contract_by_contract_id,
  46. list_contract_approvals,
  47. )
  48. from app.schemas.crm.contract import (
  49. ContractApprovalActionBody,
  50. ContractBatchAssignBody,
  51. ContractBatchSubmitBody,
  52. ContractCreateBody,
  53. ContractExportBody,
  54. ContractInvalidBody,
  55. ContractListQuery,
  56. ContractRenewBody,
  57. ContractSignBody,
  58. ContractStatisticsOverviewQuery,
  59. ContractStatisticsTrendQuery,
  60. ContractStatusBody,
  61. ContractSubmitBody,
  62. ContractTemplateSaveBody,
  63. ContractUpdateBody,
  64. ContractUploadAttachmentsBody,
  65. ContractUploadFileBody,
  66. ContractWithdrawBody,
  67. )
  68. from app.utils.crm_export_columns import CONTRACT_EXPORT_COLUMNS
  69. from app.utils.excel_export import export_rows_to_xlsx_response
  70. def register_contract_routes(router):
  71. @router.get("/contract/list")
  72. async def contract_list(
  73. pageNum: int = Query(1, ge=1),
  74. pageSize: int = Query(10, ge=1, le=100),
  75. contractNo: Optional[str] = None,
  76. contractName: Optional[str] = None,
  77. customerName: Optional[str] = None,
  78. customerId: Optional[str] = None,
  79. opportunityId: Optional[str] = None,
  80. quoteId: Optional[str] = None,
  81. contractType: Optional[str] = None,
  82. status: Optional[str] = None,
  83. excludeStatus: Optional[str] = None,
  84. approvalStatus: Optional[str] = None,
  85. ownerUserId: Optional[str] = None,
  86. pendingApprovalUserId: Optional[str] = None,
  87. approvedByUserId: Optional[str] = None,
  88. minAmount: Optional[float] = None,
  89. maxAmount: Optional[float] = None,
  90. signDateBegin: Optional[str] = None,
  91. signDateEnd: Optional[str] = None,
  92. beginTime: Optional[str] = None,
  93. endTime: Optional[str] = None,
  94. _user: dict = Depends(require_admin_or_super_admin),
  95. db: Session = Depends(get_db),
  96. ):
  97. try:
  98. tenant_no = get_tenant_no(db, _user)
  99. user_info = crm_user_info(_user)
  100. user_id = _user.get("user_id", "") or _user.get("id", "")
  101. query = ContractListQuery(
  102. pageNum=pageNum,
  103. pageSize=pageSize,
  104. contractNo=contractNo,
  105. contractName=contractName,
  106. customerName=customerName,
  107. customerId=customerId,
  108. opportunityId=opportunityId,
  109. quoteId=quoteId,
  110. contractType=contractType,
  111. status=status,
  112. excludeStatus=excludeStatus,
  113. approvalStatus=approvalStatus,
  114. ownerUserId=ownerUserId,
  115. pendingApprovalUserId=pendingApprovalUserId,
  116. approvedByUserId=approvedByUserId,
  117. minAmount=minAmount,
  118. maxAmount=maxAmount,
  119. signDateBegin=signDateBegin,
  120. signDateEnd=signDateEnd,
  121. beginTime=beginTime,
  122. endTime=endTime,
  123. )
  124. filters = query.model_dump(
  125. by_alias=True,
  126. exclude={"pageNum", "pageSize"},
  127. exclude_none=True,
  128. )
  129. rows, total = list_contracts(
  130. db,
  131. tenant_no,
  132. user_info=user_info,
  133. page=query.pageNum,
  134. page_size=query.pageSize,
  135. filters=filters,
  136. current_user_id=user_id,
  137. )
  138. tp = total_pages(total, query.pageSize)
  139. return success_response(
  140. {
  141. "list": rows,
  142. "rows": rows,
  143. "total": total,
  144. "pageNum": query.pageNum,
  145. "pageSize": query.pageSize,
  146. "totalPages": tp,
  147. }
  148. )
  149. except ValueError as e:
  150. return error_response(400, str(e))
  151. except Exception as e:
  152. return error_response(500, str(e))
  153. @router.post("/contract")
  154. @oper_log(title="CRM合同管理", business_type=1, summary="新增合同【{contractName}】")
  155. async def contract_add(
  156. body: ContractCreateBody,
  157. _user: dict = Depends(require_admin_or_super_admin),
  158. db: Session = Depends(get_db),
  159. ):
  160. try:
  161. tenant_no = get_tenant_no(db, _user)
  162. user_info = crm_user_info(_user)
  163. user_id = _user.get("user_id", "") or _user.get("id", "")
  164. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  165. detail = create_contract(
  166. db,
  167. tenant_no=tenant_no,
  168. data=body.model_dump(by_alias=True),
  169. user_id=user_id,
  170. dept_id=dept_id,
  171. )
  172. return success_response(detail["id"], message="新增成功")
  173. except ValueError as e:
  174. return error_response(400, str(e))
  175. except Exception as e:
  176. return error_response(500, str(e))
  177. @router.put("/contract")
  178. @oper_log(title="CRM合同管理", business_type=2, summary="修改合同【{contractName}】")
  179. async def contract_edit(
  180. body: ContractUpdateBody,
  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. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  189. data = body.model_dump(by_alias=True)
  190. contract_id = data.pop("id")
  191. detail = update_contract(
  192. db,
  193. contract_id,
  194. tenant_no=tenant_no,
  195. data=data,
  196. user_id=user_id,
  197. dept_id=dept_id,
  198. user_info=user_info,
  199. )
  200. return success_response(detail, message="修改成功")
  201. except ValueError as e:
  202. return error_response(400, str(e))
  203. except Exception as e:
  204. return error_response(500, str(e))
  205. @router.delete("/contract/{ids}")
  206. @oper_log(title="CRM合同管理", business_type=3, path_param="ids", summary="删除合同")
  207. async def contract_remove(
  208. ids: str,
  209. _user: dict = Depends(require_admin_or_super_admin),
  210. db: Session = Depends(get_db),
  211. ):
  212. try:
  213. tenant_no = get_tenant_no(db, _user)
  214. user_info = crm_user_info(_user)
  215. id_list = [x.strip() for x in ids.split(",") if x.strip()]
  216. soft_delete_contracts(db, id_list, tenant_no=tenant_no)
  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/submitApproval")
  223. async def contract_submit_approval(
  224. body: ContractSubmitBody,
  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. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  233. submit_contract_approval(
  234. db,
  235. body.id,
  236. tenant_no=tenant_no,
  237. approver_ids=body.approverIds,
  238. remark=body.remark,
  239. user_id=user_id,
  240. dept_id=dept_id,
  241. user_info=user_info,
  242. )
  243. return success_response(None, message="提交成功")
  244. except ValueError as e:
  245. return error_response(400, str(e))
  246. except Exception as e:
  247. return error_response(500, str(e))
  248. @router.put("/contract/batchSubmitApproval")
  249. async def contract_batch_submit_approval(
  250. body: ContractBatchSubmitBody,
  251. _user: dict = Depends(require_admin_or_super_admin),
  252. db: Session = Depends(get_db),
  253. ):
  254. try:
  255. tenant_no = get_tenant_no(db, _user)
  256. user_info = crm_user_info(_user)
  257. user_id = _user.get("user_id", "") or _user.get("id", "")
  258. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  259. count = batch_submit_contract_approval(
  260. db,
  261. body.ids,
  262. tenant_no=tenant_no,
  263. approver_ids=body.approverIds,
  264. remark=body.remark,
  265. user_id=user_id,
  266. dept_id=dept_id,
  267. user_info=user_info,
  268. )
  269. return success_response(count, 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.put("/contract/withdrawApproval")
  275. async def contract_withdraw_approval(
  276. body: ContractWithdrawBody,
  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. user_id = _user.get("user_id", "") or _user.get("id", "")
  284. withdraw_contract_approval(
  285. db,
  286. body.id,
  287. tenant_no=tenant_no,
  288. remark=body.remark,
  289. user_id=user_id,
  290. user_info=user_info,
  291. )
  292. return success_response(None, message="撤回成功")
  293. except ValueError as e:
  294. return error_response(400, str(e))
  295. except Exception as e:
  296. return error_response(500, str(e))
  297. @router.put("/contract/startExecute")
  298. async def contract_start_execute(
  299. body: ContractStatusBody,
  300. _user: dict = Depends(require_admin_or_super_admin),
  301. db: Session = Depends(get_db),
  302. ):
  303. try:
  304. tenant_no = get_tenant_no(db, _user)
  305. user_info = crm_user_info(_user)
  306. user_id = _user.get("user_id", "") or _user.get("id", "")
  307. start_execute(db, body.id, tenant_no=tenant_no, remark=body.remark, user_id=user_id)
  308. return success_response(None, message="操作成功")
  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. @router.put("/contract/finish")
  314. async def contract_finish(
  315. body: ContractStatusBody,
  316. _user: dict = Depends(require_admin_or_super_admin),
  317. db: Session = Depends(get_db),
  318. ):
  319. try:
  320. tenant_no = get_tenant_no(db, _user)
  321. user_info = crm_user_info(_user)
  322. user_id = _user.get("user_id", "") or _user.get("id", "")
  323. finish_contract(db, body.id, tenant_no=tenant_no, remark=body.remark, user_id=user_id)
  324. return success_response(None, message="操作成功")
  325. except ValueError as e:
  326. return error_response(400, str(e))
  327. except Exception as e:
  328. return error_response(500, str(e))
  329. @router.put("/contract/invalid")
  330. @oper_log(title="CRM合同管理", business_type=2, summary="作废合同")
  331. async def contract_invalid(
  332. body: ContractInvalidBody,
  333. _user: dict = Depends(require_admin_or_super_admin),
  334. db: Session = Depends(get_db),
  335. ):
  336. try:
  337. tenant_no = get_tenant_no(db, _user)
  338. user_info = crm_user_info(_user)
  339. user_id = _user.get("user_id", "") or _user.get("id", "")
  340. invalid_contract(
  341. db,
  342. body.id,
  343. tenant_no=tenant_no,
  344. invalid_reason=body.invalidReason,
  345. remark=body.remark,
  346. user_id=user_id,
  347. user_info=user_info,
  348. )
  349. return success_response(None, message="作废成功")
  350. except ValueError as e:
  351. return error_response(400, str(e))
  352. except Exception as e:
  353. return error_response(500, str(e))
  354. @router.post("/contract/saveTemplate")
  355. async def contract_save_template(
  356. body: ContractTemplateSaveBody,
  357. _user: dict = Depends(require_admin_or_super_admin),
  358. db: Session = Depends(get_db),
  359. ):
  360. try:
  361. tenant_no = get_tenant_no(db, _user)
  362. user_info = crm_user_info(_user)
  363. save_template(
  364. db,
  365. body.id,
  366. body.templateContent,
  367. tenant_no=tenant_no,
  368. settlement_clause_drafts=body.settlementClauseDrafts,
  369. user_info=user_info,
  370. )
  371. return success_response(None, message="保存成功")
  372. except ValueError as e:
  373. return error_response(400, str(e))
  374. except Exception as e:
  375. return error_response(500, str(e))
  376. @router.get("/contract/getTemplate/{contract_id}")
  377. async def contract_get_template(
  378. contract_id: str,
  379. _user: dict = Depends(require_admin_or_super_admin),
  380. db: Session = Depends(get_db),
  381. ):
  382. try:
  383. tenant_no = get_tenant_no(db, _user)
  384. user_info = crm_user_info(_user)
  385. content = get_template(db, contract_id, tenant_no=tenant_no, user_info=user_info)
  386. return success_response(content or "", message="获取模板成功")
  387. except ValueError as e:
  388. return error_response(400, str(e))
  389. except Exception as e:
  390. return error_response(500, str(e))
  391. @router.post("/contract/uploadFile")
  392. async def contract_upload_file(
  393. body: ContractUploadFileBody,
  394. _user: dict = Depends(require_admin_or_super_admin),
  395. db: Session = Depends(get_db),
  396. ):
  397. try:
  398. tenant_no = get_tenant_no(db, _user)
  399. user_info = crm_user_info(_user)
  400. upload_file(db, body.id, body.fileUrl, tenant_no=tenant_no)
  401. return success_response(None, message="上传成功")
  402. except ValueError as e:
  403. return error_response(400, str(e))
  404. except Exception as e:
  405. return error_response(500, str(e))
  406. @router.post("/contract/uploadAttachments")
  407. async def contract_upload_attachments(
  408. body: ContractUploadAttachmentsBody,
  409. _user: dict = Depends(require_admin_or_super_admin),
  410. db: Session = Depends(get_db),
  411. ):
  412. try:
  413. tenant_no = get_tenant_no(db, _user)
  414. user_info = crm_user_info(_user)
  415. if body.attachments:
  416. upload_attachments_with_category(
  417. db, body.id, body.attachments, tenant_no=tenant_no,
  418. user_info=user_info,
  419. )
  420. elif body.ossIds:
  421. upload_attachments(db, body.id, body.ossIds, tenant_no=tenant_no)
  422. else:
  423. return error_response(400, "附件参数不能为空")
  424. return success_response(None, message="上传成功")
  425. except ValueError as e:
  426. return error_response(400, str(e))
  427. except Exception as e:
  428. return error_response(500, str(e))
  429. @router.post("/contract/export")
  430. @oper_log(title="CRM合同管理", business_type=5, summary="导出合同列表")
  431. async def contract_export(
  432. body: ContractExportBody = None,
  433. _user: dict = Depends(require_admin_or_super_admin),
  434. db: Session = Depends(get_db),
  435. ):
  436. try:
  437. tenant_no = get_tenant_no(db, _user)
  438. user_info = crm_user_info(_user)
  439. list_body = body or ContractExportBody()
  440. filters = list_body.model_dump(
  441. by_alias=True,
  442. exclude={"pageNum", "pageSize"},
  443. exclude_none=True,
  444. )
  445. rows = export_contracts(db, tenant_no, filters=filters)
  446. return export_rows_to_xlsx_response(
  447. db,
  448. tenant_no=tenant_no,
  449. sheet_title="合同",
  450. columns=CONTRACT_EXPORT_COLUMNS,
  451. rows=rows,
  452. filename_stem=f"合同_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  453. include_legacy_filename=False,
  454. )
  455. except ValueError as e:
  456. return error_response(400, str(e))
  457. except Exception as e:
  458. return error_response(500, str(e))
  459. @router.get("/contract/statistics/overview")
  460. async def contract_statistics_overview(
  461. ownerUserId: Optional[str] = None,
  462. beginTime: Optional[str] = None,
  463. endTime: Optional[str] = None,
  464. _user: dict = Depends(require_admin_or_super_admin),
  465. db: Session = Depends(get_db),
  466. ):
  467. try:
  468. tenant_no = get_tenant_no(db, _user)
  469. user_info = crm_user_info(_user)
  470. req = ContractStatisticsOverviewQuery(
  471. ownerUserId=ownerUserId,
  472. beginTime=beginTime,
  473. endTime=endTime,
  474. )
  475. data = statistics_overview(
  476. db,
  477. tenant_no,
  478. owner_user_id=req.ownerUserId,
  479. begin_time=req.beginTime,
  480. end_time=req.endTime,
  481. user_info=user_info,
  482. )
  483. return success_response(data)
  484. except ValueError as e:
  485. return error_response(400, str(e))
  486. except Exception as e:
  487. return error_response(500, str(e))
  488. @router.get("/contract/statistics/status")
  489. async def contract_statistics_status(
  490. _user: dict = Depends(require_admin_or_super_admin),
  491. db: Session = Depends(get_db),
  492. ):
  493. try:
  494. tenant_no = get_tenant_no(db, _user)
  495. user_info = crm_user_info(_user)
  496. return success_response(statistics_by_status(db, tenant_no))
  497. except ValueError as e:
  498. return error_response(400, str(e))
  499. except Exception as e:
  500. return error_response(500, str(e))
  501. @router.get("/contract/statistics/type")
  502. async def contract_statistics_type(
  503. _user: dict = Depends(require_admin_or_super_admin),
  504. db: Session = Depends(get_db),
  505. ):
  506. try:
  507. tenant_no = get_tenant_no(db, _user)
  508. user_info = crm_user_info(_user)
  509. return success_response(statistics_by_type(db, tenant_no))
  510. except ValueError as e:
  511. return error_response(400, str(e))
  512. except Exception as e:
  513. return error_response(500, str(e))
  514. @router.get("/contract/statistics/trend")
  515. async def contract_statistics_trend(
  516. type: str = Query("month"),
  517. beginTime: Optional[str] = None,
  518. endTime: Optional[str] = None,
  519. _user: dict = Depends(require_admin_or_super_admin),
  520. db: Session = Depends(get_db),
  521. ):
  522. try:
  523. tenant_no = get_tenant_no(db, _user)
  524. user_info = crm_user_info(_user)
  525. req = ContractStatisticsTrendQuery(type=type, beginTime=beginTime, endTime=endTime)
  526. data = statistics_trend(
  527. db,
  528. tenant_no,
  529. period_type=req.type or "month",
  530. begin_time=req.beginTime,
  531. end_time=req.endTime,
  532. user_info=user_info,
  533. )
  534. return success_response(data)
  535. except ValueError as e:
  536. return error_response(400, str(e))
  537. except Exception as e:
  538. return error_response(500, str(e))
  539. @router.get("/contract/statistics/expiring")
  540. async def contract_statistics_expiring(
  541. days: int = Query(30, ge=1),
  542. _user: dict = Depends(require_admin_or_super_admin),
  543. db: Session = Depends(get_db),
  544. ):
  545. try:
  546. tenant_no = get_tenant_no(db, _user)
  547. user_info = crm_user_info(_user)
  548. rows = query_expiring(db, tenant_no, days=days)
  549. return success_response(rows)
  550. except ValueError as e:
  551. return error_response(400, str(e))
  552. except Exception as e:
  553. return error_response(500, str(e))
  554. @router.post("/contract/renew/{parent_id}")
  555. async def contract_renew(
  556. parent_id: str,
  557. body: ContractRenewBody,
  558. _user: dict = Depends(require_admin_or_super_admin),
  559. db: Session = Depends(get_db),
  560. ):
  561. try:
  562. tenant_no = get_tenant_no(db, _user)
  563. user_info = crm_user_info(_user)
  564. user_id = _user.get("user_id", "") or _user.get("id", "")
  565. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  566. new_id = renew_contract(
  567. db,
  568. parent_id,
  569. tenant_no=tenant_no,
  570. data=body.model_dump(by_alias=True),
  571. user_id=user_id,
  572. dept_id=dept_id,
  573. )
  574. return success_response(new_id, message="续签成功")
  575. except ValueError as e:
  576. return error_response(400, str(e))
  577. except Exception as e:
  578. return error_response(500, str(e))
  579. @router.post("/contract/createFromQuote/{quote_id}")
  580. async def contract_create_from_quote(
  581. quote_id: str,
  582. body: ContractCreateBody,
  583. _user: dict = Depends(require_admin_or_super_admin),
  584. db: Session = Depends(get_db),
  585. ):
  586. try:
  587. tenant_no = get_tenant_no(db, _user)
  588. user_info = crm_user_info(_user)
  589. user_id = _user.get("user_id", "") or _user.get("id", "")
  590. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  591. contract_id = create_from_quote(
  592. db,
  593. quote_id,
  594. tenant_no=tenant_no,
  595. data=body.model_dump(by_alias=True),
  596. user_id=user_id,
  597. dept_id=dept_id,
  598. )
  599. return success_response(contract_id, message="创建成功")
  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.post("/contract/createFromOpportunity/{opportunity_id}")
  605. async def contract_create_from_opportunity(
  606. opportunity_id: str,
  607. body: ContractCreateBody,
  608. _user: dict = Depends(require_admin_or_super_admin),
  609. db: Session = Depends(get_db),
  610. ):
  611. try:
  612. tenant_no = get_tenant_no(db, _user)
  613. user_info = crm_user_info(_user)
  614. user_id = _user.get("user_id", "") or _user.get("id", "")
  615. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  616. contract_id = create_from_opportunity(
  617. db,
  618. opportunity_id,
  619. tenant_no=tenant_no,
  620. data=body.model_dump(by_alias=True),
  621. user_id=user_id,
  622. dept_id=dept_id,
  623. )
  624. return success_response(contract_id, message="创建成功")
  625. except ValueError as e:
  626. return error_response(400, str(e))
  627. except Exception as e:
  628. return error_response(500, str(e))
  629. @router.put("/contract/sign")
  630. async def contract_sign(
  631. body: ContractSignBody,
  632. _user: dict = Depends(require_admin_or_super_admin),
  633. db: Session = Depends(get_db),
  634. ):
  635. try:
  636. tenant_no = get_tenant_no(db, _user)
  637. user_info = crm_user_info(_user)
  638. user_id = _user.get("user_id", "") or _user.get("id", "")
  639. sign_contract(
  640. db,
  641. body.id,
  642. tenant_no=tenant_no,
  643. actual_sign_date=body.actualSignDate,
  644. contract_file=body.contractFile,
  645. remark=body.remark or "",
  646. user_id=user_id,
  647. user_info=user_info,
  648. )
  649. return success_response(None, message="签订成功")
  650. except ValueError as e:
  651. return error_response(400, str(e))
  652. except Exception as e:
  653. return error_response(500, str(e))
  654. @router.put("/contract/batch/assign")
  655. async def contract_batch_assign(
  656. body: ContractBatchAssignBody,
  657. _user: dict = Depends(require_admin_or_super_admin),
  658. db: Session = Depends(get_db),
  659. ):
  660. try:
  661. tenant_no = get_tenant_no(db, _user)
  662. user_info = crm_user_info(_user)
  663. user_id = _user.get("user_id", "") or _user.get("id", "")
  664. count = batch_assign_owner(
  665. db,
  666. body.ids,
  667. tenant_no=tenant_no,
  668. owner_user_id=body.ownerUserId,
  669. remark=body.remark or "",
  670. user_id=user_id,
  671. user_info=user_info,
  672. )
  673. return success_response(count, message="分配成功")
  674. except ValueError as e:
  675. return error_response(400, str(e))
  676. except Exception as e:
  677. return error_response(500, str(e))
  678. @router.get("/contract/download/{contract_id}")
  679. async def contract_download(
  680. contract_id: str,
  681. _user: dict = Depends(require_admin_or_super_admin),
  682. db: Session = Depends(get_db),
  683. ):
  684. try:
  685. tenant_no = get_tenant_no(db, _user)
  686. user_info = crm_user_info(_user)
  687. file_path = download_contract_pdf(db, contract_id, tenant_no=tenant_no)
  688. return success_response(file_path)
  689. except ValueError as e:
  690. return error_response(400, str(e))
  691. except Exception as e:
  692. return error_response(500, str(e))
  693. @router.post("/contract/approval")
  694. async def contract_approval_by_contract_id(
  695. body: ContractApprovalActionBody,
  696. _user: dict = Depends(require_admin_or_super_admin),
  697. db: Session = Depends(get_db),
  698. ):
  699. try:
  700. tenant_no = get_tenant_no(db, _user)
  701. user_id = _user.get("user_id", "") or _user.get("id", "")
  702. approve_contract_by_contract_id(
  703. db,
  704. body.id,
  705. approval_result=body.approvalResult,
  706. approval_opinion=body.approvalOpinion or "",
  707. attachments=body.attachments or "",
  708. remark=body.remark or "",
  709. user_id=user_id,
  710. )
  711. db.commit()
  712. return success_response(None, message="审批成功")
  713. except ValueError as e:
  714. return error_response(400, str(e))
  715. except Exception as e:
  716. return error_response(500, str(e))
  717. @router.get("/contract/approval/flow/{contract_id}")
  718. async def contract_approval_flow(
  719. contract_id: str,
  720. _user: dict = Depends(require_admin_or_super_admin),
  721. db: Session = Depends(get_db),
  722. ):
  723. try:
  724. tenant_no = get_tenant_no(db, _user)
  725. user_info = crm_user_info(_user)
  726. rows = list_contract_approvals(db, contract_id, tenant_no=tenant_no)
  727. return success_response(rows)
  728. except ValueError as e:
  729. return error_response(400, str(e))
  730. except Exception as e:
  731. return error_response(500, str(e))
  732. @router.get("/contract/{contract_id}")
  733. async def contract_detail(
  734. contract_id: str,
  735. _user: dict = Depends(require_admin_or_super_admin),
  736. db: Session = Depends(get_db),
  737. ):
  738. try:
  739. tenant_no = get_tenant_no(db, _user)
  740. user_info = crm_user_info(_user)
  741. user_id = _user.get("user_id", "") or _user.get("id", "")
  742. detail = get_contract_detail(
  743. db,
  744. contract_id,
  745. tenant_no=tenant_no,
  746. current_user_id=user_id,
  747. user_info=user_info,
  748. )
  749. return success_response(detail)
  750. except ValueError as e:
  751. return error_response(400, str(e))
  752. except Exception as e:
  753. return error_response(500, str(e))