gnucash/libgnucash/engine/gnc-lot.h
2024-05-23 15:10:05 +02:00

642 lines
27 KiB
C

/********************************************************************\
* gnc-lot.h -- 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 *
\********************************************************************/
/** @addtogroup Engine
@{ */
/** @addtogroup Lot Lots: Core Function for AR/AP, Inventory, Stock Lots, Cap Gains
* One often needs to know that the item 'bought' in one transaction
* is the same one as the item 'sold' in a different transaction.
* Lots are used to make this association. One Lot holds all of the
* splits that involve the same item. A lot is typically formed when
* the item is bought, and is closed when the item is sold out.
* A lot need not be a single item, it can be a quantity of the same
* thing e.g. 500 gallons of paint (sold off a few gallons at a time).
*
* Lots are required to correctly implement invoices, inventory,
* depreciation and stock market investment gains. See the file
* src/doc/lots.txt for a detailed implementation overview.
*
* A lot is "closed" when the number of items in the lot has gone to zero.
* It is very easy to compute the gains/losses for a closed lot: it is the
* sum-total of the values of the items put into/taken out of the lot.
* (Realized) Gains on still-open lots can be computed by pro-rating the
* purchase prices.
*
* Lots are nothing more than a collection or grouping of splits in an
* account. All of the splits in a lot must belong to the same account;
* there's no mix-n-match. Thus, in this sense, a lot belongs to an
* account as well.
*
* Lots have an implicit "opening date": the date of the earliest split in
* the lot. The "close date" is the date of the split that brought the lot
* item balance down to zero.
*
@{ */
/** @file gnc-lot.h
*
* @author Created by Linas Vepstas May 2002
* @author Copyright (c) 2002,2003 Linas Vepstas <linas@linas.org>
*/
#ifndef GNC_LOT_H
#define GNC_LOT_H
//typedef struct _GncLotClass GNCLotClass;
#include "qof.h"
#include "gnc-engine.h"
/*#include "gnc-lot-p.h"*/
#include "gncInvoice.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
QofInstanceClass parent_class;
} GncLotClass;
#define GNCLotClass GncLotClass
/* --- type macros --- */
#define GNC_TYPE_LOT (gnc_lot_get_type ())
#define GNC_LOT(o) \
(G_TYPE_CHECK_INSTANCE_CAST ((o), GNC_TYPE_LOT, GNCLot))
#define GNC_LOT_CLASS(k) \
(G_TYPE_CHECK_CLASS_CAST((k), GNC_TYPE_LOT, GNCLotClass))
#define GNC_IS_LOT(o) \
(G_TYPE_CHECK_INSTANCE_TYPE ((o), GNC_TYPE_LOT))
#define GNC_IS_LOT_CLASS(k) \
(G_TYPE_CHECK_CLASS_TYPE ((k), GNC_TYPE_LOT))
#define GNC_LOT_GET_CLASS(o) \
(G_TYPE_INSTANCE_GET_CLASS ((o), GNC_TYPE_LOT, GNCLotClass))
GType gnc_lot_get_type(void);
/*@ dependent @*/
GNCLot * gnc_lot_new (QofBook *);
void gnc_lot_destroy (GNCLot *);
/*@ dependent @*/
GNCLot * gnc_lot_lookup (const GncGUID *guid, QofBook *book);
QofBook * gnc_lot_get_book (GNCLot *);
void gnc_lot_begin_edit (GNCLot *lot);
void gnc_lot_commit_edit (GNCLot *lot);
/** Adds a split to this lot.
*
* @note
* - *All* splits in a lot must be in the same account.
* - Splits are added unconditionally, with
* no regard for the accounting policy. To enforce a particular
* accounting policy, use the xaccSplitAssignToLot() routine
* instead.
*/
void gnc_lot_add_split (GNCLot *, Split *);
/** Adds a split from this lot.
*/
void gnc_lot_remove_split (GNCLot *, Split *);
/** Returns a list of all the splits in this lot.
*
* @returns GList
*
* This GList is owned and managed by the lot.
* - Do *not* free it when done.
* - Do *not* modify it directly
* - Calls to either gnc_lot_add_split() or gnc_lot_remove_split()
* will invalidate the returned pointer
*/
SplitList * gnc_lot_get_split_list (const GNCLot *);
gint gnc_lot_count_splits (const GNCLot *);
/** Returns the account with which this lot is associated.
*/
/*@ dependent @*/
Account * gnc_lot_get_account (const GNCLot *);
void gnc_lot_set_account(GNCLot*, Account*);
/** Returns the invoice with which this lot is associated.
*/
/*@ dependent @*/
GncInvoice * gnc_lot_get_cached_invoice (const GNCLot *lot);
void gnc_lot_set_cached_invoice(GNCLot* lot, GncInvoice *invoice);
/** Returns the lot balance.
* This balance will be expressed in the lot account's commodity.
*/
gnc_numeric gnc_lot_get_balance (GNCLot *);
/** Computes both the balance and value in the lot considering only splits
* in transactions prior to the one containing the given split or other
* splits in the same transaction.
* The first return value is the amount and the second is the value. */
void gnc_lot_get_balance_before (const GNCLot *, const Split *,
gnc_numeric *, gnc_numeric *);
/** Returns closed status of the given lot.
* A lot is closed if its balance is zero. This
* routine is faster than using gnc_lot_get_balance() because
* once the balance goes to zero, this fact is cached.
*
* @returns boolean
*/
gboolean gnc_lot_is_closed (GNCLot *);
/** Convenience routine to identify the earliest date in the lot.
* It loops over all of the splits in the lot, and returns the split
* with the earliest split->transaction->date_posted. It may not
* necessarily identify the lot opening split.
*/
Split * gnc_lot_get_earliest_split (GNCLot *lot);
/** Convenience routineto identify the date this lot was closed.
* It simply loops over all of the splits in the lot, and returns
* the split with the latest split->transaction->date_posted.
*/
Split * gnc_lot_get_latest_split (GNCLot *lot);
/** Reset closed flag so that it will be recalculated. */
void gnc_lot_set_closed_unknown(GNCLot*);
/** @name Get/set the account title and notes.
@{ */
const char * gnc_lot_get_title (const GNCLot *);
const char * gnc_lot_get_notes (const GNCLot *);
void gnc_lot_set_title (GNCLot *, const char *);
void gnc_lot_set_notes (GNCLot *, const char *);
/** @} */
/** @todo Document this function ? */
GNCLot * gnc_lot_make_default (Account * acc);
#define gnc_lot_get_guid(X) qof_entity_get_guid(QOF_INSTANCE(X))
#define LOT_IS_CLOSED "is-closed?"
#define LOT_BALANCE "balance"
#define LOT_TITLE "lot-title"
#define LOT_NOTES "notes"
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* GNC_LOT_H */
/** @} */
/** @} */
/** \page lotsoverview Lots Architecture & Implementation Overview
Linas Vepstas <linas@linas.org>
Last Revised May 2004
API \ref Lot
One often needs to know that the item 'bought' in one transaction
is the same one as the item 'sold' in a different transaction.
Lots are used to make this association. One Lot holds all of the
splits that involve the same item. A lot is typically formed when
the item is bought, and is closed when the item is sold out.
A lot need not be a single item, it can be a quantity of the same
thing e.g. 500 gallons of paint (sold off a few gallons at a time).
Lots are required to correctly implement invoices, inventory,
depreciation and stock market investment gains.
'Lots' capture a fundamental accounting idea behind AR/AP, billing,
inventory, capital gains, depreciation and the like. The basic idea
is that a set of items is tracked together as a 'lot'; the date of
creation of the lot helps determine when a bill is due, when depreciation
starts, or the tax rate for capital gains on a stock market investment.
\section lotsdefines Definition
In GnuCash, a 'lot' will consist of a set of splits identified with
a unique lot number. Any given split can belong to one lot and one
lot only. All splits in a lot must also belong to the same account.
Lots have a 'balance': the sum of all the splits in the lot. A lot
is 'opened' when the first split is assigned to it. The date of that
split is the 'opening date' of the lot, and its quantity is the
'opening balance'. A lot is 'closed' when its balance is zero;
once closed, a lot cannot be re-opened. Open lots are always carried
forward into the current open book; closed lots are left behind in the
book in which they were closed.
\section How Lots Are Used
The following sections review how lots are used to implement various
accounting devices, and some of the related issues.
\subsection Billing
Tracking lots is the 'definition' of A/R and A/P. You want to be able
to say that this bill is 120 days old, and its balance has been partly
paid off; and you want to be able to do this whether or not there are
also other bills, of different ages, to the same company. In GnuCash,
a 'bill' is tracked as a lot: The 'billing date' is the opening date
of the lot, and the 'balance due' is the balance of the lot. When the
bill is 'paid in full', the lot is closed. The average age of
receivables can be computed by traversing all lots, etc. Additional
information about bills, such as a due date or payment terms, should
be stored as kvp values associated with the lot.
\subsection Billing Example
Normally, there is a one-to-one correspondence between bills and
lots: there is one lot per bill, and one bill per lot. (Note: this
is not how gnucash invoices are currently implemented; this is a
proposed implementation.)
For example:
\verbatim
invoice #258 customer XXX
Order placed 20 December 2001
quant (10) gallons paint $10/each $100
quant (2) paintbrushes $15/each $30
sales tax $8.27
------------
Total due $138.27
Payment received 24 january 2002 $50 Balance Due: $88.27
Payment received 13 february 2002 $60 Balance Due: $28.27
Payment received 18 march 2002 $28.27 PAID IN FULL
\endverbatim
In this example, the lot encompasses four transactions, dated
December, January, February and March. The December transaction
opens the lot, and gives it a non-zero balance. To be precise,
the lot actually consists of four splits, belonging to four
different transactions. All four splits are a part of an imagined
"Accounts Receivable-Billing" account. The first split is for
$138.27, the second split is for $50, the third split is for $60,
and the fourth split is for $28.27. Note that the sales-tax
split, and th paint/paint-brush splits are *NOT* a part of this
lot. They are only a part of the transaction that opened this lot.
Note also that this example might also encompass two other lots:
the transfer of paint may belong to a lot in the "Paint Inventory"
account, and the split describing the paintbrushes might be a part
of a lot in the "Paintbrush Inventory" account. These lots should
not be confused with the invoice lot.
\subsection lotsinventory Inventory
The correct way of handling inventory under GnuCash is to have a
separate account for each widget type. The account balance represents
how many widgets there are in the warehouse. Lots offer an additional
level of detail that can be useful when, for example, the widgets have
an expiration date (e.g. milk) or vary slightly from batch to batch
(e.g paint), and creating a new account to track these differences
would be too heavy-weight.
In order to track widgets as single units (and prohibit fractional
widgets), set the currency denominator to 1. This will prevent
fractional widgets from being transferred into/out of an account.
Note that using accounts to track the inventory of a grocery store
causes an explosion of accounts, and this explosion would overwhelm
many of the account GUI's in GnuCash. The GUI should probably be
modified to treat inventory accounts in a special way, e.g. by
not listing them in account lists, and providing an alternate
management GUI.
\subsection Capital Gains
In the United States, gains on stock investments are taxed at different
rates depending on how long they were held before being sold. By using
lots with an investment account, it becomes easy to track when any given
share was bought or sold, and thus, the length of time that share was
held.
Note, however, that using lots might cause some confusion for naive
users, since some transactions might be split across different lots.
For example, the user may have conceptually made the following
transactions:
\verbatim
> Date Desc Buy Sell Price Value
> 18/1/01 XCORP 500 $10.00 $5000.00
> 21/3/01 XCORP 500 $12.00 $6000.00
> 14/7/02 XCORP 750 $20.00 $15000.00
\endverbatim
However, the two buy transactions create different lots (because
you can't add to a stock-investment lot after its been created, because
the purchases occurred on different dates). Thus, when the shares are
sold, the sale is split across two lots:
\verbatim
> Date Desc Lot Buy Sell Price Value
> 18/1/01 XCORP 187 500 $10.00 $5000.00
> 21/3/01 XCORP 188 500 $12.00 $6000.00
> 14/7/02 XCORP 187 500 $20.00 $10000.00
> 14/7/02 XCORP 188 250 $20.00 $5000.00
\endverbatim
In the above, lot 187 was closed, and lot 188 has a balance of 250
shares. Note that we used a FIFO accounting method in this example:
the oldest shares were sold first.
Note also, that by using lots in this example, we are able to accurately
compute the gains in this transaction: it is 500*($20-$10) + 250*($20-$12)
= $5000+$2000 = $7000. If we had used LIFO accounting, and sold the
youngest shares first, then the profits would have been 500*($20-$12)
+ 250*($20-$10) = $4000 + 2500 = $6500. Thus, different accounting
methods do affect income, and thus the tax rate.
Note that the two ledgers, the 'without-lots' and the 'with-lots'
ledgers look different, even though the conceptual transactions are
the same. If a naive user was expecting a 'without-lots' ledger,
and is shown a 'with lots' ledger, they may get confused. I don't
know of any simple way of dealing with this. In order to have lots
work (thereby allowing cap gains reports to work correctly), the GnuCash
engine *must* use the 'with-lots' representation of the data. If the
GUI wants to hide the fact that there are lots under the covers, it
must figure out a way of doing this (i.e. re-combining splits) on
its own.
\section lotsclosing Closing of Books
A few words on lots and the closing of books. Without lots, there
is really no way of correctly implementing book closing. That is
because some reports, such as the capital-gains report, need to know
not only the account balance, but also the purchase dates. Lots
provide the natural mechanism for storing this information.
When a book is closed, any open lots must be moved into/kept with
the open book. Since the splits in a lot belong to transactions,
and transactions are 'atomic', this means that the associated
transactions must be moved into the open book as well. A lot
is considered closed when its balance is zero; when a book is closed,
all of the lots that were closed stay with that book. That is,
closed lots are not propagated forward into the currently open book.
Actually, its slightly more subtle than that. Not only must open
lots stay with the open book, but so must all transactions that
have splits that participate in the open lot, and, by extension,
all closed lots that participate in these 'open transactions',
ad infinitum.
\section lotsdouble The "Double Balance" Requirement
The following is a proposal for how to handle both realized
and unrealized gains/losses by means of "adjusting transactions."
It works for simple cases, but has issues for more complex cases.
Canonical transaction balancing: If all splits in a transaction
are in the same 'currency' as the transaction currency, then the
sum of the splits *must* equal zero. This is the old, existing
double-entry requirement as implemented in Gnucash, and doesn't
change.
If some splits are in one commodity, and others in another, then
we can't force a zero balance as above. Instead, we will force
a different requirement, the 'double-balance' requirement:
- All splits that are *not* in the transaction currency C
must be made a part of a lot. (Thus, for example,
the transaction currency C is dollars, whereas the split
commodity is 'S', shares of RHAT. If a transaction
has C in dollars, and 'S' in RHAT, then the S split must
be made a part of a Lot.)
- The lot will have a designated 'lot currency' L that must
be the same as the C of every split in the lot. One
cannot enter a split into the lot if C != L. (That is,
if I'm working with a Lot of RHAT shares, then *all*
splits in the lot must belong to dollar-denominated
transactions.)
- When a lot is closed, we must have the 'double-balance'
condition: The sum total of all 'S' is zero, and the
sum total of all 'C' is zero. Thus, if I buy 100 shares
of RHAT for $5 and sell 100 shares of RHAT for $10, then
I *must* also add an 'adjusting transaction' for zero
shares of RHAT, at $500. If there is no adjusting transaction,
then the lot cannot be closed. If sum 'S' is zero,
while sum 'C' is not zero, then the lot is declared to
be 'out-of-balance', and an 'adjusting transaction' must
be forced.
It is only by 'closing a lot' that one is able to regain
'perfect balance' in the books. That is, the 'double-balance'
requirement is the generalization of the 'double-entry'
requirement for stock accounts.
Note that because the 'adjusting transaction' has one split
in dollars, and another split in RHAT shares (albeit for zero
RHAT shares), it evades the old double-entry requirement,
and will not be flagged as 'out of balance'. Note also
that because the 'adjusting transaction' contains a split
holding S (albeit zero S), it *must* be a part of a Lot.
The above seems to work for simple stock-transactions, but
fails in other more complex cases. Here's an example.
Imagine 'S' is in euros, instead of 'RHAT'. So I sell
100 dollars, and buy 110 euros. This causes a lot to open
up for the euros, with the lot currency 'L' in dollars.
Now I try to transfer the euros to other euro accounts.
What happens to the lot? Do I have to give up on it?
How can I save this bad situation?
A similar problem occurs for more complex stock transactions:
If I buy 100 shares of RHAT with Schwab, and transfer them
to another account with Etrade, then I have the same lot
closing problem. There's an even worse scenario, where
I move to Brazil, and take my RHAT stock (purchased in dollars)
to my Brazilian broker (who will sell them for cruzeiros).
Is the correct answer to just 'punt' in these cases?
How is the closing of books to be handled in such a case?
GUI Elements:
- The user should be able to specify a default 'realized-gain'
account that is associated with a stock account.
- The user should be able to specify a default 'unrealized-gain'
account that is associated with a stock account.
\section lotsfifo FIFO's
What is a FIFO ? A FIFO is a type of accounting policy where
income from selling inventory is accounted by selling the oldest
inventory first. Thus, the profit/loss of the sale corresponds
to the difference in price between when it was bought and sold.
FIFO's are also used in depreciation schedules, and in other places.
Currently the only policy that is implemented in the cap-gains
code is the FIFO policy. I believe that it's been abstracted
properly, so that it should be easy to add other policies,
e.g. LIFO. See policy.c for what would need to be implemented.
\section lotsimplement Implementation
Every split has a pointer to a lot (which may be null). A lot has a list
of splits in it (so that the other splits in the lot can be easily found).
A lot does not need to maintain a balance (this is easy enough to calculate
on the fly). A lot has a kvp tree (for storage of lot-related date, such
as due dates for invoices, etc. A lot has a GUID.
From the memory-management and data-base management point of view, lots
belong to accounts. The GnuCash account structure maintains a list of
lots so that all lots belonging to an account can be quickly retrieved.
(In principle, the lots can be found by scanning every split in the
account, but this is a painful process.)
\section lotscapgains Implementing Cap Gains (Is a Pain in the Neck)
Although Lots provide a good conceptual framework for determining
gains or losses when a lot is closed, cap-gains on half-open
lots present additional complexities. Consider the following
stock purchase and subsequent sale of half the stock:
Account A is a stock account
Account B is a bank account
Account C is an income account
\verbatim
Acct A Txn Acct B Acct C
Date Action Amt Prc Value Amt Amt
1/1/01 buy 100s $10 $1000 ($1000) -
2/2/02 sell (50)s $25 $1250 $1250 -
2/2/02 gain - - $750 $750
\endverbatim
The gain, shown in the third line, is computed as a straight
sum of purchase price to sale price.
Should the above be represented as two transactions, or as three?
One could, in principle, combine the second and third lines into
one transaction. However, this has some drawbacks: computing
the overall balance of such a transaction is tricky, because
it has so many different splits (including, possibly, splits
for brokerage fees, tax, etc. not shown). The alternative
is to represent each line as a separate transaction. This has
other drawbacks: If the date, amount, price or value is adjusted
for the second transaction, the corresponding values must be
adjusted for the third, and vice-versa.
Both schemes pose trouble for the register display: we want
the stock register to show the gain as if it were a part of
the stock sale; but the third line is a pair of splits, and
we want to show only one of these two splits. Whichever method
is chosen, the register has to filter out some of the splits
that it shows.
The implementation that seems best is to represent the sale
with two separate transactions: one for the sale itself, and a
separate one for the gains. This makes computing the balance
easier, although it makes the logic for setting the date
more complex. Ughh..
\section loanscapnotes Cap Gains Implementation Notes
Cap Gains will be handled by GnuCash as described above, using
two distinct transactions. These transactions will be marked up
using KVP, pointing to each other, so that the one can be found
from the other. Implementation in src/engine/cap-gains.c
Quick API Overview:
- xaccSplitGetCapGains(): Returns the capital gains associated with
a split. Split must have been a sale/purchase in a previously
opened lot.
- xaccSplitAssignToLot(): If a split is not already in a lot,
then it places it into a lot, using a FIFO accounting policy.
\section lotscapimplement Cap Gains Actual Implementation
Cap Gains are noted by creating a separate transaction with two
splits in it. One of the splits is as described above: zero
amount, non-zero value. There is a GUI requirement that when
the looking at a gains transaction, certain things need to be
kept in sync with the transaction that is the source of the gains.
In order to accomplish this, the engine uses a set of 'dirty'
flags, and a pair of pointers between the gains split and the
source split, so that the one can be quickly found from the other.
Things kept in sync:
- date posted
- value
- void status
- other things ?
Things not kept in sync:
- kvp trees
- description, memo, action.
The posted date is kept in sync using a data-constraint scheme.
If xaccTransactionSetDatePosted() is called, the date change is
accepted, and the split is marked date-dirty. When the transaction
is committed (using xaccTransCommitEdit()), the date-dirty flag
is evaluated, and, if needed, the date changes are propagated/rolled
back on the appropriate gains splits. Currently, one can only change
the date on the gains-source transaction; the date on the
gains-recording split cannot be changed.
The value recorded by the gains transaction is updated whenever
the value of the source changes. The actual update is done by
the xaccSplitComputeCapGains() routine, via xaccScrubLot(), which
is called at the time of xaccTransCommitEdit(). Note that two
different things can affect the gains: a change in the value of
the sale, and a change of the value of the purchase. A set of
dirty flags are used to track these.
If the amount of a split changes, then the lot that its in becomes
potentially unbalanced. This requires the lot membership to be
recomputed; this in turn may require the split to be split into
pieces, or to be recombined into one from several pieces.
\section lotsconversion Conversion
As Lots are put into production, old GnuCash datasets
will need to be converted. Conversion will be done by running
all splits in an account through an accounting policy (currently,
there is only one policy, a FIFO). The goal of the policy is to
match up purchases and sales so that these can be assigned to a Lot.
The conversion algorithm will work as follows:
\verbatim
for each account {
loop over splits {
// perform the 'double-balance' check
if (split commodity != transaction currency) account needs conversion
}
if account needs conversion
for each split {
If (split amount > 0) create new lot, put split in lot.
If (split amount < 0) find oldest lot, put split in that lot
}
}
\endverbatim
See the file Scrub2.h for details of the low-level API, and Scrub3.h
for the high-level API.
There is a bit of a problem with this conversion procedure: If the
user had previously recorded cap gains using a 'handmade' version of
lots, those cap gains will be ignored and will throw off balances.
User will need to hand-edit to recover.
*/