static void gnc_split_register_save_num_cell (BasicCell * cell, gpointer save_data, gpointer user_data) { SRSaveData *sd = save_data; SplitRegister *reg = user_data; const char *value; g_return_if_fail (gnc_basic_cell_has_name (cell, NUM_CELL)); value = gnc_basic_cell_get_value (cell); DEBUG ("NUM: %s\n", value ? value : "(null)"); /* set per book option */ gnc_set_num_action (sd->trans, sd->split, value, NULL); if (gnc_num_cell_set_last_num ((NumCell *) cell, value)) { SRInfo *info = gnc_split_register_get_info (reg); Split *blank_split = xaccSplitLookup (&info->blank_split_guid, gnc_get_current_book ()); Transaction *blank_trans = xaccSplitGetParent (blank_split); if (sd->trans == blank_trans) gnc_split_register_set_last_num (reg, gnc_basic_cell_get_value (cell)); } }
static Split * pack_split_info (TTSplitInfo *s_info, Account *parent_acct, Transaction *parent_trans, QofBook *book) { Split *split; const gchar *credit_formula; const gchar *debit_formula; const GncGUID *acc_guid; split = xaccMallocSplit(book); xaccSplitSetMemo(split, gnc_ttsplitinfo_get_memo(s_info)); /* Set split-action with gnc_set_num_action which is the same as * xaccSplitSetAction with these arguments */ gnc_set_num_action(NULL, split, NULL, gnc_ttsplitinfo_get_action(s_info)); xaccAccountInsertSplit(parent_acct, split); credit_formula = gnc_ttsplitinfo_get_credit_formula(s_info); debit_formula = gnc_ttsplitinfo_get_debit_formula(s_info); acc_guid = qof_entity_get_guid(QOF_INSTANCE(gnc_ttsplitinfo_get_account(s_info))); qof_instance_set (QOF_INSTANCE (split), "sx-credit-formula", credit_formula, "sx-debit-formula", debit_formula, "sx-account", acc_guid, NULL); return split; }
/** Adds a split to a transaction. * @param trans The transaction to add a split to * @param account The account used for the split * @param book The book where the split should be stored * @param amount The amount of the split */ static void trans_add_split (Transaction* trans, Account* account, QofBook* book, gnc_numeric amount, const char *num) { Split* split = xaccMallocSplit (book); xaccSplitSetAccount (split, account); xaccSplitSetParent (split, trans); xaccSplitSetAmount (split, amount); xaccSplitSetValue (split, amount); /* set tran-num and/or split-action per book option */ gnc_set_num_action (trans, split, num, NULL); }
void xaccSchedXactionSetTemplateTrans(SchedXaction *sx, GList *t_t_list, QofBook *book) { Transaction *new_trans; TTInfo *tti; TTSplitInfo *s_info; Split *new_split; GList *split_list; g_return_if_fail (book); /* delete any old transactions, if there are any */ delete_template_trans( sx ); for (; t_t_list != NULL; t_t_list = t_t_list->next) { tti = t_t_list->data; new_trans = xaccMallocTransaction(book); xaccTransBeginEdit(new_trans); xaccTransSetDescription(new_trans, gnc_ttinfo_get_description(tti)); xaccTransSetDatePostedSecsNormalized(new_trans, gnc_time (NULL)); /* Set tran-num with gnc_set_num_action which is the same as * xaccTransSetNum with these arguments */ gnc_set_num_action(new_trans, NULL, gnc_ttinfo_get_num(tti), NULL); xaccTransSetNotes (new_trans, gnc_ttinfo_get_notes (tti)); xaccTransSetCurrency( new_trans, gnc_ttinfo_get_currency(tti) ); for (split_list = gnc_ttinfo_get_template_splits(tti); split_list; split_list = split_list->next) { s_info = split_list->data; new_split = pack_split_info(s_info, sx->template_acct, new_trans, book); xaccTransAppendSplit(new_trans, new_split); } xaccTransCommitEdit(new_trans); } }
static void gnc_split_register_save_actn_cell (BasicCell * cell, gpointer save_data, gpointer user_data) { SRSaveData *sd = save_data; const char *value; g_return_if_fail (gnc_basic_cell_has_name (cell, ACTN_CELL)); value = gnc_basic_cell_get_value (cell); DEBUG ("ACTN: %s", value ? value : "(null)"); /* Set split-action with gnc_set_num_action which is the same as * xaccSplitSetAction with these arguments */ gnc_set_num_action (NULL, sd->split, NULL, value); }
static void gnc_split_register_save_tnum_cell (BasicCell * cell, gpointer save_data, gpointer user_data) { SRSaveData *sd = save_data; SplitRegister *reg = user_data; const char *value; g_return_if_fail (gnc_basic_cell_has_name (cell, TNUM_CELL)); value = gnc_basic_cell_get_value (cell); DEBUG ("TNUM: %s\n", value ? value : "(null)"); /* set tran-num using utility function */ gnc_set_num_action (sd->trans, NULL, value, NULL); }
void gnc_stock_split_assistant_finish (GtkAssistant *assistant, gpointer user_data) { StockSplitInfo *info = user_data; GList *account_commits; GList *node; gnc_numeric amount; Transaction *trans; Account *account; Split *split; time64 date; account = info->acct; g_return_if_fail (account != NULL); amount = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT (info->distribution_edit)); g_return_if_fail (!gnc_numeric_zero_p (amount)); gnc_suspend_gui_refresh (); trans = xaccMallocTransaction (gnc_get_current_book ()); xaccTransBeginEdit (trans); xaccTransSetCurrency (trans, gnc_default_currency ()); date = gnc_date_edit_get_date (GNC_DATE_EDIT (info->date_edit)); xaccTransSetDatePostedSecsNormalized (trans, date); { const char *description; description = gtk_entry_get_text (GTK_ENTRY (info->description_entry)); xaccTransSetDescription (trans, description); } split = xaccMallocSplit (gnc_get_current_book ()); xaccAccountBeginEdit (account); account_commits = g_list_prepend (NULL, account); xaccTransAppendSplit (trans, split); xaccAccountInsertSplit (account, split); xaccSplitSetAmount (split, amount); xaccSplitMakeStockSplit (split); /* Set split-action with gnc_set_num_action which is the same as * xaccSplitSetAction with these arguments */ /* Translators: This string has a disambiguation prefix */ gnc_set_num_action (NULL, split, NULL, Q_("Action Column|Split")); amount = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT (info->price_edit)); if (gnc_numeric_positive_p (amount)) { QofBook *book; GNCPrice *price; GNCPriceDB *pdb; GNCCurrencyEdit *ce; Timespec ts; ce = GNC_CURRENCY_EDIT (info->price_currency_edit); ts.tv_sec = date; ts.tv_nsec = 0; price = gnc_price_create (gnc_get_current_book ()); gnc_price_begin_edit (price); gnc_price_set_commodity (price, xaccAccountGetCommodity (account)); gnc_price_set_currency (price, gnc_currency_edit_get_currency (ce)); gnc_price_set_time (price, ts); gnc_price_set_source (price, PRICE_SOURCE_STOCK_SPLIT); gnc_price_set_typestr (price, PRICE_TYPE_UNK); gnc_price_set_value (price, amount); gnc_price_commit_edit (price); book = gnc_get_current_book (); pdb = gnc_pricedb_get_db (book); if (!gnc_pricedb_add_price (pdb, price)) gnc_error_dialog (info->window, "%s", _("Error adding price.")); } amount = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT (info->cash_edit)); if (gnc_numeric_positive_p (amount)) { const char *memo; memo = gtk_entry_get_text (GTK_ENTRY (info->memo_entry)); /* asset split */ account = gnc_tree_view_account_get_selected_account (GNC_TREE_VIEW_ACCOUNT(info->asset_tree)); split = xaccMallocSplit (gnc_get_current_book ()); xaccAccountBeginEdit (account); account_commits = g_list_prepend (account_commits, account); xaccAccountInsertSplit (account, split); xaccTransAppendSplit (trans, split); xaccSplitSetAmount (split, amount); xaccSplitSetValue (split, amount); xaccSplitSetMemo (split, memo); /* income split */ account = gnc_tree_view_account_get_selected_account (GNC_TREE_VIEW_ACCOUNT(info->income_tree)); split = xaccMallocSplit (gnc_get_current_book ()); xaccAccountBeginEdit (account); account_commits = g_list_prepend (account_commits, account); xaccAccountInsertSplit (account, split); xaccTransAppendSplit (trans, split); xaccSplitSetAmount (split, gnc_numeric_neg (amount)); xaccSplitSetValue (split, gnc_numeric_neg (amount)); xaccSplitSetMemo (split, memo); } xaccTransCommitEdit (trans); for (node = account_commits; node; node = node->next) xaccAccountCommitEdit (node->data); g_list_free (account_commits); gnc_resume_gui_refresh (); gnc_close_gui_component_by_data (ASSISTANT_STOCK_SPLIT_CM_CLASS, info); }
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; }
void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots) { GList *base_iter; /* General note: in the code below the term "payment" can * both mean a true payment or a document of * the opposite sign (invoice vs credit note) relative to * the lot being processed. In general this function will * perform a balancing action on a set of lots, so you * will also find frequent references to balancing instead. */ /* Payments can only be applied when at least an owner * and a list of lots to use are given */ if (!owner) return; if (!lots) return; for (base_iter = lots; base_iter; base_iter = base_iter->next) { GNCLot *base_lot = base_iter->data; QofBook *book; Account *acct; const gchar *name; GList *lot_list, *lot_iter; Transaction *txn = NULL; gnc_numeric base_lot_bal, val_to_pay, val_paid = { 0, 1 }; gboolean base_bal_is_pos; const gchar *action, *memo; /* Only attempt to apply payments to open lots. * Note that due to the iterative nature of this function lots * in the list may become closed before they are evaluated as * base lot, so we should check this for each lot. */ base_lot_bal = gnc_lot_get_balance (base_lot); if (gnc_numeric_zero_p (base_lot_bal)) continue; book = gnc_lot_get_book (base_lot); acct = gnc_lot_get_account (base_lot); name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); lot_list = base_iter->next; /* Strings used when creating splits later on. */ action = _("Lot Link"); memo = _("Internal link between invoice and payment lots"); /* Note: to balance the lot the payment to assign * must have the opposite sign of the existing lot balance */ val_to_pay = gnc_numeric_neg (base_lot_bal); base_bal_is_pos = gnc_numeric_positive_p (base_lot_bal); /* Create splits in a linking transaction between lots until * - either the invoice lot is balanced * - or there are no more balancing lots. */ for (lot_iter = lot_list; lot_iter; lot_iter = lot_iter->next) { gnc_numeric payment_lot_balance; Split *split; Account *bal_acct; gnc_numeric split_amt; GNCLot *balancing_lot = lot_iter->data; /* Only attempt to use open lots to balance the base lot. * Note that due to the iterative nature of this function lots * in the list may become closed before they are evaluated as * base lot, so we should check this for each lot. */ if (gnc_lot_is_closed (balancing_lot)) continue; /* Balancing transactions for invoice/payments can only happen * in the same account. */ bal_acct = gnc_lot_get_account (balancing_lot); if (acct != bal_acct) continue; payment_lot_balance = gnc_lot_get_balance (balancing_lot); /* Only attempt to balance if the base lot and balancing lot are * of the opposite sign. (Otherwise we would increase the balance * of the lot - Duh */ if (base_bal_is_pos == gnc_numeric_positive_p (payment_lot_balance)) continue; /* * If there is less to pay than there's open in the lot; we're done -- apply the base_lot_vale. * Note that payment_value and balance are opposite in sign, so we have to compare absolute values here * * Otherwise, apply the balance, subtract that from the payment_value, * and move on to the next one. */ if (gnc_numeric_compare (gnc_numeric_abs (val_to_pay), gnc_numeric_abs (payment_lot_balance)) <= 0) { /* abs(val_to_pay) <= abs(balance) */ split_amt = val_to_pay; } else { /* abs(val_to_pay) > abs(balance) * Remember payment_value and balance are opposite in sign, * and we want a payment to neutralize the current balance * so we need to negate here */ split_amt = payment_lot_balance; } /* If not created yet, create a new transaction linking * the base lot and the balancing lot(s) */ if (!txn) { Timespec ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (base_lot))); xaccAccountBeginEdit (acct); txn = xaccMallocTransaction (book); xaccTransBeginEdit (txn); xaccTransSetDescription (txn, name ? name : ""); xaccTransSetCurrency (txn, xaccAccountGetCommodity(acct)); xaccTransSetDateEnteredSecs (txn, gnc_time (NULL)); xaccTransSetDatePostedTS (txn, &ts); xaccTransSetTxnType (txn, TXN_TYPE_LINK); } /* Create the split for this link in current balancing lot */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, gnc_numeric_neg (split_amt), xaccAccountGetCommodity(acct)); gnc_lot_add_split (balancing_lot, split); /* If the balancing lot was linked to a document (invoice/credit note), * send an event for it as well so it gets potentially updated as paid */ { GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(balancing_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } val_paid = gnc_numeric_add (val_paid, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); val_to_pay = gnc_numeric_sub (val_to_pay, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); if (gnc_numeric_zero_p (val_to_pay)) break; } /* If the above loop managed to create a transaction and some balancing splits, * create the final split for the link transaction in the base lot */ if (txn) { GncInvoice *this_invoice; Split *split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set Action with utiltity function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, val_paid, xaccAccountGetCommodity(acct)); gnc_lot_add_split (base_lot, split); xaccTransCommitEdit (txn); xaccAccountCommitEdit (acct); /* If the base lot was linked to a document (invoice/credit note), * send an event for it as well so it gets potentially updated as paid */ this_invoice = gncInvoiceGetInvoiceFromLot(base_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } } }
GNCLot * gncOwnerCreatePaymentLot (const GncOwner *owner, Transaction *txn, Account *posted_acc, Account *xfer_acc, gnc_numeric amount, gnc_numeric exch, Timespec date, const char *memo, const char *num) { QofBook *book; Split *split; const char *name; gnc_commodity *commodity; Split *xfer_split = NULL; GNCLot *payment_lot; /* Verify our arguments */ if (!owner || !posted_acc || !xfer_acc) return NULL; g_return_val_if_fail (owner->owner.undefined != NULL, NULL); /* Compute the ancillary data */ book = gnc_account_get_book (posted_acc); name = gncOwnerGetName (gncOwnerGetEndOwner ((GncOwner*)owner)); commodity = gncOwnerGetCurrency (owner); // reverse = use_reversed_payment_amounts(owner); if (txn) { /* Pre-existing transaction was specified. We completely clear it, * except for the split in the transfer account, unless the * transaction can't be reused (wrong currency, wrong transfer account). * In that case, the transaction is simply removed and an new * one created. */ xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc); if (xaccTransGetCurrency(txn) != gncOwnerGetCurrency (owner)) { g_message("Uh oh, mismatching currency/commodity between selected transaction and owner. We fall back to manual creation of a new transaction."); xfer_split = NULL; } if (!xfer_split) { g_message("Huh? Asset account not found anymore. Fully deleting old txn and now creating a new one."); xaccTransBeginEdit (txn); xaccTransDestroy (txn); xaccTransCommitEdit (txn); txn = NULL; } else { int i = 0; xaccTransBeginEdit (txn); while (i < xaccTransCountSplits(txn)) { Split *split = xaccTransGetSplit (txn, i); if (split == xfer_split) { gnc_set_num_action (NULL, split, num, _("Payment")); ++i; } else { xaccSplitDestroy(split); } } /* Note: don't commit transaction now - that would insert an imbalance split.*/ } } /* Create the transaction if we don't have one yet */ if (!txn) { txn = xaccMallocTransaction (book); xaccTransBeginEdit (txn); } /* Insert a split for the transfer account if we don't have one yet */ if (!xfer_split) { /* Set up the transaction */ xaccTransSetDescription (txn, name ? name : ""); /* set per book option */ xaccTransSetCurrency (txn, commodity); xaccTransSetDateEnteredSecs (txn, gnc_time (NULL)); xaccTransSetDatePostedTS (txn, &date); /* The split for the transfer account */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set per book option */ gnc_set_num_action (NULL, split, num, _("Payment")); xaccAccountBeginEdit (xfer_acc); xaccAccountInsertSplit (xfer_acc, split); xaccAccountCommitEdit (xfer_acc); xaccTransAppendSplit (txn, split); if (gnc_commodity_equal(xaccAccountGetCommodity(xfer_acc), commodity)) { xaccSplitSetBaseValue (split, amount, commodity); } else { /* Need to value the payment in terms of the owner commodity */ gnc_numeric payment_value = gnc_numeric_mul(amount, exch, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetAmount(split, amount); xaccSplitSetValue(split, payment_value); } } /* Add a split in the post account */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set per book option */ gnc_set_num_action (NULL, split, num, _("Payment")); xaccAccountBeginEdit (posted_acc); xaccAccountInsertSplit (posted_acc, split); xaccAccountCommitEdit (posted_acc); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, gnc_numeric_neg (amount), commodity); /* Create a new lot for the payment */ payment_lot = gnc_lot_new (book); gncOwnerAttachToLot (owner, payment_lot); gnc_lot_add_split (payment_lot, split); /* Mark the transaction as a payment */ gnc_set_num_action (txn, NULL, num, _("Payment")); xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT); /* Commit this new transaction */ xaccTransCommitEdit (txn); return payment_lot; }
static void gncOwnerCreateLotLink (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner) { const gchar *action = _("Lot Link"); Account *acct = gnc_lot_get_account (from_lot); const gchar *name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); Transaction *ll_txn = NULL; gnc_numeric from_lot_bal, to_lot_bal; Timespec from_ts, to_ts; time64 time_posted; Split *split; /* Sanity check */ if (!gncInvoiceGetInvoiceFromLot (from_lot) || !gncInvoiceGetInvoiceFromLot (to_lot)) return; /* Determine transaction date based on lot splits */ from_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (from_lot))); to_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (to_lot))); if (timespecToTime64 (from_ts) >= timespecToTime64 (to_ts)) time_posted = timespecToTime64 (from_ts); else time_posted = timespecToTime64 (to_ts); /* Figure out how much we can offset between the lots */ from_lot_bal = gnc_lot_get_balance (from_lot); to_lot_bal = gnc_lot_get_balance (to_lot); if (gnc_numeric_compare (gnc_numeric_abs (from_lot_bal), gnc_numeric_abs (to_lot_bal)) > 0) from_lot_bal = gnc_numeric_neg (to_lot_bal); else to_lot_bal = gnc_numeric_neg (from_lot_bal); xaccAccountBeginEdit (acct); /* Look for a pre-existing lot link we can extend */ ll_txn = get_ll_transaction_from_lot (from_lot); if (!ll_txn) ll_txn = get_ll_transaction_from_lot (to_lot); if (!ll_txn) { /* No pre-existing lot link. Create one. */ Timespec ts; timespecFromTime64 (&ts, time_posted); ll_txn = xaccMallocTransaction (gnc_lot_get_book (from_lot)); xaccTransBeginEdit (ll_txn); xaccTransSetDescription (ll_txn, name ? name : "(Unknown)"); xaccTransSetCurrency (ll_txn, xaccAccountGetCommodity(acct)); xaccTransSetDateEnteredSecs (ll_txn, gnc_time (NULL)); xaccTransSetDatePostedTS (ll_txn, &ts); xaccTransSetTxnType (ll_txn, TXN_TYPE_LINK); } else { Timespec ts = xaccTransRetDatePostedTS (ll_txn); xaccTransBeginEdit (ll_txn); /* Maybe we need to update the post date of the transaction ? */ if (time_posted > timespecToTime64 (ts)) { timespecFromTime64 (&ts, time_posted); xaccTransSetDatePostedTS (ll_txn, &ts); } } /* Create a split for the from_lot */ split = xaccMallocSplit (gnc_lot_get_book (from_lot)); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (ll_txn, split); /* To offset the lot balance, the split must be of the opposite sign */ xaccSplitSetBaseValue (split, gnc_numeric_neg (from_lot_bal), xaccAccountGetCommodity(acct)); gnc_lot_add_split (from_lot, split); /* Create a split for the to_lot */ split = xaccMallocSplit (gnc_lot_get_book (to_lot)); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (ll_txn, split); /* To offset the lot balance, the split must be of the opposite sign */ xaccSplitSetBaseValue (split, gnc_numeric_neg (to_lot_bal), xaccAccountGetCommodity(acct)); gnc_lot_add_split (to_lot, split); xaccTransCommitEdit (ll_txn); /* Do some post-cleaning on the lots * The above actions may have created splits that are * in the same transaction and lot. These can be merged. */ xaccScrubMergeLotSubSplits (to_lot, FALSE); xaccScrubMergeLotSubSplits (from_lot, FALSE); /* And finally set the same memo for all remaining splits * It's a convenience for the users to identify all documents * involved in the link. */ gncOwnerSetLotLinkMemo (ll_txn); xaccAccountCommitEdit (acct); }
int ofx_proc_transaction_cb(struct OfxTransactionData data, void * transaction_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; g_assert(gnc_ofx_importer_gui); if (!data.account_id_valid) { PERR("account ID for this transaction is unavailable!"); return 0; } 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 */ xaccTransSetDatePostedSecsNormalized(transaction, data.date_posted); } else if (data.date_initiated_valid && (data.date_initiated != 0)) { /* No posted date? Maybe we have an initiated_date */ 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):", 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:", 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 #", 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, 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; /********* 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\""), 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(NULL, 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(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( 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 (ofx_get_investment_amount(&data), investment_commodity); 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, data.security_data_ptr->memo); } if (data.fi_id_valid) { gnc_import_set_split_online_id(split, 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\""), 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); 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()