Period.c

00001 /********************************************************************\
00002  * Period.c -- Implement accounting Periods                         *
00003  *                                                                  *
00004  * This program is free software; you can redistribute it and/or    *
00005  * modify it under the terms of the GNU General Public License as   *
00006  * published by the Free Software Foundation; either version 2 of   *
00007  * the License, or (at your option) any later version.              *
00008  *                                                                  *
00009  * This program is distributed in the hope that it will be useful,  *
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00012  * GNU General Public License for more details.                     *
00013  *                                                                  *
00014  * You should have received a copy of the GNU General Public License*
00015  * along with this program; if not, contact:                        *
00016  *                                                                  *
00017  * Free Software Foundation           Voice:  +1-617-542-5942       *
00018  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00019  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00020 \********************************************************************/
00021 /*
00022  * FILE:
00023  * Period.c
00024  *
00025  * FUNCTION:
00026  * Implement accounting periods, using design described in 
00027  * src/doc/books.txt
00028  *
00029  * CAUTION: poorly tested.
00030  *
00031  * HISTORY:
00032  * Created by Linas Vepstas November 2001
00033  * Copyright (c) 2001-2003 Linas Vepstas <linas@linas.org>
00034  */
00035 
00036 #include "config.h"
00037 #include "AccountP.h"
00038 #include "qof.h"
00039 #include "gnc-lot.h"
00040 #include "gnc-lot-p.h"
00041 #include "gnc-pricedb.h"
00042 #include "gnc-pricedb-p.h"
00043 #include "Period.h"
00044 #include "Transaction.h"
00045 #include "TransactionP.h"
00046 
00047 /* This static indicates the debugging module that this .o belongs to.  */
00048 static QofLogModule log_module = GNC_MOD_BOOK;
00049 
00050 /* ================================================================ */
00051 
00052 static inline Account *
00053 xaccAccountLookupTwin (Account *acc,  QofBook *book)
00054 {
00055    return (Account *) qof_instance_lookup_twin (QOF_INSTANCE(acc), book);
00056 }
00057 
00058 /* ================================================================ */
00059 /* Reparent transaction to new book.  This routine does this by 
00060  * deleting the transaction in the old book, and creating a copy
00061  * in the new book.  While technically correct, this is maybe too 
00062  * much churn on the backend ... 
00063  */
00064 
00065 void
00066 gnc_book_insert_trans_clobber (QofBook *book, Transaction *trans)
00067 {
00068    QofCollection *col;
00069    Transaction *newtrans;
00070    GList *node;
00071 
00072    if (!trans || !book) return;
00073    
00074    /* If this is the same book, its a no-op. */
00075    if (qof_instance_get_book(trans) == book) return;
00076 
00077    ENTER ("trans=%p %s", trans, trans->description);
00078    newtrans = xaccDupeTransaction (trans);
00079    for (node = newtrans->splits; node; node = node->next)
00080    {
00081       Split *s = node->data;
00082       s->parent = newtrans;
00083    }
00084 
00085    /* Utterly wipe out the transaction from the old book. */
00086    xaccTransBeginEdit (trans);
00087    xaccTransDestroy (trans);
00088    xaccTransCommitEdit (trans);
00089 
00090    /* Fiddle the transaction into place in the new book */
00091    col = qof_book_get_collection (book, GNC_ID_TRANS);
00092    qof_collection_insert_entity (col, &newtrans->inst);
00093    qof_instance_set_book(newtrans, book);
00094 
00095    col = qof_book_get_collection (book, GNC_ID_SPLIT);
00096    xaccTransBeginEdit (newtrans);
00097    for (node = newtrans->splits; node; node = node->next)
00098    {
00099       Account *twin;
00100       Split *s = node->data;
00101 
00102       /* move the split into the new book ... */
00103       qof_instance_set_book(s, book);
00104       qof_collection_insert_entity(col, &s->inst);
00105 
00106       /* find the twin account, and re-parent to that. */
00107       twin = xaccAccountLookupTwin (s->acc, book);
00108       if (!twin)
00109       {
00110          PERR ("near-fatal: twin account not found");
00111       }
00112       else
00113       {
00114         xaccAccountInsertSplit (twin, s);
00115         g_object_set(twin, "sort-dirty", TRUE, "balance-dirty", TRUE, NULL);
00116       }
00117    }
00118 
00119    xaccTransCommitEdit (newtrans);
00120    qof_event_gen (&newtrans->inst, QOF_EVENT_CREATE, NULL);
00121    LEAVE ("trans=%p %s", trans, trans->description);
00122 }
00123 
00124 /* ================================================================ */
00125 /* Reparent transaction to new book.  This routine does this by 
00126  * moving GUID's to the new book's entity tables.
00127  */
00128 
00129 void
00130 gnc_book_insert_trans (QofBook *book, Transaction *trans)
00131 {
00132    QofCollection *col;
00133    QofBook *trans_book;
00134    GList *node;
00135 
00136    if (!trans || !book) return;
00137    
00138    /* If this is the same book, its a no-op. */
00139    trans_book = qof_instance_get_book(trans);
00140    if (trans_book == book) return;
00141 
00142    /* If the old and new book don't share backends, then clobber-copy;
00143     * i.e. destroy it in one backend, create it in another.  */
00144    if (qof_book_get_backend(book) != qof_book_get_backend(trans_book))
00145    {
00146       gnc_book_insert_trans_clobber (book, trans);
00147       return;
00148    }
00149    ENTER ("trans=%p %s", trans, trans->description);
00150 
00151    /* Fiddle the transaction into place in the new book */
00152    xaccTransBeginEdit (trans);
00153 
00154    col = qof_book_get_collection (book, GNC_ID_TRANS);
00155    qof_instance_set_book(trans, book);
00156    qof_collection_insert_entity (col, &trans->inst);
00157 
00158    col = qof_book_get_collection (book, GNC_ID_SPLIT);
00159    for (node = trans->splits; node; node = node->next)
00160    {
00161       Account *twin;
00162       Split *s = node->data;
00163 
00164       /* Move the splits over (only if they haven't already been moved). */
00165       if (qof_instance_get_book(s) != book)
00166       {
00167          qof_instance_set_book(s, book);
00168          qof_collection_insert_entity (col, &s->inst);
00169       }
00170 
00171       /* Find the twin account, and re-parent to that. */
00172       twin = xaccAccountLookupTwin (s->acc, book);
00173       if (!twin)
00174       {
00175          PERR ("near-fatal: twin account not found");
00176       }
00177       else
00178       {
00179         /* Move the split too, if it hasn't been moved already */
00180         if (s->acc != twin)
00181         {
00182            xaccAccountInsertSplit (twin, s);
00183            g_object_set(twin, "sort-dirty", TRUE, "balance-dirty", TRUE, NULL);
00184         }
00185       }
00186    }
00187 
00188    xaccTransCommitEdit (trans);
00189    qof_event_gen (&trans->inst, QOF_EVENT_MODIFY, NULL);
00190    LEAVE ("trans=%p %s", trans, trans->description);
00191 }
00192 
00193 /* ================================================================ */
00194 /* Reparent lot to new book.  This routine does this by 
00195  * completely deleting and recreating the lot.
00196  */
00197 
00198 void
00199 gnc_book_insert_lot_clobber (QofBook *book, GNCLot *lot)
00200 {
00201    PERR ("Not Implemented");
00202 }
00203 
00204 /* ================================================================ */
00205 /* Reparent lot to new book.  This routine does this by 
00206  * moving GUID's to the new book's entity tables.
00207  */
00208 
00209 void
00210 gnc_book_insert_lot (QofBook *book, GNCLot *lot)
00211 {
00212    QofCollection *col;
00213    SplitList *snode;
00214    Account *twin;
00215 
00216    if (!lot || !book) return;
00217    
00218    /* If this is the same book, its a no-op. */
00219    if (gnc_lot_get_book(lot) == book) return;
00220 
00221    if (qof_book_get_backend(book) != 
00222        qof_book_get_backend(gnc_lot_get_book(lot)))
00223    {
00224       gnc_book_insert_lot_clobber (book, lot);
00225       return;
00226    }
00227    ENTER ("lot=%p", lot);
00228 
00229    col = qof_book_get_collection (book, GNC_ID_LOT);
00230    qof_instance_set_book(lot, book);
00231    qof_collection_insert_entity (col, &lot->inst);
00232 
00233    /* Move the splits over (only if they haven't already been moved). */
00234    col = qof_book_get_collection (book, GNC_ID_SPLIT);
00235    for (snode = lot->splits; snode; snode=snode->next)
00236    {
00237       Split *s = snode->data;
00238       if (qof_instance_get_book(s) != book)
00239       {
00240          qof_instance_set_book(s, book);
00241          qof_collection_insert_entity (col, &s->inst);
00242       }
00243    }
00244 
00245    twin = xaccAccountLookupTwin (lot->account, book);
00246    if (!twin)
00247    {
00248       PERR ("near-fatal: twin account not found");
00249    }
00250    else
00251    {
00252       xaccAccountInsertLot (twin, lot);
00253    }
00254    LEAVE ("lot=%p", lot);
00255 }
00256 
00257 /* ================================================================ */
00258 
00259 void
00260 gnc_book_insert_price (QofBook *book, GNCPrice *pr)
00261 {
00262    QofCollection *col;
00263    QofBook *pr_book;
00264 
00265    if (!pr || !book) return;
00266    
00267    /* If this is the same book, its a no-op. */
00268    pr_book = qof_instance_get_book(pr);
00269    if (pr_book == book) return;
00270 
00271    /* If the old and new book don't share backends, then clobber-copy;
00272     * i.e. destroy it in one backend, create it in another.  */
00273    if (qof_book_get_backend(book) != qof_book_get_backend(pr_book))
00274    {
00275       gnc_book_insert_price_clobber (book, pr);
00276       return;
00277    }
00278    ENTER ("price=%p", pr);
00279 
00280    /* Fiddle the price into place in the new book */
00281    gnc_price_ref (pr);
00282    gnc_price_begin_edit (pr);
00283 
00284    col = qof_book_get_collection (book, GNC_ID_PRICE);
00285    qof_instance_set_book(pr, book);
00286    qof_collection_insert_entity (col, &pr->inst);
00287 
00288    gnc_pricedb_remove_price (pr->db, pr);
00289    gnc_pricedb_add_price (gnc_pricedb_get_db (book), pr);
00290 
00291    gnc_price_commit_edit (pr);
00292    gnc_price_unref (pr);
00293 
00294    LEAVE ("price=%p", pr);
00295 }
00296 
00297 /* ================================================================ */
00298 
00299 void
00300 gnc_book_insert_price_clobber (QofBook *book, GNCPrice *pr)
00301 {
00302         PERR ("Not Implemented");
00303 }
00304 
00305 /* ================================================================ */
00306 /* The following routines determine whether a given lot or 
00307  * transaction is linked or related to another lot that is 'open'.
00308  * These return true if so.
00309  *
00310  * An 'open transaction' is a transaction that has a split 
00311  * that belongs to an 'open lot'.  An 'open lot' is one that
00312  * is not closed, OR ONE THAT HAS a split in it that belongs to 
00313  * an open transaction. 
00314  *
00315  * The need for this recursive definition is that some lots, 
00316  * even though themselves closed, are participants in transactions
00317  * that cannot be moved to a closed book, and thus, by association 
00318  * can't be moved either.
00319  *
00320  * Lots contain pointers to splits, and transactions contain 
00321  * pointers to splits.  Together, these form a graph, which may
00322  * be cyclic.  We want to walk the entire graph, and determine
00323  * whether there are any open lots in it.  The walk must be 
00324  * recursive,  and because it might be cyclic, we use a marker
00325  * to break the cycles.  
00326  */
00327 
00328 static gboolean trans_has_open_lot_tree (Transaction *trans);
00329 static gboolean lot_has_open_trans_tree (GNCLot *lot);
00330 
00331 static gboolean
00332 trans_has_open_lot_tree (Transaction *trans)
00333 {
00334    SplitList *split_list, *node;
00335 
00336    if (1 == trans->marker) return FALSE;
00337    if (2 == trans->marker) return TRUE;
00338    trans->marker = 1;
00339 
00340    split_list = xaccTransGetSplitList (trans);
00341    for (node = split_list; node; node=node->next)
00342    {
00343       Split *s = node->data;
00344       GNCLot *lot = s->lot;
00345       if (NULL == lot) continue;
00346       if ((FALSE == gnc_lot_is_closed(lot)) ||
00347           (lot_has_open_trans_tree (lot)))
00348       {
00349          trans->marker = 2;
00350          return TRUE;
00351       }
00352    }
00353    return FALSE;
00354 }
00355 
00356 static gboolean 
00357 lot_has_open_trans_tree (GNCLot *lot)
00358 {
00359    SplitList *split_list, *snode;
00360 
00361    if (1 == lot->marker) return FALSE;
00362    if (2 == lot->marker) return TRUE;
00363    lot->marker = 1;
00364 
00365    if (FALSE == gnc_lot_is_closed(lot))
00366    {
00367       lot->marker = 2;
00368       return TRUE;
00369    }
00370 
00371    split_list = gnc_lot_get_split_list (lot);
00372    for (snode = split_list; snode; snode=snode->next)
00373    {
00374       Split *s = snode->data;
00375       Transaction *trans = s->parent;
00376       if (trans_has_open_lot_tree (trans)) 
00377       {
00378          lot->marker = 2;
00379          return TRUE;
00380       }
00381    }
00382    return FALSE;
00383 }
00384 
00385 /* ================================================================ */
00386 /* The following routines remove 'open lots' and 'open transactions'
00387  * from the lists passed in.
00388  */
00389 
00390 static LotList *
00391 lot_list_preen_open_lots (LotList *lot_list)
00392 {
00393    LotList *lnode;
00394    ENTER (" ");
00395    for (lnode=lot_list; lnode; )
00396    {
00397       GNCLot *lot = lnode->data;
00398       LotList *lnext = lnode->next;
00399 
00400       if (lot_has_open_trans_tree (lot))
00401          lot_list = g_list_delete_link(lot_list, lnode);
00402       lnode = lnext;
00403    }
00404    LEAVE (" ");
00405    return lot_list;
00406 }
00407 
00408 static TransList *
00409 trans_list_preen_open_lots (TransList *trans_list)
00410 {
00411    TransList *tnode;
00412 
00413    ENTER (" ");
00414    for (tnode=trans_list; tnode; )
00415    {
00416       Transaction *trans = tnode->data;
00417       TransList *tnext = tnode->next;
00418 
00419       if (trans_has_open_lot_tree (trans))
00420       {
00421          trans_list = g_list_remove_link (trans_list, tnode);
00422          /* XXX freeing this node somehow leads to glib g_list
00423           * memory corruption which later takes down the system. 
00424           * I don't see why.  */
00425          /* g_list_free_1 (tnode); */
00426       }
00427       tnode = tnext;
00428    }
00429    LEAVE (" ");
00430    return trans_list;
00431 }
00432 
00433 /* ================================================================ */
00434 /* clear the markers for the above routines */
00435 
00436 static void
00437 clear_markers (Account *account, gpointer dummy)
00438 {
00439   GList *lp;
00440 
00441   if (!account) return;
00442 
00443   for (lp = xaccAccountGetSplitList(account); lp; lp = lp->next) {
00444     Split *s = lp->data;
00445     Transaction *trans = s->parent;
00446     GNCLot *lot = s->lot;
00447     trans->marker = 0;
00448     if (lot) lot->marker = 0;
00449   }
00450 }
00451 
00452 /* ================================================================ */
00453 /* Return a unique list of lots that are involved with the listed
00454  * transactions.
00455  */
00456 
00457 static LotList *
00458 create_lot_list_from_trans_list (TransList *trans_list)
00459 {
00460    LotList *lot_list = NULL;
00461    TransList *tnode;
00462 
00463    for (tnode=trans_list; tnode; tnode=tnode->next)
00464    {
00465       Transaction *trans = tnode->data;
00466       SplitList *split_list = xaccTransGetSplitList (trans);
00467       SplitList *snode;
00468       for (snode = split_list; snode; snode=snode->next)
00469       {
00470          Split *s = snode->data;
00471          GNCLot *lot = xaccSplitGetLot(s);
00472          if (NULL == lot) continue;
00473          if (g_list_find (lot_list, lot)) continue;
00474          lot_list = g_list_prepend (lot_list, lot);
00475       }
00476    }
00477    return lot_list;
00478 }
00479 
00480 /* ================================================================ */
00481 
00482 void 
00483 gnc_book_partition_pricedb (QofBook *dest_book, QofBook *src_book, QofQuery *query)
00484 {
00485    GNCPriceDB *src_pdb, *dest_pdb;
00486    GList *price_list, *pnode;
00487 
00488    if (!src_book || !dest_book || !query) return;
00489    ENTER (" src_book=%p dest_book=%p", src_book, dest_book);
00490 
00491    src_pdb = gnc_pricedb_get_db (src_book);
00492    dest_pdb = gnc_pricedb_get_db (dest_book);
00493 
00494    gnc_pricedb_begin_edit (src_pdb);
00495    gnc_pricedb_begin_edit (dest_pdb);
00496    gnc_pricedb_set_bulk_update (dest_pdb, TRUE);
00497 
00498    qof_query_set_book (query, src_book);
00499    price_list = qof_query_run (query);
00500 
00501 printf ("duude XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX prices\n");
00502    for (pnode = price_list; pnode; pnode=pnode->next)
00503    {
00504       GNCPrice *pr = pnode->data;
00505       gnc_book_insert_price (dest_book, pr);
00506    }
00507 
00508    gnc_pricedb_set_bulk_update (dest_pdb, FALSE);
00509    gnc_pricedb_commit_edit (dest_pdb);
00510    gnc_pricedb_commit_edit (src_pdb);
00511 
00512    LEAVE (" src_book=%p dest_book=%p", src_book, dest_book);
00513 }
00514 
00515 /* ================================================================ */
00516 
00517 void 
00518 gnc_book_partition_txn (QofBook *dest_book, QofBook *src_book, QofQuery *query)
00519 {
00520    gnc_commodity_table *src_tbl, *dst_tbl;
00521    Account *src_root, *dst_root;
00522    time_t now;
00523    TransList *trans_list, *tnode;
00524    LotList *lot_list, *lnode;
00525    QofInstance *book_inst;
00526 
00527    if (!src_book || !dest_book || !query) return;
00528    ENTER (" src_book=%p dest_book=%p", src_book, dest_book);
00529 
00530    /* First, copy the book's KVP tree */
00531    /* hack alert -- FIXME -- this should really be a merge, not a
00532     * clobber copy, but I am too lazy to write a kvp merge routine,
00533     * and it is not needed for the current usage. */
00534    kvp_frame_delete (qof_book_get_slots(dest_book));
00535    book_inst = (QofInstance*)dest_book;
00536    book_inst->kvp_data = kvp_frame_copy (qof_book_get_slots(src_book));
00537 
00538    /* Next, copy the commodity tables */
00539    src_tbl = gnc_commodity_table_get_table (src_book);
00540    dst_tbl = gnc_commodity_table_get_table (dest_book);
00541    gnc_commodity_table_copy (dst_tbl, src_tbl, dest_book);
00542 
00543    /* Next, copy all of the accounts */
00544    /* hack alert -- FIXME -- this should really be a merge, not a
00545     * clobber copy, but I am too lazy to write an account-group merge 
00546     * routine, and it is not needed for the current usage. */
00547    src_root = gnc_book_get_root_account (src_book);
00548    dst_root = gnc_book_get_root_account (dest_book);
00549    gnc_account_copy_children (dst_root, src_root);
00550 
00551    /* Next, run the query */
00552    xaccAccountBeginEdit (dst_root);
00553    xaccAccountBeginEdit (src_root);
00554    qof_query_set_book (query, src_book);
00555    trans_list = qof_query_run (query);
00556 
00557    /* Preen: remove open lots/ open trnasactions */
00558    gnc_account_foreach_descendant(src_root, clear_markers, NULL);
00559    trans_list = trans_list_preen_open_lots (trans_list);
00560    lot_list = create_lot_list_from_trans_list (trans_list);
00561    lot_list = lot_list_preen_open_lots (lot_list);
00562 
00563    /* Move closed lots over to destination. Do this before moving 
00564     * the txn's, so that the lots don't get trashed.  */
00565    for (lnode = lot_list; lnode; lnode = lnode->next)
00566    {
00567       GNCLot *lot = lnode->data;
00568       gnc_book_insert_lot (dest_book, lot);
00569    }
00570 
00571    /* Move the transactions over to the destination book. */
00572    for (tnode = trans_list; tnode; tnode=tnode->next)
00573    {
00574       Transaction *trans = tnode->data;
00575       gnc_book_insert_trans (dest_book, trans);
00576    }
00577 
00578    xaccAccountCommitEdit (src_root);
00579    xaccAccountCommitEdit (dst_root);
00580 
00581    /* Make note of the sibling books */
00582    now = time(0);
00583    gnc_kvp_bag_add (qof_book_get_slots(src_book), "gemini", now, 
00584                           "book_guid", qof_book_get_guid(dest_book),
00585                            NULL);
00586    gnc_kvp_bag_add (qof_book_get_slots(dest_book), "gemini", now, 
00587                           "book_guid", qof_book_get_guid(src_book),
00588                            NULL);
00589    LEAVE (" ");
00590 }
00591 
00592 /* ================================================================ */
00593 /* Find nearest equity account */
00594 
00595 static Account *
00596 find_nearest_equity_acct (Account *acc)
00597 {
00598    QofBook *book;
00599    GList *acc_list, *node;
00600    Account *parent, *root, *candidate;
00601 
00602    parent = gnc_account_get_parent (acc);
00603    g_return_val_if_fail (parent, NULL);
00604 
00605    /* See if we can find an equity account that is peered to this
00606     * account. If not, check succssively higher levels. */
00607    while (parent != NULL) {
00608      acc_list = gnc_account_get_children(parent);
00609      for (node=acc_list; node; node=node->next) {
00610        candidate = (Account *) node->data;
00611        if ((ACCT_TYPE_EQUITY == xaccAccountGetType (candidate)) &&
00612            gnc_commodity_equiv(xaccAccountGetCommodity(acc),
00613                                xaccAccountGetCommodity(candidate))) {
00614          return candidate;
00615        }
00616      }
00617      g_list_free(acc_list);
00618      parent = gnc_account_get_parent (parent);
00619    }
00620 
00621    /* If we got to here, then we are at the root account, and there is no 
00622     * equity account to be found.  So we need to create one. */
00623 
00624    book = gnc_account_get_book(acc);
00625    root = gnc_book_get_root_account(book);
00626    candidate = xaccMallocAccount (book);
00627    xaccAccountBeginEdit (candidate);
00628    gnc_account_append_child (root, candidate);
00629    xaccAccountSetType (candidate, ACCT_TYPE_EQUITY);
00630    xaccAccountSetName (candidate, xaccAccountGetTypeStr(ACCT_TYPE_EQUITY));
00631    xaccAccountSetCommodity (candidate, xaccAccountGetCommodity(acc));
00632    xaccAccountCommitEdit (candidate);
00633    
00634    return candidate;
00635 }
00636 
00637 /* ================================================================ */
00638 /* Traverse all accounts, get account balances */
00639 
00640 static void
00641 add_closing_balances (Account *parent, 
00642                       QofBook *open_book,
00643                       QofBook *closed_book,
00644                       Account *equity_account,
00645                       Timespec *post_date, Timespec *date_entered, 
00646                       const char *desc)
00647 {
00648    GList *acc_list, *node;
00649 
00650    if (!parent) return;
00651 
00652    ENTER (" enter=%s post=%s desc=%s", gnc_print_date(*date_entered),
00653        gnc_print_date (*post_date), desc);
00654    xaccAccountBeginEdit (equity_account);
00655 
00656    /* Walk accounts in closed book */
00657    acc_list = gnc_account_get_children(parent);
00658    for (node=acc_list; node; node=node->next)
00659    {
00660       KvpFrame *cwd;
00661       Account *twin;
00662       Account * candidate = (Account *) node->data;
00663       GNCAccountType tip = xaccAccountGetType (candidate);
00664 
00665       /* Find the peer account of this account in the open book  */
00666       twin = xaccAccountLookupTwin (candidate, open_book);
00667 
00668       /* -------------------------------- */
00669       /* Add KVP to open account, indicating the progenitor
00670        * of this account. */
00671       xaccAccountBeginEdit (twin);
00672       cwd = xaccAccountGetSlots (twin);
00673       kvp_frame_set_guid (cwd, "/book/prev-acct", xaccAccountGetGUID (candidate));
00674       kvp_frame_set_guid (cwd, "/book/prev-book", qof_book_get_guid(closed_book));
00675 
00676       qof_instance_set_slots(QOF_INSTANCE(twin), twin->inst.kvp_data);
00677       
00678       /* -------------------------------- */
00679       /* Add KVP to closed account, indicating where 
00680        * the next book is. */
00681       xaccAccountBeginEdit (candidate);
00682       cwd = xaccAccountGetSlots (candidate);
00683       kvp_frame_set_guid (cwd, "/book/next-book", qof_book_get_guid(open_book));
00684       kvp_frame_set_guid (cwd, "/book/next-acct", xaccAccountGetGUID (twin));
00685 
00686       qof_instance_set_slots(QOF_INSTANCE(candidate), candidate->inst.kvp_data);
00687 
00688       /* -------------------------------- */
00689       /* We need to carry a balance on any account that is not
00690        * and income or expense or equity account */
00691       if ((ACCT_TYPE_INCOME != tip) && (ACCT_TYPE_EXPENSE != tip) &&
00692           (ACCT_TYPE_EQUITY != tip)) 
00693       {
00694          gnc_numeric baln;
00695          baln = xaccAccountGetBalance (candidate);
00696 
00697          /* Don't bother with creating the equity balance if its zero */
00698          if (FALSE == gnc_numeric_zero_p(baln)) 
00699          {
00700             Split *se, *st;
00701             Transaction *trans;
00702             Account *equity;
00703    
00704             /* Find the equity account into which we'll poke the 
00705              * balancing transaction */
00706             if (NULL == equity_account)
00707             {
00708                equity = find_nearest_equity_acct (twin);
00709                xaccAccountBeginEdit (equity);
00710             }
00711             else
00712             {
00713                equity = equity_account;
00714             }
00715    
00716             /* -------------------------------- */
00717             /* Create the balancing transaction */
00718             trans = xaccMallocTransaction (open_book);
00719             xaccTransBeginEdit (trans);
00720    
00721             xaccTransSetDatePostedTS (trans, post_date);
00722             xaccTransSetDateEnteredTS (trans, date_entered);
00723             xaccTransSetDescription (trans, desc);
00724             xaccTransSetCurrency (trans, xaccAccountGetCommodity(equity));
00725    
00726             st = xaccMallocSplit(open_book);
00727             xaccTransAppendSplit(trans, st);
00728             xaccAccountInsertSplit (twin, st);
00729             
00730             se = xaccMallocSplit(open_book);
00731             xaccTransAppendSplit(trans, se);
00732             xaccAccountInsertSplit (equity, se);
00733    
00734             xaccSplitSetAmount (st, baln);
00735             xaccSplitSetValue (st, baln);
00736             xaccSplitSetAmount (se, gnc_numeric_neg(baln));
00737             xaccSplitSetValue (se, gnc_numeric_neg(baln));
00738    
00739             /* Add KVP data showing where the balancing 
00740              * transaction came from */
00741             cwd = xaccTransGetSlots (trans);
00742             kvp_frame_set_guid (cwd, "/book/closed-book", qof_book_get_guid(closed_book));
00743             kvp_frame_set_guid (cwd, "/book/closed-acct", xaccAccountGetGUID(candidate));
00744             
00745             xaccTransCommitEdit (trans);
00746    
00747             if (NULL == equity_account)
00748             {
00749                xaccAccountCommitEdit (equity);
00750             }
00751             /* -------------------------------- */
00752             /* Add KVP to closed account, indicating where the
00753              * balance was carried forward to. */
00754             cwd = xaccAccountGetSlots (candidate);
00755             kvp_frame_set_guid (cwd, "/book/balancing-trans", xaccTransGetGUID(trans));
00756          }
00757       }
00758 
00759       /* We left an open dangling above ... */
00760       xaccAccountCommitEdit (candidate);
00761       xaccAccountCommitEdit (twin);
00762 
00763       /* Recurse down to the children */
00764       if (gnc_account_n_children(candidate) > 0) 
00765       {
00766          PINFO ("add closing baln to subaccts of %s", 
00767                 xaccAccountGetDescription(candidate));
00768          add_closing_balances (candidate, open_book, closed_book,
00769                           equity_account,
00770                           post_date, date_entered, desc);
00771       }
00772    }
00773    g_list_free(acc_list);
00774    xaccAccountCommitEdit (equity_account);
00775    LEAVE (" ");
00776 }
00777 
00778 /* ================================================================ */
00779 
00780 static void 
00781 period_begin_edit (QofBook *src_book, QofBook *dest_book)
00782 {
00783     /*
00784    QofBackend *be;
00785    be = qof_book_get_backend(src_book);
00786    if (be && be->begin)
00787    {
00788       // (*be->begin)(be, GNC_ID_PERIOD, dest_book);
00789    }
00790     */
00791 }
00792    
00793 static void 
00794 period_commit_edit (QofBook *src_book, QofBook *dest_book)
00795 {
00796     /*
00797    QofBackend *be;
00798    be = qof_book_get_backend(src_book);
00799    if (be && be->commit)
00800    {
00801       // (*be->commit)(be, GNC_ID_PERIOD, dest_book);
00802    }
00803     */
00804 }
00805 
00806 /* ================================================================ */
00807 /* Split a book into two by date */
00808 
00809 QofBook * 
00810 gnc_book_close_period (QofBook *existing_book, Timespec calve_date,
00811                        Account *equity_account,
00812                        const char * memo)
00813 {
00814    QofQuery *txn_query, *prc_query;
00815    QofQueryPredData *pred_data;
00816    GSList *param_list;
00817    QofBook *closing_book;
00818    KvpFrame *exist_cwd, *partn_cwd;
00819    Timespec ts;
00820 
00821    if (!existing_book) return NULL;
00822    ENTER (" date=%s memo=%s", gnc_print_date(calve_date), memo);
00823 
00824    /* Setup closing book */
00825    closing_book = qof_book_new();
00826    qof_book_set_backend (closing_book, qof_book_get_backend(existing_book));
00827    qof_book_mark_closed(closing_book);
00828 
00829    period_begin_edit (existing_book, closing_book);
00830 
00831    /* Get all transactions that are *earlier* than the calve date,
00832     * and put them in the new book.  */
00833    txn_query = qof_query_create_for (GNC_ID_TRANS);
00834    pred_data = qof_query_date_predicate (QOF_COMPARE_LTE,
00835                                          QOF_DATE_MATCH_NORMAL,
00836                                          calve_date);
00837    param_list = qof_query_build_param_list (TRANS_DATE_POSTED, NULL);
00838    qof_query_add_term (txn_query, param_list, pred_data, QOF_QUERY_FIRST_TERM);
00839 
00840    gnc_book_partition_txn (closing_book, existing_book, txn_query);
00841    qof_query_destroy (txn_query);
00842 
00843    /* Move prices over too */
00844    prc_query = qof_query_create_for (GNC_ID_PRICE);
00845    pred_data = qof_query_date_predicate (QOF_COMPARE_LTE,
00846                                          QOF_DATE_MATCH_NORMAL,
00847                                          calve_date);
00848    param_list = qof_query_build_param_list (PRICE_DATE, NULL);
00849    qof_query_add_term (prc_query, param_list, pred_data, QOF_QUERY_FIRST_TERM);
00850 
00851    gnc_book_partition_pricedb (closing_book, existing_book, prc_query);
00852    qof_query_destroy (prc_query);
00853 
00854    /* Now add the various identifying kvp's */
00855    /* cwd == 'current working directory' */
00856    exist_cwd = qof_book_get_slots(existing_book);
00857    partn_cwd = qof_book_get_slots(closing_book);
00858    
00859    /* Mark the boundary date between the books */
00860    kvp_frame_set_timespec (exist_cwd, "/book/open-date", calve_date);
00861    kvp_frame_set_timespec (partn_cwd, "/book/close-date", calve_date);
00862 
00863    /* Mark partition as being closed */
00864    ts.tv_sec = time(0);
00865    ts.tv_nsec = 0;
00866    kvp_frame_set_timespec (partn_cwd, "/book/log-date", ts);
00867 
00868    /* Set up pointers to each book from the other. */
00869    kvp_frame_set_guid (partn_cwd, "/book/next-book", qof_book_get_guid(existing_book));
00870    kvp_frame_set_guid (exist_cwd, "/book/prev-book", qof_book_get_guid(closing_book));
00871 
00872    /* add in transactions to equity accounts that will
00873     * hold the colsing balances */
00874    add_closing_balances (gnc_book_get_root_account(closing_book), 
00875                         existing_book, closing_book,
00876                         equity_account,
00877                         &calve_date, &ts, memo);
00878 
00879    period_commit_edit (existing_book, closing_book);
00880 
00881    LEAVE (" ");
00882    return closing_book;
00883 }
00884 
00885 /* ============================= END OF FILE ====================== */

Generated on Fri Jul 25 05:05:42 2008 for GnuCash by  doxygen 1.5.2