80 lines
3.1 KiB
Swift
80 lines
3.1 KiB
Swift
// 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 (replaces areaServerId)
|
|
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?
|
|
|
|
// 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.severity = severity
|
|
self.issueDescription = description
|
|
self.issueStatus = "open"
|
|
self.photoLocalPathsJSON = "[]"
|
|
self.photoServerPathsJSON = "[]"
|
|
self.createdAt = Date()
|
|
self.syncStatus = "pending"
|
|
self.syncRetryCount = 0
|
|
self.syncErrorMessage = nil
|
|
}
|
|
|
|
var severityColor: String {
|
|
switch severity {
|
|
case "critical": return "red"
|
|
case "high": return "orange"
|
|
case "medium": return "yellow"
|
|
default: return "blue"
|
|
}
|
|
}
|
|
}
|