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

View File

@@ -6,14 +6,22 @@ class AttendanceStatus extends Equatable {
final DateTime? checkInTime;
final DateTime? checkOutTime;
final String? activeShiftId;
final String? activeApplicationId;
const AttendanceStatus({
this.isCheckedIn = false,
this.checkInTime,
this.checkOutTime,
this.activeShiftId,
this.activeApplicationId,
});
@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 {
final dc.ExampleConnector _dataConnect;
final Map<String, String> _shiftToApplicationId = {};
String? _activeApplicationId;
ClockInRepositoryImpl({
required dc.ExampleConnector dataConnect,
@@ -187,16 +188,34 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
return const AttendanceStatus(isCheckedIn: false);
}
final dc.GetApplicationsByStaffIdApplications? activeApp =
_getActiveApplication(apps);
final dc.GetApplicationsByStaffIdApplications app =
activeApp ?? apps.last;
dc.GetApplicationsByStaffIdApplications? activeApp;
for (final app in apps) {
if (app.checkInTime != null && app.checkOutTime == null) {
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(
status: app.status.stringValue,
checkInTime: _toDateTime(app.checkInTime),
checkOutTime: _toDateTime(app.checkOutTime),
activeShiftId: app.shiftId,
if (activeApp == null) {
_activeApplicationId = null;
return const AttendanceStatus(isCheckedIn: false);
}
_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)
.execute();
_activeApplicationId = app.id;
} catch (e) {
print('ClockIn updateApplicationStatus error: $e');
print('ClockIn error type: ${e.runtimeType}');
@@ -245,22 +265,43 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
}
@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 List<dc.GetApplicationsByStaffIdApplications> apps =
await _getTodaysApplications(staffId);
final dc.GetApplicationsByStaffIdApplications? app =
_getActiveApplication(apps);
if (app == null) throw Exception('No active shift found to clock out');
print(
'ClockOut request: applicationId=$applicationId '
'activeApplicationId=$_activeApplicationId',
);
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
.updateApplicationStatus(
id: app.id,
id: targetAppId,
)
.status(dc.ApplicationStatus.CHECKED_OUT)
.checkOutTime(_fromDateTime(DateTime.now()))
.execute();
.checkOutTime(_fromDateTime(DateTime.now()))
.execute();
return getAttendanceStatus();
}

View File

@@ -8,12 +8,16 @@ class ClockOutArguments extends UseCaseArgument {
/// Optional break time in minutes.
final int? breakTimeMinutes;
/// Optional application id for checkout.
final String? applicationId;
/// Creates a [ClockOutArguments] instance.
const ClockOutArguments({
this.notes,
this.breakTimeMinutes,
this.applicationId,
});
@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.
/// 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(
notes: arguments.notes,
breakTimeMinutes: arguments.breakTimeMinutes,
applicationId: arguments.applicationId,
);
}
}

View File

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