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: 2026-11-02 18:19:43 Functions: 0.0 % 14 0
Legend: Lines:     hit not hit

            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 :     splits = xaccAccountGetSplitList(acc);
     697            0 :     split_count = xaccAccountGetSplitsSize (acc);
     698              : 
     699            0 : restart:
     700            0 :     curr_split_no = 0;
     701            0 :     for (node = splits; node; node = node->next)
     702              :     {
     703            0 :         Split *split = node->data;
     704              : 
     705            0 :         PINFO("Start processing split %d of %d",
     706              :               curr_split_no + 1, split_count);
     707              : 
     708            0 :         if (gnc_get_abort_scrub ())
     709            0 :             break;
     710              : 
     711            0 :         if (curr_split_no % 100 == 0)
     712              :         {
     713            0 :             char *progress_msg = g_strdup_printf (message, str, curr_split_no, split_count);
     714            0 :             (percentagefunc)(progress_msg, (100 * curr_split_no) / split_count);
     715            0 :             g_free (progress_msg);
     716              :         }
     717              : 
     718            0 :         if (split)
     719              :             // If gncScrubBusinessSplit returns true, a split was deleted and hence
     720              :             // The account split list has become invalid, so we need to start over
     721            0 :             if (gncScrubBusinessSplit (split))
     722            0 :                 goto restart;
     723              : 
     724            0 :         PINFO("Finished processing split %d of %d",
     725              :               curr_split_no + 1, split_count);
     726            0 :         curr_split_no++;
     727              :     }
     728            0 :     g_list_free (splits);
     729            0 :     xaccAccountCommitEdit(acc);
     730            0 :     (percentagefunc)(NULL, -1.0);
     731            0 :     LEAVE ("(acc=%s)", str);
     732              : }
     733              : 
     734              : /* ============================================================== */
     735              : 
     736              : void
     737            0 : gncScrubBusinessAccount (Account *acc, QofPercentageFunc percentagefunc)
     738              : {
     739            0 :     if (!acc) return;
     740            0 :     if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
     741              : 
     742            0 :     gncScrubBusinessAccountLots (acc, percentagefunc);
     743            0 :     gncScrubBusinessAccountSplits (acc, percentagefunc);
     744              : }
     745              : 
     746              : /* ============================================================== */
     747              : 
     748              : static void
     749            0 : lot_scrub_cb (Account *acc, gpointer data)
     750              : {
     751            0 :     if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
     752            0 :     gncScrubBusinessAccount (acc, data);
     753              : }
     754              : 
     755              : void
     756            0 : gncScrubBusinessAccountTree (Account *acc, QofPercentageFunc percentagefunc)
     757              : {
     758            0 :     if (!acc) return;
     759              : 
     760            0 :     gnc_account_foreach_descendant(acc, lot_scrub_cb, percentagefunc);
     761            0 :     gncScrubBusinessAccount (acc, percentagefunc);
     762              : }
     763              : 
     764              : /* ========================== END OF FILE  ========================= */
        

Generated by: LCOV version 2.0-1