move low-level API routines to scrub2, high-level to Scrub3

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@9389 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Linas Vepstas 2003-09-21 18:07:08 +00:00
parent 288a062c13
commit 325d4ffd66
5 changed files with 282 additions and 314 deletions

View File

@ -140,23 +140,6 @@ xaccLotFill (GNCLot *lot)
/* ============================================================== */
void
xaccAccountScrubDoubleBalance (Account *acc)
{
LotList *node;
if (!acc) return;
ENTER ("acc=%s", acc->accountName);
for (node = acc->lots; node; node=node->next)
{
GNCLot *lot = node->data;
xaccLotScrubDoubleBalance (lot);
}
LEAVE ("acc=%s", acc->accountName);
}
/* ============================================================== */
void
xaccLotScrubDoubleBalance (GNCLot *lot)
{
@ -221,4 +204,231 @@ xaccLotScrubDoubleBalance (GNCLot *lot)
LEAVE ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title"));
}
/* ================================================================= */
static inline gboolean
is_subsplit (Split *split)
{
KvpValue *kval;
/* generic stop-progress conditions */
if (!split) return FALSE;
g_return_val_if_fail (split->parent, FALSE);
/* If there are no sub-splits, then there's nothing to do. */
kval = kvp_frame_get_slot (split->kvp_data, "lot-split");
if (!kval) return FALSE;
return TRUE;
}
/* ================================================================= */
void
xaccScrubSubSplitPrice (Split *split)
{
gnc_numeric src_amt, src_val;
SplitList *node;
if (FALSE == is_subsplit (split)) return;
ENTER (" ");
/* Get 'price' of the indicated split */
src_amt = xaccSplitGetAmount (split);
src_val = xaccSplitGetValue (split);
/* Loop over splits, adjust each so that it has the same
* ratio (i.e. price). Change the value to get things
* right; do not change the amount */
for (node=split->parent->splits; node; node=node->next)
{
Split *s = node->data;
Transaction *txn = s->parent;
gnc_numeric dst_amt, dst_val, target_val;
gnc_numeric delta;
int scu;
/* Skip the reference split */
if (s == split) continue;
scu = gnc_commodity_get_fraction (txn->common_currency);
dst_amt = xaccSplitGetAmount (s);
dst_val = xaccSplitGetValue (s);
target_val = gnc_numeric_mul (dst_amt, src_val,
GNC_DENOM_AUTO, GNC_DENOM_REDUCE);
target_val = gnc_numeric_div (target_val, src_amt,
scu, GNC_DENOM_EXACT);
/* If the required price changes are 'small', do nothing.
* That is a case that the user will have to deal with
* manually. This routine is really intended only for
* a gross level of synchronization.
*/
delta = gnc_numeric_sub_fixed (target_val, dst_val);
delta = gnc_numeric_abs (delta);
if (3 * delta.num < delta.denom) continue;
/* If the amount is small, pass on that too */
if ((-2 < dst_amt.num) && (dst_amt.num < 2)) continue;
/* Make the actual adjustment */
xaccTransBeginEdit (txn);
xaccSplitSetValue (s, target_val);
xaccTransCommitEdit (txn);
}
LEAVE (" ");
}
/* ================================================================= */
/* Remove the guid of b from a */
static void
remove_guids (Split *sa, Split *sb)
{
KvpFrame *ksub;
/* Find and remove the matching guid's */
ksub = gnc_kvp_bag_find_by_guid (sa->kvp_data, "lot-split",
"peer_guid", &sb->guid);
if (!ksub)
{
PERR ("merging splits that didn't have correct gemini values!\n"
"looking for guid=%s\n"
"bag held: %s",
guid_to_string (&sb->guid),
kvp_frame_to_string (sa->kvp_data));
return;
}
gnc_kvp_bag_remove_frame (sa->kvp_data, "lot-split", ksub);
kvp_frame_delete (ksub);
}
/* The 'merge_splits() routine causes the amount & value of sb
* to be merged into sa; it then destroys sb. It also performs
* some other misc cleanup */
static void
merge_splits (Split *sa, Split *sb)
{
Account *act;
Transaction *txn;
gnc_numeric amt, val;
act = xaccSplitGetAccount (sb);
xaccAccountBeginEdit (act);
txn = sa->parent;
xaccTransBeginEdit (txn);
/* Remove the guid of sb from the 'gemini' of sa */
remove_guids (sa, sb);
/* Add amount of sb into sa, ditto for value. */
amt = xaccSplitGetAmount (sa);
amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
xaccSplitSetAmount (sa, amt);
val = xaccSplitGetValue (sa);
val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
xaccSplitSetValue (sa, val);
/* Set reconcile to no; after this much violence,
* no way its reconciled. */
xaccSplitSetReconcile (sa, NREC);
/* If sb has associated gains splits, trash them. */
if ((sb->gains_split) &&
(sb->gains_split->gains & GAINS_STATUS_GAINS))
{
Transaction *t = sb->gains_split->parent;
xaccTransBeginEdit (t);
xaccTransDestroy (t);
xaccTransCommitEdit (t);
}
/* Finally, delete sb */
xaccSplitDestroy(sb);
xaccTransCommitEdit (txn);
xaccAccountCommitEdit (act);
}
gboolean
xaccScrubMergeSubSplits (Split *split)
{
gboolean rc = FALSE;
Transaction *txn;
SplitList *node;
GNCLot *lot;
if (FALSE == is_subsplit (split)) return FALSE;
txn = split->parent;
lot = xaccSplitGetLot (split);
ENTER (" ");
restart:
for (node=txn->splits; node; node=node->next)
{
Split *s = node->data;
if (xaccSplitGetLot (s) != lot) continue;
if (s == split) continue;
/* OK, this split is in the same lot (and thus same account)
* as the indicated split. It must be a subsplit (although
* we should double-check the kvp's to be sure). Merge the
* two back together again. */
merge_splits (split, s);
rc = TRUE;
goto restart;
}
LEAVE (" splits merged=%d", rc);
return rc;
}
gboolean
xaccScrubMergeTransSubSplits (Transaction *txn)
{
gboolean rc = FALSE;
SplitList *node;
if (!txn) return FALSE;
ENTER (" ");
restart:
for (node=txn->splits; node; node=node->next)
{
Split *s = node->data;
if (!xaccScrubMergeSubSplits(s)) continue;
rc = TRUE;
goto restart;
}
LEAVE (" splits merged=%d", rc);
return rc;
}
gboolean
xaccScrubMergeLotSubSplits (GNCLot *lot)
{
gboolean rc = FALSE;
SplitList *node;
if (!lot) return FALSE;
ENTER (" ");
restart:
for (node=gnc_lot_get_split_list(lot); node; node=node->next)
{
Split *s = node->data;
if (!xaccScrubMergeSubSplits(s)) continue;
rc = TRUE;
goto restart;
}
LEAVE (" splits merged=%d", rc);
return rc;
}
/* =========================== END OF FILE ======================= */

View File

@ -1,5 +1,5 @@
/********************************************************************\
* Scrub2.h -- Convert Stock Accounts to use Lots *
* Scrub2.h -- Low-level Lot Management Routines. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
@ -26,11 +26,15 @@
* @author Created by Linas Vepstas March 2003
* @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
*
*
* Provides a set of functions and utilities for checking and
* repairing ('scrubbing clean') the usage of Lots and lot balances
* in stock and commodity accounts. Broken lots are repaired using
* a first-in, first-out (FIFO) accounting schedule.
* Provides the low-level API for checking and repairing ('scrubbing
* clean') the usage of Lots and lot balances in stock and commodity
* accounts. Broken lots are repaired using a first-in, first-out
* (FIFO) accounting schedule.
*
* This is a 'low-level' API in the sense that each routine accomplishes
* only one particular task needed to clean up a Lot. To clean up a
* Lot as a whole, you almost certainly want to use one of the
* high-level API routines from the Scrub3.h file.
*/
#ifndef XACC_SCRUB2_H
@ -38,7 +42,6 @@
#include "gnc-engine.h"
/** 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)
@ -58,21 +61,6 @@ void xaccAccountAssignLots (Account *acc);
*/
void xaccLotFill (GNCLot *lot);
/** The xaccAccountScrubDoubleBalance() routine examines all
* of the closed lots in an account, and verifies that the
* lots are '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 a closed lot is
* found where 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 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.
*/
void xaccAccountScrubDoubleBalance (Account *acc);
/** 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'.
@ -88,5 +76,41 @@ void xaccAccountScrubDoubleBalance (Account *acc);
*/
void xaccLotScrubDoubleBalance (GNCLot *lot);
/** 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 fairly loose, hopefully loose enough
* so that the user can manually fine tune without having this routine
* clobber thier work.
*/
void xaccScrubSubSplitPrice (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.
*/
gboolean xaccScrubMergeSubSplits (Split *split);
gboolean xaccScrubMergeTransSubSplits (Transaction *txn);
gboolean xaccScrubMergeLotSubSplits (GNCLot *lot);
#endif /* XACC_SCRUB2_H */
/** @} */

View File

@ -43,6 +43,8 @@
#include "kvp-util-p.h"
#include "policy-p.h"
#include "Account.h"
#include "AccountP.h"
#include "Group.h"
#include "Scrub2.h"
#include "Scrub3.h"
#include "Transaction.h"
@ -52,233 +54,6 @@ static short module = MOD_LOT;
/* ================================================================= */
static inline gboolean
is_subsplit (Split *split)
{
KvpValue *kval;
/* generic stop-progress conditions */
if (!split) return FALSE;
g_return_val_if_fail (split->parent, FALSE);
/* If there are no sub-splits, then there's nothing to do. */
kval = kvp_frame_get_slot (split->kvp_data, "lot-split");
if (!kval) return FALSE;
return TRUE;
}
/* ================================================================= */
void
xaccScrubSubSplitPrice (Split *split)
{
gnc_numeric src_amt, src_val;
SplitList *node;
if (FALSE == is_subsplit (split)) return;
ENTER (" ");
/* Get 'price' of the indicated split */
src_amt = xaccSplitGetAmount (split);
src_val = xaccSplitGetValue (split);
/* Loop over splits, adjust each so that it has the same
* ratio (i.e. price). Change the value to get things
* right; do not change the amount */
for (node=split->parent->splits; node; node=node->next)
{
Split *s = node->data;
Transaction *txn = s->parent;
gnc_numeric dst_amt, dst_val, target_val;
gnc_numeric delta;
int scu;
/* Skip the reference split */
if (s == split) continue;
scu = gnc_commodity_get_fraction (txn->common_currency);
dst_amt = xaccSplitGetAmount (s);
dst_val = xaccSplitGetValue (s);
target_val = gnc_numeric_mul (dst_amt, src_val,
GNC_DENOM_AUTO, GNC_DENOM_REDUCE);
target_val = gnc_numeric_div (target_val, src_amt,
scu, GNC_DENOM_EXACT);
/* If the required price changes are 'small', do nothing.
* That is a case that the user will have to deal with
* manually. This routine is really intended only for
* a gross level of synchronization.
*/
delta = gnc_numeric_sub_fixed (target_val, dst_val);
delta = gnc_numeric_abs (delta);
if (3 * delta.num < delta.denom) continue;
/* If the amount is small, pass on that too */
if ((-2 < dst_amt.num) && (dst_amt.num < 2)) continue;
/* Make the actual adjustment */
xaccTransBeginEdit (txn);
xaccSplitSetValue (s, target_val);
xaccTransCommitEdit (txn);
}
LEAVE (" ");
}
/* ================================================================= */
/* Remove the guid of b from a */
static void
remove_guids (Split *sa, Split *sb)
{
KvpFrame *ksub;
/* Find and remove the matching guid's */
ksub = gnc_kvp_bag_find_by_guid (sa->kvp_data, "lot-split",
"peer_guid", &sb->guid);
if (!ksub)
{
PERR ("merging splits that didn't have correct gemini values!\n"
"looking for guid=%s\n"
"bag held: %s",
guid_to_string (&sb->guid),
kvp_frame_to_string (sa->kvp_data));
return;
}
gnc_kvp_bag_remove_frame (sa->kvp_data, "lot-split", ksub);
kvp_frame_delete (ksub);
}
/* The 'merge_splits() routine causes the amount & value of sb
* to be merged into sa; it then destroys sb. It also performs
* some other misc cleanup */
static void
merge_splits (Split *sa, Split *sb)
{
Account *act;
Transaction *txn;
gnc_numeric amt, val;
act = xaccSplitGetAccount (sb);
xaccAccountBeginEdit (act);
txn = sa->parent;
xaccTransBeginEdit (txn);
/* Remove the guid of sb from the 'gemini' of sa */
remove_guids (sa, sb);
/* Add amount of sb into sa, ditto for value. */
amt = xaccSplitGetAmount (sa);
amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
xaccSplitSetAmount (sa, amt);
val = xaccSplitGetValue (sa);
val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
xaccSplitSetValue (sa, val);
/* Set reconcile to no; after this much violence,
* no way its reconciled. */
xaccSplitSetReconcile (sa, NREC);
/* If sb has associated gains splits, trash them. */
if ((sb->gains_split) &&
(sb->gains_split->gains & GAINS_STATUS_GAINS))
{
Transaction *t = sb->gains_split->parent;
xaccTransBeginEdit (t);
xaccTransDestroy (t);
xaccTransCommitEdit (t);
}
/* Finally, delete sb */
xaccSplitDestroy(sb);
xaccTransCommitEdit (txn);
xaccAccountCommitEdit (act);
}
gboolean
xaccScrubMergeSubSplits (Split *split)
{
gboolean rc = FALSE;
Transaction *txn;
SplitList *node;
GNCLot *lot;
if (FALSE == is_subsplit (split)) return FALSE;
txn = split->parent;
lot = xaccSplitGetLot (split);
ENTER (" ");
restart:
for (node=txn->splits; node; node=node->next)
{
Split *s = node->data;
if (xaccSplitGetLot (s) != lot) continue;
if (s == split) continue;
/* OK, this split is in the same lot (and thus same account)
* as the indicated split. It must be a subsplit (although
* we should double-check the kvp's to be sure). Merge the
* two back together again. */
merge_splits (split, s);
rc = TRUE;
goto restart;
}
LEAVE (" splits merged=%d", rc);
return rc;
}
gboolean
xaccScrubMergeTransSubSplits (Transaction *txn)
{
gboolean rc = FALSE;
SplitList *node;
if (!txn) return FALSE;
ENTER (" ");
restart:
for (node=txn->splits; node; node=node->next)
{
Split *s = node->data;
if (!xaccScrubMergeSubSplits(s)) continue;
rc = TRUE;
goto restart;
}
LEAVE (" splits merged=%d", rc);
return rc;
}
gboolean
xaccScrubMergeLotSubSplits (GNCLot *lot)
{
gboolean rc = FALSE;
SplitList *node;
if (!lot) return FALSE;
ENTER (" ");
restart:
for (node=gnc_lot_get_split_list(lot); node; node=node->next)
{
Split *s = node->data;
if (!xaccScrubMergeSubSplits(s)) continue;
rc = TRUE;
goto restart;
}
LEAVE (" splits merged=%d", rc);
return rc;
}
/* ================================================================= */
gboolean
xaccScrubLot (GNCLot *lot)
{

View File

@ -1,5 +1,5 @@
/********************************************************************\
* Scrub3.h -- Constrain Cap Gains to Track Sources of Gains *
* Scrub3.h -- High-Level Lot Constraint routines. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
@ -22,17 +22,13 @@
/** @addtogroup Engine
@{ */
/** @file Scrub3.h
* @breif Constrain Cap Gains to Track Sources of Gains
* @breif Hiogh-Level API for imposing Lot constraints
* @author Created by Linas Vepstas Sept 2003
* @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
*
* Provides a set of functions and utilities for checking and
* repairing ('scrubbing clean') the usage of Cap Gains
* transactions in stock and commodity accounts.
*
* NOTE: Unless you have special needs, the functions you are looking
* for and almost certainly want to use are either xaccScrubLot() or
* xaccAccountScrubLots().
* Provides the high-level API for checking and repairing ('scrubbing
* clean') the usage of Lots and Cap Gains transactions in stock and
* commodity accounts.
*/
#ifndef XACC_SCRUB3_H
#define XACC_SCRUB3_H
@ -73,42 +69,5 @@ void xaccAccountScrubLots (Account *acc);
void xaccGroupScrubLots (AccountGroup *grp);
void xaccAccountTreeScrubLots (Account *acc);
/** 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 fairly loose, hopefully loose enough
* so that the user can manually fine tune without having this routine
* clobber thier work.
*/
void xaccScrubSubSplitPrice (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.
*/
gboolean xaccScrubMergeSubSplits (Split *split);
gboolean xaccScrubMergeTransSubSplits (Transaction *txn);
gboolean xaccScrubMergeLotSubSplits (GNCLot *lot);
#endif /* XACC_SCRUB3_H */
/** @} */

View File

@ -10,7 +10,7 @@
#include "Account.h"
#include "Group.h"
#include "Scrub2.h"
#include "Scrub3.h"
#include "gnc-engine-util.h"
#include "gnc-module.h"
#include "test-stuff.h"
@ -46,7 +46,7 @@ run_test (void)
add_random_transactions_to_book (book, 720);
grp = xaccGetAccountGroup (book);
xaccGroupScrubLotsBalance (grp);
xaccGroupScrubLots (grp);
/* --------------------------------------------------------- */
/* In the second test, we create an account with unrealized gains,