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 ========================= */
|