Data Validation
[GnuCash Engine: Core, Non-GUI Accounting Functions]


Detailed Description

Data scrubbing, repairing and forward migration routines. These routines check and repair data, making sure that it is in a format that the current version of the GnuCash Engine likes. These routines serve both to provide backwards compatibility with older versions of GnuCash, and to fix or at least paper over possible current problems.

It is typically expected that the scrub routines are run over newly imported data, as well as during data file input.

In some cases, it is entirely appropriate to invoke these routines from the GUI, to validate that the user input through the GUI is in a format that the system likes. This includes things like balancing individual transactions, or assigning splits to lots, so that capital gains can be computed.


Files

file  Scrub.h
 convert single-entry accounts to clean double-entry
file  Scrub2.h
 Utilities to Convert Stock Accounts to use Lots.
file  Scrub3.h
 Hiogh-Level API for imposing Lot constraints.

Double-Entry Scrubbing

Convert single-entry accounts to clean double-entry

Provides a set of functions and utilities for checking and repairing (formerly called 'scrubbing clean') single-entry accounts so that they can be promoted into self-consistent, clean double-entry accounts. Basically and additionally, this file collects all functions that turn old (deprecated) data structures into the current new data model.

The ScrubOrphans() methods search for transacations that contain splits that do not have a parent account. These "orphaned splits" are placed into an "orphan account" which the user will have to go into and clean up. Kind of like the unix "Lost+Found" directory for orphaned inodes.

void xaccTransScrubOrphans (Transaction *trans)
void xaccAccountScrubOrphans (Account *acc)
void xaccAccountTreeScrubOrphans (Account *acc)
void xaccSplitScrub (Split *split)
void xaccTransScrubSplits (Transaction *trans)
void xaccAccountScrubSplits (Account *account)
void xaccAccountTreeScrubSplits (Account *account)
void xaccTransScrubImbalance (Transaction *trans, Account *root, Account *parent)
void xaccAccountScrubImbalance (Account *acc)
void xaccAccountTreeScrubImbalance (Account *acc)
void xaccTransScrubCurrency (Transaction *trans)
void xaccTransScrubCurrencyFromSplits (Transaction *trans)
void xaccAccountScrubCommodity (Account *account)
void xaccAccountTreeScrubCommodities (Account *acc)
void xaccAccountTreeScrubQuoteSources (Account *root, gnc_commodity_table *table)
void xaccAccountScrubKvp (Account *account)

Lot Management Routines

Provides the low-level API for checking and repairing ('scrubbing clean') the usage of Lots and lot balances in stock and commodity accounts. Broken lots are repaired using a first-in, first-out (FIFO) accounting schedule.

This is a 'low-level' API in the sense that each routine accomplishes only one particular task needed to clean up a Lot. To clean up a Lot as a whole, you almost certainly want to use one of the high-level API routines from the Scrub3.h file.

void xaccAccountAssignLots (Account *acc)
void xaccLotFill (GNCLot *lot)
void xaccLotScrubDoubleBalance (GNCLot *lot)
void xaccScrubSubSplitPrice (Split *split, int maxmult, int maxamtscu)
gboolean xaccScrubMergeSubSplits (Split *split)
gboolean xaccScrubMergeTransSubSplits (Transaction *txn)
gboolean xaccScrubMergeLotSubSplits (GNCLot *lot)

High-Level Lot Constraint

Provides the high-level API for checking and repairing ('scrubbing clean') the usage of Lots and Cap Gains transactions in stock and commodity accounts.

gboolean xaccScrubLot (GNCLot *lot)
void xaccAccountScrubLots (Account *acc)
void xaccAccountTreeScrubLots (Account *acc)


Function Documentation

void xaccAccountAssignLots ( Account acc  ) 

Loop over all splits, and make sure that every split belongs to some lot. If a split does not belong to any lots, poke it into one.

Definition at line 59 of file Scrub2.c.

00060 {
00061    SplitList *splits, *node;
00062 
00063    if (!acc) return;
00064 
00065    ENTER ("acc=%s", xaccAccountGetName(acc));
00066    xaccAccountBeginEdit (acc);
00067 
00068 restart_loop:
00069    splits = xaccAccountGetSplitList(acc);
00070    for (node=splits; node; node=node->next)
00071    {
00072       Split * split = node->data;
00073 
00074       /* If already in lot, then no-op */
00075       if (split->lot) continue;
00076 
00077       /* Skip voided transactions */
00078       if (gnc_numeric_zero_p (split->amount) &&
00079           xaccTransGetVoidStatus(split->parent)) continue;
00080 
00081       if (xaccSplitAssign (split)) goto restart_loop;
00082    }
00083    xaccAccountCommitEdit (acc);
00084    LEAVE ("acc=%s", xaccAccountGetName(acc));
00085 }

void xaccAccountScrubCommodity ( Account account  ) 

The xaccAccountScrubCommodity method fixed accounts without a commodity by using the old account currency and security.

Definition at line 674 of file Scrub.c.

00675 {
00676   gnc_commodity *commodity;
00677 
00678   if (!account) return;
00679   if (xaccAccountGetType(account) == ACCT_TYPE_ROOT) return;
00680 
00681   commodity = xaccAccountGetCommodity (account);
00682   if (commodity) return;
00683 
00684   /* Use the 'obsolete' routines to try to figure out what the
00685    * account commodity should have been. */
00686   commodity = DxaccAccountGetSecurity (account);
00687   if (commodity)
00688   {
00689     xaccAccountSetCommodity (account, commodity);
00690     return;
00691   }
00692 
00693   commodity = DxaccAccountGetCurrency (account);
00694   if (commodity)
00695   {
00696     xaccAccountSetCommodity (account, commodity);
00697     return;
00698   }
00699 
00700   PERR ("Account \"%s\" does not have a commodity!",
00701         xaccAccountGetName(account));
00702 }

void xaccAccountScrubLots ( Account acc  ) 

The xaccAccountScrubLots() routine makes sure that every split in the account is assigned to a lot, and that then, every lot is self-consistent (by calling xaccScrubLot() on each lot).

This routine is the primary routine for ensuring that the lot structure, and the cap-gains for an account are in good order.

Most GUI routines will want to use one of these xacc[*]ScrubLots() routines, instead of the various component routines, since it will usually makes sense to work only with these high-level routines.

Definition at line 159 of file Scrub3.c.

00160 {
00161   LotList *lots, *node;
00162   if (!acc) return;
00163   if (FALSE == xaccAccountHasTrades (acc)) return;
00164                                                                                 
00165   ENTER ("(acc=%s)", xaccAccountGetName(acc));
00166   xaccAccountBeginEdit(acc);
00167   xaccAccountAssignLots (acc);
00168 
00169   lots = xaccAccountGetLotList(acc);
00170   for (node = lots; node; node=node->next)
00171   {
00172     GNCLot *lot = node->data;
00173     xaccScrubLot (lot);
00174   }
00175   g_list_free(lots);
00176   xaccAccountCommitEdit(acc);
00177   LEAVE ("(acc=%s)", xaccAccountGetName(acc));
00178 }

void xaccAccountScrubOrphans ( Account acc  ) 

The xaccAccountScrubOrphans() method performs this scrub only for the indicated account, and not for any of its children.

Definition at line 96 of file Scrub.c.

00097 {
00098   GList *node;
00099   const char *str;
00100 
00101   if (!acc) return;
00102 
00103   str = xaccAccountGetName (acc);
00104   str = str ? str : "(null)";
00105   PINFO ("Looking for orphans in account %s \n", str);
00106 
00107   for (node = xaccAccountGetSplitList(acc); node; node = node->next)
00108   {
00109     Split *split = node->data;
00110 
00111     TransScrubOrphansFast (xaccSplitGetParent (split),
00112                            gnc_account_get_root (acc));
00113   }
00114 }

void xaccAccountTreeScrubCommodities ( Account acc  ) 

The xaccAccountTreeScrubCommodities will scrub the currency/commodity of all accounts & transactions in the specified account or any child account.

Definition at line 732 of file Scrub.c.

00733 {
00734   if (!acc) return;
00735 
00736   xaccAccountTreeForEachTransaction (acc, scrub_trans_currency_helper, NULL);
00737 
00738   scrub_account_commodity_helper (acc, NULL);
00739   gnc_account_foreach_descendant (acc, scrub_account_commodity_helper, NULL);
00740 }

void xaccAccountTreeScrubOrphans ( Account acc  ) 

The xaccAccountTreeScrubOrphans() method performs this scrub for the indicated account and its children.

Definition at line 62 of file Scrub.c.

00063 {
00064   if (!acc) return;
00065 
00066   xaccAccountScrubOrphans (acc);
00067   gnc_account_foreach_descendant(acc,
00068                                  (AccountCb)xaccAccountScrubOrphans, NULL);
00069 }

void xaccAccountTreeScrubQuoteSources ( Account root,
gnc_commodity_table table 
)

This routine will migrate the information about price quote sources from the account data structures to the commodity data structures. It first checks to see if this is necessary since, for the time being, the quote information will still be written out as part of the account. Just in case anyone needs to fall back from CVS to a production version of code.

Parameters:
acc A pointer to the root account containing all accounts in the current book.
table A pointer to the commodity table for the current book.

Definition at line 788 of file Scrub.c.

00789 {
00790   gboolean new_style = FALSE;
00791   ENTER(" ");
00792 
00793   if (!root || !table) {
00794     LEAVE("Oops");
00795     return;
00796   }
00797 
00798   gnc_commodity_table_foreach_commodity (table, check_quote_source, &new_style);
00799 
00800   move_quote_source(root, GINT_TO_POINTER(new_style));
00801   gnc_account_foreach_descendant (root, move_quote_source,
00802                            GINT_TO_POINTER(new_style));
00803   LEAVE("Migration done");
00804 }

void xaccLotFill ( GNCLot lot  ) 

The xaccLotFill() routine attempts to assign splits to the indicated lot until the lot balance goes to zero, or until there are no suitable (i.e. unassigned) splits left in the account. It uses the default accounting policy to choose the splits to fill out the lot.

Definition at line 97 of file Scrub2.c.

00098 {
00099    Account *acc;
00100    Split *split;
00101    GNCPolicy *pcy;
00102 
00103    if (!lot) return;
00104    acc = lot->account;
00105    pcy = gnc_account_get_policy(acc);
00106 
00107    ENTER ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
00108 
00109    /* If balance already zero, we have nothing to do. */
00110    if (gnc_lot_is_closed (lot)) return;
00111 
00112    split = pcy->PolicyGetSplit (pcy, lot);
00113    if (!split) return;   /* Handle the common case */
00114 
00115    /* Reject voided transactions */
00116    if (gnc_numeric_zero_p(split->amount) &&
00117        xaccTransGetVoidStatus(split->parent)) return;
00118 
00119    xaccAccountBeginEdit (acc);
00120 
00121    /* Loop until we've filled up the lot, (i.e. till the 
00122     * balance goes to zero) or there are no splits left.  */
00123    while (1)
00124    {
00125       Split *subsplit;
00126 
00127       subsplit = xaccSplitAssignToLot (split, lot);
00128       if (subsplit == split)
00129       {
00130          PERR ("Accounting Policy gave us a split that "
00131                "doesn't fit into this lot\n"
00132                "lot baln=%s, isclosed=%d, aplit amt=%s",
00133                gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
00134                gnc_lot_is_closed (lot),
00135                gnc_num_dbg_to_string (split->amount));
00136          break;
00137       }
00138 
00139       if (gnc_lot_is_closed (lot)) break;
00140 
00141       split = pcy->PolicyGetSplit (pcy, lot);
00142       if (!split) break;
00143    }
00144    xaccAccountCommitEdit (acc);
00145    LEAVE ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
00146 }

void xaccLotScrubDoubleBalance ( GNCLot lot  ) 

The xaccLotScrubDoubleBalance() routine examines the indicated lot. If it is open, it does nothing. If it is closed, it then verifies that the lot is 'double balanced'. By 'double balance', we mean that both the sum of the split amounts is zero, and that the sum of the split values is zero. If the lot is closed and the sum of the values is not zero, the lot is considered to have a 'realized gain or loss' that hadn't been correctly handled. This routine then creates a balancing transaction to so as to record the realized gain/loss, adds it to the lot, and adds it to a gain/loss account. If there is no default gain/loss account, it creates one.

Definition at line 151 of file Scrub2.c.

00152 {
00153    gnc_commodity *currency = NULL;
00154    SplitList *snode;
00155    GList *node;
00156    gnc_numeric zero = gnc_numeric_zero();
00157    gnc_numeric value = zero;
00158 
00159    if (!lot) return;
00160 
00161    ENTER ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title"));
00162 
00163    for (snode = lot->splits; snode; snode=snode->next)
00164    {
00165       Split *s = snode->data;
00166       xaccSplitComputeCapGains (s, NULL);
00167    }
00168 
00169    /* We double-check only closed lots */
00170    if (FALSE == gnc_lot_is_closed (lot)) return;
00171 
00172    for (snode = lot->splits; snode; snode=snode->next)
00173    {
00174       Split *s = snode->data;
00175       Transaction *trans = s->parent;
00176 
00177       /* Check to make sure all splits in the lot have a common currency */
00178       if (NULL == currency)
00179       {
00180          currency = trans->common_currency;
00181       }
00182       if (FALSE == gnc_commodity_equiv (currency, trans->common_currency))
00183       {
00184          /* This lot has mixed currencies. Can't double-balance.
00185           * Silently punt */
00186          PWARN ("Lot with multiple currencies:\n"
00187                "\ttrans=%s curr=%s", xaccTransGetDescription(trans), 
00188                gnc_commodity_get_fullname(trans->common_currency)); 
00189          break;
00190       }
00191 
00192       /* Now, total up the values */
00193       value = gnc_numeric_add (value, xaccSplitGetValue (s), 
00194                   GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
00195       PINFO ("Split=%p value=%s Accum Lot value=%s", s,
00196           gnc_num_dbg_to_string (s->value),
00197           gnc_num_dbg_to_string (value));
00198           
00199    }
00200 
00201    if (FALSE == gnc_numeric_equal (value, zero))
00202    {
00203       /* Unhandled error condition. Not sure what to do here,
00204        * Since the ComputeCapGains should have gotten it right. 
00205        * I suppose there might be small rounding errors, a penny or two,
00206        * the ideal thing would to figure out why there's a rounding
00207        * error, and fix that.
00208        */
00209       PERR ("Closed lot fails to double-balance !! lot value=%s",
00210             gnc_num_dbg_to_string (value));
00211       for (node=lot->splits; node; node=node->next)
00212       {
00213         Split *s = node->data;
00214         PERR ("s=%p amt=%s val=%s", s, 
00215               gnc_num_dbg_to_string(s->amount),
00216               gnc_num_dbg_to_string(s->value));
00217       }
00218    }
00219 
00220    LEAVE ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title"));
00221 }

gboolean xaccScrubLot ( GNCLot lot  ) 

The xaccScrubLot() routine makes sure that the indicated lot is self-consistent and properly balanced, and fixes it if its not. This is an important routine to call if the amount of any split in the lot is changed. That's because (obviously) changing split values is gaurenteed to throw off lot balances. This routine may end up closing the lot, or at least trying to. It will also cause cap gains to be recomputed.

Scrubbing the lot may cause subsplits to be merged together, i.e. for splits to be deleted. This routine returns true if any splits were deleted.

Definition at line 85 of file Scrub3.c.

00086 {
00087   gboolean splits_deleted = FALSE;
00088   gnc_numeric lot_baln;
00089   gboolean opening_baln_is_pos, lot_baln_is_pos;
00090   Account *acc;
00091   GNCPolicy *pcy;
00092 
00093   if (!lot) return FALSE;
00094   ENTER ("(lot=%p) %s", lot, gnc_lot_get_title(lot));
00095 
00096   acc = gnc_lot_get_account (lot);
00097   pcy = gnc_account_get_policy(acc);
00098   xaccAccountBeginEdit(acc);
00099   xaccScrubMergeLotSubSplits (lot);
00100 
00101   /* If the lot balance is zero, we don't need to rebalance */
00102   lot_baln = gnc_lot_get_balance (lot);
00103   PINFO ("lot baln=%s for %s", gnc_num_dbg_to_string (lot_baln), 
00104                                gnc_lot_get_title(lot));
00105   if (! gnc_numeric_zero_p (lot_baln))
00106   {
00107     SplitList *node;
00108     gnc_numeric opening_baln;
00109 
00110     /* Get the opening balance for this lot */
00111     pcy->PolicyGetLotOpening (pcy, lot, &opening_baln, NULL, NULL);
00112     PINFO ("lot opener baln=%s", gnc_num_dbg_to_string (opening_baln));
00113 
00114     /* If the lot is fat, give the boot to all the non-opening 
00115      * splits, and refill it */
00116     opening_baln_is_pos = gnc_numeric_positive_p(opening_baln);
00117     lot_baln_is_pos = gnc_numeric_positive_p(lot_baln);
00118     if ((opening_baln_is_pos || lot_baln_is_pos) &&
00119         ((!opening_baln_is_pos) || (!lot_baln_is_pos)))
00120     {
00121 rethin:
00122       for (node=gnc_lot_get_split_list(lot); node; node=node->next)
00123       {
00124         Split *s = node->data;
00125         if (pcy->PolicyIsOpeningSplit (pcy, lot, s)) continue;
00126         gnc_lot_remove_split (lot, s);
00127         goto rethin;
00128       }
00129     }
00130 
00131     /* At this point the lot is thin, so try to fill it */
00132     xaccLotFill (lot);
00133 
00134     /* Make sure there are no subsplits. */
00135     splits_deleted = xaccScrubMergeLotSubSplits (lot);
00136   }
00137 
00138   /* Now re-compute cap gains, and then double-check that. 
00139    * But we only compute cap-gains if gains are possible;
00140    * that is if the lot commodity is not the same as the 
00141    * currency. That is, one can't possibly have gains 
00142    * selling dollars for dollars.  The business modules
00143    * use lots with lot commodity == lot currency.
00144    */
00145   if (gains_possible (lot))
00146   {
00147     xaccLotComputeCapGains (lot, NULL);
00148     xaccLotScrubDoubleBalance (lot);
00149   }
00150   xaccAccountCommitEdit(acc);
00151 
00152   LEAVE ("(lot=%s, deleted=%d)", gnc_lot_get_title(lot), splits_deleted);
00153   return splits_deleted;
00154 }

gboolean xaccScrubMergeSubSplits ( Split split  ) 

The xaccScrubMergeSubSplits() routine will merge together all of the splits that were at one time split off from this split, but are no longer needed to be kept separate. Splits might be split up if they need to be divided over multiple lots; they can be merged back together if the lots change. In particular, two sub-splits may be merged if they are in the same lot, or in no lot. Note that, by definition, all subsplits belong to the same transaction.

The routine returns TRUE if a merger was performed, else it returns FALSE.

The xaccScrubMergeTransSubSplits() routine does the same, except that it does it for all of the splits in the transaction. The xaccScrubMergeLotSubSplits() routine does the same, except that it does it for all of the splits in the lot.

Definition at line 397 of file Scrub2.c.

00398 {
00399    gboolean rc = FALSE;
00400    Transaction *txn;
00401    SplitList *node;
00402    GNCLot *lot;
00403    const GUID *guid;
00404 
00405    if (FALSE == is_subsplit (split)) return FALSE;
00406 
00407    txn = split->parent;
00408    lot = xaccSplitGetLot (split);
00409 
00410    ENTER ("(Lot=%s)", gnc_lot_get_title(lot));
00411 restart:
00412    for (node=txn->splits; node; node=node->next)
00413    {
00414       Split *s = node->data;
00415       if (xaccSplitGetLot (s) != lot) continue;
00416       if (s == split) continue;
00417       if (qof_instance_get_destroying(s)) continue;
00418 
00419       /* OK, this split is in the same lot (and thus same account)
00420        * as the indicated split.  Make sure it is really a subsplit
00421        * of the split we started with.  It's possible to have two 
00422        * splits in the same lot and transaction that are not subsplits
00423        * of each other, the test-period test suite does this, for
00424        * example.  Only worry about adjacent sub-splits.  By 
00425        * repeatedly merging adjacent subsplits, we'll get the non-
00426        * adjacent ones too. */
00427       guid = qof_instance_get_guid(s);
00428       if (gnc_kvp_bag_find_by_guid (split->inst.kvp_data, "lot-split",
00429                                     "peer_guid", guid) == NULL)
00430          continue;
00431          
00432       merge_splits (split, s);
00433       rc = TRUE;
00434       goto restart;
00435    }
00436    if (gnc_numeric_zero_p (split->amount))
00437    {
00438       PWARN ("Result of merge has zero amt!");
00439    }
00440    LEAVE (" splits merged=%d", rc);
00441    return rc;
00442 }

void xaccScrubSubSplitPrice ( Split split,
int  maxmult,
int  maxamtscu 
)

If a split has been pulled apart to make it fit into two (or more) lots, then it becomes theoretically possible for each subsplit to have a distinct price. But this would be wrong: each subsplit should have the same price, within rounding errors. This routine will examine the indicated split for sub-splits, and adjust the value of each so that they all have the same price.

There is a bit of a problem with the interpretation of 'rounding errors' because there are pathological corner cases of small amounts. So this routine is loose, hopefully loose enough so that the user can manually fine tune without having this routine clobber thier work.

This routine ignores price differences smaller than 1/maxmult. This routine ignores price differences when the split with a crazy price involes only a small amount: specifically, an amount that is less than maxamtscu/amount.denom.

Reasonable/recommended values might be maxmult=3, maxamtscu = 2.

Definition at line 244 of file Scrub2.c.

00245 {
00246    gnc_numeric src_amt, src_val;
00247    SplitList *node;
00248 
00249    if (FALSE == is_subsplit (split)) return;
00250 
00251    ENTER (" ");
00252    /* Get 'price' of the indicated split */
00253    src_amt = xaccSplitGetAmount (split);
00254    src_val = xaccSplitGetValue (split);
00255 
00256    /* Loop over splits, adjust each so that it has the same
00257     * ratio (i.e. price).  Change the value to get things 
00258     * right; do not change the amount */
00259    for (node=split->parent->splits; node; node=node->next)
00260    {
00261       Split *s = node->data;
00262       Transaction *txn = s->parent;
00263       gnc_numeric dst_amt, dst_val, target_val;
00264       gnc_numeric frac, delta;
00265       int scu;
00266 
00267       /* Skip the reference split */
00268       if (s == split) continue;
00269 
00270       scu = gnc_commodity_get_fraction (txn->common_currency);
00271 
00272       dst_amt = xaccSplitGetAmount (s);
00273       dst_val = xaccSplitGetValue (s);
00274       frac = gnc_numeric_div (dst_amt, src_amt, 
00275                         GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
00276       target_val = gnc_numeric_mul (frac, src_val,
00277                         scu, GNC_HOW_DENOM_EXACT|GNC_HOW_RND_ROUND);
00278       if (gnc_numeric_check (target_val))
00279       {
00280          PERR ("Numeric overflow of value\n"
00281                "\tAcct=%s txn=%s\n"
00282                "\tdst_amt=%s src_val=%s src_amt=%s\n",
00283                xaccAccountGetName (s->acc),
00284                xaccTransGetDescription(txn),
00285                gnc_num_dbg_to_string(dst_amt),
00286                gnc_num_dbg_to_string(src_val),
00287                gnc_num_dbg_to_string(src_amt));
00288          continue;
00289       }
00290 
00291       /* If the required price changes are 'small', do nothing.
00292        * That is a case that the user will have to deal with
00293        * manually.  This routine is really intended only for
00294        * a gross level of synchronization.
00295        */
00296       delta = gnc_numeric_sub_fixed (target_val, dst_val);
00297       delta = gnc_numeric_abs (delta);
00298       if (maxmult * delta.num  < delta.denom) continue;
00299 
00300       /* If the amount is small, pass on that too */
00301       if ((-maxamtscu < dst_amt.num) && (dst_amt.num < maxamtscu)) continue;
00302 
00303       /* Make the actual adjustment */
00304       xaccTransBeginEdit (txn);
00305       xaccSplitSetValue (s, target_val);
00306       xaccTransCommitEdit (txn);
00307    }
00308    LEAVE (" ");
00309 }

void xaccSplitScrub ( Split split  ) 

The xaccSplitScrub method ensures that if this split has the same commodity and currency, then it will have the same amount and value. If the commoidty is the currency, the split->amount is set to the split value. In addition, if this split is an orphan, that is fixed first. If the split account doesn't have a commodity declared, an attempt is made to fix that first.

Definition at line 167 of file Scrub.c.

00168 {
00169   Account *account;
00170   Transaction *trans;
00171   gnc_numeric value, amount;
00172   gnc_commodity *currency, *acc_commodity;
00173   int scu;
00174 
00175   if (!split) return;
00176   ENTER ("(split=%p)", split);
00177 
00178   trans = xaccSplitGetParent (split);
00179   if (!trans) {
00180     LEAVE("no trans");
00181     return;
00182   }
00183 
00184   account = xaccSplitGetAccount (split);
00185 
00186   /* If there's no account, this split is an orphan.
00187    * We need to fix that first, before proceeding.
00188    */
00189   if (!account)
00190   {
00191     xaccTransScrubOrphans (trans);
00192     account = xaccSplitGetAccount (split);
00193   }
00194 
00195   /* Grrr... the register gnc_split_register_load() line 203 of
00196    *  src/register/ledger-core/split-register-load.c will create
00197    * free-floating bogus transactions. Ignore these for now ... 
00198    */
00199   if (!account) 
00200   {
00201     PINFO ("Free Floating Transaction!");
00202     LEAVE ("no account");
00203     return;  
00204   }
00205 
00206   /* Split amounts and values should be valid numbers */
00207   value = xaccSplitGetValue (split);
00208   if (gnc_numeric_check (value))
00209   {
00210     value = gnc_numeric_zero();
00211     xaccSplitSetValue (split, value);
00212   }
00213 
00214   amount = xaccSplitGetAmount (split);
00215   if (gnc_numeric_check (amount))
00216   {
00217     amount = gnc_numeric_zero();
00218     xaccSplitSetAmount (split, amount);
00219   }
00220 
00221   currency = xaccTransGetCurrency (trans);
00222 
00223   /* If the account doesn't have a commodity, 
00224    * we should attempt to fix that first. 
00225   */
00226   acc_commodity = xaccAccountGetCommodity(account);
00227   if (!acc_commodity)
00228   {
00229     xaccAccountScrubCommodity (account);
00230   }
00231   if (!acc_commodity || !gnc_commodity_equiv(acc_commodity, currency))
00232   {
00233     LEAVE ("(split=%p) inequiv currency", split);
00234     return;
00235   }
00236 
00237   scu = MIN (xaccAccountGetCommoditySCU (account),
00238              gnc_commodity_get_fraction (currency));
00239 
00240   if (gnc_numeric_same (amount, value, scu, GNC_HOW_RND_ROUND))
00241   {
00242     LEAVE("(split=%p) different values", split);
00243     return;
00244   }
00245 
00246   /*
00247    * This will be hit every time you answer yes to the dialog "The
00248    * current transaction has changed. Would you like to record it.
00249    */
00250   PINFO ("Adjusted split with mismatched values, desc=\"%s\" memo=\"%s\"" 
00251          " old amount %s %s, new amount %s",
00252             trans->description, split->memo,
00253             gnc_num_dbg_to_string (xaccSplitGetAmount(split)),
00254             gnc_commodity_get_mnemonic (currency),
00255             gnc_num_dbg_to_string (xaccSplitGetValue(split)));
00256 
00257   xaccTransBeginEdit (trans);
00258   xaccSplitSetAmount (split, value);
00259   xaccTransCommitEdit (trans);
00260   LEAVE ("(split=%p)", split);
00261 }

void xaccTransScrubCurrency ( Transaction trans  ) 

The xaccTransScrubCurrency method fixes transactions without a common_currency by using the old account currency and security fields of the parent accounts of the transaction's splits.

Definition at line 565 of file Scrub.c.

00566 {
00567   SplitList *node;
00568   gnc_commodity *currency;
00569 
00570   if (!trans) return;
00571 
00572   /* If there are any orphaned splits in a transaction, then the 
00573    * this routine will fail.  Therefore, we want to make sure that
00574    * there are no orphans (splits without parent account).
00575    */
00576   xaccTransScrubOrphans (trans);
00577 
00578   currency = xaccTransGetCurrency (trans);
00579   if (currency) return;
00580   
00581   currency = xaccTransFindOldCommonCurrency (trans, qof_instance_get_book(trans));
00582   if (currency)
00583   {
00584     xaccTransBeginEdit (trans);
00585     xaccTransSetCurrency (trans, currency);
00586     xaccTransCommitEdit (trans);
00587   }
00588   else
00589   {
00590     if (NULL == trans->splits)
00591     {
00592       PWARN ("Transaction \"%s\" has no splits in it!", trans->description);
00593     }
00594     else
00595     {
00596       SplitList *node;
00597       char guid_str[GUID_ENCODING_LENGTH+1];
00598       guid_to_string_buff(xaccTransGetGUID(trans), guid_str);
00599       PWARN ("no common transaction currency found for trans=\"%s\" (%s)",
00600              trans->description, guid_str);
00601 
00602       for (node=trans->splits; node; node=node->next)
00603       {
00604         Split *split = node->data;
00605         if (NULL == split->acc)
00606         {
00607           PWARN (" split=\"%s\" is not in any account!", split->memo);
00608         }
00609         else
00610         {
00611           PWARN (" split=\"%s\" account=\"%s\" commodity=\"%s\"", 
00612                  split->memo, xaccAccountGetName(split->acc),
00613                  gnc_commodity_get_mnemonic(xaccAccountGetCommodity(split->acc)));
00614         }
00615       }
00616     }
00617   }
00618 
00619   for (node=trans->splits; node; node=node->next)
00620   {
00621     Split *sp = node->data;
00622 
00623     if (!gnc_numeric_equal(xaccSplitGetAmount (sp), 
00624         xaccSplitGetValue (sp))) 
00625     {
00626       gnc_commodity *acc_currency;
00627 
00628       acc_currency = sp->acc ? xaccAccountGetCommodity(sp->acc) : NULL;
00629       if (acc_currency == currency) 
00630       {
00631         /* This Split needs fixing: The transaction-currency equals
00632          * the account-currency/commodity, but the amount/values are
00633          * inequal i.e. they still correspond to the security
00634          * (amount) and the currency (value). In the new model, the
00635          * value is the amount in the account-commodity -- so it
00636          * needs to be set to equal the amount (since the
00637          * account-currency doesn't exist anymore). 
00638          *
00639          * Note: Nevertheless we lose some information here. Namely,
00640          * the information that the 'amount' in 'account-old-security'
00641          * was worth 'value' in 'account-old-currency'. Maybe it would
00642          * be better to store that information in the price database? 
00643          * But then, for old currency transactions there is still the
00644          * 'other' transaction, which is going to keep that
00645          * information. So I don't bother with that here. -- cstim,
00646          * 2002/11/20. */
00647           
00648         PWARN ("Adjusted split with mismatched values, desc=\"%s\" memo=\"%s\"" 
00649                " old amount %s %s, new amount %s",
00650                trans->description, sp->memo,
00651                gnc_num_dbg_to_string (xaccSplitGetAmount(sp)),
00652                gnc_commodity_get_mnemonic (currency),
00653                gnc_num_dbg_to_string (xaccSplitGetValue(sp)));
00654         xaccTransBeginEdit (trans);
00655         xaccSplitSetAmount (sp, xaccSplitGetValue(sp));
00656         xaccTransCommitEdit (trans);
00657       }
00658       /*else 
00659       {
00660         PINFO ("Ok: Split '%s' Amount %s %s, value %s %s",
00661         xaccSplitGetMemo (sp),
00662         gnc_num_dbg_to_string (amount),
00663         gnc_commodity_get_mnemonic (currency),
00664         gnc_num_dbg_to_string (value),
00665         gnc_commodity_get_mnemonic (acc_currency));
00666       }*/
00667     }
00668   }
00669 }

void xaccTransScrubCurrencyFromSplits ( Transaction trans  ) 

The xaccTransScrubCurrencyFromSplits method fixes transactions where the currency doesn't match the currency used in the splits in the transaction. If all splits where the amount equals the value and where the commodity is a currency have the same currency, it sets the transaction's currency to that if it is anything else. If the splits don't match that description the transaction currency is not changed.

Definition at line 297 of file Scrub.c.

00298 {
00299   GList *node;
00300   gnc_commodity *common_currency = NULL;
00301     
00302   if (!trans) return;
00303   
00304   for (node = xaccTransGetSplitList (trans); node; node = node->next) {
00305     Split *split = node->data;
00306 
00307     if (!xaccTransStillHasSplit(trans, split)) continue;
00308     if (gnc_numeric_equal(xaccSplitGetAmount (split),
00309                           xaccSplitGetValue (split))) {
00310 
00311       Account *s_account = xaccSplitGetAccount (split);
00312       gnc_commodity *s_commodity = xaccAccountGetCommodity (s_account);
00313       
00314       if (s_commodity) {
00315         if (gnc_commodity_is_currency(s_commodity)) {
00316           /* Found a split where the amount is the same as the value and
00317              the commodity is a currency.  If all splits in the transaction
00318              that fit this description are in the same currency then the
00319              transaction should be in that currency too. */
00320 
00321           if (common_currency == NULL)
00322             /* First one we've found, save the currency */
00323             common_currency = s_commodity;
00324           else if ( !gnc_commodity_equiv (common_currency, s_commodity)) {
00325             /* Splits are inconsistent, more than one has a value equal to
00326                the amount, but they aren't all in the same currency. */
00327             common_currency = NULL;
00328             break;
00329           }
00330         }
00331       }
00332     }
00333   }
00334     
00335   if (common_currency &&
00336       !gnc_commodity_equiv (common_currency, xaccTransGetCurrency (trans))) {
00337 
00338     /* Found a common currency for the splits, and the transaction is not
00339        in that currency */
00340     gboolean trans_was_open;
00341 
00342     PINFO ("transaction in wrong currency");
00343       
00344     trans_was_open = xaccTransIsOpen (trans);
00345 
00346     if (!trans_was_open)
00347       xaccTransBeginEdit (trans);
00348 
00349     xaccTransSetCurrency (trans, common_currency);
00350     
00351     if (!trans_was_open)
00352       xaccTransCommitEdit (trans);
00353   }
00354 }

void xaccTransScrubImbalance ( Transaction trans,
Account root,
Account parent 
)

The xaccScrubImbalance() method searches for transactions that do not balance to zero. If any such transactions are found, a split is created to offset this amount and is added to an "imbalance" account.

Definition at line 357 of file Scrub.c.

00359 {
00360   Split *balance_split = NULL;
00361   gnc_numeric imbalance;
00362 
00363   if (!trans) return;
00364 
00365   ENTER ("()");
00366 
00367   /* Must look for orphan splits even if there is no imbalance. */
00368   xaccTransScrubSplits (trans);
00369 
00370   /* If the transaction is balanced, nothing more to do */
00371   imbalance = xaccTransGetImbalance (trans);
00372   if (gnc_numeric_zero_p (imbalance)) {
00373     LEAVE("zero imbalance");
00374     return;
00375   }
00376 
00377   if (!account)
00378   {
00379     if (!root) 
00380     {
00381        root = gnc_book_get_root_account (xaccTransGetBook (trans));
00382        if (NULL == root)
00383        {
00384           /* This can't occur, things should be in books */
00385           PERR ("Bad data corruption, no root account in book");
00386           LEAVE("");
00387           return;
00388        }
00389     }
00390     account = xaccScrubUtilityGetOrMakeAccount (root, 
00391         trans->common_currency, _("Imbalance"));
00392     if (!account) {
00393         PERR ("Can't get balancing account");
00394         LEAVE("");
00395         return;
00396     }
00397   }
00398 
00399   balance_split = xaccTransFindSplitByAccount(trans, account);
00400 
00401   /* Put split into account before setting split value */
00402   if (!balance_split)
00403   {
00404     balance_split = xaccMallocSplit (qof_instance_get_book(trans));
00405 
00406     xaccTransBeginEdit (trans);
00407     xaccSplitSetParent(balance_split, trans);
00408     xaccSplitSetAccount(balance_split, account);
00409     xaccTransCommitEdit (trans);
00410   }
00411 
00412   PINFO ("unbalanced transaction");
00413 
00414   {
00415     const gnc_commodity *currency;
00416     const gnc_commodity *commodity;
00417     gnc_numeric old_value, new_value;
00418 
00419     xaccTransBeginEdit (trans);
00420 
00421     currency = xaccTransGetCurrency (trans);
00422     old_value = xaccSplitGetValue (balance_split);
00423 
00424     /* Note: We have to round for the commodity's fraction, NOT any
00425      * already existing denominator (bug #104343), because either one
00426      * of the denominators might already be reduced.  */
00427     new_value = gnc_numeric_sub (old_value, imbalance,
00428              gnc_commodity_get_fraction(currency), 
00429              GNC_HOW_RND_ROUND);
00430 
00431     xaccSplitSetValue (balance_split, new_value);
00432 
00433     commodity = xaccAccountGetCommodity (account);
00434     if (gnc_commodity_equiv (currency, commodity))
00435     {
00436       xaccSplitSetAmount (balance_split, new_value);
00437     }
00438 
00439     xaccSplitScrub (balance_split);
00440     xaccTransCommitEdit (trans);
00441   }
00442   LEAVE ("()");
00443 }

void xaccTransScrubOrphans ( Transaction trans  ) 

The xaccTransScrubOrphans() method scrubs only the splits in the given transaction.

Definition at line 118 of file Scrub.c.

00119 {
00120   SplitList *node;
00121   QofBook *book = NULL;
00122   Account *root = NULL;
00123   for (node = trans->splits; node; node = node->next)
00124   {
00125     Split *split = node->data;
00126 
00127     if (split->acc)
00128     {
00129       TransScrubOrphansFast (trans, gnc_account_get_root(split->acc));
00130       return;
00131     }
00132   }
00133 
00134   /* If we got to here, then *none* of the splits belonged to an 
00135    * account.  Not a happy situation.  We should dig an account
00136    * out of the book the transaction belongs to.
00137    * XXX we should probably *always* to this, instead of the above loop!
00138    */
00139   PINFO ("Free Floating Transaction!");
00140   book = xaccTransGetBook (trans);
00141   root = gnc_book_get_root_account (book);
00142   TransScrubOrphansFast (trans, root);
00143 }

void xaccTransScrubSplits ( Transaction trans  ) 

The xacc*ScrubSplits() calls xaccSplitScrub() on each split in the respective structure: transaction, account, account & it's children, account-group.

Definition at line 1800 of file Transaction.c.

01801 {
01802     gnc_commodity *currency;
01803 
01804     if (!trans) return;
01805 
01806     xaccTransBeginEdit(trans);
01807     /* The split scrub expects the transaction to have a currency! */
01808     currency = xaccTransGetCurrency (trans);
01809     if (!currency)
01810         PERR ("Transaction doesn't have a currency!");
01811 
01812     FOR_EACH_SPLIT(trans, xaccSplitScrub(s));
01813     xaccTransCommitEdit(trans);
01814 }


Generated on Fri Oct 10 05:06:54 2008 for GnuCash by  doxygen 1.5.2