| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- """CRM 合同子模块路由 — 变更/模板/审批独立页"""
- from __future__ import annotations
- from datetime import datetime
- from typing import Optional
- from fastapi import Depends, Query
- 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.contract import (
- create_contract_change,
- create_contract_template,
- export_contract_changes,
- export_contract_templates,
- execute_change,
- get_contract_change_detail,
- get_contract_template_detail,
- list_contract_changes,
- list_contract_templates,
- soft_delete_contract_changes,
- soft_delete_contract_templates,
- submit_change_approval,
- toggle_contract_template_active,
- total_pages,
- update_contract_change,
- update_contract_template,
- withdraw_change,
- )
- from app.crud.crm.contract_approval import (
- approve_pass,
- approve_reject,
- approve_transfer,
- export_contract_approvals,
- get_contract_approval_detail,
- list_contract_approvals_page,
- refresh_approver_names,
- )
- from app.schemas.crm.contract import (
- ContractApprovalExportBody,
- ContractApprovalListQuery,
- ContractApprovalPassBody,
- ContractApprovalRefreshNamesBody,
- ContractApprovalRejectBody,
- ContractApprovalTransferBody,
- ContractChangeCreateBody,
- ContractChangeExportBody,
- ContractChangeListQuery,
- ContractChangeSubmitBody,
- ContractChangeUpdateBody,
- ContractTemplateCreateBody,
- ContractTemplateExportBody,
- ContractTemplateListQuery,
- ContractTemplateUpdateBody,
- )
- from app.utils.crm_export_columns import (
- CONTRACT_APPROVAL_EXPORT_COLUMNS,
- CONTRACT_CHANGE_EXPORT_COLUMNS,
- CONTRACT_TEMPLATE_EXPORT_COLUMNS,
- )
- from app.utils.excel_export import export_rows_to_xlsx_response
- def register_contract_sub_routes(router):
- # --- 合同变更 ---
- @router.get("/contract/change/list")
- async def contract_change_list(
- pageNum: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- contractId: Optional[str] = None,
- contractNo: Optional[str] = None,
- contractName: Optional[str] = None,
- changeType: Optional[str] = None,
- approvalStatus: Optional[str] = None,
- beginTime: Optional[str] = None,
- endTime: Optional[str] = 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)
- query = ContractChangeListQuery(
- pageNum=pageNum,
- pageSize=pageSize,
- contractId=contractId,
- contractNo=contractNo,
- contractName=contractName,
- changeType=changeType,
- approvalStatus=approvalStatus,
- beginTime=beginTime,
- endTime=endTime,
- )
- rows, total = list_contract_changes(
- db,
- tenant_no,
- user_info=user_info,
- page=query.pageNum,
- page_size=query.pageSize,
- filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
- )
- return success_response(
- {
- "list": rows,
- "rows": rows,
- "total": total,
- "pageNum": query.pageNum,
- "pageSize": query.pageSize,
- "totalPages": total_pages(total, query.pageSize),
- }
- )
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.get("/contract/change/{change_id}")
- async def contract_change_detail(
- change_id: str,
- _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_contract_change_detail(db, change_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("/contract/change")
- async def contract_change_add(
- body: ContractChangeCreateBody,
- _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_contract_change(
- db,
- tenant_no=tenant_no,
- data=body.model_dump(by_alias=True),
- user_id=user_id,
- dept_id=dept_id,
- )
- return success_response(detail["id"], message="新增成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.put("/contract/change")
- async def contract_change_edit(
- body: ContractChangeUpdateBody,
- _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)
- change_id = data.pop("id")
- detail = update_contract_change(
- db,
- change_id,
- tenant_no=tenant_no,
- data=data,
- user_id=user_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.delete("/contract/change/{ids}")
- async def contract_change_remove(
- ids: str,
- _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)
- id_list = [x.strip() for x in ids.split(",") if x.strip()]
- soft_delete_contract_changes(db, id_list, tenant_no=tenant_no, 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("/contract/change/submitApproval")
- async def contract_change_submit_approval(
- body: ContractChangeSubmitBody,
- _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", "")
- submit_change_approval(
- db,
- body.id,
- tenant_no=tenant_no,
- approver_ids=body.approverIds,
- 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.put("/contract/change/withdraw/{change_id}")
- async def contract_change_withdraw(
- change_id: str,
- _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", "")
- withdraw_change(db, change_id, tenant_no=tenant_no, 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.put("/contract/change/execute/{change_id}")
- async def contract_change_execute(
- change_id: str,
- _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", "")
- execute_change(db, change_id, tenant_no=tenant_no, 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("/contract/change/export")
- async def contract_change_export(
- body: ContractChangeExportBody = 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 ContractChangeExportBody()
- filters = list_body.model_dump(
- by_alias=True,
- exclude={"pageNum", "pageSize"},
- exclude_none=True,
- )
- rows = export_contract_changes(db, tenant_no, filters=filters)
- return export_rows_to_xlsx_response(
- db,
- tenant_no=tenant_no,
- sheet_title="合同变更记录",
- columns=CONTRACT_CHANGE_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.get("/contract/template/list")
- async def contract_template_list(
- pageNum: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- templateNo: Optional[str] = None,
- templateName: Optional[str] = None,
- templateType: Optional[str] = None,
- isActive: Optional[int] = None,
- beginTime: Optional[str] = None,
- endTime: Optional[str] = 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)
- query = ContractTemplateListQuery(
- pageNum=pageNum,
- pageSize=pageSize,
- templateNo=templateNo,
- templateName=templateName,
- templateType=templateType,
- isActive=isActive,
- beginTime=beginTime,
- endTime=endTime,
- )
- rows, total = list_contract_templates(
- db,
- tenant_no,
- user_info=user_info,
- page=query.pageNum,
- page_size=query.pageSize,
- filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
- )
- return success_response(
- {
- "list": rows,
- "rows": rows,
- "total": total,
- "pageNum": query.pageNum,
- "pageSize": query.pageSize,
- "totalPages": total_pages(total, query.pageSize),
- }
- )
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.get("/contract/template/{template_id}")
- async def contract_template_detail(
- template_id: str,
- _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_contract_template_detail(db, template_id, tenant_no=tenant_no)
- 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("/contract/template")
- async def contract_template_add(
- body: ContractTemplateCreateBody,
- _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_contract_template(
- db,
- tenant_no=tenant_no,
- data=body.model_dump(by_alias=True),
- user_id=user_id,
- dept_id=dept_id,
- )
- return success_response(detail["id"], message="新增成功")
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.put("/contract/template")
- async def contract_template_edit(
- body: ContractTemplateUpdateBody,
- _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)
- template_id = data.pop("id")
- detail = update_contract_template(
- db,
- template_id,
- tenant_no=tenant_no,
- data=data,
- user_id=user_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.delete("/contract/template/{ids}")
- async def contract_template_remove(
- ids: str,
- _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)
- id_list = [x.strip() for x in ids.split(",") if x.strip()]
- soft_delete_contract_templates(db, id_list, tenant_no=tenant_no, 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.put("/contract/template/enable/{template_id}")
- async def contract_template_enable(
- template_id: str,
- _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", "")
- toggle_contract_template_active(
- db, template_id, tenant_no=tenant_no, is_active=1, 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.put("/contract/template/disable/{template_id}")
- async def contract_template_disable(
- template_id: str,
- _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", "")
- toggle_contract_template_active(
- db, template_id, tenant_no=tenant_no, is_active=0, 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("/contract/template/export")
- async def contract_template_export(
- body: ContractTemplateExportBody = 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 ContractTemplateExportBody()
- filters = list_body.model_dump(
- by_alias=True,
- exclude={"pageNum", "pageSize"},
- exclude_none=True,
- )
- rows = export_contract_templates(db, tenant_no, filters=filters)
- return export_rows_to_xlsx_response(
- db,
- tenant_no=tenant_no,
- sheet_title="合同模板",
- columns=CONTRACT_TEMPLATE_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.get("/contract/approval/list")
- async def contract_approval_list(
- pageNum: int = Query(1, ge=1),
- pageSize: int = Query(10, ge=1, le=100),
- contractId: Optional[str] = None,
- contractNo: Optional[str] = None,
- contractName: Optional[str] = None,
- approverName: Optional[str] = None,
- approvalResult: Optional[str] = None,
- beginTime: Optional[str] = None,
- endTime: Optional[str] = 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)
- query = ContractApprovalListQuery(
- pageNum=pageNum,
- pageSize=pageSize,
- contractId=contractId,
- contractNo=contractNo,
- contractName=contractName,
- approverName=approverName,
- approvalResult=approvalResult,
- beginTime=beginTime,
- endTime=endTime,
- )
- rows, total = list_contract_approvals_page(
- db,
- tenant_no,
- page=query.pageNum,
- page_size=query.pageSize,
- filters=query.model_dump(by_alias=True, exclude={"pageNum", "pageSize"}, exclude_none=True),
- )
- return success_response(
- {
- "list": rows,
- "rows": rows,
- "total": total,
- "pageNum": query.pageNum,
- "pageSize": query.pageSize,
- "totalPages": total_pages(total, query.pageSize),
- }
- )
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
- @router.get("/contract/approval/{approval_id}")
- async def contract_approval_detail(
- approval_id: str,
- _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_contract_approval_detail(db, approval_id, tenant_no=tenant_no)
- 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("/contract/approval/pass")
- async def contract_approval_pass(
- body: ContractApprovalPassBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- user_id = _user.get("user_id", "") or _user.get("id", "")
- approve_pass(db, body.id, approval_opinion=body.approvalOpinion or "", user_id=user_id)
- db.commit()
- 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("/contract/approval/reject")
- async def contract_approval_reject(
- body: ContractApprovalRejectBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- user_id = _user.get("user_id", "") or _user.get("id", "")
- approve_reject(db, body.id, approval_opinion=body.approvalOpinion, user_id=user_id)
- db.commit()
- 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("/contract/approval/transfer")
- async def contract_approval_transfer(
- body: ContractApprovalTransferBody,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- user_id = _user.get("user_id", "") or _user.get("id", "")
- approve_transfer(
- db,
- body.id,
- target_approver_id=body.targetApproverId,
- transfer_reason=body.transferReason,
- user_id=user_id,
- )
- db.commit()
- 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("/contract/approval/export")
- async def contract_approval_export(
- body: ContractApprovalExportBody = 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 ContractApprovalExportBody()
- filters = list_body.model_dump(
- by_alias=True,
- exclude={"pageNum", "pageSize"},
- exclude_none=True,
- )
- rows = export_contract_approvals(db, tenant_no, filters=filters)
- return export_rows_to_xlsx_response(
- db,
- tenant_no=tenant_no,
- sheet_title="合同审批记录",
- columns=CONTRACT_APPROVAL_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("/contract/approval/refreshNames")
- async def contract_approval_refresh_names(
- body: ContractApprovalRefreshNamesBody = None,
- _user: dict = Depends(require_admin_or_super_admin),
- db: Session = Depends(get_db),
- ):
- try:
- req = body or ContractApprovalRefreshNamesBody()
- count = refresh_approver_names(db, contract_id=req.contractId)
- db.commit()
- return success_response(count)
- except ValueError as e:
- return error_response(400, str(e))
- except Exception as e:
- return error_response(500, str(e))
|