fix recurrent spendings

This commit is contained in:
davmar
2026-09-10 09:23:40 +02:00
parent 5b22fc8694
commit 200ff72e2e
6 changed files with 116 additions and 150 deletions
+26 -74
View File
@@ -642,7 +642,7 @@ class TxMetaController extends ChangeNotifier {
_schedulePersist();
}
/// Recurring look-ahead templates (manual + auto from marked txs).
/// Explicit look-ahead recurring spendings (never auto-linked from tx flags).
List<RecurringTemplate> get recurringTemplates {
final list = _recurringTemplates.values.toList(growable: false);
list.sort((a, b) {
@@ -656,6 +656,14 @@ class TxMetaController extends ChangeNotifier {
RecurringTemplate? recurringTemplateById(String id) =>
_recurringTemplates[id];
/// True when a spending already references this bank transaction.
bool hasRecurringSpendingForTx(String txId) {
for (final tpl in _recurringTemplates.values) {
if (tpl.sourceTxId == txId) return true;
}
return false;
}
void upsertRecurringTemplate(RecurringTemplate template) {
if (template.id.isEmpty) return;
_recurringTemplates[template.id] = template;
@@ -668,42 +676,23 @@ class TxMetaController extends ChangeNotifier {
_schedulePersist(invalidateLabels: false);
}
/// Backfill auto templates for txs already marked recurring (upgrade path).
void ensureAutoRecurringTemplates(List<BankTransaction> transactions) {
var changed = false;
final recurringIds = <String>{};
for (final tx in transactions) {
if (!metaFor(tx).recurring) continue;
recurringIds.add(tx.id);
final id = RecurringTemplate.autoIdForTx(tx.id);
if (_recurringTemplates.containsKey(id)) continue;
_recurringTemplates[id] = RecurringTemplate(
id: id,
name: tx.description,
dayOfMonth: (DateTime.tryParse(tx.date)?.day ?? 1).clamp(1, 28),
currency: tx.currency,
source: RecurringTemplateSource.transaction,
amountRule: RecurringAmountRule.lastValue,
merchantKey: tx.merchantKey,
sourceTxId: tx.id,
avgCount: 5,
autoFromRecurring: true,
);
changed = true;
}
// Drop orphaned auto templates whose source is no longer recurring.
final orphanIds = [
for (final e in _recurringTemplates.entries)
if (e.value.autoFromRecurring &&
(e.value.sourceTxId == null ||
!recurringIds.contains(e.value.sourceTxId)))
e.key,
];
for (final id in orphanIds) {
_recurringTemplates.remove(id);
changed = true;
}
if (changed) _schedulePersist(invalidateLabels: false);
/// Create a spending seeded from [tx] (last-value amount rule).
RecurringTemplate addRecurringSpendingFromTx(BankTransaction tx) {
final id = 'rt_${DateTime.now().microsecondsSinceEpoch.toRadixString(16)}';
final tpl = RecurringTemplate(
id: id,
name: tx.description,
dayOfMonth: (DateTime.tryParse(tx.date)?.day ?? 1).clamp(1, 28),
currency: tx.currency,
source: RecurringTemplateSource.transaction,
amountRule: RecurringAmountRule.lastValue,
merchantKey: tx.merchantKey,
sourceTxId: tx.id,
avgCount: 5,
autoFromRecurring: false,
);
upsertRecurringTemplate(tpl);
return tpl;
}
Future<void> setRecurring(BankTransaction tx, bool recurring) async {
@@ -715,39 +704,6 @@ class TxMetaController extends ChangeNotifier {
recurring: recurring,
internalTransfer: existing.internalTransfer || current.internalTransfer,
);
if (recurring) {
final id = RecurringTemplate.autoIdForTx(tx.id);
final prev = _recurringTemplates[id];
_recurringTemplates[id] = RecurringTemplate(
id: id,
name: prev?.name ?? tx.description,
dayOfMonth: prev?.dayOfMonth ??
(DateTime.tryParse(tx.date)?.day ?? 1).clamp(1, 28),
currency: prev?.currency ?? tx.currency,
source: RecurringTemplateSource.transaction,
amountRule: prev?.amountRule ?? RecurringAmountRule.lastValue,
merchantKey: prev?.merchantKey.isNotEmpty == true
? prev!.merchantKey
: tx.merchantKey,
sourceTxId: tx.id,
labelId: prev?.labelId,
avgCount: prev?.avgCount ?? 5,
autoFromRecurring: true,
);
} else {
final id = RecurringTemplate.autoIdForTx(tx.id);
final existingTpl = _recurringTemplates[id];
if (existingTpl != null && existingTpl.autoFromRecurring) {
_recurringTemplates.remove(id);
}
// Also drop auto templates that still point at this tx under another id.
_recurringTemplates.removeWhere(
(_, tpl) =>
tpl.autoFromRecurring &&
tpl.sourceTxId == tx.id &&
tpl.id != id,
);
}
_schedulePersist(invalidateLabels: false);
}
@@ -835,10 +791,6 @@ class TxMetaController extends ChangeNotifier {
_schedulePersist();
}
Future<void> toggleRecurring(BankTransaction tx) {
return setRecurring(tx, !metaFor(tx).recurring);
}
Future<void> toggleInternalTransfer(BankTransaction tx) {
return setInternalTransfer(tx, !metaFor(tx).internalTransfer);
}