Jul 13 - Update codes to catch up with the web app updates: scheduled inspection and issue's handler

This commit is contained in:
Nguyen Ngo
2026-07-13 14:03:41 -04:00
parent c05f0029fb
commit 20f9a99646
22 changed files with 5800 additions and 18 deletions
+78
View File
@@ -60,6 +60,14 @@ class SyncManager: ObservableObject {
return f
}()
/// Parses server date-only strings ("YYYY-MM-DD"), e.g. scheduled due dates.
nonisolated static let dateOnlyFormatter: DateFormatter = {
let f = DateFormatter()
f.locale = Locale(identifier: "en_US_POSIX")
f.dateFormat = "yyyy-MM-dd"
return f
}()
static let shared = SyncManager()
private init() {}
@@ -237,6 +245,7 @@ class SyncManager: ObservableObject {
await processIssueQueue(context: context)
await pullReferenceData()
await pullAssignedIssues(context: context)
await pullScheduledInspections(context: context)
// Poll notifications immediately on every sync rather than waiting
// for the 60-second timer ensures the inspector sees assignments
@@ -730,6 +739,15 @@ class SyncManager: ObservableObject {
existing.reportedByName = api.reportedByName
existing.areaNameCache = api.areaName
existing.assignedToName = api.assignedToName
// Handler ("Handled By", phase35)
existing.handlerType = api.handlerType
existing.handlerLabel = api.handlerLabel
existing.facilityHandlerName = api.facilityHandlerName
existing.facilityHandlerContact = api.facilityHandlerContact
existing.facilityHandlerNotes = api.facilityHandlerNotes
existing.vendorName = api.vendorName
existing.vendorContact = api.vendorContact
existing.vendorNotes = api.vendorNotes
if let vts = api.verifiedAt,
let date = Self.isoFormatter.date(from: vts) {
existing.verifiedAt = date
@@ -762,6 +780,15 @@ class SyncManager: ObservableObject {
local.reportedByName = api.reportedByName
local.areaNameCache = api.areaName
local.assignedToName = api.assignedToName
// Handler ("Handled By", phase35)
local.handlerType = api.handlerType
local.handlerLabel = api.handlerLabel
local.facilityHandlerName = api.facilityHandlerName
local.facilityHandlerContact = api.facilityHandlerContact
local.facilityHandlerNotes = api.facilityHandlerNotes
local.vendorName = api.vendorName
local.vendorContact = api.vendorContact
local.vendorNotes = api.vendorNotes
if let vts = api.verifiedAt,
let date = Self.isoFormatter.date(from: vts) {
local.verifiedAt = date
@@ -808,6 +835,57 @@ class SyncManager: ObservableObject {
}
}
// Scheduled Inspections (phase36)
// Read-only pull of planned/recurring assignments for the Dashboard and
// My Inspections "Scheduled" section. Upsert by serverId, then delete rows
// the server no longer returns (schedule fulfilled, deactivated, or
// reassigned to another inspector). Best-effort never blocks the pipeline.
func pullScheduledInspections(context: ModelContext) async {
guard isOnline, AuthManager.shared.isAuthenticated else { return }
do {
let apiRows = try await APIClient.shared.fetchScheduledInspections()
// Fetch-all + filter/map in Swift no #Predicate (CLAUDE.md rule 3).
let allLocal = (try? context.fetch(FetchDescriptor<LocalScheduledInspection>())) ?? []
var byServerId: [Int: LocalScheduledInspection] = [:]
for row in allLocal { byServerId[row.serverId] = row }
for api in apiRows {
let row = byServerId[api.id] ?? {
let r = LocalScheduledInspection(serverId: api.id)
context.insert(r)
return r
}()
row.facilityServerId = api.facilityId
row.facilityName = api.facilityName ?? ""
row.templateServerId = api.templateId
row.templateName = api.templateName ?? ""
row.inspectorId = api.inspectorId
row.frequency = api.frequency
row.frequencyLabel = api.frequencyLabel ?? ""
row.dueDateString = api.nextDueDate ?? ""
row.nextDue = api.nextDueDate.flatMap { Self.dateOnlyFormatter.date(from: $0) }
row.isOverdue = api.isOverdue
row.notes = api.notes
row.updatedAt = Date()
}
// Delete rows the server no longer returns.
let returnedIds = Set(apiRows.map { $0.id })
for row in allLocal where !returnedIds.contains(row.serverId) {
context.delete(row)
}
try? context.save()
} catch APIError.notAuthenticated {
// Let AuthManager handle session expiry
} catch {
// Non-fatal stale scheduled rows stay visible until next pull
}
}
// Dashboard Stats
// Best-effort fetch a network failure silently leaves dashboardStats nil
// so the UI falls back to a placeholder card. Never blocks the sync pipeline.