Add task tool result bridge and evidence attachment workflow
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Form, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi import APIRouter, File, Form, Request, UploadFile
|
||||
from fastapi.responses import RedirectResponse, Response
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.db.common import CommonSessionLocal
|
||||
@@ -11,7 +11,13 @@ from app.core.templating import templates
|
||||
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
||||
from app.modules.core.iam.models import User
|
||||
from app.modules.core.rbac.permission_guard import require_permission
|
||||
from app.modules.services.task_tools import get_task_tool
|
||||
from app.modules.services.task_tools import build_task_tool_launch_url, get_task_tool
|
||||
from app.modules.services.task_tool_results import (
|
||||
get_task_tool_run,
|
||||
list_task_tool_runs,
|
||||
record_task_tool_result,
|
||||
start_task_tool_run,
|
||||
)
|
||||
from app.modules.services.execution import (
|
||||
TASK_COMMENT_TYPES,
|
||||
TASK_COMMENT_VISIBILITIES,
|
||||
@@ -445,6 +451,154 @@ def task_edit_page(request: Request, task_id: int):
|
||||
can_manage_fields=_can_bulk_manage_tasks(db, user),
|
||||
can_reassign=_can_assign_staff(db, user),
|
||||
task_tool=get_task_tool(getattr(task, "task_tool_code", "NONE")),
|
||||
tool_runs=list_task_tool_runs(db, tenant_id=tenant_id, task_id=task.id),
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/tool/start")
|
||||
def task_tool_start(request: Request, task_id: int, csrf_token: str = Form(...)):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user = get_current_user(request, db=db)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=303)
|
||||
tenant_id = _active_tenant_id(request, user)
|
||||
branch_id = _active_branch_id(request, user, db)
|
||||
task = get_task(
|
||||
db,
|
||||
tenant_id=tenant_id,
|
||||
task_id=task_id,
|
||||
branch_id=branch_id,
|
||||
assigned_to_user_id=_assigned_user_filter(db, user),
|
||||
partner_user_id=_partner_visibility_user_id(db, user),
|
||||
financial_year=_active_financial_year(request),
|
||||
)
|
||||
if not task:
|
||||
return RedirectResponse(url="/services/work-tracker", status_code=303)
|
||||
tool = get_task_tool(getattr(task, "task_tool_code", "NONE"))
|
||||
if tool.code == "NONE" or not tool.launch_url:
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task.id}/edit?tool_error=not_configured", status_code=303)
|
||||
run = start_task_tool_run(db, task=task, user_id=int(user.id))
|
||||
launch_url = build_task_tool_launch_url(
|
||||
tool,
|
||||
client_id=int(task.client_id),
|
||||
task_id=int(task.id),
|
||||
tool_run_id=int(run.id),
|
||||
)
|
||||
return RedirectResponse(url=launch_url, status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/tool-runs/{run_id}/result")
|
||||
def task_tool_result_submit(
|
||||
request: Request,
|
||||
run_id: int,
|
||||
status: str = Form("completed"),
|
||||
summary: str = Form(""),
|
||||
result_json: str = Form(""),
|
||||
clean_pass: str = Form(""),
|
||||
exception_count: str = Form(""),
|
||||
output_file: UploadFile | None = File(None),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user = get_current_user(request, db=db)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=303)
|
||||
tenant_id = _active_tenant_id(request, user)
|
||||
run = get_task_tool_run(db, tenant_id=tenant_id, run_id=run_id)
|
||||
if not run:
|
||||
return RedirectResponse(url="/services/work-tracker", status_code=303)
|
||||
branch_id = _active_branch_id(request, user, db)
|
||||
task = get_task(
|
||||
db,
|
||||
tenant_id=tenant_id,
|
||||
task_id=int(run.task_instance_id),
|
||||
branch_id=branch_id,
|
||||
assigned_to_user_id=_assigned_user_filter(db, user),
|
||||
partner_user_id=_partner_visibility_user_id(db, user),
|
||||
financial_year=_active_financial_year(request),
|
||||
)
|
||||
if not task:
|
||||
return _redirect_denied()
|
||||
|
||||
clean_value = None
|
||||
if clean_pass.strip().lower() in {"1", "true", "yes", "y", "on"}:
|
||||
clean_value = True
|
||||
elif clean_pass.strip().lower() in {"0", "false", "no", "n", "off"}:
|
||||
clean_value = False
|
||||
|
||||
exception_value = None
|
||||
if exception_count.strip():
|
||||
try:
|
||||
exception_value = max(0, int(exception_count))
|
||||
except ValueError:
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task.id}/edit?tool_error=invalid_exception_count", status_code=303)
|
||||
|
||||
file_bytes = None
|
||||
filename = None
|
||||
content_type = None
|
||||
if output_file is not None and getattr(output_file, "filename", None):
|
||||
file_bytes = output_file.file.read()
|
||||
filename = str(output_file.filename or "tool-output.bin")
|
||||
content_type = str(output_file.content_type or "application/octet-stream")
|
||||
|
||||
try:
|
||||
record_task_tool_result(
|
||||
db,
|
||||
run=run,
|
||||
status=status,
|
||||
summary=summary,
|
||||
result_payload=result_json,
|
||||
clean_pass=clean_value,
|
||||
exception_count=exception_value,
|
||||
output_filename=filename,
|
||||
output_content_type=content_type,
|
||||
output_bytes=file_bytes,
|
||||
completed_by_user_id=int(user.id),
|
||||
)
|
||||
except ValueError:
|
||||
db.rollback()
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task.id}/edit?tool_error=result_rejected", status_code=303)
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task.id}/edit?tool_saved=1", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/tool-runs/{run_id}/output")
|
||||
def task_tool_result_download(request: Request, run_id: int):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user = get_current_user(request, db=db)
|
||||
if not user:
|
||||
return RedirectResponse(url="/login", status_code=303)
|
||||
tenant_id = _active_tenant_id(request, user)
|
||||
run = get_task_tool_run(db, tenant_id=tenant_id, run_id=run_id)
|
||||
if not run or not run.output_bytes:
|
||||
return RedirectResponse(url="/services/work-tracker", status_code=303)
|
||||
branch_id = _active_branch_id(request, user, db)
|
||||
task = get_task(
|
||||
db,
|
||||
tenant_id=tenant_id,
|
||||
task_id=int(run.task_instance_id),
|
||||
branch_id=branch_id,
|
||||
assigned_to_user_id=_assigned_user_filter(db, user),
|
||||
partner_user_id=_partner_visibility_user_id(db, user),
|
||||
financial_year=_active_financial_year(request),
|
||||
)
|
||||
if not task:
|
||||
return _redirect_denied()
|
||||
filename = (run.output_filename or f"task-tool-run-{run.id}.bin").replace('"', "")
|
||||
return Response(
|
||||
content=bytes(run.output_bytes),
|
||||
media_type=run.output_content_type or "application/octet-stream",
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user