mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-25 10:20:18 -06:00
[gnc-autoclear] Move autoclear algorithm into gnome-utils
This commit is contained in:
parent
0a39c37b22
commit
f813f7cd14
@ -47,6 +47,7 @@ set (gnome_utils_SOURCES
|
||||
dialog-utils.c
|
||||
gnc-account-sel.c
|
||||
gnc-amount-edit.c
|
||||
gnc-autoclear.c
|
||||
gnc-autosave.c
|
||||
gnc-cell-renderer-date.c
|
||||
gnc-cell-renderer-popup.c
|
||||
|
190
gnucash/gnome-utils/gnc-autoclear.c
Normal file
190
gnucash/gnome-utils/gnc-autoclear.c
Normal file
@ -0,0 +1,190 @@
|
||||
/********************************************************************
|
||||
* gnc-autoclear.c -- Knapsack algorithm functions *
|
||||
* *
|
||||
* Copyright 2020 Cristian Klein <cristian@kleinlabs.eu> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||
*******************************************************************/
|
||||
#include <config.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "Account.h"
|
||||
#include "Split.h"
|
||||
#include "gncOwner.h"
|
||||
#include "qof.h"
|
||||
#include "gnc-autoclear.h"
|
||||
|
||||
static QofLogModule log_module = GNC_MOD_GUI;
|
||||
|
||||
/* the following functions are used in window-autoclear: */
|
||||
|
||||
#define MAXIMUM_SACK_SIZE 1000000
|
||||
|
||||
static gboolean
|
||||
ght_gnc_numeric_equal(gconstpointer v1, gconstpointer v2)
|
||||
{
|
||||
gnc_numeric n1 = *(gnc_numeric *)v1, n2 = *(gnc_numeric *)v2;
|
||||
return gnc_numeric_equal(n1, n2);
|
||||
}
|
||||
|
||||
static guint
|
||||
ght_gnc_numeric_hash(gconstpointer v1)
|
||||
{
|
||||
gnc_numeric n1 = *(gnc_numeric *)v1;
|
||||
gdouble d1 = gnc_numeric_to_double(n1);
|
||||
return g_double_hash (&d1);
|
||||
}
|
||||
|
||||
typedef struct _sack_foreach_data_t
|
||||
{
|
||||
gnc_numeric split_value;
|
||||
GList *reachable_list;
|
||||
} *sack_foreach_data_t;
|
||||
|
||||
static void sack_foreach_func(gpointer key, gpointer value, gpointer user_data)
|
||||
{
|
||||
sack_foreach_data_t data = (sack_foreach_data_t) user_data;
|
||||
gnc_numeric thisvalue = *(gnc_numeric *) key;
|
||||
gnc_numeric reachable_value = gnc_numeric_add_fixed (thisvalue, data->split_value);
|
||||
gpointer new_value = g_malloc(sizeof(gnc_numeric));
|
||||
|
||||
memcpy(new_value, &reachable_value, sizeof(gnc_numeric));
|
||||
data->reachable_list = g_list_prepend(data->reachable_list, new_value);
|
||||
}
|
||||
|
||||
GList *
|
||||
gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_value,
|
||||
gchar **errmsg)
|
||||
{
|
||||
GList *nc_list = NULL, *toclear_list = NULL;
|
||||
GHashTable *sack;
|
||||
gchar *msg = NULL;
|
||||
guint sack_size = 0;
|
||||
|
||||
g_return_val_if_fail (GNC_IS_ACCOUNT (account), NULL);
|
||||
|
||||
sack = g_hash_table_new_full (ght_gnc_numeric_hash, ght_gnc_numeric_equal,
|
||||
g_free, NULL);
|
||||
|
||||
/* Extract which splits are not cleared and compute the amount we have to clear */
|
||||
for (GList *node = xaccAccountGetSplitList (account); node; node = node->next)
|
||||
{
|
||||
Split *split = (Split *)node->data;
|
||||
|
||||
if (xaccSplitGetReconcile (split) == NREC)
|
||||
nc_list = g_list_prepend (nc_list, split);
|
||||
else
|
||||
toclear_value = gnc_numeric_sub_fixed
|
||||
(toclear_value, xaccSplitGetAmount (split));
|
||||
}
|
||||
|
||||
if (gnc_numeric_zero_p (toclear_value))
|
||||
{
|
||||
msg = _("Account is already at Auto-Clear Balance.");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
|
||||
/* Run knapsack */
|
||||
/* Entries in the hash table are:
|
||||
* - key = amount to which we know how to clear (freed by GHashTable)
|
||||
* - value = last split we used to clear this amount (not managed by GHashTable)
|
||||
*/
|
||||
for (GList *node = nc_list; node; node = node->next)
|
||||
{
|
||||
Split *split = (Split *)node->data;
|
||||
gnc_numeric split_value = xaccSplitGetAmount (split);
|
||||
gpointer new_value = g_malloc(sizeof(gnc_numeric));
|
||||
|
||||
struct _sack_foreach_data_t s_data[1];
|
||||
s_data->split_value = split_value;
|
||||
s_data->reachable_list = NULL;
|
||||
|
||||
/* For each value in the sack, compute a new reachable value */
|
||||
g_hash_table_foreach (sack, sack_foreach_func, s_data);
|
||||
|
||||
/* Add the value of the split itself to the reachable_list */
|
||||
memcpy(new_value, &split_value, sizeof(gnc_numeric));
|
||||
s_data->reachable_list = g_list_prepend
|
||||
(s_data->reachable_list, new_value);
|
||||
|
||||
/* Add everything to the sack, looking out for duplicates */
|
||||
for (GList *s_node = s_data->reachable_list; s_node; s_node = s_node->next)
|
||||
{
|
||||
gnc_numeric *reachable_value = s_node->data;
|
||||
|
||||
/* Check if it already exists */
|
||||
if (g_hash_table_lookup_extended (sack, reachable_value, NULL, NULL))
|
||||
{
|
||||
/* If yes, we are in trouble, we reached an amount
|
||||
using two solutions */
|
||||
g_hash_table_insert (sack, reachable_value, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_hash_table_insert (sack, reachable_value, split);
|
||||
sack_size++;
|
||||
|
||||
if (sack_size > MAXIMUM_SACK_SIZE)
|
||||
{
|
||||
msg = _("Too many uncleared splits");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_list_free (s_data->reachable_list);
|
||||
}
|
||||
|
||||
/* Check solution */
|
||||
while (!gnc_numeric_zero_p (toclear_value))
|
||||
{
|
||||
Split *split = NULL;
|
||||
|
||||
if (!g_hash_table_lookup_extended (sack, &toclear_value,
|
||||
NULL, (gpointer) &split))
|
||||
{
|
||||
msg = _("The selected amount cannot be cleared.");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
|
||||
if (!split)
|
||||
{
|
||||
msg = _("Cannot uniquely clear splits. Found multiple possibilities.");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
|
||||
toclear_list = g_list_prepend (toclear_list, split);
|
||||
toclear_value = gnc_numeric_sub_fixed (toclear_value,
|
||||
xaccSplitGetAmount (split));
|
||||
}
|
||||
|
||||
skip_knapsack:
|
||||
g_hash_table_destroy (sack);
|
||||
g_list_free (nc_list);
|
||||
|
||||
if (msg)
|
||||
{
|
||||
*errmsg = g_strdup (msg);
|
||||
g_list_free (toclear_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*errmsg = NULL;
|
||||
return toclear_list;
|
||||
}
|
39
gnucash/gnome-utils/gnc-autoclear.h
Normal file
39
gnucash/gnome-utils/gnc-autoclear.h
Normal file
@ -0,0 +1,39 @@
|
||||
/********************************************************************
|
||||
* gnc-autoclear.h -- Knapsack algorithm functions
|
||||
*
|
||||
* Copyright 2020 Cristian Klein <cristian@kleinlabs.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, contact:
|
||||
*
|
||||
* Free Software Foundation Voice: +1-617-542-5942
|
||||
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
|
||||
* Boston, MA 02111-1301, USA gnu@gnu.org
|
||||
*******************************************************************/
|
||||
|
||||
#ifndef GNC_AUTOCLEAR_H
|
||||
#define GNC_AUTOCLEAR_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <Account.h>
|
||||
|
||||
/** Account splits are analysed; attempts to find a unique combination
|
||||
* of uncleared splits which would set cleared balance to
|
||||
* toclear_value. If this is not possible, *errmsg will be error
|
||||
* message. errmsg must be a pointer to a gchar. If it is set, it
|
||||
* must be freed by the caller.
|
||||
*/
|
||||
GList * gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_value,
|
||||
gchar **errmsg);
|
||||
|
||||
#endif
|
@ -29,7 +29,29 @@ gnc_add_scheme_test_targets(scm-test-load-gnome-utils-module
|
||||
OUTPUT_DIR "tests"
|
||||
DEPENDS "${GUILE_DEPENDS}")
|
||||
|
||||
set(test_autoclear_SOURCES
|
||||
test-autoclear.cpp
|
||||
)
|
||||
|
||||
set(test_autoclear_INCLUDE_DIRS
|
||||
${CMAKE_BINARY_DIR}/common
|
||||
${CMAKE_SOURCE_DIR}/libgnucash/engine
|
||||
${GLIB2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(test_autoclear_LIBS
|
||||
gnc-engine
|
||||
gnc-gnome-utils
|
||||
gtest
|
||||
)
|
||||
|
||||
gnc_add_test(test-autoclear "${test_autoclear_SOURCES}"
|
||||
test_autoclear_INCLUDE_DIRS
|
||||
test_autoclear_LIBS
|
||||
)
|
||||
|
||||
gnc_add_scheme_tests(test-load-gnome-utils-module.scm)
|
||||
|
||||
|
||||
set_dist_list(test_gnome_utils_DIST CMakeLists.txt test-gnc-recurrence.c test-load-gnome-utils-module.scm)
|
||||
set_dist_list(test_gnome_utils_DIST CMakeLists.txt test-gnc-recurrence.c test-load-gnome-utils-module.scm
|
||||
${test_autoclear_SOURCES})
|
||||
|
@ -25,9 +25,10 @@
|
||||
#include <glib.h>
|
||||
// GoogleTest is written in C++, however, the function we test in C.
|
||||
extern "C" {
|
||||
#include "../gnc-ui-balances.h"
|
||||
#include "../gnc-autoclear.h"
|
||||
}
|
||||
#include <memory>
|
||||
#include <Account.h>
|
||||
#include <Split.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
@ -32,7 +32,7 @@
|
||||
#include "gnc-main-window.h"
|
||||
#include "gnc-plugin-page-register.h"
|
||||
#include "gnc-ui.h"
|
||||
#include "gnc-ui-balances.h"
|
||||
#include "gnc-autoclear.h"
|
||||
#include "window-autoclear.h"
|
||||
|
||||
#define WINDOW_AUTOCLEAR_CM_CLASS "window-autoclear"
|
||||
|
@ -341,159 +341,3 @@ gnc_ui_owner_get_print_report_balance (GncOwner *owner,
|
||||
return g_strdup (xaccPrintAmount (balance, print_info));
|
||||
}
|
||||
|
||||
|
||||
/* the following functions are used in window-autoclear: */
|
||||
|
||||
#define MAXIMUM_SACK_SIZE 1000000
|
||||
|
||||
static gboolean
|
||||
ght_gnc_numeric_equal(gconstpointer v1, gconstpointer v2)
|
||||
{
|
||||
gnc_numeric n1 = *(gnc_numeric *)v1, n2 = *(gnc_numeric *)v2;
|
||||
return gnc_numeric_equal(n1, n2);
|
||||
}
|
||||
|
||||
static guint
|
||||
ght_gnc_numeric_hash(gconstpointer v1)
|
||||
{
|
||||
gnc_numeric n1 = *(gnc_numeric *)v1;
|
||||
gdouble d1 = gnc_numeric_to_double(n1);
|
||||
return g_double_hash (&d1);
|
||||
}
|
||||
|
||||
typedef struct _sack_foreach_data_t
|
||||
{
|
||||
gnc_numeric split_value;
|
||||
GList *reachable_list;
|
||||
} *sack_foreach_data_t;
|
||||
|
||||
static void sack_foreach_func(gpointer key, gpointer value, gpointer user_data)
|
||||
{
|
||||
sack_foreach_data_t data = (sack_foreach_data_t) user_data;
|
||||
gnc_numeric thisvalue = *(gnc_numeric *) key;
|
||||
gnc_numeric reachable_value = gnc_numeric_add_fixed (thisvalue, data->split_value);
|
||||
gpointer new_value = g_malloc(sizeof(gnc_numeric));
|
||||
|
||||
memcpy(new_value, &reachable_value, sizeof(gnc_numeric));
|
||||
data->reachable_list = g_list_prepend(data->reachable_list, new_value);
|
||||
}
|
||||
|
||||
GList *
|
||||
gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_value,
|
||||
gchar **errmsg)
|
||||
{
|
||||
GList *nc_list = NULL, *toclear_list = NULL;
|
||||
GHashTable *sack;
|
||||
gchar *msg = NULL;
|
||||
guint sack_size = 0;
|
||||
|
||||
g_return_val_if_fail (GNC_IS_ACCOUNT (account), NULL);
|
||||
|
||||
sack = g_hash_table_new_full (ght_gnc_numeric_hash, ght_gnc_numeric_equal,
|
||||
g_free, NULL);
|
||||
|
||||
/* Extract which splits are not cleared and compute the amount we have to clear */
|
||||
for (GList *node = xaccAccountGetSplitList (account); node; node = node->next)
|
||||
{
|
||||
Split *split = (Split *)node->data;
|
||||
|
||||
if (xaccSplitGetReconcile (split) == NREC)
|
||||
nc_list = g_list_prepend (nc_list, split);
|
||||
else
|
||||
toclear_value = gnc_numeric_sub_fixed
|
||||
(toclear_value, xaccSplitGetAmount (split));
|
||||
}
|
||||
|
||||
if (gnc_numeric_zero_p (toclear_value))
|
||||
{
|
||||
msg = _("Account is already at Auto-Clear Balance.");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
|
||||
/* Run knapsack */
|
||||
/* Entries in the hash table are:
|
||||
* - key = amount to which we know how to clear (freed by GHashTable)
|
||||
* - value = last split we used to clear this amount (not managed by GHashTable)
|
||||
*/
|
||||
for (GList *node = nc_list; node; node = node->next)
|
||||
{
|
||||
Split *split = (Split *)node->data;
|
||||
gnc_numeric split_value = xaccSplitGetAmount (split);
|
||||
gpointer new_value = g_malloc(sizeof(gnc_numeric));
|
||||
|
||||
struct _sack_foreach_data_t s_data[1];
|
||||
s_data->split_value = split_value;
|
||||
s_data->reachable_list = NULL;
|
||||
|
||||
/* For each value in the sack, compute a new reachable value */
|
||||
g_hash_table_foreach (sack, sack_foreach_func, s_data);
|
||||
|
||||
/* Add the value of the split itself to the reachable_list */
|
||||
memcpy(new_value, &split_value, sizeof(gnc_numeric));
|
||||
s_data->reachable_list = g_list_prepend
|
||||
(s_data->reachable_list, new_value);
|
||||
|
||||
/* Add everything to the sack, looking out for duplicates */
|
||||
for (GList *s_node = s_data->reachable_list; s_node; s_node = s_node->next)
|
||||
{
|
||||
gnc_numeric *reachable_value = s_node->data;
|
||||
|
||||
/* Check if it already exists */
|
||||
if (g_hash_table_lookup_extended (sack, reachable_value, NULL, NULL))
|
||||
{
|
||||
/* If yes, we are in trouble, we reached an amount
|
||||
using two solutions */
|
||||
g_hash_table_insert (sack, reachable_value, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_hash_table_insert (sack, reachable_value, split);
|
||||
sack_size++;
|
||||
|
||||
if (sack_size > MAXIMUM_SACK_SIZE)
|
||||
{
|
||||
msg = _("Too many uncleared splits");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_list_free (s_data->reachable_list);
|
||||
}
|
||||
|
||||
/* Check solution */
|
||||
while (!gnc_numeric_zero_p (toclear_value))
|
||||
{
|
||||
Split *split = NULL;
|
||||
|
||||
if (!g_hash_table_lookup_extended (sack, &toclear_value,
|
||||
NULL, (gpointer) &split))
|
||||
{
|
||||
msg = _("The selected amount cannot be cleared.");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
|
||||
if (!split)
|
||||
{
|
||||
msg = _("Cannot uniquely clear splits. Found multiple possibilities.");
|
||||
goto skip_knapsack;
|
||||
}
|
||||
|
||||
toclear_list = g_list_prepend (toclear_list, split);
|
||||
toclear_value = gnc_numeric_sub_fixed (toclear_value,
|
||||
xaccSplitGetAmount (split));
|
||||
}
|
||||
|
||||
skip_knapsack:
|
||||
g_hash_table_destroy (sack);
|
||||
g_list_free (nc_list);
|
||||
|
||||
if (msg)
|
||||
{
|
||||
*errmsg = g_strdup (msg);
|
||||
g_list_free (toclear_list);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*errmsg = NULL;
|
||||
return toclear_list;
|
||||
}
|
||||
|
@ -71,23 +71,6 @@ endif()
|
||||
# Doesn't work yet:
|
||||
gnc_add_test_with_guile(test-app-utils "${test_app_utils_SOURCES}" APP_UTILS_TEST_INCLUDE_DIRS APP_UTILS_TEST_LIBS)
|
||||
|
||||
set(test_autoclear_SOURCES
|
||||
test-autoclear.cpp
|
||||
)
|
||||
set(test_autoclear_INCLUDE_DIRS
|
||||
${APP_UTILS_TEST_INCLUDE_DIRS}
|
||||
${GTEST_INCLUDE_DIR}
|
||||
)
|
||||
set(test_autoclear_LIBS
|
||||
${APP_UTILS_TEST_LIBS}
|
||||
gtest
|
||||
)
|
||||
|
||||
gnc_add_test(test-autoclear "${test_autoclear_SOURCES}"
|
||||
test_autoclear_INCLUDE_DIRS
|
||||
test_autoclear_LIBS
|
||||
)
|
||||
|
||||
set_dist_list(test_app_utils_DIST
|
||||
CMakeLists.txt
|
||||
test-exp-parser.c
|
||||
@ -100,5 +83,4 @@ set_dist_list(test_app_utils_DIST
|
||||
test-options.scm
|
||||
${test_app_utils_scheme_SOURCES}
|
||||
${test_app_utils_SOURCES}
|
||||
${test_autoclear_SOURCES}
|
||||
)
|
||||
|
@ -147,6 +147,7 @@ gnucash/gnome-utils/dialog-userpass.c
|
||||
gnucash/gnome-utils/dialog-utils.c
|
||||
gnucash/gnome-utils/gnc-account-sel.c
|
||||
gnucash/gnome-utils/gnc-amount-edit.c
|
||||
gnucash/gnome-utils/gnc-autoclear.c
|
||||
gnucash/gnome-utils/gnc-autosave.c
|
||||
gnucash/gnome-utils/gnc-cell-renderer-date.c
|
||||
gnucash/gnome-utils/gnc-cell-renderer-popup.c
|
||||
|
Loading…
Reference in New Issue
Block a user