Bug #537476: Implement currency trading accounts optionally, to be enabled per-book.

Patch by Mike Alexander:

This patch implements trading accounts somewhat as described in Peter
Selinger's document at
<http://www.mathstat.dal.ca/~selinger/accounting/gnucash.html>.  Although he
describes it as a multiple currency problem, it really applies to any
transactions involving multiple commodities (for example buying or selling a
stock)  Hence I've called the trading accounts "commodity exchange accounts"
which seems more descriptive.

In summary these patches add an option to use commodity exchange accounts and
if it is on a transaction must be balanced both in value (in the transaction
currency) and in each commodity or currency used in any split in the
transaction.  If a transaction only contains splits in the transaction currency
then this is the same rule as Gnucash has always enforced.

In this patch, the option to use trading accounts has been moved from
Edit->Preferences to File->Properties and is now associated with the active
book instead of being a global option.  If you have set the global value on in
a previous version you will need to set it on again in each file for which you
want trading accounts, the previous global setting will be ignored.

A more detailed list of changes follows:

1.  Added a "Use commodity exchange accounts" per-book option.

2.  Added gnc_monetary and MonetaryList data types.

3.  Renamed xaccTransGetImbalance to xaccTransGetImbalanceValue and added a new
xaccTransGetImbalance that returns a MonetaryList.  Also added
xaccTransIsBalanced to see if the transaction is balanced without returning a
GList that needs to be freed.  It calls both xaccTransGetImbalance and
xaccTransGetImbalanceValue since a transaction may be unbalanced with regard to
either without being unbalanced with regard to the other.

4.  Changed gnc_split_register_get_debcred_bg_color to use xaccTransIsBalanced.

5.  Changed gnc_split_register_balance_trans to not offer to adjust an existing
split if there imbalances in multiple currencies.  Because of bugs in the
register code this is rarely called.

6.  Changed importers to use xaccTransGetImbalanceValue to check for imbalance,
assuming that they won't create multiple currency trasactions.

7.  Changed xaccTransScrubImbalance to create a balancing split for each
imbalanced commodity in the transaction.  Also balances the transaction value.
The commodity balancing splits go into accounts in the hierarchy
Trading:NAMESPACE:COMMODITY.  The value balancing splits go into
Imbalance-CURRENCY as before.

8.  Changed xaccSplitConvertAmount to use xaccTransIsBalanced instead of
xaccTransGetImbalance.

9.  Changed gnc_split_register_get_debcred_entry to sometimes use the split
amount instead of value if using currency accounts.

If the register is a stock register (i.e., shows shares and prices), it uses
the value if the split is in the register commodity (i.e. is for the stock) and
the amount otherwise.  It shows the currency symbol unless the commodity is the
default currency.

If the register is not a stock register it always uses the amount and shows the
currency symbol if the split is not in the register commodity.

Also changed it to not return a value for a null split unless the transaction
is unbalanced in exactly one currency.  This is what goes in a blank split as
the proposed value.

10. Changed refresh_model_row to use xaccTransGetImbalanceValue to get the
imbalance, assuming that importers don't create transactions in multiple
currencies.  Also same change in gnc_import_process_trans_item,
downloaded_transaction_append, and gnc_import_TransInfo_is_balanced.

11. Changed the TRANS_IMBALANCE accessor method in xaccTransRegister to use
xaccTransGetImbalanceValue.  As far as I can tell this is only used by the
"pd-balance" query type in gnc_scm2query_term_query_v1() defined in
engine-helpers.c.  This query type only tests the result for zero/non-zero.

12. Changed xaccTransGetAccountConvRate to accept any split into the correct
commodity instead of insisting on one into the provided account.  Then can use
it in xaccTransScrubImbalance to set the value of the imbalance split from its
amount, however later changed xaccTransScrubImbalance to not use it.  Instead
it sets the value for the new split correctly to keep the value of the whole
transaction balanced.

13. Changed the balance sheet report to include a new option to not compute
unrealized gains and losses.

14. Related to 9 above, changed gnc_split_register_auto_calc to not do anything
if given a stock register where the value cell doesn't contain the value.

15. Also related to 9, changed gnc_split_register_save_amount_values to set the
amount and value fields in the split correctly when using trading accounts.

16. Changed the new account and edit account dialogs to allow any commodity or
currency for an income account if using trading accounts.  It would be better
to add a new account type for trading accounts, but that's a big deal and I'll
leave that for later after we see whether this set of changes is going to be
accepted or rejected.

17. Change gnc_xfer_dialog_run_exchange_dialog to understand that the new value
is really the split's amount if using trading accounts.

18. Changed xaccSplitGetOtherSplit to ignore trading splits if using commodity
exchange accounts.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18429 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming 2009-11-20 20:11:03 +00:00
parent 20197b89af
commit b578c56b27
38 changed files with 1067 additions and 156 deletions

View File

@ -75,6 +75,7 @@
(cons ACCT-TYPE-EXPENSE (N_ "Expense"))
(cons ACCT-TYPE-PAYABLE (N_ "Payment"))
(cons ACCT-TYPE-RECEIVABLE (N_ "Invoice"))
(cons ACCT-TYPE-TRADING (N_ "Decrease"))
(cons ACCT-TYPE-EQUITY (N_ "Decrease"))))
(define gnc:*credit-strings*
@ -91,6 +92,7 @@
(cons ACCT-TYPE-EXPENSE (N_ "Rebate"))
(cons ACCT-TYPE-PAYABLE (N_ "Bill"))
(cons ACCT-TYPE-RECEIVABLE (N_ "Payment"))
(cons ACCT-TYPE-TRADING (N_ "Increase"))
(cons ACCT-TYPE-EQUITY (N_ "Increase"))))
(define (gnc:get-debit-string type)

View File

@ -134,7 +134,7 @@ skip_expense_acct_cb (Account *account, gpointer user_data)
type = xaccAccountGetType (account);
if (type == ACCT_TYPE_PAYABLE || type == ACCT_TYPE_RECEIVABLE ||
type == ACCT_TYPE_CASH || type == ACCT_TYPE_BANK ||
type == ACCT_TYPE_EQUITY)
type == ACCT_TYPE_EQUITY || type == ACCT_TYPE_TRADING)
{
return TRUE;
}
@ -157,7 +157,7 @@ skip_income_acct_cb (Account *account, gpointer user_data)
type = xaccAccountGetType (account);
if (type == ACCT_TYPE_PAYABLE || type == ACCT_TYPE_RECEIVABLE ||
type == ACCT_TYPE_CASH || type == ACCT_TYPE_BANK ||
type == ACCT_TYPE_EQUITY)
type == ACCT_TYPE_EQUITY || type == ACCT_TYPE_TRADING)
{
return TRUE;
}

View File

@ -81,6 +81,12 @@
gnc:*business-label* (N_ "Fancy Date Format")
"g" (N_ "The default date format used for fancy printed dates")
#f))
(reg-option
(gnc:make-simple-boolean-option
gnc:*book-label* gnc:*trading-accounts*
"a" (N_ "True if trading accounts should be used for transactions involving more than one commodity")
#f))
)
(gnc-register-kvp-option-generator QOF-ID-BOOK-SCM book-options-generator)

View File

@ -18,5 +18,10 @@
gnc:*company-phone* gnc:*company-fax* gnc:*company-url*
gnc:*company-email* gnc:*company-contact*)
(define gnc:*book-label* (N_ "Accounts"))
(define gnc:*trading-accounts* (N_ "Trading Accounts"))
(export gnc:*book-label* gnc:*trading-accounts*)
(load-from-path "business-options.scm")
(load-from-path "business-prefs.scm")

View File

@ -1919,7 +1919,7 @@ static int typeorder[NUM_ACCOUNT_TYPES] = {
ACCT_TYPE_BANK, ACCT_TYPE_STOCK, ACCT_TYPE_MUTUAL, ACCT_TYPE_CURRENCY,
ACCT_TYPE_CASH, ACCT_TYPE_ASSET, ACCT_TYPE_RECEIVABLE,
ACCT_TYPE_CREDIT, ACCT_TYPE_LIABILITY, ACCT_TYPE_PAYABLE,
ACCT_TYPE_INCOME, ACCT_TYPE_EXPENSE, ACCT_TYPE_EQUITY };
ACCT_TYPE_INCOME, ACCT_TYPE_EXPENSE, ACCT_TYPE_EQUITY, ACCT_TYPE_TRADING };
static int revorder[NUM_ACCOUNT_TYPES] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
@ -3701,6 +3701,7 @@ xaccAccountTypeEnumAsString(GNCAccountType type)
GNC_RETURN_ENUM_AS_STRING(RECEIVABLE);
GNC_RETURN_ENUM_AS_STRING(PAYABLE);
GNC_RETURN_ENUM_AS_STRING(ROOT);
GNC_RETURN_ENUM_AS_STRING(TRADING);
GNC_RETURN_ENUM_AS_STRING(CHECKING);
GNC_RETURN_ENUM_AS_STRING(SAVINGS);
GNC_RETURN_ENUM_AS_STRING(MONEYMRKT);
@ -3736,6 +3737,7 @@ xaccAccountStringToType(const char* str, GNCAccountType *type)
GNC_RETURN_ON_MATCH(RECEIVABLE);
GNC_RETURN_ON_MATCH(PAYABLE);
GNC_RETURN_ON_MATCH(ROOT);
GNC_RETURN_ON_MATCH(TRADING);
GNC_RETURN_ON_MATCH(CHECKING);
GNC_RETURN_ON_MATCH(SAVINGS);
GNC_RETURN_ON_MATCH(MONEYMRKT);
@ -3777,7 +3779,9 @@ account_type_name[NUM_ACCOUNT_TYPES] = {
N_("Expense"),
N_("Equity"),
N_("A/Receivable"),
N_("A/Payable")
N_("A/Payable"),
N_("Root"),
N_("Trading")
/*
N_("Checking"),
N_("Savings"),
@ -3849,6 +3853,10 @@ xaccParentAccountTypesCompatibleWith (GNCAccountType type)
return
(1 << ACCT_TYPE_EQUITY) |
(1 << ACCT_TYPE_ROOT);
case ACCT_TYPE_TRADING:
return
(1 << ACCT_TYPE_TRADING) |
(1 << ACCT_TYPE_ROOT);
default:
PERR("bad account type: %d", type);
return 0;

View File

@ -140,18 +140,24 @@ typedef enum
ACCT_TYPE_PAYABLE = 12, /**< A/P account type */
ACCT_TYPE_ROOT = 13, /**< The hidden root account of an account tree. */
ACCT_TYPE_TRADING = 14, /**< Account used to record multiple commodity transactions.
* This is not the same as ACCT_TYPE_CURRENCY above.
* Multiple commodity transactions have splits in these
* accounts to make the transaction balance in each
* commodity as well as in total value. */
NUM_ACCOUNT_TYPES = 14, /**< stop here; the following types
NUM_ACCOUNT_TYPES = 15, /**< stop here; the following types
* just aren't ready for prime time */
/* bank account types */
ACCT_TYPE_CHECKING = 14, /**< bank account type -- don't use this
ACCT_TYPE_CHECKING = 15, /**< bank account type -- don't use this
* for now, see NUM_ACCOUNT_TYPES */
ACCT_TYPE_SAVINGS = 15, /**< bank account type -- don't use this for
ACCT_TYPE_SAVINGS = 16, /**< bank account type -- don't use this for
* now, see NUM_ACCOUNT_TYPES */
ACCT_TYPE_MONEYMRKT = 16, /**< bank account type -- don't use this
ACCT_TYPE_MONEYMRKT = 17, /**< bank account type -- don't use this
* for now, see NUM_ACCOUNT_TYPES */
ACCT_TYPE_CREDITLINE = 17, /**< line of credit -- don't use this for
ACCT_TYPE_CREDITLINE = 18, /**< line of credit -- don't use this for
* now, see NUM_ACCOUNT_TYPES */
} GNCAccountType;

View File

@ -689,7 +689,7 @@ add_closing_balances (Account *parent,
/* We need to carry a balance on any account that is not
* and income or expense or equity account */
if ((ACCT_TYPE_INCOME != tip) && (ACCT_TYPE_EXPENSE != tip) &&
(ACCT_TYPE_EQUITY != tip))
(ACCT_TYPE_EQUITY != tip && ACCT_TYPE_TRADING != tip))
{
gnc_numeric baln;
baln = xaccAccountGetBalance (candidate);

View File

@ -48,7 +48,7 @@
*
* This routine will also create 'equity transactions' in
* order to preserve the balances on accounts. For any
* account that is not of income, expense or equity type,
* account that is not of income, expense, trading or equity type,
* this routine wil find the closing balance of each account
* in the closed book. It will then create an 'equity
* transaction' in the open book, creating an opening balance

View File

@ -72,6 +72,7 @@ static void
TransScrubOrphansFast (Transaction *trans, Account *root)
{
GList *node;
gchar *accname;
if (!trans) return;
g_return_if_fail (root);
@ -85,7 +86,12 @@ TransScrubOrphansFast (Transaction *trans, Account *root)
DEBUG ("Found an orphan \n");
orph = xaccScrubUtilityGetOrMakeAccount (root, trans->common_currency, _("Orphan"));
accname = g_strconcat (_("Orphan"), "-",
gnc_commodity_get_mnemonic (trans->common_currency),
NULL);
orph = xaccScrubUtilityGetOrMakeAccount (root, trans->common_currency,
accname, ACCT_TYPE_BANK, FALSE);
g_free (accname);
if (!orph) continue;
xaccSplitSetAccount(split, orph);
@ -353,28 +359,15 @@ xaccTransScrubCurrencyFromSplits(Transaction *trans)
}
}
void
xaccTransScrubImbalance (Transaction *trans, Account *root,
Account *account)
static Split *
get_balance_split (Transaction *trans, Account *root, Account *account,
gnc_commodity *commodity)
{
Split *balance_split = NULL;
gnc_numeric imbalance;
if (!trans) return;
ENTER ("()");
/* Must look for orphan splits even if there is no imbalance. */
xaccTransScrubSplits (trans);
/* If the transaction is balanced, nothing more to do */
imbalance = xaccTransGetImbalance (trans);
if (gnc_numeric_zero_p (imbalance)) {
LEAVE("zero imbalance");
return;
}
if (!account)
Split *balance_split;
gchar *accname;
if (!account ||
!gnc_commodity_equiv (commodity, xaccAccountGetCommodity(account)))
{
if (!root)
{
@ -383,16 +376,17 @@ xaccTransScrubImbalance (Transaction *trans, Account *root,
{
/* This can't occur, things should be in books */
PERR ("Bad data corruption, no root account in book");
LEAVE("");
return;
return NULL;
}
}
account = xaccScrubUtilityGetOrMakeAccount (root,
trans->common_currency, _("Imbalance"));
accname = g_strconcat (_("Imbalance"), "-",
gnc_commodity_get_mnemonic (commodity), NULL);
account = xaccScrubUtilityGetOrMakeAccount (root, commodity,
accname, ACCT_TYPE_BANK, FALSE);
g_free (accname);
if (!account) {
PERR ("Can't get balancing account");
LEAVE("");
return;
return NULL;
}
}
@ -408,36 +402,403 @@ xaccTransScrubImbalance (Transaction *trans, Account *root,
xaccSplitSetAccount(balance_split, account);
xaccTransCommitEdit (trans);
}
return balance_split;
}
PINFO ("unbalanced transaction");
/* Get the trading split for a given commodity, creating it (and the
necessary accounts) if it doesn't exist. */
static Split *
get_trading_split (Transaction *trans, Account *root,
gnc_commodity *commodity)
{
Split *balance_split;
Account *trading_account;
Account *ns_account;
Account *account;
gnc_commodity *default_currency = NULL;
if (!root)
{
const gnc_commodity *currency;
const gnc_commodity *commodity;
gnc_numeric old_value, new_value;
xaccTransBeginEdit (trans);
currency = xaccTransGetCurrency (trans);
old_value = xaccSplitGetValue (balance_split);
/* Note: We have to round for the commodity's fraction, NOT any
* already existing denominator (bug #104343), because either one
* of the denominators might already be reduced. */
new_value = gnc_numeric_sub (old_value, imbalance,
gnc_commodity_get_fraction(currency),
GNC_HOW_RND_ROUND);
xaccSplitSetValue (balance_split, new_value);
commodity = xaccAccountGetCommodity (account);
if (gnc_commodity_equiv (currency, commodity))
root = gnc_book_get_root_account (xaccTransGetBook (trans));
if (NULL == root)
{
xaccSplitSetAmount (balance_split, new_value);
/* This can't occur, things should be in books */
PERR ("Bad data corruption, no root account in book");
return NULL;
}
}
/* Get the default currency. This is harder than it seems. It's not
possible to call gnc_default_currency() since it's a UI function. One
might think that the currency of the root account would do, but the root
account has no currency. Instead look for the Income placeholder account
and use its currency. */
default_currency = xaccAccountGetCommodity(gnc_account_lookup_by_name(root,
_("Income")));
if (! default_currency) {
default_currency = commodity;
}
trading_account = xaccScrubUtilityGetOrMakeAccount (root,
default_currency,
_("Trading"),
ACCT_TYPE_TRADING, TRUE);
if (!trading_account) {
PERR ("Can't get trading account");
return NULL;
}
ns_account = xaccScrubUtilityGetOrMakeAccount (trading_account,
default_currency,
gnc_commodity_get_namespace(commodity),
ACCT_TYPE_TRADING, TRUE);
if (!ns_account) {
PERR ("Can't get namespace account");
return NULL;
}
account = xaccScrubUtilityGetOrMakeAccount (ns_account, commodity,
gnc_commodity_get_mnemonic(commodity),
ACCT_TYPE_TRADING, FALSE);
if (!account) {
PERR ("Can't get commodity account");
return NULL;
}
balance_split = xaccTransFindSplitByAccount(trans, account);
/* Put split into account before setting split value */
if (!balance_split)
{
balance_split = xaccMallocSplit (qof_instance_get_book(trans));
xaccTransBeginEdit (trans);
xaccSplitSetParent(balance_split, trans);
xaccSplitSetAccount(balance_split, account);
xaccTransCommitEdit (trans);
}
return balance_split;
}
/* Find the trading split for a commodity, but don't create any splits
or accounts if they don't already exist. */
static Split *
find_trading_split (Transaction *trans, Account *root,
gnc_commodity *commodity)
{
Account *trading_account;
Account *ns_account;
Account *account;
if (!root)
{
root = gnc_book_get_root_account (xaccTransGetBook (trans));
if (NULL == root)
{
/* This can't occur, things should be in books */
PERR ("Bad data corruption, no root account in book");
return NULL;
}
}
trading_account = gnc_account_lookup_by_name (root, _("Trading"));
if (!trading_account) {
return NULL;
}
ns_account = gnc_account_lookup_by_name (trading_account,
gnc_commodity_get_namespace(commodity));
if (!ns_account) {
return NULL;
}
account = gnc_account_lookup_by_name (ns_account,
gnc_commodity_get_mnemonic(commodity));
if (!account) {
return NULL;
}
return xaccTransFindSplitByAccount(trans, account);
}
static void
add_balance_split (Transaction *trans, gnc_numeric imbalance,
Account *root, Account *account)
{
const gnc_commodity *commodity;
gnc_numeric old_value, new_value;
Split *balance_split;
gnc_commodity *currency = xaccTransGetCurrency (trans);
balance_split = get_balance_split(trans, root, account, currency);
if (!balance_split)
{
/* Error already logged */
LEAVE("");
return;
}
account = xaccSplitGetAccount(balance_split);
xaccTransBeginEdit (trans);
old_value = xaccSplitGetValue (balance_split);
/* Note: We have to round for the commodity's fraction, NOT any
* already existing denominator (bug #104343), because either one
* of the denominators might already be reduced. */
new_value = gnc_numeric_sub (old_value, imbalance,
gnc_commodity_get_fraction(currency),
GNC_HOW_RND_ROUND);
xaccSplitSetValue (balance_split, new_value);
commodity = xaccAccountGetCommodity (account);
if (gnc_commodity_equiv (currency, commodity))
{
xaccSplitSetAmount (balance_split, new_value);
}
xaccSplitScrub (balance_split);
xaccTransCommitEdit (trans);
}
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))
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_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_RND_ROUND);
if (! gnc_numeric_equal (old_value, new_value))
{
xaccTransBeginEdit (trans);
xaccSplitSetValue (balance_split, new_value);
xaccSplitScrub (balance_split);
xaccTransCommitEdit (trans);
}
}
}
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("()");
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 convrate;
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);
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);
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);
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 ("()");
}
@ -836,9 +1197,9 @@ xaccAccountScrubKvp (Account *account)
Account *
xaccScrubUtilityGetOrMakeAccount (Account *root, gnc_commodity * currency,
const char *name_root)
const char *accname, GNCAccountType acctype,
gboolean placeholder)
{
char * accname;
Account * acc;
g_return_val_if_fail (root, NULL);
@ -850,9 +1211,6 @@ xaccScrubUtilityGetOrMakeAccount (Account *root, gnc_commodity * currency,
return NULL;
}
accname = g_strconcat (name_root, "-",
gnc_commodity_get_mnemonic (currency), NULL);
/* See if we've got one of these going already ... */
acc = gnc_account_lookup_by_name(root, accname);
@ -863,15 +1221,14 @@ xaccScrubUtilityGetOrMakeAccount (Account *root, gnc_commodity * currency,
xaccAccountBeginEdit (acc);
xaccAccountSetName (acc, accname);
xaccAccountSetCommodity (acc, currency);
xaccAccountSetType (acc, ACCT_TYPE_BANK);
xaccAccountSetType (acc, acctype);
xaccAccountSetPlaceholder (acc, placeholder);
/* Hang the account off the root. */
gnc_account_append_child (root, acc);
xaccAccountCommitEdit (acc);
}
g_free (accname);
return acc;
}

View File

@ -34,7 +34,8 @@
/* Utility to make account by name. Not for public use. */
Account * xaccScrubUtilityGetOrMakeAccount (Account *root,
gnc_commodity * currency, const char *name_root);
gnc_commodity * currency, const char *accname,
GNCAccountType acctype, gboolean placeholder);
#endif /* XACC_SCRUB_P_H */

View File

@ -1056,7 +1056,7 @@ xaccSplitConvertAmount (const Split *split, Account * account)
* rate and just return the 'other' split amount.
*/
txn = xaccSplitGetParent (split);
if (txn && gnc_numeric_zero_p (xaccTransGetImbalance (txn))) {
if (txn && xaccTransIsBalanced (txn)) {
const Split *osplit = xaccSplitGetOtherSplit (split);
if (osplit)
@ -1668,6 +1668,9 @@ xaccSplitGetOtherSplit (const Split *split)
int count, num_splits;
Split *other = NULL;
KvpValue *sva;
Account *trading_account = NULL;
Account *root_account = NULL;
gboolean trading_accts;
if (!split) return NULL;
trans = split->parent;
@ -1684,16 +1687,20 @@ xaccSplitGetOtherSplit (const Split *split)
return s1;
#endif
trading_accts = xaccTransUseTradingAccounts (trans);
num_splits = xaccTransCountSplits(trans);
count = num_splits;
sva = kvp_frame_get_slot (split->inst.kvp_data, "lot-split");
if (!sva && (2 != count)) return NULL;
if (!sva && !trading_accts && (2 != count)) return NULL;
for (i = 0; i < num_splits; i++) {
Split *s = xaccTransGetSplit(trans, i);
if (s == split) { --count; continue; }
if (kvp_frame_get_slot (s->inst.kvp_data, "lot-split"))
{ --count; continue; }
if (trading_accts &&
xaccAccountGetType(xaccSplitGetAccount(s)) == ACCT_TYPE_TRADING)
{ --count; continue; }
other = s;
}
return (1 == count) ? other : NULL;

View File

@ -649,6 +649,18 @@ xaccTransEqual(const Transaction *ta, const Transaction *tb,
return TRUE;
}
/********************************************************************\
xaccTransUseTradingAccounts
Returns true if the transaction should include trading account splits if
it involves more than one commodity.
\********************************************************************/
gboolean xaccTransUseTradingAccounts(const Transaction *trans)
{
return qof_book_use_trading_accounts(qof_instance_get_book (trans));
}
/********************************************************************\
\********************************************************************/
@ -665,7 +677,7 @@ xaccTransLookup (const GUID *guid, QofBook *book)
\********************************************************************/
gnc_numeric
xaccTransGetImbalance (const Transaction * trans)
xaccTransGetImbalanceValue (const Transaction * trans)
{
gnc_numeric imbal = gnc_numeric_zero();
if (!trans) return imbal;
@ -680,6 +692,91 @@ xaccTransGetImbalance (const Transaction * trans)
return imbal;
}
MonetaryList *
xaccTransGetImbalance (const Transaction * trans)
{
/* imbal_value is used if either (1) the transaction has a non currency
split or (2) all the splits are in the same currency. If there are
no non-currency splits and not all splits are in the same currency then
imbal_list is used to compute the imbalance. */
MonetaryList *imbal_list = NULL;
gnc_numeric imbal_value = gnc_numeric_zero();
gboolean trading_accts;
if (!trans) return imbal_list;
ENTER("(trans=%p)", trans);
trading_accts = xaccTransUseTradingAccounts (trans);
/* If using trading accounts and there is at least one split that is not
in the transaction currency or a split that has a price or exchange
rate other than 1, then compute the balance in each commodity in the
transaction. Otherwise (all splits are in the transaction's currency)
then compute the balance using the value fields.
Optimize for the common case of only one currency and a balanced
transaction. */
FOR_EACH_SPLIT(trans, {
gnc_commodity *commodity;
commodity = xaccAccountGetCommodity(xaccSplitGetAccount(s));
if (trading_accts &&
(imbal_list ||
! gnc_commodity_equiv(commodity, trans->common_currency) ||
! gnc_numeric_equal(xaccSplitGetAmount(s), xaccSplitGetValue(s)))) {
/* Need to use (or already are using) a list of imbalances in each of
the currencies used in the transaction. */
if (! imbal_list) {
/* All previous splits have been in the transaction's common
currency, so imbal_value is in this currency. */
imbal_list = gnc_monetary_list_add_value(imbal_list,
trans->common_currency,
imbal_value);
}
imbal_list = gnc_monetary_list_add_value(imbal_list, commodity,
xaccSplitGetAmount(s));
}
/* Add it to the value accumulator in case we need it. */
imbal_value = gnc_numeric_add(imbal_value, xaccSplitGetValue(s),
GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
} );
if (!imbal_list && !gnc_numeric_zero_p(imbal_value)) {
/* Not balanced and no list, create one. If we found multiple currencies
and no non-currency commodity then imbal_list will already exist and
we won't get here. */
imbal_list = gnc_monetary_list_add_value(imbal_list,
trans->common_currency,
imbal_value);
}
/* Delete all the zero entries from the list, perhaps leaving an
empty list */
imbal_list = gnc_monetary_list_delete_zeros(imbal_list);
LEAVE("(trans=%p), imbal=%p", trans, imbal_list);
return imbal_list;
}
gboolean
xaccTransIsBalanced (const Transaction *trans)
{
MonetaryList *imbal_list;
gboolean result;
if (! gnc_numeric_zero_p(xaccTransGetImbalanceValue(trans)))
return FALSE;
if (!xaccTransUseTradingAccounts (trans))
return TRUE;
imbal_list = xaccTransGetImbalance(trans);
result = imbal_list == NULL;
gnc_monetary_list_free(imbal_list);
return result;
}
gnc_numeric
xaccTransGetAccountValue (const Transaction *trans,
const Account *acc)
@ -716,22 +813,28 @@ xaccTransGetAccountConvRate(Transaction *txn, Account *acc)
GList *splits;
Split *s;
gboolean found_acc_match = FALSE;
gnc_commodity *acc_commod = xaccAccountGetCommodity(acc);
/* We need to compute the conversion rate into _this account_. So,
* find the first split into this account, compute the conversion
* rate (based on amount/value), and then return this conversion
* rate.
*/
if (gnc_commodity_equal(xaccAccountGetCommodity(acc),
xaccTransGetCurrency(txn)))
if (gnc_commodity_equal(acc_commod, xaccTransGetCurrency(txn)))
return gnc_numeric_create(1, 1);
for (splits = txn->splits; splits; splits = splits->next) {
Account *split_acc;
gnc_commodity *split_commod;
s = splits->data;
if (!xaccTransStillHasSplit(txn, s))
continue;
if (xaccSplitGetAccount (s) != acc)
split_acc = xaccSplitGetAccount (s);
split_commod = xaccAccountGetCommodity (split_acc);
if (! (split_acc == acc ||
gnc_commodity_equal (split_commod, acc_commod)))
continue;
found_acc_match = TRUE;
@ -1932,7 +2035,7 @@ static QofObject trans_object_def = {
static gboolean
trans_is_balanced_p (const Transaction *trans)
{
return trans ? gnc_numeric_zero_p(xaccTransGetImbalance(trans)) : FALSE;
return trans ? xaccTransIsBalanced(trans) : FALSE;
}
gboolean xaccTransRegister (void)
@ -1954,7 +2057,7 @@ gboolean xaccTransRegister (void)
{ TRANS_DATE_DUE, QOF_TYPE_DATE,
(QofAccessFunc)xaccTransRetDateDueTS, NULL },
{ TRANS_IMBALANCE, QOF_TYPE_NUMERIC,
(QofAccessFunc)xaccTransGetImbalance, NULL },
(QofAccessFunc)xaccTransGetImbalanceValue, NULL },
{ TRANS_NOTES, QOF_TYPE_STRING,
(QofAccessFunc)xaccTransGetNotes,
(QofSetterFunc)qofTransSetNotes },

View File

@ -244,6 +244,10 @@ guint gnc_book_count_transactions(QofBook *book);
@{
*/
/** Determine whether this transaction should use commodity trading accounts
*/
gboolean xaccTransUseTradingAccounts(const Transaction *trans);
/** Sorts the splits in a transaction, putting the debits first,
* followed by the credits.
*/
@ -342,15 +346,34 @@ gboolean xaccTransHasSplitsInStateByAccount (const Transaction *trans,
/** Set the commodity of this transaction. */
void xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr);
/** The xaccTransGetImbalance() method returns the total value of the
/** The xaccTransGetImbalanceValue() method returns the total value of the
* transaction. In a pure double-entry system, this imbalance
* should be exactly zero, and if it is not, something is broken.
* However, when double-entry semantics are not enforced, unbalanced
* transactions can sneak in, and this routine can be used to find
* out how much things are off by. The value returned is denominated
* in the currency that is returned by the xaccTransFindCommonCurrency()
* method. */
gnc_numeric xaccTransGetImbalance (const Transaction * trans);
* method.
*
* If the use of currency exchange accounts is enabled then the a
* a transaction must be balanced in each currency it uses to be considered
* to be balanced. The method xaccTransGetImbalance is used by most
* code to take this into consideration. This method is only used in a few
* places that want the transaction value even if currency exchange accounts
* are enabled. */
gnc_numeric xaccTransGetImbalanceValue (const Transaction * trans);
/** The xaccTransGetImbalance method returns a list giving the value of
* the transaction in each currency for which the balance is not zero.
* If the use of currency accounts is disabled, then this will be only
* the common currency for the transaction and xaccTransGetImbalance
* becomes equivalent to xaccTransGetImbalanceValue. Otherwise it will
* return a list containing the imbalance in each currency. */
MonetaryList *xaccTransGetImbalance (const Transaction * trans);
/** Returns true if the transaction is balanced according to the rules
* currently in effect. */
gboolean xaccTransIsBalanced(const Transaction * trans);
/** The xaccTransGetAccountValue() method returns the total value applied
* to a particular account. In some cases there may be multiple Splits

View File

@ -227,6 +227,8 @@ static KvpFrame * gnc_book_get_slots(QofBook *book) {
SET_ENUM("ACCT-TYPE-EQUITY");
SET_ENUM("ACCT-TYPE-RECEIVABLE");
SET_ENUM("ACCT-TYPE-PAYABLE");
SET_ENUM("ACCT-TYPE-ROOT");
SET_ENUM("ACCT-TYPE-TRADING");
SET_ENUM("NUM-ACCOUNT-TYPES");
SET_ENUM("ACCT-TYPE-CHECKING");
SET_ENUM("ACCT-TYPE-SAVINGS");

View File

@ -2416,4 +2416,61 @@ gnc_commodity_table_register (void)
return qof_object_register (&commodity_table_object_def);
}
/* *******************************************************************
* gnc_monetary methods
********************************************************************/
/** Add a gnc_monetary to the list */
MonetaryList *
gnc_monetary_list_add_monetary(MonetaryList *list, gnc_monetary add_mon)
{
MonetaryList *l = list, *tmp;
for (tmp = list; tmp; tmp = tmp->next) {
gnc_monetary *list_mon = tmp->data;
if (gnc_commodity_equiv(list_mon->commodity, add_mon.commodity)) {
list_mon->value = gnc_numeric_add(list_mon->value, add_mon.value,
GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
break;
}
}
/* See if we found an entry, and add one if not */
if (tmp == NULL) {
gnc_monetary *new_mon = g_new0(gnc_monetary, 1);
*new_mon = add_mon;
l = g_list_prepend(l, new_mon);
}
return l;
}
/** Delete all entries in the list that have zero value. Return list
pointer will be a null pointer if there are no non-zero entries **/
MonetaryList *
gnc_monetary_list_delete_zeros(MonetaryList *list)
{
MonetaryList *node, *next;
for (node = list; node; node = next) {
gnc_monetary *mon = node->data;
next = node->next;
if (gnc_numeric_zero_p(mon->value)) {
g_free(mon);
list = g_list_delete_link(list, node);
}
}
return list;
}
/** Free a MonetaryList and all the monetaries it points to */
void
gnc_monetary_list_free(MonetaryList *list)
{
MonetaryList *tmp;
for (tmp = list; tmp; tmp = tmp->next) {
g_free(tmp->data);
}
g_list_free(list);
}
/* ========================= END OF FILE ============================== */

View File

@ -973,6 +973,70 @@ void gnc_commodity_commit_edit (gnc_commodity *cm);
/** @} */
/** @name Monetary value, commodity identity and numeric value
@{
*/
struct _gnc_monetary
{
gnc_commodity *commodity;
gnc_numeric value;
};
typedef struct _gnc_monetary gnc_monetary;
/* A list of monetary values. This could be a hash table, but as currently
* used it rarely contains more than one or two different commodities so
* it doesn't seem worth the trouble.
*/
typedef GList MonetaryList;
/** @name Constructors
@{
Make a gnc_monetary from a gnc_commodity and gnc_numeric */
static inline
gnc_monetary gnc_monetary_create(gnc_commodity *commod, gnc_numeric val) {
gnc_monetary out;
out.commodity = commod;
out.value = val;
return out;
}
/** @} */
/** @name Accessors
@{
*/
static inline
gnc_commodity * gnc_monetary_commodity(gnc_monetary a) { return a.commodity; }
static inline
gnc_numeric gnc_monetary_value(gnc_monetary a) { return a.value; }
/** @} */
/** @name Manipulate MonetaryList lists
@{
*/
/** Add a gnc_monetary to the list */
MonetaryList *gnc_monetary_list_add_monetary(MonetaryList *list, gnc_monetary mon);
/** Add something to the list given a commodity and value */
static inline
MonetaryList *gnc_monetary_list_add_value(MonetaryList *list,
gnc_commodity *commod,
gnc_numeric value)
{
return gnc_monetary_list_add_monetary(list,
gnc_monetary_create(commod, value));
}
/** Delete all the zero-value entries from a list */
MonetaryList *gnc_monetary_list_delete_zeros(MonetaryList *list);
/** Free a monetary list and all the items it points to */
void gnc_monetary_list_free(MonetaryList *list);
/** @} */
/** @} */
#endif /* GNC_COMMODITY_H */
/** @} */

View File

@ -169,7 +169,9 @@ gnc_account_commodity_from_type (AccountWindow * aw, gboolean update)
{
dialog_commodity_mode new_mode;
if ((aw->type == ACCT_TYPE_STOCK) || (aw->type == ACCT_TYPE_MUTUAL))
if (aw->type == ACCT_TYPE_TRADING)
new_mode = DIAG_COMM_ALL;
else if ((aw->type == ACCT_TYPE_STOCK) || (aw->type == ACCT_TYPE_MUTUAL))
new_mode = DIAG_COMM_NON_CURRENCY;
else
new_mode = DIAG_COMM_CURRENCY;
@ -1026,7 +1028,8 @@ gnc_account_type_changed_cb (GtkTreeSelection *selection, gpointer data)
sensitive = (aw->type != ACCT_TYPE_EQUITY &&
aw->type != ACCT_TYPE_CURRENCY &&
aw->type != ACCT_TYPE_STOCK &&
aw->type != ACCT_TYPE_MUTUAL);
aw->type != ACCT_TYPE_MUTUAL &&
aw->type != ACCT_TYPE_TRADING);
}
gtk_widget_set_sensitive (aw->opening_balance_page, sensitive);

View File

@ -2146,6 +2146,19 @@ gboolean gnc_xfer_dialog_run_exchange_dialog(
g_return_val_if_fail(txn_cur, TRUE);
if (xaccTransUseTradingAccounts (txn)) {
/* If we're using commodity trading accounts then "amount" is
really the split's amount and it's in xfer_com. We need an
exchange rate that will convert this amount into a value in
the transaction currency. */
if (gnc_commodity_equal(xfer_com, txn_cur)) {
/* Transaction is in the same currency as the split, exchange
rate is 1. */
*exch_rate = gnc_numeric_create(1, 1);
return FALSE;
}
swap_amounts = TRUE;
/* We know that "amount" is always in the reg_com currency.
* Unfortunately it is possible that neither xfer_com or txn_cur are
* the same as reg_com, in which case we need to convert to the txn
@ -2153,7 +2166,7 @@ gboolean gnc_xfer_dialog_run_exchange_dialog(
* need to flip-flop the commodities and the exchange rates.
*/
if (gnc_commodity_equal(reg_com, txn_cur)) {
} else if (gnc_commodity_equal(reg_com, txn_cur)) {
/* we're working in the txn currency. Great. Nothing to do! */
swap_amounts = FALSE;

View File

@ -293,6 +293,12 @@ gnc_ui_accounts_recurse (Account *parent, GList **currency_list,
case ACCT_TYPE_EQUITY:
/* no-op, see comments at top about summing assets */
break;
/**
* @fixme I don't know if this is right or if trading accounts should be
* treated like income and expense accounts.
**/
case ACCT_TYPE_TRADING:
break;
case ACCT_TYPE_CURRENCY:
default:
break;

View File

@ -706,7 +706,8 @@ balance_cell_data_func (GtkTreeViewColumn *tree_column,
string = xaccPrintAmount (balance, print_info);
}
if (xaccAccountGetType(account) == ACCT_TYPE_EQUITY) {
if (xaccAccountGetType(account) == ACCT_TYPE_EQUITY ||
xaccAccountGetType(account) == ACCT_TYPE_TRADING) {
allow_value = FALSE;
string=_("zero");
} else {

View File

@ -122,7 +122,10 @@ gboolean
gnc_import_TransInfo_is_balanced (const GNCImportTransInfo *info)
{
g_assert (info);
if(gnc_numeric_zero_p(xaccTransGetImbalance(gnc_import_TransInfo_get_trans(info))))
/* Assume that the importer won't create a transaction that involves two or more
currencies and no non-currency commodity. In that case can use the simpler
value imbalance check. */
if(gnc_numeric_zero_p(xaccTransGetImbalanceValue(gnc_import_TransInfo_get_trans(info))))
{
return TRUE;
}
@ -860,9 +863,11 @@ gnc_import_process_trans_item (GncImportMatchMap *matchmap,
(gnc_import_TransInfo_get_trans (trans_info)));*/
{
/* This is a quick workaround for the bug described in
http://gnucash.org/pipermail/gnucash-devel/2003-August/009982.html */
http://gnucash.org/pipermail/gnucash-devel/2003-August/009982.html
Assume that importers won't create transactions involving two or more
currencies so we can use xaccTransGetImbalanceValue. */
gnc_numeric v =
gnc_numeric_neg (xaccTransGetImbalance
gnc_numeric_neg (xaccTransGetImbalanceValue
(gnc_import_TransInfo_get_trans (trans_info)));
xaccSplitSetValue (split, v);
xaccSplitSetAmount (split, v);

View File

@ -605,10 +605,12 @@ refresh_model_row (GNCImportMainMatcher *gui,
}
else
{
/* Assume that importers won't create transactions in two or more
currencies so we can use xaccTransGetImbalanceValue */
imbalance =
g_strdup
(xaccPrintAmount
(gnc_numeric_neg(xaccTransGetImbalance
(gnc_numeric_neg(xaccTransGetImbalanceValue
(gnc_import_TransInfo_get_trans(info) )),
gnc_commodity_print_info
(xaccTransGetCurrency(gnc_import_TransInfo_get_trans (info)),

View File

@ -151,7 +151,10 @@ downloaded_transaction_append(GNCImportMatchPicker * matcher,
gtk_list_store_set(store, &iter, DOWNLOADED_COL_MEMO, ro_text, -1);
/*Imbalance*/
ro_text = xaccPrintAmount(xaccTransGetImbalance(trans),
/* Assume that the importer won't create a transaction that involves two or more
currencies and no non-currency commodity. In that case can use the simpler
value imbalance check. */
ro_text = xaccPrintAmount(xaccTransGetImbalanceValue(trans),
gnc_default_print_info(TRUE));
gtk_list_store_set(store, &iter, DOWNLOADED_COL_BALANCED, ro_text, -1);

View File

@ -39,6 +39,7 @@
#include <string.h>
#include <glib.h>
#include <libguile.h>
#include "qof.h"
#include "qofevent-p.h"
@ -439,6 +440,48 @@ qof_book_get_counter (const QofBook *book, const char *counter_name)
return counter;
}
static char *get_scm_string(const char *str_name)
{
SCM scm_string = scm_c_eval_string (str_name);
if (! SCM_STRINGP(scm_string))
return NULL;
return g_strdup(SCM_STRING_CHARS(scm_string));
}
/* Determine whether this book uses trading accounts */
gboolean qof_book_use_trading_accounts (const QofBook *book)
{
static char *options_name = NULL;
static char *acct_section = NULL;
static char *trading_opt = NULL;
const char *opt;
kvp_value *kvp_val;
if (options_name == NULL)
{
options_name = get_scm_string ("(car gnc:*kvp-option-path*)");
acct_section = get_scm_string ("gnc:*book-label*");
trading_opt = get_scm_string ("gnc:*trading-accounts*");
if (options_name == NULL || acct_section == NULL || trading_opt == NULL)
{
PWARN ("Unable to find trading account preference");
}
}
kvp_val = kvp_frame_get_slot_path (qof_book_get_slots (book), options_name, acct_section,
trading_opt, NULL);
if (kvp_val == NULL)
return FALSE;
opt = kvp_value_get_string (kvp_val);
if (opt && opt[0] == 't' && opt[1] == 0)
return TRUE;
return FALSE;
}
/* QofObject function implementation and registration */
gboolean qof_book_register (void)
{

View File

@ -213,6 +213,9 @@ void qof_book_set_data_fin (QofBook *book, const gchar *key, gpointer data,
/** Retrieves arbitrary pointers to structs stored by qof_book_set_data. */
gpointer qof_book_get_data (const QofBook *book, const gchar *key);
/** Returns flag indicating whether this book uses trading accounts */
gboolean qof_book_use_trading_accounts (const QofBook *book);
/** Is the book shutting down? */
gboolean qof_book_shutting_down (const QofBook *book);

View File

@ -281,6 +281,9 @@ gnc_get_reg_type (Account *leader, GNCLedgerDisplayType ld_type)
case ACCT_TYPE_CURRENCY:
return CURRENCY_REGISTER;
case ACCT_TYPE_TRADING:
return TRADING_REGISTER;
default:
PERR ("unknown account type %d\n", account_type);
@ -327,6 +330,7 @@ gnc_get_reg_type (Account *leader, GNCLedgerDisplayType ld_type)
break;
case ACCT_TYPE_EQUITY:
case ACCT_TYPE_TRADING:
reg_type = GENERAL_LEDGER;
break;

View File

@ -59,12 +59,40 @@ gnc_split_register_balance_trans (SplitRegister *reg, Transaction *trans)
Split *split;
Split *other_split;
gboolean two_accounts;
gboolean multi_currency;
imbalance = xaccTransGetImbalance (trans);
if (gnc_numeric_zero_p (imbalance))
return FALSE;
if (xaccTransIsBalanced (trans))
return FALSE;
if (xaccTransUseTradingAccounts (trans)) {
MonetaryList *imbal_list;
gnc_monetary *imbal_mon;
imbal_list = xaccTransGetImbalance (trans);
/* See if the imbalance is only in the transaction's currency */
if (!imbal_list)
/* Value imbalance, but not commodity imbalance. This shouldn't
be something that scrubbing can cause to happen. Perhaps someone
entered invalid splits. */
multi_currency = TRUE;
else {
imbal_mon = imbal_list->data;
if (!imbal_list->next &&
gnc_commodity_equiv(gnc_monetary_commodity(*imbal_mon),
xaccTransGetCurrency(trans)))
multi_currency = FALSE;
else
multi_currency = TRUE;
}
/* We're done with the imbalance list, the real work will be done
by xaccTransScrubImbalance which will get it again. */
gnc_monetary_list_free(imbal_list);
}
else
multi_currency = FALSE;
split = xaccTransGetSplit (trans, 0);
other_split = xaccSplitGetOtherSplit (split);
@ -75,7 +103,7 @@ gnc_split_register_balance_trans (SplitRegister *reg, Transaction *trans)
if (split) other_split = xaccSplitGetOtherSplit (split);
else split = xaccTransGetSplit (trans, 0);
}
if (other_split == NULL)
if (other_split == NULL || multi_currency)
{
two_accounts = FALSE;
other_account = NULL;
@ -107,7 +135,7 @@ gnc_split_register_balance_trans (SplitRegister *reg, Transaction *trans)
radio_list = g_list_append (radio_list,
_("Let GnuCash _add an adjusting split"));
if (reg->type < NUM_SINGLE_REGISTER_TYPES)
if (reg->type < NUM_SINGLE_REGISTER_TYPES && !multi_currency)
{
radio_list = g_list_append (radio_list,
_("Adjust current account _split total"));

View File

@ -92,6 +92,7 @@ gnc_split_register_set_cells (SplitRegister *reg, TableLayout *layout)
case INCOME_REGISTER:
case EXPENSE_REGISTER:
case EQUITY_REGISTER:
case TRADING_REGISTER:
{
curs = gnc_table_layout_get_cursor (layout,
CURSOR_SINGLE_LEDGER);
@ -458,6 +459,7 @@ gnc_split_register_layout_add_cursors (SplitRegister *reg,
case INCOME_REGISTER:
case EXPENSE_REGISTER:
case EQUITY_REGISTER:
case TRADING_REGISTER:
num_cols = 9;
break;

View File

@ -411,9 +411,66 @@ gnc_split_register_save_amount_values (SRSaveData *sd, SplitRegister *reg)
{
Account *acc;
gnc_numeric new_amount, convrate, amtconv, value;
gnc_commodity *curr, *reg_com, *xfer_com;
Account *xfer_acc;
new_amount = gnc_split_register_debcred_cell_value (reg);
acc = gnc_split_register_get_default_account (reg);
xfer_acc = xaccSplitGetAccount (sd->split);
xfer_com = xaccAccountGetCommodity (xfer_acc);
reg_com = xaccAccountGetCommodity (acc);
curr = xaccTransGetCurrency (sd->trans);
/* First, compute the conversion rate to convert the value to the
* amount.
*/
amtconv = convrate = gnc_split_register_get_rate_cell (reg, RATE_CELL);
if (gnc_split_register_needs_conv_rate (reg, sd->trans, acc)) {
/* If we are in an expanded register and the xfer_acc->comm !=
* reg_acc->comm then we need to compute the convrate here.
* Otherwise, we _can_ use the rate_cell!
*/
if (sd->reg_expanded && ! gnc_commodity_equal (reg_com, xfer_com))
amtconv = xaccTransGetAccountConvRate(sd->trans, acc);
}
if (xaccTransUseTradingAccounts (sd->trans)) {
/* Using currency accounts, the amount is probably really the
amount and not the value. */
gboolean is_amount;
if (reg->type == STOCK_REGISTER ||
reg->type == CURRENCY_REGISTER ||
reg->type == PORTFOLIO_LEDGER) {
if (xaccAccountIsPriced(xfer_acc) ||
!gnc_commodity_is_iso(xaccAccountGetCommodity(xfer_acc)))
is_amount = FALSE;
else
is_amount = TRUE;
}
else {
is_amount = TRUE;
}
if (is_amount) {
xaccSplitSetAmount(sd->split, new_amount);
if (gnc_split_register_split_needs_amount (reg, sd->split)) {
value = gnc_numeric_div(new_amount, amtconv,
gnc_commodity_get_fraction(curr),
GNC_RND_ROUND);
xaccSplitSetValue(sd->split, value);
}
else
xaccSplitSetValue(sd->split, new_amount);
}
else {
xaccSplitSetValue(sd->split, new_amount);
}
return;
}
/* How to interpret new_amount depends on our view of this
* transaction. If we're sitting in an account with the same
* commodity as the transaction, then we can set the Value and then
@ -422,34 +479,12 @@ gnc_split_register_save_amount_values (SRSaveData *sd, SplitRegister *reg)
* 'value' by dividing by the convrate in order to set the value.
*/
/* First, compute the conversion rate to convert the value to the
* amount.
*/
convrate = gnc_split_register_get_rate_cell (reg, RATE_CELL);
/* Now compute/set the split value. Amount is in the register
* currency but we need to convert to the txn currency.
*/
acc = gnc_split_register_get_default_account (reg);
if (gnc_split_register_needs_conv_rate (reg, sd->trans, acc)) {
gnc_commodity *curr, *reg_com, *xfer_com;
Account *xfer_acc;
xfer_acc = xaccSplitGetAccount (sd->split);
xfer_com = xaccAccountGetCommodity (xfer_acc);
reg_com = xaccAccountGetCommodity (acc);
/* If we are in an expanded register and the xfer_acc->comm !=
* reg_acc->comm then we need to compute the convrate here.
* Otherwise, we _can_ use the rate_cell!
*/
if (sd->reg_expanded && ! gnc_commodity_equal (reg_com, xfer_com))
amtconv = xaccTransGetAccountConvRate(sd->trans, acc);
else
amtconv = convrate;
/* convert the amount to the Value ... */
curr = xaccTransGetCurrency (sd->trans);
value = gnc_numeric_div (new_amount, amtconv,
gnc_commodity_get_fraction (curr),
GNC_RND_ROUND);

View File

@ -163,6 +163,11 @@ gnc_split_register_use_security_cells (SplitRegister *reg,
if (!account)
return TRUE;
if (xaccTransUseTradingAccounts (xaccSplitGetParent (split))) {
if (!gnc_commodity_is_iso(xaccAccountGetCommodity(account)))
return TRUE;
}
return xaccAccountIsPriced(account);
}
@ -687,7 +692,7 @@ gnc_split_register_get_debcred_bg_color (VirtualLocation virt_loc,
trans = gnc_split_register_get_trans (reg, virt_loc.vcell_loc);
if (trans)
*hatching = !gnc_numeric_zero_p (xaccTransGetImbalance (trans));
*hatching = !xaccTransIsBalanced (trans);
else
*hatching = FALSE;
}
@ -1504,6 +1509,7 @@ gnc_split_reg_has_rate_cell (SplitRegisterType type)
case INCOME_REGISTER:
case EXPENSE_REGISTER:
case EQUITY_REGISTER:
case TRADING_REGISTER:
case GENERAL_LEDGER:
case INCOME_LEDGER:
case PORTFOLIO_LEDGER:
@ -1569,10 +1575,43 @@ gnc_split_register_get_debcred_entry (VirtualLocation virt_loc,
gnc_numeric imbalance;
Account *acc;
imbalance = xaccTransGetImbalance (trans);
imbalance = xaccTransGetImbalanceValue (trans);
if (gnc_numeric_zero_p (imbalance))
return NULL;
if (xaccTransUseTradingAccounts (trans)) {
MonetaryList *imbal_list;
gnc_monetary *imbal_mon;
imbal_list = xaccTransGetImbalance (trans);
if (!imbal_list) {
/* No commodity imbalance, there shouldn't be a value imablance. */
return NULL;
}
if (imbal_list->next) {
/* Multiple currency imbalance. */
gnc_monetary_list_free(imbal_list);
return NULL;
}
imbal_mon = imbal_list->data;
if (!gnc_commodity_equal(gnc_monetary_commodity(*imbal_mon), currency)) {
/* Imbalance is in wrong currency */
gnc_monetary_list_free(imbal_list);
return NULL;
}
if (!gnc_numeric_equal (gnc_monetary_value(*imbal_mon), imbalance)) {
/* Value and commodity imbalances differ */
gnc_monetary_list_free(imbal_list);
return NULL;
}
/* Done with the imbalance list */
gnc_monetary_list_free(imbal_list);
}
imbalance = gnc_numeric_neg (imbalance);
@ -1604,33 +1643,74 @@ gnc_split_register_get_debcred_entry (VirtualLocation virt_loc,
{
gnc_numeric amount;
gnc_commodity *split_commodity;
GNCPrintAmountInfo print_info;
Account *account;
gnc_commodity * commodity;
account = gnc_split_register_get_default_account (reg);
commodity = xaccAccountGetCommodity (account);
split_commodity = xaccAccountGetCommodity(xaccSplitGetAccount(split));
/* If this account is not a stock/mutual/currency account, and
* currency != the account commodity, then use the SplitAmount
* instead of the SplitValue.
*/
switch (reg->type) {
case STOCK_REGISTER:
case CURRENCY_REGISTER:
amount = xaccSplitGetValue (split);
print_info = gnc_commodity_print_info (currency, FALSE);
break;
default:
{
Account *account;
gnc_commodity * commodity;
account = gnc_split_register_get_default_account (reg);
commodity = xaccAccountGetCommodity (account);
if (commodity && !gnc_commodity_equal (commodity, currency))
/* Convert this to the "local" value */
amount = xaccSplitConvertAmount(split, account);
else
amount = xaccSplitGetValue (split);
print_info = gnc_account_print_info (account, FALSE);
if (xaccTransUseTradingAccounts (trans)) {
gboolean use_symbol, is_current;
is_current = virt_cell_loc_equal (reg->table->current_cursor_loc.vcell_loc,
virt_loc.vcell_loc);
if (reg->type == STOCK_REGISTER ||
reg->type == CURRENCY_REGISTER ||
reg->type == PORTFOLIO_LEDGER) {
gnc_commodity *amount_commodity;
/* security register. If this split has price and shares columns,
use the value, otherwise use the amount. */
if (gnc_split_register_use_security_cells(reg, virt_loc)) {
amount = xaccSplitGetValue(split);
amount_commodity = currency;
}
else {
amount = xaccSplitGetAmount(split);
amount_commodity = split_commodity;
}
/* Show the currency if it is not the default currency */
if (is_current ||
gnc_commodity_equiv(amount_commodity, gnc_default_currency()))
use_symbol = FALSE;
else
use_symbol = TRUE;
print_info = gnc_commodity_print_info(amount_commodity, use_symbol);
}
else {
/* non-security register, always use the split amount. */
amount = xaccSplitGetAmount(split);
if (is_current ||
gnc_commodity_equiv(split_commodity, commodity))
use_symbol = FALSE;
else
use_symbol = TRUE;
print_info = gnc_commodity_print_info(split_commodity, use_symbol);
}
}
else {
/* If this account is not a stock/mutual/currency account, and
* currency != the account commodity, then use the SplitAmount
* instead of the SplitValue.
*/
switch (reg->type) {
case STOCK_REGISTER:
case CURRENCY_REGISTER:
amount = xaccSplitGetValue (split);
print_info = gnc_commodity_print_info (currency, FALSE);
break;
default:
{
if (commodity && !gnc_commodity_equal (commodity, currency))
/* Convert this to the "local" value */
amount = xaccSplitConvertAmount(split, account);
else
amount = xaccSplitGetValue (split);
print_info = gnc_account_print_info (account, FALSE);
}
}
}

View File

@ -1782,6 +1782,16 @@ gnc_split_register_auto_calc (SplitRegister *reg, Split *split)
if (!price_changed && !amount_changed && !shares_changed)
return TRUE;
/* If we are using commodity trading accounts then the value may
not really be the value. Punt if so. */
if (xaccTransUseTradingAccounts (xaccSplitGetParent (split))) {
gnc_commodity *acc_commodity;
acc_commodity = xaccAccountGetCommodity (account);
if (! (xaccAccountIsPriced (account) ||
!gnc_commodity_is_iso (acc_commodity)))
return TRUE;
}
if (shares_changed)
{
cell = (PriceCell *) gnc_table_layout_get_cell (reg->table->layout,
@ -2053,6 +2063,8 @@ gnc_split_register_type_to_account_type (SplitRegisterType sr_type)
return ACCT_TYPE_STOCK;
case CURRENCY_REGISTER:
return ACCT_TYPE_CURRENCY;
case TRADING_REGISTER:
return ACCT_TYPE_TRADING;
case GENERAL_LEDGER:
return ACCT_TYPE_NONE;
case EQUITY_REGISTER:
@ -2253,6 +2265,7 @@ gnc_split_register_config_action (SplitRegister *reg)
gnc_combo_cell_add_menu_item (cell, _("Paycheck"));
break;
case EXPENSE_REGISTER:
case TRADING_REGISTER:
gnc_combo_cell_add_menu_item (cell, _("Increase"));
gnc_combo_cell_add_menu_item (cell, _("Decrease"));
gnc_combo_cell_add_menu_item (cell, _("Buy"));

View File

@ -163,6 +163,7 @@ typedef enum
CURRENCY_REGISTER,
RECEIVABLE_REGISTER,
PAYABLE_REGISTER,
TRADING_REGISTER,
NUM_SINGLE_REGISTER_TYPES,
GENERAL_LEDGER = NUM_SINGLE_REGISTER_TYPES,

View File

@ -96,7 +96,8 @@
ACCT-TYPE-CREDITLINE))
(cons ACCT-TYPE-EQUITY (list ACCT-TYPE-EQUITY))
(cons ACCT-TYPE-INCOME (list ACCT-TYPE-INCOME))
(cons ACCT-TYPE-EXPENSE (list ACCT-TYPE-EXPENSE)))))
(cons ACCT-TYPE-EXPENSE (list ACCT-TYPE-EXPENSE))
(cons ACCT-TYPE-TRADING (list ACCT-TYPE-TRADING)))))
;; Returns the name of the account type as a string, and in its plural
;; form (as opposed to xaccAccountGetTypeStr which gives the
@ -120,7 +121,8 @@
(cons ACCT-TYPE-MONEYMRKT (_ "Money Market"))
(cons ACCT-TYPE-RECEIVABLE (_ "Accounts Receivable"))
(cons ACCT-TYPE-PAYABLE (_ "Accounts Payable"))
(cons ACCT-TYPE-CREDITLINE (_ "Credit Lines")))
(cons ACCT-TYPE-CREDITLINE (_ "Credit Lines"))
(cons ACCT-TYPE-TRADING (_ "Trading Accounts")))
type))
;; Get the list of all different commodities that are used within the

View File

@ -411,6 +411,7 @@
(lambda (s)
(if (and (not (or (split-account-type? s ACCT-TYPE-EXPENSE)
(split-account-type? s ACCT-TYPE-INCOME)
(split-account-type? s ACCT-TYPE-TRADING)
(split-account-type? s ACCT-TYPE-ROOT)))
(not (same-account? current (xaccSplitGetAccount s))))
(begin

View File

@ -189,7 +189,8 @@
ACCT-TYPE-ASSET ACCT-TYPE-LIABILITY
ACCT-TYPE-STOCK ACCT-TYPE-MUTUAL ACCT-TYPE-CURRENCY
ACCT-TYPE-PAYABLE ACCT-TYPE-RECEIVABLE
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE)
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE
ACCT-TYPE-TRADING)
(gnc-account-get-descendants-sorted (gnc-get-current-root-account))))
#f #t))
(gnc:options-add-account-levels!
@ -358,6 +359,8 @@
(assoc-ref split-up-accounts ACCT-TYPE-EXPENSE)))
(equity-accounts
(assoc-ref split-up-accounts ACCT-TYPE-EQUITY))
(trading-accounts
(assoc-ref split-up-accounts ACCT-TYPE-TRADING))
(doc (gnc:make-html-document))
;; this can occasionally put extra (blank) columns in our
@ -441,6 +444,8 @@
(equity-balance #f)
(neg-retained-earnings #f) ;; credit, income - expenses, < 0
(retained-earnings #f)
(neg-trading-balance #f)
(trading-balance #f)
(unrealized-gain-collector #f)
(total-equity-balance #f)
(liability-plus-equity #f)
@ -501,6 +506,13 @@
(retained-earnings 'minusmerge
neg-retained-earnings
#f)
(set! neg-trading-balance
(gnc:accountlist-get-comm-balance-at-date
trading-accounts date-tp))
(set! trading-balance (gnc:make-commodity-collector))
(trading-balance 'minusmerge
neg-trading-balance
#f)
(gnc:report-percent-done 14)
;; sum any unrealized gains
;;
@ -537,6 +549,9 @@
(total-equity-balance 'merge
unrealized-gain-collector
#f)
(total-equity-balance 'merge
trading-balance
#f)
(gnc:report-percent-done 18)
(set! liability-plus-equity (gnc:make-commodity-collector))
(liability-plus-equity 'merge
@ -649,6 +664,12 @@
(_ "Retained Earnings")
(_ "Retained Losses")
retained-earnings))
(and (not (gnc-commodity-collector-allzero?
trading-balance))
(add-subtotal-line right-table
(_ "Trading Gains")
(_ "Trading Losses")
trading-balance))
(and (not (gnc-commodity-collector-allzero?
unrealized-gain-collector))
(add-subtotal-line right-table

View File

@ -133,7 +133,8 @@
ACCT-TYPE-ASSET ACCT-TYPE-LIABILITY
ACCT-TYPE-STOCK ACCT-TYPE-MUTUAL ACCT-TYPE-CURRENCY
ACCT-TYPE-PAYABLE ACCT-TYPE-RECEIVABLE
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE)
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE
ACCT-TYPE-TRADING)
(gnc-account-get-descendants-sorted (gnc-get-current-root-account))))
#f #t))
@ -245,7 +246,8 @@
(assoc-ref split-up-accounts ACCT-TYPE-LIABILITY))
(income-expense-accounts
(append (assoc-ref split-up-accounts ACCT-TYPE-INCOME)
(assoc-ref split-up-accounts ACCT-TYPE-EXPENSE)))
(assoc-ref split-up-accounts ACCT-TYPE-EXPENSE)
(assoc-ref split-up-accounts ACCT-TYPE-TRADING)))
(equity-accounts
(assoc-ref split-up-accounts ACCT-TYPE-EQUITY))

View File

@ -612,7 +612,8 @@
ACCT-TYPE-ASSET ACCT-TYPE-LIABILITY
ACCT-TYPE-STOCK ACCT-TYPE-MUTUAL ACCT-TYPE-CURRENCY
ACCT-TYPE-PAYABLE ACCT-TYPE-RECEIVABLE
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE)
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE
ACCT-TYPE-TRADING)
(gnc-account-get-descendants-sorted (gnc-get-current-root-account))))
#f #t))

View File

@ -184,7 +184,8 @@
ACCT-TYPE-ASSET ACCT-TYPE-LIABILITY
ACCT-TYPE-STOCK ACCT-TYPE-MUTUAL ACCT-TYPE-CURRENCY
ACCT-TYPE-PAYABLE ACCT-TYPE-RECEIVABLE
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE)
ACCT-TYPE-EQUITY ACCT-TYPE-INCOME ACCT-TYPE-EXPENSE
ACCT-TYPE-TRADING)
(gnc-account-get-descendants-sorted (gnc-get-current-root-account))))
#f #t))
(gnc:options-add-account-levels!