Branch data Line data Source code
1 : : /********************************************************************\
2 : : * cap-gains.c -- Automatically Compute Capital Gains/Losses *
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 cap-gains.c
23 : : * @brief Utilities to Automatically Compute Capital Gains/Losses.
24 : : * @author Created by Linas Vepstas August 2003
25 : : * @author Copyright (c) 2003,2004 Linas Vepstas <linas@linas.org>
26 : : *
27 : : * This file implements the various routines to automatically
28 : : * compute and handle Cap Gains/Losses resulting from trading
29 : : * activities. Some of these routines might have broader
30 : : * applicability, for handling depreciation & etc.
31 : : *
32 : : * This code is under development, and is 'beta': we think we're
33 : : * mostly done, and we've tested and "things work for us", but there
34 : : * may still be something missing, and there might still be some
35 : : * bugs.
36 : : *
37 : : * This code uses a 'gains dirty' flag: A 'dirty' flag on the source
38 : : * split indicates that the gains transaction needs to be recomputed.
39 : : * Another flag, the gains transaction flag, marks the split as
40 : : * being a gains split, and that the source transaction should be
41 : : * checked for dirtiness before returning the date, the amount, the
42 : : * value, etc. Finally, these flags make amount and value read-only
43 : : * for the gains splits. (the memo is user-modifieable).
44 : : *
45 : : * If the amount in a split is changed, then the lot has to be recomputed.
46 : : * This has a potential trickle-through effect on all later lots.
47 : : * Ideally, later lots are dissolved, and recomputed. However, some
48 : : * lots may have been user-hand-built. These should be left alone.
49 : : *
50 : : ToDo:
51 : : o XXX Need to create a data-integrity scrubber, that makes sure that
52 : : the various flags, and pointers & etc. match. See sections marked
53 : : with XXX below for things that might go wrong.
54 : : */
55 : :
56 : : #include <config.h>
57 : :
58 : : #include <glib.h>
59 : : #include <glib/gi18n.h>
60 : :
61 : : #include "Account.hpp"
62 : : #include "AccountP.hpp"
63 : : #include "Scrub2.h"
64 : : #include "Scrub3.h"
65 : : #include "Transaction.h"
66 : : #include "TransactionP.hpp"
67 : : #include "cap-gains.h"
68 : : #include "gnc-engine.h"
69 : : #include "engine-helpers.h"
70 : : #include "gnc-lot.h"
71 : : #include "policy.h"
72 : : #include "policy-p.h"
73 : :
74 : : static QofLogModule log_module = GNC_MOD_LOT;
75 : :
76 : :
77 : : /* ============================================================== */
78 : :
79 : : gboolean
80 : 70 : xaccAccountHasTrades (const Account *acc)
81 : : {
82 : : gnc_commodity *acc_comm;
83 : :
84 : 70 : if (!acc) return FALSE;
85 : :
86 : 70 : if (xaccAccountIsPriced (acc))
87 : 28 : return TRUE;
88 : :
89 : 42 : acc_comm = xaccAccountGetCommodity(acc);
90 : :
91 : 44 : for (auto s : xaccAccountGetSplits (acc))
92 : : {
93 : 41 : Transaction *t = s->parent;
94 : 41 : if (s->gains == GAINS_STATUS_GAINS) continue;
95 : 41 : if (acc_comm != t->common_currency) return TRUE;
96 : : }
97 : :
98 : 3 : return FALSE;
99 : : }
100 : :
101 : : /* ============================================================== */
102 : :
103 : : struct FindLot
104 : : {
105 : : GNCLot *lot;
106 : : gnc_commodity *currency;
107 : : time64 time;
108 : : int (*numeric_pred)(gnc_numeric);
109 : : gboolean (*date_pred)(time64 e, time64 tr);
110 : : };
111 : :
112 : : static gboolean
113 : 11 : earliest_pred (time64 earl, time64 tran)
114 : : {
115 : 11 : return earl > tran;
116 : : }
117 : :
118 : : static gboolean
119 : 0 : latest_pred (time64 earl, time64 tran)
120 : : {
121 : 0 : return earl < tran;
122 : : }
123 : :
124 : : static gpointer
125 : 757 : finder_helper (GNCLot *lot, gpointer user_data)
126 : : {
127 : 757 : auto els = static_cast<FindLot*>(user_data);
128 : : Split *s;
129 : : Transaction *trans;
130 : : gnc_numeric bal;
131 : : gboolean opening_is_positive, bal_is_positive;
132 : 757 : time64 posted = 0;
133 : :
134 : 757 : if (gnc_lot_is_closed (lot)) return nullptr;
135 : :
136 : 711 : s = gnc_lot_get_earliest_split (lot);
137 : 711 : if (s == nullptr) return nullptr;
138 : :
139 : : /* We want a lot whose balance is of the correct sign. All splits
140 : : in a lot must be the opposite sign of the opening split. We also
141 : : want to ignore lots that are overfull, i.e., where the balance in
142 : : the lot is of opposite sign to the opening split in the lot. */
143 : 711 : if (0 == (els->numeric_pred) (s->amount)) return nullptr;
144 : 369 : bal = gnc_lot_get_balance (lot);
145 : 369 : opening_is_positive = gnc_numeric_positive_p (s->amount);
146 : 369 : bal_is_positive = gnc_numeric_positive_p (bal);
147 : 369 : if (opening_is_positive != bal_is_positive) return nullptr;
148 : :
149 : 369 : trans = s->parent;
150 : 738 : if (els->currency &&
151 : 369 : (FALSE == gnc_commodity_equiv (els->currency,
152 : 369 : trans->common_currency)))
153 : : {
154 : 358 : return nullptr;
155 : : }
156 : :
157 : 11 : posted = trans->date_posted;
158 : 11 : if (els->date_pred (els->time, posted))
159 : : {
160 : 11 : els->time = trans->date_posted;
161 : 11 : els->lot = lot;
162 : : }
163 : :
164 : 11 : return nullptr;
165 : : }
166 : :
167 : : static inline GNCLot *
168 : 64 : xaccAccountFindOpenLot (Account *acc, gnc_numeric sign,
169 : : gnc_commodity *currency,
170 : : gint64 guess,
171 : : gboolean (*date_pred)(time64, time64))
172 : : {
173 : : FindLot es;
174 : :
175 : 64 : es.lot = nullptr;
176 : 64 : es.currency = currency;
177 : 64 : es.time = guess;
178 : 64 : es.date_pred = date_pred;
179 : :
180 : 64 : if (gnc_numeric_positive_p(sign)) es.numeric_pred = gnc_numeric_negative_p;
181 : 34 : else es.numeric_pred = gnc_numeric_positive_p;
182 : :
183 : 64 : xaccAccountForEachLot (acc, finder_helper, &es);
184 : 64 : return es.lot;
185 : : }
186 : :
187 : : GNCLot *
188 : 64 : xaccAccountFindEarliestOpenLot (Account *acc, gnc_numeric sign,
189 : : gnc_commodity *currency)
190 : : {
191 : : GNCLot *lot;
192 : 64 : ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, sign.num,
193 : : sign.denom);
194 : :
195 : 64 : lot = xaccAccountFindOpenLot (acc, sign, currency,
196 : : G_MAXINT64, earliest_pred);
197 : 64 : LEAVE ("found lot=%p %s baln=%s", lot, gnc_lot_get_title (lot),
198 : : gnc_num_dbg_to_string(gnc_lot_get_balance(lot)));
199 : 64 : return lot;
200 : : }
201 : :
202 : : GNCLot *
203 : 0 : xaccAccountFindLatestOpenLot (Account *acc, gnc_numeric sign,
204 : : gnc_commodity *currency)
205 : : {
206 : : GNCLot *lot;
207 : 0 : ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT,
208 : : sign.num, sign.denom);
209 : :
210 : 0 : lot = xaccAccountFindOpenLot (acc, sign, currency,
211 : : G_MININT64, latest_pred);
212 : 0 : LEAVE ("found lot=%p %s", lot, gnc_lot_get_title (lot));
213 : 0 : return lot;
214 : : }
215 : :
216 : : /* ============================================================== */
217 : :
218 : : Split *
219 : 64 : xaccSplitAssignToLot (Split *split, GNCLot *lot)
220 : : {
221 : : Account *acc;
222 : : gnc_numeric baln;
223 : : int cmp;
224 : : gboolean baln_is_positive, amt_is_positive;
225 : :
226 : 64 : if (!lot) return split;
227 : 64 : if (!split) return nullptr;
228 : :
229 : : /* If this split already belongs to a lot, we are done. */
230 : 64 : if (split->lot) return nullptr;
231 : :
232 : : /* Anomalous situation; except for voided transactions,
233 : : * we don't expect to see splits with no amount ..
234 : : * unless they're gains splits, and we shouldn't see those.
235 : : */
236 : 64 : if (gnc_numeric_zero_p (split->amount))
237 : : {
238 : 0 : if (xaccTransGetVoidStatus(split->parent)) return nullptr;
239 : :
240 : 0 : PWARN ("split with zero amount; value=%s gflag=%x gsplit=%p",
241 : : gnc_num_dbg_to_string (split->amount),
242 : : split->gains,
243 : : split->gains_split);
244 : 0 : if (split->gains_split)
245 : : {
246 : 0 : PWARN ("gains amt=%s value=%s",
247 : : gnc_num_dbg_to_string (split->gains_split->amount),
248 : : gnc_num_dbg_to_string (split->gains_split->value));
249 : : }
250 : 0 : return nullptr;
251 : : }
252 : :
253 : : /* If the lot is closed, we can't add anything to it */
254 : 64 : baln = gnc_lot_get_balance (lot);
255 : 64 : if (gnc_lot_is_closed (lot)) return split;
256 : :
257 : : /* If the lot balance is zero, but the lot is open, then
258 : : * the lot is empty. Unconditionally add the split. */
259 : 64 : if (gnc_numeric_zero_p (baln))
260 : : {
261 : 55 : acc = split->acc;
262 : 55 : xaccAccountBeginEdit (acc);
263 : 55 : gnc_lot_add_split (lot, split);
264 : 55 : PINFO ("added split to empty lot, new lot baln=%s (%s)",
265 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
266 : : gnc_lot_get_title (lot));
267 : 55 : xaccAccountCommitEdit (acc);
268 : 55 : return nullptr;
269 : : }
270 : :
271 : : /* If the sign of the split is the same as the sign of the lot,
272 : : * add the split, but complain about it ... none of the currently
273 : : * implemented accounting policies should be giving us splits
274 : : * that make lots larger. One a lot is open, the FIFO/LIFO
275 : : * policies should be working only to make the lot smaller.
276 : : * We can remove the warning emssage come the day we have
277 : : * fancier policies.
278 : : */
279 : 9 : baln_is_positive = gnc_numeric_positive_p (baln);
280 : 9 : amt_is_positive = gnc_numeric_positive_p (split->amount);
281 : 9 : if ((baln_is_positive && amt_is_positive) ||
282 : 5 : ((!baln_is_positive) && (!amt_is_positive)))
283 : : {
284 : 0 : PWARN ("accounting policy gave us split that enlarges the lot!\n"
285 : : "old lot baln=%s split amt=%s lot=%s",
286 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)),
287 : : gnc_num_dbg_to_string (split->amount),
288 : : gnc_lot_get_title (lot));
289 : :
290 : 0 : acc = split->acc;
291 : 0 : xaccAccountBeginEdit (acc);
292 : 0 : gnc_lot_add_split (lot, split);
293 : 0 : xaccAccountCommitEdit (acc);
294 : 0 : return nullptr;
295 : : }
296 : :
297 : : /* If adding the split would make the lot balance change sign,
298 : : * then we split the split into two pieces: one piece that will
299 : : * bring the lot balance to zero, and another to be dealt with
300 : : * later. */
301 : 9 : cmp = gnc_numeric_compare (gnc_numeric_abs(xaccSplitGetAdjustedAmount (split)),
302 : : gnc_numeric_abs(baln));
303 : :
304 : 9 : PINFO ("found open lot with baln=%s (%s)", gnc_num_dbg_to_string (baln),
305 : : gnc_lot_get_title (lot));
306 : :
307 : : /* cmp == -1 if amt < baln, ==0 if amt==baln */
308 : 9 : if (0 >= cmp)
309 : : {
310 : 5 : acc = split->acc;
311 : 5 : xaccAccountBeginEdit (acc);
312 : 5 : gnc_lot_add_split (lot, split);
313 : 5 : PINFO ("simple added split to lot, new lot baln=%s",
314 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
315 : 5 : xaccAccountCommitEdit (acc);
316 : 5 : return nullptr;
317 : : }
318 : :
319 : : /* If we are here, then (cmp == +1 iff (amt > baln)) and we need
320 : : * to split up the split into pieces. Do it. */
321 : : {
322 : 4 : time64 now = gnc_time (nullptr), time = 0;
323 : : Split * new_split;
324 : : gnc_numeric amt_a, amt_b, amt_tot;
325 : : gnc_numeric val_a, val_b, val_tot;
326 : : gnc_numeric frac;
327 : : Transaction *trans;
328 : :
329 : 4 : acc = split->acc;
330 : 4 : xaccAccountBeginEdit (acc);
331 : 4 : trans = split->parent;
332 : 4 : xaccTransBeginEdit (trans);
333 : :
334 : : /* Adjust split's view of the lot balance for stock splits */
335 : 4 : if (!gnc_numeric_equal (split->amount, xaccSplitGetAdjustedAmount (split)))
336 : : {
337 : 0 : frac = gnc_numeric_div (split->amount, xaccSplitGetAdjustedAmount (split),
338 : : GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
339 : 0 : baln = gnc_numeric_mul (baln, frac,
340 : 0 : xaccAccountGetCommoditySCU(acc),
341 : : GNC_HOW_RND_ROUND_HALF_UP);
342 : : }
343 : :
344 : 4 : amt_tot = split->amount;
345 : 4 : amt_a = gnc_numeric_neg (baln);
346 : 4 : amt_b = gnc_numeric_sub_fixed (amt_tot, amt_a);
347 : 4 : g_return_val_if_fail(gnc_numeric_check(amt_b) == GNC_ERROR_OK, nullptr);
348 : :
349 : 4 : PINFO ("++++++++++++++ splitting split=%p into amt = %s + %s",
350 : : split,
351 : : gnc_num_dbg_to_string(amt_a),
352 : : gnc_num_dbg_to_string(amt_b) );
353 : :
354 : : /* Compute the value so that it holds in the same proportion:
355 : : * i.e. so that (amt_a / amt_tot) = (val_a / val_tot)
356 : : */
357 : 4 : val_tot = split->value;
358 : 4 : frac = gnc_numeric_div (amt_a, amt_tot,
359 : : GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
360 : 4 : val_a = gnc_numeric_mul (frac, val_tot,
361 : : gnc_numeric_denom(val_tot),
362 : : GNC_HOW_RND_ROUND_HALF_UP | GNC_HOW_DENOM_EXACT);
363 : :
364 : 4 : val_b = gnc_numeric_sub_fixed (val_tot, val_a);
365 : 4 : if (gnc_numeric_check(val_a))
366 : : {
367 : 0 : PERR("Numeric overflow\n"
368 : : "Acct=%s Txn=%s\n"
369 : : "\tval_tot=%s amt_a=%s amt_tot=%s\n",
370 : : xaccAccountGetName(acc),
371 : : xaccTransGetDescription(trans),
372 : : gnc_num_dbg_to_string(val_tot),
373 : : gnc_num_dbg_to_string(amt_a),
374 : : gnc_num_dbg_to_string(amt_tot));
375 : : }
376 : :
377 : 4 : if (gnc_numeric_zero_p(amt_a) || gnc_numeric_zero_p(amt_b))
378 : : {
379 : 0 : PERR ("Failed to split into two!");
380 : : }
381 : :
382 : 4 : PINFO ("split value is = %s = %s + %s",
383 : : gnc_num_dbg_to_string(val_tot),
384 : : gnc_num_dbg_to_string(val_a),
385 : : gnc_num_dbg_to_string(val_b) );
386 : :
387 : 4 : g_return_val_if_fail (!gnc_numeric_zero_p (amt_a), nullptr);
388 : 4 : g_return_val_if_fail (!gnc_numeric_check (val_a), nullptr);
389 : 4 : xaccSplitSetAmount (split, amt_a);
390 : 4 : xaccSplitSetValue (split, val_a);
391 : :
392 : : /* Adding this split will have the effect of closing this lot,
393 : : * because the new balance should be precisely zero. */
394 : 4 : gnc_lot_add_split (lot, split);
395 : :
396 : : /* Put the remainder of the balance into a new split,
397 : : * which is in other respects just a clone of this one. */
398 : 4 : new_split = xaccMallocSplit (qof_instance_get_book(acc));
399 : :
400 : : /* Copy most of the split attributes */
401 : 4 : xaccSplitSetMemo (new_split, xaccSplitGetMemo (split));
402 : : /* Set split-action with gnc_set_num_action which is the same as
403 : : * xaccSplitSetAction with these arguments; use gnc_get_num_action to get
404 : : * split-action which is the same as xaccSplitGetAction */
405 : 4 : gnc_set_num_action(nullptr, new_split, nullptr, gnc_get_num_action(nullptr, split));
406 : 4 : xaccSplitSetReconcile (new_split, xaccSplitGetReconcile (split));
407 : 4 : time = xaccSplitGetDateReconciled (split);
408 : 4 : xaccSplitSetDateReconciledSecs (new_split, time);
409 : :
410 : : /* Set the lot-split and peer_guid properties on the two
411 : : * splits to indicate that they're linked.
412 : : */
413 : 4 : xaccSplitAddPeerSplit(split, new_split, now);
414 : 4 : xaccSplitAddPeerSplit(new_split, split, now);
415 : 4 : xaccAccountInsertSplit (acc, new_split);
416 : 4 : xaccTransAppendSplit (trans, new_split);
417 : : /* Set the amount and value after the split is in the transaction
418 : : so it can find the correct denominator to use. Otherwise it
419 : : uses 100000 which may cause an overflow in some of the tests
420 : : in test-period */
421 : 4 : xaccSplitSetAmount (new_split, amt_b);
422 : 4 : xaccSplitSetValue (new_split, val_b);
423 : 4 : xaccTransCommitEdit (trans);
424 : 4 : xaccAccountCommitEdit (acc);
425 : 4 : return new_split;
426 : : }
427 : : }
428 : :
429 : : /* ============================================================== */
430 : :
431 : : /* Accounting-policy callback. Given an account and an amount,
432 : : * this routine should return a lot. By implementing this as
433 : : * a callback, we can 'easily' add other accounting policies.
434 : : */
435 : : gboolean
436 : 60 : xaccSplitAssign (Split *split)
437 : : {
438 : : Account *acc;
439 : 60 : gboolean splits_split_up = FALSE;
440 : : GNCLot *lot;
441 : : GNCPolicy *pcy;
442 : :
443 : 60 : if (!split) return FALSE;
444 : :
445 : : /* If this split already belongs to a lot or the account doesn't
446 : : * have lots, we are done.
447 : : */
448 : 60 : if (split->lot) return FALSE;
449 : 60 : g_return_val_if_fail (split->gains == GAINS_STATUS_UNKNOWN ||
450 : : (split->gains & GAINS_STATUS_GAINS) == FALSE, FALSE);
451 : 60 : acc = split->acc;
452 : 60 : if (!xaccAccountHasTrades (acc))
453 : 0 : return FALSE;
454 : 60 : if (gnc_numeric_zero_p (split->amount))
455 : 0 : return FALSE;
456 : :
457 : 60 : ENTER ("(split=%p)", split);
458 : :
459 : 60 : pcy = gnc_account_get_policy(acc);
460 : 60 : xaccAccountBeginEdit (acc);
461 : :
462 : : /* If we are here, this split does not belong to any lot.
463 : : * We ask the policy for a lot to assign it to. This
464 : : * block is written in the form of a while loop, since we
465 : : * may have to bust a split across several lots.
466 : : */
467 : 124 : while (split)
468 : : {
469 : 64 : PINFO ("have split %p amount=%s", split,
470 : : gnc_num_dbg_to_string (split->amount));
471 : 64 : split->gains |= GAINS_STATUS_VDIRTY;
472 : 64 : lot = pcy->PolicyGetLot (pcy, split);
473 : 64 : if (!lot)
474 : : {
475 : 55 : lot = gnc_lot_make_default (acc);
476 : 55 : PINFO ("start new lot (%s)", gnc_lot_get_title(lot));
477 : : }
478 : 64 : split = xaccSplitAssignToLot (split, lot);
479 : 64 : if (split) splits_split_up = TRUE;
480 : : }
481 : 60 : xaccAccountCommitEdit (acc);
482 : :
483 : 60 : LEAVE (" split_up=%d", splits_split_up);
484 : 60 : return splits_split_up;
485 : : }
486 : :
487 : : /* ============================================================== */
488 : :
489 : : static Split *
490 : 413 : find_split_by_type (const gchar *type, const Split *split)
491 : : {
492 : 413 : GncGUID *type_guid = nullptr;
493 : :
494 : 413 : if (!split) return nullptr;
495 : :
496 : 413 : qof_instance_get (QOF_INSTANCE (split), type, &type_guid, nullptr);
497 : 413 : if (!type_guid) return nullptr;
498 : :
499 : : /* Both splits will be in the same collection, so search there. */
500 : 18 : auto *guid_split = (Split *) qof_collection_lookup_entity (qof_instance_get_collection (split), type_guid);
501 : 18 : PINFO ("split=%p has %s=%p", split, type, guid_split);
502 : 18 : guid_free (type_guid);
503 : 18 : return guid_split;
504 : : }
505 : :
506 : :
507 : : Split *
508 : 305 : xaccSplitGetCapGainsSplit (const Split *split)
509 : : {
510 : 305 : return find_split_by_type ("gains-split", split);
511 : : }
512 : :
513 : : Split *
514 : 108 : xaccSplitGetGainsSourceSplit (const Split *split)
515 : : {
516 : 108 : return find_split_by_type ("gains-source", split);
517 : : }
518 : :
519 : : /* ============================================================== */
520 : :
521 : : void
522 : 139 : xaccSplitComputeCapGains(Split *split, Account *gain_acc)
523 : : {
524 : : SplitList *node;
525 : : GNCLot *lot;
526 : : GNCPolicy *pcy;
527 : 139 : gnc_commodity *currency = nullptr;
528 : 139 : gnc_numeric zero = gnc_numeric_zero();
529 : : gnc_numeric value;
530 : : gnc_numeric frac;
531 : : gnc_numeric opening_amount, opening_value;
532 : : gnc_numeric lot_amount, lot_value;
533 : : gnc_commodity *opening_currency;
534 : :
535 : 248 : if (!split) return;
536 : 139 : lot = split->lot;
537 : 139 : if (!lot) return;
538 : 138 : pcy = gnc_account_get_policy(gnc_lot_get_account(lot));
539 : 138 : currency = split->parent->common_currency;
540 : :
541 : 138 : ENTER ("(split=%p gains=%p status=0x%x lot=%s)", split,
542 : : split->gains_split, split->gains, gnc_lot_get_title(lot));
543 : :
544 : : /* Make sure the status flags and pointers are initialized */
545 : 138 : xaccSplitDetermineGainStatus(split);
546 : :
547 : : /* Not possible to have gains if the transaction currency and
548 : : * account commodity are identical. */
549 : 138 : if (gnc_commodity_equal (currency,
550 : 138 : xaccAccountGetCommodity(split->acc)))
551 : : {
552 : 0 : LEAVE ("Currency transfer, gains not possible, returning.");
553 : 0 : return;
554 : : }
555 : :
556 : 138 : if (pcy->PolicyIsOpeningSplit (pcy, lot, split))
557 : : {
558 : : #if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
559 : : /* Check to make sure that this opening split doesn't
560 : : * have a cap-gain transaction associated with it.
561 : : * If it does, that's wrong, and we ruthlessly destroy it.
562 : : * XXX Don't do this, it leads to infinite loops.
563 : : * We need to scrub out errors like this elsewhere!
564 : : */
565 : : if (xaccSplitGetCapGainsSplit (split))
566 : : {
567 : : Split *gains_split = xaccSplitGetCapGainsSplit(split);
568 : : Transaction *trans = gains_split->parent;
569 : : PERR ("Opening Split must not have cap gains!!\n");
570 : :
571 : : xaccTransBeginEdit (trans);
572 : : xaccTransDestroy (trans);
573 : : xaccTransCommitEdit (trans);
574 : : }
575 : : #endif
576 : 108 : LEAVE ("Lot opening split, returning.");
577 : 108 : return;
578 : : }
579 : :
580 : 30 : if (xaccSplitIsStockSplit (split))
581 : : {
582 : 0 : LEAVE ("Stock split split, returning.");
583 : 0 : return;
584 : : }
585 : :
586 : 30 : if (GAINS_STATUS_GAINS & split->gains)
587 : : {
588 : : Split *s;
589 : 12 : PINFO ("split is a gains recording split, switch over");
590 : : /* If this is the split that records the gains, then work with
591 : : * the split that generates the gains.
592 : : */
593 : : /* split = xaccSplitGetCapGainsSplit (split); */
594 : 12 : s = split->gains_split;
595 : :
596 : : /* This should never be nullptr, and if it is, and its matching
597 : : * parent can't be found, then its a bug, and we should be
598 : : * discarding this split. But ... for now .. return.
599 : : * XXX move appropriate actions to a 'scrub' routine'
600 : : */
601 : 12 : if (!s)
602 : : {
603 : 0 : PERR ("Bad gains-split pointer! .. trying to recover.");
604 : 0 : split->gains_split = xaccSplitGetCapGainsSplit (split);
605 : 0 : s = split->gains_split;
606 : 0 : if (!s) return;
607 : : #if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
608 : : xaccTransDestroy (trans);
609 : : #endif
610 : : }
611 : 12 : split = s;
612 : : }
613 : :
614 : : /* Note: if the value of the 'opening' split(s) has changed,
615 : : * then the cap gains are changed. So we need to check not
616 : : * only if this split is dirty, but also the lot-opening splits. */
617 : 30 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
618 : : {
619 : 30 : Split *s = GNC_SPLIT(node->data);
620 : 30 : if (pcy->PolicyIsOpeningSplit(pcy, lot, s))
621 : : {
622 : 30 : if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus (s);
623 : 30 : if (s->gains & GAINS_STATUS_VDIRTY)
624 : : {
625 : : /* Force a recompute to occur */
626 : 30 : split->gains |= GAINS_STATUS_VDIRTY;
627 : 30 : break;
628 : : }
629 : : }
630 : : }
631 : :
632 : : /* If it doesn't look like this split is 'dirty', then there's
633 : : * nothing to do. Just return. */
634 : 30 : if ((FALSE == (split->gains & GAINS_STATUS_A_VDIRTY)) &&
635 : 0 : (split->gains_split) &&
636 : 0 : (FALSE == (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
637 : : {
638 : 0 : LEAVE ("split not dirty, returning");
639 : 0 : return;
640 : : }
641 : :
642 : : /* Yow! If amount is zero, there's nothing to do! Amount-zero splits
643 : : * may exist if users attempted to manually record gains. */
644 : 30 : if (gnc_numeric_zero_p (split->amount)) return;
645 : :
646 : : /* If we got to here, then the split or something related is
647 : : * 'dirty' and the gains really do need to be recomputed.
648 : : * So start working things. */
649 : :
650 : : /* Get the amount and value in this lot at the time of this transaction. */
651 : 30 : gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
652 : :
653 : 30 : pcy->PolicyGetLotOpening (pcy, lot, &opening_amount, &opening_value,
654 : : &opening_currency);
655 : :
656 : : /* Check to make sure the lot-opening currency and this split
657 : : * use the same currency */
658 : 30 : if (FALSE == gnc_commodity_equiv (currency, opening_currency))
659 : : {
660 : : /* OK, the purchase and the sale were made in different currencies.
661 : : * I don't know how to compute cap gains for that. This is not
662 : : * an error. Just punt, silently.
663 : : */
664 : 0 : LEAVE ("Can't compute gains, mismatched commodities!");
665 : 0 : return;
666 : : }
667 : :
668 : : /* Opening amount should be larger (or equal) to current split,
669 : : * and it should be of the opposite sign.
670 : : * XXX This should really be a part of a scrub routine that
671 : : * cleans up the lot, before we get at it!
672 : : */
673 : 30 : if (0 > gnc_numeric_compare (gnc_numeric_abs(lot_amount),
674 : : gnc_numeric_abs(xaccSplitGetAdjustedAmount (split))))
675 : : {
676 : : GList *n;
677 : 0 : for (n = gnc_lot_get_split_list(lot); n; n = n->next)
678 : : {
679 : 0 : Split *s = GNC_SPLIT(n->data);
680 : 0 : PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
681 : : }
682 : 0 : PERR ("Malformed Lot \"%s\"! (too thin!) "
683 : : "opening amt=%s split adj amt=%s baln=%s",
684 : : gnc_lot_get_title (lot),
685 : : gnc_num_dbg_to_string (lot_amount),
686 : : gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
687 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
688 : 0 : return;
689 : : }
690 : 30 : if ( (gnc_numeric_negative_p(lot_amount) ||
691 : 44 : gnc_numeric_positive_p(xaccSplitGetAdjustedAmount (split))) &&
692 : 14 : (gnc_numeric_positive_p(lot_amount) ||
693 : 14 : gnc_numeric_negative_p(xaccSplitGetAdjustedAmount (split))))
694 : : {
695 : : GList *n;
696 : 0 : for (n = gnc_lot_get_split_list(lot); n; n = n->next)
697 : : {
698 : 0 : Split *s = GNC_SPLIT(n->data);
699 : 0 : PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
700 : : }
701 : 0 : PERR ("Malformed Lot \"%s\"! (too fat!) "
702 : : "opening adj amt=%s split adj amt=%s baln=%s",
703 : : gnc_lot_get_title (lot),
704 : : gnc_num_dbg_to_string (lot_amount),
705 : : gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
706 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
707 : 0 : return;
708 : : }
709 : :
710 : : /* The cap gains is the difference between the basis prior to the
711 : : * current split, and the current split, pro-rated for an equal
712 : : * amount of shares.
713 : : * i.e. purchase_price = lot_value / lot_amount
714 : : * cost_basis = purchase_price * current_split_amount
715 : : * cap_gain = current_split_value - cost_basis
716 : : */
717 : : /* Fraction of the lot that this split represents: */
718 : 30 : frac = gnc_numeric_div (xaccSplitGetAdjustedAmount (split), lot_amount,
719 : : GNC_DENOM_AUTO,
720 : : GNC_HOW_DENOM_REDUCE);
721 : : /* Basis for this split: */
722 : 30 : value = gnc_numeric_mul (frac, lot_value,
723 : : gnc_numeric_denom(opening_value),
724 : : GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP);
725 : : /* Capital gain for this split: */
726 : 30 : value = gnc_numeric_sub (value, split->value,
727 : : GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
728 : 30 : PINFO ("Open amt=%s val=%s; split adj amt=%s val=%s; gains=%s\n",
729 : : gnc_num_dbg_to_string (lot_amount),
730 : : gnc_num_dbg_to_string (lot_value),
731 : : gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
732 : : gnc_num_dbg_to_string (split->value),
733 : : gnc_num_dbg_to_string (value));
734 : 30 : if (gnc_numeric_check (value))
735 : : {
736 : 0 : PERR ("Numeric overflow during gains calculation\n"
737 : : "Acct=%s Txn=%s\n"
738 : : "\tOpen amt=%s val=%s\n\tsplit amt=%s val=%s\n\tgains=%s\n",
739 : : xaccAccountGetName(split->acc),
740 : : xaccTransGetDescription(split->parent),
741 : : gnc_num_dbg_to_string (lot_amount),
742 : : gnc_num_dbg_to_string (lot_value),
743 : : gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
744 : : gnc_num_dbg_to_string (split->value),
745 : : gnc_num_dbg_to_string (value));
746 : 0 : return;
747 : : }
748 : :
749 : : /* Are the cap gains zero? If not, add a balancing transaction.
750 : : * As per design doc lots.txt: the transaction has two splits,
751 : : * with equal & opposite values. The amt of one is zero (so as
752 : : * not to upset the lot balance), the amt of the other is the same
753 : : * as its value (its the realized gain/loss).
754 : : */
755 : 30 : if (FALSE == gnc_numeric_zero_p (value))
756 : : {
757 : : Transaction *trans;
758 : : Split *lot_split, *gain_split;
759 : : gboolean new_gain_split;
760 : 24 : gnc_numeric negvalue = gnc_numeric_neg (value);
761 : :
762 : : /* See if there already is an associated gains transaction.
763 : : * If there is, adjust its value as appropriate. Else, create
764 : : * a new gains transaction.
765 : : */
766 : : /* lot_split = xaccSplitGetCapGainsSplit (split); */
767 : 24 : lot_split = split->gains_split;
768 : :
769 : 24 : if (nullptr == lot_split)
770 : : {
771 : 6 : Account *lot_acc = gnc_lot_get_account(lot);
772 : 6 : QofBook *book = qof_instance_get_book(lot_acc);
773 : 6 : Transaction *base_txn = xaccSplitGetParent (split);
774 : :
775 : 6 : new_gain_split = TRUE;
776 : :
777 : 6 : lot_split = xaccMallocSplit (book);
778 : 6 : gain_split = xaccMallocSplit (book);
779 : :
780 : : /* Check to make sure the gains account currency matches. */
781 : 6 : if ((nullptr == gain_acc) ||
782 : 0 : (FALSE == gnc_commodity_equiv (currency,
783 : 0 : xaccAccountGetCommodity(gain_acc))))
784 : : {
785 : 6 : gain_acc = xaccAccountGainsAccount (lot_acc, currency);
786 : : }
787 : :
788 : 6 : xaccAccountBeginEdit (gain_acc);
789 : 6 : xaccAccountInsertSplit (gain_acc, gain_split);
790 : 6 : xaccAccountCommitEdit (gain_acc);
791 : :
792 : 6 : xaccAccountBeginEdit (lot_acc);
793 : 6 : xaccAccountInsertSplit (lot_acc, lot_split);
794 : 6 : xaccAccountCommitEdit (lot_acc);
795 : :
796 : 6 : trans = xaccMallocTransaction (book);
797 : :
798 : 6 : xaccTransBeginEdit (trans);
799 : 6 : xaccTransSetCurrency (trans, currency);
800 : 6 : xaccTransSetDescription (trans, _("Realized Gain/Loss"));
801 : :
802 : 6 : xaccTransAppendSplit (trans, lot_split);
803 : 6 : xaccTransAppendSplit (trans, gain_split);
804 : :
805 : 6 : xaccSplitSetMemo (lot_split, _("Realized Gain/Loss"));
806 : 6 : xaccSplitSetMemo (gain_split, _("Realized Gain/Loss"));
807 : :
808 : : /* For the new transaction, set the split properties indicating
809 : : * that this is the gains transaction that corresponds
810 : : * to the gains source.
811 : : */
812 : 6 : xaccTransBeginEdit (base_txn);
813 : 6 : qof_instance_set (QOF_INSTANCE (split),
814 : 6 : "gains-split", xaccSplitGetGUID (lot_split),
815 : : nullptr);
816 : 6 : xaccTransCommitEdit (base_txn);
817 : 6 : qof_instance_set (QOF_INSTANCE (lot_split),
818 : 6 : "gains-source", xaccSplitGetGUID (split),
819 : : nullptr);
820 : :
821 : : }
822 : : else
823 : : {
824 : 18 : trans = lot_split->parent;
825 : 18 : gain_split = xaccSplitGetOtherSplit (lot_split);
826 : :
827 : : /* If the gains transaction has been edited so that it no longer has
828 : : just two splits, ignore it and assume it's still correct. */
829 : 18 : if (!gain_split)
830 : : {
831 : 0 : new_gain_split = FALSE;
832 : : }
833 : : /* If the gain is already recorded correctly do nothing. This is
834 : : * more than just an optimization since this may be called during
835 : : * gnc_book_partition_txn and depending on the order in which things
836 : : * happen some splits may be in the wrong book at that time. */
837 : 54 : else if (split->gains_split == lot_split &&
838 : 18 : lot_split->gains_split == split &&
839 : 18 : gain_split->gains_split == split &&
840 : 18 : gnc_numeric_equal (xaccSplitGetValue (lot_split), value) &&
841 : 18 : gnc_numeric_zero_p (xaccSplitGetAmount (lot_split)) &&
842 : 54 : gnc_numeric_equal (xaccSplitGetValue (gain_split), negvalue) &&
843 : 18 : gnc_numeric_equal (xaccSplitGetAmount (gain_split), negvalue))
844 : : {
845 : 18 : new_gain_split = FALSE;
846 : : }
847 : : else
848 : : {
849 : 0 : new_gain_split = TRUE;
850 : 0 : xaccTransBeginEdit (trans);
851 : :
852 : : /* Make sure the existing gains trans has the correct currency,
853 : : * just in case someone screwed with it! */
854 : 0 : if (FALSE == gnc_commodity_equiv(currency, trans->common_currency))
855 : : {
856 : 0 : PWARN ("Resetting the transaction currency!");
857 : 0 : xaccTransSetCurrency (trans, currency);
858 : : }
859 : : }
860 : : }
861 : :
862 : 24 : if (new_gain_split)
863 : : {
864 : : /* Common to both */
865 : 6 : time64 time = xaccTransRetDatePosted (split->parent);
866 : 6 : xaccTransSetDatePostedSecs (trans, time);
867 : 6 : xaccTransSetDateEnteredSecs (trans, gnc_time (nullptr));
868 : :
869 : 6 : xaccSplitSetAmount (lot_split, zero);
870 : 6 : xaccSplitSetValue (lot_split, value);
871 : :
872 : 6 : xaccSplitSetAmount (gain_split, negvalue);
873 : 6 : xaccSplitSetValue (gain_split, negvalue);
874 : :
875 : : /* Some short-cuts to help avoid the above property lookup. */
876 : 6 : split->gains = GAINS_STATUS_CLEAN;
877 : 6 : split->gains_split = lot_split;
878 : 6 : lot_split->gains = GAINS_STATUS_GAINS;
879 : 6 : lot_split->gains_split = split;
880 : 6 : gain_split->gains = GAINS_STATUS_GAINS;
881 : 6 : gain_split->gains_split = split;
882 : :
883 : : /* Do this last since it may generate an event that will call us
884 : : recursively. */
885 : 6 : gnc_lot_add_split (lot, lot_split);
886 : :
887 : 6 : xaccTransCommitEdit (trans);
888 : : }
889 : : }
890 : 30 : LEAVE ("(lot=%s)", gnc_lot_get_title(lot));
891 : : }
892 : :
893 : : /* ============================================================== */
894 : :
895 : : gnc_numeric
896 : 0 : xaccLotFreeSplitCapGain(Split *split, GNCLot *lot)
897 : : {
898 : 0 : g_return_val_if_fail (split && lot && !split->lot, gnc_numeric_zero());
899 : :
900 : 0 : ENTER ("(split=%p lot=%s)", split, gnc_lot_get_title(lot));
901 : :
902 : 0 : auto pcy = gnc_account_get_policy (gnc_lot_get_account (lot));
903 : 0 : auto currency = split->parent->common_currency;
904 : :
905 : : /* Not possible to have gains if the transaction currency and
906 : : * account commodity are identical. */
907 : 0 : if (gnc_commodity_equal (currency,
908 : 0 : xaccAccountGetCommodity(split->acc)))
909 : : {
910 : 0 : LEAVE ("Currency transfer, gains not possible, returning.");
911 : 0 : return gnc_numeric_zero();
912 : : }
913 : :
914 : 0 : auto *esplit = gnc_lot_get_earliest_split (lot);
915 : 0 : if (!esplit)
916 : : {
917 : 0 : LEAVE ("Lot is empty, returning.");
918 : 0 : return gnc_numeric_zero();
919 : : }
920 : :
921 : 0 : if (xaccTransGetDate (xaccSplitGetParent (esplit)) >= xaccTransGetDate (xaccSplitGetParent (split)))
922 : : {
923 : 0 : LEAVE ("Split is too early, returning.");
924 : 0 : return gnc_numeric_zero();
925 : : }
926 : :
927 : 0 : if (xaccSplitIsStockSplit (split))
928 : : {
929 : 0 : LEAVE ("Stock split split, returning.");
930 : 0 : return gnc_numeric_zero();
931 : : }
932 : :
933 : : /* If amount is zero, there's nothing to do! Amount-zero splits
934 : : * may exist if users attempted to manually record gains. */
935 : 0 : if (gnc_numeric_zero_p (split->amount)) return gnc_numeric_zero();
936 : :
937 : : /* If we got to here, then the gains really do need to be recomputed.
938 : : * So start working things. */
939 : :
940 : : /* Get the amount and value in this lot at the time of this transaction. */
941 : : gnc_numeric lot_amount, lot_value;
942 : 0 : gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
943 : :
944 : : gnc_numeric opening_amount, opening_value;
945 : : gnc_commodity *opening_currency;
946 : 0 : pcy->PolicyGetLotOpening (pcy, lot, &opening_amount, &opening_value,
947 : : &opening_currency);
948 : :
949 : : /* Check to make sure the lot-opening currency and this split
950 : : * use the same currency */
951 : 0 : if (FALSE == gnc_commodity_equiv (currency, opening_currency))
952 : : {
953 : : /* OK, the purchase and the sale were made in different currencies.
954 : : * I don't know how to compute cap gains for that. This is not
955 : : * an error. Just punt, silently.
956 : : */
957 : 0 : LEAVE ("Can't compute gains, mismatched commodities!");
958 : 0 : return gnc_numeric_zero();
959 : : }
960 : :
961 : : /* Opening amount should be larger (or equal) to current split,
962 : : * and it should be of the opposite sign.
963 : : * XXX This should really be a part of a scrub routine that
964 : : * cleans up the lot, before we get at it!
965 : : */
966 : 0 : if (0 > gnc_numeric_compare (gnc_numeric_abs(lot_amount),
967 : : gnc_numeric_abs(xaccSplitGetAdjustedAmount (split))))
968 : : {
969 : 0 : for (auto *n = gnc_lot_get_split_list(lot); n; n = n->next)
970 : : {
971 : 0 : auto *s = GNC_SPLIT(n->data);
972 : 0 : PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
973 : : }
974 : 0 : return gnc_numeric_zero();
975 : : }
976 : 0 : if ( (gnc_numeric_negative_p(lot_amount) ||
977 : 0 : gnc_numeric_positive_p(xaccSplitGetAdjustedAmount (split))) &&
978 : 0 : (gnc_numeric_positive_p(lot_amount) ||
979 : 0 : gnc_numeric_negative_p(xaccSplitGetAdjustedAmount (split))))
980 : : {
981 : 0 : for (auto *n = gnc_lot_get_split_list(lot); n; n = n->next)
982 : : {
983 : 0 : auto *s = GNC_SPLIT(n->data);
984 : 0 : PINFO ("split adj amt=%s", gnc_num_dbg_to_string(xaccSplitGetAdjustedAmount (s)));
985 : : }
986 : 0 : return gnc_numeric_zero();
987 : : }
988 : :
989 : : /* The cap gains is the difference between the basis prior to the
990 : : * current split, and the current split, pro-rated for an equal
991 : : * amount of shares.
992 : : * i.e. purchase_price = lot_value / lot_amount
993 : : * cost_basis = purchase_price * current_split_amount
994 : : * cap_gain = current_split_value - cost_basis
995 : : */
996 : : /* Fraction of the lot that this split represents: */
997 : 0 : auto frac = gnc_numeric_div (xaccSplitGetAdjustedAmount (split), lot_amount,
998 : : GNC_DENOM_AUTO,
999 : : GNC_HOW_DENOM_REDUCE);
1000 : : /* Basis for this split: */
1001 : 0 : auto value = gnc_numeric_mul (frac, lot_value,
1002 : : gnc_numeric_denom(opening_value),
1003 : : GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP);
1004 : : /* Capital gain for this split: */
1005 : 0 : value = gnc_numeric_sub (value, split->value,
1006 : : GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
1007 : :
1008 : 0 : PINFO ("Open amt=%s val=%s; split adj amt=%s val=%s; gains=%s\n",
1009 : : gnc_num_dbg_to_string (lot_amount),
1010 : : gnc_num_dbg_to_string (lot_value),
1011 : : gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
1012 : : gnc_num_dbg_to_string (split->value),
1013 : : gnc_num_dbg_to_string (value));
1014 : :
1015 : 0 : if (gnc_numeric_check (value))
1016 : : {
1017 : 0 : PERR ("Numeric overflow during gains calculation\n"
1018 : : "Acct=%s Txn=%s\n"
1019 : : "\tOpen amt=%s val=%s\n\tsplit amt=%s val=%s\n\tgains=%s\n",
1020 : : xaccAccountGetName(split->acc),
1021 : : xaccTransGetDescription(split->parent),
1022 : : gnc_num_dbg_to_string (lot_amount),
1023 : : gnc_num_dbg_to_string (lot_value),
1024 : : gnc_num_dbg_to_string (xaccSplitGetAdjustedAmount (split)),
1025 : : gnc_num_dbg_to_string (split->value),
1026 : : gnc_num_dbg_to_string (value));
1027 : 0 : return gnc_numeric_zero();
1028 : : }
1029 : :
1030 : 0 : LEAVE ("(lot=%s)", gnc_lot_get_title(lot));
1031 : :
1032 : 0 : return value;
1033 : : }
1034 : :
1035 : : /* ============================================================== */
1036 : :
1037 : : gnc_numeric
1038 : 1 : xaccSplitGetCapGains(Split * split)
1039 : : {
1040 : 1 : if (!split) return gnc_numeric_zero();
1041 : 1 : ENTER("(split=%p)", split);
1042 : :
1043 : 1 : if (GAINS_STATUS_UNKNOWN == split->gains)
1044 : 1 : xaccSplitDetermineGainStatus(split);
1045 : 1 : if ((split->gains & GAINS_STATUS_A_VDIRTY) ||
1046 : 0 : (split->gains_split &&
1047 : 0 : (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
1048 : : {
1049 : 1 : xaccSplitComputeCapGains (split, nullptr);
1050 : : }
1051 : :
1052 : : /* If this is the source split, get the gains from the one
1053 : : * that records the gains. If this already is the gains split,
1054 : : * its a no-op. */
1055 : 1 : if (!(GAINS_STATUS_GAINS & split->gains))
1056 : : {
1057 : : /* split = xaccSplitGetCapGainsSplit (split); */
1058 : 1 : split = split->gains_split;
1059 : : }
1060 : :
1061 : 1 : LEAVE("(split=%p)", split);
1062 : 1 : if (!split) return gnc_numeric_zero();
1063 : :
1064 : 0 : return split->value;
1065 : : }
1066 : :
1067 : : /* ============================================================== */
1068 : :
1069 : : void
1070 : 54 : xaccLotComputeCapGains (GNCLot *lot, Account *gain_acc)
1071 : : {
1072 : : SplitList *node;
1073 : : GNCPolicy *pcy;
1074 : 54 : gboolean is_dirty = FALSE;
1075 : :
1076 : : /* Note: if the value of the 'opening' split(s) has changed,
1077 : : * then the cap gains are changed. To capture this, we need
1078 : : * to mark all splits dirty if the opening splits are dirty. */
1079 : :
1080 : 54 : ENTER("(lot=%p)", lot);
1081 : 54 : pcy = gnc_account_get_policy(gnc_lot_get_account(lot));
1082 : 117 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
1083 : : {
1084 : 63 : Split *s = GNC_SPLIT(node->data);
1085 : 63 : if (pcy->PolicyIsOpeningSplit(pcy, lot, s))
1086 : : {
1087 : 54 : if (GAINS_STATUS_UNKNOWN == s->gains)
1088 : 54 : xaccSplitDetermineGainStatus(s);
1089 : 54 : if (s->gains & GAINS_STATUS_VDIRTY)
1090 : : {
1091 : 54 : is_dirty = TRUE;
1092 : 54 : s->gains &= ~GAINS_STATUS_VDIRTY;
1093 : : }
1094 : : }
1095 : : }
1096 : :
1097 : 54 : if (is_dirty)
1098 : : {
1099 : 117 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
1100 : : {
1101 : 63 : Split *s = GNC_SPLIT(node->data);
1102 : 63 : s->gains |= GAINS_STATUS_VDIRTY;
1103 : : }
1104 : : }
1105 : :
1106 : 123 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
1107 : : {
1108 : 69 : Split *s = GNC_SPLIT(node->data);
1109 : 69 : xaccSplitComputeCapGains (s, gain_acc);
1110 : : }
1111 : 54 : LEAVE("(lot=%p)", lot);
1112 : 54 : }
1113 : :
1114 : : /* =========================== END OF FILE ======================= */
|