Portal Delete run (Super Admin), lab codes #3/#4, Z23 pointer #45, T1017 #46, payer note, login security-image wait

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ManikandanandClaude Opus 5.5 committed 2026-10-29 12:30:38 +05:30
1 parent a8af44e673
commit fafa5285eb
21 files changed
+1509 -63

No files matched your search

+45 -5
View File
@@ -56,7 +56,7 @@ from ecw_bot.core.run_events import run_id_of # noqa: E402
from .repo import SqlResultsRepo # noqa: E402
from . import run_status # noqa: E402
from . import runs as run_view # noqa: E402
from .users import (Accounts, CAN_ADMIN, CAN_RUN, # noqa: E402
from .users import (Accounts, CAN_ADMIN, CAN_DELETE, CAN_RUN, # noqa: E402
EcareSignIn, IDLE_MINUTES, LOCK_AFTER, LOCK_MINUTES,
MANAGES, Sessions, SqlUserStore, UserError, is_ecare)
from .bot_runner import (AlreadyRunning, BotRunner, # noqa: E402
@@ -109,6 +109,13 @@ _DB_PROBLEMS = {
"42000": ("No permission to read the results table",
"The Windows login needs db_datareader on Charge_Validation."),
}
# The Portal's SQL login reads; deleting a run needs one more grant.
_DELETE_PROBLEMS = {
"42000": ("No permission to delete runs",
"The Portal's SQL login needs GRANT DELETE ON "
"dbo.CV_ChargeSummary — see the end of "
"tools/sql/create_tables.sql."),
}
_DB_DEFAULT = (DB_DOWN, "Check that SQL Server is running and that "
"RESULTS_DB_CONN in .env points at the "
"Charge_Validation database.")
@@ -211,7 +218,9 @@ def _filters(run_at, category, claim_no):
return when, category, claim
def _db(fn, *args, **kwargs):
def _db(fn, *args, problems=None, **kwargs):
"""`fn(*args, **kwargs)`; a database error becomes DbUnavailable, named
by its SQLSTATE (`problems` adds or overrides names for one call)."""
try:
return fn(*args, **kwargs)
except UserError:
@@ -219,7 +228,8 @@ def _db(fn, *args, **kwargs):
except Exception as exc:
state = exc.args[0] if exc.args and isinstance(exc.args[0], str) \
else ""
title, hint = _DB_PROBLEMS.get(state, _DB_DEFAULT)
title, hint = {**_DB_PROBLEMS, **(problems or {})}.get(state,
_DB_DEFAULT)
print(f"[WEB] {title}: {results_db.safe_error(exc)}")
raise DbUnavailable(title, hint) from exc
@@ -429,6 +439,7 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
"can_run": bool(can_start and user
and user["role"] in CAN_RUN),
"is_admin": bool(user and user["role"] in CAN_ADMIN),
"can_delete": bool(user and user["role"] in CAN_DELETE),
**context})
@app.get("/", response_class=HTMLResponse)
@@ -464,11 +475,17 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
@app.get("/day/{day}", response_class=HTMLResponse)
def day_page(request: Request, day: str, category: str = None,
claim_no: str = None, claim: str = None):
claim_no: str = None, claim: str = None, msg: str = ""):
when = _day(day)
_, cat, search = _filters(None, category, claim_no)
_, _, opened = _filters(None, None, claim)
rows = _db(repo.day_results, when)
runs = {}
for r in rows:
run = runs.setdefault(r["run_at"], {
"run_at": r["run_at"], "run_mode": r.get("run_mode"),
"started_by": r.get("started_by"), "claims": 0})
run["claims"] += 1
shown = [_for_panel(r) for r in rows
if (not cat or r.get("category") == cat)
and (not search or search in str(r.get("claim_no") or ""))]
@@ -477,7 +494,30 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
[_long_date(when), None]],
day=when, counts=_counts(rows), rows=shown,
category=cat or "", claim_no=search or "",
opened=opened or "")
opened=opened or "", runs=list(runs.values()),
message=msg[:200])
@app.post("/admin/runs/delete", response_class=HTMLResponse)
def run_delete(request: Request, run_at: str = Form(""),
token: str = Form("")):
"""One run's rows out of the results table — the Super Admin's
only (operator, 2026-10-28). Our own data, never eCW's; the run's
Logs files stay. Recorded in the Sign-in history."""
_check_token(request, token)
me = _need(request, CAN_DELETE)["username"]
try:
when = datetime.fromisoformat(run_at)
except ValueError:
raise BadFilter(f"Not a run time: {run_at}")
gone = _db(repo.delete_run, when, problems=_DELETE_PROBLEMS)
if gone:
_db(store.record, f"run {when:%m/%d/%Y} {_clock(when)}",
"run_deleted", by=me)
claims = f"{gone} claim{'' if gone == 1 else 's'}"
return RedirectResponse(
f"/day/{when.date().isoformat()}?" + urlencode(
{"msg": f"The {_clock(when)} run is deleted: {claims} "
f"deleted"}), status_code=303)
@app.get("/claim", response_class=HTMLResponse)
def claim_page(request: Request, claim_no: str = None):