gboolean xaccScrubMergeSubSplits (Split *split, gboolean strict) { gboolean rc = FALSE; Transaction *txn; SplitList *node; GNCLot *lot; if (strict && (FALSE == is_subsplit (split))) return FALSE; txn = split->parent; // Don't mess with splits from an invoice transaction // Those are the responsibility of the business code if (gncInvoiceGetInvoiceFromTxn (txn)) return FALSE; lot = xaccSplitGetLot (split); ENTER ("(Lot=%s)", gnc_lot_get_title(lot)); restart: for (node = txn->splits; node; node = node->next) { Split *s = node->data; if (xaccSplitGetLot (s) != lot) continue; if (s == split) continue; if (qof_instance_get_destroying(s)) continue; // Don't mess with splits from an invoice transaction // Those are the responsibility of the business code if (gncInvoiceGetInvoiceFromTxn (s->parent)) return FALSE; if (strict) { /* OK, this split is in the same lot (and thus same account) * as the indicated split. Make sure it is really a subsplit * of the split we started with. It's possible to have two * splits in the same lot and transaction that are not subsplits * of each other, the test-period test suite does this, for * example. Only worry about adjacent sub-splits. By * repeatedly merging adjacent subsplits, we'll get the non- * adjacent ones too. */ if (!xaccSplitIsPeerSplit (split, s)) continue; } merge_splits (split, s); rc = TRUE; goto restart; } if (rc && gnc_numeric_zero_p (split->amount)) { time64 pdate = xaccTransGetDate (txn); gchar *pdatestr = gnc_ctime (&pdate); PWARN ("Result of merge has zero amt!"); PWARN ("Transaction details - posted date %s - description %s", pdatestr, xaccTransGetDescription(txn)); g_free (pdatestr); } LEAVE (" splits merged=%d", rc); return rc; }
gboolean gncOwnerReduceSplitTo (Split *split, gnc_numeric target_value) { gnc_numeric split_val = xaccSplitGetValue (split); gnc_numeric rem_val; Split *rem_split; Transaction *txn; GNCLot *lot; if (gnc_numeric_positive_p (split_val) != gnc_numeric_positive_p (target_value)) return FALSE; // Split and target value have to be of the same sign if (gnc_numeric_equal (split_val, target_value)) return FALSE; // Split already has the target value rem_val = gnc_numeric_sub (split_val, target_value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); // note: values are of opposite sign rem_split = xaccMallocSplit (xaccSplitGetBook (split)); xaccSplitCopyOnto (split, rem_split); xaccSplitSetValue (rem_split, rem_val); txn = xaccSplitGetParent (split); xaccTransBeginEdit (txn); xaccSplitSetValue (split, target_value); xaccSplitSetParent (rem_split, txn); xaccTransCommitEdit (txn); lot = xaccSplitGetLot (split); gnc_lot_add_split (lot, rem_split); return TRUE; }
gboolean xaccScrubMergeSubSplits (Split *split) { gboolean rc = FALSE; Transaction *txn; SplitList *node; GNCLot *lot; const GncGUID *guid; if (FALSE == is_subsplit (split)) return FALSE; txn = split->parent; lot = xaccSplitGetLot (split); ENTER ("(Lot=%s)", gnc_lot_get_title(lot)); restart: for (node = txn->splits; node; node = node->next) { Split *s = node->data; if (xaccSplitGetLot (s) != lot) continue; if (s == split) continue; if (qof_instance_get_destroying(s)) continue; /* OK, this split is in the same lot (and thus same account) * as the indicated split. Make sure it is really a subsplit * of the split we started with. It's possible to have two * splits in the same lot and transaction that are not subsplits * of each other, the test-period test suite does this, for * example. Only worry about adjacent sub-splits. By * repeatedly merging adjacent subsplits, we'll get the non- * adjacent ones too. */ guid = qof_instance_get_guid(s); if (gnc_kvp_bag_find_by_guid (split->inst.kvp_data, "lot-split", "peer_guid", guid) == NULL) continue; merge_splits (split, s); rc = TRUE; goto restart; } if (gnc_numeric_zero_p (split->amount)) { PWARN ("Result of merge has zero amt!"); } LEAVE (" splits merged=%d", rc); return rc; }
LotList * xaccQueryGetLots (QofQuery * q, query_txn_match_t runtype) { GList * splits = qof_query_run(q); GList * current = NULL; GList * retval = NULL; GHashTable * lot_hash = g_hash_table_new(g_direct_hash, g_direct_equal); GNCLot * lot = NULL; gpointer val = NULL; int count = 0; /* iterate over matching splits, incrementing a match-count in * the hash table */ for (current = splits; current; current = current->next) { lot = xaccSplitGetLot((Split *)(current->data)); /* don't waste time looking up unless we need the count * information */ if (runtype == QUERY_TXN_MATCH_ALL) { val = g_hash_table_lookup(lot_hash, lot); count = GPOINTER_TO_INT(val); } g_hash_table_insert(lot_hash, lot, GINT_TO_POINTER(count + 1)); } /* now pick out the transactions that match */ if (runtype == QUERY_TXN_MATCH_ALL) { g_hash_table_foreach(lot_hash, query_match_all_lot_filter_func, &retval); } else { g_hash_table_foreach(lot_hash, query_match_any_lot_filter_func, &retval); } g_hash_table_destroy(lot_hash); return retval; }
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; }
static xmlNodePtr split_to_dom_tree(const gchar *tag, Split *spl) { xmlNodePtr ret; ret = xmlNewNode(NULL, BAD_CAST tag); xmlAddChild(ret, guid_to_dom_tree("split:id", xaccSplitGetGUID(spl))); { char *memo = g_strdup (xaccSplitGetMemo(spl)); if (memo && g_strcmp0(memo, "") != 0) { xmlNewTextChild(ret, NULL, BAD_CAST "split:memo", checked_char_cast (memo)); } g_free (memo); } { char *action = g_strdup (xaccSplitGetAction(spl)); if (action && g_strcmp0(action, "") != 0) { xmlNewTextChild(ret, NULL, BAD_CAST "split:action", checked_char_cast (action)); } g_free (action); } { char tmp[2]; tmp[0] = xaccSplitGetReconcile(spl); tmp[1] = '\0'; xmlNewTextChild(ret, NULL, BAD_CAST "split:reconciled-state", BAD_CAST tmp); } add_timespec(ret, "split:reconcile-date", xaccSplitRetDateReconciledTS(spl), FALSE); add_gnc_num(ret, "split:value", xaccSplitGetValue(spl)); add_gnc_num(ret, "split:quantity", xaccSplitGetAmount(spl)); { Account * account = xaccSplitGetAccount (spl); xmlAddChild (ret, guid_to_dom_tree("split:account", xaccAccountGetGUID (account))); } { GNCLot * lot = xaccSplitGetLot (spl); if (lot) { xmlAddChild (ret, guid_to_dom_tree("split:lot", gnc_lot_get_guid(lot))); } } { xmlNodePtr kvpnode = kvp_frame_to_dom_tree("split:slots", xaccSplitGetSlots(spl)); if (kvpnode) { xmlAddChild(ret, kvpnode); } } return ret; }
static gboolean gncScrubLotDanglingPayments (GNCLot *lot) { SplitList * split_list, *filtered_list = NULL, *match_list = NULL, *node; Split *ll_split = gnc_lot_get_earliest_split (lot); Transaction *ll_trans = xaccSplitGetParent (ll_split); gnc_numeric ll_val = xaccSplitGetValue (ll_split); time64 ll_date = xaccTransGetDate (ll_trans); const char *ll_desc = xaccTransGetDescription (ll_trans); // look for free splits (i.e. not in any lot) which, // compared to the lot link split // - have the same date // - have the same description // - have an opposite sign amount // - free split's abs value is less than or equal to ll split's abs value split_list = xaccAccountGetSplitList(gnc_lot_get_account (lot)); for (node = split_list; node; node = node->next) { Split *free_split = node->data; Transaction *free_trans; gnc_numeric free_val; if (NULL != xaccSplitGetLot(free_split)) continue; free_trans = xaccSplitGetParent (free_split); if (ll_date != xaccTransGetDate (free_trans)) continue; if (0 != g_strcmp0 (ll_desc, xaccTransGetDescription (free_trans))) continue; free_val = xaccSplitGetValue (free_split); if (gnc_numeric_positive_p (ll_val) == gnc_numeric_positive_p (free_val)) continue; if (gnc_numeric_compare (gnc_numeric_abs (free_val), gnc_numeric_abs (ll_val)) > 0) continue; filtered_list = g_list_append(filtered_list, free_split); } match_list = gncSLFindOffsSplits (filtered_list, ll_val); g_list_free (filtered_list); for (node = match_list; node; node = node->next) { Split *match_split = node->data; gnc_lot_add_split (lot, match_split); } if (match_list) { g_list_free (match_list); return TRUE; } else 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); }
static xmlNodePtr split_to_dom_tree (const gchar* tag, Split* spl) { xmlNodePtr ret; ret = xmlNewNode (NULL, BAD_CAST tag); xmlAddChild (ret, guid_to_dom_tree ("split:id", xaccSplitGetGUID (spl))); { char* memo = g_strdup (xaccSplitGetMemo (spl)); if (memo && g_strcmp0 (memo, "") != 0) { xmlNewTextChild (ret, NULL, BAD_CAST "split:memo", checked_char_cast (memo)); } g_free (memo); } { char* action = g_strdup (xaccSplitGetAction (spl)); if (action && g_strcmp0 (action, "") != 0) { xmlNewTextChild (ret, NULL, BAD_CAST "split:action", checked_char_cast (action)); } g_free (action); } { char tmp[2]; tmp[0] = xaccSplitGetReconcile (spl); tmp[1] = '\0'; xmlNewTextChild (ret, NULL, BAD_CAST "split:reconciled-state", BAD_CAST tmp); } add_timespec (ret, "split:reconcile-date", xaccSplitRetDateReconciledTS (spl), FALSE); add_gnc_num (ret, "split:value", xaccSplitGetValue (spl)); add_gnc_num (ret, "split:quantity", xaccSplitGetAmount (spl)); { Account* account = xaccSplitGetAccount (spl); xmlAddChild (ret, guid_to_dom_tree ("split:account", xaccAccountGetGUID (account))); } { GNCLot* lot = xaccSplitGetLot (spl); if (lot) { xmlAddChild (ret, guid_to_dom_tree ("split:lot", gnc_lot_get_guid (lot))); } } /* xmlAddChild won't do anything with a NULL, so tests are superfluous. */ xmlAddChild (ret, qof_instance_slots_to_dom_tree ("split:slots", QOF_INSTANCE (spl))); return ret; }