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_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';
// Profile // Profile
export 'src/entities/profile/staff_document.dart'; 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/entities/profile/experience_skill.dart';
export 'src/adapters/profile/bank_account_adapter.dart'; export 'src/adapters/profile/bank_account_adapter.dart';
export 'src/adapters/profile/tax_form_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'; import 'package:equatable/equatable.dart';
/// Summary of staff earnings.
class PaymentSummary extends Equatable { class PaymentSummary extends Equatable {
final double weeklyEarnings; final double weeklyEarnings;
final double monthlyEarnings; final double monthlyEarnings;

View File

@@ -13,6 +13,9 @@ enum PaymentStatus {
/// Transfer failed. /// Transfer failed.
failed, failed,
/// Status unknown.
unknown,
} }
/// Represents a payout to a [Staff] member for a completed [Assignment]. /// 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: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_data_connect/src/session/staff_session_store.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/entities/payment_summary.dart';
import '../../domain/repositories/payments_repository.dart'; import '../../domain/repositories/payments_repository.dart';
class PaymentsRepositoryImpl implements PaymentsRepository { 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 @override
Future<PaymentSummary> getPaymentSummary() async { Future<PaymentSummary> getPaymentSummary() async {
// Current requirement: Mock data only for summary final StaffSession? session = StaffSessionStore.instance.session;
await Future.delayed(const Duration(milliseconds: 500)); if (session?.staff?.id == null) {
return const PaymentSummary( return const PaymentSummary(
weeklyEarnings: 847.50, weeklyEarnings: 0,
monthlyEarnings: 3240.0, monthlyEarnings: 0,
pendingEarnings: 285.0, pendingEarnings: 0,
totalEarnings: 12450.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 @override
Future<List<StaffPayment>> getPaymentHistory(String period) async { Future<List<StaffPayment>> getPaymentHistory(String period) async {
final session = StaffSessionStore.instance.session; final StaffSession? session = StaffSessionStore.instance.session;
if (session?.staff?.id == null) return []; if (session?.staff?.id == null) return <StaffPayment>[];
final String currentStaffId = session!.staff!.id; final String currentStaffId = session!.staff!.id;
try { try {
final response = await ExampleConnector.instance final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> response =
await _dataConnect
.listRecentPaymentsByStaffId(staffId: currentStaffId) .listRecentPaymentsByStaffId(staffId: currentStaffId)
.execute(); .execute();
return response.data.recentPayments.map((payment) { return response.data.recentPayments.map((dc.ListRecentPaymentsByStaffIdRecentPayments payment) {
return StaffPayment( return StaffPayment(
id: payment.id, id: payment.id,
staffId: payment.staffId, staffId: payment.staffId,
assignmentId: payment.applicationId, assignmentId: payment.applicationId,
amount: payment.invoice.amount, amount: payment.invoice.amount,
status: _mapStatus(payment.status), status: PaymentAdapter.toPaymentStatus(payment.status?.stringValue ?? 'UNKNOWN'),
paidAt: payment.invoice.issueDate?.toDate(), paidAt: _toDateTime(payment.invoice.issueDate),
); );
}).toList(); }).toList();
} catch (e) { } catch (e) {
return []; return <StaffPayment>[];
}
}
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;
} }
} }
} }
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 'package:krow_domain/krow_domain.dart';
import '../entities/payment_summary.dart';
/// Repository interface for Payments feature. /// Repository interface for Payments feature.
/// ///

View File

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

View File

@@ -1,7 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../../domain/arguments/get_payment_history_arguments.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_history_usecase.dart';
import '../../../domain/usecases/get_payment_summary_usecase.dart'; import '../../../domain/usecases/get_payment_summary_usecase.dart';
import 'payments_event.dart'; import 'payments_event.dart';

View File

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

View File

@@ -165,7 +165,7 @@ class _PaymentsPageState extends State<PaymentsPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// Pending Pay // Pending Pay
PendingPayCard( if(state.summary.pendingEarnings > 0) PendingPayCard(
amount: state.summary.pendingEarnings, amount: state.summary.pendingEarnings,
onCashOut: () { onCashOut: () {
Modular.to.pushNamed('/early-pay'); Modular.to.pushNamed('/early-pay');
@@ -173,62 +173,43 @@ class _PaymentsPageState extends State<PaymentsPage> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Recent Payments
const Text(
"Recent Payments",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A),
),
),
const SizedBox(height: 12),
Column(
children: state.history.map((StaffPayment payment) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: PaymentHistoryItem(
amount: payment.amount,
title: "Shift Payment",
location: "Varies",
address: "Payment ID: ${payment.id}",
date: payment.paidAt != null
? DateFormat('E, MMM d').format(payment.paidAt!)
: 'Pending',
workedTime: "Completed",
hours: 0,
rate: 0.0,
status: payment.status.name.toUpperCase(),
),
);
}).toList(),
),
const SizedBox(height: 16),
// Export History Button
SizedBox( // Recent Payments
width: double.infinity, if (state.history.isNotEmpty) Column(
height: 48, children: [
child: OutlinedButton.icon( const Text(
onPressed: () { "Recent Payments",
ScaffoldMessenger.of(context).showSnackBar( style: TextStyle(
const SnackBar( fontSize: 14,
content: Text('PDF Exported'), fontWeight: FontWeight.w600,
duration: Duration(seconds: 2), color: Color(0xFF0F172A),
),
);
},
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: 12),
Column(
children: state.history.map((StaffPayment payment) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: PaymentHistoryItem(
amount: payment.amount,
title: "Shift Payment",
location: "Varies",
address: "Payment ID: ${payment.id}",
date: payment.paidAt != null
? DateFormat('E, MMM d').format(payment.paidAt!)
: 'Pending',
workedTime: "Completed",
hours: 0,
rate: 0.0,
status: payment.status.name.toUpperCase(),
),
);
}).toList(),
),
],
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
], ],
), ),