LCOV - code coverage report
Current view: top level - libgnucash/engine - Scrub2.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 55.2 % 143 79
Test Date: 2026-11-02 18:19:43 Functions: 75.0 % 8 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /********************************************************************\
       2              :  * Scrub2.c -- Convert Stock Accounts to use Lots                   *
       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 Scrub2.c
      23              :  *  @brief Utilities to Convert Stock Accounts to use Lots
      24              :  *  @author Created by Linas Vepstas March 2003
      25              :  *  @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
      26              :  *
      27              :  * Provides a set of functions and utilities for checking and
      28              :  * repairing ('scrubbing clean') the usage of Lots and lot balances
      29              :  * in stock and commodity accounts.  Broken lots are repaired using
      30              :  * the accounts specific accounting policy (probably FIFO).
      31              :  */
      32              : 
      33              : #include <config.h>
      34              : 
      35              : #include <glib.h>
      36              : 
      37              : #include "qof.h"
      38              : #include "Account.h"
      39              : #include "AccountP.hpp"
      40              : #include "Account.hpp"
      41              : #include "Transaction.h"
      42              : #include "TransactionP.hpp"
      43              : #include "Scrub2.h"
      44              : #include "cap-gains.h"
      45              : #include "gnc-engine.h"
      46              : #include "gncInvoice.h"
      47              : #include "gnc-lot.h"
      48              : #include "policy-p.h"
      49              : 
      50              : static QofLogModule log_module = GNC_MOD_LOT;
      51              : 
      52              : /* ============================================================== */
      53              : /** Loop over all splits, and make sure that every split
      54              :  * belongs to some lot.  If a split does not belong to
      55              :  * any lots, poke it into one.
      56              :  */
      57              : 
      58              : void
      59            5 : xaccAccountAssignLots (Account *acc)
      60              : {
      61            5 :     if (!acc) return;
      62              : 
      63            5 :     ENTER ("acc=%s", xaccAccountGetName(acc));
      64            5 :     xaccAccountBeginEdit (acc);
      65              : 
      66            8 : restart_loop:
      67          148 :     for (auto split : xaccAccountGetSplits (acc))
      68              :     {
      69              :         /* If already in lot, then no-op */
      70          135 :         if (split->lot) continue;
      71              :         
      72              :         /* Skip stock splits */
      73           79 :         if (xaccSplitIsStockSplit(split)) continue;
      74              : 
      75              :         /* Skip voided transactions */
      76           98 :         if (gnc_numeric_zero_p (split->amount) &&
      77           38 :                 xaccTransGetVoidStatus(split->parent)) continue;
      78              : 
      79           60 :         if (xaccSplitAssign (split)) goto restart_loop;
      80              :     }
      81            5 :     xaccAccountCommitEdit (acc);
      82            5 :     LEAVE ("acc=%s", xaccAccountGetName(acc));
      83              : }
      84              : 
      85              : /* ============================================================== */
      86              : 
      87              : /** The xaccLotFill() routine attempts to assign splits to the
      88              :  *  indicated lot until the lot balance goes to zero, or until
      89              :  *  there are no suitable (i.e. unassigned) splits left in the
      90              :  *  account.  It uses the default accounting policy to choose
      91              :  *  the splits to fill out the lot.
      92              :  */
      93              : 
      94              : void
      95           48 : xaccLotFill (GNCLot *lot)
      96              : {
      97              :     Account *acc;
      98              :     Split *split;
      99              :     GNCPolicy *pcy;
     100              : 
     101           48 :     if (!lot) return;
     102           48 :     acc = gnc_lot_get_account(lot);
     103           48 :     pcy = gnc_account_get_policy(acc);
     104              : 
     105           48 :     ENTER ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
     106              : 
     107              :     /* If balance already zero, we have nothing to do. */
     108           48 :     if (gnc_lot_is_closed (lot))
     109              :     {
     110            0 :         LEAVE ("Lot Closed (lot=%s, acc=%s)", gnc_lot_get_title(lot),
     111              :                xaccAccountGetName(acc));
     112            0 :         return;
     113              :     }
     114           48 :     split = pcy->PolicyGetSplit (pcy, lot);
     115           48 :     if (!split)
     116              :     {
     117           48 :         LEAVE ("No Split (lot=%s, acc=%s)", gnc_lot_get_title(lot),
     118              :                xaccAccountGetName(acc));
     119           48 :         return;   /* Handle the common case */
     120              :     }
     121              : 
     122              :     /* Reject stock split transactions */
     123            0 :     if (xaccSplitIsStockSplit(split))
     124              :     {
     125            0 :         LEAVE ("Stock split transaction (lot=%s, acc=%s)",
     126              :            gnc_lot_get_title(lot), xaccAccountGetName(acc));
     127            0 :         return;
     128              :     }
     129              : 
     130              :     /* Reject voided transactions */
     131            0 :     if (gnc_numeric_zero_p(split->amount) &&
     132            0 :             xaccTransGetVoidStatus(split->parent))
     133              :     {
     134            0 :         LEAVE ("Voided transaction (lot=%s, acc=%s)",
     135              :                gnc_lot_get_title(lot), xaccAccountGetName(acc));
     136            0 :         return;
     137              :     }
     138              : 
     139            0 :     xaccAccountBeginEdit (acc);
     140              : 
     141              :     /* Loop until we've filled up the lot, (i.e. till the
     142              :      * balance goes to zero) or there are no splits left.  */
     143              :     while (1)
     144              :     {
     145              :         Split *subsplit;
     146              : 
     147            0 :         subsplit = xaccSplitAssignToLot (split, lot);
     148            0 :         if (subsplit == split)
     149              :         {
     150            0 :             PERR ("Accounting Policy gave us a split that "
     151              :                   "doesn't fit into this lot\n"
     152              :                   "lot baln=%s, isclosed=%d, aplit amt=%s",
     153              :                   gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
     154              :                   gnc_lot_is_closed (lot),
     155              :                   gnc_num_dbg_to_string (split->amount));
     156            0 :             break;
     157              :         }
     158              : 
     159            0 :         if (gnc_lot_is_closed (lot)) break;
     160              : 
     161            0 :         split = pcy->PolicyGetSplit (pcy, lot);
     162            0 :         if (!split) break;
     163            0 :     }
     164            0 :     xaccAccountCommitEdit (acc);
     165            0 :     LEAVE ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
     166              : }
     167              : 
     168              : /* ============================================================== */
     169              : 
     170              : void
     171           54 : xaccLotScrubDoubleBalance (GNCLot *lot)
     172              : {
     173           54 :     gnc_commodity *currency = nullptr;
     174              :     SplitList *snode;
     175              :     GList *node;
     176           54 :     gnc_numeric zero = gnc_numeric_zero();
     177           54 :     gnc_numeric value = zero;
     178              : 
     179          101 :     if (!lot) return;
     180              : 
     181           54 :     ENTER ("lot=%s", gnc_lot_get_title(lot));
     182              : 
     183          123 :     for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
     184              :     {
     185           69 :         Split *s = GNC_SPLIT(snode->data);
     186           69 :         xaccSplitComputeCapGains (s, nullptr);
     187              :     }
     188              : 
     189              :     /* We double-check only closed lots */
     190           54 :     if (FALSE == gnc_lot_is_closed (lot))
     191              :     {
     192           47 :         LEAVE ("lot=%s is closed", gnc_lot_get_title(lot));
     193           47 :         return;
     194              :     }
     195              : 
     196           25 :     for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
     197              :     {
     198           18 :         Split *s = GNC_SPLIT(snode->data);
     199           18 :         Transaction *trans = s->parent;
     200              : 
     201              :         /* Check to make sure all splits in the lot have a common currency */
     202           18 :         if (nullptr == currency)
     203              :         {
     204            7 :             currency = trans->common_currency;
     205              :         }
     206           18 :         if (FALSE == gnc_commodity_equiv (currency, trans->common_currency))
     207              :         {
     208              :             /* This lot has mixed currencies. Can't double-balance.
     209              :              * Silently punt */
     210            0 :             PWARN ("Lot with multiple currencies:\n"
     211              :                    "\ttrans=%s curr=%s", xaccTransGetDescription(trans),
     212              :                    gnc_commodity_get_fullname(trans->common_currency));
     213            0 :             break;
     214              :         }
     215              : 
     216              :         /* Now, total up the values */
     217           18 :         value = gnc_numeric_add (value, xaccSplitGetValue (s),
     218              :                                  GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
     219           18 :         PINFO ("Split=%p value=%s Accum Lot value=%s", s,
     220              :                gnc_num_dbg_to_string (s->value),
     221              :                gnc_num_dbg_to_string (value));
     222              : 
     223              :     }
     224              : 
     225            7 :     if (FALSE == gnc_numeric_equal (value, zero))
     226              :     {
     227              :         /* Unhandled error condition. Not sure what to do here,
     228              :          * Since the ComputeCapGains should have gotten it right.
     229              :          * I suppose there might be small rounding errors, a penny or two,
     230              :          * the ideal thing would to figure out why there's a rounding
     231              :          * error, and fix that.
     232              :          */
     233            0 :         PERR ("Closed lot fails to double-balance !! lot value=%s",
     234              :               gnc_num_dbg_to_string (value));
     235            0 :         for (node = gnc_lot_get_split_list(lot); node; node = node->next)
     236              :         {
     237            0 :             Split *s = GNC_SPLIT(node->data);
     238            0 :             PERR ("s=%p amt=%s val=%s", s,
     239              :                   gnc_num_dbg_to_string(s->amount),
     240              :                   gnc_num_dbg_to_string(s->value));
     241              :         }
     242              :     }
     243              : 
     244            7 :     LEAVE ("lot=%s", gnc_lot_get_title(lot));
     245              : }
     246              : 
     247              : /* ================================================================= */
     248              : 
     249              : static inline gboolean
     250          114 : is_subsplit (Split *split)
     251              : {
     252              : 
     253              :     /* generic stop-progress conditions */
     254          114 :     if (!split) return FALSE;
     255          114 :     g_return_val_if_fail (split->parent, FALSE);
     256              : 
     257              :     /* If there are no sub-splits, then there's nothing to do. */
     258          114 :     return xaccSplitHasPeers (split);
     259              : }
     260              : 
     261              : /* ================================================================= */
     262              : 
     263              : 
     264              : /* ================================================================= */
     265              : 
     266              : /* Remove the guid of b from a.  Note that a may not contain the guid
     267              :  * of b, (and v.v.) in which case, it will contain other guids which
     268              :  * establish the links. So merge them back in. */
     269              : 
     270              : static void
     271            0 : remove_guids (Split *sa, Split *sb)
     272              : {
     273            0 :      xaccSplitRemovePeerSplit (sa, sb);
     274            0 :      xaccSplitRemovePeerSplit (sb, sa);
     275            0 :      xaccSplitMergePeerSplits (sa, sb);
     276            0 : }
     277              : 
     278              : /* The merge_splits() routine causes the amount & value of sb
     279              :  * to be merged into sa; it then destroys sb.  It also performs
     280              :  * some other misc cleanup */
     281              : 
     282              : static void
     283            0 : merge_splits (Split *sa, Split *sb)
     284              : {
     285              :     Account *act;
     286              :     Transaction *txn;
     287              :     gnc_numeric amt, val;
     288              : 
     289            0 :     act = xaccSplitGetAccount (sb);
     290            0 :     xaccAccountBeginEdit (act);
     291              : 
     292            0 :     txn = sa->parent;
     293            0 :     xaccTransBeginEdit (txn);
     294              : 
     295              :     /* Remove the guid of sb from the 'gemini' of sa */
     296            0 :     remove_guids (sa, sb);
     297              : 
     298              :     /* Add amount of sb into sa, ditto for value. */
     299            0 :     amt = xaccSplitGetAmount (sa);
     300            0 :     amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
     301            0 :     xaccSplitSetAmount (sa, amt);
     302              : 
     303            0 :     val = xaccSplitGetValue (sa);
     304            0 :     val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
     305            0 :     xaccSplitSetValue (sa, val);
     306              : 
     307              :     /* Set reconcile to no; after this much violence,
     308              :      * no way its reconciled. */
     309            0 :     xaccSplitSetReconcile (sa, NREC);
     310              : 
     311              :     /* If sb has associated gains splits, trash them. */
     312            0 :     if ((sb->gains_split) &&
     313            0 :             (sb->gains_split->gains & GAINS_STATUS_GAINS))
     314              :     {
     315            0 :         Transaction *t = sb->gains_split->parent;
     316            0 :         xaccTransBeginEdit (t);
     317            0 :         xaccTransDestroy (t);
     318            0 :         xaccTransCommitEdit (t);
     319              :     }
     320              : 
     321              :     /* Finally, delete sb */
     322            0 :     xaccSplitDestroy(sb);
     323              : 
     324            0 :     xaccTransCommitEdit (txn);
     325            0 :     xaccAccountCommitEdit (act);
     326            0 : }
     327              : 
     328              : gboolean
     329          114 : xaccScrubMergeSubSplits (Split *split, gboolean strict)
     330              : {
     331          114 :     gboolean rc = FALSE;
     332              :     Transaction *txn;
     333              :     SplitList *node;
     334              :     GNCLot *lot;
     335              : 
     336          114 :     if (strict && (FALSE == is_subsplit (split))) return FALSE;
     337              : 
     338           10 :     txn = split->parent;
     339              : 
     340              :     // Don't mess with splits from an invoice transaction
     341              :     // Those are the responsibility of the business code
     342           10 :     if (gncInvoiceGetInvoiceFromTxn (txn)) return FALSE;
     343              : 
     344           10 :     lot = xaccSplitGetLot (split);
     345              : 
     346           10 :     ENTER ("(Lot=%s)", gnc_lot_get_title(lot));
     347           10 : restart:
     348           47 :     for (node = txn->splits; node; node = node->next)
     349              :     {
     350           37 :         Split *s = GNC_SPLIT(node->data);
     351           37 :         if (xaccSplitGetLot (s) != lot) continue;
     352           11 :         if (s == split) continue;
     353            1 :         if (qof_instance_get_destroying(s)) continue;
     354              : 
     355              :         // Don't mess with splits from an invoice transaction
     356              :         // Those are the responsibility of the business code
     357            1 :         if (gncInvoiceGetInvoiceFromTxn (s->parent)) return FALSE;
     358              : 
     359            1 :         if (strict)
     360              :         {
     361              :             /* OK, this split is in the same lot (and thus same account)
     362              :              * as the indicated split.  Make sure it is really a subsplit
     363              :              * of the split we started with.  It's possible to have two
     364              :              * splits in the same lot and transaction that are not subsplits
     365              :              * of each other, the test-period test suite does this, for
     366              :              * example.  Only worry about adjacent sub-splits.  By
     367              :              * repeatedly merging adjacent subsplits, we'll get the non-
     368              :              * adjacent ones too. */
     369            1 :             if (!xaccSplitIsPeerSplit (split, s))
     370            1 :                 continue;
     371              :         }
     372              : 
     373            0 :         merge_splits (split, s);
     374            0 :         rc = TRUE;
     375            0 :         goto restart;
     376              :     }
     377           10 :     if (rc && gnc_numeric_zero_p (split->amount))
     378              :     {
     379            0 :         time64 pdate = xaccTransGetDate (txn);
     380            0 :         gchar *pdatestr = gnc_ctime (&pdate);
     381            0 :         PWARN ("Result of merge has zero amt!");
     382            0 :         PWARN ("Transaction details - posted date %s - description %s", pdatestr, xaccTransGetDescription(txn));
     383            0 :         g_free (pdatestr);
     384              :     }
     385           10 :     LEAVE (" splits merged=%d", rc);
     386           10 :     return rc;
     387              : }
     388              : 
     389              : gboolean
     390          103 : xaccScrubMergeLotSubSplits (GNCLot *lot, gboolean strict)
     391              : {
     392          103 :     gboolean rc = FALSE;
     393              :     SplitList *node;
     394              : 
     395          103 :     if (!lot) return FALSE;
     396              : 
     397          103 :     ENTER (" ");
     398          103 : restart:
     399          217 :     for (node = gnc_lot_get_split_list(lot); node; node = node->next)
     400              :     {
     401          114 :         Split *s = GNC_SPLIT(node->data);
     402          114 :         if (!xaccScrubMergeSubSplits(s, strict)) continue;
     403              : 
     404            0 :         rc = TRUE;
     405            0 :         goto restart;
     406              :     }
     407          103 :     LEAVE (" splits merged=%d", rc);
     408          103 :     return rc;
     409              : }
     410              : 
     411              : /* =========================== END OF FILE ======================= */
        

Generated by: LCOV version 2.0-1