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


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.

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.


Function Documentation

void xaccAccountAssignLots ( Account acc  ) 

The xaccAccountAssignLots() routine will walk over all of the splits in an account, and make sure that each belongs to a lot. Currently, the default (and only implemented) assignment policy is a FIFO policy: Any splits that are not in a lot will be used to close the oldest open lot(s). If there are no open lots, a new lot will be started. By trying to close the oldest lots, this effectively implements a FIFO acounting policy.

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 58 of file Scrub2.c.

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

void xaccAccountScrubCommodity ( Account account  ) 

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

Definition at line 1063 of file Scrub.c.

01064 {
01065     gnc_commodity *commodity;
01066 
01067     if (!account) return;
01068     if (xaccAccountGetType(account) == ACCT_TYPE_ROOT) return;
01069 
01070     commodity = xaccAccountGetCommodity (account);
01071     if (commodity) return;
01072 
01073     /* Use the 'obsolete' routines to try to figure out what the
01074      * account commodity should have been. */
01075     commodity = DxaccAccountGetSecurity (account);
01076     if (commodity)
01077     {
01078         xaccAccountSetCommodity (account, commodity);
01079         return;
01080     }
01081 
01082     commodity = DxaccAccountGetCurrency (account);
01083     if (commodity)
01084     {
01085         xaccAccountSetCommodity (account, commodity);
01086         return;
01087     }
01088 
01089     PERR ("Account \"%s\" does not have a commodity!",
01090           xaccAccountGetName(account));
01091 }

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 102 of file Scrub.c.

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

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 1121 of file Scrub.c.

01122 {
01123     if (!acc) return;
01124 
01125     xaccAccountTreeForEachTransaction (acc, scrub_trans_currency_helper, NULL);
01126 
01127     scrub_account_commodity_helper (acc, NULL);
01128     gnc_account_foreach_descendant (acc, scrub_account_commodity_helper, NULL);
01129 }

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:
root 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 1178 of file Scrub.c.

01179 {
01180     gboolean new_style = FALSE;
01181     ENTER(" ");
01182 
01183     if (!root || !table)
01184     {
01185         LEAVE("Oops");
01186         return;
01187     }
01188 
01189     gnc_commodity_table_foreach_commodity (table, check_quote_source, &new_style);
01190 
01191     move_quote_source(root, GINT_TO_POINTER(new_style));
01192     gnc_account_foreach_descendant (root, move_quote_source,
01193                                     GINT_TO_POINTER(new_style));
01194     LEAVE("Migration done");
01195 }

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 96 of file Scrub2.c.

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

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 150 of file Scrub2.c.

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

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 396 of file Scrub2.c.

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

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 243 of file Scrub2.c.

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

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 commodity 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 173 of file Scrub.c.

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

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 954 of file Scrub.c.

00955 {
00956     SplitList *node;
00957     gnc_commodity *currency;
00958 
00959     if (!trans) return;
00960 
00961     /* If there are any orphaned splits in a transaction, then the
00962      * this routine will fail.  Therefore, we want to make sure that
00963      * there are no orphans (splits without parent account).
00964      */
00965     xaccTransScrubOrphans (trans);
00966 
00967     currency = xaccTransGetCurrency (trans);
00968     if (currency) return;
00969 
00970     currency = xaccTransFindOldCommonCurrency (trans, qof_instance_get_book(trans));
00971     if (currency)
00972     {
00973         xaccTransBeginEdit (trans);
00974         xaccTransSetCurrency (trans, currency);
00975         xaccTransCommitEdit (trans);
00976     }
00977     else
00978     {
00979         if (NULL == trans->splits)
00980         {
00981             PWARN ("Transaction \"%s\" has no splits in it!", trans->description);
00982         }
00983         else
00984         {
00985             SplitList *node;
00986             char guid_str[GUID_ENCODING_LENGTH+1];
00987             guid_to_string_buff(xaccTransGetGUID(trans), guid_str);
00988             PWARN ("no common transaction currency found for trans=\"%s\" (%s)",
00989                    trans->description, guid_str);
00990 
00991             for (node = trans->splits; node; node = node->next)
00992             {
00993                 Split *split = node->data;
00994                 if (NULL == split->acc)
00995                 {
00996                     PWARN (" split=\"%s\" is not in any account!", split->memo);
00997                 }
00998                 else
00999                 {
01000                     PWARN (" split=\"%s\" account=\"%s\" commodity=\"%s\"",
01001                            split->memo, xaccAccountGetName(split->acc),
01002                            gnc_commodity_get_mnemonic(xaccAccountGetCommodity(split->acc)));
01003                 }
01004             }
01005         }
01006     }
01007 
01008     for (node = trans->splits; node; node = node->next)
01009     {
01010         Split *sp = node->data;
01011 
01012         if (!gnc_numeric_equal(xaccSplitGetAmount (sp),
01013                                xaccSplitGetValue (sp)))
01014         {
01015             gnc_commodity *acc_currency;
01016 
01017             acc_currency = sp->acc ? xaccAccountGetCommodity(sp->acc) : NULL;
01018             if (acc_currency == currency)
01019             {
01020                 /* This Split needs fixing: The transaction-currency equals
01021                  * the account-currency/commodity, but the amount/values are
01022                  * inequal i.e. they still correspond to the security
01023                  * (amount) and the currency (value). In the new model, the
01024                  * value is the amount in the account-commodity -- so it
01025                  * needs to be set to equal the amount (since the
01026                  * account-currency doesn't exist anymore).
01027                  *
01028                  * Note: Nevertheless we lose some information here. Namely,
01029                  * the information that the 'amount' in 'account-old-security'
01030                  * was worth 'value' in 'account-old-currency'. Maybe it would
01031                  * be better to store that information in the price database?
01032                  * But then, for old currency transactions there is still the
01033                  * 'other' transaction, which is going to keep that
01034                  * information. So I don't bother with that here. -- cstim,
01035                  * 2002/11/20. */
01036 
01037                 PWARN ("Adjusted split with mismatched values, desc=\"%s\" memo=\"%s\""
01038                        " old amount %s %s, new amount %s",
01039                        trans->description, sp->memo,
01040                        gnc_num_dbg_to_string (xaccSplitGetAmount(sp)),
01041                        gnc_commodity_get_mnemonic (currency),
01042                        gnc_num_dbg_to_string (xaccSplitGetValue(sp)));
01043                 xaccTransBeginEdit (trans);
01044                 xaccSplitSetAmount (sp, xaccSplitGetValue(sp));
01045                 xaccTransCommitEdit (trans);
01046             }
01047             /*else
01048             {
01049               PINFO ("Ok: Split '%s' Amount %s %s, value %s %s",
01050               xaccSplitGetMemo (sp),
01051               gnc_num_dbg_to_string (amount),
01052               gnc_commodity_get_mnemonic (currency),
01053               gnc_num_dbg_to_string (value),
01054               gnc_commodity_get_mnemonic (acc_currency));
01055             }*/
01056         }
01057     }
01058 }

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 304 of file Scrub.c.

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

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 584 of file Scrub.c.

00586 {
00587     const gnc_commodity *currency;
00588 
00589     if (!trans) return;
00590 
00591     ENTER ("()");
00592 
00593     /* Must look for orphan splits even if there is no imbalance. */
00594     xaccTransScrubSplits (trans);
00595 
00596     /* Return immediately if things are balanced. */
00597     if (xaccTransIsBalanced (trans))
00598         return;
00599 
00600     currency = xaccTransGetCurrency (trans);
00601 
00602     if (! xaccTransUseTradingAccounts (trans))
00603     {
00604         gnc_numeric imbalance;
00605 
00606         /* Make the value sum to zero */
00607         imbalance = xaccTransGetImbalanceValue (trans);
00608         if (! gnc_numeric_zero_p (imbalance))
00609         {
00610             PINFO ("Value unbalanced transaction");
00611 
00612             add_balance_split (trans, imbalance, root, account);
00613         }
00614     }
00615     else
00616     {
00617         MonetaryList *imbal_list;
00618         MonetaryList *imbalance_commod;
00619         GList *splits;
00620         gnc_numeric imbalance;
00621         Split *balance_split = NULL;
00622 
00623         /* If there are existing trading splits, adjust the price or exchange
00624            rate in each of them to agree with the non-trading splits for the
00625            same commodity.  If there are multiple non-trading splits for the
00626            same commodity in the transaction this will use the exchange rate in
00627            the last such split.  This shouldn't happen, and if it does then there's
00628            not much we can do about it anyway.
00629 
00630            While we're at it, compute the value imbalance ignoring existing
00631            trading splits. */
00632 
00633         imbalance = gnc_numeric_zero();
00634 
00635         for (splits = trans->splits; splits; splits = splits->next)
00636         {
00637             Split *split = splits->data;
00638             gnc_numeric value, amount;
00639             gnc_commodity *commodity;
00640 
00641             if (! xaccTransStillHasSplit (trans, split)) continue;
00642 
00643             commodity = xaccAccountGetCommodity (xaccSplitGetAccount(split));
00644             if (!commodity)
00645             {
00646                 PERR("Split has no commodity");
00647                 continue;
00648             }
00649 
00650             balance_split = find_trading_split (trans, root, commodity);
00651 
00652             if (balance_split != split)
00653                 /* this is not a trading split */
00654                 imbalance = gnc_numeric_add(imbalance, xaccSplitGetValue (split),
00655                                             GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
00656 
00657             /* Ignore splits where value or amount is zero */
00658             value = xaccSplitGetValue (split);
00659             amount = xaccSplitGetAmount (split);
00660             if (gnc_numeric_zero_p(amount) || gnc_numeric_zero_p(value))
00661                 continue;
00662 
00663             if (balance_split && balance_split != split)
00664             {
00665                 gnc_numeric convrate = gnc_numeric_div (amount, value,
00666                                                         GNC_DENOM_AUTO, GNC_DENOM_REDUCE);
00667                 gnc_numeric old_value, new_value;
00668                 old_value = xaccSplitGetValue(balance_split);
00669                 new_value = gnc_numeric_div (xaccSplitGetAmount(balance_split),
00670                                              convrate,
00671                                              gnc_commodity_get_fraction(currency),
00672                                              GNC_RND_ROUND);
00673                 if (! gnc_numeric_equal (old_value, new_value))
00674                 {
00675                     xaccTransBeginEdit (trans);
00676                     xaccSplitSetValue (balance_split, new_value);
00677                     xaccSplitScrub (balance_split);
00678                     xaccTransCommitEdit (trans);
00679                 }
00680             }
00681         }
00682 
00683         /* Balance the value, ignoring existing trading splits */
00684         if (! gnc_numeric_zero_p (imbalance))
00685         {
00686             PINFO ("Value unbalanced transaction");
00687 
00688             add_balance_split (trans, imbalance, root, account);
00689         }
00690 
00691         /* If the transaction is balanced, nothing more to do */
00692         imbal_list = xaccTransGetImbalance (trans);
00693         if (!imbal_list)
00694         {
00695             LEAVE("()");
00696             return;
00697         }
00698 
00699         PINFO ("Currency unbalanced transaction");
00700 
00701         for (imbalance_commod = imbal_list; imbalance_commod;
00702                 imbalance_commod = imbalance_commod->next)
00703         {
00704             gnc_monetary *imbal_mon = imbalance_commod->data;
00705             gnc_commodity *commodity;
00706             gnc_numeric old_amount, new_amount;
00707             gnc_numeric old_value, new_value, val_imbalance;
00708             GList *splits;
00709 
00710             commodity = gnc_monetary_commodity (*imbal_mon);
00711 
00712             balance_split = get_trading_split(trans, root, commodity);
00713             if (!balance_split)
00714             {
00715                 /* Error already logged */
00716                 gnc_monetary_list_free(imbal_list);
00717                 LEAVE("");
00718                 return;
00719             }
00720 
00721             account = xaccSplitGetAccount(balance_split);
00722 
00723             if (! gnc_commodity_equal (currency, commodity))
00724             {
00725                 /* Find the value imbalance in this commodity */
00726                 val_imbalance = gnc_numeric_zero();
00727                 for (splits = trans->splits; splits; splits = splits->next)
00728                 {
00729                     Split *split = splits->data;
00730                     if (xaccTransStillHasSplit (trans, split) &&
00731                             gnc_commodity_equal (commodity,
00732                                                  xaccAccountGetCommodity(xaccSplitGetAccount(split))))
00733                         val_imbalance = gnc_numeric_add (val_imbalance, xaccSplitGetValue (split),
00734                                                          GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
00735                 }
00736             }
00737 
00738             xaccTransBeginEdit (trans);
00739 
00740             old_amount = xaccSplitGetAmount (balance_split);
00741             new_amount = gnc_numeric_sub (old_amount, gnc_monetary_value(*imbal_mon),
00742                                           gnc_commodity_get_fraction(commodity),
00743                                           GNC_HOW_RND_ROUND);
00744 
00745             xaccSplitSetAmount (balance_split, new_amount);
00746 
00747             if (gnc_commodity_equal (currency, commodity))
00748             {
00749                 /* Imbalance commodity is the transaction currency, value in the
00750                    split must be the same as the amount */
00751                 xaccSplitSetValue (balance_split, new_amount);
00752             }
00753             else
00754             {
00755                 old_value = xaccSplitGetValue (balance_split);
00756                 new_value = gnc_numeric_sub (old_value, val_imbalance,
00757                                              gnc_commodity_get_fraction(currency),
00758                                              GNC_HOW_RND_ROUND);
00759 
00760                 xaccSplitSetValue (balance_split, new_value);
00761             }
00762 
00763             xaccSplitScrub (balance_split);
00764             xaccTransCommitEdit (trans);
00765         }
00766 
00767         gnc_monetary_list_free(imbal_list);
00768 
00769         if (!gnc_numeric_zero_p(xaccTransGetImbalanceValue(trans)))
00770         {
00771             /* This is probably because there are splits with zero amount
00772                and non-zero value.  These are usually realized gain/loss
00773                splits.  Add a reversing split for each of them to balance
00774                the value. */
00775 
00776             /* Copy the split list so we don't see the splits we're adding */
00777             GList *splits_dup = g_list_copy(trans->splits);
00778             for (splits = splits_dup; splits; splits = splits->next)
00779             {
00780                 Split *split = splits->data;
00781                 if (! xaccTransStillHasSplit(trans, split)) continue;
00782                 if (!gnc_numeric_zero_p(xaccSplitGetValue(split)) &&
00783                         gnc_numeric_zero_p(xaccSplitGetAmount(split)))
00784                 {
00785                     gnc_commodity *commodity;
00786                     gnc_numeric old_value, new_value;
00787 
00788                     commodity = xaccAccountGetCommodity(xaccSplitGetAccount(split));
00789                     if (!commodity)
00790                     {
00791                         PERR("Split has no commodity");
00792                         continue;
00793                     }
00794                     balance_split = get_trading_split(trans, root, commodity);
00795                     if (!balance_split)
00796                     {
00797                         /* Error already logged */
00798                         gnc_monetary_list_free(imbal_list);
00799                         LEAVE("");
00800                         return;
00801                     }
00802                     account = xaccSplitGetAccount(balance_split);
00803 
00804                     xaccTransBeginEdit (trans);
00805 
00806                     old_value = xaccSplitGetValue (balance_split);
00807                     new_value = gnc_numeric_sub (old_value, xaccSplitGetValue(split),
00808                                                  gnc_commodity_get_fraction(currency),
00809                                                  GNC_HOW_RND_ROUND);
00810                     xaccSplitSetValue (balance_split, new_value);
00811 
00812                     /* Don't change the balance split's amount since the amount
00813                        is zero in the split we're working on */
00814 
00815                     xaccSplitScrub (balance_split);
00816                     xaccTransCommitEdit (trans);
00817                 }
00818             }
00819 
00820             g_list_free(splits_dup);
00821 
00822             if (!gnc_numeric_zero_p(xaccTransGetImbalanceValue(trans)))
00823                 PERR("Balancing currencies unbalanced value");
00824         }
00825     }
00826     LEAVE ("()");
00827 }

void xaccTransScrubOrphans ( Transaction trans  ) 

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

Definition at line 124 of file Scrub.c.

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

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 2145 of file Transaction.c.

02146 {
02147     gnc_commodity *currency;
02148 
02149     if (!trans) return;
02150 
02151     xaccTransBeginEdit(trans);
02152     /* The split scrub expects the transaction to have a currency! */
02153     currency = xaccTransGetCurrency (trans);
02154     if (!currency)
02155         PERR ("Transaction doesn't have a currency!");
02156 
02157     FOR_EACH_SPLIT(trans, xaccSplitScrub(s));
02158     xaccTransCommitEdit(trans);
02159 }


Generated on Fri Mar 19 04:58:57 2010 for GnuCash by  doxygen 1.5.7.1