Transaction * gnc_ab_trans_to_gnc(const AB_TRANSACTION *ab_trans, Account *gnc_acc) { QofBook *book; Transaction *gnc_trans; const gchar *fitid; const GWEN_TIME *valuta_date; time64 current_time; const char *custref; gchar *description; Split *split; gchar *memo; g_return_val_if_fail(ab_trans && gnc_acc, NULL); /* Create new GnuCash transaction for the given AqBanking one */ book = gnc_account_get_book(gnc_acc); gnc_trans = xaccMallocTransaction(book); xaccTransBeginEdit(gnc_trans); /* Date / Time */ valuta_date = AB_Transaction_GetValutaDate(ab_trans); if (!valuta_date) { const GWEN_TIME *normal_date = AB_Transaction_GetDate(ab_trans); if (normal_date) valuta_date = normal_date; } if (valuta_date) xaccTransSetDatePostedSecsNormalized(gnc_trans, GWEN_Time_toTime_t(valuta_date)); else g_warning("transaction_cb: Oops, date 'valuta_date' was NULL"); xaccTransSetDateEnteredSecs(gnc_trans, gnc_time (NULL)); /* Currency. We take simply the default currency of the gnucash account */ xaccTransSetCurrency(gnc_trans, xaccAccountGetCommodity(gnc_acc)); /* Trans-Num or Split-Action set with gnc_set_num_action below per book * option */ /* Description */ description = gnc_ab_description_to_gnc(ab_trans); xaccTransSetDescription(gnc_trans, description); g_free(description); /* Notes. */ /* xaccTransSetNotes(gnc_trans, g_notes); */ /* But Nobody ever uses the Notes field? */ /* Add one split */ split = xaccMallocSplit(book); xaccSplitSetParent(split, gnc_trans); xaccSplitSetAccount(split, gnc_acc); /* Set the transaction number or split action field based on book option. * We use the "customer reference", if there is one. */ custref = AB_Transaction_GetCustomerReference(ab_trans); if (custref && *custref && g_ascii_strncasecmp(custref, "NONREF", 6) != 0) gnc_set_num_action (gnc_trans, split, custref, NULL); /* Set OFX unique transaction ID */ fitid = AB_Transaction_GetFiId(ab_trans); if (fitid && *fitid) gnc_import_set_split_online_id(split, fitid); { /* Amount into the split */ const AB_VALUE *ab_value = AB_Transaction_GetValue(ab_trans); double d_value = ab_value ? AB_Value_GetValueAsDouble (ab_value) : 0.0; AB_TRANSACTION_TYPE ab_type = AB_Transaction_GetType (ab_trans); gnc_numeric gnc_amount; /*printf("Transaction with value %f has type %d\n", d_value, ab_type);*/ /* If the value is positive, but the transaction type says the money is transferred away from our account (Transfer instead of DebitNote), we switch the value to negative. */ if (d_value > 0.0 && ab_type == AB_Transaction_TypeTransfer) d_value = -d_value; gnc_amount = double_to_gnc_numeric( d_value, xaccAccountGetCommoditySCU(gnc_acc), GNC_HOW_RND_ROUND_HALF_UP); if (!ab_value) g_warning("transaction_cb: Oops, value was NULL. Using 0"); xaccSplitSetBaseValue(split, gnc_amount, xaccAccountGetCommodity(gnc_acc)); } /* Memo in the Split. */ memo = gnc_ab_memo_to_gnc(ab_trans); xaccSplitSetMemo(split, memo); g_free(memo); return gnc_trans; }
static void gnc_split_register_save_amount_values (SRSaveData *sd, SplitRegister *reg) { Account *acc; gnc_numeric new_amount, convrate, amtconv, value; gnc_commodity *curr, *reg_com, *xfer_com; Account *xfer_acc; new_amount = gnc_split_register_debcred_cell_value (reg); acc = gnc_split_register_get_default_account (reg); xfer_acc = xaccSplitGetAccount (sd->split); xfer_com = xaccAccountGetCommodity (xfer_acc); reg_com = xaccAccountGetCommodity (acc); curr = xaccTransGetCurrency (sd->trans); /* First, compute the conversion rate to convert the value to the * amount. */ amtconv = convrate = gnc_split_register_get_rate_cell (reg, RATE_CELL); if (acc && gnc_split_register_needs_conv_rate (reg, sd->trans, acc)) { /* If we are in an expanded register and the xfer_acc->comm != * reg_acc->comm then we need to compute the convrate here. * Otherwise, we _can_ use the rate_cell! */ if (sd->reg_expanded && ! gnc_commodity_equal (reg_com, xfer_com)) amtconv = xaccTransGetAccountConvRate(sd->trans, acc); } if (xaccTransUseTradingAccounts (sd->trans)) { /* Using currency accounts, the amount is probably really the amount and not the value. */ gboolean is_amount; if (reg->type == STOCK_REGISTER || reg->type == CURRENCY_REGISTER || reg->type == PORTFOLIO_LEDGER) { if (xaccAccountIsPriced(xfer_acc) || !gnc_commodity_is_iso(xaccAccountGetCommodity(xfer_acc))) is_amount = FALSE; else is_amount = TRUE; } else { is_amount = TRUE; } if (is_amount) { xaccSplitSetAmount(sd->split, new_amount); if (gnc_split_register_split_needs_amount (reg, sd->split)) { value = gnc_numeric_div(new_amount, amtconv, gnc_commodity_get_fraction(curr), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue(sd->split, value); } else xaccSplitSetValue(sd->split, new_amount); } else { xaccSplitSetValue(sd->split, new_amount); } return; } /* How to interpret new_amount depends on our view of this * transaction. If we're sitting in an account with the same * commodity as the transaction, then we can set the Value and then * compute the amount. Otherwise we are setting the "converted * value". This means we need to convert new_amount to the actual * 'value' by dividing by the convrate in order to set the value. */ /* Now compute/set the split value. Amount is in the register * currency but we need to convert to the txn currency. */ if (acc && gnc_split_register_needs_conv_rate (reg, sd->trans, acc)) { /* convert the amount to the Value ... */ value = gnc_numeric_div (new_amount, amtconv, gnc_commodity_get_fraction (curr), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue (sd->split, value); } else xaccSplitSetValue (sd->split, new_amount); /* Now re-compute the Amount from the Value. We may need to convert * from the Value back to the amount here using the convrate from * earlier. */ value = xaccSplitGetValue (sd->split); if (gnc_split_register_split_needs_amount (reg, sd->split)) { acc = xaccSplitGetAccount (sd->split); new_amount = gnc_numeric_mul (value, convrate, xaccAccountGetCommoditySCU (acc), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetAmount (sd->split, new_amount); } }
static void gnc_split_register_save_cells (gpointer save_data, gpointer user_data) { SRSaveData *sd = save_data; SplitRegister *reg = user_data; Split *other_split; gnc_commodity *txn_cur; gnc_numeric rate = gnc_numeric_zero(); g_return_if_fail (sd != NULL); if (!sd->do_scrub) return; other_split = xaccSplitGetOtherSplit (sd->split); txn_cur = xaccTransGetCurrency (sd->trans); xaccSplitScrub (sd->split); rate = gnc_split_register_get_rate_cell (reg, RATE_CELL); if (other_split && !sd->reg_expanded) { gnc_numeric amount, value = xaccSplitGetValue (sd->split); Account *acc; gboolean split_needs_amount; split_needs_amount = gnc_split_register_split_needs_amount(reg, sd->split); /* We are changing the rate on the current split, but it was not * handled in the debcred handler, so we need to do it here. */ if (!sd->handled_dc && split_needs_amount && !gnc_numeric_zero_p (rate)) { gnc_numeric amount = xaccSplitGetAmount (sd->split); value = gnc_numeric_div( amount, rate, gnc_commodity_get_fraction(txn_cur), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue (sd->split, value); /* XXX: do we need to set the amount on the other split? */ } /* Now reverse the value for the other split */ value = gnc_numeric_neg (value); if (gnc_split_register_split_needs_amount (reg, other_split)) { acc = xaccSplitGetAccount (other_split); /* If we don't have an exchange rate then figure it out. Or, if * BOTH splits require an amount, then most likely we're in the * strange case of having a transaction currency different than * _both_ accounts -- so grab the other exchange rate. */ if (gnc_numeric_zero_p (rate) || split_needs_amount) rate = xaccTransGetAccountConvRate(xaccSplitGetParent (other_split), acc); amount = gnc_numeric_mul (value, rate, xaccAccountGetCommoditySCU (acc), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetAmount (other_split, amount); } xaccSplitSetValue (other_split, value); xaccSplitScrub (other_split); } else if (gnc_split_register_split_needs_amount (reg, sd->split) && ! gnc_numeric_zero_p (rate)) { /* this is either a multi-split or expanded transaction, so only * deal with this split... In particular we need to reset the * Value if the conv-rate changed. * * If we handled the debcred then no need to do anything there -- * the debcred handler did all the computation. If NOT, then the * convrate changed -- reset the value from the amount. */ if (!sd->handled_dc) { gnc_split_register_save_amount_values (sd, reg); #if 0 gnc_numeric value, amount; amount = xaccSplitGetAmount (sd->split); value = gnc_numeric_div (amount, rate, gnc_commodity_get_fraction (txn_cur), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue (sd->split, value); #endif } } }
void gnc_tree_util_split_reg_save_amount_values (GncTreeViewSplitReg *view, Transaction *trans, Split *split, gnc_numeric input) { GncTreeModelSplitReg *model; Account *acc; gnc_numeric new_amount, convrate, amtconv, value; gnc_commodity *curr, *reg_com, *xfer_com; Account *xfer_acc; ENTER("View is %p, trans is %p, split is %p, input is %s", view, trans, split, gnc_numeric_to_string (input)); model = gnc_tree_view_split_reg_get_model_from_view (view); new_amount = input; acc = gnc_tree_model_split_reg_get_anchor (model); xfer_acc = xaccSplitGetAccount (split); xfer_com = xaccAccountGetCommodity (xfer_acc); reg_com = xaccAccountGetCommodity (acc); curr = xaccTransGetCurrency (trans); if (!xaccTransGetRateForCommodity (trans, reg_com, NULL, &convrate)) convrate = gnc_numeric_create (100, 100); amtconv = convrate; if (gnc_tree_util_split_reg_needs_conv_rate (view, trans, acc)) { /* If we are in an expanded register and the xfer_acc->comm != * reg_acc->comm then we need to compute the convrate here. * Otherwise, we _can_ use the rate_cell! */ if (gnc_commodity_equal (reg_com, xfer_com)) amtconv = xaccTransGetAccountConvRate (trans, acc); } if (xaccTransUseTradingAccounts (trans)) { /* Using currency accounts, the amount is probably really the amount and not the value. */ gboolean is_amount; if (model->type == STOCK_REGISTER2 || model->type == CURRENCY_REGISTER2 || model->type == PORTFOLIO_LEDGER2) { if (xaccAccountIsPriced (xfer_acc) || !gnc_commodity_is_iso (xaccAccountGetCommodity (xfer_acc))) is_amount = FALSE; else is_amount = TRUE; } else { is_amount = TRUE; } if (is_amount) { xaccSplitSetAmount (split, new_amount); if (gnc_tree_util_split_reg_needs_amount (view, split)) { value = gnc_numeric_div (new_amount, amtconv, gnc_commodity_get_fraction (curr), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue (split, value); } else xaccSplitSetValue (split, new_amount); } else { xaccSplitSetValue (split, new_amount); } LEAVE(" "); return; } /* How to interpret new_amount depends on our view of this * transaction. If we're sitting in an account with the same * commodity as the transaction, then we can set the Value and then * compute the amount. Otherwise we are setting the "converted * value". This means we need to convert new_amount to the actual * 'value' by dividing by the convrate in order to set the value. */ /* Now compute/set the split value. Amount is in the register * currency but we need to convert to the txn currency. */ if (gnc_tree_util_split_reg_needs_conv_rate (view, trans, acc)) { /* convert the amount to the Value ... */ value = gnc_numeric_div (new_amount, amtconv, gnc_commodity_get_fraction (curr), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue (split, value); } else { xaccSplitSetValue (split, new_amount); } /* Now re-compute the Amount from the Value. We may need to convert * from the Value back to the amount here using the convrate from * earlier. */ value = xaccSplitGetValue (split); if (gnc_tree_util_split_reg_needs_amount (view, split)) { acc = xaccSplitGetAccount (split); new_amount = gnc_numeric_mul (value, convrate, xaccAccountGetCommoditySCU (acc), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetAmount (split, new_amount); } else { xaccSplitSetAmount (split, value); } LEAVE(" "); }
void xaccSplitScrub (Split *split) { Account *account; Transaction *trans; gnc_numeric value, amount; gnc_commodity *currency, *acc_commodity; int scu; if (!split) return; ENTER ("(split=%p)", split); trans = xaccSplitGetParent (split); if (!trans) { LEAVE("no trans"); return; } account = xaccSplitGetAccount (split); /* If there's no account, this split is an orphan. * We need to fix that first, before proceeding. */ if (!account) { xaccTransScrubOrphans (trans); account = xaccSplitGetAccount (split); } /* Grrr... the register gnc_split_register_load() line 203 of * src/register/ledger-core/split-register-load.c will create * free-floating bogus transactions. Ignore these for now ... */ if (!account) { PINFO ("Free Floating Transaction!"); LEAVE ("no account"); return; } /* Split amounts and values should be valid numbers */ value = xaccSplitGetValue (split); if (gnc_numeric_check (value)) { value = gnc_numeric_zero(); xaccSplitSetValue (split, value); } amount = xaccSplitGetAmount (split); if (gnc_numeric_check (amount)) { amount = gnc_numeric_zero(); xaccSplitSetAmount (split, amount); } currency = xaccTransGetCurrency (trans); /* If the account doesn't have a commodity, * we should attempt to fix that first. */ acc_commodity = xaccAccountGetCommodity(account); if (!acc_commodity) { xaccAccountScrubCommodity (account); } if (!acc_commodity || !gnc_commodity_equiv(acc_commodity, currency)) { LEAVE ("(split=%p) inequiv currency", split); return; } scu = MIN (xaccAccountGetCommoditySCU (account), gnc_commodity_get_fraction (currency)); if (gnc_numeric_same (amount, value, scu, GNC_HOW_RND_ROUND_HALF_UP)) { LEAVE("(split=%p) different values", split); return; } /* * This will be hit every time you answer yes to the dialog "The * current transaction has changed. Would you like to record it. */ PINFO ("Adjusted split with mismatched values, desc=\"%s\" memo=\"%s\"" " old amount %s %s, new amount %s", trans->description, split->memo, gnc_num_dbg_to_string (xaccSplitGetAmount(split)), gnc_commodity_get_mnemonic (currency), gnc_num_dbg_to_string (xaccSplitGetValue(split))); xaccTransBeginEdit (trans); xaccSplitSetAmount (split, value); xaccTransCommitEdit (trans); LEAVE ("(split=%p)", split); }
void gnc_ab_maketrans(GtkWidget *parent, Account *gnc_acc, GncABTransType trans_type) { AB_BANKING *api; gboolean online = FALSE; AB_ACCOUNT *ab_acc; GList *templates = NULL; GncABTransDialog *td = NULL; gboolean successful = FALSE; gboolean aborted = FALSE; g_return_if_fail(parent && gnc_acc); /* Get the API */ api = gnc_AB_BANKING_new(); if (!api) { g_warning("gnc_ab_maketrans: Couldn't get AqBanking API"); return; } if (AB_Banking_OnlineInit(api #ifdef AQBANKING_VERSION_4_EXACTLY , 0 #endif ) != 0) { g_warning("gnc_ab_maketrans: Couldn't initialize AqBanking API"); goto cleanup; } online = TRUE; /* Get the AqBanking Account */ ab_acc = gnc_ab_get_ab_account(api, gnc_acc); if (!ab_acc) { g_warning("gnc_ab_gettrans: No AqBanking account found"); gnc_error_dialog(parent, _("No valid online banking account assigned.")); goto cleanup; } /* Get list of template transactions */ templates = gnc_ab_trans_templ_list_new_from_book( gnc_account_get_book(gnc_acc)); /* Create new ABTransDialog */ td = gnc_ab_trans_dialog_new(parent, ab_acc, xaccAccountGetCommoditySCU(gnc_acc), trans_type, templates); templates = NULL; /* Repeat until AqBanking action was successful or user pressed cancel */ do { GncGWENGui *gui = NULL; gint result; gboolean changed; const AB_TRANSACTION *ab_trans; AB_JOB *job = NULL; AB_JOB_LIST2 *job_list = NULL; XferDialog *xfer_dialog = NULL; gnc_numeric amount; gchar *description; gchar *memo; Transaction *gnc_trans = NULL; AB_IMEXPORTER_CONTEXT *context = NULL; AB_JOB_STATUS job_status; GncABImExContextImport *ieci = NULL; /* Get a GUI object */ gui = gnc_GWEN_Gui_get(parent); if (!gui) { g_warning("gnc_ab_maketrans: Couldn't initialize Gwenhywfar GUI"); aborted = TRUE; goto repeat; } /* Let the user enter the values */ result = gnc_ab_trans_dialog_run_until_ok(td); /* Save the templates */ templates = gnc_ab_trans_dialog_get_templ(td, &changed); if (changed) save_templates(parent, gnc_acc, templates, (result == GNC_RESPONSE_NOW)); g_list_free(templates); templates = NULL; if (result != GNC_RESPONSE_NOW && result != GNC_RESPONSE_LATER) { aborted = TRUE; goto repeat; } /* Get a job and enqueue it */ ab_trans = gnc_ab_trans_dialog_get_ab_trans(td); job = gnc_ab_trans_dialog_get_job(td); if (!job || AB_Job_CheckAvailability(job #ifndef AQBANKING_VERSION_5_PLUS , 0 #endif )) { if (!gnc_verify_dialog( parent, FALSE, "%s", _("The backend found an error during the preparation " "of the job. It is not possible to execute this job. \n" "\n" "Most probable the bank does not support your chosen " "job or your Online Banking account does not have the permission " "to execute this job. More error messages might be " "visible on your console log.\n" "\n" "Do you want to enter the job again?"))) aborted = TRUE; goto repeat; } job_list = AB_Job_List2_new(); AB_Job_List2_PushBack(job_list, job); /* Setup a Transfer Dialog for the GnuCash transaction */ xfer_dialog = gnc_xfer_dialog(gnc_ab_trans_dialog_get_parent(td), gnc_acc); switch (trans_type) { case SINGLE_DEBITNOTE: gnc_xfer_dialog_set_title( xfer_dialog, _("Online Banking Direct Debit Note")); gnc_xfer_dialog_lock_to_account_tree(xfer_dialog); break; case SINGLE_INTERNAL_TRANSFER: gnc_xfer_dialog_set_title( xfer_dialog, _("Online Banking Bank-Internal Transfer")); gnc_xfer_dialog_lock_from_account_tree(xfer_dialog); break; case SEPA_TRANSFER: gnc_xfer_dialog_set_title( xfer_dialog, _("Online Banking European (SEPA) Transfer")); gnc_xfer_dialog_lock_from_account_tree(xfer_dialog); break; case SEPA_DEBITNOTE: gnc_xfer_dialog_set_title( xfer_dialog, _("Online Banking European (SEPA) Debit Note")); gnc_xfer_dialog_lock_to_account_tree(xfer_dialog); break; case SINGLE_TRANSFER: default: gnc_xfer_dialog_set_title( xfer_dialog, _("Online Banking Transaction")); gnc_xfer_dialog_lock_from_account_tree(xfer_dialog); } gnc_xfer_dialog_set_to_show_button_active(xfer_dialog, TRUE); amount = double_to_gnc_numeric( AB_Value_GetValueAsDouble(AB_Transaction_GetValue(ab_trans)), xaccAccountGetCommoditySCU(gnc_acc), GNC_HOW_RND_ROUND_HALF_UP); gnc_xfer_dialog_set_amount(xfer_dialog, amount); gnc_xfer_dialog_set_amount_sensitive(xfer_dialog, FALSE); gnc_xfer_dialog_set_date_sensitive(xfer_dialog, FALSE); description = gnc_ab_description_to_gnc(ab_trans); gnc_xfer_dialog_set_description(xfer_dialog, description); g_free(description); memo = gnc_ab_memo_to_gnc(ab_trans); gnc_xfer_dialog_set_memo(xfer_dialog, memo); g_free(memo); gnc_xfer_dialog_set_txn_cb(xfer_dialog, txn_created_cb, &gnc_trans); /* And run it */ successful = gnc_xfer_dialog_run_until_done(xfer_dialog); /* On cancel, go back to the AB transaction dialog */ if (!successful || !gnc_trans) { successful = FALSE; goto repeat; } if (result == GNC_RESPONSE_NOW) { /* Create a context to store possible results */ context = AB_ImExporterContext_new(); gui = gnc_GWEN_Gui_get(parent); if (!gui) { g_warning("gnc_ab_maketrans: Couldn't initialize Gwenhywfar GUI"); aborted = TRUE; goto repeat; } /* Finally, execute the job */ AB_Banking_ExecuteJobs(api, job_list, context #ifndef AQBANKING_VERSION_5_PLUS , 0 #endif ); /* Ignore the return value of AB_Banking_ExecuteJobs(), as the job's * status always describes better whether the job was actually * transferred to and accepted by the bank. See also * http://lists.gnucash.org/pipermail/gnucash-de/2008-September/006389.html */ job_status = AB_Job_GetStatus(job); if (job_status != AB_Job_StatusFinished && job_status != AB_Job_StatusPending) { successful = FALSE; if (!gnc_verify_dialog( parent, FALSE, "%s", _("An error occurred while executing the job. Please check " "the log window for the exact error message.\n" "\n" "Do you want to enter the job again?"))) { aborted = TRUE; } } else { successful = TRUE; } if (successful) { /* Import the results, awaiting nothing */ ieci = gnc_ab_import_context(context, 0, FALSE, NULL, parent); } } /* Simply ignore any other case */ repeat: /* Clean up */ if (gnc_trans && !successful) { xaccTransBeginEdit(gnc_trans); xaccTransDestroy(gnc_trans); xaccTransCommitEdit(gnc_trans); gnc_trans = NULL; } if (ieci) g_free(ieci); if (context) AB_ImExporterContext_free(context); if (job_list) { AB_Job_List2_free(job_list); job_list = NULL; } if (job) { AB_Job_free(job); job = NULL; } if (gui) { gnc_GWEN_Gui_release(gui); gui = NULL; } } while (!successful && !aborted); cleanup: if (td) gnc_ab_trans_dialog_free(td); if (online) #ifdef AQBANKING_VERSION_4_EXACTLY AB_Banking_OnlineFini(api, 0); #else AB_Banking_OnlineFini(api); #endif gnc_AB_BANKING_fini(api); }
static void test_transaction (void) { int i; for (i = 0; i < 50; i++) { Transaction* ran_trn; xmlNodePtr test_node; gnc_commodity* com, *new_com; gchar* filename1; int fd; /* The next line exists for its side effect of creating the * account tree. */ get_random_account_tree (book); ran_trn = get_random_transaction (book); new_com = get_random_commodity (book); if (!ran_trn) { failure_args ("transaction_xml", __FILE__, __LINE__, "get_random_transaction returned NULL"); return; } { /* xaccAccountInsertSplit can reorder the splits. */ GList* list = g_list_copy (xaccTransGetSplitList (ran_trn)); GList* node = list; for (; node; node = node->next) { Split* s = static_cast<decltype (s)> (node->data); Account* a = xaccMallocAccount (book); xaccAccountBeginEdit (a); xaccAccountSetCommodity (a, new_com); xaccAccountSetCommoditySCU (a, xaccSplitGetAmount (s).denom); xaccAccountInsertSplit (a, s); xaccAccountCommitEdit (a); } g_list_free (list); } com = xaccTransGetCurrency (ran_trn); test_node = gnc_transaction_dom_tree_create (ran_trn); if (!test_node) { failure_args ("transaction_xml", __FILE__, __LINE__, "gnc_transaction_dom_tree_create returned NULL"); really_get_rid_of_transaction (ran_trn); continue; } auto compare_msg = node_and_transaction_equal (test_node, ran_trn); if (compare_msg != nullptr) { failure_args ("transaction_xml", __FILE__, __LINE__, "node and transaction were not equal: %s", compare_msg); xmlElemDump (stdout, NULL, test_node); printf ("\n"); fflush (stdout); xmlFreeNode (test_node); really_get_rid_of_transaction (ran_trn); continue; } else { success_args ("transaction_xml", __FILE__, __LINE__, "%d", i); } filename1 = g_strdup_printf ("test_file_XXXXXX"); fd = g_mkstemp (filename1); write_dom_node_to_file (test_node, fd); close (fd); { GList* node = xaccTransGetSplitList (ran_trn); for (; node; node = node->next) { Split* s = static_cast<decltype (s)> (node->data); Account* a1 = xaccSplitGetAccount (s); Account* a2 = xaccMallocAccount (book); xaccAccountBeginEdit (a2); xaccAccountSetCommoditySCU (a2, xaccAccountGetCommoditySCU (a1)); xaccAccountSetGUID (a2, xaccAccountGetGUID (a1)); xaccAccountCommitEdit (a2); } } { sixtp* parser; tran_data data; const char* msg = "[xaccAccountScrubCommodity()] Account \"\" does not have a commodity!"; const char* logdomain = "gnc.engine.scrub"; GLogLevelFlags loglevel = static_cast<decltype (loglevel)> (G_LOG_LEVEL_CRITICAL); TestErrorStruct check = { loglevel, const_cast<char*> (logdomain), const_cast<char*> (msg) }; g_log_set_handler (logdomain, loglevel, (GLogFunc)test_checked_handler, &check); data.trn = ran_trn; data.com = com; data.value = i; parser = gnc_transaction_sixtp_parser_create (); if (!gnc_xml_parse_file (parser, filename1, test_add_transaction, (gpointer)&data, book)) { failure_args ("gnc_xml_parse_file returned FALSE", __FILE__, __LINE__, "%d", i); } else really_get_rid_of_transaction (data.new_trn); } /* no handling of circular data structures. We'll do that later */ /* sixtp_destroy(parser); */ g_unlink (filename1); g_free (filename1); really_get_rid_of_transaction (ran_trn); xmlFreeNode (test_node); } }
void gnc_autoclear_window_ok_cb (GtkWidget *widget, AutoClearWindow *data) { GList *node, *nc_list = 0, *toclear_list = 0; gnc_numeric toclear_value; GHashTable *sack; gtk_label_set_text(data->status_label, _("Searching for splits to clear ...")); /* Value we have to reach */ toclear_value = gnc_amount_edit_get_amount(data->end_value); toclear_value = gnc_numeric_convert(toclear_value, xaccAccountGetCommoditySCU(data->account), GNC_HOW_RND_NEVER); /* Extract which splits are not cleared and compute the amount we have to clear */ for (node = xaccAccountGetSplitList(data->account); node; node = node->next) { Split *split = (Split *)node->data; char recn; gnc_numeric value; recn = xaccSplitGetReconcile (split); value = xaccSplitGetAmount (split); if (recn == NREC) nc_list = g_list_append(nc_list, split); else toclear_value = gnc_numeric_sub_fixed(toclear_value, value); } /* Pretty print information */ PINFO("Amount to clear: %s\n", gnc_numeric_to_string(toclear_value)); PINFO("Available splits:\n"); for (node = nc_list; node; node = node->next) { Split *split = (Split *)node->data; gnc_numeric value = xaccSplitGetAmount (split); PINFO(" %s\n", gnc_numeric_to_string(value)); } /* Run knapsack */ /* Entries in the hash table are: * - key = amount to which we know how to clear (freed by GHashTable) * - value = last split we used to clear this amount (not managed by GHashTable) */ PINFO("Knapsacking ...\n"); sack = g_hash_table_new_full (ght_gnc_numeric_hash, ght_gnc_numeric_equal, g_free, NULL); for (node = nc_list; node; node = node->next) { Split *split = (Split *)node->data; gnc_numeric split_value = xaccSplitGetAmount(split); GList *node; struct _sack_foreach_data_t data[1]; data->split_value = split_value; data->reachable_list = 0; PINFO(" Split value: %s\n", gnc_numeric_to_string(split_value)); /* For each value in the sack, compute a new reachable value */ g_hash_table_foreach (sack, sack_foreach_func, data); /* Add the value of the split itself to the reachable_list */ data->reachable_list = g_list_append(data->reachable_list, g_memdup(&split_value, sizeof(gnc_numeric))); /* Add everything to the sack, looking out for duplicates */ for (node = data->reachable_list; node; node = node->next) { gnc_numeric *reachable_value = node->data; Split *toinsert_split = split; PINFO(" Reachable value: %s ", gnc_numeric_to_string(*reachable_value)); /* Check if it already exists */ if (g_hash_table_lookup_extended(sack, reachable_value, NULL, NULL)) { /* If yes, we are in trouble, we reached an amount using two solutions */ toinsert_split = NULL; PINFO("dup"); } g_hash_table_insert (sack, reachable_value, toinsert_split); PINFO("\n"); } g_list_free(data->reachable_list); } /* Check solution */ PINFO("Rebuilding solution ...\n"); while (!gnc_numeric_zero_p(toclear_value)) { gpointer psplit = NULL; PINFO(" Left to clear: %s\n", gnc_numeric_to_string(toclear_value)); if (g_hash_table_lookup_extended(sack, &toclear_value, NULL, &psplit)) { if (psplit != NULL) { /* Cast the gpointer to the kind of pointer we actually need */ Split *split = (Split *)psplit; toclear_list = g_list_prepend(toclear_list, split); toclear_value = gnc_numeric_sub_fixed(toclear_value, xaccSplitGetAmount(split)); PINFO(" Cleared: %s -> %s\n", gnc_numeric_to_string(xaccSplitGetAmount(split)), gnc_numeric_to_string(toclear_value)); } else { /* We couldn't reconstruct the solution */ PINFO(" Solution not unique.\n"); gtk_label_set_text(data->status_label, _("Cannot uniquely clear splits. Found multiple possibilities.")); return; } } else { PINFO(" No solution found.\n"); gtk_label_set_text(data->status_label, _("The selected amount cannot be cleared.")); return; } } g_hash_table_destroy (sack); /* Show solution */ PINFO("Clearing splits:\n"); for (node = toclear_list; node; node = node->next) { Split *split = node->data; char recn; gnc_numeric value; recn = xaccSplitGetReconcile (split); value = xaccSplitGetAmount (split); PINFO(" %c %s\n", recn, gnc_numeric_to_string(value)); xaccSplitSetReconcile (split, CREC); } if (toclear_list == 0) PINFO(" None\n"); /* Free lists */ g_list_free(nc_list); g_list_free(toclear_list); /* Close window */ gtk_widget_destroy(data->window); g_free(data); }
static void test_transaction(void) { int i; for (i = 0; i < 50; i++) { Transaction *ran_trn; Account *root; xmlNodePtr test_node; gnc_commodity *com, *new_com; gchar *compare_msg; gchar *filename1; int fd; /* The next line exists for its side effect of creating the * account tree. */ root = get_random_account_tree(book); ran_trn = get_random_transaction(book); new_com = get_random_commodity( book ); if (!ran_trn) { failure_args("transaction_xml", __FILE__, __LINE__, "get_random_transaction returned NULL"); return; } { /* xaccAccountInsertSplit can reorder the splits. */ GList * list = g_list_copy(xaccTransGetSplitList (ran_trn)); GList * node = list; for ( ; node; node = node->next) { Split * s = node->data; Account * a = xaccMallocAccount(book); xaccAccountBeginEdit (a); xaccAccountSetCommodity( a, new_com ); xaccAccountSetCommoditySCU (a, xaccSplitGetAmount (s).denom); xaccAccountInsertSplit (a, s); xaccAccountCommitEdit (a); } g_list_free(list); } com = xaccTransGetCurrency (ran_trn); test_node = gnc_transaction_dom_tree_create(ran_trn); if (!test_node) { failure_args("transaction_xml", __FILE__, __LINE__, "gnc_transaction_dom_tree_create returned NULL"); really_get_rid_of_transaction(ran_trn); continue; } if ((compare_msg = node_and_transaction_equal(test_node, ran_trn)) != NULL) { failure_args("transaction_xml", __FILE__, __LINE__, "node and transaction were not equal: %s", compare_msg); xmlElemDump(stdout, NULL, test_node); printf("\n"); fflush(stdout); xmlFreeNode(test_node); really_get_rid_of_transaction(ran_trn); continue; } else { success_args("transaction_xml", __FILE__, __LINE__, "%d", i ); } filename1 = g_strdup_printf("test_file_XXXXXX"); fd = g_mkstemp(filename1); write_dom_node_to_file(test_node, fd); close(fd); { GList * node = xaccTransGetSplitList (ran_trn); for ( ; node; node = node->next) { Split * s = node->data; Account * a1 = xaccSplitGetAccount(s); Account * a2 = xaccMallocAccount(book); xaccAccountBeginEdit (a2); xaccAccountSetCommoditySCU (a2, xaccAccountGetCommoditySCU (a1)); xaccAccountSetGUID (a2, xaccAccountGetGUID (a1)); xaccAccountCommitEdit (a2); } } { sixtp *parser; tran_data data; data.trn = ran_trn; data.com = com; data.value = i; g_print(" There will follow a bunch of CRIT scrub errors about the account not having a commodity. There isn't an account in the XML, so of course not. Ignore the errors\n"); parser = gnc_transaction_sixtp_parser_create(); if (!gnc_xml_parse_file(parser, filename1, test_add_transaction, (gpointer)&data, book)) { failure_args("gnc_xml_parse_file returned FALSE", __FILE__, __LINE__, "%d", i); } else really_get_rid_of_transaction (data.new_trn); /* no handling of circular data structures. We'll do that later */ /* sixtp_destroy(parser); */ } g_unlink(filename1); g_free(filename1); really_get_rid_of_transaction(ran_trn); xmlFreeNode(test_node); } }
/** 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 = 0; last_transaction = NULL; } while (i < parse_data->orig_lines->len) { 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); GncCsvTransLine* trans_line = NULL; for (j = 0; j < line->len; j++) { /* We do nothing in "None" columns. */ if (column_types->data[j] != GNC_CSV_NONE) { /* 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 { i++; } } /* 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_RND_ROUND); while (transactions != NULL) { GncCsvTransLine* trans_line = (GncCsvTransLine*)transactions->data; if (trans_line->balance_set) { time_t 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_RND_ROUND); /* 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_RND_ROUND); 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); /* This new transaction needs to be added to the balance offset. */ balance_offset = gnc_numeric_add(balance_offset, amount, xaccAccountGetCommoditySCU(account), GNC_RND_ROUND); } 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; }
/** 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_RND_ROUND); /* 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; /* 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: xaccTransSetDatePostedSecs(trans_line->trans, *((time_t*)(prop->value))); break; case GNC_CSV_DESCRIPTION: xaccTransSetDescription(trans_line->trans, (char*)(prop->value)); break; case GNC_CSV_NUM: xaccTransSetNum(trans_line->trans, (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_RND_ROUND); 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_RND_ROUND); 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); return trans_line; }
/** Sets the value of the property by parsing str. Note: this should * only be called once on an instance of TransProperty, as calling it * more than once can cause memory leaks. * @param prop The property being set * @param str The string to be parsed * @return TRUE on success, FALSE on failure */ static gboolean trans_property_set(TransProperty* prop, char* str) { char *endptr, *possible_currency_symbol, *str_dupe; double value; switch (prop->type) { case GNC_CSV_DATE: prop->value = g_new(time_t, 1); *((time_t*)(prop->value)) = parse_date(str, prop->list->date_format); return *((time_t*)(prop->value)) != -1; case GNC_CSV_DESCRIPTION: case GNC_CSV_NUM: prop->value = g_strdup(str); return TRUE; case GNC_CSV_BALANCE: case GNC_CSV_DEPOSIT: case GNC_CSV_WITHDRAWAL: str_dupe = g_strdup(str); /* First, we make a copy so we can't mess up real data. */ /* Go through str_dupe looking for currency symbols. */ for (possible_currency_symbol = str_dupe; *possible_currency_symbol; possible_currency_symbol = g_utf8_next_char(possible_currency_symbol)) { if (g_unichar_type(g_utf8_get_char(possible_currency_symbol)) == G_UNICODE_CURRENCY_SYMBOL) { /* If we find a currency symbol, save the position just ahead * of the currency symbol (next_symbol), and find the null * terminator of the string (last_symbol). */ char *next_symbol = g_utf8_next_char(possible_currency_symbol), *last_symbol = next_symbol; while (*last_symbol) last_symbol = g_utf8_next_char(last_symbol); /* Move all of the string (including the null byte, which is * why we have +1 in the size parameter) following the * currency symbol back one character, thereby overwriting the * currency symbol. */ memmove(possible_currency_symbol, next_symbol, last_symbol - next_symbol + 1); break; } } /* Translate the string (now clean of currency symbols) into a number. */ value = strtod(str_dupe, &endptr); /* If this isn't a valid numeric string, this is an error. */ if (endptr != str_dupe + strlen(str_dupe)) { g_free(str_dupe); return FALSE; } g_free(str_dupe); if (abs(value) > 0.00001) { prop->value = g_new(gnc_numeric, 1); *((gnc_numeric*)(prop->value)) = double_to_gnc_numeric(value, xaccAccountGetCommoditySCU(prop->list->account), GNC_RND_ROUND); } return TRUE; } return FALSE; /* We should never actually get here. */ }