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.
gnucash/src/engine/Account.c

803 lines
22 KiB

/********************************************************************\
* Account.c -- the Account data structure *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997, 1998 Linas Vepstas *
* *
* 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, write to the Free Software *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
* *
* Author: Rob Clark *
* Internet: rclark@cs.hmc.edu *
* Address: 609 8th Street *
* Huntington Beach, CA 92648-4632 *
\********************************************************************/
#include <assert.h>
#include <string.h>
#include "config.h"
#include "Account.h"
#include "AccountP.h"
#include "Group.h"
#include "GroupP.h"
#include "date.h"
#include "messages.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "util.h"
int next_free_unique_account_id = 0;
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
/********************************************************************\
* Because I can't use C++ for this project, doesn't mean that I *
* can't pretend too! These functions perform actions on the *
* account data structure, in order to encapsulate the knowledge *
* of the internals of the Account in one file. *
\********************************************************************/
/********************************************************************\
\********************************************************************/
void
xaccInitAccount (Account * acc)
{
acc->id = next_free_unique_account_id;
next_free_unique_account_id ++;
acc->parent = NULL;
acc->children = NULL;
acc->balance = 0.0;
acc->cleared_balance = 0.0;
acc->reconciled_balance = 0.0;
acc->running_balance = 0.0;
acc->running_cleared_balance = 0.0;
acc->running_reconciled_balance = 0.0;
acc->flags = 0;
acc->type = -1;
acc->accountName = NULL;
acc->accountCode = NULL;
acc->description = NULL;
acc->notes = NULL;
acc->currency = NULL;
acc->numSplits = 0;
acc->splits = (Split **) _malloc (sizeof (Split *));
acc->splits[0] = NULL;
acc->changed = 0;
acc->open = 0;
}
/********************************************************************\
\********************************************************************/
Account *
xaccMallocAccount( void )
{
Account *acc = (Account *)_malloc(sizeof(Account));
xaccInitAccount (acc);
return acc;
}
/********************************************************************\
\********************************************************************/
void
xaccFreeAccount( Account *acc )
{
int i=0;
Split *s;
if (NULL == acc) return;
/* recursively free children */
xaccFreeAccountGroup (acc->children);
if (acc->accountName) free (acc->accountName);
if (acc->accountCode) free (acc->accountCode);
if (acc->description) free (acc->description);
if (acc->notes) free (acc->notes);
if (acc->currency) free (acc->currency);
/* any split pointing at this account needs to be unmarked */
i=0;
s = acc->splits[0];
while (s) {
s->acc = NULL;
i++;
s = acc->splits[i];
}
/* search for orphaned transactions, and delete them */
i=0;
s = acc->splits[0];
while (s) {
xaccSplitDestroy (s);
i++;
s = acc->splits[i];
}
/* free up array of split pointers */
_free (acc->splits);
acc->splits = NULL;
/* zero out values, just in case stray
* pointers are pointing here. */
acc->parent = NULL;
acc->children = NULL;
acc->balance = 0.0;
acc->cleared_balance = 0.0;
acc->reconciled_balance = 0.0;
acc->flags = 0;
acc->type = -1;
acc->accountName = NULL;
acc->description = NULL;
acc->notes = NULL;
acc->currency = NULL;
acc->changed = 0;
acc->open = 0;
_free(acc);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountBeginEdit (Account *acc)
{
if (!acc) return;
acc->open = 1;
}
void
xaccAccountCommitEdit (Account *acc)
{
if (!acc) return;
acc->changed = 1;
acc->open = 0;
}
/********************************************************************\
\********************************************************************/
int
xaccGetAccountID (Account *acc)
{
if (!acc) return -1;
return acc->id;
}
/********************************************************************\
\********************************************************************/
#define CHECK(acc) { \
if (0 == acc->open) { \
/* not today, soem day in the future ... */ \
/* printf ("Error: Account not open for editing\n"); */ \
/* assert (0); */ \
/* return; */ \
} \
}
/********************************************************************\
\********************************************************************/
void
xaccAccountInsertSplit ( Account *acc, Split *split )
{
int i,j;
Split **oldsplits;
if (!acc) return;
if (!split) return;
CHECK (acc);
/* mark the account as having changed, and
* the account group as requiring a save */
acc -> changed = TRUE;
if( acc->parent != NULL ) acc->parent->saved = FALSE;
/* if this split belongs to another acount, remove it from
* there first. We don't want to ever leave the system
* in an inconsistent state.
*/
if (split->acc) xaccAccountRemoveSplit (split->acc, split);
split->acc = acc;
oldsplits = acc->splits;
acc->numSplits ++;
acc->splits = (Split **)_malloc(((acc->numSplits) + 1) * sizeof(Split *));
/* Find the insertion point */
/* to get realy fancy, could use binary search. */
for(i = 0; i < (acc->numSplits - 1);) {
if(xaccSplitOrder(&(oldsplits[i]), &split) > 0) {
break;
} else {
acc->splits[i] = oldsplits[i];
}
i++; /* Don't put this in the loop guard! It'll go too far. */
}
/* Insertion point is now i */
//fprintf(stderr, "Insertion position is: %d\n", i);
/* Move all the other splits down (this could be done faster with memmove)*/
for( j = acc->numSplits; j > i; j--) {
acc->splits[j] = oldsplits[j - 1];
}
/* Now insert the new split */
acc->splits[i] = split;
/* make sure the array is NULL terminated */
acc->splits[acc->numSplits] = NULL;
_free(oldsplits);
xaccAccountRecomputeBalance (acc);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountRemoveSplit ( Account *acc, Split *split )
{
int i,j;
if (!acc) return;
if (!split) return;
CHECK (acc);
/* mark the account as having changed, and
* the account group as requiring a save */
acc -> changed = TRUE;
if( acc->parent != NULL ) acc->parent->saved = FALSE;
for( i=0,j=0; j<acc->numSplits; i++,j++ ) {
acc->splits[i] = acc->splits[j];
if (split == acc->splits[i]) i--;
}
split->acc = NULL;
acc->numSplits --;
/* make sure the array is NULL terminated */
acc->splits[acc->numSplits] = NULL;
}
/********************************************************************\
* xaccAccountRecomputeBalance *
* recomputes the partial balances and the current balance for *
* this account. *
*
* The way the computation is done depends on whether the partial
* balances are for a monetary account (bank, cash, etc.) or a
* certificate account (stock portfolio, mutual fund). For bank
* accounts, the invarient amount is the dollar amount. For share
* accounts, the invarient amount is the number of shares. For
* share accounts, the share price fluctuates, and the current
* value of such an account is the number of shares times the current
* share price.
*
* Part of the complexity of this computatation stems from the fact
* xacc uses a double-entry system, meaning that one transaction
* appears in two accounts: one account is debited, and the other
* is credited. When the transaction represents a sale of shares,
* or a purchase of shares, some care must be taken to compute
* balances correctly. For a sale of shares, the stock account must
* be debited in shares, but the bank account must be credited
* in dollars. Thus, two different mechanisms must be used to
* compute balances, depending on account type.
* *
* Args: account -- the account for which to recompute balances *
* Return: void *
\********************************************************************/
void
xaccAccountRecomputeBalance( Account * acc )
{
int i = 0;
double dbalance = 0.0;
double dcleared_balance = 0.0;
double dreconciled_balance = 0.0;
double share_balance = 0.0;
double share_cleared_balance = 0.0;
double share_reconciled_balance = 0.0;
double amt = 0.0;
Split *split, *last_split = NULL;
if( NULL == acc ) return;
if (FALSE == acc->changed) return;
split = acc->splits[0];
while (split) {
/* compute both dollar and share balances */
amt = split->damount;
share_balance += amt;
dbalance += amt * (split->share_price);
if( NREC != split -> reconciled ) {
share_cleared_balance += amt;
dcleared_balance += amt * (split->share_price);
}
if( YREC == split -> reconciled ) {
share_reconciled_balance += amt;
dreconciled_balance += amt * (split->share_price);
}
/* For bank accounts, the invarient subtotal is the dollar
* amount. For stock accoounts, the invarient is the share amount */
if ( (STOCK == acc->type) || ( MUTUAL == acc->type) ) {
split -> share_balance = share_balance;
split -> share_cleared_balance = share_cleared_balance;
split -> share_reconciled_balance = share_reconciled_balance;
split -> balance = split->share_price * share_balance;
split -> cleared_balance = split->share_price * share_cleared_balance;
split -> reconciled_balance = split->share_price * share_reconciled_balance;
} else {
split -> share_balance = dbalance;
split -> share_cleared_balance = dcleared_balance;
split -> share_reconciled_balance = dreconciled_balance;
split -> balance = dbalance;
split -> cleared_balance = dcleared_balance;
split -> reconciled_balance = dreconciled_balance;
}
last_split = split;
i++;
split = acc->splits[i];
}
if ( (STOCK == acc->type) || ( MUTUAL == acc->type) ) {
if (last_split) {
acc -> balance = share_balance * (last_split->share_price);
acc -> cleared_balance = share_cleared_balance * (last_split->share_price);
acc -> reconciled_balance = share_reconciled_balance * (last_split->share_price);
} else {
acc -> balance = 0.0;
acc -> cleared_balance = 0.0;
acc -> reconciled_balance = 0.0;
}
} else {
acc -> balance = dbalance;
acc -> cleared_balance = dcleared_balance;
acc -> reconciled_balance = dreconciled_balance;
}
return;
}
/********************************************************************\
* xaccCheckDateOrder *
* check this transaction to see if the date is in correct order *
* If it is not, reorder the transactions ... *
* *
* Args: acc -- the account to check *
* trans -- the transaction to check *
*
* Return: int -- non-zero if out of order *
\********************************************************************/
int
xaccCheckDateOrder (Account * acc, Split *split )
{
int outOfOrder = 0;
Split *s;
Split *prevSplit = NULL;
Split *nextSplit = NULL;
int position;
if (NULL == acc) return 0;
if (NULL == split) return 0;
/* find the split's location in the array */
position = 0;
s = acc->splits[0];
while (s) {
if (s == split) break;
position ++;
s = acc->splits[position];
}
if (!s) {
printf ("Internal Error: xaccCheckDateOrder(): ");
printf (" split %p not present in account \n", split);
return 0;
}
/* if zeroth split, then there is no previous */
if (0 < position) prevSplit = acc->splits [position-1];
/* if last split, OK, since array is null terminated, and last+1 is null */
nextSplit = acc->splits [position+1];
/* figure out if the transactions are out of order */
if (NULL != prevSplit) {
if( xaccTransOrder (&(prevSplit->parent), &(split->parent)) >0 ) outOfOrder = TRUE;
}
if (NULL != nextSplit) {
if( xaccTransOrder (&(split->parent), &(nextSplit->parent)) >0 ) outOfOrder = TRUE;
}
/* take care of re-ordering, if necessary */
if( outOfOrder ) {
xaccAccountRemoveSplit( acc, split );
xaccAccountInsertSplit( acc, split );
return 1;
}
return 0;
}
/********************************************************************\
* xaccCheckTransDateOrder *
* check this transaction to see if the date is in correct order *
* If it is not, reorder the transactions ... *
* This routine perfroms the check for both of the double-entry *
* transaction entries ... *
* *
* Args: trans -- the transaction to check *
* Return: int -- non-zero if out of order *
\********************************************************************/
int
xaccCheckTransDateOrder (Transaction *trans )
{
Account * acc;
int outOfOrder = 0;
Split *s;
int i = 0;
if (NULL == trans) return 0;
i=0;
s = trans->splits[0];
while (s) {
acc = (Account *) (s->acc);
outOfOrder += xaccCheckDateOrder (acc, s);
i++;
s = trans->splits[i];
}
if (outOfOrder) return 1;
return 0;
}
/********************************************************************\
\********************************************************************/
int
xaccIsAccountInList (Account * acc, Account **list)
{
Account * chk;
int nacc = 0;
int nappearances = 0;
if (!acc) return 0;
if (!list) return 0;
chk = list[0];
while (chk) {
if (acc == chk) nappearances ++;
nacc++;
chk = list[nacc];
}
return nappearances;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountRecomputeBalances( Account **list )
{
Account * acc;
int nacc = 0;
if (!list) return;
acc = list[0];
while (acc) {
xaccAccountRecomputeBalance (acc);
nacc++;
acc = list[nacc];
}
}
/********************************************************************\
\********************************************************************/
void
xaccZeroRunningBalances( Account **list )
{
Account * acc;
int nacc = 0;
if (!list) return;
acc = list[0];
while (acc) {
acc -> running_balance = 0.0;
acc -> running_cleared_balance = 0.0;
nacc++;
acc = list[nacc];
}
}
/********************************************************************\
\********************************************************************/
void
xaccMoveFarEnd (Split *split, Account *new_acc)
{
Split *partner_split = 0x0;
Transaction *trans;
Account * acc;
int numsplits = 0;
if (!split) return;
/* if the transaction has two splits, then the "far end"
* is the other one. Otherwise, fare end is undefined.
* If the new destination does not match the current dest,
* then move the far end of the split to the new location.
*/
trans = (Transaction *) (split->parent);
assert (trans);
assert (trans->splits);
numsplits = xaccCountSplits (trans->splits);
if (2 < numsplits) return;
if (split == trans->splits[0]) {
partner_split = trans->splits [1];
} else
if (split == trans->splits[1]) {
partner_split = trans->splits [0];
} else
if (new_acc) {
/* Gosh, the far end doesn't exist! create it! */
partner_split = xaccMallocSplit ();
xaccTransAppendSplit (trans, partner_split);
xaccAccountInsertSplit (new_acc, partner_split);
return;
} else {
/* no partner split, AND no far-end accouont. return */
return;
}
/* remove the partner split from the old account */
acc = (Account *) (partner_split->acc);
if (acc != new_acc) {
xaccAccountRemoveSplit (acc, partner_split);
xaccAccountInsertSplit (new_acc, partner_split);
}
}
/********************************************************************\
\********************************************************************/
void
xaccMoveFarEndByName (Split *split, const char *new_acc_name)
{
Account *acc;
if (!split) return;
if (0 == strcmp (SPLIT_STR, new_acc_name)) return;
acc = (Account *) split->acc;
acc = xaccGetPeerAccountFromName (acc, new_acc_name);
xaccMoveFarEnd (split, acc);
}
/********************************************************************\
\********************************************************************/
void
xaccConsolidateTransactions (Account * acc)
{
Split *sa, *sb;
int i,j;
if (!acc) return;
CHECK (acc);
for (i=0; i<acc->numSplits; i++) {
sa = acc->splits[i];
for (j=i+1; j<acc->numSplits; j++) {
sb = acc->splits[j];
/* if no match, then continue on in the loop.
* we really must match everything to get a duplicate */
if (sa->parent != sb->parent) continue;
if (sa->reconciled != sb->reconciled) continue;
if (0 == DEQ(sa->damount, sb->damount)) continue;
if (0 == DEQ(sa->share_price, sb->share_price)) continue;
if (strcmp (sa->memo, sb->memo)) continue;
#ifdef STILL_BROKEN
/* hack alert -- still broken from splits */
/* Free the transaction, and shuffle down by one.
* Need to shuffle in order to preserve date ordering. */
xaccFreeTransaction (tb);
for (k=j+1; k<acc->numTrans; k++) {
acc->transaction[k-1] = acc->transaction[k];
}
acc->transaction[acc->numTrans -1] = NULL;
acc->numTrans --;
#endif
}
}
}
/********************************************************************\
\********************************************************************/
void
xaccAccountSetType (Account *acc, int tip)
{
if (!acc) return;
CHECK (acc);
/* hack alert -- check for a valid type */
acc->type = tip;
}
void
xaccAccountSetName (Account *acc, char *str)
{
char * tmp;
if (!acc) return;
CHECK (acc);
/* make strdup before freeing */
tmp = strdup (str);
if (acc->accountName) free (acc->accountName);
acc->accountName = tmp;
}
void
xaccAccountSetDescription (Account *acc, char *str)
{
char * tmp;
if (!acc) return;
CHECK (acc);
/* make strdup before freeing */
tmp = strdup (str);
if (acc->description) free (acc->description);
acc->description = tmp;
}
void
xaccAccountSetNotes (Account *acc, char *str)
{
char * tmp;
if ((!acc) || (!str)) return;
CHECK (acc);
/* make strdup before freeing */
tmp = strdup (str);
if (acc->notes) free (acc->notes);
acc->notes = tmp;
}
/********************************************************************\
\********************************************************************/
AccountGroup *
xaccAccountGetChildren (Account *acc)
{
if (!acc) return NULL;
return (acc->children);
}
AccountGroup *
xaccAccountGetParent (Account *acc)
{
if (!acc) return NULL;
return (acc->parent);
}
int
xaccAccountGetType (Account *acc)
{
if (!acc) return 0;
return (acc->type);
}
char *
xaccAccountGetName (Account *acc)
{
if (!acc) return NULL;
return (acc->accountName);
}
char *
xaccAccountGetDescription (Account *acc)
{
if (!acc) return NULL;
return (acc->description);
}
char *
xaccAccountGetNotes (Account *acc)
{
if (!acc) return NULL;
return (acc->notes);
}
double
xaccAccountGetBalance (Account *acc)
{
if (!acc) return 0.0;
return (acc->balance);
}
double
xaccAccountGetClearedBalance (Account *acc)
{
if (!acc) return 0.0;
return (acc->cleared_balance);
}
double
xaccAccountGetReconciledBalance (Account *acc)
{
if (!acc) return 0.0;
return (acc->reconciled_balance);
}
Split *
xaccAccountGetSplit (Account *acc, int i)
{
if (!acc) return NULL;
if (!(acc->splits)) return NULL;
return (acc->splits[i]);
}
Split **
xaccAccountGetSplitList (Account *acc)
{
if (!acc) return NULL;
return (acc->splits);
}
/*************************** END OF FILE **************************** */