feat(clock-in): Refactor ClockInRepositoryImpl to utilize DataConnectService and simplify dependency injection
This commit is contained in:
@@ -1,69 +1,17 @@
|
||||
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_domain/krow_domain.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../../domain/repositories/clock_in_repository_interface.dart';
|
||||
|
||||
/// Implementation of [ClockInRepositoryInterface] using Firebase Data Connect.
|
||||
class ClockInRepositoryImpl
|
||||
with dc.DataErrorHandler
|
||||
implements ClockInRepositoryInterface {
|
||||
class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||
ClockInRepositoryImpl() : _service = dc.DataConnectService.instance;
|
||||
|
||||
ClockInRepositoryImpl({
|
||||
required dc.ExampleConnector dataConnect,
|
||||
}) : _dataConnect = dataConnect;
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
final dc.DataConnectService _service;
|
||||
final Map<String, String> _shiftToApplicationId = <String, String>{};
|
||||
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) {
|
||||
final DateTime dayStartUtc = DateTime.utc(
|
||||
localDay.year,
|
||||
@@ -81,8 +29,8 @@ class ClockInRepositoryImpl
|
||||
999,
|
||||
);
|
||||
return (
|
||||
start: _fromDateTime(dayStartUtc),
|
||||
end: _fromDateTime(dayEndUtc),
|
||||
start: _service.toTimestamp(dayStartUtc),
|
||||
end: _service.toTimestamp(dayEndUtc),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,26 +41,29 @@ class ClockInRepositoryImpl
|
||||
final DateTime now = DateTime.now();
|
||||
final ({fdc.Timestamp start, fdc.Timestamp end}) range = _utcDayRange(now);
|
||||
final fdc.QueryResult<dc.GetApplicationsByStaffIdData,
|
||||
dc.GetApplicationsByStaffIdVariables> result = await executeProtected(
|
||||
() => _dataConnect
|
||||
dc.GetApplicationsByStaffIdVariables> result = await _service.run(
|
||||
() => _service.connector
|
||||
.getApplicationsByStaffId(staffId: staffId)
|
||||
.dayStart(range.start)
|
||||
.dayEnd(range.end)
|
||||
.execute(),
|
||||
);
|
||||
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps = result.data.applications;
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
result.data.applications;
|
||||
if (apps.isEmpty) return const <dc.GetApplicationsByStaffIdApplications>[];
|
||||
|
||||
_shiftToApplicationId
|
||||
..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 =
|
||||
_toDateTime(a.shift.startTime) ?? _toDateTime(a.shift.date);
|
||||
_service.toDateTime(a.shift.startTime) ?? _service.toDateTime(a.shift.date);
|
||||
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) return -1;
|
||||
if (bTime == null) return 1;
|
||||
@@ -124,118 +75,124 @@ class ClockInRepositoryImpl
|
||||
return apps;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Future<List<Shift>> getTodaysShifts() async {
|
||||
final String staffId = await _getStaffId();
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
if (apps.isEmpty) return const <Shift>[];
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
if (apps.isEmpty) return const <Shift>[];
|
||||
|
||||
final List<Shift> shifts = <Shift>[];
|
||||
for (final dc.GetApplicationsByStaffIdApplications app in apps) {
|
||||
final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift;
|
||||
final DateTime? startDt = _toDateTime(app.shiftRole.startTime);
|
||||
final DateTime? endDt = _toDateTime(app.shiftRole.endTime);
|
||||
final DateTime? createdDt = _toDateTime(app.createdAt);
|
||||
final List<Shift> shifts = <Shift>[];
|
||||
for (final dc.GetApplicationsByStaffIdApplications app in apps) {
|
||||
final dc.GetApplicationsByStaffIdApplicationsShift shift = app.shift;
|
||||
final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime);
|
||||
final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime);
|
||||
final DateTime? createdDt = _service.toDateTime(app.createdAt);
|
||||
|
||||
final String roleName = app.shiftRole.role.name;
|
||||
final String orderName =
|
||||
(shift.order.eventName ?? '').trim().isNotEmpty
|
||||
? shift.order.eventName!
|
||||
: shift.order.business.businessName;
|
||||
final String title = '$roleName - $orderName';
|
||||
shifts.add(
|
||||
Shift(
|
||||
id: shift.id,
|
||||
title: title,
|
||||
clientName: shift.order.business.businessName,
|
||||
logoUrl: shift.order.business.companyLogoUrl ?? '',
|
||||
hourlyRate: app.shiftRole.role.costPerHour,
|
||||
location: shift.location ?? '',
|
||||
locationAddress: shift.order.teamHub.hubName,
|
||||
date: startDt?.toIso8601String() ?? '',
|
||||
startTime: startDt?.toIso8601String() ?? '',
|
||||
endTime: endDt?.toIso8601String() ?? '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: shift.status?.stringValue,
|
||||
description: shift.description,
|
||||
latitude: shift.latitude,
|
||||
longitude: shift.longitude,
|
||||
),
|
||||
);
|
||||
}
|
||||
final String roleName = app.shiftRole.role.name;
|
||||
final String orderName =
|
||||
(shift.order.eventName ?? '').trim().isNotEmpty
|
||||
? shift.order.eventName!
|
||||
: shift.order.business.businessName;
|
||||
final String title = '$roleName - $orderName';
|
||||
shifts.add(
|
||||
Shift(
|
||||
id: shift.id,
|
||||
title: title,
|
||||
clientName: shift.order.business.businessName,
|
||||
logoUrl: shift.order.business.companyLogoUrl ?? '',
|
||||
hourlyRate: app.shiftRole.role.costPerHour,
|
||||
location: shift.location ?? '',
|
||||
locationAddress: shift.order.teamHub.hubName,
|
||||
date: startDt?.toIso8601String() ?? '',
|
||||
startTime: startDt?.toIso8601String() ?? '',
|
||||
endTime: endDt?.toIso8601String() ?? '',
|
||||
createdDate: createdDt?.toIso8601String() ?? '',
|
||||
status: shift.status?.stringValue,
|
||||
description: shift.description,
|
||||
latitude: shift.latitude,
|
||||
longitude: shift.longitude,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return shifts;
|
||||
return shifts;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AttendanceStatus> getAttendanceStatus() async {
|
||||
final String staffId = await _getStaffId();
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
if (apps.isEmpty) {
|
||||
return const AttendanceStatus(isCheckedIn: false);
|
||||
}
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
if (apps.isEmpty) {
|
||||
return const AttendanceStatus(isCheckedIn: false);
|
||||
}
|
||||
|
||||
dc.GetApplicationsByStaffIdApplications? activeApp;
|
||||
for (final dc.GetApplicationsByStaffIdApplications 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))) {
|
||||
dc.GetApplicationsByStaffIdApplications? activeApp;
|
||||
for (final dc.GetApplicationsByStaffIdApplications app in apps) {
|
||||
if (app.checkInTime != null && app.checkOutTime == null) {
|
||||
if (activeApp == null) {
|
||||
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) {
|
||||
_activeApplicationId = null;
|
||||
return const AttendanceStatus(isCheckedIn: false);
|
||||
}
|
||||
if (activeApp == null) {
|
||||
_activeApplicationId = null;
|
||||
return const AttendanceStatus(isCheckedIn: false);
|
||||
}
|
||||
|
||||
_activeApplicationId = activeApp.id;
|
||||
_activeApplicationId = activeApp.id;
|
||||
|
||||
return AttendanceStatus(
|
||||
isCheckedIn: true,
|
||||
checkInTime: _toDateTime(activeApp.checkInTime),
|
||||
checkOutTime: _toDateTime(activeApp.checkOutTime),
|
||||
activeShiftId: activeApp.shiftId,
|
||||
activeApplicationId: activeApp.id,
|
||||
);
|
||||
return AttendanceStatus(
|
||||
isCheckedIn: true,
|
||||
checkInTime: _service.toDateTime(activeApp.checkInTime),
|
||||
checkOutTime: _service.toDateTime(activeApp.checkOutTime),
|
||||
activeShiftId: activeApp.shiftId,
|
||||
activeApplicationId: activeApp.id,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
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];
|
||||
dc.GetApplicationsByStaffIdApplications? app;
|
||||
if (cachedAppId != null) {
|
||||
try {
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps = await _getTodaysApplications(staffId);
|
||||
app = apps.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.id == cachedAppId);
|
||||
} catch (_) {}
|
||||
}
|
||||
app ??= (await _getTodaysApplications(staffId))
|
||||
.firstWhere((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId);
|
||||
final String? cachedAppId = _shiftToApplicationId[shiftId];
|
||||
dc.GetApplicationsByStaffIdApplications? app;
|
||||
if (cachedAppId != null) {
|
||||
try {
|
||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
||||
await _getTodaysApplications(staffId);
|
||||
app = apps.firstWhere(
|
||||
(dc.GetApplicationsByStaffIdApplications a) => a.id == cachedAppId);
|
||||
} catch (_) {}
|
||||
}
|
||||
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
|
||||
.updateApplicationStatus(
|
||||
id: app!.id,
|
||||
)
|
||||
.checkInTime(checkInTs)
|
||||
.execute());
|
||||
_activeApplicationId = app.id;
|
||||
await _service.run(() => _service.connector
|
||||
.updateApplicationStatus(
|
||||
id: app!.id,
|
||||
)
|
||||
.checkInTime(checkInTs)
|
||||
.execute());
|
||||
_activeApplicationId = app.id;
|
||||
|
||||
return getAttendanceStatus();
|
||||
return getAttendanceStatus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -244,32 +201,35 @@ class ClockInRepositoryImpl
|
||||
int? breakTimeMinutes,
|
||||
String? applicationId,
|
||||
}) async {
|
||||
await _getStaffId(); // Validate session
|
||||
|
||||
return _service.run(() async {
|
||||
await _service.getStaffId(); // Validate session
|
||||
|
||||
final String? targetAppId = applicationId ?? _activeApplicationId;
|
||||
if (targetAppId == null || targetAppId.isEmpty) {
|
||||
throw Exception('No active application id for checkout');
|
||||
}
|
||||
final fdc.QueryResult<dc.GetApplicationByIdData, dc.GetApplicationByIdVariables> appResult = await executeProtected(() => _dataConnect
|
||||
.getApplicationById(id: targetAppId)
|
||||
.execute());
|
||||
final dc.GetApplicationByIdApplication? app = appResult.data.application;
|
||||
final String? targetAppId = applicationId ?? _activeApplicationId;
|
||||
if (targetAppId == null || targetAppId.isEmpty) {
|
||||
throw Exception('No active application id for checkout');
|
||||
}
|
||||
final fdc.QueryResult<dc.GetApplicationByIdData,
|
||||
dc.GetApplicationByIdVariables> appResult =
|
||||
await _service.run(() => _service.connector
|
||||
.getApplicationById(id: targetAppId)
|
||||
.execute());
|
||||
final dc.GetApplicationByIdApplication? app = appResult.data.application;
|
||||
|
||||
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');
|
||||
}
|
||||
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 executeProtected(() => _dataConnect
|
||||
.updateApplicationStatus(
|
||||
id: targetAppId,
|
||||
)
|
||||
.checkOutTime(_fromDateTime(DateTime.now()))
|
||||
.execute());
|
||||
await _service.run(() => _service.connector
|
||||
.updateApplicationStatus(
|
||||
id: targetAppId,
|
||||
)
|
||||
.checkOutTime(_service.toTimestamp(DateTime.now()))
|
||||
.execute());
|
||||
|
||||
return getAttendanceStatus();
|
||||
return getAttendanceStatus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_modular/flutter_modular.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 'domain/repositories/clock_in_repository_interface.dart';
|
||||
@@ -16,9 +15,7 @@ class StaffClockInModule extends Module {
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.add<ClockInRepositoryInterface>(
|
||||
() => ClockInRepositoryImpl(dataConnect: ExampleConnector.instance),
|
||||
);
|
||||
i.add<ClockInRepositoryInterface>(ClockInRepositoryImpl.new);
|
||||
|
||||
// Use Cases
|
||||
i.add<GetTodaysShiftUseCase>(GetTodaysShiftUseCase.new);
|
||||
|
||||
Reference in New Issue
Block a user