checkin status v1, working"

"
This commit is contained in:
José Salazar
2026-02-02 21:32:22 +09:00
parent 818fc1759a
commit b1ad6f118a
7 changed files with 83 additions and 22 deletions

View File

@@ -10,6 +10,7 @@ class ClockInAdapter {
DateTime? checkInTime, DateTime? checkInTime,
DateTime? checkOutTime, DateTime? checkOutTime,
String? activeShiftId, String? activeShiftId,
String? activeApplicationId,
}) { }) {
final bool isCheckedIn = status == 'CHECKED_IN' || status == 'LATE'; // Assuming LATE is also checked in? final bool isCheckedIn = status == 'CHECKED_IN' || status == 'LATE'; // Assuming LATE is also checked in?
@@ -21,6 +22,7 @@ class ClockInAdapter {
checkInTime: checkInTime, checkInTime: checkInTime,
checkOutTime: checkOutTime, checkOutTime: checkOutTime,
activeShiftId: activeShiftId, activeShiftId: activeShiftId,
activeApplicationId: activeApplicationId,
); );
} }
} }

View File

@@ -6,14 +6,22 @@ class AttendanceStatus extends Equatable {
final DateTime? checkInTime; final DateTime? checkInTime;
final DateTime? checkOutTime; final DateTime? checkOutTime;
final String? activeShiftId; final String? activeShiftId;
final String? activeApplicationId;
const AttendanceStatus({ const AttendanceStatus({
this.isCheckedIn = false, this.isCheckedIn = false,
this.checkInTime, this.checkInTime,
this.checkOutTime, this.checkOutTime,
this.activeShiftId, this.activeShiftId,
this.activeApplicationId,
}); });
@override @override
List<Object?> get props => [isCheckedIn, checkInTime, checkOutTime, activeShiftId]; List<Object?> get props => [
isCheckedIn,
checkInTime,
checkOutTime,
activeShiftId,
activeApplicationId,
];
} }

View File

@@ -9,6 +9,7 @@ import '../../domain/repositories/clock_in_repository_interface.dart';
class ClockInRepositoryImpl implements ClockInRepositoryInterface { class ClockInRepositoryImpl implements ClockInRepositoryInterface {
final dc.ExampleConnector _dataConnect; final dc.ExampleConnector _dataConnect;
final Map<String, String> _shiftToApplicationId = {}; final Map<String, String> _shiftToApplicationId = {};
String? _activeApplicationId;
ClockInRepositoryImpl({ ClockInRepositoryImpl({
required dc.ExampleConnector dataConnect, required dc.ExampleConnector dataConnect,
@@ -187,16 +188,34 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
return const AttendanceStatus(isCheckedIn: false); return const AttendanceStatus(isCheckedIn: false);
} }
final dc.GetApplicationsByStaffIdApplications? activeApp = dc.GetApplicationsByStaffIdApplications? activeApp;
_getActiveApplication(apps); for (final app in apps) {
final dc.GetApplicationsByStaffIdApplications app = if (app.checkInTime != null && app.checkOutTime == null) {
activeApp ?? apps.last; if (activeApp == null) {
activeApp = app;
} else {
final DateTime? current = _toDateTime(activeApp.checkInTime);
final DateTime? next = _toDateTime(app.checkInTime);
if (current == null || (next != null && next.isAfter(current))) {
activeApp = app;
}
}
}
}
return ClockInAdapter.toAttendanceStatus( if (activeApp == null) {
status: app.status.stringValue, _activeApplicationId = null;
checkInTime: _toDateTime(app.checkInTime), return const AttendanceStatus(isCheckedIn: false);
checkOutTime: _toDateTime(app.checkOutTime), }
activeShiftId: app.shiftId,
_activeApplicationId = activeApp.id;
print('Active check-in appId=$_activeApplicationId');
return AttendanceStatus(
isCheckedIn: true,
checkInTime: _toDateTime(activeApp.checkInTime),
checkOutTime: _toDateTime(activeApp.checkOutTime),
activeShiftId: activeApp.shiftId,
activeApplicationId: activeApp.id,
); );
} }
@@ -227,6 +246,7 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
) )
.checkInTime(checkInTs) .checkInTime(checkInTs)
.execute(); .execute();
_activeApplicationId = app.id;
} catch (e) { } catch (e) {
print('ClockIn updateApplicationStatus error: $e'); print('ClockIn updateApplicationStatus error: $e');
print('ClockIn error type: ${e.runtimeType}'); print('ClockIn error type: ${e.runtimeType}');
@@ -245,22 +265,43 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
} }
@override @override
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}) async { Future<AttendanceStatus> clockOut({
String? notes,
int? breakTimeMinutes,
String? applicationId,
}) async {
final String staffId = await _getStaffId(); final String staffId = await _getStaffId();
final List<dc.GetApplicationsByStaffIdApplications> apps = print(
await _getTodaysApplications(staffId); 'ClockOut request: applicationId=$applicationId '
final dc.GetApplicationsByStaffIdApplications? app = 'activeApplicationId=$_activeApplicationId',
_getActiveApplication(apps); );
if (app == null) throw Exception('No active shift found to clock out'); final String? targetAppId = applicationId ?? _activeApplicationId;
if (targetAppId == null || targetAppId.isEmpty) {
throw Exception('No active application id for checkout');
}
final appResult = await _dataConnect
.getApplicationById(id: targetAppId)
.execute();
final app = appResult.data.application;
print(
'ClockOut getApplicationById: id=${app?.id} '
'checkIn=${app?.checkInTime?.toJson()} '
'checkOut=${app?.checkOutTime?.toJson()}',
);
if (app == null) {
throw Exception('Application not found for checkout');
}
if (app.checkInTime == null || app.checkOutTime != null) {
throw Exception('No active shift found to clock out');
}
await _dataConnect await _dataConnect
.updateApplicationStatus( .updateApplicationStatus(
id: app.id, id: targetAppId,
) )
.status(dc.ApplicationStatus.CHECKED_OUT) .checkOutTime(_fromDateTime(DateTime.now()))
.checkOutTime(_fromDateTime(DateTime.now())) .execute();
.execute();
return getAttendanceStatus(); return getAttendanceStatus();
} }

View File

@@ -8,12 +8,16 @@ class ClockOutArguments extends UseCaseArgument {
/// Optional break time in minutes. /// Optional break time in minutes.
final int? breakTimeMinutes; final int? breakTimeMinutes;
/// Optional application id for checkout.
final String? applicationId;
/// Creates a [ClockOutArguments] instance. /// Creates a [ClockOutArguments] instance.
const ClockOutArguments({ const ClockOutArguments({
this.notes, this.notes,
this.breakTimeMinutes, this.breakTimeMinutes,
this.applicationId,
}); });
@override @override
List<Object?> get props => [notes, breakTimeMinutes]; List<Object?> get props => [notes, breakTimeMinutes, applicationId];
} }

View File

@@ -17,5 +17,9 @@ abstract class ClockInRepositoryInterface {
/// Checks the user out for the currently active shift. /// Checks the user out for the currently active shift.
/// Optionally accepts [breakTimeMinutes] if tracked. /// Optionally accepts [breakTimeMinutes] if tracked.
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}); Future<AttendanceStatus> clockOut({
String? notes,
int? breakTimeMinutes,
String? applicationId,
});
} }

View File

@@ -14,6 +14,7 @@ class ClockOutUseCase implements UseCase<ClockOutArguments, AttendanceStatus> {
return _repository.clockOut( return _repository.clockOut(
notes: arguments.notes, notes: arguments.notes,
breakTimeMinutes: arguments.breakTimeMinutes, breakTimeMinutes: arguments.breakTimeMinutes,
applicationId: arguments.applicationId,
); );
} }
} }

View File

@@ -220,6 +220,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
ClockOutArguments( ClockOutArguments(
notes: event.notes, notes: event.notes,
breakTimeMinutes: 0, // Should be passed from event if supported breakTimeMinutes: 0, // Should be passed from event if supported
applicationId: state.attendance.activeApplicationId,
), ),
); );
emit(state.copyWith( emit(state.copyWith(