GnuCash 2.3.0
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-pricedb.h"
00041 #include "gnc-pricedb-p.h"
00042 #include "Period.h"
00043 #include "Transaction.h"
00044 #include "TransactionP.h"
00045 
00046 /* This static indicates the debugging module that this .o belongs to.  */
00047 static QofLogModule log_module = GNC_MOD_BOOK;
00048 
00049 /* ================================================================ */
00050 
00051 static inline Account *
00052 xaccAccountLookupTwin (Account *acc,  QofBook *book)
00053 {
00054     return (Account *) qof_instance_lookup_twin (QOF_INSTANCE(acc), book);
00055 }
00056 
00057 /* ================================================================ */
00058 /* Reparent transaction to new book.  This routine does this by
00059  * deleting the transaction in the old book, and creating a copy
00060  * in the new book.  While technically correct, this is maybe too
00061  * much churn on the backend ...
00062  */
00063 
00064 void
00065 gnc_book_insert_trans_clobber (QofBook *book, Transaction *trans)
00066 {
00067     QofCollection *col;
00068     Transaction *newtrans;
00069     GList *node;
00070 
00071     if (!trans || !book) return;
00072 
00073     /* If this is the same book, its a no-op. */
00074     if (qof_instance_get_book(trans) == book) return;
00075 
00076     ENTER ("trans=%p %s", trans, trans->description);
00077     newtrans = xaccDupeTransaction (trans);
00078     for (node = newtrans->splits; node; node = node->next)
00079     {
00080         Split *s = node->data;
00081         s->parent = newtrans;
00082     }
00083 
00084     /* Utterly wipe out the transaction from the old book. */
00085     xaccTransBeginEdit (trans);
00086     xaccTransDestroy (trans);
00087     xaccTransCommitEdit (trans);
00088 
00089     /* Fiddle the transaction into place in the new book */
00090     col = qof_book_get_collection (book, GNC_ID_TRANS);
00091     qof_collection_insert_entity (col, &newtrans->inst);
00092     qof_instance_set_book(newtrans, book);
00093 
00094     col = qof_book_get_collection (book, GNC_ID_SPLIT);
00095     xaccTransBeginEdit (newtrans);
00096     for (node = newtrans->splits; node; node = node->next)
00097     {
00098         Account *twin;
00099         Split *s = node->data;
00100 
00101         /* move the split into the new book ... */
00102         qof_instance_set_book(s, book);
00103         qof_collection_insert_entity(col, &s->inst);
00104 
00105         /* find the twin account, and re-parent to that. */
00106         twin = xaccAccountLookupTwin (s->acc, book);
00107         if (!twin)
00108         {
00109             PERR ("near-fatal: twin account not found");
00110         }
00111         else
00112         {
00113             xaccAccountInsertSplit (twin, s);
00114             g_object_set(twin, "sort-dirty", TRUE, "balance-dirty", TRUE, NULL);
00115         }
00116     }
00117 
00118     xaccTransCommitEdit (newtrans);
00119     qof_event_gen (&newtrans->inst, QOF_EVENT_CREATE, NULL);
00120     LEAVE ("trans=%p %s", trans, trans->description);
00121 }
00122 
00123 /* ================================================================ */
00124 /* Reparent transaction to new book.  This routine does this by
00125  * moving GncGUID's to the new book's entity tables.
00126  */
00127 
00128 void
00129 gnc_book_insert_trans (QofBook *book, Transaction *trans)
00130 {
00131     QofCollection *col;
00132     QofBook *trans_book;
00133     GList *node;
00134 
00135     if (!trans || !book) return;
00136 
00137     /* If this is the same book, its a no-op. */
00138     trans_book = qof_instance_get_book(trans);
00139     if (trans_book == book) return;
00140 
00141     /* If the old and new book don't share backends, then clobber-copy;
00142      * i.e. destroy it in one backend, create it in another.  */
00143     if (qof_book_get_backend(book) != qof_book_get_backend(trans_book))
00144     {
00145         gnc_book_insert_trans_clobber (book, trans);
00146         return;
00147     }
00148     ENTER ("trans=%p %s", trans, trans->description);
00149 
00150     /* Fiddle the transaction into place in the new book */
00151     xaccTransBeginEdit (trans);
00152 
00153     col = qof_book_get_collection (book, GNC_ID_TRANS);
00154     qof_instance_set_book(trans, book);
00155     qof_collection_insert_entity (col, &trans->inst);
00156 
00157     col = qof_book_get_collection (book, GNC_ID_SPLIT);
00158     for (node = trans->splits; node; node = node->next)
00159     {
00160         Account *twin;
00161         Split *s = node->data;
00162 
00163         /* Move the splits over (only if they haven't already been moved). */
00164         if (qof_instance_get_book(s) != book)
00165         {
00166             qof_instance_set_book(s, book);
00167             qof_collection_insert_entity (col, &s->inst);
00168         }
00169 
00170         /* Find the twin account, and re-parent to that. */
00171         twin = xaccAccountLookupTwin (s->acc, book);
00172         if (!twin)
00173         {
00174             PERR ("near-fatal: twin account not found");
00175         }
00176         else
00177         {
00178             /* Move the split too, if it hasn't been moved already */
00179             if (s->acc != twin)
00180             {
00181                 xaccAccountInsertSplit (twin, s);
00182                 g_object_set(twin, "sort-dirty", TRUE, "balance-dirty", TRUE, NULL);
00183             }
00184         }
00185     }
00186 
00187     xaccTransCommitEdit (trans);
00188     qof_event_gen (&trans->inst, QOF_EVENT_MODIFY, NULL);
00189     LEAVE ("trans=%p %s", trans, trans->description);
00190 }
00191 
00192 /* ================================================================ */
00193 /* Reparent lot to new book.  This routine does this by
00194  * completely deleting and recreating the lot.
00195  */
00196 
00197 void
00198 gnc_book_insert_lot_clobber (QofBook *book, GNCLot *lot)
00199 {
00200     PERR ("Not Implemented");
00201 }
00202 
00203 /* ================================================================ */
00204 /* Reparent lot to new book.  This routine does this by
00205  * moving GncGUID's to the new book's entity tables.
00206  */
00207 
00208 void
00209 gnc_book_insert_lot (QofBook *book, GNCLot *lot)
00210 {
00211     QofCollection *col;
00212     SplitList *snode;
00213     Account *twin;
00214 
00215     if (!lot || !book) return;
00216 
00217     /* If this is the same book, its a no-op. */
00218     if (gnc_lot_get_book(lot) == book) return;
00219 
00220     if (qof_book_get_backend(book) !=
00221             qof_book_get_backend(gnc_lot_get_book(lot)))
00222     {
00223         gnc_book_insert_lot_clobber (book, lot);
00224         return;
00225     }
00226     ENTER ("lot=%p", lot);
00227 
00228     col = qof_book_get_collection (book, GNC_ID_LOT);
00229     qof_instance_set_book(lot, book);
00230     qof_collection_insert_entity (col, QOF_INSTANCE(lot));
00231 
00232     /* Move the splits over (only if they haven't already been moved). */
00233     col = qof_book_get_collection (book, GNC_ID_SPLIT);
00234     for (snode = gnc_lot_get_split_list(lot); snode; snode = snode->next)
00235     {
00236         Split *s = snode->data;
00237         if (qof_instance_get_book(s) != book)
00238         {
00239             qof_instance_set_book(s, book);
00240             qof_collection_insert_entity (col, &s->inst);
00241         }
00242     }
00243 
00244     twin = xaccAccountLookupTwin (gnc_lot_get_account(lot), book);
00245     if (!twin)
00246     {
00247         PERR ("near-fatal: twin account not found");
00248     }
00249     else
00250     {
00251         xaccAccountInsertLot (twin, lot);
00252     }
00253     LEAVE ("lot=%p", lot);
00254 }
00255 
00256 /* ================================================================ */
00257 
00258 void
00259 gnc_book_insert_price (QofBook *book, GNCPrice *pr)
00260 {
00261     QofCollection *col;
00262     QofBook *pr_book;
00263 
00264     if (!pr || !book) return;
00265 
00266     /* If this is the same book, its a no-op. */
00267     pr_book = qof_instance_get_book(pr);
00268     if (pr_book == book) return;
00269 
00270     /* If the old and new book don't share backends, then clobber-copy;
00271      * i.e. destroy it in one backend, create it in another.  */
00272     if (qof_book_get_backend(book) != qof_book_get_backend(pr_book))
00273     {
00274         gnc_book_insert_price_clobber (book, pr);
00275         return;
00276     }
00277     ENTER ("price=%p", pr);
00278 
00279     /* Fiddle the price into place in the new book */
00280     gnc_price_ref (pr);
00281     gnc_price_begin_edit (pr);
00282 
00283     col = qof_book_get_collection (book, GNC_ID_PRICE);
00284     qof_instance_set_book(pr, book);
00285     qof_collection_insert_entity (col, &pr->inst);
00286 
00287     gnc_pricedb_remove_price (pr->db, pr);
00288     gnc_pricedb_add_price (gnc_pricedb_get_db (book), pr);
00289 
00290     gnc_price_commit_edit (pr);
00291     gnc_price_unref (pr);
00292 
00293     LEAVE ("price=%p", pr);
00294 }
00295 
00296 /* ================================================================ */
00297 
00298 void
00299 gnc_book_insert_price_clobber (QofBook *book, GNCPrice *pr)
00300 {
00301     PERR ("Not Implemented");
00302 }
00303 
00304 /* ================================================================ */
00305 /* The following routines determine whether a given lot or
00306  * transaction is linked or related to another lot that is 'open'.
00307  * These return true if so.
00308  *
00309  * An 'open transaction' is a transaction that has a split
00310  * that belongs to an 'open lot'.  An 'open lot' is one that
00311  * is not closed, OR ONE THAT HAS a split in it that belongs to
00312  * an open transaction.
00313  *
00314  * The need for this recursive definition is that some lots,
00315  * even though themselves closed, are participants in transactions
00316  * that cannot be moved to a closed book, and thus, by association
00317  * can't be moved either.
00318  *
00319  * Lots contain pointers to splits, and transactions contain
00320  * pointers to splits.  Together, these form a graph, which may
00321  * be cyclic.  We want to walk the entire graph, and determine
00322  * whether there are any open lots in it.  The walk must be
00323  * recursive,  and because it might be cyclic, we use a marker
00324  * to break the cycles.
00325  */
00326 
00327 static gboolean trans_has_open_lot_tree (Transaction *trans);
00328 static gboolean lot_has_open_trans_tree (GNCLot *lot);
00329 
00330 static gboolean
00331 trans_has_open_lot_tree (Transaction *trans)
00332 {
00333     SplitList *split_list, *node;
00334 
00335     if (1 == trans->marker) return FALSE;
00336     if (2 == trans->marker) return TRUE;
00337     trans->marker = 1;
00338 
00339     split_list = xaccTransGetSplitList (trans);
00340     for (node = split_list; node; node = node->next)
00341     {
00342         Split *s = node->data;
00343         GNCLot *lot = s->lot;
00344         if (NULL == lot) continue;
00345         if ((FALSE == gnc_lot_is_closed(lot)) ||
00346                 (lot_has_open_trans_tree (lot)))
00347         {
00348             trans->marker = 2;
00349             return TRUE;
00350         }
00351     }
00352     return FALSE;
00353 }
00354 
00355 static gboolean
00356 lot_has_open_trans_tree (GNCLot *lot)
00357 {
00358     SplitList *split_list, *snode;
00359 
00360     if (1 == gnc_lot_get_marker(lot)) return FALSE;
00361     if (2 == gnc_lot_get_marker(lot)) return TRUE;
00362     gnc_lot_set_marker(lot, 1);
00363 
00364     if (FALSE == gnc_lot_is_closed(lot))
00365     {
00366         gnc_lot_set_marker(lot, 2);
00367         return TRUE;
00368     }
00369 
00370     split_list = gnc_lot_get_split_list (lot);
00371     for (snode = split_list; snode; snode = snode->next)
00372     {
00373         Split *s = snode->data;
00374         Transaction *trans = s->parent;
00375         if (trans_has_open_lot_tree (trans))
00376         {
00377             gnc_lot_set_marker(lot, 2);
00378             return TRUE;
00379         }
00380     }
00381     return FALSE;
00382 }
00383 
00384 /* ================================================================ */
00385 /* The following routines remove 'open lots' and 'open transactions'
00386  * from the lists passed in.
00387  */
00388 
00389 static LotList *
00390 lot_list_preen_open_lots (LotList *lot_list)
00391 {
00392     LotList *lnode;
00393     ENTER (" ");
00394     for (lnode = lot_list; lnode; )
00395     {
00396         GNCLot *lot = lnode->data;
00397         LotList *lnext = lnode->next;
00398 
00399         if (lot_has_open_trans_tree (lot))
00400             lot_list = g_list_delete_link(lot_list, lnode);
00401         lnode = lnext;
00402     }
00403     LEAVE (" ");
00404     return lot_list;
00405 }
00406 
00407 static TransList *
00408 trans_list_preen_open_lots (TransList *trans_list)
00409 {
00410     TransList *tnode;
00411 
00412     ENTER (" ");
00413     for (tnode = trans_list; tnode; )
00414     {
00415         Transaction *trans = tnode->data;
00416         TransList *tnext = tnode->next;
00417 
00418         if (trans_has_open_lot_tree (trans))
00419         {
00420             trans_list = g_list_remove_link (trans_list, tnode);
00421             /* XXX freeing this node somehow leads to glib g_list
00422              * memory corruption which later takes down the system.
00423              * I don't see why.  */
00424             /* g_list_free_1 (tnode); */
00425         }
00426         tnode = tnext;
00427     }
00428     LEAVE (" ");
00429     return trans_list;
00430 }
00431 
00432 /* ================================================================ */
00433 /* clear the markers for the above routines */
00434 
00435 static void
00436 clear_markers (Account *account, gpointer dummy)
00437 {
00438     GList *lp;
00439 
00440     if (!account) return;
00441 
00442     for (lp = xaccAccountGetSplitList(account); lp; lp = lp->next)
00443     {
00444         Split *s = lp->data;
00445         Transaction *trans = s->parent;
00446         GNCLot *lot = s->lot;
00447         trans->marker = 0;
00448         if (lot) gnc_lot_set_marker(lot, 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     {
00609         acc_list = gnc_account_get_children(parent);
00610         for (node = acc_list; node; node = node->next)
00611         {
00612             candidate = (Account *) node->data;
00613             if ((ACCT_TYPE_EQUITY == xaccAccountGetType (candidate)) &&
00614                     gnc_commodity_equiv(xaccAccountGetCommodity(acc),
00615                                         xaccAccountGetCommodity(candidate)))
00616             {
00617                 return candidate;
00618             }
00619         }
00620         g_list_free(acc_list);
00621         parent = gnc_account_get_parent (parent);
00622     }
00623 
00624     /* If we got to here, then we are at the root account, and there is no
00625      * equity account to be found.  So we need to create one. */
00626 
00627     book = gnc_account_get_book(acc);
00628     root = gnc_book_get_root_account(book);
00629     candidate = xaccMallocAccount (book);
00630     xaccAccountBeginEdit (candidate);
00631     gnc_account_append_child (root, candidate);
00632     xaccAccountSetType (candidate, ACCT_TYPE_EQUITY);
00633     xaccAccountSetName (candidate, xaccAccountGetTypeStr(ACCT_TYPE_EQUITY));
00634     xaccAccountSetCommodity (candidate, xaccAccountGetCommodity(acc));
00635     xaccAccountCommitEdit (candidate);
00636 
00637     return candidate;
00638 }
00639 
00640 /* ================================================================ */
00641 /* Traverse all accounts, get account balances */
00642 
00643 static void
00644 add_closing_balances (Account *parent,
00645                       QofBook *open_book,
00646                       QofBook *closed_book,
00647                       Account *equity_account,
00648                       Timespec *post_date, Timespec *date_entered,
00649                       const char *desc)
00650 {
00651     GList *acc_list, *node;
00652 
00653     if (!parent) return;
00654 
00655     ENTER (" enter=%s post=%s desc=%s", gnc_print_date(*date_entered),
00656            gnc_print_date (*post_date), desc);
00657     xaccAccountBeginEdit (equity_account);
00658 
00659     /* Walk accounts in closed book */
00660     acc_list = gnc_account_get_children(parent);
00661     for (node = acc_list; node; node = node->next)
00662     {
00663         KvpFrame *cwd;
00664         Account *twin;
00665         Account * candidate = (Account *) node->data;
00666         GNCAccountType tip = xaccAccountGetType (candidate);
00667 
00668         /* Find the peer account of this account in the open book  */
00669         twin = xaccAccountLookupTwin (candidate, open_book);
00670 
00671         /* -------------------------------- */
00672         /* Add KVP to open account, indicating the progenitor
00673          * of this account. */
00674         xaccAccountBeginEdit (twin);
00675         cwd = xaccAccountGetSlots (twin);
00676         kvp_frame_set_guid (cwd, "/book/prev-acct", qof_entity_get_guid (QOF_INSTANCE(candidate)));
00677         kvp_frame_set_guid (cwd, "/book/prev-book", qof_book_get_guid(closed_book));
00678 
00679         qof_instance_set_slots(QOF_INSTANCE(twin), twin->inst.kvp_data);
00680 
00681         /* -------------------------------- */
00682         /* Add KVP to closed account, indicating where
00683          * the next book is. */
00684         xaccAccountBeginEdit (candidate);
00685         cwd = xaccAccountGetSlots (candidate);
00686         kvp_frame_set_guid (cwd, "/book/next-book", qof_book_get_guid(open_book));
00687         kvp_frame_set_guid (cwd, "/book/next-acct", qof_entity_get_guid (QOF_INSTANCE(twin)));
00688 
00689         qof_instance_set_slots(QOF_INSTANCE(candidate), candidate->inst.kvp_data);
00690 
00691         /* -------------------------------- */
00692         /* We need to carry a balance on any account that is not
00693          * and income or expense or equity account */
00694         if ((ACCT_TYPE_INCOME != tip) && (ACCT_TYPE_EXPENSE != tip) &&
00695                 (ACCT_TYPE_EQUITY != tip && ACCT_TYPE_TRADING != tip))
00696         {
00697             gnc_numeric baln;
00698             baln = xaccAccountGetBalance (candidate);
00699 
00700             /* Don't bother with creating the equity balance if its zero */
00701             if (FALSE == gnc_numeric_zero_p(baln))
00702             {
00703                 Split *se, *st;
00704                 Transaction *trans;
00705                 Account *equity;
00706 
00707                 /* Find the equity account into which we'll poke the
00708                  * balancing transaction */
00709                 if (NULL == equity_account)
00710                 {
00711                     equity = find_nearest_equity_acct (twin);
00712                     xaccAccountBeginEdit (equity);
00713                 }
00714                 else
00715                 {
00716                     equity = equity_account;
00717                 }
00718 
00719                 /* -------------------------------- */
00720                 /* Create the balancing transaction */
00721                 trans = xaccMallocTransaction (open_book);
00722                 xaccTransBeginEdit (trans);
00723 
00724                 xaccTransSetDatePostedTS (trans, post_date);
00725                 xaccTransSetDateEnteredTS (trans, date_entered);
00726                 xaccTransSetDescription (trans, desc);
00727                 xaccTransSetCurrency (trans, xaccAccountGetCommodity(equity));
00728 
00729                 st = xaccMallocSplit(open_book);
00730                 xaccTransAppendSplit(trans, st);
00731                 xaccAccountInsertSplit (twin, st);
00732 
00733                 se = xaccMallocSplit(open_book);
00734                 xaccTransAppendSplit(trans, se);
00735                 xaccAccountInsertSplit (equity, se);
00736 
00737                 xaccSplitSetAmount (st, baln);
00738                 xaccSplitSetValue (st, baln);
00739                 xaccSplitSetAmount (se, gnc_numeric_neg(baln));
00740                 xaccSplitSetValue (se, gnc_numeric_neg(baln));
00741 
00742                 /* Add KVP data showing where the balancing
00743                  * transaction came from */
00744                 cwd = xaccTransGetSlots (trans);
00745                 kvp_frame_set_guid (cwd, "/book/closed-book", qof_book_get_guid(closed_book));
00746                 kvp_frame_set_guid (cwd, "/book/closed-acct", qof_entity_get_guid(QOF_INSTANCE(candidate)));
00747 
00748                 xaccTransCommitEdit (trans);
00749 
00750                 if (NULL == equity_account)
00751                 {
00752                     xaccAccountCommitEdit (equity);
00753                 }
00754                 /* -------------------------------- */
00755                 /* Add KVP to closed account, indicating where the
00756                  * balance was carried forward to. */
00757                 cwd = xaccAccountGetSlots (candidate);
00758                 kvp_frame_set_guid (cwd, "/book/balancing-trans", xaccTransGetGUID(trans));
00759             }
00760         }
00761 
00762         /* We left an open dangling above ... */
00763         xaccAccountCommitEdit (candidate);
00764         xaccAccountCommitEdit (twin);
00765 
00766         /* Recurse down to the children */
00767         if (gnc_account_n_children(candidate) > 0)
00768         {
00769             PINFO ("add closing baln to subaccts of %s",
00770                    xaccAccountGetDescription(candidate));
00771             add_closing_balances (candidate, open_book, closed_book,
00772                                   equity_account,
00773                                   post_date, date_entered, desc);
00774         }
00775     }
00776     g_list_free(acc_list);
00777     xaccAccountCommitEdit (equity_account);
00778     LEAVE (" ");
00779 }
00780 
00781 /* ================================================================ */
00782 
00783 static void
00784 period_begin_edit (QofBook *src_book, QofBook *dest_book)
00785 {
00786     /*
00787        QofBackend *be;
00788        be = qof_book_get_backend(src_book);
00789        if (be && be->begin)
00790        {
00791       // (*be->begin)(be, GNC_ID_PERIOD, dest_book);
00792        }
00793     */
00794 }
00795 
00796 static void
00797 period_commit_edit (QofBook *src_book, QofBook *dest_book)
00798 {
00799     /*
00800        QofBackend *be;
00801        be = qof_book_get_backend(src_book);
00802        if (be && be->commit)
00803        {
00804       // (*be->commit)(be, GNC_ID_PERIOD, dest_book);
00805        }
00806     */
00807 }
00808 
00809 /* ================================================================ */
00810 /* Split a book into two by date */
00811 
00812 QofBook *
00813 gnc_book_close_period (QofBook *existing_book, Timespec calve_date,
00814                        Account *equity_account,
00815                        const char * memo)
00816 {
00817     QofQuery *txn_query, *prc_query;
00818     QofQueryPredData *pred_data;
00819     GSList *param_list;
00820     QofBook *closing_book;
00821     KvpFrame *exist_cwd, *partn_cwd;
00822     Timespec ts;
00823 
00824     if (!existing_book) return NULL;
00825     ENTER (" date=%s memo=%s", gnc_print_date(calve_date), memo);
00826 
00827     /* Setup closing book */
00828     closing_book = qof_book_new();
00829     qof_book_set_backend (closing_book, qof_book_get_backend(existing_book));
00830     qof_book_mark_closed(closing_book);
00831 
00832     period_begin_edit (existing_book, closing_book);
00833 
00834     /* Get all transactions that are *earlier* than the calve date,
00835      * and put them in the new book.  */
00836     txn_query = qof_query_create_for (GNC_ID_TRANS);
00837     pred_data = qof_query_date_predicate (QOF_COMPARE_LTE,
00838                                           QOF_DATE_MATCH_NORMAL,
00839                                           calve_date);
00840     param_list = qof_query_build_param_list (TRANS_DATE_POSTED, NULL);
00841     qof_query_add_term (txn_query, param_list, pred_data, QOF_QUERY_FIRST_TERM);
00842 
00843     gnc_book_partition_txn (closing_book, existing_book, txn_query);
00844     qof_query_destroy (txn_query);
00845 
00846     /* Move prices over too */
00847     prc_query = qof_query_create_for (GNC_ID_PRICE);
00848     pred_data = qof_query_date_predicate (QOF_COMPARE_LTE,
00849                                           QOF_DATE_MATCH_NORMAL,
00850                                           calve_date);
00851     param_list = qof_query_build_param_list (PRICE_DATE, NULL);
00852     qof_query_add_term (prc_query, param_list, pred_data, QOF_QUERY_FIRST_TERM);
00853 
00854     gnc_book_partition_pricedb (closing_book, existing_book, prc_query);
00855     qof_query_destroy (prc_query);
00856 
00857     /* Now add the various identifying kvp's */
00858     /* cwd == 'current working directory' */
00859     exist_cwd = qof_book_get_slots(existing_book);
00860     partn_cwd = qof_book_get_slots(closing_book);
00861 
00862     /* Mark the boundary date between the books */
00863     kvp_frame_set_timespec (exist_cwd, "/book/open-date", calve_date);
00864     kvp_frame_set_timespec (partn_cwd, "/book/close-date", calve_date);
00865 
00866     /* Mark partition as being closed */
00867     ts.tv_sec = time(0);
00868     ts.tv_nsec = 0;
00869     kvp_frame_set_timespec (partn_cwd, "/book/log-date", ts);
00870 
00871     /* Set up pointers to each book from the other. */
00872     kvp_frame_set_guid (partn_cwd, "/book/next-book", qof_book_get_guid(existing_book));
00873     kvp_frame_set_guid (exist_cwd, "/book/prev-book", qof_book_get_guid(closing_book));
00874 
00875     /* add in transactions to equity accounts that will
00876      * hold the colsing balances */
00877     add_closing_balances (gnc_book_get_root_account(closing_book),
00878                           existing_book, closing_book,
00879                           equity_account,
00880                           &calve_date, &ts, memo);
00881 
00882     period_commit_edit (existing_book, closing_book);
00883 
00884     LEAVE (" ");
00885     return closing_book;
00886 }
00887 
00888 /* ============================= END OF FILE ====================== */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines