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 86 : 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 726 : find_split_by_type (const gchar *type, const Split *split)
491 : {
492 726 : GncGUID *type_guid = nullptr;
493 :
494 726 : if (!split) return nullptr;
495 :
496 726 : qof_instance_get (QOF_INSTANCE (split), type, &type_guid, nullptr);
497 726 : if (!type_guid) return nullptr;
498 :
499 : /* Both splits will be in the same collection, so search there. */
500 21 : auto *guid_split = (Split *) qof_collection_lookup_entity (qof_instance_get_collection (split), type_guid);
501 21 : PINFO ("split=%p has %s=%p", split, type, guid_split);
502 21 : guid_free (type_guid);
503 21 : return guid_split;
504 : }
505 :
506 :
507 : Split *
508 307 : xaccSplitGetCapGainsSplit (const Split *split)
509 : {
510 307 : return find_split_by_type ("gains-split", split);
511 : }
512 :
513 : Split *
514 419 : xaccSplitGetGainsSourceSplit (const Split *split)
515 : {
516 419 : return find_split_by_type ("gains-source", split);
517 : }
518 :
519 : /* ============================================================== */
520 :
521 : void
522 140 : xaccSplitComputeCapGains(Split *split, Account *gain_acc)
523 : {
524 : SplitList *node;
525 : GNCLot *lot;
526 : GNCPolicy *pcy;
527 140 : gnc_commodity *currency = nullptr;
528 140 : 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 249 : if (!split) return;
536 140 : lot = split->lot;
537 140 : if (!lot) return;
538 139 : pcy = gnc_account_get_policy(gnc_lot_get_account(lot));
539 139 : currency = split->parent->common_currency;
540 :
541 139 : 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 139 : xaccSplitDetermineGainStatus(split);
546 :
547 : /* Not possible to have gains if the transaction currency and
548 : * account commodity are identical. */
549 139 : if (gnc_commodity_equal (currency,
550 139 : xaccAccountGetCommodity(split->acc)))
551 : {
552 0 : LEAVE ("Currency transfer, gains not possible, returning.");
553 0 : return;
554 : }
555 :
556 139 : 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 31 : if (xaccSplitIsStockSplit (split))
581 : {
582 0 : LEAVE ("Stock split split, returning.");
583 0 : return;
584 : }
585 :
586 31 : if (GAINS_STATUS_GAINS & split->gains)
587 : {
588 : Split *s;
589 13 : 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 13 : 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 13 : 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 13 : 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 31 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
618 : {
619 31 : Split *s = GNC_SPLIT(node->data);
620 31 : if (pcy->PolicyIsOpeningSplit(pcy, lot, s))
621 : {
622 31 : if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus (s);
623 31 : if (s->gains & GAINS_STATUS_VDIRTY)
624 : {
625 : /* Force a recompute to occur */
626 31 : split->gains |= GAINS_STATUS_VDIRTY;
627 31 : 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 31 : 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 31 : 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 31 : gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
652 :
653 31 : 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 31 : 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 31 : 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 31 : if ( (gnc_numeric_negative_p(lot_amount) ||
691 45 : 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 31 : frac = gnc_numeric_div (xaccSplitGetAdjustedAmount (split), lot_amount,
719 : GNC_DENOM_AUTO,
720 : GNC_HOW_DENOM_REDUCE);
721 : /* Basis for this split: */
722 31 : 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 31 : value = gnc_numeric_sub (value, split->value,
727 : GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
728 31 : 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 31 : 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 31 : if (FALSE == gnc_numeric_zero_p (value))
756 : {
757 : Transaction *trans;
758 : Split *lot_split, *gain_split;
759 : gboolean new_gain_split;
760 25 : 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 25 : lot_split = split->gains_split;
768 :
769 25 : 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 19 : trans = lot_split->parent;
825 19 : 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 19 : 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 57 : else if (split->gains_split == lot_split &&
838 19 : lot_split->gains_split == split &&
839 19 : gain_split->gains_split == split &&
840 18 : gnc_numeric_equal (xaccSplitGetValue (lot_split), value) &&
841 18 : gnc_numeric_zero_p (xaccSplitGetAmount (lot_split)) &&
842 56 : 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 1 : new_gain_split = TRUE;
850 1 : xaccTransBeginEdit (trans);
851 :
852 : /* Make sure the existing gains trans has the correct currency,
853 : * just in case someone screwed with it! */
854 1 : 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 25 : if (new_gain_split)
863 : {
864 : /* Common to both */
865 7 : time64 time = xaccTransRetDatePosted (split->parent);
866 7 : xaccTransSetDatePostedSecs (trans, time);
867 7 : xaccTransSetDateEnteredSecs (trans, gnc_time (nullptr));
868 :
869 7 : xaccSplitSetAmount (lot_split, zero);
870 7 : xaccSplitSetValue (lot_split, value);
871 :
872 7 : xaccSplitSetAmount (gain_split, negvalue);
873 7 : xaccSplitSetValue (gain_split, negvalue);
874 :
875 : /* Some short-cuts to help avoid the above property lookup. */
876 7 : split->gains = GAINS_STATUS_CLEAN;
877 7 : split->gains_split = lot_split;
878 7 : lot_split->gains = GAINS_STATUS_GAINS;
879 7 : lot_split->gains_split = split;
880 7 : gain_split->gains = GAINS_STATUS_GAINS;
881 7 : gain_split->gains_split = split;
882 :
883 : /* Do this last since it may generate an event that will call us
884 : recursively. */
885 7 : gnc_lot_add_split (lot, lot_split);
886 :
887 7 : xaccTransCommitEdit (trans);
888 : }
889 : }
890 31 : 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 ======================= */
|