Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve CsvTransactionsExporter #166

Merged
merged 2 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/src/main/java/org/gnucash/android/export/Exporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
* @author Ngewi Fet <[email protected]>
* @author Yongxin Wang <[email protected]>
*/
public abstract class Exporter implements Closeable {
public abstract class Exporter {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not Closeable ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in the commit description, the subclasses of Exporter already call close() in generateExport(), so there is no need for the users of the class to call close().


/**
* Application folder on external storage
Expand Down Expand Up @@ -260,8 +260,7 @@ public String getExportMimeType() {
return "text/plain";
}

@Override
public void close() throws IOException {
protected void close() throws IOException {
mAccountsDbAdapter.close();
mBudgetsDbAdapter.close();
mCommoditiesDbAdapter.close();
Expand Down

This file was deleted.

pnemonic78 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2018-2024 GnuCash Android developers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gnucash.android.export.csv

import android.content.Context
import org.gnucash.android.R
import org.gnucash.android.export.ExportParams
import org.gnucash.android.export.Exporter
import org.gnucash.android.model.Account
import org.gnucash.android.model.Money.CurrencyMismatchException
import org.gnucash.android.model.Split
import org.gnucash.android.model.TransactionType
import org.gnucash.android.util.PreferencesHelper
import org.gnucash.android.util.TimestampHelper
import org.joda.time.format.DateTimeFormat
import timber.log.Timber
import java.io.FileWriter
import java.io.IOException
import java.util.Arrays

/**
* Creates a GnuCash CSV transactions representation of the accounts and transactions
*
* @author Semyannikov Gleb <nightdevgame></nightdevgame>@gmail.com>
*/
class CsvTransactionsExporter(context: Context,
params: ExportParams,
bookUID: String) : Exporter(context, params, bookUID) {
private val mCsvSeparator = params.csvSeparator
private val dateFormat = DateTimeFormat.forPattern("yyyy-MM-dd")

@Throws(ExporterException::class)
override fun generateExport(): List<String> {
val outputFile = getExportCacheFilePath()
var csvWriter: CsvWriter? = null

try {
csvWriter = CsvWriter(FileWriter(outputFile), mCsvSeparator.toString())
generateExport(csvWriter)
return listOf(outputFile)
} catch (ex: IOException) {
Timber.e(ex, "Error exporting CSV")
throw ExporterException(mExportParams, ex)
} finally {
csvWriter?.close()
close()
}
}

@Throws(IOException::class, CurrencyMismatchException::class)
private fun writeSplitsToCsv(splits: List<Split>, writer: CsvWriter) {
val accountCache: MutableMap<String, Account> = HashMap()
for ((index, split) in splits.withIndex()) {
if (index > 0) {
// The first split is on the same line as the transactions. But after that, the
// transaction-specific fields are empty.
writer.write("" // Date
+ mCsvSeparator // Transaction ID
+ mCsvSeparator // Number
+ mCsvSeparator // Description
+ mCsvSeparator // Notes
+ mCsvSeparator // Commodity/Currency
+ mCsvSeparator // Void Reason
+ mCsvSeparator // Action
+ mCsvSeparator // Memo
)
}
writer.writeToken(split.memo)
val accountUID = split.accountUID!!
val account = accountCache.getOrPut(accountUID) {
mAccountsDbAdapter.getRecord(accountUID)
}
writer.writeToken(account.fullName)
writer.writeToken(account.name)
val sign = if (split.type == TransactionType.CREDIT) "-" else ""
writer.writeToken(sign + split.quantity!!.formattedString())
writer.writeToken(sign + split.quantity!!.formattedStringWithoutSymbol())
writer.writeToken(split.reconcileState.toString())
if (split.reconcileState == Split.FLAG_RECONCILED) {
val recDateString = dateFormat.print(split.reconcileDate.getTime())
writer.writeToken(recDateString)
} else {
writer.writeToken(null)
}
writer.writeEndToken(split.quantity!!.div(split.value!!).formattedStringWithoutSymbol())
}
}

@Throws(ExporterException::class)
private fun generateExport(csvWriter: CsvWriter) {
try {
mContext.resources.getStringArray(R.array.csv_transaction_headers).forEach {
csvWriter.writeToken(it)
}
csvWriter.newLine()
val cursor = mTransactionsDbAdapter.fetchTransactionsModifiedSince(mExportParams.exportStartTime)
Timber.d("Exporting %d transactions to CSV", cursor.count)
while (cursor.moveToNext()) {
val transaction = mTransactionsDbAdapter.buildModelInstance(cursor)
csvWriter.writeToken(dateFormat.print(transaction.timeMillis))
csvWriter.writeToken(transaction.uID)
csvWriter.writeToken(null) // Transaction number
csvWriter.writeToken(transaction.description)
csvWriter.writeToken(transaction.note)
csvWriter.writeToken("CURRENCY::${transaction.currencyCode}")
csvWriter.writeToken(null) // Void Reason
csvWriter.writeToken(null) // Action
writeSplitsToCsv(transaction.splits, csvWriter)
}
cursor.close()
PreferencesHelper.setLastExportTime(TimestampHelper.getTimestampFromNow())
} catch (e: Exception) {
Timber.e(e, "Error while exporting transactions to CSV")
throw ExporterException(mExportParams, e)
}
}
}
27 changes: 17 additions & 10 deletions app/src/main/kotlin/org/gnucash/android/model/Money.kt
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@ class Money : Number, Comparable<Money>, Parcelable {
return currencyFormat.format(_amount)
}

/**
* Returns a string representation of the Money object formatted according to
* the `locale` without the currency symbol.
* The output precision is limited to the number of fractional digits supported by the currency
*
* @param locale Locale to use when formatting the object. Defaults to Locale.getDefault().
* @return String containing formatted Money representation
*/
@JvmOverloads
fun formattedStringWithoutSymbol(locale: Locale = Locale.getDefault()): String {
val format = NumberFormat.getNumberInstance(locale)
format.setMinimumFractionDigits(commodity.smallestFractionDigits)
format.setMaximumFractionDigits(commodity.smallestFractionDigits)
return format.format(_amount)
}

/**
* Returns a new Money object whose amount is the negated value of this object amount.
* The original `Money` object remains unchanged.
Expand Down Expand Up @@ -419,23 +435,14 @@ class Money : Number, Comparable<Money>, Parcelable {
*
*
* This string is not locale-formatted. The decimal operator is a period (.)
* For a locale-formatted version, see the method [.toLocaleString]
* For a locale-formatted version, see the method `formattedStringWithoutSymbol()`.
*
* @return String representation of the amount (without currency) of the Money object
*/
fun toPlainString(): String {
return _amount.setScale(commodity.smallestFractionDigits, roundingMode).toPlainString()
}

/**
* Returns a locale-specific representation of the amount of the Money object (excluding the currency)
*
* @return String representation of the amount (without currency) of the Money object
*/
fun toLocaleString(): String {
return String.format(Locale.getDefault(), "%.2f", toDouble())
}

/**
* Returns the string representation of the Money object (value + currency) formatted according
* to the default locale
Expand Down