diff --git a/.claude-context/01-architecture.md b/.claude-context/01-architecture.md new file mode 100644 index 0000000..b27d436 --- /dev/null +++ b/.claude-context/01-architecture.md @@ -0,0 +1,130 @@ +# Architecture Reference + +_Last updated: 2026-09-23 (v1.5.9)_ + +## Data Flow + +``` +Disco DB (open jobs) + │ + ├─► ServiceTrackerService.BuildDashboard() + │ │ +Google Sheet CSV │ ├─► BuildDiscoTile() ─┐ + │ │ └─► BuildSheetTile() ─┤ + ▼ │ ▼ + GoogleSheetService List + (fetch → cache → parse) │ + ▼ + Bucket sort into: + - ReadyForReturn + - OnHold + - AwaitingVendor + - AwaitingParts + - Tiles (main grid) + │ + ▼ + DashboardViewModel + │ + ▼ + WebHandler → HTML string +``` + +## Persistence (JSON files) + +All under `App_Data/Plugins/Disco.Plugins.ServiceTracker/`: + +| File | Contents | +|------|---------| +| `config.json` | `ServiceTrackerConfig` — priorities, locations, statuses, technicians, sheet config | +| `tickets/` | One JSON file per Disco job (`{jobId}.json`) — `ServiceTicket` overrides, notes, changelog | +| `external_tickets/` | One JSON file per NTT sheet row (`{internalId}.json`) — same shape as ServiceTicket | +| `sheet_cache.csv` | Last fetched CSV from Google; invalidated by `RefreshMinutes` | + +## ID Generation for NTT Tickets + +Stable integer ID from djb2 hash of `timestamp|email|task|issue` (lowercased, trimmed), modulo 800000 + 100000 offset. Collisions resolved by incrementing. IDs are stable across refreshes as long as those four fields don't change. + +## Column Index Contract + +All Google Sheet column indices live in `GoogleSheetConfig`. Current mapping (updated 2026-09-23): + +``` +0 ColTimestamp — Timestamp +1 ColEmail — Email address +2 ColTask — What is the task/request? +3 ColIssue — Detailed Description +4 ColLocation — Location / Classroom +5 ColRequestedBy — Requested By +6 ColPriority — Priority Level +7 ColPreferredDate — Preferred Completion Date +8 ColCampus — Campus (location fallback if ColLocation empty) +9 ColAssignedTo — Assigned to +10 ColStatus — Status +11 — — Column 1 (junk, ignored) +12 — — Campus duplicate (ignored) +13 ColNotes — Notes +14 — — Category (ignored) +15 — — Resolution Confirmed (ignored) +ColCompletionDate = -1 (no dedicated column; disabled) +``` + +Set any index to `-1` to disable it. `SafeGet()` returns null for index < 0. + +## Sidebar Bucket Logic + +Buckets are evaluated in priority order and are mutually exclusive: + +```csharp +rfr = status contains "ready for return" OR "ready for pickup" +onHold = status contains "on hold" AND NOT in rfr +awaitingVendor= status contains "awaiting vendor" AND NOT in rfr, onHold +awaitingParts = status contains "awaiting parts" AND NOT in rfr, onHold, awaitingVendor +main = everything else +``` + +Status is checked against `StatusOverride ?? DiscoStatus`. + +## NTT Closed Ticket Suppression + +NTT tickets are skipped in `BuildDashboard` if their saved `StatusOverride` contains any of: +`resolved, dismissed, complete, closed, done, cancel, finished` + +This is independent of the sheet's own status column — it prevents jobs dismissed in the tracker from reappearing on every sheet refresh. + +## Location Resolution for NTT Tiles + +``` +ExternalTicket.Location (from ColLocation) + └─ empty? → try ColCampus + └─ still empty? → null + +MatchSheetLocation(raw): + 1. Exact ID match against cfg.Locations + 2. Exact Name match (case-insensitive) + 3. Substring match either direction + + Match found → use matched location (id/name/icon/colour) + No match → use DefaultLocation + store raw in RawSheetLocation + (displayed as italic 📍 pill on tile) + +Saved LocationId override (from external_tickets/) always wins over sheet text. +``` + +## HTML Generation Pattern + +No Razor. Every page is a method returning `string` from a `StringBuilder`: +- `BuildDashboardPage(DashboardViewModel)` — main dashboard +- `BuildDetailPage(Job, ServiceTicket, ...)` — job detail + edit +- `BuildConfigPage(ServiceTrackerConfig, ...)` — settings +- `TileHtml(DashboardTile)` — single tile card +- `SidebarPanel(title, color, items, url, showCollected)` — reusable sidebar section + +CSS is returned from `CSS()`, `DCSS()`, `CCSS()` string methods and inlined in `