gnucash/libgnucash/engine/gncOwner.c
John Ralls 16c9957cac Bug 798925 - Python bindings: "invalid unclassed pointer...
in cast to 'QofInstance'".

The list ended up with two entries of payment_lot resulting in a
dangling reference when one of them was freed.
2023-09-11 17:40:48 -07:00

1654 lines
50 KiB
C

/********************************************************************\
* gncOwner.c -- Business Interface: Object OWNERs *
* *
* 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 *
* *
\********************************************************************/
/*
* Copyright (C) 2001, 2002 Derek Atkins
* Copyright (C) 2003 Linas Vepstas <linas@linas.org>
* Copyright (c) 2005 Neil Williams <linux@codehelp.co.uk>
* Copyright (c) 2006 David Hampton <hampton@employees.org>
* Copyright (c) 2011 Geert Janssens <geert@kobaltwit.be>
* Author: Derek Atkins <warlord@MIT.EDU>
*/
#include <config.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <qofinstance-p.h>
#include "gncCustomerP.h"
#include "gncEmployeeP.h"
#include "gncJobP.h"
#include "gncOwner.h"
#include "gncOwnerP.h"
#include "gncVendorP.h"
#include "gncInvoice.h"
#include "gnc-commodity.h"
#include "Scrub2.h"
#include "Split.h"
#include "Transaction.h"
#include "engine-helpers.h"
#define _GNC_MOD_NAME GNC_ID_OWNER
#define GNC_OWNER_ID "gncOwner"
static QofLogModule log_module = GNC_MOD_ENGINE;
GncOwner * gncOwnerNew (void)
{
GncOwner *o;
o = g_new0 (GncOwner, 1);
o->type = GNC_OWNER_NONE;
return o;
}
void gncOwnerFree (GncOwner *owner)
{
if (!owner) return;
g_free (owner);
}
void gncOwnerBeginEdit (GncOwner *owner)
{
if (!owner) return;
switch (owner->type)
{
case GNC_OWNER_NONE :
case GNC_OWNER_UNDEFINED :
break;
case GNC_OWNER_CUSTOMER :
{
gncCustomerBeginEdit(owner->owner.customer);
break;
}
case GNC_OWNER_JOB :
{
gncJobBeginEdit(owner->owner.job);
break;
}
case GNC_OWNER_VENDOR :
{
gncVendorBeginEdit(owner->owner.vendor);
break;
}
case GNC_OWNER_EMPLOYEE :
{
gncEmployeeBeginEdit(owner->owner.employee);
break;
}
}
}
void gncOwnerCommitEdit (GncOwner *owner)
{
if (!owner) return;
switch (owner->type)
{
case GNC_OWNER_NONE :
case GNC_OWNER_UNDEFINED :
break;
case GNC_OWNER_CUSTOMER :
{
gncCustomerCommitEdit(owner->owner.customer);
break;
}
case GNC_OWNER_JOB :
{
gncJobCommitEdit(owner->owner.job);
break;
}
case GNC_OWNER_VENDOR :
{
gncVendorCommitEdit(owner->owner.vendor);
break;
}
case GNC_OWNER_EMPLOYEE :
{
gncEmployeeCommitEdit(owner->owner.employee);
break;
}
}
}
void gncOwnerDestroy (GncOwner *owner)
{
if (!owner) return;
switch (owner->type)
{
case GNC_OWNER_NONE :
case GNC_OWNER_UNDEFINED :
break;
case GNC_OWNER_CUSTOMER :
{
gncCustomerDestroy(owner->owner.customer);
break;
}
case GNC_OWNER_JOB :
{
gncJobDestroy(owner->owner.job);
break;
}
case GNC_OWNER_VENDOR :
{
gncVendorDestroy(owner->owner.vendor);
break;
}
case GNC_OWNER_EMPLOYEE :
{
gncEmployeeDestroy(owner->owner.employee);
break;
}
}
}
void gncOwnerInitUndefined (GncOwner *owner, gpointer obj)
{
if (!owner) return;
owner->type = GNC_OWNER_UNDEFINED;
owner->owner.undefined = obj;
}
void gncOwnerInitCustomer (GncOwner *owner, GncCustomer *customer)
{
if (!owner) return;
owner->type = GNC_OWNER_CUSTOMER;
owner->owner.customer = customer;
}
void gncOwnerInitJob (GncOwner *owner, GncJob *job)
{
if (!owner) return;
owner->type = GNC_OWNER_JOB;
owner->owner.job = job;
}
void gncOwnerInitVendor (GncOwner *owner, GncVendor *vendor)
{
if (!owner) return;
owner->type = GNC_OWNER_VENDOR;
owner->owner.vendor = vendor;
}
void gncOwnerInitEmployee (GncOwner *owner, GncEmployee *employee)
{
if (!owner) return;
owner->type = GNC_OWNER_EMPLOYEE;
owner->owner.employee = employee;
}
GncOwnerType gncOwnerGetType (const GncOwner *owner)
{
if (!owner) return GNC_OWNER_NONE;
return owner->type;
}
const char * gncOwnerGetTypeString (const GncOwner *owner)
{
GncOwnerType type = gncOwnerGetType(owner);
switch (type)
{
case GNC_OWNER_NONE:
return N_("None");
case GNC_OWNER_UNDEFINED:
return N_("Undefined");
case GNC_OWNER_CUSTOMER:
return N_("Customer");
case GNC_OWNER_JOB:
return N_("Job");
case GNC_OWNER_VENDOR:
return N_("Vendor");
case GNC_OWNER_EMPLOYEE:
return N_("Employee");
default:
PWARN ("Unknown owner type");
return NULL;
}
}
QofIdTypeConst
qofOwnerGetType(const GncOwner *owner)
{
return gncOwnerTypeToQofIdType(owner->type);
}
QofIdTypeConst gncOwnerTypeToQofIdType(GncOwnerType t)
{
QofIdTypeConst type = NULL;
switch (t)
{
case GNC_OWNER_NONE :
{
type = NULL;
break;
}
case GNC_OWNER_UNDEFINED :
{
type = NULL;
break;
}
case GNC_OWNER_CUSTOMER :
{
type = GNC_ID_CUSTOMER;
break;
}
case GNC_OWNER_JOB :
{
type = GNC_ID_JOB;
break;
}
case GNC_OWNER_VENDOR :
{
type = GNC_ID_VENDOR;
break;
}
case GNC_OWNER_EMPLOYEE :
{
type = GNC_ID_EMPLOYEE;
break;
}
}
return type;
}
QofInstance*
qofOwnerGetOwner (const GncOwner *owner)
{
QofInstance *ent;
if (!owner)
{
return NULL;
}
ent = NULL;
switch (owner->type)
{
case GNC_OWNER_NONE :
{
break;
}
case GNC_OWNER_UNDEFINED :
{
break;
}
case GNC_OWNER_CUSTOMER :
{
ent = QOF_INSTANCE(owner->owner.customer);
break;
}
case GNC_OWNER_JOB :
{
ent = QOF_INSTANCE(owner->owner.job);
break;
}
case GNC_OWNER_VENDOR :
{
ent = QOF_INSTANCE(owner->owner.vendor);
break;
}
case GNC_OWNER_EMPLOYEE :
{
ent = QOF_INSTANCE(owner->owner.employee);
break;
}
}
return ent;
}
void
qofOwnerSetEntity (GncOwner *owner, QofInstance *ent)
{
if (!owner || !ent)
{
return;
}
if (0 == g_strcmp0(ent->e_type, GNC_ID_CUSTOMER))
{
owner->type = GNC_OWNER_CUSTOMER;
gncOwnerInitCustomer(owner, (GncCustomer*)ent);
}
else if (0 == g_strcmp0(ent->e_type, GNC_ID_JOB))
{
owner->type = GNC_OWNER_JOB;
gncOwnerInitJob(owner, (GncJob*)ent);
}
else if (0 == g_strcmp0(ent->e_type, GNC_ID_VENDOR))
{
owner->type = GNC_OWNER_VENDOR;
gncOwnerInitVendor(owner, (GncVendor*)ent);
}
else if (0 == g_strcmp0(ent->e_type, GNC_ID_EMPLOYEE))
{
owner->type = GNC_OWNER_EMPLOYEE;
gncOwnerInitEmployee(owner, (GncEmployee*)ent);
}
else
{
owner->type = GNC_OWNER_NONE;
owner->owner.undefined = NULL;
}
}
gboolean GNC_IS_OWNER (QofInstance *ent)
{
if (!ent)
return FALSE;
return (GNC_IS_VENDOR(ent) ||
GNC_IS_CUSTOMER(ent) ||
GNC_IS_EMPLOYEE(ent) ||
GNC_IS_JOB(ent));
}
gpointer gncOwnerGetUndefined (const GncOwner *owner)
{
if (!owner) return NULL;
if (owner->type != GNC_OWNER_UNDEFINED) return NULL;
return owner->owner.undefined;
}
GncCustomer * gncOwnerGetCustomer (const GncOwner *owner)
{
if (!owner) return NULL;
if (owner->type != GNC_OWNER_CUSTOMER) return NULL;
return owner->owner.customer;
}
GncJob * gncOwnerGetJob (const GncOwner *owner)
{
if (!owner) return NULL;
if (owner->type != GNC_OWNER_JOB) return NULL;
return owner->owner.job;
}
GncVendor * gncOwnerGetVendor (const GncOwner *owner)
{
if (!owner) return NULL;
if (owner->type != GNC_OWNER_VENDOR) return NULL;
return owner->owner.vendor;
}
GncEmployee * gncOwnerGetEmployee (const GncOwner *owner)
{
if (!owner) return NULL;
if (owner->type != GNC_OWNER_EMPLOYEE) return NULL;
return owner->owner.employee;
}
void gncOwnerCopy (const GncOwner *src, GncOwner *dest)
{
if (!src || !dest) return;
if (src == dest) return;
*dest = *src;
}
gboolean gncOwnerEqual (const GncOwner *a, const GncOwner *b)
{
if (!a || !b) return FALSE;
if (gncOwnerGetType (a) != gncOwnerGetType (b)) return FALSE;
return (a->owner.undefined == b->owner.undefined);
}
int gncOwnerGCompareFunc (const GncOwner *a, const GncOwner *b)
{
if (gncOwnerEqual (a, b))
return 0;
else
return 1;
}
const char * gncOwnerGetID (const GncOwner *owner)
{
if (!owner) return NULL;
switch (owner->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
return NULL;
case GNC_OWNER_CUSTOMER:
return gncCustomerGetID (owner->owner.customer);
case GNC_OWNER_JOB:
return gncJobGetID (owner->owner.job);
case GNC_OWNER_VENDOR:
return gncVendorGetID (owner->owner.vendor);
case GNC_OWNER_EMPLOYEE:
return gncEmployeeGetID (owner->owner.employee);
}
}
const char * gncOwnerGetName (const GncOwner *owner)
{
if (!owner) return NULL;
switch (owner->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
return NULL;
case GNC_OWNER_CUSTOMER:
return gncCustomerGetName (owner->owner.customer);
case GNC_OWNER_JOB:
return gncJobGetName (owner->owner.job);
case GNC_OWNER_VENDOR:
return gncVendorGetName (owner->owner.vendor);
case GNC_OWNER_EMPLOYEE:
return gncEmployeeGetName (owner->owner.employee);
}
}
GncAddress * gncOwnerGetAddr (const GncOwner *owner)
{
if (!owner) return NULL;
switch (owner->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
case GNC_OWNER_JOB:
default:
return NULL;
case GNC_OWNER_CUSTOMER:
return gncCustomerGetAddr (owner->owner.customer);
case GNC_OWNER_VENDOR:
return gncVendorGetAddr (owner->owner.vendor);
case GNC_OWNER_EMPLOYEE:
return gncEmployeeGetAddr (owner->owner.employee);
}
}
gnc_commodity * gncOwnerGetCurrency (const GncOwner *owner)
{
if (!owner) return NULL;
switch (owner->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
return NULL;
case GNC_OWNER_CUSTOMER:
return gncCustomerGetCurrency (owner->owner.customer);
case GNC_OWNER_VENDOR:
return gncVendorGetCurrency (owner->owner.vendor);
case GNC_OWNER_EMPLOYEE:
return gncEmployeeGetCurrency (owner->owner.employee);
case GNC_OWNER_JOB:
return gncOwnerGetCurrency (gncJobGetOwner (owner->owner.job));
}
}
gboolean gncOwnerGetActive (const GncOwner *owner)
{
if (!owner) return FALSE;
switch (owner->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
return FALSE;
case GNC_OWNER_CUSTOMER:
return gncCustomerGetActive (owner->owner.customer);
case GNC_OWNER_VENDOR:
return gncVendorGetActive (owner->owner.vendor);
case GNC_OWNER_EMPLOYEE:
return gncEmployeeGetActive (owner->owner.employee);
case GNC_OWNER_JOB:
return gncJobGetActive (owner->owner.job);
}
}
const GncGUID * gncOwnerGetGUID (const GncOwner *owner)
{
if (!owner) return NULL;
switch (owner->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
return NULL;
case GNC_OWNER_CUSTOMER:
return qof_instance_get_guid (QOF_INSTANCE(owner->owner.customer));
case GNC_OWNER_JOB:
return qof_instance_get_guid (QOF_INSTANCE(owner->owner.job));
case GNC_OWNER_VENDOR:
return qof_instance_get_guid (QOF_INSTANCE(owner->owner.vendor));
case GNC_OWNER_EMPLOYEE:
return qof_instance_get_guid (QOF_INSTANCE(owner->owner.employee));
}
}
void
gncOwnerSetActive (const GncOwner *owner, gboolean active)
{
if (!owner) return;
switch (owner->type)
{
case GNC_OWNER_CUSTOMER:
gncCustomerSetActive (owner->owner.customer, active);
break;
case GNC_OWNER_VENDOR:
gncVendorSetActive (owner->owner.vendor, active);
break;
case GNC_OWNER_EMPLOYEE:
gncEmployeeSetActive (owner->owner.employee, active);
break;
case GNC_OWNER_JOB:
gncJobSetActive (owner->owner.job, active);
break;
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
break;
}
}
GncGUID gncOwnerRetGUID (GncOwner *owner)
{
const GncGUID *guid = gncOwnerGetGUID (owner);
if (guid)
return *guid;
return *guid_null ();
}
const GncOwner * gncOwnerGetEndOwner (const GncOwner *owner)
{
if (!owner) return NULL;
switch (owner->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
return NULL;
case GNC_OWNER_CUSTOMER:
case GNC_OWNER_VENDOR:
case GNC_OWNER_EMPLOYEE:
return owner;
case GNC_OWNER_JOB:
return gncJobGetOwner (owner->owner.job);
}
}
int gncOwnerCompare (const GncOwner *a, const GncOwner *b)
{
if (!a && !b) return 0;
if (!a && b) return 1;
if (a && !b) return -1;
if (a->type != b->type)
return (a->type - b->type);
switch (a->type)
{
case GNC_OWNER_NONE:
case GNC_OWNER_UNDEFINED:
default:
return 0;
case GNC_OWNER_CUSTOMER:
return gncCustomerCompare (a->owner.customer, b->owner.customer);
case GNC_OWNER_VENDOR:
return gncVendorCompare (a->owner.vendor, b->owner.vendor);
case GNC_OWNER_EMPLOYEE:
return gncEmployeeCompare (a->owner.employee, b->owner.employee);
case GNC_OWNER_JOB:
return gncJobCompare (a->owner.job, b->owner.job);
}
}
const GncGUID * gncOwnerGetEndGUID (const GncOwner *owner)
{
if (!owner) return NULL;
return gncOwnerGetGUID (gncOwnerGetEndOwner (owner));
}
void gncOwnerAttachToLot (const GncOwner *owner, GNCLot *lot)
{
if (!owner || !lot)
return;
gnc_lot_begin_edit (lot);
qof_instance_set (QOF_INSTANCE (lot),
GNC_OWNER_TYPE, (gint64)gncOwnerGetType (owner),
GNC_OWNER_GUID, gncOwnerGetGUID (owner),
NULL);
gnc_lot_commit_edit (lot);
}
gboolean gncOwnerGetOwnerFromLot (GNCLot *lot, GncOwner *owner)
{
GncGUID *guid = NULL;
QofBook *book;
GncOwnerType type = GNC_OWNER_NONE;
guint64 type64 = 0;
if (!lot || !owner) return FALSE;
book = gnc_lot_get_book (lot);
qof_instance_get (QOF_INSTANCE (lot),
GNC_OWNER_TYPE, &type64,
GNC_OWNER_GUID, &guid,
NULL);
type = (GncOwnerType) type64;
switch (type)
{
case GNC_OWNER_CUSTOMER:
gncOwnerInitCustomer (owner, gncCustomerLookup (book, guid));
break;
case GNC_OWNER_VENDOR:
gncOwnerInitVendor (owner, gncVendorLookup (book, guid));
break;
case GNC_OWNER_EMPLOYEE:
gncOwnerInitEmployee (owner, gncEmployeeLookup (book, guid));
break;
case GNC_OWNER_JOB:
gncOwnerInitJob (owner, gncJobLookup (book, guid));
break;
default:
guid_free (guid);
return FALSE;
}
guid_free (guid);
return (owner->owner.undefined != NULL);
}
gboolean gncOwnerGetOwnerFromTxn (Transaction *txn, GncOwner *owner)
{
Split *apar_split = NULL;
if (!txn || !owner) return FALSE;
if (xaccTransGetTxnType (txn) == TXN_TYPE_NONE)
return FALSE;
apar_split = xaccTransGetFirstAPARAcctSplit (txn, TRUE);
if (apar_split)
{
GNCLot *lot = xaccSplitGetLot (apar_split);
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
if (invoice)
gncOwnerCopy (gncInvoiceGetOwner (invoice), owner);
else if (!gncOwnerGetOwnerFromLot (lot, owner))
return FALSE;
return TRUE; // Got owner from either invoice or lot
}
return FALSE;
}
gboolean gncOwnerIsValid (const GncOwner *owner)
{
if (!owner) return FALSE;
return (owner->owner.undefined != NULL);
}
gboolean
gncOwnerLotMatchOwnerFunc (GNCLot *lot, gpointer user_data)
{
const GncOwner *req_owner = user_data;
GncOwner lot_owner;
const GncOwner *end_owner;
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
/* Determine the owner associated to the lot */
if (invoice)
/* Invoice lots */
end_owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice));
else if (gncOwnerGetOwnerFromLot (lot, &lot_owner))
/* Pre-payment lots */
end_owner = gncOwnerGetEndOwner (&lot_owner);
else
return FALSE;
/* Is this a lot for the requested owner ? */
return gncOwnerEqual (end_owner, req_owner);
}
gint
gncOwnerLotsSortFunc (GNCLot *lotA, GNCLot *lotB)
{
GncInvoice *ia, *ib;
time64 da, db;
ia = gncInvoiceGetInvoiceFromLot (lotA);
ib = gncInvoiceGetInvoiceFromLot (lotB);
if (ia)
da = gncInvoiceGetDateDue (ia);
else
da = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_earliest_split (lotA)));
if (ib)
db = gncInvoiceGetDateDue (ib);
else
db = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_earliest_split (lotB)));
return (da > db) - (da < db);
}
GNCLot *
gncOwnerCreatePaymentLotSecs (const GncOwner *owner, Transaction **preset_txn,
Account *posted_acc, Account *xfer_acc,
gnc_numeric amount, gnc_numeric exch, time64 date,
const char *memo, const char *num)
{
QofBook *book;
Split *split;
const char *name;
gnc_commodity *post_comm, *xfer_comm;
Split *xfer_split = NULL;
Transaction *txn = NULL;
GNCLot *payment_lot;
gnc_numeric xfer_amount = gnc_numeric_zero();
gnc_numeric txn_value = gnc_numeric_zero();
/* Verify our arguments */
if (!owner || !posted_acc || !xfer_acc) return NULL;
g_return_val_if_fail (owner->owner.undefined != NULL, NULL);
/* Compute the ancillary data */
book = gnc_account_get_book (posted_acc);
name = gncOwnerGetName (gncOwnerGetEndOwner ((GncOwner*)owner));
post_comm = xaccAccountGetCommodity (posted_acc);
xfer_comm = xaccAccountGetCommodity (xfer_acc);
// reverse = use_reversed_payment_amounts(owner);
if (preset_txn && *preset_txn)
txn = *preset_txn;
if (txn)
{
int i = 0;
/* Pre-existing transaction was specified. We completely clear it,
* except for a pre-existing transfer split. We're very conservative
* in preserving that one as it may have been reconciled already. */
xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc);
xaccTransBeginEdit (txn);
while (i < xaccTransCountSplits(txn))
{
Split *split = xaccTransGetSplit (txn, i);
if (split == xfer_split)
++i;
else
xaccSplitDestroy(split);
}
/* Note: don't commit transaction now - that would insert an imbalance split.*/
}
else
{
txn = xaccMallocTransaction (book);
xaccTransBeginEdit (txn);
}
/* Complete transaction setup */
xaccTransSetDescription (txn, name ? name : "");
if (!gnc_commodity_equal(xaccTransGetCurrency (txn), post_comm) &&
!gnc_commodity_equal (xaccTransGetCurrency (txn), xfer_comm))
xaccTransSetCurrency (txn, xfer_comm);
/* With all commodities involved known, define split amounts and txn value.
* - post amount (amount passed in as parameter) is always in post_acct commodity,
* - xfer amount requires conversion if the xfer account has a different
* commodity than the post account.
* - txn value requires conversion if the post account has a different
* commodity than the transaction */
if (gnc_commodity_equal(post_comm, xfer_comm))
xfer_amount = amount;
else
xfer_amount = gnc_numeric_mul (amount, exch, GNC_DENOM_AUTO,
GNC_HOW_RND_ROUND_HALF_UP);
if (gnc_commodity_equal(post_comm, xaccTransGetCurrency (txn)))
txn_value = amount;
else
txn_value = gnc_numeric_mul (amount, exch, GNC_DENOM_AUTO,
GNC_HOW_RND_ROUND_HALF_UP);
/* Insert a split for the transfer account if we don't have one yet */
if (!xfer_split)
{
/* The split for the transfer account */
xfer_split = xaccMallocSplit (book);
xaccSplitSetMemo (xfer_split, memo);
/* set per book option */
gnc_set_num_action (NULL, xfer_split, num, _("Payment"));
xaccAccountBeginEdit (xfer_acc);
xaccAccountInsertSplit (xfer_acc, xfer_split);
xaccAccountCommitEdit (xfer_acc);
xaccTransAppendSplit (txn, xfer_split);
xaccSplitSetAmount(xfer_split, xfer_amount); /* Payment in xfer account currency */
xaccSplitSetValue(xfer_split, txn_value); /* Payment in transaction currency */
}
/* For a pre-existing xfer split, let's check if the amount and value
* have changed. If so, update them and unreconcile. */
else if (!gnc_numeric_equal (xaccSplitGetAmount (xfer_split), xfer_amount) ||
!gnc_numeric_equal (xaccSplitGetValue (xfer_split), txn_value))
{
xaccSplitSetAmount (xfer_split, xfer_amount);
xaccSplitSetValue (xfer_split, txn_value);
xaccSplitSetReconcile (xfer_split, NREC);
}
/* Add a split in the post account */
split = xaccMallocSplit (book);
xaccSplitSetMemo (split, memo);
/* set per book option */
xaccSplitSetAction (split, _("Payment"));
xaccAccountBeginEdit (posted_acc);
xaccAccountInsertSplit (posted_acc, split);
xaccAccountCommitEdit (posted_acc);
xaccTransAppendSplit (txn, split);
xaccSplitSetAmount (split, gnc_numeric_neg (amount));
xaccSplitSetValue (split, gnc_numeric_neg (txn_value));
/* Create a new lot for the payment */
payment_lot = gnc_lot_new (book);
gncOwnerAttachToLot (owner, payment_lot);
gnc_lot_add_split (payment_lot, split);
/* Mark the transaction as a payment */
xaccTransSetNum (txn, num);
xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT);
/* Set date for transaction */
xaccTransSetDateEnteredSecs (txn, gnc_time (NULL));
xaccTransSetDatePostedSecs (txn, date);
/* Commit this new transaction */
xaccTransCommitEdit (txn);
if (preset_txn)
*preset_txn = txn;
return payment_lot;
}
typedef enum
{
is_equal = 8,
is_more = 4,
is_less = 2,
is_pay_split = 1
} split_flags;
Split *gncOwnerFindOffsettingSplit (GNCLot *lot, gnc_numeric target_amount)
{
SplitList *ls_iter = NULL;
Split *best_split = NULL;
gnc_numeric best_amt = { 0, 1};
gint best_flags = 0;
if (!lot)
return NULL;
for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next)
{
Split *split = ls_iter->data;
if (!split)
continue;
Transaction *txn = xaccSplitGetParent (split);
if (!txn)
{
// Ooops - the split doesn't belong to any transaction !
// This is not expected so issue a warning and continue with next split
PWARN("Encountered a split in a payment lot that's not part of any transaction. "
"This is unexpected! Skipping split %p.", split);
continue;
}
// Check if this split has the opposite sign of the target amount we want to offset
gnc_numeric split_amount = xaccSplitGetAmount (split);
if (gnc_numeric_positive_p (target_amount) == gnc_numeric_positive_p (split_amount))
continue;
// Ok we have found a split that potentially can offset the target value
// Let's see if it's better than what we have found already.
gint amt_cmp = gnc_numeric_compare (gnc_numeric_abs (split_amount),
gnc_numeric_abs (target_amount));
gint new_flags = 0;
if (amt_cmp == 0)
new_flags += is_equal;
else if (amt_cmp > 0)
new_flags += is_more;
else
new_flags += is_less;
if (xaccTransGetTxnType (txn) != TXN_TYPE_LINK)
new_flags += is_pay_split;
if ((new_flags >= best_flags) &&
(gnc_numeric_compare (gnc_numeric_abs (split_amount),
gnc_numeric_abs (best_amt)) > 0))
{
// The new split is a better match than what we found so far
best_split = split;
best_flags = new_flags;
best_amt = split_amount;
}
}
return best_split;
}
gboolean
gncOwnerReduceSplitTo (Split *split, gnc_numeric target_amount)
{
gnc_numeric split_amt = xaccSplitGetAmount (split);
if (gnc_numeric_positive_p (split_amt) != gnc_numeric_positive_p (target_amount))
return FALSE; // Split and target amount have to be of the same sign
if (gnc_numeric_equal (split_amt, target_amount))
return FALSE; // Split already has the target amount
if (gnc_numeric_zero_p (split_amt))
return FALSE; // We can't reduce a split that already has zero amount
Transaction *txn = xaccSplitGetParent (split);
xaccTransBeginEdit (txn);
/* Calculate new value for reduced split. This can be different from
* the reduced split's new amount (when account and transaction
* commodity differ) */
gnc_numeric split_val = xaccSplitGetValue (split);
gnc_numeric exch = gnc_numeric_div (split_val, split_amt,
GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
gint64 txn_comm_fraction = gnc_commodity_get_fraction (xaccTransGetCurrency (txn));
gnc_numeric target_val = gnc_numeric_mul (target_amount, exch,
txn_comm_fraction,
GNC_HOW_RND_ROUND_HALF_UP);
xaccSplitSetAmount (split, target_amount);
xaccSplitSetValue (split, target_val);
/* Calculate amount and value for remainder split.
* Note we calculate the remaining value by subtracting the new target value
* from the original split's value rather than multiplying the remaining
* amount with the exchange rate to avoid imbalances due to rounding errors. */
gnc_numeric rem_amt = gnc_numeric_sub (split_amt, target_amount, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
gnc_numeric rem_val = gnc_numeric_sub (split_val, target_val, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
Split *rem_split = xaccMallocSplit (xaccSplitGetBook (split));
xaccSplitCopyOnto (split, rem_split);
xaccSplitSetAmount (rem_split, rem_amt);
xaccSplitSetValue (rem_split, rem_val);
xaccSplitSetParent (rem_split, txn);
xaccTransCommitEdit (txn);
GNCLot *lot = xaccSplitGetLot (split);
gnc_lot_add_split (lot, rem_split);
return TRUE;
}
void
gncOwnerSetLotLinkMemo (Transaction *ll_txn)
{
gchar *memo_prefix = _("Offset between documents: ");
gchar *new_memo;
SplitList *lts_iter;
SplitList *splits = NULL, *siter;
GList *titles = NULL, *titer;
if (!ll_txn)
return;
if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK)
return;
// Find all splits in the lot link transaction that are also in a document lot
for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next)
{
Split *split = lts_iter->data;
GNCLot *lot;
GncInvoice *invoice;
gchar *title;
if (!split)
continue;
lot = xaccSplitGetLot (split);
if (!lot)
continue;
invoice = gncInvoiceGetInvoiceFromLot (lot);
if (!invoice)
continue;
title = g_strdup_printf ("%s %s", gncInvoiceGetTypeString (invoice), gncInvoiceGetID (invoice));
titles = g_list_prepend (titles, title);
splits = g_list_prepend (splits, split); // splits don't need to be sorted
}
if (!titles)
return; // We didn't find document lots
titles = g_list_sort (titles, (GCompareFunc)g_strcmp0);
// Create the memo as we'd want it to be
new_memo = g_strconcat (memo_prefix, titles->data, NULL);
for (titer = titles->next; titer; titer = titer->next)
{
gchar *tmp_memo = g_strconcat (new_memo, " - ", titer->data, NULL);
g_free (new_memo);
new_memo = tmp_memo;
}
g_list_free_full (titles, g_free);
// Update the memos of all the splits we found previously (if needed)
for (siter = splits; siter; siter = siter->next)
{
if (g_strcmp0 (xaccSplitGetMemo (siter->data), new_memo) != 0)
xaccSplitSetMemo (siter->data, new_memo);
}
g_list_free (splits);
g_free (new_memo);
}
/* Find an existing lot link transaction in the given lot
* Only use a lot link that already links at least two
* documents (to avoid perpetuating the lot link proliferation
* that happened in 2.6.0-2.6.3).
*/
static Transaction *
get_ll_transaction_from_lot (GNCLot *lot)
{
SplitList *ls_iter;
/* This should really only be called on a document lot */
if (!gncInvoiceGetInvoiceFromLot (lot))
return NULL;
/* The given lot is a valid document lot. Now iterate over all
* other lot links in this lot to find one more document lot.
*/
for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next)
{
Split *ls = ls_iter->data;
Transaction *ll_txn = xaccSplitGetParent (ls);
SplitList *ts_iter;
if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK)
continue;
for (ts_iter = xaccTransGetSplitList (ll_txn); ts_iter; ts_iter = ts_iter->next)
{
Split *ts = ts_iter->data;
GNCLot *tslot = xaccSplitGetLot (ts);
if (!tslot)
continue;
if (tslot == lot)
continue;
if (gncInvoiceGetInvoiceFromLot (lot))
return ll_txn; /* Got one more document lot - mission accomplished */
}
}
/* The lot doesn't have an ll_txn with the requested criteria... */
return NULL;
}
static void
gncOwnerCreateLotLink (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner)
{
const gchar *action = _("Lot Link");
Account *acct = gnc_lot_get_account (from_lot);
const gchar *name = gncOwnerGetName (gncOwnerGetEndOwner (owner));
Transaction *ll_txn = NULL;
gnc_numeric from_lot_bal, to_lot_bal;
time64 from_time, to_time;
time64 time_posted;
Split *split;
/* Sanity check */
if (!gncInvoiceGetInvoiceFromLot (from_lot) ||
!gncInvoiceGetInvoiceFromLot (to_lot))
return;
/* Determine transaction date based on lot splits */
from_time = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_latest_split (from_lot)));
to_time = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_latest_split (to_lot)));
if (from_time >= to_time)
time_posted = from_time;
else
time_posted = to_time;
/* Figure out how much we can offset between the lots */
from_lot_bal = gnc_lot_get_balance (from_lot);
to_lot_bal = gnc_lot_get_balance (to_lot);
if (gnc_numeric_compare (gnc_numeric_abs (from_lot_bal),
gnc_numeric_abs (to_lot_bal)) > 0)
from_lot_bal = gnc_numeric_neg (to_lot_bal);
else
to_lot_bal = gnc_numeric_neg (from_lot_bal);
xaccAccountBeginEdit (acct);
/* Look for a pre-existing lot link we can extend */
ll_txn = get_ll_transaction_from_lot (from_lot);
if (!ll_txn)
ll_txn = get_ll_transaction_from_lot (to_lot);
if (!ll_txn)
{
/* No pre-existing lot link. Create one. */
ll_txn = xaccMallocTransaction (gnc_lot_get_book (from_lot));
xaccTransBeginEdit (ll_txn);
xaccTransSetDescription (ll_txn, name ? name : "(Unknown)");
xaccTransSetCurrency (ll_txn, xaccAccountGetCommodity(acct));
xaccTransSetDateEnteredSecs (ll_txn, gnc_time (NULL));
xaccTransSetDatePostedSecs (ll_txn, time_posted);
xaccTransSetTxnType (ll_txn, TXN_TYPE_LINK);
}
else
{
time64 time = xaccTransRetDatePosted (ll_txn);
xaccTransBeginEdit (ll_txn);
/* Maybe we need to update the post date of the transaction ? */
if (time_posted > time)
xaccTransSetDatePostedSecs (ll_txn, time_posted);
}
/* Create a split for the from_lot */
split = xaccMallocSplit (gnc_lot_get_book (from_lot));
/* set Action using utility function */
gnc_set_num_action (NULL, split, NULL, action);
xaccAccountInsertSplit (acct, split);
xaccTransAppendSplit (ll_txn, split);
/* To offset the lot balance, the split must be of the opposite sign */
xaccSplitSetBaseValue (split, gnc_numeric_neg (from_lot_bal), xaccAccountGetCommodity(acct));
gnc_lot_add_split (from_lot, split);
/* Create a split for the to_lot */
split = xaccMallocSplit (gnc_lot_get_book (to_lot));
/* set Action using utility function */
gnc_set_num_action (NULL, split, NULL, action);
xaccAccountInsertSplit (acct, split);
xaccTransAppendSplit (ll_txn, split);
/* To offset the lot balance, the split must be of the opposite sign */
xaccSplitSetBaseValue (split, gnc_numeric_neg (to_lot_bal), xaccAccountGetCommodity(acct));
gnc_lot_add_split (to_lot, split);
xaccTransCommitEdit (ll_txn);
/* Do some post-cleaning on the lots
* The above actions may have created splits that are
* in the same transaction and lot. These can be merged.
*/
xaccScrubMergeLotSubSplits (to_lot, FALSE);
xaccScrubMergeLotSubSplits (from_lot, FALSE);
/* And finally set the same memo for all remaining splits
* It's a convenience for the users to identify all documents
* involved in the link.
*/
gncOwnerSetLotLinkMemo (ll_txn);
xaccAccountCommitEdit (acct);
}
static void gncOwnerOffsetLots (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner)
{
gnc_numeric target_offset;
Split *split;
/* from lot should not be a document lot because we're removing a split from there ! */
if (gncInvoiceGetInvoiceFromLot (from_lot))
{
PWARN ("from_lot %p is a document lot. That is not allowed in gncOwnerOffsetLots", from_lot);
return;
}
/* Get best matching split from from_lot to offset to_lot */
target_offset = gnc_lot_get_balance (to_lot);
if (gnc_numeric_zero_p (target_offset))
return; // to_lot is already balanced, nothing more to do
split = gncOwnerFindOffsettingSplit (from_lot, target_offset);
if (!split)
return; // No suitable offsetting split found, nothing more to do
/* If the offsetting split is bigger than the amount needed to balance
* to_lot, reduce the split so its reduced amount closes to_lot exactly.
* Note the negation in the reduction function. The split must be of
* opposite sign of to_lot's balance in order to be able to close it.
*/
if (gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetAmount (split)),
gnc_numeric_abs (target_offset)) > 0)
gncOwnerReduceSplitTo (split, gnc_numeric_neg (target_offset));
/* Move the reduced split from from_lot to to_lot */
gnc_lot_add_split (to_lot, split);
}
void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots)
{
GList *left_iter;
/* General note: in the code below the term "payment" can
* both mean a true payment or a document of
* the opposite sign (invoice vs credit note) relative to
* the lot being processed. In general this function will
* perform a balancing action on a set of lots, so you
* will also find frequent references to balancing instead. */
/* Payments can only be applied when at least an owner
* and a list of lots to use are given */
if (!owner) return;
if (!lots) return;
for (left_iter = lots; left_iter; left_iter = left_iter->next)
{
GNCLot *left_lot = left_iter->data;
gnc_numeric left_lot_bal;
gboolean left_lot_has_doc;
gboolean left_modified = FALSE;
Account *acct;
GList *right_iter;
/* Only attempt to apply payments to open lots.
* Note that due to the iterative nature of this function lots
* in the list may become empty/closed before they are evaluated as
* base lot, so we should check this for each lot. */
if (!left_lot)
continue;
if (gnc_lot_count_splits (left_lot) == 0)
{
gnc_lot_destroy (left_lot);
left_iter->data = NULL;
continue;
}
if (gnc_lot_is_closed (left_lot))
continue;
acct = gnc_lot_get_account (left_lot);
xaccAccountBeginEdit (acct);
left_lot_bal = gnc_lot_get_balance (left_lot);
left_lot_has_doc = (gncInvoiceGetInvoiceFromLot (left_lot) != NULL);
/* Attempt to offset left_lot with any of the remaining lots. To do so
* iterate over the remaining lots adding lot links or moving payments
* around.
*/
for (right_iter = left_iter->next; right_iter; right_iter = right_iter->next)
{
GNCLot *right_lot = right_iter->data;
gnc_numeric right_lot_bal;
gboolean right_lot_has_doc;
/* Only attempt to use open lots to balance the base lot.
* Note that due to the iterative nature of this function lots
* in the list may become empty/closed before they are evaluated as
* base lot, so we should check this for each lot. */
if (!right_lot)
continue;
if (gnc_lot_count_splits (right_lot) == 0)
{
gnc_lot_destroy (right_lot);
right_iter->data = NULL;
continue;
}
if (gnc_lot_is_closed (right_lot))
continue;
/* Balancing transactions for invoice/payments can only happen
* in the same account. */
if (acct != gnc_lot_get_account (right_lot))
continue;
/* Only attempt to balance if the base lot and balancing lot are
* of the opposite sign. (Otherwise we would increase the balance
* of the lot - Duh */
right_lot_bal = gnc_lot_get_balance (right_lot);
if (gnc_numeric_positive_p (left_lot_bal) == gnc_numeric_positive_p (right_lot_bal))
continue;
/* Ok we found two lots than can (partly) offset each other.
* Depending on the lot types, a different action is needed to accomplish this.
* 1. Both lots are document lots (invoices/credit notes)
* -> Create a lot linking transaction between the lots
* 2. Both lots are payment lots (lots without a document attached)
* -> Use part of the bigger lot to the close the smaller lot
* 3. One document lot with one payment lot
* -> Use (part of) the payment to offset (part of) the document lot,
* Which one will be closed depends on which is the bigger one
*/
right_lot_has_doc = (gncInvoiceGetInvoiceFromLot (right_lot) != NULL);
if (left_lot_has_doc && right_lot_has_doc)
gncOwnerCreateLotLink (left_lot, right_lot, owner);
else if (!left_lot_has_doc && !right_lot_has_doc)
{
gint cmp = gnc_numeric_compare (gnc_numeric_abs (left_lot_bal),
gnc_numeric_abs (right_lot_bal));
if (cmp >= 0)
gncOwnerOffsetLots (left_lot, right_lot, owner);
else
gncOwnerOffsetLots (right_lot, left_lot, owner);
}
else
{
GNCLot *doc_lot = left_lot_has_doc ? left_lot : right_lot;
GNCLot *pay_lot = left_lot_has_doc ? right_lot : left_lot;
// Ok, let's try to move a payment from pay_lot to doc_lot
gncOwnerOffsetLots (pay_lot, doc_lot, owner);
}
/* If we get here, then right_lot was modified
* If the lot has a document, send an event for send an event for it as well
* so it gets potentially updated as paid */
{
GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(right_lot);
if (this_invoice)
qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL);
}
left_modified = TRUE;
}
/* If left_lot was modified and the lot has a document,
* send an event for send an event for it as well
* so it gets potentially updated as paid */
if (left_modified)
{
GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(left_lot);
if (this_invoice)
qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL);
}
xaccAccountCommitEdit (acct);
}
}
/*
* Create a payment of "amount" for the owner and match it with
* the set of lots passed in.
* If
* - no lots were given
* - auto_pay is true
* then all open lots for the owner are considered.
*/
void
gncOwnerApplyPaymentSecs (const GncOwner *owner, Transaction **preset_txn,
GList *lots, Account *posted_acc, Account *xfer_acc,
gnc_numeric amount, gnc_numeric exch, time64 date,
const char *memo, const char *num, gboolean auto_pay)
{
GNCLot *payment_lot = NULL;
GList *selected_lots = NULL;
/* Verify our arguments */
if (!owner || !posted_acc
|| (!xfer_acc && !gnc_numeric_zero_p (amount)) ) return;
g_return_if_fail (owner->owner.undefined);
if (lots)
selected_lots = lots;
else if (auto_pay)
selected_lots = xaccAccountFindOpenLots (posted_acc, gncOwnerLotMatchOwnerFunc,
(gpointer)owner, NULL);
/* If there's a real amount to transfer create a lot for this payment */
if (!gnc_numeric_zero_p (amount))
payment_lot = gncOwnerCreatePaymentLotSecs (owner, preset_txn,
posted_acc, xfer_acc,
amount, exch, date, memo,
num);
/* And link the selected lots and the payment lot together as well
* as possible. If the payment was bigger than the selected
* documents/overpayments, only part of the payment will be
* used. Similarly if more documents were selected than the
* payment value set, not all documents will be marked as paid. */
if (payment_lot)
selected_lots = g_list_prepend (selected_lots, payment_lot);
gncOwnerAutoApplyPaymentsWithLots (owner, selected_lots);
g_list_free (selected_lots);
}
GList *
gncOwnerGetAccountTypesList (const GncOwner *owner)
{
g_return_val_if_fail (owner, NULL);
switch (gncOwnerGetType (owner))
{
case GNC_OWNER_CUSTOMER:
return (g_list_prepend (NULL, (gpointer)ACCT_TYPE_RECEIVABLE));
case GNC_OWNER_VENDOR:
case GNC_OWNER_EMPLOYEE:
return (g_list_prepend (NULL, (gpointer)ACCT_TYPE_PAYABLE));
break;
default:
return (g_list_prepend (NULL, (gpointer)ACCT_TYPE_NONE));
}
}
GList *
gncOwnerGetCommoditiesList (const GncOwner *owner)
{
g_return_val_if_fail (owner, NULL);
g_return_val_if_fail (gncOwnerGetCurrency(owner), NULL);
return (g_list_prepend (NULL, gncOwnerGetCurrency(owner)));
}
/*********************************************************************/
/* Owner balance calculation routines */
/*
* Given an owner, extract the open balance from the owner and then
* convert it to the desired currency.
*/
gnc_numeric
gncOwnerGetBalanceInCurrency (const GncOwner *owner,
const gnc_commodity *report_currency)
{
gnc_numeric balance = gnc_numeric_zero ();
QofBook *book;
gnc_commodity *owner_currency;
GNCPriceDB *pdb;
const gnc_numeric *cached_balance = NULL;
g_return_val_if_fail (owner, gnc_numeric_zero ());
book = qof_instance_get_book (qofOwnerGetOwner (owner));
owner_currency = gncOwnerGetCurrency (owner);
cached_balance = gncOwnerGetCachedBalance (owner);
if (cached_balance)
balance = *cached_balance;
else
{
/* No valid cache value found for balance. Let's recalculate */
GList *acct_list = gnc_account_get_descendants (gnc_book_get_root_account (book));
GList *acct_types = gncOwnerGetAccountTypesList (owner);
GList *acct_node;
/* For each account */
for (acct_node = acct_list; acct_node; acct_node = acct_node->next)
{
Account *account = acct_node->data;
GList *lot_list = NULL, *lot_node;
/* Check if this account can have lots for the owner, otherwise skip to next */
if (g_list_index (acct_types, (gpointer)xaccAccountGetType (account))
== -1)
continue;
if (!gnc_commodity_equal (owner_currency, xaccAccountGetCommodity (account)))
continue;
/* Get a list of open lots for this owner and account */
lot_list = xaccAccountFindOpenLots (account, gncOwnerLotMatchOwnerFunc,
(gpointer)owner, NULL);
/* For each lot */
for (lot_node = lot_list; lot_node; lot_node = lot_node->next)
{
GNCLot *lot = lot_node->data;
gnc_numeric lot_balance = gnc_lot_get_balance (lot);
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot(lot);
if (invoice)
balance = gnc_numeric_add (balance, lot_balance,
gnc_commodity_get_fraction (owner_currency), GNC_HOW_RND_ROUND_HALF_UP);
}
g_list_free (lot_list);
}
g_list_free (acct_list);
g_list_free (acct_types);
gncOwnerSetCachedBalance (owner, &balance);
}
pdb = gnc_pricedb_get_db (book);
if (report_currency)
balance = gnc_pricedb_convert_balance_latest_price (
pdb, balance, owner_currency, report_currency);
return balance;
}
/* XXX: Yea, this is broken, but it should work fine for Queries.
* We're single-threaded, right?
*/
static GncOwner *
owner_from_lot (GNCLot *lot)
{
static GncOwner owner;
if (!lot) return NULL;
if (gncOwnerGetOwnerFromLot (lot, &owner))
return &owner;
return NULL;
}
static void
reg_lot (void)
{
static QofParam params[] =
{
{ OWNER_FROM_LOT, _GNC_MOD_NAME, (QofAccessFunc)owner_from_lot, NULL },
{ NULL },
};
qof_class_register (GNC_ID_LOT, NULL, params);
}
gboolean gncOwnerGetOwnerFromTypeGuid (QofBook *book, GncOwner *owner, QofIdType type, GncGUID *guid)
{
if (!book || !owner || !type || !guid) return FALSE;
if (0 == g_strcmp0(type, GNC_ID_CUSTOMER))
{
GncCustomer *customer = gncCustomerLookup(book, guid);
gncOwnerInitCustomer(owner, customer);
return (NULL != customer);
}
else if (0 == g_strcmp0(type, GNC_ID_JOB))
{
GncJob *job = gncJobLookup(book, guid);
gncOwnerInitJob(owner, job);
return (NULL != job);
}
else if (0 == g_strcmp0(type, GNC_ID_VENDOR))
{
GncVendor *vendor = gncVendorLookup(book, guid);
gncOwnerInitVendor(owner, vendor);
return (NULL != vendor);
}
else if (0 == g_strcmp0(type, GNC_ID_EMPLOYEE))
{
GncEmployee *employee = gncEmployeeLookup(book, guid);
gncOwnerInitEmployee(owner, employee);
return (NULL != employee);
}
return 0;
}
gboolean gncOwnerRegister (void)
{
static QofParam params[] =
{
{ OWNER_TYPE, QOF_TYPE_INT64, (QofAccessFunc)gncOwnerGetType, NULL },
{ OWNER_CUSTOMER, GNC_ID_CUSTOMER, (QofAccessFunc)gncOwnerGetCustomer, NULL },
{ OWNER_JOB, GNC_ID_JOB, (QofAccessFunc)gncOwnerGetJob, NULL },
{ OWNER_VENDOR, GNC_ID_VENDOR, (QofAccessFunc)gncOwnerGetVendor, NULL },
{ OWNER_EMPLOYEE, GNC_ID_EMPLOYEE, (QofAccessFunc)gncOwnerGetEmployee, NULL },
{ OWNER_PARENT, GNC_ID_OWNER, (QofAccessFunc)gncOwnerGetEndOwner, NULL },
{ OWNER_PARENTG, QOF_TYPE_GUID, (QofAccessFunc)gncOwnerGetEndGUID, NULL },
{ OWNER_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncOwnerGetName, NULL },
{ QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)gncOwnerGetGUID, NULL },
{ NULL },
};
qof_class_register (GNC_ID_OWNER, (QofSortFunc)gncOwnerCompare, params);
reg_lot ();
return TRUE;
}
const gnc_numeric*
gncOwnerGetCachedBalance (const GncOwner *owner)
{
if (!owner) return NULL;
if (gncOwnerGetType (owner) == GNC_OWNER_CUSTOMER)
return gncCustomerGetCachedBalance (gncOwnerGetCustomer (owner));
else if (gncOwnerGetType (owner) == GNC_OWNER_VENDOR)
return gncVendorGetCachedBalance (gncOwnerGetVendor (owner));
else if (gncOwnerGetType (owner) == GNC_OWNER_EMPLOYEE)
return gncEmployeeGetCachedBalance (gncOwnerGetEmployee (owner));
return NULL;
}
void gncOwnerSetCachedBalance (const GncOwner *owner, const gnc_numeric *new_bal)
{
if (!owner) return;
if (gncOwnerGetType (owner) == GNC_OWNER_CUSTOMER)
gncCustomerSetCachedBalance (gncOwnerGetCustomer (owner), new_bal);
else if (gncOwnerGetType (owner) == GNC_OWNER_VENDOR)
gncVendorSetCachedBalance (gncOwnerGetVendor (owner), new_bal);
else if (gncOwnerGetType (owner) == GNC_OWNER_EMPLOYEE)
gncEmployeeSetCachedBalance (gncOwnerGetEmployee (owner), new_bal);
}