r21996 - gnucash/trunk/src - Rework interaction between payments and invoices.

Geert Janssens gjanssens at code.gnucash.org
Fri Feb 10 10:34:18 EST 2012


Author: gjanssens
Date: 2012-02-10 10:34:18 -0500 (Fri, 10 Feb 2012)
New Revision: 21996
Trac: http://svn.gnucash.org/trac/changeset/21996

Modified:
   gnucash/trunk/src/business/business-gnome/dialog-payment.c
   gnucash/trunk/src/engine/Transaction.h
   gnucash/trunk/src/engine/gncInvoice.c
   gnucash/trunk/src/engine/gncInvoice.h
   gnucash/trunk/src/engine/gncOwner.c
   gnucash/trunk/src/engine/gncOwner.h
Log:
Rework interaction between payments and invoices.
This commit deals with invoice posting and unposting only.
Before invoices and payments shared the same lot (meaning a payment was
added to an existing invoice lot or vice versa).
Now payments and lots have their own separate lots and payments are
linked to invoices via linking transactions. This opens the way to "pay"
invoices with credit notes.

Modified: gnucash/trunk/src/business/business-gnome/dialog-payment.c
===================================================================
--- gnucash/trunk/src/business/business-gnome/dialog-payment.c	2012-02-10 15:34:02 UTC (rev 21995)
+++ gnucash/trunk/src/business/business-gnome/dialog-payment.c	2012-02-10 15:34:18 UTC (rev 21996)
@@ -277,7 +277,7 @@
             if (trans)
                 doc_date = xaccTransRetDatePostedTS (trans);
             else
-                continue; /* Not valid split in this lot, skip it */
+                continue; /* No valid split in this lot, skip it */
         }
         doc_date_str = gnc_print_date (doc_date);
 
@@ -563,7 +563,7 @@
         return;
     }
 
-    /* Ok, now post the damn thing */
+    /* Ok, now execute the payment */
     gnc_suspend_gui_refresh ();
     {
         const char *memo, *num;
@@ -574,6 +574,7 @@
         memo = gtk_entry_get_text (GTK_ENTRY (pw->memo_entry));
         num = gtk_entry_get_text (GTK_ENTRY (pw->num_entry));
         date = gnc_date_edit_get_date_ts (GNC_DATE_EDIT (pw->date_edit));
+        /* FIXME Get a lotlist from the dialog */
 
         /* If the 'acc' account and the post account don't have the same
            currency, we need to get the user to specify the exchange rate */

Modified: gnucash/trunk/src/engine/Transaction.h
===================================================================
--- gnucash/trunk/src/engine/Transaction.h	2012-02-10 15:34:02 UTC (rev 21995)
+++ gnucash/trunk/src/engine/Transaction.h	2012-02-10 15:34:18 UTC (rev 21996)
@@ -119,6 +119,7 @@
 #define TXN_TYPE_NONE	 '\0' /**< No transaction type       */
 #define TXN_TYPE_INVOICE 'I'  /**< Transaction is an invoice */
 #define TXN_TYPE_PAYMENT 'P'  /**< Transaction is a payment  */
+#define TXN_TYPE_LINK    'L'  /**< Transaction is a link between (invoice and payment) lots  */
 /** @} */
 
 /* --------------------------------------------------------------- */

Modified: gnucash/trunk/src/engine/gncInvoice.c
===================================================================
--- gnucash/trunk/src/engine/gncInvoice.c	2012-02-10 15:34:02 UTC (rev 21995)
+++ gnucash/trunk/src/engine/gncInvoice.c	2012-02-10 15:34:18 UTC (rev 21996)
@@ -1179,37 +1179,6 @@
     }
 }
 
-struct lotmatch
-{
-    const GncOwner *owner;
-    gboolean positive_balance;
-};
-
-static gboolean
-gnc_lot_match_owner_payment (GNCLot *lot, gpointer user_data)
-{
-    struct lotmatch *lm = user_data;
-    GncOwner owner_def;
-    const GncOwner *owner;
-    gnc_numeric balance = gnc_lot_get_balance (lot);
-
-    /* Is this a payment lot */
-    if (gnc_numeric_positive_p (lm->positive_balance ? 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);
-}
-
 static gboolean gncInvoicePostAddSplit (QofBook *book,
                                     Account *acc,
                                     Transaction *txn,
@@ -1286,13 +1255,13 @@
     GList *iter;
     GList *splitinfo = NULL;
     gnc_numeric total;
-    gboolean positive_balance;
     gboolean is_cust_doc;
     gboolean is_cn;
     const char *name, *type;
     char *lot_title;
     Account *ccard_acct = NULL;
     const GncOwner *owner;
+    gboolean autopay = TRUE; /* FIXME this will have to become a user selectable option at some point */
 
     if (!invoice || !acc) return NULL;
 
@@ -1304,11 +1273,6 @@
         gncInvoiceSetTerms (invoice,
                             gncBillTermReturnChild (invoice->terms, TRUE));
 
-    /* Does the invoice/credit note have a positive effect on the balance ? Note that
-     * payments for such invoices by definition then have a negative effect on the balance.
-     * This is used to determine which open lots can be considered when posting the invoice. */
-    positive_balance = gncInvoiceAmountPositive (invoice);
-
     /* GncEntry functions need to know if the invoice/credit note is for a customer or a vendor/employee. */
     is_cust_doc = (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_CUSTOMER);
     is_cn = gncInvoiceGetIsCreditNote (invoice);
@@ -1318,25 +1282,8 @@
     if (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_EMPLOYEE)
         ccard_acct = gncEmployeeGetCCard (gncOwnerGetEmployee (owner));
 
-    /* Find an existing payment-lot for this owner */
-    {
-        LotList *lot_list;
-        struct lotmatch lm;
-
-        lm.positive_balance = positive_balance;
-        lm.owner = owner;
-
-        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 (book);
+    /* Create a new lot for this invoice */
+    lot = gnc_lot_new (book);
     gnc_lot_begin_edit (lot);
 
     type = gncInvoiceGetTypeString (invoice);
@@ -1536,74 +1483,15 @@
 
     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
-     *
-     * we're looking for a positive balance for bill/AP, and a negative balance
-     * for invoice/AR.
-     * (because bill payments debit AP accounts and invoice payments
-     * credit AR accounts)
-     */
-    total = gnc_lot_get_balance (lot);
-
-    if ( (gnc_numeric_negative_p (total) && positive_balance) ||
-            (gnc_numeric_positive_p (total) && !positive_balance) )
-    {
-        Transaction *t2;
-        GNCLot *lot2;
-        Split *split;
-        /* Translators: This is the memo of an auto-created split */
-        char *memo2 = _("Automatic Payment Forward");
-        char *action2 = _("Auto Split");
-
-        t2 = xaccMallocTransaction (book);
-        lot2 = gnc_lot_new (book);
-        gnc_lot_begin_edit (lot2);
-        gncOwnerAttachToLot (gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice)),
-                             lot2);
-
-        xaccTransBeginEdit (t2);
-        xaccAccountBeginEdit (acc);
-
-        /* Set Transaction Description (Owner Name), Currency */
-        xaccTransSetDescription (t2, name ? name : "");
-        xaccTransSetCurrency (t2, invoice->currency);
-
-        /* Entered and Posted at date */
-        xaccTransSetDateEnteredSecs (t2, time(NULL));
-        if (post_date)
-            xaccTransSetDatePostedTS (t2, post_date);
-
-        /* Balance out this lot */
-        split = xaccMallocSplit (book);
-        xaccSplitSetMemo (split, memo2);
-        xaccSplitSetAction (split, action2);
-        xaccAccountInsertSplit (acc, split);
-        xaccTransAppendSplit (t2, split);
-        // the value of total used here is correct for both bill/AP and
-        // invoice/AR. See the comment before this if block
-        xaccSplitSetBaseValue (split, gnc_numeric_neg (total),
-                               invoice->currency);
-        gnc_lot_add_split (lot, split);
-
-        /* And apply the pre-payment to a new lot */
-        split = xaccMallocSplit (book);
-        xaccSplitSetMemo (split, memo2);
-        xaccSplitSetAction (split, action2);
-        xaccAccountInsertSplit (acc, split);
-        xaccTransAppendSplit (t2, split);
-        xaccSplitSetBaseValue (split, total, invoice->currency);
-        gnc_lot_add_split (lot2, split);
-
-        gnc_lot_commit_edit (lot2);
-        xaccTransCommitEdit (t2);
-        xaccAccountCommitEdit (acc);
-    }
-
     gnc_lot_commit_edit (lot);
     gncInvoiceCommitEdit (invoice);
 
+    /* If requested, attempt to automatically apply open payments
+     * and reverse documents to this lot to close it (or at least
+     * reduce its balance) */
+    if (autopay)
+        gncInvoiceAutoApplyPayments (invoice);
+
     return txn;
 }
 
@@ -1612,6 +1500,7 @@
 {
     Transaction *txn;
     GNCLot *lot;
+    GList *lot_split_list, *lot_split_iter;
 
     if (!invoice) return FALSE;
     if (!gncInvoiceIsPosted (invoice)) return FALSE;
@@ -1632,6 +1521,71 @@
     gncInvoiceDetachFromLot (lot);
     gncOwnerAttachToLot (&invoice->owner, lot);
 
+    /* Check if this invoice was linked to other lots (payments/inverse signed
+     * invoices).
+     * If this is the case, recreate the link transaction between all the remaining lots.
+     *
+     * Note that before GnuCash 2.6 payments were not stored in separate lots, but
+     * always ended up in invoice lots when matched to an invoice. Over-payments
+     * were copied to a new lot, to which later an invoice was added again and so on.
+     * These over-payments were handled with automatic payment forward transactions.
+     * You could consider these transactions to be links between lots as well, but
+     * to avoid some unexpected behavior, these are will not be altered here.
+     */
+    lot_split_list = gnc_lot_get_split_list (lot);
+    for (lot_split_iter = lot_split_list; lot_split_iter; lot_split_iter = lot_split_iter->next)
+    {
+        Split *split = lot_split_iter->data;
+        GList *other_split_list, *list_iter;
+        GNCLot *other_lot;
+        Transaction *other_txn = xaccSplitGetParent (split);
+        GList *lot_list = NULL;
+
+        /* Only work with transactions that link invoices and payments.
+         * Note: this check also catches the possible case of NULL splits. */
+        if (xaccTransGetTxnType (other_txn) != TXN_TYPE_LINK)
+            continue;
+
+        /* Save a list of lots this linking transaction linked to */
+        other_split_list = xaccTransGetSplitList (other_txn);
+        for (list_iter = other_split_list; list_iter; list_iter = list_iter->next)
+        {
+            Split *other_split = other_split_list->data;
+            GNCLot *other_lot = xaccSplitGetLot (other_split);
+
+            /* Omit the lot we are about to delete */
+            if (other_lot == lot)
+                continue;
+
+            lot_list = g_list_prepend (lot_list, other_lot);
+        }
+        /* Maintain original split order */
+        lot_list = g_list_reverse (lot_list);
+
+        /* Now remove this link transaction. */
+        xaccTransClearReadOnly (other_txn);
+        xaccTransBeginEdit (other_txn);
+        xaccTransDestroy (other_txn);
+        xaccTransCommitEdit (other_txn);
+
+        /* Re-balance the saved lots as well as is possible */
+        gncOwnerAutoApplyPaymentsWithLots (&invoice->owner, lot_list);
+
+        /* If any of the saved lots has no more splits, then destroy it.
+         * Otherwise if any has an invoice associated with it,
+         * send it a modified event to reset its paid status */
+        for (list_iter = lot_list; list_iter; list_iter = list_iter->next)
+        {
+            GNCLot *other_lot = list_iter->data;
+            GncInvoice *other_invoice = gncInvoiceGetInvoiceFromLot (other_lot);
+
+            if (!gnc_lot_count_splits (other_lot))
+                gnc_lot_destroy (other_lot);
+            else if (other_invoice)
+                qof_event_gen (QOF_INSTANCE(other_invoice), QOF_EVENT_MODIFY, NULL);
+        }
+    }
+
     /* If the lot has no splits, then destroy it */
     if (!gnc_lot_count_splits (lot))
         gnc_lot_destroy (lot);
@@ -1671,6 +1625,72 @@
     return TRUE;
 }
 
+struct lotmatch
+{
+    const GncOwner *owner;
+    gboolean positive_balance;
+};
+
+static gboolean
+gnc_lot_match_owner_balancing (GNCLot *lot, gpointer user_data)
+{
+    struct lotmatch *lm = user_data;
+    GncOwner owner_def;
+    const GncOwner *owner;
+    gnc_numeric balance = gnc_lot_get_balance (lot);
+
+    /* Could (part of) this lot serve to balance the lot
+     * for which this query was run ?*/
+    if (lm->positive_balance == gnc_numeric_positive_p (balance))
+        return FALSE;
+
+    /* Is it ours? */
+    if (!gncOwnerGetOwnerFromLot (lot, &owner_def))
+        return FALSE;
+    owner = gncOwnerGetEndOwner (&owner_def);
+
+    return gncOwnerEqual (owner, lm->owner);
+}
+
+void gncInvoiceAutoApplyPayments (GncInvoice *invoice)
+{
+    GNCLot *inv_lot;
+    Account *acct;
+    const GncOwner *owner;
+    GList *lot_list;
+    struct lotmatch lm;
+
+    /* General note: "paying" in this context means balancing
+     * a lot, by linking opposite signed lots together. So below the term
+     * "payment" can both mean a true payment or it can mean a document of
+     * the opposite sign (invoice vs credit note). It just
+     * depends on what type of document was given as parameter
+     * to this function. */
+
+    /* Payments can only be applied to posted invoices */
+    g_return_if_fail (invoice);
+    g_return_if_fail (invoice->posted_lot);
+
+    inv_lot = invoice->posted_lot;
+    acct = invoice->posted_acc;
+    owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice));
+
+    /* Find all lots whose balance (or part of their balance) could be
+     * used to close this lot.
+     * To be eligible, the lots have to have an opposite signed balance
+     * and be for the same owner.
+     * For example, for an invoice lot, payment lots and credit note lots
+     * could be used. */
+    lm.positive_balance =  gnc_numeric_positive_p (gnc_lot_get_balance (inv_lot));
+    lm.owner = owner;
+    lot_list = xaccAccountFindOpenLots (acct, gnc_lot_match_owner_balancing,
+                                        &lm, NULL);
+
+    lot_list = g_list_prepend (lot_list, inv_lot);
+    gncOwnerAutoApplyPaymentsWithLots (owner, lot_list);
+    g_list_free (lot_list);
+}
+
 static gboolean gncInvoiceDateExists (const Timespec *date)
 {
     g_return_val_if_fail (date, FALSE);

Modified: gnucash/trunk/src/engine/gncInvoice.h
===================================================================
--- gnucash/trunk/src/engine/gncInvoice.h	2012-02-10 15:34:02 UTC (rev 21995)
+++ gnucash/trunk/src/engine/gncInvoice.h	2012-02-10 15:34:18 UTC (rev 21996)
@@ -184,7 +184,7 @@
                          const char *memo, gboolean accumulatesplits);
 
 /**
- * UNpost this invoice.  This will destroy the posted transaction and
+ * Unpost this invoice.  This will destroy the posted transaction and
  * return the invoice to its unposted state.  It may leave empty lots
  * out there.  If reset_tax_tables is TRUE, then it will also revert
  * all the Tax Tables to the parent, which will potentially change the
@@ -196,7 +196,15 @@
 gboolean
 gncInvoiceUnpost (GncInvoice *invoice, gboolean reset_tax_tables);
 
+/**
+ * Attempt to pay the invoice using open payment lots and
+ * lots for documents of the opposite sign (credit notes versus
+ * invoices).
+ */
+void
+gncInvoiceAutoApplyPayments (GncInvoice *invoice);
 
+
 /** Given a transaction, find and return the Invoice */
 GncInvoice * gncInvoiceGetInvoiceFromTxn (const Transaction *txn);
 

Modified: gnucash/trunk/src/engine/gncOwner.c
===================================================================
--- gnucash/trunk/src/engine/gncOwner.c	2012-02-10 15:34:02 UTC (rev 21995)
+++ gnucash/trunk/src/engine/gncOwner.c	2012-02-10 15:34:18 UTC (rev 21996)
@@ -965,6 +965,184 @@
     return txn;
 }
 
+void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots)
+{
+    GList *base_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 is given
+     * and either a list of lots to use or a first lot */
+    if (!owner) return;
+    if (!lots) return;
+
+    for (base_iter = lots; base_iter; base_iter = base_iter->next)
+    {
+        GNCLot *base_lot = base_iter->data;
+        QofBook *book;
+        Account *acct;
+        const gchar *name;
+        GList *lot_list, *lot_iter;
+        Transaction *txn;
+        gnc_numeric base_lot_bal, val_to_pay, val_paid = { 0, 1 };
+        gboolean base_bal_is_pos;
+        gboolean txn_created = FALSE;
+        const gchar *action, *memo;
+
+        /* Only attempt to apply payments to open lots.
+         * Note that due to the iterative nature of this function lots
+         * in the list may become closed before they are evaluated as
+         * base lot, so we should check this for each lot. */
+        base_lot_bal = gnc_lot_get_balance (base_lot);
+        if (gnc_numeric_zero_p (base_lot_bal))
+            continue;
+
+        book = gnc_lot_get_book (base_lot);
+        acct = gnc_lot_get_account (base_lot);
+        name = gncOwnerGetName (gncOwnerGetEndOwner (owner));
+        lot_list = base_iter->next;
+
+        /* Strings used when creating splits later on. */
+        action = _("Lot Link");
+        memo   = _("Internal link between invoice and payment lots");
+
+        /* Note: to balance the lot the payment to assign
+         * must have the opposite sign of the existing lot balance */
+        val_to_pay = gnc_numeric_neg (base_lot_bal);
+        base_bal_is_pos = gnc_numeric_positive_p (base_lot_bal);
+
+
+        /* Create splits in a linking transaction between lots until
+         * - either the invoice lot is balanced
+         * - or there are no more balancing lots.
+         */
+        for (lot_iter = lot_list; lot_iter; lot_iter = lot_iter->next)
+        {
+            gnc_numeric payment_lot_balance;
+            Split *split;
+            Account *bal_acct;
+            gnc_numeric  split_amt;
+
+            GNCLot *balancing_lot = lot_iter->data;
+
+            /* 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 closed before they are evaluated as
+             * base lot, so we should check this for each lot. */
+            if (gnc_lot_is_closed (balancing_lot))
+                continue;
+
+            /* Balancing transactions for invoice/payments can only happen
+             * in the same account. */
+            bal_acct = gnc_lot_get_account (balancing_lot);
+            if (acct != bal_acct)
+                continue;
+
+            payment_lot_balance = gnc_lot_get_balance (balancing_lot);
+
+            /* 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 */
+            if (base_bal_is_pos == gnc_numeric_positive_p (payment_lot_balance))
+                continue;
+
+            /*
+             * If there is less to pay than there's open in the lot; we're done -- apply the base_lot_vale.
+             * Note that payment_value and balance are opposite in sign, so we have to compare absolute values here
+             *
+             * Otherwise, apply the balance, subtract that from the payment_value,
+             * and move on to the next one.
+             */
+            if (gnc_numeric_compare (gnc_numeric_abs (val_to_pay), gnc_numeric_abs (payment_lot_balance)) <= 0)
+            {
+                /* abs(val_to_pay) <= abs(balance) */
+                split_amt = val_to_pay;
+            }
+            else
+            {
+                /* abs(val_to_pay) > abs(balance)
+                 * Remember payment_value and balance are opposite in sign,
+                 * and we want a payment to neutralize the current balance
+                 * so we need to negate here */
+                split_amt = payment_lot_balance;
+            }
+
+            /* If not created yet, create a new transaction linking
+             * the base lot and the balancing lot(s) */
+            if (!txn_created)
+            {
+                Timespec ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (base_lot)));
+
+                xaccAccountBeginEdit (acct);
+
+                txn = xaccMallocTransaction (book);
+                xaccTransBeginEdit (txn);
+
+                xaccTransSetDescription (txn, name ? name : "");
+                xaccTransSetCurrency (txn, xaccAccountGetCommodity(acct));
+                xaccTransSetDateEnteredSecs (txn, time(NULL));
+                xaccTransSetDatePostedTS (txn, &ts);
+                xaccTransSetTxnType (txn, TXN_TYPE_LINK);
+                txn_created = TRUE;
+            }
+
+            /* Create the split for this link in current balancing lot */
+            split = xaccMallocSplit (book);
+            xaccSplitSetMemo (split, memo);
+            xaccSplitSetAction (split, action);
+            xaccAccountInsertSplit (acct, split);
+            xaccTransAppendSplit (txn, split);
+            xaccSplitSetBaseValue (split, gnc_numeric_neg (split_amt), xaccAccountGetCommodity(acct));
+            gnc_lot_add_split (balancing_lot, split);
+
+            /* If the balancing lot was linked to a document (invoice/credit note),
+             * send an event for it as well so it gets potentially updated as paid */
+            {
+                GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(balancing_lot);
+                if (this_invoice)
+                    qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL);
+            }
+
+            val_paid   = gnc_numeric_add (val_paid, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+            val_to_pay = gnc_numeric_sub (val_to_pay, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
+            if (gnc_numeric_zero_p (val_to_pay))
+                break;
+        }
+
+
+        /* If the above loop managed to create a transaction and some balancing splits,
+         * create the final split for the link transaction in the base lot */
+        if (txn_created)
+        {
+            GncInvoice *this_invoice;
+            Split *split = xaccMallocSplit (book);
+
+            xaccSplitSetMemo (split, memo);
+            xaccSplitSetAction (split, action);
+            xaccAccountInsertSplit (acct, split);
+            xaccTransAppendSplit (txn, split);
+            xaccSplitSetBaseValue (split, val_paid, xaccAccountGetCommodity(acct));
+            gnc_lot_add_split (base_lot, split);
+
+            xaccTransCommitEdit (txn);
+            xaccAccountCommitEdit (acct);
+
+            /* If the base lot was linked to a document (invoice/credit note),
+             * send an event for it as well so it gets potentially updated as paid */
+            this_invoice = gncInvoiceGetInvoiceFromLot(base_lot);
+            if (this_invoice)
+                qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL);
+
+        }
+
+    }
+}
+
 GList *
 gncOwnerGetAccountTypesList (const GncOwner *owner)
 {

Modified: gnucash/trunk/src/engine/gncOwner.h
===================================================================
--- gnucash/trunk/src/engine/gncOwner.h	2012-02-10 15:34:02 UTC (rev 21995)
+++ gnucash/trunk/src/engine/gncOwner.h	2012-02-10 15:34:18 UTC (rev 21996)
@@ -203,6 +203,42 @@
                       const char *memo, const char *num);
 
 /**
+ * Given a list of lots, try to balance as many of them as possible
+ * by creating balancing transactions between them. This can be used
+ * to automatically link invoices to payments (to "mark" invoices as
+ * paid) or to credit notes or the other way around.
+ *
+ * The function starts with the first lot in the list and tries to
+ * create balancing transactions to the remainder of the lots in the
+ * list. If it reaches the end of the list, it will find the next
+ * still open lot in the list and tries to balance it with all lots
+ * that follow it (the ones that precede it are either already closed
+ * or not suitable or they would have been processed in a previous
+ * iteration).
+ *
+ * By intelligently sorting the list of lots, you can play with the
+ * order of precedence in which the lots should be processed. For
+ * example, by sorting the oldest invoice lots first, the code will
+ * attempt to balance these first.
+ *
+ * Some restrictions:
+ * - the algorithm is lazy: it will create the smallest balancing
+ *   transaction(s) possible, not the largest ones. Since the process
+ *   is iterative, you will have balanced the maximum amount possible
+ *   in the end, but it may be done in several transactions instead of
+ *   only one big one.
+ * - the balancing transactions only work within one account. If a
+ *   balancing lot is from another account than the lot currently being
+ *   balanced, it will be skipped during balance evaluation. However
+ *   if there is a mix of lots from two different accounts, the algorithm
+ *   will still attempt to match all lots per account.
+ * - the calling function is responsible for the memory management
+ *   of the lots list. If it created the list, it should properly free
+ *   it as well.
+ */
+void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots);
+
+/**
  * Fill in a half-finished payment transaction for the owner. The
  * transaction txn must already contain one split that belongs to a
  * bank or other asset account. This function will add the other split



More information about the gnucash-changes mailing list