feat: 增强 agent_assets 功能,支持更多资产操作
This commit is contained in:
@@ -2,19 +2,32 @@ from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Query, status
|
||||
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, status
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_db
|
||||
from app.api.deps import (
|
||||
CurrentUserContext,
|
||||
get_current_user,
|
||||
get_db,
|
||||
require_admin_user,
|
||||
require_rule_editor_user,
|
||||
require_rule_reviewer_user,
|
||||
)
|
||||
from app.schemas.agent_asset import (
|
||||
AgentAssetCreate,
|
||||
AgentAssetListItem,
|
||||
AgentAssetOnlyOfficeCallbackRead,
|
||||
AgentAssetOnlyOfficeCallbackWrite,
|
||||
AgentAssetOnlyOfficeConfigRead,
|
||||
AgentAssetRead,
|
||||
AgentAssetReviewCreate,
|
||||
AgentAssetReviewRead,
|
||||
AgentAssetVersionCompareRead,
|
||||
AgentAssetUpdate,
|
||||
AgentAssetVersionCreate,
|
||||
AgentAssetVersionRead,
|
||||
AgentAssetVersionTimelineItemRead,
|
||||
)
|
||||
from app.schemas.common import ErrorResponse
|
||||
from app.services.agent_assets import AgentAssetService
|
||||
@@ -29,6 +42,10 @@ RequestIdHeader = Annotated[
|
||||
str | None,
|
||||
Header(description="外部请求 ID,用于串联审计日志和上游调用链。"),
|
||||
]
|
||||
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]
|
||||
AdminUser = Annotated[CurrentUserContext, Depends(require_admin_user)]
|
||||
RuleEditorUser = Annotated[CurrentUserContext, Depends(require_rule_editor_user)]
|
||||
RuleReviewerUser = Annotated[CurrentUserContext, Depends(require_rule_reviewer_user)]
|
||||
|
||||
|
||||
def _handle_asset_error(exc: Exception) -> None:
|
||||
@@ -93,6 +110,185 @@ def get_agent_asset(asset_id: str, db: DbSession) -> AgentAssetRead:
|
||||
return asset
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{asset_id}/spreadsheet/onlyoffice-config",
|
||||
response_model=AgentAssetOnlyOfficeConfigRead,
|
||||
summary="读取规则 Excel 的 ONLYOFFICE 配置",
|
||||
description="为规则详情页中的 Excel 规则表生成 ONLYOFFICE 配置。",
|
||||
)
|
||||
def get_agent_asset_spreadsheet_onlyoffice_config(
|
||||
asset_id: str,
|
||||
current_user: CurrentUser,
|
||||
db: DbSession,
|
||||
version: Annotated[
|
||||
str | None,
|
||||
Query(description="可选的规则版本号;不传时默认当前版本。"),
|
||||
] = None,
|
||||
) -> AgentAssetOnlyOfficeConfigRead:
|
||||
try:
|
||||
return AgentAssetService(db).build_rule_spreadsheet_onlyoffice_config(
|
||||
asset_id,
|
||||
current_user,
|
||||
version=version,
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{asset_id}/spreadsheet/content",
|
||||
response_class=FileResponse,
|
||||
summary="下载或预览规则 Excel 文件",
|
||||
description="按版本返回规则的 Excel 快照,用于浏览器预览或下载。",
|
||||
)
|
||||
def get_agent_asset_spreadsheet_content(
|
||||
asset_id: str,
|
||||
_: CurrentUser,
|
||||
db: DbSession,
|
||||
version: Annotated[
|
||||
str | None,
|
||||
Query(description="可选的规则版本号;不传时默认当前版本。"),
|
||||
] = None,
|
||||
) -> FileResponse:
|
||||
try:
|
||||
file_path, media_type, filename = AgentAssetService(db).get_rule_spreadsheet_content(
|
||||
asset_id,
|
||||
version=version,
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
return FileResponse(file_path, media_type=media_type, filename=filename)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{asset_id}/spreadsheet/onlyoffice/content",
|
||||
response_class=FileResponse,
|
||||
summary="供 ONLYOFFICE 读取规则 Excel 源文件",
|
||||
description="使用短时令牌供 ONLYOFFICE 拉取规则表源文件。",
|
||||
)
|
||||
def get_agent_asset_spreadsheet_onlyoffice_content(
|
||||
asset_id: str,
|
||||
db: DbSession,
|
||||
version: Annotated[
|
||||
str,
|
||||
Query(min_length=1, description="规则版本号。"),
|
||||
],
|
||||
access_token: Annotated[
|
||||
str,
|
||||
Query(min_length=1, description="ONLYOFFICE 临时访问令牌。"),
|
||||
],
|
||||
) -> FileResponse:
|
||||
try:
|
||||
service = AgentAssetService(db)
|
||||
service.validate_rule_spreadsheet_access_token(asset_id, version, access_token)
|
||||
file_path, media_type, filename = service.get_rule_spreadsheet_content(
|
||||
asset_id,
|
||||
version=version,
|
||||
)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
return FileResponse(file_path, media_type=media_type, filename=filename)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{asset_id}/spreadsheet/upload",
|
||||
response_model=AgentAssetRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="上传规则 Excel 文件",
|
||||
description="为指定规则上传新的 Excel 快照,并自动生成新规则版本。",
|
||||
)
|
||||
def upload_agent_asset_spreadsheet(
|
||||
asset_id: str,
|
||||
content: Annotated[
|
||||
bytes,
|
||||
Body(
|
||||
media_type="application/octet-stream",
|
||||
description="待上传的 Excel 文件二进制内容。",
|
||||
),
|
||||
],
|
||||
filename: Annotated[str, Query(min_length=1, description="原始文件名。")],
|
||||
current_user: RuleEditorUser,
|
||||
db: DbSession,
|
||||
x_request_id: RequestIdHeader = None,
|
||||
) -> AgentAssetRead:
|
||||
try:
|
||||
return AgentAssetService(db).upload_rule_spreadsheet(
|
||||
asset_id,
|
||||
filename=filename,
|
||||
content=content,
|
||||
actor=current_user.name,
|
||||
request_id=x_request_id,
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{asset_id}/spreadsheet/import-content",
|
||||
response_model=AgentAssetRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="导入规则 Excel 表格内容",
|
||||
description="读取上传 Excel 中的工作表内容,写回当前规则表;保留当前规则文件名与规则身份。",
|
||||
)
|
||||
def import_agent_asset_spreadsheet_content(
|
||||
asset_id: str,
|
||||
content: Annotated[
|
||||
bytes,
|
||||
Body(
|
||||
media_type="application/octet-stream",
|
||||
description="待导入的 Excel 文件二进制内容。",
|
||||
),
|
||||
],
|
||||
filename: Annotated[str, Query(min_length=1, description="上传文件原始文件名。")],
|
||||
current_user: RuleEditorUser,
|
||||
db: DbSession,
|
||||
x_request_id: RequestIdHeader = None,
|
||||
) -> AgentAssetRead:
|
||||
try:
|
||||
return AgentAssetService(db).import_rule_spreadsheet_content(
|
||||
asset_id,
|
||||
filename=filename,
|
||||
content=content,
|
||||
actor=current_user.name,
|
||||
request_id=x_request_id,
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{asset_id}/spreadsheet/onlyoffice/callback",
|
||||
response_model=AgentAssetOnlyOfficeCallbackRead,
|
||||
summary="接收规则 Excel 的 ONLYOFFICE 回调",
|
||||
description="接收 ONLYOFFICE 回写内容,并自动生成新的规则版本。",
|
||||
)
|
||||
def handle_agent_asset_spreadsheet_onlyoffice_callback(
|
||||
asset_id: str,
|
||||
payload: AgentAssetOnlyOfficeCallbackWrite,
|
||||
db: DbSession,
|
||||
version: Annotated[
|
||||
str,
|
||||
Query(min_length=1, description="打开编辑器时对应的规则版本号。"),
|
||||
],
|
||||
) -> AgentAssetOnlyOfficeCallbackRead:
|
||||
try:
|
||||
AgentAssetService(db).handle_rule_spreadsheet_onlyoffice_callback(
|
||||
asset_id,
|
||||
version=version,
|
||||
payload=payload.model_dump(),
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
return AgentAssetOnlyOfficeCallbackRead()
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=AgentAssetRead,
|
||||
@@ -237,11 +433,22 @@ def create_agent_asset_version(
|
||||
def create_agent_asset_review(
|
||||
asset_id: str,
|
||||
payload: AgentAssetReviewCreate,
|
||||
current_user: CurrentUser,
|
||||
db: DbSession,
|
||||
x_actor: ActorHeader = None,
|
||||
x_request_id: RequestIdHeader = None,
|
||||
) -> AgentAssetReviewRead:
|
||||
try:
|
||||
role_codes = {item.strip() for item in current_user.role_codes}
|
||||
if payload.review_status.value == "pending":
|
||||
if not (
|
||||
current_user.is_admin
|
||||
or "manager" in role_codes
|
||||
or "finance" in role_codes
|
||||
):
|
||||
raise PermissionError("只有财务人员或高级管理人员可以提交审核。")
|
||||
elif not (current_user.is_admin or "manager" in role_codes):
|
||||
raise PermissionError("只有高级管理人员可以审核规则。")
|
||||
return AgentAssetService(db).create_review(
|
||||
asset_id,
|
||||
payload,
|
||||
@@ -270,6 +477,7 @@ def create_agent_asset_review(
|
||||
)
|
||||
def activate_agent_asset(
|
||||
asset_id: str,
|
||||
_: RuleReviewerUser,
|
||||
db: DbSession,
|
||||
x_actor: ActorHeader = None,
|
||||
x_request_id: RequestIdHeader = None,
|
||||
@@ -282,3 +490,68 @@ def activate_agent_asset(
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{asset_id}/versions/{version}/restore",
|
||||
response_model=AgentAssetRead,
|
||||
summary="基于历史版本恢复工作稿",
|
||||
description="复制指定历史版本内容生成新的工作版本,用于误上线后的快速恢复与重新审核。",
|
||||
)
|
||||
def restore_agent_asset_version(
|
||||
asset_id: str,
|
||||
version: str,
|
||||
current_user: RuleReviewerUser,
|
||||
db: DbSession,
|
||||
x_actor: ActorHeader = None,
|
||||
x_request_id: RequestIdHeader = None,
|
||||
) -> AgentAssetRead:
|
||||
try:
|
||||
return AgentAssetService(db).restore_version_as_working_copy(
|
||||
asset_id,
|
||||
version,
|
||||
actor=(x_actor or current_user.name or "system").strip() or "system",
|
||||
request_id=x_request_id,
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{asset_id}/version-timeline",
|
||||
response_model=list[AgentAssetVersionTimelineItemRead],
|
||||
summary="读取规则版本流转时间线",
|
||||
description="返回规则版本创建、提交审核、审核结果和正式上线等流转事件。",
|
||||
)
|
||||
def get_agent_asset_version_timeline(
|
||||
asset_id: str,
|
||||
_: CurrentUser,
|
||||
db: DbSession,
|
||||
) -> list[AgentAssetVersionTimelineItemRead]:
|
||||
try:
|
||||
return AgentAssetService(db).list_version_timeline(asset_id)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{asset_id}/versions/compare",
|
||||
response_model=AgentAssetVersionCompareRead,
|
||||
summary="比较两个规则表版本",
|
||||
description="对比两个 Excel 规则表版本的工作表变化与单元格级差异。",
|
||||
)
|
||||
def compare_agent_asset_spreadsheet_versions(
|
||||
asset_id: str,
|
||||
_: CurrentUser,
|
||||
db: DbSession,
|
||||
base_version: Annotated[str, Query(min_length=1, description="基准版本号")],
|
||||
target_version: Annotated[str, Query(min_length=1, description="对比版本号")],
|
||||
) -> AgentAssetVersionCompareRead:
|
||||
try:
|
||||
return AgentAssetService(db).compare_spreadsheet_versions(
|
||||
asset_id,
|
||||
base_version=base_version,
|
||||
target_version=target_version,
|
||||
)
|
||||
except Exception as exc:
|
||||
_handle_asset_error(exc)
|
||||
|
||||
@@ -25,6 +25,8 @@ class AgentAsset(Base):
|
||||
reviewer: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), index=True, default="draft")
|
||||
current_version: Mapped[str | None] = mapped_column(String(30), nullable=True)
|
||||
published_version: Mapped[str | None] = mapped_column(String(30), nullable=True)
|
||||
working_version: Mapped[str | None] = mapped_column(String(30), nullable=True)
|
||||
config_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
|
||||
@@ -36,6 +36,8 @@ class AgentAssetUpdate(BaseModel):
|
||||
reviewer: str | None = Field(default=None, max_length=100)
|
||||
status: AgentAssetStatus | None = None
|
||||
current_version: str | None = Field(default=None, max_length=30)
|
||||
published_version: str | None = Field(default=None, max_length=30)
|
||||
working_version: str | None = Field(default=None, max_length=30)
|
||||
config_json: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@@ -74,6 +76,58 @@ class AgentAssetReviewRead(BaseModel):
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class AgentAssetOnlyOfficeConfigRead(BaseModel):
|
||||
documentServerUrl: str
|
||||
config: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentAssetOnlyOfficeCallbackRead(BaseModel):
|
||||
error: int = 0
|
||||
|
||||
|
||||
class AgentAssetOnlyOfficeCallbackWrite(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
status: int = Field(description="ONLYOFFICE 回调状态码。")
|
||||
url: str | None = Field(default=None, description="文档下载地址,状态为 2 或 6 时使用。")
|
||||
users: list[str] = Field(default_factory=list, description="当前编辑用户列表。")
|
||||
|
||||
|
||||
class AgentAssetVersionTimelineItemRead(BaseModel):
|
||||
event_type: str
|
||||
version: str
|
||||
actor: str
|
||||
event_time: datetime
|
||||
title: str
|
||||
description: str = ""
|
||||
note: str | None = None
|
||||
source_version: str | None = None
|
||||
|
||||
|
||||
class AgentAssetSpreadsheetDiffCellRead(BaseModel):
|
||||
sheet_name: str
|
||||
cell: str
|
||||
change_type: str
|
||||
before_value: Any | None = None
|
||||
after_value: Any | None = None
|
||||
|
||||
|
||||
class AgentAssetSpreadsheetDiffSheetRead(BaseModel):
|
||||
sheet_name: str
|
||||
change_type: str
|
||||
|
||||
|
||||
class AgentAssetVersionCompareRead(BaseModel):
|
||||
base_version: str
|
||||
target_version: str
|
||||
added_sheet_count: int = 0
|
||||
removed_sheet_count: int = 0
|
||||
changed_sheet_count: int = 0
|
||||
changed_cell_count: int = 0
|
||||
sheet_changes: list[AgentAssetSpreadsheetDiffSheetRead] = Field(default_factory=list)
|
||||
cell_changes: list[AgentAssetSpreadsheetDiffCellRead] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AgentAssetVersionRead(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -86,6 +140,9 @@ class AgentAssetVersionRead(BaseModel):
|
||||
created_by: str
|
||||
created_at: datetime
|
||||
is_current: bool = False
|
||||
is_published: bool = False
|
||||
is_working: bool = False
|
||||
lifecycle_state: str = "history"
|
||||
|
||||
|
||||
class AgentAssetListItem(BaseModel):
|
||||
@@ -102,6 +159,8 @@ class AgentAssetListItem(BaseModel):
|
||||
reviewer: str | None
|
||||
status: str
|
||||
current_version: str | None
|
||||
published_version: str | None
|
||||
working_version: str | None
|
||||
config_json: dict[str, Any]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,12 @@
|
||||
from __future__ import annotations
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import UTC, date, datetime
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
from datetime import UTC, date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import inspect, select, text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.agent_enums import (
|
||||
@@ -26,16 +28,23 @@ from app.db.session import get_session_factory
|
||||
from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion
|
||||
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.models.financial_record import (
|
||||
AccountsPayableRecord,
|
||||
AccountsReceivableRecord,
|
||||
ExpenseClaim,
|
||||
ExpenseClaimItem,
|
||||
)
|
||||
from app.services.expense_rule_runtime import (
|
||||
build_scene_submission_standard_markdown,
|
||||
build_travel_risk_control_standard_markdown,
|
||||
)
|
||||
from app.models.financial_record import (
|
||||
AccountsPayableRecord,
|
||||
AccountsReceivableRecord,
|
||||
ExpenseClaim,
|
||||
ExpenseClaimItem,
|
||||
)
|
||||
from app.services.agent_asset_spreadsheet import (
|
||||
AgentAssetSpreadsheetManager,
|
||||
COMPANY_TRAVEL_EXPENSE_RULE_CODE,
|
||||
COMPANY_TRAVEL_EXPENSE_RULE_FILENAME,
|
||||
FINANCE_RULES_LIBRARY,
|
||||
RuleSpreadsheetMeta,
|
||||
)
|
||||
from app.services.expense_rule_runtime import (
|
||||
build_scene_submission_standard_markdown,
|
||||
build_travel_risk_control_standard_markdown,
|
||||
)
|
||||
|
||||
logger = get_logger("app.services.agent_foundation")
|
||||
|
||||
@@ -77,7 +86,8 @@ LEGACY_RULE_CODES = (
|
||||
"rule.ap.payment_dual_review",
|
||||
)
|
||||
|
||||
ATTACHMENT_RULE_ASSET_CODE = "rule.expense.attachment_submission_requirements"
|
||||
ATTACHMENT_RULE_ASSET_CODE = "rule.expense.attachment_submission_requirements"
|
||||
COMPANY_TRAVEL_RULE_VERSION = "v1.0.0"
|
||||
|
||||
ATTACHMENT_RULE_RUNTIME_CONFIG = {
|
||||
"kind": "policy_rule_draft",
|
||||
@@ -156,10 +166,11 @@ class AgentFoundationService:
|
||||
def __init__(self, db: Session) -> None:
|
||||
self.db = db
|
||||
|
||||
def ensure_foundation_ready(self) -> None:
|
||||
try:
|
||||
Base.metadata.create_all(bind=self.db.get_bind())
|
||||
self._seed_agent_assets()
|
||||
def ensure_foundation_ready(self) -> None:
|
||||
try:
|
||||
Base.metadata.create_all(bind=self.db.get_bind())
|
||||
self._ensure_agent_asset_schema()
|
||||
self._seed_agent_assets()
|
||||
self._sync_demo_financial_records()
|
||||
self._seed_runs_and_logs()
|
||||
self.db.commit()
|
||||
@@ -174,7 +185,7 @@ class AgentFoundationService:
|
||||
return
|
||||
self._purge_demo_financial_records()
|
||||
|
||||
def _seed_agent_assets(self) -> None:
|
||||
def _seed_agent_assets(self) -> None:
|
||||
existing_codes = set(self.db.scalars(select(AgentAsset.code)).all())
|
||||
if existing_codes:
|
||||
self._top_up_agent_assets(existing_codes)
|
||||
@@ -189,8 +200,10 @@ class AgentFoundationService:
|
||||
scenario_json=["expense", "risk_check", "attachment_policy", "invoice_anomaly"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="高嘉禾",
|
||||
status=AgentAssetStatus.REVIEW.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.REVIEW.value,
|
||||
current_version="v1.0.0",
|
||||
published_version=None,
|
||||
working_version="v1.0.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": False,
|
||||
@@ -209,8 +222,10 @@ class AgentFoundationService:
|
||||
scenario_json=["expense", "risk_check", "scene_policy", "attachment_policy"],
|
||||
owner="费用运营组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
@@ -218,17 +233,19 @@ class AgentFoundationService:
|
||||
"rule_template_label": "系统内置场景矩阵规则",
|
||||
},
|
||||
)
|
||||
travel_policy_rule = AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.expense.travel_risk_control_standard",
|
||||
name="差旅报销风险管控制度",
|
||||
travel_policy_rule = AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.expense.travel_risk_control_standard",
|
||||
name="差旅报销风险管控制度",
|
||||
description="统一定义差旅报销的行程闭环、酒店地点一致性、职级差标和风险处置口径。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "risk_check", "travel_policy", "travel_standard"],
|
||||
owner="风控与审计部",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.1.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.1.0",
|
||||
published_version="v1.1.0",
|
||||
working_version="v1.1.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
@@ -236,21 +253,45 @@ class AgentFoundationService:
|
||||
"warning_on_medium_risk": True,
|
||||
"source_doc": "document/development/risks/travel-risk-control-standard.md",
|
||||
"runtime_kind": "travel_policy",
|
||||
"rule_template_key": "travel_standard_v1",
|
||||
"rule_template_label": "差旅标准模板",
|
||||
},
|
||||
)
|
||||
skill_expense_asset = AgentAsset(
|
||||
asset_type=AgentAssetType.SKILL.value,
|
||||
code="skill.expense.summary_lookup",
|
||||
"rule_template_key": "travel_standard_v1",
|
||||
"rule_template_label": "差旅标准模板",
|
||||
},
|
||||
)
|
||||
company_travel_rule = AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code=COMPANY_TRAVEL_EXPENSE_RULE_CODE,
|
||||
name="公司差旅费报销规则",
|
||||
description="通过 Excel 明细表维护差旅费报销标准、票据要求和审批口径。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "travel_policy", "travel_standard"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
published_version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
working_version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
config_json={
|
||||
"severity": "medium",
|
||||
"enabled": True,
|
||||
"tag": "财务规则",
|
||||
"detail_mode": "spreadsheet",
|
||||
"rule_library": FINANCE_RULES_LIBRARY,
|
||||
"rule_template_label": "差旅报销 Excel 模板",
|
||||
},
|
||||
)
|
||||
skill_expense_asset = AgentAsset(
|
||||
asset_type=AgentAssetType.SKILL.value,
|
||||
code="skill.expense.summary_lookup",
|
||||
name="报销汇总查询技能",
|
||||
description="根据时间、员工和部门汇总报销金额与单据数量。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "query", "summary"],
|
||||
owner="平台研发组",
|
||||
reviewer="陈硕",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"input_schema": ["time_range", "employee", "department"]},
|
||||
)
|
||||
skill_ar_asset = AgentAsset(
|
||||
@@ -262,8 +303,10 @@ class AgentFoundationService:
|
||||
scenario_json=["accounts_receivable", "query", "aging_summary"],
|
||||
owner="平台研发组",
|
||||
reviewer="陈硕",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"input_schema": ["customer", "aging_bucket", "status"]},
|
||||
)
|
||||
invoice_mcp_asset = AgentAsset(
|
||||
@@ -275,8 +318,10 @@ class AgentFoundationService:
|
||||
scenario_json=["expense", "invoice_validation"],
|
||||
owner="平台研发组",
|
||||
reviewer="周悦宁",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"endpoint": "mock://invoice/verify", "timeout_ms": 1200},
|
||||
)
|
||||
ledger_mcp_asset = AgentAsset(
|
||||
@@ -288,8 +333,10 @@ class AgentFoundationService:
|
||||
scenario_json=["expense", "accounts_receivable", "accounts_payable"],
|
||||
owner="平台研发组",
|
||||
reviewer="周悦宁",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"endpoint": "mock://ledger/snapshot", "timeout_ms": 1500},
|
||||
)
|
||||
task_asset = AgentAsset(
|
||||
@@ -301,8 +348,10 @@ class AgentFoundationService:
|
||||
scenario_json=["schedule", "risk_check"],
|
||||
owner="风控与审计部",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"cron": "0 9 * * *", "agent": AgentName.HERMES.value},
|
||||
)
|
||||
ar_summary_task = AgentAsset(
|
||||
@@ -314,8 +363,10 @@ class AgentFoundationService:
|
||||
scenario_json=["schedule", "accounts_receivable", "summary"],
|
||||
owner="风控与审计部",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"cron": "0 10 * * 1", "agent": AgentName.HERMES.value},
|
||||
)
|
||||
rule_digest_task = AgentAsset(
|
||||
@@ -327,8 +378,10 @@ class AgentFoundationService:
|
||||
scenario_json=["schedule", "rule_center", "review_digest"],
|
||||
owner="风控与审计部",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"cron": "0 18 * * *", "agent": AgentName.HERMES.value},
|
||||
)
|
||||
knowledge_index_task = AgentAsset(
|
||||
@@ -340,30 +393,39 @@ class AgentFoundationService:
|
||||
scenario_json=["schedule", "knowledge", "rule_center"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
working_version="v1.0.0",
|
||||
config_json={"cron": "0 0 * * *", "agent": AgentName.HERMES.value},
|
||||
)
|
||||
|
||||
self.db.add_all(
|
||||
[
|
||||
attachment_rule,
|
||||
scene_submission_rule,
|
||||
travel_policy_rule,
|
||||
skill_expense_asset,
|
||||
skill_ar_asset,
|
||||
invoice_mcp_asset,
|
||||
attachment_rule,
|
||||
scene_submission_rule,
|
||||
travel_policy_rule,
|
||||
company_travel_rule,
|
||||
skill_expense_asset,
|
||||
skill_ar_asset,
|
||||
invoice_mcp_asset,
|
||||
ledger_mcp_asset,
|
||||
task_asset,
|
||||
ar_summary_task,
|
||||
rule_digest_task,
|
||||
knowledge_index_task,
|
||||
]
|
||||
)
|
||||
self.db.flush()
|
||||
|
||||
self.db.add_all(
|
||||
[
|
||||
)
|
||||
self.db.flush()
|
||||
|
||||
company_travel_rule_meta = self._ensure_company_travel_rule_spreadsheet_seed(
|
||||
company_travel_rule,
|
||||
version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
actor_name="系统初始化",
|
||||
)
|
||||
|
||||
self.db.add_all(
|
||||
[
|
||||
AgentAssetVersion(
|
||||
asset=attachment_rule,
|
||||
version="v0.9.0",
|
||||
@@ -402,17 +464,29 @@ class AgentFoundationService:
|
||||
change_note="首版差旅制度执行规则,覆盖行程闭环与基础差标校验。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
content=self._travel_risk_control_standard_markdown(version="v1.1.0"),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充可执行规则块,供审核引擎直接消费差旅制度标准。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=skill_expense_asset,
|
||||
version="v1.0.0",
|
||||
AgentAssetVersion(
|
||||
asset=travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
content=self._travel_risk_control_standard_markdown(version="v1.1.0"),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充可执行规则块,供审核引擎直接消费差旅制度标准。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=company_travel_rule,
|
||||
version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
content=AgentAssetSpreadsheetManager.build_version_markdown(
|
||||
rule_name=company_travel_rule.name,
|
||||
version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
metadata=company_travel_rule_meta,
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="初始化差旅费报销 Excel 规则表。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=skill_expense_asset,
|
||||
version="v1.0.0",
|
||||
content=self._json_content(
|
||||
{
|
||||
"inputs": ["time_range", "employee", "department"],
|
||||
@@ -545,16 +619,24 @@ class AgentFoundationService:
|
||||
review_note="可作为报销场景统一审核标准正式执行。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
),
|
||||
AgentAssetReview(
|
||||
asset=travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="制度口径已确认,并已补充可执行配置供审核引擎读取。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
),
|
||||
]
|
||||
)
|
||||
AgentAssetReview(
|
||||
asset=travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="制度口径已确认,并已补充可执行配置供审核引擎读取。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
),
|
||||
AgentAssetReview(
|
||||
asset=company_travel_rule,
|
||||
version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="首版 Excel 规则表已确认,可作为财务规则使用。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def _seed_financial_records(self) -> None:
|
||||
if self.db.scalar(select(ExpenseClaim.id).limit(1)) is not None:
|
||||
@@ -913,9 +995,12 @@ class AgentFoundationService:
|
||||
scene_submission_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.expense.scene_submission_standard")
|
||||
)
|
||||
travel_policy_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.expense.travel_risk_control_standard")
|
||||
)
|
||||
travel_policy_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.expense.travel_risk_control_standard")
|
||||
)
|
||||
company_travel_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == COMPANY_TRAVEL_EXPENSE_RULE_CODE)
|
||||
)
|
||||
|
||||
if ATTACHMENT_RULE_ASSET_CODE not in existing_codes:
|
||||
attachment_rule = self._create_seed_asset(
|
||||
@@ -939,9 +1024,12 @@ class AgentFoundationService:
|
||||
},
|
||||
)
|
||||
|
||||
if attachment_rule is not None:
|
||||
attachment_rule.current_version = "v1.0.0"
|
||||
attachment_rule.status = AgentAssetStatus.REVIEW.value
|
||||
if attachment_rule is not None:
|
||||
if not str(attachment_rule.current_version or "").strip():
|
||||
attachment_rule.current_version = "v1.0.0"
|
||||
if not str(attachment_rule.working_version or "").strip():
|
||||
attachment_rule.working_version = attachment_rule.current_version
|
||||
attachment_rule.status = attachment_rule.status or AgentAssetStatus.REVIEW.value
|
||||
attachment_rule.description = "统一定义报销提交时的附件数量、票据类型和补件处理口径,作为上线前待审核规则。"
|
||||
attachment_rule.config_json = {
|
||||
"severity": "high",
|
||||
@@ -1002,9 +1090,14 @@ class AgentFoundationService:
|
||||
},
|
||||
)
|
||||
|
||||
if scene_submission_rule is not None:
|
||||
scene_submission_rule.current_version = "v1.0.0"
|
||||
scene_submission_rule.status = AgentAssetStatus.ACTIVE.value
|
||||
if scene_submission_rule is not None:
|
||||
if not str(scene_submission_rule.current_version or "").strip():
|
||||
scene_submission_rule.current_version = "v1.0.0"
|
||||
if not str(scene_submission_rule.working_version or "").strip():
|
||||
scene_submission_rule.working_version = scene_submission_rule.current_version
|
||||
if not str(scene_submission_rule.published_version or "").strip():
|
||||
scene_submission_rule.published_version = scene_submission_rule.current_version
|
||||
scene_submission_rule.status = scene_submission_rule.status or AgentAssetStatus.ACTIVE.value
|
||||
scene_submission_rule.description = "统一定义各报销场景的必填字段、附件类型要求和金额阈值。"
|
||||
scene_submission_rule.config_json = {
|
||||
"severity": "high",
|
||||
@@ -1053,9 +1146,14 @@ class AgentFoundationService:
|
||||
},
|
||||
)
|
||||
|
||||
if travel_policy_rule is not None:
|
||||
travel_policy_rule.current_version = "v1.1.0"
|
||||
travel_policy_rule.status = AgentAssetStatus.ACTIVE.value
|
||||
if travel_policy_rule is not None:
|
||||
if not str(travel_policy_rule.current_version or "").strip():
|
||||
travel_policy_rule.current_version = "v1.1.0"
|
||||
if not str(travel_policy_rule.working_version or "").strip():
|
||||
travel_policy_rule.working_version = travel_policy_rule.current_version
|
||||
if not str(travel_policy_rule.published_version or "").strip():
|
||||
travel_policy_rule.published_version = travel_policy_rule.current_version
|
||||
travel_policy_rule.status = travel_policy_rule.status or AgentAssetStatus.ACTIVE.value
|
||||
travel_policy_rule.config_json = {
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
@@ -1087,12 +1185,79 @@ class AgentFoundationService:
|
||||
version="v1.1.0",
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="制度口径已确认,并已补充可执行配置供审核引擎读取。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
if "skill.ar.aging_summary" not in existing_codes:
|
||||
asset = self._create_seed_asset(
|
||||
review_note="制度口径已确认,并已补充可执行配置供审核引擎读取。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
if COMPANY_TRAVEL_EXPENSE_RULE_CODE not in existing_codes:
|
||||
company_travel_rule = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code=COMPANY_TRAVEL_EXPENSE_RULE_CODE,
|
||||
name="公司差旅费报销规则",
|
||||
description="通过 Excel 明细表维护差旅费报销标准、票据要求和审批口径。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "travel_policy", "travel_standard"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
config_json={
|
||||
"severity": "medium",
|
||||
"enabled": True,
|
||||
"tag": "财务规则",
|
||||
"detail_mode": "spreadsheet",
|
||||
"rule_template_label": "差旅报销 Excel 模板",
|
||||
},
|
||||
)
|
||||
|
||||
if company_travel_rule is not None:
|
||||
if not str(company_travel_rule.current_version or "").strip():
|
||||
company_travel_rule.current_version = COMPANY_TRAVEL_RULE_VERSION
|
||||
if not str(company_travel_rule.working_version or "").strip():
|
||||
company_travel_rule.working_version = company_travel_rule.current_version
|
||||
if not str(company_travel_rule.published_version or "").strip():
|
||||
company_travel_rule.published_version = company_travel_rule.current_version
|
||||
if not str(company_travel_rule.status or "").strip():
|
||||
company_travel_rule.status = AgentAssetStatus.ACTIVE.value
|
||||
company_travel_rule.description = "通过 Excel 明细表维护差旅费报销标准、票据要求和审批口径。"
|
||||
company_travel_rule.config_json = {
|
||||
**(company_travel_rule.config_json or {}),
|
||||
"severity": "medium",
|
||||
"enabled": True,
|
||||
"tag": "财务规则",
|
||||
"detail_mode": "spreadsheet",
|
||||
"rule_library": FINANCE_RULES_LIBRARY,
|
||||
"rule_template_label": "差旅报销 Excel 模板",
|
||||
}
|
||||
company_travel_rule_meta = self._ensure_company_travel_rule_spreadsheet_seed(
|
||||
company_travel_rule,
|
||||
version=str(company_travel_rule.current_version or COMPANY_TRAVEL_RULE_VERSION),
|
||||
actor_name="系统初始化",
|
||||
)
|
||||
self._ensure_asset_version(
|
||||
company_travel_rule,
|
||||
version=str(company_travel_rule.current_version or COMPANY_TRAVEL_RULE_VERSION),
|
||||
content=AgentAssetSpreadsheetManager.build_version_markdown(
|
||||
rule_name=company_travel_rule.name,
|
||||
version=str(company_travel_rule.current_version or COMPANY_TRAVEL_RULE_VERSION),
|
||||
metadata=company_travel_rule_meta,
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="初始化差旅费报销 Excel 规则表。",
|
||||
created_by="系统初始化",
|
||||
)
|
||||
if str(company_travel_rule.current_version or "").strip() == COMPANY_TRAVEL_RULE_VERSION:
|
||||
self._ensure_asset_review(
|
||||
company_travel_rule,
|
||||
version=COMPANY_TRAVEL_RULE_VERSION,
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="首版 Excel 规则表已确认,可作为财务规则使用。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
if "skill.ar.aging_summary" not in existing_codes:
|
||||
asset = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.SKILL.value,
|
||||
code="skill.ar.aging_summary",
|
||||
name="应收账龄汇总技能",
|
||||
@@ -1207,10 +1372,10 @@ class AgentFoundationService:
|
||||
created_by="系统初始化",
|
||||
)
|
||||
|
||||
if "task.hermes.knowledge_index_sync" not in existing_codes:
|
||||
asset = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.TASK.value,
|
||||
code="task.hermes.knowledge_index_sync",
|
||||
if "task.hermes.knowledge_index_sync" not in existing_codes:
|
||||
asset = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.TASK.value,
|
||||
code="task.hermes.knowledge_index_sync",
|
||||
name="Hermes ??????",
|
||||
description="?????????? LightRAG ???????",
|
||||
domain=AgentAssetDomain.SYSTEM.value,
|
||||
@@ -1234,14 +1399,112 @@ class AgentFoundationService:
|
||||
}
|
||||
),
|
||||
content_type=AgentAssetContentType.JSON.value,
|
||||
change_note="初始化制度知识与规则草稿形成任务。",
|
||||
created_by="系统初始化",
|
||||
)
|
||||
|
||||
def _create_seed_asset(
|
||||
self,
|
||||
*,
|
||||
asset_type: str,
|
||||
change_note="初始化制度知识与规则草稿形成任务。",
|
||||
created_by="系统初始化",
|
||||
)
|
||||
|
||||
def _ensure_company_travel_rule_spreadsheet_seed(
|
||||
self,
|
||||
asset: AgentAsset,
|
||||
*,
|
||||
version: str,
|
||||
actor_name: str,
|
||||
):
|
||||
manager = AgentAssetSpreadsheetManager()
|
||||
manager.ensure_rule_library_dirs()
|
||||
live_document = manager.store_rule_library_spreadsheet(
|
||||
library=FINANCE_RULES_LIBRARY,
|
||||
file_name=COMPANY_TRAVEL_EXPENSE_RULE_FILENAME,
|
||||
content=self._read_or_build_company_travel_rule_file(manager),
|
||||
actor_name=actor_name,
|
||||
source="rule-library",
|
||||
)
|
||||
existing_document = (
|
||||
asset.config_json.get("rule_document")
|
||||
if isinstance(asset.config_json, dict)
|
||||
else None
|
||||
)
|
||||
storage_key = (
|
||||
str(existing_document.get("storage_key") or "").strip()
|
||||
if isinstance(existing_document, dict)
|
||||
else ""
|
||||
)
|
||||
if storage_key:
|
||||
try:
|
||||
existing_path = manager.resolve_storage_path(storage_key)
|
||||
except FileNotFoundError:
|
||||
existing_path = None
|
||||
if existing_path is not None and existing_path.exists():
|
||||
metadata = RuleSpreadsheetMeta(
|
||||
file_name=str(existing_document.get("file_name") or COMPANY_TRAVEL_EXPENSE_RULE_FILENAME),
|
||||
storage_key=storage_key,
|
||||
mime_type=str(existing_document.get("mime_type") or "").strip()
|
||||
or "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
size_bytes=int(existing_document.get("size_bytes") or existing_path.stat().st_size),
|
||||
checksum=hashlib.sha256(existing_path.read_bytes()).hexdigest(),
|
||||
updated_at=str(existing_document.get("updated_at") or "").strip()
|
||||
or datetime.now(UTC).isoformat(),
|
||||
updated_by=str(existing_document.get("updated_by") or actor_name).strip()
|
||||
or actor_name,
|
||||
source=str(existing_document.get("source") or "seed").strip() or "seed",
|
||||
)
|
||||
asset.config_json = {
|
||||
**(asset.config_json or {}),
|
||||
"detail_mode": "spreadsheet",
|
||||
"tag": "财务规则",
|
||||
"rule_library": FINANCE_RULES_LIBRARY,
|
||||
"rule_document": {
|
||||
**AgentAssetSpreadsheetManager.build_rule_document_config(
|
||||
live_document,
|
||||
asset_version=version,
|
||||
),
|
||||
"storage_key": live_document.storage_key,
|
||||
},
|
||||
}
|
||||
return metadata
|
||||
|
||||
live_content = manager.resolve_storage_path(live_document.storage_key).read_bytes()
|
||||
metadata = manager.store_spreadsheet(
|
||||
asset_id=asset.id,
|
||||
version=version,
|
||||
file_name=COMPANY_TRAVEL_EXPENSE_RULE_FILENAME,
|
||||
content=live_content,
|
||||
actor_name=actor_name,
|
||||
source="seed",
|
||||
)
|
||||
asset.config_json = {
|
||||
**(asset.config_json or {}),
|
||||
"detail_mode": "spreadsheet",
|
||||
"tag": "财务规则",
|
||||
"rule_library": FINANCE_RULES_LIBRARY,
|
||||
"rule_document": {
|
||||
**AgentAssetSpreadsheetManager.build_rule_document_config(
|
||||
live_document,
|
||||
asset_version=version,
|
||||
),
|
||||
"storage_key": live_document.storage_key,
|
||||
},
|
||||
}
|
||||
return metadata
|
||||
|
||||
@staticmethod
|
||||
def _read_or_build_company_travel_rule_file(
|
||||
manager: AgentAssetSpreadsheetManager,
|
||||
) -> bytes:
|
||||
live_key = (
|
||||
Path("rules")
|
||||
/ FINANCE_RULES_LIBRARY
|
||||
/ COMPANY_TRAVEL_EXPENSE_RULE_FILENAME
|
||||
).as_posix()
|
||||
live_path = manager.resolve_storage_path(live_key)
|
||||
if live_path.exists():
|
||||
return live_path.read_bytes()
|
||||
return AgentAssetSpreadsheetManager.build_blank_rule_workbook("差旅费报销规则")
|
||||
|
||||
def _create_seed_asset(
|
||||
self,
|
||||
*,
|
||||
asset_type: str,
|
||||
code: str,
|
||||
name: str,
|
||||
description: str,
|
||||
@@ -1262,10 +1525,12 @@ class AgentFoundationService:
|
||||
scenario_json=scenario_json,
|
||||
owner=owner,
|
||||
reviewer=reviewer,
|
||||
status=status,
|
||||
current_version=current_version,
|
||||
config_json=config_json,
|
||||
)
|
||||
status=status,
|
||||
current_version=current_version,
|
||||
published_version=current_version if status == AgentAssetStatus.ACTIVE.value else None,
|
||||
working_version=current_version,
|
||||
config_json=config_json,
|
||||
)
|
||||
self.db.add(asset)
|
||||
self.db.flush()
|
||||
return asset
|
||||
@@ -1331,7 +1596,7 @@ class AgentFoundationService:
|
||||
)
|
||||
)
|
||||
|
||||
def _remove_legacy_rule_assets(self) -> None:
|
||||
def _remove_legacy_rule_assets(self) -> None:
|
||||
assets = list(
|
||||
self.db.scalars(
|
||||
select(AgentAsset).where(AgentAsset.code.in_(LEGACY_RULE_CODES))
|
||||
@@ -1345,8 +1610,38 @@ class AgentFoundationService:
|
||||
select(AuditLog).where(AuditLog.resource_id.in_(LEGACY_RULE_CODES))
|
||||
).all()
|
||||
)
|
||||
for log in obsolete_logs:
|
||||
self.db.delete(log)
|
||||
for log in obsolete_logs:
|
||||
self.db.delete(log)
|
||||
|
||||
def _ensure_agent_asset_schema(self) -> None:
|
||||
bind = self.db.get_bind()
|
||||
inspector = inspect(bind)
|
||||
if "agent_assets" not in inspector.get_table_names():
|
||||
return
|
||||
|
||||
column_names = {column["name"] for column in inspector.get_columns("agent_assets")}
|
||||
migration_statements: list[str] = []
|
||||
if "published_version" not in column_names:
|
||||
migration_statements.append("ALTER TABLE agent_assets ADD COLUMN published_version VARCHAR(30)")
|
||||
if "working_version" not in column_names:
|
||||
migration_statements.append("ALTER TABLE agent_assets ADD COLUMN working_version VARCHAR(30)")
|
||||
|
||||
for statement in migration_statements:
|
||||
self.db.execute(text(statement))
|
||||
|
||||
self.db.execute(
|
||||
text(
|
||||
"UPDATE agent_assets "
|
||||
"SET working_version = COALESCE(working_version, current_version), "
|
||||
"published_version = CASE "
|
||||
"WHEN published_version IS NOT NULL THEN published_version "
|
||||
"WHEN status = 'active' THEN current_version "
|
||||
"ELSE published_version END"
|
||||
)
|
||||
)
|
||||
|
||||
if migration_statements:
|
||||
self.db.commit()
|
||||
|
||||
def _attachment_submission_requirement_markdown(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user