| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- """CRM 报价单 API 路由 — 挂接到 crm.router"""
- from __future__ import annotations
- from datetime import datetime
- from fastapi import Depends
- from sqlalchemy.orm import Session
- from app.core.database import get_db
- from app.core.menu_guard import require_admin_or_super_admin
- from app.core.response import error_response, success_response
- from app.core.tenant_context import get_tenant_no
- from app.crud.crm.data_scope_helpers import crm_user_info
- from app.crud.crm.quote import (
- accept_quote,
- create_new_version,
- create_quote,
- generate_pdf,
- generate_share,
- get_quote_detail,
- get_template,
- invalid_quote,
- list_quotes,
- query_versions,
- reject_quote,
- save_template,
- soft_delete_quotes,
- statistics_overview,
- statistics_status,
- statistics_trend,
- submit_quote,
- to_contract,
- total_pages as quote_total_pages,
- update_quote,
- )
- from app.crud.crm.quote_approval import (
- approve_quote,
- list_quote_approvals,
- refresh_approver_names,
- withdraw_approval,
- )
- from app.crud.crm.quote_product import (
- insert_quote_product,
- list_quote_products,
- soft_delete_quote_products,
- update_quote_product,
- )
- from app.crud.crm.quote_share_log import list_share_logs, share_log_statistics
- from app.schemas.crm.quote import (
- QuoteAcceptBody,
- QuoteApprovalApproveBody,
- QuoteApprovalListBody,
- QuoteApprovalRefreshNamesBody,
- QuoteApprovalWithdrawBody,
- QuoteCreateBody,
- QuoteDeleteBody,
- QuoteDetailBody,
- QuoteInvalidBody,
- QuoteListBody,
- QuoteNewVersionBody,
- QuotePdfBody,
- QuoteProductCreateBody,
- QuoteProductDeleteBody,
- QuoteProductListBody,
- QuoteProductUpdateBody,
- QuoteRejectBody,
- QuoteShareGenerateBody,
- QuoteShareLogListBody,
- QuoteShareLogStatisticsBody,
- QuoteStatisticsOverviewBody,
- QuoteStatisticsTrendBody,
- QuoteSubmitBody,
- QuoteTemplateGetBody,
- QuoteTemplateSaveBody,
- QuoteToContractBody,
- QuoteUpdateBody,
- QuoteVersionsBody,
- )
- from app.utils.crm_export_columns import QUOTE_EXPORT_COLUMNS
- from app.utils.excel_export import export_rows_to_xlsx_response
- def register_quote_routes(router):
- @router.post("/quote/list")
- async def quote_list(
- body: QuoteListBody = None,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- list_body = body or QuoteListBody()
- user_id = _user.get("user_id", "") or _user.get("id", "")
- rows, total = list_quotes(
- db,
- tenant_no,
- user_info=user_info,
- page=list_body.page,
- page_size=list_body.pageSize,
- filters=list_body.model_dump(by_alias=True, exclude={"page", "pageSize"}),
- current_user_id=user_id,
- )
- tp = quote_total_pages(total, list_body.pageSize)
- return success_response(
- {
- "list": rows,
- "rows": rows,
- "total": total,
- "page": list_body.page,
- "pageSize": list_body.pageSize,
- "totalPages": tp,
- }
- )
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/detail")
- async def quote_detail(
- body: QuoteDetailBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- detail = get_quote_detail(db, body.id, tenant_no=tenant_no, user_info=user_info)
- return success_response(detail)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/add")
- async def quote_add(
- body: QuoteCreateBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
- detail = create_quote(
- db,
- tenant_no=tenant_no,
- data=body.model_dump(by_alias=True),
- user_id=user_id,
- dept_id=dept_id,
- )
- return success_response(detail, message="新增成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/update")
- async def quote_update(
- body: QuoteUpdateBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
- data = body.model_dump(by_alias=True)
- quote_id = data.pop("id")
- detail = update_quote(
- db,
- quote_id,
- tenant_no=tenant_no,
- data=data,
- user_id=user_id,
- dept_id=dept_id,
- user_info=user_info,
- )
- return success_response(detail, message="修改成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/delete")
- async def quote_delete(
- body: QuoteDeleteBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- soft_delete_quotes(db, body.ids, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/submit")
- async def quote_submit(
- body: QuoteSubmitBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- submit_quote(
- db,
- body.id,
- tenant_no=tenant_no,
- approver_ids=body.approverIds,
- remark=body.remark,
- user_info=user_info,
- )
- return success_response(None, message="提交成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/saveTemplate")
- async def quote_save_template(
- body: QuoteTemplateSaveBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- save_template(db, body.id, body.templateContent, tenant_no=tenant_no)
- return success_response(None, message="保存成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/getTemplate")
- async def quote_get_template(
- body: QuoteTemplateGetBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- content = get_template(db, body.id, tenant_no=tenant_no, user_info=user_info)
- return success_response(content or "")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/generatePdf")
- async def quote_generate_pdf(
- body: QuotePdfBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- pdf_url = generate_pdf(db, body.id, tenant_no=tenant_no)
- return success_response(pdf_url)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/generateShare")
- async def quote_generate_share(
- body: QuoteShareGenerateBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- link = generate_share(
- db,
- body.id,
- tenant_no=tenant_no,
- validity_days=body.validityDays,
- password=body.password or "",
- user_info=user_info,
- )
- return success_response(link)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/accept")
- async def quote_accept(
- body: QuoteAcceptBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- accept_quote(db, body.id, tenant_no=tenant_no, remark=body.remark)
- return success_response(None, message="操作成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/reject")
- async def quote_reject(
- body: QuoteRejectBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- reject_quote(
- db,
- body.id,
- tenant_no=tenant_no,
- reject_reason=body.rejectReason,
- remark=body.remark,
- user_info=user_info,
- )
- return success_response(None, message="操作成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/toContract")
- async def quote_to_contract(
- body: QuoteToContractBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
- contract_id = to_contract(
- db,
- body.id,
- tenant_no=tenant_no,
- contract_no=body.contractNo,
- contract_title=body.contractTitle,
- sign_date=body.signDate,
- start_date=body.startDate,
- end_date=body.endDate,
- user_id=user_id,
- dept_id=dept_id,
- user_info=user_info,
- )
- return success_response(contract_id)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/newVersion")
- async def quote_new_version(
- body: QuoteNewVersionBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
- new_id = create_new_version(
- db,
- body.id,
- tenant_no=tenant_no,
- version=body.version,
- remark=body.remark,
- user_id=user_id,
- dept_id=dept_id,
- user_info=user_info,
- )
- return success_response(new_id)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/versions")
- async def quote_versions(
- body: QuoteVersionsBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- rows = query_versions(db, body.quoteNo, tenant_no=tenant_no)
- return success_response(rows)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/invalid")
- async def quote_invalid(
- body: QuoteInvalidBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- invalid_quote(db, body.id, tenant_no=tenant_no, remark=body.remark)
- return success_response(None, message="作废成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/export")
- async def quote_export(
- body: QuoteListBody = None,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- list_body = body or QuoteListBody()
- rows, _ = list_quotes(
- db,
- tenant_no,
- user_info=user_info,
- page=1,
- page_size=10000,
- filters=list_body.model_dump(by_alias=True, exclude={"page", "pageSize"}),
- )
- return export_rows_to_xlsx_response(
- db,
- tenant_no=tenant_no,
- sheet_title="报价单",
- columns=QUOTE_EXPORT_COLUMNS,
- rows=rows,
- filename_stem=f"报价单_{datetime.now().strftime('%Y%m%d%H%M%S')}",
- include_legacy_filename=False,
- )
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/statistics/overview")
- async def quote_statistics_overview(
- body: QuoteStatisticsOverviewBody = None,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- req = body or QuoteStatisticsOverviewBody()
- data = statistics_overview(
- db,
- tenant_no,
- owner_user_id=req.ownerUserId,
- begin_time=req.beginTime,
- end_time=req.endTime,
- user_info=user_info,
- )
- return success_response(data)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/statistics/status")
- async def quote_statistics_status(
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- return success_response(statistics_status(db, tenant_no))
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/statistics/trend")
- async def quote_statistics_trend(
- body: QuoteStatisticsTrendBody = None,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- req = body or QuoteStatisticsTrendBody()
- data = statistics_trend(
- db,
- tenant_no,
- period_type=req.type or "month",
- begin_time=req.beginTime,
- end_time=req.endTime,
- user_info=user_info,
- )
- return success_response(data)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/product/list")
- async def quote_product_list(
- body: QuoteProductListBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- rows = list_quote_products(db, body.quoteId, tenant_no=tenant_no)
- return success_response(rows)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/product/add")
- async def quote_product_add(
- body: QuoteProductCreateBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- dept_id = _user.get("dept_id", "") or _user.get("create_dept_id", "") or ""
- obj = insert_quote_product(
- db,
- tenant_no=tenant_no,
- data=body.model_dump(by_alias=True),
- user_id=user_id,
- dept_id=dept_id,
- )
- from app.crud.crm.quote_product import product_row_to_dict
- return success_response(product_row_to_dict(obj), message="新增成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/product/update")
- async def quote_product_update(
- body: QuoteProductUpdateBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- data = body.model_dump(by_alias=True)
- line_id = data.pop("id")
- obj = update_quote_product(
- db,
- line_id,
- tenant_no=tenant_no,
- data=data,
- user_id=user_id,
- user_info=user_info,
- )
- from app.crud.crm.quote_product import product_row_to_dict
- return success_response(product_row_to_dict(obj), message="修改成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/product/delete")
- async def quote_product_delete(
- body: QuoteProductDeleteBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- soft_delete_quote_products(db, body.ids, tenant_no=tenant_no)
- return success_response(None, message="删除成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/approval/list")
- async def quote_approval_list(
- body: QuoteApprovalListBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- rows = list_quote_approvals(db, body.quoteId, tenant_no=tenant_no)
- return success_response(rows)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/approval/approve")
- async def quote_approval_approve(
- body: QuoteApprovalApproveBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- approve_quote(
- db,
- body.id,
- tenant_no=tenant_no,
- approval_result=body.approvalResult,
- approval_opinion=body.approvalOpinion or "",
- remark=body.remark or "",
- user_id=user_id,
- )
- return success_response(None, message="审批成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/approval/withdraw")
- async def quote_approval_withdraw(
- body: QuoteApprovalWithdrawBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_id = _user.get("user_id", "") or _user.get("id", "")
- withdraw_approval(
- db,
- body.quoteId,
- tenant_no=tenant_no,
- remark=body.remark or "",
- user_id=user_id,
- )
- return success_response(None, message="撤回成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/approval/refreshNames")
- async def quote_approval_refresh_names(
- body: QuoteApprovalRefreshNamesBody = None,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- req = body or QuoteApprovalRefreshNamesBody()
- count = refresh_approver_names(db, quote_id=req.quoteId)
- return success_response(count)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/shareLog/list")
- async def quote_share_log_list(
- body: QuoteShareLogListBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- rows = list_share_logs(db, body.quoteId, tenant_no=tenant_no)
- return success_response(rows)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.post("/quote/shareLog/statistics")
- async def quote_share_log_statistics(
- body: QuoteShareLogStatisticsBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- tenant_no = get_tenant_no(db, _user)
- user_info = crm_user_info(_user)
- stats = share_log_statistics(db, body.quoteId, tenant_no=tenant_no)
- return success_response(stats)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
|