Merge Sherlock's 'stock-splits' into stable.

This commit is contained in:
John Ralls committed 2026-07-14 14:24:53 -07:00
commit b766c126ee
18 files changed
+694 -389

No files matched your search

File diff suppressed because it is too large. Load diff
+29 -15
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkDialog" id="lot_viewer_dialog">
@@ -107,6 +107,22 @@
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="show_adjusted_amounts_checkbutton">
<property name="label" translatable="yes">Show stock split adjusted amounts</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkPaned" id="lot_vpaned">
<property name="visible">True</property>
@@ -191,13 +207,13 @@
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">_Title</property>
<style>
<class name="gnc-class-strong"/>
</style>
<property name="use-markup">True</property>
<property name="use-underline">True</property>
<property name="justify">center</property>
<property name="mnemonic-widget">lot_title_entry</property>
<style>
<class name="gnc-class-strong"/>
</style>
</object>
<packing>
<property name="left-attach">0</property>
@@ -224,12 +240,12 @@
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">_Lots in This Account</property>
<style>
<class name="gnc-class-strong"/>
</style>
<property name="use-markup">True</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">lot_view</property>
<style>
<class name="gnc-class-strong"/>
</style>
</object>
<packing>
<property name="left-attach">0</property>
@@ -242,8 +258,6 @@
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar-policy">always</property>
<property name="vscrollbar-policy">always</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="lot_view">
@@ -308,12 +322,12 @@
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Splits _free</property>
<style>
<class name="gnc-class-strong"/>
</style>
<property name="use-markup">True</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">split_free_view</property>
<style>
<class name="gnc-class-strong"/>
</style>
</object>
<packing>
<property name="left-attach">0</property>
@@ -366,12 +380,12 @@
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Splits _in lot</property>
<style>
<class name="gnc-class-strong"/>
</style>
<property name="use-markup">True</property>
<property name="use-underline">True</property>
<property name="mnemonic-widget">split_in_lot_view</property>
<style>
<class name="gnc-class-strong"/>
</style>
</object>
<packing>
<property name="left-attach">1</property>
@@ -82,6 +82,12 @@ gnc_numeric gnc_float_split_get_value (const FloatingSplit *fs)
return fs->m_value;
}
gboolean gnc_float_split_get_stock_split (const FloatingSplit *fs)
{
g_return_val_if_fail (fs, FALSE);
return fs->m_stock_split;
}
/* modifiers */
void gnc_float_split_set_split(FloatingSplit *fs, Split *split)
@@ -129,17 +135,21 @@ void gnc_float_split_set_reconcile_date (FloatingSplit *fs, time64 reconcile_dat
void gnc_float_split_set_amount (FloatingSplit *fs, const gnc_numeric amount)
{
g_return_if_fail (fs);
fs->m_amount = amount;
}
void gnc_float_split_set_value (FloatingSplit *fs, const gnc_numeric value)
{
g_return_if_fail (fs);
fs->m_value = value;
}
void gnc_float_split_set_stock_split (FloatingSplit *fs, gboolean stock_split)
{
g_return_if_fail (fs);
fs->m_stock_split = stock_split;
}
static void
gnc_float_template_sx_data_free (FloatingTemplateSxData *ftsd)
{
@@ -247,6 +257,7 @@ FloatingSplit *gnc_split_to_float_split (Split *split, gboolean is_template)
fs->m_reconcile_date = xaccSplitGetDateReconciled (split);
fs->m_amount = xaccSplitGetAmount (split);
fs->m_value = xaccSplitGetValue (split);
fs->m_stock_split = xaccSplitIsStockSplit (split);
if (is_template)
fs->m_template_sx_data = gnc_split_to_float_template_sx_data (split);
@@ -336,6 +347,8 @@ gnc_float_split_to_split (const FloatingSplit *fs, Split *split, Account *templa
{
xaccSplitSetAmount (split, fs->m_amount);
xaccSplitSetValue (split, fs->m_value);
if (fs->m_stock_split)
xaccSplitMakeStockSplit(split);
}
if (split_account)
@@ -55,6 +55,7 @@ typedef struct
char m_reconcile_state;
gnc_numeric m_value;
gnc_numeric m_amount;
gboolean m_stock_split;
FloatingTemplateSxData *m_template_sx_data;
@@ -83,6 +84,7 @@ char gnc_float_split_get_reconcile_state (const FloatingSplit *fs);
time64 gnc_float_split_get_reconcile_date (const FloatingSplit *fs);
gnc_numeric gnc_float_split_get_amount (const FloatingSplit *fs);
gnc_numeric gnc_float_split_get_value (const FloatingSplit *fs);
gboolean gnc_float_split_get_stock_split (const FloatingSplit *fs);
/* modifiers */
void gnc_float_split_set_split (FloatingSplit *fs, Split *split);
@@ -94,6 +96,7 @@ void gnc_float_split_set_reconcile_state (FloatingSplit *fs, char reconcile_stat
void gnc_float_split_set_reconcile_date (FloatingSplit *fs, time64 reconcile_date);
void gnc_float_split_set_amount (FloatingSplit *fs, gnc_numeric amount);
void gnc_float_split_set_value (FloatingSplit *fs, gnc_numeric value);
void gnc_float_split_set_stock_split (FloatingSplit *fs, gboolean stock_split);
FloatingSplit *gnc_split_to_float_split (Split *split, gboolean is_template);
void gnc_float_split_to_split (const FloatingSplit *fs, Split *split, Account *template_account);
@@ -1546,7 +1546,7 @@ gnc_split_register_get_mxfrm_entry (VirtualLocation virt_loc,
if (s)
name = g_strdup (SPLIT_TRANS_STR);
else if (g_strcmp0 ("stock-split", xaccSplitGetType (split)) == 0)
else if (xaccSplitIsStockSplit (split))
name = g_strdup (STOCK_SPLIT_STR);
else
name = g_strdup ("");
@@ -1588,7 +1588,7 @@ gnc_split_register_get_mxfrm_help (VirtualLocation virt_loc,
if (s)
help = _ ("This transaction has multiple splits; "
"press the Split button to see them all");
else if (g_strcmp0 ("stock-split", xaccSplitGetType (split)) == 0)
else if (xaccSplitIsStockSplit (split))
help = _ ("This transaction is a stock split; "
"press the Split button to see details");
else
@@ -2080,7 +2080,7 @@ gnc_split_register_get_debcred_io_flags (VirtualLocation virt_loc,
split = gnc_split_register_get_split (reg, virt_loc.vcell_loc);
if (g_strcmp0 ("stock-split", xaccSplitGetType (split)) == 0)
if (xaccSplitIsStockSplit (split))
return XACC_CELL_ALLOW_NONE;
return XACC_CELL_ALLOW_ALL;
+52 -25
View File
@@ -328,6 +328,7 @@ gnc_account_init(Account* acc)
priv->starting_cleared_balance = gnc_numeric_zero();
priv->starting_reconciled_balance = gnc_numeric_zero();
priv->balance_dirty = FALSE;
priv->has_stock_split = false;
new (&priv->children) AccountVec ();
new (&priv->splits) SplitsVec ();
@@ -1456,6 +1457,7 @@ xaccFreeAccount (Account *acc)
priv->commodity = nullptr;
priv->balance_dirty = FALSE;
priv->has_stock_split = false;
priv->sort_dirty = FALSE;
priv->splits.~SplitsVec();
priv->children.~AccountVec();
@@ -2137,30 +2139,26 @@ xaccAccountRemoveLot (Account *acc, GNCLot *lot)
void
xaccAccountInsertLot (Account *acc, GNCLot *lot)
{
AccountPrivate *priv, *opriv;
Account * old_acc = nullptr;
Account* lot_account;
/* errors */
g_return_if_fail(GNC_IS_ACCOUNT(acc));
g_return_if_fail(GNC_IS_LOT(lot));
/* optimizations */
lot_account = gnc_lot_get_account(lot);
if (lot_account == acc)
auto lot_acc = gnc_lot_get_account(lot);
if (lot_acc == acc)
return;
ENTER ("(acc=%p, lot=%p)", acc, lot);
/* pull it out of the old account */
if (lot_account)
if (lot_acc)
{
old_acc = lot_account;
opriv = GET_PRIVATE(old_acc);
opriv->lots = g_list_remove(opriv->lots, lot);
auto priv = GET_PRIVATE(lot_acc);
priv->lots = g_list_remove(priv->lots, lot);
qof_event_gen (&lot_acc->inst, QOF_EVENT_MODIFY, nullptr);
}
priv = GET_PRIVATE(acc);
auto priv = GET_PRIVATE(acc);
priv->lots = g_list_prepend(priv->lots, lot);
gnc_lot_set_account(lot, acc);
@@ -2276,32 +2274,52 @@ xaccAccountMoveAllSplits (Account *accfrom, Account *accto)
void
xaccAccountRecomputeBalance (Account * acc)
{
AccountPrivate *priv;
gnc_numeric balance;
gnc_numeric noclosing_balance;
gnc_numeric cleared_balance;
gnc_numeric reconciled_balance;
if (nullptr == acc) return;
priv = GET_PRIVATE(acc);
auto priv = GET_PRIVATE(acc);
if (qof_instance_get_editlevel(acc) > 0) return;
if (!priv->balance_dirty || priv->defer_bal_computation) return;
if (qof_instance_get_destroying(acc)) return;
if (qof_book_shutting_down(qof_instance_get_book(acc))) return;
balance = priv->starting_balance;
noclosing_balance = priv->starting_noclosing_balance;
cleared_balance = priv->starting_cleared_balance;
reconciled_balance = priv->starting_reconciled_balance;
auto balance = priv->starting_balance;
auto noclosing_balance = priv->starting_noclosing_balance;
auto cleared_balance = priv->starting_cleared_balance;
auto reconciled_balance = priv->starting_reconciled_balance;
auto has_stock_split = false;
PINFO ("acct=%s starting baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
priv->accountName, balance.num, balance.denom);
for (auto split : priv->splits)
{
gnc_numeric amt = xaccSplitGetAmount (split);
auto amt = xaccSplitGetAmount (split);
balance = gnc_numeric_add_fixed(balance, amt);
if (xaccSplitIsStockSplit(split))
{
if (gnc_numeric_zero_p(balance))
continue;
auto new_balance = gnc_numeric_add_fixed(balance, amt);
if (gnc_numeric_zero_p(new_balance))
continue;
auto ratio = gnc_numeric_div(new_balance, balance, GNC_DENOM_AUTO,
GNC_HOW_DENOM_REDUCE);
auto denom = xaccAccountGetCommoditySCU(acc);
for (auto psplit : priv->splits)
{
if (psplit == split)
break;
xaccSplitSetAdjustedAmount(psplit,
gnc_numeric_mul(xaccSplitGetAdjustedAmount(psplit), ratio, denom,
GNC_HOW_RND_ROUND_HALF_UP));
}
balance = new_balance;
has_stock_split = true;
}
else
{
split->adjusted_amount = amt;
balance = gnc_numeric_add_fixed(balance, amt);
}
if (NREC != split->reconciled)
{
@@ -2330,6 +2348,7 @@ xaccAccountRecomputeBalance (Account * acc)
priv->cleared_balance = cleared_balance;
priv->reconciled_balance = reconciled_balance;
priv->balance_dirty = FALSE;
priv->has_stock_split = has_stock_split;
}
/********************************************************************\
@@ -3484,6 +3503,13 @@ xaccAccountGetProjectedMinimumBalance (const Account *acc)
return minimum ? *minimum : gnc_numeric_zero();
}
gboolean
xaccAccountHasStockSplit (const Account *acc)
{
g_return_val_if_fail(GNC_IS_ACCOUNT(acc), false);
return GET_PRIVATE(acc)->has_stock_split;
}
/********************************************************************\
\********************************************************************/
@@ -3935,7 +3961,8 @@ xaccAccountGetSplitsSize (const Account *account)
return GNC_IS_ACCOUNT(account) ? GET_PRIVATE(account)->splits.size() : 0;
}
gboolean gnc_account_and_descendants_empty (Account *acc)
gboolean
gnc_account_and_descendants_empty (Account *acc)
{
g_return_val_if_fail (GNC_IS_ACCOUNT (acc), FALSE);
auto priv = GET_PRIVATE (acc);
+4
View File
@@ -648,6 +648,10 @@ typedef enum
gnc_numeric xaccAccountGetBalanceChangeForPeriod (
Account *acc, time64 date1, time64 date2, gboolean recurse);
/** Returns true if the account has a stock split,
* otherwise false. */
gboolean xaccAccountHasStockSplit (const Account *acc);
/** @} */
/** @name Account Children and Parents.
+1
View File
@@ -114,6 +114,7 @@ typedef struct AccountPrivate
gnc_numeric reconciled_balance;
gboolean balance_dirty; /* balances in splits incorrect */
gboolean has_stock_split; /* account includes a stock split */
std::vector<Split*> splits; /* list of split pointers */
GHashTable* splits_hash;
+22 -11
View File
@@ -68,6 +68,9 @@ restart_loop:
{
/* If already in lot, then no-op */
if (split->lot) continue;
/* Skip stock splits */
if (xaccSplitIsStockSplit(split)) continue;
/* Skip voided transactions */
if (gnc_numeric_zero_p (split->amount) &&
@@ -104,25 +107,33 @@ xaccLotFill (GNCLot *lot)
/* If balance already zero, we have nothing to do. */
if (gnc_lot_is_closed (lot))
{
LEAVE ("Lot Closed (lot=%s, acc=%s)", gnc_lot_get_title(lot),
xaccAccountGetName(acc));
return;
LEAVE ("Lot Closed (lot=%s, acc=%s)", gnc_lot_get_title(lot),
xaccAccountGetName(acc));
return;
}
split = pcy->PolicyGetSplit (pcy, lot);
if (!split)
{
LEAVE ("No Split (lot=%s, acc=%s)", gnc_lot_get_title(lot),
xaccAccountGetName(acc));
return; /* Handle the common case */
LEAVE ("No Split (lot=%s, acc=%s)", gnc_lot_get_title(lot),
xaccAccountGetName(acc));
return; /* Handle the common case */
}
/* Reject stock split transactions */
if (xaccSplitIsStockSplit(split))
{
LEAVE ("Stock split transaction (lot=%s, acc=%s)",
gnc_lot_get_title(lot), xaccAccountGetName(acc));
return;
}
/* Reject voided transactions */
if (gnc_numeric_zero_p(split->amount) &&
xaccTransGetVoidStatus(split->parent))
{
LEAVE ("Voided transaction (lot=%s, acc=%s)",
gnc_lot_get_title(lot), xaccAccountGetName(acc));
return;
LEAVE ("Voided transaction (lot=%s, acc=%s)",
gnc_lot_get_title(lot), xaccAccountGetName(acc));
return;
}
xaccAccountBeginEdit (acc);
@@ -178,8 +189,8 @@ xaccLotScrubDoubleBalance (GNCLot *lot)
/* We double-check only closed lots */
if (FALSE == gnc_lot_is_closed (lot))
{
LEAVE ("lot=%s is closed", gnc_lot_get_title(lot));
return;
LEAVE ("lot=%s is closed", gnc_lot_get_title(lot));
return;
}
for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
+60 -13
View File
@@ -128,6 +128,9 @@ gnc_split_init(Split* split)
split->reconciled_balance = gnc_numeric_zero();
split->noclosing_balance = gnc_numeric_zero();
split->adjusted_amount = gnc_numeric_zero();
split->split_type = nullptr;
split->gains = GAINS_STATUS_UNKNOWN;
split->gains_split = nullptr;
}
@@ -520,6 +523,9 @@ xaccSplitReinit(Split * split)
split->reconciled_balance = gnc_numeric_zero();
split->noclosing_balance = gnc_numeric_zero();
split->adjusted_amount = gnc_numeric_zero();
split->split_type = nullptr;
qof_instance_set_idata(split, 0);
split->gains = GAINS_STATUS_UNKNOWN;
@@ -604,6 +610,8 @@ xaccSplitCloneNoKvp (const Split *s)
split->cleared_balance = s->cleared_balance;
split->reconciled_balance = s->reconciled_balance;
split->noclosing_balance = s->noclosing_balance;
split->adjusted_amount = s->adjusted_amount;
split->split_type = s->split_type;
split->gains = GAINS_STATUS_UNKNOWN;
split->gains_split = nullptr;
@@ -689,6 +697,8 @@ xaccSplitDump (const Split *split, const char *tag)
printf(" RBalance: %s\n",
gnc_numeric_to_string(split->reconciled_balance));
printf(" NoClose: %s\n", gnc_numeric_to_string(split->noclosing_balance));
printf(" AdjAmt: %s\n", gnc_numeric_to_string(split->adjusted_amount));
printf(" Type: %s\n", split->split_type ? split->split_type : "(null)");
printf(" idata: %x\n", qof_instance_get_idata(split));
}
#endif
@@ -749,6 +759,13 @@ xaccFreeSplit (Split *split)
split->acc = nullptr;
split->orig_acc = nullptr;
split->balance = gnc_numeric_zero();
split->cleared_balance = gnc_numeric_zero();
split->reconciled_balance = gnc_numeric_zero();
split->noclosing_balance = gnc_numeric_zero();
split->adjusted_amount = gnc_numeric_zero();
split->split_type = nullptr;
split->date_reconciled = 0;
G_OBJECT_CLASS (QOF_INSTANCE_GET_CLASS (&split->inst))->dispose(G_OBJECT (split));
@@ -766,12 +783,11 @@ xaccFreeSplit (Split *split)
void mark_split (Split *s)
{
if (s->acc)
{
g_object_set(s->acc, "sort-dirty", TRUE, "balance-dirty", TRUE, nullptr);
}
/* set dirty flag on lot too. */
if (s->lot) gnc_lot_set_closed_unknown(s->lot);
if (s->lot)
gnc_lot_set_closed_unknown(s->lot);
}
/*
@@ -915,6 +931,9 @@ xaccSplitEqual(const Split *sa, const Split *sb,
if (!xaccSplitEqualCheckBal ("noclosing ", sa->noclosing_balance,
sb->noclosing_balance))
return FALSE;
if (!xaccSplitEqualCheckBal ("adjusted ", sa->adjusted_amount,
sb->adjusted_amount))
return FALSE;
}
if (!xaccTransEqual(sa->parent, sb->parent, check_guids, check_txn_splits,
@@ -1238,6 +1257,7 @@ xaccSplitSetAmount (Split *s, gnc_numeric amt)
}
else
s->amount = amt;
s->adjusted_amount = s->amount;
SET_GAINS_ADIRTY(s);
mark_split (s);
@@ -1316,6 +1336,21 @@ xaccSplitGetReconciledBalance (const Split *s)
return s ? s->reconciled_balance : gnc_numeric_zero();
}
gnc_numeric
xaccSplitGetAdjustedAmount (const Split *s)
{
return s ? s->adjusted_amount : gnc_numeric_zero();
}
void
xaccSplitSetAdjustedAmount (Split *s, gnc_numeric amt)
{
if (!s) return;
if (gnc_numeric_check (amt)) return;
s->adjusted_amount = amt;
}
void
xaccSplitSetBaseValue (Split *s, gnc_numeric value,
const gnc_commodity * base_currency)
@@ -2001,20 +2036,25 @@ xaccSplitGetBook (const Split *split)
}
const char *
xaccSplitGetType(const Split *s)
xaccSplitGetType(Split *s)
{
if (!s) return nullptr;
auto type{qof_instance_get_path_kvp<const char*> (QOF_INSTANCE(s), {"split-type"})};
if (!s->split_type)
{
auto type{qof_instance_get_path_kvp<const char*> (QOF_INSTANCE(s), {"split-type"})};
if (!type || !g_strcmp0 (*type, split_type_normal))
return split_type_normal;
if (!g_strcmp0 (*type, split_type_stock_split))
return split_type_stock_split;
PERR ("unexpected split-type %s, reset to normal.", *type);
return split_type_normal;
if (!type || !g_strcmp0 (*type, split_type_normal))
s->split_type = split_type_normal;
else if (!g_strcmp0 (*type, split_type_stock_split))
s->split_type = split_type_stock_split;
else
{
PERR ("unexpected split-type %s, reset to normal.", *type);
s->split_type = split_type_normal;
}
}
return s->split_type;
}
/* reconfigure a split to be a stock split - after this, you shouldn't
@@ -2025,6 +2065,7 @@ xaccSplitMakeStockSplit(Split *s)
xaccTransBeginEdit (s->parent);
s->value = gnc_numeric_zero();
s->split_type = split_type_stock_split;
qof_instance_set_path_kvp<const char*> (QOF_INSTANCE(s), g_strdup(split_type_stock_split),
{"split-type"});
SET_GAINS_VDIRTY(s);
@@ -2033,6 +2074,12 @@ xaccSplitMakeStockSplit(Split *s)
xaccTransCommitEdit(s->parent);
}
gboolean
xaccSplitIsStockSplit (Split *s)
{
return g_strcmp0(split_type_stock_split, xaccSplitGetType(s)) == 0;
}
void
xaccSplitAddPeerSplit (Split *split, const Split *other_split,
time64 timestamp)
+16 -8
View File
@@ -345,6 +345,16 @@ gnc_numeric xaccSplitGetClearedBalance (const Split *split);
*/
gnc_numeric xaccSplitGetReconciledBalance (const Split *split);
/** Sets the stock split adjusted amount of a split.
* @note The adjusted amount will be reset when the account is rebalanced.
*/
void xaccSplitSetAdjustedAmount (Split *split, gnc_numeric amount);
/** Returns the stock-split adjusted amount of the split in the account's commodity.
*/
gnc_numeric xaccSplitGetAdjustedAmount (const Split *split);
/** @} */
/** @name Split utility functions
@@ -410,20 +420,18 @@ void xaccSplitMergePeerSplits (Split *split, const Split *other_split);
*/
Split * xaccSplitGetOtherSplit (const Split *split);
/** The xaccIsPeerSplit() is a convenience routine that returns TRUE
* (a non-zero value) if the two splits share a common parent
* transaction, else it returns FALSE (zero).
*
gboolean xaccIsPeerSplit (const Split *split_1, const Split *split_2);
*/
/** Returns the split type, which is either the string "normal", or
* "stock-split" for a split from a stock split (pun intended? :-). */
const char *xaccSplitGetType(const Split *s);
* "stock-split" for a split from a stock split. */
const char *xaccSplitGetType(Split *s);
/** Mark a split to be of type stock split - after this, you shouldn't
modify the value anymore, just the amount. */
void xaccSplitMakeStockSplit(Split *s);
/** Returns true if the split is of type stock split
*/
gboolean xaccSplitIsStockSplit(Split *s);
/**
* The xaccSplitOrder(sa,sb) method is useful for sorting.
* if sa and sb have different transactions, return their xaccTransOrder
+4 -1
View File
@@ -115,7 +115,7 @@ struct split_s
gnc_numeric value;
gnc_numeric amount;
const gchar * split_type;
const char *split_type;
/* -------------------------------------------------------------- */
/* Below follow some 'temporary' fields */
@@ -128,6 +128,9 @@ struct split_s
gnc_numeric noclosing_balance;
gnc_numeric cleared_balance;
gnc_numeric reconciled_balance;
/* The stock-split adjusted amount */
gnc_numeric adjusted_amount;
};
struct _SplitClass
+184 -46
View File
@@ -298,7 +298,7 @@ xaccSplitAssignToLot (Split *split, GNCLot *lot)
* 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),
cmp = gnc_numeric_compare (gnc_numeric_abs(xaccSplitGetAdjustedAmount (split)),
gnc_numeric_abs(baln));
PINFO ("found open lot with baln=%s (%s)", gnc_num_dbg_to_string (baln),
@@ -331,6 +331,16 @@ xaccSplitAssignToLot (Split *split, GNCLot *lot)
trans = split->parent;
xaccTransBeginEdit (trans);
/* Adjust split's view of the lot balance for stock splits */
if (!gnc_numeric_equal (split->amount, xaccSplitGetAdjustedAmount (split)))
{
frac = gnc_numeric_div (split->amount, xaccSplitGetAdjustedAmount (split),
GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
baln = gnc_numeric_mul (baln, frac,
xaccAccountGetCommoditySCU(acc),
GNC_HOW_RND_ROUND_HALF_UP);
}
amt_tot = split->amount;
amt_a = gnc_numeric_neg (baln);
amt_b = gnc_numeric_sub_fixed (amt_tot, amt_a);
@@ -476,48 +486,34 @@ xaccSplitAssign (Split *split)
/* ============================================================== */
Split *
xaccSplitGetCapGainsSplit (const Split *split)
static Split *
find_split_by_type (const gchar *type, const Split *split)
{
GncGUID *gains_guid;
Split *gains_split;
GncGUID *type_guid = nullptr;
if (!split) return nullptr;
qof_instance_get (QOF_INSTANCE (split),
"gains-split", &gains_guid,
nullptr);
if (!gains_guid) return nullptr;
qof_instance_get (QOF_INSTANCE (split), type, &type_guid, nullptr);
if (!type_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;
auto *guid_split = (Split *) qof_collection_lookup_entity (qof_instance_get_collection (split), type_guid);
PINFO ("split=%p has %s=%p", split, type, guid_split);
guid_free (type_guid);
return guid_split;
}
/* ============================================================== */
Split *
xaccSplitGetCapGainsSplit (const Split *split)
{
return find_split_by_type ("gains-split", 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;
return find_split_by_type ("gains-source", split);
}
/* ============================================================== */
@@ -581,7 +577,7 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
return;
}
if (g_strcmp0 ("stock-split", xaccSplitGetType (split)) == 0)
if (xaccSplitIsStockSplit (split))
{
LEAVE ("Stock split split, returning.");
return;
@@ -675,38 +671,38 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
* cleans up the lot, before we get at it!
*/
if (0 > gnc_numeric_compare (gnc_numeric_abs(lot_amount),
gnc_numeric_abs(split->amount)))
gnc_numeric_abs(xaccSplitGetAdjustedAmount (split))))
{
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));
PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
}
PERR ("Malformed Lot \"%s\"! (too thin!) "
"opening amt=%s split amt=%s baln=%s",
"opening amt=%s split adj 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 (xaccSplitGetAdjustedAmount (split)),
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(xaccSplitGetAdjustedAmount (split))) &&
(gnc_numeric_positive_p(lot_amount) ||
gnc_numeric_negative_p(split->amount)))
gnc_numeric_negative_p(xaccSplitGetAdjustedAmount (split))))
{
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));
PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
}
PERR ("Malformed Lot \"%s\"! (too fat!) "
"opening amt=%s split amt=%s baln=%s",
"opening adj amt=%s split adj 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 (xaccSplitGetAdjustedAmount (split)),
gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
return;
}
@@ -719,7 +715,7 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
* cap_gain = current_split_value - cost_basis
*/
/* Fraction of the lot that this split represents: */
frac = gnc_numeric_div (split->amount, lot_amount,
frac = gnc_numeric_div (xaccSplitGetAdjustedAmount (split), lot_amount,
GNC_DENOM_AUTO,
GNC_HOW_DENOM_REDUCE);
/* Basis for this split: */
@@ -729,10 +725,10 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
/* 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",
PINFO ("Open amt=%s val=%s; split adj 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 (xaccSplitGetAdjustedAmount (split)),
gnc_num_dbg_to_string (split->value),
gnc_num_dbg_to_string (value));
if (gnc_numeric_check (value))
@@ -744,7 +740,7 @@ xaccSplitComputeCapGains(Split *split, Account *gain_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 (xaccSplitGetAdjustedAmount (split)),
gnc_num_dbg_to_string (split->value),
gnc_num_dbg_to_string (value));
return;
@@ -752,7 +748,7 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
/* 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
* with equal & opposite values. The amt of one is 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).
*/
@@ -896,6 +892,148 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc)
/* ============================================================== */
gnc_numeric
xaccLotFreeSplitCapGain(Split *split, GNCLot *lot)
{
g_return_val_if_fail (split && lot && !split->lot, gnc_numeric_zero());
ENTER ("(split=%p lot=%s)", split, gnc_lot_get_title(lot));
auto pcy = gnc_account_get_policy (gnc_lot_get_account (lot));
auto currency = split->parent->common_currency;
/* 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 gnc_numeric_zero();
}
auto *esplit = gnc_lot_get_earliest_split (lot);
if (!esplit)
{
LEAVE ("Lot is empty, returning.");
return gnc_numeric_zero();
}
if (xaccTransGetDate (xaccSplitGetParent (esplit)) >= xaccTransGetDate (xaccSplitGetParent (split)))
{
LEAVE ("Split is too early, returning.");
return gnc_numeric_zero();
}
if (xaccSplitIsStockSplit (split))
{
LEAVE ("Stock split split, returning.");
return gnc_numeric_zero();
}
/* 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 gnc_numeric_zero();
/* If we got to here, then 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_numeric lot_amount, lot_value;
gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
gnc_numeric opening_amount, opening_value;
gnc_commodity *opening_currency;
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 gnc_numeric_zero();
}
/* 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(xaccSplitGetAdjustedAmount (split))))
{
for (auto *n = gnc_lot_get_split_list(lot); n; n = n->next)
{
auto *s = GNC_SPLIT(n->data);
PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
}
return gnc_numeric_zero();
}
if ( (gnc_numeric_negative_p(lot_amount) ||
gnc_numeric_positive_p(xaccSplitGetAdjustedAmount (split))) &&
(gnc_numeric_positive_p(lot_amount) ||
gnc_numeric_negative_p(xaccSplitGetAdjustedAmount (split))))
{
for (auto *n = gnc_lot_get_split_list(lot); n; n = n->next)
{
auto *s = GNC_SPLIT(n->data);
PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
}
return gnc_numeric_zero();
}
/* 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: */
auto frac = gnc_numeric_div (xaccSplitGetAdjustedAmount (split), lot_amount,
GNC_DENOM_AUTO,
GNC_HOW_DENOM_REDUCE);
/* Basis for this split: */
auto 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 adj 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 (xaccSplitGetAdjustedAmount (split)),
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 (xaccSplitGetAdjustedAmount (split)),
gnc_num_dbg_to_string (split->value),
gnc_num_dbg_to_string (value));
return gnc_numeric_zero();
}
LEAVE ("(lot=%s)", gnc_lot_get_title(lot));
return value;
}
/* ============================================================== */
gnc_numeric
xaccSplitGetCapGains(Split * split)
{
+14
View File
@@ -187,6 +187,20 @@ gboolean xaccSplitAssign (Split *split);
*/
Split * xaccSplitAssignToLot (Split *split, GNCLot *lot);
/** The xaccLotFreeSplitCapGain() method returns the value of capital gain
* (if any) associated with the indicated split against the indicated
* lot. In order for there to be any capital gain, a few things must
* hold true about this split:
* (1) It must have been involved in trading (for example,
* by belonging to a stock or trading account)
* (2) It must not be assigned to a lot.
* (3) It cannot be the opening split of a lot; that
* is, it must be a matching sale of an earlier purchase
* (or vice versa).
*/
gnc_numeric xaccLotFreeSplitCapGain(Split *split, GNCLot *lot);
/** The xaccSplitComputeCapGains() routine computes the cap gains
* or losses for the indicated split. The gains are placed into
* the 'gains_acct'. If the gains_acct is NULL, then the appropriate
+2 -2
View File
@@ -505,7 +505,7 @@ gnc_lot_get_balance (GNCLot *lot)
for (node = priv->splits; node; node = node->next)
{
Split *s = GNC_SPLIT(node->data);
gnc_numeric amt = xaccSplitGetAmount (s);
gnc_numeric amt = xaccSplitGetAdjustedAmount (s);
baln = gnc_numeric_add_fixed (baln, amt);
g_assert (gnc_numeric_check (baln) == GNC_ERROR_OK);
}
@@ -561,7 +561,7 @@ gnc_lot_get_balance_before (const GNCLot *lot, const Split *split,
if ((ta == tb && source != target) ||
xaccTransOrder (ta, tb) < 0)
{
gnc_numeric tmpval = xaccSplitGetAmount (s);
gnc_numeric tmpval = xaccSplitGetAdjustedAmount (s);
amt = gnc_numeric_add_fixed (amt, tmpval);
tmpval = xaccSplitGetValue (s);
val = gnc_numeric_add_fixed (val, tmpval);
+2 -2
View File
@@ -119,7 +119,7 @@ static GNCLot *
FIFOPolicyGetLot (GNCPolicy *pcy, Split *split)
{
if (!split) return nullptr;
return xaccAccountFindEarliestOpenLot (split->acc, split->amount,
return xaccAccountFindEarliestOpenLot (split->acc, xaccSplitGetAdjustedAmount (split),
split->parent->common_currency);
}
@@ -138,7 +138,7 @@ FIFOPolicyGetLotOpening (GNCPolicy *pcy,
Split *opening_split;
opening_split = gnc_lot_get_earliest_split(lot);
if (ret_amount) *ret_amount = opening_split->amount;
if (ret_amount) *ret_amount = xaccSplitGetAdjustedAmount (opening_split);
if (ret_value) *ret_value = opening_split->value;
if (ret_currency) *ret_currency = opening_split->parent->common_currency;
}
+4 -3
View File
@@ -1565,7 +1565,7 @@ test_xaccAccountInsertRemoveLot (Fixture *fixture, gconstpointer pData)
g_assert_cmpuint (g_list_length (p_priv->lots), == , 1);
test_signal_assert_hits (sig1, 2);
test_signal_assert_hits (sig4, 1);
test_signal_assert_hits (sig2, 1);
test_signal_assert_hits (sig2, 2);
/* Check that removing the lot works */
xaccAccountRemoveLot (parent, lot);
/* The following test should fail, but it doesn't because of an
@@ -1576,7 +1576,7 @@ test_xaccAccountInsertRemoveLot (Fixture *fixture, gconstpointer pData)
g_assert_cmpuint (g_list_length (p_priv->lots), == , 0);
test_signal_assert_hits (sig3, 1);
test_signal_assert_hits (sig4, 2);
test_signal_assert_hits (sig2, 1);
test_signal_assert_hits (sig2, 2);
/* Check that destroying the lot removes its reference */
/* Because the lot's account pointer doesn't get nulled when the lot
* is removed, we have to do that for the next test to work: */
@@ -1635,7 +1635,7 @@ test_xaccAccountRecomputeBalance (Fixture *fixture, gconstpointer pData)
rec_bal = gnc_numeric_add_fixed (rec_bal, p.amount);
}
g_assert_true (gnc_numeric_zero_p (priv->starting_balance));
g_assert_true (gnc_numeric_zero_p (priv->balance));
priv->cleared_balance = gnc_numeric_zero ();
priv->balance_dirty = TRUE;
xaccAccountRecomputeBalance (fixture->acct);
g_assert_true (gnc_numeric_zero_p (priv->starting_balance));
@@ -2375,6 +2375,7 @@ test_xaccAccountFindOpenLots (Fixture *fixture, gconstpointer pData)
LotList* lots;
g_assert_true (acct);
xaccAccountRecomputeBalance (acct);
lots = xaccAccountFindOpenLots (acct, NULL, NULL, NULL);
g_assert_true (g_list_length (lots) == 2);
if (lots) g_list_free (lots);
+1
View File
@@ -91,6 +91,7 @@ setup (Fixture *fixture, gconstpointer pData)
fixture->split->balance = amount;
fixture->split->cleared_balance = amount;
fixture->split->reconciled_balance = amount;
fixture->split->adjusted_amount = amount;
qof_instance_mark_clean (QOF_INSTANCE (fixture->split));
qof_instance_mark_clean (QOF_INSTANCE (acc));
qof_instance_mark_clean (QOF_INSTANCE (txn));