This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/src/dashboard/routes/work_orders.py

245 lines
8.3 KiB
Python

"""Work Orders routes — SQLite-backed submit/review/execute pipeline."""
import logging
import sqlite3
import uuid
from collections.abc import Generator
from contextlib import closing, contextmanager
from datetime import datetime
from pathlib import Path
from fastapi import APIRouter, Form, HTTPException, Request
from fastapi.responses import HTMLResponse
from config import settings
from dashboard.templating import templates
logger = logging.getLogger(__name__)
router = APIRouter(tags=["work-orders"])
DB_PATH = Path(settings.repo_root) / "data" / "work_orders.db"
PRIORITIES = ["low", "medium", "high", "critical"]
CATEGORIES = ["bug", "feature", "suggestion", "maintenance", "security"]
VALID_STATUSES = {"submitted", "triaged", "approved", "in_progress", "completed", "rejected"}
@contextmanager
def _get_db() -> Generator[sqlite3.Connection, None, None]:
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
with closing(sqlite3.connect(str(DB_PATH))) as conn:
conn.row_factory = sqlite3.Row
conn.execute("""
CREATE TABLE IF NOT EXISTS work_orders (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT DEFAULT '',
priority TEXT DEFAULT 'medium',
category TEXT DEFAULT 'suggestion',
submitter TEXT DEFAULT 'dashboard',
related_files TEXT DEFAULT '',
status TEXT DEFAULT 'submitted',
result TEXT DEFAULT '',
rejection_reason TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now')),
completed_at TEXT
)
""")
conn.commit()
yield conn
class _EnumLike:
def __init__(self, v: str):
self.value = v
def __str__(self):
return self.value
def __eq__(self, other):
if isinstance(other, str):
return self.value == other
return NotImplemented
class _WOView:
"""View object for Jinja template rendering."""
def __init__(self, row: dict):
self.id = row["id"]
self.title = row.get("title", "")
self.description = row.get("description", "")
self.priority = _EnumLike(row.get("priority", "medium"))
self.category = _EnumLike(row.get("category", "suggestion"))
self.submitter = row.get("submitter", "dashboard")
self.status = _EnumLike(row.get("status", "submitted"))
raw_files = row.get("related_files", "")
self.related_files = (
[f.strip() for f in raw_files.split(",") if f.strip()] if raw_files else []
)
self.result = row.get("result", "")
self.rejection_reason = row.get("rejection_reason", "")
self.created_at = row.get("created_at", "")
self.completed_at = row.get("completed_at")
self.execution_mode = None
def _row_to_dict(row: sqlite3.Row) -> dict:
return dict(row)
def _query_wos(db, statuses):
placeholders = ",".join("?" for _ in statuses)
return [
_WOView(_row_to_dict(r))
for r in db.execute(
f"SELECT * FROM work_orders WHERE status IN ({placeholders}) ORDER BY created_at DESC",
statuses,
).fetchall()
]
# ---------------------------------------------------------------------------
# Page route
# ---------------------------------------------------------------------------
@router.get("/work-orders/queue", response_class=HTMLResponse)
async def work_orders_page(request: Request):
with _get_db() as db:
pending = _query_wos(db, ["submitted", "triaged"])
active = _query_wos(db, ["approved", "in_progress"])
completed = _query_wos(db, ["completed"])
rejected = _query_wos(db, ["rejected"])
return templates.TemplateResponse(
request,
"work_orders.html",
{
"pending_count": len(pending),
"pending": pending,
"active": active,
"completed": completed,
"rejected": rejected,
"priorities": PRIORITIES,
"categories": CATEGORIES,
},
)
# ---------------------------------------------------------------------------
# Form submit
# ---------------------------------------------------------------------------
@router.post("/work-orders/submit", response_class=HTMLResponse)
async def submit_work_order(
request: Request,
title: str = Form(...),
description: str = Form(""),
priority: str = Form("medium"),
category: str = Form("suggestion"),
submitter: str = Form("dashboard"),
related_files: str = Form(""),
):
wo_id = str(uuid.uuid4())
now = datetime.utcnow().isoformat()
priority = priority if priority in PRIORITIES else "medium"
category = category if category in CATEGORIES else "suggestion"
with _get_db() as db:
db.execute(
"INSERT INTO work_orders (id, title, description, priority, category, submitter, related_files, created_at) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(wo_id, title, description, priority, category, submitter, related_files, now),
)
db.commit()
row = db.execute("SELECT * FROM work_orders WHERE id=?", (wo_id,)).fetchone()
wo = _WOView(_row_to_dict(row))
return templates.TemplateResponse(request, "partials/work_order_card.html", {"wo": wo})
# ---------------------------------------------------------------------------
# HTMX partials
# ---------------------------------------------------------------------------
@router.get("/work-orders/queue/pending", response_class=HTMLResponse)
async def pending_partial(request: Request):
with _get_db() as db:
wos = _query_wos(db, ["submitted", "triaged"])
if not wos:
return HTMLResponse(
'<div style="color: var(--text-muted); font-size: 0.8rem; padding: 12px 0;">'
"No pending work orders.</div>"
)
parts = []
for wo in wos:
parts.append(
templates.TemplateResponse(
request, "partials/work_order_card.html", {"wo": wo}
).body.decode()
)
return HTMLResponse("".join(parts))
@router.get("/work-orders/queue/active", response_class=HTMLResponse)
async def active_partial(request: Request):
with _get_db() as db:
wos = _query_wos(db, ["approved", "in_progress"])
if not wos:
return HTMLResponse(
'<div style="color: var(--text-muted); font-size: 0.8rem; padding: 12px 0;">'
"No work orders currently in progress.</div>"
)
parts = []
for wo in wos:
parts.append(
templates.TemplateResponse(
request, "partials/work_order_card.html", {"wo": wo}
).body.decode()
)
return HTMLResponse("".join(parts))
# ---------------------------------------------------------------------------
# Action endpoints
# ---------------------------------------------------------------------------
async def _update_status(request: Request, wo_id: str, new_status: str, **extra):
completed_at = (
datetime.utcnow().isoformat() if new_status in ("completed", "rejected") else None
)
with _get_db() as db:
sets = ["status=?", "completed_at=COALESCE(?, completed_at)"]
vals = [new_status, completed_at]
for col, val in extra.items():
sets.append(f"{col}=?")
vals.append(val)
vals.append(wo_id)
db.execute(f"UPDATE work_orders SET {', '.join(sets)} WHERE id=?", vals)
db.commit()
row = db.execute("SELECT * FROM work_orders WHERE id=?", (wo_id,)).fetchone()
if not row:
raise HTTPException(404, "Work order not found")
wo = _WOView(_row_to_dict(row))
return templates.TemplateResponse(request, "partials/work_order_card.html", {"wo": wo})
@router.post("/work-orders/{wo_id}/approve", response_class=HTMLResponse)
async def approve_wo(request: Request, wo_id: str):
return await _update_status(request, wo_id, "approved")
@router.post("/work-orders/{wo_id}/reject", response_class=HTMLResponse)
async def reject_wo(request: Request, wo_id: str):
return await _update_status(request, wo_id, "rejected")
@router.post("/work-orders/{wo_id}/execute", response_class=HTMLResponse)
async def execute_wo(request: Request, wo_id: str):
return await _update_status(request, wo_id, "in_progress")