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,118 +75,124 @@ 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 List<dc.GetApplicationsByStaffIdApplications> apps = final String staffId = await _service.getStaffId();
await _getTodaysApplications(staffId); final List<dc.GetApplicationsByStaffIdApplications> apps =
if (apps.isEmpty) return const <Shift>[]; await _getTodaysApplications(staffId);
if (apps.isEmpty) return const <Shift>[];
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 =
(shift.order.eventName ?? '').trim().isNotEmpty (shift.order.eventName ?? '').trim().isNotEmpty
? shift.order.eventName! ? shift.order.eventName!
: shift.order.business.businessName; : shift.order.business.businessName;
final String title = '$roleName - $orderName'; final String title = '$roleName - $orderName';
shifts.add( shifts.add(
Shift( Shift(
id: shift.id, id: shift.id,
title: title, title: title,
clientName: shift.order.business.businessName, clientName: shift.order.business.businessName,
logoUrl: shift.order.business.companyLogoUrl ?? '', logoUrl: shift.order.business.companyLogoUrl ?? '',
hourlyRate: app.shiftRole.role.costPerHour, hourlyRate: app.shiftRole.role.costPerHour,
location: shift.location ?? '', location: shift.location ?? '',
locationAddress: shift.order.teamHub.hubName, locationAddress: shift.order.teamHub.hubName,
date: startDt?.toIso8601String() ?? '', date: startDt?.toIso8601String() ?? '',
startTime: startDt?.toIso8601String() ?? '', startTime: startDt?.toIso8601String() ?? '',
endTime: endDt?.toIso8601String() ?? '', endTime: endDt?.toIso8601String() ?? '',
createdDate: createdDt?.toIso8601String() ?? '', createdDate: createdDt?.toIso8601String() ?? '',
status: shift.status?.stringValue, status: shift.status?.stringValue,
description: shift.description, description: shift.description,
latitude: shift.latitude, latitude: shift.latitude,
longitude: shift.longitude, longitude: shift.longitude,
), ),
); );
} }
return shifts; return shifts;
});
} }
@override @override
Future<AttendanceStatus> getAttendanceStatus() async { Future<AttendanceStatus> getAttendanceStatus() async {
final String staffId = await _getStaffId(); return _service.run(() async {
final List<dc.GetApplicationsByStaffIdApplications> apps = final String staffId = await _service.getStaffId();
await _getTodaysApplications(staffId); final List<dc.GetApplicationsByStaffIdApplications> apps =
if (apps.isEmpty) { await _getTodaysApplications(staffId);
return const AttendanceStatus(isCheckedIn: false); if (apps.isEmpty) {
} return const AttendanceStatus(isCheckedIn: false);
}
dc.GetApplicationsByStaffIdApplications? activeApp; dc.GetApplicationsByStaffIdApplications? activeApp;
for (final dc.GetApplicationsByStaffIdApplications app in apps) { for (final dc.GetApplicationsByStaffIdApplications app in apps) {
if (app.checkInTime != null && app.checkOutTime == null) { if (app.checkInTime != null && app.checkOutTime == null) {
if (activeApp == 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; activeApp = app;
} else {
final DateTime? current = _service.toDateTime(activeApp.checkInTime);
final DateTime? next = _service.toDateTime(app.checkInTime);
if (current == null || (next != null && next.isAfter(current))) {
activeApp = app;
}
} }
} }
} }
}
if (activeApp == null) { if (activeApp == null) {
_activeApplicationId = null; _activeApplicationId = null;
return const AttendanceStatus(isCheckedIn: false); return const AttendanceStatus(isCheckedIn: false);
} }
_activeApplicationId = activeApp.id; _activeApplicationId = activeApp.id;
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);
} catch (_) {} app = apps.firstWhere(
} (dc.GetApplicationsByStaffIdApplications a) => a.id == cachedAppId);
app ??= (await _getTodaysApplications(staffId)) } catch (_) {}
.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId); }
app ??= (await _getTodaysApplications(staffId)).firstWhere(
(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,
) )
.checkInTime(checkInTs) .checkInTime(checkInTs)
.execute()); .execute());
_activeApplicationId = app.id; _activeApplicationId = app.id;
return getAttendanceStatus(); return getAttendanceStatus();
});
} }
@override @override
@@ -244,32 +201,35 @@ 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,
.getApplicationById(id: targetAppId) dc.GetApplicationByIdVariables> appResult =
.execute()); await _service.run(() => _service.connector
final dc.GetApplicationByIdApplication? app = appResult.data.application; .getApplicationById(id: targetAppId)
.execute());
final dc.GetApplicationByIdApplication? app = appResult.data.application;
if (app == null) { if (app == null) {
throw Exception('Application not found for checkout'); throw Exception('Application not found for checkout');
} }
if (app.checkInTime == null || app.checkOutTime != null) { if (app.checkInTime == null || app.checkOutTime != null) {
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);