Merge pull request #426 from Oloodi/408-feature-implement-paidunpaid-breaks---client-app-frontend-development
Implement a centralized DataConnectService to centralize the DataConnectCalls
This commit is contained in:
@@ -13,6 +13,7 @@ export 'src/session/client_session_store.dart';
|
|||||||
|
|
||||||
// Export the generated Data Connect SDK
|
// Export the generated Data Connect SDK
|
||||||
export 'src/dataconnect_generated/generated.dart';
|
export 'src/dataconnect_generated/generated.dart';
|
||||||
|
export 'src/services/data_connect_service.dart';
|
||||||
|
|
||||||
export 'src/session/staff_session_store.dart';
|
export 'src/session/staff_session_store.dart';
|
||||||
export 'src/mixins/data_error_handler.dart';
|
export 'src/mixins/data_error_handler.dart';
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
/// Mixin to handle Data Layer errors and map them to Domain Failures.
|
/// Mixin to handle Data Layer errors and map them to Domain Failures.
|
||||||
@@ -62,7 +63,7 @@ mixin DataErrorHandler {
|
|||||||
if (e is AppException) rethrow;
|
if (e is AppException) rethrow;
|
||||||
|
|
||||||
// Debugging: Log unexpected errors
|
// Debugging: Log unexpected errors
|
||||||
print('DataErrorHandler: Unhandled exception caught: $e');
|
debugPrint('DataErrorHandler: Unhandled exception caught: $e');
|
||||||
|
|
||||||
throw UnknownException(technicalMessage: e.toString());
|
throw UnknownException(technicalMessage: e.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
|
||||||
|
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import '../../krow_data_connect.dart' as dc;
|
||||||
|
import '../mixins/data_error_handler.dart';
|
||||||
|
|
||||||
|
/// A centralized service for interacting with Firebase Data Connect.
|
||||||
|
///
|
||||||
|
/// This service provides common utilities and context management for all repositories.
|
||||||
|
class DataConnectService with DataErrorHandler {
|
||||||
|
DataConnectService._();
|
||||||
|
|
||||||
|
/// The singleton instance of the [DataConnectService].
|
||||||
|
static final DataConnectService instance = DataConnectService._();
|
||||||
|
|
||||||
|
/// The Data Connect connector used for data operations.
|
||||||
|
final dc.ExampleConnector connector = dc.ExampleConnector.instance;
|
||||||
|
|
||||||
|
/// The Firebase Auth instance.
|
||||||
|
firebase_auth.FirebaseAuth get auth => _auth;
|
||||||
|
final firebase_auth.FirebaseAuth _auth = firebase_auth.FirebaseAuth.instance;
|
||||||
|
|
||||||
|
/// Cache for the current staff ID to avoid redundant lookups.
|
||||||
|
String? _cachedStaffId;
|
||||||
|
|
||||||
|
/// Gets the current staff ID from session store or persistent storage.
|
||||||
|
Future<String> getStaffId() async {
|
||||||
|
// 1. Check Session Store
|
||||||
|
final dc.StaffSession? session = dc.StaffSessionStore.instance.session;
|
||||||
|
if (session?.staff?.id != null) {
|
||||||
|
return session!.staff!.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check Cache
|
||||||
|
if (_cachedStaffId != null) return _cachedStaffId!;
|
||||||
|
|
||||||
|
// 3. Fetch from Data Connect using Firebase UID
|
||||||
|
final firebase_auth.User? user = _auth.currentUser;
|
||||||
|
if (user == null) {
|
||||||
|
throw Exception('User is not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final fdc.QueryResult<
|
||||||
|
dc.GetStaffByUserIdData,
|
||||||
|
dc.GetStaffByUserIdVariables
|
||||||
|
>
|
||||||
|
response = await executeProtected(
|
||||||
|
() => connector.getStaffByUserId(userId: user.uid).execute(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data.staffs.isNotEmpty) {
|
||||||
|
_cachedStaffId = response.data.staffs.first.id;
|
||||||
|
return _cachedStaffId!;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to fetch staff ID from Data Connect: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Fallback (should ideally not happen if DB is seeded)
|
||||||
|
return user.uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a Data Connect timestamp/string/json to a [DateTime].
|
||||||
|
DateTime? toDateTime(dynamic t) {
|
||||||
|
if (t == null) return null;
|
||||||
|
DateTime? dt;
|
||||||
|
if (t is fdc.Timestamp) {
|
||||||
|
dt = t.toDateTime();
|
||||||
|
} else if (t is String) {
|
||||||
|
dt = DateTime.tryParse(t);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
dt = DateTime.tryParse(t.toJson() as String);
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
dt = DateTime.tryParse(t.toString());
|
||||||
|
} catch (e) {
|
||||||
|
dt = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dt != null) {
|
||||||
|
return DateTimeUtils.toDeviceTime(dt);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a [DateTime] to a Firebase Data Connect [Timestamp].
|
||||||
|
fdc.Timestamp toTimestamp(DateTime dateTime) {
|
||||||
|
final DateTime utc = dateTime.toUtc();
|
||||||
|
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
||||||
|
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||||
|
return fdc.Timestamp(nanoseconds, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Unified Execution ---
|
||||||
|
// Repositories call this to benefit from centralized error handling/logging
|
||||||
|
Future<T> run<T>(
|
||||||
|
Future<T> Function() action, {
|
||||||
|
bool requiresAuthentication = true,
|
||||||
|
}) {
|
||||||
|
if (requiresAuthentication && auth.currentUser == null) {
|
||||||
|
throw const NotAuthenticatedException(
|
||||||
|
technicalMessage: 'User must be authenticated to perform this action',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return executeProtected(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the internal cache (e.g., on logout).
|
||||||
|
void clearCache() {
|
||||||
|
_cachedStaffId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,3 +15,6 @@ dependencies:
|
|||||||
path: ../domain
|
path: ../domain
|
||||||
flutter_modular: ^6.3.0
|
flutter_modular: ^6.3.0
|
||||||
firebase_data_connect: ^0.2.2+2
|
firebase_data_connect: ^0.2.2+2
|
||||||
|
firebase_core: ^4.4.0
|
||||||
|
firebase_auth: ^6.1.4
|
||||||
|
krow_core: ^0.0.1
|
||||||
|
|||||||
@@ -10,20 +10,14 @@ import '../../domain/ui_entities/auth_mode.dart';
|
|||||||
import '../../domain/repositories/auth_repository_interface.dart';
|
import '../../domain/repositories/auth_repository_interface.dart';
|
||||||
|
|
||||||
/// Implementation of [AuthRepositoryInterface].
|
/// Implementation of [AuthRepositoryInterface].
|
||||||
class AuthRepositoryImpl
|
class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||||
with DataErrorHandler
|
AuthRepositoryImpl() : _service = DataConnectService.instance;
|
||||||
implements AuthRepositoryInterface {
|
|
||||||
AuthRepositoryImpl({
|
|
||||||
required this.firebaseAuth,
|
|
||||||
required this.dataConnect,
|
|
||||||
});
|
|
||||||
|
|
||||||
final FirebaseAuth firebaseAuth;
|
final DataConnectService _service;
|
||||||
final ExampleConnector dataConnect;
|
|
||||||
Completer<String?>? _pendingVerification;
|
Completer<String?>? _pendingVerification;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<domain.User?> get currentUser => firebaseAuth
|
Stream<domain.User?> get currentUser => _service.auth
|
||||||
.authStateChanges()
|
.authStateChanges()
|
||||||
.map((User? firebaseUser) {
|
.map((User? firebaseUser) {
|
||||||
if (firebaseUser == null) {
|
if (firebaseUser == null) {
|
||||||
@@ -44,7 +38,7 @@ class AuthRepositoryImpl
|
|||||||
final Completer<String?> completer = Completer<String?>();
|
final Completer<String?> completer = Completer<String?>();
|
||||||
_pendingVerification = completer;
|
_pendingVerification = completer;
|
||||||
|
|
||||||
await firebaseAuth.verifyPhoneNumber(
|
await _service.auth.verifyPhoneNumber(
|
||||||
phoneNumber: phoneNumber,
|
phoneNumber: phoneNumber,
|
||||||
verificationCompleted: (PhoneAuthCredential credential) {
|
verificationCompleted: (PhoneAuthCredential credential) {
|
||||||
// Skip auto-verification for test numbers to allow manual code entry
|
// Skip auto-verification for test numbers to allow manual code entry
|
||||||
@@ -101,7 +95,8 @@ class AuthRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<void> signOut() {
|
Future<void> signOut() {
|
||||||
StaffSessionStore.instance.clear();
|
StaffSessionStore.instance.clear();
|
||||||
return firebaseAuth.signOut();
|
_service.clearCache();
|
||||||
|
return _service.auth.signOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies an OTP code and returns the authenticated user.
|
/// Verifies an OTP code and returns the authenticated user.
|
||||||
@@ -115,10 +110,10 @@ class AuthRepositoryImpl
|
|||||||
verificationId: verificationId,
|
verificationId: verificationId,
|
||||||
smsCode: smsCode,
|
smsCode: smsCode,
|
||||||
);
|
);
|
||||||
final UserCredential userCredential = await executeProtected(
|
final UserCredential userCredential = await _service.run(
|
||||||
() async {
|
() async {
|
||||||
try {
|
try {
|
||||||
return await firebaseAuth.signInWithCredential(credential);
|
return await _service.auth.signInWithCredential(credential);
|
||||||
} on FirebaseAuthException catch (e) {
|
} on FirebaseAuthException catch (e) {
|
||||||
if (e.code == 'invalid-verification-code') {
|
if (e.code == 'invalid-verification-code') {
|
||||||
throw const domain.InvalidCredentialsException(
|
throw const domain.InvalidCredentialsException(
|
||||||
@@ -128,45 +123,56 @@ class AuthRepositoryImpl
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
requiresAuthentication: false,
|
||||||
);
|
);
|
||||||
final User? firebaseUser = userCredential.user;
|
final User? firebaseUser = userCredential.user;
|
||||||
if (firebaseUser == null) {
|
if (firebaseUser == null) {
|
||||||
throw const domain.SignInFailedException(
|
throw const domain.SignInFailedException(
|
||||||
technicalMessage: 'Phone verification failed, no Firebase user received.',
|
technicalMessage:
|
||||||
|
'Phone verification failed, no Firebase user received.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final QueryResult<GetUserByIdData, GetUserByIdVariables> response =
|
final QueryResult<GetUserByIdData, GetUserByIdVariables> response =
|
||||||
await executeProtected(() => dataConnect
|
await _service.run(
|
||||||
.getUserById(
|
() => _service.connector
|
||||||
id: firebaseUser.uid,
|
.getUserById(
|
||||||
)
|
id: firebaseUser.uid,
|
||||||
.execute());
|
)
|
||||||
|
.execute(),
|
||||||
|
requiresAuthentication: false,
|
||||||
|
);
|
||||||
final GetUserByIdUser? user = response.data.user;
|
final GetUserByIdUser? user = response.data.user;
|
||||||
|
|
||||||
GetStaffByUserIdStaffs? staffRecord;
|
GetStaffByUserIdStaffs? staffRecord;
|
||||||
|
|
||||||
if (mode == AuthMode.signup) {
|
if (mode == AuthMode.signup) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
await executeProtected(() => dataConnect
|
await _service.run(
|
||||||
.createUser(
|
() => _service.connector
|
||||||
id: firebaseUser.uid,
|
.createUser(
|
||||||
role: UserBaseRole.USER,
|
id: firebaseUser.uid,
|
||||||
)
|
role: UserBaseRole.USER,
|
||||||
.userRole('STAFF')
|
)
|
||||||
.execute());
|
.userRole('STAFF')
|
||||||
|
.execute(),
|
||||||
|
requiresAuthentication: false,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// User exists in PostgreSQL. Check if they have a STAFF profile.
|
// User exists in PostgreSQL. Check if they have a STAFF profile.
|
||||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||||
staffResponse = await executeProtected(() => dataConnect
|
staffResponse = await _service.run(
|
||||||
.getStaffByUserId(
|
() => _service.connector
|
||||||
userId: firebaseUser.uid,
|
.getStaffByUserId(
|
||||||
)
|
userId: firebaseUser.uid,
|
||||||
.execute());
|
)
|
||||||
|
.execute(),
|
||||||
|
requiresAuthentication: false,
|
||||||
|
);
|
||||||
|
|
||||||
if (staffResponse.data.staffs.isNotEmpty) {
|
if (staffResponse.data.staffs.isNotEmpty) {
|
||||||
// If profile exists, they should use Login mode.
|
// If profile exists, they should use Login mode.
|
||||||
await firebaseAuth.signOut();
|
await _service.auth.signOut();
|
||||||
throw const domain.AccountExistsException(
|
throw const domain.AccountExistsException(
|
||||||
technicalMessage:
|
technicalMessage:
|
||||||
'This user already has a staff profile. Please log in.',
|
'This user already has a staff profile. Please log in.',
|
||||||
@@ -177,35 +183,44 @@ class AuthRepositoryImpl
|
|||||||
// they are allowed to "Sign Up" for Staff.
|
// they are allowed to "Sign Up" for Staff.
|
||||||
// We update their userRole to 'BOTH'.
|
// We update their userRole to 'BOTH'.
|
||||||
if (user.userRole == 'BUSINESS') {
|
if (user.userRole == 'BUSINESS') {
|
||||||
await executeProtected(() =>
|
await _service.run(
|
||||||
dataConnect.updateUser(id: firebaseUser.uid).userRole('BOTH').execute());
|
() => _service.connector
|
||||||
|
.updateUser(id: firebaseUser.uid)
|
||||||
|
.userRole('BOTH')
|
||||||
|
.execute(),
|
||||||
|
requiresAuthentication: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
await firebaseAuth.signOut();
|
await _service.auth.signOut();
|
||||||
throw const domain.UserNotFoundException(
|
throw const domain.UserNotFoundException(
|
||||||
technicalMessage: 'Authenticated user profile not found in database.',
|
technicalMessage: 'Authenticated user profile not found in database.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Allow STAFF or BOTH roles to log in to the Staff App
|
// Allow STAFF or BOTH roles to log in to the Staff App
|
||||||
if (user.userRole != 'STAFF' && user.userRole != 'BOTH') {
|
if (user.userRole != 'STAFF' && user.userRole != 'BOTH') {
|
||||||
await firebaseAuth.signOut();
|
await _service.auth.signOut();
|
||||||
throw const domain.UnauthorizedAppException(
|
throw const domain.UnauthorizedAppException(
|
||||||
technicalMessage: 'User is not authorized for this app.',
|
technicalMessage: 'User is not authorized for this app.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||||
staffResponse = await executeProtected(() => dataConnect
|
staffResponse = await _service.run(
|
||||||
.getStaffByUserId(
|
() => _service.connector
|
||||||
userId: firebaseUser.uid,
|
.getStaffByUserId(
|
||||||
)
|
userId: firebaseUser.uid,
|
||||||
.execute());
|
)
|
||||||
|
.execute(),
|
||||||
|
requiresAuthentication: false,
|
||||||
|
);
|
||||||
if (staffResponse.data.staffs.isEmpty) {
|
if (staffResponse.data.staffs.isEmpty) {
|
||||||
await firebaseAuth.signOut();
|
await _service.auth.signOut();
|
||||||
throw const domain.UserNotFoundException(
|
throw const domain.UserNotFoundException(
|
||||||
technicalMessage: 'Your account is not registered yet. Please register first.',
|
technicalMessage:
|
||||||
|
'Your account is not registered yet. Please register first.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
staffRecord = staffResponse.data.staffs.first;
|
staffRecord = staffResponse.data.staffs.first;
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
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_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
||||||
import '../../domain/repositories/profile_setup_repository.dart';
|
import '../../domain/repositories/profile_setup_repository.dart';
|
||||||
|
|
||||||
class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
||||||
final auth.FirebaseAuth _firebaseAuth;
|
final DataConnectService _service;
|
||||||
final ExampleConnector _dataConnect;
|
|
||||||
|
|
||||||
ProfileSetupRepositoryImpl({
|
ProfileSetupRepositoryImpl() : _service = DataConnectService.instance;
|
||||||
required auth.FirebaseAuth firebaseAuth,
|
|
||||||
required ExampleConnector dataConnect,
|
|
||||||
}) : _firebaseAuth = firebaseAuth,
|
|
||||||
_dataConnect = dataConnect;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> submitProfile({
|
Future<void> submitProfile({
|
||||||
@@ -23,17 +18,19 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
|||||||
required List<String> industries,
|
required List<String> industries,
|
||||||
required List<String> skills,
|
required List<String> skills,
|
||||||
}) async {
|
}) async {
|
||||||
final auth.User? firebaseUser = _firebaseAuth.currentUser;
|
return _service.run(() async {
|
||||||
if (firebaseUser == null) {
|
final auth.User? firebaseUser = _service.auth.currentUser;
|
||||||
throw Exception('User not authenticated.');
|
if (firebaseUser == null) {
|
||||||
}
|
throw const NotAuthenticatedException(
|
||||||
|
technicalMessage: 'User not authenticated.');
|
||||||
|
}
|
||||||
|
|
||||||
final StaffSession? session = StaffSessionStore.instance.session;
|
final StaffSession? session = StaffSessionStore.instance.session;
|
||||||
final String email = session?.user.email ?? '';
|
final String email = session?.user.email ?? '';
|
||||||
final String? phone = firebaseUser.phoneNumber;
|
final String? phone = firebaseUser.phoneNumber;
|
||||||
|
|
||||||
final fdc.OperationResult<CreateStaffData, CreateStaffVariables>
|
final fdc.OperationResult<CreateStaffData, CreateStaffVariables> result =
|
||||||
result = await _dataConnect
|
await _service.connector
|
||||||
.createStaff(
|
.createStaff(
|
||||||
userId: firebaseUser.uid,
|
userId: firebaseUser.uid,
|
||||||
fullName: fullName,
|
fullName: fullName,
|
||||||
@@ -63,5 +60,6 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
|||||||
StaffSession(user: session.user, staff: staff, ownerId: session.ownerId),
|
StaffSession(user: session.user, staff: staff, ownerId: session.ownerId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.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 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
|
||||||
import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart';
|
import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart';
|
||||||
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
|
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
|
||||||
import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart';
|
import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart';
|
||||||
@@ -28,18 +27,8 @@ class StaffAuthenticationModule extends Module {
|
|||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repositories
|
// Repositories
|
||||||
i.addLazySingleton<AuthRepositoryInterface>(
|
i.addLazySingleton<AuthRepositoryInterface>(AuthRepositoryImpl.new);
|
||||||
() => AuthRepositoryImpl(
|
i.addLazySingleton<ProfileSetupRepository>(ProfileSetupRepositoryImpl.new);
|
||||||
firebaseAuth: firebase.FirebaseAuth.instance,
|
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
i.addLazySingleton<ProfileSetupRepository>(
|
|
||||||
() => ProfileSetupRepositoryImpl(
|
|
||||||
firebaseAuth: firebase.FirebaseAuth.instance,
|
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
i.addLazySingleton<PlaceRepository>(PlaceRepositoryImpl.new);
|
i.addLazySingleton<PlaceRepository>(PlaceRepositoryImpl.new);
|
||||||
|
|
||||||
// UseCases
|
// UseCases
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
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';
|
||||||
@@ -10,44 +9,19 @@ import '../../domain/repositories/availability_repository.dart';
|
|||||||
/// not specific date availability. Therefore, updating availability for a specific
|
/// not specific date availability. Therefore, updating availability for a specific
|
||||||
/// date will update the availability for that Day of Week globally (Recurring).
|
/// date will update the availability for that Day of Week globally (Recurring).
|
||||||
class AvailabilityRepositoryImpl
|
class AvailabilityRepositoryImpl
|
||||||
with dc.DataErrorHandler
|
|
||||||
implements AvailabilityRepository {
|
implements AvailabilityRepository {
|
||||||
final dc.ExampleConnector _dataConnect;
|
final dc.DataConnectService _service;
|
||||||
final firebase.FirebaseAuth _firebaseAuth;
|
|
||||||
String? _cachedStaffId;
|
|
||||||
|
|
||||||
AvailabilityRepositoryImpl({
|
AvailabilityRepositoryImpl() : _service = dc.DataConnectService.instance;
|
||||||
required dc.ExampleConnector dataConnect,
|
|
||||||
required firebase.FirebaseAuth firebaseAuth,
|
|
||||||
}) : _dataConnect = dataConnect,
|
|
||||||
_firebaseAuth = firebaseAuth;
|
|
||||||
|
|
||||||
Future<String> _getStaffId() async {
|
|
||||||
if (_cachedStaffId != null) return _cachedStaffId!;
|
|
||||||
|
|
||||||
final firebase.User? user = _firebaseAuth.currentUser;
|
|
||||||
if (user == null) {
|
|
||||||
throw NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
final QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> result =
|
|
||||||
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
|
||||||
if (result.data.staffs.isEmpty) {
|
|
||||||
throw const ServerException(technicalMessage: 'Staff profile not found');
|
|
||||||
}
|
|
||||||
_cachedStaffId = result.data.staffs.first.id;
|
|
||||||
return _cachedStaffId!;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<DayAvailability>> getAvailability(DateTime start, DateTime end) async {
|
Future<List<DayAvailability>> getAvailability(DateTime start, DateTime end) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String staffId = await _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
|
|
||||||
// 1. Fetch Weekly recurring availability
|
// 1. Fetch Weekly recurring availability
|
||||||
final QueryResult<dc.ListStaffAvailabilitiesByStaffIdData, dc.ListStaffAvailabilitiesByStaffIdVariables> result =
|
final QueryResult<dc.ListStaffAvailabilitiesByStaffIdData, dc.ListStaffAvailabilitiesByStaffIdVariables> result =
|
||||||
await _dataConnect.listStaffAvailabilitiesByStaffId(staffId: staffId).limit(100).execute();
|
await _service.connector.listStaffAvailabilitiesByStaffId(staffId: staffId).limit(100).execute();
|
||||||
|
|
||||||
final List<dc.ListStaffAvailabilitiesByStaffIdStaffAvailabilities> items = result.data.staffAvailabilities;
|
final List<dc.ListStaffAvailabilitiesByStaffIdStaffAvailabilities> items = result.data.staffAvailabilities;
|
||||||
|
|
||||||
@@ -124,8 +98,8 @@ class AvailabilityRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DayAvailability> updateDayAvailability(DayAvailability availability) async {
|
Future<DayAvailability> updateDayAvailability(DayAvailability availability) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String staffId = await _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
final dc.DayOfWeek dow = _toBackendDay(availability.date.weekday);
|
final dc.DayOfWeek dow = _toBackendDay(availability.date.weekday);
|
||||||
|
|
||||||
// Update each slot in the backend.
|
// Update each slot in the backend.
|
||||||
@@ -143,8 +117,8 @@ class AvailabilityRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<DayAvailability>> applyQuickSet(DateTime start, DateTime end, String type) async {
|
Future<List<DayAvailability>> applyQuickSet(DateTime start, DateTime end, String type) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String staffId = await _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
|
|
||||||
// QuickSet updates the Recurring schedule for all days involved.
|
// QuickSet updates the Recurring schedule for all days involved.
|
||||||
// However, if the user selects a range that covers e.g. Mon-Fri, we update Mon-Fri.
|
// However, if the user selects a range that covers e.g. Mon-Fri, we update Mon-Fri.
|
||||||
@@ -204,7 +178,7 @@ class AvailabilityRepositoryImpl
|
|||||||
|
|
||||||
Future<void> _upsertSlot(String staffId, dc.DayOfWeek day, dc.AvailabilitySlot slot, dc.AvailabilityStatus status) async {
|
Future<void> _upsertSlot(String staffId, dc.DayOfWeek day, dc.AvailabilitySlot slot, dc.AvailabilityStatus status) async {
|
||||||
// Check if exists
|
// Check if exists
|
||||||
final result = await _dataConnect.getStaffAvailabilityByKey(
|
final result = await _service.connector.getStaffAvailabilityByKey(
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
day: day,
|
day: day,
|
||||||
slot: slot,
|
slot: slot,
|
||||||
@@ -212,14 +186,14 @@ class AvailabilityRepositoryImpl
|
|||||||
|
|
||||||
if (result.data.staffAvailability != null) {
|
if (result.data.staffAvailability != null) {
|
||||||
// Update
|
// Update
|
||||||
await _dataConnect.updateStaffAvailability(
|
await _service.connector.updateStaffAvailability(
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
day: day,
|
day: day,
|
||||||
slot: slot,
|
slot: slot,
|
||||||
).status(status).execute();
|
).status(status).execute();
|
||||||
} else {
|
} else {
|
||||||
// Create
|
// Create
|
||||||
await _dataConnect.createStaffAvailability(
|
await _service.connector.createStaffAvailability(
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
day: day,
|
day: day,
|
||||||
slot: slot,
|
slot: slot,
|
||||||
|
|||||||
@@ -18,12 +18,7 @@ class StaffAvailabilityModule extends Module {
|
|||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository
|
// Repository
|
||||||
i.add<AvailabilityRepository>(
|
i.add<AvailabilityRepository>(AvailabilityRepositoryImpl.new);
|
||||||
() => AvailabilityRepositoryImpl(
|
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// UseCases
|
// UseCases
|
||||||
i.add(GetWeeklyAvailabilityUseCase.new);
|
i.add(GetWeeklyAvailabilityUseCase.new);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -1,26 +1,13 @@
|
|||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import 'package:krow_core/core.dart';
|
|
||||||
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
||||||
|
|
||||||
extension TimestampExt on Timestamp {
|
|
||||||
DateTime toDate() {
|
|
||||||
return DateTimeUtils.toDeviceTime(toDateTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HomeRepositoryImpl
|
class HomeRepositoryImpl
|
||||||
with DataErrorHandler
|
|
||||||
implements HomeRepository {
|
implements HomeRepository {
|
||||||
HomeRepositoryImpl();
|
HomeRepositoryImpl() : _service = DataConnectService.instance;
|
||||||
|
|
||||||
String get _currentStaffId {
|
final DataConnectService _service;
|
||||||
final session = StaffSessionStore.instance.session;
|
|
||||||
if (session?.staff?.id == null) throw Exception('User not logged in');
|
|
||||||
return session!.staff!.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Shift>> getTodayShifts() async {
|
Future<List<Shift>> getTodayShifts() async {
|
||||||
@@ -33,59 +20,57 @@ class HomeRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Shift>> _getShiftsForDate(DateTime date) async {
|
Future<List<Shift>> _getShiftsForDate(DateTime date) async {
|
||||||
final staffId = _currentStaffId;
|
return _service.run(() async {
|
||||||
|
final staffId = await _service.getStaffId();
|
||||||
|
|
||||||
// Create start and end timestamps for the target date
|
// Create start and end timestamps for the target date
|
||||||
final DateTime start = DateTime(date.year, date.month, date.day);
|
final DateTime start = DateTime(date.year, date.month, date.day);
|
||||||
final DateTime end =
|
final DateTime end =
|
||||||
DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
|
DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
|
||||||
|
|
||||||
final response = await executeProtected(() => ExampleConnector.instance
|
final response = await _service.run(() => _service.connector
|
||||||
.getApplicationsByStaffId(staffId: staffId)
|
.getApplicationsByStaffId(staffId: staffId)
|
||||||
.dayStart(_toTimestamp(start))
|
.dayStart(_service.toTimestamp(start))
|
||||||
.dayEnd(_toTimestamp(end))
|
.dayEnd(_service.toTimestamp(end))
|
||||||
.execute());
|
.execute());
|
||||||
|
|
||||||
// Filter for CONFIRMED applications (same logic as shifts_repository_impl)
|
// Filter for CONFIRMED applications (same logic as shifts_repository_impl)
|
||||||
final apps = response.data.applications.where((app) =>
|
final apps = response.data.applications.where((app) =>
|
||||||
(app.status is Known &&
|
(app.status is Known &&
|
||||||
(app.status as Known).value == ApplicationStatus.CONFIRMED));
|
(app.status as Known).value == ApplicationStatus.CONFIRMED));
|
||||||
|
|
||||||
final List<Shift> shifts = [];
|
final List<Shift> shifts = [];
|
||||||
for (final app in apps) {
|
for (final app in apps) {
|
||||||
shifts.add(_mapApplicationToShift(app));
|
shifts.add(_mapApplicationToShift(app));
|
||||||
}
|
}
|
||||||
|
|
||||||
return shifts;
|
return shifts;
|
||||||
}
|
});
|
||||||
|
|
||||||
Timestamp _toTimestamp(DateTime dateTime) {
|
|
||||||
final DateTime utc = dateTime.toUtc();
|
|
||||||
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
|
||||||
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
|
||||||
return Timestamp(nanoseconds, seconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Shift>> getRecommendedShifts() async {
|
Future<List<Shift>> getRecommendedShifts() async {
|
||||||
// Logic: List ALL open shifts (simple recommendation engine)
|
// Logic: List ALL open shifts (simple recommendation engine)
|
||||||
// Limitation: listShifts might return ALL shifts. We should ideally filter by status=PUBLISHED.
|
// Limitation: listShifts might return ALL shifts. We should ideally filter by status=PUBLISHED.
|
||||||
final response = await executeProtected(() => ExampleConnector.instance.listShifts().execute());
|
return _service.run(() async {
|
||||||
|
final response =
|
||||||
|
await _service.run(() => _service.connector.listShifts().execute());
|
||||||
|
|
||||||
return response.data.shifts
|
return response.data.shifts
|
||||||
.where((s) {
|
.where((s) {
|
||||||
final isOpen =
|
final isOpen = s.status is Known &&
|
||||||
s.status is Known && (s.status as Known).value == ShiftStatus.OPEN;
|
(s.status as Known).value == ShiftStatus.OPEN;
|
||||||
if (!isOpen) return false;
|
if (!isOpen) return false;
|
||||||
|
|
||||||
final start = s.startTime?.toDate();
|
final start = _service.toDateTime(s.startTime);
|
||||||
if (start == null) return false;
|
if (start == null) return false;
|
||||||
|
|
||||||
return start.isAfter(DateTime.now());
|
return start.isAfter(DateTime.now());
|
||||||
})
|
})
|
||||||
.take(10)
|
.take(10)
|
||||||
.map((s) => _mapConnectorShiftToDomain(s))
|
.map((s) => _mapConnectorShiftToDomain(s))
|
||||||
.toList();
|
.toList();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -100,7 +85,7 @@ class HomeRepositoryImpl
|
|||||||
Shift _mapApplicationToShift(GetApplicationsByStaffIdApplications app) {
|
Shift _mapApplicationToShift(GetApplicationsByStaffIdApplications app) {
|
||||||
final s = app.shift;
|
final s = app.shift;
|
||||||
final r = app.shiftRole;
|
final r = app.shiftRole;
|
||||||
|
|
||||||
return ShiftAdapter.fromApplicationData(
|
return ShiftAdapter.fromApplicationData(
|
||||||
shiftId: s.id,
|
shiftId: s.id,
|
||||||
roleId: r.roleId,
|
roleId: r.roleId,
|
||||||
@@ -110,10 +95,10 @@ class HomeRepositoryImpl
|
|||||||
costPerHour: r.role.costPerHour,
|
costPerHour: r.role.costPerHour,
|
||||||
shiftLocation: s.location,
|
shiftLocation: s.location,
|
||||||
teamHubName: s.order.teamHub.hubName,
|
teamHubName: s.order.teamHub.hubName,
|
||||||
shiftDate: s.date?.toDate(),
|
shiftDate: _service.toDateTime(s.date),
|
||||||
startTime: r.startTime?.toDate(),
|
startTime: _service.toDateTime(r.startTime),
|
||||||
endTime: r.endTime?.toDate(),
|
endTime: _service.toDateTime(r.endTime),
|
||||||
createdAt: app.createdAt?.toDate(),
|
createdAt: _service.toDateTime(app.createdAt),
|
||||||
status: 'confirmed',
|
status: 'confirmed',
|
||||||
description: s.description,
|
description: s.description,
|
||||||
durationDays: s.durationDays,
|
durationDays: s.durationDays,
|
||||||
@@ -132,10 +117,12 @@ class HomeRepositoryImpl
|
|||||||
hourlyRate: s.cost ?? 0.0,
|
hourlyRate: s.cost ?? 0.0,
|
||||||
location: s.location ?? 'Unknown',
|
location: s.location ?? 'Unknown',
|
||||||
locationAddress: s.locationAddress ?? '',
|
locationAddress: s.locationAddress ?? '',
|
||||||
date: s.date?.toDate().toIso8601String() ?? '',
|
date: _service.toDateTime(s.date)?.toIso8601String() ?? '',
|
||||||
startTime: DateFormat('HH:mm').format(s.startTime?.toDate() ?? DateTime.now()),
|
startTime: DateFormat('HH:mm')
|
||||||
endTime: DateFormat('HH:mm').format(s.endTime?.toDate() ?? DateTime.now()),
|
.format(_service.toDateTime(s.startTime) ?? DateTime.now()),
|
||||||
createdDate: s.createdAt?.toDate().toIso8601String() ?? '',
|
endTime: DateFormat('HH:mm')
|
||||||
|
.format(_service.toDateTime(s.endTime) ?? DateTime.now()),
|
||||||
|
createdDate: _service.toDateTime(s.createdAt)?.toIso8601String() ?? '',
|
||||||
tipsAvailable: false,
|
tipsAvailable: false,
|
||||||
mealProvided: false,
|
mealProvided: false,
|
||||||
managers: [],
|
managers: [],
|
||||||
|
|||||||
@@ -1,97 +1,26 @@
|
|||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
|
||||||
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_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
|
|
||||||
import 'package:krow_core/core.dart';
|
|
||||||
import '../../domain/repositories/payments_repository.dart';
|
import '../../domain/repositories/payments_repository.dart';
|
||||||
|
|
||||||
class PaymentsRepositoryImpl
|
class PaymentsRepositoryImpl
|
||||||
with dc.DataErrorHandler
|
|
||||||
implements PaymentsRepository {
|
implements PaymentsRepository {
|
||||||
|
|
||||||
PaymentsRepositoryImpl() : _dataConnect = dc.ExampleConnector.instance;
|
PaymentsRepositoryImpl() : _service = DataConnectService.instance;
|
||||||
final dc.ExampleConnector _dataConnect;
|
final DataConnectService _service;
|
||||||
final firebase_auth.FirebaseAuth _auth = firebase_auth.FirebaseAuth.instance;
|
|
||||||
|
|
||||||
String? _cachedStaffId;
|
|
||||||
|
|
||||||
Future<String> _getStaffId() async {
|
|
||||||
// 1. Check Session Store
|
|
||||||
final dc.StaffSession? session = dc.StaffSessionStore.instance.session;
|
|
||||||
if (session?.staff?.id != null) {
|
|
||||||
return session!.staff!.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check Cache
|
|
||||||
if (_cachedStaffId != null) return _cachedStaffId!;
|
|
||||||
|
|
||||||
// 3. Fetch from Data Connect using Firebase UID
|
|
||||||
final firebase_auth.User? user = _auth.currentUser;
|
|
||||||
if (user == null) {
|
|
||||||
throw const NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User is not authenticated',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This call is protected by parent execution context if called within executeProtected,
|
|
||||||
// otherwise we might need to wrap it if called standalone.
|
|
||||||
// For now we assume it's called from public methods which are protected.
|
|
||||||
final QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> response = await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
|
||||||
if (response.data.staffs.isNotEmpty) {
|
|
||||||
_cachedStaffId = response.data.staffs.first.id;
|
|
||||||
return _cachedStaffId!;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Fallback
|
|
||||||
return user.uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper to convert Data Connect 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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PaymentSummary> getPaymentSummary() async {
|
Future<PaymentSummary> getPaymentSummary() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String currentStaffId = await _getStaffId();
|
final String currentStaffId = await _service.getStaffId();
|
||||||
|
|
||||||
// Fetch recent payments with a limit
|
// Fetch recent payments with a limit
|
||||||
// Note: limit is chained on the query builder
|
final response = await _service.connector.listRecentPaymentsByStaffId(
|
||||||
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> result =
|
|
||||||
await _dataConnect.listRecentPaymentsByStaffId(
|
|
||||||
staffId: currentStaffId,
|
staffId: currentStaffId,
|
||||||
).limit(100).execute();
|
).limit(100).execute();
|
||||||
|
|
||||||
final List<dc.ListRecentPaymentsByStaffIdRecentPayments> payments = result.data.recentPayments;
|
final List<dc.ListRecentPaymentsByStaffIdRecentPayments> payments = response.data.recentPayments;
|
||||||
|
|
||||||
double weekly = 0;
|
double weekly = 0;
|
||||||
double monthly = 0;
|
double monthly = 0;
|
||||||
@@ -103,7 +32,7 @@ class PaymentsRepositoryImpl
|
|||||||
final DateTime startOfMonth = DateTime(now.year, now.month, 1);
|
final DateTime startOfMonth = DateTime(now.year, now.month, 1);
|
||||||
|
|
||||||
for (final dc.ListRecentPaymentsByStaffIdRecentPayments p in payments) {
|
for (final dc.ListRecentPaymentsByStaffIdRecentPayments p in payments) {
|
||||||
final DateTime? date = _toDateTime(p.invoice.issueDate) ?? _toDateTime(p.createdAt);
|
final DateTime? date = _service.toDateTime(p.invoice.issueDate) ?? _service.toDateTime(p.createdAt);
|
||||||
final double amount = p.invoice.amount;
|
final double amount = p.invoice.amount;
|
||||||
final String? status = p.status?.stringValue;
|
final String? status = p.status?.stringValue;
|
||||||
|
|
||||||
@@ -129,11 +58,10 @@ class PaymentsRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<StaffPayment>> getPaymentHistory(String period) async {
|
Future<List<StaffPayment>> getPaymentHistory(String period) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String currentStaffId = await _getStaffId();
|
final String currentStaffId = await _service.getStaffId();
|
||||||
|
|
||||||
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> response =
|
final response = await _service.connector
|
||||||
await _dataConnect
|
|
||||||
.listRecentPaymentsByStaffId(staffId: currentStaffId)
|
.listRecentPaymentsByStaffId(staffId: currentStaffId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
@@ -144,7 +72,7 @@ class PaymentsRepositoryImpl
|
|||||||
assignmentId: payment.applicationId,
|
assignmentId: payment.applicationId,
|
||||||
amount: payment.invoice.amount,
|
amount: payment.invoice.amount,
|
||||||
status: PaymentAdapter.toPaymentStatus(payment.status?.stringValue ?? 'UNKNOWN'),
|
status: PaymentAdapter.toPaymentStatus(payment.status?.stringValue ?? 'UNKNOWN'),
|
||||||
paidAt: _toDateTime(payment.invoice.issueDate),
|
paidAt: _service.toDateTime(payment.invoice.issueDate),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
@@ -15,38 +14,23 @@ import '../../domain/repositories/profile_repository.dart';
|
|||||||
/// Currently uses [ProfileRepositoryMock] from data_connect.
|
/// Currently uses [ProfileRepositoryMock] from data_connect.
|
||||||
/// When Firebase Data Connect is ready, this will be swapped with a real implementation.
|
/// When Firebase Data Connect is ready, this will be swapped with a real implementation.
|
||||||
class ProfileRepositoryImpl
|
class ProfileRepositoryImpl
|
||||||
with DataErrorHandler
|
|
||||||
implements ProfileRepositoryInterface {
|
implements ProfileRepositoryInterface {
|
||||||
/// Creates a [ProfileRepositoryImpl].
|
/// Creates a [ProfileRepositoryImpl].
|
||||||
///
|
ProfileRepositoryImpl() : _service = DataConnectService.instance;
|
||||||
/// Requires a [ExampleConnector] from the data_connect package and [FirebaseAuth].
|
|
||||||
const ProfileRepositoryImpl({
|
|
||||||
required this.connector,
|
|
||||||
required this.firebaseAuth,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The Data Connect connector used for data operations.
|
final DataConnectService _service;
|
||||||
final ExampleConnector connector;
|
|
||||||
|
|
||||||
/// The Firebase Auth instance.
|
|
||||||
final FirebaseAuth firebaseAuth;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Staff> getStaffProfile() async {
|
Future<Staff> getStaffProfile() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final user = firebaseAuth.currentUser;
|
final staffId = await _service.getStaffId();
|
||||||
if (user == null) {
|
final response = await _service.connector.getStaffById(id: staffId).execute();
|
||||||
throw NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await connector.getStaffByUserId(userId: user.uid).execute();
|
|
||||||
|
|
||||||
if (response.data.staffs.isEmpty) {
|
if (response.data.staff == null) {
|
||||||
throw const ServerException(technicalMessage: 'Staff not found');
|
throw const ServerException(technicalMessage: 'Staff not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
final GetStaffByUserIdStaffs rawStaff = response.data.staffs.first;
|
final GetStaffByIdStaff rawStaff = response.data.staff!;
|
||||||
|
|
||||||
// Map the raw data connect object to the Domain Entity
|
// Map the raw data connect object to the Domain Entity
|
||||||
return Staff(
|
return Staff(
|
||||||
@@ -71,7 +55,8 @@ class ProfileRepositoryImpl
|
|||||||
@override
|
@override
|
||||||
Future<void> signOut() async {
|
Future<void> signOut() async {
|
||||||
try {
|
try {
|
||||||
await firebaseAuth.signOut();
|
await _service.auth.signOut();
|
||||||
|
_service.clearCache();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Error signing out: ${e.toString()}');
|
throw Exception('Error signing out: ${e.toString()}');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
|
|
||||||
import 'data/repositories/profile_repository_impl.dart';
|
import 'data/repositories/profile_repository_impl.dart';
|
||||||
import 'domain/repositories/profile_repository.dart';
|
import 'domain/repositories/profile_repository.dart';
|
||||||
@@ -25,10 +23,7 @@ class StaffProfileModule extends Module {
|
|||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository implementation - delegates to data_connect
|
// Repository implementation - delegates to data_connect
|
||||||
i.addLazySingleton<ProfileRepositoryInterface>(
|
i.addLazySingleton<ProfileRepositoryInterface>(
|
||||||
() => ProfileRepositoryImpl(
|
ProfileRepositoryImpl.new,
|
||||||
connector: ExampleConnector.instance,
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use cases - depend on repository interface
|
// Use cases - depend on repository interface
|
||||||
|
|||||||
@@ -1,47 +1,30 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||||
import 'package:krow_core/core.dart';
|
|
||||||
|
|
||||||
import '../../domain/repositories/certificates_repository.dart';
|
import '../../domain/repositories/certificates_repository.dart';
|
||||||
|
|
||||||
/// Implementation of [CertificatesRepository] using Data Connect.
|
/// Implementation of [CertificatesRepository] using Data Connect.
|
||||||
///
|
///
|
||||||
/// This class handles the communication with the backend via [ExampleConnector].
|
/// This class handles the communication with the backend via [DataConnectService].
|
||||||
/// It maps raw generated data types to clean [domain.StaffDocument] entities.
|
/// It maps raw generated data types to clean [domain.StaffDocument] entities.
|
||||||
class CertificatesRepositoryImpl
|
class CertificatesRepositoryImpl
|
||||||
with DataErrorHandler
|
|
||||||
implements CertificatesRepository {
|
implements CertificatesRepository {
|
||||||
/// The generated Data Connect SDK client.
|
/// The Data Connect service instance.
|
||||||
final ExampleConnector _dataConnect;
|
final DataConnectService _service;
|
||||||
|
|
||||||
/// The Firebase Authentication instance.
|
|
||||||
final FirebaseAuth _firebaseAuth;
|
|
||||||
|
|
||||||
/// Creates a [CertificatesRepositoryImpl].
|
/// Creates a [CertificatesRepositoryImpl].
|
||||||
///
|
CertificatesRepositoryImpl() : _service = DataConnectService.instance;
|
||||||
/// Requires [ExampleConnector] for data access and [FirebaseAuth] for user context.
|
|
||||||
CertificatesRepositoryImpl({
|
|
||||||
required ExampleConnector dataConnect,
|
|
||||||
required FirebaseAuth firebaseAuth,
|
|
||||||
}) : _dataConnect = dataConnect,
|
|
||||||
_firebaseAuth = firebaseAuth;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<domain.StaffDocument>> getCertificates() async {
|
Future<List<domain.StaffDocument>> getCertificates() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final User? currentUser = _firebaseAuth.currentUser;
|
final String staffId = await _service.getStaffId();
|
||||||
if (currentUser == null) {
|
|
||||||
throw domain.NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the query via DataConnect generated SDK
|
// Execute the query via DataConnect generated SDK
|
||||||
final QueryResult<ListStaffDocumentsByStaffIdData,
|
final result =
|
||||||
ListStaffDocumentsByStaffIdVariables> result =
|
await _service.connector
|
||||||
await _dataConnect
|
.listStaffDocumentsByStaffId(staffId: staffId)
|
||||||
.listStaffDocumentsByStaffId(staffId: currentUser.uid)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// Map the generated SDK types to pure Domain entities
|
// Map the generated SDK types to pure Domain entities
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.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/certificates_repository_impl.dart';
|
import 'data/repositories_impl/certificates_repository_impl.dart';
|
||||||
import 'domain/repositories/certificates_repository.dart';
|
import 'domain/repositories/certificates_repository.dart';
|
||||||
@@ -12,12 +10,7 @@ import 'presentation/pages/certificates_page.dart';
|
|||||||
class StaffCertificatesModule extends Module {
|
class StaffCertificatesModule extends Module {
|
||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
i.addLazySingleton<CertificatesRepository>(
|
i.addLazySingleton<CertificatesRepository>(CertificatesRepositoryImpl.new);
|
||||||
() => CertificatesRepositoryImpl(
|
|
||||||
dataConnect: i.get<ExampleConnector>(), // Assuming ExampleConnector is provided by parent module
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
i.addLazySingleton(GetCertificatesUseCase.new);
|
i.addLazySingleton(GetCertificatesUseCase.new);
|
||||||
i.addLazySingleton(CertificatesCubit.new);
|
i.addLazySingleton(CertificatesCubit.new);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,27 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||||
import 'package:krow_core/core.dart';
|
|
||||||
|
|
||||||
import '../../domain/repositories/documents_repository.dart';
|
import '../../domain/repositories/documents_repository.dart';
|
||||||
|
|
||||||
/// Implementation of [DocumentsRepository] using Data Connect.
|
/// Implementation of [DocumentsRepository] using Data Connect.
|
||||||
class DocumentsRepositoryImpl
|
class DocumentsRepositoryImpl
|
||||||
with DataErrorHandler
|
|
||||||
implements DocumentsRepository {
|
implements DocumentsRepository {
|
||||||
final ExampleConnector _dataConnect;
|
final DataConnectService _service;
|
||||||
final FirebaseAuth _firebaseAuth;
|
|
||||||
|
|
||||||
DocumentsRepositoryImpl({
|
DocumentsRepositoryImpl() : _service = DataConnectService.instance;
|
||||||
required ExampleConnector dataConnect,
|
|
||||||
required FirebaseAuth firebaseAuth,
|
|
||||||
}) : _dataConnect = dataConnect,
|
|
||||||
_firebaseAuth = firebaseAuth;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<domain.StaffDocument>> getDocuments() async {
|
Future<List<domain.StaffDocument>> getDocuments() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final User? currentUser = _firebaseAuth.currentUser;
|
final String? staffId = await _service.getStaffId();
|
||||||
if (currentUser == null) {
|
|
||||||
throw domain.NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MOCK IMPLEMENTATION
|
/// MOCK IMPLEMENTATION
|
||||||
/// To be replaced with real data connect query when available
|
/// To be replaced with real data connect query when available
|
||||||
return [
|
return [
|
||||||
domain.StaffDocument(
|
domain.StaffDocument(
|
||||||
id: 'doc1',
|
id: 'doc1',
|
||||||
staffId: currentUser.uid,
|
staffId: staffId!,
|
||||||
documentId: 'd1',
|
documentId: 'd1',
|
||||||
name: 'Work Permit',
|
name: 'Work Permit',
|
||||||
description: 'Valid work permit document',
|
description: 'Valid work permit document',
|
||||||
@@ -43,7 +31,7 @@ class DocumentsRepositoryImpl
|
|||||||
),
|
),
|
||||||
domain.StaffDocument(
|
domain.StaffDocument(
|
||||||
id: 'doc2',
|
id: 'doc2',
|
||||||
staffId: currentUser.uid,
|
staffId: staffId!,
|
||||||
documentId: 'd2',
|
documentId: 'd2',
|
||||||
name: 'Health and Safety Training',
|
name: 'Health and Safety Training',
|
||||||
description: 'Certificate of completion for health and safety training',
|
description: 'Certificate of completion for health and safety training',
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.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/documents_repository_impl.dart';
|
import 'data/repositories_impl/documents_repository_impl.dart';
|
||||||
import 'domain/repositories/documents_repository.dart';
|
import 'domain/repositories/documents_repository.dart';
|
||||||
import 'domain/usecases/get_documents_usecase.dart';
|
import 'domain/usecases/get_documents_usecase.dart';
|
||||||
@@ -11,12 +9,7 @@ import 'presentation/pages/documents_page.dart';
|
|||||||
class StaffDocumentsModule extends Module {
|
class StaffDocumentsModule extends Module {
|
||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
i.addLazySingleton<DocumentsRepository>(
|
i.addLazySingleton<DocumentsRepository>(DocumentsRepositoryImpl.new);
|
||||||
() => DocumentsRepositoryImpl(
|
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
i.addLazySingleton(GetDocumentsUseCase.new);
|
i.addLazySingleton(GetDocumentsUseCase.new);
|
||||||
i.addLazySingleton(DocumentsCubit.new);
|
i.addLazySingleton(DocumentsCubit.new);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
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';
|
||||||
@@ -10,45 +8,21 @@ import '../../domain/repositories/tax_forms_repository.dart';
|
|||||||
import '../mappers/tax_form_mapper.dart';
|
import '../mappers/tax_form_mapper.dart';
|
||||||
|
|
||||||
class TaxFormsRepositoryImpl
|
class TaxFormsRepositoryImpl
|
||||||
with dc.DataErrorHandler
|
|
||||||
implements TaxFormsRepository {
|
implements TaxFormsRepository {
|
||||||
TaxFormsRepositoryImpl({
|
TaxFormsRepositoryImpl() : _service = dc.DataConnectService.instance;
|
||||||
required this.firebaseAuth,
|
|
||||||
required this.dataConnect,
|
|
||||||
});
|
|
||||||
|
|
||||||
final auth.FirebaseAuth firebaseAuth;
|
final dc.DataConnectService _service;
|
||||||
final dc.ExampleConnector dataConnect;
|
|
||||||
|
|
||||||
/// Helper to get the logged-in staff ID.
|
|
||||||
String _getStaffId() {
|
|
||||||
final auth.User? user = firebaseAuth.currentUser;
|
|
||||||
if (user == null) {
|
|
||||||
throw const NotAuthenticatedException(
|
|
||||||
technicalMessage: 'Firebase User is null',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String? staffId = dc.StaffSessionStore.instance.session?.staff?.id;
|
|
||||||
if (staffId == null || staffId.isEmpty) {
|
|
||||||
throw const StaffProfileNotFoundException(
|
|
||||||
technicalMessage: 'Staff ID missing in SessionStore',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return staffId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<TaxForm>> getTaxForms() async {
|
Future<List<TaxForm>> getTaxForms() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String staffId = _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
final QueryResult<dc.GetTaxFormsByStaffIdData, dc.GetTaxFormsByStaffIdVariables>
|
final response = await _service.connector
|
||||||
result = await dataConnect
|
|
||||||
.getTaxFormsByStaffId(staffId: staffId)
|
.getTaxFormsByStaffId(staffId: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
final List<TaxForm> forms =
|
final List<TaxForm> forms =
|
||||||
result.data.taxForms.map(TaxFormMapper.fromDataConnect).toList();
|
response.data.taxForms.map(TaxFormMapper.fromDataConnect).toList();
|
||||||
|
|
||||||
// Check if required forms exist, create if not.
|
// Check if required forms exist, create if not.
|
||||||
final Set<TaxFormType> typesPresent =
|
final Set<TaxFormType> typesPresent =
|
||||||
@@ -65,11 +39,9 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (createdNew) {
|
if (createdNew) {
|
||||||
final QueryResult<
|
final response2 =
|
||||||
dc.GetTaxFormsByStaffIdData,
|
await _service.connector.getTaxFormsByStaffId(staffId: staffId).execute();
|
||||||
dc.GetTaxFormsByStaffIdVariables> result2 =
|
return response2.data.taxForms
|
||||||
await dataConnect.getTaxFormsByStaffId(staffId: staffId).execute();
|
|
||||||
return result2.data.taxForms
|
|
||||||
.map(TaxFormMapper.fromDataConnect)
|
.map(TaxFormMapper.fromDataConnect)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@@ -79,7 +51,7 @@ class TaxFormsRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _createInitialForm(String staffId, TaxFormType type) async {
|
Future<void> _createInitialForm(String staffId, TaxFormType type) async {
|
||||||
await dataConnect
|
await _service.connector
|
||||||
.createTaxForm(
|
.createTaxForm(
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
formType:
|
formType:
|
||||||
@@ -95,10 +67,10 @@ class TaxFormsRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateI9Form(I9TaxForm form) async {
|
Future<void> updateI9Form(I9TaxForm form) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder =
|
||||||
dataConnect.updateTaxForm(id: form.id);
|
_service.connector.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapI9Fields(builder, data);
|
_mapI9Fields(builder, data);
|
||||||
await builder.execute();
|
await builder.execute();
|
||||||
@@ -107,10 +79,10 @@ class TaxFormsRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> submitI9Form(I9TaxForm form) async {
|
Future<void> submitI9Form(I9TaxForm form) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder =
|
||||||
dataConnect.updateTaxForm(id: form.id);
|
_service.connector.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapI9Fields(builder, data);
|
_mapI9Fields(builder, data);
|
||||||
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
||||||
@@ -119,10 +91,10 @@ class TaxFormsRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateW4Form(W4TaxForm form) async {
|
Future<void> updateW4Form(W4TaxForm form) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder =
|
||||||
dataConnect.updateTaxForm(id: form.id);
|
_service.connector.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapW4Fields(builder, data);
|
_mapW4Fields(builder, data);
|
||||||
await builder.execute();
|
await builder.execute();
|
||||||
@@ -131,10 +103,10 @@ class TaxFormsRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> submitW4Form(W4TaxForm form) async {
|
Future<void> submitW4Form(W4TaxForm form) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final Map<String, dynamic> data = form.formData;
|
final Map<String, dynamic> data = form.formData;
|
||||||
final dc.UpdateTaxFormVariablesBuilder builder =
|
final dc.UpdateTaxFormVariablesBuilder builder =
|
||||||
dataConnect.updateTaxForm(id: form.id);
|
_service.connector.updateTaxForm(id: form.id);
|
||||||
_mapCommonFields(builder, data);
|
_mapCommonFields(builder, data);
|
||||||
_mapW4Fields(builder, data);
|
_mapW4Fields(builder, data);
|
||||||
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
await builder.status(dc.TaxFormStatus.SUBMITTED).execute();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.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 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import 'data/repositories/tax_forms_repository_impl.dart';
|
import 'data/repositories/tax_forms_repository_impl.dart';
|
||||||
import 'domain/repositories/tax_forms_repository.dart';
|
import 'domain/repositories/tax_forms_repository.dart';
|
||||||
@@ -18,12 +16,7 @@ import 'presentation/pages/tax_forms_page.dart';
|
|||||||
class StaffTaxFormsModule extends Module {
|
class StaffTaxFormsModule extends Module {
|
||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
i.addLazySingleton<TaxFormsRepository>(
|
i.addLazySingleton<TaxFormsRepository>(TaxFormsRepositoryImpl.new);
|
||||||
() => TaxFormsRepositoryImpl(
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use Cases
|
// Use Cases
|
||||||
i.addLazySingleton(GetTaxFormsUseCase.new);
|
i.addLazySingleton(GetTaxFormsUseCase.new);
|
||||||
|
|||||||
@@ -1,34 +1,31 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../../domain/repositories/bank_account_repository.dart';
|
import '../../domain/repositories/bank_account_repository.dart';
|
||||||
|
|
||||||
/// Implementation of [BankAccountRepository] that integrates with Data Connect.
|
/// Implementation of [BankAccountRepository] that integrates with Data Connect.
|
||||||
class BankAccountRepositoryImpl
|
class BankAccountRepositoryImpl implements BankAccountRepository {
|
||||||
with DataErrorHandler
|
|
||||||
implements BankAccountRepository {
|
|
||||||
/// Creates a [BankAccountRepositoryImpl].
|
/// Creates a [BankAccountRepositoryImpl].
|
||||||
const BankAccountRepositoryImpl({
|
BankAccountRepositoryImpl({
|
||||||
required this.dataConnect,
|
DataConnectService? service,
|
||||||
required this.firebaseAuth,
|
}) : _service = service ?? DataConnectService.instance;
|
||||||
});
|
|
||||||
|
|
||||||
/// The Data Connect instance.
|
/// The Data Connect service.
|
||||||
final ExampleConnector dataConnect;
|
final DataConnectService _service;
|
||||||
/// The Firebase Auth instance.
|
|
||||||
final auth.FirebaseAuth firebaseAuth;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<BankAccount>> getAccounts() async {
|
Future<List<BankAccount>> getAccounts() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String staffId = _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
|
|
||||||
|
var x = staffId;
|
||||||
|
|
||||||
|
print(x);
|
||||||
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
||||||
result = await dataConnect
|
result = await _service.connector
|
||||||
.getAccountsByOwnerId(ownerId: staffId)
|
.getAccountsByOwnerId(ownerId: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return result.data.accounts.map((GetAccountsByOwnerIdAccounts account) {
|
return result.data.accounts.map((GetAccountsByOwnerIdAccounts account) {
|
||||||
return BankAccountAdapter.fromPrimitives(
|
return BankAccountAdapter.fromPrimitives(
|
||||||
id: account.id,
|
id: account.id,
|
||||||
@@ -37,7 +34,9 @@ class BankAccountRepositoryImpl
|
|||||||
accountNumber: account.accountNumber,
|
accountNumber: account.accountNumber,
|
||||||
last4: account.last4,
|
last4: account.last4,
|
||||||
sortCode: account.routeNumber,
|
sortCode: account.routeNumber,
|
||||||
type: account.type is Known<AccountType> ? (account.type as Known<AccountType>).value.name : null,
|
type: account.type is Known<AccountType>
|
||||||
|
? (account.type as Known<AccountType>).value.name
|
||||||
|
: null,
|
||||||
isPrimary: account.isPrimary,
|
isPrimary: account.isPrimary,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
@@ -46,44 +45,31 @@ class BankAccountRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addAccount(BankAccount account) async {
|
Future<void> addAccount(BankAccount account) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String staffId = _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
|
|
||||||
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
||||||
existingAccounts = await dataConnect
|
existingAccounts = await _service.connector
|
||||||
.getAccountsByOwnerId(ownerId: staffId)
|
.getAccountsByOwnerId(ownerId: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
final bool hasAccounts = existingAccounts.data.accounts.isNotEmpty;
|
final bool hasAccounts = existingAccounts.data.accounts.isNotEmpty;
|
||||||
final bool isPrimary = !hasAccounts;
|
final bool isPrimary = !hasAccounts;
|
||||||
|
|
||||||
await dataConnect.createAccount(
|
await _service.connector
|
||||||
bank: account.bankName,
|
.createAccount(
|
||||||
type: AccountType.values.byName(BankAccountAdapter.typeToString(account.type)),
|
bank: account.bankName,
|
||||||
last4: _safeLast4(account.last4, account.accountNumber),
|
type: AccountType.values
|
||||||
ownerId: staffId,
|
.byName(BankAccountAdapter.typeToString(account.type)),
|
||||||
)
|
last4: _safeLast4(account.last4, account.accountNumber),
|
||||||
.isPrimary(isPrimary)
|
ownerId: staffId,
|
||||||
.accountNumber(account.accountNumber)
|
)
|
||||||
.routeNumber(account.sortCode)
|
.isPrimary(isPrimary)
|
||||||
.execute();
|
.accountNumber(account.accountNumber)
|
||||||
|
.routeNumber(account.sortCode)
|
||||||
|
.execute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to get the logged-in staff ID.
|
|
||||||
String _getStaffId() {
|
|
||||||
final auth.User? user = firebaseAuth.currentUser;
|
|
||||||
if (user == null) {
|
|
||||||
throw const NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
|
|
||||||
if (staffId == null || staffId.isEmpty) {
|
|
||||||
throw const ServerException(technicalMessage: 'Staff profile is missing or session not initialized.');
|
|
||||||
}
|
|
||||||
return staffId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensures we have a last4 value, either from input or derived from account number.
|
/// Ensures we have a last4 value, either from input or derived from account number.
|
||||||
String _safeLast4(String? last4, String accountNumber) {
|
String _safeLast4(String? last4, String accountNumber) {
|
||||||
if (last4 != null && last4.isNotEmpty) {
|
if (last4 != null && last4.isNotEmpty) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
import 'package:flutter/widgets.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 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
@@ -17,12 +17,7 @@ class StaffBankAccountModule extends Module {
|
|||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repositories
|
// Repositories
|
||||||
i.addLazySingleton<BankAccountRepository>(
|
i.addLazySingleton<BankAccountRepository>(BankAccountRepositoryImpl.new);
|
||||||
() => BankAccountRepositoryImpl(
|
|
||||||
firebaseAuth: auth.FirebaseAuth.instance,
|
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use Cases
|
// Use Cases
|
||||||
i.addLazySingleton<GetBankAccountsUseCase>(GetBankAccountsUseCase.new);
|
i.addLazySingleton<GetBankAccountsUseCase>(GetBankAccountsUseCase.new);
|
||||||
@@ -41,7 +36,7 @@ class StaffBankAccountModule extends Module {
|
|||||||
void routes(RouteManager r) {
|
void routes(RouteManager r) {
|
||||||
r.child(
|
r.child(
|
||||||
StaffPaths.childRoute(StaffPaths.bankAccount, StaffPaths.bankAccount),
|
StaffPaths.childRoute(StaffPaths.bankAccount, StaffPaths.bankAccount),
|
||||||
child: (_) => const BankAccountPage(),
|
child: (BuildContext context) => const BankAccountPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,46 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
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';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:krow_domain/src/adapters/financial/time_card_adapter.dart';
|
import 'package:krow_domain/src/adapters/financial/time_card_adapter.dart';
|
||||||
import 'package:krow_core/core.dart';
|
|
||||||
import '../../domain/repositories/time_card_repository.dart';
|
import '../../domain/repositories/time_card_repository.dart';
|
||||||
|
|
||||||
/// Implementation of [TimeCardRepository] using Firebase Data Connect.
|
/// Implementation of [TimeCardRepository] using Firebase Data Connect.
|
||||||
class TimeCardRepositoryImpl
|
class TimeCardRepositoryImpl implements TimeCardRepository {
|
||||||
with dc.DataErrorHandler
|
final dc.DataConnectService _service;
|
||||||
implements TimeCardRepository {
|
|
||||||
final dc.ExampleConnector _dataConnect;
|
|
||||||
final firebase.FirebaseAuth _firebaseAuth;
|
|
||||||
|
|
||||||
/// Creates a [TimeCardRepositoryImpl].
|
/// Creates a [TimeCardRepositoryImpl].
|
||||||
TimeCardRepositoryImpl({
|
TimeCardRepositoryImpl({dc.DataConnectService? service})
|
||||||
required dc.ExampleConnector dataConnect,
|
: _service = service ?? dc.DataConnectService.instance;
|
||||||
required firebase.FirebaseAuth firebaseAuth,
|
|
||||||
}) : _dataConnect = dataConnect,
|
|
||||||
_firebaseAuth = firebaseAuth;
|
|
||||||
|
|
||||||
Future<String> _getStaffId() async {
|
|
||||||
final firebase.User? user = _firebaseAuth.currentUser;
|
|
||||||
if (user == null) {
|
|
||||||
throw const NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
final fdc.QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> result =
|
|
||||||
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
|
||||||
if (result.data.staffs.isEmpty) {
|
|
||||||
throw const ServerException(technicalMessage: 'Staff profile not found');
|
|
||||||
}
|
|
||||||
return result.data.staffs.first.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<TimeCard>> getTimeCards(DateTime month) async {
|
Future<List<TimeCard>> getTimeCards(DateTime month) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final String staffId = await _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
// Fetch applications. Limit can be adjusted, assuming 100 is safe for now.
|
// Fetch applications. Limit can be adjusted, assuming 100 is safe for now.
|
||||||
final fdc.QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> result =
|
final fdc.QueryResult<dc.GetApplicationsByStaffIdData,
|
||||||
await _dataConnect.getApplicationsByStaffId(staffId: staffId).limit(100).execute();
|
dc.GetApplicationsByStaffIdVariables> result =
|
||||||
|
await _service.connector
|
||||||
|
.getApplicationsByStaffId(staffId: staffId)
|
||||||
|
.limit(100)
|
||||||
|
.execute();
|
||||||
|
|
||||||
return result.data.applications
|
return result.data.applications
|
||||||
.where((dc.GetApplicationsByStaffIdApplications app) {
|
.where((dc.GetApplicationsByStaffIdApplications app) {
|
||||||
final DateTime? shiftDate = app.shift.date == null
|
final DateTime? shiftDate = _service.toDateTime(app.shift.date);
|
||||||
? null
|
|
||||||
: DateTimeUtils.toDeviceTime(app.shift.date!.toDateTime());
|
|
||||||
if (shiftDate == null) return false;
|
if (shiftDate == null) return false;
|
||||||
return shiftDate.year == month.year && shiftDate.month == month.month;
|
return shiftDate.year == month.year &&
|
||||||
|
shiftDate.month == month.month;
|
||||||
})
|
})
|
||||||
.map((dc.GetApplicationsByStaffIdApplications app) {
|
.map((dc.GetApplicationsByStaffIdApplications app) {
|
||||||
final DateTime shiftDate =
|
final DateTime shiftDate = _service.toDateTime(app.shift.date)!;
|
||||||
DateTimeUtils.toDeviceTime(app.shift.date!.toDateTime());
|
final String startTime = _formatTime(app.checkInTime) ??
|
||||||
final String startTime = _formatTime(app.checkInTime) ?? _formatTime(app.shift.startTime) ?? '';
|
_formatTime(app.shift.startTime) ??
|
||||||
final String endTime = _formatTime(app.checkOutTime) ?? _formatTime(app.shift.endTime) ?? '';
|
'';
|
||||||
|
final String endTime = _formatTime(app.checkOutTime) ??
|
||||||
|
_formatTime(app.shift.endTime) ??
|
||||||
|
'';
|
||||||
|
|
||||||
// Prefer shiftRole values for pay/hours
|
// Prefer shiftRole values for pay/hours
|
||||||
final double hours = app.shiftRole.hours ?? 0.0;
|
final double hours = app.shiftRole.hours ?? 0.0;
|
||||||
@@ -84,7 +67,8 @@ class TimeCardRepositoryImpl
|
|||||||
|
|
||||||
String? _formatTime(fdc.Timestamp? timestamp) {
|
String? _formatTime(fdc.Timestamp? timestamp) {
|
||||||
if (timestamp == null) return null;
|
if (timestamp == null) return null;
|
||||||
return DateFormat('HH:mm')
|
final DateTime? dt = _service.toDateTime(timestamp);
|
||||||
.format(DateTimeUtils.toDeviceTime(timestamp.toDateTime()));
|
if (dt == null) return null;
|
||||||
|
return DateFormat('HH:mm').format(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
library staff_time_card;
|
library staff_time_card;
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:flutter/widgets.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 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
@@ -24,12 +24,7 @@ class StaffTimeCardModule extends Module {
|
|||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repositories
|
// Repositories
|
||||||
i.add<TimeCardRepository>(
|
i.addLazySingleton<TimeCardRepository>(TimeCardRepositoryImpl.new);
|
||||||
() => TimeCardRepositoryImpl(
|
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// UseCases
|
// UseCases
|
||||||
i.add<GetTimeCardsUseCase>(GetTimeCardsUseCase.new);
|
i.add<GetTimeCardsUseCase>(GetTimeCardsUseCase.new);
|
||||||
@@ -42,7 +37,7 @@ class StaffTimeCardModule extends Module {
|
|||||||
void routes(RouteManager r) {
|
void routes(RouteManager r) {
|
||||||
r.child(
|
r.child(
|
||||||
StaffPaths.childRoute(StaffPaths.timeCard, StaffPaths.timeCard),
|
StaffPaths.childRoute(StaffPaths.timeCard, StaffPaths.timeCard),
|
||||||
child: (context) => const TimeCardPage(),
|
child: (BuildContext context) => const TimeCardPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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/attire_repository_impl.dart';
|
import 'data/repositories_impl/attire_repository_impl.dart';
|
||||||
import 'domain/repositories/attire_repository.dart';
|
import 'domain/repositories/attire_repository.dart';
|
||||||
@@ -14,10 +13,8 @@ class StaffAttireModule extends Module {
|
|||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository
|
// Repository
|
||||||
i.addLazySingleton<AttireRepository>(
|
i.addLazySingleton<AttireRepository>(AttireRepositoryImpl.new);
|
||||||
() => AttireRepositoryImpl(ExampleConnector.instance),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use Cases
|
// Use Cases
|
||||||
i.addLazySingleton(GetAttireOptionsUseCase.new);
|
i.addLazySingleton(GetAttireOptionsUseCase.new);
|
||||||
i.addLazySingleton(SaveAttireUseCase.new);
|
i.addLazySingleton(SaveAttireUseCase.new);
|
||||||
|
|||||||
@@ -6,24 +6,30 @@ import '../../domain/repositories/attire_repository.dart';
|
|||||||
|
|
||||||
/// Implementation of [AttireRepository].
|
/// Implementation of [AttireRepository].
|
||||||
///
|
///
|
||||||
/// Delegates data access to [ExampleConnector] from `data_connect`.
|
/// Delegates data access to [DataConnectService].
|
||||||
class AttireRepositoryImpl implements AttireRepository {
|
class AttireRepositoryImpl implements AttireRepository {
|
||||||
/// The Data Connect connector instance.
|
/// The Data Connect service.
|
||||||
final ExampleConnector _connector;
|
final DataConnectService _service;
|
||||||
|
|
||||||
/// Creates an [AttireRepositoryImpl].
|
/// Creates an [AttireRepositoryImpl].
|
||||||
AttireRepositoryImpl(this._connector);
|
AttireRepositoryImpl({DataConnectService? service})
|
||||||
|
: _service = service ?? DataConnectService.instance;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<AttireItem>> getAttireOptions() async {
|
Future<List<AttireItem>> getAttireOptions() async {
|
||||||
final QueryResult<ListAttireOptionsData, void> result = await _connector.listAttireOptions().execute();
|
return _service.run(() async {
|
||||||
return result.data.attireOptions.map((ListAttireOptionsAttireOptions e) => AttireItem(
|
final QueryResult<ListAttireOptionsData, void> result =
|
||||||
id: e.itemId,
|
await _service.connector.listAttireOptions().execute();
|
||||||
label: e.label,
|
return result.data.attireOptions
|
||||||
iconName: e.icon,
|
.map((ListAttireOptionsAttireOptions e) => AttireItem(
|
||||||
imageUrl: e.imageUrl,
|
id: e.itemId,
|
||||||
isMandatory: e.isMandatory ?? false,
|
label: e.label,
|
||||||
)).toList();
|
iconName: e.icon,
|
||||||
|
imageUrl: e.imageUrl,
|
||||||
|
isMandatory: e.isMandatory ?? false,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
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 '../../domain/repositories/emergency_contact_repository_interface.dart';
|
import '../../domain/repositories/emergency_contact_repository_interface.dart';
|
||||||
@@ -7,38 +6,19 @@ import '../../domain/repositories/emergency_contact_repository_interface.dart';
|
|||||||
///
|
///
|
||||||
/// This repository delegates data operations to Firebase Data Connect.
|
/// This repository delegates data operations to Firebase Data Connect.
|
||||||
class EmergencyContactRepositoryImpl
|
class EmergencyContactRepositoryImpl
|
||||||
with dc.DataErrorHandler
|
|
||||||
implements EmergencyContactRepositoryInterface {
|
implements EmergencyContactRepositoryInterface {
|
||||||
final dc.ExampleConnector _dataConnect;
|
final dc.DataConnectService _service;
|
||||||
final FirebaseAuth _firebaseAuth;
|
|
||||||
|
|
||||||
/// Creates an [EmergencyContactRepositoryImpl].
|
/// Creates an [EmergencyContactRepositoryImpl].
|
||||||
EmergencyContactRepositoryImpl({
|
EmergencyContactRepositoryImpl({
|
||||||
required dc.ExampleConnector dataConnect,
|
dc.DataConnectService? service,
|
||||||
required FirebaseAuth firebaseAuth,
|
}) : _service = service ?? dc.DataConnectService.instance;
|
||||||
}) : _dataConnect = dataConnect,
|
|
||||||
_firebaseAuth = firebaseAuth;
|
|
||||||
|
|
||||||
Future<String> _getStaffId() async {
|
|
||||||
final user = _firebaseAuth.currentUser;
|
|
||||||
if (user == null) {
|
|
||||||
throw const NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
final result =
|
|
||||||
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
|
||||||
if (result.data.staffs.isEmpty) {
|
|
||||||
throw const ServerException(technicalMessage: 'Staff profile not found');
|
|
||||||
}
|
|
||||||
return result.data.staffs.first.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<EmergencyContact>> getContacts() async {
|
Future<List<EmergencyContact>> getContacts() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final staffId = await _getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
final result = await _dataConnect
|
final result = await _service.connector
|
||||||
.getEmergencyContactsByStaffId(staffId: staffId)
|
.getEmergencyContactsByStaffId(staffId: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
@@ -55,11 +35,11 @@ class EmergencyContactRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> saveContacts(List<EmergencyContact> contacts) async {
|
Future<void> saveContacts(List<EmergencyContact> contacts) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final staffId = await _getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
|
|
||||||
// 1. Get existing to delete
|
// 1. Get existing to delete
|
||||||
final existingResult = await _dataConnect
|
final existingResult = await _service.connector
|
||||||
.getEmergencyContactsByStaffId(staffId: staffId)
|
.getEmergencyContactsByStaffId(staffId: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
final existingIds =
|
final existingIds =
|
||||||
@@ -67,7 +47,7 @@ class EmergencyContactRepositoryImpl
|
|||||||
|
|
||||||
// 2. Delete all existing
|
// 2. Delete all existing
|
||||||
await Future.wait(existingIds.map(
|
await Future.wait(existingIds.map(
|
||||||
(id) => _dataConnect.deleteEmergencyContact(id: id).execute()));
|
(id) => _service.connector.deleteEmergencyContact(id: id).execute()));
|
||||||
|
|
||||||
// 3. Create new
|
// 3. Create new
|
||||||
await Future.wait(contacts.map((contact) {
|
await Future.wait(contacts.map((contact) {
|
||||||
@@ -87,7 +67,7 @@ class EmergencyContactRepositoryImpl
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _dataConnect
|
return _service.connector
|
||||||
.createEmergencyContact(
|
.createEmergencyContact(
|
||||||
name: contact.name,
|
name: contact.name,
|
||||||
phone: contact.phone,
|
phone: contact.phone,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
|
||||||
import 'data/repositories/emergency_contact_repository_impl.dart';
|
import 'data/repositories/emergency_contact_repository_impl.dart';
|
||||||
import 'domain/repositories/emergency_contact_repository_interface.dart';
|
import 'domain/repositories/emergency_contact_repository_interface.dart';
|
||||||
import 'domain/usecases/get_emergency_contacts_usecase.dart';
|
import 'domain/usecases/get_emergency_contacts_usecase.dart';
|
||||||
@@ -13,10 +12,7 @@ class StaffEmergencyContactModule extends Module {
|
|||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository
|
// Repository
|
||||||
i.addLazySingleton<EmergencyContactRepositoryInterface>(
|
i.addLazySingleton<EmergencyContactRepositoryInterface>(
|
||||||
() => EmergencyContactRepositoryImpl(
|
EmergencyContactRepositoryImpl.new,
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// UseCases
|
// UseCases
|
||||||
|
|||||||
@@ -1,42 +1,31 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||||
import '../../domain/repositories/experience_repository_interface.dart';
|
|
||||||
|
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import '../../domain/repositories/experience_repository_interface.dart';
|
||||||
|
|
||||||
/// Implementation of [ExperienceRepositoryInterface] that delegates to Data Connect.
|
/// Implementation of [ExperienceRepositoryInterface] that delegates to Data Connect.
|
||||||
class ExperienceRepositoryImpl
|
class ExperienceRepositoryImpl implements ExperienceRepositoryInterface {
|
||||||
with dc.DataErrorHandler
|
final dc.DataConnectService _service;
|
||||||
implements ExperienceRepositoryInterface {
|
|
||||||
final dc.ExampleConnector _dataConnect;
|
|
||||||
// ignore: unused_field
|
|
||||||
final FirebaseAuth _firebaseAuth;
|
|
||||||
|
|
||||||
/// Creates a [ExperienceRepositoryImpl] using Data Connect and Auth.
|
/// Creates a [ExperienceRepositoryImpl] using Data Connect Service.
|
||||||
ExperienceRepositoryImpl({
|
ExperienceRepositoryImpl({
|
||||||
required dc.ExampleConnector dataConnect,
|
dc.DataConnectService? service,
|
||||||
required FirebaseAuth firebaseAuth,
|
}) : _service = service ?? dc.DataConnectService.instance;
|
||||||
}) : _dataConnect = dataConnect,
|
|
||||||
_firebaseAuth = firebaseAuth;
|
|
||||||
|
|
||||||
Future<dc.GetStaffByUserIdStaffs> _getStaff() async {
|
Future<dc.GetStaffByIdStaff> _getStaff() async {
|
||||||
final user = _firebaseAuth.currentUser;
|
final staffId = await _service.getStaffId();
|
||||||
if (user == null) {
|
|
||||||
throw const NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
final result =
|
final result =
|
||||||
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
await _service.connector.getStaffById(id: staffId).execute();
|
||||||
if (result.data.staffs.isEmpty) {
|
if (result.data.staff == null) {
|
||||||
throw const ServerException(technicalMessage: 'Staff profile not found');
|
throw const ServerException(technicalMessage: 'Staff profile not found');
|
||||||
}
|
}
|
||||||
return result.data.staffs.first;
|
return result.data.staff!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getIndustries() async {
|
Future<List<String>> getIndustries() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final staff = await _getStaff();
|
final staff = await _getStaff();
|
||||||
return staff.industries ?? [];
|
return staff.industries ?? [];
|
||||||
});
|
});
|
||||||
@@ -44,7 +33,7 @@ class ExperienceRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getSkills() async {
|
Future<List<String>> getSkills() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final staff = await _getStaff();
|
final staff = await _getStaff();
|
||||||
return staff.skills ?? [];
|
return staff.skills ?? [];
|
||||||
});
|
});
|
||||||
@@ -55,9 +44,9 @@ class ExperienceRepositoryImpl
|
|||||||
List<String> industries,
|
List<String> industries,
|
||||||
List<String> skills,
|
List<String> skills,
|
||||||
) async {
|
) async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final staff = await _getStaff();
|
final staff = await _getStaff();
|
||||||
await _dataConnect
|
await _service.connector
|
||||||
.updateStaff(id: staff.id)
|
.updateStaff(id: staff.id)
|
||||||
.industries(industries)
|
.industries(industries)
|
||||||
.skills(skills)
|
.skills(skills)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
library staff_profile_experience;
|
library staff_profile_experience;
|
||||||
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
|
|
||||||
@@ -22,10 +21,7 @@ class StaffProfileExperienceModule extends Module {
|
|||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository
|
// Repository
|
||||||
i.addLazySingleton<ExperienceRepositoryInterface>(
|
i.addLazySingleton<ExperienceRepositoryInterface>(
|
||||||
() => ExperienceRepositoryImpl(
|
ExperienceRepositoryImpl.new,
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// UseCases
|
// UseCases
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
|
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
@@ -14,32 +13,24 @@ import '../../domain/repositories/personal_info_repository_interface.dart';
|
|||||||
/// - Mapping between data_connect DTOs and domain entities
|
/// - Mapping between data_connect DTOs and domain entities
|
||||||
/// - Containing no business logic
|
/// - Containing no business logic
|
||||||
class PersonalInfoRepositoryImpl
|
class PersonalInfoRepositoryImpl
|
||||||
with DataErrorHandler
|
|
||||||
implements PersonalInfoRepositoryInterface {
|
implements PersonalInfoRepositoryInterface {
|
||||||
|
|
||||||
/// Creates a [PersonalInfoRepositoryImpl].
|
/// Creates a [PersonalInfoRepositoryImpl].
|
||||||
///
|
///
|
||||||
/// Requires the Firebase Data Connect connector instance and Firebase Auth.
|
/// Requires the Firebase Data Connect service.
|
||||||
PersonalInfoRepositoryImpl({
|
PersonalInfoRepositoryImpl({
|
||||||
required ExampleConnector dataConnect,
|
DataConnectService? service,
|
||||||
required firebase_auth.FirebaseAuth firebaseAuth,
|
}) : _service = service ?? DataConnectService.instance;
|
||||||
}) : _dataConnect = dataConnect,
|
|
||||||
_firebaseAuth = firebaseAuth;
|
final DataConnectService _service;
|
||||||
final ExampleConnector _dataConnect;
|
|
||||||
final firebase_auth.FirebaseAuth _firebaseAuth;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Staff> getStaffProfile() async {
|
Future<Staff> getStaffProfile() async {
|
||||||
return executeProtected(() async {
|
return _service.run(() async {
|
||||||
final firebase_auth.User? user = _firebaseAuth.currentUser;
|
final String uid = _service.auth.currentUser!.uid;
|
||||||
if (user == null) {
|
|
||||||
throw NotAuthenticatedException(
|
|
||||||
technicalMessage: 'User not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query staff data from Firebase Data Connect
|
// Query staff data from Firebase Data Connect
|
||||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables> result =
|
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables> result =
|
||||||
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
await _service.connector.getStaffByUserId(userId: uid).execute();
|
||||||
|
|
||||||
if (result.data.staffs.isEmpty) {
|
if (result.data.staffs.isEmpty) {
|
||||||
throw const ServerException(technicalMessage: 'Staff profile not found');
|
throw const ServerException(technicalMessage: 'Staff profile not found');
|
||||||
@@ -53,10 +44,12 @@ class PersonalInfoRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Staff> updateStaffProfile({required String staffId, required Map<String, dynamic> data}) async {
|
Future<Staff> updateStaffProfile(
|
||||||
return executeProtected(() async {
|
{required String staffId, required Map<String, dynamic> data}) async {
|
||||||
|
return _service.run(() async {
|
||||||
// Start building the update mutation
|
// Start building the update mutation
|
||||||
UpdateStaffVariablesBuilder updateBuilder = _dataConnect.updateStaff(id: staffId);
|
UpdateStaffVariablesBuilder updateBuilder =
|
||||||
|
_service.connector.updateStaff(id: staffId);
|
||||||
|
|
||||||
// Apply updates from map if present
|
// Apply updates from map if present
|
||||||
if (data.containsKey('name')) {
|
if (data.containsKey('name')) {
|
||||||
@@ -72,8 +65,9 @@ class PersonalInfoRepositoryImpl
|
|||||||
updateBuilder = updateBuilder.photoUrl(data['avatar'] as String?);
|
updateBuilder = updateBuilder.photoUrl(data['avatar'] as String?);
|
||||||
}
|
}
|
||||||
if (data.containsKey('preferredLocations')) {
|
if (data.containsKey('preferredLocations')) {
|
||||||
// After schema update and SDK regeneration, preferredLocations accepts List<String>
|
// After schema update and SDK regeneration, preferredLocations accepts List<String>
|
||||||
updateBuilder = updateBuilder.preferredLocations(data['preferredLocations'] as List<String>);
|
updateBuilder = updateBuilder.preferredLocations(
|
||||||
|
data['preferredLocations'] as List<String>);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the update
|
// Execute the update
|
||||||
@@ -81,7 +75,8 @@ class PersonalInfoRepositoryImpl
|
|||||||
await updateBuilder.execute();
|
await updateBuilder.execute();
|
||||||
|
|
||||||
if (result.data.staff_update == null) {
|
if (result.data.staff_update == null) {
|
||||||
throw const ServerException(technicalMessage: 'Failed to update staff profile');
|
throw const ServerException(
|
||||||
|
technicalMessage: 'Failed to update staff profile');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the updated staff profile to return complete entity
|
// Fetch the updated staff profile to return complete entity
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
|
||||||
|
|
||||||
import 'data/repositories/personal_info_repository_impl.dart';
|
import 'data/repositories/personal_info_repository_impl.dart';
|
||||||
import 'domain/repositories/personal_info_repository_interface.dart';
|
import 'domain/repositories/personal_info_repository_interface.dart';
|
||||||
@@ -25,11 +23,7 @@ class StaffProfileInfoModule extends Module {
|
|||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository
|
// Repository
|
||||||
i.addLazySingleton<PersonalInfoRepositoryInterface>(
|
i.addLazySingleton<PersonalInfoRepositoryInterface>(
|
||||||
() => PersonalInfoRepositoryImpl(
|
PersonalInfoRepositoryImpl.new);
|
||||||
dataConnect: ExampleConnector.instance,
|
|
||||||
firebaseAuth: FirebaseAuth.instance,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use Cases - delegate business logic to repository
|
// Use Cases - delegate business logic to repository
|
||||||
i.addLazySingleton<GetPersonalInfoUseCase>(
|
i.addLazySingleton<GetPersonalInfoUseCase>(
|
||||||
|
|||||||
@@ -1,85 +1,20 @@
|
|||||||
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:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
|
|
||||||
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_core/core.dart';
|
|
||||||
import '../../domain/repositories/shifts_repository_interface.dart';
|
import '../../domain/repositories/shifts_repository_interface.dart';
|
||||||
|
|
||||||
class ShiftsRepositoryImpl
|
class ShiftsRepositoryImpl
|
||||||
with dc.DataErrorHandler
|
|
||||||
implements ShiftsRepositoryInterface {
|
implements ShiftsRepositoryInterface {
|
||||||
final dc.ExampleConnector _dataConnect;
|
final dc.DataConnectService _service;
|
||||||
final firebase_auth.FirebaseAuth _auth = firebase_auth.FirebaseAuth.instance;
|
|
||||||
|
|
||||||
ShiftsRepositoryImpl() : _dataConnect = dc.ExampleConnector.instance;
|
ShiftsRepositoryImpl() : _service = dc.DataConnectService.instance;
|
||||||
|
|
||||||
// Cache: ShiftID -> ApplicationID (For Accept/Decline)
|
// Cache: ShiftID -> ApplicationID (For Accept/Decline)
|
||||||
final Map<String, String> _shiftToAppIdMap = {};
|
final Map<String, String> _shiftToAppIdMap = {};
|
||||||
// Cache: ApplicationID -> RoleID (For Accept/Decline w/ Update mutation)
|
// Cache: ApplicationID -> RoleID (For Accept/Decline w/ Update mutation)
|
||||||
final Map<String, String> _appToRoleIdMap = {};
|
final Map<String, String> _appToRoleIdMap = {};
|
||||||
|
|
||||||
String? _cachedStaffId;
|
|
||||||
|
|
||||||
Future<String> _getStaffId() async {
|
|
||||||
// 1. Check Session Store
|
|
||||||
final dc.StaffSession? session = dc.StaffSessionStore.instance.session;
|
|
||||||
if (session?.staff?.id != null) {
|
|
||||||
return session!.staff!.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check Cache
|
|
||||||
if (_cachedStaffId != null) return _cachedStaffId!;
|
|
||||||
|
|
||||||
// 3. Fetch from Data Connect using Firebase UID
|
|
||||||
final firebase_auth.User? user = _auth.currentUser;
|
|
||||||
if (user == null) {
|
|
||||||
throw Exception('User is not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final fdc.QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> response = await executeProtected(() => _dataConnect
|
|
||||||
.getStaffByUserId(userId: user.uid)
|
|
||||||
.execute());
|
|
||||||
if (response.data.staffs.isNotEmpty) {
|
|
||||||
_cachedStaffId = response.data.staffs.first.id;
|
|
||||||
return _cachedStaffId!;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Log or handle error
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Fallback (should ideally not happen if DB is seeded)
|
|
||||||
return user.uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime? _toDateTime(dynamic t) {
|
|
||||||
if (t == null) return null;
|
|
||||||
DateTime? dt;
|
|
||||||
if (t is fdc.Timestamp) {
|
|
||||||
dt = t.toDateTime();
|
|
||||||
} else if (t is String) {
|
|
||||||
dt = DateTime.tryParse(t);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
dt = DateTime.tryParse(t.toJson() as String);
|
|
||||||
} catch (_) {
|
|
||||||
try {
|
|
||||||
dt = DateTime.tryParse(t.toString());
|
|
||||||
} catch (e) {
|
|
||||||
dt = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dt != null) {
|
|
||||||
final local = DateTimeUtils.toDeviceTime(dt);
|
|
||||||
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Shift>> getMyShifts({
|
Future<List<Shift>> getMyShifts({
|
||||||
required DateTime start,
|
required DateTime start,
|
||||||
@@ -100,8 +35,8 @@ class ShiftsRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Shift>> getHistoryShifts() async {
|
Future<List<Shift>> getHistoryShifts() async {
|
||||||
final staffId = await _getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
final fdc.QueryResult<dc.ListCompletedApplicationsByStaffIdData, dc.ListCompletedApplicationsByStaffIdVariables> response = await executeProtected(() => _dataConnect
|
final fdc.QueryResult<dc.ListCompletedApplicationsByStaffIdData, dc.ListCompletedApplicationsByStaffIdVariables> response = await _service.executeProtected(() => _service.connector
|
||||||
.listCompletedApplicationsByStaffId(staffId: staffId)
|
.listCompletedApplicationsByStaffId(staffId: staffId)
|
||||||
.execute());
|
.execute());
|
||||||
final List<Shift> shifts = [];
|
final List<Shift> shifts = [];
|
||||||
@@ -116,10 +51,10 @@ class ShiftsRepositoryImpl
|
|||||||
? app.shift.order.eventName!
|
? app.shift.order.eventName!
|
||||||
: app.shift.order.business.businessName;
|
: app.shift.order.business.businessName;
|
||||||
final String title = '$roleName - $orderName';
|
final String title = '$roleName - $orderName';
|
||||||
final DateTime? shiftDate = _toDateTime(app.shift.date);
|
final DateTime? shiftDate = _service.toDateTime(app.shift.date);
|
||||||
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);
|
||||||
|
|
||||||
shifts.add(
|
shifts.add(
|
||||||
Shift(
|
Shift(
|
||||||
@@ -157,12 +92,12 @@ class ShiftsRepositoryImpl
|
|||||||
DateTime? start,
|
DateTime? start,
|
||||||
DateTime? end,
|
DateTime? end,
|
||||||
}) async {
|
}) async {
|
||||||
final staffId = await _getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
var query = _dataConnect.getApplicationsByStaffId(staffId: staffId);
|
var query = _service.connector.getApplicationsByStaffId(staffId: staffId);
|
||||||
if (start != null && end != null) {
|
if (start != null && end != null) {
|
||||||
query = query.dayStart(_toTimestamp(start)).dayEnd(_toTimestamp(end));
|
query = query.dayStart(_service.toTimestamp(start)).dayEnd(_service.toTimestamp(end));
|
||||||
}
|
}
|
||||||
final fdc.QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> response = await executeProtected(() => query.execute());
|
final fdc.QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> response = await _service.executeProtected(() => query.execute());
|
||||||
|
|
||||||
final apps = response.data.applications;
|
final apps = response.data.applications;
|
||||||
final List<Shift> shifts = [];
|
final List<Shift> shifts = [];
|
||||||
@@ -177,10 +112,10 @@ class ShiftsRepositoryImpl
|
|||||||
? app.shift.order.eventName!
|
? app.shift.order.eventName!
|
||||||
: app.shift.order.business.businessName;
|
: app.shift.order.business.businessName;
|
||||||
final String title = '$roleName - $orderName';
|
final String title = '$roleName - $orderName';
|
||||||
final DateTime? shiftDate = _toDateTime(app.shift.date);
|
final DateTime? shiftDate = _service.toDateTime(app.shift.date);
|
||||||
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);
|
||||||
|
|
||||||
// Override status to reflect the application state (e.g., CHECKED_OUT, CONFIRMED)
|
// Override status to reflect the application state (e.g., CHECKED_OUT, CONFIRMED)
|
||||||
final bool hasCheckIn = app.checkInTime != null;
|
final bool hasCheckIn = app.checkInTime != null;
|
||||||
@@ -226,13 +161,6 @@ class ShiftsRepositoryImpl
|
|||||||
return shifts;
|
return shifts;
|
||||||
}
|
}
|
||||||
|
|
||||||
fdc.Timestamp _toTimestamp(DateTime dateTime) {
|
|
||||||
final DateTime utc = dateTime.toUtc();
|
|
||||||
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
|
|
||||||
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
|
||||||
return fdc.Timestamp(nanoseconds, seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _mapStatus(dc.ApplicationStatus status) {
|
String _mapStatus(dc.ApplicationStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case dc.ApplicationStatus.CONFIRMED:
|
case dc.ApplicationStatus.CONFIRMED:
|
||||||
@@ -255,7 +183,7 @@ class ShiftsRepositoryImpl
|
|||||||
return <Shift>[];
|
return <Shift>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
final fdc.QueryResult<dc.ListShiftRolesByVendorIdData, dc.ListShiftRolesByVendorIdVariables> result = await executeProtected(() => _dataConnect
|
final fdc.QueryResult<dc.ListShiftRolesByVendorIdData, dc.ListShiftRolesByVendorIdVariables> result = await _service.executeProtected(() => _service.connector
|
||||||
.listShiftRolesByVendorId(vendorId: vendorId)
|
.listShiftRolesByVendorId(vendorId: vendorId)
|
||||||
.execute());
|
.execute());
|
||||||
final allShiftRoles = result.data.shiftRoles;
|
final allShiftRoles = result.data.shiftRoles;
|
||||||
@@ -263,10 +191,10 @@ class ShiftsRepositoryImpl
|
|||||||
final List<Shift> mappedShifts = [];
|
final List<Shift> mappedShifts = [];
|
||||||
for (final sr in allShiftRoles) {
|
for (final sr in allShiftRoles) {
|
||||||
|
|
||||||
final DateTime? shiftDate = _toDateTime(sr.shift.date);
|
final DateTime? shiftDate = _service.toDateTime(sr.shift.date);
|
||||||
final startDt = _toDateTime(sr.startTime);
|
final startDt = _service.toDateTime(sr.startTime);
|
||||||
final endDt = _toDateTime(sr.endTime);
|
final endDt = _service.toDateTime(sr.endTime);
|
||||||
final createdDt = _toDateTime(sr.createdAt);
|
final createdDt = _service.toDateTime(sr.createdAt);
|
||||||
|
|
||||||
mappedShifts.add(
|
mappedShifts.add(
|
||||||
Shift(
|
Shift(
|
||||||
@@ -319,21 +247,21 @@ class ShiftsRepositoryImpl
|
|||||||
|
|
||||||
Future<Shift?> _getShiftDetails(String shiftId, {String? roleId}) async {
|
Future<Shift?> _getShiftDetails(String shiftId, {String? roleId}) async {
|
||||||
if (roleId != null && roleId.isNotEmpty) {
|
if (roleId != null && roleId.isNotEmpty) {
|
||||||
final roleResult = await executeProtected(() => _dataConnect
|
final roleResult = await _service.executeProtected(() => _service.connector
|
||||||
.getShiftRoleById(shiftId: shiftId, roleId: roleId)
|
.getShiftRoleById(shiftId: shiftId, roleId: roleId)
|
||||||
.execute());
|
.execute());
|
||||||
final sr = roleResult.data.shiftRole;
|
final sr = roleResult.data.shiftRole;
|
||||||
if (sr == null) return null;
|
if (sr == null) return null;
|
||||||
|
|
||||||
final DateTime? startDt = _toDateTime(sr.startTime);
|
final DateTime? startDt = _service.toDateTime(sr.startTime);
|
||||||
final DateTime? endDt = _toDateTime(sr.endTime);
|
final DateTime? endDt = _service.toDateTime(sr.endTime);
|
||||||
final DateTime? createdDt = _toDateTime(sr.createdAt);
|
final DateTime? createdDt = _service.toDateTime(sr.createdAt);
|
||||||
|
|
||||||
final String staffId = await _getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
bool hasApplied = false;
|
bool hasApplied = false;
|
||||||
String status = 'open';
|
String status = 'open';
|
||||||
final apps = await executeProtected(() =>
|
final apps = await _service.executeProtected(() =>
|
||||||
_dataConnect.getApplicationsByStaffId(staffId: staffId).execute());
|
_service.connector.getApplicationsByStaffId(staffId: staffId).execute());
|
||||||
final app = apps.data.applications
|
final app = apps.data.applications
|
||||||
.where(
|
.where(
|
||||||
(a) => a.shiftId == shiftId && a.shiftRole.roleId == roleId,
|
(a) => a.shiftId == shiftId && a.shiftRole.roleId == roleId,
|
||||||
@@ -378,7 +306,7 @@ class ShiftsRepositoryImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
final fdc.QueryResult<dc.GetShiftByIdData, dc.GetShiftByIdVariables> result =
|
final fdc.QueryResult<dc.GetShiftByIdData, dc.GetShiftByIdVariables> result =
|
||||||
await executeProtected(() => _dataConnect.getShiftById(id: shiftId).execute());
|
await _service.executeProtected(() => _service.connector.getShiftById(id: shiftId).execute());
|
||||||
final s = result.data.shift;
|
final s = result.data.shift;
|
||||||
if (s == null) return null;
|
if (s == null) return null;
|
||||||
|
|
||||||
@@ -386,8 +314,8 @@ class ShiftsRepositoryImpl
|
|||||||
int? filled;
|
int? filled;
|
||||||
Break? breakInfo;
|
Break? breakInfo;
|
||||||
try {
|
try {
|
||||||
final rolesRes = await executeProtected(() =>
|
final rolesRes = await _service.executeProtected(() =>
|
||||||
_dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute());
|
_service.connector.listShiftRolesByShiftId(shiftId: shiftId).execute());
|
||||||
if (rolesRes.data.shiftRoles.isNotEmpty) {
|
if (rolesRes.data.shiftRoles.isNotEmpty) {
|
||||||
required = 0;
|
required = 0;
|
||||||
filled = 0;
|
filled = 0;
|
||||||
@@ -404,9 +332,9 @@ class ShiftsRepositoryImpl
|
|||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
final startDt = _toDateTime(s.startTime);
|
final startDt = _service.toDateTime(s.startTime);
|
||||||
final endDt = _toDateTime(s.endTime);
|
final endDt = _service.toDateTime(s.endTime);
|
||||||
final createdDt = _toDateTime(s.createdAt);
|
final createdDt = _service.toDateTime(s.createdAt);
|
||||||
|
|
||||||
return Shift(
|
return Shift(
|
||||||
id: s.id,
|
id: s.id,
|
||||||
@@ -437,14 +365,14 @@ class ShiftsRepositoryImpl
|
|||||||
bool isInstantBook = false,
|
bool isInstantBook = false,
|
||||||
String? roleId,
|
String? roleId,
|
||||||
}) async {
|
}) async {
|
||||||
final staffId = await _getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
|
|
||||||
String targetRoleId = roleId ?? '';
|
String targetRoleId = roleId ?? '';
|
||||||
if (targetRoleId.isEmpty) {
|
if (targetRoleId.isEmpty) {
|
||||||
throw Exception('Missing role id.');
|
throw Exception('Missing role id.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final roleResult = await executeProtected(() => _dataConnect
|
final roleResult = await _service.executeProtected(() => _service.connector
|
||||||
.getShiftRoleById(shiftId: shiftId, roleId: targetRoleId)
|
.getShiftRoleById(shiftId: shiftId, roleId: targetRoleId)
|
||||||
.execute());
|
.execute());
|
||||||
final role = roleResult.data.shiftRole;
|
final role = roleResult.data.shiftRole;
|
||||||
@@ -452,12 +380,12 @@ class ShiftsRepositoryImpl
|
|||||||
throw Exception('Shift role not found');
|
throw Exception('Shift role not found');
|
||||||
}
|
}
|
||||||
final shiftResult =
|
final shiftResult =
|
||||||
await executeProtected(() => _dataConnect.getShiftById(id: shiftId).execute());
|
await _service.executeProtected(() => _service.connector.getShiftById(id: shiftId).execute());
|
||||||
final shift = shiftResult.data.shift;
|
final shift = shiftResult.data.shift;
|
||||||
if (shift == null) {
|
if (shift == null) {
|
||||||
throw Exception('Shift not found');
|
throw Exception('Shift not found');
|
||||||
}
|
}
|
||||||
final DateTime? shiftDate = _toDateTime(shift.date);
|
final DateTime? shiftDate = _service.toDateTime(shift.date);
|
||||||
if (shiftDate != null) {
|
if (shiftDate != null) {
|
||||||
final DateTime dayStartUtc = DateTime.utc(
|
final DateTime dayStartUtc = DateTime.utc(
|
||||||
shiftDate.year,
|
shiftDate.year,
|
||||||
@@ -475,16 +403,16 @@ class ShiftsRepositoryImpl
|
|||||||
999,
|
999,
|
||||||
);
|
);
|
||||||
|
|
||||||
final dayApplications = await executeProtected(() => _dataConnect
|
final dayApplications = await _service.executeProtected(() => _service.connector
|
||||||
.vaidateDayStaffApplication(staffId: staffId)
|
.vaidateDayStaffApplication(staffId: staffId)
|
||||||
.dayStart(_toTimestamp(dayStartUtc))
|
.dayStart(_service.toTimestamp(dayStartUtc))
|
||||||
.dayEnd(_toTimestamp(dayEndUtc))
|
.dayEnd(_service.toTimestamp(dayEndUtc))
|
||||||
.execute());
|
.execute());
|
||||||
if (dayApplications.data.applications.isNotEmpty) {
|
if (dayApplications.data.applications.isNotEmpty) {
|
||||||
throw Exception('The user already has a shift that day.');
|
throw Exception('The user already has a shift that day.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final existingApplicationResult = await executeProtected(() => _dataConnect
|
final existingApplicationResult = await _service.executeProtected(() => _service.connector
|
||||||
.getApplicationByStaffShiftAndRole(
|
.getApplicationByStaffShiftAndRole(
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
shiftId: shiftId,
|
shiftId: shiftId,
|
||||||
@@ -505,7 +433,7 @@ class ShiftsRepositoryImpl
|
|||||||
bool updatedRole = false;
|
bool updatedRole = false;
|
||||||
bool updatedShift = false;
|
bool updatedShift = false;
|
||||||
try {
|
try {
|
||||||
final appResult = await executeProtected(() => _dataConnect
|
final appResult = await _service.executeProtected(() => _service.connector
|
||||||
.createApplication(
|
.createApplication(
|
||||||
shiftId: shiftId,
|
shiftId: shiftId,
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
@@ -517,24 +445,24 @@ class ShiftsRepositoryImpl
|
|||||||
.execute());
|
.execute());
|
||||||
appId = appResult.data.application_insert.id;
|
appId = appResult.data.application_insert.id;
|
||||||
|
|
||||||
await executeProtected(() => _dataConnect
|
await _service.executeProtected(() => _service.connector
|
||||||
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
|
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
|
||||||
.assigned(assigned + 1)
|
.assigned(assigned + 1)
|
||||||
.execute());
|
.execute());
|
||||||
updatedRole = true;
|
updatedRole = true;
|
||||||
|
|
||||||
await executeProtected(
|
await _service.executeProtected(
|
||||||
() => _dataConnect.updateShift(id: shiftId).filled(filled + 1).execute());
|
() => _service.connector.updateShift(id: shiftId).filled(filled + 1).execute());
|
||||||
updatedShift = true;
|
updatedShift = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (updatedShift) {
|
if (updatedShift) {
|
||||||
try {
|
try {
|
||||||
await _dataConnect.updateShift(id: shiftId).filled(filled).execute();
|
await _service.connector.updateShift(id: shiftId).filled(filled).execute();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
if (updatedRole) {
|
if (updatedRole) {
|
||||||
try {
|
try {
|
||||||
await _dataConnect
|
await _service.connector
|
||||||
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
|
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
|
||||||
.assigned(assigned)
|
.assigned(assigned)
|
||||||
.execute();
|
.execute();
|
||||||
@@ -542,7 +470,7 @@ class ShiftsRepositoryImpl
|
|||||||
}
|
}
|
||||||
if (appId != null) {
|
if (appId != null) {
|
||||||
try {
|
try {
|
||||||
await _dataConnect.deleteApplication(id: appId).execute();
|
await _service.connector.deleteApplication(id: appId).execute();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -576,9 +504,9 @@ class ShiftsRepositoryImpl
|
|||||||
roleId = _appToRoleIdMap[appId];
|
roleId = _appToRoleIdMap[appId];
|
||||||
} else {
|
} else {
|
||||||
// Fallback fetch
|
// Fallback fetch
|
||||||
final staffId = await _getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
final apps = await executeProtected(() =>
|
final apps = await _service.executeProtected(() =>
|
||||||
_dataConnect.getApplicationsByStaffId(staffId: staffId).execute());
|
_service.connector.getApplicationsByStaffId(staffId: staffId).execute());
|
||||||
final app = apps.data.applications
|
final app = apps.data.applications
|
||||||
.where((a) => a.shiftId == shiftId)
|
.where((a) => a.shiftId == shiftId)
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
@@ -591,12 +519,12 @@ class ShiftsRepositoryImpl
|
|||||||
if (appId == null || roleId == null) {
|
if (appId == null || roleId == null) {
|
||||||
// If we are rejecting and can't find an application, create one as rejected (declining an available shift)
|
// If we are rejecting and can't find an application, create one as rejected (declining an available shift)
|
||||||
if (newStatus == dc.ApplicationStatus.REJECTED) {
|
if (newStatus == dc.ApplicationStatus.REJECTED) {
|
||||||
final rolesResult = await executeProtected(() =>
|
final rolesResult = await _service.executeProtected(() =>
|
||||||
_dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute());
|
_service.connector.listShiftRolesByShiftId(shiftId: shiftId).execute());
|
||||||
if (rolesResult.data.shiftRoles.isNotEmpty) {
|
if (rolesResult.data.shiftRoles.isNotEmpty) {
|
||||||
final role = rolesResult.data.shiftRoles.first;
|
final role = rolesResult.data.shiftRoles.first;
|
||||||
final staffId = await _getStaffId();
|
final staffId = await _service.getStaffId();
|
||||||
await executeProtected(() => _dataConnect
|
await _service.executeProtected(() => _service.connector
|
||||||
.createApplication(
|
.createApplication(
|
||||||
shiftId: shiftId,
|
shiftId: shiftId,
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
@@ -611,7 +539,7 @@ class ShiftsRepositoryImpl
|
|||||||
throw Exception("Application not found for shift $shiftId");
|
throw Exception("Application not found for shift $shiftId");
|
||||||
}
|
}
|
||||||
|
|
||||||
await executeProtected(() => _dataConnect
|
await _service.executeProtected(() => _service.connector
|
||||||
.updateApplicationStatus(id: appId!)
|
.updateApplicationStatus(id: appId!)
|
||||||
.status(newStatus)
|
.status(newStatus)
|
||||||
.execute());
|
.execute());
|
||||||
|
|||||||
Reference in New Issue
Block a user