crm_contract_routes.py 31 KB

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