~siretart/gnucash/ubuntu-fullsource

« back to all changes in this revision

Viewing changes to src/engine/Scrub2.c

  • Committer: Reinhard Tartler
  • Date: 2008-08-03 07:25:46 UTC
  • Revision ID: siretart@tauware.de-20080803072546-y6p8xda8zpfi62ys
import gnucash_2.2.4.orig.tar.gz

The original tarball had the md5sum: 27e660297dc5b8ce574515779d05a5a5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/********************************************************************\
 
2
 * Scrub2.c -- Convert Stock Accounts to use Lots                   *
 
3
 *                                                                  *
 
4
 * This program is free software; you can redistribute it and/or    *
 
5
 * modify it under the terms of the GNU General Public License as   *
 
6
 * published by the Free Software Foundation; either version 2 of   *
 
7
 * the License, or (at your option) any later version.              *
 
8
 *                                                                  *
 
9
 * This program is distributed in the hope that it will be useful,  *
 
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 
12
 * GNU General Public License for more details.                     *
 
13
 *                                                                  *
 
14
 * You should have received a copy of the GNU General Public License*
 
15
 * along with this program; if not, contact:                        *
 
16
 *                                                                  *
 
17
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 
18
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 
19
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 
20
\********************************************************************/
 
21
 
 
22
/** @file Scrub2.c
 
23
 *  @brief Utilities to Convert Stock Accounts to use Lots
 
24
 *  @author Created by Linas Vepstas March 2003
 
25
 *  @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
 
26
 *
 
27
 * Provides a set of functions and utilities for checking and
 
28
 * repairing ('scrubbing clean') the usage of Lots and lot balances
 
29
 * in stock and commodity accounts.  Broken lots are repaired using
 
30
 * the accounts specific accounting policy (probably FIFO).
 
31
 */
 
32
 
 
33
#include "config.h"
 
34
 
 
35
#include <glib.h>
 
36
 
 
37
#include "qof.h"
 
38
#include "Account.h"
 
39
#include "AccountP.h"
 
40
#include "Transaction.h"
 
41
#include "TransactionP.h"
 
42
#include "Scrub2.h"
 
43
#include "ScrubP.h"
 
44
#include "cap-gains.h"
 
45
#include "gnc-engine.h"
 
46
#include "gnc-lot.h"
 
47
#include "gnc-lot-p.h"
 
48
#include "policy-p.h"
 
49
 
 
50
static QofLogModule log_module = GNC_MOD_LOT;
 
51
 
 
52
/* ============================================================== */
 
53
/** Loop over all splits, and make sure that every split
 
54
 * belongs to some lot.  If a split does not belong to 
 
55
 * any lots, poke it into one.
 
56
 */
 
57
 
 
58
void
 
59
xaccAccountAssignLots (Account *acc)
 
60
{
 
61
   SplitList *splits, *node;
 
62
 
 
63
   if (!acc) return;
 
64
 
 
65
   ENTER ("acc=%s", xaccAccountGetName(acc));
 
66
   xaccAccountBeginEdit (acc);
 
67
 
 
68
restart_loop:
 
69
   splits = xaccAccountGetSplitList(acc);
 
70
   for (node=splits; node; node=node->next)
 
71
   {
 
72
      Split * split = node->data;
 
73
 
 
74
      /* If already in lot, then no-op */
 
75
      if (split->lot) continue;
 
76
 
 
77
      /* Skip voided transactions */
 
78
      if (gnc_numeric_zero_p (split->amount) &&
 
79
          xaccTransGetVoidStatus(split->parent)) continue;
 
80
 
 
81
      if (xaccSplitAssign (split)) goto restart_loop;
 
82
   }
 
83
   xaccAccountCommitEdit (acc);
 
84
   LEAVE ("acc=%s", xaccAccountGetName(acc));
 
85
}
 
86
 
 
87
/* ============================================================== */
 
88
 
 
89
/** The xaccLotFill() routine attempts to assign splits to the 
 
90
 *  indicated lot until the lot balance goes to zero, or until 
 
91
 *  there are no suitable (i.e. unassigned) splits left in the 
 
92
 *  account.  It uses the default accounting policy to choose
 
93
 *  the splits to fill out the lot.
 
94
 */
 
95
 
 
96
void
 
97
xaccLotFill (GNCLot *lot)
 
98
{
 
99
   Account *acc;
 
100
   Split *split;
 
101
   GNCPolicy *pcy;
 
102
 
 
103
   if (!lot) return;
 
104
   acc = lot->account;
 
105
   pcy = gnc_account_get_policy(acc);
 
106
 
 
107
   ENTER ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
 
108
 
 
109
   /* If balance already zero, we have nothing to do. */
 
110
   if (gnc_lot_is_closed (lot)) return;
 
111
 
 
112
   split = pcy->PolicyGetSplit (pcy, lot);
 
113
   if (!split) return;   /* Handle the common case */
 
114
 
 
115
   /* Reject voided transactions */
 
116
   if (gnc_numeric_zero_p(split->amount) &&
 
117
       xaccTransGetVoidStatus(split->parent)) return;
 
118
 
 
119
   xaccAccountBeginEdit (acc);
 
120
 
 
121
   /* Loop until we've filled up the lot, (i.e. till the 
 
122
    * balance goes to zero) or there are no splits left.  */
 
123
   while (1)
 
124
   {
 
125
      Split *subsplit;
 
126
 
 
127
      subsplit = xaccSplitAssignToLot (split, lot);
 
128
      if (subsplit == split)
 
129
      {
 
130
         PERR ("Accounting Policy gave us a split that "
 
131
               "doesn't fit into this lot\n"
 
132
               "lot baln=%s, isclosed=%d, aplit amt=%s",
 
133
               gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
 
134
               gnc_lot_is_closed (lot),
 
135
               gnc_num_dbg_to_string (split->amount));
 
136
         break;
 
137
      }
 
138
 
 
139
      if (gnc_lot_is_closed (lot)) break;
 
140
 
 
141
      split = pcy->PolicyGetSplit (pcy, lot);
 
142
      if (!split) break;
 
143
   }
 
144
   xaccAccountCommitEdit (acc);
 
145
   LEAVE ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
 
146
}
 
147
 
 
148
/* ============================================================== */
 
149
 
 
150
void
 
151
xaccLotScrubDoubleBalance (GNCLot *lot)
 
152
{
 
153
   gnc_commodity *currency = NULL;
 
154
   SplitList *snode;
 
155
   GList *node;
 
156
   gnc_numeric zero = gnc_numeric_zero();
 
157
   gnc_numeric value = zero;
 
158
 
 
159
   if (!lot) return;
 
160
 
 
161
   ENTER ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title"));
 
162
 
 
163
   for (snode = lot->splits; snode; snode=snode->next)
 
164
   {
 
165
      Split *s = snode->data;
 
166
      xaccSplitComputeCapGains (s, NULL);
 
167
   }
 
168
 
 
169
   /* We double-check only closed lots */
 
170
   if (FALSE == gnc_lot_is_closed (lot)) return;
 
171
 
 
172
   for (snode = lot->splits; snode; snode=snode->next)
 
173
   {
 
174
      Split *s = snode->data;
 
175
      Transaction *trans = s->parent;
 
176
 
 
177
      /* Check to make sure all splits in the lot have a common currency */
 
178
      if (NULL == currency)
 
179
      {
 
180
         currency = trans->common_currency;
 
181
      }
 
182
      if (FALSE == gnc_commodity_equiv (currency, trans->common_currency))
 
183
      {
 
184
         /* This lot has mixed currencies. Can't double-balance.
 
185
          * Silently punt */
 
186
         PWARN ("Lot with multiple currencies:\n"
 
187
               "\ttrans=%s curr=%s", xaccTransGetDescription(trans), 
 
188
               gnc_commodity_get_fullname(trans->common_currency)); 
 
189
         break;
 
190
      }
 
191
 
 
192
      /* Now, total up the values */
 
193
      value = gnc_numeric_add (value, xaccSplitGetValue (s), 
 
194
                  GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
 
195
      PINFO ("Split=%p value=%s Accum Lot value=%s", s,
 
196
          gnc_num_dbg_to_string (s->value),
 
197
          gnc_num_dbg_to_string (value));
 
198
          
 
199
   }
 
200
 
 
201
   if (FALSE == gnc_numeric_equal (value, zero))
 
202
   {
 
203
      /* Unhandled error condition. Not sure what to do here,
 
204
       * Since the ComputeCapGains should have gotten it right. 
 
205
       * I suppose there might be small rounding errors, a penny or two,
 
206
       * the ideal thing would to figure out why there's a rounding
 
207
       * error, and fix that.
 
208
       */
 
209
      PERR ("Closed lot fails to double-balance !! lot value=%s",
 
210
            gnc_num_dbg_to_string (value));
 
211
      for (node=lot->splits; node; node=node->next)
 
212
      {
 
213
        Split *s = node->data;
 
214
        PERR ("s=%p amt=%s val=%s", s, 
 
215
              gnc_num_dbg_to_string(s->amount),
 
216
              gnc_num_dbg_to_string(s->value));
 
217
      }
 
218
   }
 
219
 
 
220
   LEAVE ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title"));
 
221
}
 
222
 
 
223
/* ================================================================= */
 
224
 
 
225
static inline gboolean 
 
226
is_subsplit (Split *split)
 
227
{
 
228
   KvpValue *kval;
 
229
 
 
230
   /* generic stop-progress conditions */
 
231
   if (!split) return FALSE;
 
232
   g_return_val_if_fail (split->parent, FALSE);
 
233
 
 
234
   /* If there are no sub-splits, then there's nothing to do. */
 
235
   kval = kvp_frame_get_slot (split->inst.kvp_data, "lot-split");
 
236
   if (!kval) return FALSE;  
 
237
 
 
238
   return TRUE;
 
239
}
 
240
 
 
241
/* ================================================================= */
 
242
 
 
243
void
 
244
xaccScrubSubSplitPrice (Split *split, int maxmult, int maxamtscu)
 
245
{
 
246
   gnc_numeric src_amt, src_val;
 
247
   SplitList *node;
 
248
 
 
249
   if (FALSE == is_subsplit (split)) return;
 
250
 
 
251
   ENTER (" ");
 
252
   /* Get 'price' of the indicated split */
 
253
   src_amt = xaccSplitGetAmount (split);
 
254
   src_val = xaccSplitGetValue (split);
 
255
 
 
256
   /* Loop over splits, adjust each so that it has the same
 
257
    * ratio (i.e. price).  Change the value to get things 
 
258
    * right; do not change the amount */
 
259
   for (node=split->parent->splits; node; node=node->next)
 
260
   {
 
261
      Split *s = node->data;
 
262
      Transaction *txn = s->parent;
 
263
      gnc_numeric dst_amt, dst_val, target_val;
 
264
      gnc_numeric frac, delta;
 
265
      int scu;
 
266
 
 
267
      /* Skip the reference split */
 
268
      if (s == split) continue;
 
269
 
 
270
      scu = gnc_commodity_get_fraction (txn->common_currency);
 
271
 
 
272
      dst_amt = xaccSplitGetAmount (s);
 
273
      dst_val = xaccSplitGetValue (s);
 
274
      frac = gnc_numeric_div (dst_amt, src_amt, 
 
275
                        GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
 
276
      target_val = gnc_numeric_mul (frac, src_val,
 
277
                        scu, GNC_HOW_DENOM_EXACT|GNC_HOW_RND_ROUND);
 
278
      if (gnc_numeric_check (target_val))
 
279
      {
 
280
         PERR ("Numeric overflow of value\n"
 
281
               "\tAcct=%s txn=%s\n"
 
282
               "\tdst_amt=%s src_val=%s src_amt=%s\n",
 
283
               xaccAccountGetName (s->acc),
 
284
               xaccTransGetDescription(txn),
 
285
               gnc_num_dbg_to_string(dst_amt),
 
286
               gnc_num_dbg_to_string(src_val),
 
287
               gnc_num_dbg_to_string(src_amt));
 
288
         continue;
 
289
      }
 
290
 
 
291
      /* If the required price changes are 'small', do nothing.
 
292
       * That is a case that the user will have to deal with
 
293
       * manually.  This routine is really intended only for
 
294
       * a gross level of synchronization.
 
295
       */
 
296
      delta = gnc_numeric_sub_fixed (target_val, dst_val);
 
297
      delta = gnc_numeric_abs (delta);
 
298
      if (maxmult * delta.num  < delta.denom) continue;
 
299
 
 
300
      /* If the amount is small, pass on that too */
 
301
      if ((-maxamtscu < dst_amt.num) && (dst_amt.num < maxamtscu)) continue;
 
302
 
 
303
      /* Make the actual adjustment */
 
304
      xaccTransBeginEdit (txn);
 
305
      xaccSplitSetValue (s, target_val);
 
306
      xaccTransCommitEdit (txn);
 
307
   }
 
308
   LEAVE (" ");
 
309
}
 
310
 
 
311
/* ================================================================= */
 
312
 
 
313
/* Remove the guid of b from a.  Note that a may not contain the guid 
 
314
 * of b, (and v.v.) in which case, it will contain other guids which
 
315
 * establish the links. So merge them back in. */
 
316
 
 
317
static void
 
318
remove_guids (Split *sa, Split *sb)
 
319
{
 
320
   KvpFrame *ksub;
 
321
 
 
322
   /* Find and remove the matching guid's */
 
323
   ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sa->inst.kvp_data, "lot-split",
 
324
                    "peer_guid", qof_instance_get_guid(sb));
 
325
   if (ksub) 
 
326
   {
 
327
      gnc_kvp_bag_remove_frame (sa->inst.kvp_data, "lot-split", ksub);
 
328
      kvp_frame_delete (ksub);
 
329
   }
 
330
 
 
331
   /* Now do it in the other direction */
 
332
   ksub = (KvpFrame*)gnc_kvp_bag_find_by_guid (sb->inst.kvp_data, "lot-split",
 
333
                    "peer_guid", qof_instance_get_guid(sa));
 
334
   if (ksub) 
 
335
   {
 
336
      gnc_kvp_bag_remove_frame (sb->inst.kvp_data, "lot-split", ksub);
 
337
      kvp_frame_delete (ksub);
 
338
   }
 
339
 
 
340
   /* Finally, merge b's lot-splits, if any, into a's */
 
341
   /* This is an important step, if it got busted into many pieces. */
 
342
   gnc_kvp_bag_merge (sa->inst.kvp_data, "lot-split",
 
343
                      sb->inst.kvp_data, "lot-split");
 
344
}
 
345
 
 
346
/* The merge_splits() routine causes the amount & value of sb 
 
347
 * to be merged into sa; it then destroys sb.  It also performs
 
348
 * some other misc cleanup */
 
349
 
 
350
static void
 
351
merge_splits (Split *sa, Split *sb)
 
352
{
 
353
   Account *act;
 
354
   Transaction *txn;
 
355
   gnc_numeric amt, val;
 
356
 
 
357
   act = xaccSplitGetAccount (sb);
 
358
   xaccAccountBeginEdit (act);
 
359
 
 
360
   txn = sa->parent;
 
361
   xaccTransBeginEdit (txn);
 
362
 
 
363
   /* Remove the guid of sb from the 'gemini' of sa */
 
364
   remove_guids (sa, sb);
 
365
 
 
366
   /* Add amount of sb into sa, ditto for value. */
 
367
   amt = xaccSplitGetAmount (sa);
 
368
   amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
 
369
   xaccSplitSetAmount (sa, amt);
 
370
 
 
371
   val = xaccSplitGetValue (sa);
 
372
   val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
 
373
   xaccSplitSetValue (sa, val);
 
374
 
 
375
   /* Set reconcile to no; after this much violence, 
 
376
    * no way its reconciled. */
 
377
   xaccSplitSetReconcile (sa, NREC);
 
378
 
 
379
   /* If sb has associated gains splits, trash them. */
 
380
   if ((sb->gains_split) && 
 
381
       (sb->gains_split->gains & GAINS_STATUS_GAINS))
 
382
   {
 
383
      Transaction *t = sb->gains_split->parent;
 
384
      xaccTransBeginEdit (t);
 
385
      xaccTransDestroy (t);
 
386
      xaccTransCommitEdit (t);
 
387
   }
 
388
 
 
389
   /* Finally, delete sb */
 
390
   xaccSplitDestroy(sb);
 
391
 
 
392
   xaccTransCommitEdit (txn);
 
393
   xaccAccountCommitEdit (act);
 
394
}
 
395
 
 
396
gboolean 
 
397
xaccScrubMergeSubSplits (Split *split)
 
398
{
 
399
   gboolean rc = FALSE;
 
400
   Transaction *txn;
 
401
   SplitList *node;
 
402
   GNCLot *lot;
 
403
   const GUID *guid;
 
404
 
 
405
   if (FALSE == is_subsplit (split)) return FALSE;
 
406
 
 
407
   txn = split->parent;
 
408
   lot = xaccSplitGetLot (split);
 
409
 
 
410
   ENTER ("(Lot=%s)", gnc_lot_get_title(lot));
 
411
restart:
 
412
   for (node=txn->splits; node; node=node->next)
 
413
   {
 
414
      Split *s = node->data;
 
415
      if (xaccSplitGetLot (s) != lot) continue;
 
416
      if (s == split) continue;
 
417
      if (qof_instance_get_destroying(s)) continue;
 
418
 
 
419
      /* OK, this split is in the same lot (and thus same account)
 
420
       * as the indicated split.  Make sure it is really a subsplit
 
421
       * of the split we started with.  It's possible to have two 
 
422
       * splits in the same lot and transaction that are not subsplits
 
423
       * of each other, the test-period test suite does this, for
 
424
       * example.  Only worry about adjacent sub-splits.  By 
 
425
       * repeatedly merging adjacent subsplits, we'll get the non-
 
426
       * adjacent ones too. */
 
427
      guid = qof_instance_get_guid(s);
 
428
      if (gnc_kvp_bag_find_by_guid (split->inst.kvp_data, "lot-split",
 
429
                                    "peer_guid", guid) == NULL)
 
430
         continue;
 
431
         
 
432
      merge_splits (split, s);
 
433
      rc = TRUE;
 
434
      goto restart;
 
435
   }
 
436
   if (gnc_numeric_zero_p (split->amount))
 
437
   {
 
438
      PWARN ("Result of merge has zero amt!");
 
439
   }
 
440
   LEAVE (" splits merged=%d", rc);
 
441
   return rc;
 
442
}
 
443
 
 
444
gboolean 
 
445
xaccScrubMergeTransSubSplits (Transaction *txn)
 
446
{
 
447
   gboolean rc = FALSE;
 
448
   SplitList *node;
 
449
 
 
450
   if (!txn) return FALSE;
 
451
 
 
452
   ENTER (" ");
 
453
restart:
 
454
   for (node=txn->splits; node; node=node->next)
 
455
   {
 
456
      Split *s = node->data;
 
457
      if (!xaccScrubMergeSubSplits(s)) continue;
 
458
 
 
459
      rc = TRUE;
 
460
      goto restart;
 
461
   }
 
462
   LEAVE (" splits merged=%d", rc);
 
463
   return rc;
 
464
}
 
465
 
 
466
gboolean 
 
467
xaccScrubMergeLotSubSplits (GNCLot *lot)
 
468
{
 
469
   gboolean rc = FALSE;
 
470
   SplitList *node;
 
471
 
 
472
   if (!lot) return FALSE;
 
473
 
 
474
   ENTER (" ");
 
475
restart:
 
476
   for (node=gnc_lot_get_split_list(lot); node; node=node->next)
 
477
   {
 
478
      Split *s = node->data;
 
479
      if (!xaccScrubMergeSubSplits(s)) continue;
 
480
 
 
481
      rc = TRUE;
 
482
      goto restart;
 
483
   }
 
484
   LEAVE (" splits merged=%d", rc);
 
485
   return rc;
 
486
}
 
487
 
 
488
/* =========================== END OF FILE ======================= */