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_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';
|
||||||
|
|||||||
@@ -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';
|
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;
|
||||||
@@ -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].
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user