mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Account matching: Test for full and partial online-id matches.
An OFX file import use-case includes a master account with sub accounts
having additional qualifiers appended. 7853f5a2
broke that because the
trailing characters aren't noise.
Instead return only perfect matches (less a trailing space if present,
replacing a second traversal) and cache partial matches and continue the
search. If we find a longer partial match replace the shorter one. If we
find a second account with the same online-id raise an error. If at the
end there's a unambiguous partial match but no full match then it's
either trailing noise from AQBanking or it's a new account. We use the
passed-in new_account_default_type to decide which: If it's
ACCT_TYPE_NONE then we use the partially-matched account, otherwise we
continue on to handle the remaining arguments.
This commit is contained in:
parent
d7ccea592a
commit
0d5bfd79a6
@ -50,6 +50,23 @@ static QofLogModule log_module = GNC_MOD_IMPORT;
|
||||
|
||||
#define GNC_PREFS_GROUP "dialogs.import.generic.account-picker"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Account* partial_match;
|
||||
int count;
|
||||
const char* online_id;
|
||||
} AccountOnlineMatch;
|
||||
|
||||
static Account*
|
||||
partial_match_if_valid (AccountOnlineMatch *match)
|
||||
{
|
||||
if (match->partial_match && match->count == 1)
|
||||
return match->partial_match;
|
||||
else
|
||||
PERR("Online ID %s partially matches %d accounts and fully matches none",
|
||||
match->online_id, match->count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*-******************************************************************\
|
||||
* Functions needed by gnc_import_select_account
|
||||
@ -81,19 +98,66 @@ static AccountPickerDialog* gnc_import_new_account_picker(void)
|
||||
*
|
||||
* test for match of account online_ids.
|
||||
**************************************************/
|
||||
static gpointer test_acct_online_id_match(Account *acct, gpointer param_online_id)
|
||||
static gpointer test_acct_online_id_match(Account *acct, gpointer data)
|
||||
{
|
||||
const gchar * current_online_id = gnc_import_get_acc_online_id(acct);
|
||||
if ((current_online_id != NULL && param_online_id != NULL)
|
||||
&& strncmp (current_online_id, param_online_id,
|
||||
strlen (current_online_id)) == 0)
|
||||
{
|
||||
return (gpointer *) acct;
|
||||
}
|
||||
else
|
||||
{
|
||||
AccountOnlineMatch *match = (AccountOnlineMatch*)data;
|
||||
const char *acct_online_id = gnc_import_get_acc_online_id(acct);
|
||||
int acct_len, match_len;
|
||||
|
||||
if (acct_online_id == NULL || match->online_id == NULL)
|
||||
return NULL;
|
||||
|
||||
acct_len = strlen(acct_online_id);
|
||||
match_len = strlen(match->online_id);
|
||||
|
||||
if (acct_online_id[acct_len - 1] == ' ')
|
||||
--acct_len;
|
||||
if (match->online_id[match_len - 1] == ' ')
|
||||
--match_len;
|
||||
|
||||
if (strncmp (acct_online_id, match->online_id, acct_len) == 0)
|
||||
{
|
||||
if (strncmp(acct_online_id, match->online_id, match_len) == 0)
|
||||
return (gpointer *) acct;
|
||||
if (match->partial_match == NULL)
|
||||
{
|
||||
match->partial_match = acct;
|
||||
++match->count;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *partial_online_id =
|
||||
gnc_import_get_acc_online_id(match->partial_match);
|
||||
int partial_len = strlen(partial_online_id);
|
||||
if (partial_online_id[partial_len - 1] == ' ')
|
||||
--partial_len;
|
||||
/* Both partial_online_id and acct_online_id are substrings of
|
||||
* match->online_id, but whichever is longer is the better match.
|
||||
* Reset match->count to 1 just in case there was ambiguity on the
|
||||
* shorter partial match.
|
||||
*/
|
||||
if (partial_len < acct_len)
|
||||
{
|
||||
match->partial_match = acct;
|
||||
match->count = 1;
|
||||
}
|
||||
/* If they're the same size then there are two accounts with the
|
||||
* same online id and we don't know which one to select. Increment
|
||||
* match->count to dissuade gnc_import_find_account from using
|
||||
* match->online_id and log an error.
|
||||
*/
|
||||
else if (partial_len == acct_len)
|
||||
{
|
||||
++match->count;
|
||||
PERR("Accounts %s and %s have the same online-id %s",
|
||||
gnc_account_get_full_name(match->partial_match),
|
||||
gnc_account_get_full_name(acct),
|
||||
partial_online_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@ -266,39 +330,14 @@ Account * gnc_import_select_account(GtkWidget *parent,
|
||||
/*DEBUG("Looking for account with online_id: \"%s\"", account_online_id_value);*/
|
||||
if (account_online_id_value != NULL)
|
||||
{
|
||||
AccountOnlineMatch match = {NULL, 0, account_online_id_value};
|
||||
retval =
|
||||
gnc_account_foreach_descendant_until(gnc_get_current_root_account (),
|
||||
test_acct_online_id_match,
|
||||
/* This argument will only be used as a "const char*" */
|
||||
(void*)account_online_id_value);
|
||||
|
||||
/* BEGIN: try again without extra space at the end */
|
||||
/*
|
||||
* libofx, used for file import, generates online_id as
|
||||
* ACCTID + space + ACCTKEY which differs from the online_id
|
||||
* generated by aqbanking for online ofx transfer as ACCTID.
|
||||
*
|
||||
* If a gnucash account has been associated with an online_id
|
||||
* via aqbanking data, it is not possible to construct an OFX
|
||||
* file for gnucash import that matches the same online_id
|
||||
* because even with no ACCTKEY in the file, there will be a
|
||||
* trailing space.
|
||||
*
|
||||
* This is a hack to overcome that problem.
|
||||
*/
|
||||
if ((retval == NULL) && g_str_has_suffix(account_online_id_value, " "))
|
||||
{
|
||||
gchar *trimmed = g_strndup(account_online_id_value, strlen(account_online_id_value) - 1);
|
||||
if (trimmed)
|
||||
{
|
||||
retval = gnc_account_foreach_descendant_until(
|
||||
gnc_get_current_root_account (),
|
||||
test_acct_online_id_match,
|
||||
(void *)trimmed);
|
||||
}
|
||||
g_free(trimmed);
|
||||
}
|
||||
/* END: try again without extra space at the end */
|
||||
test_acct_online_id_match,
|
||||
(void*)&match);
|
||||
if (!retval && match.count == 1 &&
|
||||
new_account_default_type == ACCT_TYPE_NONE)
|
||||
retval = match.partial_match;
|
||||
}
|
||||
if (retval == NULL && auto_create != 0)
|
||||
{
|
||||
|
@ -76,7 +76,7 @@ protected:
|
||||
create_account(stocks, ACCT_TYPE_STOCK, "HPE", "BrokerStocksHPE");
|
||||
create_account(broker, ACCT_TYPE_BANK, "Cash Management",
|
||||
"BrokerCash Management");
|
||||
create_account(expenses, ACCT_TYPE_EXPENSE, "Food", nullptr);
|
||||
create_account(expenses, ACCT_TYPE_EXPENSE, "Food", nullptr);
|
||||
create_account(expenses, ACCT_TYPE_EXPENSE, "Gas", nullptr);
|
||||
create_account(expenses, ACCT_TYPE_EXPENSE, "Rent", nullptr);
|
||||
}
|
||||
@ -115,8 +115,7 @@ TEST_F(ImportMatcherTest, test_match_with_subaccounts)
|
||||
nullptr, nullptr, ACCT_TYPE_NONE,
|
||||
nullptr, nullptr);
|
||||
ASSERT_NE(nullptr, found);
|
||||
// Should be equal
|
||||
EXPECT_STRNE("Stocks", xaccAccountGetName(found));
|
||||
EXPECT_STREQ("Stocks", xaccAccountGetName(found));
|
||||
}
|
||||
|
||||
TEST_F(ImportMatcherTest, test_subaccount_match)
|
||||
@ -125,8 +124,7 @@ TEST_F(ImportMatcherTest, test_subaccount_match)
|
||||
nullptr, nullptr, ACCT_TYPE_NONE,
|
||||
nullptr, nullptr);
|
||||
ASSERT_NE(nullptr, found);
|
||||
//should be equal
|
||||
EXPECT_STRNE("HPE", xaccAccountGetName(found));
|
||||
EXPECT_STREQ("HPE", xaccAccountGetName(found));
|
||||
}
|
||||
|
||||
TEST_F(ImportMatcherTest, test_subaccount_match_trailing_noise)
|
||||
@ -134,21 +132,16 @@ TEST_F(ImportMatcherTest, test_subaccount_match_trailing_noise)
|
||||
auto found = gnc_import_select_account(nullptr, "BrokerStocksHPEUSD", FALSE,
|
||||
nullptr, nullptr, ACCT_TYPE_NONE,
|
||||
nullptr, nullptr);
|
||||
// Should be equal
|
||||
EXPECT_STRNE("HPE", xaccAccountGetName(found));
|
||||
ASSERT_NE(nullptr, found);
|
||||
EXPECT_STREQ("HPE", xaccAccountGetName(found));
|
||||
}
|
||||
|
||||
TEST_F(ImportMatcherTest, test_subaccount_no_match)
|
||||
{
|
||||
auto found = gnc_import_select_account(nullptr, "BrokerStocksINTC", FALSE,
|
||||
nullptr, nullptr, ACCT_TYPE_NONE,
|
||||
nullptr, nullptr, ACCT_TYPE_STOCK,
|
||||
nullptr, nullptr);
|
||||
/* We really want nullptr in this case, but the algorithm can't tell the
|
||||
* difference between trailing noise and a new security that needs a new
|
||||
* account.
|
||||
*/
|
||||
ASSERT_NE(nullptr, found);
|
||||
EXPECT_STREQ("Stocks", xaccAccountGetName(found));
|
||||
ASSERT_EQ(nullptr, found);
|
||||
}
|
||||
|
||||
TEST_F(ImportMatcherTest, test_subaccount_match_trailing_space)
|
||||
@ -157,8 +150,7 @@ TEST_F(ImportMatcherTest, test_subaccount_match_trailing_space)
|
||||
nullptr, nullptr, ACCT_TYPE_NONE,
|
||||
nullptr, nullptr);
|
||||
ASSERT_NE(nullptr, found);
|
||||
//Should be equal
|
||||
EXPECT_STRNE("MSFT", xaccAccountGetName(found));
|
||||
EXPECT_STREQ("MSFT", xaccAccountGetName(found));
|
||||
}
|
||||
|
||||
TEST_F(ImportMatcherTest, test_subaccount_match_trim_trailing_space)
|
||||
@ -167,8 +159,7 @@ TEST_F(ImportMatcherTest, test_subaccount_match_trim_trailing_space)
|
||||
nullptr, nullptr, ACCT_TYPE_NONE,
|
||||
nullptr, nullptr);
|
||||
ASSERT_NE(nullptr, found);
|
||||
//Should be equal
|
||||
EXPECT_STRNE("MSFT", xaccAccountGetName(found));
|
||||
EXPECT_STREQ("MSFT", xaccAccountGetName(found));
|
||||
}
|
||||
|
||||
TEST_F(ImportMatcherTest, test_subaccount_match_internal_space)
|
||||
@ -177,6 +168,5 @@ TEST_F(ImportMatcherTest, test_subaccount_match_internal_space)
|
||||
FALSE, nullptr, nullptr,
|
||||
ACCT_TYPE_NONE, nullptr, nullptr);
|
||||
ASSERT_NE(nullptr, found);
|
||||
// Should be equal, of course
|
||||
EXPECT_STRNE("Cash Management", xaccAccountGetName(found));
|
||||
EXPECT_STREQ("Cash Management", xaccAccountGetName(found));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user