Line data Source code
1 : /********************************************************************\
2 : * Scrub2.c -- Convert Stock Accounts to use Lots *
3 : * *
4 : * This program is free software; you can redistribute it and/or *
5 : * modify it under the terms of the GNU General Public License as *
6 : * published by the Free Software Foundation; either version 2 of *
7 : * the License, or (at your option) any later version. *
8 : * *
9 : * This program is distributed in the hope that it will be useful, *
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 : * GNU General Public License for more details. *
13 : * *
14 : * You should have received a copy of the GNU General Public License*
15 : * along with this program; if not, contact: *
16 : * *
17 : * Free Software Foundation Voice: +1-617-542-5942 *
18 : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19 : * Boston, MA 02110-1301, USA gnu@gnu.org *
20 : \********************************************************************/
21 :
22 : /** @file Scrub2.c
23 : * @brief Utilities to Convert Stock Accounts to use Lots
24 : * @author Created by Linas Vepstas March 2003
25 : * @author Copyright (c) 2003 Linas Vepstas <linas@linas.org>
26 : *
27 : * Provides a set of functions and utilities for checking and
28 : * repairing ('scrubbing clean') the usage of Lots and lot balances
29 : * in stock and commodity accounts. Broken lots are repaired using
30 : * the accounts specific accounting policy (probably FIFO).
31 : */
32 :
33 : #include <config.h>
34 :
35 : #include <glib.h>
36 :
37 : #include "qof.h"
38 : #include "Account.h"
39 : #include "AccountP.hpp"
40 : #include "Account.hpp"
41 : #include "Transaction.h"
42 : #include "TransactionP.hpp"
43 : #include "Scrub2.h"
44 : #include "cap-gains.h"
45 : #include "gnc-engine.h"
46 : #include "gncInvoice.h"
47 : #include "gnc-lot.h"
48 : #include "policy-p.h"
49 :
50 : static QofLogModule log_module = GNC_MOD_LOT;
51 :
52 : /* ============================================================== */
53 : /** Loop over all splits, and make sure that every split
54 : * belongs to some lot. If a split does not belong to
55 : * any lots, poke it into one.
56 : */
57 :
58 : void
59 5 : xaccAccountAssignLots (Account *acc)
60 : {
61 5 : if (!acc) return;
62 :
63 5 : ENTER ("acc=%s", xaccAccountGetName(acc));
64 5 : xaccAccountBeginEdit (acc);
65 :
66 8 : restart_loop:
67 148 : for (auto split : xaccAccountGetSplits (acc))
68 : {
69 : /* If already in lot, then no-op */
70 135 : if (split->lot) continue;
71 :
72 : /* Skip stock splits */
73 79 : if (xaccSplitIsStockSplit(split)) continue;
74 :
75 : /* Skip voided transactions */
76 98 : if (gnc_numeric_zero_p (split->amount) &&
77 38 : xaccTransGetVoidStatus(split->parent)) continue;
78 :
79 60 : if (xaccSplitAssign (split)) goto restart_loop;
80 : }
81 5 : xaccAccountCommitEdit (acc);
82 5 : LEAVE ("acc=%s", xaccAccountGetName(acc));
83 : }
84 :
85 : /* ============================================================== */
86 :
87 : /** The xaccLotFill() routine attempts to assign splits to the
88 : * indicated lot until the lot balance goes to zero, or until
89 : * there are no suitable (i.e. unassigned) splits left in the
90 : * account. It uses the default accounting policy to choose
91 : * the splits to fill out the lot.
92 : */
93 :
94 : void
95 48 : xaccLotFill (GNCLot *lot)
96 : {
97 : Account *acc;
98 : Split *split;
99 : GNCPolicy *pcy;
100 :
101 48 : if (!lot) return;
102 48 : acc = gnc_lot_get_account(lot);
103 48 : pcy = gnc_account_get_policy(acc);
104 :
105 48 : ENTER ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
106 :
107 : /* If balance already zero, we have nothing to do. */
108 48 : if (gnc_lot_is_closed (lot))
109 : {
110 0 : LEAVE ("Lot Closed (lot=%s, acc=%s)", gnc_lot_get_title(lot),
111 : xaccAccountGetName(acc));
112 0 : return;
113 : }
114 48 : split = pcy->PolicyGetSplit (pcy, lot);
115 48 : if (!split)
116 : {
117 48 : LEAVE ("No Split (lot=%s, acc=%s)", gnc_lot_get_title(lot),
118 : xaccAccountGetName(acc));
119 48 : return; /* Handle the common case */
120 : }
121 :
122 : /* Reject stock split transactions */
123 0 : if (xaccSplitIsStockSplit(split))
124 : {
125 0 : LEAVE ("Stock split transaction (lot=%s, acc=%s)",
126 : gnc_lot_get_title(lot), xaccAccountGetName(acc));
127 0 : return;
128 : }
129 :
130 : /* Reject voided transactions */
131 0 : if (gnc_numeric_zero_p(split->amount) &&
132 0 : xaccTransGetVoidStatus(split->parent))
133 : {
134 0 : LEAVE ("Voided transaction (lot=%s, acc=%s)",
135 : gnc_lot_get_title(lot), xaccAccountGetName(acc));
136 0 : return;
137 : }
138 :
139 0 : xaccAccountBeginEdit (acc);
140 :
141 : /* Loop until we've filled up the lot, (i.e. till the
142 : * balance goes to zero) or there are no splits left. */
143 : while (1)
144 : {
145 : Split *subsplit;
146 :
147 0 : subsplit = xaccSplitAssignToLot (split, lot);
148 0 : if (subsplit == split)
149 : {
150 0 : PERR ("Accounting Policy gave us a split that "
151 : "doesn't fit into this lot\n"
152 : "lot baln=%s, isclosed=%d, aplit amt=%s",
153 : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
154 : gnc_lot_is_closed (lot),
155 : gnc_num_dbg_to_string (split->amount));
156 0 : break;
157 : }
158 :
159 0 : if (gnc_lot_is_closed (lot)) break;
160 :
161 0 : split = pcy->PolicyGetSplit (pcy, lot);
162 0 : if (!split) break;
163 0 : }
164 0 : xaccAccountCommitEdit (acc);
165 0 : LEAVE ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
166 : }
167 :
168 : /* ============================================================== */
169 :
170 : void
171 54 : xaccLotScrubDoubleBalance (GNCLot *lot)
172 : {
173 54 : gnc_commodity *currency = nullptr;
174 : SplitList *snode;
175 : GList *node;
176 54 : gnc_numeric zero = gnc_numeric_zero();
177 54 : gnc_numeric value = zero;
178 :
179 101 : if (!lot) return;
180 :
181 54 : ENTER ("lot=%s", gnc_lot_get_title(lot));
182 :
183 123 : for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
184 : {
185 69 : Split *s = GNC_SPLIT(snode->data);
186 69 : xaccSplitComputeCapGains (s, nullptr);
187 : }
188 :
189 : /* We double-check only closed lots */
190 54 : if (FALSE == gnc_lot_is_closed (lot))
191 : {
192 47 : LEAVE ("lot=%s is closed", gnc_lot_get_title(lot));
193 47 : return;
194 : }
195 :
196 25 : for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
197 : {
198 18 : Split *s = GNC_SPLIT(snode->data);
199 18 : Transaction *trans = s->parent;
200 :
201 : /* Check to make sure all splits in the lot have a common currency */
202 18 : if (nullptr == currency)
203 : {
204 7 : currency = trans->common_currency;
205 : }
206 18 : if (FALSE == gnc_commodity_equiv (currency, trans->common_currency))
207 : {
208 : /* This lot has mixed currencies. Can't double-balance.
209 : * Silently punt */
210 0 : PWARN ("Lot with multiple currencies:\n"
211 : "\ttrans=%s curr=%s", xaccTransGetDescription(trans),
212 : gnc_commodity_get_fullname(trans->common_currency));
213 0 : break;
214 : }
215 :
216 : /* Now, total up the values */
217 18 : value = gnc_numeric_add (value, xaccSplitGetValue (s),
218 : GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
219 18 : PINFO ("Split=%p value=%s Accum Lot value=%s", s,
220 : gnc_num_dbg_to_string (s->value),
221 : gnc_num_dbg_to_string (value));
222 :
223 : }
224 :
225 7 : if (FALSE == gnc_numeric_equal (value, zero))
226 : {
227 : /* Unhandled error condition. Not sure what to do here,
228 : * Since the ComputeCapGains should have gotten it right.
229 : * I suppose there might be small rounding errors, a penny or two,
230 : * the ideal thing would to figure out why there's a rounding
231 : * error, and fix that.
232 : */
233 0 : PERR ("Closed lot fails to double-balance !! lot value=%s",
234 : gnc_num_dbg_to_string (value));
235 0 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
236 : {
237 0 : Split *s = GNC_SPLIT(node->data);
238 0 : PERR ("s=%p amt=%s val=%s", s,
239 : gnc_num_dbg_to_string(s->amount),
240 : gnc_num_dbg_to_string(s->value));
241 : }
242 : }
243 :
244 7 : LEAVE ("lot=%s", gnc_lot_get_title(lot));
245 : }
246 :
247 : /* ================================================================= */
248 :
249 : static inline gboolean
250 114 : is_subsplit (Split *split)
251 : {
252 :
253 : /* generic stop-progress conditions */
254 114 : if (!split) return FALSE;
255 114 : g_return_val_if_fail (split->parent, FALSE);
256 :
257 : /* If there are no sub-splits, then there's nothing to do. */
258 114 : return xaccSplitHasPeers (split);
259 : }
260 :
261 : /* ================================================================= */
262 :
263 :
264 : /* ================================================================= */
265 :
266 : /* Remove the guid of b from a. Note that a may not contain the guid
267 : * of b, (and v.v.) in which case, it will contain other guids which
268 : * establish the links. So merge them back in. */
269 :
270 : static void
271 0 : remove_guids (Split *sa, Split *sb)
272 : {
273 0 : xaccSplitRemovePeerSplit (sa, sb);
274 0 : xaccSplitRemovePeerSplit (sb, sa);
275 0 : xaccSplitMergePeerSplits (sa, sb);
276 0 : }
277 :
278 : /* The merge_splits() routine causes the amount & value of sb
279 : * to be merged into sa; it then destroys sb. It also performs
280 : * some other misc cleanup */
281 :
282 : static void
283 0 : merge_splits (Split *sa, Split *sb)
284 : {
285 : Account *act;
286 : Transaction *txn;
287 : gnc_numeric amt, val;
288 :
289 0 : act = xaccSplitGetAccount (sb);
290 0 : xaccAccountBeginEdit (act);
291 :
292 0 : txn = sa->parent;
293 0 : xaccTransBeginEdit (txn);
294 :
295 : /* Remove the guid of sb from the 'gemini' of sa */
296 0 : remove_guids (sa, sb);
297 :
298 : /* Add amount of sb into sa, ditto for value. */
299 0 : amt = xaccSplitGetAmount (sa);
300 0 : amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
301 0 : xaccSplitSetAmount (sa, amt);
302 :
303 0 : val = xaccSplitGetValue (sa);
304 0 : val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
305 0 : xaccSplitSetValue (sa, val);
306 :
307 : /* Set reconcile to no; after this much violence,
308 : * no way its reconciled. */
309 0 : xaccSplitSetReconcile (sa, NREC);
310 :
311 : /* If sb has associated gains splits, trash them. */
312 0 : if ((sb->gains_split) &&
313 0 : (sb->gains_split->gains & GAINS_STATUS_GAINS))
314 : {
315 0 : Transaction *t = sb->gains_split->parent;
316 0 : xaccTransBeginEdit (t);
317 0 : xaccTransDestroy (t);
318 0 : xaccTransCommitEdit (t);
319 : }
320 :
321 : /* Finally, delete sb */
322 0 : xaccSplitDestroy(sb);
323 :
324 0 : xaccTransCommitEdit (txn);
325 0 : xaccAccountCommitEdit (act);
326 0 : }
327 :
328 : gboolean
329 114 : xaccScrubMergeSubSplits (Split *split, gboolean strict)
330 : {
331 114 : gboolean rc = FALSE;
332 : Transaction *txn;
333 : SplitList *node;
334 : GNCLot *lot;
335 :
336 114 : if (strict && (FALSE == is_subsplit (split))) return FALSE;
337 :
338 10 : txn = split->parent;
339 :
340 : // Don't mess with splits from an invoice transaction
341 : // Those are the responsibility of the business code
342 10 : if (gncInvoiceGetInvoiceFromTxn (txn)) return FALSE;
343 :
344 10 : lot = xaccSplitGetLot (split);
345 :
346 10 : ENTER ("(Lot=%s)", gnc_lot_get_title(lot));
347 10 : restart:
348 47 : for (node = txn->splits; node; node = node->next)
349 : {
350 37 : Split *s = GNC_SPLIT(node->data);
351 37 : if (xaccSplitGetLot (s) != lot) continue;
352 11 : if (s == split) continue;
353 1 : if (qof_instance_get_destroying(s)) continue;
354 :
355 : // Don't mess with splits from an invoice transaction
356 : // Those are the responsibility of the business code
357 1 : if (gncInvoiceGetInvoiceFromTxn (s->parent)) return FALSE;
358 :
359 1 : if (strict)
360 : {
361 : /* OK, this split is in the same lot (and thus same account)
362 : * as the indicated split. Make sure it is really a subsplit
363 : * of the split we started with. It's possible to have two
364 : * splits in the same lot and transaction that are not subsplits
365 : * of each other, the test-period test suite does this, for
366 : * example. Only worry about adjacent sub-splits. By
367 : * repeatedly merging adjacent subsplits, we'll get the non-
368 : * adjacent ones too. */
369 1 : if (!xaccSplitIsPeerSplit (split, s))
370 1 : continue;
371 : }
372 :
373 0 : merge_splits (split, s);
374 0 : rc = TRUE;
375 0 : goto restart;
376 : }
377 10 : if (rc && gnc_numeric_zero_p (split->amount))
378 : {
379 0 : time64 pdate = xaccTransGetDate (txn);
380 0 : gchar *pdatestr = gnc_ctime (&pdate);
381 0 : PWARN ("Result of merge has zero amt!");
382 0 : PWARN ("Transaction details - posted date %s - description %s", pdatestr, xaccTransGetDescription(txn));
383 0 : g_free (pdatestr);
384 : }
385 10 : LEAVE (" splits merged=%d", rc);
386 10 : return rc;
387 : }
388 :
389 : gboolean
390 103 : xaccScrubMergeLotSubSplits (GNCLot *lot, gboolean strict)
391 : {
392 103 : gboolean rc = FALSE;
393 : SplitList *node;
394 :
395 103 : if (!lot) return FALSE;
396 :
397 103 : ENTER (" ");
398 103 : restart:
399 217 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
400 : {
401 114 : Split *s = GNC_SPLIT(node->data);
402 114 : if (!xaccScrubMergeSubSplits(s, strict)) continue;
403 :
404 0 : rc = TRUE;
405 0 : goto restart;
406 : }
407 103 : LEAVE (" splits merged=%d", rc);
408 103 : return rc;
409 : }
410 :
411 : /* =========================== END OF FILE ======================= */
|