crm_contract_routes.py 31 KB

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