From ce93bd96a2f10265946203289d68fa349874414c Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Sat, 20 Sep 2003 22:53:28 +0000 Subject: [PATCH] merg changes from the cap-gains7 branch. These include: -- numerous fixes to make cap-gains work -- crude ideas about a generic constraints system to keep data in engine correct. After these changes, all known cap-gains bugs are fixed, and most cap-gains features are done. Have at it. git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@9378 57a11ea4-9604-0410-9ed3-97b8803252fd --- src/engine/Account.c | 30 ++++-- src/engine/Scrub2.c | 17 +++- src/engine/Scrub3.c | 11 ++- src/engine/Scrub3.h | 6 +- src/engine/Transaction.c | 202 ++++++++++++++++++++++---------------- src/engine/Transaction.h | 1 - src/engine/TransactionP.h | 17 +++- src/engine/cap-gains.c | 7 +- src/engine/gnc-lot.c | 2 +- 9 files changed, 184 insertions(+), 109 deletions(-) diff --git a/src/engine/Account.c b/src/engine/Account.c index 50f0d7e231..3089ca5edb 100644 --- a/src/engine/Account.c +++ b/src/engine/Account.c @@ -1067,7 +1067,9 @@ xaccAccountInsertSplit (Account *acc, Split *split) /* Setting the amount casues a conversion to the new account's * denominator AKA 'SCU Smallest Currency Unit'. */ - xaccSplitSetAmount(split, old_amt); + /* xaccSplitSetAmount(split, old_amt); */ + split->amount = gnc_numeric_convert (old_amt, + xaccAccountGetCommoditySCU(acc), GNC_RND_ROUND); xaccTransCommitEdit(trans); xaccAccountCommitEdit(acc); LEAVE ("(acc=%p, split=%p)", acc, split); @@ -1169,17 +1171,23 @@ xaccAccountRecomputeBalance (Account * acc) cleared_balance = acc->starting_cleared_balance; reconciled_balance = acc->starting_reconciled_balance; - for(lp = acc->splits; lp; lp = lp->next) { + PINFO ("acct=%s starting baln=%lld/%lld", acc->accountName, + balance.num, balance.denom); + for(lp = acc->splits; lp; lp = lp->next) + { Split *split = (Split *) lp->data; gnc_numeric amt = xaccSplitGetAmount (split); balance = gnc_numeric_add_fixed(balance, amt); if (NREC != split->reconciled) + { cleared_balance = gnc_numeric_add_fixed(cleared_balance, amt); + } if (YREC == split->reconciled || - FREC == split->reconciled) { + FREC == split->reconciled) + { reconciled_balance = gnc_numeric_add_fixed(reconciled_balance, amt); } @@ -1879,6 +1887,9 @@ xaccAccountGetPresentBalance (Account *account) /********************************************************************\ \********************************************************************/ +/* XXX TODO: These 'GetBal' routines should be moved to some + * utility area outside of the core account engine area. + */ /* * Convert a balance from one currency to another. @@ -1916,12 +1927,12 @@ xaccAccountGetXxxBalanceInCurrency (Account *account, { gnc_numeric balance; - if (!account || !fn || !report_currency) - return gnc_numeric_zero (); + if (!account || !fn || !report_currency) return gnc_numeric_zero (); balance = fn(account); - return xaccAccountConvertBalanceToCurrency(account, balance, + balance = xaccAccountConvertBalanceToCurrency(account, balance, account->commodity, report_currency); + return balance; } /* @@ -1993,9 +2004,12 @@ xaccAccountGetBalanceInCurrency (Account *account, gnc_commodity *report_commodity, gboolean include_children) { - return - xaccAccountGetXxxBalanceInCurrencyRecursive (account, xaccAccountGetBalance, + gnc_numeric rc; + rc = xaccAccountGetXxxBalanceInCurrencyRecursive (account, + xaccAccountGetBalance, report_commodity, include_children); + PINFO (" baln=%lld/%lld", rc.num, rc.denom); + return rc; } diff --git a/src/engine/Scrub2.c b/src/engine/Scrub2.c index d134c83e45..1015c8da17 100644 --- a/src/engine/Scrub2.c +++ b/src/engine/Scrub2.c @@ -98,6 +98,7 @@ xaccLotFill (GNCLot *lot) { gnc_numeric lot_baln; Account *acc; + Split *split; if (!lot) return; acc = lot->account; @@ -108,15 +109,17 @@ xaccLotFill (GNCLot *lot) lot_baln = gnc_lot_get_balance (lot); if (gnc_numeric_zero_p (lot_baln)) return; + split = FIFOPolicyGetSplit (lot, NULL); + if (!split) return; /* Handle the common case */ + xaccAccountBeginEdit (acc); /* Loop until we've filled up the lot, (i.e. till the * balance goes to zero) or there are no splits left. */ while (1) { - Split *split, *subsplit; + Split *subsplit; - split = FIFOPolicyGetSplit (lot, NULL); subsplit = xaccSplitAssignToLot (split, lot); if (subsplit == split) { @@ -127,6 +130,9 @@ xaccLotFill (GNCLot *lot) lot_baln = gnc_lot_get_balance (lot); if (gnc_numeric_zero_p (lot_baln)) break; + + split = FIFOPolicyGetSplit (lot, NULL); + if (!split) break; } xaccAccountCommitEdit (acc); LEAVE ("acc=%s", acc->accountName); @@ -193,9 +199,10 @@ xaccLotScrubDoubleBalance (GNCLot *lot) } /* Now, total up the values */ - value = gnc_numeric_add_fixed (value, xaccSplitGetValue (s)); - PINFO ("Split value=%s Accum Lot value=%s", - gnc_numeric_to_string (xaccSplitGetValue(s)), + value = gnc_numeric_add (value, xaccSplitGetValue (s), + GNC_DENOM_AUTO, GNC_DENOM_EXACT); + PINFO ("Split=%p value=%s Accum Lot value=%s", s, + gnc_numeric_to_string (s->value), gnc_numeric_to_string (value)); } diff --git a/src/engine/Scrub3.c b/src/engine/Scrub3.c index 699b8dc249..dc13e7cdd3 100644 --- a/src/engine/Scrub3.c +++ b/src/engine/Scrub3.c @@ -220,6 +220,7 @@ restart: { Split *s = node->data; if (xaccSplitGetLot (s) != lot) continue; + if (s == split) continue; /* OK, this split is in the same lot (and thus same account) * as the indicated split. It must be a subsplit (although @@ -279,14 +280,15 @@ restart: /* ================================================================= */ -void +gboolean xaccScrubLot (GNCLot *lot) { + gboolean splits_deleted = FALSE; gnc_numeric lot_baln; gboolean opening_baln_is_pos, lot_baln_is_pos; Account *acc; - if (!lot) return; + if (!lot) return FALSE; ENTER (" "); acc = gnc_lot_get_account (lot); @@ -324,7 +326,7 @@ rethin: xaccLotFill (lot); /* Make sure there are no subsplits. */ - xaccScrubMergeLotSubSplits (lot); + splits_deleted = xaccScrubMergeLotSubSplits (lot); } /* Now re-compute cap gains, and then double-check that. */ @@ -332,7 +334,8 @@ rethin: xaccLotScrubDoubleBalance (lot); xaccAccountCommitEdit(acc); - LEAVE (" "); + LEAVE (" deleted=%d", splits_deleted); + return splits_deleted; } /* ========================== END OF FILE ========================= */ diff --git a/src/engine/Scrub3.h b/src/engine/Scrub3.h index 76aaf24a81..54e2e60350 100644 --- a/src/engine/Scrub3.h +++ b/src/engine/Scrub3.h @@ -78,8 +78,12 @@ gboolean xaccScrubMergeLotSubSplits (GNCLot *lot); * split values is gaurenteed to throw off lot balances. * This routine may end up closing the lot, or at least trying * to. It will also cause cap gains to be recomputed. + * + * Scrubbing the lot may cause subsplits to be merged together, + * i.e. for splits to be deleted. This routine returns true if + * any splits were deleted. */ -void xaccScrubLot (GNCLot *lot); +gboolean xaccScrubLot (GNCLot *lot); #endif /* XACC_SCRUB3_H */ /** @} */ diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c index 1d2c5a9af5..900fa262b6 100644 --- a/src/engine/Transaction.c +++ b/src/engine/Transaction.c @@ -33,6 +33,7 @@ #include "AccountP.h" #include "Group.h" +#include "Scrub3.h" #include "TransactionP.h" #include "TransLog.h" #include "cap-gains.h" @@ -87,15 +88,13 @@ const char *void_former_notes_str = "void-former-notes"; /* This static indicates the debugging module that this .o belongs to. */ static short module = MOD_ENGINE; - G_INLINE_FUNC void check_open (Transaction *trans); G_INLINE_FUNC void check_open (Transaction *trans) { if (trans && 0 >= trans->editlevel) { - PERR ("transaction %p not open for editing\n", trans); - PERR ("\t%s:%d \n", __FILE__, __LINE__); + PERR ("transaction %p not open for editing", trans); } } @@ -782,10 +781,10 @@ void xaccSplitSetAmount (Split *s, gnc_numeric amt) { if(!s) return; - ENTER ("old amt=%lld/%lld new amt=%lld/%lld", + ENTER ("split=%p old amt=%lld/%lld new amt=%lld/%lld", s, s->amount.num, s->amount.denom, amt.num, amt.denom); - check_open (s->parent); + check_open (s->parent); s->amount = gnc_numeric_convert(amt, get_commodity_denom(s), GNC_RND_ROUND); SET_GAINS_ADIRTY(s); @@ -798,10 +797,10 @@ void xaccSplitSetValue (Split *s, gnc_numeric amt) { if(!s) return; - ENTER ("old val=%lld/%lld new val=%lld/%lld", + ENTER ("split=%p old val=%lld/%lld new val=%lld/%lld", s, s->value.num, s->value.denom, amt.num, amt.denom); - check_open (s->parent); + check_open (s->parent); s->value = gnc_numeric_convert(amt, get_currency_denom(s), GNC_RND_ROUND); SET_GAINS_VDIRTY(s); @@ -1455,7 +1454,7 @@ xaccSplitsComputeValue (GList *splits, Split * skip_me, /* The split-editor often sends us 'temp' splits whose account * hasn't yet been set. Be lenient, and assume an implied base - * currency. If theres a problem later, teh scrub routines will + * currency. If theres a problem later, the scrub routines will * pick it up. */ if (NULL == s->acc) @@ -1674,6 +1673,67 @@ do_destroy (Transaction *trans) /********************************************************************\ \********************************************************************/ +/** The xaccScrubGainsDate() routine is used to keep the posted date + * of gains splis in sync with the posted date of the transaction + * that caused the gains. + * + * The posted date is kept in sync using a lazy-evaluation scheme. + * If xaccTransactionSetDatePosted() is called, the date change is + * accepted, and the split is marked date-dirty. If the posted date + * is queried for (using GetDatePosted()), then the transaction is + * evaluated. If its a gains-transaction, then it's date is copied + * from the source transaction that created the gains. + */ + +static inline void +xaccScrubGainsDate (Transaction *trans) +{ + SplitList *node; + Timespec ts = {0,0}; + gboolean do_set; +restart_search: + do_set = FALSE; + for (node = trans->splits; node; node=node->next) + { + Split *s = node->data; + if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus(s); + + if ((GAINS_STATUS_GAINS & s->gains) && + s->gains_split && + ((s->gains_split->gains & GAINS_STATUS_DATE_DIRTY) || + (s->gains & GAINS_STATUS_DATE_DIRTY))) + { + Transaction *source_trans = s->gains_split->parent; + ts = source_trans->date_posted; + do_set = TRUE; + s->gains &= ~GAINS_STATUS_DATE_DIRTY; + s->gains_split->gains &= ~GAINS_STATUS_DATE_DIRTY; + break; + } + } + + if (do_set) + { + xaccTransBeginEdit (trans); + xaccTransSetDatePostedTS(trans, &ts); + xaccTransCommitEdit (trans); + for (node = trans->splits; node; node=node->next) + { + Split *s = node->data; + s->gains &= ~GAINS_STATUS_DATE_DIRTY; + } + goto restart_search; + } +} + +/********************************************************************\ +\********************************************************************/ + +/* Temporary hack for data consitency */ +static int scrub_data = 1; +void xaccEnableDataScrubbing(void) { scrub_data = 1; } +void xaccDisableDataScrubbing(void) { scrub_data = 0; } + void xaccTransCommitEdit (Transaction *trans) @@ -1697,6 +1757,55 @@ xaccTransCommitEdit (Transaction *trans) * call to xaccTransCommitEdit. */ trans->editlevel++; + /* Before commiting the transaction, we're gonna enforce certain + * constraints. In particular, we want to enforce the cap-gains + * and the balanced lot constraints. These constraints might + * change the numbr of splits in this transaction, and the + * transaction itself might be deleted. This is also why + * we can't really enforce these constraints elsewhere: they + * can cause pointers to splits and transactions to disapear out + * from under the holder. + */ + if (scrub_data) + { + /* Lock down posted date to be synced to the source of cap gains */ + xaccScrubGainsDate(trans); + + /* Fix up split value */ + if (trans->splits && !(trans->do_free)) + { + SplitList *node; + + /* Fix up split amount */ +restart: + for (node=trans->splits; node; node=node->next) + { + split = node->data; + CHECK_GAINS_STATUS (split); + if (split->gains & GAINS_STATUS_ADIRTY) + { + gboolean altered = FALSE; + split->gains |= ~GAINS_STATUS_ADIRTY; + if (split->lot) altered = xaccScrubLot (split->lot); + if (altered) goto restart; + } + } + + /* Fix up gains split value */ + for (node=trans->splits; node; node=node->next) + { + split = node->data; + if ((split->gains & GAINS_STATUS_VDIRTY) || + (split->gains_split && + (split->gains_split->gains & GAINS_STATUS_VDIRTY))) + { + xaccSplitComputeCapGains (split, NULL); + } + } + } + } + + /* At this point, we check to see if we have a valid transaction. * There are two possiblities: * 1) Its more or less OK, and needs a little cleanup @@ -2696,64 +2805,11 @@ xaccTransGetNotes (const Transaction *trans) /********************************************************************\ \********************************************************************/ -/** The xaccScrubGainsDate() routine is used to keep the posted date - * of gains splis in sync with the posted date of the transaction - * that caused the gains. - * - * The posted date is kept in sync using a lazy-evaluation scheme. - * If xaccTransactionSetDatePosted() is called, the date change is - * accepted, and the split is marked date-dirty. If the posted date - * is queried for (using GetDatePosted()), then the transaction is - * evaluated. If its a gains-transaction, then it's date is copied - * from the source transaction that created the gains. - */ - -static inline void -xaccScrubGainsDate (Transaction *trans) -{ - SplitList *node; - Timespec ts = {0,0}; - gboolean do_set; -restart_search: - do_set = FALSE; - for (node = trans->splits; node; node=node->next) - { - Split *s = node->data; - if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus(s); - - if ((GAINS_STATUS_GAINS & s->gains) && - s->gains_split && - ((s->gains_split->gains & GAINS_STATUS_DATE_DIRTY) || - (s->gains & GAINS_STATUS_DATE_DIRTY))) - { - Transaction *source_trans = s->gains_split->parent; - ts = source_trans->date_posted; - do_set = TRUE; - s->gains &= ~GAINS_STATUS_DATE_DIRTY; - s->gains_split->gains &= ~GAINS_STATUS_DATE_DIRTY; - break; - } - } - - if (do_set) - { - xaccTransBeginEdit (trans); - xaccTransSetDatePostedTS(trans, &ts); - xaccTransCommitEdit (trans); - for (node = trans->splits; node; node=node->next) - { - Split *s = node->data; - s->gains &= ~GAINS_STATUS_DATE_DIRTY; - } - goto restart_search; - } -} time_t xaccTransGetDate (const Transaction *trans) { if (!trans) return 0; - xaccScrubGainsDate((Transaction *) trans); /* XXX wrong not const ! */ return (trans->date_posted.tv_sec); } @@ -2761,7 +2817,6 @@ void xaccTransGetDatePostedTS (const Transaction *trans, Timespec *ts) { if (!trans || !ts) return; - xaccScrubGainsDate((Transaction *) trans); /* XXX wrong not const ! */ *ts = (trans->date_posted); } @@ -2777,7 +2832,6 @@ xaccTransRetDatePostedTS (const Transaction *trans) { Timespec ts = {0, 0}; if (!trans) return ts; - xaccScrubGainsDate((Transaction *) trans); /* XXX wrong not const ! */ return (trans->date_posted); } @@ -3040,13 +3094,6 @@ xaccSplitGetAmount (const Split * cs) { Split *split = (Split *) cs; if (!split) return gnc_numeric_zero(); - - /* The value of cap-gains splits is slave to the - * transaction that's actually causing the gains. -XXX implementation not finished!! - */ - - CHECK_GAINS_STATUS(split); return split->amount; } @@ -3055,21 +3102,6 @@ xaccSplitGetValue (const Split * cs) { Split *split = (Split *) cs; if (!split) return gnc_numeric_zero(); - - /* The value of cap-gains splits is slave to the - * transaction that's actually causing the gains. - -XXX this test is wrong, it also needs to check for changed amount -which will should casue lot to recomputed! - */ - CHECK_GAINS_STATUS(split); - if ((split->gains & GAINS_STATUS_GAINS) && - split->gains_split && - (split->gains_split->gains & GAINS_STATUS_VDIRTY)) - { - xaccSplitComputeCapGains (split, NULL); - split->gains_split->gains |= ~GAINS_STATUS_VDIRTY; - } return split->value; } diff --git a/src/engine/Transaction.h b/src/engine/Transaction.h index 5d0e3c0a9c..1d3043ec63 100644 --- a/src/engine/Transaction.h +++ b/src/engine/Transaction.h @@ -147,7 +147,6 @@ Transaction * xaccTransLookup (const GUID *guid, QofBook *book); if there is no such transaction. */ Transaction * xaccTransLookupDirect (GUID guid, QofBook *book); - /** \warning XXX FIXME * gnc_book_count_transactions is a utility function, * probably needs to be moved to a utility file somewhere. diff --git a/src/engine/TransactionP.h b/src/engine/TransactionP.h index 75d0a6ed5e..807117a42b 100644 --- a/src/engine/TransactionP.h +++ b/src/engine/TransactionP.h @@ -267,13 +267,26 @@ gint32 xaccTransGetVersion (const Transaction*); gboolean xaccSplitRegister (void); gboolean xaccTransRegister (void); -/* - * The xaccTransactionGetBackend() subroutine will find the +/* The xaccTransactionGetBackend() subroutine will find the * persistent-data storage backend associated with this * transaction. */ QofBackend * xaccTransactionGetBackend (Transaction *trans); +/* The xaccEnable/DisableDataScrubbing() routines affect what + * happens during transaction commit. When scrubbing is enabled, + * then transactions are fixed up during transaction commit, + * so that only consistent transactions are commited to the engine. + * However, when data is being loaded from a backend (in particular, + * from the file backend), the data might not be consistent until + * its completely loaded. In particular, gains transactions might + * be loaded at a different time than the transactions that casued + * the gains. Thus, scrubbing needs do be disabled during file + * load. These routines enable that. + */ +void xaccEnableDataScrubbing(void); +void xaccDisableDataScrubbing(void); + /* The xaccSplitDetermineGainStatus() routine will analyze the * the split, and try to set the internal status flags * appropriately for the split. These flags indicate if the split diff --git a/src/engine/cap-gains.c b/src/engine/cap-gains.c index b7ba1f3929..681e1367df 100644 --- a/src/engine/cap-gains.c +++ b/src/engine/cap-gains.c @@ -545,6 +545,7 @@ xaccSplitGetCapGainsSplit (Split *split) gains_split = qof_entity_lookup (qof_book_get_entity_table(split->book), gains_guid, GNC_ID_SPLIT); + PINFO ("split=%p has gains-split=%p", split, gains_split); return gains_split; } @@ -565,7 +566,8 @@ xaccSplitComputeCapGains(Split *split, Account *gain_acc) if (!lot) return; currency = split->parent->common_currency; - ENTER ("split=%p lot=%s", split, + ENTER ("split=%p gains=%p status=0x%x lot=%s", split, + split->gains_split, split->gains, kvp_frame_get_string (gnc_lot_get_slots (lot), "/title")); /* Make sure the status flags and pointers are initialized */ @@ -696,7 +698,8 @@ XXX this should be a part of a scrub routine ! * If there is, adjust its value as appropriate. Else, create * a new gains transaction. */ - lot_split = xaccSplitGetCapGainsSplit (split); + /* lot_split = xaccSplitGetCapGainsSplit (split); */ + lot_split = split->gains_split; if (NULL == lot_split) { diff --git a/src/engine/gnc-lot.c b/src/engine/gnc-lot.c index a88e74f0ae..ba28608d48 100644 --- a/src/engine/gnc-lot.c +++ b/src/engine/gnc-lot.c @@ -209,7 +209,7 @@ gnc_lot_get_balance (GNCLot *lot) { Split *s = node->data; gnc_numeric amt = xaccSplitGetAmount (s); - baln = gnc_numeric_add (baln, amt, GNC_DENOM_AUTO, GNC_DENOM_FIXED); + baln = gnc_numeric_add_fixed (baln, amt); } /* cache a zero balance as a closed lot */