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 : 3 : xaccAccountAssignLots (Account *acc)
60 : : {
61 : 3 : if (!acc) return;
62 : :
63 : 3 : ENTER ("acc=%s", xaccAccountGetName(acc));
64 : 3 : xaccAccountBeginEdit (acc);
65 : :
66 : 6 : restart_loop:
67 : 134 : for (auto split : xaccAccountGetSplits (acc))
68 : : {
69 : : /* If already in lot, then no-op */
70 : 131 : if (split->lot) continue;
71 : :
72 : : /* Skip voided transactions */
73 : 94 : if (gnc_numeric_zero_p (split->amount) &&
74 : 38 : xaccTransGetVoidStatus(split->parent)) continue;
75 : :
76 : 56 : if (xaccSplitAssign (split)) goto restart_loop;
77 : : }
78 : 3 : xaccAccountCommitEdit (acc);
79 : 3 : LEAVE ("acc=%s", xaccAccountGetName(acc));
80 : : }
81 : :
82 : : /* ============================================================== */
83 : :
84 : : /** The xaccLotFill() routine attempts to assign splits to the
85 : : * indicated lot until the lot balance goes to zero, or until
86 : : * there are no suitable (i.e. unassigned) splits left in the
87 : : * account. It uses the default accounting policy to choose
88 : : * the splits to fill out the lot.
89 : : */
90 : :
91 : : void
92 : 48 : xaccLotFill (GNCLot *lot)
93 : : {
94 : : Account *acc;
95 : : Split *split;
96 : : GNCPolicy *pcy;
97 : :
98 : 48 : if (!lot) return;
99 : 48 : acc = gnc_lot_get_account(lot);
100 : 48 : pcy = gnc_account_get_policy(acc);
101 : :
102 : 48 : ENTER ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
103 : :
104 : : /* If balance already zero, we have nothing to do. */
105 : 48 : if (gnc_lot_is_closed (lot))
106 : : {
107 : 0 : LEAVE ("Lot Closed (lot=%s, acc=%s)", gnc_lot_get_title(lot),
108 : : xaccAccountGetName(acc));
109 : 0 : return;
110 : : }
111 : 48 : split = pcy->PolicyGetSplit (pcy, lot);
112 : 48 : if (!split)
113 : : {
114 : 48 : LEAVE ("No Split (lot=%s, acc=%s)", gnc_lot_get_title(lot),
115 : : xaccAccountGetName(acc));
116 : 48 : return; /* Handle the common case */
117 : : }
118 : :
119 : : /* Reject voided transactions */
120 : 0 : if (gnc_numeric_zero_p(split->amount) &&
121 : 0 : xaccTransGetVoidStatus(split->parent))
122 : : {
123 : 0 : LEAVE ("Voided transaction (lot=%s, acc=%s)",
124 : : gnc_lot_get_title(lot), xaccAccountGetName(acc));
125 : 0 : return;
126 : : }
127 : :
128 : 0 : xaccAccountBeginEdit (acc);
129 : :
130 : : /* Loop until we've filled up the lot, (i.e. till the
131 : : * balance goes to zero) or there are no splits left. */
132 : : while (1)
133 : : {
134 : : Split *subsplit;
135 : :
136 : 0 : subsplit = xaccSplitAssignToLot (split, lot);
137 : 0 : if (subsplit == split)
138 : : {
139 : 0 : PERR ("Accounting Policy gave us a split that "
140 : : "doesn't fit into this lot\n"
141 : : "lot baln=%s, isclosed=%d, aplit amt=%s",
142 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
143 : : gnc_lot_is_closed (lot),
144 : : gnc_num_dbg_to_string (split->amount));
145 : 0 : break;
146 : : }
147 : :
148 : 0 : if (gnc_lot_is_closed (lot)) break;
149 : :
150 : 0 : split = pcy->PolicyGetSplit (pcy, lot);
151 : 0 : if (!split) break;
152 : 0 : }
153 : 0 : xaccAccountCommitEdit (acc);
154 : 0 : LEAVE ("(lot=%s, acc=%s)", gnc_lot_get_title(lot), xaccAccountGetName(acc));
155 : : }
156 : :
157 : : /* ============================================================== */
158 : :
159 : : void
160 : 52 : xaccLotScrubDoubleBalance (GNCLot *lot)
161 : : {
162 : 52 : gnc_commodity *currency = nullptr;
163 : : SplitList *snode;
164 : : GList *node;
165 : 52 : gnc_numeric zero = gnc_numeric_zero();
166 : 52 : gnc_numeric value = zero;
167 : :
168 : 99 : if (!lot) return;
169 : :
170 : 52 : ENTER ("lot=%s", gnc_lot_get_title(lot));
171 : :
172 : 117 : for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
173 : : {
174 : 65 : Split *s = GNC_SPLIT(snode->data);
175 : 65 : xaccSplitComputeCapGains (s, nullptr);
176 : : }
177 : :
178 : : /* We double-check only closed lots */
179 : 52 : if (FALSE == gnc_lot_is_closed (lot))
180 : : {
181 : 47 : LEAVE ("lot=%s is closed", gnc_lot_get_title(lot));
182 : 47 : return;
183 : : }
184 : :
185 : 19 : for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
186 : : {
187 : 14 : Split *s = GNC_SPLIT(snode->data);
188 : 14 : Transaction *trans = s->parent;
189 : :
190 : : /* Check to make sure all splits in the lot have a common currency */
191 : 14 : if (nullptr == currency)
192 : : {
193 : 5 : currency = trans->common_currency;
194 : : }
195 : 14 : if (FALSE == gnc_commodity_equiv (currency, trans->common_currency))
196 : : {
197 : : /* This lot has mixed currencies. Can't double-balance.
198 : : * Silently punt */
199 : 0 : PWARN ("Lot with multiple currencies:\n"
200 : : "\ttrans=%s curr=%s", xaccTransGetDescription(trans),
201 : : gnc_commodity_get_fullname(trans->common_currency));
202 : 0 : break;
203 : : }
204 : :
205 : : /* Now, total up the values */
206 : 14 : value = gnc_numeric_add (value, xaccSplitGetValue (s),
207 : : GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
208 : 14 : PINFO ("Split=%p value=%s Accum Lot value=%s", s,
209 : : gnc_num_dbg_to_string (s->value),
210 : : gnc_num_dbg_to_string (value));
211 : :
212 : : }
213 : :
214 : 5 : if (FALSE == gnc_numeric_equal (value, zero))
215 : : {
216 : : /* Unhandled error condition. Not sure what to do here,
217 : : * Since the ComputeCapGains should have gotten it right.
218 : : * I suppose there might be small rounding errors, a penny or two,
219 : : * the ideal thing would to figure out why there's a rounding
220 : : * error, and fix that.
221 : : */
222 : 0 : PERR ("Closed lot fails to double-balance !! lot value=%s",
223 : : gnc_num_dbg_to_string (value));
224 : 0 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
225 : : {
226 : 0 : Split *s = GNC_SPLIT(node->data);
227 : 0 : PERR ("s=%p amt=%s val=%s", s,
228 : : gnc_num_dbg_to_string(s->amount),
229 : : gnc_num_dbg_to_string(s->value));
230 : : }
231 : : }
232 : :
233 : 5 : LEAVE ("lot=%s", gnc_lot_get_title(lot));
234 : : }
235 : :
236 : : /* ================================================================= */
237 : :
238 : : static inline gboolean
239 : 110 : is_subsplit (Split *split)
240 : : {
241 : :
242 : : /* generic stop-progress conditions */
243 : 110 : if (!split) return FALSE;
244 : 110 : g_return_val_if_fail (split->parent, FALSE);
245 : :
246 : : /* If there are no sub-splits, then there's nothing to do. */
247 : 110 : return xaccSplitHasPeers (split);
248 : : }
249 : :
250 : : /* ================================================================= */
251 : :
252 : :
253 : : /* ================================================================= */
254 : :
255 : : /* Remove the guid of b from a. Note that a may not contain the guid
256 : : * of b, (and v.v.) in which case, it will contain other guids which
257 : : * establish the links. So merge them back in. */
258 : :
259 : : static void
260 : 0 : remove_guids (Split *sa, Split *sb)
261 : : {
262 : 0 : xaccSplitRemovePeerSplit (sa, sb);
263 : 0 : xaccSplitRemovePeerSplit (sb, sa);
264 : 0 : xaccSplitMergePeerSplits (sa, sb);
265 : 0 : }
266 : :
267 : : /* The merge_splits() routine causes the amount & value of sb
268 : : * to be merged into sa; it then destroys sb. It also performs
269 : : * some other misc cleanup */
270 : :
271 : : static void
272 : 0 : merge_splits (Split *sa, Split *sb)
273 : : {
274 : : Account *act;
275 : : Transaction *txn;
276 : : gnc_numeric amt, val;
277 : :
278 : 0 : act = xaccSplitGetAccount (sb);
279 : 0 : xaccAccountBeginEdit (act);
280 : :
281 : 0 : txn = sa->parent;
282 : 0 : xaccTransBeginEdit (txn);
283 : :
284 : : /* Remove the guid of sb from the 'gemini' of sa */
285 : 0 : remove_guids (sa, sb);
286 : :
287 : : /* Add amount of sb into sa, ditto for value. */
288 : 0 : amt = xaccSplitGetAmount (sa);
289 : 0 : amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb));
290 : 0 : xaccSplitSetAmount (sa, amt);
291 : :
292 : 0 : val = xaccSplitGetValue (sa);
293 : 0 : val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb));
294 : 0 : xaccSplitSetValue (sa, val);
295 : :
296 : : /* Set reconcile to no; after this much violence,
297 : : * no way its reconciled. */
298 : 0 : xaccSplitSetReconcile (sa, NREC);
299 : :
300 : : /* If sb has associated gains splits, trash them. */
301 : 0 : if ((sb->gains_split) &&
302 : 0 : (sb->gains_split->gains & GAINS_STATUS_GAINS))
303 : : {
304 : 0 : Transaction *t = sb->gains_split->parent;
305 : 0 : xaccTransBeginEdit (t);
306 : 0 : xaccTransDestroy (t);
307 : 0 : xaccTransCommitEdit (t);
308 : : }
309 : :
310 : : /* Finally, delete sb */
311 : 0 : xaccSplitDestroy(sb);
312 : :
313 : 0 : xaccTransCommitEdit (txn);
314 : 0 : xaccAccountCommitEdit (act);
315 : 0 : }
316 : :
317 : : gboolean
318 : 110 : xaccScrubMergeSubSplits (Split *split, gboolean strict)
319 : : {
320 : 110 : gboolean rc = FALSE;
321 : : Transaction *txn;
322 : : SplitList *node;
323 : : GNCLot *lot;
324 : :
325 : 110 : if (strict && (FALSE == is_subsplit (split))) return FALSE;
326 : :
327 : 10 : txn = split->parent;
328 : :
329 : : // Don't mess with splits from an invoice transaction
330 : : // Those are the responsibility of the business code
331 : 10 : if (gncInvoiceGetInvoiceFromTxn (txn)) return FALSE;
332 : :
333 : 10 : lot = xaccSplitGetLot (split);
334 : :
335 : 10 : ENTER ("(Lot=%s)", gnc_lot_get_title(lot));
336 : 10 : restart:
337 : 47 : for (node = txn->splits; node; node = node->next)
338 : : {
339 : 37 : Split *s = GNC_SPLIT(node->data);
340 : 37 : if (xaccSplitGetLot (s) != lot) continue;
341 : 11 : if (s == split) continue;
342 : 1 : if (qof_instance_get_destroying(s)) continue;
343 : :
344 : : // Don't mess with splits from an invoice transaction
345 : : // Those are the responsibility of the business code
346 : 1 : if (gncInvoiceGetInvoiceFromTxn (s->parent)) return FALSE;
347 : :
348 : 1 : if (strict)
349 : : {
350 : : /* OK, this split is in the same lot (and thus same account)
351 : : * as the indicated split. Make sure it is really a subsplit
352 : : * of the split we started with. It's possible to have two
353 : : * splits in the same lot and transaction that are not subsplits
354 : : * of each other, the test-period test suite does this, for
355 : : * example. Only worry about adjacent sub-splits. By
356 : : * repeatedly merging adjacent subsplits, we'll get the non-
357 : : * adjacent ones too. */
358 : 1 : if (!xaccSplitIsPeerSplit (split, s))
359 : 1 : continue;
360 : : }
361 : :
362 : 0 : merge_splits (split, s);
363 : 0 : rc = TRUE;
364 : 0 : goto restart;
365 : : }
366 : 10 : if (rc && gnc_numeric_zero_p (split->amount))
367 : : {
368 : 0 : time64 pdate = xaccTransGetDate (txn);
369 : 0 : gchar *pdatestr = gnc_ctime (&pdate);
370 : 0 : PWARN ("Result of merge has zero amt!");
371 : 0 : PWARN ("Transaction details - posted date %s - description %s", pdatestr, xaccTransGetDescription(txn));
372 : 0 : g_free (pdatestr);
373 : : }
374 : 10 : LEAVE (" splits merged=%d", rc);
375 : 10 : return rc;
376 : : }
377 : :
378 : : gboolean
379 : 101 : xaccScrubMergeLotSubSplits (GNCLot *lot, gboolean strict)
380 : : {
381 : 101 : gboolean rc = FALSE;
382 : : SplitList *node;
383 : :
384 : 101 : if (!lot) return FALSE;
385 : :
386 : 101 : ENTER (" ");
387 : 101 : restart:
388 : 211 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
389 : : {
390 : 110 : Split *s = GNC_SPLIT(node->data);
391 : 110 : if (!xaccScrubMergeSubSplits(s, strict)) continue;
392 : :
393 : 0 : rc = TRUE;
394 : 0 : goto restart;
395 : : }
396 : 101 : LEAVE (" splits merged=%d", rc);
397 : 101 : return rc;
398 : : }
399 : :
400 : : /* =========================== END OF FILE ======================= */
|