|
GnuCash 2.3.0
|
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 ====================== */
1.7.4