Implemented persisting and deleting Holdings (but not updating yet)

This commit is contained in:
dankito 2024-09-12 13:33:20 +02:00
parent 0ff2f684ea
commit 202c9217e3
7 changed files with 210 additions and 5 deletions

View File

@ -2,8 +2,10 @@ package net.codinux.banking.dataaccess
import net.codinux.banking.client.model.AccountTransaction import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.User
import net.codinux.banking.client.model.securitiesaccount.Holding
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.HoldingEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.UserEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
@ -16,6 +18,13 @@ interface BankingRepository {
suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity> suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity>
suspend fun persistHoldings(bankAccount: BankAccountEntity, holdings: List<Holding>): List<HoldingEntity>
suspend fun updateHoldings(holdings: List<HoldingEntity>)
suspend fun deleteHoldings(holdings: List<HoldingEntity>)
fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel>
fun getAllAccountTransactions(): List<AccountTransactionEntity> fun getAllAccountTransactions(): List<AccountTransactionEntity>

View File

@ -2,8 +2,10 @@ package net.codinux.banking.dataaccess
import net.codinux.banking.client.model.AccountTransaction import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.User
import net.codinux.banking.client.model.securitiesaccount.Holding
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.HoldingEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.UserEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
@ -32,6 +34,17 @@ class InMemoryBankingRepository(
} }
override suspend fun persistHoldings(bankAccount: BankAccountEntity, holdings: List<Holding>): List<HoldingEntity> = emptyList() // no-op
override suspend fun updateHoldings(holdings: List<HoldingEntity>) {
// no-op
}
override suspend fun deleteHoldings(holdings: List<HoldingEntity>) {
// no-op
}
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> = override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
transactions.map { AccountTransactionViewModel(it) } transactions.map { AccountTransactionViewModel(it) }

View File

@ -4,6 +4,7 @@ import app.cash.sqldelight.db.SqlDriver
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import net.codinux.banking.client.model.* import net.codinux.banking.client.model.*
import net.codinux.banking.client.model.securitiesaccount.Holding
import net.codinux.banking.client.model.tan.* import net.codinux.banking.client.model.tan.*
import net.codinux.banking.dataaccess.entities.* import net.codinux.banking.dataaccess.entities.*
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
@ -29,13 +30,20 @@ open class SqliteBankingRepository(
val bankAccounts = getAllBankAccounts().groupBy { it.userId } val bankAccounts = getAllBankAccounts().groupBy { it.userId }
val tanMethods = getAllTanMethods().groupBy { it.userId } val tanMethods = getAllTanMethods().groupBy { it.userId }
val tanMedia = getAllTanMedia().groupBy { it.userId } val tanMedia = getAllTanMedia().groupBy { it.userId }
val holdings = getAllHoldings().groupBy { it.bankAccountId }
return userQueries.selectAllUsers { id, bankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, userSetDisplayName, clientData, displayIndex, iconUrl, wrongCredentialsEntered -> return userQueries.selectAllUsers { id, bankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, userSetDisplayName, clientData, displayIndex, iconUrl, wrongCredentialsEntered ->
UserEntity(id, bankCode, loginName, password, bankName, bic, customerName, userId, bankAccounts[id] ?: emptyList(), selectedTanMethodIdentifier, tanMethods[id] ?: emptyList(), selectedTanMediumIdentifier, tanMedia[id] ?: emptyList(), UserEntity(id, bankCode, loginName, password, bankName, bic, customerName, userId, getAccountsOfUser(id, bankAccounts, holdings), selectedTanMethodIdentifier, tanMethods[id] ?: emptyList(), selectedTanMediumIdentifier, tanMedia[id] ?: emptyList(),
bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered) bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered)
}.executeAsList() }.executeAsList()
} }
protected open fun getAccountsOfUser(userId: Long, bankAccounts: Map<Long, List<BankAccountEntity>>, holdings: Map<Long, List<HoldingEntity>>): List<BankAccountEntity> {
return bankAccounts[userId].orEmpty().onEach {
it.holdings = holdings[it.id].orEmpty()
}
}
override suspend fun persistUser(user: User): UserEntity { override suspend fun persistUser(user: User): UserEntity {
return userQueries.transactionWithResult { return userQueries.transactionWithResult {
userQueries.insertUser(user.bankCode, user.loginName, user.password, user.bankName, user.bic, userQueries.insertUser(user.bankCode, user.loginName, user.password, user.bankName, user.bic,
@ -106,7 +114,9 @@ open class SqliteBankingRepository(
persistTransaction(userId, accountId, transaction) persistTransaction(userId, accountId, transaction)
} }
return BankAccountEntity(accountId, userId, account, accountTransactionEntities, account.holdings) val holdings = account.holdings.map { holding -> persistHolding(userId, accountId, holding) }
return BankAccountEntity(accountId, userId, account, accountTransactionEntities, holdings)
} }
@ -196,6 +206,50 @@ open class SqliteBankingRepository(
} }
protected open fun getAllHoldings(): List<HoldingEntity> =
accountTransactionQueries.selectAllHoldings { id, userId, bankAccountId, name, isin, wkn, quantity, currency, totalBalance, marketValue, performancePercentage, totalCostPrice, averageCostPrice, pricingTime, buyingDate ->
HoldingEntity(id, userId, bankAccountId, name, isin, wkn, mapToInt(quantity), currency, mapToAmount(totalBalance), mapToAmount(marketValue), performancePercentage?.toFloat(), mapToAmount(totalCostPrice), mapToAmount(averageCostPrice), mapToInstant(pricingTime), mapToDate(buyingDate))
}.executeAsList()
override suspend fun persistHoldings(bankAccount: BankAccountEntity, holdings: List<Holding>): List<HoldingEntity> =
accountTransactionQueries.transactionWithResult {
holdings.map { persistHolding(bankAccount.userId, bankAccount.id, it) }
}
/**
* Has to be executed in a transaction in order that getting persisted Holding's id works~
*/
protected open suspend fun persistHolding(userId: Long, bankAccountId: Long, holding: Holding): HoldingEntity {
accountTransactionQueries.insertHolding(
userId, bankAccountId,
holding.name, holding.isin, holding.wkn,
mapInt(holding.quantity), holding.currency,
mapAmount(holding.totalBalance), mapAmount(holding.marketValue),
holding.performancePercentage?.toDouble(),
mapAmount(holding.totalCostPrice), mapAmount(holding.averageCostPrice),
mapInstant(holding.pricingTime), mapDate(holding.buyingDate)
)
return HoldingEntity(getLastInsertedId(), userId, bankAccountId, holding)
}
override suspend fun updateHoldings(holdings: List<HoldingEntity>) {
// TODO
}
override suspend fun deleteHoldings(holdings: List<HoldingEntity>) {
accountTransactionQueries.transaction {
holdings.forEach { holding ->
accountTransactionQueries.deleteHolding(holding.id)
}
}
}
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> = override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
accountTransactionQueries.selectAllTransactionsAsViewModel { id, userId, bankAccountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, category -> accountTransactionQueries.selectAllTransactionsAsViewModel { id, userId, bankAccountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, category ->
AccountTransactionViewModel(id, userId, bankAccountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetDisplayName, category) AccountTransactionViewModel(id, userId, bankAccountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetDisplayName, category)

View File

@ -30,7 +30,7 @@ class BankAccountEntity(
bookedTransactions: MutableList<AccountTransactionEntity> = mutableListOf(), bookedTransactions: MutableList<AccountTransactionEntity> = mutableListOf(),
prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(), prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
holdings: List<Holding> = emptyList(), override var holdings: List<HoldingEntity> = emptyList(),
userSetDisplayName: String? = null, userSetDisplayName: String? = null,
displayIndex: Int = 0, displayIndex: Int = 0,
@ -54,7 +54,7 @@ class BankAccountEntity(
userSetDisplayName, displayIndex, userSetDisplayName, displayIndex,
hideAccount, includeInAutomaticAccountsUpdate hideAccount, includeInAutomaticAccountsUpdate
) { ) {
constructor(id: Long, userId: Long, account: BankAccount, transactions: List<AccountTransactionEntity> = emptyList(), holdings: List<Holding> = emptyList()) : this( constructor(id: Long, userId: Long, account: BankAccount, transactions: List<AccountTransactionEntity> = emptyList(), holdings: List<HoldingEntity> = emptyList()) : this(
id, userId, id, userId,
account.identifier, account.subAccountNumber, account.iban, account.productName, account.identifier, account.subAccountNumber, account.iban, account.productName,

View File

@ -0,0 +1,46 @@
package net.codinux.banking.dataaccess.entities
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import net.codinux.banking.client.model.Amount
import net.codinux.banking.client.model.securitiesaccount.Holding
class HoldingEntity(
val id: Long,
val userId: Long,
val bankAccountId: Long,
name: String,
isin: String? = null,
wkn: String? = null,
quantity: Int? = null,
currency: String? = null,
totalBalance: Amount? = null,
marketValue: Amount? = null,
performancePercentage: Float? = null,
totalCostPrice: Amount? = null,
averageCostPrice: Amount? = null,
pricingTime: Instant? = null,
buyingDate: LocalDate? = null
) : Holding(name, isin, wkn, quantity, currency, totalBalance, marketValue, performancePercentage, totalCostPrice, averageCostPrice, pricingTime, buyingDate) {
constructor(id: Long, userId: Long, bankAccountId: Long, holding: Holding) : this(
id, userId, bankAccountId,
holding.name, holding.isin, holding.wkn,
holding.quantity, holding.currency,
holding.totalBalance, holding.marketValue,
holding.performancePercentage,
holding.totalCostPrice, holding.averageCostPrice,
holding.pricingTime, holding.buyingDate
)
}

View File

@ -172,6 +172,22 @@ class BankingService(
emptyList() emptyList()
} }
val existingHoldingsByIsin = account.holdings.associateBy { it.identifier }
val retrievedHoldingsByIsin = response.holdings.associateBy { it.identifier }
val (newHoldings, updatedRetrievedHoldings) = response.holdings.partition { existingHoldingsByIsin.keys.contains(it.identifier) == false }
val (updatedExistingHoldings, deletedHoldings) = account.holdings.partition { retrievedHoldingsByIsin.keys.contains(it.identifier) }
val persistedNewHoldings = bankingRepository.persistHoldings(account, newHoldings)
// bankingRepository.updateHoldings(updatedExistingHoldings) // TODO: map data of updatedRetrievedHoldings to updatedExistingHoldings
bankingRepository.deleteHoldings(deletedHoldings)
account.holdings = account.holdings.toMutableList().apply {
addAll(persistedNewHoldings)
removeAll(deletedHoldings)
}
updateHoldingsInUi(response.holdings) updateHoldingsInUi(response.holdings)
val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities) val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities)

View File

@ -133,4 +133,71 @@ FROM AccountTransaction WHERE userId = ?;
getTransactionWithId: getTransactionWithId:
SELECT AccountTransaction.* SELECT AccountTransaction.*
FROM AccountTransaction WHERE id = ?; FROM AccountTransaction WHERE id = ?;
CREATE TABLE IF NOT EXISTS Holding (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId INTEGER NOT NULL,
bankAccountId INTEGER NOT NULL,
name TEXT NOT NULL,
isin TEXT,
wkn TEXT,
quantity INTEGER ,
currency TEXT,
totalBalance TEXT,
marketValue TEXT,
performancePercentage REAL,
totalCostPrice TEXT,
averageCostPrice TEXT,
pricingTime TEXT,
buyingDate TEXT
);
insertHolding:
INSERT INTO Holding(
userId, bankAccountId,
name, isin, wkn,
quantity, currency,
totalBalance, marketValue,
performancePercentage,
totalCostPrice, averageCostPrice,
pricingTime, buyingDate
)
VALUES(
?, ?,
?, ?, ?,
?, ?,
?, ?,
?,
?,?,
?, ?
);
selectAllHoldings:
SELECT Holding.*
FROM Holding;
deleteHolding:
DELETE FROM Holding WHERE id = ?;