Bug 715123 - Post invoice problem, cannot unpost

Look for missing exchange rates in tax table entries
as well as in invoice entries

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@23485 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Geert Janssens 2013-12-04 16:56:55 +00:00
parent f5e443c22e
commit ae23373ed6
3 changed files with 151 additions and 95 deletions

View File

@ -765,9 +765,10 @@ gnc_invoice_post(InvoiceWindow *iw, struct post_invoice_params *post_params)
KvpFrame *kvpf;
KvpValue *kvp_val;
const char *text;
EntryList *entries, *entries_iter;
GncEntry* entry;
gboolean is_cust_doc, is_cn, auto_pay;
GHashTable *foreign_currs;
GHashTableIter foreign_currs_iter;
gpointer key,value;
gboolean is_cust_doc, auto_pay;
gboolean show_dialog = TRUE;
gboolean post_ok = TRUE;
@ -788,7 +789,6 @@ gnc_invoice_post(InvoiceWindow *iw, struct post_invoice_params *post_params)
}
is_cust_doc = (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_CUSTOMER);
is_cn = gncInvoiceGetIsCreditNote (invoice);
/* Ok, we can post this invoice. Ask for verification, set the due date,
* post date, and posted account
@ -821,104 +821,87 @@ gnc_invoice_post(InvoiceWindow *iw, struct post_invoice_params *post_params)
/* Fill in the conversion prices with feedback from the user */
text = _("One or more of the entries are for accounts different from the invoice/bill currency. You will be asked a conversion rate for each.");
/* Get the invoice entries */
entries = gncInvoiceGetEntries (invoice);
for (entries_iter = entries; entries_iter != NULL; entries_iter = g_list_next(entries_iter))
/* Ask the user for conversion rates for all foreign currencies
* (relative to the invoice currency) */
foreign_currs = gncInvoiceGetForeignCurrencies (invoice);
g_hash_table_iter_init (&foreign_currs_iter, foreign_currs);
while (g_hash_table_iter_next (&foreign_currs_iter, &key, &value))
{
Account *this_acc;
gnc_commodity *account_currency;
GNCPrice *convprice;
gnc_commodity *account_currency = (gnc_commodity*)key;
gnc_numeric *amount = (gnc_numeric*)value;
entry = (GncEntry*)entries_iter->data;
this_acc = (is_cust_doc ? gncEntryGetInvAccount (entry) :
gncEntryGetBillAccount (entry));
account_currency = xaccAccountGetCommodity (this_acc);
if (this_acc &&
!gnc_commodity_equal (gncInvoiceGetCurrency (invoice), account_currency))
if (show_dialog)
{
GNCPrice *convprice;
gnc_info_dialog(iw_get_window(iw), "%s", text);
show_dialog = FALSE;
}
if (show_dialog)
convprice = gncInvoiceGetPrice(invoice, account_currency);
if (convprice == NULL)
{
XferDialog *xfer;
gnc_numeric exch_rate;
Timespec date;
/* Note some twisted logic here:
* We ask the exchange rate
* FROM invoice currency
* TO other account currency
* Because that's what happens logically.
* But the internal posting logic works backwards:
* It searches for an exchange rate
* FROM other account currency
* TO invoice currency
* So we will store the inverted exchange rate
*/
/* create the exchange-rate dialog */
xfer = gnc_xfer_dialog (iw_get_window(iw), acc);
gnc_xfer_dialog_is_exchange_dialog(xfer, &exch_rate);
gnc_xfer_dialog_select_to_currency(xfer, account_currency);
/* Even if amount is 0 ask for an exchange rate. It's required
* for the transaction generating code. Use an amount of 1 in
* that case as the dialog won't allow to specify an exchange
* rate for 0. */
gnc_xfer_dialog_set_amount(xfer, gnc_numeric_zero_p (*amount) ?
(gnc_numeric){1, 1} : *amount);
/* All we want is the exchange rate so prevent the user from thinking
it makes sense to mess with other stuff */
gnc_xfer_dialog_set_from_show_button_active(xfer, FALSE);
gnc_xfer_dialog_set_to_show_button_active(xfer, FALSE);
gnc_xfer_dialog_hide_from_account_tree(xfer);
gnc_xfer_dialog_hide_to_account_tree(xfer);
if (gnc_xfer_dialog_run_until_done(xfer))
{
gnc_info_dialog(iw_get_window(iw), "%s", text);
show_dialog = FALSE;
/* User finished the transfer dialog successfully */
/* Invert the exchange rate as explained above */
if (!gnc_numeric_zero_p (exch_rate))
exch_rate = gnc_numeric_div ((gnc_numeric){1, 1}, exch_rate,
GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
convprice = gnc_price_create(iw->book);
gnc_price_begin_edit (convprice);
gnc_price_set_commodity (convprice, account_currency);
gnc_price_set_currency (convprice, gncInvoiceGetCurrency (invoice));
date.tv_sec = gnc_time (NULL);
date.tv_nsec = 0;
gnc_price_set_time (convprice, date);
gnc_price_set_source (convprice, "user:invoice-post");
/* Yes, magic strings are evil but I can't find any defined constants
for this..*/
gnc_price_set_typestr (convprice, "last");
gnc_price_set_value (convprice, exch_rate);
gncInvoiceAddPrice(invoice, convprice);
gnc_price_commit_edit (convprice);
}
convprice = gncInvoiceGetPrice(invoice, account_currency);
if (convprice == NULL)
else
{
XferDialog *xfer;
gnc_numeric exch_rate;
Timespec date;
gnc_numeric amount = gnc_numeric_create(1, 1);
gnc_numeric value;
gnc_numeric tax;
/* Note some twisted logic here:
* We ask the exchange rate
* FROM invoice currency
* TO other account currency
* Because that's what happens logically.
* But the internal posting logic works backwards:
* It searches for an exchange rate
* FROM other account currency
* TO invoice currency
* So we will store the inverted exchange rate
*/
/* Obtain the Entry's total value (net + tax) */
value = gncEntryGetDocValue (entry, FALSE, is_cust_doc, is_cn);
tax = gncEntryGetDocTaxValue (entry, FALSE, is_cust_doc, is_cn);
amount = gnc_numeric_add (value, tax,
gnc_commodity_get_fraction (account_currency),
GNC_HOW_RND_ROUND_HALF_UP );
/* create the exchange-rate dialog */
xfer = gnc_xfer_dialog (iw_get_window(iw), acc);
gnc_xfer_dialog_select_to_account(xfer, this_acc);
gnc_xfer_dialog_set_amount(xfer, amount);
/* All we want is the exchange rate so prevent the user from thinking
it makes sense to mess with other stuff */
gnc_xfer_dialog_set_from_show_button_active(xfer, FALSE);
gnc_xfer_dialog_set_to_show_button_active(xfer, FALSE);
gnc_xfer_dialog_hide_from_account_tree(xfer);
gnc_xfer_dialog_hide_to_account_tree(xfer);
gnc_xfer_dialog_is_exchange_dialog(xfer, &exch_rate);
if (gnc_xfer_dialog_run_until_done(xfer))
{
/* User finished the transfer dialog successfully */
/* Invert the exchange rate as explained above */
if (!gnc_numeric_zero_p (exch_rate))
exch_rate = gnc_numeric_div ((gnc_numeric)
{
1, 1
}, exch_rate,
GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
convprice = gnc_price_create(iw->book);
gnc_price_begin_edit (convprice);
gnc_price_set_commodity (convprice, account_currency);
gnc_price_set_currency (convprice, gncInvoiceGetCurrency (invoice));
date.tv_sec = gnc_time (NULL);
date.tv_nsec = 0;
gnc_price_set_time (convprice, date);
gnc_price_set_source (convprice, "user:invoice-post");
/* Yes, magic strings are evil but I can't find any defined constants
for this..*/
gnc_price_set_typestr (convprice, "last");
gnc_price_set_value (convprice, exch_rate);
gncInvoiceAddPrice(invoice, convprice);
gnc_price_commit_edit (convprice);
}
else
{
/* User canceled the transfer dialog, abort posting */
post_ok = FALSE;
goto cleanup;
}
/* User canceled the transfer dialog, abort posting */
post_ok = FALSE;
goto cleanup;
}
}
}
@ -943,6 +926,7 @@ gnc_invoice_post(InvoiceWindow *iw, struct post_invoice_params *post_params)
cleanup:
gncInvoiceCommitEdit (invoice);
g_hash_table_unref (foreign_currs);
gnc_resume_gui_refresh ();
if (memo)

View File

@ -1226,6 +1226,66 @@ gboolean gncInvoiceAmountPositive (const GncInvoice *invoice)
}
}
GHashTable *gncInvoiceGetForeignCurrencies (const GncInvoice *invoice)
{
EntryList *entries_iter;
gboolean is_cust_doc = (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_CUSTOMER);
gboolean is_cn = gncInvoiceGetIsCreditNote (invoice);
GHashTable *amt_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_free);
for (entries_iter = invoice->entries; entries_iter != NULL; entries_iter = g_list_next(entries_iter))
{
GncEntry *entry = (GncEntry*)entries_iter->data;
Account *this_acc;
gnc_commodity *account_currency;
AccountValueList *tt_amts = NULL, *tt_iter;
/* Check entry's account currency */
this_acc = (is_cust_doc ? gncEntryGetInvAccount (entry) :
gncEntryGetBillAccount (entry));
account_currency = xaccAccountGetCommodity (this_acc);
if (this_acc &&
!gnc_commodity_equal (gncInvoiceGetCurrency (invoice), account_currency))
{
gnc_numeric *curr_amt = (gnc_numeric*) g_hash_table_lookup (amt_hash, account_currency);
gnc_numeric *entry_amt = (gnc_numeric*) g_new0 (gnc_numeric, 1);
*entry_amt = gncEntryGetDocValue (entry, FALSE, is_cust_doc, is_cn);
if (curr_amt)
*entry_amt = gnc_numeric_add (*entry_amt, *curr_amt, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
g_hash_table_insert (amt_hash, account_currency, entry_amt);
}
/* Check currencies of each account in the tax table linked
* to the current entry */
tt_amts = gncEntryGetDocTaxValues (entry, is_cust_doc, is_cn);
if (!tt_amts)
continue;
for (tt_iter = tt_amts; tt_iter != NULL; tt_iter = g_list_next(tt_iter))
{
GncAccountValue *tt_amt_val = (GncAccountValue*)tt_iter->data;
Account *tt_acc = tt_amt_val->account;
gnc_commodity *tt_acc_currency = xaccAccountGetCommodity (tt_acc);
if (tt_acc &&
!gnc_commodity_equal (gncInvoiceGetCurrency (invoice), tt_acc_currency))
{
gnc_numeric *curr_amt = (gnc_numeric*) g_hash_table_lookup (amt_hash, tt_acc_currency);
gnc_numeric *tt_acc_amt = (gnc_numeric*) g_new0 (gnc_numeric, 1);
*tt_acc_amt = tt_amt_val->value;
if (curr_amt)
*tt_acc_amt = gnc_numeric_add (*tt_acc_amt, *curr_amt, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
g_hash_table_insert (amt_hash, tt_acc_currency, tt_acc_amt);
}
}
gncAccountValueDestroy (tt_amts);
}
return amt_hash;
}
static gboolean gncInvoicePostAddSplit (QofBook *book,
Account *acc,
Transaction *txn,

View File

@ -41,6 +41,7 @@ typedef struct _gncInvoice GncInvoice;
typedef struct _gncInvoiceClass GncInvoiceClass;
typedef GList GncInvoiceList;
#include <glib.h>
#include "gncBillTerm.h"
#include "gncEntry.h"
#include "gncOwner.h"
@ -178,6 +179,17 @@ GNCPrice * gncInvoiceGetPrice(GncInvoice *invoice, gnc_commodity* commodity);
*/
gboolean gncInvoiceAmountPositive (const GncInvoice *invoice);
/** Return an overview of amounts on this invoice that will be posted to
* accounts in currencies that are different from the invoice currency.
* These accounts can be the accounts referred to in invoice entries
* or tax tables. This information is returned in the from of a hash
* table. The keys in the hash table are the foreign currencies, the
* values are the accumulated amounts in that currency.
* Drop the reference to the hash table with g_hash_table_unref when
* no longer needed.
*/
GHashTable *gncInvoiceGetForeignCurrencies (const GncInvoice *invoice);
/** Post this invoice to an account. Returns the new Transaction
* that is tied to this invoice. The transaction is set with
* the supplied posted date, due date, and memo. The Transaction