Fixed JSON serialization (as cannot add Jackson annotations to model classes in common project)
This commit is contained in:
parent
60e4a82fe0
commit
a3696a4716
|
@ -15,4 +15,12 @@ compileTestKotlin {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':BankingUiCommon')
|
implementation project(':BankingUiCommon')
|
||||||
|
|
||||||
|
|
||||||
|
testImplementation "junit:junit:$junitVersion"
|
||||||
|
|
||||||
|
testImplementation "org.assertj:assertj-core:$assertJVersion"
|
||||||
|
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
||||||
|
|
||||||
|
testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
|
||||||
}
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
package net.dankito.banking.persistence
|
package net.dankito.banking.persistence
|
||||||
|
|
||||||
|
import net.dankito.banking.persistence.mapper.EntitiesMapper
|
||||||
|
import net.dankito.banking.persistence.model.CustomerEntity
|
||||||
import net.dankito.utils.multiplatform.File
|
import net.dankito.utils.multiplatform.File
|
||||||
import net.dankito.banking.ui.model.Customer
|
import net.dankito.banking.ui.model.Customer
|
||||||
import net.dankito.banking.ui.model.AccountTransaction
|
import net.dankito.banking.ui.model.AccountTransaction
|
||||||
import net.dankito.banking.ui.model.BankAccount
|
import net.dankito.banking.ui.model.BankAccount
|
||||||
import net.dankito.banking.util.ISerializer
|
import net.dankito.banking.util.ISerializer
|
||||||
|
import net.dankito.utils.multiplatform.Month
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
@ -14,6 +17,8 @@ open class BankingPersistenceJson(
|
||||||
protected val serializer: ISerializer
|
protected val serializer: ISerializer
|
||||||
) : IBankingPersistence {
|
) : IBankingPersistence {
|
||||||
|
|
||||||
|
protected val mapper = EntitiesMapper()
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
jsonFile.absoluteFile.parentFile.mkdirs()
|
jsonFile.absoluteFile.parentFile.mkdirs()
|
||||||
|
@ -21,20 +26,30 @@ open class BankingPersistenceJson(
|
||||||
|
|
||||||
|
|
||||||
override fun saveOrUpdateAccount(customer: Customer, allCustomers: List<Customer>) {
|
override fun saveOrUpdateAccount(customer: Customer, allCustomers: List<Customer>) {
|
||||||
serializer.serializeObject(allCustomers, jsonFile)
|
saveAllCustomers(allCustomers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteAccount(customer: Customer, allCustomers: List<Customer>) {
|
override fun deleteAccount(customer: Customer, allCustomers: List<Customer>) {
|
||||||
serializer.serializeObject(allCustomers, jsonFile)
|
saveAllCustomers(allCustomers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readPersistedAccounts(): List<Customer> {
|
override fun readPersistedAccounts(): List<Customer> {
|
||||||
return serializer.deserializeListOr(jsonFile, Customer::class, listOf())
|
val deserializedCustomers = serializer.deserializeListOr(jsonFile, CustomerEntity::class)
|
||||||
|
|
||||||
|
return mapper.mapCustomerEntities(deserializedCustomers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun saveOrUpdateAccountTransactions(bankAccount: BankAccount, transactions: List<AccountTransaction>) {
|
override fun saveOrUpdateAccountTransactions(bankAccount: BankAccount, transactions: List<AccountTransaction>) {
|
||||||
// done when called saveOrUpdateAccount()
|
// done when called saveOrUpdateAccount()
|
||||||
|
// TODO: or also call saveAllCustomers()?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun saveAllCustomers(allCustomers: List<Customer>) {
|
||||||
|
val mappedCustomers = mapper.mapCustomers(allCustomers)
|
||||||
|
|
||||||
|
serializer.serializeObject(mappedCustomers, jsonFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
package net.dankito.banking.persistence.mapper
|
||||||
|
|
||||||
|
import net.dankito.banking.persistence.model.AccountTransactionEntity
|
||||||
|
import net.dankito.banking.persistence.model.BankAccountEntity
|
||||||
|
import net.dankito.banking.persistence.model.CustomerEntity
|
||||||
|
import net.dankito.banking.ui.model.AccountTransaction
|
||||||
|
import net.dankito.banking.ui.model.BankAccount
|
||||||
|
import net.dankito.banking.ui.model.Customer
|
||||||
|
|
||||||
|
|
||||||
|
open class EntitiesMapper {
|
||||||
|
|
||||||
|
open fun mapCustomers(customers: List<Customer>): List<CustomerEntity> {
|
||||||
|
return customers.map { mapCustomer(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun mapCustomer(customer: Customer): CustomerEntity {
|
||||||
|
val mappedCustomer = CustomerEntity(
|
||||||
|
customer.bankCode, customer.customerId, customer.password, customer.finTsServerAddress,
|
||||||
|
customer.bankName, customer.bic, customer.customerName, customer.userId, customer.iconUrl,
|
||||||
|
listOf(), customer.supportedTanProcedures, customer.selectedTanProcedure, customer.tanMedia
|
||||||
|
)
|
||||||
|
|
||||||
|
mappedCustomer.id = customer.technicalId
|
||||||
|
|
||||||
|
mappedCustomer.accounts = mapBankAccounts(customer.accounts, mappedCustomer)
|
||||||
|
|
||||||
|
return mappedCustomer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun mapCustomerEntities(customers: List<CustomerEntity>): List<Customer> {
|
||||||
|
return customers.map { mapCustomer(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun mapCustomer(customer: CustomerEntity): Customer {
|
||||||
|
val mappedCustomer = Customer(
|
||||||
|
customer.bankCode, customer.customerId, customer.password, customer.finTsServerAddress,
|
||||||
|
customer.bankName, customer.bic, customer.customerName, customer.userId, customer.iconUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
mappedCustomer.technicalId = customer.id
|
||||||
|
|
||||||
|
mappedCustomer.accounts = mapBankAccounts(customer.accounts, mappedCustomer)
|
||||||
|
|
||||||
|
|
||||||
|
mappedCustomer.supportedTanProcedures = customer.supportedTanProcedures
|
||||||
|
mappedCustomer.selectedTanProcedure = customer.selectedTanProcedure
|
||||||
|
mappedCustomer.tanMedia = customer.tanMedia
|
||||||
|
|
||||||
|
return mappedCustomer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun mapBankAccounts(transactions: List<BankAccount>, customer: CustomerEntity): List<BankAccountEntity> {
|
||||||
|
return transactions.map { mapBankAccount(it, customer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun mapBankAccount(account: BankAccount, customer: CustomerEntity): BankAccountEntity {
|
||||||
|
val mappedAccount = BankAccountEntity(
|
||||||
|
customer, account.identifier, account.accountHolderName, account.iban, account.subAccountNumber,
|
||||||
|
account.customerId, account.balance, account.currency, account.type, account.productName,
|
||||||
|
account.accountLimit, account.lastRetrievedTransactionsTimestamp,
|
||||||
|
account.supportsRetrievingAccountTransactions, account.supportsRetrievingBalance,
|
||||||
|
account.supportsTransferringMoney, account.supportsInstantPaymentMoneyTransfer
|
||||||
|
)
|
||||||
|
|
||||||
|
mappedAccount.id = account.technicalId
|
||||||
|
|
||||||
|
mappedAccount.bookedTransactions = mapTransactions(account.bookedTransactions, mappedAccount)
|
||||||
|
|
||||||
|
return mappedAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun mapBankAccounts(transactions: List<BankAccountEntity>, customer: Customer): List<BankAccount> {
|
||||||
|
return transactions.map { mapBankAccount(it, customer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun mapBankAccount(account: BankAccountEntity, customer: Customer): BankAccount {
|
||||||
|
val mappedAccount = BankAccount(
|
||||||
|
customer, account.identifier, account.accountHolderName, account.iban, account.subAccountNumber,
|
||||||
|
account.customerId, account.balance, account.currency, account.type, account.productName,
|
||||||
|
account.accountLimit, account.lastRetrievedTransactionsTimestamp,
|
||||||
|
account.supportsRetrievingAccountTransactions, account.supportsRetrievingBalance,
|
||||||
|
account.supportsTransferringMoney, account.supportsInstantPaymentMoneyTransfer
|
||||||
|
)
|
||||||
|
|
||||||
|
mappedAccount.technicalId = account.id
|
||||||
|
|
||||||
|
mappedAccount.bookedTransactions = mapTransactions(account.bookedTransactions, mappedAccount)
|
||||||
|
|
||||||
|
return mappedAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun mapTransactions(transactions: List<AccountTransaction>, account: BankAccountEntity): List<AccountTransactionEntity> {
|
||||||
|
return transactions.map { mapTransaction(it, account) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun mapTransaction(transaction: AccountTransaction, account: BankAccountEntity): AccountTransactionEntity {
|
||||||
|
return AccountTransactionEntity(account, transaction.amount, transaction.currency, transaction.unparsedUsage, transaction.bookingDate,
|
||||||
|
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, transaction.bookingText,
|
||||||
|
transaction.valueDate, transaction.statementNumber, transaction.sequenceNumber, transaction.openingBalance, transaction.closingBalance,
|
||||||
|
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
||||||
|
transaction.compensationAmount, transaction.originalAmount, transaction.sepaUsage, transaction.deviantOriginator, transaction.deviantRecipient,
|
||||||
|
transaction.usageWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement, transaction.currencyType, transaction.bookingKey,
|
||||||
|
transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution, transaction.supplementaryDetails,
|
||||||
|
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber, transaction.technicalId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun mapTransactions(transactions: List<AccountTransactionEntity>, account: BankAccount): List<AccountTransaction> {
|
||||||
|
return transactions.map { mapTransaction(it, account) }
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun mapTransaction(transaction: AccountTransactionEntity, account: BankAccount): AccountTransaction {
|
||||||
|
val mappedTransaction = AccountTransaction(account, transaction.amount, transaction.currency, transaction.unparsedUsage, transaction.bookingDate,
|
||||||
|
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, transaction.bookingText,
|
||||||
|
transaction.valueDate, transaction.statementNumber, transaction.sequenceNumber, transaction.openingBalance, transaction.closingBalance,
|
||||||
|
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
||||||
|
transaction.compensationAmount, transaction.originalAmount, transaction.sepaUsage, transaction.deviantOriginator, transaction.deviantRecipient,
|
||||||
|
transaction.usageWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement, transaction.currencyType, transaction.bookingKey,
|
||||||
|
transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution, transaction.supplementaryDetails,
|
||||||
|
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber)
|
||||||
|
|
||||||
|
mappedTransaction.technicalId = transaction.id
|
||||||
|
|
||||||
|
return mappedTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package net.dankito.banking.persistence.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIdentityInfo
|
||||||
|
import com.fasterxml.jackson.annotation.ObjectIdGenerators
|
||||||
|
import net.dankito.utils.multiplatform.BigDecimal
|
||||||
|
import net.dankito.utils.multiplatform.Date
|
||||||
|
import net.dankito.utils.multiplatform.UUID
|
||||||
|
|
||||||
|
|
||||||
|
@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
|
||||||
|
// had to define all properties as 'var' 'cause MapStruct cannot handle vals
|
||||||
|
open class AccountTransactionEntity(
|
||||||
|
open var bankAccount: BankAccountEntity,
|
||||||
|
open var amount: BigDecimal,
|
||||||
|
open var currency: String,
|
||||||
|
open var unparsedUsage: String,
|
||||||
|
open var bookingDate: Date,
|
||||||
|
open var otherPartyName: String?,
|
||||||
|
open var otherPartyBankCode: String?,
|
||||||
|
open var otherPartyAccountId: String?,
|
||||||
|
open var bookingText: String?,
|
||||||
|
open var valueDate: Date,
|
||||||
|
open var statementNumber: Int,
|
||||||
|
open var sequenceNumber: Int?,
|
||||||
|
open var openingBalance: BigDecimal?,
|
||||||
|
open var closingBalance: BigDecimal?,
|
||||||
|
|
||||||
|
open var endToEndReference: String?,
|
||||||
|
open var customerReference: String?,
|
||||||
|
open var mandateReference: String?,
|
||||||
|
open var creditorIdentifier: String?,
|
||||||
|
open var originatorsIdentificationCode: String?,
|
||||||
|
open var compensationAmount: String?,
|
||||||
|
open var originalAmount: String?,
|
||||||
|
open var sepaUsage: String?,
|
||||||
|
open var deviantOriginator: String?,
|
||||||
|
open var deviantRecipient: String?,
|
||||||
|
open var usageWithNoSpecialType: String?,
|
||||||
|
open var primaNotaNumber: String?,
|
||||||
|
open var textKeySupplement: String?,
|
||||||
|
|
||||||
|
open var currencyType: String?,
|
||||||
|
open var bookingKey: String,
|
||||||
|
open var referenceForTheAccountOwner: String,
|
||||||
|
open var referenceOfTheAccountServicingInstitution: String?,
|
||||||
|
open var supplementaryDetails: String?,
|
||||||
|
|
||||||
|
open var transactionReferenceNumber: String,
|
||||||
|
open var relatedReferenceNumber: String?,
|
||||||
|
var id: String = UUID.random().toString()
|
||||||
|
) {
|
||||||
|
|
||||||
|
// for object deserializers
|
||||||
|
internal constructor() : this(BankAccountEntity(), BigDecimal.Zero, "", "", Date(), null, null, null, null, Date(),
|
||||||
|
-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null,
|
||||||
|
null, "", null)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package net.dankito.banking.persistence.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIdentityInfo
|
||||||
|
import com.fasterxml.jackson.annotation.ObjectIdGenerators
|
||||||
|
import net.dankito.banking.ui.model.BankAccountType
|
||||||
|
import net.dankito.utils.multiplatform.BigDecimal
|
||||||
|
import net.dankito.utils.multiplatform.Date
|
||||||
|
import net.dankito.utils.multiplatform.UUID
|
||||||
|
|
||||||
|
|
||||||
|
@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
|
||||||
|
// had to define all properties as 'var' 'cause MapStruct cannot handle vals (and cannot use Pozo's mapstruct-kotlin as SerializableBankAccountBuilder would fail with @Context)
|
||||||
|
open class BankAccountEntity(
|
||||||
|
open var customer: CustomerEntity,
|
||||||
|
open var identifier: String,
|
||||||
|
open var accountHolderName: String,
|
||||||
|
open var iban: String?,
|
||||||
|
open var subAccountNumber: String?,
|
||||||
|
open var customerId: String,
|
||||||
|
open var balance: BigDecimal = BigDecimal.Zero,
|
||||||
|
open var currency: String = "EUR",
|
||||||
|
open var type: BankAccountType = BankAccountType.Girokonto,
|
||||||
|
open var productName: String? = null,
|
||||||
|
open var accountLimit: String? = null,
|
||||||
|
open var lastRetrievedTransactionsTimestamp: Date? = null,
|
||||||
|
open var supportsRetrievingAccountTransactions: Boolean = false,
|
||||||
|
open var supportsRetrievingBalance: Boolean = false,
|
||||||
|
open var supportsTransferringMoney: Boolean = false,
|
||||||
|
open var supportsInstantPaymentMoneyTransfer: Boolean = false,
|
||||||
|
open var bookedTransactions: List<AccountTransactionEntity> = listOf(),
|
||||||
|
open var unbookedTransactions: List<Any> = listOf(),
|
||||||
|
open var id: String = UUID.random().toString()
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
internal constructor() : this(CustomerEntity(), "", "", null, null, "") // for object deserializers
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package net.dankito.banking.persistence.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.*
|
||||||
|
import net.dankito.banking.ui.model.tan.TanMedium
|
||||||
|
import net.dankito.banking.ui.model.tan.TanProcedure
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
|
||||||
|
// had to define all properties as 'var' 'cause MapStruct cannot handle vals (and cannot use Pozo's mapstruct-kotlin as SerializableCustomerBuilder would fail with @Context)
|
||||||
|
open class CustomerEntity(
|
||||||
|
var bankCode: String,
|
||||||
|
var customerId: String,
|
||||||
|
var password: String,
|
||||||
|
var finTsServerAddress: String,
|
||||||
|
var bankName: String,
|
||||||
|
var bic: String,
|
||||||
|
var customerName: String,
|
||||||
|
var userId: String = customerId,
|
||||||
|
var iconUrl: String? = null,
|
||||||
|
var accounts: List<BankAccountEntity> = listOf(),
|
||||||
|
var supportedTanProcedures: List<TanProcedure> = listOf(),
|
||||||
|
var selectedTanProcedure: TanProcedure? = null,
|
||||||
|
var tanMedia: List<TanMedium> = listOf(),
|
||||||
|
var id: String = UUID.randomUUID().toString()
|
||||||
|
) {
|
||||||
|
|
||||||
|
internal constructor() : this("", "", "", "", "", "", "") // for object deserializers
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
package net.dankito.banking.persistence
|
||||||
|
|
||||||
|
import net.dankito.banking.persistence.mapper.CustomerConverter
|
||||||
|
import net.dankito.banking.persistence.mapper.CycleAvoidingMappingContext
|
||||||
|
import net.dankito.banking.persistence.model.AccountTransactionEntity
|
||||||
|
import net.dankito.banking.persistence.model.BankAccountEntity
|
||||||
|
import net.dankito.banking.persistence.model.CustomerEntity
|
||||||
|
import net.dankito.banking.ui.model.AccountTransaction
|
||||||
|
import net.dankito.banking.ui.model.BankAccount
|
||||||
|
import net.dankito.banking.ui.model.Customer
|
||||||
|
import net.dankito.banking.util.JacksonJsonSerializer
|
||||||
|
import net.dankito.utils.multiplatform.BigDecimal
|
||||||
|
import net.dankito.utils.multiplatform.Date
|
||||||
|
import net.dankito.utils.multiplatform.File
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mapstruct.factory.Mappers
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
|
class BankingPersistenceJsonTest {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val BankCode = "12345678"
|
||||||
|
|
||||||
|
const val CustomerId = "0987654321"
|
||||||
|
|
||||||
|
const val Password = "12345"
|
||||||
|
|
||||||
|
const val FinTsServerAddress = "http://i-do-not-exist.fail/givemeyourmoney"
|
||||||
|
|
||||||
|
const val BankName = "Abzock GmbH"
|
||||||
|
|
||||||
|
const val Bic = "ABCDDEBB123"
|
||||||
|
|
||||||
|
const val CustomerName = "Hans Dampf"
|
||||||
|
|
||||||
|
const val UserId = CustomerId
|
||||||
|
|
||||||
|
const val IconUrl = "http://i-do-not-exist.fail/favicon.ico"
|
||||||
|
|
||||||
|
val NowMillis = System.currentTimeMillis()
|
||||||
|
|
||||||
|
val TwoYearsAgoMillis = NowMillis - (2 * 365 * 24 * 60 * 60 * 1000L)
|
||||||
|
|
||||||
|
|
||||||
|
val TestDataFolder = File("testData")
|
||||||
|
|
||||||
|
init {
|
||||||
|
TestDataFolder.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val file = File(TestDataFolder, "test_accounts.json")
|
||||||
|
|
||||||
|
private val serializer = JacksonJsonSerializer()
|
||||||
|
|
||||||
|
private val underTest = BankingPersistenceJson(file, serializer)
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveOrUpdateAccount() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
val customers = listOf(
|
||||||
|
createCustomer(2),
|
||||||
|
createCustomer(3)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.saveOrUpdateAccount(customers.first(), customers)
|
||||||
|
|
||||||
|
|
||||||
|
// then
|
||||||
|
val result = serializer.deserializeListOr(file, CustomerEntity::class)
|
||||||
|
|
||||||
|
assertCustomersEqual(result, customers)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveOrUpdateAccountWithBankAccountsAndTransactions() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
val customer = createCustomer(2)
|
||||||
|
|
||||||
|
|
||||||
|
// when
|
||||||
|
underTest.saveOrUpdateAccount(customer, listOf(customer))
|
||||||
|
|
||||||
|
|
||||||
|
// then
|
||||||
|
val result = serializer.deserializeListOr(file, CustomerEntity::class)
|
||||||
|
|
||||||
|
assertCustomersEqual(result, listOf(customer))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun readPersistedAccounts() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
val customers = listOf(
|
||||||
|
createCustomer(2),
|
||||||
|
createCustomer(3)
|
||||||
|
)
|
||||||
|
val serializableCustomers = Mappers.getMapper(CustomerConverter::class.java).mapToEntities(customers, CycleAvoidingMappingContext())
|
||||||
|
|
||||||
|
serializer.serializeObject(serializableCustomers, file)
|
||||||
|
|
||||||
|
|
||||||
|
// when
|
||||||
|
val result = underTest.readPersistedAccounts()
|
||||||
|
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertCustomersEqual(serializableCustomers, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createCustomer(countBankAccounts: Int = 0, customerId: String = CustomerId): Customer {
|
||||||
|
val result = Customer(BankCode, customerId, Password, FinTsServerAddress, BankName, Bic, CustomerName, UserId, IconUrl)
|
||||||
|
|
||||||
|
result.accounts = createBankAccounts(countBankAccounts, result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBankAccounts(count: Int, customer: Customer): List<BankAccount> {
|
||||||
|
val random = Random(System.nanoTime())
|
||||||
|
|
||||||
|
return IntRange(1, count).map { accountIndex ->
|
||||||
|
createBankAccount("Account_$accountIndex", customer, random.nextInt(2, 50))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBankAccount(productName: String, customer: Customer, countTransactions: Int = 0): BankAccount {
|
||||||
|
val result = BankAccount(customer, customer.customerId, "AccountHolder", "DE00" + customer.bankCode + customer.customerId, null,
|
||||||
|
customer.customerId, BigDecimal(84.25), productName = productName)
|
||||||
|
|
||||||
|
result.bookedTransactions = createAccountTransactions(countTransactions, result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAccountTransactions(countTransactions: Int, account: BankAccount): List<AccountTransaction> {
|
||||||
|
return IntRange(1, countTransactions).map { transactionIndex ->
|
||||||
|
createAccountTransaction(transactionIndex, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAccountTransaction(transactionIndex: Int, account: BankAccount): AccountTransaction {
|
||||||
|
return AccountTransaction(account, "OtherParty_$transactionIndex", "Usage_$transactionIndex", BigDecimal(transactionIndex.toDouble()), createDate(), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDate(): Date {
|
||||||
|
return Date(Random(System.nanoTime()).nextLong(TwoYearsAgoMillis, NowMillis))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun assertCustomersEqual(deserializedCustomers: List<CustomerEntity>, customers: List<Customer>) {
|
||||||
|
assertThat(deserializedCustomers.size).isEqualTo(customers.size)
|
||||||
|
|
||||||
|
deserializedCustomers.forEach { deserializedCustomer ->
|
||||||
|
val customer = customers.firstOrNull { it.technicalId == deserializedCustomer.id }
|
||||||
|
|
||||||
|
if (customer == null) {
|
||||||
|
Assert.fail("Could not find matching customer for deserialized customer $deserializedCustomer. customers = $customers")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertCustomersEqual(deserializedCustomer, customer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertCustomersEqual(deserializedCustomer: CustomerEntity, customer: Customer) {
|
||||||
|
assertThat(deserializedCustomer.bankCode).isEqualTo(customer.bankCode)
|
||||||
|
assertThat(deserializedCustomer.customerId).isEqualTo(customer.customerId)
|
||||||
|
assertThat(deserializedCustomer.password).isEqualTo(customer.password)
|
||||||
|
assertThat(deserializedCustomer.finTsServerAddress).isEqualTo(customer.finTsServerAddress)
|
||||||
|
|
||||||
|
assertThat(deserializedCustomer.bankName).isEqualTo(customer.bankName)
|
||||||
|
assertThat(deserializedCustomer.bic).isEqualTo(customer.bic)
|
||||||
|
assertThat(deserializedCustomer.customerName).isEqualTo(customer.customerName)
|
||||||
|
assertThat(deserializedCustomer.userId).isEqualTo(customer.userId)
|
||||||
|
assertThat(deserializedCustomer.iconUrl).isEqualTo(customer.iconUrl)
|
||||||
|
|
||||||
|
assertBankAccountsEqual(deserializedCustomer.accounts, customer.accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertBankAccountsEqual(deserializedAccounts: List<BankAccountEntity>, accounts: List<BankAccount>) {
|
||||||
|
assertThat(deserializedAccounts.size).isEqualTo(accounts.size)
|
||||||
|
|
||||||
|
deserializedAccounts.forEach { deserializedAccount ->
|
||||||
|
val account = accounts.firstOrNull { it.technicalId == deserializedAccount.id }
|
||||||
|
|
||||||
|
if (account == null) {
|
||||||
|
Assert.fail("Could not find matching account for deserialized account $deserializedAccount. accounts = $accounts")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertBankAccountsEqual(deserializedAccount, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertBankAccountsEqual(deserializedAccount: BankAccountEntity, account: BankAccount) {
|
||||||
|
// to check if MapStruct created reference correctly
|
||||||
|
assertThat(deserializedAccount.customer.id).isEqualTo(account.customer.technicalId)
|
||||||
|
|
||||||
|
assertThat(deserializedAccount.identifier).isEqualTo(account.identifier)
|
||||||
|
assertThat(deserializedAccount.iban).isEqualTo(account.iban)
|
||||||
|
assertThat(deserializedAccount.customerId).isEqualTo(account.customerId)
|
||||||
|
assertThat(deserializedAccount.balance).isEqualTo(account.balance)
|
||||||
|
assertThat(deserializedAccount.productName).isEqualTo(account.productName)
|
||||||
|
|
||||||
|
assertAccountTransactionsEqual(deserializedAccount.bookedTransactions, account.bookedTransactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertAccountTransactionsEqual(deserializedTransactions: List<AccountTransactionEntity>, transactions: List<AccountTransaction>) {
|
||||||
|
assertThat(deserializedTransactions.size).isEqualTo(transactions.size)
|
||||||
|
|
||||||
|
deserializedTransactions.forEach { deserializedTransaction ->
|
||||||
|
val transaction = transactions.firstOrNull { it.technicalId == deserializedTransaction.id }
|
||||||
|
|
||||||
|
if (transaction == null) {
|
||||||
|
Assert.fail("Could not find matching transaction for deserialized transaction $deserializedTransaction. transactions = $transactions")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertAccountTransactionsEqual(deserializedTransaction, transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertAccountTransactionsEqual(deserializedTransaction: AccountTransactionEntity, transaction: AccountTransaction) {
|
||||||
|
// to check if MapStruct created reference correctly
|
||||||
|
assertThat(deserializedTransaction.bankAccount.id).isEqualTo(transaction.bankAccount.technicalId)
|
||||||
|
|
||||||
|
assertThat(deserializedTransaction.otherPartyName).isEqualTo(transaction.otherPartyName)
|
||||||
|
assertThat(deserializedTransaction.unparsedUsage).isEqualTo(transaction.unparsedUsage)
|
||||||
|
assertThat(deserializedTransaction.amount).isEqualTo(transaction.amount)
|
||||||
|
assertThat(deserializedTransaction.valueDate).isEqualTo(transaction.valueDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,14 +1,11 @@
|
||||||
package net.dankito.banking.ui.model
|
package net.dankito.banking.ui.model
|
||||||
|
|
||||||
//import com.fasterxml.jackson.annotation.JsonIdentityInfo
|
|
||||||
//import com.fasterxml.jackson.annotation.ObjectIdGenerators
|
|
||||||
import net.dankito.utils.multiplatform.BigDecimal
|
import net.dankito.utils.multiplatform.BigDecimal
|
||||||
import net.dankito.utils.multiplatform.Date
|
import net.dankito.utils.multiplatform.Date
|
||||||
import net.dankito.utils.multiplatform.DateFormatStyle
|
import net.dankito.utils.multiplatform.DateFormatStyle
|
||||||
import net.dankito.utils.multiplatform.DateFormatter
|
import net.dankito.utils.multiplatform.DateFormatter
|
||||||
|
|
||||||
|
|
||||||
//@JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
|
|
||||||
open class AccountTransaction(
|
open class AccountTransaction(
|
||||||
open val bankAccount: BankAccount,
|
open val bankAccount: BankAccount,
|
||||||
open val amount: BigDecimal,
|
open val amount: BigDecimal,
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package net.dankito.banking.ui.model
|
package net.dankito.banking.ui.model
|
||||||
|
|
||||||
//import com.fasterxml.jackson.annotation.JsonIdentityInfo
|
|
||||||
//import com.fasterxml.jackson.annotation.ObjectIdGenerators
|
|
||||||
import net.dankito.utils.multiplatform.BigDecimal
|
import net.dankito.utils.multiplatform.BigDecimal
|
||||||
import net.dankito.utils.multiplatform.Date
|
import net.dankito.utils.multiplatform.Date
|
||||||
import net.dankito.utils.multiplatform.UUID
|
import net.dankito.utils.multiplatform.UUID
|
||||||
import kotlin.jvm.JvmOverloads
|
import kotlin.jvm.JvmOverloads
|
||||||
|
|
||||||
|
|
||||||
//@JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
|
|
||||||
open class BankAccount @JvmOverloads constructor(
|
open class BankAccount @JvmOverloads constructor(
|
||||||
open val customer: Customer,
|
open val customer: Customer,
|
||||||
open val identifier: String,
|
open val identifier: String,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package net.dankito.banking.ui.model
|
package net.dankito.banking.ui.model
|
||||||
|
|
||||||
//import com.fasterxml.jackson.annotation.JsonIdentityInfo
|
|
||||||
//import com.fasterxml.jackson.annotation.ObjectIdGenerators
|
|
||||||
import net.dankito.utils.multiplatform.BigDecimal
|
import net.dankito.utils.multiplatform.BigDecimal
|
||||||
import net.dankito.utils.multiplatform.sum
|
import net.dankito.utils.multiplatform.sum
|
||||||
import net.dankito.banking.ui.model.tan.TanMedium
|
import net.dankito.banking.ui.model.tan.TanMedium
|
||||||
|
@ -10,7 +8,6 @@ import net.dankito.banking.ui.model.tan.TanProcedure
|
||||||
import net.dankito.utils.multiplatform.UUID
|
import net.dankito.utils.multiplatform.UUID
|
||||||
|
|
||||||
|
|
||||||
//@JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
|
|
||||||
open class Customer(
|
open class Customer(
|
||||||
open var bankCode: String,
|
open var bankCode: String,
|
||||||
open var customerId: String,
|
open var customerId: String,
|
||||||
|
|
Loading…
Reference in New Issue