Rework interaction between payments and invoices part 2.

This commit deals with paying invoices.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@22000 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Geert Janssens 2012-02-10 15:34:55 +00:00
parent 6467765d7b
commit de91d24722
4 changed files with 211 additions and 76 deletions

View File

@ -204,7 +204,9 @@ gnc_payment_dialog_document_selection_changed (PaymentWindow *pw)
/* Set the payment amount in the dialog */
val = gnc_payment_dialog_calculate_selected_total (pw);
gnc_ui_payment_window_set_amount(pw, val);
/* XXX It may not always be correct to use the absolute value of amount here
* This is an assumption from before the credit notes implementation. */
gnc_ui_payment_window_set_amount(pw, gnc_numeric_abs (val));
}
static gboolean
@ -259,6 +261,7 @@ gnc_payment_window_fill_docs_list (PaymentWindow *pw)
GtkTreeIter iter;
Timespec doc_date;
GncInvoice *document;
gnc_numeric value = gnc_numeric_zero();
gnc_numeric debit = gnc_numeric_zero();
gnc_numeric credit = gnc_numeric_zero();
GncInvoiceType doc_type = GNC_INVOICE_UNDEFINED;
@ -297,22 +300,16 @@ gnc_payment_window_fill_docs_list (PaymentWindow *pw)
}
/* Find the debit/credit amount.
* Invoices/bills are debit
* Credit notes are credit
* Pre-payments are credit
* Invoices/vendor credit notes are debit (increasing the balance)
* Customer credit notes/bills are credit (decreasing the balance)
* Pre-payments are debit or credit depending on their sign
*/
if (document)
{
if (!gncInvoiceGetIsCreditNote (document))
debit = gnc_lot_get_balance (lot);
else
credit = gnc_lot_get_balance (lot);
}
value = gnc_lot_get_balance (lot);
if (gnc_numeric_positive_p (value))
debit = value;
else
{
/* This is a pre-payment */
credit = gnc_lot_get_balance (lot);
}
credit = gnc_numeric_neg (value);
/* Only display non-zero debits/credits */
if (!gnc_numeric_zero_p (debit))
@ -514,6 +511,29 @@ gnc_payment_dialog_post_to_changed_cb (GtkWidget *widget, gpointer data)
return FALSE;
}
/*
* This helper function is called once for each row in the tree view
* that is currently selected. Its task is to add the corresponding
* lot to the end of a glist.
*/
static void
get_selected_lots (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer data)
{
GList **return_list = data;
GNCLot *lot;
GValue value = { 0 };
gtk_tree_model_get_value (model, iter, 5, &value);
lot = (GNCLot *) g_value_get_pointer (&value);
g_value_unset (&value);
if (lot)
*return_list = g_list_append(*return_list, lot);
}
void
gnc_payment_ok_cb (GtkWidget *widget, gpointer data)
{
@ -527,6 +547,9 @@ gnc_payment_ok_cb (GtkWidget *widget, gpointer data)
/* Verify the amount is non-zero */
amount = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT (pw->amount_edit));
/* XXX Amounts could possibly be negative as well if you take credit notes into account
* This still has to be reviewed */
if (gnc_numeric_check (amount) || !gnc_numeric_positive_p (amount))
{
text = _("You must enter the amount of the payment. "
@ -569,12 +592,18 @@ gnc_payment_ok_cb (GtkWidget *widget, gpointer data)
const char *memo, *num;
Timespec date;
gnc_numeric exch = gnc_numeric_create(1, 1); //default to "one to one" rate
GNCLot *payment_lot;
GList *selected_lots = NULL;
GtkTreeSelection *selection;
/* Obtain all our ancillary information */
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 */
/* Obtain the list of selected lots (documents/payments) from the dialog */
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(pw->docs_list_tree_view));
gtk_tree_selection_selected_foreach (selection, get_selected_lots, &selected_lots);
/* 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 */
@ -600,64 +629,17 @@ gnc_payment_ok_cb (GtkWidget *widget, gpointer data)
gnc_xfer_dialog_run_until_done(xfer);
}
if (!gnc_payment_dialog_has_pre_existing_txn(pw))
{
/* Now apply the payment */
gncOwnerApplyPayment (&pw->owner, pw->invoice,
post, acc, amount, exch, date, memo, num);
}
else
{
// New implementation: Allow the user to have a
// pre-selected transaction
Transaction *txn = pw->pre_existing_txn;
GncOwner *owner = &pw->owner;
Split *xfer_split = xaccTransFindSplitByAccount(txn, acc);
if (xaccTransGetCurrency(txn) != gncOwnerGetCurrency (owner))
{
g_message("Uh oh, mismatching currency/commodity between selected transaction and owner. We fall back to manual creation of a new transaction.");
xfer_split = NULL;
}
if (!xfer_split)
{
g_message("Huh? Asset account not found anymore. Fully deleting old txn and now creating a new one.");
xaccTransBeginEdit (txn);
xaccTransDestroy (txn);
xaccTransCommitEdit (txn);
pw->pre_existing_txn = NULL;
gncOwnerApplyPayment (owner, pw->invoice,
post, acc, amount, exch, date, memo, num);
}
else
{
int i = 0;
xaccTransBeginEdit (txn);
while (i < xaccTransCountSplits(txn))
{
Split *split = xaccTransGetSplit (txn, i);
if (split == xfer_split)
{
++i;
}
else
{
xaccSplitDestroy(split);
}
}
// We have opened the txn for editing, and we have deleted all the other splits.
gncOwnerAssignPaymentTxn (owner, txn,
post, pw->invoice);
xaccTransCommitEdit (txn);
}
}
/* Create a lot for this payment */
payment_lot = gncOwnerCreatePaymentLot (&pw->owner, pw->pre_existing_txn,
post, 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 (&pw->owner, selected_lots);
}
gnc_resume_gui_refresh ();
@ -1094,6 +1076,8 @@ PaymentWindow * gnc_ui_payment_new_with_txn (GncOwner *owner, Transaction *txn)
GDate txn_date = xaccTransGetDatePostedGDate (txn);
gnc_ui_payment_window_set_date(pw, &txn_date);
}
/* XXX It may not always be correct to use the absolute value of amount here
* This is an assumption from before the credit notes implementation. */
gnc_ui_payment_window_set_amount(pw, gnc_numeric_abs(amount));
gnc_ui_payment_window_set_xferaccount(pw, xaccSplitGetAccount(assetaccount_split));
if (postaccount_split)

View File

@ -183,7 +183,7 @@
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="min_width">50</property>
<property name="title" translatable="yes">Debet</property>
<property name="title" translatable="yes">Debit</property>
<property name="reorderable">True</property>
<child>
<object class="GtkCellRendererText" id="docs_list_deb_renderer"/>

View File

@ -895,6 +895,144 @@ gncOwnerAssignPaymentTxn(const GncOwner *owner, Transaction *txn,
}
/*
* Apply a payment of "amount" for the owner, between the xfer_account
* (bank or other asset) and the posted_account (A/R or A/P).
*/
GNCLot *
gncOwnerCreatePaymentLot (const GncOwner *owner, Transaction *txn,
Account *posted_acc, Account *xfer_acc,
gnc_numeric amount, gnc_numeric exch, Timespec date,
const char *memo, const char *num)
{
QofBook *book;
Split *split;
const char *name;
gnc_commodity *commodity;
gboolean reverse;
gnc_numeric payment_value = amount;
Split *xfer_split = NULL;
GNCLot *payment_lot;
/* 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));
commodity = gncOwnerGetCurrency (owner);
reverse = use_reversed_payment_amounts(owner);
if (txn)
{
/* Pre-existing transaction was specified. We completely clear it,
* except for the split in the transfer account, unless the
* transaction can't be reused (wrong currency, wrong transfer account).
* In that case, the transaction is simply removed and an new
* one created. */
xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc);
if (xaccTransGetCurrency(txn) != gncOwnerGetCurrency (owner))
{
g_message("Uh oh, mismatching currency/commodity between selected transaction and owner. We fall back to manual creation of a new transaction.");
xfer_split = NULL;
}
if (!xfer_split)
{
g_message("Huh? Asset account not found anymore. Fully deleting old txn and now creating a new one.");
xaccTransBeginEdit (txn);
xaccTransDestroy (txn);
xaccTransCommitEdit (txn);
txn = NULL;
}
else
{
int i = 0;
xaccTransBeginEdit (txn);
while (i < xaccTransCountSplits(txn))
{
Split *split = xaccTransGetSplit (txn, i);
if (split == xfer_split)
{
++i;
}
else
{
xaccSplitDestroy(split);
}
}
xaccTransCommitEdit (txn);
}
}
/* Create the transaction if we don't have one yet */
if (!txn)
txn = xaccMallocTransaction (book);
/* Insert a split for the transfer account if we don't have one yet */
if (!xfer_split)
{
xaccTransBeginEdit (txn);
/* Set up the transaction */
xaccTransSetDescription (txn, name ? name : "");
xaccTransSetNum (txn, num);
xaccTransSetCurrency (txn, commodity);
xaccTransSetDateEnteredSecs (txn, time(NULL));
xaccTransSetDatePostedTS (txn, &date);
xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT);
/* The split for the transfer account */
split = xaccMallocSplit (book);
xaccSplitSetMemo (split, memo);
xaccSplitSetAction (split, _("Payment"));
xaccAccountBeginEdit (xfer_acc);
xaccAccountInsertSplit (xfer_acc, split);
xaccAccountCommitEdit (xfer_acc);
xaccTransAppendSplit (txn, split);
if (gnc_commodity_equal(xaccAccountGetCommodity(xfer_acc), commodity))
{
xaccSplitSetBaseValue (split, reverse ? amount :
gnc_numeric_neg (amount), commodity);
}
else
{
/* Need to value the payment in terms of the owner commodity */
xaccSplitSetAmount(split, reverse ? amount : gnc_numeric_neg (amount));
payment_value = gnc_numeric_mul(amount, exch, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
xaccSplitSetValue(split, reverse ? payment_value : gnc_numeric_neg(payment_value));
}
}
/* Add a split in the post account */
split = xaccMallocSplit (book);
xaccSplitSetMemo (split, memo);
xaccSplitSetAction (split, _("Payment"));
xaccAccountBeginEdit (posted_acc);
xaccAccountInsertSplit (posted_acc, split);
xaccAccountCommitEdit (posted_acc);
xaccTransAppendSplit (txn, split);
xaccSplitSetBaseValue (split, reverse ? gnc_numeric_neg (amount) : amount, commodity);
/* Create a new lot for the payment */
payment_lot = gnc_lot_new (book);
gncOwnerAttachToLot (owner, payment_lot);
gnc_lot_add_split (payment_lot, split);
/* Commit this new transaction */
xaccTransCommitEdit (txn);
return payment_lot;
}
/*
* Apply a payment of "amount" for the owner, between the xfer_account
* (bank or other asset) and the posted_account (A/R or A/P).
@ -976,8 +1114,8 @@ void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots)
* 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 */
/* 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;

View File

@ -190,6 +190,19 @@ gboolean gncOwnerGetOwnerFromTypeGuid (QofBook *book, GncOwner *owner, QofIdType
/** Get the kvp-frame from the underlying owner object */
KvpFrame* gncOwnerGetSlots(GncOwner* owner);
/**
* Create a lot for a payment for the given owner and with the given
* parameters. If a transaction is passed, this transaction will be
* reused if possible (meaning, if the transaction currency matches
* the owner's currency and if the transaction has (at least?) one
* split in the transfer account).
*/
GNCLot *
gncOwnerCreatePaymentLot (const GncOwner *owner, Transaction *txn,
Account *posted_acc, Account *xfer_acc,
gnc_numeric amount, gnc_numeric exch, Timespec date,
const char *memo, const char *num);
/**
* Apply a payment of "amount" for the owner, between the xfer_account
* (bank or other asset) and the posted_account (A/R or A/P). If the