104 lines
4.0 KiB
Swift
104 lines
4.0 KiB
Swift
// Models/LocalScheduledInspection.swift
|
|
// --------------------------------------
|
|
// SwiftData model for planned/recurring inspection assignments (phase36).
|
|
//
|
|
// Read-only reference data pulled from the server (GET /api/v1/scheduled-inspections)
|
|
// and refreshed by SyncManager.pullScheduledInspections() — never created or
|
|
// mutated on device. Surfaced in the "Scheduled" section on the Dashboard and
|
|
// My Inspections. Tapping "Start" opens the normal new-inspection flow with the
|
|
// facility + template preselected; the schedule lifecycle (fulfil / roll-forward)
|
|
// stays server-driven.
|
|
//
|
|
// Follows the same pattern as LocalFacility / LocalArea: a `.unique` serverId
|
|
// WITHOUT an inline default (a default on the unique key breaks @Model's
|
|
// PersistentModel conformance) and a full init(from:)/update(from:) pair.
|
|
|
|
import Foundation
|
|
import SwiftData
|
|
|
|
@Model
|
|
final class LocalScheduledInspection {
|
|
|
|
/// Server ID of the ScheduledInspection row — stable unique identity.
|
|
@Attribute(.unique) var serverId: Int
|
|
|
|
var facilityServerId: Int
|
|
var facilityName: String
|
|
var templateServerId: Int
|
|
var templateName: String
|
|
var inspectorId: Int?
|
|
|
|
var frequency: String // once | daily | weekly | monthly
|
|
var frequencyLabel: String
|
|
|
|
/// Raw server date string "YYYY-MM-DD" — sortable (ISO strings sort
|
|
/// chronologically) and the source for the parsed `nextDue`.
|
|
var dueDateString: String
|
|
|
|
var isOverdue: Bool
|
|
var notes: String?
|
|
|
|
/// Manager-authored instructions for this occurrence, normalised.
|
|
///
|
|
/// The wire field, the server model attribute and the DB column are all
|
|
/// still `notes` — only the user-facing wording changed to "Instructions"
|
|
/// (web form label + both iPad surfaces). Renaming the stored property would
|
|
/// break `init(from:)`/`update(from:)` against `APIScheduledInspection.notes`
|
|
/// for no benefit, so the rename lives here.
|
|
///
|
|
/// Returns nil for nil, empty, or whitespace-only text so callers can guard
|
|
/// with a single `if let` instead of repeating the emptiness check.
|
|
/// Computed, not stored — no SwiftData schema change.
|
|
var instructions: String? {
|
|
guard let t = notes?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!t.isEmpty
|
|
else { return nil }
|
|
return t
|
|
}
|
|
|
|
/// Last time this row was refreshed from the server pull.
|
|
var updatedAt: Date
|
|
|
|
/// Parsed due date for display. Computed properties are not persisted by
|
|
/// SwiftData; sort on `dueDateString` (not this) in @Query.
|
|
var nextDue: Date? {
|
|
Self.dateOnlyFormatter.date(from: dueDateString)
|
|
}
|
|
|
|
private static let dateOnlyFormatter: DateFormatter = {
|
|
let f = DateFormatter()
|
|
f.locale = Locale(identifier: "en_US_POSIX")
|
|
f.dateFormat = "yyyy-MM-dd"
|
|
return f
|
|
}()
|
|
|
|
init(from api: APIScheduledInspection) {
|
|
self.serverId = api.id
|
|
self.facilityServerId = api.facilityId
|
|
self.facilityName = api.facilityName ?? ""
|
|
self.templateServerId = api.templateId
|
|
self.templateName = api.templateName ?? ""
|
|
self.inspectorId = api.inspectorId
|
|
self.frequency = api.frequency
|
|
self.frequencyLabel = api.frequencyLabel ?? ""
|
|
self.dueDateString = api.nextDueDate ?? ""
|
|
self.isOverdue = api.isOverdue
|
|
self.notes = api.notes
|
|
self.updatedAt = Date()
|
|
}
|
|
|
|
func update(from api: APIScheduledInspection) {
|
|
self.facilityServerId = api.facilityId
|
|
self.facilityName = api.facilityName ?? ""
|
|
self.templateServerId = api.templateId
|
|
self.templateName = api.templateName ?? ""
|
|
self.inspectorId = api.inspectorId
|
|
self.frequency = api.frequency
|
|
self.frequencyLabel = api.frequencyLabel ?? ""
|
|
self.dueDateString = api.nextDueDate ?? ""
|
|
self.isOverdue = api.isOverdue
|
|
self.notes = api.notes
|
|
self.updatedAt = Date()
|
|
}
|
|
}
|