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

@@ -1479,7 +1479,9 @@
}, },
"clock_in": { "clock_in": {
"location_verification_required": "Please wait for location verification before clocking in.", "location_verification_required": "Please wait for location verification before clocking in.",
"notes_required_for_timeout": "Please add a note explaining why your location can't be verified." "notes_required_for_timeout": "Please add a note explaining why your location can't be verified.",
"already_clocked_in": "You're already clocked in to this shift.",
"already_clocked_out": "You've already clocked out of this shift."
}, },
"generic": { "generic": {
"unknown": "Something went wrong. Please try again.", "unknown": "Something went wrong. Please try again.",

View File

@@ -1474,7 +1474,9 @@
}, },
"clock_in": { "clock_in": {
"location_verification_required": "Por favor, espera la verificaci\u00f3n de ubicaci\u00f3n antes de registrar entrada.", "location_verification_required": "Por favor, espera la verificaci\u00f3n de ubicaci\u00f3n antes de registrar entrada.",
"notes_required_for_timeout": "Por favor, agrega una nota explicando por qu\u00e9 no se puede verificar tu ubicaci\u00f3n." "notes_required_for_timeout": "Por favor, agrega una nota explicando por qu\u00e9 no se puede verificar tu ubicaci\u00f3n.",
"already_clocked_in": "Ya est\u00e1s registrado en este turno.",
"already_clocked_out": "Ya registraste tu salida de este turno."
}, },
"generic": { "generic": {
"unknown": "Algo sali\u00f3 mal. Por favor, intenta de nuevo.", "unknown": "Algo sali\u00f3 mal. Por favor, intenta de nuevo.",

View File

@@ -204,6 +204,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
action: () async { action: () async {
final DeviceLocation? location = geofenceState.currentLocation; final DeviceLocation? location = geofenceState.currentLocation;
try {
final AttendanceStatus newStatus = await _clockIn( final AttendanceStatus newStatus = await _clockIn(
ClockInArguments( ClockInArguments(
shiftId: event.shiftId, shiftId: event.shiftId,
@@ -227,6 +228,25 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
event: event, event: event,
activeShiftId: newStatus.activeShiftId, 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( onError: (String errorKey) => state.copyWith(
status: ClockInStatus.failure, status: ClockInStatus.failure,
@@ -261,6 +281,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
final GeofenceState currentGeofence = _geofenceBloc.state; final GeofenceState currentGeofence = _geofenceBloc.state;
final DeviceLocation? location = currentGeofence.currentLocation; final DeviceLocation? location = currentGeofence.currentLocation;
try {
final AttendanceStatus newStatus = await _clockOut( final AttendanceStatus newStatus = await _clockOut(
ClockOutArguments( ClockOutArguments(
notes: event.notes, notes: event.notes,
@@ -284,6 +305,27 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
clockOutBody: event.clockOutBody, 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( onError: (String errorKey) => state.copyWith(
status: ClockInStatus.failure, status: ClockInStatus.failure,