diff --git a/src/engine/Scrub2.c b/src/engine/Scrub2.c index bb604252d6..124f6a1f83 100644 --- a/src/engine/Scrub2.c +++ b/src/engine/Scrub2.c @@ -140,23 +140,6 @@ xaccLotFill (GNCLot *lot) /* ============================================================== */ -void -xaccAccountScrubDoubleBalance (Account *acc) -{ - LotList *node; - if (!acc) return; - - ENTER ("acc=%s", acc->accountName); - for (node = acc->lots; node; node=node->next) - { - GNCLot *lot = node->data; - xaccLotScrubDoubleBalance (lot); - } - LEAVE ("acc=%s", acc->accountName); -} - -/* ============================================================== */ - void xaccLotScrubDoubleBalance (GNCLot *lot) { @@ -221,4 +204,231 @@ xaccLotScrubDoubleBalance (GNCLot *lot) LEAVE ("lot=%s", kvp_frame_get_string (gnc_lot_get_slots (lot), "/title")); } +/* ================================================================= */ + +static inline gboolean +is_subsplit (Split *split) +{ + KvpValue *kval; + + /* generic stop-progress conditions */ + if (!split) return FALSE; + g_return_val_if_fail (split->parent, FALSE); + + /* If there are no sub-splits, then there's nothing to do. */ + kval = kvp_frame_get_slot (split->kvp_data, "lot-split"); + if (!kval) return FALSE; + + return TRUE; +} + +/* ================================================================= */ + +void +xaccScrubSubSplitPrice (Split *split) +{ + gnc_numeric src_amt, src_val; + SplitList *node; + + if (FALSE == is_subsplit (split)) return; + + ENTER (" "); + /* Get 'price' of the indicated split */ + src_amt = xaccSplitGetAmount (split); + src_val = xaccSplitGetValue (split); + + /* Loop over splits, adjust each so that it has the same + * ratio (i.e. price). Change the value to get things + * right; do not change the amount */ + for (node=split->parent->splits; node; node=node->next) + { + Split *s = node->data; + Transaction *txn = s->parent; + gnc_numeric dst_amt, dst_val, target_val; + gnc_numeric delta; + int scu; + + /* Skip the reference split */ + if (s == split) continue; + + scu = gnc_commodity_get_fraction (txn->common_currency); + + dst_amt = xaccSplitGetAmount (s); + dst_val = xaccSplitGetValue (s); + target_val = gnc_numeric_mul (dst_amt, src_val, + GNC_DENOM_AUTO, GNC_DENOM_REDUCE); + target_val = gnc_numeric_div (target_val, src_amt, + scu, GNC_DENOM_EXACT); + + /* If the required price changes are 'small', do nothing. + * That is a case that the user will have to deal with + * manually. This routine is really intended only for + * a gross level of synchronization. + */ + delta = gnc_numeric_sub_fixed (target_val, dst_val); + delta = gnc_numeric_abs (delta); + if (3 * delta.num < delta.denom) continue; + + /* If the amount is small, pass on that too */ + if ((-2 < dst_amt.num) && (dst_amt.num < 2)) continue; + + /* Make the actual adjustment */ + xaccTransBeginEdit (txn); + xaccSplitSetValue (s, target_val); + xaccTransCommitEdit (txn); + } + LEAVE (" "); +} + +/* ================================================================= */ + +/* Remove the guid of b from a */ +static void +remove_guids (Split *sa, Split *sb) +{ + KvpFrame *ksub; + + /* Find and remove the matching guid's */ + ksub = gnc_kvp_bag_find_by_guid (sa->kvp_data, "lot-split", + "peer_guid", &sb->guid); + if (!ksub) + { + PERR ("merging splits that didn't have correct gemini values!\n" + "looking for guid=%s\n" + "bag held: %s", + guid_to_string (&sb->guid), + kvp_frame_to_string (sa->kvp_data)); + return; + } + gnc_kvp_bag_remove_frame (sa->kvp_data, "lot-split", ksub); + kvp_frame_delete (ksub); +} + +/* The 'merge_splits() routine causes the amount & value of sb + * to be merged into sa; it then destroys sb. It also performs + * some other misc cleanup */ + +static void +merge_splits (Split *sa, Split *sb) +{ + Account *act; + Transaction *txn; + gnc_numeric amt, val; + + act = xaccSplitGetAccount (sb); + xaccAccountBeginEdit (act); + + txn = sa->parent; + xaccTransBeginEdit (txn); + + /* Remove the guid of sb from the 'gemini' of sa */ + remove_guids (sa, sb); + + /* Add amount of sb into sa, ditto for value. */ + amt = xaccSplitGetAmount (sa); + amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb)); + xaccSplitSetAmount (sa, amt); + + val = xaccSplitGetValue (sa); + val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb)); + xaccSplitSetValue (sa, val); + + /* Set reconcile to no; after this much violence, + * no way its reconciled. */ + xaccSplitSetReconcile (sa, NREC); + + /* If sb has associated gains splits, trash them. */ + if ((sb->gains_split) && + (sb->gains_split->gains & GAINS_STATUS_GAINS)) + { + Transaction *t = sb->gains_split->parent; + xaccTransBeginEdit (t); + xaccTransDestroy (t); + xaccTransCommitEdit (t); + } + + /* Finally, delete sb */ + xaccSplitDestroy(sb); + + xaccTransCommitEdit (txn); + xaccAccountCommitEdit (act); +} + +gboolean +xaccScrubMergeSubSplits (Split *split) +{ + gboolean rc = FALSE; + Transaction *txn; + SplitList *node; + GNCLot *lot; + + if (FALSE == is_subsplit (split)) return FALSE; + + txn = split->parent; + lot = xaccSplitGetLot (split); + + ENTER (" "); +restart: + for (node=txn->splits; node; node=node->next) + { + 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 + * we should double-check the kvp's to be sure). Merge the + * two back together again. */ + merge_splits (split, s); + rc = TRUE; + goto restart; + } + LEAVE (" splits merged=%d", rc); + return rc; +} + +gboolean +xaccScrubMergeTransSubSplits (Transaction *txn) +{ + gboolean rc = FALSE; + SplitList *node; + + if (!txn) return FALSE; + + ENTER (" "); +restart: + for (node=txn->splits; node; node=node->next) + { + Split *s = node->data; + if (!xaccScrubMergeSubSplits(s)) continue; + + rc = TRUE; + goto restart; + } + LEAVE (" splits merged=%d", rc); + return rc; +} + +gboolean +xaccScrubMergeLotSubSplits (GNCLot *lot) +{ + gboolean rc = FALSE; + SplitList *node; + + if (!lot) return FALSE; + + ENTER (" "); +restart: + for (node=gnc_lot_get_split_list(lot); node; node=node->next) + { + Split *s = node->data; + if (!xaccScrubMergeSubSplits(s)) continue; + + rc = TRUE; + goto restart; + } + LEAVE (" splits merged=%d", rc); + return rc; +} + /* =========================== END OF FILE ======================= */ diff --git a/src/engine/Scrub2.h b/src/engine/Scrub2.h index 5e180facdf..357e2169d6 100644 --- a/src/engine/Scrub2.h +++ b/src/engine/Scrub2.h @@ -1,5 +1,5 @@ /********************************************************************\ - * Scrub2.h -- Convert Stock Accounts to use Lots * + * Scrub2.h -- Low-level Lot Management Routines. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -26,11 +26,15 @@ * @author Created by Linas Vepstas March 2003 * @author Copyright (c) 2003 Linas Vepstas * - * - * Provides a set of functions and utilities for checking and - * repairing ('scrubbing clean') the usage of Lots and lot balances - * in stock and commodity accounts. Broken lots are repaired using - * a first-in, first-out (FIFO) accounting schedule. + * Provides the low-level API for checking and repairing ('scrubbing + * clean') the usage of Lots and lot balances in stock and commodity + * accounts. Broken lots are repaired using a first-in, first-out + * (FIFO) accounting schedule. + * + * This is a 'low-level' API in the sense that each routine accomplishes + * only one particular task needed to clean up a Lot. To clean up a + * Lot as a whole, you almost certainly want to use one of the + * high-level API routines from the Scrub3.h file. */ #ifndef XACC_SCRUB2_H @@ -38,7 +42,6 @@ #include "gnc-engine.h" - /** The xaccAccountAssignLots() routine will walk over all of * the splits in an account, and make sure that each belongs * to a lot. Currently, the default (and only implemented) @@ -58,21 +61,6 @@ void xaccAccountAssignLots (Account *acc); */ void xaccLotFill (GNCLot *lot); -/** The xaccAccountScrubDoubleBalance() routine examines all - * of the closed lots in an account, and verifies that the - * lots are 'double balanced'. By 'double balance', we mean - * that both the sum of the split amounts is zero, and that - * the sum of the split values is zero. If a closed lot is - * found where the sum of the values is not zero, the lot - * is considered to have a 'realized gain or loss' that - * hadn't been correctly handled. This routine then creates - * a balancing transaction so as to record the realized - * gain/loss, adds it to the lot, and adds it to a gain/loss - * account. If there is no default gain/loss account, it - * creates one. - */ -void xaccAccountScrubDoubleBalance (Account *acc); - /** The xaccLotScrubDoubleBalance() routine examines the indicated * lot. If it is open, it does nothing. If it is closed, * it then verifies that the lot is 'double balanced'. @@ -88,5 +76,41 @@ void xaccAccountScrubDoubleBalance (Account *acc); */ void xaccLotScrubDoubleBalance (GNCLot *lot); +/** If a split has been pulled apart to make it fit into two (or more) + * lots, then it becomes theoretically possible for each subsplit to + * have a distinct price. But this would be wrong: each subsplit should + * have the same price, within rounding errors. This routine will + * examine the indicated split for sub-splits, and adjust the value + * of each so that they all have the same price. + * + * There is a bit of a problem with the interpretation of 'rounding + * errors' because there are pathological corner cases of small + * amounts. So this routine is fairly loose, hopefully loose enough + * so that the user can manually fine tune without having this routine + * clobber thier work. + */ +void xaccScrubSubSplitPrice (Split *split); + +/** The xaccScrubMergeSubSplits() routine will merge together + * all of the splits that were at one time split off from this + * split, but are no longer needed to be kept separate. Splits + * might be split up if they need to be divided over multiple + * lots; they can be merged back together if the lots change. + * In particular, two sub-splits may be merged if they are in + * the same lot, or in no lot. Note that, by definition, all + * subsplits belong to the same transaction. + * + * The routine returns TRUE if a merger was performed, else + * it returns FALSE. + * + * The xaccScrubMergeTransSubSplits() routine does the same, except + * that it does it for all of the splits in the transaction. + * The xaccScrubMergeLotSubSplits() routine does the same, except + * that it does it for all of the splits in the lot. + */ +gboolean xaccScrubMergeSubSplits (Split *split); +gboolean xaccScrubMergeTransSubSplits (Transaction *txn); +gboolean xaccScrubMergeLotSubSplits (GNCLot *lot); + #endif /* XACC_SCRUB2_H */ /** @} */ diff --git a/src/engine/Scrub3.c b/src/engine/Scrub3.c index 39cd8b22d3..a0471449de 100644 --- a/src/engine/Scrub3.c +++ b/src/engine/Scrub3.c @@ -43,6 +43,8 @@ #include "kvp-util-p.h" #include "policy-p.h" #include "Account.h" +#include "AccountP.h" +#include "Group.h" #include "Scrub2.h" #include "Scrub3.h" #include "Transaction.h" @@ -52,233 +54,6 @@ static short module = MOD_LOT; /* ================================================================= */ -static inline gboolean -is_subsplit (Split *split) -{ - KvpValue *kval; - - /* generic stop-progress conditions */ - if (!split) return FALSE; - g_return_val_if_fail (split->parent, FALSE); - - /* If there are no sub-splits, then there's nothing to do. */ - kval = kvp_frame_get_slot (split->kvp_data, "lot-split"); - if (!kval) return FALSE; - - return TRUE; -} - -/* ================================================================= */ - -void -xaccScrubSubSplitPrice (Split *split) -{ - gnc_numeric src_amt, src_val; - SplitList *node; - - if (FALSE == is_subsplit (split)) return; - - ENTER (" "); - /* Get 'price' of the indicated split */ - src_amt = xaccSplitGetAmount (split); - src_val = xaccSplitGetValue (split); - - /* Loop over splits, adjust each so that it has the same - * ratio (i.e. price). Change the value to get things - * right; do not change the amount */ - for (node=split->parent->splits; node; node=node->next) - { - Split *s = node->data; - Transaction *txn = s->parent; - gnc_numeric dst_amt, dst_val, target_val; - gnc_numeric delta; - int scu; - - /* Skip the reference split */ - if (s == split) continue; - - scu = gnc_commodity_get_fraction (txn->common_currency); - - dst_amt = xaccSplitGetAmount (s); - dst_val = xaccSplitGetValue (s); - target_val = gnc_numeric_mul (dst_amt, src_val, - GNC_DENOM_AUTO, GNC_DENOM_REDUCE); - target_val = gnc_numeric_div (target_val, src_amt, - scu, GNC_DENOM_EXACT); - - /* If the required price changes are 'small', do nothing. - * That is a case that the user will have to deal with - * manually. This routine is really intended only for - * a gross level of synchronization. - */ - delta = gnc_numeric_sub_fixed (target_val, dst_val); - delta = gnc_numeric_abs (delta); - if (3 * delta.num < delta.denom) continue; - - /* If the amount is small, pass on that too */ - if ((-2 < dst_amt.num) && (dst_amt.num < 2)) continue; - - /* Make the actual adjustment */ - xaccTransBeginEdit (txn); - xaccSplitSetValue (s, target_val); - xaccTransCommitEdit (txn); - } - LEAVE (" "); -} - -/* ================================================================= */ - -/* Remove the guid of b from a */ -static void -remove_guids (Split *sa, Split *sb) -{ - KvpFrame *ksub; - - /* Find and remove the matching guid's */ - ksub = gnc_kvp_bag_find_by_guid (sa->kvp_data, "lot-split", - "peer_guid", &sb->guid); - if (!ksub) - { - PERR ("merging splits that didn't have correct gemini values!\n" - "looking for guid=%s\n" - "bag held: %s", - guid_to_string (&sb->guid), - kvp_frame_to_string (sa->kvp_data)); - return; - } - gnc_kvp_bag_remove_frame (sa->kvp_data, "lot-split", ksub); - kvp_frame_delete (ksub); -} - -/* The 'merge_splits() routine causes the amount & value of sb - * to be merged into sa; it then destroys sb. It also performs - * some other misc cleanup */ - -static void -merge_splits (Split *sa, Split *sb) -{ - Account *act; - Transaction *txn; - gnc_numeric amt, val; - - act = xaccSplitGetAccount (sb); - xaccAccountBeginEdit (act); - - txn = sa->parent; - xaccTransBeginEdit (txn); - - /* Remove the guid of sb from the 'gemini' of sa */ - remove_guids (sa, sb); - - /* Add amount of sb into sa, ditto for value. */ - amt = xaccSplitGetAmount (sa); - amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb)); - xaccSplitSetAmount (sa, amt); - - val = xaccSplitGetValue (sa); - val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb)); - xaccSplitSetValue (sa, val); - - /* Set reconcile to no; after this much violence, - * no way its reconciled. */ - xaccSplitSetReconcile (sa, NREC); - - /* If sb has associated gains splits, trash them. */ - if ((sb->gains_split) && - (sb->gains_split->gains & GAINS_STATUS_GAINS)) - { - Transaction *t = sb->gains_split->parent; - xaccTransBeginEdit (t); - xaccTransDestroy (t); - xaccTransCommitEdit (t); - } - - /* Finally, delete sb */ - xaccSplitDestroy(sb); - - xaccTransCommitEdit (txn); - xaccAccountCommitEdit (act); -} - -gboolean -xaccScrubMergeSubSplits (Split *split) -{ - gboolean rc = FALSE; - Transaction *txn; - SplitList *node; - GNCLot *lot; - - if (FALSE == is_subsplit (split)) return FALSE; - - txn = split->parent; - lot = xaccSplitGetLot (split); - - ENTER (" "); -restart: - for (node=txn->splits; node; node=node->next) - { - 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 - * we should double-check the kvp's to be sure). Merge the - * two back together again. */ - merge_splits (split, s); - rc = TRUE; - goto restart; - } - LEAVE (" splits merged=%d", rc); - return rc; -} - -gboolean -xaccScrubMergeTransSubSplits (Transaction *txn) -{ - gboolean rc = FALSE; - SplitList *node; - - if (!txn) return FALSE; - - ENTER (" "); -restart: - for (node=txn->splits; node; node=node->next) - { - Split *s = node->data; - if (!xaccScrubMergeSubSplits(s)) continue; - - rc = TRUE; - goto restart; - } - LEAVE (" splits merged=%d", rc); - return rc; -} - -gboolean -xaccScrubMergeLotSubSplits (GNCLot *lot) -{ - gboolean rc = FALSE; - SplitList *node; - - if (!lot) return FALSE; - - ENTER (" "); -restart: - for (node=gnc_lot_get_split_list(lot); node; node=node->next) - { - Split *s = node->data; - if (!xaccScrubMergeSubSplits(s)) continue; - - rc = TRUE; - goto restart; - } - LEAVE (" splits merged=%d", rc); - return rc; -} - -/* ================================================================= */ - gboolean xaccScrubLot (GNCLot *lot) { diff --git a/src/engine/Scrub3.h b/src/engine/Scrub3.h index 7ac5578d7b..f54e39b2c4 100644 --- a/src/engine/Scrub3.h +++ b/src/engine/Scrub3.h @@ -1,5 +1,5 @@ /********************************************************************\ - * Scrub3.h -- Constrain Cap Gains to Track Sources of Gains * + * Scrub3.h -- High-Level Lot Constraint routines. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -22,17 +22,13 @@ /** @addtogroup Engine @{ */ /** @file Scrub3.h - * @breif Constrain Cap Gains to Track Sources of Gains + * @breif Hiogh-Level API for imposing Lot constraints * @author Created by Linas Vepstas Sept 2003 * @author Copyright (c) 2003 Linas Vepstas * - * Provides a set of functions and utilities for checking and - * repairing ('scrubbing clean') the usage of Cap Gains - * transactions in stock and commodity accounts. - * - * NOTE: Unless you have special needs, the functions you are looking - * for and almost certainly want to use are either xaccScrubLot() or - * xaccAccountScrubLots(). + * Provides the high-level API for checking and repairing ('scrubbing + * clean') the usage of Lots and Cap Gains transactions in stock and + * commodity accounts. */ #ifndef XACC_SCRUB3_H #define XACC_SCRUB3_H @@ -73,42 +69,5 @@ void xaccAccountScrubLots (Account *acc); void xaccGroupScrubLots (AccountGroup *grp); void xaccAccountTreeScrubLots (Account *acc); - -/** If a split has been pulled apart to make it fit into two (or more) - * lots, then it becomes theoretically possible for each subsplit to - * have a distinct price. But this would be wrong: each subsplit should - * have the same price, within rounding errors. This routine will - * examine the indicated split for sub-splits, and adjust the value - * of each so that they all have the same price. - * - * There is a bit of a problem with the interpretation of 'rounding - * errors' because there are pathological corner cases of small - * amounts. So this routine is fairly loose, hopefully loose enough - * so that the user can manually fine tune without having this routine - * clobber thier work. - */ -void xaccScrubSubSplitPrice (Split *split); - -/** The xaccScrubMergeSubSplits() routine will merge together - * all of the splits that were at one time split off from this - * split, but are no longer needed to be kept separate. Splits - * might be split up if they need to be divided over multiple - * lots; they can be merged back together if the lots change. - * In particular, two sub-splits may be merged if they are in - * the same lot, or in no lot. Note that, by definition, all - * subsplits belong to the same transaction. - * - * The routine returns TRUE if a merger was performed, else - * it returns FALSE. - * - * The xaccScrubMergeTransSubSplits() routine does the same, except - * that it does it for all of the splits in the transaction. - * The xaccScrubMergeLotSubSplits() routine does the same, except - * that it does it for all of the splits in the lot. - */ -gboolean xaccScrubMergeSubSplits (Split *split); -gboolean xaccScrubMergeTransSubSplits (Transaction *txn); -gboolean xaccScrubMergeLotSubSplits (GNCLot *lot); - #endif /* XACC_SCRUB3_H */ /** @} */ diff --git a/src/engine/test/test-lots.c b/src/engine/test/test-lots.c index 18d4f74333..b54cabdbcf 100644 --- a/src/engine/test/test-lots.c +++ b/src/engine/test/test-lots.c @@ -10,7 +10,7 @@ #include "Account.h" #include "Group.h" -#include "Scrub2.h" +#include "Scrub3.h" #include "gnc-engine-util.h" #include "gnc-module.h" #include "test-stuff.h" @@ -46,7 +46,7 @@ run_test (void) add_random_transactions_to_book (book, 720); grp = xaccGetAccountGroup (book); - xaccGroupScrubLotsBalance (grp); + xaccGroupScrubLots (grp); /* --------------------------------------------------------- */ /* In the second test, we create an account with unrealized gains,