/********************************************************************\ * gnc-lot.c -- AR/AP invoices; inventory lots; stock lots * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * \********************************************************************/ /* * FILE: * gnc-lot.c * * FUNCTION: * Lots implement the fundamental conceptual idea behind invoices, * inventory lots, and stock market investment lots. See the file * src/doc/lots.txt for implementation overview. * * XXX Lots are not currently treated in a correct transactional * manner. There's now a per-Lot dirty flag in the QofInstance, but * this code still needs to emit the correct signals when a lot has * changed. This is true both in the Scrub2.c and in * src/gnome/dialog-lot-viewer.c * * HISTORY: * Created by Linas Vepstas May 2002 * Copyright (c) 2002,2003 Linas Vepstas */ #include #include #include #include #include "Account.h" #include "AccountP.h" #include "gnc-lot.h" #include "gnc-lot-p.h" #include "cap-gains.h" #include "Transaction.h" #include "TransactionP.h" #include "gncInvoice.h" /* This static indicates the debugging module that this .o belongs to. */ static QofLogModule log_module = GNC_MOD_LOT; struct gnc_lot_s { QofInstance inst; }; enum { PROP_0, // PROP_ACCOUNT, /* Table */ PROP_IS_CLOSED, /* Table */ PROP_INVOICE, /* KVP */ PROP_OWNER_TYPE, /* KVP */ PROP_OWNER_GUID, /* KVP */ PROP_RUNTIME_0, PROP_MARKER, /* Runtime */ }; typedef struct GNCLotPrivate { /* Account to which this lot applies. All splits in the lot must * belong to this account. */ Account * account; /* List of splits that belong to this lot. */ SplitList *splits; char *title; char *notes; GncInvoice *cached_invoice; /* Handy cached value to indicate if lot is closed. */ /* If value is negative, then the cache is invalid. */ signed char is_closed; #define LOT_CLOSED_UNKNOWN (-1) /* traversal marker, handy for preventing recursion */ unsigned char marker; } GNCLotPrivate; #define GET_PRIVATE(o) \ ((GNCLotPrivate*)gnc_lot_get_instance_private((GNCLot*)o)) #define gnc_lot_set_guid(L,G) qof_instance_set_guid(QOF_INSTANCE(L),&(G)) /* ============================================================= */ /* GObject Initialization */ G_DEFINE_TYPE_WITH_PRIVATE(GNCLot, gnc_lot, QOF_TYPE_INSTANCE) static void gnc_lot_init(GNCLot* lot) { GNCLotPrivate* priv; priv = GET_PRIVATE(lot); priv->account = NULL; priv->splits = NULL; priv->cached_invoice = NULL; priv->is_closed = LOT_CLOSED_UNKNOWN; priv->marker = 0; } static void gnc_lot_dispose(GObject *lotp) { G_OBJECT_CLASS(gnc_lot_parent_class)->dispose(lotp); } static void gnc_lot_finalize(GObject* lotp) { G_OBJECT_CLASS(gnc_lot_parent_class)->finalize(lotp); } static void gnc_lot_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) { GNCLot* lot; GNCLotPrivate* priv; g_return_if_fail(GNC_IS_LOT(object)); lot = GNC_LOT(object); priv = GET_PRIVATE(lot); switch (prop_id) { case PROP_IS_CLOSED: g_value_set_int(value, priv->is_closed); break; case PROP_MARKER: g_value_set_int(value, priv->marker); break; case PROP_INVOICE: qof_instance_get_kvp (QOF_INSTANCE (lot), value, 2, GNC_INVOICE_ID, GNC_INVOICE_GUID); break; case PROP_OWNER_TYPE: qof_instance_get_kvp (QOF_INSTANCE (lot), value, 2, GNC_OWNER_ID, GNC_OWNER_TYPE); break; case PROP_OWNER_GUID: qof_instance_get_kvp (QOF_INSTANCE (lot), value, 2, GNC_OWNER_ID, GNC_OWNER_GUID); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gnc_lot_set_property (GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) { GNCLot* lot; GNCLotPrivate* priv; g_return_if_fail(GNC_IS_LOT(object)); lot = GNC_LOT(object); if (prop_id < PROP_RUNTIME_0) g_assert (qof_instance_get_editlevel(lot)); priv = GET_PRIVATE(lot); switch (prop_id) { case PROP_IS_CLOSED: priv->is_closed = g_value_get_int(value); break; case PROP_MARKER: priv->marker = g_value_get_int(value); break; case PROP_INVOICE: qof_instance_set_kvp (QOF_INSTANCE (lot), value, 2, GNC_INVOICE_ID, GNC_INVOICE_GUID); break; case PROP_OWNER_TYPE: qof_instance_set_kvp (QOF_INSTANCE (lot), value, 2, GNC_OWNER_ID, GNC_OWNER_TYPE); break; case PROP_OWNER_GUID: qof_instance_set_kvp (QOF_INSTANCE (lot), value, 2, GNC_OWNER_ID, GNC_OWNER_GUID); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gnc_lot_class_init(GNCLotClass* klass) { GObjectClass* gobject_class = G_OBJECT_CLASS(klass); gobject_class->dispose = gnc_lot_dispose; gobject_class->finalize = gnc_lot_finalize; gobject_class->get_property = gnc_lot_get_property; gobject_class->set_property = gnc_lot_set_property; g_object_class_install_property( gobject_class, PROP_IS_CLOSED, g_param_spec_int("is-closed", "Is Lot Closed", "Indication of whether this lot is open " "or closed to further changes.", -1, 1, 0, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, PROP_MARKER, g_param_spec_int("marker", "Lot marker", "Ipsum Lorem", 0, G_MAXINT8, 0, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, PROP_INVOICE, g_param_spec_boxed("invoice", "Invoice attached to lot", "Used by GncInvoice", GNC_TYPE_GUID, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, PROP_OWNER_TYPE, g_param_spec_int64("owner-type", "Owning Entity Type of lot", "Used by GncOwner", 0, G_MAXINT64, 0, G_PARAM_READWRITE)); g_object_class_install_property( gobject_class, PROP_OWNER_GUID, g_param_spec_boxed("owner-guid", "Owner attached to lot", "Used by GncOwner", GNC_TYPE_GUID, G_PARAM_READWRITE)); } GNCLot * gnc_lot_new (QofBook *book) { GNCLot *lot; g_return_val_if_fail (book, NULL); lot = g_object_new (GNC_TYPE_LOT, NULL); qof_instance_init_data(QOF_INSTANCE(lot), GNC_ID_LOT, book); qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_CREATE, NULL); return lot; } static void gnc_lot_free(GNCLot* lot) { GList *node; GNCLotPrivate* priv; if (!lot) return; ENTER ("(lot=%p)", lot); qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_DESTROY, NULL); priv = GET_PRIVATE(lot); for (node = priv->splits; node; node = node->next) { Split *s = node->data; s->lot = NULL; } g_list_free (priv->splits); if (priv->account && !qof_instance_get_destroying(priv->account)) xaccAccountRemoveLot (priv->account, lot); priv->account = NULL; priv->is_closed = TRUE; /* qof_instance_release (&lot->inst); */ g_object_unref (lot); LEAVE(); } void gnc_lot_destroy (GNCLot *lot) { if (!lot) return; gnc_lot_begin_edit(lot); qof_instance_set_destroying(lot, TRUE); gnc_lot_commit_edit(lot); } /* ============================================================= */ void gnc_lot_begin_edit (GNCLot *lot) { qof_begin_edit(QOF_INSTANCE(lot)); } static void commit_err (QofInstance *inst, QofBackendError errcode) { PERR ("Failed to commit: %d", errcode); gnc_engine_signal_commit_error( errcode ); } static void lot_free(QofInstance* inst) { GNCLot* lot = GNC_LOT(inst); gnc_lot_free(lot); } static void noop (QofInstance *inst) {} void gnc_lot_commit_edit (GNCLot *lot) { if (!qof_commit_edit (QOF_INSTANCE(lot))) return; qof_commit_edit_part2 (QOF_INSTANCE(lot), commit_err, noop, lot_free); } /* ============================================================= */ GNCLot * gnc_lot_lookup (const GncGUID *guid, QofBook *book) { QofCollection *col; if (!guid || !book) return NULL; col = qof_book_get_collection (book, GNC_ID_LOT); return (GNCLot *) qof_collection_lookup_entity (col, guid); } QofBook * gnc_lot_get_book (GNCLot *lot) { return qof_instance_get_book(QOF_INSTANCE(lot)); } /* ============================================================= */ gboolean gnc_lot_is_closed (GNCLot *lot) { GNCLotPrivate* priv; if (!lot) return TRUE; priv = GET_PRIVATE(lot); if (0 > priv->is_closed) gnc_lot_get_balance (lot); return priv->is_closed; } Account * gnc_lot_get_account (const GNCLot *lot) { GNCLotPrivate* priv; if (!lot) return NULL; priv = GET_PRIVATE(lot); return priv->account; } GncInvoice * gnc_lot_get_cached_invoice (const GNCLot *lot) { if (!lot) return NULL; else { GNCLotPrivate *priv = GET_PRIVATE(lot); return priv->cached_invoice; } } void gnc_lot_set_cached_invoice(GNCLot* lot, GncInvoice *invoice) { if (!lot) return; GET_PRIVATE(lot)->cached_invoice = invoice; } void gnc_lot_set_account(GNCLot* lot, Account* account) { if (lot != NULL) { GNCLotPrivate* priv; priv = GET_PRIVATE(lot); priv->account = account; } } void gnc_lot_set_closed_unknown(GNCLot* lot) { GNCLotPrivate* priv; if (lot != NULL) { priv = GET_PRIVATE(lot); priv->is_closed = LOT_CLOSED_UNKNOWN; } } SplitList * gnc_lot_get_split_list (const GNCLot *lot) { GNCLotPrivate* priv; if (!lot) return NULL; priv = GET_PRIVATE(lot); return priv->splits; } gint gnc_lot_count_splits (const GNCLot *lot) { GNCLotPrivate* priv; if (!lot) return 0; priv = GET_PRIVATE(lot); return g_list_length (priv->splits); } /* ============================================================== */ /* Hmm, we should probably inline these. */ const char * gnc_lot_get_title (const GNCLot *lot) { if (!lot) return NULL; GValue v = G_VALUE_INIT; qof_instance_get_kvp (QOF_INSTANCE (lot), &v, 1, "title"); const char *rv = G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL; g_value_unset (&v); return rv; } const char * gnc_lot_get_notes (const GNCLot *lot) { if (!lot) return NULL; GValue v = G_VALUE_INIT; qof_instance_get_kvp (QOF_INSTANCE (lot), &v, 1, "notes"); const char *rv = G_VALUE_HOLDS_STRING (&v) ? g_value_get_string (&v) : NULL; g_value_unset (&v); return rv; } void gnc_lot_set_title (GNCLot *lot, const char *str) { GValue v = G_VALUE_INIT; if (!lot) return; qof_begin_edit(QOF_INSTANCE(lot)); g_value_init (&v, G_TYPE_STRING); g_value_set_static_string (&v, str); qof_instance_set_kvp (QOF_INSTANCE (lot), &v, 1, "title"); qof_instance_set_dirty(QOF_INSTANCE(lot)); gnc_lot_commit_edit(lot); g_value_unset (&v); } void gnc_lot_set_notes (GNCLot *lot, const char *str) { GValue v = G_VALUE_INIT; if (!lot) return; qof_begin_edit(QOF_INSTANCE(lot)); g_value_init (&v, G_TYPE_STRING); g_value_set_static_string (&v, str); qof_instance_set_kvp (QOF_INSTANCE (lot), &v, 1, "notes"); qof_instance_set_dirty(QOF_INSTANCE(lot)); gnc_lot_commit_edit(lot); g_value_unset (&v); } /* ============================================================= */ gnc_numeric gnc_lot_get_balance (GNCLot *lot) { GNCLotPrivate* priv; GList *node; gnc_numeric zero = gnc_numeric_zero(); gnc_numeric baln = zero; if (!lot) return zero; priv = GET_PRIVATE(lot); if (!priv->splits) { priv->is_closed = FALSE; return zero; } /* Sum over splits; because they all belong to same account * they will have same denominator. */ for (node = priv->splits; node; node = node->next) { Split *s = node->data; gnc_numeric amt = xaccSplitGetAmount (s); baln = gnc_numeric_add_fixed (baln, amt); g_assert (gnc_numeric_check (baln) == GNC_ERROR_OK); } /* cache a zero balance as a closed lot */ if (gnc_numeric_equal (baln, zero)) { priv->is_closed = TRUE; } else { priv->is_closed = FALSE; } return baln; } /* ============================================================= */ void gnc_lot_get_balance_before (const GNCLot *lot, const Split *split, gnc_numeric *amount, gnc_numeric *value) { GNCLotPrivate* priv; GList *node; gnc_numeric zero = gnc_numeric_zero(); gnc_numeric amt = zero; gnc_numeric val = zero; *amount = amt; *value = val; if (lot == NULL) return; priv = GET_PRIVATE(lot); if (priv->splits) { Transaction *ta, *tb; const Split *target; /* If this is a gains split, find the source of the gains and use its transaction for the comparison. Gains splits are in separate transactions that may sort after non-gains transactions. */ target = xaccSplitGetGainsSourceSplit (split); if (target == NULL) target = split; tb = xaccSplitGetParent (target); for (node = priv->splits; node; node = node->next) { Split *s = node->data; Split *source = xaccSplitGetGainsSourceSplit (s); if (source == NULL) source = s; ta = xaccSplitGetParent (source); if ((ta == tb && source != target) || xaccTransOrder (ta, tb) < 0) { gnc_numeric tmpval = xaccSplitGetAmount (s); amt = gnc_numeric_add_fixed (amt, tmpval); tmpval = xaccSplitGetValue (s); val = gnc_numeric_add_fixed (val, tmpval); } } } *amount = amt; *value = val; } /* ============================================================= */ void gnc_lot_add_split (GNCLot *lot, Split *split) { GNCLotPrivate* priv; Account * acc; if (!lot || !split) return; priv = GET_PRIVATE(lot); ENTER ("(lot=%p, split=%p) %s amt=%s val=%s", lot, split, gnc_lot_get_title (lot), gnc_num_dbg_to_string (split->amount), gnc_num_dbg_to_string (split->value)); gnc_lot_begin_edit(lot); acc = xaccSplitGetAccount (split); qof_instance_set_dirty(QOF_INSTANCE(lot)); if (NULL == priv->account) { xaccAccountInsertLot (acc, lot); } else if (priv->account != acc) { PERR ("splits from different accounts cannot " "be added to this lot!\n" "\tlot account=\'%s\', split account=\'%s\'\n", xaccAccountGetName(priv->account), xaccAccountGetName (acc)); gnc_lot_commit_edit(lot); LEAVE("different accounts"); return; } if (lot == split->lot) { gnc_lot_commit_edit(lot); LEAVE("already in lot"); return; /* handle not-uncommon no-op */ } if (split->lot) { gnc_lot_remove_split (split->lot, split); } xaccSplitSetLot(split, lot); priv->splits = g_list_append (priv->splits, split); /* for recomputation of is-closed */ priv->is_closed = LOT_CLOSED_UNKNOWN; gnc_lot_commit_edit(lot); qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_MODIFY, NULL); LEAVE("added to lot"); } void gnc_lot_remove_split (GNCLot *lot, Split *split) { GNCLotPrivate* priv; if (!lot || !split) return; priv = GET_PRIVATE(lot); ENTER ("(lot=%p, split=%p)", lot, split); gnc_lot_begin_edit(lot); qof_instance_set_dirty(QOF_INSTANCE(lot)); priv->splits = g_list_remove (priv->splits, split); xaccSplitSetLot(split, NULL); priv->is_closed = LOT_CLOSED_UNKNOWN; /* force an is-closed computation */ if (NULL == priv->splits) { xaccAccountRemoveLot (priv->account, lot); priv->account = NULL; } gnc_lot_commit_edit(lot); qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_MODIFY, NULL); LEAVE("removed from lot"); } /* ============================================================== */ /* Utility function, get earliest split in lot */ Split * gnc_lot_get_earliest_split (GNCLot *lot) { GNCLotPrivate* priv; if (!lot) return NULL; priv = GET_PRIVATE(lot); if (! priv->splits) return NULL; priv->splits = g_list_sort (priv->splits, (GCompareFunc) xaccSplitOrderDateOnly); return priv->splits->data; } /* Utility function, get latest split in lot */ Split * gnc_lot_get_latest_split (GNCLot *lot) { GNCLotPrivate* priv; SplitList *node; if (!lot) return NULL; priv = GET_PRIVATE(lot); if (! priv->splits) return NULL; priv->splits = g_list_sort (priv->splits, (GCompareFunc) xaccSplitOrderDateOnly); for (node = priv->splits; node->next; node = node->next) ; return node->data; } /* ============================================================= */ static void destroy_lot_on_book_close(QofInstance *ent, gpointer data) { GNCLot* lot = GNC_LOT(ent); gnc_lot_destroy(lot); } static void gnc_lot_book_end(QofBook* book) { QofCollection *col; col = qof_book_get_collection(book, GNC_ID_LOT); qof_collection_foreach(col, destroy_lot_on_book_close, NULL); } #ifdef _MSC_VER /* MSVC compiler doesn't have C99 "designated initializers" * so we wrap them in a macro that is empty on MSVC. */ # define DI(x) /* */ #else # define DI(x) x #endif static QofObject gncLotDesc = { DI(.interface_version = ) QOF_OBJECT_VERSION, DI(.e_type = ) GNC_ID_LOT, DI(.type_label = ) "Lot", DI(.create = ) (gpointer)gnc_lot_new, DI(.book_begin = ) NULL, DI(.book_end = ) gnc_lot_book_end, DI(.is_dirty = ) qof_collection_is_dirty, DI(.mark_clean = ) qof_collection_mark_clean, DI(.foreach = ) qof_collection_foreach, DI(.printable = ) NULL, DI(.version_cmp = ) (int (*)(gpointer, gpointer))qof_instance_version_cmp, }; gboolean gnc_lot_register (void) { static const QofParam params[] = { { LOT_TITLE, QOF_TYPE_STRING, (QofAccessFunc) gnc_lot_get_title, (QofSetterFunc) gnc_lot_set_title }, { LOT_NOTES, QOF_TYPE_STRING, (QofAccessFunc) gnc_lot_get_notes, (QofSetterFunc) gnc_lot_set_notes }, { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc) qof_entity_get_guid, NULL }, { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc) gnc_lot_get_book, NULL }, { LOT_IS_CLOSED, QOF_TYPE_BOOLEAN, (QofAccessFunc) gnc_lot_is_closed, NULL }, { LOT_BALANCE, QOF_TYPE_NUMERIC, (QofAccessFunc) gnc_lot_get_balance, NULL }, { NULL }, }; qof_class_register (GNC_ID_LOT, NULL, params); return qof_object_register(&gncLotDesc); } GNCLot * gnc_lot_make_default (Account *acc) { GNCLot * lot; gint64 id = 0; gchar *buff; lot = gnc_lot_new (qof_instance_get_book(acc)); /* Provide a reasonable title for the new lot */ xaccAccountBeginEdit (acc); qof_instance_get (QOF_INSTANCE (acc), "lot-next-id", &id, NULL); buff = g_strdup_printf ("%s %" G_GINT64_FORMAT, _("Lot"), id); gnc_lot_set_title (lot, buff); id ++; qof_instance_set (QOF_INSTANCE (acc), "lot-next-id", id, NULL); xaccAccountCommitEdit (acc); g_free (buff); return lot; } /* ========================== END OF FILE ========================= */