PMFlow/backend/app/api/sstc_api/charters_detail_update.py

196 lines
7.0 KiB
Python

from __future__ import annotations
from .shared import *
from ...services.sstc.charter_data import _build_charter_summary_payload, _load_signal_rows, _select_source_signal, _serialize_alignment_record, _serialize_charter_signal, _serialize_charter_task, _serialize_source_signal
from ...services.sstc.entity_lookup import _ensure_charter, _ensure_project
from ...services.sstc.feishu_routing import _backfill_feishu_sender_names
from ...services.sstc.governance import _require_manager
from ...services.sstc.signal_core import _as_dict, _parse_node_id, _parse_project_id, _sync_pmflow_rag_async
router = APIRouter(tags=["sstc"])
@router.get("/projects/{project_id}/charters/{charter_id}")
async def get_charter_detail(
project_id: str,
charter_id: str,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
pid = _parse_project_id(project_id)
cid = _parse_node_id(charter_id, "charter_id")
tenant_id = int(current_user.tenant_id)
svc = SSTCService(db, tenant_id=tenant_id)
await _ensure_project(svc, pid)
charter = await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=cid)
content = _as_dict(charter.content)
signal_ids = [sid for sid in content.get("signal_ids", []) if isinstance(sid, int)]
signal_rows = await _load_signal_rows(
db,
tenant_id=tenant_id,
signal_ids=signal_ids,
)
if signal_rows:
await _backfill_feishu_sender_names(
db,
tenant_id=tenant_id,
signals=signal_rows,
)
task_result = await db.execute(
select(Task).where(
and_(
Task.truth_node_id == cid,
Task.project_id == pid,
Task.tenant_id == tenant_id,
)
)
)
tasks = list(task_result.scalars().all())
align_result = await db.execute(
select(AlignmentRecord).where(
and_(
AlignmentRecord.truth_node_id == cid,
AlignmentRecord.tenant_id == tenant_id,
)
)
)
alignments = list(align_result.scalars().all())
alignment_user_name_map: dict[int, str] = {}
alignment_user_ids = sorted({int(item.user_id) for item in alignments if item.user_id is not None})
if alignment_user_ids:
user_result = await db.execute(
select(User.id, User.name, User.username).where(
and_(
User.tenant_id == tenant_id,
User.id.in_(alignment_user_ids),
)
)
)
for user_id, name, username in user_result.all():
uid = int(user_id)
alignment_user_name_map[uid] = str(name or username or f"用户 {uid}")
source_signal = _select_source_signal(charter, signal_rows)
return success_response(
{
"id": str(charter.id),
"title": charter.title,
"content": content,
"signal_ids": signal_ids,
"signals": [_serialize_charter_signal(signal) for signal in signal_rows],
"source_signal": _serialize_source_signal(source_signal) if source_signal else None,
"tasks": [_serialize_charter_task(task) for task in tasks],
"alignment_records": [
_serialize_alignment_record(
record,
alignment_user_name_map.get(int(record.user_id)),
)
for record in alignments
],
"status": charter.status,
"version": charter.version,
}
)
@router.patch("/projects/{project_id}/charters/{charter_id}")
async def update_charter(
project_id: str,
charter_id: str,
body: CharterUpdateRequest,
permission_user: User = Depends(require_project_member_or_manager),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
_ = permission_user
_require_manager(current_user)
pid = _parse_project_id(project_id)
cid = _parse_node_id(charter_id, "charter_id")
tenant_id = int(current_user.tenant_id)
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
charter = await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=cid)
before = {
"title": charter.title,
"content": _as_dict(charter.content),
"version": charter.version,
"status": charter.status,
}
aligned = (charter.status or "").lower() in ALIGNED_STATUSES
if aligned and body.force_new_version:
new_node = TruthNode(
node_type="charter",
parent_id=int(charter.id),
project_id=pid,
created_by=int(current_user.id),
title=compact_title((body.title or charter.title), max_len=24),
content=body.content if body.content is not None else _as_dict(charter.content),
version=int(charter.version or 1) + 1,
status="draft",
tenant_id=tenant_id,
)
db.add(new_node)
await log_operation(
db,
tenant_id=tenant_id,
user_id=int(current_user.id),
project_id=pid,
entity_type="charter",
entity_id=str(charter.id),
action="new_version",
before_data=before,
after_data={"new_charter_id": "pending"},
)
await db.commit()
await db.refresh(new_node)
await AIPMOKernelService.invalidate_world_model_cache(tenant_id=tenant_id, project_id=pid)
_sync_pmflow_rag_async(
tenant_id=tenant_id,
record_type="charter",
record_id=int(new_node.id),
project_id=pid,
title=new_node.title,
content=_as_dict(new_node.content),
)
new_payload = _build_charter_summary_payload(new_node)
new_payload["new_version_created"] = True
return success_response(new_payload)
if body.title is not None:
charter.title = compact_title(body.title, max_len=24)
if body.content is not None:
charter.content = body.content
charter.status = "draft"
await log_operation(
db,
tenant_id=tenant_id,
user_id=int(current_user.id),
project_id=pid,
entity_type="charter",
entity_id=str(charter.id),
action="update",
before_data=before,
after_data={
"title": charter.title,
"content": _as_dict(charter.content),
"version": charter.version,
"status": charter.status,
},
)
await db.commit()
await db.refresh(charter)
await AIPMOKernelService.invalidate_world_model_cache(tenant_id=tenant_id, project_id=pid)
_sync_pmflow_rag_async(
tenant_id=tenant_id,
record_type="charter",
record_id=int(charter.id),
project_id=pid,
title=charter.title,
content=_as_dict(charter.content),
)
update_payload = _build_charter_summary_payload(charter)
update_payload["new_version_created"] = False
return success_response(update_payload)