GnuCash 2.4.99
gnc-lot.c
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 ========================= */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines