feat: implement BankAccountAdapter for mapping data layer values to domain entity and refactor BankAccountRepositoryImpl for improved staff ID handling
This commit is contained in:
@@ -84,3 +84,4 @@ export 'src/entities/availability/day_availability.dart';
|
|||||||
export 'src/adapters/profile/emergency_contact_adapter.dart';
|
export 'src/adapters/profile/emergency_contact_adapter.dart';
|
||||||
export 'src/adapters/profile/experience_adapter.dart';
|
export 'src/adapters/profile/experience_adapter.dart';
|
||||||
export 'src/entities/profile/experience_skill.dart';
|
export 'src/entities/profile/experience_skill.dart';
|
||||||
|
export 'src/adapters/profile/bank_account_adapter.dart';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,58 +2,47 @@ 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].
|
/// Implementation of [BankAccountRepository] that integrates with Data Connect.
|
||||||
class BankAccountRepositoryImpl implements BankAccountRepository {
|
class BankAccountRepositoryImpl implements BankAccountRepository {
|
||||||
|
/// Creates a [BankAccountRepositoryImpl].
|
||||||
const BankAccountRepositoryImpl({
|
const BankAccountRepositoryImpl({
|
||||||
required this.dataConnect,
|
required this.dataConnect,
|
||||||
required this.firebaseAuth,
|
required this.firebaseAuth,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The Data Connect instance.
|
||||||
final ExampleConnector dataConnect;
|
final ExampleConnector dataConnect;
|
||||||
|
/// The Firebase Auth instance.
|
||||||
final auth.FirebaseAuth firebaseAuth;
|
final auth.FirebaseAuth firebaseAuth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<BankAccount>> getAccounts() async {
|
Future<List<BankAccount>> getAccounts() async {
|
||||||
final auth.User? user = firebaseAuth.currentUser;
|
final String staffId = _getStaffId();
|
||||||
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 QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
||||||
result = await dataConnect
|
result = await dataConnect
|
||||||
.getAccountsByOwnerId(ownerId: staffId)
|
.getAccountsByOwnerId(ownerId: staffId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
return result.data.accounts.map((GetAccountsByOwnerIdAccounts account) {
|
return result.data.accounts.map((GetAccountsByOwnerIdAccounts account) {
|
||||||
return BankAccount(
|
return BankAccountAdapter.fromPrimitives(
|
||||||
id: account.id,
|
id: account.id,
|
||||||
userId: account.ownerId,
|
userId: account.ownerId,
|
||||||
bankName: account.bank,
|
bankName: account.bank,
|
||||||
accountNumber: account.accountNumber ?? '',
|
accountNumber: account.accountNumber,
|
||||||
last4: account.last4,
|
last4: account.last4,
|
||||||
accountName: '', // Not returned by API
|
|
||||||
sortCode: account.routeNumber,
|
sortCode: account.routeNumber,
|
||||||
type: _mapAccountType(account.type),
|
type: account.type is Known<AccountType> ? (account.type as Known<AccountType>).value.name : null,
|
||||||
isPrimary: account.isPrimary ?? false,
|
isPrimary: account.isPrimary,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addAccount(BankAccount account) async {
|
Future<void> addAccount(BankAccount account) async {
|
||||||
final auth.User? user = firebaseAuth.currentUser;
|
final String staffId = _getStaffId();
|
||||||
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 QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
||||||
existingAccounts = await dataConnect
|
existingAccounts = await dataConnect
|
||||||
@@ -64,44 +53,41 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
|
|||||||
|
|
||||||
await dataConnect.createAccount(
|
await dataConnect.createAccount(
|
||||||
bank: account.bankName,
|
bank: account.bankName,
|
||||||
type: _mapDomainType(account.type),
|
type: AccountType.values.byName(BankAccountAdapter.typeToString(account.type)),
|
||||||
last4: _safeLast4(account.last4, account.accountNumber),
|
last4: _safeLast4(account.last4, account.accountNumber),
|
||||||
ownerId: staffId,
|
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) {
|
/// Helper to get the logged-in staff ID.
|
||||||
if (type is Known<AccountType>) {
|
String _getStaffId() {
|
||||||
switch (type.value) {
|
final auth.User? user = firebaseAuth.currentUser;
|
||||||
case AccountType.CHECKING:
|
if (user == null) {
|
||||||
return BankAccountType.checking;
|
throw Exception('User not authenticated');
|
||||||
case AccountType.SAVINGS:
|
|
||||||
return BankAccountType.savings;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return BankAccountType.other;
|
|
||||||
}
|
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
|
||||||
|
if (staffId == null || staffId.isEmpty) {
|
||||||
AccountType _mapDomainType(BankAccountType type) {
|
throw Exception('Staff profile is missing or session not initialized.');
|
||||||
switch (type) {
|
|
||||||
case BankAccountType.checking:
|
|
||||||
return AccountType.CHECKING;
|
|
||||||
case BankAccountType.savings:
|
|
||||||
return AccountType.SAVINGS;
|
|
||||||
default:
|
|
||||||
return AccountType.CHECKING; // Default fallback
|
|
||||||
}
|
}
|
||||||
|
return staffId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
return last4;
|
return last4;
|
||||||
}
|
}
|
||||||
if (accountNumber.isEmpty) {
|
if (accountNumber.isEmpty) {
|
||||||
return '';
|
return '0000';
|
||||||
}
|
}
|
||||||
return accountNumber.length > 4
|
if (accountNumber.length < 4) {
|
||||||
? accountNumber.substring(accountNumber.length - 4)
|
return accountNumber.padLeft(4, '0');
|
||||||
: accountNumber;
|
}
|
||||||
|
return accountNumber.substring(accountNumber.length - 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.dart';
|
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../../domain/arguments/add_bank_account_params.dart';
|
import '../../domain/arguments/add_bank_account_params.dart';
|
||||||
import '../../domain/usecases/add_bank_account_usecase.dart';
|
import '../../domain/usecases/add_bank_account_usecase.dart';
|
||||||
@@ -20,7 +19,7 @@ class BankAccountCubit extends Cubit<BankAccountState> {
|
|||||||
Future<void> loadAccounts() async {
|
Future<void> loadAccounts() async {
|
||||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||||
try {
|
try {
|
||||||
final accounts = await _getBankAccountsUseCase();
|
final List<BankAccount> accounts = await _getBankAccountsUseCase();
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
status: BankAccountStatus.loaded,
|
status: BankAccountStatus.loaded,
|
||||||
accounts: accounts,
|
accounts: accounts,
|
||||||
@@ -45,7 +44,7 @@ class BankAccountCubit extends Cubit<BankAccountState> {
|
|||||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||||
|
|
||||||
// Create domain entity
|
// Create domain entity
|
||||||
final newAccount = BankAccount(
|
final BankAccount newAccount = BankAccount(
|
||||||
id: '', // Generated by server usually
|
id: '', // Generated by server usually
|
||||||
userId: '', // Handled by Repo/Auth
|
userId: '', // Handled by Repo/Auth
|
||||||
bankName: 'New Bank', // Mock
|
bankName: 'New Bank', // Mock
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class StaffProfileExperienceModule extends Module {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// BLoC
|
// BLoC
|
||||||
i.addLazySingleton<ExperienceBloc>(
|
i.add<ExperienceBloc>(
|
||||||
() => ExperienceBloc(
|
() => ExperienceBloc(
|
||||||
getIndustries: i.get<GetStaffIndustriesUseCase>(),
|
getIndustries: i.get<GetStaffIndustriesUseCase>(),
|
||||||
getSkills: i.get<GetStaffSkillsUseCase>(),
|
getSkills: i.get<GetStaffSkillsUseCase>(),
|
||||||
|
|||||||
Reference in New Issue
Block a user