mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-25 10:20:18 -06:00
977 lines
34 KiB
C++
977 lines
34 KiB
C++
/********************************************************************\
|
|
* 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, that 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.hpp"
|
|
#include "AccountP.hpp"
|
|
#include "Scrub2.h"
|
|
#include "Scrub3.h"
|
|
#include "Transaction.h"
|
|
#include "TransactionP.hpp"
|
|
#include "cap-gains.h"
|
|
#include "gnc-engine.h"
|
|
#include "engine-helpers.h"
|
|
#include "gnc-lot.h"
|
|
#include "policy.h"
|
|
#include "policy-p.h"
|
|
|
|
static QofLogModule log_module = GNC_MOD_LOT;
|
|
|
|
|
|
/* ============================================================== */
|
|
|
|
gboolean
|
|
xaccAccountHasTrades (const Account *acc)
|
|
{
|
|
gnc_commodity *acc_comm;
|
|
|
|
if (!acc) return FALSE;
|
|
|
|
if (xaccAccountIsPriced (acc))
|
|
return TRUE;
|
|
|
|
acc_comm = xaccAccountGetCommodity(acc);
|
|
|
|
for (auto s : xaccAccountGetSplits (acc))
|
|
{
|
|
Transaction *t = s->parent;
|
|
if (s->gains == GAINS_STATUS_GAINS) continue;
|
|
if (acc_comm != t->common_currency) return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
struct FindLot
|
|
{
|
|
GNCLot *lot;
|
|
gnc_commodity *currency;
|
|
time64 time;
|
|
int (*numeric_pred)(gnc_numeric);
|
|
gboolean (*date_pred)(time64 e, time64 tr);
|
|
};
|
|
|
|
static gboolean
|
|
earliest_pred (time64 earl, time64 tran)
|
|
{
|
|
return earl > tran;
|
|
}
|
|
|
|
static gboolean
|
|
latest_pred (time64 earl, time64 tran)
|
|
{
|
|
return earl < tran;
|
|
}
|
|
|
|
static gpointer
|
|
finder_helper (GNCLot *lot, gpointer user_data)
|
|
{
|
|
auto els = static_cast<FindLot*>(user_data);
|
|
Split *s;
|
|
Transaction *trans;
|
|
gnc_numeric bal;
|
|
gboolean opening_is_positive, bal_is_positive;
|
|
time64 posted = 0;
|
|
|
|
if (gnc_lot_is_closed (lot)) return nullptr;
|
|
|
|
s = gnc_lot_get_earliest_split (lot);
|
|
if (s == nullptr) return nullptr;
|
|
|
|
/* We want a lot whose balance is of the correct sign. All splits
|
|
in a lot must be the opposite sign of the opening split. We also
|
|
want to ignore lots that are overfull, i.e., where the balance in
|
|
the lot is of opposite sign to the opening split in the lot. */
|
|
if (0 == (els->numeric_pred) (s->amount)) return nullptr;
|
|
bal = gnc_lot_get_balance (lot);
|
|
opening_is_positive = gnc_numeric_positive_p (s->amount);
|
|
bal_is_positive = gnc_numeric_positive_p (bal);
|
|
if (opening_is_positive != bal_is_positive) return nullptr;
|
|
|
|
trans = s->parent;
|
|
if (els->currency &&
|
|
(FALSE == gnc_commodity_equiv (els->currency,
|
|
trans->common_currency)))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
posted = trans->date_posted;
|
|
if (els->date_pred (els->time, posted))
|
|
{
|
|
els->time = trans->date_posted;
|
|
els->lot = lot;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static inline GNCLot *
|
|
xaccAccountFindOpenLot (Account *acc, gnc_numeric sign,
|
|
gnc_commodity *currency,
|
|
gint64 guess,
|
|
gboolean (*date_pred)(time64, time64))
|
|
{
|
|
FindLot es;
|
|
|
|
es.lot = nullptr;
|
|
es.currency = currency;
|
|
es.time = guess;
|
|
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_MAXINT64, 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,
|
|
G_MININT64, latest_pred);
|
|
LEAVE ("found lot=%p %s", lot, gnc_lot_get_title (lot));
|
|
return lot;
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
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 nullptr;
|
|
|
|
/* If this split already belongs to a lot, we are done. */
|
|
if (split->lot) return nullptr;
|
|
|
|
/* Anomalous 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 nullptr;
|
|
|
|
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 nullptr;
|
|
}
|
|
|
|
/* 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 nullptr;
|
|
}
|
|
|
|
/* 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 the 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 nullptr;
|
|
}
|
|
|
|
/* 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 nullptr;
|
|
}
|
|
|
|
/* If we are here, then (cmp == +1 iff (amt > baln)) and we need
|
|
* to split up the split into pieces. Do it. */
|
|
{
|
|
time64 now = gnc_time (nullptr), 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;
|
|
|
|
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);
|
|
g_return_val_if_fail(gnc_numeric_check(amt_b) == GNC_ERROR_OK, nullptr);
|
|
|
|
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_HALF_UP | 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(amt_a) || gnc_numeric_zero_p(amt_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) );
|
|
|
|
g_return_val_if_fail (!gnc_numeric_zero_p (amt_a), nullptr);
|
|
g_return_val_if_fail (!gnc_numeric_check (val_a), nullptr);
|
|
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 (qof_instance_get_book(acc));
|
|
|
|
/* Copy most of the split attributes */
|
|
xaccSplitSetMemo (new_split, xaccSplitGetMemo (split));
|
|
/* Set split-action with gnc_set_num_action which is the same as
|
|
* xaccSplitSetAction with these arguments; use gnc_get_num_action to get
|
|
* split-action which is the same as xaccSplitGetAction */
|
|
gnc_set_num_action(nullptr, new_split, nullptr, gnc_get_num_action(nullptr, split));
|
|
xaccSplitSetReconcile (new_split, xaccSplitGetReconcile (split));
|
|
time = xaccSplitGetDateReconciled (split);
|
|
xaccSplitSetDateReconciledSecs (new_split, time);
|
|
|
|
/* Set the lot-split and peer_guid properties on the two
|
|
* splits to indicate that they're linked.
|
|
*/
|
|
xaccSplitAddPeerSplit(split, new_split, now);
|
|
xaccSplitAddPeerSplit(new_split, split, now);
|
|
xaccAccountInsertSplit (acc, new_split);
|
|
xaccTransAppendSplit (trans, new_split);
|
|
/* Set the amount and value after the split is in the transaction
|
|
so it can find the correct denominator to use. Otherwise it
|
|
uses 100000 which may cause an overflow in some of the tests
|
|
in test-period */
|
|
xaccSplitSetAmount (new_split, amt_b);
|
|
xaccSplitSetValue (new_split, val_b);
|
|
xaccTransCommitEdit (trans);
|
|
xaccAccountCommitEdit (acc);
|
|
return new_split;
|
|
}
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
/* 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;
|
|
g_return_val_if_fail (split->gains == GAINS_STATUS_UNKNOWN ||
|
|
(split->gains & GAINS_STATUS_GAINS) == FALSE, FALSE);
|
|
acc = split->acc;
|
|
if (!xaccAccountHasTrades (acc))
|
|
return FALSE;
|
|
if (gnc_numeric_zero_p (split->amount))
|
|
return FALSE;
|
|
|
|
ENTER ("(split=%p)", split);
|
|
|
|
pcy = gnc_account_get_policy(acc);
|
|
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 = gnc_lot_make_default (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)
|
|
{
|
|
GncGUID *gains_guid;
|
|
Split *gains_split;
|
|
|
|
if (!split) return nullptr;
|
|
|
|
qof_instance_get (QOF_INSTANCE (split),
|
|
"gains-split", &gains_guid,
|
|
nullptr);
|
|
if (!gains_guid) return nullptr;
|
|
|
|
/* Both splits will be in the same collection, so search there. */
|
|
gains_split = (Split*) qof_collection_lookup_entity (
|
|
qof_instance_get_collection(split), gains_guid);
|
|
PINFO ("split=%p has gains-split=%p", split, gains_split);
|
|
guid_free (gains_guid);
|
|
return gains_split;
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
Split *
|
|
xaccSplitGetGainsSourceSplit (const Split *split)
|
|
{
|
|
GncGUID *source_guid;
|
|
Split *source_split;
|
|
|
|
if (!split) return nullptr;
|
|
|
|
qof_instance_get (QOF_INSTANCE (split),
|
|
"gains-source", &source_guid,
|
|
nullptr);
|
|
if (!source_guid) return nullptr;
|
|
|
|
/* Both splits will be in the same collection, so search there. */
|
|
source_split = (Split*) qof_collection_lookup_entity(
|
|
qof_instance_get_collection(split), source_guid);
|
|
PINFO ("split=%p has source-split=%p", split, source_split);
|
|
guid_free (source_guid);
|
|
return source_split;
|
|
}
|
|
|
|
/* ============================================================== */
|
|
|
|
void
|
|
xaccSplitComputeCapGains(Split *split, Account *gain_acc)
|
|
{
|
|
SplitList *node;
|
|
GNCLot *lot;
|
|
GNCPolicy *pcy;
|
|
gnc_commodity *currency = nullptr;
|
|
gnc_numeric zero = gnc_numeric_zero();
|
|
gnc_numeric value;
|
|
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 = gnc_account_get_policy(gnc_lot_get_account(lot));
|
|
currency = split->parent->common_currency;
|
|
|
|
ENTER ("(split=%p gains=%p status=0x%x lot=%s)", split,
|
|
split->gains_split, split->gains, gnc_lot_get_title(lot));
|
|
|
|
/* Make sure the status flags and pointers are initialized */
|
|
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 (g_strcmp0 ("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 nullptr, 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 = gnc_lot_get_split_list(lot); node; node = node->next)
|
|
{
|
|
Split *s = GNC_SPLIT(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. */
|
|
|
|
/* Get the amount and value in this lot at the time of this transaction. */
|
|
gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
|
|
|
|
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(lot_amount),
|
|
gnc_numeric_abs(split->amount)))
|
|
{
|
|
GList *n;
|
|
for (n = gnc_lot_get_split_list(lot); n; n = n->next)
|
|
{
|
|
Split *s = GNC_SPLIT(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 (lot_amount),
|
|
gnc_num_dbg_to_string (split->amount),
|
|
gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
|
|
return;
|
|
}
|
|
if ( (gnc_numeric_negative_p(lot_amount) ||
|
|
gnc_numeric_positive_p(split->amount)) &&
|
|
(gnc_numeric_positive_p(lot_amount) ||
|
|
gnc_numeric_negative_p(split->amount)))
|
|
{
|
|
GList *n;
|
|
for (n = gnc_lot_get_split_list(lot); n; n = n->next)
|
|
{
|
|
Split *s = GNC_SPLIT(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 (lot_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
|
|
*/
|
|
/* 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_HALF_UP);
|
|
/* 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 (lot_amount),
|
|
gnc_num_dbg_to_string (lot_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 (lot_amount),
|
|
gnc_num_dbg_to_string (lot_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;
|
|
gboolean new_gain_split;
|
|
gnc_numeric negvalue = gnc_numeric_neg (value);
|
|
|
|
/* 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 (nullptr == lot_split)
|
|
{
|
|
Account *lot_acc = gnc_lot_get_account(lot);
|
|
QofBook *book = qof_instance_get_book(lot_acc);
|
|
Transaction *base_txn = xaccSplitGetParent (split);
|
|
|
|
new_gain_split = TRUE;
|
|
|
|
lot_split = xaccMallocSplit (book);
|
|
gain_split = xaccMallocSplit (book);
|
|
|
|
/* Check to make sure the gains account currency matches. */
|
|
if ((nullptr == gain_acc) ||
|
|
(FALSE == gnc_commodity_equiv (currency,
|
|
xaccAccountGetCommodity(gain_acc))))
|
|
{
|
|
gain_acc = xaccAccountGainsAccount (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, set the split properties indicating
|
|
* that this is the gains transaction that corresponds
|
|
* to the gains source.
|
|
*/
|
|
xaccTransBeginEdit (base_txn);
|
|
qof_instance_set (QOF_INSTANCE (split),
|
|
"gains-split", xaccSplitGetGUID (lot_split),
|
|
nullptr);
|
|
xaccTransCommitEdit (base_txn);
|
|
qof_instance_set (QOF_INSTANCE (lot_split),
|
|
"gains-source", xaccSplitGetGUID (split),
|
|
nullptr);
|
|
|
|
}
|
|
else
|
|
{
|
|
trans = lot_split->parent;
|
|
gain_split = xaccSplitGetOtherSplit (lot_split);
|
|
|
|
/* If the gains transaction has been edited so that it no longer has
|
|
just two splits, ignore it and assume it's still correct. */
|
|
if (!gain_split)
|
|
{
|
|
new_gain_split = FALSE;
|
|
}
|
|
/* If the gain is already recorded correctly do nothing. This is
|
|
* more than just an optimization since this may be called during
|
|
* gnc_book_partition_txn and depending on the order in which things
|
|
* happen some splits may be in the wrong book at that time. */
|
|
else if (split->gains_split == lot_split &&
|
|
lot_split->gains_split == split &&
|
|
gain_split->gains_split == split &&
|
|
gnc_numeric_equal (xaccSplitGetValue (lot_split), value) &&
|
|
gnc_numeric_zero_p (xaccSplitGetAmount (lot_split)) &&
|
|
gnc_numeric_equal (xaccSplitGetValue (gain_split), negvalue) &&
|
|
gnc_numeric_equal (xaccSplitGetAmount (gain_split), negvalue))
|
|
{
|
|
new_gain_split = FALSE;
|
|
}
|
|
else
|
|
{
|
|
new_gain_split = TRUE;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (new_gain_split)
|
|
{
|
|
/* Common to both */
|
|
time64 time = xaccTransRetDatePosted (split->parent);
|
|
xaccTransSetDatePostedSecs (trans, time);
|
|
xaccTransSetDateEnteredSecs (trans, gnc_time (nullptr));
|
|
|
|
xaccSplitSetAmount (lot_split, zero);
|
|
xaccSplitSetValue (lot_split, value);
|
|
|
|
xaccSplitSetAmount (gain_split, negvalue);
|
|
xaccSplitSetValue (gain_split, negvalue);
|
|
|
|
/* Some short-cuts to help avoid the above property 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;
|
|
|
|
/* Do this last since it may generate an event that will call us
|
|
recursively. */
|
|
gnc_lot_add_split (lot, lot_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, nullptr);
|
|
}
|
|
|
|
/* 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 = gnc_account_get_policy(gnc_lot_get_account(lot));
|
|
for (node = gnc_lot_get_split_list(lot); node; node = node->next)
|
|
{
|
|
Split *s = GNC_SPLIT(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 = gnc_lot_get_split_list(lot); node; node = node->next)
|
|
{
|
|
Split *s = GNC_SPLIT(node->data);
|
|
s->gains |= GAINS_STATUS_VDIRTY;
|
|
}
|
|
}
|
|
|
|
for (node = gnc_lot_get_split_list(lot); node; node = node->next)
|
|
{
|
|
Split *s = GNC_SPLIT(node->data);
|
|
xaccSplitComputeCapGains (s, gain_acc);
|
|
}
|
|
LEAVE("(lot=%p)", lot);
|
|
}
|
|
|
|
/* =========================== END OF FILE ======================= */
|