r18429 - gnucash/trunk/src - Bug #537476: Implement currency trading accounts optionally, to be enabled per-book.

Christian Stimming cstim at code.gnucash.org
Fri Nov 20 15:11:04 EST 2009


Author: cstim
Date: 2009-11-20 15:11:03 -0500 (Fri, 20 Nov 2009)
New Revision: 18429
Trac: http://svn.gnucash.org/trac/changeset/18429

Modified:
   gnucash/trunk/src/app-utils/prefs.scm
   gnucash/trunk/src/business/business-ledger/gncEntryLedgerLoad.c
   gnucash/trunk/src/business/business-utils/business-prefs.scm
   gnucash/trunk/src/business/business-utils/business-utils.scm
   gnucash/trunk/src/engine/Account.c
   gnucash/trunk/src/engine/Account.h
   gnucash/trunk/src/engine/Period.c
   gnucash/trunk/src/engine/Period.h
   gnucash/trunk/src/engine/Scrub.c
   gnucash/trunk/src/engine/ScrubP.h
   gnucash/trunk/src/engine/Split.c
   gnucash/trunk/src/engine/Transaction.c
   gnucash/trunk/src/engine/Transaction.h
   gnucash/trunk/src/engine/engine.i
   gnucash/trunk/src/engine/gnc-commodity.c
   gnucash/trunk/src/engine/gnc-commodity.h
   gnucash/trunk/src/gnome-utils/dialog-account.c
   gnucash/trunk/src/gnome-utils/dialog-transfer.c
   gnucash/trunk/src/gnome-utils/window-main-summarybar.c
   gnucash/trunk/src/gnome/druid-hierarchy.c
   gnucash/trunk/src/import-export/import-backend.c
   gnucash/trunk/src/import-export/import-main-matcher.c
   gnucash/trunk/src/import-export/import-match-picker.c
   gnucash/trunk/src/libqof/qof/qofbook.c
   gnucash/trunk/src/libqof/qof/qofbook.h
   gnucash/trunk/src/register/ledger-core/gnc-ledger-display.c
   gnucash/trunk/src/register/ledger-core/split-register-control.c
   gnucash/trunk/src/register/ledger-core/split-register-layout.c
   gnucash/trunk/src/register/ledger-core/split-register-model-save.c
   gnucash/trunk/src/register/ledger-core/split-register-model.c
   gnucash/trunk/src/register/ledger-core/split-register.c
   gnucash/trunk/src/register/ledger-core/split-register.h
   gnucash/trunk/src/report/report-system/report-utilities.scm
   gnucash/trunk/src/report/standard-reports/advanced-portfolio.scm
   gnucash/trunk/src/report/standard-reports/balance-sheet.scm
   gnucash/trunk/src/report/standard-reports/equity-statement.scm
   gnucash/trunk/src/report/standard-reports/transaction.scm
   gnucash/trunk/src/report/standard-reports/trial-balance.scm
Log:
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.

Modified: gnucash/trunk/src/app-utils/prefs.scm
===================================================================
--- gnucash/trunk/src/app-utils/prefs.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/app-utils/prefs.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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)

Modified: gnucash/trunk/src/business/business-ledger/gncEntryLedgerLoad.c
===================================================================
--- gnucash/trunk/src/business/business-ledger/gncEntryLedgerLoad.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/business/business-ledger/gncEntryLedgerLoad.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -134,7 +134,7 @@
   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 @@
   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;
   }

Modified: gnucash/trunk/src/business/business-utils/business-prefs.scm
===================================================================
--- gnucash/trunk/src/business/business-utils/business-prefs.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/business/business-utils/business-prefs.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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)

Modified: gnucash/trunk/src/business/business-utils/business-utils.scm
===================================================================
--- gnucash/trunk/src/business/business-utils/business-utils.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/business/business-utils/business-utils.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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")

Modified: gnucash/trunk/src/engine/Account.c
===================================================================
--- gnucash/trunk/src/engine/Account.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Account.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -1919,7 +1919,7 @@
      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 @@
     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 @@
   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 @@
   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 @@
     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;

Modified: gnucash/trunk/src/engine/Account.h
===================================================================
--- gnucash/trunk/src/engine/Account.h	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Account.h	2009-11-20 20:11:03 UTC (rev 18429)
@@ -140,18 +140,24 @@
   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;
 

Modified: gnucash/trunk/src/engine/Period.c
===================================================================
--- gnucash/trunk/src/engine/Period.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Period.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -689,7 +689,7 @@
       /* 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);

Modified: gnucash/trunk/src/engine/Period.h
===================================================================
--- gnucash/trunk/src/engine/Period.h	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Period.h	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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 

Modified: gnucash/trunk/src/engine/Scrub.c
===================================================================
--- gnucash/trunk/src/engine/Scrub.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Scrub.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -72,6 +72,7 @@
 TransScrubOrphansFast (Transaction *trans, Account *root)
 {
   GList *node;
+  gchar *accname;
 
   if (!trans) return;
   g_return_if_fail (root);
@@ -85,7 +86,12 @@
 
     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 @@
   }
 }
 
-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 @@
        {
           /* 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 @@
     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) 
+  {
+    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;
+    }
+  }
+  
+  /* 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) 
   {
-    const gnc_commodity *currency;
-    const gnc_commodity *commodity;
-    gnc_numeric old_value, new_value;
+    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);
+}
 
-    xaccTransBeginEdit (trans);
+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);
+}
 
-    currency = xaccTransGetCurrency (trans);
-    old_value = xaccSplitGetValue (balance_split);
+void
+xaccTransScrubImbalance (Transaction *trans, Account *root,
+                         Account *account)
+{
+  const gnc_commodity *currency;
 
-    /* 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);
+  if (!trans) return;
 
-    xaccSplitSetValue (balance_split, new_value);
+  ENTER ("()");
 
-    commodity = xaccAccountGetCommodity (account);
-    if (gnc_commodity_equiv (currency, commodity))
+  /* 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))
     {
-      xaccSplitSetAmount (balance_split, new_value);
+      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 @@
 
 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 @@
     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 @@
     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;
 }
 

Modified: gnucash/trunk/src/engine/ScrubP.h
===================================================================
--- gnucash/trunk/src/engine/ScrubP.h	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/ScrubP.h	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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 */

Modified: gnucash/trunk/src/engine/Split.c
===================================================================
--- gnucash/trunk/src/engine/Split.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Split.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -1056,7 +1056,7 @@
    * 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 @@
   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 @@
   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;

Modified: gnucash/trunk/src/engine/Transaction.c
===================================================================
--- gnucash/trunk/src/engine/Transaction.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Transaction.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -650,8 +650,20 @@
 }
 
 /********************************************************************\
+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));
+}
+
+/********************************************************************\
+\********************************************************************/
+
 Transaction *
 xaccTransLookup (const GUID *guid, QofBook *book)
 {
@@ -665,7 +677,7 @@
 \********************************************************************/
 
 gnc_numeric
-xaccTransGetImbalance (const Transaction * trans)
+xaccTransGetImbalanceValue (const Transaction * trans)
 {
   gnc_numeric imbal = gnc_numeric_zero();
   if (!trans) return imbal;
@@ -680,6 +692,91 @@
   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 @@
   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 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 @@
     { 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 },

Modified: gnucash/trunk/src/engine/Transaction.h
===================================================================
--- gnucash/trunk/src/engine/Transaction.h	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/Transaction.h	2009-11-20 20:11:03 UTC (rev 18429)
@@ -244,6 +244,10 @@
  @{
 */
 
+/** 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,16 +346,35 @@
 /** 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
  *  in a single Transaction applied to one account (in particular when

Modified: gnucash/trunk/src/engine/engine.i
===================================================================
--- gnucash/trunk/src/engine/engine.i	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/engine.i	2009-11-20 20:11:03 UTC (rev 18429)
@@ -227,6 +227,8 @@
     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");

Modified: gnucash/trunk/src/engine/gnc-commodity.c
===================================================================
--- gnucash/trunk/src/engine/gnc-commodity.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/gnc-commodity.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -2416,4 +2416,61 @@
   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 ============================== */

Modified: gnucash/trunk/src/engine/gnc-commodity.h
===================================================================
--- gnucash/trunk/src/engine/gnc-commodity.h	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/engine/gnc-commodity.h	2009-11-20 20:11:03 UTC (rev 18429)
@@ -973,7 +973,71 @@
 		  
 /** @} */
 
+/** @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 */
 /** @} */
 /** @} */

Modified: gnucash/trunk/src/gnome/druid-hierarchy.c
===================================================================
--- gnucash/trunk/src/gnome/druid-hierarchy.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/gnome/druid-hierarchy.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -706,7 +706,8 @@
 		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 {

Modified: gnucash/trunk/src/gnome-utils/dialog-account.c
===================================================================
--- gnucash/trunk/src/gnome-utils/dialog-account.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/gnome-utils/dialog-account.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -169,7 +169,9 @@
 {
   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 @@
     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);

Modified: gnucash/trunk/src/gnome-utils/dialog-transfer.c
===================================================================
--- gnucash/trunk/src/gnome-utils/dialog-transfer.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/gnome-utils/dialog-transfer.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -2146,6 +2146,19 @@
 
     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 @@
      * 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;
         

Modified: gnucash/trunk/src/gnome-utils/window-main-summarybar.c
===================================================================
--- gnucash/trunk/src/gnome-utils/window-main-summarybar.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/gnome-utils/window-main-summarybar.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -293,6 +293,12 @@
       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;

Modified: gnucash/trunk/src/import-export/import-backend.c
===================================================================
--- gnucash/trunk/src/import-export/import-backend.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/import-export/import-backend.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -122,7 +122,10 @@
 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_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);

Modified: gnucash/trunk/src/import-export/import-main-matcher.c
===================================================================
--- gnucash/trunk/src/import-export/import-main-matcher.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/import-export/import-main-matcher.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -605,10 +605,12 @@
 	}
       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)),

Modified: gnucash/trunk/src/import-export/import-match-picker.c
===================================================================
--- gnucash/trunk/src/import-export/import-match-picker.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/import-export/import-match-picker.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -151,7 +151,10 @@
   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);
 

Modified: gnucash/trunk/src/libqof/qof/qofbook.c
===================================================================
--- gnucash/trunk/src/libqof/qof/qofbook.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/libqof/qof/qofbook.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -39,6 +39,7 @@
 #include <string.h>
 
 #include <glib.h>
+#include <libguile.h>
 
 #include "qof.h"
 #include "qofevent-p.h"
@@ -439,6 +440,48 @@
     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)
 {

Modified: gnucash/trunk/src/libqof/qof/qofbook.h
===================================================================
--- gnucash/trunk/src/libqof/qof/qofbook.h	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/libqof/qof/qofbook.h	2009-11-20 20:11:03 UTC (rev 18429)
@@ -213,6 +213,9 @@
 /** 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);
 

Modified: gnucash/trunk/src/register/ledger-core/gnc-ledger-display.c
===================================================================
--- gnucash/trunk/src/register/ledger-core/gnc-ledger-display.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/register/ledger-core/gnc-ledger-display.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -281,6 +281,9 @@
 
       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 @@
       break;
 
     case ACCT_TYPE_EQUITY:
+    case ACCT_TYPE_TRADING:
       reg_type = GENERAL_LEDGER;
       break;
 

Modified: gnucash/trunk/src/register/ledger-core/split-register-control.c
===================================================================
--- gnucash/trunk/src/register/ledger-core/split-register-control.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/register/ledger-core/split-register-control.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -59,12 +59,40 @@
     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 @@
        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 @@
     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"));

Modified: gnucash/trunk/src/register/ledger-core/split-register-layout.c
===================================================================
--- gnucash/trunk/src/register/ledger-core/split-register-layout.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/register/ledger-core/split-register-layout.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -92,6 +92,7 @@
     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 @@
     case INCOME_REGISTER:
     case EXPENSE_REGISTER:
     case EQUITY_REGISTER:
+    case TRADING_REGISTER:
       num_cols = 9;
       break;
 

Modified: gnucash/trunk/src/register/ledger-core/split-register-model-save.c
===================================================================
--- gnucash/trunk/src/register/ledger-core/split-register-model-save.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/register/ledger-core/split-register-model-save.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -411,9 +411,66 @@
 {
   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 @@
    * '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);

Modified: gnucash/trunk/src/register/ledger-core/split-register-model.c
===================================================================
--- gnucash/trunk/src/register/ledger-core/split-register-model.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/register/ledger-core/split-register-model.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -163,6 +163,11 @@
   if (!account)
     return TRUE;
 
+  if (xaccTransUseTradingAccounts (xaccSplitGetParent (split))) {
+    if (!gnc_commodity_is_iso(xaccAccountGetCommodity(account)))
+      return TRUE;
+  }
+  
   return xaccAccountIsPriced(account);
 }
 
@@ -687,7 +692,7 @@
     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 @@
   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_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,35 +1643,76 @@
 
   {
     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);
+        }
+      }
+    }
 
     if (gnc_numeric_zero_p (amount))
       return NULL;

Modified: gnucash/trunk/src/register/ledger-core/split-register.c
===================================================================
--- gnucash/trunk/src/register/ledger-core/split-register.c	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/register/ledger-core/split-register.c	2009-11-20 20:11:03 UTC (rev 18429)
@@ -1782,6 +1782,16 @@
   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 @@
       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_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"));

Modified: gnucash/trunk/src/register/ledger-core/split-register.h
===================================================================
--- gnucash/trunk/src/register/ledger-core/split-register.h	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/register/ledger-core/split-register.h	2009-11-20 20:11:03 UTC (rev 18429)
@@ -163,6 +163,7 @@
   CURRENCY_REGISTER,
   RECEIVABLE_REGISTER,
   PAYABLE_REGISTER,
+  TRADING_REGISTER,
   NUM_SINGLE_REGISTER_TYPES,
 
   GENERAL_LEDGER = NUM_SINGLE_REGISTER_TYPES,

Modified: gnucash/trunk/src/report/report-system/report-utilities.scm
===================================================================
--- gnucash/trunk/src/report/report-system/report-utilities.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/report/report-system/report-utilities.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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

Modified: gnucash/trunk/src/report/standard-reports/advanced-portfolio.scm
===================================================================
--- gnucash/trunk/src/report/standard-reports/advanced-portfolio.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/report/standard-reports/advanced-portfolio.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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

Modified: gnucash/trunk/src/report/standard-reports/balance-sheet.scm
===================================================================
--- gnucash/trunk/src/report/standard-reports/balance-sheet.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/report/standard-reports/balance-sheet.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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
@@ -650,6 +665,12 @@
 				  (_ "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
 				  (_ "Unrealized Gains")

Modified: gnucash/trunk/src/report/standard-reports/equity-statement.scm
===================================================================
--- gnucash/trunk/src/report/standard-reports/equity-statement.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/report/standard-reports/equity-statement.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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))
 

Modified: gnucash/trunk/src/report/standard-reports/transaction.scm
===================================================================
--- gnucash/trunk/src/report/standard-reports/transaction.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/report/standard-reports/transaction.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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))
 

Modified: gnucash/trunk/src/report/standard-reports/trial-balance.scm
===================================================================
--- gnucash/trunk/src/report/standard-reports/trial-balance.scm	2009-11-20 19:57:51 UTC (rev 18428)
+++ gnucash/trunk/src/report/standard-reports/trial-balance.scm	2009-11-20 20:11:03 UTC (rev 18429)
@@ -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!



More information about the gnucash-changes mailing list