feat: integrate payment summary and adapter for staff earnings management
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Summary of staff earnings.
|
||||
class PaymentSummary extends Equatable {
|
||||
final double weeklyEarnings;
|
||||
final double monthlyEarnings;
|
||||
@@ -13,6 +13,9 @@ enum PaymentStatus {
|
||||
|
||||
/// Transfer failed.
|
||||
failed,
|
||||
|
||||
/// Status unknown.
|
||||
unknown,
|
||||
}
|
||||
|
||||
/// Represents a payout to a [Staff] member for a completed [Assignment].
|
||||
|
||||
@@ -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));
|
||||
return const PaymentSummary(
|
||||
weeklyEarnings: 847.50,
|
||||
monthlyEarnings: 3240.0,
|
||||
pendingEarnings: 285.0,
|
||||
totalEarnings: 12450.0,
|
||||
final StaffSession? session = StaffSessionStore.instance.session;
|
||||
if (session?.staff?.id == null) {
|
||||
return const PaymentSummary(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../entities/payment_summary.dart';
|
||||
|
||||
/// Repository interface for Payments feature.
|
||||
///
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,62 +173,43 @@ class _PaymentsPageState extends State<PaymentsPage> {
|
||||
),
|
||||
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(
|
||||
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),
|
||||
|
||||
// Recent Payments
|
||||
if (state.history.isNotEmpty) Column(
|
||||
children: [
|
||||
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: 32),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user