Scrub2.c

Go to the documentation of this file.
00001 /********************************************************************\
00002  * Scrub2.c -- Convert Stock Accounts to use Lots                   *
00003  *                                                                  *
00004  * This program is free software; you can redistribute it and/or    *
00005  * modify it under the terms of the GNU General Public License as   *
00006  * published by the Free Software Foundation; either version 2 of   *
00007  * the License, or (at your option) any later version.              *
00008  *                                                                  *
00009  * This program is distributed in the hope that it will be useful,  *
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00012  * GNU General Public License for more details.                     *
00013  *                                                                  *
00014  * You should have received a copy of the GNU General Public License*
00015  * along with this program; if not, contact:                        *
00016  *                                                                  *
00017  * Free Software Foundation           Voice:  +1-617-542-5942       *
00018  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00019  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00020 \********************************************************************/
00021 
00033 #include "config.h"
00034 
00035 #include <glib.h>
00036 
00037 #include "qof.h"
00038 #include "Account.h"
00039 #include "AccountP.h"
00040 #include "Transaction.h"
00041 #include "TransactionP.h"
00042 #include "Scrub2.h"
00043 #include "ScrubP.h"
00044 #include "cap-gains.h"
00045 #include "gnc-engine.h"
00046 #include "gnc-lot.h"
00047 #include "gnc-lot-p.h"
00048 #include "policy-p.h"
00049 
00050 static QofLogModule log_module = GNC_MOD_LOT;
00051 
00052 /* ============================================================== */
00058 void
00059 xaccAccountAssignLots (Account *acc)
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 }
00086 
00087 /* ============================================================== */
00088 
00096 void
00097 xaccLotFill (GNCLot *lot)
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 }
00147 
00148 /* ============================================================== */
00149 
00150 void
00151 xaccLotScrubDoubleBalance (GNCLot *lot)
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 }
00222 
00223 /* ================================================================= */
00224 
00225 static inline gboolean 
00226 is_subsplit (Split *split)
00227 {
00228    KvpValue *kval;
00229 
00230    /* generic stop-progress conditions */
00231    if (!split) return FALSE;
00232    g_return_val_if_fail (split->parent, FALSE);
00233 
00234    /* If there are no sub-splits, then there's nothing to do. */
00235    kval = kvp_frame_get_slot (split->inst.kvp_data, "lot-split");
00236    if (!kval) return FALSE;  
00237 
00238    return TRUE;
00239 }
00240 
00241 /* ================================================================= */
00242 
00243 void
00244 xaccScrubSubSplitPrice (Split *split, int maxmult, int maxamtscu)
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 }
00310 
00311 /* ================================================================= */
00312 
00313 /* Remove the guid of b from a.  Note that a may not contain the guid 
00314  * of b, (and v.v.) in which case, it will contain other guids which
00315  * establish the links. So merge them back in. */
00316 
00317 static void
00318 remove_guids (Split *sa, Split *sb)
00319 {
00320    KvpFrame *ksub;
00321 
00322    /* Find and remove the matching guid's */
00323    ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sa->inst.kvp_data, "lot-split",
00324                     "peer_guid", qof_instance_get_guid(sb));
00325    if (ksub) 
00326    {
00327       gnc_kvp_bag_remove_frame (sa->inst.kvp_data, "lot-split", ksub);
00328       kvp_frame_delete (ksub);
00329    }
00330 
00331    /* Now do it in the other direction */
00332    ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sb->inst.kvp_data, "lot-split",
00333                     "peer_guid", qof_instance_get_guid(sa));
00334    if (ksub) 
00335    {
00336       gnc_kvp_bag_remove_frame (sb->inst.kvp_data, "lot-split", ksub);
00337       kvp_frame_delete (ksub);
00338    }
00339 
00340    /* Finally, merge b's lot-splits, if any, into a's */
00341    /* This is an important step, if it got busted into many pieces. */
00342    gnc_kvp_bag_merge (sa->inst.kvp_data, "lot-split",
00343                       sb->inst.kvp_data, "lot-split");
00344 }
00345 
00346 /* The merge_splits() routine causes the amount & value of sb 
00347  * to be merged into sa; it then destroys sb.  It also performs
00348  * some other misc cleanup */
00349 
00350 static void
00351 merge_splits (Split *sa, Split *sb)
00352 {
00353    Account *act;
00354    Transaction *txn;
00355    gnc_numeric amt, val;
00356 
00357    act = xaccSplitGetAccount (sb);
00358    xaccAccountBeginEdit (act);
00359 
00360    txn = sa->parent;
00361    xaccTransBeginEdit (txn);
00362 
00363    /* Remove the guid of sb from the 'gemini' of sa */
00364    remove_guids (sa, sb);
00365 
00366    /* Add amount of sb into sa, ditto for value. */
00367    amt = xaccSplitGetAmount (sa);
00368    amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
00369    xaccSplitSetAmount (sa, amt);
00370 
00371    val = xaccSplitGetValue (sa);
00372    val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
00373    xaccSplitSetValue (sa, val);
00374 
00375    /* Set reconcile to no; after this much violence, 
00376     * no way its reconciled. */
00377    xaccSplitSetReconcile (sa, NREC);
00378 
00379    /* If sb has associated gains splits, trash them. */
00380    if ((sb->gains_split) && 
00381        (sb->gains_split->gains & GAINS_STATUS_GAINS))
00382    {
00383       Transaction *t = sb->gains_split->parent;
00384       xaccTransBeginEdit (t);
00385       xaccTransDestroy (t);
00386       xaccTransCommitEdit (t);
00387    }
00388 
00389    /* Finally, delete sb */
00390    xaccSplitDestroy(sb);
00391 
00392    xaccTransCommitEdit (txn);
00393    xaccAccountCommitEdit (act);
00394 }
00395 
00396 gboolean 
00397 xaccScrubMergeSubSplits (Split *split)
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 }
00443 
00444 gboolean 
00445 xaccScrubMergeTransSubSplits (Transaction *txn)
00446 {
00447    gboolean rc = FALSE;
00448    SplitList *node;
00449 
00450    if (!txn) return FALSE;
00451 
00452    ENTER (" ");
00453 restart:
00454    for (node=txn->splits; node; node=node->next)
00455    {
00456       Split *s = node->data;
00457       if (!xaccScrubMergeSubSplits(s)) continue;
00458 
00459       rc = TRUE;
00460       goto restart;
00461    }
00462    LEAVE (" splits merged=%d", rc);
00463    return rc;
00464 }
00465 
00466 gboolean 
00467 xaccScrubMergeLotSubSplits (GNCLot *lot)
00468 {
00469    gboolean rc = FALSE;
00470    SplitList *node;
00471 
00472    if (!lot) return FALSE;
00473 
00474    ENTER (" ");
00475 restart:
00476    for (node=gnc_lot_get_split_list(lot); node; node=node->next)
00477    {
00478       Split *s = node->data;
00479       if (!xaccScrubMergeSubSplits(s)) continue;
00480 
00481       rc = TRUE;
00482       goto restart;
00483    }
00484    LEAVE (" splits merged=%d", rc);
00485    return rc;
00486 }
00487 
00488 /* =========================== END OF FILE ======================= */

Generated on Mon Sep 8 05:04:23 2008 for GnuCash by  doxygen 1.5.2