feat: Refactor bank account handling in billing and staff modules
- Introduced new bank account entities: BusinessBankAccount and StaffBankAccount. - Updated bank account adapter to handle new entities. - Removed legacy BankAccount entity and its adapter. - Implemented use case for fetching bank accounts in billing repository. - Updated BillingBloc and BillingState to include bank accounts. - Refactored PaymentMethodCard to display bank account information. - Adjusted actions widget layout for better UI consistency. - Updated staff bank account repository and use cases to utilize new entity structure. - Ensured all references to bank accounts in the codebase are updated to the new structure.
This commit is contained in:
@@ -53,6 +53,10 @@ export 'src/entities/financial/invoice_item.dart';
|
|||||||
export 'src/entities/financial/invoice_decline.dart';
|
export 'src/entities/financial/invoice_decline.dart';
|
||||||
export 'src/entities/financial/staff_payment.dart';
|
export 'src/entities/financial/staff_payment.dart';
|
||||||
export 'src/entities/financial/payment_summary.dart';
|
export 'src/entities/financial/payment_summary.dart';
|
||||||
|
export 'src/entities/financial/bank_account/bank_account.dart';
|
||||||
|
export 'src/entities/financial/bank_account/business_bank_account.dart';
|
||||||
|
export 'src/entities/financial/bank_account/staff_bank_account.dart';
|
||||||
|
export 'src/adapters/financial/bank_account/bank_account_adapter.dart';
|
||||||
|
|
||||||
// Profile
|
// Profile
|
||||||
export 'src/entities/profile/staff_document.dart';
|
export 'src/entities/profile/staff_document.dart';
|
||||||
@@ -68,7 +72,6 @@ export 'src/entities/ratings/business_staff_preference.dart';
|
|||||||
|
|
||||||
// Staff Profile
|
// Staff Profile
|
||||||
export 'src/entities/profile/emergency_contact.dart';
|
export 'src/entities/profile/emergency_contact.dart';
|
||||||
export 'src/entities/profile/bank_account.dart';
|
|
||||||
export 'src/entities/profile/accessibility.dart';
|
export 'src/entities/profile/accessibility.dart';
|
||||||
export 'src/entities/profile/schedule.dart';
|
export 'src/entities/profile/schedule.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import '../../../entities/financial/bank_account/business_bank_account.dart';
|
||||||
|
|
||||||
|
/// Adapter for [BusinessBankAccount] to map data layer values to domain entity.
|
||||||
|
class BusinessBankAccountAdapter {
|
||||||
|
/// Maps primitive values to [BusinessBankAccount].
|
||||||
|
static BusinessBankAccount fromPrimitives({
|
||||||
|
required String id,
|
||||||
|
required String bank,
|
||||||
|
required String last4,
|
||||||
|
required bool isPrimary,
|
||||||
|
DateTime? expiryTime,
|
||||||
|
}) {
|
||||||
|
return BusinessBankAccount(
|
||||||
|
id: id,
|
||||||
|
bankName: bank,
|
||||||
|
last4: last4,
|
||||||
|
isPrimary: isPrimary,
|
||||||
|
expiryTime: expiryTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import '../../entities/profile/bank_account.dart';
|
import '../../entities/financial/bank_account/staff_bank_account.dart';
|
||||||
|
|
||||||
/// Adapter for [BankAccount] to map data layer values to domain entity.
|
/// Adapter for [StaffBankAccount] to map data layer values to domain entity.
|
||||||
class BankAccountAdapter {
|
class BankAccountAdapter {
|
||||||
/// Maps primitive values to [BankAccount].
|
/// Maps primitive values to [StaffBankAccount].
|
||||||
static BankAccount fromPrimitives({
|
static StaffBankAccount fromPrimitives({
|
||||||
required String id,
|
required String id,
|
||||||
required String userId,
|
required String userId,
|
||||||
required String bankName,
|
required String bankName,
|
||||||
@@ -13,7 +13,7 @@ class BankAccountAdapter {
|
|||||||
String? sortCode,
|
String? sortCode,
|
||||||
bool? isPrimary,
|
bool? isPrimary,
|
||||||
}) {
|
}) {
|
||||||
return BankAccount(
|
return StaffBankAccount(
|
||||||
id: id,
|
id: id,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
bankName: bankName,
|
bankName: bankName,
|
||||||
@@ -26,25 +26,25 @@ class BankAccountAdapter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static BankAccountType _stringToType(String? value) {
|
static StaffBankAccountType _stringToType(String? value) {
|
||||||
if (value == null) return BankAccountType.checking;
|
if (value == null) return StaffBankAccountType.checking;
|
||||||
try {
|
try {
|
||||||
// Assuming backend enum names match or are uppercase
|
// Assuming backend enum names match or are uppercase
|
||||||
return BankAccountType.values.firstWhere(
|
return StaffBankAccountType.values.firstWhere(
|
||||||
(e) => e.name.toLowerCase() == value.toLowerCase(),
|
(StaffBankAccountType e) => e.name.toLowerCase() == value.toLowerCase(),
|
||||||
orElse: () => BankAccountType.other,
|
orElse: () => StaffBankAccountType.other,
|
||||||
);
|
);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return BankAccountType.other;
|
return StaffBankAccountType.other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts domain type to string for backend.
|
/// Converts domain type to string for backend.
|
||||||
static String typeToString(BankAccountType type) {
|
static String typeToString(StaffBankAccountType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case BankAccountType.checking:
|
case StaffBankAccountType.checking:
|
||||||
return 'CHECKING';
|
return 'CHECKING';
|
||||||
case BankAccountType.savings:
|
case StaffBankAccountType.savings:
|
||||||
return 'SAVINGS';
|
return 'SAVINGS';
|
||||||
default:
|
default:
|
||||||
return 'CHECKING';
|
return 'CHECKING';
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
/// Abstract base class for all types of bank accounts.
|
||||||
|
abstract class BankAccount extends Equatable {
|
||||||
|
/// Creates a [BankAccount].
|
||||||
|
const BankAccount({
|
||||||
|
required this.id,
|
||||||
|
required this.bankName,
|
||||||
|
required this.isPrimary,
|
||||||
|
this.last4,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Unique identifier.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// Name of the bank or provider.
|
||||||
|
final String bankName;
|
||||||
|
|
||||||
|
/// Whether this is the primary payment method.
|
||||||
|
final bool isPrimary;
|
||||||
|
|
||||||
|
/// Last 4 digits of the account/card.
|
||||||
|
final String? last4;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[id, bankName, isPrimary, last4];
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'bank_account.dart';
|
||||||
|
|
||||||
|
/// Domain model representing a business bank account or payment method.
|
||||||
|
class BusinessBankAccount extends BankAccount {
|
||||||
|
/// Creates a [BusinessBankAccount].
|
||||||
|
const BusinessBankAccount({
|
||||||
|
required super.id,
|
||||||
|
required super.bankName,
|
||||||
|
required String last4,
|
||||||
|
required super.isPrimary,
|
||||||
|
this.expiryTime,
|
||||||
|
}) : super(last4: last4);
|
||||||
|
|
||||||
|
/// Expiration date if applicable.
|
||||||
|
final DateTime? expiryTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[
|
||||||
|
...super.props,
|
||||||
|
expiryTime,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Getter for non-nullable last4 in Business context.
|
||||||
|
@override
|
||||||
|
String get last4 => super.last4!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import 'bank_account.dart';
|
||||||
|
|
||||||
|
/// Type of staff bank account.
|
||||||
|
enum StaffBankAccountType {
|
||||||
|
/// Checking account.
|
||||||
|
checking,
|
||||||
|
|
||||||
|
/// Savings account.
|
||||||
|
savings,
|
||||||
|
|
||||||
|
/// Other type.
|
||||||
|
other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Domain entity representing a staff's bank account.
|
||||||
|
class StaffBankAccount extends BankAccount {
|
||||||
|
/// Creates a [StaffBankAccount].
|
||||||
|
const StaffBankAccount({
|
||||||
|
required super.id,
|
||||||
|
required this.userId,
|
||||||
|
required super.bankName,
|
||||||
|
required this.accountNumber,
|
||||||
|
required this.accountName,
|
||||||
|
required super.isPrimary,
|
||||||
|
super.last4,
|
||||||
|
this.sortCode,
|
||||||
|
this.type = StaffBankAccountType.checking,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// User identifier.
|
||||||
|
final String userId;
|
||||||
|
|
||||||
|
/// Full account number.
|
||||||
|
final String accountNumber;
|
||||||
|
|
||||||
|
/// Name of the account holder.
|
||||||
|
final String accountName;
|
||||||
|
|
||||||
|
/// Sort code (optional).
|
||||||
|
final String? sortCode;
|
||||||
|
|
||||||
|
/// Account type.
|
||||||
|
final StaffBankAccountType type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props =>
|
||||||
|
<Object?>[...super.props, userId, accountNumber, accountName, sortCode, type];
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
|
|
||||||
/// Account type (Checking, Savings, etc).
|
|
||||||
enum BankAccountType {
|
|
||||||
checking,
|
|
||||||
savings,
|
|
||||||
other,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents bank account details for payroll.
|
|
||||||
class BankAccount extends Equatable {
|
|
||||||
|
|
||||||
const BankAccount({
|
|
||||||
required this.id,
|
|
||||||
required this.userId,
|
|
||||||
required this.bankName,
|
|
||||||
required this.accountNumber,
|
|
||||||
required this.accountName,
|
|
||||||
this.sortCode,
|
|
||||||
this.type = BankAccountType.checking,
|
|
||||||
this.isPrimary = false,
|
|
||||||
this.last4,
|
|
||||||
});
|
|
||||||
/// Unique identifier.
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
/// The [User] owning the account.
|
|
||||||
final String userId;
|
|
||||||
|
|
||||||
/// Name of the bank.
|
|
||||||
final String bankName;
|
|
||||||
|
|
||||||
/// Account number.
|
|
||||||
final String accountNumber;
|
|
||||||
|
|
||||||
/// Name on the account.
|
|
||||||
final String accountName;
|
|
||||||
|
|
||||||
/// Sort code (if applicable).
|
|
||||||
final String? sortCode;
|
|
||||||
|
|
||||||
/// Type of account.
|
|
||||||
final BankAccountType type;
|
|
||||||
|
|
||||||
/// Whether this is the primary account.
|
|
||||||
final bool isPrimary;
|
|
||||||
|
|
||||||
/// Last 4 digits.
|
|
||||||
final String? last4;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => <Object?>[id, userId, bankName, accountNumber, accountName, sortCode, type, isPrimary, last4];
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ import 'package:krow_core/core.dart';
|
|||||||
|
|
||||||
import 'data/repositories_impl/billing_repository_impl.dart';
|
import 'data/repositories_impl/billing_repository_impl.dart';
|
||||||
import 'domain/repositories/billing_repository.dart';
|
import 'domain/repositories/billing_repository.dart';
|
||||||
|
import 'domain/usecases/get_bank_accounts.dart';
|
||||||
import 'domain/usecases/get_current_bill_amount.dart';
|
import 'domain/usecases/get_current_bill_amount.dart';
|
||||||
import 'domain/usecases/get_invoice_history.dart';
|
import 'domain/usecases/get_invoice_history.dart';
|
||||||
import 'domain/usecases/get_pending_invoices.dart';
|
import 'domain/usecases/get_pending_invoices.dart';
|
||||||
@@ -21,6 +22,7 @@ class BillingModule extends Module {
|
|||||||
i.addSingleton<BillingRepository>(BillingRepositoryImpl.new);
|
i.addSingleton<BillingRepository>(BillingRepositoryImpl.new);
|
||||||
|
|
||||||
// Use Cases
|
// Use Cases
|
||||||
|
i.addSingleton(GetBankAccountsUseCase.new);
|
||||||
i.addSingleton(GetCurrentBillAmountUseCase.new);
|
i.addSingleton(GetCurrentBillAmountUseCase.new);
|
||||||
i.addSingleton(GetSavingsAmountUseCase.new);
|
i.addSingleton(GetSavingsAmountUseCase.new);
|
||||||
i.addSingleton(GetPendingInvoicesUseCase.new);
|
i.addSingleton(GetPendingInvoicesUseCase.new);
|
||||||
@@ -30,6 +32,7 @@ class BillingModule extends Module {
|
|||||||
// BLoCs
|
// BLoCs
|
||||||
i.addSingleton<BillingBloc>(
|
i.addSingleton<BillingBloc>(
|
||||||
() => BillingBloc(
|
() => BillingBloc(
|
||||||
|
getBankAccounts: i.get<GetBankAccountsUseCase>(),
|
||||||
getCurrentBillAmount: i.get<GetCurrentBillAmountUseCase>(),
|
getCurrentBillAmount: i.get<GetCurrentBillAmountUseCase>(),
|
||||||
getSavingsAmount: i.get<GetSavingsAmountUseCase>(),
|
getSavingsAmount: i.get<GetSavingsAmountUseCase>(),
|
||||||
getPendingInvoices: i.get<GetPendingInvoicesUseCase>(),
|
getPendingInvoices: i.get<GetPendingInvoicesUseCase>(),
|
||||||
|
|||||||
@@ -16,6 +16,23 @@ class BillingRepositoryImpl implements BillingRepository {
|
|||||||
|
|
||||||
final data_connect.DataConnectService _service;
|
final data_connect.DataConnectService _service;
|
||||||
|
|
||||||
|
/// Fetches bank accounts associated with the business.
|
||||||
|
@override
|
||||||
|
Future<List<BusinessBankAccount>> getBankAccounts() async {
|
||||||
|
return _service.run(() async {
|
||||||
|
final String businessId = await _service.getBusinessId();
|
||||||
|
|
||||||
|
final fdc.QueryResult<
|
||||||
|
data_connect.GetAccountsByOwnerIdData,
|
||||||
|
data_connect.GetAccountsByOwnerIdVariables> result =
|
||||||
|
await _service.connector
|
||||||
|
.getAccountsByOwnerId(ownerId: businessId)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return result.data.accounts.map(_mapBankAccount).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetches the current bill amount by aggregating open invoices.
|
/// Fetches the current bill amount by aggregating open invoices.
|
||||||
@override
|
@override
|
||||||
Future<double> getCurrentBillAmount() async {
|
Future<double> getCurrentBillAmount() async {
|
||||||
@@ -182,6 +199,18 @@ class BillingRepositoryImpl implements BillingRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BusinessBankAccount _mapBankAccount(
|
||||||
|
data_connect.GetAccountsByOwnerIdAccounts account,
|
||||||
|
) {
|
||||||
|
return BusinessBankAccountAdapter.fromPrimitives(
|
||||||
|
id: account.id,
|
||||||
|
bank: account.bank,
|
||||||
|
last4: account.last4,
|
||||||
|
isPrimary: account.isPrimary ?? false,
|
||||||
|
expiryTime: _service.toDateTime(account.expiryTime),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
InvoiceStatus _mapInvoiceStatus(
|
InvoiceStatus _mapInvoiceStatus(
|
||||||
data_connect.EnumValue<data_connect.InvoiceStatus> status,
|
data_connect.EnumValue<data_connect.InvoiceStatus> status,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import '../models/billing_period.dart';
|
|||||||
/// acting as a boundary between the Domain and Data layers.
|
/// acting as a boundary between the Domain and Data layers.
|
||||||
/// It allows the Domain layer to remain independent of specific data sources.
|
/// It allows the Domain layer to remain independent of specific data sources.
|
||||||
abstract class BillingRepository {
|
abstract class BillingRepository {
|
||||||
|
/// Fetches bank accounts associated with the business.
|
||||||
|
Future<List<BusinessBankAccount>> getBankAccounts();
|
||||||
|
|
||||||
/// Fetches invoices that are pending approval or payment.
|
/// Fetches invoices that are pending approval or payment.
|
||||||
Future<List<Invoice>> getPendingInvoices();
|
Future<List<Invoice>> getPendingInvoices();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../repositories/billing_repository.dart';
|
||||||
|
|
||||||
|
/// Use case for fetching the bank accounts associated with the business.
|
||||||
|
class GetBankAccountsUseCase extends NoInputUseCase<List<BusinessBankAccount>> {
|
||||||
|
/// Creates a [GetBankAccountsUseCase].
|
||||||
|
GetBankAccountsUseCase(this._repository);
|
||||||
|
|
||||||
|
final BillingRepository _repository;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<BusinessBankAccount>> call() => _repository.getBankAccounts();
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../../domain/usecases/get_bank_accounts.dart';
|
||||||
import '../../domain/usecases/get_current_bill_amount.dart';
|
import '../../domain/usecases/get_current_bill_amount.dart';
|
||||||
import '../../domain/usecases/get_invoice_history.dart';
|
import '../../domain/usecases/get_invoice_history.dart';
|
||||||
import '../../domain/usecases/get_pending_invoices.dart';
|
import '../../domain/usecases/get_pending_invoices.dart';
|
||||||
@@ -16,12 +17,14 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
with BlocErrorHandler<BillingState> {
|
with BlocErrorHandler<BillingState> {
|
||||||
/// Creates a [BillingBloc] with the given use cases.
|
/// Creates a [BillingBloc] with the given use cases.
|
||||||
BillingBloc({
|
BillingBloc({
|
||||||
|
required GetBankAccountsUseCase getBankAccounts,
|
||||||
required GetCurrentBillAmountUseCase getCurrentBillAmount,
|
required GetCurrentBillAmountUseCase getCurrentBillAmount,
|
||||||
required GetSavingsAmountUseCase getSavingsAmount,
|
required GetSavingsAmountUseCase getSavingsAmount,
|
||||||
required GetPendingInvoicesUseCase getPendingInvoices,
|
required GetPendingInvoicesUseCase getPendingInvoices,
|
||||||
required GetInvoiceHistoryUseCase getInvoiceHistory,
|
required GetInvoiceHistoryUseCase getInvoiceHistory,
|
||||||
required GetSpendingBreakdownUseCase getSpendingBreakdown,
|
required GetSpendingBreakdownUseCase getSpendingBreakdown,
|
||||||
}) : _getCurrentBillAmount = getCurrentBillAmount,
|
}) : _getBankAccounts = getBankAccounts,
|
||||||
|
_getCurrentBillAmount = getCurrentBillAmount,
|
||||||
_getSavingsAmount = getSavingsAmount,
|
_getSavingsAmount = getSavingsAmount,
|
||||||
_getPendingInvoices = getPendingInvoices,
|
_getPendingInvoices = getPendingInvoices,
|
||||||
_getInvoiceHistory = getInvoiceHistory,
|
_getInvoiceHistory = getInvoiceHistory,
|
||||||
@@ -31,6 +34,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
on<BillingPeriodChanged>(_onPeriodChanged);
|
on<BillingPeriodChanged>(_onPeriodChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final GetBankAccountsUseCase _getBankAccounts;
|
||||||
final GetCurrentBillAmountUseCase _getCurrentBillAmount;
|
final GetCurrentBillAmountUseCase _getCurrentBillAmount;
|
||||||
final GetSavingsAmountUseCase _getSavingsAmount;
|
final GetSavingsAmountUseCase _getSavingsAmount;
|
||||||
final GetPendingInvoicesUseCase _getPendingInvoices;
|
final GetPendingInvoicesUseCase _getPendingInvoices;
|
||||||
@@ -52,12 +56,15 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
_getPendingInvoices.call(),
|
_getPendingInvoices.call(),
|
||||||
_getInvoiceHistory.call(),
|
_getInvoiceHistory.call(),
|
||||||
_getSpendingBreakdown.call(state.period),
|
_getSpendingBreakdown.call(state.period),
|
||||||
|
_getBankAccounts.call(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final double savings = results[1] as double;
|
final double savings = results[1] as double;
|
||||||
final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
|
final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
|
||||||
final List<Invoice> invoiceHistory = results[3] as List<Invoice>;
|
final List<Invoice> invoiceHistory = results[3] as List<Invoice>;
|
||||||
final List<InvoiceItem> spendingItems = results[4] as List<InvoiceItem>;
|
final List<InvoiceItem> spendingItems = results[4] as List<InvoiceItem>;
|
||||||
|
final List<BusinessBankAccount> bankAccounts =
|
||||||
|
results[5] as List<BusinessBankAccount>;
|
||||||
|
|
||||||
// Map Domain Entities to Presentation Models
|
// Map Domain Entities to Presentation Models
|
||||||
final List<BillingInvoice> uiPendingInvoices =
|
final List<BillingInvoice> uiPendingInvoices =
|
||||||
@@ -79,6 +86,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
|||||||
pendingInvoices: uiPendingInvoices,
|
pendingInvoices: uiPendingInvoices,
|
||||||
invoiceHistory: uiInvoiceHistory,
|
invoiceHistory: uiInvoiceHistory,
|
||||||
spendingBreakdown: uiSpendingBreakdown,
|
spendingBreakdown: uiSpendingBreakdown,
|
||||||
|
bankAccounts: bankAccounts,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../../domain/models/billing_period.dart';
|
import '../../domain/models/billing_period.dart';
|
||||||
import '../models/billing_invoice_model.dart';
|
import '../models/billing_invoice_model.dart';
|
||||||
import '../models/spending_breakdown_model.dart';
|
import '../models/spending_breakdown_model.dart';
|
||||||
@@ -28,6 +29,7 @@ class BillingState extends Equatable {
|
|||||||
this.pendingInvoices = const <BillingInvoice>[],
|
this.pendingInvoices = const <BillingInvoice>[],
|
||||||
this.invoiceHistory = const <BillingInvoice>[],
|
this.invoiceHistory = const <BillingInvoice>[],
|
||||||
this.spendingBreakdown = const <SpendingBreakdownItem>[],
|
this.spendingBreakdown = const <SpendingBreakdownItem>[],
|
||||||
|
this.bankAccounts = const <BusinessBankAccount>[],
|
||||||
this.period = BillingPeriod.week,
|
this.period = BillingPeriod.week,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
@@ -50,6 +52,9 @@ class BillingState extends Equatable {
|
|||||||
/// Breakdown of spending by category.
|
/// Breakdown of spending by category.
|
||||||
final List<SpendingBreakdownItem> spendingBreakdown;
|
final List<SpendingBreakdownItem> spendingBreakdown;
|
||||||
|
|
||||||
|
/// Bank accounts associated with the business.
|
||||||
|
final List<BusinessBankAccount> bankAccounts;
|
||||||
|
|
||||||
/// Selected period for the breakdown.
|
/// Selected period for the breakdown.
|
||||||
final BillingPeriod period;
|
final BillingPeriod period;
|
||||||
|
|
||||||
@@ -64,6 +69,7 @@ class BillingState extends Equatable {
|
|||||||
List<BillingInvoice>? pendingInvoices,
|
List<BillingInvoice>? pendingInvoices,
|
||||||
List<BillingInvoice>? invoiceHistory,
|
List<BillingInvoice>? invoiceHistory,
|
||||||
List<SpendingBreakdownItem>? spendingBreakdown,
|
List<SpendingBreakdownItem>? spendingBreakdown,
|
||||||
|
List<BusinessBankAccount>? bankAccounts,
|
||||||
BillingPeriod? period,
|
BillingPeriod? period,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
@@ -74,6 +80,7 @@ class BillingState extends Equatable {
|
|||||||
pendingInvoices: pendingInvoices ?? this.pendingInvoices,
|
pendingInvoices: pendingInvoices ?? this.pendingInvoices,
|
||||||
invoiceHistory: invoiceHistory ?? this.invoiceHistory,
|
invoiceHistory: invoiceHistory ?? this.invoiceHistory,
|
||||||
spendingBreakdown: spendingBreakdown ?? this.spendingBreakdown,
|
spendingBreakdown: spendingBreakdown ?? this.spendingBreakdown,
|
||||||
|
bankAccounts: bankAccounts ?? this.bankAccounts,
|
||||||
period: period ?? this.period,
|
period: period ?? this.period,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
);
|
);
|
||||||
@@ -87,6 +94,7 @@ class BillingState extends Equatable {
|
|||||||
pendingInvoices,
|
pendingInvoices,
|
||||||
invoiceHistory,
|
invoiceHistory,
|
||||||
spendingBreakdown,
|
spendingBreakdown,
|
||||||
|
bankAccounts,
|
||||||
period,
|
period,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -71,19 +71,20 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocConsumer<BillingBloc, BillingState>(
|
return Scaffold(
|
||||||
listener: (BuildContext context, BillingState state) {
|
body: BlocConsumer<BillingBloc, BillingState>(
|
||||||
if (state.status == BillingStatus.failure && state.errorMessage != null) {
|
listener: (BuildContext context, BillingState state) {
|
||||||
UiSnackbar.show(
|
if (state.status == BillingStatus.failure &&
|
||||||
context,
|
state.errorMessage != null) {
|
||||||
message: translateErrorKey(state.errorMessage!),
|
UiSnackbar.show(
|
||||||
type: UiSnackbarType.error,
|
context,
|
||||||
);
|
message: translateErrorKey(state.errorMessage!),
|
||||||
}
|
type: UiSnackbarType.error,
|
||||||
},
|
);
|
||||||
builder: (BuildContext context, BillingState state) {
|
}
|
||||||
return Scaffold(
|
},
|
||||||
body: CustomScrollView(
|
builder: (BuildContext context, BillingState state) {
|
||||||
|
return CustomScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
@@ -97,7 +98,7 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
leading: Center(
|
leading: Center(
|
||||||
child: UiIconButton.secondary(
|
child: UiIconButton.secondary(
|
||||||
icon: UiIcons.arrowLeft,
|
icon: UiIcons.arrowLeft,
|
||||||
onTap: () => Modular.to.toClientHome()
|
onTap: () => Modular.to.toClientHome(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: AnimatedSwitcher(
|
title: AnimatedSwitcher(
|
||||||
@@ -132,8 +133,9 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
const SizedBox(height: UiConstants.space1),
|
const SizedBox(height: UiConstants.space1),
|
||||||
Text(
|
Text(
|
||||||
'\$${state.currentBill.toStringAsFixed(2)}',
|
'\$${state.currentBill.toStringAsFixed(2)}',
|
||||||
style: UiTypography.display1b
|
style: UiTypography.display1b.copyWith(
|
||||||
.copyWith(color: UiColors.white),
|
color: UiColors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space2),
|
const SizedBox(height: UiConstants.space2),
|
||||||
Container(
|
Container(
|
||||||
@@ -171,16 +173,14 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate(
|
delegate: SliverChildListDelegate(<Widget>[
|
||||||
<Widget>[
|
_buildContent(context, state),
|
||||||
_buildContent(context, state),
|
]),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,9 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
const SizedBox(height: UiConstants.space4),
|
const SizedBox(height: UiConstants.space4),
|
||||||
UiButton.secondary(
|
UiButton.secondary(
|
||||||
text: 'Retry',
|
text: 'Retry',
|
||||||
onPressed: () => BlocProvider.of<BillingBloc>(context).add(const BillingLoadStarted()),
|
onPressed: () => BlocProvider.of<BillingBloc>(
|
||||||
|
context,
|
||||||
|
).add(const BillingLoadStarted()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -230,8 +232,10 @@ class _BillingViewState extends State<BillingView> {
|
|||||||
],
|
],
|
||||||
const PaymentMethodCard(),
|
const PaymentMethodCard(),
|
||||||
const SpendingBreakdownCard(),
|
const SpendingBreakdownCard(),
|
||||||
if (state.invoiceHistory.isEmpty) _buildEmptyState(context)
|
if (state.invoiceHistory.isEmpty)
|
||||||
else InvoiceHistorySection(invoices: state.invoiceHistory),
|
_buildEmptyState(context)
|
||||||
|
else
|
||||||
|
InvoiceHistorySection(invoices: state.invoiceHistory),
|
||||||
|
|
||||||
const SizedBox(height: UiConstants.space32),
|
const SizedBox(height: UiConstants.space32),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,166 +1,133 @@
|
|||||||
import 'package:core_localization/core_localization.dart';
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../blocs/billing_bloc.dart';
|
||||||
|
import '../blocs/billing_state.dart';
|
||||||
|
|
||||||
/// Card showing the current payment method.
|
/// Card showing the current payment method.
|
||||||
class PaymentMethodCard extends StatefulWidget {
|
class PaymentMethodCard extends StatelessWidget {
|
||||||
/// Creates a [PaymentMethodCard].
|
/// Creates a [PaymentMethodCard].
|
||||||
const PaymentMethodCard({super.key});
|
const PaymentMethodCard({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<PaymentMethodCard> createState() => _PaymentMethodCardState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PaymentMethodCardState extends State<PaymentMethodCard> {
|
|
||||||
late final Future<dc.GetAccountsByOwnerIdData?> _accountsFuture =
|
|
||||||
_loadAccounts();
|
|
||||||
|
|
||||||
Future<dc.GetAccountsByOwnerIdData?> _loadAccounts() async {
|
|
||||||
final String? businessId =
|
|
||||||
dc.ClientSessionStore.instance.session?.business?.id;
|
|
||||||
if (businessId == null || businessId.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final fdc.QueryResult<
|
|
||||||
dc.GetAccountsByOwnerIdData,
|
|
||||||
dc.GetAccountsByOwnerIdVariables
|
|
||||||
>
|
|
||||||
result = await dc.ExampleConnector.instance
|
|
||||||
.getAccountsByOwnerId(ownerId: businessId)
|
|
||||||
.execute();
|
|
||||||
return result.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<dc.GetAccountsByOwnerIdData?>(
|
return BlocBuilder<BillingBloc, BillingState>(
|
||||||
future: _accountsFuture,
|
builder: (BuildContext context, BillingState state) {
|
||||||
builder:
|
final List<BusinessBankAccount> accounts = state.bankAccounts;
|
||||||
(
|
final BusinessBankAccount? account =
|
||||||
BuildContext context,
|
accounts.isNotEmpty ? accounts.first : null;
|
||||||
AsyncSnapshot<dc.GetAccountsByOwnerIdData?> snapshot,
|
|
||||||
) {
|
|
||||||
final List<dc.GetAccountsByOwnerIdAccounts> accounts =
|
|
||||||
snapshot.data?.accounts ?? <dc.GetAccountsByOwnerIdAccounts>[];
|
|
||||||
final dc.GetAccountsByOwnerIdAccounts? account = accounts.isNotEmpty
|
|
||||||
? accounts.first
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final String bankLabel = account.bank.isNotEmpty == true
|
final String bankLabel =
|
||||||
? account.bank
|
account.bankName.isNotEmpty == true ? account.bankName : '----';
|
||||||
: '----';
|
final String last4 =
|
||||||
final String last4 = account.last4.isNotEmpty == true
|
account.last4.isNotEmpty == true ? account.last4 : '----';
|
||||||
? account.last4
|
final bool isPrimary = account.isPrimary;
|
||||||
: '----';
|
final String expiryLabel = _formatExpiry(account.expiryTime);
|
||||||
final bool isPrimary = account.isPrimary ?? false;
|
|
||||||
final String expiryLabel = _formatExpiry(account.expiryTime);
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: UiColors.white,
|
color: UiColors.white,
|
||||||
borderRadius: UiConstants.radiusLg,
|
borderRadius: UiConstants.radiusLg,
|
||||||
border: Border.all(color: UiColors.border),
|
border: Border.all(color: UiColors.border),
|
||||||
boxShadow: <BoxShadow>[
|
boxShadow: <BoxShadow>[
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: UiColors.black.withValues(alpha: 0.04),
|
color: UiColors.black.withValues(alpha: 0.04),
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Column(
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
t.client_billing.payment_method,
|
||||||
children: <Widget>[
|
style: UiTypography.title2b.textPrimary,
|
||||||
Text(
|
|
||||||
t.client_billing.payment_method,
|
|
||||||
style: UiTypography.title2b.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox.shrink(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space3),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space3),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.bgSecondary,
|
|
||||||
borderRadius: UiConstants.radiusMd,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: UiConstants.space10,
|
|
||||||
height: UiConstants.space6 + 4,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.primary,
|
|
||||||
borderRadius: UiConstants.radiusSm,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
bankLabel,
|
|
||||||
style: UiTypography.footnote2b.white,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: UiConstants.space3),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
'•••• $last4',
|
|
||||||
style: UiTypography.body2b.textPrimary,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
t.client_billing.expires(date: expiryLabel),
|
|
||||||
style: UiTypography.footnote2r.textSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isPrimary)
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space2,
|
|
||||||
vertical: UiConstants.space1,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.accent,
|
|
||||||
borderRadius: UiConstants.radiusSm,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
t.client_billing.default_badge,
|
|
||||||
style: UiTypography.titleUppercase4b.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
const SizedBox.shrink(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(height: UiConstants.space3),
|
||||||
},
|
Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.bgSecondary,
|
||||||
|
borderRadius: UiConstants.radiusMd,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
width: UiConstants.space10,
|
||||||
|
height: UiConstants.space6 + 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.primary,
|
||||||
|
borderRadius: UiConstants.radiusSm,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
bankLabel,
|
||||||
|
style: UiTypography.footnote2b.white,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: UiConstants.space3),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
'•••• $last4',
|
||||||
|
style: UiTypography.body2b.textPrimary,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
t.client_billing.expires(date: expiryLabel),
|
||||||
|
style: UiTypography.footnote2r.textSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isPrimary)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space2,
|
||||||
|
vertical: UiConstants.space1,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.accent,
|
||||||
|
borderRadius: UiConstants.radiusSm,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
t.client_billing.default_badge,
|
||||||
|
style: UiTypography.titleUppercase4b.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatExpiry(fdc.Timestamp? expiryTime) {
|
String _formatExpiry(DateTime? expiryTime) {
|
||||||
if (expiryTime == null) {
|
if (expiryTime == null) {
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
}
|
}
|
||||||
final DateTime date = expiryTime.toDateTime();
|
final String month = expiryTime.month.toString().padLeft(2, '0');
|
||||||
final String month = date.month.toString().padLeft(2, '0');
|
final String year = (expiryTime.year % 100).toString().padLeft(2, '0');
|
||||||
final String year = (date.year % 100).toString().padLeft(2, '0');
|
|
||||||
return '$month/$year';
|
return '$month/$year';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
/// A widget that displays quick actions for the client.
|
/// A widget that displays quick actions for the client.
|
||||||
class ActionsWidget extends StatelessWidget {
|
class ActionsWidget extends StatelessWidget {
|
||||||
|
|
||||||
/// Creates an [ActionsWidget].
|
/// Creates an [ActionsWidget].
|
||||||
const ActionsWidget({
|
const ActionsWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -12,6 +11,7 @@ class ActionsWidget extends StatelessWidget {
|
|||||||
required this.onCreateOrderPressed,
|
required this.onCreateOrderPressed,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Callback when RAPID is pressed.
|
/// Callback when RAPID is pressed.
|
||||||
final VoidCallback onRapidPressed;
|
final VoidCallback onRapidPressed;
|
||||||
|
|
||||||
@@ -26,12 +26,9 @@ class ActionsWidget extends StatelessWidget {
|
|||||||
// Check if client_home exists in t
|
// Check if client_home exists in t
|
||||||
final TranslationsClientHomeActionsEn i18n = t.client_home.actions;
|
final TranslationsClientHomeActionsEn i18n = t.client_home.actions;
|
||||||
|
|
||||||
return Column(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
spacing: UiConstants.space4,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
/// TODO: FEATURE_NOT_YET_IMPLEMENTED
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _ActionCard(
|
child: _ActionCard(
|
||||||
title: i18n.rapid,
|
title: i18n.rapid,
|
||||||
@@ -46,7 +43,6 @@ class ActionsWidget extends StatelessWidget {
|
|||||||
onTap: onRapidPressed,
|
onTap: onRapidPressed,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// const SizedBox(width: UiConstants.space2),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _ActionCard(
|
child: _ActionCard(
|
||||||
title: i18n.create_order,
|
title: i18n.create_order,
|
||||||
@@ -62,14 +58,11 @@ class ActionsWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ActionCard extends StatelessWidget {
|
class _ActionCard extends StatelessWidget {
|
||||||
|
|
||||||
const _ActionCard({
|
const _ActionCard({
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
|
|||||||
@@ -14,13 +14,10 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
|
|||||||
final DataConnectService _service;
|
final DataConnectService _service;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<BankAccount>> getAccounts() async {
|
Future<List<StaffBankAccount>> getAccounts() async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final String staffId = await _service.getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
|
|
||||||
var x = staffId;
|
|
||||||
|
|
||||||
print(x);
|
|
||||||
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
||||||
result = await _service.connector
|
result = await _service.connector
|
||||||
.getAccountsByOwnerId(ownerId: staffId)
|
.getAccountsByOwnerId(ownerId: staffId)
|
||||||
@@ -44,7 +41,7 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addAccount(BankAccount account) async {
|
Future<void> addAccount(StaffBankAccount account) async {
|
||||||
return _service.run(() async {
|
return _service.run(() async {
|
||||||
final String staffId = await _service.getStaffId();
|
final String staffId = await _service.getStaffId();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
|
|
||||||
/// Arguments for adding a bank account.
|
/// Arguments for adding a bank account.
|
||||||
class AddBankAccountParams extends UseCaseArgument with EquatableMixin {
|
class AddBankAccountParams extends UseCaseArgument with EquatableMixin {
|
||||||
final BankAccount account;
|
final StaffBankAccount account;
|
||||||
|
|
||||||
const AddBankAccountParams({required this.account});
|
const AddBankAccountParams({required this.account});
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
/// Repository interface for managing bank accounts.
|
/// Repository interface for managing bank accounts.
|
||||||
abstract class BankAccountRepository {
|
abstract class BankAccountRepository {
|
||||||
/// Fetches the list of bank accounts for the current user.
|
/// Fetches the list of bank accounts for the current user.
|
||||||
Future<List<BankAccount>> getAccounts();
|
Future<List<StaffBankAccount>> getAccounts();
|
||||||
|
|
||||||
/// adds a new bank account.
|
/// adds a new bank account.
|
||||||
Future<void> addAccount(BankAccount account);
|
Future<void> addAccount(StaffBankAccount account);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import '../repositories/bank_account_repository.dart';
|
import '../repositories/bank_account_repository.dart';
|
||||||
|
|
||||||
/// Use case to fetch bank accounts.
|
/// Use case to fetch bank accounts.
|
||||||
class GetBankAccountsUseCase implements NoInputUseCase<List<BankAccount>> {
|
class GetBankAccountsUseCase implements NoInputUseCase<List<StaffBankAccount>> {
|
||||||
final BankAccountRepository _repository;
|
final BankAccountRepository _repository;
|
||||||
|
|
||||||
GetBankAccountsUseCase(this._repository);
|
GetBankAccountsUseCase(this._repository);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<BankAccount>> call() {
|
Future<List<StaffBankAccount>> call() {
|
||||||
return _repository.getAccounts();
|
return _repository.getAccounts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,19 +23,15 @@ class BankAccountCubit extends Cubit<BankAccountState>
|
|||||||
await handleError(
|
await handleError(
|
||||||
emit: emit,
|
emit: emit,
|
||||||
action: () async {
|
action: () async {
|
||||||
final List<BankAccount> accounts = await _getBankAccountsUseCase();
|
final List<StaffBankAccount> accounts = await _getBankAccountsUseCase();
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(status: BankAccountStatus.loaded, accounts: accounts),
|
||||||
status: BankAccountStatus.loaded,
|
|
||||||
accounts: accounts,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError:
|
onError: (String errorKey) => state.copyWith(
|
||||||
(String errorKey) => state.copyWith(
|
status: BankAccountStatus.error,
|
||||||
status: BankAccountStatus.error,
|
errorMessage: errorKey,
|
||||||
errorMessage: errorKey,
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,21 +48,18 @@ class BankAccountCubit extends Cubit<BankAccountState>
|
|||||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||||
|
|
||||||
// Create domain entity
|
// Create domain entity
|
||||||
final BankAccount newAccount = BankAccount(
|
final StaffBankAccount newAccount = StaffBankAccount(
|
||||||
id: '', // Generated by server usually
|
id: '', // Generated by server usually
|
||||||
userId: '', // Handled by Repo/Auth
|
userId: '', // Handled by Repo/Auth
|
||||||
bankName: bankName,
|
bankName: bankName,
|
||||||
accountNumber: accountNumber,
|
accountNumber: accountNumber.length > 4
|
||||||
|
? accountNumber.substring(accountNumber.length - 4)
|
||||||
|
: accountNumber,
|
||||||
accountName: '',
|
accountName: '',
|
||||||
sortCode: routingNumber,
|
sortCode: routingNumber,
|
||||||
type:
|
type: type == 'CHECKING'
|
||||||
type == 'CHECKING'
|
? StaffBankAccountType.checking
|
||||||
? BankAccountType.checking
|
: StaffBankAccountType.savings,
|
||||||
: BankAccountType.savings,
|
|
||||||
last4:
|
|
||||||
accountNumber.length > 4
|
|
||||||
? accountNumber.substring(accountNumber.length - 4)
|
|
||||||
: accountNumber,
|
|
||||||
isPrimary: false,
|
isPrimary: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -85,12 +78,10 @@ class BankAccountCubit extends Cubit<BankAccountState>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError:
|
onError: (String errorKey) => state.copyWith(
|
||||||
(String errorKey) => state.copyWith(
|
status: BankAccountStatus.error,
|
||||||
status: BankAccountStatus.error,
|
errorMessage: errorKey,
|
||||||
errorMessage: errorKey,
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ enum BankAccountStatus { initial, loading, loaded, error, accountAdded }
|
|||||||
|
|
||||||
class BankAccountState extends Equatable {
|
class BankAccountState extends Equatable {
|
||||||
final BankAccountStatus status;
|
final BankAccountStatus status;
|
||||||
final List<BankAccount> accounts;
|
final List<StaffBankAccount> accounts;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
final bool showForm;
|
final bool showForm;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ class BankAccountState extends Equatable {
|
|||||||
|
|
||||||
BankAccountState copyWith({
|
BankAccountState copyWith({
|
||||||
BankAccountStatus? status,
|
BankAccountStatus? status,
|
||||||
List<BankAccount>? accounts,
|
List<StaffBankAccount>? accounts,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
bool? showForm,
|
bool? showForm,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class BankAccountPage extends StatelessWidget {
|
|||||||
style: UiTypography.headline4m.copyWith(color: UiColors.textPrimary),
|
style: UiTypography.headline4m.copyWith(color: UiColors.textPrimary),
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space3),
|
const SizedBox(height: UiConstants.space3),
|
||||||
...state.accounts.map((BankAccount a) => _buildAccountCard(a, strings)), // Added type
|
...state.accounts.map((StaffBankAccount a) => _buildAccountCard(a, strings)), // Added type
|
||||||
|
|
||||||
// Add extra padding at bottom
|
// Add extra padding at bottom
|
||||||
const SizedBox(height: UiConstants.space20),
|
const SizedBox(height: UiConstants.space20),
|
||||||
@@ -183,7 +183,7 @@ class BankAccountPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAccountCard(BankAccount account, dynamic strings) {
|
Widget _buildAccountCard(StaffBankAccount account, dynamic strings) {
|
||||||
final bool isPrimary = account.isPrimary;
|
final bool isPrimary = account.isPrimary;
|
||||||
const Color primaryColor = UiColors.primary;
|
const Color primaryColor = UiColors.primary;
|
||||||
|
|
||||||
|
|||||||
@@ -141,10 +141,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -741,6 +741,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -809,18 +817,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.18"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
melos:
|
melos:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -1318,26 +1326,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
|
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.29.0"
|
version: "1.26.3"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
version: "0.7.7"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
|
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.15"
|
version: "0.6.12"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# --- Mobile App Development ---
|
# --- Mobile App Development ---
|
||||||
|
|
||||||
.PHONY: mobile-install mobile-info mobile-client-dev-android mobile-staff-dev-android mobile-client-build mobile-staff-build mobile-hot-reload mobile-hot-restart
|
.PHONY: mobile-install mobile-info mobile-analyze mobile-client-dev-android mobile-staff-dev-android mobile-client-build mobile-staff-build mobile-hot-reload mobile-hot-restart
|
||||||
|
|
||||||
MOBILE_DIR := apps/mobile
|
MOBILE_DIR := apps/mobile
|
||||||
|
|
||||||
@@ -19,6 +19,10 @@ mobile-info:
|
|||||||
@echo "--> Fetching mobile command info..."
|
@echo "--> Fetching mobile command info..."
|
||||||
@cd $(MOBILE_DIR) && melos run info
|
@cd $(MOBILE_DIR) && melos run info
|
||||||
|
|
||||||
|
mobile-analyze:
|
||||||
|
@echo "--> Analyzing mobile workspace for compile-time errors..."
|
||||||
|
@cd $(MOBILE_DIR) && flutter analyze
|
||||||
|
|
||||||
# --- Hot Reload & Restart ---
|
# --- Hot Reload & Restart ---
|
||||||
mobile-hot-reload:
|
mobile-hot-reload:
|
||||||
@echo "--> Triggering hot reload for running Flutter app..."
|
@echo "--> Triggering hot reload for running Flutter app..."
|
||||||
|
|||||||
Reference in New Issue
Block a user