LCOV - code coverage report
Current view: top level - libgnucash/engine - Scrub2.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 56.1 % 139 78
Test Date: 2025-02-07 16:25:45 Functions: 75.0 % 8 6
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

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

Generated by: LCOV version 2.0-1