crm_quote_routes.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. """CRM 报价单 API 路由 — 挂接到 crm.router"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from fastapi import Depends
  5. from sqlalchemy.orm import Session
  6. from app.core.database import get_db
  7. from app.core.menu_guard import require_admin_or_super_admin
  8. from app.core.response import error_response, success_response
  9. from app.core.tenant_context import get_tenant_no
  10. from app.crud.crm.data_scope_helpers import crm_user_info
  11. from app.crud.crm.quote import (
  12. accept_quote,
  13. create_new_version,
  14. create_quote,
  15. generate_pdf,
  16. generate_share,
  17. get_quote_detail,
  18. get_template,
  19. invalid_quote,
  20. list_quotes,
  21. query_versions,
  22. reject_quote,
  23. save_template,
  24. soft_delete_quotes,
  25. statistics_overview,
  26. statistics_status,
  27. statistics_trend,
  28. submit_quote,
  29. to_contract,
  30. total_pages as quote_total_pages,
  31. update_quote,
  32. )
  33. from app.crud.crm.quote_approval import (
  34. approve_quote,
  35. list_quote_approvals,
  36. refresh_approver_names,
  37. withdraw_approval,
  38. )
  39. from app.crud.crm.quote_product import (
  40. insert_quote_product,
  41. list_quote_products,
  42. soft_delete_quote_products,
  43. update_quote_product,
  44. )
  45. from app.crud.crm.quote_share_log import list_share_logs, share_log_statistics
  46. from app.schemas.crm.quote import (
  47. QuoteAcceptBody,
  48. QuoteApprovalApproveBody,
  49. QuoteApprovalListBody,
  50. QuoteApprovalRefreshNamesBody,
  51. QuoteApprovalWithdrawBody,
  52. QuoteCreateBody,
  53. QuoteDeleteBody,
  54. QuoteDetailBody,
  55. QuoteInvalidBody,
  56. QuoteListBody,
  57. QuoteNewVersionBody,
  58. QuotePdfBody,
  59. QuoteProductCreateBody,
  60. QuoteProductDeleteBody,
  61. QuoteProductListBody,
  62. QuoteProductUpdateBody,
  63. QuoteRejectBody,
  64. QuoteShareGenerateBody,
  65. QuoteShareLogListBody,
  66. QuoteShareLogStatisticsBody,
  67. QuoteStatisticsOverviewBody,
  68. QuoteStatisticsTrendBody,
  69. QuoteSubmitBody,
  70. QuoteTemplateGetBody,
  71. QuoteTemplateSaveBody,
  72. QuoteToContractBody,
  73. QuoteUpdateBody,
  74. QuoteVersionsBody,
  75. )
  76. from app.utils.crm_export_columns import QUOTE_EXPORT_COLUMNS
  77. from app.utils.excel_export import export_rows_to_xlsx_response
  78. def register_quote_routes(router):
  79. @router.post("/quote/list")
  80. async def quote_list(
  81. body: QuoteListBody = 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. list_body = body or QuoteListBody()
  89. user_id = _user.get("user_id", "") or _user.get("id", "")
  90. rows, total = list_quotes(
  91. db,
  92. tenant_no,
  93. user_info=user_info,
  94. page=list_body.page,
  95. page_size=list_body.pageSize,
  96. filters=list_body.model_dump(by_alias=True, exclude={"page", "pageSize"}),
  97. current_user_id=user_id,
  98. )
  99. tp = quote_total_pages(total, list_body.pageSize)
  100. return success_response(
  101. {
  102. "list": rows,
  103. "rows": rows,
  104. "total": total,
  105. "page": list_body.page,
  106. "pageSize": list_body.pageSize,
  107. "totalPages": tp,
  108. }
  109. )
  110. except ValueError as e:
  111. return error_response(400, str(e))
  112. except Exception as e:
  113. return error_response(500, str(e))
  114. @router.post("/quote/detail")
  115. async def quote_detail(
  116. body: QuoteDetailBody,
  117. _user: dict = Depends(require_admin_or_super_admin),
  118. db: Session = Depends(get_db),
  119. ):
  120. try:
  121. tenant_no = get_tenant_no(db, _user)
  122. user_info = crm_user_info(_user)
  123. detail = get_quote_detail(db, body.id, tenant_no=tenant_no, user_info=user_info)
  124. return success_response(detail)
  125. except ValueError as e:
  126. return error_response(400, str(e))
  127. except Exception as e:
  128. return error_response(500, str(e))
  129. @router.post("/quote/add")
  130. async def quote_add(
  131. body: QuoteCreateBody,
  132. _user: dict = Depends(require_admin_or_super_admin),
  133. db: Session = Depends(get_db),
  134. ):
  135. try:
  136. tenant_no = get_tenant_no(db, _user)
  137. user_info = crm_user_info(_user)
  138. user_id = _user.get("user_id", "") or _user.get("id", "")
  139. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  140. detail = create_quote(
  141. db,
  142. tenant_no=tenant_no,
  143. data=body.model_dump(by_alias=True),
  144. user_id=user_id,
  145. dept_id=dept_id,
  146. )
  147. return success_response(detail, message="新增成功")
  148. except ValueError as e:
  149. return error_response(400, str(e))
  150. except Exception as e:
  151. return error_response(500, str(e))
  152. @router.post("/quote/update")
  153. async def quote_update(
  154. body: QuoteUpdateBody,
  155. _user: dict = Depends(require_admin_or_super_admin),
  156. db: Session = Depends(get_db),
  157. ):
  158. try:
  159. tenant_no = get_tenant_no(db, _user)
  160. user_info = crm_user_info(_user)
  161. user_id = _user.get("user_id", "") or _user.get("id", "")
  162. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  163. data = body.model_dump(by_alias=True)
  164. quote_id = data.pop("id")
  165. detail = update_quote(
  166. db,
  167. quote_id,
  168. tenant_no=tenant_no,
  169. data=data,
  170. user_id=user_id,
  171. dept_id=dept_id,
  172. user_info=user_info,
  173. )
  174. return success_response(detail, message="修改成功")
  175. except ValueError as e:
  176. return error_response(400, str(e))
  177. except Exception as e:
  178. return error_response(500, str(e))
  179. @router.post("/quote/delete")
  180. async def quote_delete(
  181. body: QuoteDeleteBody,
  182. _user: dict = Depends(require_admin_or_super_admin),
  183. db: Session = Depends(get_db),
  184. ):
  185. try:
  186. tenant_no = get_tenant_no(db, _user)
  187. user_info = crm_user_info(_user)
  188. soft_delete_quotes(db, body.ids, tenant_no=tenant_no)
  189. return success_response(None, message="删除成功")
  190. except ValueError as e:
  191. return error_response(400, str(e))
  192. except Exception as e:
  193. return error_response(500, str(e))
  194. @router.post("/quote/submit")
  195. async def quote_submit(
  196. body: QuoteSubmitBody,
  197. _user: dict = Depends(require_admin_or_super_admin),
  198. db: Session = Depends(get_db),
  199. ):
  200. try:
  201. tenant_no = get_tenant_no(db, _user)
  202. user_info = crm_user_info(_user)
  203. submit_quote(
  204. db,
  205. body.id,
  206. tenant_no=tenant_no,
  207. approver_ids=body.approverIds,
  208. remark=body.remark,
  209. user_info=user_info,
  210. )
  211. return success_response(None, message="提交成功")
  212. except ValueError as e:
  213. return error_response(400, str(e))
  214. except Exception as e:
  215. return error_response(500, str(e))
  216. @router.post("/quote/saveTemplate")
  217. async def quote_save_template(
  218. body: QuoteTemplateSaveBody,
  219. _user: dict = Depends(require_admin_or_super_admin),
  220. db: Session = Depends(get_db),
  221. ):
  222. try:
  223. tenant_no = get_tenant_no(db, _user)
  224. user_info = crm_user_info(_user)
  225. save_template(db, body.id, body.templateContent, tenant_no=tenant_no)
  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.post("/quote/getTemplate")
  232. async def quote_get_template(
  233. body: QuoteTemplateGetBody,
  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. content = get_template(db, body.id, tenant_no=tenant_no, user_info=user_info)
  241. return success_response(content or "")
  242. except ValueError as e:
  243. return error_response(400, str(e))
  244. except Exception as e:
  245. return error_response(500, str(e))
  246. @router.post("/quote/generatePdf")
  247. async def quote_generate_pdf(
  248. body: QuotePdfBody,
  249. _user: dict = Depends(require_admin_or_super_admin),
  250. db: Session = Depends(get_db),
  251. ):
  252. try:
  253. tenant_no = get_tenant_no(db, _user)
  254. user_info = crm_user_info(_user)
  255. pdf_url = generate_pdf(db, body.id, tenant_no=tenant_no)
  256. return success_response(pdf_url)
  257. except ValueError as e:
  258. return error_response(400, str(e))
  259. except Exception as e:
  260. return error_response(500, str(e))
  261. @router.post("/quote/generateShare")
  262. async def quote_generate_share(
  263. body: QuoteShareGenerateBody,
  264. _user: dict = Depends(require_admin_or_super_admin),
  265. db: Session = Depends(get_db),
  266. ):
  267. try:
  268. tenant_no = get_tenant_no(db, _user)
  269. user_info = crm_user_info(_user)
  270. link = generate_share(
  271. db,
  272. body.id,
  273. tenant_no=tenant_no,
  274. validity_days=body.validityDays,
  275. password=body.password or "",
  276. user_info=user_info,
  277. )
  278. return success_response(link)
  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.post("/quote/accept")
  284. async def quote_accept(
  285. body: QuoteAcceptBody,
  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. accept_quote(db, body.id, tenant_no=tenant_no, remark=body.remark)
  293. return success_response(None, message="操作成功")
  294. except ValueError as e:
  295. return error_response(400, str(e))
  296. except Exception as e:
  297. return error_response(500, str(e))
  298. @router.post("/quote/reject")
  299. async def quote_reject(
  300. body: QuoteRejectBody,
  301. _user: dict = Depends(require_admin_or_super_admin),
  302. db: Session = Depends(get_db),
  303. ):
  304. try:
  305. tenant_no = get_tenant_no(db, _user)
  306. user_info = crm_user_info(_user)
  307. reject_quote(
  308. db,
  309. body.id,
  310. tenant_no=tenant_no,
  311. reject_reason=body.rejectReason,
  312. remark=body.remark,
  313. user_info=user_info,
  314. )
  315. return success_response(None, message="操作成功")
  316. except ValueError as e:
  317. return error_response(400, str(e))
  318. except Exception as e:
  319. return error_response(500, str(e))
  320. @router.post("/quote/toContract")
  321. async def quote_to_contract(
  322. body: QuoteToContractBody,
  323. _user: dict = Depends(require_admin_or_super_admin),
  324. db: Session = Depends(get_db),
  325. ):
  326. try:
  327. tenant_no = get_tenant_no(db, _user)
  328. user_info = crm_user_info(_user)
  329. user_id = _user.get("user_id", "") or _user.get("id", "")
  330. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  331. contract_id = to_contract(
  332. db,
  333. body.id,
  334. tenant_no=tenant_no,
  335. contract_no=body.contractNo,
  336. contract_title=body.contractTitle,
  337. sign_date=body.signDate,
  338. start_date=body.startDate,
  339. end_date=body.endDate,
  340. user_id=user_id,
  341. dept_id=dept_id,
  342. user_info=user_info,
  343. )
  344. return success_response(contract_id)
  345. except ValueError as e:
  346. return error_response(400, str(e))
  347. except Exception as e:
  348. return error_response(500, str(e))
  349. @router.post("/quote/newVersion")
  350. async def quote_new_version(
  351. body: QuoteNewVersionBody,
  352. _user: dict = Depends(require_admin_or_super_admin),
  353. db: Session = Depends(get_db),
  354. ):
  355. try:
  356. tenant_no = get_tenant_no(db, _user)
  357. user_info = crm_user_info(_user)
  358. user_id = _user.get("user_id", "") or _user.get("id", "")
  359. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  360. new_id = create_new_version(
  361. db,
  362. body.id,
  363. tenant_no=tenant_no,
  364. version=body.version,
  365. remark=body.remark,
  366. user_id=user_id,
  367. dept_id=dept_id,
  368. user_info=user_info,
  369. )
  370. return success_response(new_id)
  371. except ValueError as e:
  372. return error_response(400, str(e))
  373. except Exception as e:
  374. return error_response(500, str(e))
  375. @router.post("/quote/versions")
  376. async def quote_versions(
  377. body: QuoteVersionsBody,
  378. _user: dict = Depends(require_admin_or_super_admin),
  379. db: Session = Depends(get_db),
  380. ):
  381. try:
  382. tenant_no = get_tenant_no(db, _user)
  383. user_info = crm_user_info(_user)
  384. rows = query_versions(db, body.quoteNo, tenant_no=tenant_no)
  385. return success_response(rows)
  386. except ValueError as e:
  387. return error_response(400, str(e))
  388. except Exception as e:
  389. return error_response(500, str(e))
  390. @router.post("/quote/invalid")
  391. async def quote_invalid(
  392. body: QuoteInvalidBody,
  393. _user: dict = Depends(require_admin_or_super_admin),
  394. db: Session = Depends(get_db),
  395. ):
  396. try:
  397. tenant_no = get_tenant_no(db, _user)
  398. user_info = crm_user_info(_user)
  399. invalid_quote(db, body.id, tenant_no=tenant_no, remark=body.remark)
  400. return success_response(None, message="作废成功")
  401. except ValueError as e:
  402. return error_response(400, str(e))
  403. except Exception as e:
  404. return error_response(500, str(e))
  405. @router.post("/quote/export")
  406. async def quote_export(
  407. body: QuoteListBody = None,
  408. _user: dict = Depends(require_admin_or_super_admin),
  409. db: Session = Depends(get_db),
  410. ):
  411. try:
  412. tenant_no = get_tenant_no(db, _user)
  413. user_info = crm_user_info(_user)
  414. list_body = body or QuoteListBody()
  415. rows, _ = list_quotes(
  416. db,
  417. tenant_no,
  418. user_info=user_info,
  419. page=1,
  420. page_size=10000,
  421. filters=list_body.model_dump(by_alias=True, exclude={"page", "pageSize"}),
  422. )
  423. return export_rows_to_xlsx_response(
  424. db,
  425. tenant_no=tenant_no,
  426. sheet_title="报价单",
  427. columns=QUOTE_EXPORT_COLUMNS,
  428. rows=rows,
  429. filename_stem=f"报价单_{datetime.now().strftime('%Y%m%d%H%M%S')}",
  430. include_legacy_filename=False,
  431. )
  432. except ValueError as e:
  433. return error_response(400, str(e))
  434. except Exception as e:
  435. return error_response(500, str(e))
  436. @router.post("/quote/statistics/overview")
  437. async def quote_statistics_overview(
  438. body: QuoteStatisticsOverviewBody = None,
  439. _user: dict = Depends(require_admin_or_super_admin),
  440. db: Session = Depends(get_db),
  441. ):
  442. try:
  443. tenant_no = get_tenant_no(db, _user)
  444. user_info = crm_user_info(_user)
  445. req = body or QuoteStatisticsOverviewBody()
  446. data = statistics_overview(
  447. db,
  448. tenant_no,
  449. owner_user_id=req.ownerUserId,
  450. begin_time=req.beginTime,
  451. end_time=req.endTime,
  452. user_info=user_info,
  453. )
  454. return success_response(data)
  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.post("/quote/statistics/status")
  460. async def quote_statistics_status(
  461. _user: dict = Depends(require_admin_or_super_admin),
  462. db: Session = Depends(get_db),
  463. ):
  464. try:
  465. tenant_no = get_tenant_no(db, _user)
  466. user_info = crm_user_info(_user)
  467. return success_response(statistics_status(db, tenant_no))
  468. except ValueError as e:
  469. return error_response(400, str(e))
  470. except Exception as e:
  471. return error_response(500, str(e))
  472. @router.post("/quote/statistics/trend")
  473. async def quote_statistics_trend(
  474. body: QuoteStatisticsTrendBody = None,
  475. _user: dict = Depends(require_admin_or_super_admin),
  476. db: Session = Depends(get_db),
  477. ):
  478. try:
  479. tenant_no = get_tenant_no(db, _user)
  480. user_info = crm_user_info(_user)
  481. req = body or QuoteStatisticsTrendBody()
  482. data = statistics_trend(
  483. db,
  484. tenant_no,
  485. period_type=req.type or "month",
  486. begin_time=req.beginTime,
  487. end_time=req.endTime,
  488. user_info=user_info,
  489. )
  490. return success_response(data)
  491. except ValueError as e:
  492. return error_response(400, str(e))
  493. except Exception as e:
  494. return error_response(500, str(e))
  495. @router.post("/quote/product/list")
  496. async def quote_product_list(
  497. body: QuoteProductListBody,
  498. _user: dict = Depends(require_admin_or_super_admin),
  499. db: Session = Depends(get_db),
  500. ):
  501. try:
  502. tenant_no = get_tenant_no(db, _user)
  503. user_info = crm_user_info(_user)
  504. rows = list_quote_products(db, body.quoteId, tenant_no=tenant_no)
  505. return success_response(rows)
  506. except ValueError as e:
  507. return error_response(400, str(e))
  508. except Exception as e:
  509. return error_response(500, str(e))
  510. @router.post("/quote/product/add")
  511. async def quote_product_add(
  512. body: QuoteProductCreateBody,
  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. user_id = _user.get("user_id", "") or _user.get("id", "")
  520. dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
  521. obj = insert_quote_product(
  522. db,
  523. tenant_no=tenant_no,
  524. data=body.model_dump(by_alias=True),
  525. user_id=user_id,
  526. dept_id=dept_id,
  527. )
  528. from app.crud.crm.quote_product import product_row_to_dict
  529. return success_response(product_row_to_dict(obj), message="新增成功")
  530. except ValueError as e:
  531. return error_response(400, str(e))
  532. except Exception as e:
  533. return error_response(500, str(e))
  534. @router.post("/quote/product/update")
  535. async def quote_product_update(
  536. body: QuoteProductUpdateBody,
  537. _user: dict = Depends(require_admin_or_super_admin),
  538. db: Session = Depends(get_db),
  539. ):
  540. try:
  541. tenant_no = get_tenant_no(db, _user)
  542. user_info = crm_user_info(_user)
  543. user_id = _user.get("user_id", "") or _user.get("id", "")
  544. data = body.model_dump(by_alias=True)
  545. line_id = data.pop("id")
  546. obj = update_quote_product(
  547. db,
  548. line_id,
  549. tenant_no=tenant_no,
  550. data=data,
  551. user_id=user_id,
  552. user_info=user_info,
  553. )
  554. from app.crud.crm.quote_product import product_row_to_dict
  555. return success_response(product_row_to_dict(obj), message="修改成功")
  556. except ValueError as e:
  557. return error_response(400, str(e))
  558. except Exception as e:
  559. return error_response(500, str(e))
  560. @router.post("/quote/product/delete")
  561. async def quote_product_delete(
  562. body: QuoteProductDeleteBody,
  563. _user: dict = Depends(require_admin_or_super_admin),
  564. db: Session = Depends(get_db),
  565. ):
  566. try:
  567. tenant_no = get_tenant_no(db, _user)
  568. user_info = crm_user_info(_user)
  569. soft_delete_quote_products(db, body.ids, tenant_no=tenant_no)
  570. return success_response(None, message="删除成功")
  571. except ValueError as e:
  572. return error_response(400, str(e))
  573. except Exception as e:
  574. return error_response(500, str(e))
  575. @router.post("/quote/approval/list")
  576. async def quote_approval_list(
  577. body: QuoteApprovalListBody,
  578. _user: dict = Depends(require_admin_or_super_admin),
  579. db: Session = Depends(get_db),
  580. ):
  581. try:
  582. tenant_no = get_tenant_no(db, _user)
  583. user_info = crm_user_info(_user)
  584. rows = list_quote_approvals(db, body.quoteId, tenant_no=tenant_no)
  585. return success_response(rows)
  586. except ValueError as e:
  587. return error_response(400, str(e))
  588. except Exception as e:
  589. return error_response(500, str(e))
  590. @router.post("/quote/approval/approve")
  591. async def quote_approval_approve(
  592. body: QuoteApprovalApproveBody,
  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_id = _user.get("user_id", "") or _user.get("id", "")
  599. approve_quote(
  600. db,
  601. body.id,
  602. tenant_no=tenant_no,
  603. approval_result=body.approvalResult,
  604. approval_opinion=body.approvalOpinion or "",
  605. remark=body.remark or "",
  606. user_id=user_id,
  607. )
  608. return success_response(None, message="审批成功")
  609. except ValueError as e:
  610. return error_response(400, str(e))
  611. except Exception as e:
  612. return error_response(500, str(e))
  613. @router.post("/quote/approval/withdraw")
  614. async def quote_approval_withdraw(
  615. body: QuoteApprovalWithdrawBody,
  616. _user: dict = Depends(require_admin_or_super_admin),
  617. db: Session = Depends(get_db),
  618. ):
  619. try:
  620. tenant_no = get_tenant_no(db, _user)
  621. user_id = _user.get("user_id", "") or _user.get("id", "")
  622. withdraw_approval(
  623. db,
  624. body.quoteId,
  625. tenant_no=tenant_no,
  626. remark=body.remark or "",
  627. user_id=user_id,
  628. )
  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("/quote/approval/refreshNames")
  635. async def quote_approval_refresh_names(
  636. body: QuoteApprovalRefreshNamesBody = None,
  637. _user: dict = Depends(require_admin_or_super_admin),
  638. db: Session = Depends(get_db),
  639. ):
  640. try:
  641. req = body or QuoteApprovalRefreshNamesBody()
  642. count = refresh_approver_names(db, quote_id=req.quoteId)
  643. return success_response(count)
  644. except ValueError as e:
  645. return error_response(400, str(e))
  646. except Exception as e:
  647. return error_response(500, str(e))
  648. @router.post("/quote/shareLog/list")
  649. async def quote_share_log_list(
  650. body: QuoteShareLogListBody,
  651. _user: dict = Depends(require_admin_or_super_admin),
  652. db: Session = Depends(get_db),
  653. ):
  654. try:
  655. tenant_no = get_tenant_no(db, _user)
  656. user_info = crm_user_info(_user)
  657. rows = list_share_logs(db, body.quoteId, tenant_no=tenant_no)
  658. return success_response(rows)
  659. except ValueError as e:
  660. return error_response(400, str(e))
  661. except Exception as e:
  662. return error_response(500, str(e))
  663. @router.post("/quote/shareLog/statistics")
  664. async def quote_share_log_statistics(
  665. body: QuoteShareLogStatisticsBody,
  666. _user: dict = Depends(require_admin_or_super_admin),
  667. db: Session = Depends(get_db),
  668. ):
  669. try:
  670. tenant_no = get_tenant_no(db, _user)
  671. user_info = crm_user_info(_user)
  672. stats = share_log_statistics(db, body.quoteId, tenant_no=tenant_no)
  673. return success_response(stats)
  674. except ValueError as e:
  675. return error_response(400, str(e))
  676. except Exception as e:
  677. return error_response(500, str(e))