/* * gncInvoice.c -- the Core Business Invoice * Copyright (C) 2001,2002 Derek Atkins * Author: Derek Atkins */ #include "config.h" #include #include "Transaction.h" #include "Account.h" #include "messages.h" #include "gnc-numeric.h" #include "kvp_frame.h" #include "gnc-engine-util.h" #include "gnc-book-p.h" #include "GNCIdP.h" #include "QueryObject.h" #include "gnc-event-p.h" #include "gnc-lot.h" #include "gncBusiness.h" #include "gncEntry.h" #include "gncEntryP.h" #include "gncInvoice.h" #include "gncInvoiceP.h" #include "gncOwner.h" struct _gncInvoice { GNCBook *book; GUID guid; char * id; char * notes; char * billing_id; char * printname; GncBillTerm * terms; GList * entries; GncOwner owner; GncJob * job; Timespec date_opened; Timespec date_posted; gnc_commodity * common_commodity; Account * posted_acc; Transaction * posted_txn; GNCLot * posted_lot; gboolean active; gboolean dirty; }; #define _GNC_MOD_NAME GNC_INVOICE_MODULE_NAME #define GNC_INVOICE_ID "gncInvoice" #define GNC_INVOICE_GUID "invoice-guid" #define CACHE_INSERT(str) g_cache_insert(gnc_engine_get_string_cache(), (gpointer)(str)); #define CACHE_REMOVE(str) g_cache_remove(gnc_engine_get_string_cache(), (str)); #define SET_STR(member, str) { \ char * tmp; \ \ if (!safe_strcmp (member, str)) return; \ tmp = CACHE_INSERT (str); \ CACHE_REMOVE (member); \ member = tmp; \ } static void addObj (GncInvoice *invoice); static void remObj (GncInvoice *invoice); static void mark_invoice (GncInvoice *invoice); static void mark_invoice (GncInvoice *invoice) { invoice->dirty = TRUE; gnc_engine_generate_event (&invoice->guid, GNC_EVENT_MODIFY); } /* Create/Destroy Functions */ GncInvoice *gncInvoiceCreate (GNCBook *book) { GncInvoice *invoice; if (!book) return NULL; invoice = g_new0 (GncInvoice, 1); invoice->book = book; invoice->id = CACHE_INSERT (""); invoice->notes = CACHE_INSERT (""); invoice->billing_id = CACHE_INSERT (""); invoice->active = TRUE; xaccGUIDNew (&invoice->guid, book); addObj (invoice); gnc_engine_generate_event (&invoice->guid, GNC_EVENT_CREATE); return invoice; } void gncInvoiceDestroy (GncInvoice *invoice) { if (!invoice) return; gnc_engine_generate_event (&invoice->guid, GNC_EVENT_DESTROY); CACHE_REMOVE (invoice->id); CACHE_REMOVE (invoice->notes); CACHE_REMOVE (invoice->billing_id); g_list_free (invoice->entries); remObj (invoice); if (invoice->printname) g_free (invoice->printname); g_free (invoice); } /* Set Functions */ void gncInvoiceSetGUID (GncInvoice *invoice, const GUID *guid) { if (!invoice || !guid) return; if (guid_equal (guid, &invoice->guid)) return; remObj (invoice); invoice->guid = *guid; addObj (invoice); } void gncInvoiceSetID (GncInvoice *invoice, const char *id) { if (!invoice || !id) return; SET_STR (invoice->id, id); mark_invoice (invoice); } void gncInvoiceSetOwner (GncInvoice *invoice, GncOwner *owner) { if (!invoice || !owner) return; if (gncOwnerEqual (&invoice->owner, owner)) return; gncOwnerCopy (owner, &invoice->owner); mark_invoice (invoice); } void gncInvoiceSetDateOpened (GncInvoice *invoice, Timespec date) { if (!invoice) return; if (timespec_equal (&invoice->date_opened, &date)) return; invoice->date_opened = date; mark_invoice (invoice); } void gncInvoiceSetDatePosted (GncInvoice *invoice, Timespec date) { if (!invoice) return; if (timespec_equal (&invoice->date_posted, &date)) return; invoice->date_posted = date; mark_invoice (invoice); } void gncInvoiceSetTerms (GncInvoice *invoice, GncBillTerm *terms) { if (!invoice) return; if (invoice->terms == terms) return; if (invoice->terms) gncBillTermDecRef (invoice->terms); invoice->terms = terms; if (invoice->terms) gncBillTermIncRef (invoice->terms); mark_invoice (invoice); } void gncInvoiceSetBillingID (GncInvoice *invoice, const char *billing_id) { if (!invoice) return; SET_STR (invoice->billing_id, billing_id); mark_invoice (invoice); } void gncInvoiceSetNotes (GncInvoice *invoice, const char *notes) { if (!invoice || !notes) return; SET_STR (invoice->notes, notes); mark_invoice (invoice); } void gncInvoiceSetActive (GncInvoice *invoice, gboolean active) { if (!invoice) return; if (invoice->active == active) return; invoice->active = active; mark_invoice (invoice); } void gncInvoiceSetCommonCommodity (GncInvoice *invoice, gnc_commodity *com) { if (!invoice || !com) return; if (invoice->common_commodity && gnc_commodity_equal (invoice->common_commodity, com)) return; invoice->common_commodity = com; mark_invoice (invoice); } void gncInvoiceSetDirty (GncInvoice *invoice, gboolean dirty) { if (!invoice) return; invoice->dirty = dirty; } void gncInvoiceSetPostedTxn (GncInvoice *invoice, Transaction *txn) { if (!invoice) return; g_return_if_fail (invoice->posted_txn == NULL); invoice->posted_txn = txn; mark_invoice (invoice); } void gncInvoiceSetPostedLot (GncInvoice *invoice, GNCLot *lot) { if (!invoice) return; g_return_if_fail (invoice->posted_lot == NULL); invoice->posted_lot = lot; mark_invoice (invoice); } void gncInvoiceSetPostedAcc (GncInvoice *invoice, Account *acc) { if (!invoice) return; g_return_if_fail (invoice->posted_acc == NULL); invoice->posted_acc = acc; mark_invoice (invoice); } void gncInvoiceAddEntry (GncInvoice *invoice, GncEntry *entry) { GncInvoice *old; if (!invoice || !entry) return; old = gncEntryGetInvoice (entry); if (old == invoice) return; /* I already own this one */ if (old) gncInvoiceRemoveEntry (old, entry); gncEntrySetInvoice (entry, invoice); invoice->entries = g_list_insert_sorted (invoice->entries, entry, (GCompareFunc)gncEntryCompare); mark_invoice (invoice); } void gncInvoiceRemoveEntry (GncInvoice *invoice, GncEntry *entry) { if (!invoice || !entry) return; gncEntrySetInvoice (entry, NULL); invoice->entries = g_list_remove (invoice->entries, entry); mark_invoice (invoice); } /* Get Functions */ GNCBook * gncInvoiceGetBook (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->book; } const GUID * gncInvoiceGetGUID (GncInvoice *invoice) { if (!invoice) return NULL; return &(invoice->guid); } const char * gncInvoiceGetID (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->id; } GncOwner * gncInvoiceGetOwner (GncInvoice *invoice) { if (!invoice) return NULL; return &invoice->owner; } Timespec gncInvoiceGetDateOpened (GncInvoice *invoice) { Timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0; if (!invoice) return ts; return invoice->date_opened; } Timespec gncInvoiceGetDatePosted (GncInvoice *invoice) { Timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0; if (!invoice) return ts; return invoice->date_posted; } Timespec gncInvoiceGetDateDue (GncInvoice *invoice) { Transaction *txn; Timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0; if (!invoice) return ts; txn = gncInvoiceGetPostedTxn (invoice); if (!txn) return ts; return xaccTransRetDateDueTS (txn); } GncBillTerm * gncInvoiceGetTerms (GncInvoice *invoice) { if (!invoice) return 0; return invoice->terms; } const char * gncInvoiceGetBillingID (GncInvoice *invoice) { if (!invoice) return 0; return invoice->billing_id; } const char * gncInvoiceGetNotes (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->notes; } static GncOwnerType gncInvoiceGetOwnerType (GncInvoice *invoice) { GncOwner *owner; g_return_val_if_fail (invoice, GNC_OWNER_NONE); owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice)); return (gncOwnerGetType (owner)); } const char * gncInvoiceGetType (GncInvoice *invoice) { if (!invoice) return NULL; switch (gncInvoiceGetOwnerType (invoice)) { case GNC_OWNER_CUSTOMER: return _("Invoice"); case GNC_OWNER_VENDOR: return _("Bill"); default: return NULL; } } gnc_commodity * gncInvoiceGetCommonCommodity (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->common_commodity; } GNCLot * gncInvoiceGetPostedLot (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->posted_lot; } Transaction * gncInvoiceGetPostedTxn (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->posted_txn; } Account * gncInvoiceGetPostedAcc (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->posted_acc; } gboolean gncInvoiceGetActive (GncInvoice *invoice) { if (!invoice) return FALSE; return invoice->active; } GList * gncInvoiceGetEntries (GncInvoice *invoice) { if (!invoice) return NULL; return invoice->entries; } gboolean gncInvoiceIsDirty (GncInvoice *invoice) { if (!invoice) return FALSE; return invoice->dirty; } static void gncInvoiceAttachInvoiceToLot (GncInvoice *invoice, GNCLot *lot) { kvp_frame *kvp; kvp_value *value; if (!invoice || !lot) return; if (invoice->posted_lot) return; /* Cannot reset invoice's lot */ kvp = gnc_lot_get_slots (lot); value = kvp_value_new_guid (gncInvoiceGetGUID (invoice)); kvp_frame_set_slot_path (kvp, value, GNC_INVOICE_ID, GNC_INVOICE_GUID, NULL); kvp_value_delete (value); gncInvoiceSetPostedLot (invoice, lot); } GncInvoice * gncInvoiceGetInvoiceFromLot (GNCLot *lot) { kvp_frame *kvp; kvp_value *value; GUID *guid; GNCBook *book; if (!lot) return NULL; book = gnc_lot_get_book (lot); kvp = gnc_lot_get_slots (lot); value = kvp_frame_get_slot_path (kvp, GNC_INVOICE_ID, GNC_INVOICE_GUID, NULL); if (!value) return NULL; guid = kvp_value_get_guid (value); return xaccLookupEntity (gnc_book_get_entity_table (book), guid, _GNC_MOD_NAME); } static void gncInvoiceAttachInvoiceToTxn (GncInvoice *invoice, Transaction *txn) { kvp_frame *kvp; kvp_value *value; if (!invoice || !txn) return; if (invoice->posted_txn) return; /* Cannot reset invoice's txn */ xaccTransBeginEdit (txn); kvp = xaccTransGetSlots (txn); value = kvp_value_new_guid (gncInvoiceGetGUID (invoice)); kvp_frame_set_slot_path (kvp, value, GNC_INVOICE_ID, GNC_INVOICE_GUID, NULL); kvp_value_delete (value); xaccTransSetTxnType (txn, TXN_TYPE_INVOICE); xaccTransCommitEdit (txn); gncInvoiceSetPostedTxn (invoice, txn); } GncInvoice * gncInvoiceGetInvoiceFromTxn (Transaction *txn) { kvp_frame *kvp; kvp_value *value; GUID *guid; GNCBook *book; if (!txn) return NULL; book = xaccTransGetBook (txn); kvp = xaccTransGetSlots (txn); value = kvp_frame_get_slot_path (kvp, GNC_INVOICE_ID, GNC_INVOICE_GUID, NULL); if (!value) return NULL; guid = kvp_value_get_guid (value); return xaccLookupEntity (gnc_book_get_entity_table (book), guid, _GNC_MOD_NAME); } struct lotmatch { GncOwner *owner; gboolean reverse; }; static gboolean gnc_lot_match_owner_payment (GNCLot *lot, gpointer user_data) { struct lotmatch *lm = user_data; GncOwner owner_def, *owner; gnc_numeric balance = gnc_lot_get_balance (lot); /* Is this a payment lot */ if (gnc_numeric_positive_p (lm->reverse ? balance : gnc_numeric_neg (balance))) return FALSE; /* Is there an invoice attached? */ if (gncInvoiceGetInvoiceFromLot (lot)) return FALSE; /* Is it ours? */ if (!gncOwnerGetOwnerFromLot (lot, &owner_def)) return FALSE; owner = gncOwnerGetEndOwner (&owner_def); return gncOwnerEqual (owner, lm->owner); } Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, Timespec *post_date, Timespec *due_date, const char * memo) { Transaction *txn; GNCLot *lot = NULL; GList *iter; GList *splitinfo = NULL; gnc_numeric total; gboolean reverse; const char *name; if (!invoice || !acc) return NULL; /* Stabilize the Billing Terms of this invoice */ if (invoice->terms) gncInvoiceSetTerms (invoice, gncBillTermReturnChild (invoice->terms, TRUE)); /* Figure out if we need to "reverse" the numbers. */ reverse = (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_CUSTOMER); /* Find an existing payment-lot for this owner */ { LotList *lot_list; struct lotmatch lm; lm.reverse = reverse; lm.owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice)); lot_list = xaccAccountFindOpenLots (acc, gnc_lot_match_owner_payment, &lm, NULL); if (lot_list) lot = lot_list->data; g_list_free (lot_list); } /* Create a new lot for this invoice, if we need to do so */ if (!lot) lot = gnc_lot_new (invoice->book); /* Create a new transaction */ txn = xaccMallocTransaction (invoice->book); xaccTransBeginEdit (txn); name = gncOwnerGetName (gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice))); /* Set Transaction Description (Owner Name) , Num (invoice ID), Currency */ xaccTransSetDescription (txn, name); xaccTransSetNum (txn, gncInvoiceGetID (invoice)); xaccTransSetCurrency (txn, invoice->common_commodity); /* Entered and Posted at date */ if (post_date) { xaccTransSetDateEnteredTS (txn, post_date); xaccTransSetDatePostedTS (txn, post_date); gncInvoiceSetDatePosted (invoice, *post_date); } if (due_date) xaccTransSetDateDueTS (txn, due_date); /* Iterate through the entries; sum up everything for each account. * then create the appropriate splits in this txn. */ total = gnc_numeric_zero(); for (iter = gncInvoiceGetEntries(invoice); iter; iter = iter->next) { gnc_numeric value, tax; GList *taxes; GncEntry * entry = iter->data; Account *this_acc; /* Stabilize the TaxTable in this entry */ gncEntrySetTaxTable (entry, gncTaxTableReturnChild (gncEntryGetTaxTable (entry), TRUE)); /* Obtain the Entry's Value and TaxValues */ gncEntryGetValue (entry, &value, NULL, &tax, &taxes); /* add the value for the account split */ this_acc = gncEntryGetAccount (entry); if (this_acc) { if (gnc_numeric_check (value) == GNC_ERROR_OK) { splitinfo = gncAccountValueAdd (splitinfo, this_acc, value); total = gnc_numeric_add (total, value, GNC_DENOM_AUTO, GNC_DENOM_LCD); } else g_warning ("bad value in our entry"); } /* now merge in the TaxValues */ splitinfo = gncAccountValueAddList (splitinfo, taxes); /* ... and add the tax total */ if (gnc_numeric_check (tax) == GNC_ERROR_OK) total = gnc_numeric_add (total, tax, GNC_DENOM_AUTO, GNC_DENOM_LCD); else g_warning ("bad tax in our entry"); } /* for */ /* Iterate through the splitinfo list and generate the splits */ for (iter = splitinfo; iter; iter = iter->next) { Split *split; GncAccountValue *acc_val = iter->data; split = xaccMallocSplit (invoice->book); /* set action and memo? */ xaccSplitSetMemo (split, memo); xaccSplitSetBaseValue (split, (reverse ? gnc_numeric_neg (acc_val->value) : acc_val->value), invoice->common_commodity); xaccAccountBeginEdit (acc_val->account); xaccAccountInsertSplit (acc_val->account, split); xaccAccountCommitEdit (acc_val->account); xaccTransAppendSplit (txn, split); } /* Now create the Posted split (which is negative -- it's a credit) */ { Split *split = xaccMallocSplit (invoice->book); /* Set action/memo */ xaccSplitSetMemo (split, memo); xaccSplitSetAction (split, gncInvoiceGetType (invoice)); xaccSplitSetBaseValue (split, (reverse ? total : gnc_numeric_neg (total)), invoice->common_commodity); xaccAccountBeginEdit (acc); xaccAccountInsertSplit (acc, split); xaccAccountCommitEdit (acc); xaccTransAppendSplit (txn, split); /* add this split to the lot */ gnc_lot_add_split (lot, split); } /* Now attach this invoice to the txn, lot, and account */ gncInvoiceAttachInvoiceToLot (invoice, lot); gncInvoiceAttachInvoiceToTxn (invoice, txn); gncInvoiceSetPostedAcc (invoice, acc); xaccTransCommitEdit (txn); gncAccountValueDestroy (splitinfo); /* check the lot -- if we still look like a payment lot, then that * means we need to create a balancing split and create a new payment * lot for the next invoice */ total = gnc_lot_get_balance (lot); if (!reverse) total = gnc_numeric_neg (total); if (gnc_numeric_negative_p (total)) { Transaction *t2; GNCLot *lot2; Split *split; char *memo2 = _("Automatic Payment Forward"); t2 = xaccMallocTransaction (invoice->book); lot2 = gnc_lot_new (invoice->book); gncOwnerAttachToLot (gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice)), lot2); xaccTransBeginEdit (t2); xaccAccountBeginEdit (acc); /* Set Transaction Description (Owner Name), Currency */ xaccTransSetDescription (t2, name); xaccTransSetCurrency (t2, invoice->common_commodity); /* Entered and Posted at date */ if (post_date) { xaccTransSetDateEnteredTS (t2, post_date); xaccTransSetDatePostedTS (t2, post_date); } /* Balance out this lot */ split = xaccMallocSplit (invoice->book); xaccSplitSetMemo (split, memo2); xaccSplitSetBaseValue (split, gnc_numeric_neg (total), invoice->common_commodity); xaccAccountInsertSplit (acc, split); xaccTransAppendSplit (t2, split); gnc_lot_add_split (lot, split); /* And apply the pre-payment to a new lot */ split = xaccMallocSplit (invoice->book); xaccSplitSetMemo (split, memo2); xaccSplitSetBaseValue (split, total, invoice->common_commodity); xaccAccountInsertSplit (acc, split); xaccTransAppendSplit (t2, split); gnc_lot_add_split (lot2, split); xaccTransCommitEdit (t2); xaccAccountCommitEdit (acc); } return txn; } static gboolean gncInvoiceDateExists (Timespec *date) { g_return_val_if_fail (date, FALSE); if (date->tv_sec || date->tv_nsec) return TRUE; return FALSE; } gboolean gncInvoiceIsPosted (GncInvoice *invoice) { if (!invoice) return FALSE; return gncInvoiceDateExists (&(invoice->date_posted)); } GUID gncInvoiceRetGUID (GncInvoice *invoice) { if (!invoice) return *xaccGUIDNULL(); return invoice->guid; } GncInvoice * gncInvoiceLookupDirect (GUID guid, GNCBook *book) { if (!book) return NULL; return gncInvoiceLookup (book, &guid); } GncInvoice * gncInvoiceLookup (GNCBook *book, const GUID *guid) { if (!book || !guid) return NULL; return xaccLookupEntity (gnc_book_get_entity_table (book), guid, _GNC_MOD_NAME); } void gncInvoiceBeginEdit (GncInvoice *invoice) { if (!invoice) return; } void gncInvoiceCommitEdit (GncInvoice *invoice) { if (!invoice) return; if (invoice->dirty) gncBusinessSetDirtyFlag (invoice->book, _GNC_MOD_NAME, TRUE); invoice->dirty = FALSE; } int gncInvoiceCompare (GncInvoice *a, GncInvoice *b) { int compare; if (a == b) return 0; if (!a && b) return -1; if (a && !b) return 1; compare = safe_strcmp (a->id, b->id); if (compare) return compare; compare = timespec_cmp (&(a->date_opened), &(b->date_opened)); if (compare) return compare; compare = timespec_cmp (&(a->date_posted), &(b->date_posted)); if (compare) return compare; return guid_compare (&(a->guid), &(b->guid)); } /* Package-Private functions */ static void addObj (GncInvoice *invoice) { gncBusinessAddObject (invoice->book, _GNC_MOD_NAME, invoice, &invoice->guid); } static void remObj (GncInvoice *invoice) { gncBusinessRemoveObject (invoice->book, _GNC_MOD_NAME, &invoice->guid); } static void _gncInvoiceCreate (GNCBook *book) { gncBusinessCreate (book, _GNC_MOD_NAME); } static void _gncInvoiceDestroy (GNCBook *book) { gncBusinessDestroy (book, _GNC_MOD_NAME); } static gboolean _gncInvoiceIsDirty (GNCBook *book) { return gncBusinessIsDirty (book, _GNC_MOD_NAME); } static void _gncInvoiceMarkClean (GNCBook *book) { gncBusinessSetDirtyFlag (book, _GNC_MOD_NAME, FALSE); } static void _gncInvoiceForeach (GNCBook *book, foreachObjectCB cb, gpointer user_data) { gncBusinessForeach (book, _GNC_MOD_NAME, cb, user_data); } static const char * _gncInvoicePrintable (gpointer obj) { GncInvoice *invoice = obj; g_return_val_if_fail (invoice, NULL); if (invoice->dirty || invoice->printname == NULL) { if (invoice->printname) g_free (invoice->printname); invoice->printname = g_strdup_printf ("%s%s", invoice->id, gncInvoiceIsPosted (invoice) ? _(" (posted)") : ""); } return invoice->printname; } static GncObject_t gncInvoiceDesc = { GNC_OBJECT_VERSION, _GNC_MOD_NAME, "Invoice", _gncInvoiceCreate, _gncInvoiceDestroy, _gncInvoiceIsDirty, _gncInvoiceMarkClean, _gncInvoiceForeach, _gncInvoicePrintable, }; gboolean gncInvoiceRegister (void) { static QueryObjectDef params[] = { { INVOICE_ID, QUERYCORE_STRING, (QueryAccess)gncInvoiceGetID }, { INVOICE_OWNER, GNC_OWNER_MODULE_NAME, (QueryAccess)gncInvoiceGetOwner }, { INVOICE_OPENED, QUERYCORE_DATE, (QueryAccess)gncInvoiceGetDateOpened }, { INVOICE_DUE, QUERYCORE_DATE, (QueryAccess)gncInvoiceGetDateDue }, { INVOICE_POSTED, QUERYCORE_DATE, (QueryAccess)gncInvoiceGetDatePosted }, { INVOICE_IS_POSTED, QUERYCORE_BOOLEAN, (QueryAccess)gncInvoiceIsPosted }, { INVOICE_BILLINGID, QUERYCORE_STRING, (QueryAccess)gncInvoiceGetBillingID }, { INVOICE_NOTES, QUERYCORE_STRING, (QueryAccess)gncInvoiceGetNotes }, { INVOICE_ACC, GNC_ID_ACCOUNT, (QueryAccess)gncInvoiceGetPostedAcc }, { INVOICE_POST_TXN, GNC_ID_TRANS, (QueryAccess)gncInvoiceGetPostedTxn }, { INVOICE_TYPE, QUERYCORE_STRING, (QueryAccess)gncInvoiceGetType }, { INVOICE_TERMS, GNC_BILLTERM_MODULE_NAME, (QueryAccess)gncInvoiceGetTerms }, { QUERY_PARAM_BOOK, GNC_ID_BOOK, (QueryAccess)gncInvoiceGetBook }, { QUERY_PARAM_GUID, QUERYCORE_GUID, (QueryAccess)gncInvoiceGetGUID }, { NULL }, }; gncQueryObjectRegister (_GNC_MOD_NAME, (QuerySort)gncInvoiceCompare, params); return gncObjectRegister (&gncInvoiceDesc); } gint64 gncInvoiceNextID (GNCBook *book) { return gnc_book_get_counter (book, _GNC_MOD_NAME); }