Jul 30 - Update iPad - inspector can re-inspect and create follow-up
This commit is contained in:
@@ -429,6 +429,56 @@ actor APIClient {
|
||||
return result.scheduled
|
||||
}
|
||||
|
||||
/// Plan a follow-up re-inspection of `parentInspectionId` for `dueDate`
|
||||
/// (phase45) — the deferred twin of "Re-inspect Now" in history detail.
|
||||
///
|
||||
/// Only the parent and the date are sent: the server derives facility,
|
||||
/// template and assignee from the parent inspection, so a follow-up can
|
||||
/// only ever target the thing it is a follow-up of. The schedule it creates
|
||||
/// carries `parent_inspection_id`, which the inspection started from it
|
||||
/// inherits — that is what makes the eventual run a linked re-inspection.
|
||||
///
|
||||
/// Idempotent server-side: retrying re-dates the existing active follow-up
|
||||
/// for this parent instead of creating a second one.
|
||||
///
|
||||
/// `dueDate` must be formatted `yyyy-MM-dd`; the server rejects a past date.
|
||||
func createScheduledFollowUp(
|
||||
parentInspectionId: Int,
|
||||
dueDate: String,
|
||||
notes: String?
|
||||
) async throws -> APIScheduledInspection {
|
||||
var body: [String: Any] = [
|
||||
"parent_inspection_id": parentInspectionId,
|
||||
"due_date": dueDate,
|
||||
]
|
||||
// Raw snake_case body keys — JSONSerialization applies no key strategy
|
||||
// (rule 65).
|
||||
if let n = notes?.trimmingCharacters(in: .whitespacesAndNewlines), !n.isEmpty {
|
||||
body["notes"] = n
|
||||
}
|
||||
let result: APIScheduledFollowUpResponseData = try await post(
|
||||
"/api/v1/scheduled-inspections/follow-up", body: body
|
||||
)
|
||||
return result.scheduled
|
||||
}
|
||||
|
||||
// ── Follow-up Requests ────────────────────────────────────────────────
|
||||
|
||||
/// Inspections a director/admin has flagged as needing a follow-up.
|
||||
///
|
||||
/// Same endpoint and response shape as `fetchInspectionHistory`, but with
|
||||
/// the `follow_up_required` filter so the server returns the complete
|
||||
/// outstanding set rather than the recent page history shows. The limit is
|
||||
/// the endpoint's maximum for the same reason — this list drives actionable
|
||||
/// work, and a follow-up raised on a months-old inspection must still
|
||||
/// appear. Inspector-scoped server-side.
|
||||
func fetchFollowUpRequests() async throws -> [APIInspectionSummary] {
|
||||
let result: InspectionHistoryResponseData = try await request(
|
||||
"/api/v1/inspections?follow_up_required=true&limit=200"
|
||||
)
|
||||
return result.inspections
|
||||
}
|
||||
|
||||
// ── Issue Handler ("Handled By") ──────────────────────────────────────
|
||||
|
||||
/// Set who handles an issue. `details` carries any of the optional
|
||||
|
||||
@@ -658,6 +658,11 @@ struct APIScheduledInspection: Decodable, Identifiable, Sendable {
|
||||
let nextDueDate: String? // ISO date "YYYY-MM-DD"
|
||||
let isOverdue: Bool
|
||||
let notes: String?
|
||||
/// Set when this schedule is a planned follow-up of a specific inspection
|
||||
/// (phase45, "Schedule Follow-up"). Carried onto the inspection started
|
||||
/// from it so the run lands as a linked re-inspection. Nil for an ordinary
|
||||
/// schedule, and on servers older than phase45.
|
||||
let parentInspectionId: Int?
|
||||
|
||||
nonisolated init(from decoder: any Decoder) throws {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
@@ -672,10 +677,12 @@ struct APIScheduledInspection: Decodable, Identifiable, Sendable {
|
||||
nextDueDate = try? c.decode(String.self, forKey: .nextDueDate)
|
||||
isOverdue = (try? c.decode(Bool.self, forKey: .isOverdue)) ?? false
|
||||
notes = try? c.decode(String.self, forKey: .notes)
|
||||
parentInspectionId = try? c.decode(Int.self, forKey: .parentInspectionId)
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id, facilityId, facilityName, templateId, templateName
|
||||
case inspectorId, frequency, frequencyLabel, nextDueDate, isOverdue, notes
|
||||
case parentInspectionId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,6 +702,21 @@ struct APIScheduledInspectionsResponseData: Decodable, Sendable {
|
||||
private enum CodingKeys: String, CodingKey { case scheduled, total, limit, offset }
|
||||
}
|
||||
|
||||
/// Response of `POST /api/v1/scheduled-inspections/follow-up` (phase45).
|
||||
/// `created` is false when the server re-dated an existing follow-up for the
|
||||
/// same parent instead of adding a second one.
|
||||
struct APIScheduledFollowUpResponseData: Decodable, Sendable {
|
||||
let scheduled: APIScheduledInspection
|
||||
let created: Bool
|
||||
|
||||
nonisolated init(from decoder: any Decoder) throws {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
scheduled = try c.decode(APIScheduledInspection.self, forKey: .scheduled)
|
||||
created = (try? c.decode(Bool.self, forKey: .created)) ?? true
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey { case scheduled, created }
|
||||
}
|
||||
|
||||
// ── Dashboard Stats (Phase B) ─────────────────────────────────────────────────
|
||||
|
||||
struct APIDashboardStats: Decodable, Sendable {
|
||||
|
||||
Reference in New Issue
Block a user