feat(clock-in): Refactor ClockInRepositoryImpl to utilize DataConnectService and simplify dependency injection

This commit is contained in:
Achintha Isuru
2026-02-16 16:00:27 -05:00
parent 1f7134799b
commit 66859e4241
2 changed files with 135 additions and 178 deletions

View File

@@ -1,69 +1,17 @@
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:krow_core/core.dart';
import '../../domain/repositories/clock_in_repository_interface.dart'; import '../../domain/repositories/clock_in_repository_interface.dart';
/// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect. /// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect.
class ClockInRepositoryImpl class ClockInRepositoryImpl implements ClockInRepositoryInterface {
with dc.DataErrorHandler ClockInRepositoryImpl() : _service = dc.DataConnectService.instance;
implements ClockInRepositoryInterface {
ClockInRepositoryImpl({ final dc.DataConnectService _service;
required dc.ExampleConnector dataConnect,
}) : _dataConnect = dataConnect;
final dc.ExampleConnector _dataConnect;
final Map<String, String> _shiftToApplicationId = <String, String>{}; final Map<String, String> _shiftToApplicationId = <String, String>{};
String? _activeApplicationId; String? _activeApplicationId;
Future<String> _getStaffId() async {
final dc.StaffSession? session = dc.StaffSessionStore.instance.session;
final String? staffId = session?.staff?.id;
if (staffId != null && staffId.isNotEmpty) {
return staffId;
}
throw Exception('Staff session not found');
}
/// Helper to convert Data Connect fdc.Timestamp to DateTime
DateTime? _toDateTime(dynamic t) {
if (t == null) return null;
DateTime? dt;
if (t is DateTime) {
dt = t;
} else if (t is String) {
dt = DateTime.tryParse(t);
} else {
try {
if (t is fdc.Timestamp) {
dt = t.toDateTime();
}
} catch (_) {}
try {
if (dt == null && t.runtimeType.toString().contains('Timestamp')) {
dt = (t as dynamic).toDate();
}
} catch (_) {}
try {
dt ??= DateTime.tryParse(t.toString());
} catch (_) {}
}
if (dt != null) {
return DateTimeUtils.toDeviceTime(dt);
}
return null;
}
/// Helper to create fdc.Timestamp from DateTime
fdc.Timestamp _fromDateTime(DateTime d) {
// Assuming fdc.Timestamp.fromJson takes an ISO string
return fdc.Timestamp.fromJson(d.toUtc().toIso8601String());
}
({fdc.Timestamp start, fdc.Timestamp end}) _utcDayRange(DateTime localDay) { ({fdc.Timestamp start, fdc.Timestamp end}) _utcDayRange(DateTime localDay) {
final DateTime dayStartUtc = DateTime.utc( final DateTime dayStartUtc = DateTime.utc(
localDay.year, localDay.year,
@@ -81,8 +29,8 @@ class ClockInRepositoryImpl
999, 999,
); );
return ( return (
start: _fromDateTime(dayStartUtc), start: _service.toTimestamp(dayStartUtc),
end: _fromDateTime(dayEndUtc), end: _service.toTimestamp(dayEndUtc),
); );
} }
@@ -93,26 +41,29 @@ class ClockInRepositoryImpl
final DateTime now = DateTime.now(); final DateTime now = DateTime.now();
final ({fdc.Timestamp start, fdc.Timestamp end}) range = _utcDayRange(now); final ({fdc.Timestamp start, fdc.Timestamp end}) range = _utcDayRange(now);
final fdc.QueryResult<dc.GetApplicationsByStaffIdData, final fdc.QueryResult<dc.GetApplicationsByStaffIdData,
dc.GetApplicationsByStaffIdVariables> result = await executeProtected( dc.GetApplicationsByStaffIdVariables> result = await _service.run(
() => _dataConnect () => _service.connector
.getApplicationsByStaffId(staffId: staffId) .getApplicationsByStaffId(staffId: staffId)
.dayStart(range.start) .dayStart(range.start)
.dayEnd(range.end) .dayEnd(range.end)
.execute(), .execute(),
); );
final List<dc.GetApplicationsByStaffIdApplications> apps = result.data.applications; final List<dc.GetApplicationsByStaffIdApplications> apps =
result.data.applications;
if (apps.isEmpty) return const <dc.GetApplicationsByStaffIdApplications>[]; if (apps.isEmpty) return const <dc.GetApplicationsByStaffIdApplications>[];
_shiftToApplicationId _shiftToApplicationId
..clear() ..clear()
..addEntries(apps.map((dc.GetApplicationsByStaffIdApplications app) => MapEntry(app.shiftId, app.id))); ..addEntries(apps.map((dc.GetApplicationsByStaffIdApplications app) =>
MapEntry<String, String>(app.shiftId, app.id)));
apps.sort((dc.GetApplicationsByStaffIdApplications a, dc.GetApplicationsByStaffIdApplications b) { apps.sort((dc.GetApplicationsByStaffIdApplications a,
dc.GetApplicationsByStaffIdApplications b) {
final DateTime? aTime = final DateTime? aTime =
_toDateTime(a.shift.startTime) ?? _toDateTime(a.shift.date); _service.toDateTime(a.shift.startTime) ?? _service.toDateTime(a.shift.date);
final DateTime? bTime = final DateTime? bTime =
_toDateTime(b.shift.startTime) ?? _toDateTime(b.shift.date); _service.toDateTime(b.shift.startTime) ?? _service.toDateTime(b.shift.date);
if (aTime == null && bTime == null) return 0; if (aTime == null && bTime == null) return 0;
if (aTime == null) return -1; if (aTime == null) return -1;
if (bTime == null) return 1; if (bTime == null) return 1;
@@ -124,11 +75,10 @@ class ClockInRepositoryImpl
return apps; return apps;
} }
@override @override
Future<List<Shift>> getTodaysShifts() async { Future<List<Shift>> getTodaysShifts() async {
final String staffId = await _getStaffId(); return _service.run(() async {
final String staffId = await _service.getStaffId();
final List<dc.GetApplicationsByStaffIdApplications> apps = final List<dc.GetApplicationsByStaffIdApplications> apps =
await _getTodaysApplications(staffId); await _getTodaysApplications(staffId);
if (apps.isEmpty) return const <Shift>[]; if (apps.isEmpty) return const <Shift>[];
@@ -136,9 +86,9 @@ class ClockInRepositoryImpl
final List<Shift> shifts = <Shift>[]; final List<Shift> shifts = <Shift>[];
for (final dc.GetApplicationsByStaffIdApplications app in apps) { for (final dc.GetApplicationsByStaffIdApplications app in apps) {
final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift; final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift;
final DateTime? startDt = _toDateTime(app.shiftRole.startTime); final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime);
final DateTime? endDt = _toDateTime(app.shiftRole.endTime); final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime);
final DateTime? createdDt = _toDateTime(app.createdAt); final DateTime? createdDt = _service.toDateTime(app.createdAt);
final String roleName = app.shiftRole.role.name; final String roleName = app.shiftRole.role.name;
final String orderName = final String orderName =
@@ -168,11 +118,13 @@ class ClockInRepositoryImpl
} }
return shifts; return shifts;
});
} }
@override @override
Future<AttendanceStatus> getAttendanceStatus() async { Future<AttendanceStatus> getAttendanceStatus() async {
final String staffId = await _getStaffId(); return _service.run(() async {
final String staffId = await _service.getStaffId();
final List<dc.GetApplicationsByStaffIdApplications> apps = final List<dc.GetApplicationsByStaffIdApplications> apps =
await _getTodaysApplications(staffId); await _getTodaysApplications(staffId);
if (apps.isEmpty) { if (apps.isEmpty) {
@@ -185,8 +137,8 @@ class ClockInRepositoryImpl
if (activeApp == null) { if (activeApp == null) {
activeApp = app; activeApp = app;
} else { } else {
final DateTime? current = _toDateTime(activeApp.checkInTime); final DateTime? current = _service.toDateTime(activeApp.checkInTime);
final DateTime? next = _toDateTime(app.checkInTime); final DateTime? next = _service.toDateTime(app.checkInTime);
if (current == null || (next != null && next.isAfter(current))) { if (current == null || (next != null && next.isAfter(current))) {
activeApp = app; activeApp = app;
} }
@@ -203,31 +155,35 @@ class ClockInRepositoryImpl
return AttendanceStatus( return AttendanceStatus(
isCheckedIn: true, isCheckedIn: true,
checkInTime: _toDateTime(activeApp.checkInTime), checkInTime: _service.toDateTime(activeApp.checkInTime),
checkOutTime: _toDateTime(activeApp.checkOutTime), checkOutTime: _service.toDateTime(activeApp.checkOutTime),
activeShiftId: activeApp.shiftId, activeShiftId: activeApp.shiftId,
activeApplicationId: activeApp.id, activeApplicationId: activeApp.id,
); );
});
} }
@override @override
Future<AttendanceStatus> clockIn({required String shiftId, String? notes}) async { Future<AttendanceStatus> clockIn({required String shiftId, String? notes}) async {
final String staffId = await _getStaffId(); return _service.run(() async {
final String staffId = await _service.getStaffId();
final String? cachedAppId = _shiftToApplicationId[shiftId]; final String? cachedAppId = _shiftToApplicationId[shiftId];
dc.GetApplicationsByStaffIdApplications? app; dc.GetApplicationsByStaffIdApplications? app;
if (cachedAppId != null) { if (cachedAppId != null) {
try { try {
final List<dc.GetApplicationsByStaffIdApplications> apps = await _getTodaysApplications(staffId); final List<dc.GetApplicationsByStaffIdApplications> apps =
app = apps.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.id == cachedAppId); await _getTodaysApplications(staffId);
app = apps.firstWhere(
(dc.GetApplicationsByStaffIdApplications a) => a.id == cachedAppId);
} catch (_) {} } catch (_) {}
} }
app ??= (await _getTodaysApplications(staffId)) app ??= (await _getTodaysApplications(staffId)).firstWhere(
.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId); (dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId);
final fdc.Timestamp checkInTs = _fromDateTime(DateTime.now()); final fdc.Timestamp checkInTs = _service.toTimestamp(DateTime.now());
await executeProtected(() => _dataConnect await _service.run(() => _service.connector
.updateApplicationStatus( .updateApplicationStatus(
id: app!.id, id: app!.id,
) )
@@ -236,6 +192,7 @@ class ClockInRepositoryImpl
_activeApplicationId = app.id; _activeApplicationId = app.id;
return getAttendanceStatus(); return getAttendanceStatus();
});
} }
@override @override
@@ -244,14 +201,16 @@ class ClockInRepositoryImpl
int? breakTimeMinutes, int? breakTimeMinutes,
String? applicationId, String? applicationId,
}) async { }) async {
await _getStaffId(); // Validate session return _service.run(() async {
await _service.getStaffId(); // Validate session
final String? targetAppId = applicationId ?? _activeApplicationId; final String? targetAppId = applicationId ?? _activeApplicationId;
if (targetAppId == null || targetAppId.isEmpty) { if (targetAppId == null || targetAppId.isEmpty) {
throw Exception('No active application id for checkout'); throw Exception('No active application id for checkout');
} }
final fdc.QueryResult<dc.GetApplicationByIdData, dc.GetApplicationByIdVariables> appResult = await executeProtected(() => _dataConnect final fdc.QueryResult<dc.GetApplicationByIdData,
dc.GetApplicationByIdVariables> appResult =
await _service.run(() => _service.connector
.getApplicationById(id: targetAppId) .getApplicationById(id: targetAppId)
.execute()); .execute());
final dc.GetApplicationByIdApplication? app = appResult.data.application; final dc.GetApplicationByIdApplication? app = appResult.data.application;
@@ -263,13 +222,14 @@ class ClockInRepositoryImpl
throw Exception('No active shift found to clock out'); throw Exception('No active shift found to clock out');
} }
await executeProtected(() => _dataConnect await _service.run(() => _service.connector
.updateApplicationStatus( .updateApplicationStatus(
id: targetAppId, id: targetAppId,
) )
.checkOutTime(_fromDateTime(DateTime.now())) .checkOutTime(_service.toTimestamp(DateTime.now()))
.execute()); .execute());
return getAttendanceStatus(); return getAttendanceStatus();
});
} }
} }

View File

@@ -1,7 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'data/repositories_impl/clock_in_repository_impl.dart'; import 'data/repositories_impl/clock_in_repository_impl.dart';
import 'domain/repositories/clock_in_repository_interface.dart'; import 'domain/repositories/clock_in_repository_interface.dart';
@@ -16,9 +15,7 @@ class StaffClockInModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.add<ClockInRepositoryInterface>( i.add<ClockInRepositoryInterface>(ClockInRepositoryImpl.new);
() => ClockInRepositoryImpl(dataConnect: ExampleConnector.instance),
);
// Use Cases // Use Cases
i.add<GetTodaysShiftUseCase>(GetTodaysShiftUseCase.new); i.add<GetTodaysShiftUseCase>(GetTodaysShiftUseCase.new);