Files

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 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 <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).