static int get_num_xactions_before_date(QofBook *book, time64 close_date) { QofQuery *q; GSList *param; QofQueryPredData *pred; Timespec ts; GList *res, *n; int cnt = 0; q = qof_query_create_for(GNC_ID_TRANS); qof_query_set_max_results(q, -1); qof_query_set_book (q, book); /* Look for transactions earlier than the closing date */ param = g_slist_prepend (NULL, TRANS_DATE_POSTED); timespecFromTime64 (&ts, close_date); pred = qof_query_date_predicate (QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, ts); qof_query_add_term (q, param, pred, QOF_QUERY_FIRST_TERM); /* Run the query, find how many transactions there are */ res = qof_query_run (q); cnt = 0; for (n = res; n; n = n->next) cnt ++; qof_query_destroy (q); return cnt; }
void ap_assistant_finish (GtkAssistant *assistant, gpointer user_data) { AcctPeriodInfo *info = user_data; GtkTextBuffer * buffer; GtkTextIter startiter, enditer; gint len; const char *btitle; char *bnotes; Timespec closing_date; ENTER("info=%p", info); btitle = gtk_entry_get_text (GTK_ENTRY(info->book_title)); buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(info->book_notes)); len = gtk_text_buffer_get_char_count (buffer); gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0); gtk_text_buffer_get_iter_at_offset(buffer, &enditer, len); bnotes = gtk_text_buffer_get_text(buffer, &startiter, &enditer , 0); PINFO("Book title is - %s\n", btitle); timespecFromTime64 (&closing_date, gnc_time64_get_day_end_gdate (&info->closing_date)); g_free(bnotes); /* Report the status back to the user. */ info->close_status = 0; /* XXX fixme success or failure? */ /* Find the next closing date ... */ info->prev_closing_date = info->closing_date; recurrenceListNextInstance (info->period, &info->prev_closing_date, &info->closing_date); /* FIXME Test for valid closing date, not sure why it won't be!!! */ if (g_date_valid(&info->closing_date) == TRUE) { /* If the next closing date is in the future, then we are done. */ if (gnc_time (NULL) > gnc_time64_get_day_end_gdate (&info->closing_date)) { /* Load up the GUI for the next closing period. */ gnc_frequency_setup_recurrence (info->period_menu, NULL, &info->closing_date); /* Jump back to the Close Book page. */ gtk_assistant_set_current_page (GTK_ASSISTANT(info->window), 1); } } }
/*********************************************************************** * @todo Maybe invoice checking should be done in gnc_bi_import_fix_bis (...) * rather than in here? But that is more concerned with ensuring the csv is consistent. * @param GtkListStore *store * @param guint *n_invoices_created * @param guint *n_invoices_updated * @return void ***********************************************************************/ void gnc_bi_import_create_bis (GtkListStore * store, QofBook * book, guint * n_invoices_created, guint * n_invoices_updated, gchar * type, gchar * open_mode, GString * info) { gboolean valid; GtkTreeIter iter; gchar *id, *date_opened, *owner_id, *billing_id, *notes; gchar *date, *desc, *action, *account, *quantity, *price, *disc_type, *disc_how, *discount, *taxable, *taxincluded, *tax_table; gchar *date_posted, *due_date, *account_posted, *memo_posted, *accumulatesplits; guint dummy; GncInvoice *invoice; GncEntry *entry; gint day, month, year; gnc_numeric value; GncOwner *owner; Account *acc; enum update {YES = GTK_RESPONSE_YES, NO = GTK_RESPONSE_NO} update; GtkWidget *dialog; Timespec today; InvoiceWindow *iw; gchar *new_id = NULL; gint64 denom = 0; gnc_commodity *currency; // these arguments are needed g_return_if_fail (store && book); // logic of this function only works for bills or invoices g_return_if_fail ((g_ascii_strcasecmp (type, "INVOICE") == 0) || (g_ascii_strcasecmp (type, "BILL") == 0)); // allow to call this function without statistics if (!n_invoices_created) n_invoices_created = &dummy; if (!n_invoices_updated) n_invoices_updated = &dummy; *n_invoices_created = 0; *n_invoices_updated = 0; invoice = NULL; update = NO; valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); while (valid) { // Walk through the list, reading each row gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, DATE_OPENED, &date_opened, DATE_POSTED, &date_posted, // if autoposting requested DUE_DATE, &due_date, // if autoposting requested ACCOUNT_POSTED, &account_posted, // if autoposting requested MEMO_POSTED, &memo_posted, // if autoposting requested ACCU_SPLITS, &accumulatesplits, // if autoposting requested OWNER_ID, &owner_id, BILLING_ID, &billing_id, NOTES, ¬es, DATE, &date, DESC, &desc, ACTION, &action, ACCOUNT, &account, QUANTITY, &quantity, PRICE, &price, DISC_TYPE, &disc_type, DISC_HOW, &disc_how, DISCOUNT, &discount, TAXABLE, &taxable, TAXINCLUDED, &taxincluded, TAX_TABLE, &tax_table, -1); // TODO: Assign a new invoice number if one is absent. BUT we don't want to assign a new invoice for every line!! // so we'd have to flag this up somehow or add an option in the import GUI. The former implies that we make // an assumption about what the importer (person) wants to do. It seems reasonable that a CSV file full of items with // If an invoice exists then we add to it in this current schema. // no predefined invoice number is a new invoice that's in need of a new number. // This was not designed to satisfy the need for repeat invoices however, so maybe we need a another method for this, after all // It should be easier to copy an invoice with a new ID than to go through all this malarky. if (g_ascii_strcasecmp (type, "BILL") == 0) invoice = gnc_search_bill_on_id (book, id); else if (g_ascii_strcasecmp (type, "INVOICE") == 0) invoice = gnc_search_invoice_on_id (book, id); DEBUG( "Existing %s ID: %s\n", type, gncInvoiceGetID(invoice)); // If the search is empty then there is no existing invoice so make a new one if (invoice == NULL) { DEBUG( "Creating a new : %s\n", type ); // new invoice invoice = gncInvoiceCreate (book); /* Protect against thrashing the DB and trying to write the invoice * record prematurely */ gncInvoiceBeginEdit (invoice); gncInvoiceSetID (invoice, id); owner = gncOwnerNew (); if (g_ascii_strcasecmp (type, "BILL") == 0) gncOwnerInitVendor (owner, gnc_search_vendor_on_id (book, owner_id)); else if (g_ascii_strcasecmp (type, "INVOICE") == 0) gncOwnerInitCustomer (owner, gnc_search_customer_on_id (book, owner_id)); gncInvoiceSetOwner (invoice, owner); gncInvoiceSetCurrency (invoice, gncOwnerGetCurrency (owner)); // Set the invoice currency based on the owner if (strlen (date_opened) != 0) // If a date is specified in CSV { // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (date_opened, &day, &month, &year); gncInvoiceSetDateOpened (invoice, gnc_dmy2timespec (day, month, year)); } else // If no date in CSV { time64 now = gnc_time (NULL); Timespec now_timespec; timespecFromTime64 (&now_timespec, now); gncInvoiceSetDateOpened (invoice, now_timespec); } gncInvoiceSetBillingID (invoice, billing_id ? billing_id : ""); gncInvoiceSetNotes (invoice, notes ? notes : ""); gncInvoiceSetActive (invoice, TRUE); //if (g_ascii_strcasecmp(type,"INVOICE"))gncInvoiceSetBillTo( invoice, billto ); (*n_invoices_created)++; update = YES; // open new bill / invoice in a tab, if requested if (g_ascii_strcasecmp(open_mode, "ALL") == 0 || (g_ascii_strcasecmp(open_mode, "NOT_POSTED") == 0 && strlen(date_posted) == 0)) { iw = gnc_ui_invoice_edit (invoice); gnc_plugin_page_invoice_new (iw); } gncInvoiceCommitEdit (invoice); } // I want to warn the user that an existing billvoice exists, but not every // time. // An import can contain many lines usually referring to the same invoice. // NB: Posted invoices are NEVER updated. else // if invoice exists { if (gncInvoiceIsPosted (invoice)) // Is it already posted? { valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); continue; // If already posted then never import } if (update != YES) // Pop up a dialog to ask if updates are the expected action { dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO, "%s", _("Are you sure you have bills/invoices to update?")); update = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (update == NO) { // Cleanup and leave g_free (id); g_free (date_opened); g_free (owner_id); g_free (billing_id); g_free (notes); g_free (date); g_free (desc); g_free (action); g_free (account); g_free (quantity); g_free (price); g_free (disc_type); g_free (disc_how); g_free (discount); g_free (taxable); g_free (taxincluded); g_free (tax_table); g_free (date_posted); g_free (due_date); g_free (account_posted); g_free (memo_posted); g_free (accumulatesplits); return; } } (*n_invoices_updated)++; } // add entry to invoice/bill entry = gncEntryCreate (book); gncEntryBeginEdit(entry); currency = gncInvoiceGetCurrency(invoice); if (currency) denom = gnc_commodity_get_fraction(currency); // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (date, &day, &month, &year); { GDate *date = g_date_new_dmy(day, month, year); gncEntrySetDateGDate (entry, date); g_date_free (date); } timespecFromTime64 (&today, gnc_time (NULL)); // set today to the current date gncEntrySetDateEntered (entry, today); gncEntrySetDescription (entry, desc); gncEntrySetAction (entry, action); value = gnc_numeric_zero(); gnc_exp_parser_parse (quantity, &value, NULL); gncEntrySetQuantity (entry, value); acc = gnc_account_lookup_for_register (gnc_get_current_root_account (), account); if (g_ascii_strcasecmp (type, "BILL") == 0) { gncEntrySetBillAccount (entry, acc); value = gnc_numeric_zero(); gnc_exp_parser_parse (price, &value, NULL); gncEntrySetBillPrice (entry, value); gncEntrySetBillTaxable (entry, text2bool (taxable)); gncEntrySetBillTaxIncluded (entry, text2bool (taxincluded)); gncEntrySetBillTaxTable (entry, gncTaxTableLookupByName (book, tax_table)); gncEntryCommitEdit(entry); gncBillAddEntry (invoice, entry); } else if (g_ascii_strcasecmp (type, "INVOICE") == 0) { gncEntrySetNotes (entry, notes); gncEntrySetInvAccount (entry, acc); value = gnc_numeric_zero(); gnc_exp_parser_parse (price, &value, NULL); gncEntrySetInvPrice (entry, value); gncEntrySetInvTaxable (entry, text2bool (taxable)); gncEntrySetInvTaxIncluded (entry, text2bool (taxincluded)); gncEntrySetInvTaxTable (entry, gncTaxTableLookupByName (book, tax_table)); value = gnc_numeric_zero(); gnc_exp_parser_parse (discount, &value, NULL); gncEntrySetInvDiscount (entry, value); gncEntrySetInvDiscountType (entry, text2disc_type (disc_type)); gncEntrySetInvDiscountHow (entry, text2disc_how (disc_how)); gncEntryCommitEdit(entry); gncInvoiceAddEntry (invoice, entry); } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); // handle auto posting of invoices if (valid) gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &new_id, -1); if (g_strcmp0 (id, new_id) != 0) { // the next invoice id is different => try to autopost this invoice if (qof_scan_date (date_posted, &day, &month, &year)) { // autopost this invoice gboolean auto_pay; Timespec d1, d2; if (g_ascii_strcasecmp (type, "INVOICE") == 0) auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_INVOICE, GNC_PREF_AUTO_PAY); else auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_BILL, GNC_PREF_AUTO_PAY); d1 = gnc_dmy2timespec (day, month, year); // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (due_date, &day, &month, &year); // obtains the due date, or leaves it at date_posted d2 = gnc_dmy2timespec (day, month, year); acc = gnc_account_lookup_for_register (gnc_get_current_root_account (), account_posted); gncInvoicePostToAccount (invoice, acc, &d1, &d2, memo_posted, text2bool (accumulatesplits), auto_pay); } } } // cleanup g_free (new_id); g_free (id); g_free (date_opened); g_free (owner_id); g_free (billing_id); g_free (notes); g_free (date); g_free (desc); g_free (action); g_free (account); g_free (quantity); g_free (price); g_free (disc_type); g_free (disc_how); g_free (discount); g_free (taxable); g_free (taxincluded); g_free (tax_table); g_free (date_posted); g_free (due_date); g_free (account_posted); g_free (memo_posted); g_free (accumulatesplits); }
static void gnc_plugin_business_cmd_test_init_data (GtkAction *action, GncMainWindowActionData *data) { QofBook *book = gnc_get_current_book(); GncCustomer *customer = gncCustomerCreate(book); GncAddress *address = gncCustomerGetAddr(customer); GncInvoice *invoice = gncInvoiceCreate(book); GncOwner *owner = gncOwnerNew(); GncJob *job = gncJobCreate(book); Account *root = gnc_book_get_root_account(book); Account *inc_acct = xaccMallocAccount(book); Account *bank_acct = xaccMallocAccount(book); Account *tax_acct = xaccMallocAccount(book); Account *ar_acct = xaccMallocAccount(book); Timespec now; // Create Customer gncCustomerSetID(customer, "000001"); gncCustomerSetName(customer, "Test Customer"); gncCustomerSetCurrency(customer, gnc_default_currency()); gncAddressSetName(address, "Contact Person"); gncAddressSetAddr1(address, "20 Customer Lane"); gncAddressSetAddr2(address, "Customer M/S"); gncAddressSetAddr3(address, "Addr3, XXX 12345"); // Create the Owner gncOwnerInitCustomer(owner, customer); // Create the Invoice timespecFromTime64(&now, time(NULL)); gncInvoiceSetID(invoice, "000012"); gncInvoiceSetOwner(invoice, owner); gncInvoiceSetDateOpened(invoice, now); gncInvoiceSetCurrency(invoice, gnc_default_currency()); // Create the Job gncJobSetID(job, "000025"); gncJobSetName(job, "Test Job"); gncJobSetReference(job, "Customer's ref#"); gncJobSetOwner(job, owner); // MODIFY THE OWNER gncOwnerInitJob(owner, job); // Create the A/R account xaccAccountSetType(ar_acct, ACCT_TYPE_RECEIVABLE); xaccAccountSetName(ar_acct, "A/R"); xaccAccountSetCommodity(ar_acct, gnc_default_currency()); gnc_account_append_child(root, ar_acct); // Create the Income account xaccAccountSetType(inc_acct, ACCT_TYPE_INCOME); xaccAccountSetName(inc_acct, "Income"); xaccAccountSetCommodity(inc_acct, gnc_default_currency()); gnc_account_append_child(root, inc_acct); // Create the Bank account xaccAccountSetType(bank_acct, ACCT_TYPE_BANK); xaccAccountSetName(bank_acct, "Bank"); xaccAccountSetCommodity(bank_acct, gnc_default_currency()); gnc_account_append_child(root, bank_acct); // Create the Tax account xaccAccountSetType(tax_acct, ACCT_TYPE_LIABILITY); xaccAccountSetName(tax_acct, "Tax-Holding"); xaccAccountSetCommodity(tax_acct, gnc_default_currency()); gnc_account_append_child(root, tax_acct); // Launch the invoice editor gnc_ui_invoice_edit (GTK_WINDOW (data->window), invoice); }
static inline void mod_table (GncTaxTable *table) { timespecFromTime64 (&table->modtime, gnc_time (NULL)); }
/** * @fixme Move this non-GUI code into the engine. **/ static void gnc_ui_accounts_recurse (Account *parent, GList **currency_list, GNCSummarybarOptions options) { gnc_numeric start_amount; gnc_numeric start_amount_default_currency; gnc_numeric end_amount; gnc_numeric end_amount_default_currency; GNCAccountType account_type; gnc_commodity * account_currency; GNCCurrencyAcc *currency_accum = NULL; GNCCurrencyAcc *grand_total_accum = NULL; GNCCurrencyAcc *non_curr_accum = NULL; GList *children, *node; gboolean non_currency = FALSE; Timespec end_timespec; Timespec start_timespec; if (parent == NULL) return; children = gnc_account_get_children(parent); for (node = children; node; node = g_list_next(node)) { Account *account = node->data; account_type = xaccAccountGetType(account); account_currency = xaccAccountGetCommodity(account); if (options.grand_total) grand_total_accum = gnc_ui_get_currency_accumulator(currency_list, options.default_currency, TOTAL_GRAND_TOTAL); if (!gnc_commodity_is_currency(account_currency)) { non_currency = TRUE; non_curr_accum = gnc_ui_get_currency_accumulator(currency_list, options.default_currency, TOTAL_NON_CURR_TOTAL); } if (!non_currency || options.non_currency) { currency_accum = gnc_ui_get_currency_accumulator(currency_list, account_currency, TOTAL_SINGLE); } switch (account_type) { case ACCT_TYPE_BANK: case ACCT_TYPE_CASH: case ACCT_TYPE_ASSET: case ACCT_TYPE_STOCK: case ACCT_TYPE_MUTUAL: case ACCT_TYPE_CREDIT: case ACCT_TYPE_LIABILITY: case ACCT_TYPE_PAYABLE: case ACCT_TYPE_RECEIVABLE: end_amount = xaccAccountGetBalanceAsOfDate(account, options.end_date); timespecFromTime64(&end_timespec, options.end_date); end_amount_default_currency = xaccAccountConvertBalanceToCurrencyAsOfDate (account, end_amount, account_currency, options.default_currency, timespecToTime64(timespecCanonicalDayTime(end_timespec))); if (!non_currency || options.non_currency) { currency_accum->assets = gnc_numeric_add (currency_accum->assets, end_amount, gnc_commodity_get_fraction (account_currency), GNC_HOW_RND_ROUND_HALF_UP); } if (non_currency) { non_curr_accum->assets = gnc_numeric_add (non_curr_accum->assets, end_amount_default_currency, gnc_commodity_get_fraction (options.default_currency), GNC_HOW_RND_ROUND_HALF_UP); } if (options.grand_total) { grand_total_accum->assets = gnc_numeric_add (grand_total_accum->assets, end_amount_default_currency, gnc_commodity_get_fraction (options.default_currency), GNC_HOW_RND_ROUND_HALF_UP); } gnc_ui_accounts_recurse(account, currency_list, options); break; case ACCT_TYPE_INCOME: case ACCT_TYPE_EXPENSE: start_amount = xaccAccountGetBalanceAsOfDate(account, options.start_date); timespecFromTime64(&start_timespec, options.start_date); start_amount_default_currency = xaccAccountConvertBalanceToCurrencyAsOfDate (account, start_amount, account_currency, options.default_currency, timespecToTime64(timespecCanonicalDayTime(start_timespec))); end_amount = xaccAccountGetBalanceAsOfDate(account, options.end_date); timespecFromTime64(&end_timespec, options.end_date); end_amount_default_currency = xaccAccountConvertBalanceToCurrencyAsOfDate (account, end_amount, account_currency, options.default_currency, timespecToTime64(timespecCanonicalDayTime(end_timespec))); if (!non_currency || options.non_currency) { currency_accum->profits = gnc_numeric_add (currency_accum->profits, start_amount, gnc_commodity_get_fraction (account_currency), GNC_HOW_RND_ROUND_HALF_UP); currency_accum->profits = gnc_numeric_sub (currency_accum->profits, end_amount, gnc_commodity_get_fraction (account_currency), GNC_HOW_RND_ROUND_HALF_UP); } if (non_currency) { non_curr_accum->profits = gnc_numeric_add (non_curr_accum->profits, start_amount_default_currency, gnc_commodity_get_fraction (options.default_currency), GNC_HOW_RND_ROUND_HALF_UP); non_curr_accum->profits = gnc_numeric_sub (non_curr_accum->profits, end_amount_default_currency, gnc_commodity_get_fraction (options.default_currency), GNC_HOW_RND_ROUND_HALF_UP); } if (options.grand_total) { grand_total_accum->profits = gnc_numeric_add (grand_total_accum->profits, start_amount_default_currency, gnc_commodity_get_fraction (options.default_currency), GNC_HOW_RND_ROUND_HALF_UP); grand_total_accum->profits = gnc_numeric_sub (grand_total_accum->profits, end_amount_default_currency, gnc_commodity_get_fraction (options.default_currency), GNC_HOW_RND_ROUND_HALF_UP); } gnc_ui_accounts_recurse(account, currency_list, options); break; case ACCT_TYPE_EQUITY: /* no-op, see comments at top about summing assets */ break; /** * @fixme I don't know if this is right or if trading accounts should be * treated like income and expense accounts. **/ case ACCT_TYPE_TRADING: break; case ACCT_TYPE_CURRENCY: default: break; } } g_list_free(children); }
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()