mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Merge branch 'maint'
Resolved Conflicts: src/engine/test/utest-Transaction.cpp src/gnome-utils/ui/osx_accel_map src/libqof/qof/qofbook.cpp
This commit is contained in:
commit
a0fa6d2fe7
55
configure.ac
55
configure.ac
@ -542,26 +542,45 @@ AC_CHECK_FUNCS(gethostid link)
|
||||
### --------------------------------------------------------------------------
|
||||
### Guile version checks
|
||||
|
||||
GUILE_EFFECTIVE_VERSION=0
|
||||
# - check minimum version
|
||||
# - determine GUILE_CFLAGS and GUILE_LIBS
|
||||
gnc_have_guile_2=no
|
||||
gnc_have_guile_www=no
|
||||
PKG_CHECK_MODULES(GUILE,
|
||||
[guile-2.0 >= 2.0.0],
|
||||
[gnc_have_guile_2=yes
|
||||
GUILE_EFFECTIVE_VERSION=2.0
|
||||
AC_PATH_PROG([GUILD], guild)],
|
||||
[PKG_CHECK_MODULES(GUILE,
|
||||
[guile-1.8 >= 1.8.5],
|
||||
[GUILE_EFFECTIVE_VERSION=1.8],
|
||||
[AC_MSG_ERROR([
|
||||
guile does not appear to be installed correctly, or is not in the
|
||||
correct version range. Perhaps you have not installed the guile
|
||||
development packages? Gnucash requires at least version 1.8.5 to build.
|
||||
])])
|
||||
])
|
||||
|
||||
AM_CONDITIONAL(GNC_HAVE_GUILE_2, test "x${gnc_have_guile_2}" = xyes)
|
||||
AC_ARG_WITH([guile],
|
||||
AS_HELP_STRING([--with-guile=1.8|2.0|auto],
|
||||
[which guile version to compile against @<:@default: auto@:>@]),
|
||||
[],
|
||||
[with_guile=auto]
|
||||
)
|
||||
|
||||
AS_IF([test "$with_guile" = "2.0"],
|
||||
[PKG_CHECK_MODULES(GUILE, [guile-2.0 >= 2.0.0],
|
||||
[GUILE_EFFECTIVE_VERSION=2.0
|
||||
AC_PATH_PROG([GUILD], guild)])],
|
||||
[test "$with_guile" = "1.8"],
|
||||
[PKG_CHECK_MODULES(GUILE, [guile-1.8 >= 1.8.5],
|
||||
[GUILE_EFFECTIVE_VERSION=1.8])],
|
||||
[test "$with_guile" = "auto"],
|
||||
[PKG_CHECK_MODULES(GUILE, [guile-2.0 >= 2.0.0],
|
||||
[GUILE_EFFECTIVE_VERSION=2.0
|
||||
AC_PATH_PROG([GUILD], guild)],
|
||||
[PKG_CHECK_MODULES(GUILE, [guile-1.8 >= 1.8.5],
|
||||
[GUILE_EFFECTIVE_VERSION=1.8],
|
||||
[GUILE_EFFECTIVE_VERSION=0])
|
||||
])],
|
||||
# else
|
||||
[AC_MSG_ERROR([invalid guile version specified])]
|
||||
)
|
||||
|
||||
AS_IF([test "$GUILE_EFFECTIVE_VERSION" = "0"],
|
||||
[AC_MSG_ERROR([
|
||||
guile does not appear to be installed correctly, or is not in the
|
||||
correct version range. Perhaps you have not installed the guile
|
||||
development packages? Gnucash requires at least version 1.8.5 to build.
|
||||
])]
|
||||
)
|
||||
|
||||
AM_CONDITIONAL(GNC_HAVE_GUILE_2, [test "$GUILE_EFFECTIVE_VERSION" = "2.0"])
|
||||
AC_SUBST(GUILE_EFFECTIVE_VERSION)
|
||||
AC_SUBST(GUILE, [`pwd`/gnc-guile])
|
||||
|
||||
@ -588,7 +607,7 @@ if test "${BUILDING_FROM_VCS}" = yes
|
||||
then
|
||||
AX_PKG_SWIG(2.0.10, [gnc_have_swig_2_0_10=yes], [gnc_have_swig_2_0_10=no])
|
||||
|
||||
if test "${gnc_have_guile_2}" = yes
|
||||
if test "${GUILE_EFFECTIVE_VERSION}" = "2.0"
|
||||
then
|
||||
if test "${gnc_have_swig_2_0_10}" = no
|
||||
then
|
||||
|
@ -102,3 +102,7 @@ Douglas Adams, \"The Restaurant at the End of the Universe\"")
|
||||
N_( "To search through all your transactions, start a search (Edit -> \
|
||||
Find...) from the main accounts hierarchy page. To limit your search \
|
||||
to a single account, start the search from that account's register.")
|
||||
|
||||
N_( "To visually compare on screen the contents of 2 tabs, \
|
||||
in one of the tabs, select Window -> New Window with Page \
|
||||
from the menu to duplicate that tab in a new window.")
|
||||
|
@ -2888,17 +2888,41 @@ static void post_one_invoice_cb(gpointer data, gpointer user_data)
|
||||
gnc_invoice_post(iw, post_params);
|
||||
}
|
||||
|
||||
static void gnc_invoice_is_posted(gpointer inv, gpointer test_value)
|
||||
{
|
||||
GncInvoice *invoice = inv;
|
||||
gboolean *test = (gboolean*)test_value;
|
||||
|
||||
if (gncInvoiceIsPosted (invoice))
|
||||
{
|
||||
*test = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
multi_post_invoice_cb (GList *invoice_list, gpointer user_data)
|
||||
{
|
||||
struct post_invoice_params post_params;
|
||||
gboolean test;
|
||||
InvoiceWindow *iw;
|
||||
|
||||
if (g_list_length(invoice_list) == 0)
|
||||
return;
|
||||
|
||||
// Get the posting parameters for these invoices
|
||||
iw = gnc_ui_invoice_edit(invoice_list->data);
|
||||
test = FALSE;
|
||||
gnc_suspend_gui_refresh (); // Turn off GUI refresh for the duration.
|
||||
// Check if any of the selected invoices have already been posted.
|
||||
g_list_foreach(invoice_list, gnc_invoice_is_posted, &test);
|
||||
gnc_resume_gui_refresh ();
|
||||
if (test)
|
||||
{
|
||||
gnc_error_dialog (iw_get_window(iw), "%s",
|
||||
_("One or more selected invoices have already been posted.\nRe-check you selection."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gnc_dialog_post_invoice(iw, _("Do you really want to post these invoices?"),
|
||||
&post_params.ddue, &post_params.postdate,
|
||||
&post_params.memo, &post_params.acc,
|
||||
|
@ -462,6 +462,50 @@ gncScrubBusinessLot (GNCLot *lot)
|
||||
return splits_deleted;
|
||||
}
|
||||
|
||||
void
|
||||
gncScrubBusinessSplit (Split *split)
|
||||
{
|
||||
const gchar *memo = _("Please delete this transaction. Explanation at http://wiki.gnucash.org/wiki/Business_Features_Issues#Double_Posting");
|
||||
Transaction *txn;
|
||||
|
||||
if (!split) return;
|
||||
ENTER ("(split=%p)", split);
|
||||
|
||||
txn = xaccSplitGetParent (split);
|
||||
if (txn)
|
||||
{
|
||||
gchar txntype = xaccTransGetTxnType (txn);
|
||||
const gchar *read_only = xaccTransGetReadOnly (txn);
|
||||
gboolean is_void = xaccTransGetVoidStatus (txn);
|
||||
GNCLot *lot = xaccSplitGetLot (split);
|
||||
|
||||
/* Look for transactions as a result of double posting an invoice or bill
|
||||
* Refer to https://bugzilla.gnome.org/show_bug.cgi?id=754209
|
||||
* to learn how this could have happened in the past.
|
||||
* Characteristics of such transaction are:
|
||||
* - read only
|
||||
* - not voided (to ensure read only is set by the business functions)
|
||||
* - transaction type is none (should be type invoice for proper post transactions)
|
||||
* - assigned to a lot
|
||||
*/
|
||||
if ((txntype == TXN_TYPE_NONE) && read_only && !is_void && lot)
|
||||
{
|
||||
gchar *txn_date = qof_print_date (xaccTransGetDateEntered (txn));
|
||||
xaccTransClearReadOnly (txn);
|
||||
xaccSplitSetMemo (split, memo);
|
||||
gnc_lot_remove_split (lot, split);
|
||||
PWARN("Cleared double post status of transaction \"%s\", dated %s. "
|
||||
"Please delete transaction and verify balance.",
|
||||
xaccTransGetDescription (txn),
|
||||
txn_date);
|
||||
g_free (txn_date);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LEAVE ("(split=%p)", split);
|
||||
}
|
||||
|
||||
/* ============================================================== */
|
||||
|
||||
void
|
||||
@ -505,20 +549,72 @@ gncScrubBusinessAccountLots (Account *acc)
|
||||
|
||||
/* ============================================================== */
|
||||
|
||||
void
|
||||
gncScrubBusinessAccountSplits (Account *acc)
|
||||
{
|
||||
SplitList *splits, *node;
|
||||
gint split_count = 0;
|
||||
gint curr_split_no = 1;
|
||||
const gchar *str;
|
||||
|
||||
if (!acc) return;
|
||||
if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
|
||||
|
||||
str = xaccAccountGetName(acc);
|
||||
str = str ? str : "(null)";
|
||||
|
||||
ENTER ("(acc=%s)", str);
|
||||
PINFO ("Cleaning up superfluous lot links in account %s \n", str);
|
||||
xaccAccountBeginEdit(acc);
|
||||
|
||||
splits = xaccAccountGetSplitList(acc);
|
||||
split_count = g_list_length (splits);
|
||||
for (node = splits; node; node = node->next)
|
||||
{
|
||||
Split *split = node->data;
|
||||
|
||||
PINFO("Start processing split %d of %d",
|
||||
curr_split_no, split_count);
|
||||
|
||||
if (split)
|
||||
gncScrubBusinessSplit (split);
|
||||
|
||||
PINFO("Finished processing split %d of %d",
|
||||
curr_split_no, split_count);
|
||||
curr_split_no++;
|
||||
}
|
||||
xaccAccountCommitEdit(acc);
|
||||
LEAVE ("(acc=%s)", str);
|
||||
}
|
||||
|
||||
/* ============================================================== */
|
||||
|
||||
void
|
||||
gncScrubBusinessAccount (Account *acc)
|
||||
{
|
||||
if (!acc) return;
|
||||
if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
|
||||
|
||||
gncScrubBusinessAccountLots (acc);
|
||||
gncScrubBusinessAccountSplits (acc);
|
||||
}
|
||||
|
||||
/* ============================================================== */
|
||||
|
||||
static void
|
||||
lot_scrub_cb (Account *acc, gpointer data)
|
||||
{
|
||||
if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return;
|
||||
gncScrubBusinessAccountLots (acc);
|
||||
gncScrubBusinessAccount (acc);
|
||||
}
|
||||
|
||||
void
|
||||
gncScrubBusinessAccountTreeLots (Account *acc)
|
||||
gncScrubBusinessAccountTree (Account *acc)
|
||||
{
|
||||
if (!acc) return;
|
||||
|
||||
gnc_account_foreach_descendant(acc, lot_scrub_cb, NULL);
|
||||
gncScrubBusinessAccountLots (acc);
|
||||
gncScrubBusinessAccount (acc);
|
||||
}
|
||||
|
||||
/* ========================== END OF FILE ========================= */
|
||||
|
@ -54,6 +54,21 @@
|
||||
*/
|
||||
gboolean gncScrubBusinessLot (GNCLot *lot);
|
||||
|
||||
/** The gncScrubBusinessSplit() function will fix all issues found with
|
||||
* the given split.
|
||||
*
|
||||
* Currently this function only does one thing: check if the split is
|
||||
* part of a transaction that was generated as the result of a doubly
|
||||
* posted invoice/bill/credit note. Refer to
|
||||
* https://bugzilla.gnome.org/show_bug.cgi?id=754209 to learn how this
|
||||
* could have happened in the past.
|
||||
* If such a transaction is found, its read-only status is removed and
|
||||
* a warning is written to the trace file. Considering the user may
|
||||
* already have added a correcting transaction we leave it up to the user
|
||||
* to decide whether to also delete the transaction or not.
|
||||
*/
|
||||
void gncScrubBusinessSplit (Split *split);
|
||||
|
||||
/** The gncScrubBusinessAccountLots() function will call
|
||||
* gncScrubBusinessLot() on each lot in the given account.
|
||||
*
|
||||
@ -63,15 +78,26 @@ gboolean gncScrubBusinessLot (GNCLot *lot);
|
||||
*/
|
||||
void gncScrubBusinessAccountLots (Account *acc);
|
||||
|
||||
/** The gncScrubBusinessAccountTreeLots() function will call
|
||||
* gncScrubBusinessAccountLots() on each lot in the given account
|
||||
* and its sub accounts.
|
||||
*
|
||||
* This routine is the primary routine for ensuring that the
|
||||
* lot structure of every lot of a business account is in good
|
||||
* order.
|
||||
/** The gncScrubBusinessAccountSplits() function will call
|
||||
* gncScrubBusinessSplit() on each split in the given account.
|
||||
*/
|
||||
void gncScrubBusinessAccountTreeLots (Account *acc);
|
||||
void gncScrubBusinessAccountSplits (Account *acc);
|
||||
|
||||
/** The gncScrubBusinessAccount() function will call
|
||||
* all scrub functions relevant for a given account
|
||||
* on condition the account is a business related account
|
||||
* (Accounts Receivable or Accounts Payable type).
|
||||
*
|
||||
* This routine is the primary routine for fixing all
|
||||
* (known) issues in a business account.
|
||||
*/
|
||||
void gncScrubBusinessAccount (Account *acc);
|
||||
|
||||
/** The gncScrubBusinessAccountTreeLots() function will call
|
||||
* gncScrubBusinessAccount() on the given account
|
||||
* and its sub accounts.
|
||||
*/
|
||||
void gncScrubBusinessAccountTree (Account *acc);
|
||||
|
||||
/** @} */
|
||||
#endif /* GNC_SCRUBBUSINESS_H */
|
||||
|
@ -756,6 +756,8 @@ xaccTransCopyFromClipBoard(const Transaction *from_trans, Transaction *to_trans,
|
||||
xaccTransBeginEdit(to_trans);
|
||||
|
||||
FOR_EACH_SPLIT(to_trans, xaccSplitDestroy(s));
|
||||
g_list_free(to_trans->splits);
|
||||
to_trans->splits = NULL;
|
||||
|
||||
xaccTransSetCurrency(to_trans, xaccTransGetCurrency(from_trans));
|
||||
xaccTransSetDescription(to_trans, xaccTransGetDescription(from_trans));
|
||||
@ -1354,22 +1356,78 @@ xaccTransGetCurrency (const Transaction *trans)
|
||||
return trans ? trans->common_currency : NULL;
|
||||
}
|
||||
|
||||
/* Helper functions for xaccTransSetCurrency */
|
||||
static gnc_numeric
|
||||
find_new_rate(Transaction *trans, gnc_commodity *curr)
|
||||
{
|
||||
GList *node;
|
||||
gnc_numeric rate = gnc_numeric_zero();
|
||||
for (node = trans->splits; node != NULL; node = g_list_next (node))
|
||||
{
|
||||
Split *split = GNC_SPLIT(node->data);
|
||||
gnc_commodity *split_com =
|
||||
xaccAccountGetCommodity(xaccSplitGetAccount(split));
|
||||
if (gnc_commodity_equal(curr, split_com))
|
||||
{
|
||||
/* This looks backwards, but the amount of the balancing transaction
|
||||
* that we're going to use it on is in the value's currency. */
|
||||
rate = gnc_numeric_div(xaccSplitGetAmount(split),
|
||||
xaccSplitGetValue(split),
|
||||
GNC_DENOM_AUTO, GNC_HOW_RND_NEVER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rate;
|
||||
}
|
||||
|
||||
static void
|
||||
split_set_new_value(Split* split, gnc_commodity *curr, gnc_commodity *old_curr,
|
||||
gnc_numeric rate)
|
||||
{
|
||||
gnc_commodity *split_com =
|
||||
xaccAccountGetCommodity(xaccSplitGetAccount(split));
|
||||
if (gnc_commodity_equal(curr, split_com))
|
||||
xaccSplitSetValue(split, xaccSplitGetAmount(split));
|
||||
else if (gnc_commodity_equal(old_curr, split_com))
|
||||
xaccSplitSetSharePrice(split, rate);
|
||||
else
|
||||
{
|
||||
gnc_numeric old_rate = gnc_numeric_div(xaccSplitGetValue(split),
|
||||
xaccSplitGetAmount(split),
|
||||
GNC_DENOM_AUTO,
|
||||
GNC_HOW_RND_NEVER);
|
||||
gnc_numeric new_rate = gnc_numeric_div(old_rate, rate, GNC_DENOM_AUTO,
|
||||
GNC_HOW_RND_NEVER);
|
||||
xaccSplitSetSharePrice(split, new_rate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new currency on a transaction.
|
||||
* When we do that to a transaction with splits we need to re-value
|
||||
* all of the splits in the new currency.
|
||||
* @param trans: The transaction to change
|
||||
* @param curr: The new currency to set.
|
||||
*/
|
||||
void
|
||||
xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr)
|
||||
{
|
||||
gint fraction, old_fraction;
|
||||
|
||||
gnc_commodity *old_curr = trans->common_currency;
|
||||
if (!trans || !curr || trans->common_currency == curr) return;
|
||||
xaccTransBeginEdit(trans);
|
||||
|
||||
old_fraction = gnc_commodity_get_fraction (trans->common_currency);
|
||||
trans->common_currency = curr;
|
||||
fraction = gnc_commodity_get_fraction (curr);
|
||||
|
||||
/* avoid needless crud if fraction didn't change */
|
||||
if (fraction != old_fraction)
|
||||
if (old_curr != NULL && trans->splits != NULL)
|
||||
{
|
||||
FOR_EACH_SPLIT(trans, xaccSplitSetValue(s, xaccSplitGetValue(s)));
|
||||
gnc_numeric rate = find_new_rate(trans, curr);
|
||||
if (!gnc_numeric_zero_p (rate))
|
||||
{
|
||||
FOR_EACH_SPLIT(trans, split_set_new_value(s, curr, old_curr, rate));
|
||||
}
|
||||
else
|
||||
{
|
||||
FOR_EACH_SPLIT(trans, xaccSplitSetValue(s, xaccSplitGetValue(s)));
|
||||
}
|
||||
}
|
||||
|
||||
qof_instance_set_dirty(QOF_INSTANCE(trans));
|
||||
@ -2531,6 +2589,14 @@ xaccTransVoid(Transaction *trans, const char *reason)
|
||||
|
||||
g_return_if_fail(trans && reason);
|
||||
|
||||
/* Prevent voiding transactions that are already marked
|
||||
* read only, for example generated by the business features.
|
||||
*/
|
||||
if (xaccTransGetReadOnly (trans))
|
||||
{
|
||||
PWARN ("Refusing to void a read-only transaction!");
|
||||
return;
|
||||
}
|
||||
xaccTransBeginEdit(trans);
|
||||
qof_instance_get_kvp (QOF_INSTANCE (trans), trans_notes_str, &v);
|
||||
if (G_VALUE_HOLDS_STRING (&v))
|
||||
@ -2645,6 +2711,9 @@ xaccTransReverse (Transaction *orig)
|
||||
g_value_set_boxed (&v, xaccTransGetGUID(trans));
|
||||
qof_instance_set_kvp (QOF_INSTANCE (orig), TRANS_REVERSED_BY, &v);
|
||||
|
||||
/* Make sure the reverse transaction is not read-only */
|
||||
xaccTransClearReadOnly(trans);
|
||||
|
||||
qof_instance_set_dirty(QOF_INSTANCE(trans));
|
||||
xaccTransCommitEdit(trans);
|
||||
return trans;
|
||||
|
@ -1384,7 +1384,8 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc,
|
||||
const GncOwner *owner;
|
||||
|
||||
if (!invoice || !acc) return NULL;
|
||||
|
||||
if (gncInvoiceIsPosted (invoice)) return NULL;
|
||||
|
||||
gncInvoiceBeginEdit (invoice);
|
||||
book = qof_instance_get_book(invoice);
|
||||
|
||||
|
@ -156,7 +156,7 @@ run_test (void)
|
||||
|
||||
if (xaccTransGetVoidStatus(transaction))
|
||||
{
|
||||
failure("void status reports trus after restoring transaction");
|
||||
failure("void status reports true after restoring transaction");
|
||||
}
|
||||
|
||||
if (xaccTransGetVoidReason(transaction))
|
||||
@ -190,6 +190,21 @@ run_test (void)
|
||||
failure("former value (after restore) should be zero");
|
||||
}
|
||||
|
||||
xaccTransSetReadOnly (transaction, "Read-only void test");
|
||||
xaccTransVoid(transaction, reason);
|
||||
|
||||
ts = xaccTransGetVoidTime (transaction);
|
||||
|
||||
if (xaccTransGetVoidStatus(transaction))
|
||||
{
|
||||
failure("void status reports true while read-only transaction can't be voided");
|
||||
}
|
||||
|
||||
if (xaccTransGetVoidReason(transaction))
|
||||
{
|
||||
failure("void reason exists while read-only transaction can't be voided");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -935,20 +935,18 @@ test_xaccTransEqual (Fixture *fixture, gconstpointer pData)
|
||||
|
||||
g_free (check->msg);
|
||||
g_free (check2->msg);
|
||||
check->msg = g_strdup("[xaccSplitEqual] amounts differ: 13333/1000 vs 100000/1000");
|
||||
check2->msg = g_strdup_printf (
|
||||
"[xaccTransEqual] splits %s and %s differ", split_guid0, split_guid0);
|
||||
qof_instance_set_guid (split1, qof_instance_get_guid (split0));
|
||||
g_assert (!xaccTransEqual (clone, txn0, TRUE, TRUE, TRUE, TRUE));
|
||||
g_assert (xaccTransEqual (clone, txn0, TRUE, FALSE, FALSE, TRUE));
|
||||
g_assert_cmpint (check->hits, ==, 11);
|
||||
g_assert_cmpint (check->hits, ==, 10);
|
||||
g_assert_cmpint (check2->hits, ==, 2);
|
||||
|
||||
qof_instance_set_guid (xaccTransGetSplit (txn1, 0),
|
||||
qof_instance_get_guid (split0));
|
||||
qof_instance_set_guid (xaccTransGetSplit (txn1, 1),
|
||||
qof_instance_get_guid (xaccTransGetSplit (txn0, 1)));
|
||||
g_free (check->msg);
|
||||
{
|
||||
Split* split00 = xaccTransGetSplit (txn0, 0);
|
||||
Split* split01 = xaccTransGetSplit (txn0, 1);
|
||||
@ -964,7 +962,7 @@ test_xaccTransEqual (Fixture *fixture, gconstpointer pData)
|
||||
test_add_error (check3);
|
||||
g_assert (!xaccTransEqual (txn1, txn0, TRUE, TRUE, TRUE, TRUE));
|
||||
g_assert (xaccTransEqual (txn1, txn0, TRUE, TRUE, FALSE, TRUE));
|
||||
g_assert_cmpint (check->hits, ==, 12);
|
||||
g_assert_cmpint (check->hits, ==, 11);
|
||||
g_assert_cmpint (check2->hits, ==, 3);
|
||||
g_assert_cmpint (check3->hits, ==, 0);
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
||||
; (gtk_accel_path "<Actions>/gnc-plugin-business-actions/CustomerNewJobOpenAction" "")
|
||||
; (gtk_accel_path "<Actions>/MenuAdditions/PayableAgingAction" "")
|
||||
; (gtk_accel_path "<Actions>/MenuAdditions/GeneralJournalAction" "")
|
||||
; (gtk_accel_path "<Actions>/MainWindowActions/HelpTutorialAction" "")
|
||||
(gtk_accel_path "<Actions>/MainWindowActions/HelpTutorialAction" "")
|
||||
; (gtk_accel_path "<Actions>/gnc-plugin-basic-commands-actions/ActionsSinceLastRunAction" "")
|
||||
; (gtk_accel_path "<Actions>/gnc-plugin-business-actions/BusinessTestReloadOwnerAction" "")
|
||||
; (gtk_accel_path "<Actions>/MenuAdditions/ReceivableAgingAction" "")
|
||||
|
@ -154,6 +154,7 @@ struct GncBudgetViewPrivate
|
||||
Account* expenses;
|
||||
Account* assets;
|
||||
Account* liabilities;
|
||||
Account* rootAcct;
|
||||
};
|
||||
|
||||
#define GNC_BUDGET_VIEW_GET_PRIVATE(o) \
|
||||
@ -216,9 +217,11 @@ gnc_budget_view_init(GncBudgetView *budget_view)
|
||||
ENTER("view %p", budget_view);
|
||||
priv = GNC_BUDGET_VIEW_GET_PRIVATE(budget_view);
|
||||
|
||||
/* Keep track of the top level asset, liability, income and expense accounts */
|
||||
/* Keep track of the root and top level asset, liability, income and expense accounts */
|
||||
root = gnc_book_get_root_account(gnc_get_current_book());
|
||||
num_top_accounts = gnc_account_n_children(root);
|
||||
|
||||
priv->rootAcct = root;
|
||||
|
||||
for (i = 0; i < num_top_accounts; ++i)
|
||||
{
|
||||
@ -721,9 +724,9 @@ budget_accum_helper(Account* account, gpointer data)
|
||||
}
|
||||
}
|
||||
|
||||
/** \brief Function to calculate the accumulated budget amount in a given account for a specified period.
|
||||
/** \brief Function to calculate the accumulated budget amount in a given account at a specified period number.
|
||||
|
||||
This function uses the \ref budget_accum_helper to calculate the accumulated budget amount in a given budget account for a specified period. Specifically, it uses the function \ref gnc_account_foreach_child function passing through an instance of \ref budget_accum_helper.
|
||||
This function uses the \ref budget_accum_helper to calculate the accumulated budget amount in a given budget account for a specified period number. If the acocunt does not have children, then it simply returns the balance of the account.
|
||||
*/
|
||||
static gnc_numeric
|
||||
gbv_get_accumulated_budget_amount(GncBudget* budget, Account* account, guint period_num)
|
||||
@ -733,9 +736,19 @@ gbv_get_accumulated_budget_amount(GncBudget* budget, Account* account, guint per
|
||||
info.total = gnc_numeric_zero();
|
||||
info.budget = budget;
|
||||
info.period_num = period_num;
|
||||
gnc_account_foreach_child(account, budget_accum_helper, &info);
|
||||
|
||||
|
||||
return info.total;
|
||||
|
||||
if (!gnc_budget_is_account_period_value_set(budget, account, period_num))
|
||||
{
|
||||
gnc_account_foreach_child(account, budget_accum_helper, &info);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.total = gnc_budget_get_account_period_value(budget, account, period_num);
|
||||
}
|
||||
return info.total;
|
||||
|
||||
}
|
||||
|
||||
/** \brief Calculates and displays budget amount for a period in a defined account.
|
||||
@ -871,7 +884,10 @@ budget_col_edited(Account *account, GtkTreeViewColumn *col,
|
||||
|
||||
/** \brief Function to find the total in a column of budget provided and display the info in the totals tree widget.
|
||||
|
||||
This function looks at which type of account is in question, and then calls the function \ref gbv_get_accumulated_budget_amount on the root account (assuming that we are not at a totals column) or \ref bgv_get_total_for_account if we are. It then displays this information in the totals tree widget.
|
||||
This function is called on each row within the totals tree (i.e. assets, expenses, transfers, and totals) in order to
|
||||
update the total values in the totals tree (grand totals at the bottom of the budget page). It looks at which type of account is currently being examined, and then calls the function
|
||||
\ref gbv_get_accumulated_budget_amount on all of the relevant children accounts of the root. It then sets the value and color of the cell based on this information in the totals tree widget.
|
||||
|
||||
*/
|
||||
static void
|
||||
totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
|
||||
@ -882,10 +898,20 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
|
||||
GncBudgetViewPrivate* priv;
|
||||
gint row_type;
|
||||
GncBudget *budget;
|
||||
Account* account; // used to make things easier in the adding up processes
|
||||
gint period_num;
|
||||
gnc_numeric value;
|
||||
gnc_numeric value; // used to assist in adding and subtracting
|
||||
gchar amtbuff[100]; //FIXME: overkill, where's the #define?
|
||||
gint width;
|
||||
|
||||
gint width; // FIXME: VARIABLE NOT NEEDED?
|
||||
|
||||
gint i;
|
||||
gint num_top_accounts;
|
||||
|
||||
gnc_numeric totalincome = gnc_numeric_zero();
|
||||
gnc_numeric totalexpenses = gnc_numeric_zero();
|
||||
gnc_numeric totalassets = gnc_numeric_zero();
|
||||
gnc_numeric totalliabilities = gnc_numeric_zero();
|
||||
|
||||
view = GNC_BUDGET_VIEW(user_data);
|
||||
priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
|
||||
@ -895,78 +921,83 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
|
||||
period_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(col),
|
||||
"period_num"));
|
||||
|
||||
|
||||
num_top_accounts = gnc_account_n_children(priv->rootAcct);
|
||||
|
||||
// step through each child account of the root, find the total income, expenses, liabilities, and assets.
|
||||
|
||||
for (i = 0; i < num_top_accounts; ++i)
|
||||
{
|
||||
account = gnc_account_nth_child(priv->rootAcct, i);
|
||||
|
||||
// find the total for this account
|
||||
|
||||
if (period_num < 0)
|
||||
{
|
||||
value = bgv_get_total_for_account(account, budget);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = gbv_get_accumulated_budget_amount(budget, account, period_num);
|
||||
}
|
||||
|
||||
// test for what account type, and add 'value' to the appopriate total
|
||||
|
||||
if (xaccAccountGetType(account) == ACCT_TYPE_INCOME)
|
||||
{
|
||||
totalincome = gnc_numeric_add(totalincome, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
}
|
||||
else if (xaccAccountGetType(account) == ACCT_TYPE_EXPENSE)
|
||||
{
|
||||
totalexpenses = gnc_numeric_add(totalexpenses, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
}
|
||||
else if (xaccAccountGetType(account) == ACCT_TYPE_ASSET)
|
||||
{
|
||||
totalassets = gnc_numeric_add(totalassets, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
}
|
||||
else if (xaccAccountGetType(account) == ACCT_TYPE_LIABILITY)
|
||||
{
|
||||
totalliabilities = gnc_numeric_add(totalliabilities, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing because this account is not of interest
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// at this point we should have variables holding the values for assets, liabilities, expenses and incomes.
|
||||
|
||||
// Set the text to display, depending on which of the totals rows we are currently looking at
|
||||
|
||||
if (row_type == TOTALS_TYPE_INCOME)
|
||||
{
|
||||
if (period_num >= 0)
|
||||
{
|
||||
value = gbv_get_accumulated_budget_amount(budget, priv->income, period_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = bgv_get_total_for_account(priv->income, budget);
|
||||
}
|
||||
xaccSPrintAmount(amtbuff, value,
|
||||
// FIXME: There must be a better way to get the GncAccountPrintInfo object than this. Would prefer to depreciate the tracking of top level accounts.
|
||||
xaccSPrintAmount(amtbuff, totalincome,
|
||||
gnc_account_print_info(priv->income, FALSE));
|
||||
g_object_set(cell, "foreground", "black", NULL);
|
||||
}
|
||||
else if (row_type == TOTALS_TYPE_EXPENSES)
|
||||
{
|
||||
if (period_num >= 0)
|
||||
{
|
||||
value = gbv_get_accumulated_budget_amount(budget, priv->expenses, period_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = bgv_get_total_for_account(priv->expenses, budget);
|
||||
}
|
||||
xaccSPrintAmount(amtbuff, value,
|
||||
|
||||
xaccSPrintAmount(amtbuff, totalexpenses,
|
||||
gnc_account_print_info(priv->expenses, FALSE));
|
||||
g_object_set(cell, "foreground", "black", NULL);
|
||||
}
|
||||
else if (row_type == TOTALS_TYPE_TRANSFERS)
|
||||
{
|
||||
gnc_numeric assets;
|
||||
gnc_numeric liabilities;
|
||||
|
||||
if (period_num >= 0)
|
||||
{
|
||||
assets = gbv_get_accumulated_budget_amount(budget, priv->assets, period_num);
|
||||
liabilities = gbv_get_accumulated_budget_amount(budget, priv->liabilities, period_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
assets = bgv_get_total_for_account(priv->assets, budget);
|
||||
liabilities = bgv_get_total_for_account(priv->liabilities, budget);
|
||||
}
|
||||
value = gnc_numeric_sub(assets, liabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
xaccSPrintAmount(amtbuff, value,
|
||||
|
||||
xaccSPrintAmount(amtbuff, gnc_numeric_sub(totalassets, totalliabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD),
|
||||
gnc_account_print_info(priv->assets, FALSE));
|
||||
g_object_set(cell, "foreground", "black", NULL);
|
||||
}
|
||||
else if (row_type == TOTALS_TYPE_TOTAL)
|
||||
{
|
||||
gnc_numeric income;
|
||||
gnc_numeric expenses;
|
||||
gnc_numeric assets;
|
||||
gnc_numeric liabilities;
|
||||
|
||||
if (period_num >= 0)
|
||||
{
|
||||
income = gbv_get_accumulated_budget_amount(budget, priv->income, period_num);
|
||||
expenses = gbv_get_accumulated_budget_amount(budget, priv->expenses, period_num);
|
||||
assets = gbv_get_accumulated_budget_amount(budget, priv->assets, period_num);
|
||||
liabilities = gbv_get_accumulated_budget_amount(budget, priv->liabilities, period_num);
|
||||
}
|
||||
else
|
||||
{
|
||||
income = bgv_get_total_for_account(priv->income, budget);
|
||||
expenses = bgv_get_total_for_account(priv->expenses, budget);
|
||||
assets = bgv_get_total_for_account(priv->assets, budget);
|
||||
liabilities = bgv_get_total_for_account(priv->liabilities, budget);
|
||||
}
|
||||
value = gnc_numeric_sub(income, expenses, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
value = gnc_numeric_sub(value, assets, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
value = gnc_numeric_add(value, liabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
value = gnc_numeric_sub(totalincome, totalexpenses, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
value = gnc_numeric_sub(value, totalassets, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
value = gnc_numeric_add(value, totalliabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
|
||||
xaccSPrintAmount(amtbuff, value,
|
||||
gnc_account_print_info(priv->assets, FALSE));
|
||||
if (gnc_numeric_negative_p(value))
|
||||
@ -980,6 +1011,7 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
|
||||
}
|
||||
else
|
||||
{
|
||||
// if it reaches here then the row type was not set correctly
|
||||
g_strlcpy(amtbuff, "error", sizeof(amtbuff));
|
||||
}
|
||||
|
||||
|
@ -1574,7 +1574,7 @@ gnc_plugin_page_account_tree_cmd_scrub (GtkAction *action, GncPluginPageAccountT
|
||||
if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL)
|
||||
xaccAccountScrubLots(account);
|
||||
|
||||
gncScrubBusinessAccountLots(account);
|
||||
gncScrubBusinessAccount(account);
|
||||
|
||||
|
||||
gnc_resume_gui_refresh ();
|
||||
@ -1596,7 +1596,7 @@ gnc_plugin_page_account_tree_cmd_scrub_sub (GtkAction *action, GncPluginPageAcco
|
||||
if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL)
|
||||
xaccAccountTreeScrubLots(account);
|
||||
|
||||
gncScrubBusinessAccountTreeLots(account);
|
||||
gncScrubBusinessAccountTree(account);
|
||||
|
||||
gnc_resume_gui_refresh ();
|
||||
}
|
||||
@ -1614,7 +1614,7 @@ gnc_plugin_page_account_tree_cmd_scrub_all (GtkAction *action, GncPluginPageAcco
|
||||
if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL)
|
||||
xaccAccountTreeScrubLots(root);
|
||||
|
||||
gncScrubBusinessAccountTreeLots(root);
|
||||
gncScrubBusinessAccountTree(root);
|
||||
|
||||
gnc_resume_gui_refresh ();
|
||||
}
|
||||
|
@ -2972,6 +2972,12 @@ gnc_plugin_page_register_cmd_void_transaction (GtkAction *action,
|
||||
gnc_error_dialog(NULL, "%s", _("You cannot void a transaction with reconciled or cleared splits."));
|
||||
return;
|
||||
}
|
||||
reason = xaccTransGetReadOnly (trans);
|
||||
if (reason)
|
||||
{
|
||||
gnc_error_dialog(NULL, _("This transaction is marked read-only with the comment: '%s'"), reason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gnc_plugin_page_register_finish_pending(GNC_PLUGIN_PAGE(page)))
|
||||
return;
|
||||
@ -3744,7 +3750,10 @@ gnc_plugin_page_register_cmd_scrub_current (GtkAction *action,
|
||||
split = gnc_split_register_get_current_split (reg);
|
||||
lot = xaccSplitGetLot (split);
|
||||
if (lot && xaccAccountIsAPARType (xaccAccountGetType (xaccSplitGetAccount (split))))
|
||||
{
|
||||
gncScrubBusinessLot (lot);
|
||||
gncScrubBusinessSplit (split);
|
||||
}
|
||||
gnc_resume_gui_refresh();
|
||||
LEAVE(" ");
|
||||
}
|
||||
@ -3791,7 +3800,10 @@ gnc_plugin_page_register_cmd_scrub_all (GtkAction *action,
|
||||
|
||||
lot = xaccSplitGetLot (split);
|
||||
if (lot && xaccAccountIsAPARType (xaccAccountGetType (xaccSplitGetAccount (split))))
|
||||
{
|
||||
gncScrubBusinessLot (lot);
|
||||
gncScrubBusinessSplit (split);
|
||||
}
|
||||
|
||||
PINFO("Finished processing split %d of %d",
|
||||
curr_split_no, split_count);
|
||||
|
@ -64,13 +64,16 @@ backends (when reading the GncGUID from the data source). */
|
||||
#define qof_book_set_guid(book,guid) \
|
||||
qof_instance_set_guid(QOF_INSTANCE(book), guid)
|
||||
|
||||
/** Validate a counter format string with the given
|
||||
* G_GINT64_FORMAT. Returns an error message if the format string
|
||||
* was invalid, or NULL if it is ok. The caller should free the
|
||||
* error message with g_free.
|
||||
/** Validate a counter format string with a given format specifier.
|
||||
* If valid, returns a normalized format string,
|
||||
* that is whatever long int specifier was used will be replaced with the value of
|
||||
* the posix "PRIx64" macro.
|
||||
* If not valid returns NULL and optionally set an error message is a non-null
|
||||
* err_msg parameter was passed.
|
||||
* The caller should free the returned format string and error message with g_free.
|
||||
*/
|
||||
gchar *qof_book_validate_counter_format_internal(const gchar *p,
|
||||
const gchar* gint64_format);
|
||||
gchar *qof_book_normalize_counter_format_internal(const gchar *p,
|
||||
const gchar* gint64_format, gchar **err_msg);
|
||||
|
||||
/** This debugging function can be used to traverse the book structure
|
||||
* and all subsidiary structures, printing out which structures
|
||||
|
@ -44,6 +44,7 @@ extern "C"
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@ -668,7 +669,8 @@ qof_book_increment_and_format_counter (QofBook *book, const char *counter_name)
|
||||
KvpFrame *kvp;
|
||||
KvpValue *value;
|
||||
gint64 counter;
|
||||
const char* format;
|
||||
gchar* format;
|
||||
gchar* result;
|
||||
|
||||
if (!book)
|
||||
{
|
||||
@ -717,16 +719,19 @@ qof_book_increment_and_format_counter (QofBook *book, const char *counter_name)
|
||||
}
|
||||
|
||||
/* Generate a string version of the counter */
|
||||
return g_strdup_printf(format, counter);
|
||||
result = g_strdup_printf(format, counter);
|
||||
g_free (format);
|
||||
return result;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
char *
|
||||
qof_book_get_counter_format(const QofBook *book, const char *counter_name)
|
||||
{
|
||||
KvpFrame *kvp;
|
||||
const char *format;
|
||||
const char *user_format = NULL;
|
||||
gchar *norm_format = NULL;
|
||||
KvpValue *value;
|
||||
gchar *error;
|
||||
gchar *error = NULL;
|
||||
|
||||
if (!book)
|
||||
{
|
||||
@ -749,49 +754,75 @@ qof_book_get_counter_format(const QofBook *book, const char *counter_name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
format = NULL;
|
||||
|
||||
/* Get the format string */
|
||||
value = kvp->get_slot({"counter_formats", counter_name});
|
||||
if (value)
|
||||
{
|
||||
format = value->get<const char*>();
|
||||
error = qof_book_validate_counter_format(format);
|
||||
if (error != NULL)
|
||||
user_format = value->get<const char*>();
|
||||
norm_format = qof_book_normalize_counter_format(user_format, &error);
|
||||
if (!norm_format)
|
||||
{
|
||||
PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", format, counter_name, error);
|
||||
PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", user_format, counter_name, error);
|
||||
/* Invalid format string */
|
||||
format = NULL;
|
||||
user_format = NULL;
|
||||
g_free(error);
|
||||
}
|
||||
}
|
||||
|
||||
/* If no (valid) format string was found, use the default format
|
||||
* string */
|
||||
if (!format)
|
||||
if (!norm_format)
|
||||
{
|
||||
/* Use the default format */
|
||||
format = "%.6" G_GINT64_FORMAT;
|
||||
norm_format = g_strdup ("%.6" PRIi64);
|
||||
}
|
||||
return format;
|
||||
return norm_format;
|
||||
}
|
||||
|
||||
gchar *
|
||||
qof_book_validate_counter_format(const gchar *p)
|
||||
qof_book_normalize_counter_format(const gchar *p, gchar **err_msg)
|
||||
{
|
||||
return qof_book_validate_counter_format_internal(p, G_GINT64_FORMAT);
|
||||
const gchar *valid_formats [] = {
|
||||
G_GINT64_FORMAT,
|
||||
"lli",
|
||||
"I64i",
|
||||
PRIi64,
|
||||
"li",
|
||||
NULL,
|
||||
};
|
||||
int i = 0;
|
||||
gchar *normalized_spec = NULL;
|
||||
|
||||
while (valid_formats[i])
|
||||
{
|
||||
|
||||
if (err_msg && *err_msg)
|
||||
{
|
||||
g_free (*err_msg);
|
||||
*err_msg = NULL;
|
||||
}
|
||||
|
||||
normalized_spec = qof_book_normalize_counter_format_internal(p, valid_formats[i], err_msg);
|
||||
if (normalized_spec)
|
||||
return normalized_spec; /* Found a valid format specifier, return */
|
||||
i++;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gchar *
|
||||
qof_book_validate_counter_format_internal(const gchar *p,
|
||||
const gchar *gint64_format)
|
||||
qof_book_normalize_counter_format_internal(const gchar *p,
|
||||
const gchar *gint64_format, gchar **err_msg)
|
||||
{
|
||||
const gchar *conv_start, *tmp = NULL;
|
||||
const gchar *conv_start, *base, *tmp = NULL;
|
||||
gchar *normalized_str = NULL, *aux_str = NULL;
|
||||
|
||||
/* Validate a counter format. This is a very simple "parser" that
|
||||
* simply checks for a single gint64 conversion specification,
|
||||
* allowing all modifiers and flags that printf(3) specifies (except
|
||||
* for the * width and precision, which need an extra argument). */
|
||||
base = p;
|
||||
|
||||
/* Skip a prefix of any character except % */
|
||||
while (*p)
|
||||
@ -812,7 +843,11 @@ qof_book_validate_counter_format_internal(const gchar *p,
|
||||
}
|
||||
|
||||
if (!*p)
|
||||
return g_strdup("Format string ended without any conversion specification");
|
||||
{
|
||||
if (err_msg)
|
||||
*err_msg = g_strdup("Format string ended without any conversion specification");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Store the start of the conversion for error messages */
|
||||
conv_start = p;
|
||||
@ -824,6 +859,13 @@ qof_book_validate_counter_format_internal(const gchar *p,
|
||||
* specification (e.g. "li" on Unix, "I64i" on Windows). */
|
||||
tmp = strstr(p, gint64_format);
|
||||
|
||||
if (!tmp)
|
||||
{
|
||||
if (err_msg)
|
||||
*err_msg = g_strdup_printf("Format string doesn't contain requested format specifier: %s", gint64_format);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Skip any number of flag characters */
|
||||
while (*p && (tmp != p) && strchr("#0- +'I", *p))
|
||||
{
|
||||
@ -831,39 +873,45 @@ qof_book_validate_counter_format_internal(const gchar *p,
|
||||
tmp = strstr(p, gint64_format);
|
||||
}
|
||||
|
||||
/* Skip any number of field width digits */
|
||||
while (*p && (tmp != p) && strchr("0123456789", *p))
|
||||
/* Skip any number of field width digits,
|
||||
* and precision specifier digits (including the leading dot) */
|
||||
while (*p && (tmp != p) && strchr("0123456789.", *p))
|
||||
{
|
||||
p++;
|
||||
tmp = strstr(p, gint64_format);
|
||||
}
|
||||
|
||||
/* A precision specifier always starts with a dot */
|
||||
if (*p && *p == '.')
|
||||
{
|
||||
/* Skip the . */
|
||||
p++;
|
||||
/* Skip any number of precision digits */
|
||||
while (*p && strchr("0123456789", *p)) p++;
|
||||
}
|
||||
|
||||
if (!*p)
|
||||
return g_strdup_printf("Format string ended during the conversion specification. Conversion seen so far: %s", conv_start);
|
||||
{
|
||||
if (err_msg)
|
||||
*err_msg = g_strdup_printf("Format string ended during the conversion specification. Conversion seen so far: %s", conv_start);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* See if the format string starts with the correct format
|
||||
* specification. */
|
||||
tmp = strstr(p, gint64_format);
|
||||
if (tmp == NULL)
|
||||
{
|
||||
return g_strdup_printf("Invalid length modifier and/or conversion specifier ('%.4s'), it should be: %s", p, gint64_format);
|
||||
if (err_msg)
|
||||
*err_msg = g_strdup_printf("Invalid length modifier and/or conversion specifier ('%.4s'), it should be: %s", p, gint64_format);
|
||||
return NULL;
|
||||
}
|
||||
else if (tmp != p)
|
||||
{
|
||||
return g_strdup_printf("Garbage before length modifier and/or conversion specifier: '%*s'", (int)(tmp - p), p);
|
||||
if (err_msg)
|
||||
*err_msg = g_strdup_printf("Garbage before length modifier and/or conversion specifier: '%*s'", (int)(tmp - p), p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Copy the string we have so far and add normalized format specifier for long int */
|
||||
aux_str = g_strndup (base, p - base);
|
||||
normalized_str = g_strconcat (aux_str, PRIi64, NULL);
|
||||
g_free (aux_str);
|
||||
|
||||
/* Skip length modifier / conversion specifier */
|
||||
p += strlen(gint64_format);
|
||||
tmp = p;
|
||||
|
||||
/* Skip a suffix of any character except % */
|
||||
while (*p)
|
||||
@ -878,14 +926,24 @@ qof_book_validate_counter_format_internal(const gchar *p,
|
||||
/* Break on a single percent mark, which is the start of the
|
||||
* conversion specification */
|
||||
if (*p == '%')
|
||||
return g_strdup_printf("Format string contains unescaped %% signs (or multiple conversion specifications) at '%s'", p);
|
||||
{
|
||||
if (err_msg)
|
||||
*err_msg = g_strdup_printf("Format string contains unescaped %% signs (or multiple conversion specifications) at '%s'", p);
|
||||
g_free (normalized_str);
|
||||
return NULL;
|
||||
}
|
||||
/* Skip all other characters */
|
||||
p++;
|
||||
}
|
||||
|
||||
/* Add the suffix to our normalized string */
|
||||
aux_str = normalized_str;
|
||||
normalized_str = g_strconcat (aux_str, tmp, NULL);
|
||||
g_free (aux_str);
|
||||
|
||||
/* If we end up here, the string was valid, so return no error
|
||||
* message */
|
||||
return NULL;
|
||||
return normalized_str;
|
||||
}
|
||||
|
||||
/** Returns pointer to book-currency name for book, if one exists in the
|
||||
|
@ -332,18 +332,21 @@ gint64 qof_book_get_counter (QofBook *book, const char *counter_name);
|
||||
*/
|
||||
gchar *qof_book_increment_and_format_counter (QofBook *book, const char *counter_name);
|
||||
|
||||
/** Validate a counter format string. Returns an error message if the
|
||||
* format string was invalid, or NULL if it is ok. The caller should
|
||||
* free the error message with g_free.
|
||||
/** Validate a counter format string. If valid, returns a normalized format string,
|
||||
* that is whatever long int specifier was used will be replaced with the value of
|
||||
* the posix "PRIx64" macro.
|
||||
* If not valid returns NULL and optionally set an error message is a non-null
|
||||
* err_msg parameter was passed.
|
||||
* The caller should free the returned format string and error message with g_free.
|
||||
*/
|
||||
gchar * qof_book_validate_counter_format(const gchar *format);
|
||||
gchar * qof_book_normalize_counter_format(const gchar *format, gchar **err_msg);
|
||||
|
||||
/** Get the format string to use for the named counter.
|
||||
* The return value is NULL on error or the format string of the
|
||||
* counter. The string should not be freed.
|
||||
*/
|
||||
const char *qof_book_get_counter_format (const QofBook *book,
|
||||
const char *counter_name);
|
||||
char *qof_book_get_counter_format (const QofBook *book,
|
||||
const char *counter_name);
|
||||
|
||||
const char* qof_book_get_string_option(const QofBook* book, const char* opt_name);
|
||||
void qof_book_set_string_option(QofBook* book, const char* opt_name, const char* opt_val);
|
||||
|
@ -27,6 +27,7 @@ extern "C"
|
||||
#include "config.h"
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <inttypes.h>
|
||||
#include <unittest-support.h>
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@ -132,64 +133,133 @@ test_book_readonly( Fixture *fixture, gconstpointer pData )
|
||||
g_assert( qof_book_is_readonly( fixture->book ) );
|
||||
}
|
||||
static void
|
||||
test_book_validate_counter( void )
|
||||
test_book_normalize_counter( void )
|
||||
{
|
||||
gchar *r;
|
||||
gchar *r, *err_msg = NULL;
|
||||
g_test_bug("644036");
|
||||
g_test_bug("728722");
|
||||
|
||||
/* Test for detection of missing format conversion */
|
||||
r = qof_book_validate_counter_format("This string is missing the conversion specifier");
|
||||
g_assert(r);
|
||||
if (r && g_test_verbose())
|
||||
r = qof_book_normalize_counter_format("This string is missing the conversion specifier", &err_msg);
|
||||
g_assert(!r);
|
||||
g_assert(err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format validation correctly failed: %s", r);
|
||||
g_test_message("Counter format normalization correctly failed: %s", err_msg);
|
||||
}
|
||||
g_free(r);
|
||||
g_free(err_msg);
|
||||
err_msg = NULL;
|
||||
|
||||
/* Test the usual Linux/Unix G_GINT64_FORMAT */
|
||||
r = qof_book_validate_counter_format_internal("Test - %li", "li");
|
||||
if (r && g_test_verbose())
|
||||
r = qof_book_normalize_counter_format("Test - %li", &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format validation erroneously failed: %s", r);
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert(r == NULL);
|
||||
g_assert_cmpstr( r, == , "Test - %" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the Windows G_GINT64_FORMAT */
|
||||
r = qof_book_validate_counter_format_internal("Test - %I64i", "I64i");
|
||||
if (r && g_test_verbose())
|
||||
r = qof_book_normalize_counter_format("Test - %I64i", &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format validation erroneously failed: %s", r);
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert(r == NULL);
|
||||
g_assert_cmpstr( r, == , "Test - %" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the system's GINT64_FORMAT */
|
||||
r = qof_book_validate_counter_format("Test - %" G_GINT64_FORMAT);
|
||||
if (r && g_test_verbose())
|
||||
/* Test the system's G_INT64_FORMAT */
|
||||
r = qof_book_normalize_counter_format("Test - %" G_GINT64_FORMAT, &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format validation erroneously failed: %s", r);
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert(r == NULL);
|
||||
g_assert_cmpstr( r, == , "Test - %" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the posix' PRIi64 */
|
||||
r = qof_book_normalize_counter_format("Test - %" PRIi64, &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert_cmpstr( r, == , "Test - %" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the posix' PRIi64 with precision field */
|
||||
r = qof_book_normalize_counter_format("Test - %.3" PRIi64, &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert_cmpstr( r, == , "Test - %.3" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the posix' PRIi64 with width field */
|
||||
r = qof_book_normalize_counter_format("Test - %5" PRIi64, &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert_cmpstr( r, == , "Test - %5" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the posix' PRIi64 with width and precision field */
|
||||
r = qof_book_normalize_counter_format("Test - %5.4" PRIi64, &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert_cmpstr( r, == , "Test - %5.4" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the usual Linux/Unix G_GINT64_FORMAT */
|
||||
r = qof_book_normalize_counter_format_internal("Test - %li", "li", &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert_cmpstr( r, == , "Test - %" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test the Windows G_GINT64_FORMAT */
|
||||
r = qof_book_normalize_counter_format_internal("Test - %I64i", "I64i", &err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format normalization erroneously failed: %s", err_msg);
|
||||
}
|
||||
g_assert_cmpstr( r, == , "Test - %" PRIi64);
|
||||
g_assert(err_msg == NULL);
|
||||
g_free(r);
|
||||
|
||||
/* Test an erroneous Windows G_GINT64_FORMAT */
|
||||
r = qof_book_validate_counter_format_internal("Test - %li", "I64i");
|
||||
if (r && g_test_verbose())
|
||||
r = qof_book_normalize_counter_format_internal("Test - %li", "I64i", &err_msg);
|
||||
g_assert(!r);
|
||||
g_assert(err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format validation correctly failed: %s", r);
|
||||
g_test_message("Counter format normalization correctly failed: %s", err_msg);
|
||||
}
|
||||
g_assert(r);
|
||||
g_free(r);
|
||||
g_free(err_msg);
|
||||
err_msg = NULL;
|
||||
|
||||
/* Test an erroneous Linux G_GINT64_FORMAT */
|
||||
r = qof_book_validate_counter_format_internal("Test - %I64i", "li");
|
||||
if (r && g_test_verbose())
|
||||
r = qof_book_normalize_counter_format_internal("Test - %I64i", "li", &err_msg);
|
||||
g_assert(!r);
|
||||
g_assert(err_msg);
|
||||
if (!r && g_test_verbose())
|
||||
{
|
||||
g_test_message("Counter format validation correctly failed: %s", r);
|
||||
g_test_message("Counter format normalization correctly failed: %s", err_msg);
|
||||
}
|
||||
g_assert(r);
|
||||
g_free(r);
|
||||
g_free(err_msg);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -298,18 +368,18 @@ test_book_get_counter_format ( Fixture *fixture, gconstpointer pData )
|
||||
g_free( test_struct.msg );
|
||||
|
||||
g_test_message( "Testing counter format when counter name is empty string" );
|
||||
r = qof_book_get_counter_format( fixture->book, NULL );
|
||||
r = qof_book_get_counter_format( fixture->book, "" );
|
||||
g_assert_cmpstr( r, == , NULL );
|
||||
g_assert( g_strrstr( test_struct.msg, err_invalid_cnt ) != NULL );
|
||||
g_free( test_struct.msg );
|
||||
|
||||
g_test_message( "Testing counter format with existing counter" );
|
||||
r = qof_book_get_counter_format( fixture->book, counter_name );
|
||||
g_assert_cmpstr( r, == , "%.6" G_GINT64_FORMAT);
|
||||
g_assert_cmpstr( r, == , "%.6" PRIi64);
|
||||
|
||||
g_test_message( "Testing counter format for default value" );
|
||||
r = qof_book_get_counter_format( fixture->book, counter_name );
|
||||
g_assert_cmpstr( r, == , "%.6" G_GINT64_FORMAT);
|
||||
g_assert_cmpstr( r, == , "%.6" PRIi64);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -340,7 +410,7 @@ test_book_increment_and_format_counter ( Fixture *fixture, gconstpointer pData )
|
||||
g_free( test_struct.msg );
|
||||
|
||||
g_test_message( "Testing increment and format when counter name is empty string" );
|
||||
r = qof_book_increment_and_format_counter( fixture->book, NULL );
|
||||
r = qof_book_increment_and_format_counter( fixture->book, "" );
|
||||
g_assert_cmpstr( r, == , NULL );
|
||||
g_free( r );
|
||||
g_assert( g_strrstr( test_struct.msg, err_invalid_cnt ) != NULL );
|
||||
@ -833,7 +903,7 @@ void
|
||||
test_suite_qofbook ( void )
|
||||
{
|
||||
GNC_TEST_ADD( suitename, "readonly", Fixture, NULL, setup, test_book_readonly, teardown );
|
||||
GNC_TEST_ADD_FUNC( suitename, "validate counter", test_book_validate_counter );
|
||||
GNC_TEST_ADD_FUNC( suitename, "validate counter", test_book_normalize_counter );
|
||||
GNC_TEST_ADD( suitename, "get string option", Fixture, NULL, setup, test_book_get_string_option, teardown );
|
||||
GNC_TEST_ADD( suitename, "set string option", Fixture, NULL, setup, test_book_set_string_option, teardown );
|
||||
GNC_TEST_ADD( suitename, "session not saved", Fixture, NULL, setup, test_book_session_not_saved, teardown );
|
||||
|
@ -885,11 +885,15 @@ gnc_split_register_auto_completion (SplitRegister *reg,
|
||||
|
||||
if (gnc_split_register_get_default_account (reg) != NULL)
|
||||
{
|
||||
Account *default_account;
|
||||
Account *default_account =
|
||||
gnc_split_register_get_default_account (reg);
|
||||
gnc_commodity *trans_cmdty = xaccTransGetCurrency(trans);
|
||||
gnc_commodity *acct_cmdty = xaccAccountGetCommodity(default_account);
|
||||
Split *s;
|
||||
int i = 0;
|
||||
|
||||
default_account = gnc_split_register_get_default_account (reg);
|
||||
if (gnc_commodity_is_currency(acct_cmdty) &&
|
||||
!gnc_commodity_equal(trans_cmdty, acct_cmdty))
|
||||
xaccTransSetCurrency(trans, acct_cmdty);
|
||||
|
||||
while ((s = xaccTransGetSplit(trans, i)) != NULL)
|
||||
{
|
||||
|
@ -424,6 +424,14 @@
|
||||
(loop (+ 1 col) 1)))))
|
||||
|
||||
|
||||
(push "var all_ticks = [")
|
||||
(for-each
|
||||
(lambda (val)
|
||||
(push "\"")
|
||||
(push val)
|
||||
(push "\","))
|
||||
(gnc:html-barchart-row-labels barchart))
|
||||
(push "];\n")
|
||||
(push "var options = {
|
||||
shadowAlpha: 0.07,
|
||||
stackSeries: false,
|
||||
@ -493,28 +501,43 @@
|
||||
(push " options.axes.yaxis.label = \"")
|
||||
(push y-label)
|
||||
(push "\";\n")))
|
||||
(if (and (string? row-labels) (> (string-length row-labels) 0))
|
||||
(begin
|
||||
(push " options.axes.xaxis.ticks = [")
|
||||
(for-each (lambda (val)
|
||||
(push "\"")
|
||||
(push val)
|
||||
(push "\","))
|
||||
(gnc:html-barchart-row-labels barchart))
|
||||
(push "];\n")))
|
||||
(push " options.axes.xaxis.ticks = all_ticks;\n")
|
||||
|
||||
|
||||
(push "$.jqplot.config.enablePlugins = true;\n")
|
||||
(push "var plot = $.jqplot('")(push chart-id)(push"', data, options);
|
||||
(push "$(document).ready(function() {
|
||||
var plot = $.jqplot('")(push chart-id)(push"', data, options);
|
||||
var int_chart_width = document.getElementById(\"")(push chart-id)(push"\").getElementsByClassName(\"jqplot-zoom-canvas\")[0].width;
|
||||
plot.axes.xaxis.ticks = getVisualTicks(int_chart_width);
|
||||
plot.replot();
|
||||
});
|
||||
|
||||
function formatTooltip(str, seriesIndex, pointIndex) {
|
||||
if (options.axes.xaxis.ticks[pointIndex] !== undefined)
|
||||
x = options.axes.xaxis.ticks[pointIndex];
|
||||
else
|
||||
x = pointIndex;
|
||||
y = data[seriesIndex][pointIndex][1].toFixed(2);
|
||||
return options.series[seriesIndex].label + '<br/>' + x + '<br/><b>' + y + '</b>';
|
||||
}\n")
|
||||
function formatTooltip(str, seriesIndex, pointIndex) {
|
||||
if (options.axes.xaxis.ticks[pointIndex] !== undefined)
|
||||
x = options.axes.xaxis.ticks[pointIndex];
|
||||
else
|
||||
x = pointIndex;
|
||||
y = data[seriesIndex][pointIndex][1].toFixed(2);
|
||||
return options.series[seriesIndex].label + '<br/>' + x + '<br/><b>' + y + '</b>';
|
||||
}
|
||||
|
||||
function getVisualTicks(chart_width) {
|
||||
var num_ticks = all_ticks.length;
|
||||
var label_width = 25;
|
||||
var num_labels = chart_width / label_width;
|
||||
var show_every_nth_label = Math.ceil (num_ticks / num_labels);
|
||||
var visual_ticks = [];
|
||||
|
||||
if (show_every_nth_label == 0)
|
||||
show_every_nth_label = 1;
|
||||
for (counter = 0; counter < all_ticks.length; counter++) {
|
||||
if ((counter % show_every_nth_label) == 0)
|
||||
visual_ticks.push (all_ticks[counter]);
|
||||
else
|
||||
visual_ticks.push (' ');
|
||||
}
|
||||
return visual_ticks;
|
||||
}\n")
|
||||
|
||||
(push "});\n</script>")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user