GnuCash 2.4.99
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 "policy-p.h"
00048 
00049 static QofLogModule log_module = GNC_MOD_LOT;
00050 
00051 /* ============================================================== */
00057 void
00058 xaccAccountAssignLots (Account *acc)
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 }
00085 
00086 /* ============================================================== */
00087 
00095 void
00096 xaccLotFill (GNCLot *lot)
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 }
00146 
00147 /* ============================================================== */
00148 
00149 void
00150 xaccLotScrubDoubleBalance (GNCLot *lot)
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 }
00221 
00222 /* ================================================================= */
00223 
00224 static inline gboolean
00225 is_subsplit (Split *split)
00226 {
00227     KvpValue *kval;
00228 
00229     /* generic stop-progress conditions */
00230     if (!split) return FALSE;
00231     g_return_val_if_fail (split->parent, FALSE);
00232 
00233     /* If there are no sub-splits, then there's nothing to do. */
00234     kval = kvp_frame_get_slot (split->inst.kvp_data, "lot-split");
00235     if (!kval) return FALSE;
00236 
00237     return TRUE;
00238 }
00239 
00240 /* ================================================================= */
00241 
00242 
00243 /* ================================================================= */
00244 
00245 /* Remove the guid of b from a.  Note that a may not contain the guid
00246  * of b, (and v.v.) in which case, it will contain other guids which
00247  * establish the links. So merge them back in. */
00248 
00249 static void
00250 remove_guids (Split *sa, Split *sb)
00251 {
00252     KvpFrame *ksub;
00253 
00254     /* Find and remove the matching guid's */
00255     ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sa->inst.kvp_data, "lot-split",
00256             "peer_guid", qof_instance_get_guid(sb));
00257     if (ksub)
00258     {
00259         gnc_kvp_bag_remove_frame (sa->inst.kvp_data, "lot-split", ksub);
00260         kvp_frame_delete (ksub);
00261     }
00262 
00263     /* Now do it in the other direction */
00264     ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sb->inst.kvp_data, "lot-split",
00265             "peer_guid", qof_instance_get_guid(sa));
00266     if (ksub)
00267     {
00268         gnc_kvp_bag_remove_frame (sb->inst.kvp_data, "lot-split", ksub);
00269         kvp_frame_delete (ksub);
00270     }
00271 
00272     /* Finally, merge b's lot-splits, if any, into a's */
00273     /* This is an important step, if it got busted into many pieces. */
00274     gnc_kvp_bag_merge (sa->inst.kvp_data, "lot-split",
00275                        sb->inst.kvp_data, "lot-split");
00276 }
00277 
00278 /* The merge_splits() routine causes the amount & value of sb
00279  * to be merged into sa; it then destroys sb.  It also performs
00280  * some other misc cleanup */
00281 
00282 static void
00283 merge_splits (Split *sa, Split *sb)
00284 {
00285     Account *act;
00286     Transaction *txn;
00287     gnc_numeric amt, val;
00288 
00289     act = xaccSplitGetAccount (sb);
00290     xaccAccountBeginEdit (act);
00291 
00292     txn = sa->parent;
00293     xaccTransBeginEdit (txn);
00294 
00295     /* Remove the guid of sb from the 'gemini' of sa */
00296     remove_guids (sa, sb);
00297 
00298     /* Add amount of sb into sa, ditto for value. */
00299     amt = xaccSplitGetAmount (sa);
00300     amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
00301     xaccSplitSetAmount (sa, amt);
00302 
00303     val = xaccSplitGetValue (sa);
00304     val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
00305     xaccSplitSetValue (sa, val);
00306 
00307     /* Set reconcile to no; after this much violence,
00308      * no way its reconciled. */
00309     xaccSplitSetReconcile (sa, NREC);
00310 
00311     /* If sb has associated gains splits, trash them. */
00312     if ((sb->gains_split) &&
00313             (sb->gains_split->gains & GAINS_STATUS_GAINS))
00314     {
00315         Transaction *t = sb->gains_split->parent;
00316         xaccTransBeginEdit (t);
00317         xaccTransDestroy (t);
00318         xaccTransCommitEdit (t);
00319     }
00320 
00321     /* Finally, delete sb */
00322     xaccSplitDestroy(sb);
00323 
00324     xaccTransCommitEdit (txn);
00325     xaccAccountCommitEdit (act);
00326 }
00327 
00328 gboolean
00329 xaccScrubMergeSubSplits (Split *split)
00330 {
00331     gboolean rc = FALSE;
00332     Transaction *txn;
00333     SplitList *node;
00334     GNCLot *lot;
00335     const GncGUID *guid;
00336 
00337     if (FALSE == is_subsplit (split)) return FALSE;
00338 
00339     txn = split->parent;
00340     lot = xaccSplitGetLot (split);
00341 
00342     ENTER ("(Lot=%s)", gnc_lot_get_title(lot));
00343 restart:
00344     for (node = txn->splits; node; node = node->next)
00345     {
00346         Split *s = node->data;
00347         if (xaccSplitGetLot (s) != lot) continue;
00348         if (s == split) continue;
00349         if (qof_instance_get_destroying(s)) continue;
00350 
00351         /* OK, this split is in the same lot (and thus same account)
00352          * as the indicated split.  Make sure it is really a subsplit
00353          * of the split we started with.  It's possible to have two
00354          * splits in the same lot and transaction that are not subsplits
00355          * of each other, the test-period test suite does this, for
00356          * example.  Only worry about adjacent sub-splits.  By
00357          * repeatedly merging adjacent subsplits, we'll get the non-
00358          * adjacent ones too. */
00359         guid = qof_instance_get_guid(s);
00360         if (gnc_kvp_bag_find_by_guid (split->inst.kvp_data, "lot-split",
00361                                       "peer_guid", guid) == NULL)
00362             continue;
00363 
00364         merge_splits (split, s);
00365         rc = TRUE;
00366         goto restart;
00367     }
00368     if (gnc_numeric_zero_p (split->amount))
00369     {
00370         PWARN ("Result of merge has zero amt!");
00371     }
00372     LEAVE (" splits merged=%d", rc);
00373     return rc;
00374 }
00375 
00376 gboolean
00377 xaccScrubMergeLotSubSplits (GNCLot *lot)
00378 {
00379     gboolean rc = FALSE;
00380     SplitList *node;
00381 
00382     if (!lot) return FALSE;
00383 
00384     ENTER (" ");
00385 restart:
00386     for (node = gnc_lot_get_split_list(lot); node; node = node->next)
00387     {
00388         Split *s = node->data;
00389         if (!xaccScrubMergeSubSplits(s)) continue;
00390 
00391         rc = TRUE;
00392         goto restart;
00393     }
00394     LEAVE (" splits merged=%d", rc);
00395     return rc;
00396 }
00397 
00398 /* =========================== END OF FILE ======================= */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines