diff --git a/gnucash/gnome/dialog-invoice.c b/gnucash/gnome/dialog-invoice.c index d928f0e914..a04cec31b7 100644 --- a/gnucash/gnome/dialog-invoice.c +++ b/gnucash/gnome/dialog-invoice.c @@ -3391,7 +3391,7 @@ gnc_invoice_show_docs_due (GtkWindow *parent, QofBook *book, double days_in_adva param_list = gnc_search_param_prepend (param_list, _("Amount"), NULL, type, INVOICE_POST_LOT, LOT_BALANCE, NULL); param_list = gnc_search_param_prepend (param_list, _("Company"), NULL, type, - INVOICE_OWNER, OWNER_NAME, NULL); + INVOICE_OWNER, OWNER_PARENT, OWNER_NAME, NULL); param_list = gnc_search_param_prepend (param_list, _("Due"), NULL, type, INVOICE_DUE, NULL); } diff --git a/gnucash/import-export/import-backend.c b/gnucash/import-export/import-backend.c index ebc37c3a7b..888b913b2f 100644 --- a/gnucash/import-export/import-backend.c +++ b/gnucash/import-export/import-backend.c @@ -387,8 +387,24 @@ tokenize_string(GList* existing_tokens, const char *string) /* add each token to the token GList */ while (stringpos && *stringpos) { - /* prepend the char* to the token GList */ - existing_tokens = g_list_prepend(existing_tokens, g_strdup(*stringpos)); + if (strlen(*stringpos) > 0) + { + /* check for duplicated tokens */ + gboolean duplicated = FALSE; + for (GList* token = existing_tokens; token != NULL; token = token->next) + { + if (g_strcmp0(token->data, *stringpos) == 0) + { + duplicated = TRUE; + break; + } + } + if (duplicated == FALSE) + { + /* prepend the char* to the token GList */ + existing_tokens = g_list_prepend(existing_tokens, g_strdup(*stringpos)); + } + } /* then move to the next string */ stringpos++; diff --git a/gnucash/import-export/qif-imp/qif-guess-map.scm b/gnucash/import-export/qif-imp/qif-guess-map.scm index 067013dee0..da8ff87dfb 100644 --- a/gnucash/import-export/qif-imp/qif-guess-map.scm +++ b/gnucash/import-export/qif-imp/qif-guess-map.scm @@ -25,6 +25,7 @@ (use-modules (srfi srfi-13)) +(use-modules (ice-9 match)) (define GNC-BANK-TYPE 0) (define GNC-CASH-TYPE 1) @@ -227,24 +228,21 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (qif-import:read-securities security-list) - (let ((table (make-hash-table 20))) + (let ((table (make-hash-table)) + (comm-table (gnc-commodity-table-get-table (gnc-get-current-book)))) (for-each - (lambda (entry) - (if (and (list? entry) - (= 3 (length entry))) - ;; The saved information about each security mapping is a - ;; list of three items: the QIF name, and the GnuCash - ;; namespace and mnemonic (symbol) to which it maps. - ;; Example: ("McDonald's" "NYSE" "MCD") - (let ((commodity (gnc-commodity-table-lookup - (gnc-commodity-table-get-table - (gnc-get-current-book)) - (cadr entry) - (caddr entry)))) - (if (and commodity (not (null? commodity))) - ;; There is an existing GnuCash commodity for this - ;; combination of namespace and symbol. - (hash-set! table (car entry) commodity))))) + (match-lambda + ((name ns mnemonic) + ;; The saved information about each security mapping is a + ;; list of three items: the QIF name, and the GnuCash + ;; namespace and mnemonic (symbol) to which it maps. + ;; Example: ("McDonald's" "NYSE" "MCD") + (let ((commodity (gnc-commodity-table-lookup comm-table ns mnemonic))) + (if (and commodity (not (null? commodity))) + ;; There is an existing GnuCash commodity for this + ;; combination of namespace and symbol. + (hash-set! table name commodity)))) + (_ #f)) security-list) table)) @@ -462,18 +460,11 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (qif-import:find-new-acct qif-acct allowed-types gnc-acct-info) - (cond ((and (string? qif-acct) - (string=? qif-acct (default-equity-account))) - (let ((existing-equity - (qif-import:find-similar-acct (default-equity-account) - (list GNC-EQUITY-TYPE) - gnc-acct-info))) - (if existing-equity - existing-equity - (list (default-equity-account) (list GNC-EQUITY-TYPE))))) - ((and (string? qif-acct) - (not (string=? qif-acct ""))) - (list qif-acct allowed-types)) - (#t - (list (default-unspec-acct) allowed-types)))) + (cond + ((equal? qif-acct (default-equity-account)) + (or (qif-import:find-similar-acct + (default-equity-account) (list GNC-EQUITY-TYPE) gnc-acct-info) + (list (default-equity-account) (list GNC-EQUITY-TYPE)))) + ((equal? qif-acct "") (list (default-unspec-acct) allowed-types)) + (else (list qif-acct allowed-types)))) diff --git a/gnucash/import-export/qif-imp/qif-merge-groups.scm b/gnucash/import-export/qif-imp/qif-merge-groups.scm index e576f48a94..c8bdd12159 100644 --- a/gnucash/import-export/qif-imp/qif-merge-groups.scm +++ b/gnucash/import-export/qif-imp/qif-merge-groups.scm @@ -24,21 +24,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; gnc:account-tree-get-transactions -;; -;; Given an account tree, this procedure returns a list of all -;; transactions whose splits only use accounts in the tree. -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(define (gnc:account-tree-get-transactions root) - (let ((accounts (gnc-account-get-descendants-sorted root))) - (let ((q (qof-query-create-for-splits))) - (qof-query-set-book q (gnc-account-get-book root)) - (xaccQueryAddAccountMatch q accounts QOF-GUID-MATCH-ANY QOF-QUERY-AND) - (let ((xtns (xaccQueryGetTransactions q QUERY-TXN-MATCH-ALL))) - (qof-query-destroy q) - xtns)))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; gnc:account-tree-find-duplicates ;; diff --git a/gnucash/import-export/qif-imp/qif-to-gnc.scm b/gnucash/import-export/qif-imp/qif-to-gnc.scm index 03c8419850..7e1d6a586b 100644 --- a/gnucash/import-export/qif-imp/qif-to-gnc.scm +++ b/gnucash/import-export/qif-imp/qif-to-gnc.scm @@ -26,8 +26,15 @@ (use-modules (srfi srfi-13)) +(use-modules (ice-9 match)) (use-modules (gnucash string)) +(define (n- n) (gnc-numeric-neg n)) +(define (nsub a b) (gnc-numeric-sub a b 0 GNC-DENOM-LCD)) +(define (n+ a b) (gnc-numeric-add a b 0 GNC-DENOM-LCD)) +(define (n* a b) (gnc-numeric-mul a b 0 GNC-DENOM-REDUCE)) +(define (n/ a b) (gnc-numeric-div a b 0 GNC-DENOM-REDUCE)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; qif-import:find-or-make-acct ;; @@ -464,12 +471,7 @@ (qif-memo #f) (qif-date (qif-xtn:date qif-xtn)) (qif-from-acct (qif-xtn:from-acct qif-xtn)) - (qif-cleared (qif-xtn:cleared qif-xtn)) - (n- (lambda (n) (gnc-numeric-neg n))) - (nsub (lambda (a b) (gnc-numeric-sub a b 0 GNC-DENOM-LCD))) - (n+ (lambda (a b) (gnc-numeric-add a b 0 GNC-DENOM-LCD))) - (n* (lambda (a b) (gnc-numeric-mul a b 0 GNC-DENOM-REDUCE))) - (n/ (lambda (a b) (gnc-numeric-div a b 0 GNC-DENOM-REDUCE)))) + (qif-cleared (qif-xtn:cleared qif-xtn))) ;; Set properties of the whole transaction. @@ -513,12 +515,10 @@ ;; Look for the transaction status (QIF "C" line). When it exists, apply ;; the cleared (c) or reconciled (y) status to the split. Otherwise, apply ;; user preference. - (if (eq? qif-cleared 'cleared) - (xaccSplitSetReconcile gnc-near-split #\c) - (if (eq? qif-cleared 'reconciled) - (xaccSplitSetReconcile gnc-near-split #\y) - ;; Apply user preference by default. - (xaccSplitSetReconcile gnc-near-split transaction-status-pref))) + (case qif-cleared + ((cleared) (xaccSplitSetReconcile gnc-near-split #\c)) + ((reconciled) (xaccSplitSetReconcile gnc-near-split #\y)) + (else (xaccSplitSetReconcile gnc-near-split transaction-status-pref))) (if (not qif-security) (begin @@ -585,21 +585,17 @@ (hash-ref qif-memo-map qif-memo))) (and (string? memo) (not (string=? memo "")) - (hash-ref qif-memo-map memo)))) - (if (not far-acct-info) - (set! far-acct-info - (hash-ref qif-memo-map - (default-unspec-acct)))))) + (hash-ref qif-memo-map memo)))))) - (set! far-acct-name (qif-map-entry:gnc-name far-acct-info)) + (set! far-acct-name (if far-acct-info + (qif-map-entry:gnc-name far-acct-info) + (default-unspec-acct))) (set! far-acct (hash-ref gnc-acct-hash far-acct-name)) ;; set the reconcile status. - (let ((cleared (qif-split:matching-cleared qif-split))) - (if (eq? 'cleared cleared) - (xaccSplitSetReconcile gnc-far-split #\c)) - (if (eq? 'reconciled cleared) - (xaccSplitSetReconcile gnc-far-split #\y))) + (case (qif-split:matching-cleared qif-split) + ((cleared) (xaccSplitSetReconcile gnc-far-split #\c)) + ((reconciled) (xaccSplitSetReconcile gnc-far-split #\y))) ;; finally, plug the split into the account (xaccSplitSetAccount gnc-far-split far-acct) @@ -762,12 +758,9 @@ (xaccSplitSetValue gnc-near-split (n- split-amt)) (xaccSplitSetValue gnc-far-split split-amt)))) - (let ((cleared (qif-split:matching-cleared - (car (qif-xtn:splits qif-xtn))))) - (if (eq? 'cleared cleared) - (xaccSplitSetReconcile gnc-far-split #\c)) - (if (eq? 'reconciled cleared) - (xaccSplitSetReconcile gnc-far-split #\y))) + (case (qif-split:matching-cleared (car (qif-xtn:splits qif-xtn))) + ((cleared) (xaccSplitSetReconcile gnc-far-split #\c)) + ((reconciled) (xaccSplitSetReconcile gnc-far-split #\y))) (if qif-commission-acct (let* ((commission-acct-info @@ -842,12 +835,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (qif-import:mark-some-splits splits xtn candidate-xtns errorproc) - (let* ((n- (lambda (n) (gnc-numeric-neg n))) - (nsub (lambda (a b) (gnc-numeric-sub a b 0 GNC-DENOM-LCD))) - (n+ (lambda (a b) (gnc-numeric-add a b 0 GNC-DENOM-LCD))) - (n* (lambda (a b) (gnc-numeric-mul a b 0 GNC-DENOM-REDUCE))) - (n/ (lambda (a b) (gnc-numeric-div a b 0 GNC-DENOM-REDUCE))) - (split (car splits)) + (let* ((split (car splits)) (near-acct-name #f) (far-acct-name #f) (date (qif-xtn:date xtn)) @@ -957,19 +945,9 @@ (this-group-amt (gnc-numeric-zero)) (how #f) (date-matches - (let ((self-date (qif-xtn:date xtn))) - (and (pair? self-date) - (pair? date) - (eq? (length self-date) 3) - (eq? (length date) 3) - (= (car self-date) (car date)) - (= (cadr self-date) (cadr date)) - (= (caddr self-date) (caddr date))))) - (n- (lambda (n) (gnc-numeric-neg n))) - (nsub (lambda (a b) (gnc-numeric-sub a b 0 GNC-DENOM-LCD))) - (n+ (lambda (a b) (gnc-numeric-add a b 0 GNC-DENOM-LCD))) - (n* (lambda (a b) (gnc-numeric-mul a b 0 GNC-DENOM-REDUCE))) - (n/ (lambda (a b) (gnc-numeric-div a b 0 GNC-DENOM-REDUCE)))) + (match (cons date (qif-xtn:date xtn)) + (((a b c) . (a b c)) #t) + (_ #f)))) (if date-matches (begin @@ -1230,6 +1208,20 @@ (if all-marked (qif-xtn:set-mark! xtn #t)))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; gnc:account-tree-get-transactions +;; +;; Given an account tree, this procedure returns a list of all +;; transactions whose splits only use accounts in the tree. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(define (gnc:account-tree-get-transactions root) + (let ((accounts (gnc-account-get-descendants-sorted root))) + (let ((q (qof-query-create-for-splits))) + (qof-query-set-book q (gnc-account-get-book root)) + (xaccQueryAddAccountMatch q accounts QOF-GUID-MATCH-ANY QOF-QUERY-AND) + (let ((xtns (xaccQueryGetTransactions q QUERY-TXN-MATCH-ALL))) + (qof-query-destroy q) + xtns)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; qif-import:qif-to-gnc-undo diff --git a/gnucash/report/trep-engine.scm b/gnucash/report/trep-engine.scm index 1708de8449..5b0423a816 100644 --- a/gnucash/report/trep-engine.scm +++ b/gnucash/report/trep-engine.scm @@ -1401,6 +1401,11 @@ be excluded from periodic reporting.") (case level ((primary) optname-prime-sortkey) ((secondary) optname-sec-sortkey)))) + (data (if (and (any (lambda (c) (eq? 'bal-bf (vector-ref c 5))) + calculated-cells) + (memq sortkey ACCOUNT-SORTING-TYPES)) + (string-append data ": " (_ "Balance b/f")) + data)) (renderer-fn (keylist-get-info (sortkey-list BOOK-SPLIT-ACTION) sortkey 'renderer-fn)) diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp index be52f55f47..f35d6507c6 100644 --- a/libgnucash/engine/Account.cpp +++ b/libgnucash/engine/Account.cpp @@ -5352,15 +5352,14 @@ struct AccountInfo }; static void -build_token_info(char const * key, KvpValue * value, TokenAccountsInfo & tokenInfo) +build_token_info(char const * suffix, KvpValue * value, TokenAccountsInfo & tokenInfo) { - tokenInfo.total_count += value->get(); - AccountTokenCount this_account; - std::string account_guid {key}; - /*By convention, the key ends with the account GUID.*/ - this_account.account_guid = account_guid.substr(account_guid.size() - GUID_ENCODING_LENGTH); - this_account.token_count = value->get(); - tokenInfo.accounts.push_back(this_account); + if (strlen(suffix) == GUID_ENCODING_LENGTH) + { + tokenInfo.total_count += value->get(); + /*By convention, the key ends with the account GUID.*/ + tokenInfo.accounts.emplace_back(AccountTokenCount{std::string{suffix}, value->get()}); + } } /** We scale the probability values by probability_factor. @@ -5405,7 +5404,7 @@ get_first_pass_probabilities(GncImportMatchMap * imap, GList * tokens) for (auto current_token = tokens; current_token; current_token = current_token->next) { TokenAccountsInfo tokenInfo{}; - auto path = std::string{IMAP_FRAME_BAYES "/"} + static_cast (current_token->data); + auto path = std::string{IMAP_FRAME_BAYES "/"} + static_cast (current_token->data) + "/"; qof_instance_foreach_slot_prefix (QOF_INSTANCE (imap->acc), path, &build_token_info, tokenInfo); for (auto const & current_account_token : tokenInfo.accounts) { @@ -5738,38 +5737,27 @@ build_non_bayes (const char *key, const GValue *value, gpointer user_data) g_free (guid_string); } -static std::tuple -parse_bayes_imap_info (std::string const & imap_bayes_entry) -{ - auto header_length = strlen (IMAP_FRAME_BAYES); - std::string header {imap_bayes_entry.substr (0, header_length)}; - auto guid_start = imap_bayes_entry.size() - GUID_ENCODING_LENGTH; - std::string keyword {imap_bayes_entry.substr (header_length + 1, guid_start - header_length - 2)}; - std::string account_guid {imap_bayes_entry.substr (guid_start)}; - return std::tuple {header, keyword, account_guid}; -} - static void -build_bayes (const char *key, KvpValue * value, GncImapInfo & imapInfo) +build_bayes (const char *suffix, KvpValue * value, GncImapInfo & imapInfo) { - auto parsed_key = parse_bayes_imap_info (key); + size_t guid_start = strlen(suffix) - GUID_ENCODING_LENGTH; + std::string account_guid {&suffix[guid_start]}; GncGUID guid; try { - auto temp_guid = gnc::GUID::from_string (std::get <2> (parsed_key)); - guid = temp_guid; + guid = gnc::GUID::from_string (account_guid); } catch (const gnc::guid_syntax_exception& err) { - PWARN("Invalid GUID string from %s", key); + PWARN("Invalid GUID string from %s%s", IMAP_FRAME_BAYES, suffix); } auto map_account = xaccAccountLookup (&guid, gnc_account_get_book (imapInfo.source_account)); auto imap_node = static_cast (g_malloc (sizeof (GncImapInfo))); auto count = value->get (); imap_node->source_account = imapInfo.source_account; imap_node->map_account = map_account; - imap_node->head = g_strdup (key); - imap_node->match_string = g_strdup (std::get <1> (parsed_key).c_str ()); + imap_node->head = g_strdup_printf ("%s%s", IMAP_FRAME_BAYES, suffix); + imap_node->match_string = g_strndup (&suffix[1], guid_start - 2); imap_node->category = g_strdup(" "); imap_node->count = g_strdup_printf ("%" G_GINT64_FORMAT, count); imapInfo.list = g_list_prepend (imapInfo.list, imap_node); diff --git a/libgnucash/engine/kvp-frame.hpp b/libgnucash/engine/kvp-frame.hpp index 23eb0c6153..253eec3187 100644 --- a/libgnucash/engine/kvp-frame.hpp +++ b/libgnucash/engine/kvp-frame.hpp @@ -235,23 +235,6 @@ struct KvpFrameImpl KvpValue * set_impl (std::string const &, KvpValue *) noexcept; }; -template -void KvpFrame::for_each_slot_prefix(std::string const & prefix, - func_type const & func) const noexcept -{ - std::for_each (m_valuemap.begin(), m_valuemap.end(), - [&prefix,&func](const KvpFrameImpl::map_type::value_type & a) - { - std::string temp_key {a.first}; - if (temp_key.size() < prefix.size()) - return; - /* Testing for prefix matching */ - if (std::mismatch(prefix.begin(), prefix.end(), temp_key.begin()).first == prefix.end()) - func (a.first, a.second); - } - ); -} - template void KvpFrame::for_each_slot_prefix(std::string const & prefix, func_type const & func, data_type & data) const noexcept @@ -259,12 +242,9 @@ void KvpFrame::for_each_slot_prefix(std::string const & prefix, std::for_each (m_valuemap.begin(), m_valuemap.end(), [&prefix,&func,&data](const KvpFrameImpl::map_type::value_type & a) { - std::string temp_key {a.first}; - if (temp_key.size() < prefix.size()) - return; /* Testing for prefix matching */ - if (std::mismatch(prefix.begin(), prefix.end(), temp_key.begin()).first == prefix.end()) - func (a.first, a.second, data); + if (strncmp(a.first, prefix.c_str(), prefix.size()) == 0) + func (&a.first[prefix.size()], a.second, data); } ); } diff --git a/libgnucash/engine/test/gtest-import-map.cpp b/libgnucash/engine/test/gtest-import-map.cpp index f2e4509e61..33d64ab18e 100644 --- a/libgnucash/engine/test/gtest-import-map.cpp +++ b/libgnucash/engine/test/gtest-import-map.cpp @@ -271,6 +271,14 @@ TEST_F(ImapBayesTest, FindAccountBayes) EXPECT_EQ(t_expense_account2, account); account = gnc_account_imap_find_account_bayes(t_imap, t_list5); EXPECT_EQ(nullptr, account); + + // only imap entries with exact token matching should be considered + root->set_path({std::string{IMAP_FRAME_BAYES} + "/" + pepper + waldo + "/" + acct2_guid}, new KvpValue{*value}); + account = gnc_account_imap_find_account_bayes(t_imap, t_list3); + EXPECT_EQ(t_expense_account1, account); + root->set_path({std::string{IMAP_FRAME_BAYES} + "/" + pepper + "/" + waldo + "/" + acct2_guid}, new KvpValue{*value}); + account = gnc_account_imap_find_account_bayes(t_imap, t_list3); + EXPECT_EQ(t_expense_account1, account); } TEST_F(ImapBayesTest, AddAccountBayes) diff --git a/util/ci/arch-docker b/util/ci/arch-docker index 47f0c5e984..b6e0703b38 100644 --- a/util/ci/arch-docker +++ b/util/ci/arch-docker @@ -4,6 +4,6 @@ run echo en_US.UTF-8 UTF-8 >> /etc/locale.gen run echo en_GB.UTF-8 UTF-8 >> /etc/locale.gen run echo fr_FR.UTF-8 UTF-8 >> /etc/locale.gen run locale-gen -copy arch-testscript afterfailure commonbuild / -run chmod +x /arch-testscript /afterfailure /commonbuild +copy arch-testscript commonbuild / +run chmod +x /arch-testscript /commonbuild entrypoint /arch-testscript diff --git a/util/ci/commonbuild b/util/ci/commonbuild index 9b1875d55e..cf27b1286a 100644 --- a/util/ci/commonbuild +++ b/util/ci/commonbuild @@ -5,15 +5,16 @@ mkdir -p "$HOME"/.local/share mkdir build cd build export TZ="America/Los_Angeles" +export CTEST_OUTPUT_ON_FAILURE=On if [[ "$BUILDTYPE" == "cmake-make" ]]; then cmake ../gnucash $PLATFORM_CMAKE_OPTS make -j 4 - make check || ../afterfailure + make check elif [[ "$BUILDTYPE" == "cmake-ninja" ]]; then cmake ../gnucash -DWITH_PYTHON=ON -DCMAKE_BUILD_TYPE=debug -G Ninja $PLATFORM_CMAKE_OPTS ninja - ninja check || ../afterfailure; + ninja check else echo "Unknown buildtype: \"$BUILDTYPE\". Not building." fi