feat(clock_in): add messages for already clocked in/out states and handle API call failures

This commit is contained in:
Achintha Isuru
2026-03-18 16:37:59 -04:00
parent d13cabb30d
commit 1552f60e5b
3 changed files with 92 additions and 46 deletions

View File

@@ -204,29 +204,49 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
action: () async {
final DeviceLocation? location = geofenceState.currentLocation;
final AttendanceStatus newStatus = await _clockIn(
ClockInArguments(
shiftId: event.shiftId,
notes: event.notes,
latitude: location?.latitude,
longitude: location?.longitude,
accuracyMeters: location?.accuracy,
capturedAt: location?.timestamp,
overrideReason: geofenceState.isGeofenceOverridden
? geofenceState.overrideNotes
: null,
),
);
emit(state.copyWith(
status: ClockInStatus.success,
attendance: newStatus,
));
try {
final AttendanceStatus newStatus = await _clockIn(
ClockInArguments(
shiftId: event.shiftId,
notes: event.notes,
latitude: location?.latitude,
longitude: location?.longitude,
accuracyMeters: location?.accuracy,
capturedAt: location?.timestamp,
overrideReason: geofenceState.isGeofenceOverridden
? geofenceState.overrideNotes
: null,
),
);
emit(state.copyWith(
status: ClockInStatus.success,
attendance: newStatus,
));
// Start background tracking after successful clock-in.
_dispatchBackgroundTrackingStarted(
event: event,
activeShiftId: newStatus.activeShiftId,
);
// Start background tracking after successful clock-in.
_dispatchBackgroundTrackingStarted(
event: event,
activeShiftId: newStatus.activeShiftId,
);
} on AppException catch (_) {
// The clock-in API call failed. Re-fetch attendance status to
// reconcile: if the worker is already clocked in (e.g. duplicate
// session from Postgres constraint 23505), treat it as success.
final AttendanceStatus currentStatus = await _getAttendanceStatus();
if (currentStatus.isClockedIn) {
emit(state.copyWith(
status: ClockInStatus.success,
attendance: currentStatus,
));
_dispatchBackgroundTrackingStarted(
event: event,
activeShiftId: currentStatus.activeShiftId,
);
} else {
// Worker is genuinely not clocked in — surface the error.
rethrow;
}
}
},
onError: (String errorKey) => state.copyWith(
status: ClockInStatus.failure,
@@ -261,29 +281,51 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
final GeofenceState currentGeofence = _geofenceBloc.state;
final DeviceLocation? location = currentGeofence.currentLocation;
final AttendanceStatus newStatus = await _clockOut(
ClockOutArguments(
notes: event.notes,
breakTimeMinutes: event.breakTimeMinutes,
shiftId: activeShiftId,
latitude: location?.latitude,
longitude: location?.longitude,
accuracyMeters: location?.accuracy,
capturedAt: location?.timestamp,
),
);
emit(state.copyWith(
status: ClockInStatus.success,
attendance: newStatus,
));
try {
final AttendanceStatus newStatus = await _clockOut(
ClockOutArguments(
notes: event.notes,
breakTimeMinutes: event.breakTimeMinutes,
shiftId: activeShiftId,
latitude: location?.latitude,
longitude: location?.longitude,
accuracyMeters: location?.accuracy,
capturedAt: location?.timestamp,
),
);
emit(state.copyWith(
status: ClockInStatus.success,
attendance: newStatus,
));
// Stop background tracking after successful clock-out.
_geofenceBloc.add(
BackgroundTrackingStopped(
clockOutTitle: event.clockOutTitle,
clockOutBody: event.clockOutBody,
),
);
// Stop background tracking after successful clock-out.
_geofenceBloc.add(
BackgroundTrackingStopped(
clockOutTitle: event.clockOutTitle,
clockOutBody: event.clockOutBody,
),
);
} on AppException catch (_) {
// The clock-out API call failed. Re-fetch attendance status to
// reconcile: if the worker is already clocked out (e.g. duplicate
// end-session), treat it as success.
final AttendanceStatus currentStatus = await _getAttendanceStatus();
if (!currentStatus.isClockedIn) {
emit(state.copyWith(
status: ClockInStatus.success,
attendance: currentStatus,
));
_geofenceBloc.add(
BackgroundTrackingStopped(
clockOutTitle: event.clockOutTitle,
clockOutBody: event.clockOutBody,
),
);
} else {
// Worker is still clocked in — surface the error.
rethrow;
}
}
},
onError: (String errorKey) => state.copyWith(
status: ClockInStatus.failure,