fix the incorrect transaction type and amount when importing some Firefly III data

This commit is contained in:
MaysWind
2025-07-13 16:00:53 +08:00
parent 29d14bb5ef
commit 06ef2220d6
3 changed files with 299 additions and 88 deletions
@@ -4,6 +4,7 @@ import (
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/log"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
@@ -11,15 +12,22 @@ import (
const fireflyIIITransactionTimeColumnName = "date"
const fireflyIIITransactionTypeColumnName = "type"
const fireflyIIITransactionCategoryColumnName = "category"
const fireflyIIITransactionSourceAccountColumnName = "source_name"
const fireflyIIITransactionSourceAccountNameColumnName = "source_name"
const fireflyIIITransactionSourceAccountTypeColumnName = "source_type"
const fireflyIIITransactionCurrencyCodeColumnName = "currency_code"
const fireflyIIITransactionAmountColumnName = "amount"
const fireflyIIITransactionDestinationAccountColumnName = "destination_name"
const fireflyIIITransactionDestinationAccountNameColumnName = "destination_name"
const fireflyIIITransactionDestinationAccountTypeColumnName = "destination_type"
const fireflyIIITransactionForeignCurrencyCodeColumnName = "foreign_currency_code"
const fireflyIIITransactionForeignAmountColumnName = "foreign_amount"
const fireflyIIITransactionTagsColumnName = "tags"
const fireflyIIITransactionDescriptionColumnName = "description"
const fireflyIIIAssetAccountName = "Asset account"
const fireflyIIIExpenseAccountName = "Expense account"
const fireflyIIIRevenueAccountName = "Revenue account"
const fireflyIIIDebtAccountName = "Debt"
// fireflyIIITransactionDataRowParser defines the structure of firefly III transaction data row parser
type fireflyIIITransactionDataRowParser struct {
}
@@ -38,63 +46,69 @@ func (p *fireflyIIITransactionDataRowParser) Parse(ctx core.Context, user *model
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location())
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(dateTime.Location())
// parse transaction type
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataRow.GetData(fireflyIIITransactionTypeColumnName)
// parse transaction type, transaction category and amount
transactionType := dataRow.GetData(fireflyIIITransactionTypeColumnName)
sourceAccountType := dataRow.GetData(fireflyIIITransactionSourceAccountTypeColumnName)
destinationAccountType := dataRow.GetData(fireflyIIITransactionDestinationAccountTypeColumnName)
amount, err := utils.ParseAmount(utils.TrimTrailingZerosInDecimal(dataRow.GetData(fireflyIIITransactionAmountColumnName)))
if err != nil {
return nil, false, errs.ErrAmountInvalid
}
foreignAmount := amount
if dataRow.HasData(fireflyIIITransactionForeignAmountColumnName) && dataRow.GetData(fireflyIIITransactionForeignAmountColumnName) != "" {
foreignAmount, err = utils.ParseAmount(utils.TrimTrailingZerosInDecimal(dataRow.GetData(fireflyIIITransactionForeignAmountColumnName)))
if err != nil {
return nil, false, errs.ErrAmountInvalid
}
}
// parse transaction category
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionCategoryColumnName)
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] {
if sourceAccountType == fireflyIIIRevenueAccountName && (destinationAccountType == fireflyIIIAssetAccountName || destinationAccountType == fireflyIIIDebtAccountName) { // income
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME]
// if the category is empty, use the source account (revenue account) name as the category name
if rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] == "" {
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionSourceAccountColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionSourceAccountNameColumnName)
}
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountColumnName)
} else if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] {
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
} else if (sourceAccountType == fireflyIIIAssetAccountName || sourceAccountType == fireflyIIIDebtAccountName) && destinationAccountType == fireflyIIIExpenseAccountName { // expense
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE]
// if the category is empty, use the destination account (expense account) name as the category name
if rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] == "" {
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionDestinationAccountColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
}
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionSourceAccountColumnName)
} else if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionSourceAccountColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountColumnName)
} else if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE] {
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionSourceAccountNameColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
} else if transactionType == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE] { // opening balance
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE]
rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = ""
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountColumnName)
} else {
return nil, false, errs.ErrTransactionTypeInvalid
}
// parse amount
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = dataRow.GetData(fireflyIIITransactionAmountColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = dataRow.GetData(fireflyIIITransactionForeignAmountColumnName)
// trim trailing zero in decimal
if rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] != "" {
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.TrimTrailingZerosInDecimal(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
amount, err := utils.ParseAmount(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
if err != nil {
return nil, false, errs.ErrAmountInvalid
}
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
}
} else if (sourceAccountType == fireflyIIIAssetAccountName || sourceAccountType == fireflyIIIDebtAccountName) && (destinationAccountType == fireflyIIIAssetAccountName || destinationAccountType == fireflyIIIDebtAccountName) { // transfer
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
rowData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionSourceAccountNameColumnName)
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = dataRow.GetData(fireflyIIITransactionDestinationAccountNameColumnName)
if rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] != "" {
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.TrimTrailingZerosInDecimal(rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT])
amount, err := utils.ParseAmount(rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT])
if err != nil {
return nil, false, errs.ErrAmountInvalid
if transactionType == fireflyIIITransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] {
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(-foreignAmount)
} else {
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(foreignAmount)
}
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(amount)
} else {
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT]
log.Errorf(ctx, "[fireflyiii_transaction_data_row_parser.Parse] cannot detect transaction type, source account type is \"%s\", destination account type is \"%s\", Firefly III transaction type is \"%s\"", sourceAccountType, destinationAccountType, transactionType)
return nil, false, errs.ErrTransactionTypeInvalid
}
// parse account currency