merge branch cap-gains6:

new code that recalculates lots (incl. cap gains)
to be used if the value of any split in the lot changes.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@9341 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Linas Vepstas 2003-09-16 12:44:01 +00:00
parent 3573ffc694
commit 572d2faa56
12 changed files with 679 additions and 180 deletions

View File

@ -41,6 +41,7 @@ libgncmod_engine_la_SOURCES = \
kvp-util.c \
md5.c \
messages.c \
policy.c \
qofbackend.c \
qofbook.c \
qofid.c \
@ -93,6 +94,7 @@ gncinclude_HEADERS = \
kvp-scm.h \
kvp-util.h \
messages.h \
policy.h \
qof.h \
qofbackend.h \
qofbook.h \
@ -121,6 +123,7 @@ noinst_HEADERS = \
md5.h \
gw-engine.h \
gw-kvp.h \
policy-p.h \
qofbackend-p.h \
qofbook-p.h \
qofid-p.h \

View File

@ -50,13 +50,14 @@
#include "gnc-trace.h"
#include "kvp-util-p.h"
#include "messages.h"
#include "policy-p.h"
static short module = MOD_LOT;
/* ============================================================== */
void
xaccAccountScrubLots (Account *acc)
xaccAccountAssignLots (Account *acc)
{
SplitList *node;
@ -67,8 +68,7 @@ xaccAccountScrubLots (Account *acc)
/* Loop over all splits, and make sure that every split
* belongs to some lot. If a split does not belong to
* any lots, its is placed into the earliest possible
* lot (thus enforcing FIFO accounting rules).
* any lots, poke it into one.
*/
restart_loop:
for (node=acc->splits; node; node=node->next)
@ -77,13 +77,61 @@ restart_loop:
/* If already in lot, then no-op */
if (split->lot) continue;
if (xaccSplitFIFOAssignToLot (split)) goto restart_loop;
if (xaccSplitAssign (split)) goto restart_loop;
}
xaccAccountCommitEdit (acc);
LEAVE ("acc=%s", acc->accountName);
}
/* ============================================================== */
/** 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.
*/
void
xaccLotFill (GNCLot *lot)
{
gnc_numeric lot_baln;
Account *acc;
if (!lot) return;
acc = lot->account;
ENTER ("acc=%s", acc->accountName);
/* If balance already zero, we have nothing to do. */
lot_baln = gnc_lot_get_balance (lot);
if (gnc_numeric_zero_p (lot_baln)) return;
xaccAccountBeginEdit (acc);
/* Loop until we've filled up the lot, (i.e. till the
* balance goes to zero) or there are no splits left. */
while (1)
{
Split *split, *subsplit;
split = FIFOPolicyGetSplit (lot, NULL);
subsplit = xaccSplitAssignToLot (split, lot);
if (subsplit == split)
{
PERR ("Accounting Policy gave us a split that "
"doesn't fit into this lot");
break;
}
lot_baln = gnc_lot_get_balance (lot);
if (gnc_numeric_zero_p (lot_baln)) break;
}
xaccAccountCommitEdit (acc);
LEAVE ("acc=%s", acc->accountName);
}
/* ============================================================== */
void
@ -172,7 +220,7 @@ static gpointer
lot_scrub_cb (Account *acc, gpointer data)
{
if (FALSE == xaccAccountHasTrades (acc)) return NULL;
xaccAccountScrubLots (acc);
xaccAccountAssignLots (acc);
xaccAccountScrubDoubleBalance (acc);
return NULL;
}
@ -189,7 +237,7 @@ xaccAccountScrubLotsBalance (Account *acc)
{
if (!acc) return;
if (FALSE == xaccAccountHasTrades (acc)) return;
xaccAccountScrubLots (acc);
xaccAccountAssignLots (acc);
xaccAccountScrubDoubleBalance (acc);
}
@ -201,7 +249,7 @@ xaccAccountTreeScrubLotsBalance (Account *acc)
xaccGroupScrubLotsBalance (acc->children);
if (FALSE == xaccAccountHasTrades (acc)) return;
xaccAccountScrubLots (acc);
xaccAccountAssignLots (acc);
xaccAccountScrubDoubleBalance (acc);
}

View File

@ -56,15 +56,24 @@ void xaccGroupScrubLotsBalance (AccountGroup *grp);
void xaccAccountScrubLotsBalance (Account *acc);
void xaccAccountTreeScrubLotsBalance (Account *acc);
/** The xaccAccountScrubLots() routine will walk over all of
/** The xaccAccountAssignLots() routine will walk over all of
* the splits in an account, and make sure that each belongs
* to a lot. 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 routine implements a FIFO acounting
* policy.
* 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.
*/
void xaccAccountScrubLots (Account *acc);
void xaccAccountAssignLots (Account *acc);
/** 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.
*/
void xaccLotFill (GNCLot *lot);
/** The xaccAccountScrubDoubleBalance() routine examines all
* of the closed lots in an account, and verifies that the

View File

@ -33,13 +33,17 @@
#include <glib.h>
#include "cap-gains.h"
#include "gnc-commodity.h"
#include "gnc-engine.h"
#include "gnc-lot.h"
#include "gnc-numeric.h"
#include "gnc-trace.h"
#include "kvp_frame.h"
#include "kvp-util-p.h"
#include "policy-p.h"
#include "Account.h"
#include "Scrub2.h"
#include "Scrub3.h"
#include "Transaction.h"
#include "TransactionP.h"
@ -178,6 +182,16 @@ merge_splits (Split *sa, Split *sb)
* 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);
@ -220,7 +234,7 @@ restart:
}
gboolean
xaccScrubMergeTxnSubSplits (Transaction *txn)
xaccScrubMergeTransSubSplits (Transaction *txn)
{
gboolean rc = FALSE;
SplitList *node;
@ -241,4 +255,84 @@ restart:
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;
}
/* ================================================================= */
void
xaccScrubLot (GNCLot *lot)
{
gnc_numeric lot_baln;
gboolean opening_baln_is_pos, lot_baln_is_pos;
Account *acc;
if (!lot) return;
ENTER (" ");
acc = gnc_lot_get_account (lot);
xaccAccountBeginEdit(acc);
xaccScrubMergeLotSubSplits (lot);
/* If the lot balance is zero, we don't need to rebalance */
lot_baln = gnc_lot_get_balance (lot);
if (! gnc_numeric_zero_p (lot_baln))
{
SplitList *node;
gnc_numeric opening_baln;
/* Get the opening balance for this lot */
FIFOPolicyGetLotOpening (lot, &opening_baln, NULL, NULL, NULL);
/* If the lot is fat, give the boot to all the non-opening
* splits, and refill it */
opening_baln_is_pos = gnc_numeric_positive_p(opening_baln);
lot_baln_is_pos = gnc_numeric_positive_p(lot_baln);
if ((opening_baln_is_pos || lot_baln_is_pos) &&
((!opening_baln_is_pos) || (!lot_baln_is_pos)))
{
rethin:
for (node=gnc_lot_get_split_list(lot); node; node=node->next)
{
Split *s = node->data;
if (FIFOPolicyIsOpeningSplit (lot, s, NULL)) continue;
gnc_lot_remove_split (lot, s);
goto rethin;
}
}
/* At this point the lot is thin, so try to fill it */
xaccLotFill (lot);
/* Make sure there are no subsplits. */
xaccScrubMergeLotSubSplits (lot);
}
/* Now re-compute cap gains, and then double-check that. */
xaccLotComputeCapGains (lot, NULL);
xaccLotScrubDoubleBalance (lot);
xaccAccountCommitEdit(acc);
LEAVE (" ");
}
/* ========================== END OF FILE ========================= */

View File

@ -53,7 +53,7 @@ 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
* migt be split up if they need to be divided over multiple
* 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
@ -62,11 +62,24 @@ void xaccScrubSubSplitPrice (Split *split);
* The routine returns TRUE if a merger was performed, else
* it returns FALSE.
*
* The xaccScrubMergeTxnSubSplits() routine does the same, except
* 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 xaccScrubMergeTxnSubSplits (Transaction *txn);
gboolean xaccScrubMergeTransSubSplits (Transaction *txn);
gboolean xaccScrubMergeLotSubSplits (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.
*/
void xaccScrubLot (GNCLot *lot);
#endif /* XACC_SCRUB3_H */
/** @} */

View File

@ -51,10 +51,6 @@ ToDo List:
Ideally, later lots are dissolved, and recomputed. However, some
lots may have been user-hand-built. These should be left alone.
o XXX if the split has been split, and the lots need to be recomputed,
then the peers need to be reunified first! And that implies that
gain transactions need to be 'reunified' too.
o XXX Need to create a data-integrity scrubber, tht makes sure that
the various flags, and pointers & etc. match. See sections marked
with XXX below for things that might go wrong.
@ -68,6 +64,8 @@ ToDo List:
#include "AccountP.h"
#include "Group.h"
#include "GroupP.h"
#include "Scrub2.h"
#include "Scrub3.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "cap-gains.h"
@ -78,6 +76,8 @@ ToDo List:
#include "gnc-trace.h"
#include "kvp-util-p.h"
#include "messages.h"
#include "policy.h"
#include "policy-p.h"
#include "qofid-p.h"
static short module = MOD_LOT;
@ -255,7 +255,8 @@ xaccAccountGetDefaultGainAccount (Account *acc, gnc_commodity * currency)
/* ============================================================== */
/* Functionally identical to the following:
* if (!xaccAccountGetDefaultGainAccount()) xaccAccountSetDefaultGainAccount ();
* if (!xaccAccountGetDefaultGainAccount()) {
* xaccAccountSetDefaultGainAccount (); }
* except that it saves a few cycles.
*/
@ -298,20 +299,189 @@ GetOrMakeGainAcct (Account *acc, gnc_commodity * currency)
}
/* ============================================================== */
Split *
xaccSplitAssignToLot (Split *split, GNCLot *lot)
{
Account *acc;
gnc_numeric baln;
int cmp;
gboolean baln_is_positive, amt_is_positive;
if (!lot) return split;
if (!split) return NULL;
/* If this split already belongs to a lot, we are done. */
if (split->lot) return NULL;
/* If the lot is closed, we can't add anything to it */
baln = gnc_lot_get_balance (lot);
if (gnc_lot_is_closed (lot)) return split;
/* If the lot balance is zero, but the lot is open, then
* the lot is empty. Unconditionally add the split. */
if (gnc_numeric_zero_p (baln))
{
acc = split->acc;
xaccAccountBeginEdit (acc);
gnc_lot_add_split (lot, split);
PINFO ("simple added split to lot, new lot baln=%s",
gnc_numeric_to_string (gnc_lot_get_balance(lot)));
xaccAccountCommitEdit (acc);
return NULL;
}
/* If the sign of the split is the same as the sign of the lot,
* we won't add it, because that would make the lot bigger, not
* smaller. Our only function here is to make lot balances smaller.
*/
baln_is_positive = gnc_numeric_positive_p (baln);
amt_is_positive = gnc_numeric_positive_p (split->amount);
if ((baln_is_positive && amt_is_positive) ||
((!baln_is_positive) && (!amt_is_positive)))
{
return split;
}
/* If adding the split would make the lot balance change sign,
* then we split the split into two pieces: one piece that will
* bring the lot balance to zero, and another to be dealt with
* later. */
cmp = gnc_numeric_compare (gnc_numeric_abs(split->amount),
gnc_numeric_abs(baln));
PINFO ("found open lot with baln=%s", gnc_numeric_to_string (baln));
/* cmp == -1 if amt < baln, ==0 if amt==baln */
if (0 >= cmp)
{
acc = split->acc;
xaccAccountBeginEdit (acc);
gnc_lot_add_split (lot, split);
PINFO ("simple added split to lot, new lot baln=%s",
gnc_numeric_to_string (gnc_lot_get_balance(lot)));
xaccAccountCommitEdit (acc);
return NULL;
}
/* If we are here, then (cmp == +1 iff (amt > baln)) and we need
* to split up the split into pieces. Do it. */
{
time_t now = time(0);
Split * new_split;
gnc_numeric amt_a, amt_b, amt_tot;
gnc_numeric val_a, val_b, val_tot;
gnc_numeric tmp;
Transaction *trans;
Timespec ts;
acc = split->acc;
xaccAccountBeginEdit (acc);
trans = split->parent;
xaccTransBeginEdit (trans);
amt_tot = split->amount;
amt_a = gnc_numeric_neg (baln);
amt_b = gnc_numeric_sub_fixed (amt_tot, amt_a);
PINFO ("++++++++++++++ splitting split into amt = %s + %s",
gnc_numeric_to_string(amt_a),
gnc_numeric_to_string(amt_b) );
/* Compute the value so that it holds in the same proportion:
* i.e. so that (amt_a / amt_tot) = (val_a / val_tot)
*/
val_tot = split->value;
val_a = gnc_numeric_mul (amt_a, val_tot,
GNC_DENOM_AUTO, GNC_DENOM_REDUCE);
tmp = gnc_numeric_div (val_a, amt_tot,
gnc_numeric_denom(val_tot), GNC_DENOM_EXACT);
val_a = tmp;
val_b = gnc_numeric_sub_fixed (val_tot, val_a);
PINFO ("split value is = %s = %s + %s",
gnc_numeric_to_string(val_tot),
gnc_numeric_to_string(val_a),
gnc_numeric_to_string(val_b) );
xaccSplitSetAmount (split, amt_a);
xaccSplitSetValue (split, val_a);
/* Adding this split will have the effect of closing this lot,
* because the new balance should be precisely zero. */
gnc_lot_add_split (lot, split);
/* Put the remainder of the balance into a new split,
* which is in other respects just a clone of this one. */
new_split = xaccMallocSplit (acc->book);
/* Copy most of the split attributes */
xaccSplitSetMemo (new_split, xaccSplitGetMemo (split));
xaccSplitSetAction (new_split, xaccSplitGetAction (split));
xaccSplitSetReconcile (new_split, xaccSplitGetReconcile (split));
ts = xaccSplitRetDateReconciledTS (split);
xaccSplitSetDateReconciledTS (new_split, &ts);
/* We do not copy the KVP tree, as it seems like a dangerous
* thing to do. If the user wants to access stuff in the 'old'
* kvp tree from the 'new' split, they shoudl follow the
* 'split-lot' pointers. Yes, this is complicated, but what
* else can one do ??
*/
/* Add kvp markup to indicate that these two splits used
* to be one before being 'split'
*/
gnc_kvp_bag_add (split->kvp_data, "lot-split", now,
"peer_guid", xaccSplitGetGUID (new_split),
NULL);
gnc_kvp_bag_add (new_split->kvp_data, "lot-split", now,
"peer_guid", xaccSplitGetGUID (split),
NULL);
xaccSplitSetAmount (new_split, amt_b);
xaccSplitSetValue (new_split, val_b);
xaccAccountInsertSplit (acc, new_split);
xaccTransAppendSplit (trans, new_split);
xaccTransCommitEdit (trans);
xaccAccountCommitEdit (acc);
return new_split;
}
}
/* ============================================================== */
static GNCLot *
MakeDefaultLot (Account *acc)
{
GNCLot * lot;
gint64 id;
char buff[200];
lot = gnc_lot_new (acc->book);
/* Provide a reasonable title for the new lot */
id = kvp_frame_get_gint64 (xaccAccountGetSlots (acc), "/lot-mgmt/next-id");
snprintf (buff, 200, _("Lot %lld"), id);
kvp_frame_set_str (gnc_lot_get_slots (lot), "/title", buff);
id ++;
kvp_frame_set_gint64 (xaccAccountGetSlots (acc), "/lot-mgmt/next-id", id);
return lot;
}
/* Accounting-policy callback. Given an account and an amount,
* this routine should return a lot. By implementing this as
* a callback, we can 'easily' add other accounting policies.
* Currently, we only implement the FIFO policy.
*/
typedef GNCLot * (*AccountingPolicy) (Account *,
Split *,
gpointer user_data);
static gboolean
xaccSplitAssignToLot (Split *split,
AccountingPolicy policy, gpointer user_data)
PolicyAssignSplit (Split *split,
AccountingPolicyGetLot policy, gpointer user_data)
{
Account *acc;
gboolean splits_added = FALSE;
gboolean splits_split_up = FALSE;
GNCLot *lot;
if (!split) return FALSE;
@ -324,7 +494,7 @@ xaccSplitAssignToLot (Split *split,
xaccAccountBeginEdit (acc);
/* If we are here, this split does not belong to any lot.
* Lets put it in the earliest one we can find. This
* We ask the policy for a lot to assign it to. This
* block is written in the form of a while loop, since we
* may have to bust a split across several lots.
*/
@ -332,142 +502,29 @@ xaccSplitAssignToLot (Split *split,
{
PINFO ("have split amount=%s", gnc_numeric_to_string (split->amount));
split->gains |= GAINS_STATUS_VDIRTY;
lot = policy (acc, split, user_data);
if (lot)
lot = policy (split, user_data);
if (!lot)
{
/* If the amount is smaller than open balance ... */
gnc_numeric baln = gnc_lot_get_balance (lot);
int cmp = gnc_numeric_compare (gnc_numeric_abs(split->amount),
gnc_numeric_abs(baln));
PINFO ("found open lot with baln=%s", gnc_numeric_to_string (baln));
/* cmp == +1 if amt > baln */
if (0 < cmp)
{
time_t now = time(0);
Split * new_split;
gnc_numeric amt_a, amt_b, amt_tot;
gnc_numeric val_a, val_b, val_tot;
gnc_numeric tmp;
Transaction *trans;
Timespec ts;
trans = split->parent;
xaccTransBeginEdit (trans);
amt_tot = split->amount;
amt_a = gnc_numeric_neg (baln);
amt_b = gnc_numeric_sub_fixed (amt_tot, amt_a);
PINFO ("++++++++++++++ splitting split into amt = %s + %s",
gnc_numeric_to_string(amt_a),
gnc_numeric_to_string(amt_b) );
/* Compute the value so that it holds in the same proportion:
* i.e. so that (amt_a / amt_tot) = (val_a / val_tot)
*/
val_tot = split->value;
val_a = gnc_numeric_mul (amt_a, val_tot,
GNC_DENOM_AUTO, GNC_DENOM_REDUCE);
tmp = gnc_numeric_div (val_a, amt_tot,
gnc_numeric_denom(val_tot), GNC_DENOM_EXACT);
val_a = tmp;
val_b = gnc_numeric_sub_fixed (val_tot, val_a);
PINFO ("split value is = %s = %s + %s",
gnc_numeric_to_string(val_tot),
gnc_numeric_to_string(val_a),
gnc_numeric_to_string(val_b) );
xaccSplitSetAmount (split, amt_a);
xaccSplitSetValue (split, val_a);
/* Adding this split will have the effect of closing this lot,
* because the new balance should be precisely zero. */
gnc_lot_add_split (lot, split);
/* Put the remainder of the balance into a new split,
* which is in other respects just a clone of this one. */
new_split = xaccMallocSplit (acc->book);
/* Copy most of the split attributes */
xaccSplitSetMemo (new_split, xaccSplitGetMemo (split));
xaccSplitSetAction (new_split, xaccSplitGetAction (split));
xaccSplitSetReconcile (new_split, xaccSplitGetReconcile (split));
ts = xaccSplitRetDateReconciledTS (split);
xaccSplitSetDateReconciledTS (new_split, &ts);
/* We do not copy the KVP tree, as it seems like a dangerous
* thing to do. If the user wants to access stuff in the 'old'
* kvp tree from the 'new' split, they shoudl follow the
* 'split-lot' pointers. Yes, this is complicated, but what
* else can one do ??
*/
/* Add kvp markup to indicate that these two splits used
* to be one before being 'split'
*/
gnc_kvp_bag_add (split->kvp_data, "lot-split", now,
"peer_guid", xaccSplitGetGUID (new_split),
NULL);
gnc_kvp_bag_add (new_split->kvp_data, "lot-split", now,
"peer_guid", xaccSplitGetGUID (split),
NULL);
xaccSplitSetAmount (new_split, amt_b);
xaccSplitSetValue (new_split, val_b);
xaccAccountInsertSplit (acc, new_split);
xaccTransAppendSplit (trans, new_split);
xaccTransCommitEdit (trans);
split = new_split;
splits_added = TRUE;
}
else
{
gnc_lot_add_split (lot, split);
split = NULL;
PINFO ("added split to lot, new lot baln=%s",
gnc_numeric_to_string (gnc_lot_get_balance(lot)));
}
}
else
{
gint64 id;
char buff[200];
/* No lot was found. Start a new lot */
lot = MakeDefaultLot (acc);
PINFO ("start new lot");
lot = gnc_lot_new (acc->book);
gnc_lot_add_split (lot, split);
split = NULL;
/* Provide a reasonable title for the new lot */
id = kvp_frame_get_gint64 (xaccAccountGetSlots (acc), "/lot-mgmt/next-id");
snprintf (buff, 200, _("Lot %lld"), id);
kvp_frame_set_str (gnc_lot_get_slots (lot), "/title", buff);
id ++;
kvp_frame_set_gint64 (xaccAccountGetSlots (acc), "/lot-mgmt/next-id", id);
}
split = xaccSplitAssignToLot (split, lot);
if (split) splits_split_up = TRUE;
}
xaccAccountCommitEdit (acc);
LEAVE ("added=%d", splits_added);
return splits_added;
}
static GNCLot *
FIFOPolicy (Account *acc, Split *split, gpointer user_data)
{
return xaccAccountFindEarliestOpenLot (acc, split->amount);
LEAVE ("split_up=%d", splits_split_up);
return splits_split_up;
}
gboolean
xaccSplitFIFOAssignToLot (Split *split)
xaccSplitAssign (Split *split)
{
return xaccSplitAssignToLot (split, FIFOPolicy, NULL);
/* XXX FIXME: instead of a hard-wired fifo policy, we should
* be using the policy as specified by the account. i.e.
* we should be using split->acc->polcy as the function
*/
return PolicyAssignSplit (split, FIFOPolicyGetLot, NULL);
}
/* ============================================================== */
@ -496,11 +553,12 @@ xaccSplitGetCapGainsSplit (Split *split)
void
xaccSplitComputeCapGains(Split *split, Account *gain_acc)
{
Split *opening_split;
GNCLot *lot;
gnc_commodity *currency = NULL;
gnc_numeric zero = gnc_numeric_zero();
gnc_numeric value = zero;
gnc_numeric opening_amount, opening_value;
gnc_commodity *opening_currency;
if (!split) return;
lot = split->lot;
@ -545,16 +603,18 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
* may exist if users attempted to manually record gains. */
if (gnc_numeric_zero_p (split->amount)) return;
opening_split = gnc_lot_get_earliest_split(lot);
if (split == opening_split)
FIFOPolicyGetLotOpening (lot, &opening_amount, &opening_value,
&opening_currency, NULL);
if (FIFOPolicyIsOpeningSplit (lot, split, NULL))
{
#if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
/* Check to make sure that this opening split doesn't
* have a cap-gain transaction associated with it.
* If it does, that's wrong, and we ruthlessly destroy it.
* XXX Don't do this, it leads to infinite loops.
* We need to scrub out errors like this elsewhere!
*/
#if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
if (xaccSplitGetCapGainsSplit (split))
{
Split *gains_split = xaccSplitGetCapGainsSplit(split);
@ -569,10 +629,9 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
return;
}
/* Check to make sure the opening split and this split
/* Check to make sure the lot-opening currency and this split
* use the same currency */
if (FALSE == gnc_commodity_equiv (currency,
opening_split->parent->common_currency))
if (FALSE == gnc_commodity_equiv (currency, opening_currency))
{
/* OK, the purchase and the sale were made in different currencies.
* I don't know how to compute cap gains for that. This is not
@ -583,16 +642,17 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
/* Opening amount should be larger (or equal) to current split,
* and it should be of the opposite sign.
XXX this should be a part of a scrub routine !
*/
if (0 > gnc_numeric_compare (gnc_numeric_abs(opening_split->amount),
if (0 > gnc_numeric_compare (gnc_numeric_abs(opening_amount),
gnc_numeric_abs(split->amount)))
{
PERR ("Malformed Lot! (too thin!)\n");
return;
}
if ( (gnc_numeric_negative_p(opening_split->amount) ||
if ( (gnc_numeric_negative_p(opening_amount) ||
gnc_numeric_positive_p(split->amount)) &&
(gnc_numeric_positive_p(opening_split->amount) ||
(gnc_numeric_positive_p(opening_amount) ||
gnc_numeric_negative_p(split->amount)))
{
PERR ("Malformed Lot! (too fat!)\n");
@ -606,16 +666,16 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
* cost_basis = purchase_price * current_amount
* cap_gain = current_value - cost_basis
*/
value = gnc_numeric_mul (opening_split->value, split->amount,
value = gnc_numeric_mul (opening_value, split->amount,
GNC_DENOM_AUTO, GNC_RND_NEVER);
value = gnc_numeric_div (value, opening_split->amount,
gnc_numeric_denom(opening_split->value), GNC_DENOM_EXACT);
value = gnc_numeric_div (value, opening_amount,
gnc_numeric_denom(opening_value), GNC_DENOM_EXACT);
value = gnc_numeric_sub (value, split->value,
GNC_DENOM_AUTO, GNC_DENOM_LCD);
PINFO ("Open amt=%s val=%s; split amt=%s val=%s; gains=%s\n",
gnc_numeric_to_string (opening_split->amount),
gnc_numeric_to_string (opening_split->value),
gnc_numeric_to_string (opening_amount),
gnc_numeric_to_string (opening_value),
gnc_numeric_to_string (split->amount),
gnc_numeric_to_string (split->value),
gnc_numeric_to_string (value));
@ -626,7 +686,7 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
* not to upset the lot balance), the amt of the other is the same
* as its value (its the realized gain/loss).
*/
if (FALSE == gnc_numeric_equal (value, zero))
if (FALSE == gnc_numeric_zero_p (value))
{
Transaction *trans;
Split *lot_split, *gain_split;
@ -752,4 +812,18 @@ xaccSplitGetCapGains(Split * split)
return split->value;
}
/* ============================================================== */
void
xaccLotComputeCapGains (GNCLot *lot, Account *gain_acc)
{
SplitList *node;
for (node=lot->splits; node; node=node->next)
{
Split *s = node->data;
xaccSplitComputeCapGains (s, gain_acc);
}
}
/* =========================== END OF FILE ======================= */

View File

@ -44,6 +44,7 @@
#define XACC_CAP_GAINS_H
#include "gnc-engine.h"
#include "gnc-numeric.h"
/** The xaccSplitGetCapGains() method returns the value of
* capital gains (if any) associated with the indicated
@ -107,12 +108,13 @@ void xaccAccountSetDefaultGainAccount (Account *acc, Account *gains_acct);
Split * xaccSplitGetCapGainsSplit (Split *);
/** The`xaccSplitFIFOAssignToLot() routine will take the indicated
* split and assign it to the earliest open lot that it can find.
/** The`xaccSplitAssign() routine will take the indicated
* split and, if it doesn't already belong to a lot, it will attempt
* to assign it to an appropriate lot.
* If the split already belongs to a Lot, this routine does nothing.
* If there are no open Lots, this routine will create a new lot
* and place the split into it. If there's an open lot, and its
* big enough to accept the split in it's entrety, then the split
* big enough to accept the split in it's entirety, then the split
* will be placed into that lot. If the split is too big to fit
* into the currently open lot, it will be busted up into two
* (or more) pieces, and each placed into a lot accordingly.
@ -123,12 +125,33 @@ Split * xaccSplitGetCapGainsSplit (Split *);
* directory is used to identify the peers. 'gemini'-style kvp's
* are used.
*
* Because this routine always uses the earliest open lot, it
* implments a "FIFO" First-In First-Out accounting policy.
* (Adding new policies is 'easy', read the source luke).
* This routine uses the "FIFOPolicy" callback, and thus
* implements a "FIFO" First-In First-Out accounting policy.
* This is currently the only implemented policy; adding new
* policies should be 'easy'; read the source luke.
*/
gboolean xaccSplitFIFOAssignToLot (Split *split);
gboolean xaccSplitAssign (Split *split);
/** The xaccSplitAssignToLot() routine will fit the indicated split
* into the indicated lot, with the goal of closing the lot, or
* at least bringing the lot balance closer to closure. (A closed
* lot has a balance of zero). To make this "fit", a variety of
* checks and actions are performed. First, the lot must be open,
* and the sign of the split amount must be opposite to the sign
* of the lot balance. The 'opposite-sign' requirement is so that
* inserting the split will cause the size of the lot to decrease.
* If the amount of the split is too small, or is just right to
* close the lot, the split is added, and NULL is returned. If
* the split is larger than the lot balance, the split will be
* divided into sub-splits, one of which is just right to close
* the lot. A pointer to the other sub-split will be returned.
*
* If the split had to be broken up, kvp markup in the "/lot-split"
* directory is used to identify the peers. 'gemini'-style kvp's
* are used.
*/
Split * xaccSplitAssignToLot (Split *split, GNCLot *lot);
/** The xaccSplitComputeCapGains() routine computes the cap gains
* or losses for the indicated split. The gains are placed into
@ -144,9 +167,13 @@ gboolean xaccSplitFIFOAssignToLot (Split *split);
* and must be equal to or smaller, than the 'amount' of the opening
* split; its an error otherwise. If the 'amount' of the split is
* less than the opening amount, the gains are pro-rated.
*
* The xaccLotComputeCapGains() routine merely invokes the above on
* each split in the lot.
*/
void xaccSplitComputeCapGains(Split *split, Account *gain_acc);
void xaccLotComputeCapGains (GNCLot *lot, Account *gain_acc);
#endif /* XACC_CAP_GAINS_H */
/** @} */

View File

@ -196,7 +196,11 @@ gnc_lot_get_balance (GNCLot *lot)
gnc_numeric baln = zero;
if (!lot) return zero;
if (!lot->splits) return zero;
if (!lot->splits)
{
lot->is_closed = FALSE;
return zero;
}
/* Sum over splits; because they all belong to same account
* they will have same denominator.

View File

@ -55,6 +55,10 @@ QofBook * gnc_lot_get_book (GNCLot *);
/** The gnc_lot_add_split() routine adds a split to this lot. Note
* that *all* splits in a lot must also be in the same account.
* Note that this routine adds the split unconditionally, with
* no regard for the accounting policy. To enforce a particular
* accounting polciy, use the xaccSplitAssignToLot() routine
* instead.
*/
void gnc_lot_add_split (GNCLot *, Split *);
void gnc_lot_remove_split (GNCLot *, Split *);

75
src/engine/policy-p.h Normal file
View File

@ -0,0 +1,75 @@
/********************************************************************\
* policy-.h -- Implement Accounting Policy Private Header File *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
/** @file policy-p.h
* @breif Implement Accounting Policy Private header File.
* @author Created by Linas Vepstas August 2003
* @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
*
* This file implements Accounting Policy. The Accounting Polciy
* determines how splits are assigned to lots. The default policy
* is teh FIFO policy: the first thing bought is also the first
* thing sold.
*/
#ifndef XACC_POLICY_P_H
#define XACC_POLICY_P_H
#include "gnc-engine.h"
/* ============================================================== */
/** The FIFOPolicy routines try to encapsulate the FIFO-specific
* parts of the cap-gains routine, and can eb replaced by something
* else for other policies (e.g. LIFO)
*
* The FIFOPolicyGetLot() routine returns a lot into which the
* indicated split should be placed.
*
* The FIFOPolicyGetSplit() routine returns an unassinged split
* from the account that is appropriate for placing into the
* indicated lot. For the FIFO policy, that would be the
* earliest split that is not in any account, and is of the
* appropriate sign.
*
* The FIFOPolicyIsOpeningSplit() predicate returns a true/false
* value, indicating if the indicated split was used to 'open'
* or 'grow' the lot.
*
* The FIFOPolicyGetLotOpening() routine returns information about
* the opening balances for the lot. The 'opening balances'
* are the sum of all the splits used to grow (increase the size
* of) the lot. For a FIFO polciy, there is only one split
* that opens a lot.
*/
GNCLot * FIFOPolicyGetLot (Split *split, gpointer user_data);
Split * FIFOPolicyGetSplit (GNCLot *lot, gpointer user_data);
void FIFOPolicyGetLotOpening (GNCLot *lot,
gnc_numeric *ret_amount, gnc_numeric *ret_value,
gnc_commodity **ret_currency,
gpointer user_data);
gboolean FIFOPolicyIsOpeningSplit (GNCLot *lot, Split *split,
gpointer user_data);
#endif /* XACC_POLICY_P_H */

103
src/engine/policy.c Normal file
View File

@ -0,0 +1,103 @@
/********************************************************************\
* policy.c -- Implement FIFO Accounting Policy *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
/** @file policy.c
* @breif Implement FIFO Accounting Policy.
* @author Created by Linas Vepstas August 2003
* @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
*
* This file implements the FIFO Accounting Policy (and, in the
* future, others as well). The Accounting Polciy determines
* how splits are assigned to lots.
*/
#include "config.h"
#include <glib.h>
#include "Account.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "cap-gains.h"
#include "gnc-engine.h"
#include "gnc-lot.h"
#include "gnc-lot-p.h"
#include "gnc-trace.h"
#include "policy.h"
#include "policy-p.h"
/* static short module = MOD_LOT; */
/* ============================================================== */
GNCLot *
FIFOPolicyGetLot (Split *split, gpointer user_data)
{
return xaccAccountFindEarliestOpenLot (split->acc, split->amount);
}
Split *
FIFOPolicyGetSplit (GNCLot *lot, gpointer user_data)
{
SplitList *node;
gboolean want_positive;
want_positive = gnc_numeric_negative_p (gnc_lot_get_balance (lot));
/* Make use of the fact that the splits in a lot are already
* in date order; so we don't have to search for the earliest. */
for (node = xaccAccountGetSplitList (lot->account); node; node=node->next)
{
gboolean is_positive;
Split *split = node->data;
if (split->lot) continue;
is_positive = gnc_numeric_positive_p (split->amount);
if ((want_positive && is_positive) ||
((!want_positive) && (!is_positive))) return split;
}
return NULL;
}
void
FIFOPolicyGetLotOpening (GNCLot *lot,
gnc_numeric *ret_amount, gnc_numeric *ret_value,
gnc_commodity **ret_currency,
gpointer user_data)
{
Split *opening_split;
opening_split = gnc_lot_get_earliest_split(lot);
if (ret_amount) *ret_amount = opening_split->amount;
if (ret_value) *ret_value = opening_split->value;
if (ret_currency) *ret_currency = opening_split->parent->common_currency;
}
gboolean
FIFOPolicyIsOpeningSplit (GNCLot *lot, Split *split, gpointer user_data)
{
Split *opening_split;
opening_split = gnc_lot_get_earliest_split(lot);
return (split == opening_split);
}
/* =========================== END OF FILE ======================= */

45
src/engine/policy.h Normal file
View File

@ -0,0 +1,45 @@
/********************************************************************\
* policy.h -- Implement Accounting Policy *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
/** @file policy.h
* @breif Implement Accounting Policy.
* @author Created by Linas Vepstas August 2003
* @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
*
* This file implements Accounting Policy. The Accounting Polciy
* determines how splits are assigned to lots. The default policy
* is teh FIFO policy: the first thing bought is also the first
* thing sold.
*/
#ifndef XACC_POLICY_H
#define XACC_POLICY_H
#include "gnc-engine.h"
/* Accounting-policy callback. Given an account and an amount,
* this routine should return a lot. By implementing this as
* a callback, we can 'easily' add other accounting policies.
* Currently, we only implement the FIFO policy.
*/
typedef GNCLot * (*AccountingPolicyGetLot) (Split *,
gpointer user_data);
#endif /* XACC_POLICY_H */