gnucash/src/engine/cap-gains.c

1168 lines
37 KiB
C
Raw Normal View History

/********************************************************************\
* cap-gains.c -- Automatically Compute Capital Gains/Losses *
* *
* 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 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
/** @file cap-gains.c
* @brief Utilities to Automatically Compute Capital Gains/Losses.
* @author Created by Linas Vepstas August 2003
* @author Copyright (c) 2003,2004 Linas Vepstas <linas@linas.org>
*
* This file implements the various routines to automatically
* compute and handle Cap Gains/Losses resulting from trading
* activities. Some of these routines might have broader
* applicability, for handling depreciation & etc.
*
* This code is under development, and is 'beta': we think we're
* mostly done, and we've tested and "things work for us", but there
* may still be something missing, and there might still be some
* bugs.
*
* This code uses a 'gains dirty' flag: A 'dirty' flag on the source
* split indicates that the gains transaction needs to be recomputed.
* Another flag, the gains transaction flag, marks the split as
* being a gains split, and that the source transaction should be
* checked for dirtiness before returning the date, the amount, the
* value, etc. Finally, these flags make amount and value read-only
* for the gains splits. (the memo is user-modifieable).
*
* If the amount in a split is changed, then the lot has to be recomputed.
* This has a potential trickle-through effect on all later lots.
* Ideally, later lots are dissolved, and recomputed. However, some
* lots may have been user-hand-built. These should be left alone.
*
ToDo:
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.
*/
#include "config.h"
#include <glib.h>
#include <glib/gi18n.h>
#include "Account.h"
#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"
#include "gnc-engine.h"
#include "gnc-lot.h"
#include "gnc-lot-p.h"
#include "policy.h"
#include "policy-p.h"
static QofLogModule log_module = GNC_MOD_LOT;
/* ============================================================== */
gboolean
xaccAccountHasTrades (Account *acc)
{
gnc_commodity *acc_comm;
SplitList *node;
if (!acc) return FALSE;
if (xaccAccountIsPriced (acc))
return TRUE;
acc_comm = acc->commodity;
for (node=acc->splits; node; node=node->next)
{
Split *s = node->data;
Transaction *t = s->parent;
if (acc_comm != t->common_currency) return TRUE;
}
return FALSE;
}
/* ============================================================== */
struct find_lot_s
{
GNCLot *lot;
gnc_commodity *currency;
Timespec ts;
int (*numeric_pred)(gnc_numeric);
gboolean (*date_pred)(Timespec e, Timespec tr);
};
static gboolean
earliest_pred (Timespec earl, Timespec tran)
{
return ((earl.tv_sec > tran.tv_sec) ||
((earl.tv_sec == tran.tv_sec) && (earl.tv_nsec > tran.tv_nsec)));
}
static gboolean
latest_pred (Timespec earl, Timespec tran)
{
return ((earl.tv_sec < tran.tv_sec) ||
((earl.tv_sec == tran.tv_sec) && (earl.tv_nsec < tran.tv_nsec)));
}
static gpointer
finder_helper (GNCLot *lot, gpointer user_data)
{
struct find_lot_s *els = user_data;
Split *s;
Transaction *trans;
gnc_numeric bal;
if (gnc_lot_is_closed (lot)) return NULL;
/* We want a lot whose balance is of the correct sign */
bal = gnc_lot_get_balance (lot);
if (0 == (els->numeric_pred) (bal)) return NULL;
s = gnc_lot_get_earliest_split (lot);
trans = s->parent;
if (els->currency &&
(FALSE == gnc_commodity_equiv (els->currency,
trans->common_currency)))
{
return NULL;
}
if (els->date_pred (els->ts, trans->date_posted))
{
els->ts = trans->date_posted;
els->lot = lot;
}
return NULL;
}
static inline GNCLot *
xaccAccountFindOpenLot (Account *acc, gnc_numeric sign,
gnc_commodity *currency,
guint64 guess,
gboolean (*date_pred)(Timespec, Timespec))
{
struct find_lot_s es;
es.lot = NULL;
es.currency = currency;
es.ts.tv_sec = guess;
es.ts.tv_nsec = 0;
es.date_pred = date_pred;
if (gnc_numeric_positive_p(sign)) es.numeric_pred = gnc_numeric_negative_p;
else es.numeric_pred = gnc_numeric_positive_p;
xaccAccountForEachLot (acc, finder_helper, &es);
return es.lot;
}
GNCLot *
xaccAccountFindEarliestOpenLot (Account *acc, gnc_numeric sign,
gnc_commodity *currency)
{
GNCLot *lot;
ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, sign.num, sign.denom);
lot = xaccAccountFindOpenLot (acc, sign, currency,
G_MAXUINT64, earliest_pred);
LEAVE ("found lot=%p %s baln=%s", lot, gnc_lot_get_title (lot),
gnc_num_dbg_to_string(gnc_lot_get_balance(lot)));
return lot;
}
GNCLot *
xaccAccountFindLatestOpenLot (Account *acc, gnc_numeric sign,
gnc_commodity *currency)
{
GNCLot *lot;
ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
sign.num, sign.denom);
lot = xaccAccountFindOpenLot (acc, sign, currency,
0, latest_pred);
LEAVE ("found lot=%p %s", lot, gnc_lot_get_title (lot));
return lot;
}
/* ============================================================== */
/* Similar to GetOrMakeAccount, but different in important ways */
static Account *
GetOrMakeLotOrphanAccount (AccountGroup *root, gnc_commodity * currency)
{
char * accname;
Account * acc;
g_return_val_if_fail (root, NULL);
/* build the account name */
if (!currency)
{
PERR ("No currency specified!");
return NULL;
}
accname = g_strconcat (_("Orphaned Gains"), "-",
gnc_commodity_get_mnemonic (currency), NULL);
/* See if we've got one of these going already ... */
acc = xaccGetAccountFromName (root, accname);
if (acc == NULL)
{
/* Guess not. We'll have to build one. */
acc = xaccMallocAccount (root->book);
xaccAccountBeginEdit (acc);
xaccAccountSetName (acc, accname);
xaccAccountSetCommodity (acc, currency);
xaccAccountSetType (acc, INCOME);
xaccAccountSetDescription (acc, _("Realized Gain/Loss"));
xaccAccountSetNotes (acc,
_("Realized Gains or Losses from "
"Commodity or Trading Accounts "
"that haven't been recorded elsewhere."));
/* Hang the account off the root. */
xaccGroupInsertAccount (root, acc);
xaccAccountCommitEdit (acc);
}
g_free (accname);
return acc;
}
/* ============================================================== */
void
xaccAccountSetDefaultGainAccount (Account *acc, Account *gain_acct)
{
KvpFrame *cwd;
KvpValue *vvv;
const char * cur_name;
if (!acc || !gain_acct) return;
cwd = xaccAccountGetSlots (acc);
cwd = kvp_frame_get_frame_slash (cwd, "/lot-mgmt/gains-act/");
/* Accounts are indexed by thier unique currency name */
cur_name = gnc_commodity_get_unique_name (acc->commodity);
xaccAccountBeginEdit (acc);
vvv = kvp_value_new_guid (xaccAccountGetGUID (gain_acct));
kvp_frame_set_slot_nc (cwd, cur_name, vvv);
xaccAccountSetSlots_nc (acc, acc->inst.kvp_data);
xaccAccountCommitEdit (acc);
}
/* ============================================================== */
Account *
xaccAccountGetDefaultGainAccount (Account *acc, gnc_commodity * currency)
{
Account *gain_acct = NULL;
KvpFrame *cwd;
KvpValue *vvv;
GUID * gain_acct_guid;
const char * cur_name;
if (!acc || !currency) return NULL;
cwd = xaccAccountGetSlots (acc);
cwd = kvp_frame_get_frame_slash (cwd, "/lot-mgmt/gains-act/");
/* Accounts are indexed by thier unique currency name */
cur_name = gnc_commodity_get_unique_name (currency);
vvv = kvp_frame_get_slot (cwd, cur_name);
gain_acct_guid = kvp_value_get_guid (vvv);
gain_acct = xaccAccountLookup (gain_acct_guid, acc->inst.book);
return gain_acct;
}
/* ============================================================== */
/* Functionally identical to the following:
* if (!xaccAccountGetDefaultGainAccount()) {
* xaccAccountSetDefaultGainAccount (); }
* except that it saves a few cycles.
*/
static Account *
GetOrMakeGainAcct (Account *acc, gnc_commodity * currency)
{
Account *gain_acct = NULL;
KvpFrame *cwd;
KvpValue *vvv;
GUID * gain_acct_guid;
const char * cur_name;
cwd = xaccAccountGetSlots (acc);
cwd = kvp_frame_get_frame_slash (cwd, "/lot-mgmt/gains-act/");
/* Accounts are indexed by thier unique currency name */
cur_name = gnc_commodity_get_unique_name (currency);
vvv = kvp_frame_get_slot (cwd, cur_name);
gain_acct_guid = kvp_value_get_guid (vvv);
gain_acct = xaccAccountLookup (gain_acct_guid, acc->inst.book);
/* If there is no default place to put gains/losses
* for this account, then create such a place */
if (NULL == gain_acct)
{
AccountGroup *root;
xaccAccountBeginEdit (acc);
root = xaccAccountGetRoot(acc);
gain_acct = GetOrMakeLotOrphanAccount (root, currency);
vvv = kvp_value_new_guid (xaccAccountGetGUID (gain_acct));
kvp_frame_set_slot_nc (cwd, cur_name, vvv);
xaccAccountSetSlots_nc (acc, acc->inst.kvp_data);
xaccAccountCommitEdit (acc);
}
return gain_acct;
}
/* ============================================================== */
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;
/* Anomolous situation; except for voided transactions,
* we don't expect to see splits with no amount ..
* unless they're gains splits, and we shouldn't see those.
*/
if (gnc_numeric_zero_p (split->amount))
{
if (xaccTransGetVoidStatus(split->parent)) return NULL;
PWARN ("split with zero amount; value=%s gflag=%x gsplit=%p",
gnc_num_dbg_to_string (split->amount),
split->gains,
split->gains_split);
if (split->gains_split)
{
PWARN ("gains amt=%s value=%s",
gnc_num_dbg_to_string (split->gains_split->amount),
gnc_num_dbg_to_string (split->gains_split->value));
}
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 ("added split to empty lot, new lot baln=%s (%s)",
gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
gnc_lot_get_title (lot));
xaccAccountCommitEdit (acc);
return NULL;
}
/* If the sign of the split is the same as the sign of the lot,
* add the split, but complain about it ... none of the currently
* implemented accounting policies should be giving us splits
* that make lots larger. One a lot is open, the FIFO/LIFO
* policies should be working only to make the lot smaller.
* We can remove teh warning emssage come the day we have
* fancier policies.
*/
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)))
{
PWARN ("accounting policy gave us split that enlarges the lot!\n"
"old lot baln=%s split amt=%s lot=%s",
gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
gnc_num_dbg_to_string (split->amount),
gnc_lot_get_title (lot));
acc = split->acc;
xaccAccountBeginEdit (acc);
gnc_lot_add_split (lot, split);
xaccAccountCommitEdit (acc);
return NULL;
}
/* 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 (%s)", gnc_num_dbg_to_string (baln),
gnc_lot_get_title (lot));
/* 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_num_dbg_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 frac;
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=%p into amt = %s + %s",
split,
gnc_num_dbg_to_string(amt_a),
gnc_num_dbg_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;
frac = gnc_numeric_div (amt_a, amt_tot,
GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
val_a = gnc_numeric_mul (frac, val_tot,
gnc_numeric_denom(val_tot),
GNC_HOW_RND_ROUND| GNC_HOW_DENOM_EXACT);
val_b = gnc_numeric_sub_fixed (val_tot, val_a);
if (gnc_numeric_check(val_a))
{
PERR("Numeric overflow\n"
"Acct=%s Txn=%s\n"
"\tval_tot=%s amt_a=%s amt_tot=%s\n",
xaccAccountGetName(acc),
xaccTransGetDescription(trans),
gnc_num_dbg_to_string(val_tot),
gnc_num_dbg_to_string(amt_a),
gnc_num_dbg_to_string(amt_tot));
}
if (gnc_numeric_zero_p(val_a) || gnc_numeric_zero_p(val_b))
{
PERR ("Failed to split into two!");
}
PINFO ("split value is = %s = %s + %s",
gnc_num_dbg_to_string(val_tot),
gnc_num_dbg_to_string(val_a),
gnc_num_dbg_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->inst.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->inst.kvp_data, "lot-split", now,
"peer_guid", xaccSplitGetGUID (new_split),
NULL);
gnc_kvp_bag_add (new_split->inst.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->inst.book);
/* Provide a reasonable title for the new lot */
id = kvp_frame_get_gint64 (xaccAccountGetSlots (acc), "/lot-mgmt/next-id");
snprintf (buff, 200, ("%s %" G_GINT64_FORMAT), _("Lot"), 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.
*/
gboolean
xaccSplitAssign (Split *split)
{
Account *acc;
gboolean splits_split_up = FALSE;
GNCLot *lot;
GNCPolicy *pcy;
if (!split) return FALSE;
/* If this split already belongs to a lot or the account doesn't
* have lots, we are done.
*/
if (split->lot) return FALSE;
acc = split->acc;
if (!xaccAccountHasTrades (acc))
return FALSE;
ENTER ("(split=%p)", split);
pcy = acc->policy;
xaccAccountBeginEdit (acc);
/* If we are here, this split does not belong to any lot.
* 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.
*/
while (split)
{
PINFO ("have split %p amount=%s", split,
gnc_num_dbg_to_string (split->amount));
split->gains |= GAINS_STATUS_VDIRTY;
lot = pcy->PolicyGetLot (pcy, split);
if (!lot)
{
lot = MakeDefaultLot (acc);
PINFO ("start new lot (%s)", gnc_lot_get_title(lot));
}
split = xaccSplitAssignToLot (split, lot);
if (split) splits_split_up = TRUE;
}
xaccAccountCommitEdit (acc);
LEAVE (" split_up=%d", splits_split_up);
return splits_split_up;
}
/* ============================================================== */
Split *
xaccSplitGetCapGainsSplit (const Split *split)
{
KvpValue *val;
GUID *gains_guid;
Split *gains_split;
if (!split) return NULL;
val = kvp_frame_get_slot (split->inst.kvp_data, "gains-split");
if (!val) return NULL;
gains_guid = kvp_value_get_guid (val);
if (!gains_guid) return NULL;
/* Both splits will be in the same collection, so seearch there. */
gains_split = (Split*) qof_collection_lookup_entity (split->inst.entity.collection, gains_guid);
PINFO ("split=%p has gains-split=%p", split, gains_split);
return gains_split;
}
/* ============================================================== */
void
xaccSplitComputeCapGains(Split *split, Account *gain_acc)
{
SplitList *node;
GNCLot *lot;
GNCPolicy *pcy;
gnc_commodity *currency = NULL;
gnc_numeric zero = gnc_numeric_zero();
gnc_numeric value = zero;
gnc_numeric frac;
gnc_numeric opening_amount, opening_value;
gnc_numeric lot_amount, lot_value;
gnc_commodity *opening_currency;
if (!split) return;
lot = split->lot;
if (!lot) return;
pcy = lot->account->policy;
currency = split->parent->common_currency;
ENTER ("(split=%p gains=%p status=0x%x lot=%s)", split,
split->gains_split, split->gains,
kvp_frame_get_string (gnc_lot_get_slots (lot), "/title"));
/* Make sure the status flags and pointers are initialized */
if (GAINS_STATUS_UNKNOWN == split->gains) xaccSplitDetermineGainStatus(split);
/* Not possible to have gains if the transaction currency and
* account commodity are identical. */
if (gnc_commodity_equal (currency,
xaccAccountGetCommodity(split->acc)))
{
LEAVE ("Currency transfer, gains not possible, returning.");
return;
}
if (pcy->PolicyIsOpeningSplit (pcy, lot, split))
{
#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 (xaccSplitGetCapGainsSplit (split))
{
Split *gains_split = xaccSplitGetCapGainsSplit(split);
Transaction *trans = gains_split->parent;
PERR ("Opening Split must not have cap gains!!\n");
xaccTransBeginEdit (trans);
xaccTransDestroy (trans);
xaccTransCommitEdit (trans);
}
#endif
LEAVE ("Lot opening split, returning.");
return;
}
if (safe_strcmp ("stock-split", xaccSplitGetType (split)) == 0)
{
LEAVE ("Stock split split, returning.");
return;
}
if (GAINS_STATUS_GAINS & split->gains)
{
Split *s;
PINFO ("split is a gains recording split, switch over");
/* If this is the split that records the gains, then work with
* the split that generates the gains.
*/
/* split = xaccSplitGetCapGainsSplit (split); */
s = split->gains_split;
/* This should never be NULL, and if it is, and its matching
* parent can't be found, then its a bug, and we should be
* discarding this split. But ... for now .. return.
* XXX move appropriate actions to a 'scrub' routine'
*/
if (!s)
{
PERR ("Bad gains-split pointer! .. trying to recover.");
split->gains_split = xaccSplitGetCapGainsSplit (split);
s = split->gains_split;
if (!s) return;
#if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
xaccTransDestroy (trans);
#endif
}
split = s;
}
/* Note: if the value of the 'opening' split(s) has changed,
* then the cap gains are changed. So we need to check not
* only if this split is dirty, but also the lot-opening splits. */
for (node=lot->splits; node; node=node->next)
{
Split *s = node->data;
if (pcy->PolicyIsOpeningSplit(pcy,lot,s))
{
if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus (s);
if (s->gains & GAINS_STATUS_VDIRTY)
{
/* Force a recompute to occur */
split->gains |= GAINS_STATUS_VDIRTY;
break;
}
}
}
/* If it doesn't look like this split is 'dirty', then there's
* nothing to do. Just return. */
if ((FALSE == (split->gains & GAINS_STATUS_A_VDIRTY)) &&
(split->gains_split) &&
(FALSE == (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
{
LEAVE ("split not dirty, returning");
return;
}
/* Yow! If amount is zero, there's nothing to do! Amount-zero splits
* may exist if users attempted to manually record gains. */
if (gnc_numeric_zero_p (split->amount)) return;
/* If we got to here, then the split or something related is
* 'dirty' and the gains really do need to be recomputed.
* So start working things. */
pcy->PolicyGetLotOpening (pcy, lot, &opening_amount, &opening_value,
&opening_currency);
/* Check to make sure the lot-opening currency and this split
* use the same 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
* an error. Just punt, silently.
*/
LEAVE ("Can't compute gains, mismatched commodities!");
return;
}
/* Opening amount should be larger (or equal) to current split,
* and it should be of the opposite sign.
* XXX This should really be a part of a scrub routine that
* cleans up the lot, before we get at it!
*/
if (0 > gnc_numeric_compare (gnc_numeric_abs(opening_amount),
gnc_numeric_abs(split->amount)))
{
GList *n;
for (n=lot->splits; n; n=n->next)
{
Split *s = n->data;
PINFO ("split amt=%s", gnc_num_dbg_to_string(s->amount));
}
PERR ("Malformed Lot \"%s\"! (too thin!) "
"opening amt=%s split amt=%s baln=%s",
gnc_lot_get_title (lot),
gnc_num_dbg_to_string (opening_amount),
gnc_num_dbg_to_string (split->amount),
gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
return;
}
if ( (gnc_numeric_negative_p(opening_amount) ||
gnc_numeric_positive_p(split->amount)) &&
(gnc_numeric_positive_p(opening_amount) ||
gnc_numeric_negative_p(split->amount)))
{
GList *n;
for (n=lot->splits; n; n=n->next)
{
Split *s = n->data;
PINFO ("split amt=%s", gnc_num_dbg_to_string(s->amount));
}
PERR ("Malformed Lot \"%s\"! (too fat!) "
"opening amt=%s split amt=%s baln=%s",
gnc_lot_get_title (lot),
gnc_num_dbg_to_string (opening_amount),
gnc_num_dbg_to_string (split->amount),
gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
return;
}
/* The cap gains is the difference between the basis prior to the
* current split, and the current split, pro-rated for an equal
* amount of shares.
* i.e. purchase_price = lot_value / lot_amount
* cost_basis = purchase_price * current_split_amount
* cap_gain = current_split_value - cost_basis
*/
gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
/* Fraction of the lot that this split represents: */
frac = gnc_numeric_div (split->amount, lot_amount,
GNC_DENOM_AUTO,
GNC_HOW_DENOM_REDUCE);
/* Basis for this split: */
value = gnc_numeric_mul (frac, lot_value,
gnc_numeric_denom(opening_value),
GNC_HOW_DENOM_EXACT|GNC_HOW_RND_ROUND);
/* Capital gain for this split: */
value = gnc_numeric_sub (value, split->value,
GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
PINFO ("Open amt=%s val=%s; split amt=%s val=%s; gains=%s\n",
gnc_num_dbg_to_string (opening_amount),
gnc_num_dbg_to_string (opening_value),
gnc_num_dbg_to_string (split->amount),
gnc_num_dbg_to_string (split->value),
gnc_num_dbg_to_string (value));
if (gnc_numeric_check (value))
{
PERR ("Numeric overflow during gains calculation\n"
"Acct=%s Txn=%s\n"
"\tOpen amt=%s val=%s\n\tsplit amt=%s val=%s\n\tgains=%s\n",
xaccAccountGetName(split->acc),
xaccTransGetDescription(split->parent),
gnc_num_dbg_to_string (opening_amount),
gnc_num_dbg_to_string (opening_value),
gnc_num_dbg_to_string (split->amount),
gnc_num_dbg_to_string (split->value),
gnc_num_dbg_to_string (value));
return;
}
/* Are the cap gains zero? If not, add a balancing transaction.
* As per design doc lots.txt: the transaction has two splits,
* with equal & opposite values. The amt of one iz zero (so as
* 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_zero_p (value))
{
Transaction *trans;
Split *lot_split, *gain_split;
Timespec ts;
/* See if there already is an associated gains transaction.
* If there is, adjust its value as appropriate. Else, create
* a new gains transaction.
*/
/* lot_split = xaccSplitGetCapGainsSplit (split); */
lot_split = split->gains_split;
if (NULL == lot_split)
{
Account *lot_acc = lot->account;
QofBook *book = lot_acc->inst.book;
lot_split = xaccMallocSplit (book);
gain_split = xaccMallocSplit (book);
/* Check to make sure the gains account currency matches. */
if ((NULL == gain_acc) ||
(FALSE == gnc_commodity_equiv (currency,
xaccAccountGetCommodity(gain_acc))))
{
gain_acc = GetOrMakeGainAcct (lot_acc, currency);
}
xaccAccountBeginEdit (gain_acc);
xaccAccountInsertSplit (gain_acc, gain_split);
xaccAccountCommitEdit (gain_acc);
xaccAccountBeginEdit (lot_acc);
xaccAccountInsertSplit (lot_acc, lot_split);
xaccAccountCommitEdit (lot_acc);
trans = xaccMallocTransaction (book);
xaccTransBeginEdit (trans);
xaccTransSetCurrency (trans, currency);
xaccTransSetDescription (trans, _("Realized Gain/Loss"));
xaccTransAppendSplit (trans, lot_split);
xaccTransAppendSplit (trans, gain_split);
xaccSplitSetMemo (lot_split, _("Realized Gain/Loss"));
xaccSplitSetMemo (gain_split, _("Realized Gain/Loss"));
/* For the new transaction, install KVP markup indicating
* that this is the gains transaction that corresponds
* to the gains source.
*/
kvp_frame_set_guid (split->inst.kvp_data, "gains-split",
xaccSplitGetGUID (lot_split));
kvp_frame_set_guid (lot_split->inst.kvp_data, "gains-source",
xaccSplitGetGUID (split));
}
else
{
trans = lot_split->parent;
gain_split = xaccSplitGetOtherSplit (lot_split);
xaccTransBeginEdit (trans);
/* Make sure the existing gains trans has the correct currency,
* just in case someone screwed with it! */
if (FALSE == gnc_commodity_equiv(currency,trans->common_currency))
{
PWARN ("Resetting the transaction currency!");
xaccTransSetCurrency (trans, currency);
}
}
/* Common to both */
ts = xaccTransRetDatePostedTS (split->parent);
xaccTransSetDatePostedTS (trans, &ts);
xaccTransSetDateEnteredSecs (trans, time(0));
xaccSplitSetAmount (lot_split, zero);
xaccSplitSetValue (lot_split, value);
gnc_lot_add_split (lot, lot_split);
value = gnc_numeric_neg (value);
xaccSplitSetAmount (gain_split, value);
xaccSplitSetValue (gain_split, value);
/* Some short-cuts to help avoid the above kvp lookup. */
split->gains = GAINS_STATUS_CLEAN;
split->gains_split = lot_split;
lot_split->gains = GAINS_STATUS_GAINS;
lot_split->gains_split = split;
gain_split->gains = GAINS_STATUS_GAINS;
gain_split->gains_split = split;
xaccTransCommitEdit (trans);
}
LEAVE ("(lot=%s)", gnc_lot_get_title(lot));
}
/* ============================================================== */
gnc_numeric
xaccSplitGetCapGains(Split * split)
{
if (!split) return gnc_numeric_zero();
ENTER("(split=%p)", split);
if (GAINS_STATUS_UNKNOWN == split->gains) xaccSplitDetermineGainStatus(split);
if ((split->gains & GAINS_STATUS_A_VDIRTY) ||
(split->gains_split && (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
{
xaccSplitComputeCapGains (split, NULL);
}
/* If this is the source split, get the gains from the one
* that records the gains. If this already is the gains split,
* its a no-op. */
if (!(GAINS_STATUS_GAINS & split->gains))
{
/* split = xaccSplitGetCapGainsSplit (split); */
split = split->gains_split;
}
LEAVE("(split=%p)", split);
if (!split) return gnc_numeric_zero();
return split->value;
}
/* ============================================================== */
void
xaccLotComputeCapGains (GNCLot *lot, Account *gain_acc)
{
SplitList *node;
GNCPolicy *pcy;
gboolean is_dirty = FALSE;
/* Note: if the value of the 'opening' split(s) has changed,
* then the cap gains are changed. To capture this, we need
* to mark all splits dirty if the opening splits are dirty. */
ENTER("(lot=%p)", lot);
pcy = lot->account->policy;
for (node=lot->splits; node; node=node->next)
{
Split *s = node->data;
if (pcy->PolicyIsOpeningSplit(pcy,lot,s))
{
if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus (s);
if (s->gains & GAINS_STATUS_VDIRTY)
{
is_dirty = TRUE;
s->gains &= ~GAINS_STATUS_VDIRTY;
}
}
}
if (is_dirty)
{
for (node=lot->splits; node; node=node->next)
{
Split *s = node->data;
s->gains |= GAINS_STATUS_VDIRTY;
}
}
for (node=lot->splits; node; node=node->next)
{
Split *s = node->data;
xaccSplitComputeCapGains (s, gain_acc);
}
LEAVE("(lot=%p)", lot);
}
/* ============================================================== */
/** The xaccScrubGainsDate() routine is used to keep the posted date
* of gains splis in sync with the posted date of the transaction
* that caused the gains.
*
* The posted date is kept in sync using a lazy-evaluation scheme.
* If xaccTransactionSetDatePosted() is called, the date change is
* accepted, and the split is marked date-dirty. If the posted date
* is queried for (using GetDatePosted()), then the transaction is
* evaluated. If its a gains-transaction, then it's date is copied
* from the source transaction that created the gains.
*/
static void
xaccScrubGainsDate (Transaction *trans)
{
SplitList *node;
Timespec ts = {0,0};
gboolean do_set;
restart_search:
do_set = FALSE;
for (node = trans->splits; node; node=node->next)
{
Split *s = node->data;
if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus(s);
if ((GAINS_STATUS_GAINS & s->gains) &&
s->gains_split &&
((s->gains_split->gains & GAINS_STATUS_DATE_DIRTY) ||
(s->gains & GAINS_STATUS_DATE_DIRTY)))
{
Transaction *source_trans = s->gains_split->parent;
ts = source_trans->date_posted;
do_set = TRUE;
s->gains &= ~GAINS_STATUS_DATE_DIRTY;
s->gains_split->gains &= ~GAINS_STATUS_DATE_DIRTY;
break;
}
}
if (do_set)
{
xaccTransBeginEdit (trans);
xaccTransSetDatePostedTS(trans, &ts);
xaccTransCommitEdit (trans);
for (node = trans->splits; node; node=node->next)
{
Split *s = node->data;
s->gains &= ~GAINS_STATUS_DATE_DIRTY;
}
goto restart_search;
}
}
/* ============================================================== */
void
xaccTransScrubGains (Transaction *trans, Account *gain_acc)
{
SplitList *node;
ENTER("(trans=%p)", trans);
/* Lock down posted date, its to be synced to the posted date
* for the source of the cap gains. */
xaccScrubGainsDate(trans);
/* Fix up the split amount */
restart:
for (node=trans->splits; node; node=node->next)
{
Split *split = node->data;
if (GAINS_STATUS_UNKNOWN == split->gains)
{
xaccSplitDetermineGainStatus(split);
}
if (split->gains & GAINS_STATUS_ADIRTY)
{
gboolean altered = FALSE;
split->gains |= ~GAINS_STATUS_ADIRTY;
if (split->lot)
altered = xaccScrubLot (split->lot);
else
altered = xaccSplitAssign (split);
if (altered) goto restart;
}
}
/* Fix up gains split value */
for (node=trans->splits; node; node=node->next)
{
Split *split = node->data;
if ((split->gains & GAINS_STATUS_VDIRTY) ||
(split->gains_split &&
(split->gains_split->gains & GAINS_STATUS_VDIRTY)))
{
xaccSplitComputeCapGains (split, gain_acc);
}
}
LEAVE("(trans=%p)", trans);
}
/* =========================== END OF FILE ======================= */