Aug 25 - Fix inspection lost photos
This commit is contained in:
@@ -367,10 +367,14 @@ class SyncManager: ObservableObject {
|
||||
photo.uploadRetryCount = 0
|
||||
uploadedPaths[photo.localFilePath] = serverPath
|
||||
|
||||
photo.lastUploadError = nil
|
||||
attachServerPath(serverPath, for: photo,
|
||||
inspections: allInspections, issues: allIssues)
|
||||
await pushLateIssuePhotoIfNeeded(serverPath, for: photo,
|
||||
issues: allIssues, context: context)
|
||||
await pushLateInspectionPhotoIfNeeded(serverPath, for: photo,
|
||||
inspections: allInspections,
|
||||
context: context)
|
||||
try? context.save()
|
||||
|
||||
} catch {
|
||||
@@ -386,6 +390,9 @@ class SyncManager: ObservableObject {
|
||||
// photo permanently, silently, with the inspection still
|
||||
// reported as successfully synced.
|
||||
photo.uploadRetryCount += 1
|
||||
// Keep the reason. Without it a lost evidence photo is
|
||||
// undiagnosable after the fact — see PendingPhoto.lastUploadError.
|
||||
photo.lastUploadError = error.localizedDescription
|
||||
if photo.uploadRetryCount >= Self.maxPhotoUploadAttempts {
|
||||
photo.uploadStatus = "failed"
|
||||
syncError = "A photo failed to upload after "
|
||||
@@ -403,6 +410,7 @@ class SyncManager: ObservableObject {
|
||||
dup.serverPath = serverPath
|
||||
dup.uploadStatus = "uploaded"
|
||||
dup.uploadRetryCount = 0
|
||||
dup.lastUploadError = nil
|
||||
attachServerPath(serverPath, for: dup,
|
||||
inspections: allInspections, issues: allIssues)
|
||||
await pushLateIssuePhotoIfNeeded(serverPath, for: dup,
|
||||
@@ -455,6 +463,56 @@ class SyncManager: ObservableObject {
|
||||
try? context.save()
|
||||
}
|
||||
|
||||
/// Push a recovered photo onto an inspection that has ALREADY been submitted.
|
||||
///
|
||||
/// The inspection counterpart of pushLateIssuePhotoIfNeeded, and the gap
|
||||
/// that made inspection photo loss permanent:
|
||||
///
|
||||
/// 1. the upload fails `maxPhotoUploadAttempts` times -> row goes "failed"
|
||||
/// 2. "failed" counts as ready, so processInspectionQueue submits anyway
|
||||
/// and APIClient.submitInspection rewrites the surviving local:// value
|
||||
/// to "" — the field lands BLANK on the server
|
||||
/// 3. the inspection is marked synced; nothing ever revisits it
|
||||
///
|
||||
/// Step 3 was the dead end. "Retry Failed Items" could re-upload the file
|
||||
/// successfully, but attachServerPath only wrote the path into LOCAL form
|
||||
/// data, which no longer goes anywhere — the inspection was already synced.
|
||||
/// The photo sat on the device, recoverable in principle and unreachable in
|
||||
/// practice. This closes the loop by PATCHing the server copy.
|
||||
///
|
||||
/// Normal path: processPhotoQueue runs BEFORE processInspectionQueue, so a
|
||||
/// first-time inspection has no serverId yet and this does nothing — the
|
||||
/// path travels in the submit body as usual. Only a recovery reaches here.
|
||||
///
|
||||
/// Best-effort: a failure leaves the photo attached locally and retried on
|
||||
/// the next pass, exactly like the issue version.
|
||||
private func pushLateInspectionPhotoIfNeeded(
|
||||
_ serverPath: String,
|
||||
for photo: PendingPhoto,
|
||||
inspections: [LocalInspection],
|
||||
context: ModelContext
|
||||
) async {
|
||||
guard photo.entityType == "inspection",
|
||||
let fieldId = photo.fieldId
|
||||
else { return }
|
||||
let entityId = photo.entityLocalId
|
||||
guard let inspection = inspections.first(where: { $0.localId == entityId }),
|
||||
let inspectionServerId = inspection.serverId
|
||||
else { return }
|
||||
|
||||
do {
|
||||
try await APIClient.shared.updateInspectionFormData(
|
||||
inspectionId: inspectionServerId,
|
||||
fields: [fieldId: serverPath]
|
||||
)
|
||||
inspection.syncErrorMessage = nil
|
||||
} catch {
|
||||
inspection.syncErrorMessage =
|
||||
"A recovered photo could not be attached: \(error.localizedDescription)"
|
||||
}
|
||||
try? context.save()
|
||||
}
|
||||
|
||||
/// Write a freshly uploaded server path onto whichever record owns the photo.
|
||||
/// Both collections are pre-fetched by the caller — see processPhotoQueue.
|
||||
private func attachServerPath(
|
||||
|
||||
Reference in New Issue
Block a user