gnucash maint: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Thu May 3 17:36:52 EDT 2018


Updated	 via  https://github.com/Gnucash/gnucash/commit/5aacb581 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/1b814f44 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/be9b16c5 (commit)
	from  https://github.com/Gnucash/gnucash/commit/a9f35ed7 (commit)



commit 5aacb581d781e7dc2cce821ad7a544f438fc9743
Merge: a9f35ed 1b814f4
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu May 3 17:36:10 2018 -0400

    Merge PR116 into maint.

diff --cc gnucash/import-export/ofx/gnc-ofx-import.c
index 47d822c,0000000..b8df83e
mode 100644,000000..100644
--- a/gnucash/import-export/ofx/gnc-ofx-import.c
+++ b/gnucash/import-export/ofx/gnc-ofx-import.c
@@@ -1,1058 -1,0 +1,1075 @@@
 +/*******************************************************************\
 + * 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 gnc-ofx-import.c
 +     @brief Ofx import module code
 +     @author Copyright (c) 2002 Benoit Grégoire <bock at step.polymtl.ca>
 + */
 +#include <config.h>
 +
 +#include <gtk/gtk.h>
 +#include <glib/gi18n.h>
 +#include <stdio.h>
 +#include <string.h>
 +#include <sys/time.h>
 +#include <math.h>
 +
 +#include <libofx/libofx.h>
 +#include "import-account-matcher.h"
 +#include "import-commodity-matcher.h"
 +#include "import-utilities.h"
 +#include "import-main-matcher.h"
 +
 +#include "Account.h"
 +#include "Transaction.h"
 +#include "engine-helpers.h"
 +#include "gnc-ofx-import.h"
 +#include "gnc-file.h"
 +#include "gnc-engine.h"
 +#include "gnc-ui-util.h"
 +#include "gnc-glib-utils.h"
 +#include "gnc-prefs.h"
 +#include "gnc-ui.h"
 +#include "dialog-account.h"
 +#include "dialog-utils.h"
 +
 +#include "gnc-ofx-kvp.h"
 +
 +#define GNC_PREFS_GROUP "dialogs.import.ofx"
 +#define GNC_PREF_AUTO_COMMODITY "auto-create-commodity"
 +
 +static QofLogModule log_module = GNC_MOD_IMPORT;
 +
 +/********************************************************************\
 + * gnc_file_ofx_import
 + * Entry point
 +\********************************************************************/
 +
 +/* CS: Store the reference to the created importer gui so that the
 +   ofx_proc_transaction_cb can use it. */
 +GNCImportMainMatcher *gnc_ofx_importer_gui = NULL;
 +static gboolean auto_create_commodity = FALSE;
 +static Account *ofx_parent_account = NULL;
 +
 +GList *ofx_created_commodites = NULL;
 +
 +/*
 +int ofx_proc_status_cb(struct OfxStatusData data)
 +{
 +  return 0;
 +}
 +*/
 +
 +int ofx_proc_security_cb(const struct OfxSecurityData data, void * security_user_data);
 +int ofx_proc_transaction_cb (struct OfxTransactionData data, void *user_data);
 +int ofx_proc_account_cb(struct OfxAccountData data, void * account_user_data);
 +static double ofx_get_investment_amount(const struct OfxTransactionData* data);
 +
 +static const gchar *gnc_ofx_ttype_to_string(TransactionType t)
 +{
 +    switch (t)
 +    {
 +    case OFX_CREDIT:
 +        return "Generic credit";
 +    case OFX_DEBIT:
 +        return "Generic debit";
 +    case OFX_INT:
 +        return "Interest earned or paid (Note: Depends on signage of amount)";
 +    case OFX_DIV:
 +        return "Dividend";
 +    case OFX_FEE:
 +        return "FI fee";
 +    case OFX_SRVCHG:
 +        return "Service charge";
 +    case OFX_DEP:
 +        return "Deposit";
 +    case OFX_ATM:
 +        return "ATM debit or credit (Note: Depends on signage of amount)";
 +    case OFX_POS:
 +        return "Point of sale debit or credit (Note: Depends on signage of amount)";
 +    case OFX_XFER:
 +        return "Transfer";
 +    case OFX_CHECK:
 +        return "Check";
 +    case OFX_PAYMENT:
 +        return "Electronic payment";
 +    case OFX_CASH:
 +        return "Cash withdrawal";
 +    case OFX_DIRECTDEP:
 +        return "Direct deposit";
 +    case OFX_DIRECTDEBIT:
 +        return "Merchant initiated debit";
 +    case OFX_REPEATPMT:
 +        return "Repeating payment/standing order";
 +    case OFX_OTHER:
 +        return "Other";
 +    default:
 +        return "Unknown transaction type";
 +    }
 +}
 +
 +static const gchar *gnc_ofx_invttype_to_str(InvTransactionType t)
 +{
 +    switch (t)
 +    {
 +    case OFX_BUYDEBT:
 +        return "BUYDEBT (Buy debt security)";
 +    case OFX_BUYMF:
 +        return "BUYMF (Buy mutual fund)";
 +    case OFX_BUYOPT:
 +        return "BUYOPT (Buy option)";
 +    case OFX_BUYOTHER:
 +        return "BUYOTHER (Buy other security type)";
 +    case OFX_BUYSTOCK:
 +        return "BUYSTOCK (Buy stock))";
 +    case OFX_CLOSUREOPT:
 +        return "CLOSUREOPT (Close a position for an option)";
 +    case OFX_INCOME:
 +        return "INCOME (Investment income is realized as cash into the investment account)";
 +    case OFX_INVEXPENSE:
 +        return "INVEXPENSE (Misc investment expense that is associated with a specific security)";
 +    case OFX_JRNLFUND:
 +        return "JRNLFUND (Journaling cash holdings between subaccounts within the same investment account)";
 +    case OFX_MARGININTEREST:
 +        return "MARGININTEREST (Margin interest expense)";
 +    case OFX_REINVEST:
 +        return "REINVEST (Reinvestment of income)";
 +    case OFX_RETOFCAP:
 +        return "RETOFCAP (Return of capital)";
 +    case OFX_SELLDEBT:
 +        return "SELLDEBT (Sell debt security.  Used when debt is sold, called, or reached maturity)";
 +    case OFX_SELLMF:
 +        return "SELLMF (Sell mutual fund)";
 +    case OFX_SELLOPT:
 +        return "SELLOPT (Sell option)";
 +    case OFX_SELLOTHER:
 +        return "SELLOTHER (Sell other type of security)";
 +    case OFX_SELLSTOCK:
 +        return "SELLSTOCK (Sell stock)";
 +    case OFX_SPLIT:
 +        return "SPLIT (Stock or mutial fund split)";
 +    case OFX_TRANSFER:
 +        return "TRANSFER (Transfer holdings in and out of the investment account)";
 +    default:
 +        return "ERROR, this investment transaction type is unknown.  This is a bug in ofxdump";
 +    }
 +
 +}
 +
 +static gchar*
 +sanitize_string (gchar* str)
 +{
 +    gchar *inval;
 +    const int length = -1; /*Assumes str is null-terminated */
 +    while (!g_utf8_validate (str, length, (const gchar **)(&inval)))
 +	*inval = '@';
 +    return str;
 +}
 +
 +int ofx_proc_security_cb(const struct OfxSecurityData data, void * security_user_data)
 +{
 +    char* cusip = NULL;
 +    char* default_fullname = NULL;
 +    char* default_mnemonic = NULL;
 +
 +    if (data.unique_id_valid)
 +    {
 +        cusip = gnc_utf8_strip_invalid_strdup (data.unique_id);
 +    }
 +    if (data.secname_valid)
 +    {
 +        default_fullname = gnc_utf8_strip_invalid_strdup (data.secname);
 +    }
 +    if (data.ticker_valid)
 +    {
 +        default_mnemonic = gnc_utf8_strip_invalid_strdup (data.ticker);
 +    }
 +
 +    if (auto_create_commodity)
 +    {
 +        gnc_commodity *commodity =
 +            gnc_import_select_commodity(cusip,
 +                                        FALSE,
 +                                        default_fullname,
 +                                        default_mnemonic);
 +
 +        if (!commodity)
 +        {
 +            QofBook *book = gnc_get_current_book();
 +            gnc_quote_source *source;
 +            gint source_selection = 0; // FIXME: This is just a wild guess
 +            char *commodity_namespace = NULL;
 +            int fraction = 1;
 +
 +            if (data.unique_id_type_valid)
 +            {
 +                commodity_namespace = gnc_utf8_strip_invalid_strdup (data.unique_id_type);
 +            }
 +
 +            g_warning("Creating a new commodity, cusip=%s", cusip);
 +            /* Create the new commodity */
 +            commodity = gnc_commodity_new(book,
 +                                          default_fullname,
 +                                          commodity_namespace,
 +                                          default_mnemonic,
 +                                          cusip,
 +                                          fraction);
 +
 +            /* Also set a single quote source */
 +            gnc_commodity_begin_edit(commodity);
 +            gnc_commodity_user_set_quote_flag (commodity, TRUE);
 +            source = gnc_quote_source_lookup_by_ti (SOURCE_SINGLE, source_selection);
 +            gnc_commodity_set_quote_source(commodity, source);
 +            gnc_commodity_commit_edit(commodity);
 +
 +            /* Remember the commodity */
 +            gnc_commodity_table_insert(gnc_get_current_commodities(), commodity);
 +
 +            /* Remember this new commodity for us as well */
 +            ofx_created_commodites = g_list_prepend(ofx_created_commodites, commodity);
 +	    g_free (commodity_namespace);
 +
 +        }
 +    }
 +    else
 +    {
 +        gnc_import_select_commodity(cusip,
 +                                    TRUE,
 +                                    default_fullname,
 +                                    default_mnemonic);
 +    }
 +
 +    g_free (cusip);
 +    g_free (default_mnemonic);
 +    g_free (default_fullname);
 +    return 0;
 +}
 +
 +static void gnc_ofx_set_split_memo(const struct OfxTransactionData* data, Split *split)
 +{
 +    g_assert(data);
 +    g_assert(split);
 +    /* Also put the ofx transaction name in
 +     * the splits memo field, or ofx memo if
 +     * name is unavailable */
 +    if (data->name_valid)
 +    {
 +        xaccSplitSetMemo(split, data->name);
 +    }
 +    else if (data->memo_valid)
 +    {
 +        xaccSplitSetMemo(split, data->memo);
 +    }
 +}
 +static gnc_numeric gnc_ofx_numeric_from_double(double value, const gnc_commodity *commodity)
 +{
 +    return double_to_gnc_numeric (value,
 +                                  gnc_commodity_get_fraction(commodity),
 +                                  GNC_HOW_RND_ROUND_HALF_UP);
 +}
 +static gnc_numeric gnc_ofx_numeric_from_double_txn(double value, const Transaction* txn)
 +{
 +    return gnc_ofx_numeric_from_double(value, xaccTransGetCurrency(txn));
 +}
 +
 +/* Opens the dialog to create a new account with given name, commodity, parent, type.
 + * Returns the new account, or NULL if it couldn't be created.. */
 +static Account *gnc_ofx_new_account(GtkWindow* parent,
 +                                    const char* name,
 +                                    const gnc_commodity * account_commodity,
 +                                    Account *parent_account,
 +                                    GNCAccountType new_account_default_type)
 +{
 +    Account *result;
 +    GList * valid_types = NULL;
 +
 +    g_assert(name);
 +    g_assert(account_commodity);
 +    g_assert(parent_account);
 +
 +    if (new_account_default_type != ACCT_TYPE_NONE)
 +    {
 +        // Passing the types as gpointer
 +        valid_types =
 +            g_list_prepend(valid_types,
 +                           GINT_TO_POINTER(new_account_default_type));
 +        if (!xaccAccountTypesCompatible(xaccAccountGetType(parent_account), new_account_default_type))
 +        {
 +            // Need to add the parent's account type
 +            valid_types =
 +                g_list_prepend(valid_types,
 +                               GINT_TO_POINTER(xaccAccountGetType(parent_account)));
 +        }
 +    }
 +    result = gnc_ui_new_accounts_from_name_with_defaults (parent, name,
 +                                                          valid_types,
 +                                                          account_commodity,
 +                                                          parent_account);
 +    g_list_free(valid_types);
 +    return result;
 +}
 +/* LibOFX has a daylight time handling bug,
 + * https://sourceforge.net/p/libofx/bugs/39/, which causes it to adjust the
 + * timestamp for daylight time even when daylight time is not in
 + * effect. HAVE_OFX_BUG_39 reflects the result of checking for this bug during
 + * configuration, and fix_ofx_bug_39() corrects for it.
 + */
 +static time64
 +fix_ofx_bug_39 (time64 t)
 +{
 +#if HAVE_OFX_BUG_39
 +    struct tm stm;
++
++#ifdef __FreeBSD__
++    time64 now;
++    /*
++     * FreeBSD has it's own libc implementation which differs from glibc. In particular:
++     * There is no daylight global
++     * tzname members are set to the string "   " (three spaces) when not explicitly populated
++     *
++     * To check that the current timezone does not observe DST I check if tzname[1] starts with a space.
++     */
++    now = gnc_time (NULL);
++    gnc_localtime_r(&now, &stm);
++    tzset();
++
++    if (tzname[1][0] != ' ' && !stm.tm_isdst)
++#else
 +    gnc_localtime_r(&t, &stm);
 +    if (daylight && !stm.tm_isdst)
++#endif
 +        t += 3600;
 +#endif
 +    return t;
 +}
 +
 +int ofx_proc_transaction_cb(struct OfxTransactionData data, void *user_data)
 +{
 +    char dest_string[255];
 +    time64 current_time = gnc_time (NULL);
 +    Account *account;
 +    Account *investment_account = NULL;
 +    Account *income_account = NULL;
 +    gchar *investment_account_text, *investment_account_onlineid;
 +    gnc_commodity *currency = NULL;
 +    gnc_commodity *investment_commodity = NULL;
 +    gnc_numeric gnc_amount, gnc_units;
 +    QofBook *book;
 +    Transaction *transaction;
 +    Split *split;
 +    gchar *notes, *tmp;
 +    GtkWindow *parent = GTK_WINDOW (user_data);
 +
 +    g_assert(gnc_ofx_importer_gui);
 +
 +    if (!data.account_id_valid)
 +    {
 +        PERR("account ID for this transaction is unavailable!");
 +        return 0;
 +    }
 +    else
 +	gnc_utf8_strip_invalid (data.account_id);
 +
 +    account = gnc_import_select_account(gnc_gen_trans_list_widget(gnc_ofx_importer_gui),
 +                                        data.account_id,
 +					0, NULL, NULL, ACCT_TYPE_NONE,
 +					NULL, NULL);
 +    if (account == NULL)
 +    {
 +        PERR("Unable to find account for id %s", data.account_id);
 +        return 0;
 +    }
 +    /***** Validate the input strings to ensure utf8 *****/
 +    if (data.name_valid)
 +        gnc_utf8_strip_invalid(data.name);
 +    if (data.memo_valid)
 +        gnc_utf8_strip_invalid(data.memo);
 +    if (data.check_number_valid)
 +        gnc_utf8_strip_invalid(data.check_number);
 +    if (data.reference_number_valid)
 +        gnc_utf8_strip_invalid(data.reference_number);
 +
 +    /***** Create the transaction and setup transaction data *******/
 +    book = gnc_account_get_book(account);
 +    transaction = xaccMallocTransaction(book);
 +    xaccTransBeginEdit(transaction);
 +
 +    /* Note: Unfortunately libofx <= 0.9.5 will not report a missing
 +     * date field as an invalid one. Instead, it will report it as
 +     * valid and return a completely bogus date. Starting with
 +     * libofx-0.9.6 (not yet released as of 2012-09-09), it will still
 +     * be reported as valid but at least the date integer itself is
 +     * just plain zero. */
 +    if (data.date_posted_valid && (data.date_posted != 0))
 +    {
 +        /* The hopeful case: We have a posted_date */
 +        data.date_posted = fix_ofx_bug_39 (data.date_posted);
 +        xaccTransSetDatePostedSecsNormalized(transaction, data.date_posted);
 +    }
 +    else if (data.date_initiated_valid && (data.date_initiated != 0))
 +    {
 +        /* No posted date? Maybe we have an initiated_date */
 +        data.date_initiated = fix_ofx_bug_39 (data.date_initiated);
 +        xaccTransSetDatePostedSecsNormalized(transaction, data.date_initiated);
 +    }
 +    else
 +    {
 +        /* Uh no, no valid date. As a workaround use today's date */
 +        xaccTransSetDatePostedSecsNormalized(transaction, current_time);
 +    }
 +
 +    xaccTransSetDateEnteredSecs(transaction, current_time);
 +
 +    /* Put transaction name in Description, or memo if name unavailable */
 +    if (data.name_valid)
 +    {
 +        xaccTransSetDescription(transaction, data.name);
 +    }
 +    else if (data.memo_valid)
 +    {
 +        xaccTransSetDescription(transaction, data.memo);
 +    }
 +
 +    /* Put everything else in the Notes field */
 +    notes = g_strdup_printf("OFX ext. info: ");
 +
 +    if (data.transactiontype_valid)
 +    {
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%s", tmp, "|Trans type:",
 +                                gnc_ofx_ttype_to_string(data.transactiontype));
 +        g_free(tmp);
 +    }
 +
 +    if (data.invtransactiontype_valid)
 +    {
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%s", tmp, "|Investment Trans type:",
 +                                gnc_ofx_invttype_to_str(data.invtransactiontype));
 +        g_free(tmp);
 +    }
 +    if (data.memo_valid && data.name_valid) /* Copy only if memo wasn't put in Description */
 +    {
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%s", tmp, "|Memo:", data.memo);
 +        g_free(tmp);
 +    }
 +    if (data.date_funds_available_valid)
 +    {
 +        Timespec ts;
 +        timespecFromTime64(&ts, data.date_funds_available);
 +        gnc_timespec_to_iso8601_buff (ts, dest_string);
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%s", tmp,
 +				"|Date funds available:", dest_string);
 +        g_free(tmp);
 +    }
 +    if (data.server_transaction_id_valid)
 +    {
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%s", tmp,
 +				"|Server trans ID (conf. number):",
 +				sanitize_string (data.server_transaction_id));
 +        g_free(tmp);
 +    }
 +    if (data.standard_industrial_code_valid)
 +    {
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%ld", tmp,
 +				"|Standard Industrial Code:",
 +                                data.standard_industrial_code);
 +        g_free(tmp);
 +
 +    }
 +    if (data.payee_id_valid)
 +    {
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%s", tmp, "|Payee ID:",
 +				sanitize_string (data.payee_id));
 +        g_free(tmp);
 +    }
 +
 +    //PERR("WRITEME: GnuCash ofx_proc_transaction():Add PAYEE and ADRESS here once supported by libofx! Notes=%s\n", notes);
 +
 +    /* Ideally, gnucash should process the corrected transactions */
 +    if (data.fi_id_corrected_valid)
 +    {
 +        PERR("WRITEME: GnuCash ofx_proc_transaction(): WARNING: This transaction corrected a previous transaction, but we created a new one instead!\n");
 +        tmp = notes;
 +        notes = g_strdup_printf("%s%s%s%s", tmp,
 +				"|This corrects transaction #",
 +				sanitize_string (data.fi_id_corrected),
 +				"but GnuCash didn't process the correction!");
 +        g_free(tmp);
 +    }
 +    xaccTransSetNotes(transaction, notes);
 +    g_free(notes);
 +
 +    if (data.account_ptr && data.account_ptr->currency_valid)
 +    {
 +        DEBUG("Currency from libofx: %s", data.account_ptr->currency);
 +        currency = gnc_commodity_table_lookup( gnc_get_current_commodities (),
 +                                               GNC_COMMODITY_NS_CURRENCY,
 +                                               data.account_ptr->currency);
 +    }
 +    else
 +    {
 +        DEBUG("Currency from libofx unavailable, defaulting to account's default");
 +        currency = xaccAccountGetCommodity(account);
 +    }
 +
 +    xaccTransSetCurrency(transaction, currency);
 +    if (data.amount_valid)
 +    {
 +        if (!data.invtransactiontype_valid)
 +        {
 +            /***** Process a normal transaction ******/
 +            DEBUG("Adding split; Ordinary banking transaction, money flows from or into the source account");
 +            split = xaccMallocSplit(book);
 +            xaccTransAppendSplit(transaction, split);
 +            xaccAccountInsertSplit(account, split);
 +
 +            gnc_amount = gnc_ofx_numeric_from_double_txn(data.amount, transaction);
 +            xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
 +
 +            /* set tran-num and/or split-action per book option */
 +            if (data.check_number_valid)
 +            {
 +                gnc_set_num_action(transaction, split, data.check_number, NULL);
 +            }
 +            else if (data.reference_number_valid)
 +            {
 +                gnc_set_num_action(transaction, split, data.reference_number, NULL);
 +            }
 +            /* Also put the ofx transaction's memo in the
 +             * split's memo field */
 +            if (data.memo_valid)
 +            {
 +                xaccSplitSetMemo(split, data.memo);
 +            }
 +            if (data.fi_id_valid)
 +            {
 +                gnc_import_set_split_online_id(split,
 +					       sanitize_string (data.fi_id));
 +            }
 +        }
 +
 +        else if (data.unique_id_valid
 +                 && data.security_data_valid
 +                 && data.security_data_ptr != NULL
 +                 && data.security_data_ptr->secname_valid)
 +        {
 +            gboolean choosing_account = TRUE;
 +	    gnc_utf8_strip_invalid (data.unique_id);
 +            /********* Process an investment transaction **********/
 +            /* Note that the ACCT_TYPE_STOCK account type
 +               should be replaced with something derived from
 +               data.invtranstype*/
 +
 +            // We have an investment transaction. First select the correct commodity.
 +            investment_commodity = gnc_import_select_commodity(data.unique_id,
 +                                   FALSE,
 +                                   NULL,
 +                                   NULL);
 +            if (investment_commodity != NULL)
 +            {
 +                // As we now have the commodity, select the account with that commodity.
 +
 +                investment_account_text = g_strdup_printf( /* This string is a default account
 +                                                              name. It MUST NOT contain the
 +                                                              character ':' anywhere in it or
 +                                                              in any translations.  */
 +                                         _("Stock account for security \"%s\""),
 +                             sanitize_string (data.security_data_ptr->secname));
 +
 +                investment_account_onlineid = g_strdup_printf( "%s%s",
 +							       data.account_id,
 +							       data.unique_id);
 +                investment_account = gnc_import_select_account(
 +                                     gnc_gen_trans_list_widget(gnc_ofx_importer_gui),
 +                                     investment_account_onlineid,
 +                                     1,
 +                                     investment_account_text,
 +                                     investment_commodity,
 +                                     ACCT_TYPE_STOCK,
 +                                     NULL,
 +                                     NULL);
 +
 +                // but use it only if that's really the right commodity
 +                if (investment_account
 +                        && xaccAccountGetCommodity(investment_account) != investment_commodity)
 +                    investment_account = NULL;
 +
 +                // Loop until we either have an account, or the user pressed Cancel
 +                while (!investment_account && choosing_account)
 +                {
 +                    // No account with correct commodity automatically found.
 +
 +                    // But are we in auto-create mode and already know a parent?
 +                    if (auto_create_commodity && ofx_parent_account)
 +                    {
 +                        // Yes, so use that as parent when auto-creating the new account below.
 +                        investment_account = ofx_parent_account;
 +                    }
 +                    else
 +                    {
 +                        // Let the user choose an account
 +                        investment_account = gnc_import_select_account(
 +                                                 gnc_gen_trans_list_widget(gnc_ofx_importer_gui),
 +                                                 data.unique_id,
 +                                                 TRUE,
 +                                                 investment_account_text,
 +                                                 investment_commodity,
 +                                                 ACCT_TYPE_STOCK,
 +                                                 NULL,
 +                                                 &choosing_account);
 +                    }
 +                    // Does the chosen account have the right commodity?
 +                    if (investment_account && xaccAccountGetCommodity(investment_account) != investment_commodity)
 +                    {
 +                        if (auto_create_commodity
 +                                && xaccAccountTypesCompatible(xaccAccountGetType(investment_account),
 +                                                              ACCT_TYPE_STOCK))
 +                        {
 +                            // The user chose an account, but it does
 +                            // not have the right commodity. Also,
 +                            // auto-creation is on. Hence, we create a
 +                            // new child account of the selected one,
 +                            // and this one will have the right
 +                            // commodity.
 +                            Account *parent_account = investment_account;
 +                            investment_account =
 +                                gnc_ofx_new_account(parent,
 +                                                    investment_account_text,
 +                                                    investment_commodity,
 +                                                    parent_account,
 +                                                    ACCT_TYPE_STOCK);
 +                            if (investment_account)
 +                            {
 +                                gnc_import_set_acc_online_id(investment_account, data.unique_id);
 +                                choosing_account = FALSE;
 +                                ofx_parent_account = parent_account;
 +                            }
 +                            else
 +                            {
 +                                ofx_parent_account = NULL;
 +                            }
 +                        }
 +                        else
 +                        {
 +                            // No account with matching commodity. Ask the user
 +                            // whether to continue or abort.
 +                            choosing_account =
 +                                gnc_verify_dialog(
 +                                    GTK_WINDOW (gnc_gen_trans_list_widget(gnc_ofx_importer_gui)), TRUE,
 +                                    "The chosen account \"%s\" does not have the correct "
 +                                    "currency/security \"%s\" (it has \"%s\" instead). "
 +                                    "This account cannot be used. "
 +                                    "Do you want to choose again?",
 +                                    xaccAccountGetName(investment_account),
 +                                    gnc_commodity_get_fullname(investment_commodity),
 +                                    gnc_commodity_get_fullname(xaccAccountGetCommodity(investment_account)));
 +                            // We must also delete the online_id that was set in gnc_import_select_account()
 +                            gnc_import_set_acc_online_id(investment_account, "");
 +                            investment_account = NULL;
 +                        }
 +                    }
 +                }
 +                if (!investment_account)
 +                {
 +                    PERR("No investment account found for text: %s\n", investment_account_text);
 +                }
 +                g_free (investment_account_text);
 +                g_free (investment_account_onlineid);
 +                investment_account_text = NULL;
 +
 +                if (investment_account != NULL &&
 +                        data.unitprice_valid &&
 +                        data.units_valid &&
 +                        ( data.invtransactiontype != OFX_INCOME ) )
 +                {
 +                    DEBUG("Adding investment split; Money flows from or into the stock account");
 +                    split = xaccMallocSplit(book);
 +                    xaccTransAppendSplit(transaction, split);
 +                    xaccAccountInsertSplit(investment_account, split);
 +
 +                    gnc_amount = gnc_ofx_numeric_from_double_txn (ofx_get_investment_amount(&data),
 +                                 transaction);
 +                    gnc_units = gnc_ofx_numeric_from_double (data.units, investment_commodity);
 +                    xaccSplitSetAmount(split, gnc_units);
 +                    xaccSplitSetValue(split, gnc_amount);
 +
 +                    /* set tran-num and/or split-action per book option */
 +                    if (data.check_number_valid)
 +                    {
 +                        gnc_set_num_action(transaction, split, data.check_number, NULL);
 +                    }
 +                    else if (data.reference_number_valid)
 +                    {
 +                        gnc_set_num_action(transaction, split,
 +                                           data.reference_number, NULL);
 +                    }
 +                    if (data.security_data_ptr->memo_valid)
 +                    {
 +                        xaccSplitSetMemo(split,
 +                                sanitize_string (data.security_data_ptr->memo));
 +                    }
 +                    if (data.fi_id_valid)
 +                    {
 +                        gnc_import_set_split_online_id(split,
 +                                                 sanitize_string (data.fi_id));
 +                    }
 +                }
 +                else
 +                {
 +                    if (investment_account)
 +                        PERR("The investment account, units or unitprice was not found for the investment transaction");
 +                }
 +            }
 +            else
 +            {
 +                PERR("Commodity not found for the investment transaction");
 +            }
 +
 +            if (data.invtransactiontype_valid && investment_account)
 +            {
 +                if (data.invtransactiontype == OFX_REINVEST
 +                        || data.invtransactiontype == OFX_INCOME)
 +                {
 +                    DEBUG("Now let's find an account for the destination split");
 +
 +                    income_account = gnc_ofx_kvp_get_assoc_account(investment_account);
 +
 +                    if (income_account == NULL)
 +                    {
 +                        DEBUG("Couldn't find an associated income account");
 +                        investment_account_text = g_strdup_printf( /* This string is a default account
 +                                                                      name. It MUST NOT contain the
 +                                                                      character ':' anywhere in it or
 +                                                                      in any translations.  */
 +                                                      _("Income account for security \"%s\""),
 +                                                      sanitize_string (data.security_data_ptr->secname));
 +                        income_account = gnc_import_select_account(
 +                                             gnc_gen_trans_list_widget(gnc_ofx_importer_gui),
 +                                             NULL,
 +                                             1,
 +                                             investment_account_text,
 +                                             currency,
 +                                             ACCT_TYPE_INCOME,
 +                                             NULL,
 +                                             NULL);
 +                        if (income_account != NULL)
 +                        {
 +                            gnc_ofx_kvp_set_assoc_account(investment_account,
 +                                                          income_account);
 +                            DEBUG("KVP written");
 +                        }
 +
 +                    }
 +                    else
 +                    {
 +                        DEBUG("Found at least one associated income account");
 +                    }
 +                }
 +                if (income_account != NULL &&
 +                        data.invtransactiontype == OFX_REINVEST)
 +                {
 +                    DEBUG("Adding investment split; Money flows from the income account");
 +                    split = xaccMallocSplit(book);
 +                    xaccTransAppendSplit(transaction, split);
 +                    xaccAccountInsertSplit(income_account, split);
 +
 +                    gnc_amount = gnc_ofx_numeric_from_double_txn (data.amount, transaction);
 +                    xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
 +
 +                    // Set split memo from ofx transaction name or memo
 +                    gnc_ofx_set_split_memo(&data, split);
 +                }
 +                if (income_account != NULL &&
 +                        data.invtransactiontype == OFX_INCOME)
 +                {
 +                    DEBUG("Adding investment split; Money flows from the income account");
 +                    split = xaccMallocSplit(book);
 +                    xaccTransAppendSplit(transaction, split);
 +                    xaccAccountInsertSplit(income_account, split);
 +
 +                    gnc_amount = gnc_ofx_numeric_from_double_txn (-data.amount,/*OFX_INCOME amounts come in as positive numbers*/
 +                                 transaction);
 +                    xaccSplitSetBaseValue(split, gnc_amount, xaccTransGetCurrency(transaction));
 +
 +                    // Set split memo from ofx transaction name or memo
 +                    gnc_ofx_set_split_memo(&data, split);
 +                }
 +            }
 +
 +            if (data.invtransactiontype_valid
 +                    && data.invtransactiontype != OFX_REINVEST)
 +            {
 +                DEBUG("Adding investment split; Money flows from or to the cash account");
 +                split = xaccMallocSplit(book);
 +                xaccTransAppendSplit(transaction, split);
 +                xaccAccountInsertSplit(account, split);
 +
 +                gnc_amount = gnc_ofx_numeric_from_double_txn(
 +                                 -ofx_get_investment_amount(&data), transaction);
 +                xaccSplitSetBaseValue(split, gnc_amount,
 +                                      xaccTransGetCurrency(transaction));
 +
 +                // Set split memo from ofx transaction name or memo
 +                gnc_ofx_set_split_memo(&data, split);
 +            }
 +        }
 +
 +        /* Send transaction to importer GUI. */
 +        if (xaccTransCountSplits(transaction) > 0)
 +        {
 +            DEBUG("%d splits sent to the importer gui", xaccTransCountSplits(transaction));
 +            gnc_gen_trans_list_add_trans (gnc_ofx_importer_gui, transaction);
 +        }
 +        else
 +        {
 +            PERR("No splits in transaction (missing account?), ignoring.");
 +            xaccTransDestroy(transaction);
 +            xaccTransCommitEdit(transaction);
 +        }
 +    }
 +    else
 +    {
 +        PERR("The transaction doesn't have a valid amount");
 +        xaccTransDestroy(transaction);
 +        xaccTransCommitEdit(transaction);
 +    }
 +
 +    return 0;
 +}//end ofx_proc_transaction()
 +
 +/*
 +int ofx_proc_statement_cb(struct OfxStatementData data, void * statement_user_data)
 +{
 +  return 0;
 +}//end ofx_proc_statement()
 +*/
 +
 +int ofx_proc_account_cb(struct OfxAccountData data, void * account_user_data)
 +{
 +    gnc_commodity_table * commodity_table;
 +    gnc_commodity * default_commodity;
 +    GNCAccountType default_type = ACCT_TYPE_NONE;
 +    gchar * account_description;
 +    /* In order to trigger a book options display on the creation of a new book,
 +     * we need to detect when we are dealing with a new book. */
 +    gboolean new_book = gnc_is_new_book();
 +
 +    const gchar * account_type_name = _("Unknown OFX account");
 +
 +    if (data.account_id_valid)
 +    {
 +        commodity_table = gnc_get_current_commodities ();
 +        if (data.currency_valid)
 +        {
 +            DEBUG("Currency from libofx: %s", data.currency);
 +            default_commodity = gnc_commodity_table_lookup(commodity_table,
 +                                GNC_COMMODITY_NS_CURRENCY,
 +                                data.currency);
 +        }
 +        else
 +        {
 +            default_commodity = NULL;
 +        }
 +
 +        if (data.account_type_valid)
 +        {
 +            switch (data.account_type)
 +            {
 +            case OFX_CHECKING :
 +                default_type = ACCT_TYPE_BANK;
 +                account_type_name = _("Unknown OFX checking account");
 +                break;
 +            case OFX_SAVINGS :
 +                default_type = ACCT_TYPE_BANK;
 +                account_type_name = _("Unknown OFX savings account");
 +                break;
 +            case OFX_MONEYMRKT :
 +                default_type = ACCT_TYPE_MONEYMRKT;
 +                account_type_name = _("Unknown OFX money market account");
 +                break;
 +            case OFX_CREDITLINE :
 +                default_type = ACCT_TYPE_CREDITLINE;
 +                account_type_name = _("Unknown OFX credit line account");
 +                break;
 +            case OFX_CMA :
 +                default_type = ACCT_TYPE_NONE;
 +                /* Cash Management Account */
 +                account_type_name = _("Unknown OFX CMA account");
 +                break;
 +            case OFX_CREDITCARD :
 +                default_type = ACCT_TYPE_CREDIT;
 +                account_type_name = _("Unknown OFX credit card account");
 +                break;
 +            case OFX_INVESTMENT :
 +                default_type = ACCT_TYPE_BANK;
 +                account_type_name = _("Unknown OFX investment account");
 +                break;
 +            default:
 +                PERR("WRITEME: ofx_proc_account() This is an unknown account type!");
 +                break;
 +            }
 +        }
 +
 +        /* If the OFX importer was started in Gnucash in a 'new_book' situation,
 +         * as described above, the first time the 'ofx_proc_account_cb' function
 +         * is called a book is created. (This happens after the 'new_book' flag
 +         * is set in 'gnc_get_current_commodities', called above.) So, before
 +         * calling 'gnc_import_select_account', allow the user to set book
 +         * options. */
 +        if (new_book)
 +            new_book = gnc_new_book_option_display (GTK_WIDGET (gnc_ui_get_main_window (NULL)));
 +
 +        gnc_utf8_strip_invalid(data.account_name);
 +        gnc_utf8_strip_invalid(data.account_id);
 +        account_description = g_strdup_printf( /* This string is a default account
 +                                                  name. It MUST NOT contain the
 +                                                  character ':' anywhere in it or
 +                                                  in any translation.  */
 +                                  "%s \"%s\"",
 +                                  account_type_name,
 +                                  data.account_name);
 +        gnc_import_select_account(gnc_gen_trans_list_widget(gnc_ofx_importer_gui),
 +                                  data.account_id, 1,
 +                                  account_description, default_commodity,
 +                                  default_type, NULL, NULL);
 +        g_free(account_description);
 +    }
 +    else
 +    {
 +        PERR("account online ID not available");
 +    }
 +
 +    return 0;
 +}
 +
 +double ofx_get_investment_amount(const struct OfxTransactionData* data)
 +{
 +    g_assert(data);
 +    switch (data->invtransactiontype)
 +    {
 +    case OFX_BUYDEBT:
 +    case OFX_BUYMF:
 +    case OFX_BUYOPT:
 +    case OFX_BUYOTHER:
 +    case OFX_BUYSTOCK:
 +        return fabs(data->amount);
 +    case OFX_SELLDEBT:
 +    case OFX_SELLMF:
 +    case OFX_SELLOPT:
 +    case OFX_SELLOTHER:
 +    case OFX_SELLSTOCK:
 +        return -1 * fabs(data->amount);
 +    default:
 +        return -1 * data->amount;
 +    }
 +}
 +
 +void gnc_file_ofx_import (GtkWindow *parent)
 +{
 +    extern int ofx_PARSER_msg;
 +    extern int ofx_DEBUG_msg;
 +    extern int ofx_WARNING_msg;
 +    extern int ofx_ERROR_msg;
 +    extern int ofx_INFO_msg;
 +    extern int ofx_STATUS_msg;
 +    char *selected_filename;
 +    char *default_dir;
 +    LibofxContextPtr libofx_context = libofx_get_new_context();
 +
 +    ofx_PARSER_msg = false;
 +    ofx_DEBUG_msg = false;
 +    ofx_WARNING_msg = true;
 +    ofx_ERROR_msg = true;
 +    ofx_INFO_msg = true;
 +    ofx_STATUS_msg = false;
 +
 +    DEBUG("gnc_file_ofx_import(): Begin...\n");
 +
 +    default_dir = gnc_get_default_directory(GNC_PREFS_GROUP);
 +    selected_filename = gnc_file_dialog(parent,
 +                                        _("Select an OFX/QFX file to process"),
 +                                        NULL,
 +                                        default_dir,
 +                                        GNC_FILE_DIALOG_IMPORT);
 +    g_free(default_dir);
 +
 +    if (selected_filename != NULL)
 +    {
 +#ifdef G_OS_WIN32
 +        gchar *conv_name;
 +#endif
 +
 +        /* Remember the directory as the default. */
 +        default_dir = g_path_get_dirname(selected_filename);
 +        gnc_set_default_directory(GNC_PREFS_GROUP, default_dir);
 +        g_free(default_dir);
 +
 +        /*strncpy(file,selected_filename, 255);*/
 +        DEBUG("Filename found: %s", selected_filename);
 +
 +        /* Create the Generic transaction importer GUI. */
 +        gnc_ofx_importer_gui = gnc_gen_trans_list_new (GTK_WIDGET(parent), NULL, FALSE, 42);
 +
 +        /* Look up the needed preferences */
 +        auto_create_commodity =
 +            gnc_prefs_get_bool (GNC_PREFS_GROUP_IMPORT, GNC_PREF_AUTO_COMMODITY);
 +
 +        /* Initialize libofx */
 +
 +        /*ofx_set_statement_cb(libofx_context, ofx_proc_statement_cb, 0);*/
 +        ofx_set_account_cb(libofx_context, ofx_proc_account_cb, 0);
 +        ofx_set_transaction_cb(libofx_context, ofx_proc_transaction_cb, parent);
 +        ofx_set_security_cb(libofx_context, ofx_proc_security_cb, 0);
 +        /*ofx_set_status_cb(libofx_context, ofx_proc_status_cb, 0);*/
 +
 +#ifdef G_OS_WIN32
 +        conv_name = g_win32_locale_filename_from_utf8(selected_filename);
 +        g_free(selected_filename);
 +        selected_filename = conv_name;
 +#endif
 +
 +        DEBUG("Opening selected file");
 +        libofx_proc_file(libofx_context, selected_filename, AUTODETECT);
 +        g_free(selected_filename);
 +    }
 +
 +    if (ofx_created_commodites)
 +    {
 +        /* FIXME: Present some result window about the newly created
 +         * commodities */
 +        g_warning("Created %d new commodities during import", g_list_length(ofx_created_commodites));
 +        g_list_free(ofx_created_commodites);
 +        ofx_created_commodites = NULL;
 +    }
 +    else
 +    {
 +        //g_warning("No new commodities created");
 +    }
 +}
 +
 +
 +/** @} */

commit 1b814f441cb8abd6b4f5596897d2d4cf6c342b43
Author: Guido Falsi <mad at madpilot.net>
Date:   Tue Jul 11 21:54:31 2017 +0200

    Call tzset() to make sure tzname[] is initializaed with local timezone data.
    Perform the check on the local timezone, not the transaction one.
    Add comment explaining FreeBSD differences.

diff --git a/src/import-export/ofx/gnc-ofx-import.c b/src/import-export/ofx/gnc-ofx-import.c
index b43aa2d..159c6c9 100644
--- a/src/import-export/ofx/gnc-ofx-import.c
+++ b/src/import-export/ofx/gnc-ofx-import.c
@@ -336,10 +336,23 @@ fix_ofx_bug_39 (time64 t)
 {
 #if HAVE_OFX_BUG_39
     struct tm stm;
-    gnc_localtime_r(&t, &stm);
+
 #ifdef __FreeBSD__
+    time64 now;
+    /*
+     * FreeBSD has it's own libc implementation which differs from glibc. In particular:
+     * There is no daylight global
+     * tzname members are set to the string "   " (three spaces) when not explicitly populated
+     *
+     * To check that the current timezone does not observe DST I check if tzname[1] starts with a space.
+     */
+    now = gnc_time (NULL);
+    gnc_localtime_r(&now, &stm);
+    tzset();
+
     if (tzname[1][0] != ' ' && !stm.tm_isdst)
 #else
+    gnc_localtime_r(&t, &stm);
     if (daylight && !stm.tm_isdst)
 #endif
       t += 3600;

commit be9b16c50006a6f12d2cae9a94c8e75fb969cde1
Author: Guido Falsi <mad at madpilot.net>
Date:   Tue Jul 11 19:11:46 2017 +0200

    FreeBSD does not have the daylight global. Add alternative implementation of the check emulating the glibc daylight global.

diff --git a/src/import-export/ofx/gnc-ofx-import.c b/src/import-export/ofx/gnc-ofx-import.c
index da6a479..b43aa2d 100644
--- a/src/import-export/ofx/gnc-ofx-import.c
+++ b/src/import-export/ofx/gnc-ofx-import.c
@@ -337,7 +337,11 @@ fix_ofx_bug_39 (time64 t)
 #if HAVE_OFX_BUG_39
     struct tm stm;
     gnc_localtime_r(&t, &stm);
+#ifdef __FreeBSD__
+    if (tzname[1][0] != ' ' && !stm.tm_isdst)
+#else
     if (daylight && !stm.tm_isdst)
+#endif
       t += 3600;
 #endif
     return t;



Summary of changes:
 gnucash/import-export/ofx/gnc-ofx-import.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)



More information about the gnucash-changes mailing list