Branch data 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 : 140 : 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 ======================= */
|