From 54923ba341646f8e49dc4e0b0558f9658b8df547 Mon Sep 17 00:00:00 2001 From: Schwifty Date: Tue, 24 Mar 2026 22:10:56 +0000 Subject: [PATCH] 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) --- PayfritWorks/Views/TaskDetailScreen.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/PayfritWorks/Views/TaskDetailScreen.swift b/PayfritWorks/Views/TaskDetailScreen.swift index f5a56c4..89ab296 100644 --- a/PayfritWorks/Views/TaskDetailScreen.swift +++ b/PayfritWorks/Views/TaskDetailScreen.swift @@ -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 } }