Account.c

00001 /********************************************************************\
00002  * Account.c -- Account data structure implementation               *
00003  * Copyright (C) 1997 Robin D. Clark                                *
00004  * Copyright (C) 1997-2003 Linas Vepstas <linas@linas.org>          *
00005  * Copyright (C) 2007 David Hampton <hampton@employees.org>         *
00006  *                                                                  *
00007  * This program is free software; you can redistribute it and/or    *
00008  * modify it under the terms of the GNU General Public License as   *
00009  * published by the Free Software Foundation; either version 2 of   *
00010  * the License, or (at your option) any later version.              *
00011  *                                                                  *
00012  * This program is distributed in the hope that it will be useful,  *
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00015  * GNU General Public License for more details.                     *
00016  *                                                                  *
00017  * You should have received a copy of the GNU General Public License*
00018  * along with this program; if not, contact:                        *
00019  *                                                                  *
00020  * Free Software Foundation           Voice:  +1-617-542-5942       *
00021  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00022  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00023  *                                                                  *
00024 \********************************************************************/
00025 
00026 #include "config.h"
00027 
00028 #include <glib.h>
00029 #include <glib/gi18n.h>
00030 #include <stdlib.h>
00031 #include <string.h>
00032 
00033 #include "AccountP.h"
00034 #include "Split.h"
00035 #include "Transaction.h"
00036 #include "TransactionP.h"
00037 #include "gnc-event.h"
00038 #include "gnc-glib-utils.h"
00039 #include "gnc-lot.h"
00040 #include "gnc-lot-p.h"
00041 #include "gnc-pricedb.h"
00042 
00043 #define GNC_ID_ROOT_ACCOUNT        "RootAccount"
00044 
00045 static QofLogModule log_module = GNC_MOD_ACCOUNT;
00046 
00047 /* The Canonical Account Separator.  Pre-Initialized. */
00048 static gchar account_separator[8] = ".";
00049 gunichar account_uc_separator = ':';
00050 
00051 enum {
00052     LAST_SIGNAL
00053 };
00054 
00055 enum {
00056   PROP_0,
00057   PROP_NAME,
00058   PROP_FULL_NAME,
00059   PROP_CODE,
00060   PROP_DESCRIPTION,
00061   PROP_NOTES,
00062   PROP_TYPE,
00063 
00064   PROP_COMMODITY,
00065   PROP_COMMODITY_SCU,
00066   PROP_NON_STD_SCU,
00067   PROP_SORT_DIRTY,
00068   PROP_BALANCE_DIRTY,
00069   PROP_START_BALANCE,
00070   PROP_START_CLEARED_BALANCE,
00071   PROP_START_RECONCILED_BALANCE,
00072   PROP_END_BALANCE,
00073   PROP_END_CLEARED_BALANCE,
00074   PROP_END_RECONCILED_BALANCE,
00075 
00076   PROP_POLICY,
00077   PROP_MARK,
00078   PROP_TAX_RELATED,
00079   PROP_TAX_CODE,
00080   PROP_TAX_SOURCE,
00081 };
00082 
00083 typedef struct AccountPrivate
00084 {
00085     /* The accountName is an arbitrary string assigned by the user. 
00086      * It is intended to a short, 5 to 30 character long string that
00087      * is displayed by the GUI as the account mnemonic. 
00088      */
00089     char *accountName;
00090 
00091     /* The accountCode is an arbitrary string assigned by the user.
00092      * It is intended to be reporting code that is a synonym for the 
00093      * accountName. Typically, it will be a numeric value that follows 
00094      * the numbering assignments commonly used by accountants, such 
00095      * as 100, 200 or 600 for top-level accounts, and 101, 102..  etc.
00096      * for detail accounts.
00097      */
00098     char *accountCode;
00099 
00100     /* The description is an arbitrary string assigned by the user. 
00101      * It is intended to be a longer, 1-5 sentence description of what
00102      * this account is all about.
00103      */
00104     char *description;
00105 
00106     /* The type field is the account type, picked from the enumerated
00107      * list that includes ACCT_TYPE_BANK, ACCT_TYPE_STOCK,
00108      * ACCT_TYPE_CREDIT, ACCT_TYPE_INCOME, etc.  Its intended use is to
00109      * be a hint to the GUI as to how to display and format the
00110      * transaction data.
00111      */
00112     GNCAccountType type;
00113 
00114     /* 
00115      * The commodity field denotes the kind of 'stuff' stored 
00116      * in this account.  The 'amount' field of a split indicates
00117      * how much of the 'stuff' there is.
00118      */
00119     gnc_commodity * commodity;
00120     int commodity_scu;
00121     gboolean non_standard_scu;
00122 
00123     /* The parent and children pointers are used to implement an account
00124      * hierarchy, of accounts that have sub-accounts ("detail accounts").
00125      */
00126     Account *parent;    /* back-pointer to parent */
00127     GList *children;    /* list of sub-accounts */
00128 
00129     /* protected data - should only be set by backends */
00130     gnc_numeric starting_balance;
00131     gnc_numeric starting_cleared_balance;
00132     gnc_numeric starting_reconciled_balance;
00133 
00134     /* cached parameters */
00135     gnc_numeric balance;
00136     gnc_numeric cleared_balance;
00137     gnc_numeric reconciled_balance;
00138 
00139     gboolean balance_dirty;     /* balances in splits incorrect */
00140 
00141     GList *splits;              /* list of split pointers */
00142     gboolean sort_dirty;        /* sort order of splits is bad */
00143 
00144     LotList   *lots;            /* list of lot pointers */
00145     GNCPolicy *policy;          /* Cached pointer to policy method */
00146 
00147     /* The "mark" flag can be used by the user to mark this account
00148      * in any way desired.  Handy for specialty traversals of the 
00149      * account tree. */
00150     short mark;
00151 } AccountPrivate;
00152 
00153 #define GET_PRIVATE(o)  \
00154    (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_ACCOUNT, AccountPrivate))
00155 
00156 /********************************************************************\
00157  * Because I can't use C++ for this project, doesn't mean that I    *
00158  * can't pretend to!  These functions perform actions on the        *
00159  * account data structure, in order to encapsulate the knowledge    *
00160  * of the internals of the Account in one file.                     *
00161 \********************************************************************/
00162 
00163 static void xaccAccountBringUpToDate (Account *acc);
00164 
00165 
00166 /********************************************************************\
00167  * gnc_get_account_separator                                        *
00168  *   returns the current account separator character                *
00169  *                                                                  *
00170  * Args: none                                                       *
00171  * Returns: account separator character                             *
00172  \*******************************************************************/
00173 const gchar *
00174 gnc_get_account_separator_string (void)
00175 {
00176   return account_separator;
00177 }
00178 
00179 gunichar
00180 gnc_get_account_separator (void)
00181 {
00182   return account_uc_separator;
00183 }
00184 
00185 void
00186 gnc_set_account_separator (const gchar *separator)
00187 {
00188   gunichar uc;
00189   gint count;
00190 
00191   uc = g_utf8_get_char_validated(separator, -1);
00192   if ((uc == (gunichar)-2) || (uc == (gunichar)-1) || g_unichar_isalnum(uc)) {
00193     account_uc_separator = ':';
00194     strcpy(account_separator, ":");
00195     return;
00196   }
00197 
00198   account_uc_separator = uc;
00199   count = g_unichar_to_utf8(uc, account_separator);
00200   account_separator[count] = '\0';
00201 }
00202 
00203 /********************************************************************\
00204 \********************************************************************/
00205 
00206 G_INLINE_FUNC void mark_account (Account *acc);
00207 void
00208 mark_account (Account *acc)
00209 {
00210   qof_instance_set_dirty(&acc->inst);
00211 }
00212 
00213 /********************************************************************\
00214 \********************************************************************/
00215 
00216 /* GObject Initialization */
00217 G_DEFINE_TYPE(Account, gnc_account, QOF_TYPE_INSTANCE)
00218 
00219 static void
00220 gnc_account_init(Account* acc)
00221 {
00222     AccountPrivate *priv;
00223 
00224     priv = GET_PRIVATE(acc);
00225     priv->parent   = NULL;
00226     priv->children = NULL;
00227 
00228     priv->accountName = CACHE_INSERT("");
00229     priv->accountCode = CACHE_INSERT("");
00230     priv->description = CACHE_INSERT("");
00231 
00232     priv->type = ACCT_TYPE_NONE;
00233 
00234     priv->mark = 0;
00235 
00236     priv->policy = xaccGetFIFOPolicy();
00237     priv->lots = NULL;
00238 
00239     priv->commodity = NULL;
00240     priv->commodity_scu = 0;
00241     priv->non_standard_scu = FALSE;
00242 
00243     priv->balance = gnc_numeric_zero();
00244     priv->cleared_balance = gnc_numeric_zero();
00245     priv->reconciled_balance = gnc_numeric_zero();
00246     priv->starting_balance = gnc_numeric_zero();
00247     priv->starting_cleared_balance = gnc_numeric_zero();
00248     priv->starting_reconciled_balance = gnc_numeric_zero();
00249     priv->balance_dirty = FALSE;
00250 
00251     priv->splits = NULL;
00252     priv->sort_dirty = FALSE;
00253  }
00254 
00255 static void
00256 gnc_account_dispose (GObject *acctp)
00257 {
00258     G_OBJECT_CLASS(gnc_account_parent_class)->dispose(acctp);
00259 }
00260 
00261 static void
00262 gnc_account_finalize(GObject* acctp)
00263 {
00264     G_OBJECT_CLASS(gnc_account_parent_class)->finalize(acctp);
00265 }
00266 
00267 static void
00268 gnc_account_get_property (GObject         *object,
00269                           guint            prop_id,
00270                           GValue          *value,
00271                           GParamSpec      *pspec)
00272 {
00273     Account *account;
00274     AccountPrivate *priv;
00275 
00276     g_return_if_fail(GNC_IS_ACCOUNT(object));
00277 
00278     account = GNC_ACCOUNT(object);
00279     priv = GET_PRIVATE(account);
00280     switch (prop_id) {
00281         case PROP_NAME:
00282             g_value_set_string(value, priv->accountName);
00283             break;
00284         case PROP_FULL_NAME:
00285             g_value_take_string(value, xaccAccountGetFullName(account));
00286             break;
00287         case PROP_CODE:
00288             g_value_set_string(value, priv->accountCode);
00289             break;
00290         case PROP_DESCRIPTION:
00291             g_value_set_string(value, priv->description);
00292             break;
00293         case PROP_NOTES:
00294             g_value_set_string(value, xaccAccountGetNotes(account));
00295             break;
00296         case PROP_TYPE:
00297             // NEED TO BE CONVERTED TO A G_TYPE_ENUM
00298             g_value_set_int(value, priv->type);
00299             break;
00300         case PROP_COMMODITY:
00301             g_value_set_object(value, priv->commodity);
00302             break;
00303         case PROP_COMMODITY_SCU:
00304             g_value_set_int(value, priv->commodity_scu);
00305             break;
00306         case PROP_NON_STD_SCU:
00307             g_value_set_boolean(value, priv->non_standard_scu);
00308             break;
00309         case PROP_SORT_DIRTY:
00310             g_value_set_boolean(value, priv->sort_dirty);
00311             break;
00312         case PROP_BALANCE_DIRTY:
00313             g_value_set_boolean(value, priv->balance_dirty);
00314             break;
00315         case PROP_START_BALANCE:
00316             g_value_set_boxed(value, &priv->starting_balance);
00317             break;
00318         case PROP_START_CLEARED_BALANCE:
00319             g_value_set_boxed(value, &priv->starting_cleared_balance);
00320             break;
00321         case PROP_START_RECONCILED_BALANCE:
00322             g_value_set_boxed(value, &priv->starting_reconciled_balance);
00323             break;
00324         case PROP_END_BALANCE:
00325             g_value_set_boxed(value, &priv->balance);
00326             break;
00327         case PROP_END_CLEARED_BALANCE:
00328             g_value_set_boxed(value, &priv->cleared_balance);
00329             break;
00330         case PROP_END_RECONCILED_BALANCE:
00331             g_value_set_boxed(value, &priv->reconciled_balance);
00332             break;
00333         case PROP_POLICY:
00334             /* MAKE THIS A BOXED VALUE */
00335             g_value_set_pointer(value, priv->policy);
00336             break;
00337         case PROP_MARK:
00338             g_value_set_int(value, priv->mark);
00339             break;
00340         case PROP_TAX_RELATED:
00341             g_value_set_boolean(value, xaccAccountGetTaxRelated(account));
00342             break;
00343         case PROP_TAX_CODE:
00344             g_value_set_string(value, xaccAccountGetTaxUSCode(account));
00345             break;
00346         case PROP_TAX_SOURCE:
00347             g_value_set_string(value,
00348                                xaccAccountGetTaxUSPayerNameSource(account));
00349             break;
00350         default:
00351             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
00352             break;
00353     }
00354 }
00355 
00356 static void
00357 gnc_account_set_property (GObject         *object,
00358                           guint            prop_id,
00359                           const GValue    *value,
00360                           GParamSpec      *pspec)
00361 {
00362     Account *account;
00363     gnc_numeric *number;
00364 
00365     g_return_if_fail(GNC_IS_ACCOUNT(object));
00366 
00367     account = GNC_ACCOUNT(object);
00368 
00369     switch (prop_id) {
00370         case PROP_NAME:
00371             xaccAccountSetName(account, g_value_get_string(value));
00372             break;
00373         case PROP_CODE:
00374             xaccAccountSetCode(account, g_value_get_string(value));
00375             break;
00376         case PROP_DESCRIPTION:
00377             xaccAccountSetDescription(account, g_value_get_string(value));
00378             break;
00379         case PROP_NOTES:
00380             xaccAccountSetNotes(account, g_value_get_string(value));
00381             break;
00382         case PROP_TYPE:
00383             // NEED TO BE CONVERTED TO A G_TYPE_ENUM
00384             xaccAccountSetType(account, g_value_get_int(value));
00385             break;
00386         case PROP_COMMODITY:
00387             xaccAccountSetCommodity(account, g_value_get_object(value));
00388             break;
00389         case PROP_COMMODITY_SCU:
00390             xaccAccountSetCommoditySCU(account, g_value_get_int(value));
00391             break;
00392         case PROP_SORT_DIRTY:
00393             gnc_account_set_sort_dirty(account);
00394             break;
00395         case PROP_BALANCE_DIRTY:
00396             gnc_account_set_balance_dirty(account);
00397             break;
00398         case PROP_START_BALANCE:
00399             number = g_value_get_boxed(value);
00400             gnc_account_set_start_balance(account, *number);
00401             break;
00402         case PROP_START_CLEARED_BALANCE:
00403             number = g_value_get_boxed(value);
00404             gnc_account_set_start_cleared_balance(account, *number);
00405             break;
00406         case PROP_START_RECONCILED_BALANCE:
00407             number = g_value_get_boxed(value);
00408             gnc_account_set_start_reconciled_balance(account, *number);
00409             break;
00410         case PROP_POLICY:
00411             gnc_account_set_policy(account, g_value_get_pointer(value));
00412             break;
00413         case PROP_MARK:
00414             xaccAccountSetMark(account, g_value_get_int(value));
00415             break;
00416         case PROP_TAX_RELATED:
00417             xaccAccountSetTaxRelated(account, g_value_get_boolean(value));
00418             break;
00419         case PROP_TAX_CODE:
00420             xaccAccountSetTaxUSCode(account, g_value_get_string(value));
00421             break;
00422         case PROP_TAX_SOURCE:
00423             xaccAccountSetTaxUSPayerNameSource(account,
00424                                                g_value_get_string(value));
00425         default:
00426             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
00427             break;
00428     }    
00429 }
00430 
00431 
00432 
00433 static void
00434 gnc_account_class_init (AccountClass *klass)
00435 {
00436     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
00437         
00438     gobject_class->dispose = gnc_account_dispose;
00439     gobject_class->finalize = gnc_account_finalize;
00440     gobject_class->set_property = gnc_account_set_property;
00441     gobject_class->get_property = gnc_account_get_property;
00442 
00443     g_type_class_add_private(klass, sizeof(AccountPrivate));
00444 
00445     g_object_class_install_property
00446         (gobject_class,
00447          PROP_NAME,
00448          g_param_spec_string ("name",
00449                               "Account Name",
00450                               "The accountName is an arbitrary string "
00451                               "assigned by the user.  It is intended to "
00452                               "a short, 5 to 30 character long string "
00453                               "that is displayed by the GUI as the "
00454                               "account mnemonic.  Account names may be "
00455                               "repeasted. but no two accounts that share "
00456                               "a parent may have the same name.",
00457                               NULL,
00458                               G_PARAM_READWRITE));
00459 
00460     g_object_class_install_property
00461         (gobject_class,
00462          PROP_FULL_NAME,
00463          g_param_spec_string ("fullname",
00464                               "Full Account Name",
00465                               "The name of the account concatenated with "
00466                               "all its parent account names to indicate "
00467                               "a unique account.",
00468                               NULL,
00469                               G_PARAM_READABLE));
00470 
00471     g_object_class_install_property
00472         (gobject_class,
00473          PROP_CODE,
00474          g_param_spec_string ("code",
00475                               "Account Code",
00476                               "The account code is an arbitrary string "
00477                               "assigned by the user. It is intended to "
00478                               "be reporting code that is a synonym for "
00479                               "the accountName.",
00480                               NULL,
00481                               G_PARAM_READWRITE));
00482 
00483     g_object_class_install_property
00484         (gobject_class,
00485          PROP_DESCRIPTION,
00486          g_param_spec_string ("description",
00487                               "Account Description",
00488                               "The account description is an arbitrary "
00489                               "string assigned by the user. It is intended "
00490                               "to be a longer, 1-5 sentence description of "
00491                               "what this account is all about.",
00492                               NULL,
00493                               G_PARAM_READWRITE));
00494 
00495     g_object_class_install_property
00496         (gobject_class,
00497          PROP_NOTES,
00498          g_param_spec_string ("notes",
00499                               "Account Notes",
00500                               "The account notes is an arbitrary provided "
00501                               "for the user to attach any orther text that "
00502                               "they would like to associate with the account.",
00503                               NULL,
00504                               G_PARAM_READWRITE));
00505 
00506     g_object_class_install_property
00507         (gobject_class,
00508          PROP_TYPE,
00509          g_param_spec_int ("type",
00510                            "Account Type",
00511                            "The account type, picked from the enumerated list "
00512                            "that includes ACCT_TYPE_BANK, ACCT_TYPE_STOCK, "
00513                            "ACCT_TYPE_CREDIT, ACCT_TYPE_INCOME, etc.",
00514                            ACCT_TYPE_NONE,
00515                            NUM_ACCOUNT_TYPES - 1,
00516                            ACCT_TYPE_BANK,
00517                            G_PARAM_READWRITE));
00518 
00519     g_object_class_install_property
00520         (gobject_class,
00521          PROP_COMMODITY,
00522          g_param_spec_object ("commodity",
00523                               "Commodity",
00524                               "The commodity field denotes the kind of "
00525                               "'stuff' stored  in this account, whether "
00526                               "it is USD, gold, stock, etc.",
00527                               GNC_TYPE_COMMODITY,
00528                               G_PARAM_READWRITE));
00529 
00530     g_object_class_install_property
00531         (gobject_class,
00532          PROP_COMMODITY_SCU,
00533          g_param_spec_int ("commodity-scu",
00534                            "Commodity SCU",
00535                            "The smallest fraction of the commodity that is "
00536                            "tracked.  This number is used as the denominator "
00537                            "value in 1/x, so a value of 100 says that the "
00538                            "commodity can be divided into hundreths.  E.G."
00539                            "1 USD can be divided into 100 cents.",
00540                            0,
00541                            G_MAXINT32,
00542                            1000000,
00543                            G_PARAM_READWRITE));
00544 
00545     g_object_class_install_property
00546         (gobject_class,
00547          PROP_NON_STD_SCU,
00548          g_param_spec_boolean ("non-std-scu",
00549                                "Non-std SCU",
00550                                "TRUE id the account SCU doesn't match "
00551                                "the commodity SCU.  This indicates a case "
00552                                "where the two were accidentally set to "
00553                                "mismatched values in older versions of "
00554                                "GnuCash.",
00555                                FALSE,
00556                                G_PARAM_READWRITE));
00557 
00558     g_object_class_install_property
00559         (gobject_class,
00560          PROP_SORT_DIRTY,
00561          g_param_spec_boolean("sort-dirty",
00562                               "Sort Dirty",
00563                               "TRUE if the splits in the account needs to be "
00564                               "resorted.  This flag is set by the accounts "
00565                               "code for certain internal modifications, or "
00566                               "when external code calls the engine to say a "
00567                               "split has been modified in a way that may "
00568                               "affect the sort order of the account. Note: "
00569                               "This value can only be set to TRUE.",
00570                               FALSE,
00571                               G_PARAM_READWRITE));
00572 
00573     g_object_class_install_property
00574         (gobject_class,
00575          PROP_BALANCE_DIRTY,
00576          g_param_spec_boolean("balance-dirty",
00577                               "Balance Dirty",
00578                               "TRUE if the running balances in the account "
00579                               "needs to be recalculated.  This flag is set "
00580                               "by the accounts code for certain internal "
00581                               "modifications, or when external code calls "
00582                               "the engine to say a split has been modified. "
00583                               "Note: This value can only be set to TRUE.",
00584                               FALSE,
00585                               G_PARAM_READWRITE));
00586 
00587     g_object_class_install_property
00588         (gobject_class,
00589          PROP_START_BALANCE,
00590          g_param_spec_boxed("start-balance",
00591                             "Starting Balance",
00592                             "The starting balance for the account.  This "
00593                             "parameter is intended for use with backends that "
00594                             "do not return the complete list of splits for an "
00595                             "account, but rather return a partial list.  In "
00596                             "such a case, the backend will typically return "
00597                             "all of the splits after some certain date, and "
00598                             "the 'starting balance' will represent the "
00599                             "summation of the splits up to that date.",
00600                             GNC_TYPE_NUMERIC,
00601                             G_PARAM_READWRITE));
00602 
00603     g_object_class_install_property
00604         (gobject_class,
00605          PROP_START_CLEARED_BALANCE,
00606          g_param_spec_boxed("start-cleared-balance",
00607                             "Starting Cleared Balance",
00608                             "The starting cleared balance for the account.  "
00609                             "This parameter is intended for use with backends "
00610                             "that do not return the complete list of splits "
00611                             "for an account, but rather return a partial "
00612                             "list.  In such a case, the backend will "
00613                             "typically return all of the splits after "
00614                             "some certain date, and the 'starting cleared "
00615                             "balance' will represent the summation of the "
00616                             "splits up to that date.",
00617                             GNC_TYPE_NUMERIC,
00618                             G_PARAM_READWRITE));
00619 
00620     g_object_class_install_property
00621         (gobject_class,
00622          PROP_START_RECONCILED_BALANCE,
00623          g_param_spec_boxed("start-reconciled-balance",
00624                             "Starting Reconciled Balance",
00625                             "The starting reconciled balance for the "
00626                             "account.  This parameter is intended for use "
00627                             "with backends that do not return the complete "
00628                             "list of splits for an account, but rather return "
00629                             "a partial list.  In such a case, the backend "
00630                             "will typically return all of the splits after "
00631                             "some certain date, and the 'starting recontiled "
00632                             "balance' will represent the summation of the "
00633                             "splits up to that date.",
00634                              GNC_TYPE_NUMERIC,
00635                              G_PARAM_READWRITE));
00636 
00637     g_object_class_install_property
00638         (gobject_class,
00639          PROP_END_BALANCE,
00640          g_param_spec_boxed("end-balance",
00641                             "Ending Account Balance",
00642                             "This is the current ending balance for the "
00643                             "account.  It is computed from the sum of the "
00644                             "starting balance and all splits in the account.",
00645                             GNC_TYPE_NUMERIC,
00646                             G_PARAM_READABLE));
00647 
00648     g_object_class_install_property
00649         (gobject_class,
00650          PROP_END_CLEARED_BALANCE,
00651          g_param_spec_boxed("end-cleared-balance",
00652                             "Ending Account Cleared Balance",
00653                             "This is the current ending cleared balance for "
00654                             "the account.  It is computed from the sum of the "
00655                             "starting balance and all cleared splits in the "
00656                             "account.",
00657                             GNC_TYPE_NUMERIC,
00658                             G_PARAM_READABLE));
00659 
00660     g_object_class_install_property
00661         (gobject_class,
00662          PROP_END_RECONCILED_BALANCE,
00663          g_param_spec_boxed("end-reconciled-balance",
00664                             "Ending Account Reconciled Balance",
00665                             "This is the current ending reconciled balance "
00666                             "for the account.  It is computed from the sum of "
00667                             "the starting balance and all reconciled splits "
00668                             "in the account.",
00669                             GNC_TYPE_NUMERIC,
00670                             G_PARAM_READABLE));
00671 
00672     g_object_class_install_property
00673         (gobject_class,
00674          PROP_POLICY,
00675          g_param_spec_pointer ("policy",
00676                                "Policy",
00677                                "The account lots policy.",
00678                                G_PARAM_READWRITE));
00679 
00680     g_object_class_install_property
00681         (gobject_class,
00682          PROP_MARK,
00683          g_param_spec_int ("acct-mark",
00684                            "Account Mark",
00685                            "Ipsum Lorem",
00686                            0,
00687                            G_MAXINT16,
00688                            0,
00689                            G_PARAM_READWRITE));
00690 
00691     g_object_class_install_property
00692         (gobject_class,
00693          PROP_TAX_RELATED,
00694          g_param_spec_boolean ("tax-related",
00695                                "Tax Related",
00696                                "Whether the account maps to an entry on an "
00697                                "income tax document.",
00698                                FALSE,
00699                                G_PARAM_READWRITE));
00700 
00701     g_object_class_install_property
00702         (gobject_class,
00703          PROP_TAX_CODE,
00704          g_param_spec_string ("tax-code",
00705                               "Tax Code",
00706                               "This is the code for mapping an account to a "
00707                               "specific entry on a taxable document.  In the "
00708                               "United States it is used to transfer totals "
00709                               "into tax preparation software.",
00710                               NULL,
00711                               G_PARAM_READWRITE));
00712 
00713     g_object_class_install_property
00714         (gobject_class,
00715          PROP_TAX_SOURCE,
00716          g_param_spec_string ("tax-source",
00717                               "Tax Source",
00718                               "This is an unknown tax related field.",
00719                               NULL,
00720                               G_PARAM_READWRITE));
00721 }
00722 
00723 static void
00724 xaccInitAccount (Account * acc, QofBook *book)
00725 {
00726   ENTER ("book=%p\n", book);
00727   qof_instance_init_data (&acc->inst, GNC_ID_ACCOUNT, book);
00728 
00729   LEAVE ("account=%p\n", acc);
00730 }
00731 
00732 /********************************************************************\
00733 \********************************************************************/
00734 
00735 QofBook *
00736 gnc_account_get_book(const Account *account)
00737 {
00738   return qof_instance_get_book(QOF_INSTANCE(account));
00739 }
00740 
00741 /********************************************************************\
00742 \********************************************************************/
00743 
00744 static Account * 
00745 gnc_coll_get_root_account (QofCollection *col)
00746 {
00747   if (!col) return NULL;
00748   return qof_collection_get_data (col);
00749 }
00750 
00751 static void
00752 gnc_coll_set_root_account (QofCollection *col, Account *root)
00753 {
00754   AccountPrivate *rpriv;
00755   Account *old_root;
00756   if (!col) return;
00757 
00758   old_root = gnc_coll_get_root_account (col);
00759   if (old_root == root) return;
00760 
00761   /* If the new root is already linked into the tree somewhere, then
00762    * remove it from its current position before adding it at the
00763    * top. */
00764   rpriv = GET_PRIVATE(root);
00765   if (rpriv->parent) {
00766     xaccAccountBeginEdit(root);
00767     gnc_account_remove_child(rpriv->parent, root);
00768     xaccAccountCommitEdit(root);
00769   }
00770     
00771   qof_collection_set_data (col, root);
00772 
00773   if (old_root) {
00774     xaccAccountBeginEdit (old_root);
00775     xaccAccountDestroy (old_root);
00776   }
00777 }
00778 
00779 Account *
00780 gnc_book_get_root_account (QofBook *book)
00781 {
00782   QofCollection *col;
00783   Account *root;
00784 
00785   if (!book) return NULL;
00786   col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
00787   root = gnc_coll_get_root_account (col);
00788   if (root == NULL)
00789     root = gnc_account_create_root(book);
00790   return root;
00791 }
00792 
00793 void
00794 gnc_book_set_root_account (QofBook *book, Account *root)
00795 {
00796   QofCollection *col;
00797   if (!book) return;
00798 
00799   if (root && gnc_account_get_book(root) != book)
00800   {
00801      PERR ("cannot mix and match books freely!");
00802      return;
00803   }
00804 
00805   col = qof_book_get_collection (book, GNC_ID_ROOT_ACCOUNT);
00806   gnc_coll_set_root_account (col, root);
00807 }
00808 
00809 /********************************************************************\
00810 \********************************************************************/
00811 
00812 Account *
00813 xaccMallocAccount (QofBook *book)
00814 {
00815   Account *acc;
00816 
00817   g_return_val_if_fail (book, NULL);
00818 
00819   acc = g_object_new (GNC_TYPE_ACCOUNT, NULL);
00820   xaccInitAccount (acc, book);
00821   qof_event_gen (&acc->inst, QOF_EVENT_CREATE, NULL);
00822 
00823   return acc;
00824 }
00825 
00826 Account *
00827 gnc_account_create_root (QofBook *book)
00828 {
00829   Account *root;
00830   AccountPrivate *rpriv;
00831 
00832   root = xaccMallocAccount(book);
00833   rpriv = GET_PRIVATE(root);
00834   xaccAccountBeginEdit(root);
00835   rpriv->type = ACCT_TYPE_ROOT;
00836   CACHE_REPLACE(rpriv->accountName, "Root Account");
00837   xaccAccountCommitEdit(root);
00838   gnc_book_set_root_account(book, root);
00839   return root;
00840 }
00841 
00842 static Account *
00843 xaccCloneAccountCommon(const Account *from, QofBook *book)
00844 {
00845     Account *ret;
00846     AccountPrivate *from_priv, *priv;
00847 
00848     g_return_val_if_fail(GNC_IS_ACCOUNT(from), NULL);
00849     g_return_val_if_fail(QOF_IS_BOOK(book), NULL);
00850 
00851     ENTER (" ");
00852     ret = g_object_new (GNC_TYPE_ACCOUNT, NULL);
00853     g_return_val_if_fail (ret, NULL);
00854 
00855     from_priv = GET_PRIVATE(from);
00856     priv = GET_PRIVATE(ret);
00857     xaccInitAccount (ret, book);
00858 
00859     /* Do not Begin/CommitEdit() here; give the caller 
00860      * a chance to fix things up, and let them do it.
00861      * Also let caller issue the generate_event (EVENT_CREATE) */
00862     priv->type = from_priv->type;
00863 
00864     priv->accountName = CACHE_INSERT(from_priv->accountName);
00865     priv->accountCode = CACHE_INSERT(from_priv->accountCode);
00866     priv->description = CACHE_INSERT(from_priv->description);
00867 
00868     kvp_frame_delete(ret->inst.kvp_data);
00869     ret->inst.kvp_data = kvp_frame_copy(from->inst.kvp_data);
00870 
00871     /* The new book should contain a commodity that matches
00872      * the one in the old book. Find it, use it. */
00873     priv->commodity = gnc_commodity_obtain_twin(from_priv->commodity, book);
00874     gnc_commodity_increment_usage_count(priv->commodity);
00875 
00876     priv->commodity_scu = from_priv->commodity_scu;
00877     priv->non_standard_scu = from_priv->non_standard_scu;
00878 
00879     LEAVE (" ");
00880     return ret;
00881 }
00882 
00883 Account *
00884 xaccCloneAccount (const Account *from, QofBook *book)
00885 {
00886     Account *ret = xaccCloneAccountCommon(from, book);
00887     qof_instance_gemini (&ret->inst, (QofInstance *) &from->inst);
00888     g_assert (ret ==
00889               (Account*) qof_instance_lookup_twin (QOF_INSTANCE(from), book));
00890     return ret;
00891 }
00892 
00893 Account *
00894 xaccCloneAccountSimple (const Account *from, QofBook *book)
00895 {
00896     Account *ret = xaccCloneAccountCommon(from, book);    
00897     qof_instance_set_dirty(&ret->inst);
00898     return ret;
00899 }
00900 
00901 /********************************************************************\
00902 \********************************************************************/
00903 
00904 static void
00905 xaccFreeOneChildAccount (Account *acc, gpointer dummy)
00906 {
00907     /* FIXME: this code is kind of hacky.  actually, all this code
00908      * seems to assume that the account edit levels are all 1. */
00909     if (qof_instance_get_editlevel(acc) == 0)
00910       xaccAccountBeginEdit(acc);
00911     xaccAccountDestroy(acc);
00912 }
00913 
00914 static void
00915 xaccFreeAccountChildren (Account *acc)
00916 {
00917   AccountPrivate *priv;
00918   GList *children;
00919 
00920   /* Copy the list since it will be modified */
00921   priv = GET_PRIVATE(acc);
00922   children = g_list_copy(priv->children);
00923   g_list_foreach(children, (GFunc)xaccFreeOneChildAccount, NULL);
00924   g_list_free(children);
00925 
00926   /* The foreach should have removed all the children already. */
00927   if (priv->children)
00928     g_list_free(priv->children);
00929   priv->children = NULL;
00930 }
00931 
00932 /* The xaccFreeAccount() routine releases memory associated with the
00933  * account.  It should never be called directly from user code;
00934  * instead, the xaccAccountDestroy() routine should be used (because
00935  * xaccAccountDestroy() has the correct commit semantics). */
00936 static void
00937 xaccFreeAccount (Account *acc)
00938 {
00939   AccountPrivate *priv;
00940   GList *lp;
00941 
00942   g_return_if_fail(GNC_IS_ACCOUNT(acc));
00943 
00944   priv = GET_PRIVATE(acc);
00945   qof_event_gen (&acc->inst, QOF_EVENT_DESTROY, NULL);
00946 
00947   if (priv->children) 
00948   {
00949     PERR (" instead of calling xaccFreeAccount(), please call \n"
00950           " xaccAccountBeginEdit(); xaccAccountDestroy(); \n");
00951 
00952     /* First, recursively free children */
00953     xaccFreeAccountChildren(acc);
00954   }
00955 
00956   /* remove lots -- although these should be gone by now. */
00957   if (priv->lots)
00958   {
00959     PERR (" instead of calling xaccFreeAccount(), please call \n"
00960           " xaccAccountBeginEdit(); xaccAccountDestroy(); \n");
00961   
00962     for (lp=priv->lots; lp; lp=lp->next)
00963     {
00964       GNCLot *lot = lp->data;
00965       gnc_lot_destroy (lot);
00966     }
00967     g_list_free (priv->lots);
00968     priv->lots = NULL;
00969   }
00970 
00971   /* Next, clean up the splits */
00972   /* NB there shouldn't be any splits by now ... they should 
00973    * have been all been freed by CommitEdit().  We can remove this
00974    * check once we know the warning isn't occurring any more. */
00975   if (priv->splits) 
00976   {
00977     GList *slist;
00978     PERR (" instead of calling xaccFreeAccount(), please call \n"
00979           " xaccAccountBeginEdit(); xaccAccountDestroy(); \n");
00980   
00981     qof_instance_reset_editlevel(acc);
00982 
00983     slist = g_list_copy(priv->splits);
00984     for (lp = slist; lp; lp = lp->next) {
00985       Split *s = (Split *) lp->data;
00986       g_assert(xaccSplitGetAccount(s) == acc);
00987       xaccSplitDestroy (s);
00988     }
00989     g_list_free(slist);
00990     g_assert(priv->splits == NULL);
00991   }
00992 
00993   CACHE_REPLACE(priv->accountName, NULL);
00994   CACHE_REPLACE(priv->accountCode, NULL);
00995   CACHE_REPLACE(priv->description, NULL);
00996 
00997   /* zero out values, just in case stray 
00998    * pointers are pointing here. */
00999 
01000   priv->parent = NULL;
01001   priv->children = NULL;
01002 
01003   priv->balance  = gnc_numeric_zero();
01004   priv->cleared_balance = gnc_numeric_zero();
01005   priv->reconciled_balance = gnc_numeric_zero();
01006 
01007   priv->type = ACCT_TYPE_NONE;
01008   gnc_commodity_decrement_usage_count(priv->commodity);
01009   priv->commodity = NULL;
01010 
01011   priv->balance_dirty = FALSE;
01012   priv->sort_dirty = FALSE;
01013 
01014   /* qof_instance_release (&acc->inst); */
01015   g_object_unref(acc);
01016 }
01017 
01018 /********************************************************************\
01019  * transactional routines
01020 \********************************************************************/
01021 
01022 void 
01023 xaccAccountBeginEdit (Account *acc) 
01024 {
01025         g_return_if_fail(acc);
01026         qof_begin_edit(&acc->inst);
01027 }
01028 
01029 static void on_done(QofInstance *inst) 
01030 {
01031     /* old event style */
01032     qof_event_gen (inst, QOF_EVENT_MODIFY, NULL);
01033 }
01034 
01035 static void on_err (QofInstance *inst, QofBackendError errcode)
01036 {
01037   PERR("commit error: %d", errcode);
01038 }
01039 
01040 static void acc_free (QofInstance *inst)
01041 {
01042   AccountPrivate *priv;
01043   Account *acc = (Account *) inst;
01044 
01045   priv = GET_PRIVATE(acc);
01046   if (priv->parent)
01047     gnc_account_remove_child(priv->parent, acc);
01048   xaccFreeAccount(acc);
01049 }
01050 
01051 static void
01052 destroy_pending_splits_for_account(QofInstance *ent, gpointer acc)
01053 {
01054     Transaction *trans = (Transaction *) ent;
01055     Split *split;
01056 
01057     if (xaccTransIsOpen(trans))
01058         while ((split = xaccTransFindSplitByAccount(trans, acc)))
01059             xaccSplitDestroy(split);
01060 }
01061 
01062 void 
01063 xaccAccountCommitEdit (Account *acc) 
01064 {
01065   AccountPrivate *priv;
01066   QofBook *book;
01067 
01068   g_return_if_fail(acc);
01069   if (!qof_commit_edit(&acc->inst)) return;
01070 
01071   /* If marked for deletion, get rid of subaccounts first,
01072    * and then the splits ... */
01073   priv = GET_PRIVATE(acc);
01074   if (qof_instance_get_destroying(acc))
01075   {
01076     GList *lp, *slist;
01077     QofCollection *col;
01078  
01079     qof_instance_increase_editlevel(acc);
01080 
01081     /* First, recursively free children */
01082     xaccFreeAccountChildren(acc);
01083 
01084     PINFO ("freeing splits for account %p (%s)",
01085            acc, priv->accountName ? priv->accountName : "(null)");
01086 
01087     slist = g_list_copy(priv->splits);
01088     for (lp = slist; lp; lp = lp->next)
01089     {
01090       Split *s = lp->data;
01091       xaccSplitDestroy (s);
01092     }
01093     g_list_free(slist); 
01094     /* It turns out there's a case where this assertion does not hold:
01095        When the user tries to delete an Imbalance account, while also
01096        deleting all the splits in it.  The splits will just get
01097        recreated and put right back into the same account!
01098 
01099        g_assert(priv->splits == NULL || qof_book_shutting_down(acc->inst.book));
01100     */
01101 
01102     book = qof_instance_get_book(acc);
01103     if (!qof_book_shutting_down(book)) {
01104       col = qof_book_get_collection(book, GNC_ID_TRANS);
01105       qof_collection_foreach(col, destroy_pending_splits_for_account, acc);
01106     }
01107 
01108     /* the lots should be empty by now */
01109     for (lp = priv->lots; lp; lp = lp->next)
01110     {
01111       GNCLot *lot = lp->data;
01112       gnc_lot_destroy (lot);
01113     }
01114     g_list_free(priv->lots);
01115     priv->lots = NULL;
01116 
01117     qof_instance_set_dirty(&acc->inst);
01118     qof_instance_decrease_editlevel(acc);
01119   }
01120   else 
01121   {
01122     xaccAccountBringUpToDate(acc);
01123   }
01124 
01125   qof_commit_edit_part2(&acc->inst, on_err, on_done, acc_free);
01126 }
01127 
01128 void 
01129 xaccAccountDestroy (Account *acc) 
01130 {
01131   g_return_if_fail(GNC_IS_ACCOUNT(acc));
01132 
01133   qof_instance_set_destroying(acc, TRUE);
01134 
01135   xaccAccountCommitEdit (acc);
01136 }
01137 
01138 /********************************************************************\
01139 \********************************************************************/
01140 
01141 static gboolean
01142 xaccAcctChildrenEqual(const GList *na,
01143                       const GList *nb,
01144                       gboolean check_guids)
01145 {
01146   if ((!na && nb) || (na && !nb))
01147   {
01148     PWARN ("only one has accounts");
01149     return(FALSE);
01150   }
01151 
01152   while (na && nb)
01153   {
01154     Account *aa = na->data;
01155     Account *ab = nb->data;
01156 
01157     if (!xaccAccountEqual(aa, ab, check_guids))
01158     {
01159       char sa[GUID_ENCODING_LENGTH + 1];
01160       char sb[GUID_ENCODING_LENGTH + 1];
01161 
01162       guid_to_string_buff (xaccAccountGetGUID (aa), sa);
01163       guid_to_string_buff (xaccAccountGetGUID (ab), sb);
01164 
01165       PWARN ("accounts %s and %s differ", sa, sb);
01166 
01167       return(FALSE);
01168     }
01169 
01170     na = na->next;
01171     nb = nb->next;
01172   }
01173 
01174   if (na || nb)
01175   {
01176     PWARN ("different numbers of accounts");
01177     return(FALSE);
01178   }
01179 
01180   return(TRUE);
01181 }
01182 
01183 gboolean
01184 xaccAccountEqual(const Account *aa, const Account *ab, gboolean check_guids)
01185 {
01186   AccountPrivate *priv_aa, *priv_ab;
01187 
01188   if(!aa && !ab) return TRUE;
01189 
01190   g_return_val_if_fail(GNC_IS_ACCOUNT(aa), FALSE);
01191   g_return_val_if_fail(GNC_IS_ACCOUNT(ab), FALSE);
01192 
01193   priv_aa = GET_PRIVATE(aa);
01194   priv_ab = GET_PRIVATE(ab);
01195   if (priv_aa->type != priv_ab->type)
01196   {
01197     PWARN ("types differ: %d vs %d", priv_aa->type, priv_ab->type);
01198     return FALSE;
01199   }
01200 
01201   if (safe_strcmp(priv_aa->accountName, priv_ab->accountName) != 0)
01202   {
01203     PWARN ("names differ: %s vs %s", priv_aa->accountName, priv_ab->accountName);
01204     return FALSE;
01205   }
01206 
01207   if (safe_strcmp(priv_aa->accountCode, priv_ab->accountCode) != 0)
01208   {
01209     PWARN ("codes differ: %s vs %s", priv_aa->accountCode, priv_ab->accountCode);
01210     return FALSE;
01211   }
01212 
01213   if (safe_strcmp(priv_aa->description, priv_ab->description) != 0)
01214   {
01215     PWARN ("descriptions differ: %s vs %s", priv_aa->description, priv_ab->description);
01216     return FALSE;
01217   }
01218 
01219   if (!gnc_commodity_equal(priv_aa->commodity, priv_ab->commodity))
01220   {
01221     PWARN ("commodities differ");
01222     return FALSE;
01223   }
01224 
01225   if(check_guids) {
01226     if(qof_instance_guid_compare(aa, ab) != 0)
01227     {
01228       PWARN ("GUIDs differ");
01229       return FALSE;
01230     }
01231   }
01232 
01233   if (kvp_frame_compare(aa->inst.kvp_data, ab->inst.kvp_data) != 0)
01234   {
01235     char *frame_a;
01236     char *frame_b;
01237 
01238     frame_a = kvp_frame_to_string (aa->inst.kvp_data);
01239     frame_b = kvp_frame_to_string (ab->inst.kvp_data);
01240 
01241