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: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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user