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 : 64 : xaccAccountHasTrades (const Account *acc)
81 : : {
82 : : gnc_commodity *acc_comm;
83 : :
84 : 64 : if (!acc) return FALSE;
85 : :
86 : 64 : if (xaccAccountIsPriced (acc))
87 : 28 : return TRUE;
88 : :
89 : 36 : acc_comm = xaccAccountGetCommodity(acc);
90 : :
91 : 38 : for (auto s : xaccAccountGetSplits (acc))
92 : : {
93 : 35 : Transaction *t = s->parent;
94 : 35 : if (s->gains == GAINS_STATUS_GAINS) continue;
95 : 35 : 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 : 9 : earliest_pred (time64 earl, time64 tran)
114 : : {
115 : 9 : 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 : 755 : finder_helper (GNCLot *lot, gpointer user_data)
126 : : {
127 : 755 : 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 : 755 : time64 posted = 0;
133 : :
134 : 755 : if (gnc_lot_is_closed (lot)) return nullptr;
135 : :
136 : 709 : s = gnc_lot_get_earliest_split (lot);
137 : 709 : 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 : 709 : if (0 == (els->numeric_pred) (s->amount)) return nullptr;
144 : 367 : bal = gnc_lot_get_balance (lot);
145 : 367 : opening_is_positive = gnc_numeric_positive_p (s->amount);
146 : 367 : bal_is_positive = gnc_numeric_positive_p (bal);
147 : 367 : if (opening_is_positive != bal_is_positive) return nullptr;
148 : :
149 : 367 : trans = s->parent;
150 : 734 : if (els->currency &&
151 : 367 : (FALSE == gnc_commodity_equiv (els->currency,
152 : 367 : trans->common_currency)))
153 : : {
154 : 358 : return nullptr;
155 : : }
156 : :
157 : 9 : posted = trans->date_posted;
158 : 9 : if (els->date_pred (els->time, posted))
159 : : {
160 : 9 : els->time = trans->date_posted;
161 : 9 : els->lot = lot;
162 : : }
163 : :
164 : 9 : return nullptr;
165 : : }
166 : :
167 : : static inline GNCLot *
168 : 60 : 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 : 60 : es.lot = nullptr;
176 : 60 : es.currency = currency;
177 : 60 : es.time = guess;
178 : 60 : es.date_pred = date_pred;
179 : :
180 : 60 : if (gnc_numeric_positive_p(sign)) es.numeric_pred = gnc_numeric_negative_p;
181 : 32 : else es.numeric_pred = gnc_numeric_positive_p;
182 : :
183 : 60 : xaccAccountForEachLot (acc, finder_helper, &es);
184 : 60 : return es.lot;
185 : : }
186 : :
187 : : GNCLot *
188 : 60 : xaccAccountFindEarliestOpenLot (Account *acc, gnc_numeric sign,
189 : : gnc_commodity *currency)
190 : : {
191 : : GNCLot *lot;
192 : 60 : ENTER (" sign=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, sign.num,
193 : : sign.denom);
194 : :
195 : 60 : lot = xaccAccountFindOpenLot (acc, sign, currency,
196 : : G_MAXINT64, earliest_pred);
197 : 60 : 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 : 60 : 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 : 60 : 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 : 60 : if (!lot) return split;
227 : 60 : if (!split) return nullptr;
228 : :
229 : : /* If this split already belongs to a lot, we are done. */
230 : 60 : 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 : 60 : 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 : 60 : baln = gnc_lot_get_balance (lot);
255 : 60 : 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 : 60 : if (gnc_numeric_zero_p (baln))
260 : : {
261 : 53 : acc = split->acc;
262 : 53 : xaccAccountBeginEdit (acc);
263 : 53 : gnc_lot_add_split (lot, split);
264 : 53 : 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 : 53 : xaccAccountCommitEdit (acc);
268 : 53 : 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 : 7 : baln_is_positive = gnc_numeric_positive_p (baln);
280 : 7 : amt_is_positive = gnc_numeric_positive_p (split->amount);
281 : 7 : if ((baln_is_positive && amt_is_positive) ||
282 : 3 : ((!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 : 7 : cmp = gnc_numeric_compare (gnc_numeric_abs(split->amount),
302 : : gnc_numeric_abs(baln));
303 : :
304 : 7 : 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 : 7 : if (0 >= cmp)
309 : : {
310 : 3 : acc = split->acc;
311 : 3 : xaccAccountBeginEdit (acc);
312 : 3 : gnc_lot_add_split (lot, split);
313 : 3 : PINFO ("simple added split to lot, new lot baln=%s",
314 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
315 : 3 : xaccAccountCommitEdit (acc);
316 : 3 : 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 : 4 : amt_tot = split->amount;
335 : 4 : amt_a = gnc_numeric_neg (baln);
336 : 4 : amt_b = gnc_numeric_sub_fixed (amt_tot, amt_a);
337 : 4 : g_return_val_if_fail(gnc_numeric_check(amt_b) == GNC_ERROR_OK, nullptr);
338 : :
339 : 4 : PINFO ("++++++++++++++ splitting split=%p into amt = %s + %s",
340 : : split,
341 : : gnc_num_dbg_to_string(amt_a),
342 : : gnc_num_dbg_to_string(amt_b) );
343 : :
344 : : /* Compute the value so that it holds in the same proportion:
345 : : * i.e. so that (amt_a / amt_tot) = (val_a / val_tot)
346 : : */
347 : 4 : val_tot = split->value;
348 : 4 : frac = gnc_numeric_div (amt_a, amt_tot,
349 : : GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
350 : 4 : val_a = gnc_numeric_mul (frac, val_tot,
351 : : gnc_numeric_denom(val_tot),
352 : : GNC_HOW_RND_ROUND_HALF_UP | GNC_HOW_DENOM_EXACT);
353 : :
354 : 4 : val_b = gnc_numeric_sub_fixed (val_tot, val_a);
355 : 4 : if (gnc_numeric_check(val_a))
356 : : {
357 : 0 : PERR("Numeric overflow\n"
358 : : "Acct=%s Txn=%s\n"
359 : : "\tval_tot=%s amt_a=%s amt_tot=%s\n",
360 : : xaccAccountGetName(acc),
361 : : xaccTransGetDescription(trans),
362 : : gnc_num_dbg_to_string(val_tot),
363 : : gnc_num_dbg_to_string(amt_a),
364 : : gnc_num_dbg_to_string(amt_tot));
365 : : }
366 : :
367 : 4 : if (gnc_numeric_zero_p(amt_a) || gnc_numeric_zero_p(amt_b))
368 : : {
369 : 0 : PERR ("Failed to split into two!");
370 : : }
371 : :
372 : 4 : PINFO ("split value is = %s = %s + %s",
373 : : gnc_num_dbg_to_string(val_tot),
374 : : gnc_num_dbg_to_string(val_a),
375 : : gnc_num_dbg_to_string(val_b) );
376 : :
377 : 4 : g_return_val_if_fail (!gnc_numeric_zero_p (amt_a), nullptr);
378 : 4 : g_return_val_if_fail (!gnc_numeric_check (val_a), nullptr);
379 : 4 : xaccSplitSetAmount (split, amt_a);
380 : 4 : xaccSplitSetValue (split, val_a);
381 : :
382 : : /* Adding this split will have the effect of closing this lot,
383 : : * because the new balance should be precisely zero. */
384 : 4 : gnc_lot_add_split (lot, split);
385 : :
386 : : /* Put the remainder of the balance into a new split,
387 : : * which is in other respects just a clone of this one. */
388 : 4 : new_split = xaccMallocSplit (qof_instance_get_book(acc));
389 : :
390 : : /* Copy most of the split attributes */
391 : 4 : xaccSplitSetMemo (new_split, xaccSplitGetMemo (split));
392 : : /* Set split-action with gnc_set_num_action which is the same as
393 : : * xaccSplitSetAction with these arguments; use gnc_get_num_action to get
394 : : * split-action which is the same as xaccSplitGetAction */
395 : 4 : gnc_set_num_action(nullptr, new_split, nullptr, gnc_get_num_action(nullptr, split));
396 : 4 : xaccSplitSetReconcile (new_split, xaccSplitGetReconcile (split));
397 : 4 : time = xaccSplitGetDateReconciled (split);
398 : 4 : xaccSplitSetDateReconciledSecs (new_split, time);
399 : :
400 : : /* Set the lot-split and peer_guid properties on the two
401 : : * splits to indicate that they're linked.
402 : : */
403 : 4 : xaccSplitAddPeerSplit(split, new_split, now);
404 : 4 : xaccSplitAddPeerSplit(new_split, split, now);
405 : 4 : xaccAccountInsertSplit (acc, new_split);
406 : 4 : xaccTransAppendSplit (trans, new_split);
407 : : /* Set the amount and value after the split is in the transaction
408 : : so it can find the correct denominator to use. Otherwise it
409 : : uses 100000 which may cause an overflow in some of the tests
410 : : in test-period */
411 : 4 : xaccSplitSetAmount (new_split, amt_b);
412 : 4 : xaccSplitSetValue (new_split, val_b);
413 : 4 : xaccTransCommitEdit (trans);
414 : 4 : xaccAccountCommitEdit (acc);
415 : 4 : return new_split;
416 : : }
417 : : }
418 : :
419 : : /* ============================================================== */
420 : :
421 : : /* Accounting-policy callback. Given an account and an amount,
422 : : * this routine should return a lot. By implementing this as
423 : : * a callback, we can 'easily' add other accounting policies.
424 : : */
425 : : gboolean
426 : 56 : xaccSplitAssign (Split *split)
427 : : {
428 : : Account *acc;
429 : 56 : gboolean splits_split_up = FALSE;
430 : : GNCLot *lot;
431 : : GNCPolicy *pcy;
432 : :
433 : 56 : if (!split) return FALSE;
434 : :
435 : : /* If this split already belongs to a lot or the account doesn't
436 : : * have lots, we are done.
437 : : */
438 : 56 : if (split->lot) return FALSE;
439 : 56 : g_return_val_if_fail (split->gains == GAINS_STATUS_UNKNOWN ||
440 : : (split->gains & GAINS_STATUS_GAINS) == FALSE, FALSE);
441 : 56 : acc = split->acc;
442 : 56 : if (!xaccAccountHasTrades (acc))
443 : 0 : return FALSE;
444 : 56 : if (gnc_numeric_zero_p (split->amount))
445 : 0 : return FALSE;
446 : :
447 : 56 : ENTER ("(split=%p)", split);
448 : :
449 : 56 : pcy = gnc_account_get_policy(acc);
450 : 56 : xaccAccountBeginEdit (acc);
451 : :
452 : : /* If we are here, this split does not belong to any lot.
453 : : * We ask the policy for a lot to assign it to. This
454 : : * block is written in the form of a while loop, since we
455 : : * may have to bust a split across several lots.
456 : : */
457 : 116 : while (split)
458 : : {
459 : 60 : PINFO ("have split %p amount=%s", split,
460 : : gnc_num_dbg_to_string (split->amount));
461 : 60 : split->gains |= GAINS_STATUS_VDIRTY;
462 : 60 : lot = pcy->PolicyGetLot (pcy, split);
463 : 60 : if (!lot)
464 : : {
465 : 53 : lot = gnc_lot_make_default (acc);
466 : 53 : PINFO ("start new lot (%s)", gnc_lot_get_title(lot));
467 : : }
468 : 60 : split = xaccSplitAssignToLot (split, lot);
469 : 60 : if (split) splits_split_up = TRUE;
470 : : }
471 : 56 : xaccAccountCommitEdit (acc);
472 : :
473 : 56 : LEAVE (" split_up=%d", splits_split_up);
474 : 56 : return splits_split_up;
475 : : }
476 : :
477 : : /* ============================================================== */
478 : :
479 : : Split *
480 : 290 : xaccSplitGetCapGainsSplit (const Split *split)
481 : : {
482 : : GncGUID *gains_guid;
483 : : Split *gains_split;
484 : :
485 : 290 : if (!split) return nullptr;
486 : :
487 : 290 : qof_instance_get (QOF_INSTANCE (split),
488 : : "gains-split", &gains_guid,
489 : : nullptr);
490 : 290 : if (!gains_guid) return nullptr;
491 : :
492 : : /* Both splits will be in the same collection, so search there. */
493 : 0 : gains_split = (Split*) qof_collection_lookup_entity (
494 : 0 : qof_instance_get_collection(split), gains_guid);
495 : 0 : PINFO ("split=%p has gains-split=%p", split, gains_split);
496 : 0 : guid_free (gains_guid);
497 : 0 : return gains_split;
498 : : }
499 : :
500 : : /* ============================================================== */
501 : :
502 : : Split *
503 : 96 : xaccSplitGetGainsSourceSplit (const Split *split)
504 : : {
505 : : GncGUID *source_guid;
506 : : Split *source_split;
507 : :
508 : 96 : if (!split) return nullptr;
509 : :
510 : 96 : qof_instance_get (QOF_INSTANCE (split),
511 : : "gains-source", &source_guid,
512 : : nullptr);
513 : 96 : if (!source_guid) return nullptr;
514 : :
515 : : /* Both splits will be in the same collection, so search there. */
516 : 54 : source_split = (Split*) qof_collection_lookup_entity(
517 : 18 : qof_instance_get_collection(split), source_guid);
518 : 18 : PINFO ("split=%p has source-split=%p", split, source_split);
519 : 18 : guid_free (source_guid);
520 : 18 : return source_split;
521 : : }
522 : :
523 : : /* ============================================================== */
524 : :
525 : : void
526 : 130 : xaccSplitComputeCapGains(Split *split, Account *gain_acc)
527 : : {
528 : : SplitList *node;
529 : : GNCLot *lot;
530 : : GNCPolicy *pcy;
531 : 130 : gnc_commodity *currency = nullptr;
532 : 130 : gnc_numeric zero = gnc_numeric_zero();
533 : : gnc_numeric value;
534 : : gnc_numeric frac;
535 : : gnc_numeric opening_amount, opening_value;
536 : : gnc_numeric lot_amount, lot_value;
537 : : gnc_commodity *opening_currency;
538 : :
539 : 234 : if (!split) return;
540 : 130 : lot = split->lot;
541 : 130 : if (!lot) return;
542 : 130 : pcy = gnc_account_get_policy(gnc_lot_get_account(lot));
543 : 130 : currency = split->parent->common_currency;
544 : :
545 : 130 : ENTER ("(split=%p gains=%p status=0x%x lot=%s)", split,
546 : : split->gains_split, split->gains, gnc_lot_get_title(lot));
547 : :
548 : : /* Make sure the status flags and pointers are initialized */
549 : 130 : xaccSplitDetermineGainStatus(split);
550 : :
551 : : /* Not possible to have gains if the transaction currency and
552 : : * account commodity are identical. */
553 : 130 : if (gnc_commodity_equal (currency,
554 : 130 : xaccAccountGetCommodity(split->acc)))
555 : : {
556 : 0 : LEAVE ("Currency transfer, gains not possible, returning.");
557 : 0 : return;
558 : : }
559 : :
560 : 130 : if (pcy->PolicyIsOpeningSplit (pcy, lot, split))
561 : : {
562 : : #if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
563 : : /* Check to make sure that this opening split doesn't
564 : : * have a cap-gain transaction associated with it.
565 : : * If it does, that's wrong, and we ruthlessly destroy it.
566 : : * XXX Don't do this, it leads to infinite loops.
567 : : * We need to scrub out errors like this elsewhere!
568 : : */
569 : : if (xaccSplitGetCapGainsSplit (split))
570 : : {
571 : : Split *gains_split = xaccSplitGetCapGainsSplit(split);
572 : : Transaction *trans = gains_split->parent;
573 : : PERR ("Opening Split must not have cap gains!!\n");
574 : :
575 : : xaccTransBeginEdit (trans);
576 : : xaccTransDestroy (trans);
577 : : xaccTransCommitEdit (trans);
578 : : }
579 : : #endif
580 : 104 : LEAVE ("Lot opening split, returning.");
581 : 104 : return;
582 : : }
583 : :
584 : 26 : if (g_strcmp0 ("stock-split", xaccSplitGetType (split)) == 0)
585 : : {
586 : 0 : LEAVE ("Stock split split, returning.");
587 : 0 : return;
588 : : }
589 : :
590 : 26 : if (GAINS_STATUS_GAINS & split->gains)
591 : : {
592 : : Split *s;
593 : 12 : PINFO ("split is a gains recording split, switch over");
594 : : /* If this is the split that records the gains, then work with
595 : : * the split that generates the gains.
596 : : */
597 : : /* split = xaccSplitGetCapGainsSplit (split); */
598 : 12 : s = split->gains_split;
599 : :
600 : : /* This should never be nullptr, and if it is, and its matching
601 : : * parent can't be found, then its a bug, and we should be
602 : : * discarding this split. But ... for now .. return.
603 : : * XXX move appropriate actions to a 'scrub' routine'
604 : : */
605 : 12 : if (!s)
606 : : {
607 : 0 : PERR ("Bad gains-split pointer! .. trying to recover.");
608 : 0 : split->gains_split = xaccSplitGetCapGainsSplit (split);
609 : 0 : s = split->gains_split;
610 : 0 : if (!s) return;
611 : : #if MOVE_THIS_TO_A_DATA_INTEGRITY_SCRUBBER
612 : : xaccTransDestroy (trans);
613 : : #endif
614 : : }
615 : 12 : split = s;
616 : : }
617 : :
618 : : /* Note: if the value of the 'opening' split(s) has changed,
619 : : * then the cap gains are changed. So we need to check not
620 : : * only if this split is dirty, but also the lot-opening splits. */
621 : 26 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
622 : : {
623 : 26 : Split *s = GNC_SPLIT(node->data);
624 : 26 : if (pcy->PolicyIsOpeningSplit(pcy, lot, s))
625 : : {
626 : 26 : if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus (s);
627 : 26 : if (s->gains & GAINS_STATUS_VDIRTY)
628 : : {
629 : : /* Force a recompute to occur */
630 : 26 : split->gains |= GAINS_STATUS_VDIRTY;
631 : 26 : break;
632 : : }
633 : : }
634 : : }
635 : :
636 : : /* If it doesn't look like this split is 'dirty', then there's
637 : : * nothing to do. Just return. */
638 : 26 : if ((FALSE == (split->gains & GAINS_STATUS_A_VDIRTY)) &&
639 : 0 : (split->gains_split) &&
640 : 0 : (FALSE == (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
641 : : {
642 : 0 : LEAVE ("split not dirty, returning");
643 : 0 : return;
644 : : }
645 : :
646 : : /* Yow! If amount is zero, there's nothing to do! Amount-zero splits
647 : : * may exist if users attempted to manually record gains. */
648 : 26 : if (gnc_numeric_zero_p (split->amount)) return;
649 : :
650 : : /* If we got to here, then the split or something related is
651 : : * 'dirty' and the gains really do need to be recomputed.
652 : : * So start working things. */
653 : :
654 : : /* Get the amount and value in this lot at the time of this transaction. */
655 : 26 : gnc_lot_get_balance_before (lot, split, &lot_amount, &lot_value);
656 : :
657 : 26 : pcy->PolicyGetLotOpening (pcy, lot, &opening_amount, &opening_value,
658 : : &opening_currency);
659 : :
660 : : /* Check to make sure the lot-opening currency and this split
661 : : * use the same currency */
662 : 26 : if (FALSE == gnc_commodity_equiv (currency, opening_currency))
663 : : {
664 : : /* OK, the purchase and the sale were made in different currencies.
665 : : * I don't know how to compute cap gains for that. This is not
666 : : * an error. Just punt, silently.
667 : : */
668 : 0 : LEAVE ("Can't compute gains, mismatched commodities!");
669 : 0 : return;
670 : : }
671 : :
672 : : /* Opening amount should be larger (or equal) to current split,
673 : : * and it should be of the opposite sign.
674 : : * XXX This should really be a part of a scrub routine that
675 : : * cleans up the lot, before we get at it!
676 : : */
677 : 26 : if (0 > gnc_numeric_compare (gnc_numeric_abs(lot_amount),
678 : : gnc_numeric_abs(split->amount)))
679 : : {
680 : : GList *n;
681 : 0 : for (n = gnc_lot_get_split_list(lot); n; n = n->next)
682 : : {
683 : 0 : Split *s = GNC_SPLIT(n->data);
684 : 0 : PINFO ("split amt=%s", gnc_num_dbg_to_string(s->amount));
685 : : }
686 : 0 : PERR ("Malformed Lot \"%s\"! (too thin!) "
687 : : "opening amt=%s split amt=%s baln=%s",
688 : : gnc_lot_get_title (lot),
689 : : gnc_num_dbg_to_string (lot_amount),
690 : : gnc_num_dbg_to_string (split->amount),
691 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
692 : 0 : return;
693 : : }
694 : 26 : if ( (gnc_numeric_negative_p(lot_amount) ||
695 : 36 : gnc_numeric_positive_p(split->amount)) &&
696 : 10 : (gnc_numeric_positive_p(lot_amount) ||
697 : 10 : gnc_numeric_negative_p(split->amount)))
698 : : {
699 : : GList *n;
700 : 0 : for (n = gnc_lot_get_split_list(lot); n; n = n->next)
701 : : {
702 : 0 : Split *s = GNC_SPLIT(n->data);
703 : 0 : PINFO ("split amt=%s", gnc_num_dbg_to_string(s->amount));
704 : : }
705 : 0 : PERR ("Malformed Lot \"%s\"! (too fat!) "
706 : : "opening amt=%s split amt=%s baln=%s",
707 : : gnc_lot_get_title (lot),
708 : : gnc_num_dbg_to_string (lot_amount),
709 : : gnc_num_dbg_to_string (split->amount),
710 : : gnc_num_dbg_to_string (gnc_lot_get_balance(lot)));
711 : 0 : return;
712 : : }
713 : :
714 : : /* The cap gains is the difference between the basis prior to the
715 : : * current split, and the current split, pro-rated for an equal
716 : : * amount of shares.
717 : : * i.e. purchase_price = lot_value / lot_amount
718 : : * cost_basis = purchase_price * current_split_amount
719 : : * cap_gain = current_split_value - cost_basis
720 : : */
721 : : /* Fraction of the lot that this split represents: */
722 : 26 : frac = gnc_numeric_div (split->amount, lot_amount,
723 : : GNC_DENOM_AUTO,
724 : : GNC_HOW_DENOM_REDUCE);
725 : : /* Basis for this split: */
726 : 26 : value = gnc_numeric_mul (frac, lot_value,
727 : : gnc_numeric_denom(opening_value),
728 : : GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP);
729 : : /* Capital gain for this split: */
730 : 26 : value = gnc_numeric_sub (value, split->value,
731 : : GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
732 : 26 : PINFO ("Open amt=%s val=%s; split amt=%s val=%s; gains=%s\n",
733 : : gnc_num_dbg_to_string (lot_amount),
734 : : gnc_num_dbg_to_string (lot_value),
735 : : gnc_num_dbg_to_string (split->amount),
736 : : gnc_num_dbg_to_string (split->value),
737 : : gnc_num_dbg_to_string (value));
738 : 26 : if (gnc_numeric_check (value))
739 : : {
740 : 0 : PERR ("Numeric overflow during gains calculation\n"
741 : : "Acct=%s Txn=%s\n"
742 : : "\tOpen amt=%s val=%s\n\tsplit amt=%s val=%s\n\tgains=%s\n",
743 : : xaccAccountGetName(split->acc),
744 : : xaccTransGetDescription(split->parent),
745 : : gnc_num_dbg_to_string (lot_amount),
746 : : gnc_num_dbg_to_string (lot_value),
747 : : gnc_num_dbg_to_string (split->amount),
748 : : gnc_num_dbg_to_string (split->value),
749 : : gnc_num_dbg_to_string (value));
750 : 0 : return;
751 : : }
752 : :
753 : : /* Are the cap gains zero? If not, add a balancing transaction.
754 : : * As per design doc lots.txt: the transaction has two splits,
755 : : * with equal & opposite values. The amt of one iz zero (so as
756 : : * not to upset the lot balance), the amt of the other is the same
757 : : * as its value (its the realized gain/loss).
758 : : */
759 : 26 : if (FALSE == gnc_numeric_zero_p (value))
760 : : {
761 : : Transaction *trans;
762 : : Split *lot_split, *gain_split;
763 : : gboolean new_gain_split;
764 : 24 : gnc_numeric negvalue = gnc_numeric_neg (value);
765 : :
766 : : /* See if there already is an associated gains transaction.
767 : : * If there is, adjust its value as appropriate. Else, create
768 : : * a new gains transaction.
769 : : */
770 : : /* lot_split = xaccSplitGetCapGainsSplit (split); */
771 : 24 : lot_split = split->gains_split;
772 : :
773 : 24 : if (nullptr == lot_split)
774 : : {
775 : 6 : Account *lot_acc = gnc_lot_get_account(lot);
776 : 6 : QofBook *book = qof_instance_get_book(lot_acc);
777 : 6 : Transaction *base_txn = xaccSplitGetParent (split);
778 : :
779 : 6 : new_gain_split = TRUE;
780 : :
781 : 6 : lot_split = xaccMallocSplit (book);
782 : 6 : gain_split = xaccMallocSplit (book);
783 : :
784 : : /* Check to make sure the gains account currency matches. */
785 : 6 : if ((nullptr == gain_acc) ||
786 : 0 : (FALSE == gnc_commodity_equiv (currency,
787 : 0 : xaccAccountGetCommodity(gain_acc))))
788 : : {
789 : 6 : gain_acc = xaccAccountGainsAccount (lot_acc, currency);
790 : : }
791 : :
792 : 6 : xaccAccountBeginEdit (gain_acc);
793 : 6 : xaccAccountInsertSplit (gain_acc, gain_split);
794 : 6 : xaccAccountCommitEdit (gain_acc);
795 : :
796 : 6 : xaccAccountBeginEdit (lot_acc);
797 : 6 : xaccAccountInsertSplit (lot_acc, lot_split);
798 : 6 : xaccAccountCommitEdit (lot_acc);
799 : :
800 : 6 : trans = xaccMallocTransaction (book);
801 : :
802 : 6 : xaccTransBeginEdit (trans);
803 : 6 : xaccTransSetCurrency (trans, currency);
804 : 6 : xaccTransSetDescription (trans, _("Realized Gain/Loss"));
805 : :
806 : 6 : xaccTransAppendSplit (trans, lot_split);
807 : 6 : xaccTransAppendSplit (trans, gain_split);
808 : :
809 : 6 : xaccSplitSetMemo (lot_split, _("Realized Gain/Loss"));
810 : 6 : xaccSplitSetMemo (gain_split, _("Realized Gain/Loss"));
811 : :
812 : : /* For the new transaction, set the split properties indicating
813 : : * that this is the gains transaction that corresponds
814 : : * to the gains source.
815 : : */
816 : 6 : xaccTransBeginEdit (base_txn);
817 : 6 : qof_instance_set (QOF_INSTANCE (split),
818 : 6 : "gains-split", xaccSplitGetGUID (lot_split),
819 : : nullptr);
820 : 6 : xaccTransCommitEdit (base_txn);
821 : 6 : qof_instance_set (QOF_INSTANCE (lot_split),
822 : 6 : "gains-source", xaccSplitGetGUID (split),
823 : : nullptr);
824 : :
825 : : }
826 : : else
827 : : {
828 : 18 : trans = lot_split->parent;
829 : 18 : gain_split = xaccSplitGetOtherSplit (lot_split);
830 : :
831 : : /* If the gains transaction has been edited so that it no longer has
832 : : just two splits, ignore it and assume it's still correct. */
833 : 18 : if (!gain_split)
834 : : {
835 : 0 : new_gain_split = FALSE;
836 : : }
837 : : /* If the gain is already recorded correctly do nothing. This is
838 : : * more than just an optimization since this may be called during
839 : : * gnc_book_partition_txn and depending on the order in which things
840 : : * happen some splits may be in the wrong book at that time. */
841 : 54 : else if (split->gains_split == lot_split &&
842 : 18 : lot_split->gains_split == split &&
843 : 18 : gain_split->gains_split == split &&
844 : 18 : gnc_numeric_equal (xaccSplitGetValue (lot_split), value) &&
845 : 18 : gnc_numeric_zero_p (xaccSplitGetAmount (lot_split)) &&
846 : 54 : gnc_numeric_equal (xaccSplitGetValue (gain_split), negvalue) &&
847 : 18 : gnc_numeric_equal (xaccSplitGetAmount (gain_split), negvalue))
848 : : {
849 : 18 : new_gain_split = FALSE;
850 : : }
851 : : else
852 : : {
853 : 0 : new_gain_split = TRUE;
854 : 0 : xaccTransBeginEdit (trans);
855 : :
856 : : /* Make sure the existing gains trans has the correct currency,
857 : : * just in case someone screwed with it! */
858 : 0 : if (FALSE == gnc_commodity_equiv(currency, trans->common_currency))
859 : : {
860 : 0 : PWARN ("Resetting the transaction currency!");
861 : 0 : xaccTransSetCurrency (trans, currency);
862 : : }
863 : : }
864 : : }
865 : :
866 : 24 : if (new_gain_split)
867 : : {
868 : : /* Common to both */
869 : 6 : time64 time = xaccTransRetDatePosted (split->parent);
870 : 6 : xaccTransSetDatePostedSecs (trans, time);
871 : 6 : xaccTransSetDateEnteredSecs (trans, gnc_time (nullptr));
872 : :
873 : 6 : xaccSplitSetAmount (lot_split, zero);
874 : 6 : xaccSplitSetValue (lot_split, value);
875 : :
876 : 6 : xaccSplitSetAmount (gain_split, negvalue);
877 : 6 : xaccSplitSetValue (gain_split, negvalue);
878 : :
879 : : /* Some short-cuts to help avoid the above property lookup. */
880 : 6 : split->gains = GAINS_STATUS_CLEAN;
881 : 6 : split->gains_split = lot_split;
882 : 6 : lot_split->gains = GAINS_STATUS_GAINS;
883 : 6 : lot_split->gains_split = split;
884 : 6 : gain_split->gains = GAINS_STATUS_GAINS;
885 : 6 : gain_split->gains_split = split;
886 : :
887 : : /* Do this last since it may generate an event that will call us
888 : : recursively. */
889 : 6 : gnc_lot_add_split (lot, lot_split);
890 : :
891 : 6 : xaccTransCommitEdit (trans);
892 : : }
893 : : }
894 : 26 : LEAVE ("(lot=%s)", gnc_lot_get_title(lot));
895 : : }
896 : :
897 : : /* ============================================================== */
898 : :
899 : : gnc_numeric
900 : 0 : xaccSplitGetCapGains(Split * split)
901 : : {
902 : 0 : if (!split) return gnc_numeric_zero();
903 : 0 : ENTER("(split=%p)", split);
904 : :
905 : 0 : if (GAINS_STATUS_UNKNOWN == split->gains)
906 : 0 : xaccSplitDetermineGainStatus(split);
907 : 0 : if ((split->gains & GAINS_STATUS_A_VDIRTY) ||
908 : 0 : (split->gains_split &&
909 : 0 : (split->gains_split->gains & GAINS_STATUS_A_VDIRTY)))
910 : : {
911 : 0 : xaccSplitComputeCapGains (split, nullptr);
912 : : }
913 : :
914 : : /* If this is the source split, get the gains from the one
915 : : * that records the gains. If this already is the gains split,
916 : : * its a no-op. */
917 : 0 : if (!(GAINS_STATUS_GAINS & split->gains))
918 : : {
919 : : /* split = xaccSplitGetCapGainsSplit (split); */
920 : 0 : split = split->gains_split;
921 : : }
922 : :
923 : 0 : LEAVE("(split=%p)", split);
924 : 0 : if (!split) return gnc_numeric_zero();
925 : :
926 : 0 : return split->value;
927 : : }
928 : :
929 : : /* ============================================================== */
930 : :
931 : : void
932 : 52 : xaccLotComputeCapGains (GNCLot *lot, Account *gain_acc)
933 : : {
934 : : SplitList *node;
935 : : GNCPolicy *pcy;
936 : 52 : gboolean is_dirty = FALSE;
937 : :
938 : : /* Note: if the value of the 'opening' split(s) has changed,
939 : : * then the cap gains are changed. To capture this, we need
940 : : * to mark all splits dirty if the opening splits are dirty. */
941 : :
942 : 52 : ENTER("(lot=%p)", lot);
943 : 52 : pcy = gnc_account_get_policy(gnc_lot_get_account(lot));
944 : 111 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
945 : : {
946 : 59 : Split *s = GNC_SPLIT(node->data);
947 : 59 : if (pcy->PolicyIsOpeningSplit(pcy, lot, s))
948 : : {
949 : 52 : if (GAINS_STATUS_UNKNOWN == s->gains)
950 : 52 : xaccSplitDetermineGainStatus(s);
951 : 52 : if (s->gains & GAINS_STATUS_VDIRTY)
952 : : {
953 : 52 : is_dirty = TRUE;
954 : 52 : s->gains &= ~GAINS_STATUS_VDIRTY;
955 : : }
956 : : }
957 : : }
958 : :
959 : 52 : if (is_dirty)
960 : : {
961 : 111 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
962 : : {
963 : 59 : Split *s = GNC_SPLIT(node->data);
964 : 59 : s->gains |= GAINS_STATUS_VDIRTY;
965 : : }
966 : : }
967 : :
968 : 117 : for (node = gnc_lot_get_split_list(lot); node; node = node->next)
969 : : {
970 : 65 : Split *s = GNC_SPLIT(node->data);
971 : 65 : xaccSplitComputeCapGains (s, gain_acc);
972 : : }
973 : 52 : LEAVE("(lot=%p)", lot);
974 : 52 : }
975 : :
976 : : /* =========================== END OF FILE ======================= */
|