fix: add hasCompleted guard to prevent double-completion race condition

If a user confirms cash payment AND a beacon triggers auto-complete at the
same time, two completion calls could fire. Added @State hasCompleted flag
that gates all completion paths (manual complete, beacon auto-complete, and
cash collection). Resets on error/cancel so user can retry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Schwifty 2026-03-24 22:10:56 +00:00
parent db7fe31b8a
commit 54923ba341

View file

@ -28,6 +28,7 @@ struct TaskDetailScreen: View {
@State private var showCancelOrderAlert = false
@State private var isCancelingOrder = false
@State private var taskAccepted = false // Track if task was just accepted
@State private var hasCompleted = false // Guard against double-completion
@State private var customerAvatarUrl: String? // Fetched separately if not in task details
// Rating dialog
@ -100,6 +101,7 @@ struct TaskDetailScreen: View {
} else if result == "cancelled" || result == "error" {
autoCompleting = false
beaconDetected = false
hasCompleted = false
beaconScanner?.resetSamples()
beaconScanner?.startScanning()
}
@ -629,6 +631,7 @@ struct TaskDetailScreen: View {
}
.buttonStyle(.borderedProminent)
.tint(Color(red: 0.13, green: 0.55, blue: 0.13))
.disabled(hasCompleted)
} else {
Button { showCompleteAlert = true } label: {
Label("Complete Task", systemImage: "checkmark.circle.fill")
@ -637,6 +640,7 @@ struct TaskDetailScreen: View {
}
.buttonStyle(.borderedProminent)
.tint(.green)
.disabled(hasCompleted)
}
}
}
@ -723,9 +727,10 @@ struct TaskDetailScreen: View {
let scanner = BeaconScanner(
targetServicePointId: servicePointId,
onBeaconDetected: { [self] _ in
if !beaconDetected && !autoCompleting {
if !beaconDetected && !autoCompleting && !hasCompleted {
beaconDetected = true
autoCompleting = true
hasCompleted = true
beaconScanner?.stopScanning()
showAutoCompleteDialog = true
}
@ -760,6 +765,8 @@ struct TaskDetailScreen: View {
}
private func completeTask() {
guard !hasCompleted else { return }
hasCompleted = true
Task {
do {
try await APIService.shared.completeTask(taskId: task.taskId)
@ -768,9 +775,11 @@ struct TaskDetailScreen: View {
if case .ratingRequired = apiError {
showRatingDialog = true
} else {
hasCompleted = false
self.error = apiError.localizedDescription
}
} catch {
hasCompleted = false
self.error = error.localizedDescription
}
}