Skip to main content

MC #106259 — Bilko Admin Portal Contract Fix

MC #106259 — fix for 4 contract defects found by Angie Jones' platform_admin UAT catalog (MC #106258). Backend DTOs, frontend types/guards, and one missing route were brought back in sync so the admin portal stops crashing and stops showing wrong data.

Defects fixed

#SeverityDefectFix
1 H GET /admin/dashboard returned 404 — no handler existed anywhere server-side despite being called on first admin page load and documented in the OpenAPI spec. Likely the first thing CEO saw fail on /admin. Implemented in AdminPortalRoutes.kt (get("/dashboard") inside the existing route("/admin") block, same requirePlatformAdmin() gate as every other admin route). AdminOrgService.getDashboardStats() added with real DB aggregates: totalOrgs (COUNT non-deleted orgs), activeTrials (BASIC plan + trial not expired), recentAudit (audit_log LEFT JOIN users, DESC limit 10), mrr (explicit 0.0, see limitation below).
2 H /admin/orgs/{id} detail page crashed on every org. Backend OrgDetail DTO never set mrr, so Jackson omitted the key entirely; frontend called Intl.NumberFormat().format(org.mrr)TypeError: Cannot convert undefined to a number, tripping the global Next.js error boundary. This is almost certainly the "admin stranice puca" CEO reported. OrgDetail DTO gained mrr: Double (always explicit, never omitted), plus vatMethod/trialStatus. Frontend defense-in-depth: all three MRR render sites (orgs/[id]/page.tsx, orgs/page.tsx, admin dashboard page.tsx) now guard == null before formatting, not just the one crash site.
3 M OrgListItem (the /admin/orgs list) was missing vatMethod, trialStatus, userCount, mrr even though the frontend TS interface declared them as required. Effect: VAT Method column always "—", Users column always blank, and every org — trial or paid — rendered with the green "paid" badge since trialBadge(undefined) silently fell through. OrgListItem gained all four fields. trialStatus derived via new resolveTrialStatus(planTier, trialEndsAt) (read-model projection only — does not touch TrialGatePlugin/TrialService enforcement). userCount is one batched query per page (grouped/counted in one round-trip), not N+1 per row.
4 L Pagination/total count silently broken. Backend has always returned nested {data, meta:{total,page,perPage,totalPages}}; the frontend AdminOrgsListResponse/AdminOrgUsersResponse TS types declared a flat shape ({data,total,...}). Result: header count never rendered, and pagination controls would silently never appear once org count exceeded the 50-per-page limit (untriggered today at 9 orgs, but a live landmine). Frontend types changed to nest under meta: AdminOrgsPaginationMeta (chosen over flattening the backend, to avoid touching other PaginatedResponse consumers). Call sites in orgs/page.tsx updated to read data.meta.total / data.meta.totalPages.

Branch / commit

  • Repo: Bilko (canonical remote azdo)
  • Branch: feat/106259-admin-contract-fix, created off azdo/main at cb3746d6
  • Commit: 5513d0e6339864b31c2d3507ca0219ede5b052ba — pushed to azdo, verified on remote via git ls-remote
  • 8 files changed: AdminPortalRoutes.kt, AdminOrgService.kt, AdminPortalRoutesHttpIntegrationTest.kt (new tests T16–T19b), orgs/[id]/page.tsx, orgs/page.tsx, admin dashboard page.tsx, lib/api.ts, and a new frontend test file admin-contract-106259.test.tsx

Test results

  • Backend: AdminPortalRoutesHttpIntegrationTest23/23 passed (9 new tests T16–T19b, one per fix + auth-gate coverage, plus 14 pre-existing T1–T15 unmodified, zero regressions). Sibling admin suites unmodified and green: AdminPortalRoutesP2Test 10/10, AdminRoutesHttpIntegrationTest 6/6.
  • Frontend: new admin-contract-106259.test.tsx9/9 passed (React Testing Library, real rendered component tree — MRR undefined/null guards, real-number formatting, vatMethod/trialStatus/userCount render, nested meta.total/meta.totalPages reads, pagination-controls visibility). Full production next build succeeds (65/65 routes), tsc --noEmit clean, pre-push turbo run type-check across all 12 workspace packages green.
  • Independent verification (Angie Jones / Proveo) — read code directly in the isolated worktree at commit 5513d0e6 (did not trust the builder's self-report), re-ran backend integration tests and frontend Vitest herself, and diffed the commit against base to confirm scope boundaries. Verdict: PASS on all 4 items. Confirmed independently via her own grep that no aggregate MRR computation exists anywhere in the codebase, and via direct diff that zero auth files were touched.

Known limitation

mrr is returned as an explicit 0.0 everywhere (dashboard, org detail, org list) — not fabricated, but not real. There is no Stripe MRR aggregation anywhere in the Bilko codebase today: stripeSubscriptionId is stored per-org but only ever resolved for trial-gate checks and billing/webhook flows, never rolled up into an aggregate revenue figure. Wiring a real MRR source (either a live Stripe API call per org, or a synced mrr_amount column updated via webhook) is separate, larger follow-on work. Flagged in code comments on the 3 DTOs (OrgDetail, OrgListItem, dashboard stats) for whoever picks that up next.

Deliberately not touched

  • AdminAuthPlugin.kt, JwtService.kt, Authentication.kt, and the bilko-jwt verifier — confirmed untouched by direct diff (git diff cb3746d6 5513d0e6 --name-only, zero overlap). Angie's original catalog established that the /admin/orgs 401 CEO/UAT saw was a stale/expired access-token test artefact, not a server-side auth defect — nothing in the auth stack needed fixing.
  • /admin/users 405 — by design, only a POST handler exists; no frontend page calls GET /admin/users. Not a bug.
  • "Report error" support-ticket button — confirmed working live during the original catalog investigation (real POST /support/tickets → 201).
  • Flyway V146 warning — not reproducible; DB and deployed image agreed on schema version at investigation time.

Follow-on: deploy is separate

This ticket covers code + test correctness only (worktree/local verification). PR → CI → merge to azdo/main → stage/demo promotion is a separate follow-on step, not yet done as of this page. Do not treat this fix as live on stage/demo until that pipeline runs and is verified (curl/browser check on the actual environment, per ZAKON PI2).

Evidence

  • Defect catalog (spec): ~/system/evidence/106258/admin-defect-catalog.md
  • Build evidence: ~/system/evidence/106259/build-evidence.md
  • Independent verify (PASS): ~/system/evidence/106259/angie-verify-2026-07-24.md