GnuCash  5.6-150-g038405b370+
gnc-autoclear.c
1 /********************************************************************
2  * gnc-autoclear.c -- Knapsack algorithm functions *
3  * *
4  * Copyright 2020 Cristian Klein <cristian@kleinlabs.eu> *
5  * Modified 2021 Christopher Lam to clear same-amount splits *
6  * *
7  * This program is free software; you can redistribute it and/or *
8  * modify it under the terms of the GNU General Public License as *
9  * published by the Free Software Foundation; either version 2 of *
10  * the License, or (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License*
18  * along with this program; if not, contact: *
19  * *
20  * Free Software Foundation Voice: +1-617-542-5942 *
21  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
22  * Boston, MA 02110-1301, USA gnu@gnu.org *
23  *******************************************************************/
24 #include <config.h>
25 
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
28 
29 #include "Account.h"
30 #include "Split.h"
31 #include "Transaction.h"
32 #include "gncOwner.h"
33 #include "qof.h"
34 #include "gnc-autoclear.h"
35 
36 /* the following functions are used in window-autoclear: */
37 
38 typedef enum
39 {
40  AUTOCLEAR_OVERLOAD = 1,
41  AUTOCLEAR_UNABLE,
42  AUTOCLEAR_MULTIPLE,
43  AUTOCLEAR_NOP,
44 } autoclear_error_type;
45 
46 #define MAXIMUM_SACK_SIZE 1000000
47 
48 #define log_module "autoclear"
49 
50 typedef struct
51 {
52  GList *worklist;
53  GHashTable *sack;
54  Split *split;
55 } sack_data;
56 
57 typedef struct
58 {
59  gnc_numeric reachable_amount;
60  GList *list_of_splits;
61 } WorkItem;
62 
63 static GList *DUP_LIST;
64 
65 static WorkItem *
66 make_workitem (GHashTable *hash, gnc_numeric amount,
67  Split *split, GList *splits)
68 {
69  WorkItem *item = g_new0 (WorkItem, 1);
70  item->reachable_amount = amount;
71  if (g_hash_table_lookup (hash, GINT_TO_POINTER (amount.num)) || splits == DUP_LIST)
72  item->list_of_splits = DUP_LIST;
73  else
74  item->list_of_splits = g_list_prepend (g_list_copy (splits), split);
75  return item;
76 }
77 
78 static void
79 sack_foreach_func (gint thisvalue_num, GList *splits, sack_data *data)
80 {
81  gnc_numeric itemval = xaccSplitGetAmount (data->split);
82  gnc_numeric this_value = gnc_numeric_create (thisvalue_num, itemval.denom);
83  gnc_numeric new_value = gnc_numeric_add_fixed (this_value, itemval);
84  WorkItem *item = make_workitem (data->sack, new_value, data->split, splits);
85 
86  data->worklist = g_list_prepend (data->worklist, item);
87 }
88 
89 static void
90 sack_free (gpointer thisvalue_num, GList *splits, sack_data *data)
91 {
92  if (splits != DUP_LIST)
93  g_list_free (splits);
94 }
95 
96 static void
97 process_work (WorkItem *item, GHashTable *sack)
98 {
99  GList *existing = g_hash_table_lookup (sack, GINT_TO_POINTER(item->reachable_amount.num));
100  if (existing && existing != DUP_LIST)
101  {
102  DEBUG ("removing existing for %6.2f\n",
103  gnc_numeric_to_double (item->reachable_amount));
104  g_list_free (existing);
105  }
106  g_hash_table_insert (sack, GINT_TO_POINTER(item->reachable_amount.num), item->list_of_splits);
107 }
108 
109 gboolean
110 gnc_autoclear_get_splits (Account *account, gnc_numeric toclear_value,
111  time64 end_date,
112  GList **splits, GError **error, GtkLabel *label)
113 {
114  GList *nc_list = NULL, *toclear_list = NULL;
115  GHashTable *sack;
116  GQuark autoclear_quark = g_quark_from_static_string ("autoclear");
117 
118  g_return_val_if_fail (GNC_IS_ACCOUNT (account), FALSE);
119  g_return_val_if_fail (splits != NULL, FALSE);
120 
121  sack = g_hash_table_new (NULL, NULL);
122  DUP_LIST = g_list_prepend (NULL, NULL);
123 
124  /* Extract which splits are not cleared and compute the amount we have to clear */
125  GList *acc_splits = xaccAccountGetSplitList (account);
126  for (GList *node = acc_splits; node; node = node->next)
127  {
128  Split *split = (Split *)node->data;
129  gnc_numeric amount = xaccSplitGetAmount (split);
130 
131  if (amount.denom != toclear_value.denom)
132  {
133  g_set_error (error, autoclear_quark, AUTOCLEAR_NOP, "Split amount and toclear amount have different denoms");
134  goto skip_knapsack;
135  }
136  if (xaccSplitGetReconcile (split) != NREC)
137  toclear_value = gnc_numeric_sub_fixed (toclear_value, amount);
138  else if (gnc_numeric_zero_p (amount))
139  DEBUG ("skipping zero-amount split %p", split);
140  else if (end_date != INT64_MAX &&
141  xaccTransGetDate (xaccSplitGetParent (split)) > end_date)
142  DEBUG ("skipping split after statement_date %p", split);
143  else
144  nc_list = g_list_prepend (nc_list, split);
145  }
146  g_list_free (acc_splits);
147 
148  if (gnc_numeric_zero_p (toclear_value))
149  {
150  g_set_error (error, autoclear_quark, AUTOCLEAR_NOP,
151  _("Account is already at Auto-Clear Balance."));
152  goto skip_knapsack;
153  }
154  else if (!nc_list)
155  {
156  g_set_error (error, autoclear_quark, AUTOCLEAR_NOP,
157  _("No uncleared splits found."));
158  goto skip_knapsack;
159  }
160 
161  for (GList *node = nc_list; node; node = node->next)
162  {
163  Split *split = (Split *)node->data;
164  WorkItem *item = make_workitem (sack, xaccSplitGetAmount (split), split, NULL);
165  sack_data s_data = { g_list_prepend (NULL, item), sack, split };
166 
167  g_hash_table_foreach (sack, (GHFunc) sack_foreach_func, &s_data);
168  g_list_foreach (s_data.worklist, (GFunc) process_work, sack);
169  g_list_free_full (s_data.worklist, g_free);
170  if (g_hash_table_size (sack) > MAXIMUM_SACK_SIZE)
171  {
172  g_set_error (error, autoclear_quark, AUTOCLEAR_OVERLOAD,
173  _("Too many uncleared splits"));
174  goto skip_knapsack;
175  }
176  else if (g_hash_table_lookup (sack, GINT_TO_POINTER(toclear_value.num)) == DUP_LIST)
177  {
178  g_set_error (error, autoclear_quark, AUTOCLEAR_MULTIPLE,
179  _("Cannot uniquely clear splits. Found multiple possibilities."));
180  goto skip_knapsack;
181  }
182  }
183 
184  toclear_list = g_hash_table_lookup (sack, GINT_TO_POINTER(toclear_value.num));
185 
186  /* Check solution */
187  if (!toclear_list)
188  {
189  g_set_error (error, autoclear_quark, AUTOCLEAR_UNABLE,
190  _("The selected amount cannot be cleared."));
191  goto skip_knapsack;
192  }
193  else
194  /* copy GList because GHashTable value will be freed */
195  *splits = g_list_copy (toclear_list);
196 
197  skip_knapsack:
198  g_hash_table_foreach (sack, (GHFunc) sack_free, NULL);
199  g_hash_table_destroy (sack);
200  g_list_free (nc_list);
201  g_list_free (DUP_LIST);
202 
203  return (toclear_list != NULL);
204 }
205 
206 
207 GList *
208 gnc_account_get_autoclear_splits (Account *account, gnc_numeric toclear_value,
209  gchar **errmsg)
210 {
211  GError *error = NULL;
212  GList *splits = NULL;
213 
214  gnc_autoclear_get_splits (account, toclear_value, INT64_MAX,
215  &splits, &error, NULL);
216 
217  if (error)
218  {
219  *errmsg = g_strdup (error->message);
220  g_error_free (error);
221  return NULL;
222  }
223 
224  *errmsg = NULL;
225  return splits;
226 }
Business Interface: Object OWNERs.
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
SplitList * xaccAccountGetSplitList(const Account *acc)
The xaccAccountGetSplitList() routine returns a pointer to a GList of the splits in the account...
Definition: Account.cpp:3905
STRUCTS.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
char xaccSplitGetReconcile(const Split *split)
Returns the value of the reconcile flag.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
API for Transactions and Splits (journal entries)
gdouble gnc_numeric_to_double(gnc_numeric n)
Convert numeric to floating-point value.
Account handling public routines.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
API for Transactions and Splits (journal entries)
#define NREC
not reconciled or cleared
Definition: Split.h:76
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account&#39;s commodity.
Definition: gmock-Split.cpp:69