277 lines
9.9 KiB
Dart
277 lines
9.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:intl/intl.dart';
|
|
import '../domain/customer.dart';
|
|
import '../providers/invoices_provider.dart';
|
|
import '../../transactions/providers/providers.dart';
|
|
|
|
class CustomerLedgerScreen extends ConsumerStatefulWidget {
|
|
final Customer customer;
|
|
|
|
const CustomerLedgerScreen({super.key, required this.customer});
|
|
|
|
@override
|
|
ConsumerState<CustomerLedgerScreen> createState() =>
|
|
_CustomerLedgerScreenState();
|
|
}
|
|
|
|
class _CustomerLedgerScreenState extends ConsumerState<CustomerLedgerScreen> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final invoicesState = ref.watch(invoicesProvider);
|
|
final formatCurrency = NumberFormat.currency(symbol: '₹');
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text('${widget.customer.name} Ledger'),
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: Colors.black,
|
|
elevation: 0,
|
|
),
|
|
body: invoicesState.when(
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (err, stack) => Center(child: Text('Error: $err')),
|
|
data: (invoices) {
|
|
final customerInvoices = invoices
|
|
.where((i) => i.customerId == widget.customer.id)
|
|
.toList();
|
|
|
|
if (customerInvoices.isEmpty) {
|
|
return const Center(
|
|
child: Text('No invoices found for this customer.'),
|
|
);
|
|
}
|
|
|
|
// Sort chronological
|
|
customerInvoices.sort((a, b) => a.issueDate.compareTo(b.issueDate));
|
|
|
|
double runningBalance = 0.0;
|
|
List<DataRow> rows = [];
|
|
final wallets = ref.read(walletProvider).value ?? [];
|
|
|
|
int index = 1;
|
|
for (var inv in customerInvoices) {
|
|
// Add row for Invoice (Debit)
|
|
runningBalance += inv.totalAmount;
|
|
rows.add(
|
|
DataRow(
|
|
cells: [
|
|
DataCell(Text('${index++}')),
|
|
DataCell(
|
|
Text(DateFormat('dd MMM yyyy').format(inv.issueDate)),
|
|
),
|
|
DataCell(
|
|
Text(
|
|
inv.invoiceNumber,
|
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
const DataCell(Text('Invoice')),
|
|
const DataCell(Text('-')),
|
|
const DataCell(Text('-')),
|
|
DataCell(
|
|
Text(
|
|
formatCurrency.format(inv.totalAmount),
|
|
style: TextStyle(
|
|
color: Colors.red.shade700,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
DataCell(
|
|
Text(
|
|
formatCurrency.format(runningBalance),
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
// Add row for Payment (Credit)
|
|
if (inv.payments != null && inv.payments!.isNotEmpty) {
|
|
for (var p in inv.payments!) {
|
|
runningBalance -= p.amount;
|
|
final walletName = wallets
|
|
.firstWhere(
|
|
(w) => w.id == p.walletId,
|
|
orElse: () => wallets.first,
|
|
)
|
|
.name;
|
|
rows.add(
|
|
DataRow(
|
|
cells: [
|
|
DataCell(Text('${index++}')),
|
|
DataCell(
|
|
Text(
|
|
p.paymentDate != null
|
|
? DateFormat('dd MMM yyyy').format(p.paymentDate!)
|
|
: DateFormat('dd MMM yyyy').format(inv.issueDate),
|
|
),
|
|
),
|
|
DataCell(
|
|
Text(
|
|
inv.invoiceNumber,
|
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
const DataCell(Text('Payment')),
|
|
DataCell(Text(p.paymentMethod)),
|
|
DataCell(Text(p.walletId != null ? walletName : '-')),
|
|
DataCell(
|
|
Text(
|
|
formatCurrency.format(p.amount),
|
|
style: TextStyle(
|
|
color: Colors.green.shade700,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
DataCell(
|
|
Text(
|
|
formatCurrency.format(runningBalance),
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} else if (inv.amountPaid > 0) {
|
|
runningBalance -= inv.amountPaid;
|
|
rows.add(
|
|
DataRow(
|
|
cells: [
|
|
DataCell(Text('${index++}')),
|
|
DataCell(
|
|
Text(DateFormat('dd MMM yyyy').format(inv.issueDate)),
|
|
),
|
|
DataCell(
|
|
Text(
|
|
inv.invoiceNumber,
|
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
const DataCell(Text('Payment')),
|
|
const DataCell(Text('-')),
|
|
const DataCell(Text('-')),
|
|
DataCell(
|
|
Text(
|
|
formatCurrency.format(inv.amountPaid),
|
|
style: TextStyle(
|
|
color: Colors.green.shade700,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
DataCell(
|
|
Text(
|
|
formatCurrency.format(runningBalance),
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
return Column(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
color: Colors.blue.withOpacity(0.05),
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
const Text(
|
|
'Outstanding Balance',
|
|
style: TextStyle(color: Colors.grey, fontSize: 14),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
formatCurrency.format(runningBalance),
|
|
style: TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.bold,
|
|
color: runningBalance > 0
|
|
? Colors.red.shade700
|
|
: Colors.green.shade700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.vertical,
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: DataTable(
|
|
headingRowColor: WidgetStateProperty.resolveWith(
|
|
(states) => Colors.grey.shade100,
|
|
),
|
|
columnSpacing: 24,
|
|
columns: const [
|
|
DataColumn(
|
|
label: Text(
|
|
'S.No',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
DataColumn(
|
|
label: Text(
|
|
'Date',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
DataColumn(
|
|
label: Text(
|
|
'Invoice #',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
DataColumn(
|
|
label: Text(
|
|
'Type',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
DataColumn(
|
|
label: Text(
|
|
'Mode',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
DataColumn(
|
|
label: Text(
|
|
'Wallet',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
DataColumn(
|
|
label: Text(
|
|
'Amount',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
DataColumn(
|
|
label: Text(
|
|
'Balance',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
],
|
|
rows: rows,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|