[office/kmymoney] kmymoney/plugins/csv/import: CSV Importer: support Fee column in bank statements

Dawid Wrobel <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 8f3fdf30346b655852e15a71818981a98ca9ce0c by Dawid Wrobel, on behalf of Dawid Wróbel.
Committed on 19/07/2026 at 11:10.
Pushed by wrobelda into branch 'master'.

CSV Importer: support Fee column in bank statements

- Adds support for additional Fee column in banking statements
- relies on existing fee calculation logic in MyMoneyStatementReader
- adds relevant CSV importer test

M  +5    -0    kmymoney/plugins/csv/import/bankingwizardpage.cpp
M  +38   -0    kmymoney/plugins/csv/import/bankingwizardpage.ui
M  +20   -3    kmymoney/plugins/csv/import/core/csvimportercore.cpp
M  +32   -0    kmymoney/plugins/csv/import/core/tests/test-csvimportercore.cpp
M  +2    -0    kmymoney/plugins/csv/import/core/tests/test-csvimportercore.h

https://invent.kde.org/office/kmymoney/-/commit/8f3fdf30346b655852e15a71818981a98ca9ce0c

diff --git a/kmymoney/plugins/csv/import/bankingwizardpage.cpp b/kmymoney/plugins/csv/import/bankingwizardpage.cpp
index ef2b030a6..ca5891906 100644
--- a/kmymoney/plugins/csv/import/bankingwizardpage.cpp
+++ b/kmymoney/plugins/csv/import/bankingwizardpage.cpp
@@ -63,6 +63,7 @@ BankingPage::BankingPage(CSVWizard* dlg, CSVImporterCore* imp)
         {Column::Category, ui->m_categoryCol},
         {Column::CreditDebitIndicator, ui->m_creditDebitIndicatorCol},
         {Column::Balance, ui->m_balanceCol},
+        {Column::Fee, ui->m_feeCol},
     };
 
     connect(ui->m_clear, &QAbstractButton::clicked, this, &BankingPage::clearColumns);
@@ -101,6 +102,9 @@ BankingPage::BankingPage(CSVWizard* dlg, CSVImporterCore* imp)
     connect(ui->m_balanceCol, signal, this, [&](int col) { // clazy:exclude=connect-non-signal
         validateSelectedColumn(col, Column::Balance);
     });
+    connect(ui->m_feeCol, signal, this, [&](int col) { // clazy:exclude=connect-non-signal
+        validateSelectedColumn(col, Column::Fee);
+    });
 
     connect(ui->m_creditIndicator, &QLineEdit::textEdited, this, [&](const QString& indicator) {
         m_profile->m_creditIndicator = indicator;
@@ -130,6 +134,7 @@ BankingPage::BankingPage(CSVWizard* dlg, CSVImporterCore* imp)
     connectClearButton(m_payeeCol);
     connectClearButton(m_categoryCol);
     connectClearButton(m_balanceCol);
+    connectClearButton(m_feeCol);
     connectClearButton(m_amountCol);
     connectClearButton(m_creditDebitIndicatorCol);
     connectClearButton(m_debitCol);
diff --git a/kmymoney/plugins/csv/import/bankingwizardpage.ui b/kmymoney/plugins/csv/import/bankingwizardpage.ui
index 71c215734..8e16afb8f 100644
--- a/kmymoney/plugins/csv/import/bankingwizardpage.ui
+++ b/kmymoney/plugins/csv/import/bankingwizardpage.ui
@@ -355,6 +355,42 @@
            </property>
           </widget>
          </item>
+         <item row="7" column="0">
+          <widget class="QLabel" name="labelBnk_fee">
+           <property name="text">
+            <string>Fee</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+           </property>
+           <property name="buddy">
+            <cstring>m_feeCol</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="7" column="1">
+          <widget class="QComboBox" name="m_feeCol">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>Select column containing fee field.</string>
+           </property>
+          </widget>
+         </item>
+         <item row="7" column="2">
+          <widget class="QToolButton" name="m_feeColClear">
+           <property name="toolTip">
+            <string>Clear fee field selection.</string>
+           </property>
+           <property name="text">
+            <string>...</string>
+           </property>
+          </widget>
+         </item>
         </layout>
        </item>
        <item>
@@ -740,6 +776,8 @@
   <tabstop>m_memoColClear</tabstop>
   <tabstop>m_balanceCol</tabstop>
   <tabstop>m_balanceColClear</tabstop>
+  <tabstop>m_feeCol</tabstop>
+  <tabstop>m_feeColClear</tabstop>
   <tabstop>m_amountTabWidget</tabstop>
   <tabstop>m_amountCol</tabstop>
   <tabstop>m_amountColClear</tabstop>
diff --git a/kmymoney/plugins/csv/import/core/csvimportercore.cpp b/kmymoney/plugins/csv/import/core/csvimportercore.cpp
index 9b93a0003..3137bb3ad 100644
--- a/kmymoney/plugins/csv/import/core/csvimportercore.cpp
+++ b/kmymoney/plugins/csv/import/core/csvimportercore.cpp
@@ -2,7 +2,7 @@
     SPDX-FileCopyrightText: 2010 Allan Anderson <[email protected]>
     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <[email protected]>
     SPDX-FileCopyrightText: 2020 Thomas Baumgart <[email protected]>
-    SPDX-FileCopyrightText: 2021 Dawid Wróbel <[email protected]>
+    SPDX-FileCopyrightText: 2021-2026 Dawid Wróbel <[email protected]>
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
@@ -927,6 +927,24 @@ bool CSVImporterCore::processBankRow(MyMoneyStatement& st, const BankingProfile*
             return false;
     }
 
+    // process fee field
+    col = profile->m_colTypeNum.value(Column::Fee, -1);
+    if (col != -1) {
+        if (profile->m_decimalSymbol == DecimalSymbol::Auto) {
+            DecimalSymbol decimalSymbol = m_decimalSymbolIndexMap.value(col);
+            m_file->m_parse->setDecimalSymbol(decimalSymbol);
+        }
+
+        txt = m_file->m_model->item(row, col)->text();
+        if (txt.startsWith(QLatin1Char('('))) // check if brackets notation is used for negative numbers
+            txt.remove(QRegularExpression(QStringLiteral("[()]")));
+
+        if (!txt.isEmpty()) {
+            const auto fee = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt)).abs();
+            tr.m_fees = fee;
+        }
+    }
+
     MyMoneyStatement::Split s1;
     s1.m_amount = tr.m_amount;
     s1.m_strMemo = tr.m_strMemo;
@@ -1056,8 +1074,7 @@ bool CSVImporterCore::processInvestRow(MyMoneyStatement& st, const InvestmentPro
             MyMoneyMoney fee(m_file->m_parse->possiblyReplaceSymbol(txt));
             if (profile->m_feeIsPercentage && profile->m_feeRate.isEmpty()) //   fee is percent
                 fee *= tr.m_amount / MyMoneyMoney(100); // as percentage
-            fee.abs();
-            tr.m_fees = fee;
+            tr.m_fees = fee.abs();
         }
     }
 
diff --git a/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.cpp b/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.cpp
index a8415d217..e56b908fc 100644
--- a/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.cpp
+++ b/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.cpp
@@ -1,5 +1,6 @@
 /*
     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <[email protected]>
+    SPDX-FileCopyrightText: 2026 Dawid Wróbel <[email protected]>
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
@@ -207,6 +208,37 @@ void CSVImporterCoreTest::testImportByAmount()
     QVERIFY(st.m_listTransactions[1].m_amount == MyMoneyMoney(56.78));
 }
 
+void CSVImporterCoreTest::testImportByAmountWithFee()
+{
+    QString csvContent;
+    csvContent += QLatin1String("\"Trans Date\",\"Post Date\",\"Description\",\"Amount\",\"Fee\",\"Category\"\n");
+    csvContent += QLatin1String("05/16/2016,05/17/2016,FOO1,100.00,2.50,BAR\n");
+    csvContent += QLatin1String("06/17/2016,06/18/2016,FOO2,-100.00,2.50,BAR\n");
+    csvContent += QLatin1String("07/18/2016,07/19/2016,FOO3,-100.00,-2.50,BAR\n");
+    csvContent += QLatin1String("08/19/2016,08/20/2016,FOO4,100.00,,BAR\n");
+
+    QString filename("import-by-amount-with-fee.csv");
+    writeStatementToCSV(csvContent, filename);
+    amountProfile->m_colTypeNum.insert(Column::Fee, 4);
+    amountProfile->m_colTypeNum[Column::Category] = 5;
+    amountProfile->m_colNumType.remove(4);
+    amountProfile->m_colNumType.insert(4, Column::Fee);
+    amountProfile->m_colNumType.insert(5, Column::Category);
+
+    auto st = csvImporter->unattendedImport(filename, amountProfile);
+    QVERIFY(st.m_listTransactions.count() == 4);
+    QVERIFY(st.m_listTransactions[0].m_fees == MyMoneyMoney(2.50));
+    QVERIFY(st.m_listTransactions[0].m_amount == MyMoneyMoney(100.00));
+    QVERIFY(st.m_listTransactions[0].m_listSplits.first().m_amount == MyMoneyMoney(-100.00));
+    QVERIFY(st.m_listTransactions[1].m_fees == MyMoneyMoney(2.50));
+    QVERIFY(st.m_listTransactions[1].m_amount == MyMoneyMoney(-100.00));
+    QVERIFY(st.m_listTransactions[1].m_listSplits.first().m_amount == MyMoneyMoney(100.00));
+    QVERIFY(st.m_listTransactions[2].m_fees == MyMoneyMoney(2.50));
+    QVERIFY(st.m_listTransactions[2].m_amount == MyMoneyMoney(-100.00));
+    QVERIFY(st.m_listTransactions[3].m_fees == MyMoneyMoney());
+    QVERIFY(st.m_listTransactions[3].m_amount == MyMoneyMoney(100.00));
+}
+
 void CSVImporterCoreTest::testImportByName()
 {
     auto stockNames = csvImporter->m_mapSymbolName.values();
diff --git a/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.h b/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.h
index 37fec7e71..66b737e86 100644
--- a/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.h
+++ b/kmymoney/plugins/csv/import/core/tests/test-csvimportercore.h
@@ -1,5 +1,6 @@
 /*
     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <[email protected]>
+    SPDX-FileCopyrightText: 2026 Dawid Wróbel <[email protected]>
     SPDX-License-Identifier: GPL-2.0-or-later
 */
 
@@ -38,6 +39,7 @@ private Q_SLOTS:
     void testPriceFractionSetting();
     void testImportByDebitCredit();
     void testImportByAmount();
+    void testImportByAmountWithFee();
     void testImportByName();
     void testImportBySymbol();
     void testFeeColumn();
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.