feat(server): 新增申请核对预览快速建单接口与平台管理员判定统一
- reimbursements 新增 POST /application-preview-action,AI 工作台表格核对后直接走 UserAgentService 建单/提交,免去通用 Orchestrator 编排 - 平台管理员判定统一抽取 PLATFORM_ADMIN_IDENTITIES 常量,identity 与 role_codes 均支持 admin/superadmin,含 header 开关 - docker-compose 镜像补装 openssh-server - 同步更新差旅/交通/通信等财务规则表与 reimbursements 端点测试
This commit is contained in:
@@ -8,6 +8,10 @@ from sqlalchemy.orm import Session
|
||||
from app.db.session import get_session_factory
|
||||
|
||||
|
||||
PLATFORM_ADMIN_IDENTITIES = {"admin", "superadmin"}
|
||||
ADMIN_HEADER_TRUE_VALUES = {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
db = get_session_factory()()
|
||||
try:
|
||||
@@ -124,14 +128,15 @@ def _resolve_platform_admin_flag(
|
||||
role_codes: list[str],
|
||||
header_value: str | None,
|
||||
) -> bool:
|
||||
if str(header_value or "").strip().lower() in {"1", "true", "yes", "on"}:
|
||||
if str(header_value or "").strip().lower() in ADMIN_HEADER_TRUE_VALUES:
|
||||
return True
|
||||
|
||||
identities = {
|
||||
str(username or "").strip().lower(),
|
||||
str(name or "").strip().lower(),
|
||||
}
|
||||
return "admin" in identities or "admin" in {_normalize_role_code(item) for item in role_codes}
|
||||
normalized_role_codes = {_normalize_role_code(item) for item in role_codes}
|
||||
return bool(identities & PLATFORM_ADMIN_IDENTITIES) or bool(normalized_role_codes & PLATFORM_ADMIN_IDENTITIES)
|
||||
|
||||
|
||||
def require_admin_user(
|
||||
|
||||
@@ -11,6 +11,9 @@ from app.api.pagination import PageNumber, PageSize, page_payload, wants_page
|
||||
from app.schemas.budget import BudgetClaimAnalysisRead
|
||||
from app.schemas.common import ErrorResponse, PaginatedResponse
|
||||
from app.schemas.reimbursement import (
|
||||
ExpenseApplicationPreviewActionPayload,
|
||||
ExpenseApplicationPreviewActionResponse,
|
||||
ExpenseApplicationPreviewActionResult,
|
||||
ExpenseClaimAttachmentActionResponse,
|
||||
ExpenseClaimActionResponse,
|
||||
ExpenseClaimAttachmentRead,
|
||||
@@ -27,10 +30,13 @@ from app.schemas.reimbursement import (
|
||||
TravelReimbursementCalculatorRequest,
|
||||
TravelReimbursementCalculatorResponse,
|
||||
)
|
||||
from app.schemas.ontology import OntologyParseResult, OntologyPermission
|
||||
from app.schemas.user_agent import UserAgentRequest
|
||||
from app.services.budget import BudgetService
|
||||
from app.services.expense_claims import ExpenseClaimService
|
||||
from app.services.reimbursement import ReimbursementService
|
||||
from app.services.travel_reimbursement_calculator import TravelReimbursementCalculatorService
|
||||
from app.services.user_agent import UserAgentService
|
||||
|
||||
router = APIRouter()
|
||||
DbSession = Annotated[Session, Depends(get_db)]
|
||||
@@ -88,6 +94,90 @@ def calculate_travel_reimbursement(
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
|
||||
|
||||
|
||||
def _build_application_preview_action_context(
|
||||
payload: ExpenseApplicationPreviewActionPayload,
|
||||
current_user: CurrentUserContext,
|
||||
) -> dict[str, object]:
|
||||
context_json = dict(payload.context_json or {})
|
||||
context_json.setdefault("session_type", "application")
|
||||
context_json.setdefault("entry_source", "workbench_ai_inline")
|
||||
context_json.setdefault("document_type", "expense_application")
|
||||
context_json.setdefault("application_stage", "expense_application")
|
||||
context_json.setdefault("role_codes", current_user.role_codes)
|
||||
context_json.setdefault("is_admin", current_user.is_admin)
|
||||
context_json.setdefault("username", current_user.username)
|
||||
context_json.setdefault("name", current_user.name)
|
||||
context_json.setdefault("department_name", current_user.department_name)
|
||||
context_json.setdefault("position", current_user.position)
|
||||
context_json.setdefault("grade", current_user.grade)
|
||||
context_json.setdefault("employee_no", current_user.employee_no)
|
||||
context_json.setdefault("manager_name", current_user.manager_name)
|
||||
return context_json
|
||||
|
||||
|
||||
@router.post(
|
||||
"/application-preview-action",
|
||||
response_model=ExpenseApplicationPreviewActionResponse,
|
||||
summary="按申请核对预览快速保存或提交申请单",
|
||||
description="用于 AI 工作台已完成表格核对后的轻量建单/提交流程,避免重复进入通用 Orchestrator 编排。",
|
||||
)
|
||||
def run_application_preview_action(
|
||||
payload: ExpenseApplicationPreviewActionPayload,
|
||||
db: DbSession,
|
||||
current_user: CurrentUser,
|
||||
) -> ExpenseApplicationPreviewActionResponse:
|
||||
context_json = _build_application_preview_action_context(payload, current_user)
|
||||
run_id = f"application-preview-action:{payload.conversation_id or current_user.username}"
|
||||
request = UserAgentRequest(
|
||||
run_id=run_id,
|
||||
user_id=payload.user_id or current_user.username or current_user.name,
|
||||
message=payload.message,
|
||||
ontology=OntologyParseResult(
|
||||
scenario="expense",
|
||||
intent="operate",
|
||||
permission=OntologyPermission(
|
||||
level="approval_required",
|
||||
allowed=True,
|
||||
reason="application preview fast action",
|
||||
),
|
||||
confidence=1.0,
|
||||
run_id=run_id,
|
||||
),
|
||||
context_json=context_json,
|
||||
tool_payload={},
|
||||
selected_capability_codes=[],
|
||||
degraded=False,
|
||||
requires_confirmation=False,
|
||||
)
|
||||
try:
|
||||
user_agent_response = UserAgentService(db)._build_expense_application_response(
|
||||
request,
|
||||
risk_flags=[],
|
||||
)
|
||||
except ValueError as error:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
|
||||
|
||||
return ExpenseApplicationPreviewActionResponse(
|
||||
status="succeeded",
|
||||
conversation_id=payload.conversation_id,
|
||||
result=ExpenseApplicationPreviewActionResult(
|
||||
message=user_agent_response.answer,
|
||||
answer=user_agent_response.answer,
|
||||
suggested_actions=[
|
||||
action.model_dump(mode="json")
|
||||
for action in user_agent_response.suggested_actions
|
||||
],
|
||||
risk_flags=user_agent_response.risk_flags,
|
||||
requires_confirmation=user_agent_response.requires_confirmation,
|
||||
draft_payload=(
|
||||
user_agent_response.draft_payload.model_dump(mode="json")
|
||||
if user_agent_response.draft_payload is not None
|
||||
else None
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/claims",
|
||||
response_model=list[ExpenseClaimRead] | PaginatedResponse[ExpenseClaimRead],
|
||||
|
||||
Reference in New Issue
Block a user