void xaccTransScrubImbalance (Transaction *trans, Account *root, Account *account) { gnc_numeric imbalance; if (!trans) return; ENTER ("()"); /* Must look for orphan splits even if there is no imbalance. */ xaccTransScrubSplits (trans); /* Return immediately if things are balanced. */ if (xaccTransIsBalanced (trans)) { LEAVE ("transaction is balanced"); return; } if (! xaccTransUseTradingAccounts (trans)) { gnc_transaction_balance_no_trading (trans, root, account); LEAVE ("transaction balanced, no trading accounts"); return; } imbalance = gnc_transaction_adjust_trading_splits (trans, root); /* Balance the value, ignoring existing trading splits */ if (! gnc_numeric_zero_p (imbalance)) { PINFO ("Value unbalanced transaction"); add_balance_split (trans, imbalance, root, account); } gnc_transaction_balance_trading (trans, root); if (gnc_numeric_zero_p(xaccTransGetImbalanceValue(trans))) { LEAVE ("()"); return; } /* If the transaction is still not balanced, it's probably because there are splits with zero amount and non-zero value. These are usually realized gain/loss splits. Add a reversing split for each of them to balance the value. */ gnc_transaction_balance_trading_more_splits (trans, root); if (!gnc_numeric_zero_p(xaccTransGetImbalanceValue(trans))) PERR("Balancing currencies unbalanced value"); }
void xaccTransScrubImbalance (Transaction *trans, Account *root, Account *account) { const gnc_commodity *currency; if (!trans) return; ENTER ("()"); /* Must look for orphan splits even if there is no imbalance. */ xaccTransScrubSplits (trans); /* Return immediately if things are balanced. */ if (xaccTransIsBalanced (trans)) { LEAVE ("transaction is balanced"); return; } currency = xaccTransGetCurrency (trans); if (! xaccTransUseTradingAccounts (trans)) { gnc_numeric imbalance; /* Make the value sum to zero */ imbalance = xaccTransGetImbalanceValue (trans); if (! gnc_numeric_zero_p (imbalance)) { PINFO ("Value unbalanced transaction"); add_balance_split (trans, imbalance, root, account); } } else { MonetaryList *imbal_list; MonetaryList *imbalance_commod; GList *splits; gnc_numeric imbalance; Split *balance_split = NULL; /* If there are existing trading splits, adjust the price or exchange rate in each of them to agree with the non-trading splits for the same commodity. If there are multiple non-trading splits for the same commodity in the transaction this will use the exchange rate in the last such split. This shouldn't happen, and if it does then there's not much we can do about it anyway. While we're at it, compute the value imbalance ignoring existing trading splits. */ imbalance = gnc_numeric_zero(); for (splits = trans->splits; splits; splits = splits->next) { Split *split = splits->data; gnc_numeric value, amount; gnc_commodity *commodity; if (! xaccTransStillHasSplit (trans, split)) continue; commodity = xaccAccountGetCommodity (xaccSplitGetAccount(split)); if (!commodity) { PERR("Split has no commodity"); continue; } balance_split = find_trading_split (trans, root, commodity); if (balance_split != split) /* this is not a trading split */ imbalance = gnc_numeric_add(imbalance, xaccSplitGetValue (split), GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT); /* Ignore splits where value or amount is zero */ value = xaccSplitGetValue (split); amount = xaccSplitGetAmount (split); if (gnc_numeric_zero_p(amount) || gnc_numeric_zero_p(value)) continue; if (balance_split && balance_split != split) { gnc_numeric convrate = gnc_numeric_div (amount, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE); gnc_numeric old_value, new_value; old_value = xaccSplitGetValue(balance_split); new_value = gnc_numeric_div (xaccSplitGetAmount(balance_split), convrate, gnc_commodity_get_fraction(currency), GNC_HOW_RND_ROUND_HALF_UP); if (! gnc_numeric_equal (old_value, new_value)) { xaccTransBeginEdit (trans); xaccSplitSetValue (balance_split, new_value); xaccSplitScrub (balance_split); xaccTransCommitEdit (trans); } } } /* Balance the value, ignoring existing trading splits */ if (! gnc_numeric_zero_p (imbalance)) { PINFO ("Value unbalanced transaction"); add_balance_split (trans, imbalance, root, account); } /* If the transaction is balanced, nothing more to do */ imbal_list = xaccTransGetImbalance (trans); if (!imbal_list) { LEAVE("transaction is balanced"); return; } PINFO ("Currency unbalanced transaction"); for (imbalance_commod = imbal_list; imbalance_commod; imbalance_commod = imbalance_commod->next) { gnc_monetary *imbal_mon = imbalance_commod->data; gnc_commodity *commodity; gnc_numeric old_amount, new_amount; gnc_numeric old_value, new_value, val_imbalance; GList *splits; commodity = gnc_monetary_commodity (*imbal_mon); balance_split = get_trading_split(trans, root, commodity); if (!balance_split) { /* Error already logged */ gnc_monetary_list_free(imbal_list); LEAVE(""); return; } account = xaccSplitGetAccount(balance_split); if (! gnc_commodity_equal (currency, commodity)) { /* Find the value imbalance in this commodity */ val_imbalance = gnc_numeric_zero(); for (splits = trans->splits; splits; splits = splits->next) { Split *split = splits->data; if (xaccTransStillHasSplit (trans, split) && gnc_commodity_equal (commodity, xaccAccountGetCommodity(xaccSplitGetAccount(split)))) val_imbalance = gnc_numeric_add (val_imbalance, xaccSplitGetValue (split), GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT); } } xaccTransBeginEdit (trans); old_amount = xaccSplitGetAmount (balance_split); new_amount = gnc_numeric_sub (old_amount, gnc_monetary_value(*imbal_mon), gnc_commodity_get_fraction(commodity), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetAmount (balance_split, new_amount); if (gnc_commodity_equal (currency, commodity)) { /* Imbalance commodity is the transaction currency, value in the split must be the same as the amount */ xaccSplitSetValue (balance_split, new_amount); } else { old_value = xaccSplitGetValue (balance_split); new_value = gnc_numeric_sub (old_value, val_imbalance, gnc_commodity_get_fraction(currency), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue (balance_split, new_value); } xaccSplitScrub (balance_split); xaccTransCommitEdit (trans); } gnc_monetary_list_free(imbal_list); if (!gnc_numeric_zero_p(xaccTransGetImbalanceValue(trans))) { /* This is probably because there are splits with zero amount and non-zero value. These are usually realized gain/loss splits. Add a reversing split for each of them to balance the value. */ /* Copy the split list so we don't see the splits we're adding */ GList *splits_dup = g_list_copy(trans->splits); for (splits = splits_dup; splits; splits = splits->next) { Split *split = splits->data; if (! xaccTransStillHasSplit(trans, split)) continue; if (!gnc_numeric_zero_p(xaccSplitGetValue(split)) && gnc_numeric_zero_p(xaccSplitGetAmount(split))) { gnc_commodity *commodity; gnc_numeric old_value, new_value; commodity = xaccAccountGetCommodity(xaccSplitGetAccount(split)); if (!commodity) { PERR("Split has no commodity"); continue; } balance_split = get_trading_split(trans, root, commodity); if (!balance_split) { /* Error already logged */ gnc_monetary_list_free(imbal_list); LEAVE(""); return; } account = xaccSplitGetAccount(balance_split); xaccTransBeginEdit (trans); old_value = xaccSplitGetValue (balance_split); new_value = gnc_numeric_sub (old_value, xaccSplitGetValue(split), gnc_commodity_get_fraction(currency), GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetValue (balance_split, new_value); /* Don't change the balance split's amount since the amount is zero in the split we're working on */ xaccSplitScrub (balance_split); xaccTransCommitEdit (trans); } } g_list_free(splits_dup); if (!gnc_numeric_zero_p(xaccTransGetImbalanceValue(trans))) PERR("Balancing currencies unbalanced value"); } } LEAVE ("()"); }