|
GnuCash 2.4.99
|
00001 /********************************************************************\ 00002 * gnc-lot.c -- AR/AP invoices; inventory lots; stock lots * 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 /* 00023 * FILE: 00024 * gnc-lot.c 00025 * 00026 * FUNCTION: 00027 * Lots implement the fundamental conceptual idea behind invoices, 00028 * inventory lots, and stock market investment lots. See the file 00029 * src/doc/lots.txt for implementation overview. 00030 * 00031 * XXX Lots are not currently treated in a correct transactional 00032 * manner. There's now a per-Lot dirty flag in the QofInstance, but 00033 * this code still needs to emit the correct signals when a lot has 00034 * changed. This is true both in the Scrub2.c and in 00035 * src/gnome/dialog-lot-viewer.c 00036 * 00037 * HISTORY: 00038 * Created by Linas Vepstas May 2002 00039 * Copyright (c) 2002,2003 Linas Vepstas <linas@linas.org> 00040 */ 00041 00042 #include "config.h" 00043 00044 #include <glib.h> 00045 #include <glib/gi18n.h> 00046 00047 #include "Account.h" 00048 #include "AccountP.h" 00049 #include "gnc-lot.h" 00050 #include "gnc-lot-p.h" 00051 #include "cap-gains.h" 00052 #include "Transaction.h" 00053 #include "TransactionP.h" 00054 00055 /* This static indicates the debugging module that this .o belongs to. */ 00056 static QofLogModule log_module = GNC_MOD_LOT; 00057 00058 struct gnc_lot_s 00059 { 00060 QofInstance inst; 00061 }; 00062 00063 enum 00064 { 00065 PROP_0, 00066 PROP_IS_CLOSED, 00067 PROP_MARKER 00068 }; 00069 00070 typedef struct LotPrivate 00071 { 00072 /* Account to which this lot applies. All splits in the lot must 00073 * belong to this account. 00074 */ 00075 Account * account; 00076 00077 /* List of splits that belong to this lot. */ 00078 SplitList *splits; 00079 00080 /* Handy cached value to indicate if lot is closed. */ 00081 /* If value is negative, then the cache is invalid. */ 00082 signed char is_closed; 00083 #define LOT_CLOSED_UNKNOWN (-1) 00084 00085 /* traversal marker, handy for preventing recursion */ 00086 unsigned char marker; 00087 } LotPrivate; 00088 00089 #define GET_PRIVATE(o) \ 00090 (G_TYPE_INSTANCE_GET_PRIVATE((o), GNC_TYPE_LOT, LotPrivate)) 00091 00092 #define gnc_lot_set_guid(L,G) qof_instance_set_guid(QOF_INSTANCE(L),&(G)) 00093 00094 /* ============================================================= */ 00095 00096 /* GObject Initialization */ 00097 G_DEFINE_TYPE(GNCLot, gnc_lot, QOF_TYPE_INSTANCE) 00098 00099 static void 00100 gnc_lot_init(GNCLot* lot) 00101 { 00102 LotPrivate* priv; 00103 00104 priv = GET_PRIVATE(lot); 00105 priv->account = NULL; 00106 priv->splits = NULL; 00107 priv->is_closed = LOT_CLOSED_UNKNOWN; 00108 priv->marker = 0; 00109 } 00110 00111 static void 00112 gnc_lot_dispose(GObject *lotp) 00113 { 00114 G_OBJECT_CLASS(gnc_lot_parent_class)->dispose(lotp); 00115 } 00116 00117 static void 00118 gnc_lot_finalize(GObject* lotp) 00119 { 00120 G_OBJECT_CLASS(gnc_lot_parent_class)->finalize(lotp); 00121 } 00122 00123 static void 00124 gnc_lot_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) 00125 { 00126 GNCLot* lot; 00127 LotPrivate* priv; 00128 00129 g_return_if_fail(GNC_IS_LOT(object)); 00130 00131 lot = GNC_LOT(object); 00132 priv = GET_PRIVATE(lot); 00133 switch (prop_id) 00134 { 00135 case PROP_IS_CLOSED: 00136 g_value_set_int(value, priv->is_closed); 00137 break; 00138 case PROP_MARKER: 00139 g_value_set_int(value, priv->marker); 00140 break; 00141 } 00142 } 00143 00144 static void 00145 gnc_lot_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) 00146 { 00147 GNCLot* lot; 00148 LotPrivate* priv; 00149 00150 g_return_if_fail(GNC_IS_LOT(object)); 00151 00152 lot = GNC_LOT(object); 00153 priv = GET_PRIVATE(lot); 00154 switch (prop_id) 00155 { 00156 case PROP_IS_CLOSED: 00157 priv->is_closed = g_value_get_int(value); 00158 break; 00159 case PROP_MARKER: 00160 priv->marker = g_value_get_int(value); 00161 break; 00162 } 00163 } 00164 00165 static void 00166 gnc_lot_class_init(GNCLotClass* klass) 00167 { 00168 GObjectClass* gobject_class = G_OBJECT_CLASS(klass); 00169 00170 gobject_class->dispose = gnc_lot_dispose; 00171 gobject_class->finalize = gnc_lot_finalize; 00172 gobject_class->get_property = gnc_lot_get_property; 00173 gobject_class->set_property = gnc_lot_set_property; 00174 00175 g_type_class_add_private(klass, sizeof(LotPrivate)); 00176 00177 g_object_class_install_property( 00178 gobject_class, 00179 PROP_IS_CLOSED, 00180 g_param_spec_int("is-closed", 00181 "Is Lot Closed", 00182 "Indication of whether this lot is open " 00183 "or closed to further changes.", 00184 -1, 1, 0, 00185 G_PARAM_READWRITE)); 00186 00187 g_object_class_install_property( 00188 gobject_class, 00189 PROP_MARKER, 00190 g_param_spec_int("marker", 00191 "Lot marker", 00192 "Ipsum Lorem", 00193 0, G_MAXINT8, 0, 00194 G_PARAM_READWRITE)); 00195 00196 00197 00198 } 00199 00200 GNCLot * 00201 gnc_lot_new (QofBook *book) 00202 { 00203 GNCLot *lot; 00204 g_return_val_if_fail (book, NULL); 00205 00206 lot = g_object_new (GNC_TYPE_LOT, NULL); 00207 qof_instance_init_data(QOF_INSTANCE(lot), GNC_ID_LOT, book); 00208 qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_CREATE, NULL); 00209 return lot; 00210 } 00211 00212 static void 00213 gnc_lot_free(GNCLot* lot) 00214 { 00215 GList *node; 00216 LotPrivate* priv; 00217 if (!lot) return; 00218 00219 ENTER ("(lot=%p)", lot); 00220 qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_DESTROY, NULL); 00221 00222 priv = GET_PRIVATE(lot); 00223 for (node = priv->splits; node; node = node->next) 00224 { 00225 Split *s = node->data; 00226 s->lot = NULL; 00227 } 00228 g_list_free (priv->splits); 00229 00230 priv->account = NULL; 00231 priv->is_closed = TRUE; 00232 /* qof_instance_release (&lot->inst); */ 00233 g_object_unref (lot); 00234 } 00235 00236 void 00237 gnc_lot_destroy (GNCLot *lot) 00238 { 00239 if (!lot) return; 00240 00241 gnc_lot_begin_edit(lot); 00242 qof_instance_set_destroying(lot, TRUE); 00243 gnc_lot_commit_edit(lot); 00244 } 00245 00246 /* ============================================================= */ 00247 00248 void 00249 gnc_lot_begin_edit (GNCLot *lot) 00250 { 00251 qof_begin_edit(QOF_INSTANCE(lot)); 00252 } 00253 00254 static void commit_err (QofInstance *inst, QofBackendError errcode) 00255 { 00256 PERR ("Failed to commit: %d", errcode); 00257 gnc_engine_signal_commit_error( errcode ); 00258 } 00259 00260 static void lot_free(QofInstance* inst) 00261 { 00262 GNCLot* lot = GNC_LOT(inst); 00263 00264 gnc_lot_free(lot); 00265 } 00266 00267 static void noop (QofInstance *inst) {} 00268 00269 void 00270 gnc_lot_commit_edit (GNCLot *lot) 00271 { 00272 if (!qof_commit_edit (QOF_INSTANCE(lot))) return; 00273 qof_commit_edit_part2 (QOF_INSTANCE(lot), commit_err, noop, lot_free); 00274 } 00275 00276 /* ============================================================= */ 00277 00278 GNCLot * 00279 gnc_lot_lookup (const GncGUID *guid, QofBook *book) 00280 { 00281 QofCollection *col; 00282 if (!guid || !book) return NULL; 00283 col = qof_book_get_collection (book, GNC_ID_LOT); 00284 return (GNCLot *) qof_collection_lookup_entity (col, guid); 00285 } 00286 00287 QofBook * 00288 gnc_lot_get_book (GNCLot *lot) 00289 { 00290 return qof_instance_get_book(QOF_INSTANCE(lot)); 00291 } 00292 00293 /* ============================================================= */ 00294 00295 gboolean 00296 gnc_lot_is_closed (GNCLot *lot) 00297 { 00298 LotPrivate* priv; 00299 if (!lot) return TRUE; 00300 priv = GET_PRIVATE(lot); 00301 if (0 > priv->is_closed) gnc_lot_get_balance (lot); 00302 return priv->is_closed; 00303 } 00304 00305 Account * 00306 gnc_lot_get_account (const GNCLot *lot) 00307 { 00308 LotPrivate* priv; 00309 if (!lot) return NULL; 00310 priv = GET_PRIVATE(lot); 00311 return priv->account; 00312 } 00313 00314 void 00315 gnc_lot_set_account(GNCLot* lot, Account* account) 00316 { 00317 if (lot != NULL) 00318 { 00319 LotPrivate* priv; 00320 priv = GET_PRIVATE(lot); 00321 priv->account = account; 00322 } 00323 } 00324 00325 void 00326 gnc_lot_set_closed_unknown(GNCLot* lot) 00327 { 00328 LotPrivate* priv; 00329 if (lot != NULL) 00330 { 00331 priv = GET_PRIVATE(lot); 00332 priv->is_closed = LOT_CLOSED_UNKNOWN; 00333 } 00334 } 00335 00336 KvpFrame * 00337 gnc_lot_get_slots (const GNCLot *lot) 00338 { 00339 return qof_instance_get_slots(QOF_INSTANCE(lot)); 00340 } 00341 00342 SplitList * 00343 gnc_lot_get_split_list (const GNCLot *lot) 00344 { 00345 LotPrivate* priv; 00346 if (!lot) return NULL; 00347 priv = GET_PRIVATE(lot); 00348 return priv->splits; 00349 } 00350 00351 gint gnc_lot_count_splits (const GNCLot *lot) 00352 { 00353 LotPrivate* priv; 00354 if (!lot) return 0; 00355 priv = GET_PRIVATE(lot); 00356 return g_list_length (priv->splits); 00357 } 00358 00359 /* ============================================================== */ 00360 /* Hmm, we should probably inline these. */ 00361 00362 const char * 00363 gnc_lot_get_title (const GNCLot *lot) 00364 { 00365 if (!lot) return NULL; 00366 return kvp_frame_get_string (gnc_lot_get_slots(lot), "/title"); 00367 } 00368 00369 const char * 00370 gnc_lot_get_notes (const GNCLot *lot) 00371 { 00372 if (!lot) return NULL; 00373 return kvp_frame_get_string (gnc_lot_get_slots(lot), "/notes"); 00374 } 00375 00376 void 00377 gnc_lot_set_title (GNCLot *lot, const char *str) 00378 { 00379 if (!lot) return; 00380 qof_begin_edit(QOF_INSTANCE(lot)); 00381 qof_instance_set_dirty(QOF_INSTANCE(lot)); 00382 kvp_frame_set_str (gnc_lot_get_slots(lot), "/title", str); 00383 gnc_lot_commit_edit(lot); 00384 } 00385 00386 void 00387 gnc_lot_set_notes (GNCLot *lot, const char *str) 00388 { 00389 if (!lot) return; 00390 gnc_lot_begin_edit(lot); 00391 qof_instance_set_dirty(QOF_INSTANCE(lot)); 00392 kvp_frame_set_str (gnc_lot_get_slots(lot), "/notes", str); 00393 gnc_lot_commit_edit(lot); 00394 } 00395 00396 /* ============================================================= */ 00397 00398 gnc_numeric 00399 gnc_lot_get_balance (GNCLot *lot) 00400 { 00401 LotPrivate* priv; 00402 GList *node; 00403 gnc_numeric zero = gnc_numeric_zero(); 00404 gnc_numeric baln = zero; 00405 if (!lot) return zero; 00406 00407 priv = GET_PRIVATE(lot); 00408 if (!priv->splits) 00409 { 00410 priv->is_closed = FALSE; 00411 return zero; 00412 } 00413 00414 /* Sum over splits; because they all belong to same account 00415 * they will have same denominator. 00416 */ 00417 for (node = priv->splits; node; node = node->next) 00418 { 00419 Split *s = node->data; 00420 gnc_numeric amt = xaccSplitGetAmount (s); 00421 baln = gnc_numeric_add_fixed (baln, amt); 00422 } 00423 00424 /* cache a zero balance as a closed lot */ 00425 if (gnc_numeric_equal (baln, zero)) 00426 { 00427 priv->is_closed = TRUE; 00428 } 00429 else 00430 { 00431 priv->is_closed = FALSE; 00432 } 00433 00434 return baln; 00435 } 00436 00437 /* ============================================================= */ 00438 00439 void 00440 gnc_lot_get_balance_before (const GNCLot *lot, const Split *split, 00441 gnc_numeric *amount, gnc_numeric *value) 00442 { 00443 LotPrivate* priv; 00444 GList *node; 00445 gnc_numeric zero = gnc_numeric_zero(); 00446 gnc_numeric amt = zero; 00447 gnc_numeric val = zero; 00448 00449 *amount = amt; 00450 *value = val; 00451 if (lot == NULL) return; 00452 00453 priv = GET_PRIVATE(lot); 00454 if (priv->splits) 00455 { 00456 Transaction *ta, *tb; 00457 const Split *target; 00458 /* If this is a gains split, find the source of the gains and use 00459 its transaction for the comparison. Gains splits are in separate 00460 transactions that may sort after non-gains transactions. */ 00461 target = xaccSplitGetGainsSourceSplit (split); 00462 if (target == NULL) 00463 target = split; 00464 tb = xaccSplitGetParent (target); 00465 for (node = priv->splits; node; node = node->next) 00466 { 00467 Split *s = node->data; 00468 Split *source = xaccSplitGetGainsSourceSplit (s); 00469 if (source == NULL) 00470 source = s; 00471 ta = xaccSplitGetParent (source); 00472 if ((ta == tb && source != target) || 00473 xaccTransOrder (ta, tb) < 0) 00474 { 00475 gnc_numeric tmpval = xaccSplitGetAmount (s); 00476 amt = gnc_numeric_add_fixed (amt, tmpval); 00477 tmpval = xaccSplitGetValue (s); 00478 val = gnc_numeric_add_fixed (val, tmpval); 00479 } 00480 } 00481 } 00482 00483 *amount = amt; 00484 *value = val; 00485 } 00486 00487 /* ============================================================= */ 00488 00489 void 00490 gnc_lot_add_split (GNCLot *lot, Split *split) 00491 { 00492 LotPrivate* priv; 00493 Account * acc; 00494 if (!lot || !split) return; 00495 priv = GET_PRIVATE(lot); 00496 00497 ENTER ("(lot=%p, split=%p) %s amt=%s val=%s", lot, split, 00498 gnc_lot_get_title (lot), 00499 gnc_num_dbg_to_string (split->amount), 00500 gnc_num_dbg_to_string (split->value)); 00501 gnc_lot_begin_edit(lot); 00502 acc = xaccSplitGetAccount (split); 00503 qof_instance_set_dirty(QOF_INSTANCE(lot)); 00504 if (NULL == priv->account) 00505 { 00506 xaccAccountInsertLot (acc, lot); 00507 } 00508 else if (priv->account != acc) 00509 { 00510 PERR ("splits from different accounts cannot " 00511 "be added to this lot!\n" 00512 "\tlot account=\'%s\', split account=\'%s\'\n", 00513 xaccAccountGetName(priv->account), xaccAccountGetName (acc)); 00514 gnc_lot_commit_edit(lot); 00515 LEAVE("different accounts"); 00516 return; 00517 } 00518 00519 if (lot == split->lot) 00520 { 00521 gnc_lot_commit_edit(lot); 00522 LEAVE("already in lot"); 00523 return; /* handle not-uncommon no-op */ 00524 } 00525 if (split->lot) 00526 { 00527 gnc_lot_remove_split (split->lot, split); 00528 } 00529 xaccSplitSetLot(split, lot); 00530 00531 priv->splits = g_list_append (priv->splits, split); 00532 00533 /* for recomputation of is-closed */ 00534 priv->is_closed = LOT_CLOSED_UNKNOWN; 00535 gnc_lot_commit_edit(lot); 00536 00537 qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_MODIFY, NULL); 00538 LEAVE("added to lot"); 00539 } 00540 00541 void 00542 gnc_lot_remove_split (GNCLot *lot, Split *split) 00543 { 00544 LotPrivate* priv; 00545 if (!lot || !split) return; 00546 priv = GET_PRIVATE(lot); 00547 00548 ENTER ("(lot=%p, split=%p)", lot, split); 00549 gnc_lot_begin_edit(lot); 00550 qof_instance_set_dirty(QOF_INSTANCE(lot)); 00551 priv->splits = g_list_remove (priv->splits, split); 00552 xaccSplitSetLot(split, NULL); 00553 priv->is_closed = LOT_CLOSED_UNKNOWN; /* force an is-closed computation */ 00554 00555 if (NULL == priv->splits) 00556 { 00557 xaccAccountRemoveLot (priv->account, lot); 00558 priv->account = NULL; 00559 } 00560 gnc_lot_commit_edit(lot); 00561 qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_MODIFY, NULL); 00562 } 00563 00564 /* ============================================================== */ 00565 /* Utility function, get earliest split in lot */ 00566 00567 Split * 00568 gnc_lot_get_earliest_split (GNCLot *lot) 00569 { 00570 LotPrivate* priv; 00571 if (!lot) return NULL; 00572 priv = GET_PRIVATE(lot); 00573 if (! priv->splits) return NULL; 00574 priv->splits = g_list_sort (priv->splits, (GCompareFunc) xaccSplitOrderDateOnly); 00575 return priv->splits->data; 00576 } 00577 00578 /* Utility function, get latest split in lot */ 00579 Split * 00580 gnc_lot_get_latest_split (GNCLot *lot) 00581 { 00582 LotPrivate* priv; 00583 SplitList *node; 00584 00585 if (!lot) return NULL; 00586 priv = GET_PRIVATE(lot); 00587 if (! priv->splits) return NULL; 00588 priv->splits = g_list_sort (priv->splits, (GCompareFunc) xaccSplitOrderDateOnly); 00589 00590 for (node = priv->splits; node->next; node = node->next) 00591 ; 00592 00593 return node->data; 00594 } 00595 00596 /* ============================================================= */ 00597 00598 static void 00599 destroy_lot_on_book_close(QofInstance *ent, gpointer data) 00600 { 00601 GNCLot* lot = GNC_LOT(ent); 00602 00603 gnc_lot_destroy(lot); 00604 } 00605 00606 static void 00607 gnc_lot_book_end(QofBook* book) 00608 { 00609 QofCollection *col; 00610 00611 col = qof_book_get_collection(book, GNC_ID_LOT); 00612 qof_collection_foreach(col, destroy_lot_on_book_close, NULL); 00613 } 00614 00615 #ifdef _MSC_VER 00616 /* MSVC compiler doesn't have C99 "designated initializers" 00617 * so we wrap them in a macro that is empty on MSVC. */ 00618 # define DI(x) /* */ 00619 #else 00620 # define DI(x) x 00621 #endif 00622 static QofObject gncLotDesc = 00623 { 00624 DI(.interface_version = ) QOF_OBJECT_VERSION, 00625 DI(.e_type = ) GNC_ID_LOT, 00626 DI(.type_label = ) "Lot", 00627 DI(.create = ) (gpointer)gnc_lot_new, 00628 DI(.book_begin = ) NULL, 00629 DI(.book_end = ) gnc_lot_book_end, 00630 DI(.is_dirty = ) qof_collection_is_dirty, 00631 DI(.mark_clean = ) qof_collection_mark_clean, 00632 DI(.foreach = ) qof_collection_foreach, 00633 DI(.printable = ) NULL, 00634 DI(.version_cmp = ) (int (*)(gpointer, gpointer))qof_instance_version_cmp, 00635 }; 00636 00637 00638 gboolean gnc_lot_register (void) 00639 { 00640 static const QofParam params[] = 00641 { 00642 { 00643 LOT_TITLE, QOF_TYPE_STRING, 00644 (QofAccessFunc) gnc_lot_get_title, 00645 (QofSetterFunc) gnc_lot_set_title 00646 }, 00647 { 00648 LOT_NOTES, QOF_TYPE_STRING, 00649 (QofAccessFunc) gnc_lot_get_notes, 00650 (QofSetterFunc) gnc_lot_set_notes 00651 }, 00652 { 00653 QOF_PARAM_GUID, QOF_TYPE_GUID, 00654 (QofAccessFunc) qof_entity_get_guid, NULL 00655 }, 00656 { 00657 QOF_PARAM_BOOK, QOF_ID_BOOK, 00658 (QofAccessFunc) gnc_lot_get_book, NULL 00659 }, 00660 { 00661 LOT_IS_CLOSED, QOF_TYPE_BOOLEAN, 00662 (QofAccessFunc) gnc_lot_is_closed, NULL 00663 }, 00664 { 00665 LOT_BALANCE, QOF_TYPE_NUMERIC, 00666 (QofAccessFunc) gnc_lot_get_balance, NULL 00667 }, 00668 { NULL }, 00669 }; 00670 00671 qof_class_register (GNC_ID_LOT, NULL, params); 00672 return qof_object_register(&gncLotDesc); 00673 } 00674 00675 GNCLot * gnc_lot_make_default (Account *acc) 00676 { 00677 GNCLot * lot; 00678 gint64 id; 00679 char buff[200]; 00680 00681 lot = gnc_lot_new (qof_instance_get_book(acc)); 00682 00683 /* Provide a reasonable title for the new lot */ 00684 xaccAccountBeginEdit (acc); 00685 id = kvp_frame_get_gint64 (xaccAccountGetSlots (acc), "/lot-mgmt/next-id"); 00686 snprintf (buff, 200, ("%s %" G_GINT64_FORMAT), _("Lot"), id); 00687 kvp_frame_set_str (gnc_lot_get_slots (lot), "/title", buff); 00688 id ++; 00689 kvp_frame_set_gint64 (xaccAccountGetSlots (acc), "/lot-mgmt/next-id", id); 00690 qof_instance_set_dirty (QOF_INSTANCE(acc)); 00691 xaccAccountCommitEdit (acc); 00692 return lot; 00693 } 00694 00695 /* ========================== END OF FILE ========================= */
1.7.4