LCOV - code coverage report
Current view: top level - libgnucash/engine - ScrubBusiness.c (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 0.0 % 338 0
Test Date: 2025-02-07 16:25:45 Functions: 0.0 % 14 0
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     Line data    Source code
       1                 :             : /********************************************************************\
       2                 :             :  * ScrubBusiness.h -- Cleanup functions for the business objects.   *
       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 ScrubBusiness.h
      23                 :             :  *  @brief Cleanup functions for business objects
      24                 :             :  *  @author Created by Geert Janssens August 2014
      25                 :             :  *  @author Copyright (c) 2014 Geert Janssens <geert@kobaltwit.be>
      26                 :             :  *
      27                 :             :  * Provides the high-level API for checking and repairing ('scrubbing
      28                 :             :  * clean') the various data objects used by the business functions.*/
      29                 :             : 
      30                 :             : #include <config.h>
      31                 :             : 
      32                 :             : #include <glib.h>
      33                 :             : #include <glib/gi18n.h>
      34                 :             : 
      35                 :             : #include "gnc-engine.h"
      36                 :             : #include "gnc-lot.h"
      37                 :             : #include "policy-p.h"
      38                 :             : #include "Account.h"
      39                 :             : #include "gncInvoice.h"
      40                 :             : #include "gncInvoiceP.h"
      41                 :             : #include "Scrub.h"
      42                 :             : #include "Scrub2.h"
      43                 :             : #include "ScrubBusiness.h"
      44                 :             : #include "Transaction.h"
      45                 :             : 
      46                 :             : #undef G_LOG_DOMAIN
      47                 :             : #define G_LOG_DOMAIN "gnc.engine.scrub"
      48                 :             : 
      49                 :             : static QofLogModule log_module = G_LOG_DOMAIN;
      50                 :             : 
      51                 :             : static void
      52                 :           0 : gncScrubInvoiceState (GNCLot *lot)
      53                 :             : {
      54                 :           0 :     SplitList *ls_iter = NULL;
      55                 :           0 :     GncInvoice *invoice = NULL;
      56                 :           0 :     GncInvoice *lot_invoice = gncInvoiceGetInvoiceFromLot (lot);
      57                 :             : 
      58                 :           0 :     for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next)
      59                 :             :     {
      60                 :           0 :         Split *split = ls_iter->data;
      61                 :           0 :         Transaction *txn = NULL; // ll_txn = "Lot Link Transaction"
      62                 :             : 
      63                 :           0 :         if (!split)
      64                 :           0 :             continue; // next scrub lot split
      65                 :             : 
      66                 :           0 :         txn = xaccSplitGetParent (split);
      67                 :           0 :         invoice = gncInvoiceGetInvoiceFromTxn (txn);
      68                 :           0 :         if (invoice)
      69                 :           0 :             break;
      70                 :             : 
      71                 :             :     }
      72                 :             : 
      73                 :           0 :     if (invoice != lot_invoice)
      74                 :             :     {
      75                 :           0 :         PINFO("Correcting lot invoice associaton. Old invoice: %p, new invoice %p", lot_invoice, invoice);
      76                 :           0 :         gncInvoiceDetachFromLot(lot);
      77                 :             : 
      78                 :           0 :         if (invoice)
      79                 :           0 :             gncInvoiceAttachToLot (invoice, lot);
      80                 :             :         else
      81                 :           0 :             gncOwnerAttachToLot (gncInvoiceGetOwner(lot_invoice), lot);
      82                 :             :     }
      83                 :           0 : }
      84                 :             : 
      85                 :             : // A helper function that takes two splits. If the splits are  of opposite sign
      86                 :             : // it reduces the biggest split to have the same value (but with opposite sign)
      87                 :             : // of the smaller split.
      88                 :             : // To make sure everything still continues to balance in addition a "remainder" split
      89                 :             : // will be created that will be added to the same lot and transaction as the biggest
      90                 :             : // split.
      91                 :             : // The opposite sign restriction is because that's the only scenario that makes sense
      92                 :             : // in the context of scrubbing business lots below.
      93                 :             : // If we created new splits, return TRUE, otherwise FALSE
      94                 :           0 : static gboolean reduce_biggest_split (Split *splitA, Split *splitB)
      95                 :             : {
      96                 :           0 :     gnc_numeric valA = xaccSplitGetValue (splitA);
      97                 :           0 :     gnc_numeric valB = xaccSplitGetValue (splitB);
      98                 :             : 
      99                 :           0 :     if (gnc_numeric_compare (gnc_numeric_abs (valA), gnc_numeric_abs (valB)) >= 0)
     100                 :           0 :         return gncOwnerReduceSplitTo (splitA, gnc_numeric_neg (valB));
     101                 :             :     else
     102                 :           0 :         return gncOwnerReduceSplitTo (splitB, gnc_numeric_neg (valA));
     103                 :             : }
     104                 :             : 
     105                 :             : // Attempt to eliminate or reduce the lot link splits (ll_*_split)
     106                 :             : // between from_lot and to_lot. To do so this function will attempt
     107                 :             : // to move a payment split from from_lot to to_lot in order to
     108                 :             : // balance the lot link split that will be deleted.
     109                 :             : // To ensure everything remains balanced at most
     110                 :             : // min (val-ll-*-split, val-pay-split) (in absolute values) can be moved.
     111                 :             : // If any split involved has a larger value, it will be split in two
     112                 :             : // and only the part matching the other splits' value will be used.
     113                 :             : // The leftover splits are kept in the respective transactions/lots.
     114                 :             : // A future scrub action can still act on those if needed.
     115                 :             : //
     116                 :             : // Note that this function assumes that ll_from_split and ll_to_split are
     117                 :             : // of opposite sign. The calling function should check this.
     118                 :             : 
     119                 :             : static gboolean
     120                 :           0 : scrub_other_link (GNCLot *from_lot, Split *ll_from_split,
     121                 :             :                   GNCLot *to_lot,   Split *ll_to_split)
     122                 :             : {
     123                 :             :     Split *real_from_split; // This refers to the split in the payment lot representing the payment itself
     124                 :           0 :     gboolean modified = FALSE;
     125                 :             :     gnc_numeric real_from_val;
     126                 :           0 :     gnc_numeric from_val = xaccSplitGetValue (ll_from_split);
     127                 :           0 :     gnc_numeric to_val = xaccSplitGetValue (ll_to_split);
     128                 :           0 :     Transaction *ll_txn = xaccSplitGetParent (ll_to_split);
     129                 :             : 
     130                 :             :     // Per iteration we can only scrub at most min (val-doc-split, val-pay-split)
     131                 :             :     // So set the ceiling for finding a potential offsetting split in the lot
     132                 :           0 :     if (gnc_numeric_compare (gnc_numeric_abs (from_val), gnc_numeric_abs (to_val)) >= 0)
     133                 :           0 :         from_val = gnc_numeric_neg (to_val);
     134                 :             : 
     135                 :             :     // Next we have to find the original payment split so we can
     136                 :             :     // add (part of) it to the document lot
     137                 :           0 :     real_from_split = gncOwnerFindOffsettingSplit (from_lot, from_val);
     138                 :           0 :     if (!real_from_split)
     139                 :           0 :         return FALSE; // No usable split in the payment lot
     140                 :             : 
     141                 :             :     // We now have found 3 splits involved in the scrub action:
     142                 :             :     // 2 lot link splits which we want to reduce
     143                 :             :     // 1 other split to move into the original lot instead of the lot link split
     144                 :             :     // As said only value of the split can be offset.
     145                 :             :     // So split the bigger ones in two if needed and continue with equal valued splits only
     146                 :             :     // The remainder is added to the lot link transaction and the lot to keep everything balanced
     147                 :             :     // and will be processed in a future iteration
     148                 :           0 :     modified = reduce_biggest_split (ll_from_split, ll_to_split);
     149                 :           0 :     modified |= reduce_biggest_split (real_from_split, ll_from_split);
     150                 :           0 :     modified |= reduce_biggest_split (ll_from_split, ll_to_split);
     151                 :             : 
     152                 :             :     // At this point ll_to_split and real_from_split should have the same value
     153                 :             :     // If not, flag a warning and skip to the next iteration
     154                 :           0 :     to_val        = xaccSplitGetValue (ll_to_split);
     155                 :           0 :     real_from_val = xaccSplitGetValue (real_from_split);
     156                 :           0 :     if (!gnc_numeric_equal (real_from_val, to_val))
     157                 :             :     {
     158                 :             :         // This is unexpected - write a warning message and skip this split
     159                 :           0 :         PWARN("real_from_val (%s) and to_val (%s) differ. "
     160                 :             :               "This is unexpected! Skip scrubbing of real_from_split %p against ll_to_split %p.",
     161                 :             :               gnc_numeric_to_string (real_from_val), // gnc_numeric_denom (real_from_val),
     162                 :             :               gnc_numeric_to_string (to_val), // gnc_numeric_denom (to_val),
     163                 :             :               real_from_split, ll_to_split);
     164                 :           0 :         return modified;
     165                 :             :     }
     166                 :             : 
     167                 :             :     // Now do the actual split dance
     168                 :             :     // - move real payment split to doc lot
     169                 :             :     // - delete both lot link splits from the lot link transaction
     170                 :           0 :     gnc_lot_add_split (to_lot, real_from_split);
     171                 :           0 :     xaccTransBeginEdit (ll_txn);
     172                 :           0 :     xaccSplitDestroy (ll_to_split);
     173                 :           0 :     xaccSplitDestroy (ll_from_split);
     174                 :           0 :     xaccTransCommitEdit (ll_txn);
     175                 :             : 
     176                 :             :     // Cleanup the lots
     177                 :           0 :     xaccScrubMergeLotSubSplits (to_lot, FALSE);
     178                 :           0 :     xaccScrubMergeLotSubSplits (from_lot, FALSE);
     179                 :             : 
     180                 :           0 :     return TRUE; // We did change splits/transactions/lots...
     181                 :             : }
     182                 :             : 
     183                 :             : static gboolean
     184                 :           0 : gncScrubLotLinks (GNCLot *scrub_lot)
     185                 :             : {
     186                 :           0 :     gboolean modified = FALSE, restart_needed = FALSE;
     187                 :           0 :     SplitList *sls_iter = NULL;
     188                 :             : 
     189                 :           0 : scrub_start:
     190                 :           0 :     restart_needed = FALSE;
     191                 :             : 
     192                 :             :     // Iterate over all splits in the lot
     193                 :           0 :     for (sls_iter = gnc_lot_get_split_list (scrub_lot); sls_iter; sls_iter = sls_iter->next)
     194                 :             :     {
     195                 :           0 :         Split *sl_split = sls_iter->data;
     196                 :           0 :         Transaction *ll_txn = NULL; // ll_txn = "Lot Link Transaction"
     197                 :           0 :         SplitList *lts_iter = NULL;
     198                 :             : 
     199                 :           0 :         if (!sl_split)
     200                 :           0 :             continue; // next scrub lot split
     201                 :             : 
     202                 :           0 :         ll_txn = xaccSplitGetParent (sl_split);
     203                 :             : 
     204                 :           0 :         if (!ll_txn)
     205                 :             :         {
     206                 :             :             // Ooops - the split doesn't belong to any transaction !
     207                 :             :             // This is not expected so issue a warning and continue with next split
     208                 :           0 :             PWARN("Encountered a split in a business lot that's not part of any transaction. "
     209                 :             :                   "This is unexpected! Skipping split %p.", sl_split);
     210                 :           0 :             continue;
     211                 :             :         }
     212                 :             : 
     213                 :             :         // Don't scrub invoice type transactions
     214                 :           0 :         if (xaccTransGetTxnType (ll_txn) == TXN_TYPE_INVOICE)
     215                 :           0 :             continue; // next scrub lot split
     216                 :             : 
     217                 :             :         // Empty splits can be immediately removed from the list.
     218                 :           0 :         if (gnc_numeric_zero_p (xaccSplitGetValue (sl_split)))
     219                 :           0 :         {
     220                 :           0 :             GList *tmp_iter = sls_iter->prev;
     221                 :           0 :             PINFO("Removing 0-value split from the lot.");
     222                 :             : 
     223                 :           0 :             if (xaccTransGetReadOnly(xaccSplitGetParent(sl_split)))
     224                 :           0 :                 gnc_lot_remove_split (scrub_lot, sl_split);
     225                 :             :             else
     226                 :           0 :                 xaccSplitDestroy (sl_split);
     227                 :             : 
     228                 :           0 :             sls_iter = tmp_iter;
     229                 :           0 :             if (!sls_iter)
     230                 :           0 :                 goto scrub_start; // Otherwise sls_iter->next will crash
     231                 :           0 :             continue;
     232                 :             :         }
     233                 :             : 
     234                 :             :         // Iterate over all splits in the lot link transaction
     235                 :           0 :         for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next)
     236                 :             :         {
     237                 :           0 :             Split *ll_txn_split = lts_iter->data; // These all refer to splits in the lot link transaction
     238                 :           0 :             GNCLot *remote_lot = NULL; // lot at the other end of the lot link transaction
     239                 :             :             gboolean sl_is_doc_lot, rl_is_doc_lot;
     240                 :             : 
     241                 :           0 :             if (!ll_txn_split)
     242                 :           0 :                 continue; // next lot link transaction split
     243                 :             : 
     244                 :             :             // Skip the split in the lot we're currently scrubbing
     245                 :           0 :             if (sl_split == ll_txn_split)
     246                 :           0 :                 continue; // next lot link transaction split
     247                 :             : 
     248                 :             :             // Skip empty other splits. They'll be scrubbed in the outer for loop later
     249                 :           0 :             if (gnc_numeric_zero_p (xaccSplitGetValue (ll_txn_split)) ||
     250                 :           0 :                     gnc_numeric_zero_p(xaccSplitGetValue (ll_txn_split)))
     251                 :           0 :                 continue;
     252                 :             : 
     253                 :             :             // Only splits of opposite signed values can be scrubbed
     254                 :           0 :             if (gnc_numeric_positive_p (xaccSplitGetValue (sl_split)) ==
     255                 :           0 :                 gnc_numeric_positive_p (xaccSplitGetValue (ll_txn_split)))
     256                 :           0 :                 continue; // next lot link transaction split
     257                 :             : 
     258                 :             :             // We can only scrub if the other split is in a lot as well
     259                 :             :             // Link transactions always have their other split in another lot
     260                 :             :             // however ordinary payment transactions may not
     261                 :           0 :             remote_lot = xaccSplitGetLot (ll_txn_split);
     262                 :           0 :             if (!remote_lot)
     263                 :           0 :                 continue;
     264                 :             : 
     265                 :           0 :             sl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (scrub_lot) != NULL);
     266                 :           0 :             rl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (remote_lot) != NULL);
     267                 :             : 
     268                 :             :             // Depending on the type of lots we're comparing, we need different actions
     269                 :             :             // - Two document lots (an invoice and a credit note):
     270                 :             :             //   Special treatment - look for all document lots linked via ll_txn
     271                 :             :             //   and update the memo to be of more use to the users.
     272                 :             :             // - Two payment lots:
     273                 :             :             //   (Part of) the link will be eliminated and instead (part of)
     274                 :             :             //   one payment will be added to the other lot to keep the balance.
     275                 :             :             //   If the payments are not equal in abs value part of the bigger payment
     276                 :             :             //   will be moved to the smaller payment's lot.
     277                 :             :             // - A document and a payment lot:
     278                 :             :             //   (Part of) the link will be eliminated and instead (part of) the real
     279                 :             :             //   payment will be added to the document lot to handle the payment.
     280                 :           0 :             if (sl_is_doc_lot && rl_is_doc_lot)
     281                 :           0 :                 gncOwnerSetLotLinkMemo (ll_txn);
     282                 :           0 :             else if (!sl_is_doc_lot && !rl_is_doc_lot)
     283                 :           0 :             {
     284                 :           0 :                 gint cmp = gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (sl_split)),
     285                 :             :                                                 gnc_numeric_abs (xaccSplitGetValue (ll_txn_split)));
     286                 :           0 :                 if (cmp >= 0)
     287                 :           0 :                     restart_needed = scrub_other_link (scrub_lot, sl_split, remote_lot, ll_txn_split);
     288                 :             :                 else
     289                 :           0 :                     restart_needed = scrub_other_link (remote_lot, ll_txn_split, scrub_lot, sl_split);
     290                 :             :             }
     291                 :             :             else
     292                 :             :             {
     293                 :           0 :                 GNCLot *doc_lot = sl_is_doc_lot ? scrub_lot : remote_lot;
     294                 :           0 :                 GNCLot *pay_lot = sl_is_doc_lot ? remote_lot : scrub_lot;
     295                 :           0 :                 Split *ll_doc_split = sl_is_doc_lot ? sl_split : ll_txn_split;
     296                 :           0 :                 Split *ll_pay_split = sl_is_doc_lot ? ll_txn_split : sl_split;
     297                 :             :                 // Ok, let's try to move a payment from pay_lot to doc_lot
     298                 :           0 :                 restart_needed = scrub_other_link (pay_lot, ll_pay_split, doc_lot, ll_doc_split);
     299                 :             :             }
     300                 :             : 
     301                 :             :             // If we got here, the splits in our lot and ll_txn have been severely mixed up
     302                 :             :             // And our iterator lists are probably no longer valid
     303                 :             :             // So let's start over
     304                 :           0 :             if (restart_needed)
     305                 :             :             {
     306                 :           0 :                 modified = TRUE;
     307                 :           0 :                 goto scrub_start;
     308                 :             :             }
     309                 :             : 
     310                 :             :         }
     311                 :             :     }
     312                 :             : 
     313                 :           0 :     return modified;
     314                 :             : }
     315                 :             : 
     316                 :             : // Note this is a recursive function. It presumes the number of splits
     317                 :             : // in avail_splits is relatively low. With many splits the performance will
     318                 :             : // quickly degrade.
     319                 :             : // Careful: this function assumes all splits in avail_splits to be valid
     320                 :             : // and with values of opposite sign of target_value
     321                 :             : // Ignoring this can cause unexpected results!
     322                 :             : static SplitList *
     323                 :           0 : gncSLFindOffsSplits (SplitList *avail_splits, gnc_numeric target_value)
     324                 :             : {
     325                 :           0 :     gint curr_recurse_level = 0;
     326                 :           0 :     gint max_recurse_level = g_list_length (avail_splits) - 1;
     327                 :             : 
     328                 :           0 :     if (!avail_splits)
     329                 :           0 :         return NULL;
     330                 :             : 
     331                 :           0 :     for (curr_recurse_level = 0;
     332                 :           0 :          curr_recurse_level <= max_recurse_level;
     333                 :           0 :          curr_recurse_level++)
     334                 :             :     {
     335                 :           0 :         SplitList *split_iter = NULL;
     336                 :           0 :         for (split_iter = avail_splits; split_iter; split_iter = split_iter->next)
     337                 :             :         {
     338                 :           0 :             Split *split = split_iter->data;
     339                 :           0 :             SplitList *match_splits = NULL;
     340                 :             :             gnc_numeric split_value, remaining_value;
     341                 :             : 
     342                 :           0 :             split_value = xaccSplitGetValue (split);
     343                 :             :             // Attention: target_value and split_value are of opposite sign
     344                 :             :             // So to get the remaining target value, they should be *added*
     345                 :           0 :             remaining_value = gnc_numeric_add (target_value, split_value,
     346                 :             :                                                GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
     347                 :             : 
     348                 :           0 :             if (curr_recurse_level == 0)
     349                 :             :             {
     350                 :           0 :                 if (gnc_numeric_zero_p (remaining_value))
     351                 :           0 :                     match_splits = g_list_prepend (NULL, split);
     352                 :             :             }
     353                 :             :             else
     354                 :             :             {
     355                 :           0 :                 if (gnc_numeric_positive_p (target_value) ==
     356                 :           0 :                     gnc_numeric_positive_p (remaining_value))
     357                 :           0 :                     match_splits = gncSLFindOffsSplits (split_iter->next,
     358                 :             :                                                         remaining_value);
     359                 :             :             }
     360                 :             : 
     361                 :           0 :             if (match_splits)
     362                 :           0 :                 return g_list_prepend (match_splits, split);
     363                 :             :         }
     364                 :             :     }
     365                 :             : 
     366                 :           0 :     return NULL;
     367                 :             : }
     368                 :             : 
     369                 :             : 
     370                 :             : static gboolean
     371                 :           0 : gncScrubLotDanglingPayments (GNCLot *lot)
     372                 :             : {
     373                 :           0 :     SplitList * split_list, *filtered_list = NULL, *match_list = NULL, *node;
     374                 :           0 :     Split *ll_split = gnc_lot_get_earliest_split (lot);
     375                 :           0 :     Transaction *ll_trans = xaccSplitGetParent (ll_split);
     376                 :           0 :     gnc_numeric ll_val = xaccSplitGetValue (ll_split);
     377                 :           0 :     time64 ll_date = xaccTransGetDate (ll_trans);
     378                 :           0 :     const char *ll_desc = xaccTransGetDescription (ll_trans);
     379                 :             : 
     380                 :             :     // look for free splits (i.e. not in any lot) which,
     381                 :             :     // compared to the lot link split
     382                 :             :     // - have the same date
     383                 :             :     // - have the same description
     384                 :             :     // - have an opposite sign amount
     385                 :             :     // - free split's abs value is less than or equal to ll split's abs value
     386                 :           0 :     split_list = xaccAccountGetSplitList(gnc_lot_get_account (lot));
     387                 :           0 :     for (node = split_list; node; node = node->next)
     388                 :             :     {
     389                 :           0 :         Split *free_split = node->data;
     390                 :             :         Transaction *free_trans;
     391                 :             :         gnc_numeric free_val;
     392                 :             : 
     393                 :           0 :         if (NULL != xaccSplitGetLot(free_split))
     394                 :           0 :             continue;
     395                 :             : 
     396                 :           0 :         free_trans = xaccSplitGetParent (free_split);
     397                 :           0 :         if (ll_date != xaccTransGetDate (free_trans))
     398                 :           0 :             continue;
     399                 :             : 
     400                 :           0 :         if (0 != g_strcmp0 (ll_desc, xaccTransGetDescription (free_trans)))
     401                 :           0 :             continue;
     402                 :             : 
     403                 :           0 :         free_val = xaccSplitGetValue (free_split);
     404                 :           0 :         if (gnc_numeric_positive_p (ll_val) ==
     405                 :           0 :             gnc_numeric_positive_p (free_val))
     406                 :           0 :             continue;
     407                 :             : 
     408                 :           0 :         if (gnc_numeric_compare (gnc_numeric_abs (free_val), gnc_numeric_abs (ll_val)) > 0)
     409                 :           0 :             continue;
     410                 :             : 
     411                 :           0 :         filtered_list = g_list_prepend (filtered_list, free_split);
     412                 :             :     }
     413                 :           0 :     g_list_free (split_list);
     414                 :             : 
     415                 :           0 :     filtered_list = g_list_reverse (filtered_list);
     416                 :           0 :     match_list = gncSLFindOffsSplits (filtered_list, ll_val);
     417                 :           0 :     g_list_free (filtered_list);
     418                 :             : 
     419                 :           0 :     for (node = match_list; node; node = node->next)
     420                 :             :     {
     421                 :           0 :         Split *match_split = node->data;
     422                 :           0 :         gnc_lot_add_split (lot, match_split);
     423                 :             :     }
     424                 :             : 
     425                 :           0 :     if (match_list)
     426                 :             :     {
     427                 :           0 :         g_list_free (match_list);
     428                 :           0 :         return TRUE;
     429                 :             :     }
     430                 :             :     else
     431                 :           0 :         return FALSE;
     432                 :             : }
     433                 :             : 
     434                 :             : static gboolean
     435                 :           0 : gncScrubLotIsSingleLotLinkSplit (GNCLot *lot)
     436                 :             : {
     437                 :           0 :     Split *split = NULL;
     438                 :           0 :     Transaction *trans = NULL;
     439                 :             : 
     440                 :             :     // Lots with a single split which is also a lot link transaction split
     441                 :             :     // may be sign of a dangling payment. Let's try to fix that
     442                 :             : 
     443                 :             :     // Only works for single split lots...
     444                 :           0 :     if (1 != gnc_lot_count_splits (lot))
     445                 :           0 :         return FALSE;
     446                 :             : 
     447                 :           0 :     split = gnc_lot_get_earliest_split (lot);
     448                 :           0 :     trans = xaccSplitGetParent (split);
     449                 :             : 
     450                 :           0 :     if (!trans)
     451                 :             :     {
     452                 :             :         // Ooops - the split doesn't belong to any transaction !
     453                 :             :         // This is not expected so issue a warning and continue with next split
     454                 :           0 :         PWARN("Encountered a split in a business lot that's not part of any transaction. "
     455                 :             :               "This is unexpected! Skipping split %p.", split);
     456                 :           0 :         return FALSE;
     457                 :             :     }
     458                 :             : 
     459                 :             :     // Only works if single split belongs to a lot link transaction...
     460                 :           0 :     if (xaccTransGetTxnType (trans) != TXN_TYPE_LINK)
     461                 :           0 :         return FALSE;
     462                 :             : 
     463                 :           0 :     return TRUE;
     464                 :             : }
     465                 :             : 
     466                 :             : gboolean
     467                 :           0 : gncScrubBusinessLot (GNCLot *lot)
     468                 :             : {
     469                 :           0 :     gboolean splits_deleted = FALSE;
     470                 :           0 :     gboolean dangling_payments = FALSE;
     471                 :           0 :     gboolean dangling_lot_link = FALSE;
     472                 :             :     Account *acc;
     473                 :           0 :     gchar *lotname=NULL;
     474                 :             : 
     475                 :           0 :     if (!lot) return FALSE;
     476                 :           0 :     lotname = g_strdup (gnc_lot_get_title (lot));
     477                 :           0 :     ENTER ("(lot=%p) %s", lot, lotname ? lotname : "(no lotname)");
     478                 :             : 
     479                 :           0 :     acc = gnc_lot_get_account (lot);
     480                 :           0 :     if (acc)
     481                 :           0 :         xaccAccountBeginEdit(acc);
     482                 :             : 
     483                 :             :     /* Check invoice link consistency
     484                 :             :      * A lot should have both or neither of:
     485                 :             :      * - one split from an invoice transaction
     486                 :             :      * - an invoice-guid set
     487                 :             :      */
     488                 :           0 :     gncScrubInvoiceState (lot);
     489                 :             : 
     490                 :             :     // Scrub lot links.
     491                 :             :     // They should only remain when two document lots are linked together
     492                 :           0 :     xaccScrubMergeLotSubSplits (lot, FALSE);
     493                 :           0 :     splits_deleted = gncScrubLotLinks (lot);
     494                 :             : 
     495                 :             :     // Look for dangling payments and repair if found
     496                 :           0 :     dangling_lot_link = gncScrubLotIsSingleLotLinkSplit (lot);
     497                 :           0 :     if (dangling_lot_link)
     498                 :             :     {
     499                 :           0 :         dangling_payments = gncScrubLotDanglingPayments (lot);
     500                 :           0 :         if (dangling_payments)
     501                 :           0 :             splits_deleted |= gncScrubLotLinks (lot);
     502                 :             :         else
     503                 :             :         {
     504                 :           0 :             Split *split = gnc_lot_get_earliest_split (lot);
     505                 :           0 :             Transaction *trans = xaccSplitGetParent (split);
     506                 :           0 :             xaccTransDestroy (trans);
     507                 :             :         }
     508                 :             :     }
     509                 :             : 
     510                 :             :     // If lot is empty now, delete it
     511                 :           0 :     if (0 == gnc_lot_count_splits (lot))
     512                 :             :     {
     513                 :           0 :         PINFO("All splits were removed from lot, deleting");
     514                 :           0 :         gnc_lot_destroy (lot);
     515                 :             :     }
     516                 :             : 
     517                 :           0 :     if (acc)
     518                 :           0 :         xaccAccountCommitEdit(acc);
     519                 :             : 
     520                 :           0 :     LEAVE ("(lot=%s, deleted=%d, dangling lot link=%d, dangling_payments=%d)",
     521                 :             :             lotname ? lotname : "(no lotname)", splits_deleted, dangling_lot_link,
     522                 :             :             dangling_payments);
     523                 :           0 :     g_free (lotname);
     524                 :             : 
     525                 :           0 :     return splits_deleted;
     526                 :             : }
     527                 :             : 
     528                 :             : gboolean
     529                 :           0 : gncScrubBusinessSplit (Split *split)
     530                 :             : {
     531                 :             :     Transaction *txn;
     532                 :           0 :     gboolean deleted_split = FALSE;
     533                 :             : 
     534                 :           0 :     if (!split) return FALSE;
     535                 :           0 :     ENTER ("(split=%p)", split);
     536                 :             : 
     537                 :           0 :     txn = xaccSplitGetParent (split);
     538                 :           0 :     if (txn)
     539                 :             :     {
     540                 :           0 :         gchar txntype = xaccTransGetTxnType (txn);
     541                 :           0 :         const gchar *read_only = xaccTransGetReadOnly (txn);
     542                 :           0 :         gboolean is_void = xaccTransGetVoidStatus (txn);
     543                 :           0 :         GNCLot *lot = xaccSplitGetLot (split);
     544                 :           0 :         GncInvoice *invoice = gncInvoiceGetInvoiceFromTxn (txn);
     545                 :           0 :         Transaction *posted_txn = gncInvoiceGetPostedTxn (invoice);
     546                 :             : 
     547                 :             :         /* Look for transactions as a result of double posting an invoice or bill
     548                 :             :          * Refer to https://bugs.gnucash.org/show_bug.cgi?id=754209
     549                 :             :          * to learn how this could have happened in the past.
     550                 :             :          * Characteristics of such transaction are:
     551                 :             :          * - read only
     552                 :             :          * - not voided (to ensure read only is set by the business functions)
     553                 :             :          * - transaction type is none (should be type invoice for proper post transactions)
     554                 :             :          * - assigned to a lot
     555                 :             :          */
     556                 :           0 :         if ((txntype == TXN_TYPE_NONE) && read_only && !is_void && lot)
     557                 :           0 :         {
     558                 :           0 :             const gchar *memo = _("Please delete this transaction. Explanation at https://wiki.gnucash.org/wiki/Business_Features_Issues#Double_posting");
     559                 :           0 :             gchar *txn_date = qof_print_date (xaccTransGetDateEntered (txn));
     560                 :           0 :             xaccTransClearReadOnly (txn);
     561                 :           0 :             xaccSplitSetMemo (split, memo);
     562                 :           0 :             gnc_lot_remove_split (lot, split);
     563                 :           0 :             PWARN("Cleared double post status of transaction \"%s\", dated %s. "
     564                 :             :                   "Please delete transaction and verify balance.",
     565                 :             :                   xaccTransGetDescription (txn),
     566                 :             :                   txn_date);
     567                 :           0 :             g_free (txn_date);
     568                 :             :         }
     569                 :             :         /* Next check for transactions which claim to be the posted transaction of
     570                 :             :          * an invoice but the invoice disagrees. In that case
     571                 :             :          */
     572                 :           0 :         else if (invoice && (txn != posted_txn))
     573                 :           0 :         {
     574                 :           0 :             const gchar *memo = _("Please delete this transaction. Explanation at https://wiki.gnucash.org/wiki/Business_Features_Issues#I_can.27t_delete_a_transaction_of_type_.22I.22_from_the_AR.2FAP_account");
     575                 :           0 :             gchar *txn_date = qof_print_date (xaccTransGetDateEntered (txn));
     576                 :           0 :             xaccTransClearReadOnly (txn);
     577                 :           0 :             xaccTransSetTxnType (txn, TXN_TYPE_NONE);
     578                 :           0 :             xaccSplitSetMemo (split, memo);
     579                 :           0 :             if (lot)
     580                 :             :             {
     581                 :           0 :                 gnc_lot_remove_split (lot, split);
     582                 :           0 :                 gncInvoiceDetachFromLot (lot);
     583                 :           0 :                 gncOwnerAttachToLot (gncInvoiceGetOwner(invoice), lot);
     584                 :             :             }
     585                 :           0 :             PWARN("Cleared double post status of transaction \"%s\", dated %s. "
     586                 :             :             "Please delete transaction and verify balance.",
     587                 :             :             xaccTransGetDescription (txn),
     588                 :             :                   txn_date);
     589                 :           0 :             g_free (txn_date);
     590                 :             :         }
     591                 :             :         /* Next delete any empty splits that aren't part of an invoice transaction
     592                 :             :          * Such splits may be the result of scrubbing the business lots, which can
     593                 :             :          * merge splits together while reducing superfluous lot links
     594                 :             :          */
     595                 :           0 :         else if (gnc_numeric_zero_p (xaccSplitGetAmount(split)) && !gncInvoiceGetInvoiceFromTxn (txn) && !is_void)
     596                 :             :         {
     597                 :           0 :             GNCLot *lot = xaccSplitGetLot (split);
     598                 :           0 :             time64 pdate = xaccTransGetDate (txn);
     599                 :           0 :             gchar *pdatestr = gnc_ctime (&pdate);
     600                 :           0 :             PINFO ("Destroying empty split %p from transaction %s (%s)", split, pdatestr, xaccTransGetDescription(txn));
     601                 :           0 :             xaccSplitDestroy (split);
     602                 :           0 :             g_free (pdatestr);
     603                 :             : 
     604                 :             :             // Also delete the lot containing this split if it was the last split in that lot
     605                 :           0 :             if (lot && (gnc_lot_count_splits (lot) == 0))
     606                 :           0 :                 gnc_lot_destroy (lot);
     607                 :             : 
     608                 :           0 :             deleted_split = TRUE;
     609                 :             :         }
     610                 :             : 
     611                 :             :     }
     612                 :             : 
     613                 :           0 :     LEAVE ("(split=%p)", split);
     614                 :           0 :     return deleted_split;
     615                 :             : }
     616                 :             : 
     617                 :             : /* ============================================================== */
     618                 :             : 
     619                 :             : void
     620                 :           0 : gncScrubBusinessAccountLots (Account *acc, QofPercentageFunc percentagefunc)
     621                 :             : {
     622                 :             :     LotList *lots, *node;
     623                 :           0 :     gint lot_count = 0;
     624                 :           0 :     gint curr_lot_no = 0;
     625                 :             :     const gchar *str;
     626                 :           0 :     const char *message = _( "Checking business lots in account %s: %u of %u");
     627                 :             : 
     628                 :           0 :     if (!acc) return;
     629                 :             : 
     630                 :           0 :     if (gnc_get_abort_scrub())
     631                 :           0 :         (percentagefunc)(NULL, -1.0);
     632                 :             : 
     633                 :           0 :     if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
     634                 :             : 
     635                 :           0 :     str = xaccAccountGetName(acc);
     636                 :           0 :     str = str ? str : "(null)";
     637                 :             : 
     638                 :           0 :     ENTER ("(acc=%s)", str);
     639                 :           0 :     PINFO ("Cleaning up superfluous lot links in account %s\n", str);
     640                 :           0 :     xaccAccountBeginEdit(acc);
     641                 :             : 
     642                 :           0 :     lots = xaccAccountGetLotList(acc);
     643                 :           0 :     lot_count = g_list_length (lots);
     644                 :           0 :     for (node = lots; node; node = node->next)
     645                 :             :     {
     646                 :           0 :         GNCLot *lot = node->data;
     647                 :             : 
     648                 :           0 :         PINFO("Start processing lot %d of %d",
     649                 :             :               curr_lot_no + 1, lot_count);
     650                 :             : 
     651                 :           0 :         if (curr_lot_no % 100 == 0)
     652                 :             :         {
     653                 :           0 :             char *progress_msg = g_strdup_printf (message, str, curr_lot_no, lot_count);
     654                 :           0 :             (percentagefunc)(progress_msg, (100 * curr_lot_no) / lot_count);
     655                 :           0 :             g_free (progress_msg);
     656                 :             :         }
     657                 :             : 
     658                 :           0 :         if (lot)
     659                 :           0 :             gncScrubBusinessLot (lot);
     660                 :             : 
     661                 :           0 :         PINFO("Finished processing lot %d of %d",
     662                 :             :               curr_lot_no + 1, lot_count);
     663                 :           0 :         curr_lot_no++;
     664                 :             :     }
     665                 :           0 :     g_list_free(lots);
     666                 :           0 :     xaccAccountCommitEdit(acc);
     667                 :           0 :     (percentagefunc)(NULL, -1.0);
     668                 :           0 :     LEAVE ("(acc=%s)", str);
     669                 :             : }
     670                 :             : 
     671                 :             : /* ============================================================== */
     672                 :             : 
     673                 :             : void
     674                 :           0 : gncScrubBusinessAccountSplits (Account *acc, QofPercentageFunc percentagefunc)
     675                 :             : {
     676                 :             :     SplitList *splits, *node;
     677                 :           0 :     gint split_count = 0;
     678                 :             :     gint curr_split_no;
     679                 :             :     const gchar *str;
     680                 :           0 :     const char *message = _( "Checking business splits in account %s: %u of %u");
     681                 :             : 
     682                 :           0 :     if (!acc) return;
     683                 :             : 
     684                 :           0 :     if (gnc_get_abort_scrub())
     685                 :           0 :         (percentagefunc)(NULL, -1.0);
     686                 :             : 
     687                 :           0 :     if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
     688                 :             : 
     689                 :           0 :     str = xaccAccountGetName(acc);
     690                 :           0 :     str = str ? str : "(null)";
     691                 :             : 
     692                 :           0 :     ENTER ("(acc=%s)", str);
     693                 :           0 :     PINFO ("Cleaning up superfluous lot links in account %s\n", str);
     694                 :           0 :     xaccAccountBeginEdit(acc);
     695                 :             : 
     696                 :           0 : restart:
     697                 :           0 :     curr_split_no = 0;
     698                 :           0 :     splits = xaccAccountGetSplitList(acc);
     699                 :           0 :     split_count = xaccAccountGetSplitsSize (acc);
     700                 :           0 :     for (node = splits; node; node = node->next)
     701                 :             :     {
     702                 :           0 :         Split *split = node->data;
     703                 :             : 
     704                 :           0 :         PINFO("Start processing split %d of %d",
     705                 :             :               curr_split_no + 1, split_count);
     706                 :             : 
     707                 :           0 :         if (gnc_get_abort_scrub ())
     708                 :           0 :             break;
     709                 :             : 
     710                 :           0 :         if (curr_split_no % 100 == 0)
     711                 :             :         {
     712                 :           0 :             char *progress_msg = g_strdup_printf (message, str, curr_split_no, split_count);
     713                 :           0 :             (percentagefunc)(progress_msg, (100 * curr_split_no) / split_count);
     714                 :           0 :             g_free (progress_msg);
     715                 :             :         }
     716                 :             : 
     717                 :           0 :         if (split)
     718                 :             :             // If gncScrubBusinessSplit returns true, a split was deleted and hence
     719                 :             :             // The account split list has become invalid, so we need to start over
     720                 :           0 :             if (gncScrubBusinessSplit (split))
     721                 :           0 :                 goto restart;
     722                 :             : 
     723                 :           0 :         PINFO("Finished processing split %d of %d",
     724                 :             :               curr_split_no + 1, split_count);
     725                 :           0 :         curr_split_no++;
     726                 :             :     }
     727                 :           0 :     g_list_free (splits);
     728                 :           0 :     xaccAccountCommitEdit(acc);
     729                 :           0 :     (percentagefunc)(NULL, -1.0);
     730                 :           0 :     LEAVE ("(acc=%s)", str);
     731                 :             : }
     732                 :             : 
     733                 :             : /* ============================================================== */
     734                 :             : 
     735                 :             : void
     736                 :           0 : gncScrubBusinessAccount (Account *acc, QofPercentageFunc percentagefunc)
     737                 :             : {
     738                 :           0 :     if (!acc) return;
     739                 :           0 :     if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
     740                 :             : 
     741                 :           0 :     gncScrubBusinessAccountLots (acc, percentagefunc);
     742                 :           0 :     gncScrubBusinessAccountSplits (acc, percentagefunc);
     743                 :             : }
     744                 :             : 
     745                 :             : /* ============================================================== */
     746                 :             : 
     747                 :             : static void
     748                 :           0 : lot_scrub_cb (Account *acc, gpointer data)
     749                 :             : {
     750                 :           0 :     if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
     751                 :           0 :     gncScrubBusinessAccount (acc, data);
     752                 :             : }
     753                 :             : 
     754                 :             : void
     755                 :           0 : gncScrubBusinessAccountTree (Account *acc, QofPercentageFunc percentagefunc)
     756                 :             : {
     757                 :           0 :     if (!acc) return;
     758                 :             : 
     759                 :           0 :     gnc_account_foreach_descendant(acc, lot_scrub_cb, percentagefunc);
     760                 :           0 :     gncScrubBusinessAccount (acc, percentagefunc);
     761                 :             : }
     762                 :             : 
     763                 :             : /* ========================== END OF FILE  ========================= */
        

Generated by: LCOV version 2.0-1