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,
workAmount: 1400.0,
addonsAmount: 100.0,
invoiceNumber: 'INV-1',
issueDate: null,
),
];
}

View File

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

View File

@@ -44,12 +44,22 @@ class BillingRepositoryImpl implements BillingRepository {
/// Fetches the history of paid invoices.
@override
Future<List<Invoice>> getInvoiceHistory() async {
final List<Invoice> invoices = await _financialRepository.getInvoices(
'current_business',
);
return invoices
.where((Invoice i) => i.status == InvoiceStatus.paid)
.toList();
final String? businessId =
data_connect.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return <Invoice>[];
}
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).
@@ -161,6 +171,42 @@ class BillingRepositoryImpl implements BillingRepository {
(dateTime.millisecondsSinceEpoch % 1000) * 1000000;
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 {

View File

@@ -114,17 +114,21 @@ class BillingBloc extends Bloc<BillingEvent, BillingState> {
// In a real app, fetches related Event/Business names via ID.
// For now, mapping available fields and hardcoding missing UI placeholders.
// 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(
id: invoice.id,
id: titleLabel,
title: 'Invoice #${invoice.id}', // Placeholder as Invoice lacks title
locationAddress:
'Location for ${invoice.eventId}', // Placeholder for address
clientName: 'Client ${invoice.businessId}', // Placeholder for client name
date: '2024-01-24', // Placeholder date
date: dateLabel,
totalAmount: invoice.totalAmount,
workersCount: 5, // Placeholder count
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)}',
style: UiTypography.body2b.textPrimary,
),
const _PaidBadge(),
_StatusBadge(status: invoice.status),
],
),
const SizedBox(width: UiConstants.space2),
@@ -125,21 +125,24 @@ class _InvoiceItem extends StatelessWidget {
}
}
class _PaidBadge extends StatelessWidget {
const _PaidBadge();
class _StatusBadge extends StatelessWidget {
const _StatusBadge({required this.status});
final String status;
@override
Widget build(BuildContext context) {
final bool isPaid = status.toUpperCase() == 'PAID';
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: UiColors.tagSuccess,
color: isPaid ? UiColors.tagSuccess : UiColors.tagPending,
borderRadius: BorderRadius.circular(4),
),
child: Text(
t.client_billing.paid_badge,
isPaid ? t.client_billing.paid_badge : t.client_billing.pending_badge,
style: UiTypography.titleUppercase4b.copyWith(
color: UiColors.iconSuccess,
color: isPaid ? UiColors.iconSuccess : UiColors.textWarning,
),
),
);