gnucash stable: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Tue Jun 23 18:45:27 EDT 2026
Updated via https://github.com/Gnucash/gnucash/commit/7f4e8c29 (commit)
via https://github.com/Gnucash/gnucash/commit/3e98c5d8 (commit)
via https://github.com/Gnucash/gnucash/commit/222714d7 (commit)
from https://github.com/Gnucash/gnucash/commit/32a6868f (commit)
commit 7f4e8c29e06d21c33a511e2202dcbb3504ad16fa
Merge: 32a6868f52 3e98c5d8c8
Author: John Ralls <jralls at ceridwen.us>
Date: Tue Jun 23 14:55:41 2026 -0700
Merge Noah Noerr's 'online-id-engine-accessors' into stable.
commit 3e98c5d8c8aa9a9556ba595c8d6c80970e145d50
Author: Noah R <Noerr at users.noreply.github.com>
Date: Sun Jun 21 16:39:24 2026 -0700
[engine] Add first-class online_id accessors for Split and Account
Promote the OFX/HBCI online_id to named engine accessors:
xaccSplitGetOnlineID / SetOnlineID / HasOnlineID
xaccAccountGetOnlineID / SetOnlineID
The getters return an instance-owned const char* (mirroring
xaccTransGetDocLink / xaccTransGetNotes), so the existing
add_methods_with_prefix auto-wrapper exposes them in the Python bindings
with no .i changes and no %newobject. They write to the same engine KVP
slot ("online_id") the desktop importer uses, so there is no data or
behavior change.
With the accessors in place the gnc_import_*_online_id wrappers in
import-utilities are redundant, so replace every call site (OFX,
AqBanking, and the generic matcher/backend) with them, delete
import-utilities.cpp, and drop the online_id declarations from
import-utilities.h (its importer preference-key macros are retained).
- The engine getters return an instance-owned const char* instead of a
g_strdup'd copy, so callers no longer free the result; the affected
locals are retyped const and their g_free()s dropped.
hash_account_online_ids() g_strdups before inserting, since its hash
table owns its keys (g_free key-destructor).
- xaccAccountSetOnlineID(acc, "") clears the slot, matching the OFX
"delete the online_id" intent (the old wrapper stored an empty string).
- Drop the two unused wrappers: gnc_import_set_trans_online_id (marked
"Not actually used") and gnc_import_trans_has_online_id (no callers).
- Add xaccSplitGet/SetOnlineID to the Split gmock so test-import-backend
links without import-utilities.
diff --git a/bindings/python/tests/test_account.py b/bindings/python/tests/test_account.py
index 7c1ab96668..008916d5c8 100644
--- a/bindings/python/tests/test_account.py
+++ b/bindings/python/tests/test_account.py
@@ -17,6 +17,15 @@ class TestAccount(AccountSession):
self.account.SetName(NAME)
self.assertEqual( NAME, self.account.GetName() )
+ def test_online_id(self):
+ ONLINE_ID = "061000104:0123456789:CHECKING"
+ self.assertEqual( None, self.account.GetOnlineID() )
+ self.account.SetOnlineID(ONLINE_ID)
+ self.assertEqual( ONLINE_ID, self.account.GetOnlineID() )
+ # Passing None (or "") clears it.
+ self.account.SetOnlineID(None)
+ self.assertEqual( None, self.account.GetOnlineID() )
+
def test_split(self):
SPLIT = Split(self.book)
self.assertTrue(self.account.insert_split(SPLIT))
diff --git a/bindings/python/tests/test_split.py b/bindings/python/tests/test_split.py
index 7358d37761..de8d1b2ca9 100644
--- a/bindings/python/tests/test_split.py
+++ b/bindings/python/tests/test_split.py
@@ -21,6 +21,19 @@ class TestSplit(SplitSession):
self.split.SetMemo(MEMO)
self.assertEqual( MEMO, self.split.GetMemo() )
+ def test_online_id(self):
+ FITID = "20240131-0001-1234567890"
+ # Unset online_id reads back as None (no KVP slot present).
+ self.assertEqual( None, self.split.GetOnlineID() )
+ self.assertFalse( self.split.HasOnlineID() )
+ self.split.SetOnlineID(FITID)
+ self.assertEqual( FITID, self.split.GetOnlineID() )
+ self.assertTrue( self.split.HasOnlineID() )
+ # Passing "" (or None) clears it.
+ self.split.SetOnlineID("")
+ self.assertEqual( None, self.split.GetOnlineID() )
+ self.assertFalse( self.split.HasOnlineID() )
+
def test_account(self):
ACCT = Account(self.book)
ACCT.SetCommodity(self.currency)
diff --git a/gnucash/import-export/CMakeLists.txt b/gnucash/import-export/CMakeLists.txt
index 3765468acd..b7b7cf0ea7 100644
--- a/gnucash/import-export/CMakeLists.txt
+++ b/gnucash/import-export/CMakeLists.txt
@@ -20,7 +20,6 @@ set (generic_import_SOURCES
import-format-dialog.cpp
import-match-picker.cpp
import-parse.cpp
- import-utilities.cpp
import-settings.cpp
import-main-matcher.cpp
import-pending-matches.cpp
diff --git a/gnucash/import-export/aqb/assistant-ab-initial.c b/gnucash/import-export/aqb/assistant-ab-initial.c
index c65acb956b..5a2dba7f30 100644
--- a/gnucash/import-export/aqb/assistant-ab-initial.c
+++ b/gnucash/import-export/aqb/assistant-ab-initial.c
@@ -63,7 +63,6 @@
#include "gnc-ui-util.h"
#include "gnc-session.h"
#include "import-account-matcher.h"
-#include "import-utilities.h"
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_ASSISTANT;
@@ -684,7 +683,7 @@ save_kvp_acc_cb(gpointer key, gpointer value, gpointer user_data)
const gchar *ab_accountid, *gnc_accountid;
const gchar *ab_bankcode, *gnc_bankcode;
gchar *ab_online_id;
- gchar *gnc_online_id;
+ const gchar *gnc_online_id;
g_return_if_fail(ab_acc && gnc_acc);
@@ -707,11 +706,10 @@ save_kvp_acc_cb(gpointer key, gpointer value, gpointer user_data)
gnc_ab_set_account_bankcode(gnc_acc, ab_bankcode);
ab_online_id = gnc_ab_create_online_id(ab_bankcode, ab_accountid);
- gnc_online_id = gnc_import_get_acc_online_id(gnc_acc);
+ gnc_online_id = xaccAccountGetOnlineID(gnc_acc);
if (ab_online_id && (!gnc_online_id || (strcmp(ab_online_id, gnc_online_id) != 0)))
- gnc_import_set_acc_online_id(gnc_acc, ab_online_id);
+ xaccAccountSetOnlineID(gnc_acc, ab_online_id);
g_free(ab_online_id);
- g_free (gnc_online_id);
}
static void
diff --git a/gnucash/import-export/aqb/gnc-ab-utils.c b/gnucash/import-export/aqb/gnc-ab-utils.c
index 935468304d..2c4defcc92 100644
--- a/gnucash/import-export/aqb/gnc-ab-utils.c
+++ b/gnucash/import-export/aqb/gnc-ab-utils.c
@@ -49,7 +49,6 @@
#include "gnc-ui.h"
#include "import-account-matcher.h"
#include "import-main-matcher.h"
-#include "import-utilities.h"
#include "qof.h"
#include "engine-helpers.h"
#include <aqbanking/gui/abgui.h>
@@ -569,7 +568,7 @@ gnc_ab_trans_to_gnc (const AB_TRANSACTION *ab_trans, Account *gnc_acc)
/* Set OFX unique transaction ID */
if (fitid && *fitid)
- gnc_import_set_split_online_id (split, fitid);
+ xaccSplitSetOnlineID (split, fitid);
/* FIXME: Extract function */
{
diff --git a/gnucash/import-export/import-account-matcher.cpp b/gnucash/import-export/import-account-matcher.cpp
index 4179e27d18..c82589ab8a 100644
--- a/gnucash/import-export/import-account-matcher.cpp
+++ b/gnucash/import-export/import-account-matcher.cpp
@@ -34,7 +34,6 @@
#include <glib/gi18n.h>
#include "import-account-matcher.h"
-#include "import-utilities.h"
#include "dialog-account.h"
#include "dialog-utils.h"
@@ -70,15 +69,11 @@ typedef struct
static gpointer test_acct_online_id_match(Account *acct, gpointer data)
{
AccountOnlineMatch *match = (AccountOnlineMatch*)data;
- char *acct_online_id = gnc_import_get_acc_online_id(acct);
+ const char *acct_online_id = xaccAccountGetOnlineID(acct);
int acct_len, match_len;
if (acct_online_id == NULL || match->online_id == NULL)
- {
- if (acct_online_id)
- g_free (acct_online_id);
return NULL;
- }
acct_len = strlen(acct_online_id);
match_len = strlen(match->online_id);
@@ -91,10 +86,7 @@ static gpointer test_acct_online_id_match(Account *acct, gpointer data)
if (strncmp (acct_online_id, match->online_id, acct_len) == 0)
{
if (strncmp(acct_online_id, match->online_id, match_len) == 0)
- {
- g_free (acct_online_id);
return (gpointer *) acct;
- }
if (match->partial_match == NULL)
{
match->partial_match = acct;
@@ -102,8 +94,8 @@ static gpointer test_acct_online_id_match(Account *acct, gpointer data)
}
else
{
- char *partial_online_id =
- gnc_import_get_acc_online_id(match->partial_match);
+ const char *partial_online_id =
+ xaccAccountGetOnlineID(match->partial_match);
int partial_len = strlen(partial_online_id);
if (partial_online_id[partial_len - 1] == ' ')
--partial_len;
@@ -133,11 +125,9 @@ static gpointer test_acct_online_id_match(Account *acct, gpointer data)
g_free (name1);
g_free (name2);
}
- g_free (partial_online_id);
}
}
- g_free (acct_online_id);
return NULL;
}
@@ -469,7 +459,7 @@ Account * gnc_import_select_account(GtkWidget *parent,
if (account_online_id_value)
{
- gnc_import_set_acc_online_id(retval, account_online_id_value);
+ xaccAccountSetOnlineID(retval, account_online_id_value);
}
ok_pressed_retval = TRUE;
break;
diff --git a/gnucash/import-export/import-backend.cpp b/gnucash/import-export/import-backend.cpp
index 2028645597..4cef7ecbc0 100644
--- a/gnucash/import-export/import-backend.cpp
+++ b/gnucash/import-export/import-backend.cpp
@@ -861,11 +861,9 @@ process_reconcile(Account *base_acc,
/* Copy the online id to the reconciled transaction, so
* the match will be remembered */
- auto online_id = gnc_import_get_split_online_id(trans_info->first_split);
+ auto online_id = xaccSplitGetOnlineID(trans_info->first_split);
if (online_id && *online_id)
- gnc_import_set_split_online_id(selected_match->split, online_id);
-
- g_free (online_id);
+ xaccSplitSetOnlineID(selected_match->split, online_id);
/* Done editing. */
/*DEBUG("CommitEdit selected_match")*/
@@ -1040,9 +1038,9 @@ hash_account_online_ids (Account *account)
(g_str_hash, g_str_equal, g_free, nullptr);
for (auto split : xaccAccountGetSplits (account))
{
- auto id = gnc_import_get_split_online_id (split);
+ auto id = xaccSplitGetOnlineID (split);
if (id && *id)
- g_hash_table_insert (acct_hash, (void*) id, GINT_TO_POINTER (1));
+ g_hash_table_insert (acct_hash, (void*) g_strdup (id), GINT_TO_POINTER (1));
}
return acct_hash;
}
@@ -1056,7 +1054,7 @@ gboolean gnc_import_exists_online_id (Transaction *trans, GHashTable* acct_id_ha
auto source_split = xaccTransGetSplit(trans, 0);
g_assert(source_split);
- auto source_online_id = gnc_import_get_split_online_id (source_split);
+ auto source_online_id = xaccSplitGetOnlineID (source_split);
// No online id, no point in continuing. We'd crash if we tried.
if (!source_online_id)
@@ -1081,7 +1079,6 @@ gboolean gnc_import_exists_online_id (Transaction *trans, GHashTable* acct_id_ha
DEBUG("Transaction with online ID %s already exists, date: %s", source_online_id, date_str);
g_free (date_str);
}
- g_free (source_online_id);
return online_id_exists;
}
diff --git a/gnucash/import-export/import-main-matcher.cpp b/gnucash/import-export/import-main-matcher.cpp
index f8fb60ea05..3a5e7c2bcd 100644
--- a/gnucash/import-export/import-main-matcher.cpp
+++ b/gnucash/import-export/import-main-matcher.cpp
@@ -2360,7 +2360,7 @@ create_hash_of_potential_matches (GList *candidate_splits,
candidate = g_list_next (candidate))
{
auto split = static_cast<Split*>(candidate->data);
- if (gnc_import_split_has_online_id (split))
+ if (xaccSplitHasOnlineID (split))
continue;
/* In this context an open transaction represents a freshly
* downloaded one. That can't possibly be a match yet */
diff --git a/gnucash/import-export/import-utilities.cpp b/gnucash/import-export/import-utilities.cpp
deleted file mode 100644
index a0480221c9..0000000000
--- a/gnucash/import-export/import-utilities.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/********************************************************************\
- * This program is free software; you can redistribute it and/or *
- * modify it under the terms of the GNU General Public License as *
- * published by the Free Software Foundation; either version 2 of *
- * the License, or (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, contact: *
- * *
- * Free Software Foundation Voice: +1-617-542-5942 *
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
- * Boston, MA 02110-1301, USA gnu at gnu.org *
-\********************************************************************/
-/** @addtogroup Import_Export
- @{ */
-/** @internal
- @file import-utilities.c
- @brief Utility functions for writing import modules.
- @author Copyright (C) 2002 Benoit Grégoire <bock at step.polymtl.ca>
-*/
-#include <config.h>
-
-
-#include <glib.h>
-
-#include <stdlib.h>
-#include "import-utilities.h"
-#include "qof.h"
-#include "Account.h"
-#include "Transaction.h"
-
-
-/********************************************************************\
- * Setter and getter functions for the online_id kvp frame in
- * Account, Transaction and Split
-\********************************************************************/
-
-gchar *
-gnc_import_get_acc_online_id (Account * account)
-{
- gchar *id = NULL;
- qof_instance_get (QOF_INSTANCE (account), "online-id", &id, NULL);
- return id;
-}
-
-/* Used in the midst of editing a transaction; make it save the
- * account data. */
-void
-gnc_import_set_acc_online_id (Account *account, const gchar *id)
-{
- g_return_if_fail (account != NULL);
- xaccAccountBeginEdit (account);
- qof_instance_set (QOF_INSTANCE (account), "online-id", id, NULL);
- xaccAccountCommitEdit (account);
-}
-
-gchar *
-gnc_import_get_trans_online_id (Transaction * transaction)
-{
- gchar *id = NULL;
- qof_instance_get (QOF_INSTANCE (transaction), "online-id", &id, NULL);
- return id;
-}
-
-/* Not actually used */
-void
-gnc_import_set_trans_online_id (Transaction *transaction, const gchar *id)
-{
- g_return_if_fail (transaction != NULL);
- xaccTransBeginEdit (transaction);
- qof_instance_set (QOF_INSTANCE (transaction), "online-id", id, NULL);
- xaccTransCommitEdit (transaction);
-}
-
-gboolean
-gnc_import_trans_has_online_id (Transaction * transaction)
-{
- gchar *online_id = gnc_import_get_trans_online_id(transaction);
- gboolean retval = (online_id && *online_id);
- g_free (online_id);
- return retval;
-}
-
-gchar *
-gnc_import_get_split_online_id (Split * split)
-{
- gchar *id = NULL;
- qof_instance_get (QOF_INSTANCE (split), "online-id", &id, NULL);
- return id;
-}
-
-/* Used several places in a transaction edit where many other
- * parameters are also being set, so individual commits wouldn't be
- * appropriate. Besides, there isn't a function for one.*/
-void
-gnc_import_set_split_online_id (Split *split, const gchar *id)
-{
- g_return_if_fail (split != NULL);
- qof_instance_set (QOF_INSTANCE (split), "online-id", id, NULL);
-}
-
-gboolean
-gnc_import_split_has_online_id (Split * split)
-{
- gchar *online_id = gnc_import_get_split_online_id(split);
- gboolean retval = (online_id && *online_id);
- g_free (online_id);
- return retval;
-}
-
-/* @} */
diff --git a/gnucash/import-export/import-utilities.h b/gnucash/import-export/import-utilities.h
index 3f20886a6d..0dbb7f9bba 100644
--- a/gnucash/import-export/import-utilities.h
+++ b/gnucash/import-export/import-utilities.h
@@ -19,7 +19,7 @@
/** @addtogroup Import_Export
@{ */
/** @file import-utilities.h
- @brief Utility functions for writing import modules.
+ @brief Preference keys for the generic importer.
@author Copyright (C) 2002 Benoit Grégoire <bock at step.polymtl.ca>
*/
#ifndef IMPORT_UTILITIES_H
@@ -40,50 +40,5 @@
#include "Account.h"
-#ifdef __cplusplus
-extern "C"
-{
#endif
-
-/** @name Setter-getters
- Setter and getter functions for the online_id field for
- Accounts.
- @{
-*/
-gchar * gnc_import_get_acc_online_id(Account * account);
-void gnc_import_set_acc_online_id(Account * account,
- const gchar * string_value);
/** @} */
-/** @name Setter-getters
- Setter and getter functions for the online_id field for
- Transactions.
- @{
-*/
-gchar * gnc_import_get_trans_online_id(Transaction * transaction);
-void gnc_import_set_trans_online_id(Transaction * transaction,
- const gchar * string_value);
-/** @} */
-
-gboolean gnc_import_trans_has_online_id(Transaction * transaction);
-
-/** @name Setter-getters
- Setter and getter functions for the online_id field for
- Splits.
- @{
-*/
-gchar * gnc_import_get_split_online_id(Split * split);
-void gnc_import_set_split_online_id(Split * split,
- const gchar * string_value);
-/** @} */
-
-gboolean gnc_import_split_has_online_id(Split * split);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
-/** @} */
-
-
-
diff --git a/gnucash/import-export/ofx/gnc-ofx-import.cpp b/gnucash/import-export/ofx/gnc-ofx-import.cpp
index 312d1e9240..8e4d218917 100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.cpp
+++ b/gnucash/import-export/ofx/gnc-ofx-import.cpp
@@ -592,8 +592,7 @@ process_bank_transaction(Transaction *transaction, Account *import_account,
}
if (data->fi_id_valid)
{
- gnc_import_set_split_online_id(split,
- sanitize_string (data->fi_id));
+ xaccSplitSetOnlineID(split, sanitize_string (data->fi_id));
}
}
@@ -618,7 +617,7 @@ create_investment_subaccount(GtkWindow *parent, Account* parent_acct,
ACCT_TYPE_STOCK);
if (investment_account)
{
- gnc_import_set_acc_online_id(investment_account, inv_data->online_id);
+ xaccAccountSetOnlineID(investment_account, inv_data->online_id);
inv_data->choosing = FALSE;
ofx_parent_account = parent_acct;
}
@@ -644,7 +643,7 @@ continue_account_selection(GtkWidget* parent, Account* account,
gnc_commodity_get_fullname(commodity),
gnc_commodity_get_fullname(xaccAccountGetCommodity(account)));
// We must also delete the online_id that was set in gnc_import_select_account()
- gnc_import_set_acc_online_id(account, "");
+ xaccAccountSetOnlineID(account, "");
return keep_going;
}
@@ -815,8 +814,7 @@ add_investment_split(Transaction* transaction, Account* account,
xaccAccountTypesCompatible(xaccAccountGetType(account),
ACCT_TYPE_ASSET))
{
- gnc_import_set_split_online_id(split,
- sanitize_string (data->fi_id));
+ xaccSplitSetOnlineID(split, sanitize_string (data->fi_id));
}
}
@@ -837,7 +835,7 @@ add_currency_split(Transaction *transaction, Account* account,
// Set split memo from ofx transaction name or memo
gnc_ofx_set_split_memo(data, split);
if (data->fi_id_valid)
- gnc_import_set_split_online_id (split, sanitize_string (data->fi_id));
+ xaccSplitSetOnlineID (split, sanitize_string (data->fi_id));
}
/* ******** Process an investment transaction **********/
diff --git a/gnucash/import-export/test/CMakeLists.txt b/gnucash/import-export/test/CMakeLists.txt
index 0c1859462f..af2b1c0506 100644
--- a/gnucash/import-export/test/CMakeLists.txt
+++ b/gnucash/import-export/test/CMakeLists.txt
@@ -59,7 +59,6 @@ set(gtest_import_backend_SOURCES
gtest-import-backend.cpp
${CMAKE_SOURCE_DIR}/gnucash/import-export/import-backend.cpp
${CMAKE_SOURCE_DIR}/gnucash/import-export/import-settings.cpp
- ${CMAKE_SOURCE_DIR}/gnucash/import-export/import-utilities.cpp
${CMAKE_SOURCE_DIR}/libgnucash/engine/mocks/gmock-qofinstance.cpp
${CMAKE_SOURCE_DIR}/libgnucash/app-utils/mocks/gmock-gnc-prefs.cpp
${CMAKE_SOURCE_DIR}/libgnucash/engine/mocks/gmock-qofbook.cpp
diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp
index 019c613a53..852d5ac674 100644
--- a/libgnucash/engine/Account.cpp
+++ b/libgnucash/engine/Account.cpp
@@ -2638,6 +2638,13 @@ xaccAccountSetNotes (Account *acc, const char *str)
set_kvp_string_path (acc, {"notes"}, str);
}
+void
+xaccAccountSetOnlineID (Account *acc, const char *id)
+{
+ g_return_if_fail (GNC_IS_ACCOUNT(acc));
+ set_kvp_string_path (acc, {KEY_ONLINE_ID}, id);
+}
+
void
xaccAccountSetAssociatedAccount (Account *acc, const char *tag, const Account* assoc_acct)
@@ -3350,6 +3357,13 @@ xaccAccountGetNotes (const Account *acc)
return get_kvp_string_path (acc, {"notes"});
}
+const char *
+xaccAccountGetOnlineID (const Account *acc)
+{
+ g_return_val_if_fail (GNC_IS_ACCOUNT(acc), nullptr);
+ return get_kvp_string_path (acc, {KEY_ONLINE_ID});
+}
+
Account*
xaccAccountGetAssociatedAccount (const Account *acc, const char *tag)
{
diff --git a/libgnucash/engine/Account.h b/libgnucash/engine/Account.h
index 0043d6a410..d8e7dd406b 100644
--- a/libgnucash/engine/Account.h
+++ b/libgnucash/engine/Account.h
@@ -317,6 +317,14 @@ typedef enum
/** Set the account's notes */
void xaccAccountSetNotes (Account *account, const char *notes);
+ /** Set the account's online_id, the identifier (e.g. an OFX/HBCI
+ * BANKID+ACCTID composite) of the online account this GnuCash account
+ * is mapped to for bank imports. This is the same value (engine KVP
+ * slot "online_id") that the desktop OFX/HBCI importer uses to match a
+ * downloaded statement to its GnuCash account. Passing NULL or ""
+ * clears it. Wraps its own begin/commit edit. */
+ void xaccAccountSetOnlineID (Account *account, const char *id);
+
/** Set the account's associated account e.g. stock account -> dividend account */
void xaccAccountSetAssociatedAccount (Account *acc, const char *tag,
const Account *assoc_acct);
@@ -426,6 +434,11 @@ typedef enum
/** Get the account's notes */
const char * xaccAccountGetNotes (const Account *account);
+ /** Get the account's online_id (see xaccAccountSetOnlineID). The
+ * returned string is owned by the account and must NOT be freed;
+ * returns NULL if no online_id is set. */
+ const char * xaccAccountGetOnlineID (const Account *account);
+
/** Get the account's associated account e.g. stock account -> dividend account */
Account* xaccAccountGetAssociatedAccount (const Account *acc, const char *tag);
/** Get the last num field of an Account */
diff --git a/libgnucash/engine/Split.cpp b/libgnucash/engine/Split.cpp
index ad257808b4..1050e28b8d 100644
--- a/libgnucash/engine/Split.cpp
+++ b/libgnucash/engine/Split.cpp
@@ -101,6 +101,7 @@ enum
static const char * split_type_normal = "normal";
static const char * split_type_stock_split = "stock-split";
+static const char * split_online_id = "online_id";
/* GObject Initialization */
G_DEFINE_TYPE(Split, gnc_split, QOF_TYPE_INSTANCE)
@@ -1901,6 +1902,35 @@ xaccSplitGetMemo (const Split *split)
return split ? split->memo : nullptr;
}
+void
+xaccSplitSetOnlineID (Split *split, const char *id)
+{
+ if (!split) return;
+ /* No xaccTransBeginEdit/CommitEdit here: the online_id is normally set
+ * while many other parameters of a transaction are being changed, so
+ * the caller wraps this in the parent transaction's edit. */
+ std::optional<const char*> val;
+ if (id && *id)
+ val = g_strdup (id);
+ qof_instance_set_path_kvp<const char*> (QOF_INSTANCE(split), val, {split_online_id});
+ qof_instance_set_dirty (QOF_INSTANCE(split));
+}
+
+const char *
+xaccSplitGetOnlineID (const Split *split)
+{
+ if (!split) return nullptr;
+ auto rv{qof_instance_get_path_kvp<const char*> (QOF_INSTANCE(split), {split_online_id})};
+ return rv ? *rv : nullptr;
+}
+
+gboolean
+xaccSplitHasOnlineID (const Split *split)
+{
+ auto id = xaccSplitGetOnlineID (split);
+ return (id && *id);
+}
+
const char *
xaccSplitGetAction (const Split *split)
{
diff --git a/libgnucash/engine/Split.h b/libgnucash/engine/Split.h
index 14f1457c51..26fd6b01c3 100644
--- a/libgnucash/engine/Split.h
+++ b/libgnucash/engine/Split.h
@@ -160,6 +160,25 @@ void xaccSplitSetMemo (Split *split, const char *memo);
/** Returns the memo string. */
const char * xaccSplitGetMemo (const Split *split);
+/** The online_id is the OFX/HBCI "FITID" recorded on a split when it is
+ * imported. It is used to recognise the split on re-import so that
+ * overlapping or re-downloaded statements don't create duplicates. This
+ * is the same value (engine KVP slot "online_id") that the desktop
+ * OFX/HBCI importer reads and writes.
+ *
+ * The setter only changes engine data: call it inside the parent
+ * transaction's edit (xaccTransBeginEdit()/xaccTransCommitEdit()), and a
+ * session save persists it. Passing NULL or "" clears the online_id. */
+void xaccSplitSetOnlineID (Split *split, const char *id);
+
+/** Returns the split's online_id. The returned string is owned by the
+ * split and must NOT be freed; it is valid until the online_id is changed
+ * or the split is destroyed. Returns NULL if no online_id is set. */
+const char * xaccSplitGetOnlineID (const Split *split);
+
+/** Returns TRUE if the split has a non-empty online_id. */
+gboolean xaccSplitHasOnlineID (const Split *split);
+
/** The Action is an arbitrary user-assigned string.
* The action field is an arbitrary user-assigned value.
* It is meant to be a very short (one to ten character) string that
diff --git a/libgnucash/engine/mocks/gmock-Split.cpp b/libgnucash/engine/mocks/gmock-Split.cpp
index 523d99a6d9..f80d929f5b 100644
--- a/libgnucash/engine/mocks/gmock-Split.cpp
+++ b/libgnucash/engine/mocks/gmock-Split.cpp
@@ -110,6 +110,21 @@ xaccSplitSetMemo (Split *split, const char *memo)
gnc_mocksplit(split)->set_memo(memo);
}
+const char *
+xaccSplitGetOnlineID (const Split *split)
+{
+ SCOPED_TRACE("");
+ auto mocksplit = gnc_mocksplit(split);
+ return mocksplit ? mocksplit->get_online_id() : nullptr;
+}
+
+void
+xaccSplitSetOnlineID (Split *split, const char *id)
+{
+ ASSERT_TRUE(GNC_IS_MOCKSPLIT(split));
+ gnc_mocksplit(split)->set_online_id(id);
+}
+
char
xaccSplitGetReconcile (const Split *split)
{
diff --git a/libgnucash/engine/mocks/gmock-Split.hpp b/libgnucash/engine/mocks/gmock-Split.hpp
index b2667d3381..a156ff9c8e 100644
--- a/libgnucash/engine/mocks/gmock-Split.hpp
+++ b/libgnucash/engine/mocks/gmock-Split.hpp
@@ -73,6 +73,8 @@ public:
MOCK_METHOD1(set_value, void(gnc_numeric));
MOCK_CONST_METHOD0(get_memo, const char *());
MOCK_METHOD1(set_memo, void(const char *));
+ MOCK_CONST_METHOD0(get_online_id, const char *());
+ MOCK_METHOD1(set_online_id, void(const char *));
MOCK_CONST_METHOD0(get_reconcile, char());
MOCK_METHOD1(set_reconcile, void(char));
MOCK_METHOD1(set_date_reconciled_secs, void(time64));
diff --git a/libgnucash/engine/test/test-engine-kvp-properties.c b/libgnucash/engine/test/test-engine-kvp-properties.c
index f9a6577eea..2db9e95299 100644
--- a/libgnucash/engine/test/test-engine-kvp-properties.c
+++ b/libgnucash/engine/test/test-engine-kvp-properties.c
@@ -442,11 +442,67 @@ test_vendor_kvp_properties (Fixture *fixture, gconstpointer pData)
}
+static void
+test_split_online_id_accessors (Fixture *fixture, gconstpointer pData)
+{
+ Split *split = fixture->split;
+ const char *fitid = "20240131-0001-1234567890";
+ gchar *raw = NULL;
+
+ /* Initially unset. */
+ g_assert_true (xaccSplitGetOnlineID (split) == NULL);
+ g_assert_true (!xaccSplitHasOnlineID (split));
+
+ /* Round-trip via the new accessors. */
+ xaccSplitSetOnlineID (split, fitid);
+ g_assert_cmpstr (xaccSplitGetOnlineID (split), ==, fitid);
+ g_assert_true (xaccSplitHasOnlineID (split));
+
+ /* GUI parity: the value is stored in the engine KVP slot "online_id",
+ * readable via the "online-id" GObject property used by the importer. */
+ qof_instance_get (QOF_INSTANCE (split), "online-id", &raw, NULL);
+ g_assert_cmpstr (raw, ==, fitid);
+ g_free (raw);
+
+ /* "" clears it. */
+ xaccSplitSetOnlineID (split, "");
+ g_assert_true (xaccSplitGetOnlineID (split) == NULL);
+ g_assert_true (!xaccSplitHasOnlineID (split));
+
+ /* NULL clears it too. */
+ xaccSplitSetOnlineID (split, fitid);
+ xaccSplitSetOnlineID (split, NULL);
+ g_assert_true (xaccSplitGetOnlineID (split) == NULL);
+}
+
+static void
+test_account_online_id_accessors (Fixture *fixture, gconstpointer pData)
+{
+ Account *acct = fixture->acct;
+ const char *acct_online_id = "061000104:0123456789:CHECKING";
+ gchar *raw = NULL;
+
+ g_assert_true (xaccAccountGetOnlineID (acct) == NULL);
+
+ xaccAccountSetOnlineID (acct, acct_online_id);
+ g_assert_cmpstr (xaccAccountGetOnlineID (acct), ==, acct_online_id);
+
+ /* GUI parity: stored in the "online_id" KVP slot. */
+ qof_instance_get (QOF_INSTANCE (acct), "online-id", &raw, NULL);
+ g_assert_cmpstr (raw, ==, acct_online_id);
+ g_free (raw);
+
+ xaccAccountSetOnlineID (acct, NULL);
+ g_assert_true (xaccAccountGetOnlineID (acct) == NULL);
+}
+
void test_suite_engine_kvp_properties (void)
{
GNC_TEST_ADD (suitename, "Account", Fixture, NULL, setup_account, test_account_kvp_properties, teardown);
GNC_TEST_ADD (suitename, "Transaction", Fixture, NULL, setup_trans, test_trans_kvp_properties, teardown);
GNC_TEST_ADD (suitename, "Split", Fixture, NULL, setup_split, test_split_kvp_properties, teardown);
+ GNC_TEST_ADD (suitename, "Split online_id accessors", Fixture, NULL, setup_split, test_split_online_id_accessors, teardown);
+ GNC_TEST_ADD (suitename, "Account online_id accessors", Fixture, NULL, setup_account, test_account_online_id_accessors, teardown);
GNC_TEST_ADD (suitename, "Lot", Fixture, NULL, setup_lot, test_lot_kvp_properties, teardown);
GNC_TEST_ADD (suitename, "Customer", Fixture, NULL, setup_customer, test_customer_kvp_properties, teardown);
GNC_TEST_ADD (suitename, "Employee", Fixture, NULL, setup_employee, test_employee_kvp_properties, teardown);
commit 222714d764e36ea030db0a8791af608f0e0ef0bd
Author: Noah R <Noerr at users.noreply.github.com>
Date: Sun Jun 21 16:35:56 2026 -0700
[engine] Remove the unused Transaction online_id property
The "online-id" GObject property on Transaction (KVP slot "online_id")
was vestigial: GnuCash records the OFX/HBCI import identifier on the
bank/asset Split, not on the Transaction. Drop the property, its
get/set_property cases, the xaccTransClone special-case that cleared it,
and the corresponding test_trans_kvp_properties coverage.
diff --git a/libgnucash/engine/Transaction.cpp b/libgnucash/engine/Transaction.cpp
index c169700b55..67c1bbcf89 100644
--- a/libgnucash/engine/Transaction.cpp
+++ b/libgnucash/engine/Transaction.cpp
@@ -200,7 +200,6 @@ enum
PROP_DESCRIPTION, /* Table */
PROP_INVOICE, /* KVP */
PROP_SX_TXN, /* KVP */
- PROP_ONLINE_ACCOUNT,/* KVP */
};
void
@@ -331,9 +330,6 @@ gnc_transaction_get_property(GObject* object,
case PROP_SX_TXN:
qof_instance_get_kvp (QOF_INSTANCE (tx), value, 1, GNC_SX_FROM);
break;
- case PROP_ONLINE_ACCOUNT:
- qof_instance_get_kvp (QOF_INSTANCE (tx), value, 1, "online_id");
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -379,9 +375,6 @@ gnc_transaction_set_property(GObject* object,
case PROP_SX_TXN:
qof_instance_set_kvp (QOF_INSTANCE (tx), value, 1, GNC_SX_FROM);
break;
- case PROP_ONLINE_ACCOUNT:
- qof_instance_set_kvp (QOF_INSTANCE (tx), value, 1, "online_id");
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
@@ -470,16 +463,6 @@ gnc_transaction_class_init(TransactionClass* klass)
"transactions",
GNC_TYPE_GUID,
G_PARAM_READWRITE));
-
- g_object_class_install_property
- (gobject_class,
- PROP_ONLINE_ACCOUNT,
- g_param_spec_string ("online-id",
- "Online Account ID",
- "The online account which corresponds to this "
- "account for OFX/HCBI import",
- nullptr,
- G_PARAM_READWRITE));
}
/********************************************************************\
@@ -678,9 +661,6 @@ xaccTransClone (const Transaction *from)
xaccTransBeginEdit (to);
qof_instance_copy_kvp (QOF_INSTANCE (to), QOF_INSTANCE (from));
- /* But not the online-id! */
- qof_instance_set (QOF_INSTANCE (to), "online-id", nullptr, nullptr);
-
for (GList* lfrom = from->splits, *lto = to->splits; lfrom && lto;
lfrom = g_list_next (lfrom), lto = g_list_next (lto))
xaccSplitCopyKvp (GNC_SPLIT(lfrom->data), GNC_SPLIT(lto->data));
diff --git a/libgnucash/engine/Transaction.h b/libgnucash/engine/Transaction.h
index 3f6f1fdef2..937dadc629 100644
--- a/libgnucash/engine/Transaction.h
+++ b/libgnucash/engine/Transaction.h
@@ -352,7 +352,6 @@ const char * xaccTransGetDocLink(const Transaction *trans);
The Notes field is only visible in the register in double-line mode */
const char * xaccTransGetNotes (const Transaction *trans);
-
/** Sets whether or not this transaction is a "closing transaction" */
void xaccTransSetIsClosingTxn (Transaction *trans, gboolean is_closing);
diff --git a/libgnucash/engine/test/test-engine-kvp-properties.c b/libgnucash/engine/test/test-engine-kvp-properties.c
index 1a9f2fb107..f9a6577eea 100644
--- a/libgnucash/engine/test/test-engine-kvp-properties.c
+++ b/libgnucash/engine/test/test-engine-kvp-properties.c
@@ -189,14 +189,11 @@ test_trans_kvp_properties (Fixture *fixture, gconstpointer pData)
GncGUID *invoice = guid_new ();
GncGUID *from_sx = guid_new ();
GncGUID *invoice_r, *from_sx_r;
- gchar *online_id = "my online id";
- gchar *online_id_r;
xaccTransBeginEdit (fixture->trans);
qof_instance_set (QOF_INSTANCE (fixture->trans),
"invoice", invoice,
"from-sched-xaction", from_sx,
- "online-id", online_id,
NULL);
g_assert_true (qof_instance_is_dirty (QOF_INSTANCE (fixture->trans)));
@@ -205,17 +202,14 @@ test_trans_kvp_properties (Fixture *fixture, gconstpointer pData)
qof_instance_get (QOF_INSTANCE (fixture->trans),
"invoice", &invoice_r,
"from-sched-xaction", &from_sx_r,
- "online-id", &online_id_r,
NULL);
g_assert_true (guid_equal (invoice, invoice_r));
g_assert_true (guid_equal (from_sx, from_sx_r));
- g_assert_cmpstr (online_id, ==, online_id_r);
g_assert_true (!qof_instance_is_dirty (QOF_INSTANCE (fixture->trans)));
guid_free (invoice);
guid_free (invoice_r);
guid_free (from_sx);
guid_free (from_sx_r);
- g_free (online_id_r);
}
static void
Summary of changes:
bindings/python/tests/test_account.py | 9 ++
bindings/python/tests/test_split.py | 13 +++
gnucash/import-export/CMakeLists.txt | 1 -
gnucash/import-export/aqb/assistant-ab-initial.c | 8 +-
gnucash/import-export/aqb/gnc-ab-utils.c | 3 +-
gnucash/import-export/import-account-matcher.cpp | 18 +---
gnucash/import-export/import-backend.cpp | 13 +--
gnucash/import-export/import-main-matcher.cpp | 2 +-
gnucash/import-export/import-utilities.cpp | 116 ---------------------
gnucash/import-export/import-utilities.h | 47 +--------
gnucash/import-export/ofx/gnc-ofx-import.cpp | 12 +--
gnucash/import-export/test/CMakeLists.txt | 1 -
libgnucash/engine/Account.cpp | 14 +++
libgnucash/engine/Account.h | 13 +++
libgnucash/engine/Split.cpp | 30 ++++++
libgnucash/engine/Split.h | 19 ++++
libgnucash/engine/Transaction.cpp | 20 ----
libgnucash/engine/Transaction.h | 1 -
libgnucash/engine/mocks/gmock-Split.cpp | 15 +++
libgnucash/engine/mocks/gmock-Split.hpp | 2 +
.../engine/test/test-engine-kvp-properties.c | 62 +++++++++--
21 files changed, 191 insertions(+), 228 deletions(-)
delete mode 100644 gnucash/import-export/import-utilities.cpp
More information about the gnucash-changes
mailing list