Jul 26 - Update scheduled inspection settings
This commit is contained in:
@@ -238,6 +238,11 @@ actor APIClient {
|
|||||||
if let score = inspection.overallScore { body["overall_score"] = score }
|
if let score = inspection.overallScore { body["overall_score"] = score }
|
||||||
if let areaId = inspection.areaServerId { body["area_id"] = areaId }
|
if let areaId = inspection.areaServerId { body["area_id"] = areaId }
|
||||||
if let parentId = inspection.parentServerId { body["parent_inspection_id"] = parentId }
|
if let parentId = inspection.parentServerId { body["parent_inspection_id"] = parentId }
|
||||||
|
// Links the submission back to the schedule it was started from so the
|
||||||
|
// server fulfils it (clears the banner) and badges it as "Scheduled".
|
||||||
|
if let schedId = inspection.scheduledInspectionServerId {
|
||||||
|
body["scheduled_inspection_id"] = schedId
|
||||||
|
}
|
||||||
if !inspection.inspectorNotes.isEmpty { body["notes"] = inspection.inspectorNotes }
|
if !inspection.inspectorNotes.isEmpty { body["notes"] = inspection.inspectorNotes }
|
||||||
if let lat = inspection.submitLatitude { body["submit_latitude"] = lat }
|
if let lat = inspection.submitLatitude { body["submit_latitude"] = lat }
|
||||||
if let lng = inspection.submitLongitude { body["submit_longitude"] = lng }
|
if let lng = inspection.submitLongitude { body["submit_longitude"] = lng }
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ PendingPhoto.self, SyncQueueEntry.self
|
|||||||
| `LocalFacility` | Read-only cached facility reference | `serverId` (`@Attribute(.unique)`), `name`, `projectId`, `projectName`, `areas` (cascade) |
|
| `LocalFacility` | Read-only cached facility reference | `serverId` (`@Attribute(.unique)`), `name`, `projectId`, `projectName`, `areas` (cascade) |
|
||||||
| `LocalArea` | Read-only cached area reference | `serverId`, `facilityServerId`, `name`, `areaType` |
|
| `LocalArea` | Read-only cached area reference | `serverId`, `facilityServerId`, `name`, `areaType` |
|
||||||
| `LocalTemplate` | Cached template + raw JSON schema | `serverId`, `formSchemaJSON`, `formSchema` (computed) |
|
| `LocalTemplate` | Cached template + raw JSON schema | `serverId`, `formSchemaJSON`, `formSchema` (computed) |
|
||||||
| `LocalInspection` | Inspector-authored inspection record | `localId` (UUID, unique), `serverId`, `status`, `syncStatus`, `formDataJSON`, `followUpRequired`, `parentLocalId`, `parentServerId`, `submitLatitude` (Double?), `submitLongitude` (Double?) |
|
| `LocalInspection` | Inspector-authored inspection record | `localId` (UUID, unique), `serverId`, `status`, `syncStatus`, `formDataJSON`, `followUpRequired`, `parentLocalId`, `parentServerId`, `scheduledInspectionServerId` (Int?, inline default — links the submission to the schedule it fulfils), `submitLatitude` (Double?), `submitLongitude` (Double?) |
|
||||||
| `LocalIssue` | Issue record | `localId` (UUID, unique), `serverId`, `inspectionLocalId` (`""` for standalone/server-pulled), `facilityServerId`, `severity`, `syncStatus`, `photoLocalPathsJSON`, `photoServerPathsJSON`, **handler fields** (`handlerType`, `handlerLabel`, `facilityHandler*`, `vendor*` — all optional, synced from server, inspector-editable) |
|
| `LocalIssue` | Issue record | `localId` (UUID, unique), `serverId`, `inspectionLocalId` (`""` for standalone/server-pulled), `facilityServerId`, `severity`, `syncStatus`, `photoLocalPathsJSON`, `photoServerPathsJSON`, **handler fields** (`handlerType`, `handlerLabel`, `facilityHandler*`, `vendor*` — all optional, synced from server, inspector-editable) |
|
||||||
| `LocalScheduledInspection` | Read-only cached scheduled/recurring assignment (phase36) | `serverId` (`@Attribute(.unique)`, **no default** — rule 63), `facilityServerId`, `facilityName`, `templateServerId`, `templateName`, `inspectorId`, `frequency`, `frequencyLabel`, `dueDateString` (sort key), `isOverdue`, `nextDue` (computed). Pulled by `pullScheduledInspections()`; `init(from:)`/`update(from:)` like `LocalFacility` |
|
| `LocalScheduledInspection` | Read-only cached scheduled/recurring assignment (phase36) | `serverId` (`@Attribute(.unique)`, **no default** — rule 63), `facilityServerId`, `facilityName`, `templateServerId`, `templateName`, `inspectorId`, `frequency`, `frequencyLabel`, `dueDateString` (sort key), `isOverdue`, `nextDue` (computed). Pulled by `pullScheduledInspections()`; `init(from:)`/`update(from:)` like `LocalFacility` |
|
||||||
| `PendingPhoto` | Photo awaiting upload | `localId`, `localFilePath`, `serverPath`, `uploadStatus`, `entityType` (`"issue"` or `"inspection"`), `fieldId` |
|
| `PendingPhoto` | Photo awaiting upload | `localId`, `localFilePath`, `serverPath`, `uploadStatus`, `entityType` (`"issue"` or `"inspection"`), `fieldId` |
|
||||||
@@ -381,7 +381,13 @@ Contract → Facility cascade pickers (same as `StandaloneIssueView`). `onChange
|
|||||||
|
|
||||||
### Scheduled inspections (phase36, July 2026)
|
### Scheduled inspections (phase36, July 2026)
|
||||||
|
|
||||||
`ScheduledInspectionsView.swift` holds `ScheduledRow` + `ScheduledInspectionsCard`. The card renders on the Dashboard (`DashboardStatsView`); `MyInspectionsView` renders its own inline "Scheduled" `List` section reusing `ScheduledRow`. Both query `LocalScheduledInspection` (sorted by `dueDateString`), self-hide when empty, and tap-to-Start opens `StartInspectionView(preFillTemplateId:preFillFacilityId:)`. Data is pulled read-only by `pullScheduledInspections()` (see rules 63–64 for the model + cover pitfalls hit while building it).
|
`ScheduledInspectionsView.swift` holds `ScheduledRow` + `ScheduledInspectionsCard`. The card renders on the Dashboard (`DashboardStatsView`); `MyInspectionsView` renders its own inline "Scheduled" `List` section reusing `ScheduledRow`. Both query `LocalScheduledInspection` (sorted by `dueDateString`), self-hide when empty, and tap-to-Start opens `StartInspectionView(preFillTemplateId:preFillFacilityId:preFillScheduleId:)`. Data is pulled read-only by `pullScheduledInspections()` (see rules 63–64 for the model + cover pitfalls hit while building it).
|
||||||
|
|
||||||
|
**Fulfilling the schedule (July 2026 fix).** `preFillScheduleId` is the schedule's `serverId`; `startInspection()` copies it onto `LocalInspection.scheduledInspectionServerId`, and `submitInspection()` sends it as **`scheduled_inspection_id`**. The server then fulfils the schedule (one-time → deactivated, recurring → rolled forward) in the same commit as the inspection.
|
||||||
|
|
||||||
|
Without it — the original bug — the schedule was never fulfilled: the banner stayed on the inspector's Dashboard and My Inspections, it stayed on the web dashboard for admin/director, and the web inspection list showed no "Scheduled" badge. **Both** Start call sites must pass `preFillScheduleId` (`ScheduledInspectionsCard` and the `MyInspectionsView` inline section); re-inspection launches correctly leave it nil.
|
||||||
|
|
||||||
|
No SyncManager change was needed: `pullScheduledInspections()` already runs after `processInspectionQueue()` in the same `triggerSync()` pass and deletes rows the server no longer returns, so the section clears on the same sync that submits the inspection.
|
||||||
|
|
||||||
### Inspection-start presentation (July 2026)
|
### Inspection-start presentation (July 2026)
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,16 @@ final class LocalInspection {
|
|||||||
/// followUpRequired badge without relying on parentServerId being non-nil.
|
/// followUpRequired badge without relying on parentServerId being non-nil.
|
||||||
var parentLocalId: String?
|
var parentLocalId: String?
|
||||||
|
|
||||||
|
// ── Scheduled inspection link ──────────────────────────────────────────
|
||||||
|
/// Server ID of the ScheduledInspection this inspection was started from,
|
||||||
|
/// set when the inspector taps Start on a scheduled row. Sent as
|
||||||
|
/// `scheduled_inspection_id` on submit so the server can fulfil the
|
||||||
|
/// schedule (deactivate a one-time / roll a recurring one forward) and
|
||||||
|
/// flag the inspection as "Scheduled" in the web list. Nil for ad-hoc work.
|
||||||
|
///
|
||||||
|
/// Declared with an inline default so existing stores migrate lightweight.
|
||||||
|
var scheduledInspectionServerId: Int? = nil
|
||||||
|
|
||||||
// ── GPS (captured at submit time via CoreLocation) ─────────────────────
|
// ── GPS (captured at submit time via CoreLocation) ─────────────────────
|
||||||
/// Device latitude at the moment the inspector tapped Submit. Nil if
|
/// Device latitude at the moment the inspector tapped Submit. Nil if
|
||||||
/// location permission was denied or a fix could not be obtained in time.
|
/// location permission was denied or a fix could not be obtained in time.
|
||||||
@@ -89,6 +99,7 @@ final class LocalInspection {
|
|||||||
self.followUpNote = nil
|
self.followUpNote = nil
|
||||||
self.parentServerId = nil
|
self.parentServerId = nil
|
||||||
self.parentLocalId = nil
|
self.parentLocalId = nil
|
||||||
|
self.scheduledInspectionServerId = nil
|
||||||
self.submitLatitude = nil
|
self.submitLatitude = nil
|
||||||
self.submitLongitude = nil
|
self.submitLongitude = nil
|
||||||
self.pendingPhotos = []
|
self.pendingPhotos = []
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ struct MyInspectionsView: View {
|
|||||||
.fullScreenCover(item: $scheduledStartTarget) { s in
|
.fullScreenCover(item: $scheduledStartTarget) { s in
|
||||||
StartInspectionView(
|
StartInspectionView(
|
||||||
preFillTemplateId: s.templateServerId,
|
preFillTemplateId: s.templateServerId,
|
||||||
preFillFacilityId: s.facilityServerId
|
preFillFacilityId: s.facilityServerId,
|
||||||
|
preFillScheduleId: s.serverId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ struct ScheduledInspectionsCard: View {
|
|||||||
.fullScreenCover(item: $startTarget) { s in
|
.fullScreenCover(item: $startTarget) { s in
|
||||||
StartInspectionView(
|
StartInspectionView(
|
||||||
preFillTemplateId: s.templateServerId,
|
preFillTemplateId: s.templateServerId,
|
||||||
preFillFacilityId: s.facilityServerId
|
preFillFacilityId: s.facilityServerId,
|
||||||
|
preFillScheduleId: s.serverId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ struct StartInspectionView: View {
|
|||||||
var parentServerId: Int? = nil
|
var parentServerId: Int? = nil
|
||||||
var parentLocalId: String? = nil
|
var parentLocalId: String? = nil
|
||||||
|
|
||||||
|
// ── Scheduled inspection launch ───────────────────────────────────────
|
||||||
|
/// Server ID of the ScheduledInspection this run fulfils, passed when the
|
||||||
|
/// inspector taps Start on a scheduled row. Carried onto the LocalInspection
|
||||||
|
/// so submitInspection() can send it; without it the server cannot fulfil
|
||||||
|
/// the schedule and the "Scheduled" banner never clears.
|
||||||
|
var preFillScheduleId: Int? = nil
|
||||||
|
|
||||||
// ── Derived lists ─────────────────────────────────────────────────────
|
// ── Derived lists ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Unique contracts (projectId, projectName) sorted by name.
|
/// Unique contracts (projectId, projectName) sorted by name.
|
||||||
@@ -266,6 +273,8 @@ struct StartInspectionView: View {
|
|||||||
// Link to parent if this is a re-inspection
|
// Link to parent if this is a re-inspection
|
||||||
inspection.parentServerId = parentServerId
|
inspection.parentServerId = parentServerId
|
||||||
inspection.parentLocalId = parentLocalId
|
inspection.parentLocalId = parentLocalId
|
||||||
|
// Link to the schedule if launched from a scheduled row
|
||||||
|
inspection.scheduledInspectionServerId = preFillScheduleId
|
||||||
|
|
||||||
// ── Pre-fill from parent (mirrors web app behaviour) ───────────────
|
// ── Pre-fill from parent (mirrors web app behaviour) ───────────────
|
||||||
// Copy non-scoring field values from the parent inspection so the
|
// Copy non-scoring field values from the parent inspection so the
|
||||||
|
|||||||
Reference in New Issue
Block a user