Import ClientFlow production v4928.1.5.132.4

This commit is contained in:
plx
2026-07-29 13:11:01 +00:00
parent 6445044ac6
commit 261d342057
405 changed files with 48373 additions and 1401 deletions

View File

@@ -7,20 +7,22 @@ from __future__ import annotations
from hmac import compare_digest
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi import APIRouter, Body, Depends, HTTPException, Request
from sqlalchemy import text
from app.config import settings
from app.config import is_production_like_env, settings
from app.db import engine
def require_internal_access(request: Request) -> None:
expected = (settings.clientflow_admin_token or "").strip()
if not expected:
if is_production_like_env():
raise HTTPException(status_code=503, detail="internal api auth not configured")
return
received = (
request.headers.get("X-ClientFlow-Admin-Token")
or request.cookies.get("clientflow_admin_token")
or request.query_params.get("admin_token")
or (request.query_params.get("admin_token") if not is_production_like_env() else None)
or ""
).strip()
if not received or not compare_digest(received, expected):
@@ -41,6 +43,38 @@ def internal_health() -> dict:
}
@router.post("/tasks/{task_id}/reply-draft")
def internal_task_reply_draft(task_id: str, payload: dict | None = Body(default=None)) -> dict:
"""Generate a safe editable reply draft for a task.
Internal API counterpart of the admin UI button. It persists a draft in
message_drafts, returns the suggested message and never sends it.
"""
payload = payload or {}
try:
from app.reply_assistant_service import generate_reply_draft
state = generate_reply_draft(
task_id,
template_code=str(payload.get("template_code") or "").strip() or None,
selected_document_ids=payload.get("selected_document_ids") or [],
persist=True,
)
return {
"draft_id": state.get("draft_id"),
"task_id": task_id,
"template_code": (state.get("template") or {}).get("code"),
"message_body": state.get("message_body"),
"blockers": state.get("blockers") or [],
"warnings": state.get("warnings") or [],
"email_agent": state.get("email_agent") or {},
"intent_gate": state.get("intent_gate") or {},
"business_knowledge": state.get("business_knowledge") or {},
}
except Exception as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
@router.get("/outbox/summary")
def outbox_summary() -> dict:
with engine.begin() as conn:
@@ -98,3 +132,10 @@ def communications_summary() -> dict:
def communications_recent(limit: int = 20) -> dict:
from app.communication_service import list_communications
return {"items": list_communications(limit=limit)}
@router.get("/forecast")
@router.get("/revenue-forecast")
def revenue_forecast(limit: int = 1000, month: str | None = None, metric: str = "invoiced") -> dict:
from app.revenue_forecast_service import get_revenue_forecast
return get_revenue_forecast(limit=limit, month=month, metric=metric)