static gboolean gncScrubLotIsSingleLotLinkSplit (GNCLot *lot) { Split *split = NULL; Transaction *trans = NULL; // Lots with a single split which is also a lot link transaction split // may be sign of a dangling payment. Let's try to fix that // Only works for single split lots... if (1 != gnc_lot_count_splits (lot)) return FALSE; split = gnc_lot_get_earliest_split (lot); trans = xaccSplitGetParent (split); if (!trans) { // Ooops - the split doesn't belong to any transaction ! // This is not expected so issue a warning and continue with next split PWARN("Encountered a split in a business lot that's not part of any transaction. " "This is unexpected! Skipping split %p.", split); return FALSE; } // Only works if single split belongs to a lot link transaction... if (xaccTransGetTxnType (trans) != TXN_TYPE_LINK) return FALSE; return TRUE; }
// Transaction Type static gchar* add_type (gchar *so_far, Transaction *trans, CsvExportInfo *info) { gchar *result; char type; static char ss[2]; type = xaccTransGetTxnType (trans); if (type == TXN_TYPE_NONE) type = ' '; ss[0] = type; ss[1] = '\0'; result = g_strconcat (so_far, ss, info->mid_sep, NULL); g_free (so_far); return result; }
void gncScrubBusinessSplit (Split *split) { const gchar *memo = _("Please delete this transaction. Explanation at http://wiki.gnucash.org/wiki/Business_Features_Issues#Double_Posting"); Transaction *txn; if (!split) return; ENTER ("(split=%p)", split); txn = xaccSplitGetParent (split); if (txn) { gchar txntype = xaccTransGetTxnType (txn); const gchar *read_only = xaccTransGetReadOnly (txn); gboolean is_void = xaccTransGetVoidStatus (txn); GNCLot *lot = xaccSplitGetLot (split); /* Look for transactions as a result of double posting an invoice or bill * Refer to https://bugzilla.gnome.org/show_bug.cgi?id=754209 * to learn how this could have happened in the past. * Characteristics of such transaction are: * - read only * - not voided (to ensure read only is set by the business functions) * - transaction type is none (should be type invoice for proper post transactions) * - assigned to a lot */ if ((txntype == TXN_TYPE_NONE) && read_only && !is_void && lot) { gchar *txn_date = qof_print_date (xaccTransGetDateEntered (txn)); xaccTransClearReadOnly (txn); xaccSplitSetMemo (split, memo); gnc_lot_remove_split (lot, split); PWARN("Cleared double post status of transaction \"%s\", dated %s. " "Please delete transaction and verify balance.", xaccTransGetDescription (txn), txn_date); g_free (txn_date); } } LEAVE ("(split=%p)", split); }
/* Find an existing lot link transaction in the given lot * Only use a lot link that already links at least two * documents (to avoid perpetuating the lot link proliferation * that happened in 2.6.0-2.6.3). */ static Transaction * get_ll_transaction_from_lot (GNCLot *lot) { SplitList *ls_iter; /* This should really only be called on a document lot */ if (!gncInvoiceGetInvoiceFromLot (lot)) return NULL; /* The given lot is a valid document lot. Now iterate over all * other lot links in this lot to find one more document lot. */ for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next) { Split *ls = ls_iter->data; Transaction *ll_txn = xaccSplitGetParent (ls); SplitList *ts_iter; if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK) continue; for (ts_iter = xaccTransGetSplitList (ll_txn); ts_iter; ts_iter = ts_iter->next) { Split *ts = ts_iter->data; GNCLot *tslot = xaccSplitGetLot (ts); if (!tslot) continue; if (tslot == lot) continue; if (gncInvoiceGetInvoiceFromLot (lot)) return ll_txn; /* Got one more document lot - mission accomplished */ } } /* The lot doesn't have an ll_txn with the requested criteria... */ return NULL; }
// Second Date static gchar* add_second_date (gchar *so_far, Transaction *trans, CsvExportInfo *info) { gchar *result; const gchar *second_date; char type; Timespec ts = {0,0}; type = xaccTransGetTxnType (trans); if (type == TXN_TYPE_INVOICE) { xaccTransGetDateDueTS (trans, &ts); second_date = gnc_print_date (ts); result = g_strconcat (so_far, second_date, info->mid_sep, NULL); } else result = g_strconcat (so_far, info->mid_sep, NULL); g_free (so_far); return result; }
static void gnc_plugin_business_update_menus (GncPluginPage *plugin_page) { GncMainWindow *window; GtkActionGroup *action_group; gboolean is_txn_register, is_bus_txn = FALSE, is_bus_doc = FALSE; // We continue only if the current page is a plugin page if (!plugin_page || !GNC_IS_PLUGIN_PAGE(plugin_page)) return; // Check that this is a main window and not embedded sx if (!GNC_IS_MAIN_WINDOW(plugin_page->window)) return; is_txn_register = GNC_IS_PLUGIN_PAGE_REGISTER(plugin_page); window = GNC_MAIN_WINDOW(plugin_page->window); g_return_if_fail(GNC_IS_MAIN_WINDOW(window)); action_group = gnc_main_window_get_action_group(window, PLUGIN_ACTIONS_NAME); g_return_if_fail(GTK_IS_ACTION_GROUP(action_group)); if (is_txn_register) { Transaction *trans = gnc_plugin_page_register_get_current_txn (GNC_PLUGIN_PAGE_REGISTER(plugin_page)); if (xaccTransCountSplits(trans) > 0) is_bus_txn = (xaccTransGetFirstAPARAcctSplit(trans, TRUE) != NULL); is_bus_doc = (xaccTransGetTxnType (trans) == TXN_TYPE_INVOICE); } // Change visibility and also sensitivity according to whether we are in a txn register gnc_plugin_update_actions (action_group, register_txn_actions, "sensitive", is_txn_register && !is_bus_txn && !is_bus_doc); gnc_plugin_update_actions (action_group, register_txn_actions, "visible", is_txn_register && !is_bus_txn && !is_bus_doc); gnc_plugin_update_actions (action_group, register_bus_txn_actions, "sensitive", is_txn_register && is_bus_txn && !is_bus_doc); gnc_plugin_update_actions (action_group, register_bus_txn_actions, "visible", is_txn_register && is_bus_txn && !is_bus_doc); }
gboolean gnc_tree_util_split_reg_rotate (GncTreeViewSplitReg *view, GtkTreeViewColumn *col, Transaction *trans, Split *split) { GtkCellRenderer *cr0 = NULL; GList *renderers; ViewCol viewcol; // Get the first renderer, it has the view-column value. renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col)); cr0 = g_list_nth_data (renderers, 0); g_list_free (renderers); viewcol = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cr0), "view_column")); if (viewcol == COL_RECN) { const char recn_flags[] = {NREC, CREC, 0}; // List of reconciled flags const gchar *flags; const gchar *text; gchar *this_flag; gint index = 0; char rec; flags = recn_flags; text = g_strdup_printf("%c", xaccSplitGetReconcile (split)); /* Find the existing text in the list of flags */ this_flag = strstr (flags, text); if (this_flag != NULL && *this_flag != '\0') { /* In the list, choose the next item in the list (wrapping around as necessary). */ index = this_flag - flags; if (flags[index + 1] != '\0') index = index + 1; else index = 0; rec = recn_flags[index]; } else rec = NREC; gnc_tree_view_split_reg_set_dirty_trans (view, trans); if (!xaccTransIsOpen (trans)) xaccTransBeginEdit (trans); xaccSplitSetReconcile (split, rec); return TRUE; } if (viewcol == COL_TYPE) { const char type_flags[] = {TXN_TYPE_INVOICE, TXN_TYPE_PAYMENT, 0}; // list of type flags const gchar *flags; const gchar *text; gchar *this_flag; gint index = 0; char type; flags = type_flags; text = g_strdup_printf("%c", xaccTransGetTxnType (trans)); /* Find the existing text in the list of flags */ this_flag = strstr (flags, text); if (this_flag != NULL && *this_flag != '\0') { /* In the list, choose the next item in the list (wrapping around as necessary). */ index = this_flag - flags; if (flags[index + 1] != '\0') index = index + 1; else index = 0; type = type_flags[index]; } else type = TXN_TYPE_NONE; gnc_tree_view_split_reg_set_dirty_trans (view, trans); if (!xaccTransIsOpen (trans)) xaccTransBeginEdit (trans); xaccTransSetTxnType (trans, type); return TRUE; } return FALSE; }
static gboolean gncScrubLotLinks (GNCLot *scrub_lot) { gboolean modified = FALSE, restart_needed = FALSE; SplitList *sls_iter = NULL; scrub_start: restart_needed = FALSE; // Iterate over all splits in the lot for (sls_iter = gnc_lot_get_split_list (scrub_lot); sls_iter; sls_iter = sls_iter->next) { Split *sl_split = sls_iter->data; Transaction *ll_txn = NULL; // ll_txn = "Lot Link Transaction" SplitList *lts_iter = NULL; if (!sl_split) continue; // next scrub lot split // Only lot link transactions need to be scrubbed ll_txn = xaccSplitGetParent (sl_split); if (!ll_txn) { // Ooops - the split doesn't belong to any transaction ! // This is not expected so issue a warning and continue with next split PWARN("Encountered a split in a business lot that's not part of any transaction. " "This is unexpected! Skipping split %p.", sl_split); continue; } if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK) continue; // next scrub lot split // Iterate over all splits in the lot link transaction for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next) { Split *ll_txn_split = lts_iter->data; // These all refer to splits in the lot link transaction GNCLot *remote_lot = NULL; // lot at the other end of the lot link transaction gboolean sl_is_doc_lot, rl_is_doc_lot; if (!ll_txn_split) continue; // next lot link transaction split // Skip the split in the lot we're currently scrubbing if (sl_split == ll_txn_split) continue; // next lot link transaction split // Only splits of opposite signed values can be scrubbed if (gnc_numeric_positive_p (xaccSplitGetValue (sl_split)) == gnc_numeric_positive_p (xaccSplitGetValue (ll_txn_split))) continue; // next lot link transaction split // Find linked lot via split remote_lot = xaccSplitGetLot (ll_txn_split); if (!remote_lot) { // This is unexpected - write a warning message and skip this split PWARN("Encountered a Lot Link transaction with a split that's not in any lot. " "This is unexpected! Skipping split %p from transaction %p.", ll_txn_split, ll_txn); continue; } sl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (scrub_lot) != NULL); rl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (remote_lot) != NULL); // Depending on the type of lots we're comparing, we need different actions // - Two document lots (an invoice and a credit note): // Special treatment - look for all document lots linked via ll_txn // and update the memo to be of more use to the uses. // - Two payment lots: // (Part of) the link will be eliminated and instead (part of) // one payment will be added to the other lot to keep the balance. // If the payments are not equal in abs value part of the bigger payment // will be moved to the smaller payment's lot. // - A document and a payment lot: // (Part of) the link will be eliminated and instead (part of) the real // payment will be added to the document lot to handle the payment. if (sl_is_doc_lot && rl_is_doc_lot) gncOwnerSetLotLinkMemo (ll_txn); else if (!sl_is_doc_lot && !rl_is_doc_lot) { gint cmp = gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (sl_split)), gnc_numeric_abs (xaccSplitGetValue (ll_txn_split))); if (cmp >= 0) restart_needed = scrub_other_link (scrub_lot, sl_split, remote_lot, ll_txn_split); else restart_needed = scrub_other_link (remote_lot, ll_txn_split, scrub_lot, sl_split); } else { GNCLot *doc_lot = sl_is_doc_lot ? scrub_lot : remote_lot; GNCLot *pay_lot = sl_is_doc_lot ? remote_lot : scrub_lot; Split *ll_doc_split = sl_is_doc_lot ? sl_split : ll_txn_split; Split *ll_pay_split = sl_is_doc_lot ? ll_txn_split : sl_split; // Ok, let's try to move a payment from pay_lot to doc_lot restart_needed = scrub_other_link (pay_lot, ll_pay_split, doc_lot, ll_doc_split); } // If we got here, the splits in our lot and ll_txn have been severely mixed up // And our iterator lists are probably no longer valid // So let's start over if (restart_needed) { modified = TRUE; goto scrub_start; } } } return modified; }
void gncOwnerSetLotLinkMemo (Transaction *ll_txn) { gchar *memo_prefix = _("Offset between documents: "); gchar *new_memo; SplitList *lts_iter; SplitList *splits = NULL, *siter; GList *titles = NULL, *titer; if (!ll_txn) return; if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK) return; // Find all splits in the lot link transaction that are also in a document lot for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next) { Split *split = lts_iter->data; GNCLot *lot; GncInvoice *invoice; gchar *title; if (!split) continue; lot = xaccSplitGetLot (split); if (!lot) continue; invoice = gncInvoiceGetInvoiceFromLot (lot); if (!invoice) continue; title = g_strdup_printf ("%s %s", gncInvoiceGetTypeString (invoice), gncInvoiceGetID (invoice)); titles = g_list_insert_sorted (titles, title, (GCompareFunc)g_strcmp0); splits = g_list_prepend (splits, split); // splits don't need to be sorted } if (!titles) return; // We didn't find document lots // Create the memo as we'd want it to be new_memo = g_strconcat (memo_prefix, titles->data, NULL); for (titer = titles->next; titer; titer = titer->next) { gchar *tmp_memo = g_strconcat (new_memo, " - ", titer->data, NULL); g_free (new_memo); new_memo = tmp_memo; } g_list_free_full (titles, g_free); // Update the memos of all the splits we found previously (if needed) for (siter = splits; siter; siter = siter->next) { if (g_strcmp0 (xaccSplitGetMemo (siter->data), new_memo) != 0) xaccSplitSetMemo (siter->data, new_memo); } g_list_free (splits); g_free (new_memo); }
Split *gncOwnerFindOffsettingSplit (GNCLot *lot, gnc_numeric target_value) { SplitList *ls_iter = NULL; Split *best_split = NULL; gnc_numeric best_val = { 0, 1}; gint best_flags = 0; if (!lot) return NULL; for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next) { Split *split = ls_iter->data; Transaction *txn; gnc_numeric split_value; gint new_flags = 0; gint val_cmp = 0; if (!split) continue; txn = xaccSplitGetParent (split); if (!txn) { // Ooops - the split doesn't belong to any transaction ! // This is not expected so issue a warning and continue with next split PWARN("Encountered a split in a payment lot that's not part of any transaction. " "This is unexpected! Skipping split %p.", split); continue; } // Check if this split has the opposite sign of the target value we want to offset split_value = xaccSplitGetValue (split); if (gnc_numeric_positive_p (target_value) == gnc_numeric_positive_p (split_value)) continue; // Ok we have found a split that potentially can offset the target value // Let's see if it's better than what we have found already. val_cmp = gnc_numeric_compare (gnc_numeric_abs (split_value), gnc_numeric_abs (target_value)); if (val_cmp == 0) new_flags += is_equal; else if (val_cmp > 0) new_flags += is_more; else new_flags += is_less; if (xaccTransGetTxnType (txn) != TXN_TYPE_LINK) new_flags += is_pay_split; if ((new_flags >= best_flags) && (gnc_numeric_compare (gnc_numeric_abs (split_value), gnc_numeric_abs (best_val)) > 0)) { // The new split is a better match than what we found so far best_split = split; best_flags = new_flags; best_val = split_value; } } return best_split; }