Implemented writing account transactions to CSV files (with a very primitive CSV file writer)
This commit is contained in:
parent
ed66168c0b
commit
bed96199c8
|
@ -1,5 +1,6 @@
|
|||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlinVersion"
|
||||
id "maven-publish"
|
||||
}
|
||||
|
||||
|
@ -75,10 +76,12 @@ kotlin {
|
|||
dependencies {
|
||||
api project(":multiplatform-utils")
|
||||
|
||||
implementation "co.touchlab:stately-concurrency:1.2.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
||||
|
||||
implementation "co.touchlab:stately-concurrency:1.2.0"
|
||||
|
||||
implementation "io.ktor:ktor-client-core:$ktorVersion"
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +147,9 @@ kotlin {
|
|||
implementation "io.ktor:ktor-client-curl:$ktorVersion"
|
||||
|
||||
implementation "com.github.ajalt.clikt:clikt:3.4.0"
|
||||
|
||||
// only needed for writing files to output
|
||||
implementation "com.soywiz.korlibs.korio:korio:2.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import com.soywiz.korio.file.PathInfo
|
||||
import com.soywiz.korio.file.isAbsolute
|
||||
import com.soywiz.korio.file.std.localCurrentDirVfs
|
||||
import com.soywiz.korio.file.std.rootLocalVfs
|
||||
import com.soywiz.korio.file.std.userHomeVfs
|
||||
import com.soywiz.korio.lang.substr
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.encodeToString
|
||||
|
@ -13,6 +18,8 @@ import net.dankito.banking.fints.getAccountData
|
|||
import net.dankito.banking.fints.model.TanChallenge
|
||||
import net.dankito.banking.fints.transferMoney
|
||||
import net.dankito.utils.multiplatform.extensions.*
|
||||
import util.CsvWriter
|
||||
import util.OutputFormat
|
||||
|
||||
|
||||
class NativeApp {
|
||||
|
@ -24,7 +31,7 @@ class NativeApp {
|
|||
getAccountData(GetAccountDataParameter(bankCode, loginName, password))
|
||||
}
|
||||
|
||||
fun getAccountData(param: GetAccountDataParameter, outputFilePath: String? = null) {
|
||||
fun getAccountData(param: GetAccountDataParameter, outputFilePath: String? = null, outputFormat: OutputFormat = OutputFormat.Json) {
|
||||
val response = client.getAccountData(param)
|
||||
|
||||
if (response.error != null) {
|
||||
|
@ -33,7 +40,7 @@ class NativeApp {
|
|||
|
||||
response.customerAccount?.let { account ->
|
||||
if (outputFilePath != null) {
|
||||
writeResponseToFile(outputFilePath, account)
|
||||
writeResponseToFile(outputFilePath, outputFormat, account)
|
||||
} else {
|
||||
println("Retrieved response from ${account.bankName} for ${account.customerName}")
|
||||
|
||||
|
@ -109,14 +116,21 @@ class NativeApp {
|
|||
}
|
||||
|
||||
|
||||
private fun writeResponseToFile(outputFilePath: String, customer: CustomerAccount) {
|
||||
private fun writeResponseToFile(outputFilePath: String, outputFormat: OutputFormat, customer: CustomerAccount) {
|
||||
try {
|
||||
val outputFile = localCurrentDirVfs.get(outputFilePath)
|
||||
val outputFileInfo = PathInfo(outputFilePath)
|
||||
val outputFile = if (outputFileInfo.isAbsolute()) rootLocalVfs.get(outputFilePath)
|
||||
else if (outputFilePath.startsWith("~/")) userHomeVfs.get(outputFilePath.substr(2))
|
||||
else localCurrentDirVfs.get(outputFilePath)
|
||||
println("Writing file to ${outputFile.absolutePath}")
|
||||
|
||||
val json = Json.encodeToString(customer)
|
||||
if (outputFormat == OutputFormat.Json) {
|
||||
val json = Json.encodeToString(customer)
|
||||
|
||||
runBlocking { outputFile.writeString(json) }
|
||||
runBlocking { outputFile.writeString(json) }
|
||||
} else {
|
||||
CsvWriter().writeToFile(outputFile, if (outputFormat == OutputFormat.SemicolonSeparated) ";" else ",", customer)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Could not write file to $outputFilePath: $e")
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
|||
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
||||
import net.dankito.banking.fints.model.TanMethodType
|
||||
import net.dankito.utils.multiplatform.extensions.todayAtEuropeBerlin
|
||||
import util.OutputFormat
|
||||
|
||||
|
||||
class fints4kCommandLineInterface : CliktCommand(name = "fints", printHelpOnEmptyArgs = true, invokeWithoutSubcommand = true) {
|
||||
|
@ -40,6 +41,8 @@ class fints4kCommandLineInterface : CliktCommand(name = "fints", printHelpOnEmpt
|
|||
|
||||
val outputFile by option("-o", help = "Write retrieved account transactions to file instead of stdout. Supported formats: JSON")
|
||||
|
||||
val outputFormat by option("-f", "--format").enum<OutputFormat>().default(OutputFormat.Json)
|
||||
|
||||
val preferredTanMethods by option("-m", "--tan-method", help = "Your preferred TAN methods to use if action affords a TAN. Can be repeated like '-m AppTan -m SmsTan'").enum<TanMethodType>().multiple()
|
||||
|
||||
val abortIfRequiresTan by option("-a", "--abort-if-requires-tan", help = "If actions should be aborted if it affords a TAN. Defaults to false").flag(default = false)
|
||||
|
@ -69,11 +72,14 @@ class fints4kCommandLineInterface : CliktCommand(name = "fints", printHelpOnEmpt
|
|||
|
||||
val retrieveTransactionsToDate = if (retrieveTransactionsTo.isNullOrBlank()) null else LocalDate.parse(retrieveTransactionsTo!!)
|
||||
val effectiveRetrieveTransactions = if (retrieveTransactionsFromDate != null || retrieveTransactionsToDate != null) RetrieveTransactions.AccordingToRetrieveFromAndTo
|
||||
else retrieveTransactions
|
||||
else retrieveTransactions
|
||||
|
||||
val effectiveOutputFormat = if (outputFile != null && outputFile?.endsWith(".csv", true) == true && outputFormat == OutputFormat.Json) OutputFormat.CommaSeparated
|
||||
else outputFormat
|
||||
|
||||
|
||||
app.getAccountData(GetAccountDataParameter(bankCode, loginName, password, null, retrieveBalance, effectiveRetrieveTransactions,
|
||||
retrieveTransactionsFromDate, retrieveTransactionsToDate, preferredTanMethods, abortIfTanIsRequired = abortIfRequiresTan), outputFile)
|
||||
retrieveTransactionsFromDate, retrieveTransactionsToDate, preferredTanMethods, abortIfTanIsRequired = abortIfRequiresTan), outputFile, effectiveOutputFormat)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package util
|
||||
|
||||
import com.soywiz.korio.file.VfsFile
|
||||
import com.soywiz.korio.file.VfsOpenMode
|
||||
import com.soywiz.korio.stream.AsyncStream
|
||||
import com.soywiz.korio.stream.writeString
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.dankito.banking.client.model.AccountTransaction
|
||||
import net.dankito.banking.client.model.BankAccount
|
||||
import net.dankito.banking.client.model.CustomerAccount
|
||||
|
||||
|
||||
/**
|
||||
* A very basic implementation of a CSV writer. Do not use in production
|
||||
*/
|
||||
open class CsvWriter {
|
||||
|
||||
companion object {
|
||||
const val NewLine = "\r\n"
|
||||
}
|
||||
|
||||
|
||||
open fun writeToFile(outputFile: VfsFile, valueSeparator: String, customer: CustomerAccount) {
|
||||
runBlocking {
|
||||
val stream = outputFile.open(VfsOpenMode.CREATE_OR_TRUNCATE)
|
||||
|
||||
// print header
|
||||
stream.writeString(listOf("Bank", "Account", "Date", "Amount", "Currency", "Booking text", "Reference", "Other party name", "Other party bank id", "Other party account id").joinToString(valueSeparator))
|
||||
stream.writeString(NewLine)
|
||||
|
||||
customer.accounts.forEach { writeToFile(stream, valueSeparator, customer, it) }
|
||||
|
||||
stream.close()
|
||||
}
|
||||
}
|
||||
|
||||
protected open suspend fun writeToFile(stream: AsyncStream, valueSeparator: String, customer: CustomerAccount, account: BankAccount) {
|
||||
account.bookedTransactions.forEach { writeToFile(stream, valueSeparator, customer, account, it) }
|
||||
}
|
||||
|
||||
protected open suspend fun writeToFile(stream: AsyncStream, valueSeparator: String, customer: CustomerAccount, account: BankAccount, transaction: AccountTransaction) {
|
||||
val amount = if (valueSeparator == ";") transaction.amount.amount.string.replace('.', ',') else transaction.amount.amount.string.replace(',', '.')
|
||||
|
||||
stream.writeString(listOf(customer.bankName, account.identifier, transaction.valueDate, amount, transaction.amount.currency, ensureNotNull(transaction.bookingText), wrap(transaction.reference),
|
||||
ensureNotNull(transaction.otherPartyName), ensureNotNull(transaction.otherPartyBankCode), ensureNotNull(transaction.otherPartyAccountId)).joinToString(valueSeparator))
|
||||
|
||||
stream.writeString(NewLine)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps values that potentially contain the value separator
|
||||
*/
|
||||
protected open fun wrap(value: String): String {
|
||||
return "\"$value\""
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that 'null' doesn't get written to output
|
||||
*/
|
||||
protected open fun ensureNotNull(value: Any?): Any {
|
||||
return value ?: ""
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package util
|
||||
|
||||
|
||||
enum class OutputFormat {
|
||||
|
||||
Json,
|
||||
|
||||
CommaSeparated,
|
||||
|
||||
SemicolonSeparated
|
||||
|
||||
}
|
Loading…
Reference in New Issue