Cache current owner balances

These are queried continuously by the owner tree view (on Customer/Vendor/Employee
Overview pages) and recalculating them is an expensive operation.
The cache will be invalidated each time a lot reated to the owner
changes (modify or delete). The net effect is a huge responsiveness
improvement of said overviews in case of a large book.
This commit is contained in:
Geert Janssens 2018-09-23 15:59:27 +02:00
parent 40bcd1e377
commit 3991ccb9c2
9 changed files with 293 additions and 104 deletions

View File

@ -46,9 +46,9 @@
#include "gncJobP.h"
#include "gncTaxTableP.h"
static gint gs_address_event_handler_id = 0;
static void listen_for_address_events(QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data);
static gint cust_qof_event_handler_id = 0;
static void cust_handle_qof_events (QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data);
struct _gncCustomer
{
@ -66,6 +66,7 @@ struct _gncCustomer
GncTaxIncluded taxincluded;
gboolean active;
GList * jobs;
gnc_numeric * balance; /* cached customer balance, will not be stored */
/* The following fields are unique to 'customer' */
gnc_numeric credit;
@ -319,15 +320,14 @@ GncCustomer *gncCustomerCreate (QofBook *book)
cust->taxincluded = GNC_TAXINCLUDED_USEGLOBAL;
cust->active = TRUE;
cust->jobs = NULL;
cust->balance = NULL;
cust->discount = gnc_numeric_zero();
cust->credit = gnc_numeric_zero();
cust->shipaddr = gncAddressCreate (book, &cust->inst);
if (gs_address_event_handler_id == 0)
{
gs_address_event_handler_id = qof_event_register_handler(listen_for_address_events, NULL);
}
if (cust_qof_event_handler_id == 0)
cust_qof_event_handler_id = qof_event_register_handler (cust_handle_qof_events, NULL);
qof_event_gen (&cust->inst, QOF_EVENT_CREATE, NULL);
@ -356,6 +356,7 @@ static void gncCustomerFree (GncCustomer *cust)
gncAddressBeginEdit (cust->shipaddr);
gncAddressDestroy (cust->shipaddr);
g_list_free (cust->jobs);
g_free (cust->balance);
if (cust->terms)
gncBillTermDecRef (cust->terms);
@ -825,8 +826,11 @@ gncCustomerEqual(const GncCustomer *a, const GncCustomer *b)
}
/**
* Listens for MODIFY events from addresses. If the address belongs to a customer,
* mark the customer as dirty.
* Listen for qof events.
*
* - If the address of a customer has changed, mark the customer as dirty.
* - If a lot related to a customer has changed, clear the customer's
* cached balance as it likely has become invalid.
*
* @param entity Entity for the event
* @param event_type Event type
@ -834,28 +838,50 @@ gncCustomerEqual(const GncCustomer *a, const GncCustomer *b)
* @param event_data Event data passed with the event.
*/
static void
listen_for_address_events(QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data)
cust_handle_qof_events (QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data)
{
GncCustomer* cust;
/* Handle address change events */
if ((GNC_IS_ADDRESS (entity) &&
(event_type & QOF_EVENT_MODIFY) != 0))
{
if (GNC_IS_CUSTOMER (event_data))
{
GncCustomer* cust = GNC_CUSTOMER (event_data);
gncCustomerBeginEdit (cust);
mark_customer (cust);
gncCustomerCommitEdit (cust);
}
return;
}
if ((event_type & QOF_EVENT_MODIFY) == 0)
/* Handle lot change events */
if (GNC_IS_LOT (entity))
{
GNCLot *lot = GNC_LOT (entity);
GncOwner lot_owner;
const GncOwner *end_owner = NULL;
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
/* Determine the owner associated with the lot */
if (invoice)
/* Invoice lots */
end_owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice));
else if (gncOwnerGetOwnerFromLot (lot, &lot_owner))
/* Pre-payment lots */
end_owner = gncOwnerGetEndOwner (&lot_owner);
if (gncOwnerGetType (end_owner) == GNC_OWNER_CUSTOMER)
{
/* Clear the cached balance */
GncCustomer* cust = gncOwnerGetCustomer (end_owner);
g_free (cust->balance);
cust->balance = NULL;
}
return;
}
if (!GNC_IS_ADDRESS(entity))
{
return;
}
if (!GNC_IS_CUSTOMER(event_data))
{
return;
}
cust = GNC_CUSTOMER(event_data);
gncCustomerBeginEdit(cust);
mark_customer(cust);
gncCustomerCommitEdit(cust);
}
/* ============================================================== */
/* Package-Private functions */
static const char * _gncCustomerPrintable (gpointer item)
@ -952,3 +978,24 @@ gchar *gncCustomerNextID (QofBook *book)
{
return qof_book_increment_and_format_counter (book, _GNC_MOD_NAME);
}
const gnc_numeric*
gncCustomerGetCachedBalance (GncCustomer *cust)
{
return cust->balance;
}
void gncCustomerSetCachedBalance (GncCustomer *cust, const gnc_numeric *new_bal)
{
if (!new_bal && cust->balance)
{
g_free (cust->balance);
cust->balance = NULL;
return;
}
if (!cust->balance)
cust->balance = g_new0 (gnc_numeric, 1);
*cust->balance = *new_bal;
}

View File

@ -33,6 +33,8 @@
gboolean gncCustomerRegister (void);
gchar *gncCustomerNextID (QofBook *book);
const gnc_numeric *gncCustomerGetCachedBalance (GncCustomer *cust);
void gncCustomerSetCachedBalance (GncCustomer *cust, const gnc_numeric *new_bal);
#define gncCustomerSetGUID(E,G) qof_instance_set_guid(QOF_INSTANCE(E),(G))

View File

@ -37,10 +37,12 @@
#include "gncAddressP.h"
#include "gncEmployee.h"
#include "gncEmployeeP.h"
#include "gnc-lot.h"
#include "gncOwner.h"
static gint gs_address_event_handler_id = 0;
static void listen_for_address_events(QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data);
static gint empl_qof_event_handler_id = 0;
static void empl_handle_qof_events (QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data);
struct _gncEmployee
{
@ -50,6 +52,7 @@ struct _gncEmployee
GncAddress * addr;
gnc_commodity * currency;
gboolean active;
gnc_numeric * balance;
char * language;
char * acl;
@ -439,11 +442,10 @@ GncEmployee *gncEmployeeCreate (QofBook *book)
employee->workday = gnc_numeric_zero();
employee->rate = gnc_numeric_zero();
employee->active = TRUE;
employee->balance = NULL;
if (gs_address_event_handler_id == 0)
{
gs_address_event_handler_id = qof_event_register_handler(listen_for_address_events, NULL);
}
if (empl_qof_event_handler_id == 0)
empl_qof_event_handler_id = qof_event_register_handler (empl_handle_qof_events, NULL);
qof_event_gen (&employee->inst, QOF_EVENT_CREATE, NULL);
@ -469,6 +471,7 @@ static void gncEmployeeFree (GncEmployee *employee)
CACHE_REMOVE (employee->acl);
gncAddressBeginEdit (employee->addr);
gncAddressDestroy (employee->addr);
g_free (employee->balance);
/* qof_instance_release (&employee->inst); */
g_object_unref (employee);
@ -816,8 +819,11 @@ static const char * _gncEmployeePrintable (gpointer item)
}
/**
* Listens for MODIFY events from addresses. If the address belongs to an employee,
* mark the employee as dirty.
* Listen for qof events.
*
* - If the address of an employee has changed, mark the employee as dirty.
* - If a lot related to an employee has changed, clear the employee's
* cached balance as it likely has become invalid.
*
* @param entity Entity for the event
* @param event_type Event type
@ -825,27 +831,49 @@ static const char * _gncEmployeePrintable (gpointer item)
* @param event_data Event data passed with the event.
*/
static void
listen_for_address_events(QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data)
empl_handle_qof_events (QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data)
{
GncEmployee* empl;
if ((event_type & QOF_EVENT_MODIFY) == 0)
/* Handle address change events */
if ((GNC_IS_ADDRESS (entity) &&
(event_type & QOF_EVENT_MODIFY) != 0))
{
if (GNC_IS_EMPLOYEE (event_data))
{
GncEmployee* empl = GNC_EMPLOYEE (event_data);
gncEmployeeBeginEdit (empl);
mark_employee (empl);
gncEmployeeCommitEdit (empl);
}
return;
}
if (!GNC_IS_ADDRESS(entity))
/* Handle lot change events */
if (GNC_IS_LOT (entity))
{
GNCLot *lot = GNC_LOT (entity);
GncOwner lot_owner;
const GncOwner *end_owner = NULL;
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
/* Determine the owner associated with the lot */
if (invoice)
/* Invoice lots */
end_owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice));
else if (gncOwnerGetOwnerFromLot (lot, &lot_owner))
/* Pre-payment lots */
end_owner = gncOwnerGetEndOwner (&lot_owner);
if (gncOwnerGetType (end_owner) == GNC_OWNER_EMPLOYEE)
{
/* Clear the cached balance */
GncEmployee* empl = gncOwnerGetEmployee (end_owner);
g_free (empl->balance);
empl->balance = NULL;
}
return;
}
if (!GNC_IS_EMPLOYEE(event_data))
{
return;
}
empl = GNC_EMPLOYEE(event_data);
gncEmployeeBeginEdit(empl);
mark_employee(empl);
gncEmployeeCommitEdit(empl);
}
static void
@ -925,3 +953,24 @@ gchar *gncEmployeeNextID (QofBook *book)
{
return qof_book_increment_and_format_counter (book, _GNC_MOD_NAME);
}
const gnc_numeric*
gncEmployeeGetCachedBalance (GncEmployee *empl)
{
return empl->balance;
}
void gncEmployeeSetCachedBalance (GncEmployee *empl, const gnc_numeric *new_bal)
{
if (!new_bal && empl->balance)
{
g_free (empl->balance);
empl->balance = NULL;
return;
}
if (!empl->balance)
empl->balance = g_new0 (gnc_numeric, 1);
*empl->balance = *new_bal;
}

View File

@ -33,6 +33,8 @@
gboolean gncEmployeeRegister (void);
gchar *gncEmployeeNextID (QofBook *book);
const gnc_numeric *gncEmployeeGetCachedBalance (GncEmployee *vend);
void gncEmployeeSetCachedBalance (GncEmployee *vend, const gnc_numeric *new_bal);
#define gncEmployeeSetGUID(E,G) qof_instance_set_guid(QOF_INSTANCE(E),(G))

View File

@ -1483,6 +1483,7 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc,
/* Create a new lot for this invoice */
lot = gnc_lot_new (book);
gncInvoiceAttachToLot (invoice, lot);
gnc_lot_begin_edit (lot);
type = gncInvoiceGetTypeString (invoice);
@ -1697,8 +1698,7 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc,
gnc_lot_add_split (lot, split);
}
/* Now attach this invoice to the txn, lot, and account */
gncInvoiceAttachToLot (invoice, lot);
/* Now attach this invoice to the txn and account */
gncInvoiceAttachToTxn (invoice, txn);
gncInvoiceSetPostedAcc (invoice, acc);

View File

@ -1453,50 +1453,61 @@ gncOwnerGetBalanceInCurrency (const GncOwner *owner,
const gnc_commodity *report_currency)
{
gnc_numeric balance = gnc_numeric_zero ();
GList *acct_list, *acct_node, *acct_types, *lot_list = NULL, *lot_node;
QofBook *book;
gnc_commodity *owner_currency;
GNCPriceDB *pdb;
const gnc_numeric *cached_balance = NULL;
g_return_val_if_fail (owner, gnc_numeric_zero ());
/* Get account list */
book = qof_instance_get_book (qofOwnerGetOwner (owner));
acct_list = gnc_account_get_descendants (gnc_book_get_root_account (book));
acct_types = gncOwnerGetAccountTypesList (owner);
owner_currency = gncOwnerGetCurrency (owner);
/* For each account */
for (acct_node = acct_list; acct_node; acct_node = acct_node->next)
cached_balance = gncOwnerGetCachedBalance (owner);
if (cached_balance)
balance = *cached_balance;
else
{
Account *account = acct_node->data;
/* No valid cache value found for balance. Let's recalculate */
GList *acct_list = gnc_account_get_descendants (gnc_book_get_root_account (book));
GList *acct_types = gncOwnerGetAccountTypesList (owner);
GList *acct_node;
/* Check if this account can have lots for the owner, otherwise skip to next */
if (g_list_index (acct_types, (gpointer)xaccAccountGetType (account))
== -1)
continue;
if (!gnc_commodity_equal (owner_currency, xaccAccountGetCommodity (account)))
continue;
/* Get a list of open lots for this owner and account */
lot_list = xaccAccountFindOpenLots (account, gncOwnerLotMatchOwnerFunc,
(gpointer)owner, NULL);
/* For each lot */
for (lot_node = lot_list; lot_node; lot_node = lot_node->next)
/* For each account */
for (acct_node = acct_list; acct_node; acct_node = acct_node->next)
{
GNCLot *lot = lot_node->data;
gnc_numeric lot_balance = gnc_lot_get_balance (lot);
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot(lot);
if (invoice)
balance = gnc_numeric_add (balance, lot_balance,
gnc_commodity_get_fraction (owner_currency), GNC_HOW_RND_ROUND_HALF_UP);
Account *account = acct_node->data;
GList *lot_list = NULL, *lot_node;
/* Check if this account can have lots for the owner, otherwise skip to next */
if (g_list_index (acct_types, (gpointer)xaccAccountGetType (account))
== -1)
continue;
if (!gnc_commodity_equal (owner_currency, xaccAccountGetCommodity (account)))
continue;
/* Get a list of open lots for this owner and account */
lot_list = xaccAccountFindOpenLots (account, gncOwnerLotMatchOwnerFunc,
(gpointer)owner, NULL);
/* For each lot */
for (lot_node = lot_list; lot_node; lot_node = lot_node->next)
{
GNCLot *lot = lot_node->data;
gnc_numeric lot_balance = gnc_lot_get_balance (lot);
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot(lot);
if (invoice)
balance = gnc_numeric_add (balance, lot_balance,
gnc_commodity_get_fraction (owner_currency), GNC_HOW_RND_ROUND_HALF_UP);
}
g_list_free (lot_list);
}
g_list_free (lot_list);
g_list_free (acct_list);
g_list_free (acct_types);
gncOwnerSetCachedBalance (owner, &balance);
}
g_list_free (acct_list);
g_list_free (acct_types);
pdb = gnc_pricedb_get_db (book);
@ -1587,3 +1598,30 @@ gboolean gncOwnerRegister (void)
return TRUE;
}
const gnc_numeric*
gncOwnerGetCachedBalance (const GncOwner *owner)
{
if (!owner) return NULL;
if (gncOwnerGetType (owner) == GNC_OWNER_CUSTOMER)
return gncCustomerGetCachedBalance (gncOwnerGetCustomer (owner));
else if (gncOwnerGetType (owner) == GNC_OWNER_VENDOR)
return gncVendorGetCachedBalance (gncOwnerGetVendor (owner));
else if (gncOwnerGetType (owner) == GNC_OWNER_EMPLOYEE)
return gncEmployeeGetCachedBalance (gncOwnerGetEmployee (owner));
return NULL;
}
void gncOwnerSetCachedBalance (const GncOwner *owner, const gnc_numeric *new_bal)
{
if (!owner) return;
if (gncOwnerGetType (owner) == GNC_OWNER_CUSTOMER)
gncCustomerSetCachedBalance (gncOwnerGetCustomer (owner), new_bal);
else if (gncOwnerGetType (owner) == GNC_OWNER_VENDOR)
gncVendorSetCachedBalance (gncOwnerGetVendor (owner), new_bal);
else if (gncOwnerGetType (owner) == GNC_OWNER_EMPLOYEE)
gncEmployeeSetCachedBalance (gncOwnerGetEmployee (owner), new_bal);
}

View File

@ -31,6 +31,8 @@
#include "gncOwner.h"
gboolean gncOwnerRegister (void);
const gnc_numeric *gncOwnerGetCachedBalance (const GncOwner *owner);
void gncOwnerSetCachedBalance (const GncOwner *owner, const gnc_numeric *new_bal);
#endif /* GNC_OWNERP_H_ */

View File

@ -41,9 +41,9 @@
#include "gncVendor.h"
#include "gncVendorP.h"
static gint gs_address_event_handler_id = 0;
static void listen_for_address_events(QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data);
static gint vend_qof_event_handler_id = 0;
static void vend_handle_qof_events (QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data);
static void qofVendorSetAddr (GncVendor *vendor, QofInstance *addr_ent);
static const char* qofVendorGetTaxIncluded(const GncVendor *vendor);
static void qofVendorSetTaxIncluded(GncVendor *vendor, const char* type_string);
@ -63,6 +63,7 @@ struct _gncVendor
GncTaxIncluded taxincluded;
gboolean active;
GList * jobs;
gnc_numeric * balance; /* cached vendor balance, will not be stored */
};
struct _gncVendorClass
@ -467,11 +468,10 @@ GncVendor *gncVendorCreate (QofBook *book)
vendor->taxincluded = GNC_TAXINCLUDED_USEGLOBAL;
vendor->active = TRUE;
vendor->jobs = NULL;
vendor->balance = NULL;
if (gs_address_event_handler_id == 0)
{
gs_address_event_handler_id = qof_event_register_handler(listen_for_address_events, NULL);
}
if (vend_qof_event_handler_id == 0)
vend_qof_event_handler_id = qof_event_register_handler (vend_handle_qof_events, NULL);
qof_event_gen (&vendor->inst, QOF_EVENT_CREATE, NULL);
@ -497,6 +497,7 @@ static void gncVendorFree (GncVendor *vendor)
gncAddressBeginEdit (vendor->addr);
gncAddressDestroy (vendor->addr);
g_list_free (vendor->jobs);
g_free (vendor->balance);
if (vendor->terms)
gncBillTermDecRef (vendor->terms);
@ -882,8 +883,11 @@ gncVendorIsDirty (const GncVendor *vendor)
}
/**
* Listens for MODIFY events from addresses. If the address belongs to a vendor,
* mark the vendor as dirty.
* Listen for qof events.
*
* - If the address of a vendor has changed, mark the vendor as dirty.
* - If a lot related to a vendor has changed, clear the vendor's
* cached balance as it likely has become invalid.
*
* @param entity Entity for the event
* @param event_type Event type
@ -891,28 +895,50 @@ gncVendorIsDirty (const GncVendor *vendor)
* @param event_data Event data passed with the event.
*/
static void
listen_for_address_events(QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data)
vend_handle_qof_events (QofInstance *entity, QofEventId event_type,
gpointer user_data, gpointer event_data)
{
GncVendor* v;
/* Handle address change events */
if ((GNC_IS_ADDRESS (entity) &&
(event_type & QOF_EVENT_MODIFY) != 0))
{
if (GNC_IS_VENDOR (event_data))
{
GncVendor* vend = GNC_VENDOR (event_data);
gncVendorBeginEdit (vend);
mark_vendor (vend);
gncVendorCommitEdit (vend);
}
return;
}
if ((event_type & QOF_EVENT_MODIFY) == 0)
/* Handle lot change events */
if (GNC_IS_LOT (entity))
{
GNCLot *lot = GNC_LOT (entity);
GncOwner lot_owner;
const GncOwner *end_owner = NULL;
GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
/* Determine the owner associated with the lot */
if (invoice)
/* Invoice lots */
end_owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice));
else if (gncOwnerGetOwnerFromLot (lot, &lot_owner))
/* Pre-payment lots */
end_owner = gncOwnerGetEndOwner (&lot_owner);
if (gncOwnerGetType (end_owner) == GNC_OWNER_VENDOR)
{
/* Clear the cached balance */
GncVendor* vend = gncOwnerGetVendor (end_owner);
g_free (vend->balance);
vend->balance = NULL;
}
return;
}
if (!GNC_IS_ADDRESS(entity))
{
return;
}
if (!GNC_IS_VENDOR(event_data))
{
return;
}
v = GNC_VENDOR(event_data);
gncVendorBeginEdit(v);
mark_vendor(v);
gncVendorCommitEdit(v);
}
/* ============================================================== */
/* Package-Private functions */
@ -1005,3 +1031,24 @@ gchar *gncVendorNextID (QofBook *book)
{
return qof_book_increment_and_format_counter (book, _GNC_MOD_NAME);
}
const gnc_numeric*
gncVendorGetCachedBalance (GncVendor *vend)
{
return vend->balance;
}
void gncVendorSetCachedBalance (GncVendor *vend, const gnc_numeric *new_bal)
{
if (!new_bal && vend->balance)
{
g_free (vend->balance);
vend->balance = NULL;
return;
}
if (!vend->balance)
vend->balance = g_new0 (gnc_numeric, 1);
*vend->balance = *new_bal;
}

View File

@ -32,6 +32,8 @@
gboolean gncVendorRegister (void);
gchar *gncVendorNextID (QofBook *book);
const gnc_numeric *gncVendorGetCachedBalance (GncVendor *vend);
void gncVendorSetCachedBalance (GncVendor *vend, const gnc_numeric *new_bal);
#define gncVendorSetGUID(V,G) qof_instance_set_guid(QOF_INSTANCE(V),(G))