void GncPreSplit::create_split (Transaction* trans) { if (created) return; /* Gently refuse to create the split if the basics are not set correctly * This should have been tested before calling this function though! */ auto check = verify_essentials(); if (!check.empty()) { PWARN ("Not creating split because essentials not set properly: %s", check.c_str()); return; } Account *account = nullptr; Account *taccount = nullptr; auto deposit = GncNumeric(); auto withdrawal = GncNumeric(); auto amount = GncNumeric(); if (m_account) account = *m_account; if (m_taccount) taccount = *m_taccount; if (m_deposit) deposit = *m_deposit; if (m_withdrawal) withdrawal = *m_withdrawal; amount = deposit + withdrawal; /* Add a split with the cumulative amount value. */ trans_add_split (trans, account, amount, m_action, m_memo, m_rec_state, m_rec_date, m_price); if (taccount) { /* Note: the current importer assumes at most 2 splits. This means the second split amount * will be the negative of the the first split amount. */ auto inv_price = m_price; if (m_price) inv_price = m_price->inv(); trans_add_split (trans, taccount, -amount, m_taction, m_tmemo, m_trec_state, m_trec_date, inv_price); } created = true; }
/** Create a Transaction from a TransPropertyList. * @param list The list of properties * @param error Contains an error on failure * @return On success, a GncCsvTransLine; on failure, the trans pointer is NULL */ static GncCsvTransLine* trans_property_list_to_trans (TransPropertyList* list, gchar** error) { GncCsvTransLine* trans_line = g_new (GncCsvTransLine, 1); GList* properties_begin = list->properties; QofBook* book = gnc_account_get_book (list->account); gnc_commodity* currency = xaccAccountGetCommodity (list->account); gnc_numeric amount = double_to_gnc_numeric (0.0, xaccAccountGetCommoditySCU (list->account), GNC_HOW_RND_ROUND_HALF_UP); gchar *num = NULL; /* This flag is set to TRUE if we can use the "Deposit" or "Withdrawal" column. */ gboolean amount_set = FALSE; /* The balance is 0 by default. */ trans_line->balance_set = FALSE; trans_line->balance = amount; trans_line->num = NULL; /* We make the line_no -1 just to mark that it hasn't been set. We * may get rid of line_no soon anyway, so it's not particularly * important. */ trans_line->line_no = -1; /* Make sure this is a transaction with all the columns we need. */ if (!trans_property_list_verify_essentials (list, error)) { g_free(trans_line); return NULL; } trans_line->trans = xaccMallocTransaction (book); xaccTransBeginEdit (trans_line->trans); xaccTransSetCurrency (trans_line->trans, currency); /* Go through each of the properties and edit the transaction accordingly. */ list->properties = properties_begin; while (list->properties != NULL) { TransProperty* prop = (TransProperty*)(list->properties->data); switch (prop->type) { case GNC_CSV_DATE: xaccTransSetDatePostedSecsNormalized (trans_line->trans, *((time64*)(prop->value))); break; case GNC_CSV_DESCRIPTION: xaccTransSetDescription (trans_line->trans, (char*)(prop->value)); break; case GNC_CSV_NOTES: xaccTransSetNotes (trans_line->trans, (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 * per book option */ num = g_strdup ((char*)(prop->value)); /* the 'num' is also saved and used in 'gnc_csv_parse_to_trans' when * it calls 'trans_add_split' after deleting the splits added below * when a balance is used by the user */ trans_line->num = g_strdup ((char*)(prop->value)); break; case GNC_CSV_DEPOSIT: /* Add deposits to the existing amount. */ if (prop->value != NULL) { amount = gnc_numeric_add (*((gnc_numeric*)(prop->value)), amount, xaccAccountGetCommoditySCU (list->account), GNC_HOW_RND_ROUND_HALF_UP); amount_set = TRUE; /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */ trans_line->balance_set = FALSE; } break; case GNC_CSV_WITHDRAWAL: /* Withdrawals are just negative deposits. */ if (prop->value != NULL) { amount = gnc_numeric_add (gnc_numeric_neg(*((gnc_numeric*)(prop->value))), amount, xaccAccountGetCommoditySCU (list->account), GNC_HOW_RND_ROUND_HALF_UP); amount_set = TRUE; /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */ trans_line->balance_set = FALSE; } break; case GNC_CSV_BALANCE: /* The balance gets stored in a separate field in trans_line. */ /* We will use the "Deposit" and "Withdrawal" columns in preference to "Balance". */ if (!amount_set && prop->value != NULL) { /* This gets put into the actual transaction at the end of gnc_csv_parse_to_trans. */ trans_line->balance = *((gnc_numeric*)(prop->value)); trans_line->balance_set = TRUE; } break; } list->properties = g_list_next (list->properties); } /* Add a split with the cumulative amount value. */ trans_add_split (trans_line->trans, list->account, book, amount, num); if (num) g_free (num); return trans_line; }
/** Creates a list of transactions from parsed data. Transactions that * could be created from rows are placed in parse_data->transactions; * rows that fail are placed in parse_data->error_lines. (Note: there * is no way for this function to "fail," i.e. it only returns 0, so * it may be changed to a void function in the future.) * @param parse_data Data that is being parsed * @param account Account with which transactions are created * @param redo_errors TRUE to convert only error data, FALSE for all data * @return 0 on success, 1 on failure */ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account, gboolean redo_errors) { gboolean hasBalanceColumn; int i, j, max_cols = 0; GArray* column_types = parse_data->column_types; GList *error_lines = NULL, *begin_error_lines = NULL; /* last_transaction points to the last element in * parse_data->transactions, or NULL if it's empty. */ GList* last_transaction = NULL; /* 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. */ last_transaction = parse_data->transactions; while (g_list_next (last_transaction) != NULL) { last_transaction = g_list_next (last_transaction); } } /* ... we use only the lines in error_lines. */ if (error_lines == NULL) i = parse_data->orig_lines->len; /* Don't go into the for loop. */ else i = GPOINTER_TO_INT(error_lines->data); } else /* Otherwise, we look at all the data. */ { /* The following while-loop effectively behaves like the following for-loop: * for(i = 0; i < parse_data->orig_lines->len; i++). */ i = parse_data->start_row; last_transaction = NULL; } /* set parse_data->end_row to number of lines */ if (parse_data->end_row > parse_data->orig_lines->len) parse_data->end_row = parse_data->orig_lines->len; while (i < parse_data->end_row) { GPtrArray* line = parse_data->orig_lines->pdata[i]; /* 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); GncCsvTransLine* trans_line = NULL; for (j = 0; j < line->len; j++) { /* We do nothing in "None" or "Account" columns. */ if ((column_types->data[j] != GNC_CSV_NONE) && (column_types->data[j] != GNC_CSV_ACCOUNT)) { /* Affect the transaction appropriately. */ TransProperty* property = trans_property_new (column_types->data[j], list); gboolean succeeded = trans_property_set (property, line->pdata[j]); /* TODO Maybe move error handling to within TransPropertyList functions? */ if (succeeded) { trans_property_list_add (property); } else { errors = TRUE; error_message = g_strdup_printf (_("%s column could not be understood."), _(gnc_csv_column_type_strs[property->type])); trans_property_free (property); break; } } } /* If we had success, add the transaction to parse_data->transaction. */ if (!errors) { 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) { parse_data->error_lines = g_list_append (parse_data->error_lines, GINT_TO_POINTER(i)); /* If there's already an error message, we need to replace it. */ if (line->len > (int)(parse_data->orig_row_lengths->data[i])) { g_free(line->pdata[line->len - 1]); line->pdata[line->len - 1] = error_message; } else { /* Put the error message at the end of the line. */ g_ptr_array_add (line, error_message); } } else { /* If all went well, add this transaction to the list. */ trans_line->line_no = i; /* We keep the transactions sorted by date. We start at the end * of the list and go backward, simply because the file itself * is probably also sorted by date (but we need to handle the * exception anyway). */ /* If we can just put it at the end, do so and increment last_transaction. */ if (last_transaction == NULL || xaccTransGetDate (((GncCsvTransLine*)(last_transaction->data))->trans) <= xaccTransGetDate (trans_line->trans)) { parse_data->transactions = g_list_append (parse_data->transactions, trans_line); /* If this is the first transaction, we need to get last_transaction on track. */ if (last_transaction == NULL) last_transaction = parse_data->transactions; else /* Otherwise, we can just continue. */ last_transaction = g_list_next (last_transaction); } /* Otherwise, search backward for the correct spot. */ else { GList* insertion_spot = last_transaction; while (insertion_spot != NULL && xaccTransGetDate (((GncCsvTransLine*)(insertion_spot->data))->trans) > xaccTransGetDate (trans_line->trans)) { insertion_spot = g_list_previous (insertion_spot); } /* Move insertion_spot one location forward since we have to * use the g_list_insert_before function. */ if (insertion_spot == NULL) /* We need to handle the case of inserting at the beginning of the list. */ insertion_spot = parse_data->transactions; else insertion_spot = g_list_next (insertion_spot); parse_data->transactions = g_list_insert_before (parse_data->transactions, insertion_spot, trans_line); } } /* Increment to the next row. */ if (redo_errors) { /* Move to the next error line in the list. */ error_lines = g_list_next (error_lines); if (error_lines == NULL) i = parse_data->orig_lines->len; /* Don't continue the for loop. */ else i = GPOINTER_TO_INT(error_lines->data); } else { if (parse_data->skip_rows == FALSE) i++; else i = i + 2; } } /* If we have a balance column, set the appropriate amounts on the transactions. */ hasBalanceColumn = FALSE; for (i = 0; i < parse_data->column_types->len; i++) { if (parse_data->column_types->data[i] == GNC_CSV_BALANCE) { hasBalanceColumn = TRUE; break; } } if (hasBalanceColumn) { GList* transactions = parse_data->transactions; /* 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), GNC_HOW_RND_ROUND_HALF_UP); while (transactions != NULL) { GncCsvTransLine* trans_line = (GncCsvTransLine*)transactions->data; if (trans_line->balance_set) { 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), 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), GNC_HOW_RND_ROUND_HALF_UP); SplitList* splits = xaccTransGetSplitList (trans_line->trans); while (splits) { SplitList* next_splits = g_list_next (splits); xaccSplitDestroy ((Split*)splits->data); splits = next_splits; } 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), GNC_HOW_RND_ROUND_HALF_UP); } transactions = g_list_next (transactions); } } 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++) { if (max_cols < ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len) max_cols = ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len; } i = parse_data->column_types->len; parse_data->column_types = g_array_set_size (parse_data->column_types, max_cols); for (; i < max_cols; i++) { parse_data->column_types->data[i] = GNC_CSV_NONE; } return 0; }