5.4 KiB
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<DashboardTile>
(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:
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 dashboardBuildDetailPage(Job, ServiceTicket, ...)— job detail + editBuildConfigPage(ServiceTrackerConfig, ...)— settingsTileHtml(DashboardTile)— single tile cardSidebarPanel(title, color, items, url, showCollected)— reusable sidebar section
CSS is returned from CSS(), DCSS(), CCSS() string methods and inlined in <style> tags. No external stylesheets.
Cache Invalidation
DashboardCache watches file modification times on config.json, external_tickets.json, and the tickets directory. Any file change invalidates the cache. Cache key includes sortBy + filter string.
Priority SLA Logic
Each PriorityLevel has a SlaHours value. SLA deadline = job.OpenedDate + SlaHours. Warning fires at 75% elapsed. SlaHours = 0 means no SLA (used for "Scheduled" priority).