// Models/LocalIssue.swift // ----------------------- // SwiftData model for issues flagged during an offline inspection. import Foundation import SwiftData @Model final class LocalIssue { @Attribute(.unique) var localId: String var serverId: Int? var inspectionLocalId: String // references LocalInspection.localId var facilityServerId: Int // facility this issue belongs to /// Server ID of the area this issue was flagged in. Set when flagged during /// an inspection that has an area selected. Nil for standalone issues. var areaServerId: Int? var severity: String // "low" | "medium" | "high" | "critical" var issueDescription: String var issueStatus: String = "open" // server status: "open" | "in_progress" | "resolved" | "pending_verification" /// JSON-encoded array of absolute local file paths, e.g. ["/var/.../photo1.jpg", ...] var photoLocalPathsJSON: String = "[]" /// JSON-encoded array of server paths after upload, e.g. ["uploads/issue_photos/abc.jpg", ...] var photoServerPathsJSON: String = "[]" /// Decoded local photo paths (up to 5) var photoLocalPaths: [String] { get { (try? JSONDecoder().decode([String].self, from: Data(photoLocalPathsJSON.utf8))) ?? [] } set { photoLocalPathsJSON = (try? String(data: JSONEncoder().encode(newValue), encoding: .utf8)) ?? "[]" } } /// Decoded server photo paths var photoServerPaths: [String] { get { (try? JSONDecoder().decode([String].self, from: Data(photoServerPathsJSON.utf8))) ?? [] } set { photoServerPathsJSON = (try? String(data: JSONEncoder().encode(newValue), encoding: .utf8)) ?? "[]" } } var createdAt: Date var syncStatus: String // "pending" | "synced" | "failed" var syncRetryCount: Int var syncErrorMessage: String? // ── Phase A additions — persisted from server response ──────────────── // All new String?/Date? fields default to nil; SwiftData lightweight migration // supports nil-default optional properties without a migration plan. /// Facility display name cached from the server response. Used when the /// local facility reference cache has been cleared (Settings → Clear Cache). var facilityNameCache: String? /// Server-side reported_at timestamp. More accurate than createdAt for /// server-pulled issues because createdAt falls back to device time when /// the issue was created offline. var serverReportedAt: Date? /// Resolution notes added by web staff after fixing the issue. var resultNotes: String? /// Timestamp when a director/admin verified the fix. var verifiedAt: Date? /// Note left by the verifier. var verificationNote: String? /// Display name of the user who originally reported this issue. var reportedByName: String? /// Name of the area this issue was flagged in (e.g. "Main Lobby"). /// Set from server response; nil for standalone issues without area context. var areaNameCache: String? /// Display name of the user currently assigned to this issue. /// Nil when unassigned. Updated on every pullAssignedIssues(). var assignedToName: String? // Explicit inverse declared so SwiftData has an unambiguous relationship // graph at schema-build time. Without it the relationship is implicit, // which can cause migration warnings or incorrect cascade behaviour on some // SwiftData versions. The deleteRule is .nullify (default) — deleting the // parent inspection cascades via LocalInspection.localIssues; this side // only nullifies the back-pointer. @Relationship(deleteRule: .nullify, inverse: \LocalInspection.localIssues) var inspection: LocalInspection? init( inspectionLocalId: String, facilityServerId: Int, severity: String, description: String ) { self.localId = UUID().uuidString self.serverId = nil self.inspectionLocalId = inspectionLocalId self.facilityServerId = facilityServerId self.areaServerId = nil self.severity = severity self.issueDescription = description self.issueStatus = "open" self.photoLocalPathsJSON = "[]" self.photoServerPathsJSON = "[]" self.createdAt = Date() self.syncStatus = "pending" self.syncRetryCount = 0 self.syncErrorMessage = nil // Phase A fields — nil by default self.facilityNameCache = nil self.serverReportedAt = nil self.resultNotes = nil self.verifiedAt = nil self.verificationNote = nil self.reportedByName = nil self.areaNameCache = nil self.assignedToName = nil } var severityColor: String { switch severity { case "critical": return "red" case "high": return "orange" case "medium": return "yellow" default: return "blue" } } }