From d29498edbbde06d52eaed18a89062d36bab62c91 Mon Sep 17 00:00:00 2001 From: ngenohkevin Date: Sat, 21 Mar 2026 22:26:45 +0300 Subject: [PATCH] Add re-parse unparsed messages button Settings > Data now has "Re-parse Unparsed Messages" button that re-runs the fixed parser on all UNKNOWN transactions, properly categorizing previously missed Fuliza auto-deductions, withdrawals, airtime, and reversals. --- .../ledga/app/data/db/dao/TransactionDao.kt | 6 ++++ .../data/repository/TransactionRepository.kt | 31 +++++++++++++++++++ .../ledga/app/ui/settings/SettingsScreen.kt | 17 +++++++++- .../app/ui/settings/SettingsViewModel.kt | 10 ++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/ledga/app/data/db/dao/TransactionDao.kt b/app/src/main/java/com/ledga/app/data/db/dao/TransactionDao.kt index f17540e..9aa4387 100644 --- a/app/src/main/java/com/ledga/app/data/db/dao/TransactionDao.kt +++ b/app/src/main/java/com/ledga/app/data/db/dao/TransactionDao.kt @@ -102,4 +102,10 @@ interface TransactionDao { @Query("SELECT COUNT(*) FROM transactions WHERE type = 'UNKNOWN'") fun getUnparsedCount(): Flow + + @Query("SELECT * FROM transactions WHERE type = 'UNKNOWN'") + suspend fun getUnparsedSync(): List + + @Query("DELETE FROM transactions WHERE id = :id") + suspend fun deleteById(id: Long) } diff --git a/app/src/main/java/com/ledga/app/data/repository/TransactionRepository.kt b/app/src/main/java/com/ledga/app/data/repository/TransactionRepository.kt index 43dc492..97652fe 100644 --- a/app/src/main/java/com/ledga/app/data/repository/TransactionRepository.kt +++ b/app/src/main/java/com/ledga/app/data/repository/TransactionRepository.kt @@ -85,6 +85,35 @@ class TransactionRepository @Inject constructor( transactionDao.updateCategory(transactionId, categoryId) } + suspend fun reparseUnknownTransactions(): ReparseResult { + val unknowns = transactionDao.getUnparsedSync() + var fixed = 0 + var stillUnknown = 0 + + for (entity in unknowns) { + val result = com.ledga.app.data.parser.MpesaSmsParser.parse(entity.rawSms, entity.timestamp) + when (result) { + is com.ledga.app.data.parser.ParseResult.Success -> { + if (result.transaction.type != com.ledga.app.data.parser.TransactionType.UNKNOWN) { + // Delete old UNKNOWN entry, insert re-parsed one + transactionDao.deleteById(entity.id) + insertTransaction(result.transaction) + fixed++ + } else { + stillUnknown++ + } + } + is com.ledga.app.data.parser.ParseResult.Failure -> { + // Balance checks etc get filtered — delete the UNKNOWN entry + transactionDao.deleteById(entity.id) + fixed++ + } + } + } + + return ReparseResult(total = unknowns.size, fixed = fixed, stillUnknown = stillUnknown) + } + private suspend fun autoCategorize(parsed: ParsedTransaction): Long? { // 1. Type-based defaults val typeDefault = when (parsed.type) { @@ -130,3 +159,5 @@ class TransactionRepository @Inject constructor( return typeDefault ?: 13L // Default to "Other" } } + +data class ReparseResult(val total: Int, val fixed: Int, val stillUnknown: Int) diff --git a/app/src/main/java/com/ledga/app/ui/settings/SettingsScreen.kt b/app/src/main/java/com/ledga/app/ui/settings/SettingsScreen.kt index 92dd4d1..fc10a6a 100644 --- a/app/src/main/java/com/ledga/app/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/ledga/app/ui/settings/SettingsScreen.kt @@ -45,6 +45,7 @@ fun SettingsScreen( val largeTxnAlert by viewModel.largeTxnAlertEnabled.collectAsState() val unparsedCount by viewModel.unparsedCount.collectAsState() val importStatus by viewModel.importStatus.collectAsState() + val reparseResult by viewModel.reparseResult.collectAsState() val exportResult by viewModel.exportResult.collectAsState() val fileImportResult by viewModel.fileImportResult.collectAsState() @@ -160,8 +161,22 @@ fun SettingsScreen( } if (unparsedCount > 0) { + Spacer(modifier = Modifier.height(8.dp)) + OutlinedButton( + onClick = { viewModel.reparseUnknown() }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Re-parse Unparsed Messages ($unparsedCount)") + } + if (reparseResult != null) { + Text( + text = "Fixed ${reparseResult!!.fixed} of ${reparseResult!!.total} (${reparseResult!!.stillUnknown} still unknown)", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } Text( - text = "Unparsed Messages ($unparsedCount)", + text = "View Unparsed Messages", style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.primary, modifier = Modifier diff --git a/app/src/main/java/com/ledga/app/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/ledga/app/ui/settings/SettingsViewModel.kt index 7dd86a2..1555ecc 100644 --- a/app/src/main/java/com/ledga/app/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/ledga/app/ui/settings/SettingsViewModel.kt @@ -9,6 +9,7 @@ import com.ledga.app.data.repository.ExportResult import com.ledga.app.data.repository.FontScale import com.ledga.app.data.repository.ImportFromFileResult import com.ledga.app.data.repository.ImportResult +import com.ledga.app.data.repository.ReparseResult import com.ledga.app.data.repository.SettingsRepository import com.ledga.app.data.repository.SmsImporter import com.ledga.app.data.repository.TransactionRepository @@ -99,6 +100,15 @@ class SettingsViewModel @Inject constructor( } } + private val _reparseResult = MutableStateFlow(null) + val reparseResult: StateFlow = _reparseResult + + fun reparseUnknown() { + viewModelScope.launch { + _reparseResult.value = transactionRepository.reparseUnknownTransactions() + } + } + private val _exportResult = MutableStateFlow(null) val exportResult: StateFlow = _exportResult