Implemented updating AccountTransaction properties

This commit is contained in:
dankito 2024-09-24 00:56:52 +02:00
parent bbfc591e5b
commit 18ea0e35f1
11 changed files with 149 additions and 13 deletions

View File

@ -51,4 +51,6 @@ interface BankingRepository {
fun getTransactionById(transactionId: Long): AccountTransactionEntity?
suspend fun updateTransaction(transaction: AccountTransactionEntity, userSetOtherPartyName: String?, userSetReference: String?, notes: String?)
}

View File

@ -88,6 +88,10 @@ class InMemoryBankingRepository(
override fun getTransactionById(transactionId: Long): AccountTransactionEntity? =
getAllAccountTransactions().firstOrNull { it.id == transactionId }
override suspend fun updateTransaction(transaction: AccountTransactionEntity, userSetOtherPartyName: String?, userSetReference: String?, notes: String?) {
// no-op
}
private fun map(bank: BankAccess) = BankAccessEntity(
nextId++,

View File

@ -441,6 +441,22 @@ open class SqliteBankingRepository : BankingRepository {
return AccountTransactionEntity(getLastInsertedId(), bankId, accountId, transaction)
}
override suspend fun updateTransaction(transaction: AccountTransactionEntity, userSetOtherPartyName: String?, userSetReference: String?, notes: String?) {
accountTransactionQueries.transaction {
if (transaction.userSetOtherPartyName != userSetOtherPartyName) {
accountTransactionQueries.updateAccountTransactionUserSetOtherPartyName(userSetOtherPartyName, transaction.id)
}
if (transaction.userSetReference != userSetReference) {
accountTransactionQueries.updateAccountTransactionUserSetOReference(userSetReference, transaction.id)
}
if (transaction.notes != notes) {
accountTransactionQueries.updateAccountTransactionNotes(notes, transaction.id)
}
}
}
private fun getLastInsertedId(): Long =
bankQueries.getLastInsertedId().executeAsOne()

View File

@ -17,8 +17,8 @@ data class AccountTransactionViewModel(
val otherPartyName: String? = null,
val postingText: String? = null,
val userSetReference: String? = null,
val userSetOtherPartyName: String? = null
var userSetReference: String? = null,
var userSetOtherPartyName: String? = null
) {
constructor(entity: AccountTransactionEntity) : this(entity.id, entity.bankId, entity.accountId, entity)

View File

@ -145,6 +145,22 @@ SELECT AccountTransaction.*
FROM AccountTransaction WHERE id = ?;
updateAccountTransactionUserSetOtherPartyName:
UPDATE AccountTransaction
SET userSetOtherPartyName = ?
WHERE id = ?;
updateAccountTransactionUserSetOReference:
UPDATE AccountTransaction
SET userSetReference = ?
WHERE id = ?;
updateAccountTransactionNotes:
UPDATE AccountTransaction
SET notes = ?
WHERE id = ?;
deleteTransactionsByBankId {
DELETE FROM BankAccount
WHERE bankId = :bankId;

View File

@ -51,11 +51,11 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
DI.uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData(
DI.uiState.banks.value.firstNotNullOf { it.accounts.firstOrNull { it.id == transaction.accountId } },
transaction.otherPartyName,
transaction.otherPartyName, // we don't use userSetOtherPartyName here on purpose
transactionEntity?.otherPartyBankId,
transactionEntity?.otherPartyAccountId,
if (withSameData) transaction.amount else null,
if (withSameData) transaction.reference else null
if (withSameData) transaction.reference else null // we don't use userSetReference here on purpose
)
}
}
@ -83,7 +83,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
}
Text(
text = transaction.otherPartyName ?: transaction.postingText ?: "",
text = transaction.userSetOtherPartyName ?: transaction.otherPartyName ?: transaction.postingText ?: "",
Modifier.fillMaxWidth(),
color = Style.ListItemHeaderTextColor,
fontWeight = Style.ListItemHeaderWeight,
@ -95,7 +95,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
Spacer(modifier = Modifier.height(6.dp))
Text(
text = transaction.reference ?: "",
text = transaction.userSetReference ?: transaction.reference ?: "",
Modifier.fillMaxWidth(),
maxLines = 1,
overflow = TextOverflow.Ellipsis
@ -121,7 +121,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
offset = showMenuAt ?: DpOffset.Zero,
) {
DropdownMenuItem({ newMoneyTransferToOtherParty(false) }) {
Text("Neue Überweisung an ${transaction.otherPartyName} ...")
Text("Neue Überweisung an ${transaction.userSetOtherPartyName ?: transaction.otherPartyName} ...") // really use userSetOtherPartyName here as we don't use it in ShowTransferMoneyDialogData
}
DropdownMenuItem({ newMoneyTransferToOtherParty(true) }) {

View File

@ -1,16 +1,20 @@
package net.codinux.banking.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import net.codinux.banking.client.model.isNegative
import net.codinux.banking.persistence.entities.AccountTransactionEntity
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.extensions.verticalScroll
import net.codinux.banking.ui.forms.LabelledValue
import net.codinux.banking.ui.forms.OutlinedTextField
import net.codinux.banking.ui.forms.SectionHeader
private val formatUtil = DI.formatUtil
@ -35,14 +39,50 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
|| transaction.journalNumber != null || transaction.textKeyAddition != null
FullscreenViewBase("Umsatzdetails", onClosed = onClosed) {
var enteredOtherPartyName by remember { mutableStateOf(transaction.userSetOtherPartyName ?: transaction.otherPartyName ?: "") }
var enteredReference by remember { mutableStateOf(transaction.userSetReference ?: transaction.reference ?: "") }
var enteredNotes by remember { mutableStateOf(transaction.notes ?: "") }
val hasDataChanged by remember(enteredOtherPartyName, enteredReference, enteredNotes) {
mutableStateOf(
(enteredOtherPartyName != transaction.userSetOtherPartyName && (transaction.userSetOtherPartyName?.isNotBlank() == true || enteredOtherPartyName.isNotBlank()))
|| (enteredReference != transaction.userSetReference && (transaction.userSetReference?.isNotBlank() == true || enteredReference.isNotBlank()))
|| (enteredNotes != transaction.notes && enteredNotes.isNotBlank())
)
}
val coroutineScope = rememberCoroutineScope()
fun saveChanges() {
coroutineScope.launch {
DI.bankingService.updateAccountTransactionEntity(transaction, enteredOtherPartyName.takeUnless { it.isBlank() }, enteredReference.takeUnless { it.isBlank() }, enteredNotes.takeUnless { it.isBlank() })
}
}
FullscreenViewBase(
"Umsatzdetails",
confirmButtonTitle = "Speichern",
confirmButtonEnabled = hasDataChanged,
showDismissButton = true,
onConfirm = { saveChanges() },
onClosed = onClosed
) {
SelectionContainer {
Column(Modifier.fillMaxSize().verticalScroll().padding(8.dp)) {
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(8.dp)) {
Column(Modifier.fillMaxWidth()) {
SectionHeader(if (isExpense) "Empfänger*in" else "Zahlende*r", false)
LabelledValue("Name", transaction.otherPartyName ?: "")
OutlinedTextField(
label = { Text("Name") },
value = enteredOtherPartyName,
onValueChange = { enteredOtherPartyName = it },
modifier = Modifier.fillMaxWidth()
)
LabelledValue("BIC", transaction.otherPartyBankId ?: "")
@ -57,7 +97,12 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
LabelledValue("Buchungstext", transaction.postingText ?: "")
LabelledValue("Verwendungszweck", transaction.reference ?: "")
OutlinedTextField(
label = { Text("Verwendungszweck") },
value = enteredReference,
onValueChange = { enteredReference = it },
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
)
LabelledValue("Buchungsdatum", formatUtil.formatDate(transaction.bookingDate))
@ -70,6 +115,16 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
transaction.closingBalance?.let {
LabelledValue("Tagesendsaldo", formatUtil.formatAmount(it, accountCurrency))
}
OutlinedTextField(
label = { Text("Notizen") },
value = enteredNotes,
onValueChange = { enteredNotes = it },
singleLine = false,
minLines = 2,
maxLines = 3,
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
)
}
if (hasDetailedValues) {

View File

@ -46,7 +46,10 @@ class AccountTransactionsFilterService {
private fun matchesSearchTerm(transaction: AccountTransactionViewModel, searchTerm: String): Boolean =
transaction.reference?.contains(searchTerm, true) == true
|| transaction.userSetReference?.contains(searchTerm, true) == true
|| transaction.otherPartyName?.contains(searchTerm, true) == true
|| transaction.userSetOtherPartyName?.contains(searchTerm, true) == true
|| transaction.postingText?.contains(searchTerm, true) == true
fun filterHoldings(holdings: List<HoldingEntity>, filter: AccountTransactionsFilter): List<HoldingEntity> {

View File

@ -19,6 +19,7 @@ class BankDataImporterAndExporter {
transactions.forEach { transaction ->
writer.writeRow(
// TODO: add bank and bank account
// TODO: also regard userSetOtherPartyName and userSetReference?
formatAmount(transaction.amount, decimalSeparator), transaction.currency,
transaction.valueDate.toString(), transaction.bookingDate.toString(),
transaction.reference,

View File

@ -303,6 +303,31 @@ class BankingService(
}
suspend fun updateAccountTransactionEntity(transaction: AccountTransactionEntity, userSetOtherPartyName: String?, userSetReference: String?, notes: String?) {
try {
bankingRepository.updateTransaction(transaction, userSetOtherPartyName, userSetReference, notes)
val transactionViewModel = uiState.transactions.value.firstOrNull { it.id == transaction.id } // should actually never be null
if (transaction.userSetOtherPartyName != userSetOtherPartyName) {
transaction.userSetOtherPartyName = userSetOtherPartyName
transactionViewModel?.userSetOtherPartyName = userSetOtherPartyName // also update displayed AccountTransactionViewModel
}
if (transaction.userSetReference != userSetReference) {
transaction.userSetReference = userSetReference
transactionViewModel?.userSetReference = userSetReference
}
if (transaction.notes != notes) {
transaction.notes = notes
}
notifyAccountTransactionListUpdated()
} catch (e: Throwable) {
showAndLogError(ErroneousAction.SaveToDatabase, "Could not update account transaction $transaction", "Kontoumsatz konnten nicht aktualisisert werden", e)
}
}
private fun getCurrentUiBanksList() = uiState.banks.value
private suspend fun notifyBanksListUpdated() {
@ -321,6 +346,18 @@ class BankingService(
uiState.banks.emit(banks)
}
private suspend fun notifyAccountTransactionListUpdated() {
val currentTransactionsList = uiState.transactions.value
if (currentTransactionsList.isNotEmpty()) {
// if we only would call uiState.banks.emit(banks) with the same banks list as currently, nothing would change ->
// update does not get triggered -> for a short time display a different banks list and then return to actual banks list
uiState.transactions.emit(currentTransactionsList.toMutableList().also { it.add(it.last()) })
try { delay(10) } catch (e: Throwable) { }
uiState.transactions.emit(currentTransactionsList)
}
}
private fun updateTransactionsInUi(addedTransactions: List<AccountTransactionEntity>): List<AccountTransactionViewModel> {
val transactionsViewModel = addedTransactions.map { AccountTransactionViewModel(it) }

View File

@ -32,6 +32,7 @@ class RecipientFinder(private val bankFinder: BankFinder) {
suspend fun updateData(transactions: List<AccountTransaction>) {
availableRecipients = transactions.mapNotNull {
// TODO: also regard userSetOtherPartyName?
if (it.otherPartyName != null && it.otherPartyAccountId != null) {
RecipientSuggestion(it.otherPartyName!!, it.otherPartyBankId, it.otherPartyAccountId!!)
} else {
@ -45,6 +46,7 @@ class RecipientFinder(private val bankFinder: BankFinder) {
transactionsByIban = transactions.filter { it.otherPartyAccountId != null }.groupBy { it.otherPartyAccountId!! }
.mapValues { it.value.map {
// TODO: also regard userSetReference?
PaymentDataSuggestion(it.reference ?: "", Amount(it.amount.toString().replace("-", "")), it.currency, it.valueDate)
}.toSet().sortedByDescending { it.valueDate } }
}