feat: integrate payment summary and adapter for staff earnings management

This commit is contained in:
Achintha Isuru
2026-01-30 17:37:08 -05:00
parent 1268da45b0
commit 452f029108
10 changed files with 138 additions and 100 deletions

View File

@@ -49,6 +49,7 @@ export 'src/entities/financial/time_card.dart';
export 'src/entities/financial/invoice_item.dart';
export 'src/entities/financial/invoice_decline.dart';
export 'src/entities/financial/staff_payment.dart';
export 'src/entities/financial/payment_summary.dart';
// Profile
export 'src/entities/profile/staff_document.dart';
@@ -91,3 +92,4 @@ export 'src/adapters/profile/experience_adapter.dart';
export 'src/entities/profile/experience_skill.dart';
export 'src/adapters/profile/bank_account_adapter.dart';
export 'src/adapters/profile/tax_form_adapter.dart';
export 'src/adapters/financial/payment_adapter.dart';

View File

@@ -0,0 +1,19 @@
import '../../entities/financial/staff_payment.dart';
/// Adapter for Payment related data.
class PaymentAdapter {
/// Converts string status to [PaymentStatus].
static PaymentStatus toPaymentStatus(String status) {
switch (status) {
case 'PAID':
return PaymentStatus.paid;
case 'PENDING':
return PaymentStatus.pending;
case 'FAILED':
return PaymentStatus.failed;
default:
return PaymentStatus.unknown;
}
}
}

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
/// Summary of staff earnings.
class PaymentSummary extends Equatable {
final double weeklyEarnings;
final double monthlyEarnings;

View File

@@ -13,6 +13,9 @@ enum PaymentStatus {
/// Transfer failed.
failed,
/// Status unknown.
unknown,
}
/// Represents a payout to a [Staff] member for a completed [Assignment].

View File

@@ -1,76 +1,111 @@
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' as dc;
import 'package:krow_data_connect/src/session/staff_session_store.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/entities/payment_summary.dart';
import '../../domain/repositories/payments_repository.dart';
class PaymentsRepositoryImpl implements PaymentsRepository {
PaymentsRepositoryImpl();
final dc.ExampleConnector _dataConnect;
PaymentsRepositoryImpl() : _dataConnect = dc.ExampleConnector.instance;
/// Helper to convert Data Connect Timestamp to DateTime
DateTime? _toDateTime(dynamic t) {
if (t == null) return null;
try {
// Attempt to deserialize via standard methods
return DateTime.tryParse(t.toJson() as String);
} catch (_) {
try {
return DateTime.tryParse(t.toString());
} catch (e) {
return null;
}
}
}
@override
Future<PaymentSummary> getPaymentSummary() async {
// Current requirement: Mock data only for summary
await Future.delayed(const Duration(milliseconds: 500));
final StaffSession? session = StaffSessionStore.instance.session;
if (session?.staff?.id == null) {
return const PaymentSummary(
weeklyEarnings: 847.50,
monthlyEarnings: 3240.0,
pendingEarnings: 285.0,
totalEarnings: 12450.0,
weeklyEarnings: 0,
monthlyEarnings: 0,
pendingEarnings: 0,
totalEarnings: 0,
);
}
final String currentStaffId = session!.staff!.id;
// Fetch recent payments with a limit
// Note: limit is chained on the query builder
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> result =
await _dataConnect.listRecentPaymentsByStaffId(
staffId: currentStaffId,
).limit(100).execute();
final List<dc.ListRecentPaymentsByStaffIdRecentPayments> payments = result.data.recentPayments;
double weekly = 0;
double monthly = 0;
double pending = 0;
double total = 0;
final DateTime now = DateTime.now();
final DateTime startOfWeek = now.subtract(const Duration(days: 7));
final DateTime startOfMonth = DateTime(now.year, now.month, 1);
for (final dc.ListRecentPaymentsByStaffIdRecentPayments p in payments) {
final DateTime? date = _toDateTime(p.invoice.issueDate) ?? _toDateTime(p.createdAt);
final double amount = p.invoice.amount;
final String? status = p.status?.stringValue;
if (status == 'PENDING') {
pending += amount;
} else if (status == 'PAID') {
total += amount;
if (date != null) {
if (date.isAfter(startOfWeek)) weekly += amount;
if (date.isAfter(startOfMonth)) monthly += amount;
}
}
}
return PaymentSummary(
weeklyEarnings: weekly,
monthlyEarnings: monthly,
pendingEarnings: pending,
totalEarnings: total,
);
}
@override
Future<List<StaffPayment>> getPaymentHistory(String period) async {
final session = StaffSessionStore.instance.session;
if (session?.staff?.id == null) return [];
final StaffSession? session = StaffSessionStore.instance.session;
if (session?.staff?.id == null) return <StaffPayment>[];
final String currentStaffId = session!.staff!.id;
try {
final response = await ExampleConnector.instance
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> response =
await _dataConnect
.listRecentPaymentsByStaffId(staffId: currentStaffId)
.execute();
return response.data.recentPayments.map((payment) {
return response.data.recentPayments.map((dc.ListRecentPaymentsByStaffIdRecentPayments payment) {
return StaffPayment(
id: payment.id,
staffId: payment.staffId,
assignmentId: payment.applicationId,
amount: payment.invoice.amount,
status: _mapStatus(payment.status),
paidAt: payment.invoice.issueDate?.toDate(),
status: PaymentAdapter.toPaymentStatus(payment.status?.stringValue ?? 'UNKNOWN'),
paidAt: _toDateTime(payment.invoice.issueDate),
);
}).toList();
} catch (e) {
return [];
}
}
PaymentStatus _mapStatus(EnumValue<RecentPaymentStatus>? status) {
if (status == null || status is! Known) return PaymentStatus.pending;
switch ((status as Known).value) {
case RecentPaymentStatus.PAID:
return PaymentStatus.paid;
case RecentPaymentStatus.PENDING:
return PaymentStatus.pending;
case RecentPaymentStatus.FAILED:
return PaymentStatus.failed;
default:
return PaymentStatus.pending;
return <StaffPayment>[];
}
}
}
extension on DateTime {
// Simple toDate if needed, but Data Connect Timestamp has toDate() usually
// or we need the extension from earlier
}
extension TimestampExt on Timestamp {
DateTime toDate() {
return DateTime.fromMillisecondsSinceEpoch(seconds.toInt() * 1000 + nanoseconds ~/ 1000000);
}
}

View File

@@ -1,5 +1,4 @@
import 'package:krow_domain/krow_domain.dart';
import '../entities/payment_summary.dart';
/// Repository interface for Payments feature.
///

View File

@@ -1,5 +1,5 @@
import 'package:krow_core/core.dart';
import '../entities/payment_summary.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/payments_repository.dart';
/// Use case to retrieve payment summary information.

View File

@@ -1,7 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../../domain/arguments/get_payment_history_arguments.dart';
import '../../../domain/entities/payment_summary.dart';
import '../../../domain/usecases/get_payment_history_usecase.dart';
import '../../../domain/usecases/get_payment_summary_usecase.dart';
import 'payments_event.dart';

View File

@@ -1,6 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../../domain/entities/payment_summary.dart';
abstract class PaymentsState extends Equatable {
const PaymentsState();

View File

@@ -165,7 +165,7 @@ class _PaymentsPageState extends State<PaymentsPage> {
const SizedBox(height: 16),
// Pending Pay
PendingPayCard(
if(state.summary.pendingEarnings > 0) PendingPayCard(
amount: state.summary.pendingEarnings,
onCashOut: () {
Modular.to.pushNamed('/early-pay');
@@ -173,7 +173,11 @@ class _PaymentsPageState extends State<PaymentsPage> {
),
const SizedBox(height: 24),
// Recent Payments
if (state.history.isNotEmpty) Column(
children: [
const Text(
"Recent Payments",
style: TextStyle(
@@ -203,32 +207,9 @@ class _PaymentsPageState extends State<PaymentsPage> {
);
}).toList(),
),
const SizedBox(height: 16),
],
),
// Export History Button
SizedBox(
width: double.infinity,
height: 48,
child: OutlinedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('PDF Exported'),
duration: Duration(seconds: 2),
),
);
},
icon: const Icon(LucideIcons.download, size: 16),
label: const Text("Export History"),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF0F172A),
side: const BorderSide(color: Color(0xFFE2E8F0)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(height: 32),
],
),