fix pending transaction

This commit is contained in:
davmar
2026-09-16 23:59:03 +02:00
parent f4b914a2ad
commit e1881acc18
48 changed files with 1488 additions and 15 deletions
+76
View File
@@ -27,7 +27,31 @@ bool isSameTransaction(BankTransaction a, BankTransaction b) {
if (a.date != b.date) return false;
if (a.currency.toUpperCase() != b.currency.toUpperCase()) return false;
if (_amountCents(a.amount) != _amountCents(b.amount)) return false;
return _descriptionsLikelyMatch(a, b);
}
/// True when a cached/API [pending] row is the unsettled form of [booked].
///
/// Banks often change booking date (and sometimes wording) when a payment
/// settles, so this allows a small date window unlike [isSameTransaction].
bool isPendingSettledBy(
BankTransaction pending,
BankTransaction booked, {
int maxDayGap = 10,
}) {
if (!pending.pending || booked.pending) return false;
if (pending.isManual || booked.isManual) return false;
if (pending.currency.toUpperCase() != booked.currency.toUpperCase()) {
return false;
}
if (_amountCents(pending.amount) != _amountCents(booked.amount)) return false;
if (!_descriptionsLikelyMatch(pending, booked)) return false;
final gap = _dateDiffDays(pending.date, booked.date);
if (gap == null) return false;
return gap.abs() <= maxDayGap;
}
bool _descriptionsLikelyMatch(BankTransaction a, BankTransaction b) {
if (a.descriptionKey == b.descriptionKey) return true;
final ma = a.merchantKey;
@@ -47,10 +71,62 @@ bool isSameTransaction(BankTransaction a, BankTransaction b) {
int _amountCents(double amount) => (amount * 100).round();
int? _dateDiffDays(String a, String b) {
final da = DateTime.tryParse(a);
final db = DateTime.tryParse(b);
if (da == null || db == null) return null;
return da.difference(db).inDays;
}
/// Coarse bucket so fuzzy [isSameTransaction] only compares likely peers.
String _dedupeBucket(BankTransaction tx) =>
'${tx.date}|${tx.currency.toUpperCase()}|${_amountCents(tx.amount)}';
String _amountCurrencyBucket(BankTransaction tx) =>
'${tx.currency.toUpperCase()}|${_amountCents(tx.amount)}';
/// Drop pending rows that already have a booked twin (same money + merchant).
List<BankTransaction> dropSettledPendingDuplicates(
List<BankTransaction> txs,
) {
final booked = [for (final tx in txs) if (!tx.pending) tx];
if (booked.isEmpty) return txs;
final bookedBuckets = <String, List<BankTransaction>>{};
for (final tx in booked) {
bookedBuckets.putIfAbsent(_amountCurrencyBucket(tx), () => []).add(tx);
}
final out = <BankTransaction>[];
for (final tx in txs) {
if (!tx.pending || tx.isManual) {
out.add(tx);
continue;
}
final peers = bookedBuckets[_amountCurrencyBucket(tx)];
final settled =
peers != null && peers.any((b) => isPendingSettledBy(tx, b));
if (!settled) out.add(tx);
}
return out;
}
/// Merge a fresh bank API page with locally cached history.
///
/// Pending rows are ephemeral: only the current API list may keep them
/// (except admin manuals). That stops settled/cancelled pendings from
/// living forever in the on-device cache after the bank drops them.
List<BankTransaction> mergeApiTransactionsWithCache(
List<BankTransaction> apiTxs,
List<BankTransaction> cached,
) {
final history = [
for (final tx in cached)
if (!tx.pending || tx.isManual) tx,
];
return dropSettledPendingDuplicates(mergeTransactions(apiTxs, history));
}
/// Merge [extra] into [existing], skipping duplicates. Newest date first.
List<BankTransaction> mergeTransactions(
List<BankTransaction> existing,