Apply clinic lab workflow feedback

This commit is contained in:
egecankomur
2026-07-15 22:13:01 +03:00
parent ac42681f7e
commit f44e07f378
9 changed files with 478 additions and 119 deletions
@@ -85,4 +85,28 @@ class LabFinanceRepository {
}
await FinanceService.instance.confirmJobPayment(jobId);
}
Future<void> confirmPayments(List<FinanceEntry> entries) async {
final jobIds = <String>{};
final standaloneIds = <String>[];
for (final entry in entries) {
if (!entry.status.isOpen) continue;
if (entry.jobId.isNotEmpty) {
jobIds.add(entry.jobId);
} else {
standaloneIds.add(entry.id);
}
}
for (final jobId in jobIds) {
await FinanceService.instance.confirmJobPayment(jobId);
}
final paidAt = DateTime.now().toIso8601String();
for (final id in standaloneIds) {
await _pb.collection('finance_entries').update(id, body: {
'status': 'paid',
'paid_at': paidAt,
});
}
}
}
@@ -146,6 +146,68 @@ class _LabFinanceScreenState extends ConsumerState<LabFinanceScreen>
}
}
Future<void> _confirmCounterpartyPayments(
CounterpartyFinanceSummary item,
List<FinanceEntry> pending,
String Function(double) formatAmount,
) async {
final entries = pending
.where((entry) =>
entry.status.isOpen &&
(entry.counterpartyTenantId == item.counterpartyTenantId ||
entry.counterpartyName == item.counterpartyName))
.toList();
if (entries.isEmpty) return;
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Toplu Ödeme Onayla'),
content: Text(
'${item.counterpartyName} için ${entries.length} açık kayıt '
'${formatAmount(item.pendingAmount)} toplam tutarla onaylansın mı?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('İptal'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('Toplu Onayla'),
),
],
),
);
if (confirmed != true || !mounted) return;
try {
await LabFinanceRepository.instance.confirmPayments(entries);
_load();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Toplu ödeme onaylandı.')),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Hata: $e')),
);
}
}
}
Future<void> _sendPaymentReminder(CounterpartyFinanceSummary item) async {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'${item.counterpartyName} için ödeme hatırlatması hazırlandı.',
),
),
);
}
@override
Widget build(BuildContext context) {
final isSortActive = _sort != _FinanceSort.newestFirst;
@@ -252,6 +314,9 @@ class _LabFinanceScreenState extends ConsumerState<LabFinanceScreen>
title: 'Klinik Bazlı Alacak',
items: data.counterparties,
formatAmount: formatAmount,
onConfirmAll: (item) => _confirmCounterpartyPayments(
item, pending, formatAmount),
onSendReminder: _sendPaymentReminder,
),
),
PillTabs(
@@ -568,11 +633,15 @@ class _CounterpartySummaryList extends StatelessWidget {
required this.title,
required this.items,
required this.formatAmount,
required this.onConfirmAll,
required this.onSendReminder,
});
final String title;
final List<CounterpartyFinanceSummary> items;
final String Function(double) formatAmount;
final Future<void> Function(CounterpartyFinanceSummary item) onConfirmAll;
final Future<void> Function(CounterpartyFinanceSummary item) onSendReminder;
@override
Widget build(BuildContext context) {
@@ -616,6 +685,26 @@ class _CounterpartySummaryList extends StatelessWidget {
fontWeight: FontWeight.w700,
),
),
if (item.pendingAmount > 0) ...[
const SizedBox(width: 8),
IconButton(
onPressed: () => onSendReminder(item),
tooltip: 'Ödeme hatırlat',
icon: const Icon(Icons.notifications_active_outlined),
color: AppColors.pending,
visualDensity: VisualDensity.compact,
),
TextButton.icon(
onPressed: () => onConfirmAll(item),
icon: const Icon(Icons.done_all_rounded, size: 16),
label: const Text('Toplu Onay'),
style: TextButton.styleFrom(
foregroundColor: AppColors.success,
padding: const EdgeInsets.symmetric(horizontal: 8),
visualDensity: VisualDensity.compact,
),
),
],
],
),
const SizedBox(height: 8),