invoices history ready

This commit is contained in:
José Salazar
2026-01-26 18:23:52 -05:00
parent fd06391e63
commit 57ea214871
5 changed files with 81 additions and 16 deletions

View File

@@ -13,6 +13,8 @@ class FinancialRepositoryMock {
totalAmount: 1500.0, totalAmount: 1500.0,
workAmount: 1400.0, workAmount: 1400.0,
addonsAmount: 100.0, addonsAmount: 100.0,
invoiceNumber: 'INV-1',
issueDate: null,
), ),
]; ];
} }

View File

@@ -35,6 +35,8 @@ class Invoice extends Equatable {
required this.totalAmount, required this.totalAmount,
required this.workAmount, required this.workAmount,
required this.addonsAmount, required this.addonsAmount,
this.invoiceNumber,
this.issueDate,
}); });
/// Unique identifier. /// Unique identifier.
final String id; final String id;
@@ -57,6 +59,12 @@ class Invoice extends Equatable {
/// Total amount for addons/extras. /// Total amount for addons/extras.
final double addonsAmount; final double addonsAmount;
/// Human-readable invoice number.
final String? invoiceNumber;
/// Date when the invoice was issued.
final DateTime? issueDate;
@override @override
List<Object?> get props => <Object?>[ List<Object?> get props => <Object?>[
id, id,
@@ -66,5 +74,7 @@ class Invoice extends Equatable {
totalAmount, totalAmount,
workAmount, workAmount,
addonsAmount, addonsAmount,
invoiceNumber,
issueDate,
]; ];
} }

View File

@@ -44,12 +44,22 @@ class BillingRepositoryImpl implements BillingRepository {
/// Fetches the history of paid invoices. /// Fetches the history of paid invoices.
@override @override
Future<List<Invoice>> getInvoiceHistory() async { Future<List<Invoice>> getInvoiceHistory() async {
final List<Invoice> invoices = await _financialRepository.getInvoices( final String? businessId =
'current_business', data_connect.ClientSessionStore.instance.session?.business?.id;
); if (businessId == null || businessId.isEmpty) {
return invoices return <Invoice>[];
.where((Invoice i) => i.status == InvoiceStatus.paid) }
.toList();
final fdc.QueryResult<data_connect.ListInvoicesByBusinessIdData,
data_connect.ListInvoicesByBusinessIdVariables> result =
await _dataConnect
.listInvoicesByBusinessId(
businessId: businessId,
)
.limit(10)
.execute();
return result.data.invoices.map(_mapInvoice).toList();
} }
/// Fetches pending invoices (Open or Disputed). /// Fetches pending invoices (Open or Disputed).
@@ -161,6 +171,42 @@ class BillingRepositoryImpl implements BillingRepository {
(dateTime.millisecondsSinceEpoch % 1000) * 1000000; (dateTime.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds); return fdc.Timestamp(nanoseconds, seconds);
} }
Invoice _mapInvoice(data_connect.ListInvoicesByBusinessIdInvoices invoice) {
return Invoice(
id: invoice.id,
eventId: invoice.orderId,
businessId: invoice.businessId,
status: _mapInvoiceStatus(invoice.status),
totalAmount: invoice.amount,
workAmount: invoice.amount,
addonsAmount: invoice.otherCharges ?? 0,
invoiceNumber: invoice.invoiceNumber,
issueDate: invoice.issueDate.toDateTime(),
);
}
InvoiceStatus _mapInvoiceStatus(
data_connect.EnumValue<data_connect.InvoiceStatus> status,
) {
if (status is data_connect.Known<data_connect.InvoiceStatus>) {
switch (status.value) {
case data_connect.InvoiceStatus.PAID:
return InvoiceStatus.paid;
case data_connect.InvoiceStatus.OVERDUE:
return InvoiceStatus.overdue;
case data_connect.InvoiceStatus.DISPUTED:
return InvoiceStatus.disputed;
case data_connect.InvoiceStatus.APPROVED:
return InvoiceStatus.verified;
case data_connect.InvoiceStatus.PENDING_REVIEW:
case data_connect.InvoiceStatus.PENDING:
case data_connect.InvoiceStatus.DRAFT:
return InvoiceStatus.open;
}
}
return InvoiceStatus.open;
}
} }
class _RoleSummary { class _RoleSummary {

View File

@@ -114,17 +114,21 @@ class BillingBloc extends Bloc<BillingEvent, BillingState> {
// In a real app, fetches related Event/Business names via ID. // In a real app, fetches related Event/Business names via ID.
// For now, mapping available fields and hardcoding missing UI placeholders. // For now, mapping available fields and hardcoding missing UI placeholders.
// Preserving "Existing Behavior" means we show something. // Preserving "Existing Behavior" means we show something.
final String dateLabel = invoice.issueDate == null
? '2024-01-24'
: invoice.issueDate!.toIso8601String().split('T').first;
final String titleLabel = invoice.invoiceNumber ?? invoice.id;
return BillingInvoice( return BillingInvoice(
id: invoice.id, id: titleLabel,
title: 'Invoice #${invoice.id}', // Placeholder as Invoice lacks title title: 'Invoice #${invoice.id}', // Placeholder as Invoice lacks title
locationAddress: locationAddress:
'Location for ${invoice.eventId}', // Placeholder for address 'Location for ${invoice.eventId}', // Placeholder for address
clientName: 'Client ${invoice.businessId}', // Placeholder for client name clientName: 'Client ${invoice.businessId}', // Placeholder for client name
date: '2024-01-24', // Placeholder date date: dateLabel,
totalAmount: invoice.totalAmount, totalAmount: invoice.totalAmount,
workersCount: 5, // Placeholder count workersCount: 5, // Placeholder count
totalHours: invoice.workAmount / 25.0, // Estimating hours from amount totalHours: invoice.workAmount / 25.0, // Estimating hours from amount
status: invoice.status.name, status: invoice.status.name.toUpperCase(),
); );
} }

View File

@@ -114,7 +114,7 @@ class _InvoiceItem extends StatelessWidget {
'\$${invoice.totalAmount.toStringAsFixed(2)}', '\$${invoice.totalAmount.toStringAsFixed(2)}',
style: UiTypography.body2b.textPrimary, style: UiTypography.body2b.textPrimary,
), ),
const _PaidBadge(), _StatusBadge(status: invoice.status),
], ],
), ),
const SizedBox(width: UiConstants.space2), const SizedBox(width: UiConstants.space2),
@@ -125,21 +125,24 @@ class _InvoiceItem extends StatelessWidget {
} }
} }
class _PaidBadge extends StatelessWidget { class _StatusBadge extends StatelessWidget {
const _PaidBadge(); const _StatusBadge({required this.status});
final String status;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isPaid = status.toUpperCase() == 'PAID';
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.tagSuccess, color: isPaid ? UiColors.tagSuccess : UiColors.tagPending,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: Text( child: Text(
t.client_billing.paid_badge, isPaid ? t.client_billing.paid_badge : t.client_billing.pending_badge,
style: UiTypography.titleUppercase4b.copyWith( style: UiTypography.titleUppercase4b.copyWith(
color: UiColors.iconSuccess, color: isPaid ? UiColors.iconSuccess : UiColors.textWarning,
), ),
), ),
); );