admin user able to create test txs
This commit is contained in:
@@ -30,9 +30,22 @@ class BankController extends ChangeNotifier {
|
||||
_remoteTransactions = cached.transactions;
|
||||
}
|
||||
_importedTransactions = _storage?.loadImportedTransactions() ?? const [];
|
||||
_importedTransactions = [
|
||||
for (final tx in _importedTransactions) _upgradeLegacyManual(tx),
|
||||
];
|
||||
_remoteTransactions = [
|
||||
for (final tx in _remoteTransactions) _upgradeLegacyManual(tx),
|
||||
];
|
||||
_invalidateMerged();
|
||||
}
|
||||
|
||||
/// Older admin test rows used a ·#stamp suffix before [isManual] existed.
|
||||
static BankTransaction _upgradeLegacyManual(BankTransaction tx) {
|
||||
if (tx.isManual) return tx;
|
||||
if (!RegExp(r' ·#\d+$').hasMatch(tx.description)) return tx;
|
||||
return tx.copyWith(isManual: true);
|
||||
}
|
||||
|
||||
final IngtrakApi _api;
|
||||
final StorageService? _storage;
|
||||
|
||||
@@ -46,6 +59,51 @@ class BankController extends ChangeNotifier {
|
||||
BankBalance? _balance;
|
||||
List<BankTransaction> _remoteTransactions = const [];
|
||||
List<BankTransaction> _importedTransactions = const [];
|
||||
List<String> _pendingRemoteDeletes = const [];
|
||||
|
||||
/// Local tx_ids removed/replaced that should be deleted on the next PB push.
|
||||
List<String> takePendingRemoteDeletes() {
|
||||
final out = List<String>.of(_pendingRemoteDeletes);
|
||||
_pendingRemoteDeletes = const [];
|
||||
return out;
|
||||
}
|
||||
|
||||
void requeueRemoteDeletes(Iterable<String> txIds) {
|
||||
for (final id in txIds) {
|
||||
_queueRemoteDelete(id);
|
||||
}
|
||||
}
|
||||
|
||||
void _queueRemoteDelete(String txId) {
|
||||
if (txId.isEmpty) return;
|
||||
if (_pendingRemoteDeletes.contains(txId)) return;
|
||||
_pendingRemoteDeletes = [..._pendingRemoteDeletes, txId];
|
||||
}
|
||||
|
||||
/// Keep [isManual] when a synced/API row replaces a matching imported test tx.
|
||||
void _reapplyManualFlags(Set<String> manualIds) {
|
||||
if (manualIds.isEmpty) return;
|
||||
BankTransaction stamp(BankTransaction tx) =>
|
||||
manualIds.contains(tx.id) && !tx.isManual
|
||||
? tx.copyWith(isManual: true)
|
||||
: tx;
|
||||
_remoteTransactions = [for (final tx in _remoteTransactions) stamp(tx)];
|
||||
_importedTransactions = [
|
||||
for (final tx in _importedTransactions) stamp(tx),
|
||||
];
|
||||
}
|
||||
|
||||
Set<String> _collectManualIds([
|
||||
Iterable<BankTransaction> extra = const [],
|
||||
]) =>
|
||||
{
|
||||
for (final tx in [
|
||||
..._remoteTransactions,
|
||||
..._importedTransactions,
|
||||
...extra,
|
||||
])
|
||||
if (tx.isManual) tx.id,
|
||||
};
|
||||
List<BankTransaction>? _mergedTransactions;
|
||||
|
||||
bool _isLoading = false;
|
||||
@@ -170,7 +228,8 @@ class BankController extends ChangeNotifier {
|
||||
String currency = 'EUR',
|
||||
bool pending = false,
|
||||
}) async {
|
||||
final desc = description.trim().isEmpty ? 'Test transaction' : description.trim();
|
||||
final desc =
|
||||
description.trim().isEmpty ? 'Test transaction' : description.trim();
|
||||
// Include a short unique suffix so identical drafts do not collide on id.
|
||||
final stamp = DateTime.now().millisecondsSinceEpoch % 100000;
|
||||
final tx = BankTransaction(
|
||||
@@ -182,6 +241,7 @@ class BankController extends ChangeNotifier {
|
||||
amount: amount,
|
||||
currency: currency.trim().isEmpty ? 'EUR' : currency.trim().toUpperCase(),
|
||||
pending: pending,
|
||||
isManual: true,
|
||||
);
|
||||
|
||||
_importedTransactions = mergeTransactions(_importedTransactions, [tx]);
|
||||
@@ -195,6 +255,81 @@ class BankController extends ChangeNotifier {
|
||||
return tx;
|
||||
}
|
||||
|
||||
/// Replace an admin test transaction. Returns the updated row (id may change).
|
||||
Future<BankTransaction> updateManualTransaction({
|
||||
required BankTransaction previous,
|
||||
required DateTime date,
|
||||
required String description,
|
||||
required double amount,
|
||||
String currency = 'EUR',
|
||||
bool pending = false,
|
||||
}) async {
|
||||
if (!previous.isManual) {
|
||||
throw StateError('Only admin test transactions can be edited');
|
||||
}
|
||||
final desc =
|
||||
description.trim().isEmpty ? 'Test transaction' : description.trim();
|
||||
final stamped = () {
|
||||
if (RegExp(r' ·#\d+$').hasMatch(desc)) return desc;
|
||||
final prevStamp = RegExp(r' ·#(\d+)$').firstMatch(previous.description);
|
||||
final prevBase =
|
||||
previous.description.replaceFirst(RegExp(r' ·#\d+$'), '');
|
||||
if (prevStamp != null && prevBase == desc) {
|
||||
return '$desc ·#${prevStamp.group(1)}';
|
||||
}
|
||||
return '$desc ·#${DateTime.now().millisecondsSinceEpoch % 100000}';
|
||||
}();
|
||||
final updated = BankTransaction(
|
||||
date:
|
||||
'${date.year.toString().padLeft(4, '0')}-'
|
||||
'${date.month.toString().padLeft(2, '0')}-'
|
||||
'${date.day.toString().padLeft(2, '0')}',
|
||||
description: stamped,
|
||||
amount: amount,
|
||||
currency: currency.trim().isEmpty ? 'EUR' : currency.trim().toUpperCase(),
|
||||
pending: pending,
|
||||
isManual: true,
|
||||
);
|
||||
|
||||
_importedTransactions = [
|
||||
for (final tx in _importedTransactions)
|
||||
if (tx.id != previous.id) tx,
|
||||
];
|
||||
_remoteTransactions = [
|
||||
for (final tx in _remoteTransactions)
|
||||
if (tx.id != previous.id) tx,
|
||||
];
|
||||
if (previous.id != updated.id) {
|
||||
_queueRemoteDelete(previous.id);
|
||||
}
|
||||
_importedTransactions = mergeTransactions(_importedTransactions, [updated]);
|
||||
_invalidateMerged();
|
||||
await _persistImported();
|
||||
await _persistCache();
|
||||
notifyListeners();
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// Remove an admin test transaction from local caches.
|
||||
Future<void> deleteManualTransaction(BankTransaction tx) async {
|
||||
if (!tx.isManual) {
|
||||
throw StateError('Only admin test transactions can be deleted');
|
||||
}
|
||||
_importedTransactions = [
|
||||
for (final row in _importedTransactions)
|
||||
if (row.id != tx.id) row,
|
||||
];
|
||||
_remoteTransactions = [
|
||||
for (final row in _remoteTransactions)
|
||||
if (row.id != tx.id) row,
|
||||
];
|
||||
_queueRemoteDelete(tx.id);
|
||||
_invalidateMerged();
|
||||
await _persistImported();
|
||||
await _persistCache();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Union cloud txs into the local bank cache without dropping either side.
|
||||
///
|
||||
/// Rows land in the API cache; CSV imports that match are deduped away.
|
||||
@@ -204,11 +339,16 @@ class BankController extends ChangeNotifier {
|
||||
}) async {
|
||||
if (fromCloud.isEmpty) return;
|
||||
|
||||
_remoteTransactions = mergeTransactions(_remoteTransactions, fromCloud);
|
||||
final manuals = _collectManualIds(fromCloud.map(_upgradeLegacyManual));
|
||||
_remoteTransactions = mergeTransactions(
|
||||
_remoteTransactions,
|
||||
[for (final tx in fromCloud) _upgradeLegacyManual(tx)],
|
||||
);
|
||||
_importedTransactions = filterNewTransactions(
|
||||
_remoteTransactions,
|
||||
_importedTransactions,
|
||||
);
|
||||
_reapplyManualFlags(manuals);
|
||||
_invalidateMerged();
|
||||
|
||||
if (!_status.linked && _remoteTransactions.isNotEmpty) {
|
||||
@@ -288,6 +428,7 @@ class BankController extends ChangeNotifier {
|
||||
final apiTxs = results[1] as List<BankTransaction>;
|
||||
// Bank API is ~89 days. Prefer API rows for overlaps, but keep any
|
||||
// cloud/CSV history that the API window does not include.
|
||||
final manuals = _collectManualIds();
|
||||
_remoteTransactions = mergeTransactions(apiTxs, _remoteTransactions);
|
||||
if (!_status.linked) {
|
||||
_status = ConnectStatus(
|
||||
@@ -303,6 +444,7 @@ class BankController extends ChangeNotifier {
|
||||
_remoteTransactions,
|
||||
_importedTransactions,
|
||||
);
|
||||
_reapplyManualFlags(manuals);
|
||||
_invalidateMerged();
|
||||
await _persistImported();
|
||||
_error = null;
|
||||
|
||||
Reference in New Issue
Block a user