feat: implement BankAccountAdapter for mapping data layer values to domain entity and refactor BankAccountRepositoryImpl for improved staff ID handling

This commit is contained in:
Achintha Isuru
2026-01-27 15:43:21 -05:00
parent 1870be4cb8
commit 35c0b19d6b
5 changed files with 90 additions and 51 deletions

View File

@@ -84,3 +84,4 @@ export 'src/entities/availability/day_availability.dart';
export 'src/adapters/profile/emergency_contact_adapter.dart';
export 'src/adapters/profile/experience_adapter.dart';
export 'src/entities/profile/experience_skill.dart';
export 'src/adapters/profile/bank_account_adapter.dart';

View File

@@ -0,0 +1,53 @@
import '../../entities/profile/bank_account.dart';
/// Adapter for [BankAccount] to map data layer values to domain entity.
class BankAccountAdapter {
/// Maps primitive values to [BankAccount].
static BankAccount fromPrimitives({
required String id,
required String userId,
required String bankName,
required String? type,
String? accountNumber,
String? last4,
String? sortCode,
bool? isPrimary,
}) {
return BankAccount(
id: id,
userId: userId,
bankName: bankName,
accountNumber: accountNumber ?? '',
accountName: '', // Not provided by backend
last4: last4,
sortCode: sortCode,
type: _stringToType(type),
isPrimary: isPrimary ?? false,
);
}
static BankAccountType _stringToType(String? value) {
if (value == null) return BankAccountType.checking;
try {
// Assuming backend enum names match or are uppercase
return BankAccountType.values.firstWhere(
(e) => e.name.toLowerCase() == value.toLowerCase(),
orElse: () => BankAccountType.other,
);
} catch (_) {
return BankAccountType.other;
}
}
/// Converts domain type to string for backend.
static String typeToString(BankAccountType type) {
switch (type) {
case BankAccountType.checking:
return 'CHECKING';
case BankAccountType.savings:
return 'SAVINGS';
default:
return 'CHECKING';
}
}
}

View File

@@ -2,58 +2,47 @@ import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/bank_account_repository.dart';
/// Implementation of [BankAccountRepository].
/// Implementation of [BankAccountRepository] that integrates with Data Connect.
class BankAccountRepositoryImpl implements BankAccountRepository {
/// Creates a [BankAccountRepositoryImpl].
const BankAccountRepositoryImpl({
required this.dataConnect,
required this.firebaseAuth,
});
/// The Data Connect instance.
final ExampleConnector dataConnect;
/// The Firebase Auth instance.
final auth.FirebaseAuth firebaseAuth;
@override
Future<List<BankAccount>> getAccounts() async {
final auth.User? user = firebaseAuth.currentUser;
if (user == null) throw Exception('User not authenticated');
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
if (staffId == null || staffId.isEmpty) {
print('BankAccount getAccounts: missing staffId userId=${user.uid} session=${StaffSessionStore.instance.session}');
throw Exception('Staff profile is missing.');
}
final String staffId = _getStaffId();
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
result = await dataConnect
.getAccountsByOwnerId(ownerId: staffId)
.execute();
return result.data.accounts.map((GetAccountsByOwnerIdAccounts account) {
return BankAccount(
return BankAccountAdapter.fromPrimitives(
id: account.id,
userId: account.ownerId,
bankName: account.bank,
accountNumber: account.accountNumber ?? '',
accountNumber: account.accountNumber,
last4: account.last4,
accountName: '', // Not returned by API
sortCode: account.routeNumber,
type: _mapAccountType(account.type),
isPrimary: account.isPrimary ?? false,
type: account.type is Known<AccountType> ? (account.type as Known<AccountType>).value.name : null,
isPrimary: account.isPrimary,
);
}).toList();
}
@override
Future<void> addAccount(BankAccount account) async {
final auth.User? user = firebaseAuth.currentUser;
if (user == null) throw Exception('User not authenticated');
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
if (staffId == null || staffId.isEmpty) {
print('BankAccount addAccount: missing staffId userId=${user.uid} session=${StaffSessionStore.instance.session}');
throw Exception('Staff profile is missing.');
}
final String staffId = _getStaffId();
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
existingAccounts = await dataConnect
@@ -64,44 +53,41 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
await dataConnect.createAccount(
bank: account.bankName,
type: _mapDomainType(account.type),
type: AccountType.values.byName(BankAccountAdapter.typeToString(account.type)),
last4: _safeLast4(account.last4, account.accountNumber),
ownerId: staffId,
).isPrimary(isPrimary).accountNumber(account.accountNumber).routeNumber(account.sortCode).execute();
)
.isPrimary(isPrimary)
.accountNumber(account.accountNumber)
.routeNumber(account.sortCode)
.execute();
}
BankAccountType _mapAccountType(EnumValue<AccountType> type) {
if (type is Known<AccountType>) {
switch (type.value) {
case AccountType.CHECKING:
return BankAccountType.checking;
case AccountType.SAVINGS:
return BankAccountType.savings;
}
/// Helper to get the logged-in staff ID.
String _getStaffId() {
final auth.User? user = firebaseAuth.currentUser;
if (user == null) {
throw Exception('User not authenticated');
}
return BankAccountType.other;
}
AccountType _mapDomainType(BankAccountType type) {
switch (type) {
case BankAccountType.checking:
return AccountType.CHECKING;
case BankAccountType.savings:
return AccountType.SAVINGS;
default:
return AccountType.CHECKING; // Default fallback
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
if (staffId == null || staffId.isEmpty) {
throw Exception('Staff profile is missing or session not initialized.');
}
return staffId;
}
/// Ensures we have a last4 value, either from input or derived from account number.
String _safeLast4(String? last4, String accountNumber) {
if (last4 != null && last4.isNotEmpty) {
return last4;
}
if (accountNumber.isEmpty) {
return '';
return '0000';
}
return accountNumber.length > 4
? accountNumber.substring(accountNumber.length - 4)
: accountNumber;
if (accountNumber.length < 4) {
return accountNumber.padLeft(4, '0');
}
return accountNumber.substring(accountNumber.length - 4);
}
}

View File

@@ -1,5 +1,4 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/add_bank_account_params.dart';
import '../../domain/usecases/add_bank_account_usecase.dart';
@@ -20,7 +19,7 @@ class BankAccountCubit extends Cubit<BankAccountState> {
Future<void> loadAccounts() async {
emit(state.copyWith(status: BankAccountStatus.loading));
try {
final accounts = await _getBankAccountsUseCase();
final List<BankAccount> accounts = await _getBankAccountsUseCase();
emit(state.copyWith(
status: BankAccountStatus.loaded,
accounts: accounts,
@@ -45,7 +44,7 @@ class BankAccountCubit extends Cubit<BankAccountState> {
emit(state.copyWith(status: BankAccountStatus.loading));
// Create domain entity
final newAccount = BankAccount(
final BankAccount newAccount = BankAccount(
id: '', // Generated by server usually
userId: '', // Handled by Repo/Auth
bankName: 'New Bank', // Mock

View File

@@ -40,7 +40,7 @@ class StaffProfileExperienceModule extends Module {
);
// BLoC
i.addLazySingleton<ExperienceBloc>(
i.add<ExperienceBloc>(
() => ExperienceBloc(
getIndustries: i.get<GetStaffIndustriesUseCase>(),
getSkills: i.get<GetStaffSkillsUseCase>(),