LCOV - code coverage report
Current view: top level - libgnucash/engine - cap-gains.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 73.0 % 392 286
Test Date: 2026-11-02 18:19:43 Functions: 81.2 % 16 13
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /********************************************************************\
       2              :  * cap-gains.c -- Automatically Compute Capital Gains/Losses        *
       3              :  *                                                                  *
       4              :  * This program is free software; you can redistribute it and/or    *
       5              :  * modify it under the terms of the GNU General Public License as   *
       6              :  * published by the Free Software Foundation; either version 2 of   *
       7              :  * the License, or (at your option) any later version.              *
       8              :  *                                                                  *
       9              :  * This program is distributed in the hope that it will be useful,  *
      10              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
      11              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
      12              :  * GNU General Public License for more details.                     *
      13              :  *                                                                  *
      14              :  * You should have received a copy of the GNU General Public License*
      15              :  * along with this program; if not, contact:                        *
      16              :  *                                                                  *
      17              :  * Free Software Foundation           Voice:  +1-617-542-5942       *
      18              :  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
      19              :  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
      20              : \********************************************************************/
      21              : 
      22              : /** @file cap-gains.c
      23              :  *  @brief Utilities to Automatically Compute Capital Gains/Losses.
      24              :  *  @author Created by Linas Vepstas August 2003
      25              :  *  @author Copyright (c) 2003,2004 Linas Vepstas <linas@linas.org>
      26              :  *
      27              :  *  This file implements the various routines to automatically
      28              :  *  compute and handle Cap Gains/Losses resulting from trading
      29              :  *  activities.  Some of these routines might have broader
      30              :  *  applicability, for handling depreciation & etc.
      31              :  *
      32              :  *  This code is under development, and is 'beta': we think we're
      33              :  *  mostly done, and we've tested and "things work for us", but there
      34              :  *  may still be something missing, and there might still be some
      35              :  *  bugs.
      36              :  *
      37              :  * This code uses a 'gains dirty' flag: A 'dirty' flag on the source
      38              :  * split indicates that the gains transaction needs to be recomputed.
      39              :  * Another flag, the gains transaction flag, marks the split as
      40              :  * being a gains split, and that the source transaction should be
      41              :  * checked for dirtiness before returning the date, the amount, the
      42              :  * value, etc.  Finally, these flags make amount and value read-only
      43              :  * for the gains splits. (the memo is user-modifieable).
      44              :  *
      45              :  * If the amount in a split is changed, then the lot has to be recomputed.
      46              :  * This has a potential trickle-through effect on all later lots.
      47              :  * Ideally, later lots are dissolved, and recomputed.  However, some
      48              :  * lots may have been user-hand-built. These should be left alone.
      49              :  *
      50              : ToDo:
      51              :  o XXX Need to create a data-integrity scrubber, that makes sure that
      52              :    the various flags, and pointers & etc. match. See sections marked
      53              :    with XXX below for things that might go wrong.
      54              :  */
      55              : 
      56              : #include <config.h>
      57              : 
      58              : #include <glib.h>
      59              : #include <glib/gi18n.h>
      60              : 
      61              : #include "Account.hpp"
      62              : #include "AccountP.hpp"
      63              : #include "Scrub2.h"
      64              : #include "Scrub3.h"
      65              : #include "Transaction.h"
      66              : #include "TransactionP.hpp"
      67              : #include "cap-gains.h"
      68              : #include "gnc-engine.h"
      69              : #include "engine-helpers.h"
      70              : #include "gnc-lot.h"
      71              : #include "policy.h"
      72              : #include "policy-p.h"
      73              : 
      74              : static QofLogModule log_module = GNC_MOD_LOT;
      75              : 
      76              : 
      77              : /* ============================================================== */
      78              : 
      79              : gboolean
      80           70 : xaccAccountHasTrades (const Account *acc)
      81              : {
      82              :     gnc_commodity *acc_comm;
      83              : 
      84           70 :     if (!acc) return FALSE;
      85              : 
      86           70 :     if (xaccAccountIsPriced (acc))
      87           28 :         return TRUE;
      88              : 
      89           42 :     acc_comm = xaccAccountGetCommodity(acc);
      90              : 
      91           86 :     for (auto s : xaccAccountGetSplits (acc))
      92              :     {
      93           41 :         Transaction *t = s->parent;
      94           41 :         if (s->gains == GAINS_STATUS_GAINS) continue;
      95           41 :         if (acc_comm != t->common_currency) return TRUE;
      96              :     }
      97              : 
      98            3 :     return FALSE;
      99              : }
     100              : 
     101              : /* ============================================================== */
     102              : 
     103              : struct FindLot
     104              : {
     105              :     GNCLot *lot;
     106              :     gnc_commodity *currency;
     107              :     time64 time;
     108              :     int (*numeric_pred)(gnc_numeric);
     109              :     gboolean (*date_pred)(time64 e, time64 tr);
     110              : };
     111              : 
     112              : static gboolean
     113           11 : earliest_pred (time64 earl, time64 tran)
     114              : {
     115           11 :     return earl > tran;
     116              : }
     117              : 
     118              : static gboolean
     119            0 : latest_pred (time64 earl, time64 tran)
     120              : {
     121            0 :     return earl < tran;
     122              : }
     123              : 
     124              : static gpointer
     125          757 : finder_helper (GNCLot *lot,  gpointer user_data)
     126              : {
     127          757 :     auto els = static_cast<FindLot*>(user_data);
     128              :     Split *s;
     129              :     Transaction *trans;
     130              :     gnc_numeric bal;
     131              :     gboolean opening_is_positive, bal_is_positive;
     132          757 :     time64 posted = 0;
     133              : 
     134          757 :     if (gnc_lot_is_closed (lot)) return nullptr;
     135              : 
     136          711 :     s = gnc_lot_get_earliest_split (lot);
     137          711 :     if (s == nullptr) return nullptr;
     138              : 
     139              :     /* We want a lot whose balance is of the correct sign.  All splits
     140              :        in a lot must be the opposite sign of the opening split.  We also
     141              :        want to ignore lots that are overfull, i.e., where the balance in
     142              :        the lot is of opposite sign to the opening split in the lot. */
     143          711 :     if (0 == (els->numeric_pred) (s->amount)) return nullptr;
     144          369 :     bal = gnc_lot_get_balance (lot);
     145          369 :     opening_is_positive = gnc_numeric_positive_p (s->amount);
     146          369 :     bal_is_positive = gnc_numeric_positive_p (bal);
     147          369 :     if (opening_is_positive != bal_is_positive) return nullptr;
     148              : 
     149          369 :     trans = s->parent;
     150          738 :     if (els->currency &&
     151          369 :             (FALSE == gnc_commodity_equiv (els->currency,
     152          369 :                                            trans->common_currency)))
     153              :     {
     154          358 :         return nullptr;
     155              :     }
     156              : 
     157           11 :     posted = trans->date_posted;
     158           11 :     if (els->date_pred (els->time, posted))
     159              :     {
     160           11 :         els->time = trans->date_posted;
     161           11 :         els->lot = lot;
     162              :     }
     163              : 
     164           11 :     return nullptr;
     165              : }
     166              : 
     167              : static inline GNCLot *
     168           64 : xaccAccountFindOpenLot (Account *acc, gnc_numeric sign,
     169              :                         gnc_commodity *currency,
     170              :                         gint64 guess,
     171              :                         gboolean (*date_pred)(time64, time64))
     172              : {
     173              :     FindLot es;
     174              : 
     175           64 :     es.lot = nullptr;
     176           64 :     es.currency = currency;
     177           64 :     es.time = guess;
     178           64 :     es.date_pred = date_pred;
     179              : 
     180           64 :     if (gnc_numeric_positive_p(sign)) es.numeric_pred = gnc_numeric_negative_p;
     181           34 :     else es.numeric_pred = gnc_numeric_positive_p;
     182              : 
     183           64 :     xaccAccountForEachLot (acc, finder_helper, &es);
     184           64 :     return es.lot;
     185              : }
     186              : 
     187              : GNCLot *
     188           64 : xaccAccountFindEarliestOpenLot (Account *acc, gnc_numeric sign,
     189              :                                 gnc_commodity *currency)
     190              : {
     191              :     GNCLot *lot;
     192           64 :     ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, sign.num,
     193              :            sign.denom);
     194              : 
     195           64 :     lot = xaccAccountFindOpenLot (acc, sign, currency,
     196              :                                   G_MAXINT64, earliest_pred);
     197           64 :     LEAVE ("found lot=%p %s baln=%s", lot, gnc_lot_get_title (lot),
     198              :            gnc_num_dbg_to_string(gnc_lot_get_balance(lot)));
     199           64 :     return lot;
     200              : }
     201              : 
     202              : GNCLot *
     203            0 : xaccAccountFindLatestOpenLot (Account *acc, gnc_numeric sign,
     204              :                               gnc_commodity *currency)
     205              : {
     206              :     GNCLot *lot;
     207            0 :     ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
     208              :            sign.num, sign.denom);
     209              : 
     210            0 :     lot = xaccAccountFindOpenLot (acc, sign, currency,
     211              :                                   G_MININT64, latest_pred);
     212            0 :     LEAVE ("found lot=%p %s", lot, gnc_lot_get_title (lot));
     213            0 :     return lot;
     214              : }
     215              : 
     216              : /* ============================================================== */
     217              : 
     218              : Split *
     219           64 : xaccSplitAssignToLot (Split *split, GNCLot *lot)
     220              : {
     221              :     Account *acc;
     222              :     gnc_numeric baln;
     223              :     int cmp;
     224              :     gboolean baln_is_positive, amt_is_positive;
     225              : 
     226           64 :     if (!lot) return split;
     227           64 :     if (!split) return nullptr;
     228              : 
     229              :     /* If this split already belongs to a lot, we are done. */
     230           64 :     if (split->lot) return nullptr;
     231              : 
     232              :     /* Anomalous situation; except for voided transactions,
     233              :      * we don't expect to see splits with no amount ..
     234              :      * unless they're gains splits, and we shouldn't see those.
     235              :      */
     236           64 :     if (gnc_numeric_zero_p (split->amount))
     237              :     {
     238            0 :         if (xaccTransGetVoidStatus(split->parent)) return nullptr;
     239              : 
     240            0 :         PWARN ("split with zero amount; value=%s gflag=%x gsplit=%p",
     241              :                gnc_num_dbg_to_string (split->amount),
     242              :                split->gains,
     243              :                split->gains_split);
     244            0 :         if (split->gains_split)
     245              :         {
     246            0 :             PWARN ("gains amt=%s value=%s",
     247              :                    gnc_num_dbg_to_string (split->gains_split->amount),
     248              :                    gnc_num_dbg_to_string (split->gains_split->value));
     249              :         }
     250            0 :         return nullptr;
     251              :     }
     252              : 
     253              :     /* If the lot is closed, we can't add anything to it */
     254           64 :     baln = gnc_lot_get_balance (lot);
     255           64 :     if (gnc_lot_is_closed (lot)) return split;
     256              : 
     257              :     /* If the lot balance is zero, but the lot is open, then
     258              :      * the lot is empty. Unconditionally add the split. */
     259           64 :     if (gnc_numeric_zero_p (baln))
     260              :     {
     261           55 :         acc = split->acc;
     262           55 :         xaccAccountBeginEdit (acc);
     263           55 :         gnc_lot_add_split (lot, split);
     264           55 :         PINFO ("added split to empty lot, new lot baln=%s (%s)",
     265              :                gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
     266              :                gnc_lot_get_title (lot));
     267           55 :         xaccAccountCommitEdit (acc);
     268           55 :         return nullptr;
     269              :     }
     270              : 
     271              :     /* If the sign of the split is the same as the sign of the lot,
     272              :      * add the split, but complain about it ... none of the currently
     273              :      * implemented accounting policies should be giving us splits
     274              :      * that make lots larger.  One a lot is open, the FIFO/LIFO
     275              :      * policies should be working only to make the lot smaller.
     276              :      * We can remove the warning emssage come the day we have
     277              :      * fancier policies.
     278              :      */
     279            9 :     baln_is_positive = gnc_numeric_positive_p (baln);
     280            9 :     amt_is_positive = gnc_numeric_positive_p (split->amount);
     281            9 :     if ((baln_is_positive && amt_is_positive) ||
     282            5 :             ((!baln_is_positive) && (!amt_is_positive)))
     283              :     {
     284            0 :         PWARN ("accounting policy gave us split that enlarges the lot!\n"
     285              :                "old lot baln=%s split amt=%s lot=%s",
     286              :                gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
     287              :                gnc_num_dbg_to_string (split->amount),
     288              :                gnc_lot_get_title (lot));
     289              : 
     290            0 :         acc = split->acc;
     291            0 :         xaccAccountBeginEdit (acc);
     292            0 :         gnc_lot_add_split (lot, split);
     293            0 :         xaccAccountCommitEdit (acc);
     294            0 :         return nullptr;
     295              :     }
     296              : 
     297              :     /* If adding the split would make the lot balance change sign,
     298              :      * then we split the split into two pieces: one piece that will
     299              :      * bring the lot balance to zero, and another to be dealt with
     300              :      * later.  */
     301            9 :     cmp = gnc_numeric_compare (gnc_numeric_abs(xaccSplitGetAdjustedAmount (split)),
     302              :                                gnc_numeric_abs(baln));
     303              : 
     304            9 :     PINFO ("found open lot with baln=%s (%s)", gnc_num_dbg_to_string (baln),
     305              :            gnc_lot_get_title (lot));
     306              : 
     307              :     /* cmp == -1 if amt < baln, ==0 if amt==baln */
     308            9 :     if (0 >= cmp)
     309              :     {
     310            5 :         acc = split->acc;
     311            5 :         xaccAccountBeginEdit (acc);
     312            5 :         gnc_lot_add_split (lot, split);
     313            5 :         PINFO ("simple added split to lot, new lot baln=%s",
     314              :                gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
     315            5 :         xaccAccountCommitEdit (acc);
     316            5 :         return nullptr;
     317              :     }
     318              : 
     319              :     /* If we are here, then (cmp == +1 iff (amt > baln)) and we need
     320              :      * to split up the split into pieces. Do it. */
     321              :     {
     322            4 :         time64 now = gnc_time (nullptr), time = 0;
     323              :         Split * new_split;
     324              :         gnc_numeric amt_a, amt_b, amt_tot;
     325              :         gnc_numeric val_a, val_b, val_tot;
     326              :         gnc_numeric frac;
     327              :         Transaction *trans;
     328              : 
     329            4 :         acc = split->acc;
     330            4 :         xaccAccountBeginEdit (acc);
     331            4 :         trans = split->parent;
     332            4 :         xaccTransBeginEdit (trans);
     333              : 
     334              :         /* Adjust split's view of the lot balance for stock splits */
     335            4 :         if (!gnc_numeric_equal (split->amount, xaccSplitGetAdjustedAmount (split)))
     336              :         {
     337            0 :             frac = gnc_numeric_div (split->amount, xaccSplitGetAdjustedAmount (split),
     338              :                                     GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
     339            0 :             baln = gnc_numeric_mul (baln, frac,
     340            0 :                                     xaccAccountGetCommoditySCU(acc),
     341              :                                     GNC_HOW_RND_ROUND_HALF_UP);
     342              :         }
     343              : 
     344            4 :         amt_tot = split->amount;
     345            4 :         amt_a = gnc_numeric_neg (baln);
     346            4 :         amt_b = gnc_numeric_sub_fixed (amt_tot, amt_a);
     347            4 :         g_return_val_if_fail(gnc_numeric_check(amt_b) == GNC_ERROR_OK, nullptr);
     348              : 
     349            4 :         PINFO ("++++++++++++++ splitting split=%p into amt = %s + %s",
     350              :                split,
     351              :                gnc_num_dbg_to_string(amt_a),
     352              :                gnc_num_dbg_to_string(amt_b) );
     353              : 
     354              :         /* Compute the value so that it holds in the same proportion:
     355              :          * i.e. so that (amt_a / amt_tot) = (val_a / val_tot)
     356              :          */
     357            4 :         val_tot = split->value;
     358            4 :         frac = gnc_numeric_div (amt_a, amt_tot,
     359              :                                 GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
     360            4 :         val_a = gnc_numeric_mul (frac, val_tot,
     361              :                                  gnc_numeric_denom(val_tot),
     362              :                                  GNC_HOW_RND_ROUND_HALF_UP | GNC_HOW_DENOM_EXACT);
     363              : 
     364            4 :         val_b = gnc_numeric_sub_fixed (val_tot, val_a);
     365            4 :         if (gnc_numeric_check(val_a))
     366              :         {
     367            0 :             PERR("Numeric overflow\n"
     368              :                  "Acct=%s Txn=%s\n"
     369              :                  "\tval_tot=%s amt_a=%s amt_tot=%s\n",
     370              :                  xaccAccountGetName(acc),
     371              :                  xaccTransGetDescription(trans),
     372              :                  gnc_num_dbg_to_string(val_tot),
     373              :                  gnc_num_dbg_to_string(amt_a),
     374              :                  gnc_num_dbg_to_string(amt_tot));
     375              :         }
     376              : 
     377            4 :         if (gnc_numeric_zero_p(amt_a) || gnc_numeric_zero_p(amt_b))
     378              :         {
     379            0 :             PERR ("Failed to split into two!");
     380              :         }
     381              : 
     382            4 :         PINFO ("split value is = %s = %s + %s",
     383              :                gnc_num_dbg_to_string(val_tot),
     384              :                gnc_num_dbg_to_string(val_a),
     385              :                gnc_num_dbg_to_string(val_b) );
     386              : 
     387            4 :         g_return_val_if_fail (!gnc_numeric_zero_p (amt_a), nullptr);
     388            4 :         g_return_val_if_fail (!gnc_numeric_check (val_a), nullptr);
     389            4 :         xaccSplitSetAmount (split, amt_a);
     390            4 :         xaccSplitSetValue (split, val_a);
     391              : 
     392              :         /* Adding this split will have the effect of closing this lot,
     393              :          * because the new balance should be precisely zero. */
     394            4 :         gnc_lot_add_split (lot, split);
     395              : 
     396              :         /* Put the remainder of the balance into a new split,
     397              :          * which is in other respects just a clone of this one. */
     398            4 :         new_split = xaccMallocSplit (qof_instance_get_book(acc));
     399              : 
     400              :         /* Copy most of the split attributes */
     401            4 :         xaccSplitSetMemo (new_split, xaccSplitGetMemo (split));
     402              :         /* Set split-action with gnc_set_num_action which is the same as
     403              :          * xaccSplitSetAction with these arguments; use gnc_get_num_action to get
     404              :          * split-action which is the same as xaccSplitGetAction */
     405            4 :         gnc_set_num_action(nullptr, new_split, nullptr, gnc_get_num_action(nullptr, split));
     406            4 :         xaccSplitSetReconcile (new_split, xaccSplitGetReconcile (split));
     407            4 :         time = xaccSplitGetDateReconciled (split);
     408            4 :         xaccSplitSetDateReconciledSecs (new_split, time);
     409              : 
     410              :         /* Set the lot-split and peer_guid properties on the two
     411              :          * splits to indicate that they're linked. 
     412              :          */
     413            4 :         xaccSplitAddPeerSplit(split, new_split, now);
     414            4 :         xaccSplitAddPeerSplit(new_split, split, now);
     415            4 :         xaccAccountInsertSplit (acc, new_split);
     416            4 :         xaccTransAppendSplit (trans, new_split);
     417              :         /* Set the amount and value after the split is in the transaction
     418              :            so it can find the correct denominator to use.  Otherwise it
     419              :            uses 100000 which may cause an overflow in some of the tests
     420              :            in test-period */
     421            4 :         xaccSplitSetAmount (new_split, amt_b);
     422            4 :         xaccSplitSetValue (new_split, val_b);
     423            4 :         xaccTransCommitEdit (trans);
     424            4 :         xaccAccountCommitEdit (acc);
     425            4 :         return new_split;
     426              :     }
     427              : }
     428              : 
     429              : /* ============================================================== */
     430              : 
     431              : /* Accounting-policy callback.  Given an account and an amount,
     432              :  * this routine should return a lot.  By implementing this as
     433              :  * a callback, we can 'easily' add other accounting policies.
     434              :  */
     435              : gboolean
     436           60 : xaccSplitAssign (Split *split)
     437              : {
     438              :     Account *acc;
     439           60 :     gboolean splits_split_up = FALSE;
     440              :     GNCLot *lot;
     441              :     GNCPolicy *pcy;
     442              : 
     443           60 :     if (!split) return FALSE;
     444              : 
     445              :     /* If this split already belongs to a lot or the account doesn't
     446              :      * have lots, we are done.
     447              :      */
     448           60 :     if (split->lot) return FALSE;
     449           60 :     g_return_val_if_fail (split->gains == GAINS_STATUS_UNKNOWN ||
     450              :                           (split->gains & GAINS_STATUS_GAINS) == FALSE, FALSE);
     451           60 :     acc = split->acc;
     452           60 :     if (!xaccAccountHasTrades (acc))
     453            0 :         return FALSE;
     454           60 :     if (gnc_numeric_zero_p (split->amount))
     455            0 :         return FALSE;
     456              : 
     457           60 :     ENTER ("(split=%p)", split);
     458              : 
     459           60 :     pcy = gnc_account_get_policy(acc);
     460           60 :     xaccAccountBeginEdit (acc);
     461              : 
     462              :     /* If we are here, this split does not belong to any lot.
     463              :      * We ask the policy for a lot to assign it to.  This
     464              :      * block is written in the form of a while loop, since we
     465              :      * may have to bust a split across several lots.
     466              :      */
     467          124 :     while (split)
     468              :     {
     469           64 :         PINFO ("have split %p amount=%s", split,
     470              :                gnc_num_dbg_to_string (split->amount));
     471           64 :         split->gains |= GAINS_STATUS_VDIRTY;
     472           64 :         lot = pcy->PolicyGetLot (pcy, split);
     473           64 :         if (!lot)
     474              :         {
     475           55 :             lot = gnc_lot_make_default (acc);
     476           55 :             PINFO ("start new lot (%s)", gnc_lot_get_title(lot));
     477              :         }
     478           64 :         split = xaccSplitAssignToLot (split, lot);
     479           64 :         if (split) splits_split_up = TRUE;
     480              :     }
     481           60 :     xaccAccountCommitEdit (acc);
     482              : 
     483           60 :     LEAVE (" split_up=%d", splits_split_up);
     484           60 :     return splits_split_up;
     485              : }
     486              : 
     487              : /* ============================================================== */
     488              : 
     489              : static Split *
     490          726 : find_split_by_type (const gchar *type, const Split *split)
     491              : {
     492          726 :     GncGUID *type_guid = nullptr;
     493              : 
     494          726 :     if (!split) return nullptr;
     495              : 
     496          726 :     qof_instance_get (QOF_INSTANCE (split), type, &type_guid, nullptr);
     497          726 :     if (!type_guid) return nullptr;
     498              : 
     499              :     /* Both splits will be in the same collection, so search there. */
     500           21 :     auto *guid_split = (Split *) qof_collection_lookup_entity (qof_instance_get_collection (split), type_guid);
     501           21 :     PINFO ("split=%p has %s=%p", split, type, guid_split);
     502           21 :     guid_free (type_guid);
     503           21 :     return guid_split;
     504              : }
     505              : 
     506              : 
     507              : Split *
     508          307 : xaccSplitGetCapGainsSplit (const Split *split)
     509              : {
     510          307 :     return find_split_by_type ("gains-split", split);
     511              : }
     512              : 
     513              : Split *
     514          419 : xaccSplitGetGainsSourceSplit (const Split *split)
     515              : {
     516          419 :     return find_split_by_type ("gains-source", split);
     517              : }
     518              : 
     519              : /* ============================================================== */
     520              : 
     521              : void
     522          140 : xaccSplitComputeCapGains(Split *split, Account *gain_acc)
     523              : {
     524              :     SplitList *node;
     525              :     GNCLot *lot;
     526              :     GNCPolicy *pcy;
     527          140 :     gnc_commodity *currency = nullptr;
     528          140 :     gnc_numeric zero = gnc_numeric_zero();
     529              :     gnc_numeric value;
     530              :     gnc_numeric frac;
     531              :     gnc_numeric opening_amount, opening_value;
     532              :     gnc_numeric lot_amount, lot_value;
     533              :     gnc_commodity *opening_currency;
     534              : 
     535          249 :     if (!split) return;
     536          140 :     lot = split->lot;
     537          140 :     if (!lot) return;
     538          139 :     pcy = gnc_account_get_policy(gnc_lot_get_account(lot));
     539          139 :     currency = split->parent->common_currency;
     540              : 
     541          139 :     ENTER ("(split=%p gains=%p status=0x%x lot=%s)", split,
     542              :            split->gains_split, split->gains, gnc_lot_get_title(lot));
     543              : 
     544              :     /* Make sure the status flags and pointers are initialized */
     545          139 :     xaccSplitDetermineGainStatus(split);
     546              : 
     547              :     /* Not possible to have gains if the transaction currency and
     548              :      * account commodity are identical. */
     549          139 :     if (gnc_commodity_equal (currency,
     550          139 :                              xaccAccountGetCommodity(split->acc)))
     551              :     {
     552            0 :         LEAVE ("Currency transfer, gains not possible, returning.");
     553            0 :         return;
     554              :     }
     555              : 
     556          139 :     if (pcy->PolicyIsOpeningSplit (pcy, lot, split))
     557              :     {
     558              : #if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
     559              :         /* Check to make sure that this opening split doesn't
     560              :          * have a cap-gain transaction associated with it.
     561              :          * If it does, that's wrong, and we ruthlessly destroy it.
     562              :          * XXX Don't do this, it leads to infinite loops.
     563              :          * We need to scrub out errors like this elsewhere!
     564              :          */
     565              :         if (xaccSplitGetCapGainsSplit (split))
     566              :         {
     567              :             Split *gains_split = xaccSplitGetCapGainsSplit(split);
     568              :             Transaction *trans = gains_split->parent;
     569              :             PERR ("Opening Split must not have cap gains!!\n");
     570              : 
     571              :             xaccTransBeginEdit (trans);
     572              :             xaccTransDestroy (trans);
     573              :             xaccTransCommitEdit (trans);
     574              :         }
     575              : #endif
     576          108 :         LEAVE ("Lot opening split, returning.");
     577          108 :         return;
     578              :     }
     579              : 
     580           31 :     if (xaccSplitIsStockSplit (split))
     581              :     {
     582            0 :         LEAVE ("Stock split split, returning.");
     583            0 :         return;
     584              :     }
     585              : 
     586           31 :     if (GAINS_STATUS_GAINS & split->gains)
     587              :     {
     588              :         Split *s;
     589           13 :         PINFO ("split is a gains recording split, switch over");
     590              :         /* If this is the split that records the gains, then work with
     591              :          * the split that generates the gains.
     592              :          */
     593              :         /* split = xaccSplitGetCapGainsSplit (split); */
     594           13 :         s = split->gains_split;
     595              : 
     596              :         /* This should never be nullptr, and if it is, and its matching
     597              :          * parent can't be found, then its a bug, and we should be
     598              :          * discarding this split.   But ... for now .. return.
     599              :          * XXX move appropriate actions to a 'scrub' routine'
     600              :          */
     601           13 :         if (!s)
     602              :         {
     603            0 :             PERR ("Bad gains-split pointer! .. trying to recover.");
     604            0 :             split->gains_split = xaccSplitGetCapGainsSplit (split);
     605            0 :             s = split->gains_split;
     606            0 :             if (!s) return;
     607              : #if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
     608              :             xaccTransDestroy (trans);
     609              : #endif
     610              :         }
     611           13 :         split = s;
     612              :     }
     613              : 
     614              :     /* Note: if the value of the 'opening' split(s) has changed,
     615              :      * then the cap gains are changed. So we need to check not
     616              :      * only if this split is dirty, but also the lot-opening splits. */
     617           31 :     for (node = gnc_lot_get_split_list(lot); node; node = node->next)
     618              :     {
     619           31 :         Split *s = GNC_SPLIT(node->data);
     620           31 :         if (pcy->PolicyIsOpeningSplit(pcy, lot, s))
     621              :         {
     622           31 :             if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus (s);
     623           31 :             if (s->gains & GAINS_STATUS_VDIRTY)
     624              :             {
     625              :                 /* Force a recompute to occur */
     626           31 :                 split->gains |= GAINS_STATUS_VDIRTY;
     627           31 :                 break;
     628              :             }
     629              :         }
     630              :     }
     631              : 
     632              :     /* If it doesn't look like this split is 'dirty', then there's
     633              :      * nothing to do. Just return. */
     634           31 :     if ((FALSE == (split->gains & GAINS_STATUS_A_VDIRTY))  &&
     635            0 :             (split->gains_split) &&
     636            0 :             (FALSE == (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
     637              :     {
     638            0 :         LEAVE ("split not dirty, returning");
     639            0 :         return;
     640              :     }
     641              : 
     642              :     /* Yow! If amount is zero, there's nothing to do! Amount-zero splits
     643              :      * may exist if users attempted to manually record gains. */
     644           31 :     if (gnc_numeric_zero_p (split->amount)) return;
     645              : 
     646              :     /* If we got to here, then the split or something related is
     647              :      * 'dirty' and the gains really do need to be recomputed.
     648              :      * So start working things. */
     649              : 
     650              :     /* Get the amount and value in this lot at the time of this transaction. */
     651           31 :     gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
     652              : 
     653           31 :     pcy->PolicyGetLotOpening (pcy, lot, &opening_amount, &opening_value,
     654              :                               &opening_currency);
     655              : 
     656              :     /* Check to make sure the lot-opening currency and this split
     657              :      * use the same currency */
     658           31 :     if (FALSE == gnc_commodity_equiv (currency, opening_currency))
     659              :     {
     660              :         /* OK, the purchase and the sale were made in different currencies.
     661              :          * I don't know how to compute cap gains for that.  This is not
     662              :          * an error. Just punt, silently.
     663              :          */
     664            0 :         LEAVE ("Can't compute gains, mismatched commodities!");
     665            0 :         return;
     666              :     }
     667              : 
     668              :     /* Opening amount should be larger (or equal) to current split,
     669              :      * and it should be of the opposite sign.
     670              :      * XXX This should really be a part of a scrub routine that
     671              :      * cleans up the lot, before we get at it!
     672              :      */
     673           31 :     if (0 > gnc_numeric_compare (gnc_numeric_abs(lot_amount),
     674              :                                  gnc_numeric_abs(xaccSplitGetAdjustedAmount (split))))
     675              :     {
     676              :         GList *n;
     677            0 :         for (n = gnc_lot_get_split_list(lot); n; n = n->next)
     678              :         {
     679            0 :             Split *s = GNC_SPLIT(n->data);
     680            0 :             PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
     681              :         }
     682            0 :         PERR ("Malformed Lot \"%s\"! (too thin!) "
     683              :               "opening amt=%s split adj amt=%s baln=%s",
     684              :               gnc_lot_get_title (lot),
     685              :               gnc_num_dbg_to_string (lot_amount),
     686              :               gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
     687              :               gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
     688            0 :         return;
     689              :     }
     690           31 :     if ( (gnc_numeric_negative_p(lot_amount) ||
     691           45 :             gnc_numeric_positive_p(xaccSplitGetAdjustedAmount (split))) &&
     692           14 :             (gnc_numeric_positive_p(lot_amount) ||
     693           14 :              gnc_numeric_negative_p(xaccSplitGetAdjustedAmount (split))))
     694              :     {
     695              :         GList *n;
     696            0 :         for (n = gnc_lot_get_split_list(lot); n; n = n->next)
     697              :         {
     698            0 :             Split *s = GNC_SPLIT(n->data);
     699            0 :             PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
     700              :         }
     701            0 :         PERR ("Malformed Lot \"%s\"! (too fat!) "
     702              :               "opening adj amt=%s split adj amt=%s baln=%s",
     703              :               gnc_lot_get_title (lot),
     704              :               gnc_num_dbg_to_string (lot_amount),
     705              :               gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
     706              :               gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
     707            0 :         return;
     708              :     }
     709              : 
     710              :     /* The cap gains is the difference between the basis prior to the
     711              :      * current split, and the current split, pro-rated for an equal
     712              :      * amount of shares.
     713              :      * i.e. purchase_price = lot_value / lot_amount
     714              :      * cost_basis = purchase_price * current_split_amount
     715              :      * cap_gain = current_split_value - cost_basis
     716              :      */
     717              :     /* Fraction of the lot that this split represents: */
     718           31 :     frac = gnc_numeric_div (xaccSplitGetAdjustedAmount (split), lot_amount,
     719              :                             GNC_DENOM_AUTO,
     720              :                             GNC_HOW_DENOM_REDUCE);
     721              :     /* Basis for this split: */
     722           31 :     value = gnc_numeric_mul (frac, lot_value,
     723              :                              gnc_numeric_denom(opening_value),
     724              :                              GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP);
     725              :     /* Capital gain for this split: */
     726           31 :     value = gnc_numeric_sub (value, split->value,
     727              :                              GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
     728           31 :     PINFO ("Open amt=%s val=%s;  split adj amt=%s val=%s; gains=%s\n",
     729              :            gnc_num_dbg_to_string (lot_amount),
     730              :            gnc_num_dbg_to_string (lot_value),
     731              :            gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
     732              :            gnc_num_dbg_to_string (split->value),
     733              :            gnc_num_dbg_to_string (value));
     734           31 :     if (gnc_numeric_check (value))
     735              :     {
     736            0 :         PERR ("Numeric overflow during gains calculation\n"
     737              :               "Acct=%s Txn=%s\n"
     738              :               "\tOpen amt=%s val=%s\n\tsplit amt=%s val=%s\n\tgains=%s\n",
     739              :               xaccAccountGetName(split->acc),
     740              :               xaccTransGetDescription(split->parent),
     741              :               gnc_num_dbg_to_string (lot_amount),
     742              :               gnc_num_dbg_to_string (lot_value),
     743              :               gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
     744              :               gnc_num_dbg_to_string (split->value),
     745              :               gnc_num_dbg_to_string (value));
     746            0 :         return;
     747              :     }
     748              : 
     749              :     /* Are the cap gains zero?  If not, add a balancing transaction.
     750              :      * As per design doc lots.txt: the transaction has two splits,
     751              :      * with equal & opposite values.  The amt of one is zero (so as
     752              :      * not to upset the lot balance), the amt of the other is the same
     753              :      * as its value (its the realized gain/loss).
     754              :      */
     755           31 :     if (FALSE == gnc_numeric_zero_p (value))
     756              :     {
     757              :         Transaction *trans;
     758              :         Split *lot_split, *gain_split;
     759              :         gboolean new_gain_split;
     760           25 :         gnc_numeric negvalue = gnc_numeric_neg (value);
     761              : 
     762              :         /* See if there already is an associated gains transaction.
     763              :          * If there is, adjust its value as appropriate. Else, create
     764              :          * a new gains transaction.
     765              :          */
     766              :         /* lot_split = xaccSplitGetCapGainsSplit (split);  */
     767           25 :         lot_split = split->gains_split;
     768              : 
     769           25 :         if (nullptr == lot_split)
     770              :         {
     771            6 :             Account *lot_acc = gnc_lot_get_account(lot);
     772            6 :             QofBook *book = qof_instance_get_book(lot_acc);
     773            6 :             Transaction *base_txn = xaccSplitGetParent (split);
     774              : 
     775            6 :             new_gain_split = TRUE;
     776              : 
     777            6 :             lot_split = xaccMallocSplit (book);
     778            6 :             gain_split = xaccMallocSplit (book);
     779              : 
     780              :             /* Check to make sure the gains account currency matches. */
     781            6 :             if ((nullptr == gain_acc) ||
     782            0 :                     (FALSE == gnc_commodity_equiv (currency,
     783            0 :                                                    xaccAccountGetCommodity(gain_acc))))
     784              :             {
     785            6 :                 gain_acc = xaccAccountGainsAccount (lot_acc, currency);
     786              :             }
     787              : 
     788            6 :             xaccAccountBeginEdit (gain_acc);
     789            6 :             xaccAccountInsertSplit (gain_acc, gain_split);
     790            6 :             xaccAccountCommitEdit (gain_acc);
     791              : 
     792            6 :             xaccAccountBeginEdit (lot_acc);
     793            6 :             xaccAccountInsertSplit (lot_acc, lot_split);
     794            6 :             xaccAccountCommitEdit (lot_acc);
     795              : 
     796            6 :             trans = xaccMallocTransaction (book);
     797              : 
     798            6 :             xaccTransBeginEdit (trans);
     799            6 :             xaccTransSetCurrency (trans, currency);
     800            6 :             xaccTransSetDescription (trans, _("Realized Gain/Loss"));
     801              : 
     802            6 :             xaccTransAppendSplit (trans, lot_split);
     803            6 :             xaccTransAppendSplit (trans, gain_split);
     804              : 
     805            6 :             xaccSplitSetMemo (lot_split, _("Realized Gain/Loss"));
     806            6 :             xaccSplitSetMemo (gain_split, _("Realized Gain/Loss"));
     807              : 
     808              :             /* For the new transaction, set the split properties indicating
     809              :              * that this is the gains transaction that corresponds
     810              :              * to the gains source.
     811              :              */
     812            6 :             xaccTransBeginEdit (base_txn);
     813            6 :             qof_instance_set (QOF_INSTANCE (split),
     814            6 :                               "gains-split", xaccSplitGetGUID (lot_split),
     815              :                               nullptr);
     816            6 :             xaccTransCommitEdit (base_txn);
     817            6 :             qof_instance_set (QOF_INSTANCE (lot_split),
     818            6 :                               "gains-source", xaccSplitGetGUID (split),
     819              :                               nullptr);
     820              : 
     821              :         }
     822              :         else
     823              :         {
     824           19 :             trans = lot_split->parent;
     825           19 :             gain_split = xaccSplitGetOtherSplit (lot_split);
     826              : 
     827              :             /* If the gains transaction has been edited so that it no longer has
     828              :                just two splits, ignore it and assume it's still correct. */
     829           19 :             if (!gain_split)
     830              :             {
     831            0 :                 new_gain_split = FALSE;
     832              :             }
     833              :             /* If the gain is already recorded correctly do nothing.  This is
     834              :              * more than just an optimization since this may be called during
     835              :              * gnc_book_partition_txn and depending on the order in which things
     836              :              * happen some splits may be in the wrong book at that time. */
     837           57 :             else if (split->gains_split == lot_split &&
     838           19 :                      lot_split->gains_split == split &&
     839           19 :                      gain_split->gains_split == split &&
     840           18 :                      gnc_numeric_equal (xaccSplitGetValue (lot_split), value) &&
     841           18 :                      gnc_numeric_zero_p (xaccSplitGetAmount (lot_split)) &&
     842           56 :                      gnc_numeric_equal (xaccSplitGetValue (gain_split), negvalue) &&
     843           18 :                      gnc_numeric_equal (xaccSplitGetAmount (gain_split), negvalue))
     844              :             {
     845           18 :                 new_gain_split = FALSE;
     846              :             }
     847              :             else
     848              :             {
     849            1 :                 new_gain_split = TRUE;
     850            1 :                 xaccTransBeginEdit (trans);
     851              : 
     852              :                 /* Make sure the existing gains trans has the correct currency,
     853              :                  * just in case someone screwed with it! */
     854            1 :                 if (FALSE == gnc_commodity_equiv(currency, trans->common_currency))
     855              :                 {
     856            0 :                     PWARN ("Resetting the transaction currency!");
     857            0 :                     xaccTransSetCurrency (trans, currency);
     858              :                 }
     859              :             }
     860              :         }
     861              : 
     862           25 :         if (new_gain_split)
     863              :         {
     864              :             /* Common to both */
     865            7 :             time64 time = xaccTransRetDatePosted (split->parent);
     866            7 :             xaccTransSetDatePostedSecs (trans, time);
     867            7 :             xaccTransSetDateEnteredSecs (trans, gnc_time (nullptr));
     868              : 
     869            7 :             xaccSplitSetAmount (lot_split, zero);
     870            7 :             xaccSplitSetValue (lot_split, value);
     871              : 
     872            7 :             xaccSplitSetAmount (gain_split, negvalue);
     873            7 :             xaccSplitSetValue (gain_split, negvalue);
     874              : 
     875              :             /* Some short-cuts to help avoid the above property lookup. */
     876            7 :             split->gains = GAINS_STATUS_CLEAN;
     877            7 :             split->gains_split = lot_split;
     878            7 :             lot_split->gains = GAINS_STATUS_GAINS;
     879            7 :             lot_split->gains_split = split;
     880            7 :             gain_split->gains = GAINS_STATUS_GAINS;
     881            7 :             gain_split->gains_split = split;
     882              : 
     883              :             /* Do this last since it may generate an event that will call us
     884              :                recursively. */
     885            7 :             gnc_lot_add_split (lot, lot_split);
     886              : 
     887            7 :             xaccTransCommitEdit (trans);
     888              :         }
     889              :     }
     890           31 :     LEAVE ("(lot=%s)", gnc_lot_get_title(lot));
     891              : }
     892              : 
     893              : /* ============================================================== */
     894              : 
     895              : gnc_numeric
     896            0 : xaccLotFreeSplitCapGain(Split *split, GNCLot *lot)
     897              : {
     898            0 :     g_return_val_if_fail (split && lot && !split->lot, gnc_numeric_zero());
     899              : 
     900            0 :     ENTER ("(split=%p lot=%s)", split, gnc_lot_get_title(lot));
     901              : 
     902            0 :     auto pcy = gnc_account_get_policy (gnc_lot_get_account (lot));
     903            0 :     auto currency = split->parent->common_currency;
     904              : 
     905              :     /* Not possible to have gains if the transaction currency and
     906              :      * account commodity are identical. */
     907            0 :     if (gnc_commodity_equal (currency,
     908            0 :                              xaccAccountGetCommodity(split->acc)))
     909              :     {
     910            0 :         LEAVE ("Currency transfer, gains not possible, returning.");
     911            0 :         return gnc_numeric_zero();
     912              :     }
     913              : 
     914            0 :     auto *esplit = gnc_lot_get_earliest_split (lot);
     915            0 :     if (!esplit)
     916              :     {
     917            0 :         LEAVE ("Lot is empty, returning.");
     918            0 :         return gnc_numeric_zero();
     919              :     }
     920              : 
     921            0 :     if (xaccTransGetDate (xaccSplitGetParent (esplit)) >= xaccTransGetDate (xaccSplitGetParent (split))) 
     922              :     {
     923            0 :         LEAVE ("Split is too early, returning.");
     924            0 :         return gnc_numeric_zero();
     925              :     }
     926              : 
     927            0 :     if (xaccSplitIsStockSplit (split))
     928              :     {
     929            0 :         LEAVE ("Stock split split, returning.");
     930            0 :         return gnc_numeric_zero();
     931              :     }
     932              : 
     933              :     /* If amount is zero, there's nothing to do! Amount-zero splits
     934              :      * may exist if users attempted to manually record gains. */
     935            0 :     if (gnc_numeric_zero_p (split->amount)) return gnc_numeric_zero();
     936              : 
     937              :     /* If we got to here, then the gains really do need to be recomputed.
     938              :      * So start working things. */
     939              : 
     940              :     /* Get the amount and value in this lot at the time of this transaction. */
     941              :     gnc_numeric lot_amount, lot_value;
     942            0 :     gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
     943              : 
     944              :     gnc_numeric opening_amount, opening_value;
     945              :     gnc_commodity *opening_currency;
     946            0 :     pcy->PolicyGetLotOpening (pcy, lot, &opening_amount, &opening_value,
     947              :                               &opening_currency);
     948              : 
     949              :     /* Check to make sure the lot-opening currency and this split
     950              :      * use the same currency */
     951            0 :     if (FALSE == gnc_commodity_equiv (currency, opening_currency))
     952              :     {
     953              :         /* OK, the purchase and the sale were made in different currencies.
     954              :          * I don't know how to compute cap gains for that.  This is not
     955              :          * an error. Just punt, silently.
     956              :          */
     957            0 :         LEAVE ("Can't compute gains, mismatched commodities!");
     958            0 :         return gnc_numeric_zero();
     959              :     }
     960              : 
     961              :     /* Opening amount should be larger (or equal) to current split,
     962              :      * and it should be of the opposite sign.
     963              :      * XXX This should really be a part of a scrub routine that
     964              :      * cleans up the lot, before we get at it!
     965              :      */
     966            0 :     if (0 > gnc_numeric_compare (gnc_numeric_abs(lot_amount),
     967              :                                  gnc_numeric_abs(xaccSplitGetAdjustedAmount (split))))
     968              :     {
     969            0 :         for (auto *n = gnc_lot_get_split_list(lot); n; n = n->next)
     970              :         {
     971            0 :             auto *s = GNC_SPLIT(n->data);
     972            0 :             PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
     973              :         }
     974            0 :         return gnc_numeric_zero();
     975              :     }
     976            0 :     if ( (gnc_numeric_negative_p(lot_amount) ||
     977            0 :             gnc_numeric_positive_p(xaccSplitGetAdjustedAmount (split))) &&
     978            0 :             (gnc_numeric_positive_p(lot_amount) ||
     979            0 :              gnc_numeric_negative_p(xaccSplitGetAdjustedAmount (split))))
     980              :     {
     981            0 :         for (auto *n = gnc_lot_get_split_list(lot); n; n = n->next)
     982              :         {
     983            0 :             auto *s = GNC_SPLIT(n->data);
     984            0 :             PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
     985              :         }
     986            0 :         return gnc_numeric_zero();
     987              :     }
     988              : 
     989              :     /* The cap gains is the difference between the basis prior to the
     990              :      * current split, and the current split, pro-rated for an equal
     991              :      * amount of shares.
     992              :      * i.e. purchase_price = lot_value / lot_amount
     993              :      * cost_basis = purchase_price * current_split_amount
     994              :      * cap_gain = current_split_value - cost_basis
     995              :      */
     996              :     /* Fraction of the lot that this split represents: */
     997            0 :     auto frac = gnc_numeric_div (xaccSplitGetAdjustedAmount (split), lot_amount,
     998              :                             GNC_DENOM_AUTO,
     999              :                             GNC_HOW_DENOM_REDUCE);
    1000              :     /* Basis for this split: */
    1001            0 :     auto value = gnc_numeric_mul (frac, lot_value,
    1002              :                              gnc_numeric_denom(opening_value),
    1003              :                              GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP);
    1004              :     /* Capital gain for this split: */
    1005            0 :     value = gnc_numeric_sub (value, split->value,
    1006              :                              GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
    1007              : 
    1008            0 :     PINFO ("Open amt=%s val=%s;  split adj amt=%s val=%s; gains=%s\n",
    1009              :            gnc_num_dbg_to_string (lot_amount),
    1010              :            gnc_num_dbg_to_string (lot_value),
    1011              :            gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
    1012              :            gnc_num_dbg_to_string (split->value),
    1013              :            gnc_num_dbg_to_string (value));
    1014              : 
    1015            0 :     if (gnc_numeric_check (value))
    1016              :     {
    1017            0 :         PERR ("Numeric overflow during gains calculation\n"
    1018              :               "Acct=%s Txn=%s\n"
    1019              :               "\tOpen amt=%s val=%s\n\tsplit amt=%s val=%s\n\tgains=%s\n",
    1020              :               xaccAccountGetName(split->acc),
    1021              :               xaccTransGetDescription(split->parent),
    1022              :               gnc_num_dbg_to_string (lot_amount),
    1023              :               gnc_num_dbg_to_string (lot_value),
    1024              :               gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
    1025              :               gnc_num_dbg_to_string (split->value),
    1026              :               gnc_num_dbg_to_string (value));
    1027            0 :         return gnc_numeric_zero();
    1028              :     }
    1029              : 
    1030            0 :     LEAVE ("(lot=%s)", gnc_lot_get_title(lot));
    1031              : 
    1032            0 :     return value;
    1033              : }
    1034              : 
    1035              : /* ============================================================== */
    1036              : 
    1037              : gnc_numeric
    1038            1 : xaccSplitGetCapGains(Split * split)
    1039              : {
    1040            1 :     if (!split) return gnc_numeric_zero();
    1041            1 :     ENTER("(split=%p)", split);
    1042              : 
    1043            1 :     if (GAINS_STATUS_UNKNOWN == split->gains)
    1044            1 :         xaccSplitDetermineGainStatus(split);
    1045            1 :     if ((split->gains & GAINS_STATUS_A_VDIRTY) ||
    1046            0 :             (split->gains_split &&
    1047            0 :              (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
    1048              :     {
    1049            1 :         xaccSplitComputeCapGains (split, nullptr);
    1050              :     }
    1051              : 
    1052              :     /* If this is the source split, get the gains from the one
    1053              :      * that records the gains.  If this already is the gains split,
    1054              :      * its a no-op. */
    1055            1 :     if (!(GAINS_STATUS_GAINS & split->gains))
    1056              :     {
    1057              :         /* split = xaccSplitGetCapGainsSplit (split); */
    1058            1 :         split = split->gains_split;
    1059              :     }
    1060              : 
    1061            1 :     LEAVE("(split=%p)", split);
    1062            1 :     if (!split) return gnc_numeric_zero();
    1063              : 
    1064            0 :     return split->value;
    1065              : }
    1066              : 
    1067              : /* ============================================================== */
    1068              : 
    1069              : void
    1070           54 : xaccLotComputeCapGains (GNCLot *lot, Account *gain_acc)
    1071              : {
    1072              :     SplitList *node;
    1073              :     GNCPolicy *pcy;
    1074           54 :     gboolean is_dirty = FALSE;
    1075              : 
    1076              :     /* Note: if the value of the 'opening' split(s) has changed,
    1077              :      * then the cap gains are changed. To capture this, we need
    1078              :      * to mark all splits dirty if the opening splits are dirty. */
    1079              : 
    1080           54 :     ENTER("(lot=%p)", lot);
    1081           54 :     pcy = gnc_account_get_policy(gnc_lot_get_account(lot));
    1082          117 :     for (node = gnc_lot_get_split_list(lot); node; node = node->next)
    1083              :     {
    1084           63 :         Split *s = GNC_SPLIT(node->data);
    1085           63 :         if (pcy->PolicyIsOpeningSplit(pcy, lot, s))
    1086              :         {
    1087           54 :             if (GAINS_STATUS_UNKNOWN == s->gains)
    1088           54 :                 xaccSplitDetermineGainStatus(s);
    1089           54 :             if (s->gains & GAINS_STATUS_VDIRTY)
    1090              :             {
    1091           54 :                 is_dirty = TRUE;
    1092           54 :                 s->gains &= ~GAINS_STATUS_VDIRTY;
    1093              :             }
    1094              :         }
    1095              :     }
    1096              : 
    1097           54 :     if (is_dirty)
    1098              :     {
    1099          117 :         for (node = gnc_lot_get_split_list(lot); node; node = node->next)
    1100              :         {
    1101           63 :             Split *s = GNC_SPLIT(node->data);
    1102           63 :             s->gains |= GAINS_STATUS_VDIRTY;
    1103              :         }
    1104              :     }
    1105              : 
    1106          123 :     for (node = gnc_lot_get_split_list(lot); node; node = node->next)
    1107              :     {
    1108           69 :         Split *s = GNC_SPLIT(node->data);
    1109           69 :         xaccSplitComputeCapGains (s, gain_acc);
    1110              :     }
    1111           54 :     LEAVE("(lot=%p)", lot);
    1112           54 : }
    1113              : 
    1114              : /* =========================== END OF FILE ======================= */
        

Generated by: LCOV version 2.0-1