GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
ScrubBusiness.c
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 [email protected] *
20 \********************************************************************/
21 
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 "Scrub2.h"
41 #include "ScrubBusiness.h"
42 #include "Transaction.h"
43 
44 static QofLogModule log_module = GNC_MOD_LOT;
45 
46 // A helper function that takes two splits. If the splits are of opposite sign
47 // it reduces the biggest split to have the same value (but with opposite sign)
48 // of the smaller split.
49 // To make sure everything still continues to balance in addition a "remainder" split
50 // will be created that will be added to the same lot and transaction as the biggest
51 // split.
52 // The opposite sign restriction is because that's the only scenario that makes sense
53 // in the context of scrubbing business lots below.
54 // If we created new splits, return TRUE, otherwise FALSE
55 static gboolean reduce_biggest_split (Split *splitA, Split *splitB)
56 {
57  gnc_numeric valA = xaccSplitGetValue (splitA);
58  gnc_numeric valB = xaccSplitGetValue (splitB);
59 
60  if (gnc_numeric_compare (gnc_numeric_abs (valA), gnc_numeric_abs (valB)) >= 0)
61  return gncOwnerReduceSplitTo (splitA, gnc_numeric_neg (valB));
62  else
63  return gncOwnerReduceSplitTo (splitB, gnc_numeric_neg (valA));
64 }
65 
66 // Attempt to eliminate or reduce the lot link splits (ll_*_split)
67 // between from_lot and to_lot. To do so this function will attempt
68 // to move a payment split from from_lot to to_lot in order to
69 // balance the lot link split that will be deleted.
70 // To ensure everything remains balanced at most
71 // min (val-ll-*-split, val-pay-split) (in absolute values) can be moved.
72 // If any split involved has a larger value, it will be split in two
73 // and only the part matching the other splits' value will be used.
74 // The leftover splits are kept in the respective transactions/lots.
75 // A future scrub action can still act on those if needed.
76 //
77 // Note that this function assumes that ll_from_split and ll_to_split are
78 // of opposite sign. The calling function should check this.
79 
80 static gboolean
81 scrub_other_link (GNCLot *from_lot, Split *ll_from_split,
82  GNCLot *to_lot, Split *ll_to_split)
83 {
84  Split *real_from_split; // This refers to the split in the payment lot representing the payment itself
85  gnc_numeric from_val, real_from_val, to_val;
86  gboolean modified = FALSE;
87  Transaction *ll_txn = xaccSplitGetParent (ll_to_split);
88 
89  // Per iteration we can only scrub at most max (val-doc-split, val-pay-split)
90  // So split the bigger one in two if needed and continue with the equal valued splits only
91  // The remainder is added to the lot link transaction and the lot to keep everything balanced
92  // and will be processed in a future iteration
93  modified = reduce_biggest_split (ll_from_split, ll_to_split);
94 
95  // Next we have to find the original payment split so we can
96  // add (part of) it to the document lot
97  real_from_split = gncOwnerFindOffsettingSplit (from_lot, xaccSplitGetValue (ll_from_split));
98  if (!real_from_split)
99  return modified; // No usable split in the payment lot
100 
101  // Here again per iteration we can only scrub at most max (val-other-pay-split, val-pay-split)
102  // So split the bigger one in two if needed and continue with the equal valued splits only
103  // The remainder is added to the lot link transaction and the lot to keep everything balanced
104  // and will be processed in a future iteration
105  modified = reduce_biggest_split (real_from_split, ll_from_split);
106 
107  // Once more check for max (val-doc-split, val-pay-split), and reduce if necessary.
108  // It may have changed while looking for the real payment split
109  modified = reduce_biggest_split (ll_from_split, ll_to_split);
110 
111  // At this point ll_to_split and real_from_split should have the same value
112  // If not, flag a warning and skip to the next iteration
113  to_val = xaccSplitGetValue (ll_to_split);
114  from_val = xaccSplitGetValue (ll_from_split);
115  real_from_val = xaccSplitGetValue (real_from_split);
116  if (!gnc_numeric_equal (real_from_val, to_val))
117  {
118  // This is unexpected - write a warning message and skip this split
119  PWARN("real_from_val and to_val differ. "
120  "This is unexpected! Skip scrubbing of real_from_split %p against ll_to_split %p.", real_from_split, ll_to_split);
121  return modified;
122  }
123 
124  // Now do the actual split dance
125  // - move real payment split to doc lot
126  // - delete both lot link splits from the lot link transaction
127  gnc_lot_add_split (to_lot, real_from_split);
128  xaccTransBeginEdit (ll_txn);
129  xaccSplitDestroy (ll_to_split);
130  xaccSplitDestroy (ll_from_split);
131  xaccTransCommitEdit (ll_txn);
132 
133  // Cleanup the lots
134  xaccScrubMergeLotSubSplits (to_lot, FALSE);
135  xaccScrubMergeLotSubSplits (from_lot, FALSE);
136 
137  return TRUE; // We did change splits/transactions/lots...
138 }
139 
140 static gboolean
141 gncScrubLotLinks (GNCLot *scrub_lot)
142 {
143  gboolean modified = FALSE, restart_needed = FALSE;
144  SplitList *sls_iter = NULL;
145 
146 scrub_start:
147  restart_needed = FALSE;
148 
149  // Iterate over all splits in the lot
150  for (sls_iter = gnc_lot_get_split_list (scrub_lot); sls_iter; sls_iter = sls_iter->next)
151  {
152  Split *sl_split = sls_iter->data;
153  Transaction *ll_txn = NULL; // ll_txn = "Lot Link Transaction"
154  SplitList *lts_iter = NULL;
155 
156  if (!sl_split)
157  continue; // next scrub lot split
158 
159  // Only lot link transactions need to be scrubbed
160  ll_txn = xaccSplitGetParent (sl_split);
161 
162  if (!ll_txn)
163  {
164  // Ooops - the split doesn't belong to any transaction !
165  // This is not expected so issue a warning and continue with next split
166  PWARN("Encountered a split in a business lot that's not part of any transaction. "
167  "This is unexpected! Skipping split %p.", sl_split);
168  continue;
169  }
170 
171  if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK)
172  continue; // next scrub lot split
173 
174  // Iterate over all splits in the lot link transaction
175  for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next)
176  {
177  Split *ll_txn_split = lts_iter->data; // These all refer to splits in the lot link transaction
178  GNCLot *remote_lot = NULL; // lot at the other end of the lot link transaction
179  gboolean sl_is_doc_lot, rl_is_doc_lot;
180 
181  if (!ll_txn_split)
182  continue; // next lot link transaction split
183 
184  // Skip the split in the lot we're currently scrubbing
185  if (sl_split == ll_txn_split)
186  continue; // next lot link transaction split
187 
188  // Only splits of opposite sign can be scrubbed
189  if (gnc_numeric_positive_p (xaccSplitGetValue (sl_split)) ==
190  gnc_numeric_positive_p (xaccSplitGetValue (ll_txn_split)))
191  continue; // next lot link transaction split
192 
193  // Find linked lot via split
194  remote_lot = xaccSplitGetLot (ll_txn_split);
195  if (!remote_lot)
196  {
197  // This is unexpected - write a warning message and skip this split
198  PWARN("Encountered a Lot Link transaction with a split that's not in any lot. "
199  "This is unexpected! Skipping split %p from transaction %p.", ll_txn_split, ll_txn);
200  continue;
201  }
202 
203  sl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (scrub_lot) != NULL);
204  rl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (remote_lot) != NULL);
205 
206  // Depending on the type of lots we're comparing, we need different actions
207  // - Two document lots (an invoice and a credit note):
208  // Special treatment - look for all document lots linked via ll_txn
209  // and update the memo to be of more use to the uses.
210  // - Two payment lots:
211  // (Part of) the link will be eliminated and instead (part of)
212  // one payment will be added to the other lot to keep the balance.
213  // If the payments are not equal in abs value part of the bigger payment
214  // will be moved to the smaller payment's lot.
215  // - A document and a payment lot:
216  // (Part of) the link will be eliminated and instead (part of) the real
217  // payment will be added to the document lot to handle the payment.
218  if (sl_is_doc_lot && rl_is_doc_lot)
219  gncOwnerSetLotLinkMemo (ll_txn);
220  else if (!sl_is_doc_lot && !rl_is_doc_lot)
221  {
222  gint cmp = gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (sl_split)),
223  gnc_numeric_abs (xaccSplitGetValue (ll_txn_split)));
224  if (cmp >= 0)
225  restart_needed = scrub_other_link (scrub_lot, sl_split, remote_lot, ll_txn_split);
226  else
227  restart_needed = scrub_other_link (remote_lot, ll_txn_split, scrub_lot, sl_split);
228  }
229  else
230  {
231  GNCLot *doc_lot = sl_is_doc_lot ? scrub_lot : remote_lot;
232  GNCLot *pay_lot = sl_is_doc_lot ? remote_lot : scrub_lot;
233  Split *ll_doc_split = sl_is_doc_lot ? sl_split : ll_txn_split;
234  Split *ll_pay_split = sl_is_doc_lot ? ll_txn_split : sl_split;
235  // Ok, let's try to move a payment from pay_lot to doc_lot
236  restart_needed = scrub_other_link (pay_lot, ll_pay_split, doc_lot, ll_doc_split);
237  }
238 
239  // If we got here, the splits in our lot and ll_txn have been severely mixed up
240  // And our iterator lists are probably no longer valid
241  // So let's start over
242  if (restart_needed)
243  {
244  modified = TRUE;
245  goto scrub_start;
246  }
247 
248  }
249  }
250 
251  return modified;
252 }
253 
254 
255 gboolean
257 {
258  gboolean splits_deleted = FALSE;
259  Account *acc;
260  gchar *lotname=NULL;
261 
262  if (!lot) return FALSE;
263  lotname = g_strdup (gnc_lot_get_title (lot));
264  ENTER ("(lot=%p) %s", lot, lotname ? lotname : "(no lotname)");
265 
266  acc = gnc_lot_get_account (lot);
267  if (acc)
269 
270  // Scrub lot links.
271  // They should only remain when two document lots are linked together
272  xaccScrubMergeLotSubSplits (lot, FALSE);
273  splits_deleted = gncScrubLotLinks (lot);
274 
275  // If lot is empty now, delete it
276  if (0 == gnc_lot_count_splits (lot))
277  {
278  PINFO("All splits were removed from lot, deleting");
279  gnc_lot_destroy (lot);
280  }
281 
282  if (acc)
284 
285  LEAVE ("(lot=%s, deleted=%d)", lotname ? lotname : "(no lotname)", splits_deleted);
286  g_free (lotname);
287 
288  return splits_deleted;
289 }
290 
291 /* ============================================================== */
292 
293 void
295 {
296  LotList *lots, *node;
297  if (!acc) return;
298  if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
299 
300  ENTER ("(acc=%s)", xaccAccountGetName(acc));
302 
303  lots = xaccAccountGetLotList(acc);
304  for (node = lots; node; node = node->next)
305  {
306  GNCLot *lot = node->data;
307  if (lot)
308  gncScrubBusinessLot (lot);
309  }
310  g_list_free(lots);
312  LEAVE ("(acc=%s)", xaccAccountGetName(acc));
313 }
314 
315 /* ============================================================== */
316 
317 static void
318 lot_scrub_cb (Account *acc, gpointer data)
319 {
320  if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
322 }
323 
324 void
326 {
327  if (!acc) return;
328 
329  gnc_account_foreach_descendant(acc, lot_scrub_cb, NULL);
331 }
332 
333 /* ========================== END OF FILE ========================= */
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
gboolean xaccScrubMergeLotSubSplits(GNCLot *lot, gboolean strict)
Definition: Scrub2.c:398
char xaccTransGetTxnType(const Transaction *trans)
Definition: Transaction.c:2302
GList LotList
Definition: gnc-engine.h:201
void gnc_account_foreach_descendant(const Account *acc, AccountCb thunk, gpointer user_data)
Definition: Account.c:2958
#define PINFO(format, args...)
Definition: qoflog.h:249
GNCAccountType xaccAccountGetType(const Account *acc)
Definition: Account.c:3009
gboolean xaccSplitDestroy(Split *split)
Definition: Split.c:1492
Utilities to Convert Stock Accounts to use Lots.
void gncScrubBusinessAccountLots(Account *acc)
gnc_numeric gnc_numeric_neg(gnc_numeric a)
void gnc_lot_add_split(GNCLot *lot, Split *split)
Definition: gnc-lot.c:569
Transaction * xaccSplitGetParent(const Split *split)
Definition: Split.c:1903
gint gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
#define ENTER(format, args...)
Definition: qoflog.h:261
Cleanup functions for business objects.
const char * gnc_lot_get_title(const GNCLot *lot)
Definition: gnc-lot.c:437
#define PWARN(format, args...)
Definition: qoflog.h:243
GList SplitList
Definition: gnc-engine.h:203
Account handling public routines.
gboolean gncOwnerReduceSplitTo(Split *split, gnc_numeric target_value)
Definition: gncOwner.c:914
Implement Accounting Policy Private header File.
SplitList * gnc_lot_get_split_list(const GNCLot *lot)
Definition: gnc-lot.c:417
LotList * xaccAccountGetLotList(const Account *acc)
Definition: Account.c:3744
gboolean xaccAccountIsAPARType(GNCAccountType t)
Definition: Account.c:4225
gnc_numeric gnc_numeric_abs(gnc_numeric a)
void xaccTransCommitEdit(Transaction *trans)
Definition: Transaction.c:1579
#define TXN_TYPE_LINK
Definition: Transaction.h:122
void xaccTransBeginEdit(Transaction *trans)
Definition: Transaction.c:1380
All type declarations for the whole Gnucash engine.
gboolean gnc_numeric_positive_p(gnc_numeric a)
GncInvoice * gncInvoiceGetInvoiceFromLot(GNCLot *lot)
Definition: gncInvoice.c:1174
Business Invoice Interface.
Definition: SplitP.h:71
gnc_numeric xaccSplitGetValue(const Split *split)
Definition: Split.c:1993
gboolean gncScrubBusinessLot(GNCLot *lot)
void xaccAccountBeginEdit(Account *acc)
Definition: Account.c:1280
#define LEAVE(format, args...)
Definition: qoflog.h:271
Split * gncOwnerFindOffsettingSplit(GNCLot *lot, gnc_numeric target_value)
Definition: gncOwner.c:848
void gncOwnerSetLotLinkMemo(Transaction *ll_txn)
Definition: gncOwner.c:946
Account * gnc_lot_get_account(const GNCLot *lot)
Definition: gnc-lot.c:386
void gncScrubBusinessAccountTreeLots(Account *acc)
const char * xaccAccountGetName(const Account *acc)
Definition: Account.c:3031
API for Transactions and Splits (journal entries)
void xaccAccountCommitEdit(Account *acc)
Definition: Account.c:1321
SplitList * xaccTransGetSplitList(const Transaction *trans)
Definition: Transaction.c:2164
GNCLot * xaccSplitGetLot(const Split *split)
Definition: Split.c:1953
const gchar * QofLogModule
Definition: qofid.h:89