Bug 706021 Add Other Account to CSV Transaction Import

This patch adds the option to specify the other account and memo
when doing a CSV import.
This commit is contained in:
Robert Fewell 2015-10-17 12:51:10 +01:00 committed by Geert Janssens
parent 4ee5763075
commit 0e7c7c3b42
6 changed files with 974 additions and 210 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,30 @@
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkListStore" id="account_match_store">
<columns>
<!-- column-name import_string -->
<column type="gchararray"/>
<!-- column-name full_name -->
<column type="gchararray"/>
<!-- column-name account -->
<column type="gpointer"/>
</columns>
</object>
<object class="GtkAdjustment" id="end_row_adj">
<property name="lower">1</property>
<property name="upper">100</property>
<property name="value">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="start_row_adj">
<property name="lower">1</property>
<property name="upper">100</property>
<property name="value">1</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAssistant" id="CSV Transaction Assistant">
<property name="can_focus">False</property>
<property name="border_width">12</property>
@ -18,7 +42,11 @@
<property name="can_focus">False</property>
<property name="label" translatable="yes">This assistant will help you import a delimited file containing a list of transactions.
All transactions imported will be associated to one account for each import and if you select the account column, the account in the first row will be used for all rows.
There is a minimum number of columns that have to be present for a successful import, these are Date, Description and one of Balance, Deposit or Withdrawal.
If there is no Account column, all transactions imported will be associated to a selected account. If an Account column is used with a Balance column, then this column should only equate to one account.
If you have an Other Memo column, you must have an Other Account column.
Various options exist for specifying the delimiter as well as a fixed width option. With the fixed width option, double click on the bar above the displayed rows to set the column width.
@ -736,14 +764,13 @@ Select location and file name for the Import, then click 'OK'...
<property name="can_focus">False</property>
<child>
<object class="GtkCheckButton" id="check_butt">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">1</property>
<property name="image_position">right</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="csv_import_trans_auto_cb" swapped="no"/>
<signal name="toggled" handler="csv_import_trans_skip_errors_cb" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -754,10 +781,9 @@ Select location and file name for the Import, then click 'OK'...
</child>
<child>
<object class="GtkLabel" id="check_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">5</property>
<property name="label" translatable="yes">Step over Account Page if Setup</property>
<property name="label" translatable="yes">Skip Errors</property>
</object>
<packing>
<property name="expand">False</property>
@ -785,11 +811,11 @@ Select location and file name for the Import, then click 'OK'...
<property name="can_focus">False</property>
<property name="border_width">12</property>
<child>
<object class="GtkLabel" id="account_label">
<object class="GtkLabel" id="account_page_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Error text.</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
@ -803,6 +829,119 @@ Select location and file name for the Import, then click 'OK'...
<property name="title" translatable="yes">Account Selection</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="account_match_page">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">12</property>
<child>
<object class="GtkLabel" id="label7609">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="xpad">5</property>
<property name="label" translatable="yes">Select a row to change the mappings:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="account_match_swindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="account_match_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">account_match_store</property>
<property name="rules_hint">True</property>
<property name="enable_search">False</property>
<property name="enable_tree_lines">True</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn1">
<property name="resizable">True</property>
<property name="title" translatable="yes">Account ID</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn2">
<property name="resizable">True</property>
<property name="title" translatable="yes">Account Name</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox7609">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="account_match_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Error text.</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="account_match_change">
<property name="label" translatable="yes">Change GnuCash _Account...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="title" translatable="yes">Match Import accounts with GnuCash accounts</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="doc_page">
<property name="visible">True</property>
@ -813,6 +952,8 @@ Select location and file name for the Import, then click 'OK'...
<property name="can_focus">False</property>
<property name="label" translatable="yes">On the following page you will be able to associate each transaction to a category.
If there were problems with the import settings, pressing forward will take you back to the preview page to try and correct.
If this is the first time importing, you will find that all lines may need to be associated. On subsequent imports, the importer will try to associate the transactions based on previous imports.
If this is your initial import into a new file, you will first see a dialog for setting book options, since these can affect how imported data are converted to GnuCash transactions. If this is an existing file, the dialog will not be shown.
@ -886,18 +1027,4 @@ More information can be displayed by using the help button.</property>
</packing>
</child>
</object>
<object class="GtkAdjustment" id="end_row_adj">
<property name="lower">1</property>
<property name="upper">100</property>
<property name="value">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="start_row_adj">
<property name="lower">1</property>
<property name="upper">100</property>
<property name="value">1</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
</interface>

View File

@ -35,6 +35,8 @@
#endif
#include <goffice/utils/go-glib-extras.h>
#include "gnc-csv-account-map.h"
#include "gnc-ui-util.h"
#include "engine-helpers.h"
@ -73,7 +75,10 @@ gchar* gnc_csv_column_type_strs[GNC_CSV_NUM_COL_TYPES] = {N_("None"),
N_("Account"),
N_("Deposit"),
N_("Withdrawal"),
N_("Balance")
N_("Balance"),
N_("Memo"),
N_("Other Account"),
N_("Other Memo")
};
/** A set of sensible defaults for parsing CSV files.
@ -605,7 +610,6 @@ int gnc_csv_parse (GncCsvParseData* parse_data, gboolean guessColTypes, GError**
parse_data->column_types->data[i] = GNC_CSV_NONE;
}
}
return 0;
}
@ -682,10 +686,16 @@ static gboolean trans_property_set (TransProperty* prop, char* str)
case GNC_CSV_DESCRIPTION:
case GNC_CSV_NOTES:
case GNC_CSV_MEMO:
case GNC_CSV_OMEMO:
case GNC_CSV_NUM:
prop->value = g_strdup (str);
return TRUE;
case GNC_CSV_OACCOUNT:
prop->value = gnc_csv_account_map_search (str);
return TRUE;
case GNC_CSV_BALANCE:
case GNC_CSV_DEPOSIT:
case GNC_CSV_WITHDRAWAL:
@ -806,17 +816,35 @@ static void trans_property_list_add (TransProperty* property)
* @param amount The amount of the split
*/
static void trans_add_split (Transaction* trans, Account* account, QofBook* book,
gnc_numeric amount, const char *num)
gnc_numeric amount, const char *num, const char *memo)
{
Split* split = xaccMallocSplit (book);
xaccSplitSetAccount (split, account);
xaccSplitSetParent (split, trans);
xaccSplitSetAmount (split, amount);
xaccSplitSetValue (split, amount);
xaccSplitSetMemo (split, memo);
/* set tran-num and/or split-action per book option */
gnc_set_num_action (trans, split, num, NULL);
}
/** Adds a other split to a transaction.
* @param trans The transaction to add a split to
* @param account The account used for the other split
* @param book The book where the split should be stored
* @param amount The amount of the split
*/
static void trans_add_osplit (Transaction* trans, Account* account, QofBook* book,
gnc_numeric amount, const char *num, const char *memo)
{
Split *osplit = xaccMallocSplit (book);
xaccSplitSetAccount (osplit, account);
xaccSplitSetParent (osplit, trans);
xaccSplitSetAmount (osplit, amount);
xaccSplitSetValue (osplit, gnc_numeric_neg (amount));
xaccSplitSetMemo (osplit, memo);
}
/** Tests a TransPropertyList for having enough essential properties.
* Essential properties are "Date" and one of the following: "Balance", "Deposit", or
* "Withdrawal".
@ -924,6 +952,9 @@ static GncCsvTransLine* trans_property_list_to_trans (TransPropertyList* list, g
gnc_numeric amount = double_to_gnc_numeric (0.0, xaccAccountGetCommoditySCU (list->account),
GNC_HOW_RND_ROUND_HALF_UP);
gchar *num = NULL;
gchar *memo = NULL;
gchar *omemo = NULL;
Account *oaccount = NULL;
/* This flag is set to TRUE if we can use the "Deposit" or "Withdrawal" column. */
gboolean amount_set = FALSE;
@ -968,6 +999,18 @@ static GncCsvTransLine* trans_property_list_to_trans (TransPropertyList* list, g
xaccTransSetNotes (trans_line->trans, (char*)(prop->value));
break;
case GNC_CSV_OACCOUNT:
oaccount = ((Account*)(prop->value));
break;
case GNC_CSV_MEMO:
memo = g_strdup ((char*)(prop->value));
break;
case GNC_CSV_OMEMO:
omemo = g_strdup ((char*)(prop->value));
break;
case GNC_CSV_NUM:
/* the 'num' is saved and passed to 'trans_add_split' below where
* 'gnc_set_num_action' is used to set tran-num and/or split-action
@ -1019,9 +1062,17 @@ static GncCsvTransLine* trans_property_list_to_trans (TransPropertyList* list, g
}
/* Add a split with the cumulative amount value. */
trans_add_split (trans_line->trans, list->account, book, amount, num);
trans_add_split (trans_line->trans, list->account, book, amount, num, memo);
if (oaccount)
trans_add_osplit (trans_line->trans, oaccount, book, amount, num, omemo);
if (num)
g_free (num);
if (memo)
g_free (memo);
if (omemo)
g_free (omemo);
return trans_line;
}
@ -1043,6 +1094,7 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
int i, j, max_cols = 0;
GArray* column_types = parse_data->column_types;
GList *error_lines = NULL, *begin_error_lines = NULL;
Account *home_account = NULL;
/* last_transaction points to the last element in
* parse_data->transactions, or NULL if it's empty. */
@ -1051,28 +1103,21 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
/* Free parse_data->error_lines and parse_data->transactions if they
* already exist. */
if (redo_errors) /* If we're redoing errors, we save freeing until the end. */
{
begin_error_lines = error_lines = parse_data->error_lines;
}
else
{
if (parse_data->error_lines != NULL)
{
g_list_free(parse_data->error_lines);
}
if (parse_data->transactions != NULL)
{
g_list_free (parse_data->transactions);
}
}
parse_data->error_lines = NULL;
if (redo_errors) /* If we're looking only at error data ... */
{
if (parse_data->transactions == NULL)
{
last_transaction = NULL;
}
else
{
/* Move last_transaction to the end. */
@ -1106,9 +1151,33 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
/* This flag is TRUE if there are any errors in this row. */
gboolean errors = FALSE;
gchar* error_message = NULL;
TransPropertyList* list = trans_property_list_new (account, parse_data->date_format, parse_data->currency_format);
TransPropertyList* list;
GncCsvTransLine* trans_line = NULL;
home_account = account;
// If account = NULL, we should have an Account column
if (home_account == NULL)
{
for (j = 0; j < line->len; j++)
{
/* Look for "Account" columns. */
if (column_types->data[j] == GNC_CSV_ACCOUNT)
{
home_account = gnc_csv_account_map_search (line->pdata[j]);
}
}
}
if (home_account == NULL)
{
error_message = g_strdup_printf (_("Account column could not be understood."));
errors = TRUE;
}
else
{
list = trans_property_list_new (home_account, parse_data->date_format, parse_data->currency_format);
for (j = 0; j < line->len; j++)
{
/* We do nothing in "None" or "Account" columns. */
@ -1120,9 +1189,7 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
/* TODO Maybe move error handling to within TransPropertyList functions? */
if (succeeded)
{
trans_property_list_add (property);
}
else
{
errors = TRUE;
@ -1140,8 +1207,8 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
trans_line = trans_property_list_to_trans (list, &error_message);
errors = trans_line == NULL;
}
trans_property_list_free (list);
}
/* If there were errors, add this line to parse_data->error_lines. */
if (errors)
@ -1231,17 +1298,22 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
}
}
if (hasBalanceColumn)
if (hasBalanceColumn) // This is only used if we have one home account
{
Split *split, *osplit;
gnc_numeric balance_offset;
GList *transactions = parse_data->transactions;
if (account != NULL)
home_account = account;
/* balance_offset is how much the balance currently in the account
* differs from what it will be after the transactions are
* imported. This will be sum of all the previous transactions for
* any given transaction. */
gnc_numeric balance_offset = double_to_gnc_numeric (0.0,
xaccAccountGetCommoditySCU (account),
balance_offset = double_to_gnc_numeric (0.0, xaccAccountGetCommoditySCU (home_account),
GNC_HOW_RND_ROUND_HALF_UP);
while (transactions != NULL)
{
GncCsvTransLine* trans_line = (GncCsvTransLine*)transactions->data;
@ -1250,33 +1322,36 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
time64 date = xaccTransGetDate (trans_line->trans);
/* Find what the balance should be by adding the offset to the actual balance. */
gnc_numeric existing_balance = gnc_numeric_add (balance_offset,
xaccAccountGetBalanceAsOfDate (account, date),
xaccAccountGetCommoditySCU (account),
xaccAccountGetBalanceAsOfDate (home_account, date),
xaccAccountGetCommoditySCU (home_account),
GNC_HOW_RND_ROUND_HALF_UP);
/* The amount of the transaction is the difference between the new and existing balance. */
gnc_numeric amount = gnc_numeric_sub (trans_line->balance,
existing_balance,
xaccAccountGetCommoditySCU (account),
xaccAccountGetCommoditySCU (home_account),
GNC_HOW_RND_ROUND_HALF_UP);
SplitList* splits = xaccTransGetSplitList (trans_line->trans);
while (splits)
// Find home account split
split = xaccTransFindSplitByAccount (trans_line->trans, home_account);
xaccSplitSetAmount (split, amount);
xaccSplitSetValue (split, amount);
// If we have two splits, change other side
if (xaccTransCountSplits (trans_line->trans) == 2)
{
SplitList* next_splits = g_list_next (splits);
xaccSplitDestroy ((Split*)splits->data);
splits = next_splits;
osplit = xaccSplitGetOtherSplit (split);
xaccSplitSetAmount (split, amount);
xaccSplitSetValue (split, gnc_numeric_neg (amount));
}
trans_add_split (trans_line->trans, account,
gnc_account_get_book (account), amount, trans_line->num);
if (trans_line->num)
g_free (trans_line->num);
/* This new transaction needs to be added to the balance offset. */
balance_offset = gnc_numeric_add (balance_offset,
amount,
xaccAccountGetCommoditySCU (account),
xaccAccountGetCommoditySCU (home_account),
GNC_HOW_RND_ROUND_HALF_UP);
}
transactions = g_list_next (transactions);
@ -1284,9 +1359,7 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
}
if (redo_errors) /* Now that we're at the end, we do the freeing. */
{
g_list_free (begin_error_lines);
}
/* We need to resize parse_data->column_types since errors may have added columns. */
for (i = 0; i < parse_data->orig_lines->len; i++)
@ -1300,6 +1373,21 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account,
{
parse_data->column_types->data[i] = GNC_CSV_NONE;
}
return 0;
}
gboolean
gnc_csv_parse_check_for_column_type (GncCsvParseData* parse_data, gint type)
{
GArray* column_types = parse_data->column_types;
gboolean ret = FALSE;
int j, ncols = column_types->len; /* ncols is the number of columns in the data. */
for (j = 0; j < ncols; j++)
{
if (column_types->data[j] == type)
ret = TRUE;
}
return ret;
}

View File

@ -47,6 +47,9 @@ enum GncCsvColumnType {GNC_CSV_NONE,
GNC_CSV_DEPOSIT,
GNC_CSV_WITHDRAWAL,
GNC_CSV_BALANCE,
GNC_CSV_MEMO,
GNC_CSV_OACCOUNT,
GNC_CSV_OMEMO,
GNC_CSV_NUM_COL_TYPES
};
@ -134,4 +137,6 @@ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account, gbool
time64 parse_date (const char* date_str, int format);
gboolean gnc_csv_parse_check_for_column_type (GncCsvParseData* parse_data, gint type);
#endif

View File

@ -412,7 +412,7 @@ AccountPickerDialog* gnc_import_account_assist_setup(GtkWidget *parent)
{
AccountPickerDialog * picker;
GtkBuilder *builder;
GtkWidget *button, *box, *h_box;
GtkWidget *box, *h_box;
/* Init the account picker structure */
picker = gnc_import_new_account_picker();
@ -435,13 +435,13 @@ AccountPickerDialog* gnc_import_account_assist_setup(GtkWidget *parent)
picker->account_online_id_label = GTK_WIDGET(gtk_builder_get_object (builder, "online_id_label"));
/* Add the New Account Button */
button = gtk_button_new_with_mnemonic ("_New Account");
picker->new_button = gtk_button_new_with_mnemonic ("_New Account");
h_box = gtk_hbox_new(TRUE, 0);
gtk_box_pack_start(GTK_BOX(h_box), button, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(h_box), picker->new_button, FALSE, FALSE, 0);
gtk_box_pack_start( GTK_BOX(box), h_box, FALSE, FALSE, 6);
gtk_button_set_use_stock (GTK_BUTTON(button), TRUE);
gtk_widget_show (button);
g_signal_connect(button, "clicked",
gtk_button_set_use_stock (GTK_BUTTON(picker->new_button), TRUE);
gtk_widget_show (picker->new_button);
g_signal_connect(picker->new_button, "clicked",
G_CALLBACK(gnc_import_add_account), picker);
build_acct_tree(picker);
@ -454,6 +454,19 @@ AccountPickerDialog* gnc_import_account_assist_setup(GtkWidget *parent)
}
/*******************************************************
* gnc_import_account_assist_disable
*
* disables account picker input.
*******************************************************/
void
gnc_import_account_assist_disable (AccountPickerDialog *picker, gboolean disable)
{
gtk_widget_set_sensitive (picker->account_tree_sw, !disable);
gtk_widget_set_sensitive (picker->new_button, !disable);
}
/*******************************************************
* gnc_import_account_assist_update
*

View File

@ -39,6 +39,7 @@ typedef struct
{
GtkWidget *dialog; /* Dialog Widget */
GtkWidget *assistant; /* assistant Widget */
GtkWidget *new_button; /* new account button Widget */
GncTreeViewAccount *account_tree; /* Account tree */
GtkWidget *account_tree_sw; /* Scroll Window for Account tree */
gboolean auto_create; /* Auto create retAccount, can be used to step over this stage */
@ -146,5 +147,15 @@ AccountPickerDialog * gnc_import_account_assist_setup (GtkWidget *parent);
*/
Account * gnc_import_account_assist_update (AccountPickerDialog *picker);
/** Must be called with an AccountPickerDialog structure allready setup.
Set the sensitivity of the account picker to disable input.
@param Account picker Dialog structure, AccountPickerDialog
@param TRUE to make picker insensitve.
*/
void gnc_import_account_assist_disable (AccountPickerDialog *picker, gboolean disable);
#endif
/**@}*/