mirror of https://github.com/Gnucash/gnucash
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
12 KiB
470 lines
12 KiB
/********************************************************************\
|
|
* 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 implmentation 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/lot-viewer.c
|
|
*
|
|
* HISTORY:
|
|
* Created by Linas Vepstas May 2002
|
|
* Copyright (c) 2002,2003 Linas Vepstas <linas@linas.org>
|
|
*/
|
|
|
|
#include "Account.h"
|
|
#include "gnc-lot.h"
|
|
#include "gnc-lot-p.h"
|
|
#include "cap-gains.h"
|
|
#include "Transaction.h"
|
|
#include "TransactionP.h"
|
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static QofLogModule log_module = GNC_MOD_LOT;
|
|
|
|
/* ============================================================= */
|
|
|
|
static void
|
|
gnc_lot_init (GNCLot *lot, QofBook *book)
|
|
{
|
|
ENTER ("(lot=%p, book=%p)", lot, book);
|
|
lot->account = NULL;
|
|
lot->splits = NULL;
|
|
lot->is_closed = -1;
|
|
lot->marker = 0;
|
|
|
|
qof_instance_init(&lot->inst, GNC_ID_LOT, book);
|
|
LEAVE ("(lot=%p, book=%p)", lot, book);
|
|
}
|
|
|
|
GNCLot *
|
|
gnc_lot_new (QofBook *book)
|
|
{
|
|
GNCLot *lot;
|
|
g_return_val_if_fail (book, NULL);
|
|
|
|
lot = g_new (GNCLot, 1);
|
|
gnc_lot_init (lot, book);
|
|
qof_event_gen (&lot->inst.entity, QOF_EVENT_CREATE, NULL);
|
|
return lot;
|
|
}
|
|
|
|
void
|
|
gnc_lot_destroy (GNCLot *lot)
|
|
{
|
|
GList *node;
|
|
if (!lot) return;
|
|
|
|
ENTER ("(lot=%p)", lot);
|
|
qof_event_gen (&lot->inst.entity, QOF_EVENT_DESTROY, NULL);
|
|
|
|
|
|
for (node=lot->splits; node; node=node->next)
|
|
{
|
|
Split *s = node->data;
|
|
s->lot = NULL;
|
|
}
|
|
g_list_free (lot->splits);
|
|
|
|
lot->account = NULL;
|
|
lot->is_closed = TRUE;
|
|
qof_instance_release (&lot->inst);
|
|
g_free (lot);
|
|
}
|
|
|
|
/* ============================================================= */
|
|
|
|
void
|
|
gnc_lot_begin_edit (GNCLot *lot)
|
|
{
|
|
qof_begin_edit(&lot->inst);
|
|
}
|
|
|
|
static inline void commit_err (QofInstance *inst, QofBackendError errcode)
|
|
{
|
|
PERR ("Failed to commit: %d", errcode);
|
|
}
|
|
|
|
static inline void noop (QofInstance *inst) {}
|
|
|
|
void
|
|
gnc_lot_commit_edit (GNCLot *lot)
|
|
{
|
|
if (!qof_commit_edit (QOF_INSTANCE(lot))) return;
|
|
qof_commit_edit_part2 (&lot->inst, commit_err, noop, noop);
|
|
}
|
|
|
|
/* ============================================================= */
|
|
|
|
GNCLot *
|
|
gnc_lot_lookup (const GUID *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)
|
|
{
|
|
if (!lot) return TRUE;
|
|
if (0 > lot->is_closed) gnc_lot_get_balance (lot);
|
|
return lot->is_closed;
|
|
}
|
|
|
|
Account *
|
|
gnc_lot_get_account (GNCLot *lot)
|
|
{
|
|
if (!lot) return NULL;
|
|
return lot->account;
|
|
}
|
|
|
|
KvpFrame *
|
|
gnc_lot_get_slots (GNCLot *lot)
|
|
{
|
|
return qof_instance_get_slots(QOF_INSTANCE(lot));
|
|
}
|
|
|
|
SplitList *
|
|
gnc_lot_get_split_list (GNCLot *lot)
|
|
{
|
|
if (!lot) return NULL;
|
|
return lot->splits;
|
|
}
|
|
|
|
gint gnc_lot_count_splits (GNCLot *lot)
|
|
{
|
|
if (!lot) return 0;
|
|
return g_list_length (lot->splits);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
/* Hmm, we should probably inline these. */
|
|
|
|
const char *
|
|
gnc_lot_get_title (GNCLot *lot)
|
|
{
|
|
if (!lot) return NULL;
|
|
return kvp_frame_get_string (lot->inst.kvp_data, "/title");
|
|
}
|
|
|
|
const char *
|
|
gnc_lot_get_notes (GNCLot *lot)
|
|
{
|
|
if (!lot) return NULL;
|
|
return kvp_frame_get_string (lot->inst.kvp_data, "/notes");
|
|
}
|
|
|
|
void
|
|
gnc_lot_set_title (GNCLot *lot, const char *str)
|
|
{
|
|
if (!lot) return;
|
|
qof_begin_edit(QOF_INSTANCE(lot));
|
|
qof_instance_set_dirty(QOF_INSTANCE(lot));
|
|
kvp_frame_set_str (lot->inst.kvp_data, "/title", str);
|
|
gnc_lot_commit_edit(lot);
|
|
}
|
|
|
|
void
|
|
gnc_lot_set_notes (GNCLot *lot, const char *str)
|
|
{
|
|
if (!lot) return;
|
|
gnc_lot_begin_edit(lot);
|
|
qof_instance_set_dirty(QOF_INSTANCE(lot));
|
|
kvp_frame_set_str (lot->inst.kvp_data, "/notes", str);
|
|
gnc_lot_commit_edit(lot);
|
|
}
|
|
|
|
/* ============================================================= */
|
|
|
|
gnc_numeric
|
|
gnc_lot_get_balance (GNCLot *lot)
|
|
{
|
|
GList *node;
|
|
gnc_numeric zero = gnc_numeric_zero();
|
|
gnc_numeric baln = zero;
|
|
if (!lot) return zero;
|
|
|
|
if (!lot->splits)
|
|
{
|
|
lot->is_closed = FALSE;
|
|
return zero;
|
|
}
|
|
|
|
/* Sum over splits; because they all belong to same account
|
|
* they will have same denominator.
|
|
*/
|
|
for (node=lot->splits; node; node=node->next)
|
|
{
|
|
Split *s = node->data;
|
|
gnc_numeric amt = xaccSplitGetAmount (s);
|
|
baln = gnc_numeric_add_fixed (baln, amt);
|
|
}
|
|
|
|
/* cache a zero balance as a closed lot */
|
|
if (gnc_numeric_equal (baln, zero))
|
|
{
|
|
lot->is_closed = TRUE;
|
|
}
|
|
else
|
|
{
|
|
lot->is_closed = FALSE;
|
|
}
|
|
|
|
return baln;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
|
|
void
|
|
gnc_lot_get_balance_before (GNCLot *lot, Split *split,
|
|
gnc_numeric *amount, gnc_numeric *value)
|
|
{
|
|
GList *node;
|
|
gnc_numeric zero = gnc_numeric_zero();
|
|
gnc_numeric amt = zero;
|
|
gnc_numeric val = zero;
|
|
|
|
if (lot && lot->splits)
|
|
{
|
|
Transaction *ta, *tb;
|
|
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 = lot->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)
|
|
{
|
|
Account * acc;
|
|
if (!lot || !split) return;
|
|
|
|
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 == lot->account)
|
|
{
|
|
xaccAccountInsertLot (acc, lot);
|
|
}
|
|
else if (lot->account != acc)
|
|
{
|
|
PERR ("splits from different accounts cannot "
|
|
"be added to this lot!\n"
|
|
"\tlot account=\'%s\', split account=\'%s\'\n",
|
|
xaccAccountGetName(lot->account), xaccAccountGetName (acc));
|
|
gnc_lot_commit_edit(lot);
|
|
return;
|
|
}
|
|
|
|
if (lot == split->lot) {
|
|
gnc_lot_commit_edit(lot);
|
|
return; /* handle not-uncommon no-op */
|
|
}
|
|
if (split->lot)
|
|
{
|
|
gnc_lot_remove_split (split->lot, split);
|
|
}
|
|
split->lot = lot;
|
|
|
|
lot->splits = g_list_append (lot->splits, split);
|
|
|
|
/* for recomputation of is-closed */
|
|
lot->is_closed = -1;
|
|
gnc_lot_commit_edit(lot);
|
|
|
|
qof_event_gen (&lot->inst.entity, QOF_EVENT_MODIFY, NULL);
|
|
}
|
|
|
|
void
|
|
gnc_lot_remove_split (GNCLot *lot, Split *split)
|
|
{
|
|
if (!lot || !split) return;
|
|
|
|
ENTER ("(lot=%p, split=%p)", lot, split);
|
|
gnc_lot_begin_edit(lot);
|
|
qof_instance_set_dirty(QOF_INSTANCE(lot));
|
|
lot->splits = g_list_remove (lot->splits, split);
|
|
split->lot = NULL;
|
|
lot->is_closed = -1; /* force an is-closed computation */
|
|
|
|
if (NULL == lot->splits)
|
|
{
|
|
xaccAccountRemoveLot (lot->account, lot);
|
|
lot->account = NULL;
|
|
}
|
|
gnc_lot_commit_edit(lot);
|
|
qof_event_gen (&lot->inst.entity, QOF_EVENT_MODIFY, NULL);
|
|
}
|
|
|
|
/* ============================================================== */
|
|
/* Utility function, get earliest split in lot */
|
|
|
|
Split *
|
|
gnc_lot_get_earliest_split (GNCLot *lot)
|
|
{
|
|
SplitList *node;
|
|
Timespec ts;
|
|
Split *earliest = NULL;
|
|
|
|
ts.tv_sec = ((long long) ULONG_MAX);
|
|
ts.tv_nsec = 0;
|
|
if (!lot) return NULL;
|
|
|
|
for (node=lot->splits; node; node=node->next)
|
|
{
|
|
Split *s = node->data;
|
|
Transaction *trans = s->parent;
|
|
if (!trans) continue;
|
|
if ((ts.tv_sec > trans->date_posted.tv_sec) ||
|
|
((ts.tv_sec == trans->date_posted.tv_sec) &&
|
|
(ts.tv_nsec > trans->date_posted.tv_nsec)))
|
|
|
|
{
|
|
ts = trans->date_posted;
|
|
earliest = s;
|
|
}
|
|
}
|
|
|
|
return earliest;
|
|
}
|
|
|
|
Split *
|
|
gnc_lot_get_latest_split (GNCLot *lot)
|
|
{
|
|
SplitList *node;
|
|
Timespec ts;
|
|
Split *latest = NULL;
|
|
|
|
ts.tv_sec = 0;
|
|
ts.tv_nsec = 0;
|
|
if (!lot) return NULL;
|
|
|
|
for (node=lot->splits; node; node=node->next)
|
|
{
|
|
Split *s = node->data;
|
|
Transaction *trans = s->parent;
|
|
if (!trans) continue;
|
|
if ((ts.tv_sec < trans->date_posted.tv_sec) ||
|
|
((ts.tv_sec == trans->date_posted.tv_sec) &&
|
|
(ts.tv_nsec < trans->date_posted.tv_nsec)))
|
|
|
|
{
|
|
ts = trans->date_posted;
|
|
latest = s;
|
|
}
|
|
}
|
|
|
|
return latest;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
|
|
static QofObject gncLotDesc =
|
|
{
|
|
interface_version: QOF_OBJECT_VERSION,
|
|
e_type: GNC_ID_LOT,
|
|
type_label: "Lot",
|
|
create: (gpointer)gnc_lot_new,
|
|
book_begin: NULL,
|
|
book_end: NULL,
|
|
is_dirty: qof_collection_is_dirty,
|
|
mark_clean: qof_collection_mark_clean,
|
|
foreach: qof_collection_foreach,
|
|
printable: NULL,
|
|
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);
|
|
}
|
|
|
|
/* ========================== END OF FILE ========================= */
|