Extracted AccountTransactionViewModel that only the fetches the properties from database that are really displayed in UI
This commit is contained in:
parent
e8e304f574
commit
d356d45db7
|
@ -4,6 +4,7 @@ import net.codinux.banking.client.model.AccountTransaction
|
||||||
import net.codinux.banking.client.model.UserAccount
|
import net.codinux.banking.client.model.UserAccount
|
||||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||||
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
|
|
||||||
interface BankingRepository {
|
interface BankingRepository {
|
||||||
|
|
||||||
|
@ -12,8 +13,10 @@ interface BankingRepository {
|
||||||
suspend fun persistUserAccount(userAccount: UserAccount): Long
|
suspend fun persistUserAccount(userAccount: UserAccount): Long
|
||||||
|
|
||||||
|
|
||||||
|
fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel>
|
||||||
|
|
||||||
fun getAllAccountTransactions(): List<AccountTransactionEntity>
|
fun getAllAccountTransactions(): List<AccountTransactionEntity>
|
||||||
|
|
||||||
suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>)
|
suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>): Map<Long, AccountTransaction>
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ import net.codinux.banking.client.model.AccountTransaction
|
||||||
import net.codinux.banking.client.model.UserAccount
|
import net.codinux.banking.client.model.UserAccount
|
||||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||||
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
|
|
||||||
class InMemoryBankingRepository(
|
class InMemoryBankingRepository(
|
||||||
userAccounts: Collection<UserAccount> = emptyList(),
|
userAccounts: Collection<UserAccount> = emptyList(),
|
||||||
|
@ -26,10 +27,17 @@ class InMemoryBankingRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
|
||||||
|
transactions.map { AccountTransactionViewModel(it.id, it) }
|
||||||
|
|
||||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> = transactions.toList()
|
override fun getAllAccountTransactions(): List<AccountTransactionEntity> = transactions.toList()
|
||||||
|
|
||||||
override suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>) {
|
override suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>): Map<Long, AccountTransaction> {
|
||||||
this.transactions.addAll(transactions.map { map(it) })
|
val entities = transactions.map { map(it) }
|
||||||
|
|
||||||
|
this.transactions.addAll(entities)
|
||||||
|
|
||||||
|
return entities.associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import net.codinux.banking.client.model.*
|
||||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||||
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
||||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||||
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
import kotlin.enums.EnumEntries
|
import kotlin.enums.EnumEntries
|
||||||
import kotlin.js.JsName
|
import kotlin.js.JsName
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
|
@ -62,9 +63,12 @@ class SqliteBankingRepository(
|
||||||
)
|
)
|
||||||
}.executeAsList()
|
}.executeAsList()
|
||||||
|
|
||||||
suspend fun persistBankAccounts(userAccountId: Long, bankAccounts: Collection<BankAccount>): Map<Long, BankAccount> =
|
private suspend fun persistBankAccounts(userAccountId: Long, bankAccounts: Collection<BankAccount>): Map<Long, BankAccount> =
|
||||||
bankAccounts.associate { persistBankAccount(userAccountId, it) }
|
bankAccounts.associate { persistBankAccount(userAccountId, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has to be executed in a transaction in order that getting persisted BankAccount's id works~
|
||||||
|
*/
|
||||||
private suspend fun persistBankAccount(userAccountId: Long, account: BankAccount): Pair<Long, BankAccount> {
|
private suspend fun persistBankAccount(userAccountId: Long, account: BankAccount): Pair<Long, BankAccount> {
|
||||||
userAccountQueries.insertBankAccount(
|
userAccountQueries.insertBankAccount(
|
||||||
userAccountId,
|
userAccountId,
|
||||||
|
@ -86,6 +90,11 @@ class SqliteBankingRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
|
||||||
|
accountTransactionQueries.selectAllTransactionsAsViewModel { id, amount, currency, reference, valueDate, otherPartyName, bookingText, sepaReference, userSetDisplayName, category ->
|
||||||
|
AccountTransactionViewModel(id, mapToAmount(amount), currency, sepaReference ?: reference, mapToDate(valueDate), otherPartyName, bookingText, userSetDisplayName, category)
|
||||||
|
}.executeAsList()
|
||||||
|
|
||||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
|
override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
|
||||||
return accountTransactionQueries.selectAllTransactions { id, amount, currency, reference, bookingDate, valueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, userSetDisplayName, category, notes, information, statementNumber, sequenceNumber, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier, originatorsIdentificationCode, compensationAmount, originalAmount, sepaReference, deviantOriginator, deviantRecipient, referenceWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner, referenceOfTheAccountServicingInstitution, supplementaryDetails, transactionReferenceNumber, relatedReferenceNumber ->
|
return accountTransactionQueries.selectAllTransactions { id, amount, currency, reference, bookingDate, valueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, userSetDisplayName, category, notes, information, statementNumber, sequenceNumber, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier, originatorsIdentificationCode, compensationAmount, originalAmount, sepaReference, deviantOriginator, deviantRecipient, referenceWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner, referenceOfTheAccountServicingInstitution, supplementaryDetails, transactionReferenceNumber, relatedReferenceNumber ->
|
||||||
AccountTransactionEntity(
|
AccountTransactionEntity(
|
||||||
|
@ -119,13 +128,17 @@ class SqliteBankingRepository(
|
||||||
}.executeAsList()
|
}.executeAsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>) {
|
override suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>): Map<Long, AccountTransaction> =
|
||||||
transactions.forEach {
|
accountTransactionQueries.transactionWithResult {
|
||||||
|
transactions.associate {
|
||||||
saveAccountTransaction(it)
|
saveAccountTransaction(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveAccountTransaction(transaction: AccountTransaction) {
|
/**
|
||||||
|
* Has to be executed in a transaction in order that getting persisted BankAccount's id works~
|
||||||
|
*/
|
||||||
|
private suspend fun saveAccountTransaction(transaction: AccountTransaction): Pair<Long, AccountTransaction> {
|
||||||
accountTransactionQueries.insertTransaction(
|
accountTransactionQueries.insertTransaction(
|
||||||
mapAmount(transaction.amount), transaction.currency, transaction.reference,
|
mapAmount(transaction.amount), transaction.currency, transaction.reference,
|
||||||
mapDate(transaction.bookingDate), mapDate(transaction.valueDate),
|
mapDate(transaction.bookingDate), mapDate(transaction.valueDate),
|
||||||
|
@ -151,6 +164,8 @@ class SqliteBankingRepository(
|
||||||
|
|
||||||
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber
|
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return Pair(getLastInsertedId(), transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,13 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import net.codinux.banking.client.model.AccountTransaction
|
|
||||||
import net.codinux.banking.ui.config.DI
|
import net.codinux.banking.ui.config.DI
|
||||||
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
|
|
||||||
private val formatUtil = DI.formatUtil
|
private val formatUtil = DI.formatUtil
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TransactionListItem(transaction: AccountTransaction, backgroundColor: Color) {
|
fun TransactionListItem(transaction: AccountTransactionViewModel, backgroundColor: Color) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.background(color = backgroundColor)
|
.background(color = backgroundColor)
|
||||||
|
|
|
@ -63,7 +63,7 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
Row {
|
Row {
|
||||||
Text("${challenge.customer.bankName}, Nutzer ${challenge.customer.loginName}${challenge.account?.let { ", Konto ${it.productName ?: it.identifier}" } ?: ""}")
|
Text("${challenge.user.bankName}, Nutzer ${challenge.user.loginName}${challenge.account?.let { ", Konto ${it.productName ?: it.identifier}" } ?: ""}")
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
"TAN benötigt ${Internationalization.getTextForActionRequiringTan(challenge.forAction)}",
|
"TAN benötigt ${Internationalization.getTextForActionRequiringTan(challenge.forAction)}",
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.codinux.banking.ui.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import net.codinux.banking.client.model.AccountTransaction
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
|
|
||||||
|
data class AccountTransactionViewModel(
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
val amount: Amount,
|
||||||
|
val currency: String,
|
||||||
|
val reference: String,
|
||||||
|
val valueDate: LocalDate,
|
||||||
|
val otherPartyName: String? = null,
|
||||||
|
|
||||||
|
val bookingText: String? = null,
|
||||||
|
val userSetDisplayName: String? = null,
|
||||||
|
val category: String? = null
|
||||||
|
) {
|
||||||
|
constructor(id: Long, transaction: AccountTransaction)
|
||||||
|
: this(id, transaction.amount, transaction.currency, transaction.reference, transaction.valueDate, transaction.otherPartyName, transaction.bookingText)
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import net.codinux.banking.dataaccess.BankingRepository
|
||||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||||
import net.codinux.banking.fints.config.FinTsClientOptions
|
import net.codinux.banking.fints.config.FinTsClientOptions
|
||||||
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
import net.codinux.banking.ui.model.BankInfo
|
import net.codinux.banking.ui.model.BankInfo
|
||||||
import net.codinux.banking.ui.model.TanChallengeReceived
|
import net.codinux.banking.ui.model.TanChallengeReceived
|
||||||
import net.codinux.banking.ui.model.error.BankingClientAction
|
import net.codinux.banking.ui.model.error.BankingClientAction
|
||||||
|
@ -42,7 +43,7 @@ class BankingService(
|
||||||
try {
|
try {
|
||||||
uiState.userAccounts.value = bankingRepository.getAllUserAccounts()
|
uiState.userAccounts.value = bankingRepository.getAllUserAccounts()
|
||||||
|
|
||||||
uiState.transactions.value = bankingRepository.getAllAccountTransactions()
|
uiState.transactions.value = bankingRepository.getAllAccountTransactionsAsViewModel()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not read all user accounts and account transactions from repository" }
|
log.error(e) { "Could not read all user accounts and account transactions from repository" }
|
||||||
}
|
}
|
||||||
|
@ -72,25 +73,27 @@ class BankingService(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleSuccessfulGetAccountDataResponse(response: GetAccountDataResponse) {
|
private suspend fun handleSuccessfulGetAccountDataResponse(response: GetAccountDataResponse) {
|
||||||
val transactions = uiState.transactions.value.toMutableList()
|
|
||||||
transactions.addAll(response.bookedTransactions)
|
|
||||||
uiState.transactions.value = transactions.sortedByDescending { it.valueDate }
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bankingRepository.persistAccountTransactions(response.bookedTransactions)
|
val newTransactions = response.bookedTransactions
|
||||||
|
val createdIds = bankingRepository.persistAccountTransactions(newTransactions)
|
||||||
|
|
||||||
log.info { "Saved ${response.bookedTransactions.size} transactions" }
|
log.info { "Saved ${newTransactions.size} transactions" }
|
||||||
|
|
||||||
|
val transactions = uiState.transactions.value.toMutableList()
|
||||||
|
transactions.addAll(createdIds.map { AccountTransactionViewModel(it.key, it.value) })
|
||||||
|
uiState.transactions.value = transactions.sortedByDescending { it.valueDate }
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not save account transactions ${response.bookedTransactions}" }
|
log.error(e) { "Could not save account transactions ${response.bookedTransactions}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val newUserAccountId = bankingRepository.persistUserAccount(response.user)
|
val newUser = response.user
|
||||||
|
val newUserAccountId = bankingRepository.persistUserAccount(newUser)
|
||||||
|
|
||||||
log.info { "Saved user account ${response.user}" }
|
log.info { "Saved user account $newUser" }
|
||||||
|
|
||||||
val userAccounts = uiState.userAccounts.value.toMutableList()
|
val userAccounts = uiState.userAccounts.value.toMutableList()
|
||||||
userAccounts.add(UserAccountEntity(newUserAccountId, response.user))
|
userAccounts.add(UserAccountEntity(newUserAccountId, newUser))
|
||||||
uiState.userAccounts.value = userAccounts
|
uiState.userAccounts.value = userAccounts
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not save user account ${response.user}" }
|
log.error(e) { "Could not save user account ${response.user}" }
|
||||||
|
|
|
@ -2,8 +2,8 @@ package net.codinux.banking.ui.state
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import net.codinux.banking.client.model.AccountTransaction
|
|
||||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||||
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
import net.codinux.banking.ui.model.TanChallengeReceived
|
import net.codinux.banking.ui.model.TanChallengeReceived
|
||||||
import net.codinux.banking.ui.model.error.ApplicationError
|
import net.codinux.banking.ui.model.error.ApplicationError
|
||||||
import net.codinux.banking.ui.model.error.BankingClientError
|
import net.codinux.banking.ui.model.error.BankingClientError
|
||||||
|
@ -13,7 +13,7 @@ class UiState : ViewModel() {
|
||||||
|
|
||||||
val userAccounts = MutableStateFlow<List<UserAccountEntity>>(emptyList())
|
val userAccounts = MutableStateFlow<List<UserAccountEntity>>(emptyList())
|
||||||
|
|
||||||
val transactions = MutableStateFlow<List<AccountTransaction>>(emptyList())
|
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())
|
||||||
|
|
||||||
val applicationErrorOccurred = MutableStateFlow<ApplicationError?>(null)
|
val applicationErrorOccurred = MutableStateFlow<ApplicationError?>(null)
|
||||||
|
|
||||||
|
|
|
@ -109,3 +109,7 @@ VALUES(
|
||||||
selectAllTransactions:
|
selectAllTransactions:
|
||||||
SELECT AccountTransaction.*
|
SELECT AccountTransaction.*
|
||||||
FROM AccountTransaction;
|
FROM AccountTransaction;
|
||||||
|
|
||||||
|
selectAllTransactionsAsViewModel:
|
||||||
|
SELECT id, amount, currency, reference, valueDate, otherPartyName, bookingText, sepaReference, userSetDisplayName, category
|
||||||
|
FROM AccountTransaction;
|
|
@ -73,6 +73,7 @@ class SqliteBankingRepositoryTest {
|
||||||
assertEquals(bankAccounts.first().includeInAutomaticAccountsUpdate, persistedBankAccount.includeInAutomaticAccountsUpdate)
|
assertEquals(bankAccounts.first().includeInAutomaticAccountsUpdate, persistedBankAccount.includeInAutomaticAccountsUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun saveTransaction() = runTest {
|
fun saveTransaction() = runTest {
|
||||||
val transaction = AccountTransaction(Amount("12.45"), "EUR", "Lohn", LocalDate(2024, 5, 7), LocalDate(2024, 6, 15), "Dein Boss")
|
val transaction = AccountTransaction(Amount("12.45"), "EUR", "Lohn", LocalDate(2024, 5, 7), LocalDate(2024, 6, 15), "Dein Boss")
|
||||||
|
@ -94,4 +95,24 @@ class SqliteBankingRepositoryTest {
|
||||||
assertEquals(transaction.otherPartyName, persisted.otherPartyName)
|
assertEquals(transaction.otherPartyName, persisted.otherPartyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveTransaction_GetAsViewModel() = runTest {
|
||||||
|
val transaction = AccountTransaction(Amount("12.45"), "EUR", "Lohn", LocalDate(2024, 5, 7), LocalDate(2024, 6, 15), "Dein Boss")
|
||||||
|
|
||||||
|
underTest.persistAccountTransactions(listOf(transaction))
|
||||||
|
|
||||||
|
val result = underTest.getAllAccountTransactionsAsViewModel()
|
||||||
|
|
||||||
|
assertEquals(1, result.size)
|
||||||
|
|
||||||
|
val persisted = result.first()
|
||||||
|
assertNotNull(persisted.id)
|
||||||
|
|
||||||
|
assertEquals(transaction.amount, persisted.amount)
|
||||||
|
assertEquals(transaction.currency, persisted.currency)
|
||||||
|
assertEquals(transaction.reference, persisted.reference)
|
||||||
|
assertEquals(transaction.valueDate, persisted.valueDate)
|
||||||
|
assertEquals(transaction.otherPartyName, persisted.otherPartyName)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue