1 /*
2  * gnc-sx-instance-model.c
3  *
4  * Copyright (C) 2006 Josh Sled <jsled@asynchronous.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of version 2 and/or version 3 of the GNU General Public
8  * License as published by the Free Software Foundation.
9  *
10  * As a special exception, permission is granted to link the binary module
11  * resultant from this code with the OpenSSL project's "OpenSSL" library (or
12  * modified versions of it that use the same license as the "OpenSSL"
13  * library), and distribute the linked executable.  You must obey the GNU
14  * General Public License in all respects for all of the code used other than
15  * "OpenSSL". If you modify this file, you may extend this exception to your
16  * version of the file, but you are not obligated to do so. If you do not
17  * wish to do so, delete this exception statement from your version of this
18  * file.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, contact:
27  *
28  * Free Software Foundation           Voice:  +1-617-542-5942
29  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
30  * Boston, MA  02110-1301,  USA       gnu@gnu.org
31  */
32 
33 #include <config.h>
34 #include <glib.h>
35 #include <glib/gi18n.h>
36 #include <glib-object.h>
37 #include <stdlib.h>
38 
39 #include "Account.h"
40 #include "SX-book.h"
41 #include "SchedXaction.h"
42 #include "Scrub.h"
43 #include "Split.h"
44 #include "Transaction.h"
45 #include "gnc-commodity.h"
46 #include "gnc-date.h"
47 #include "gnc-event.h"
48 #include "gnc-exp-parser.h"
49 #include "gnc-glib-utils.h"
50 #include "gnc-sx-instance-model.h"
51 #include "gnc-ui-util.h"
52 #include "qof.h"
53 
54 #undef G_LOG_DOMAIN
55 #define G_LOG_DOMAIN "gnc.app-utils.sx"
56 static QofLogModule log_module = G_LOG_DOMAIN;
57 
58 /** Report errors bilingual:
59  *  in g_critical untranslated and
60  *  in g_list_append translated.
61  */
62 #define REPORT_ERROR(list, format, ...) do { \
63     g_critical(format, __VA_ARGS__); \
64     if (list != NULL) \
65         *list = g_list_append(*list, g_strdup_printf(_(format), __VA_ARGS__)); \
66 } while (0)
67 
68 static GObjectClass *parent_class = NULL;
69 
70 typedef struct _SxTxnCreationData
71 {
72     GncSxInstance *instance;
73     GList **created_txn_guids;
74     GList **creation_errors;
75 } SxTxnCreationData;
76 
77 static void gnc_sx_instance_model_class_init (GncSxInstanceModelClass *klass);
78 static void gnc_sx_instance_model_init(GTypeInstance *instance, gpointer klass);
79 static GncSxInstanceModel* gnc_sx_instance_model_new(void);
80 
81 static GncSxInstance* gnc_sx_instance_new(GncSxInstances *parent, GncSxInstanceState state, GDate *date, void *temporal_state, gint sequence_num);
82 
83 static gint _get_vars_helper(Transaction *txn, void *var_hash_data);
84 
85 static GncSxVariable* gnc_sx_variable_new(gchar *name);
86 
87 static void _gnc_sx_instance_event_handler(QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data);
88 static gnc_commodity* get_transaction_currency(SxTxnCreationData *creation_data, SchedXaction *sx, Transaction *template_txn);
89 /* ------------------------------------------------------------ */
90 
91 static gboolean
scrub_sx_split_numeric(Split * split,const char * debcred)92 scrub_sx_split_numeric (Split* split, const char *debcred)
93 {
94     const gboolean is_credit = g_strcmp0 (debcred, "credit") == 0;
95     const char *formula = is_credit ?
96         "sx-credit-formula" : "sx-debit-formula";
97     const char *numeric = is_credit ?
98         "sx-credit-numeric" : "sx-debit-numeric";
99     char *formval;
100     gnc_numeric *numval = NULL;
101     GHashTable *parser_vars = g_hash_table_new (g_str_hash, g_str_equal);
102     char *error_loc;
103     gnc_numeric amount = gnc_numeric_zero ();
104     gboolean parse_result = FALSE;
105     gboolean num_val_changed = FALSE;
106     qof_instance_get (QOF_INSTANCE (split),
107 		  formula, &formval,
108 		  numeric, &numval,
109 		  NULL);
110     parse_result =
111         gnc_exp_parser_parse_separate_vars (formval, &amount,
112                                             &error_loc, parser_vars);
113     if (!parse_result || g_hash_table_size (parser_vars) != 0)
114         amount = gnc_numeric_zero ();
115     g_hash_table_unref (parser_vars);
116     if (!numval || !gnc_numeric_eq (amount, *numval))
117     {
118         qof_instance_set (QOF_INSTANCE (split),
119                           numeric, &amount,
120                           NULL);
121         num_val_changed = TRUE;
122     }
123     g_free (numval);
124     return num_val_changed;
125 }
126 
127 /* Fixes error in pre-2.6.16 where the numeric slot wouldn't get changed if the
128  * formula slot was edited.
129  */
130 void
gnc_sx_scrub_split_numerics(gpointer psplit,gpointer puser)131 gnc_sx_scrub_split_numerics (gpointer psplit, gpointer puser)
132 {
133     Split *split = GNC_SPLIT (psplit);
134     Transaction *trans = xaccSplitGetParent (split);
135     gboolean changed;
136     xaccTransBeginEdit (trans);
137     changed = scrub_sx_split_numeric (split, "credit") +
138         scrub_sx_split_numeric (split, "debit");
139     if (!changed)
140         xaccTransRollbackEdit (trans);
141     else
142         xaccTransCommitEdit (trans);
143 }
144 
145 static void
_sx_var_to_raw_numeric(gchar * name,GncSxVariable * var,GHashTable * parser_var_hash)146 _sx_var_to_raw_numeric(gchar *name, GncSxVariable *var, GHashTable *parser_var_hash)
147 {
148     g_hash_table_insert(parser_var_hash, g_strdup(name), &var->value);
149 }
150 
151 static void
_var_numeric_to_sx_var(gchar * name,gnc_numeric * num,GHashTable * sx_var_hash)152 _var_numeric_to_sx_var(gchar *name, gnc_numeric *num, GHashTable *sx_var_hash)
153 {
154     gpointer p_var;
155     if (!g_hash_table_lookup_extended(sx_var_hash, name, NULL, &p_var))
156     {
157         p_var = (gpointer)gnc_sx_variable_new(name);
158         g_hash_table_insert(sx_var_hash, g_strdup(name), p_var);
159     }
160     ((GncSxVariable*)p_var)->value = *num;
161 }
162 
163 static void
_wipe_parsed_sx_var(gchar * key,GncSxVariable * var,gpointer unused_user_data)164 _wipe_parsed_sx_var(gchar *key, GncSxVariable *var, gpointer unused_user_data)
165 {
166     var->value = gnc_numeric_error(GNC_ERROR_ARG);
167 }
168 
169 static gboolean
split_is_marker(Split * split)170 split_is_marker(Split *split)
171 {
172     gchar *credit_formula = NULL;
173     gchar *debit_formula = NULL;
174     gboolean split_is_marker = TRUE;
175 
176     qof_instance_get (QOF_INSTANCE (split),
177                       "sx-credit-formula", &credit_formula,
178                       "sx-debit-formula", &debit_formula,
179                       NULL);
180 
181     if ((credit_formula && *credit_formula) ||
182         (debit_formula && *debit_formula))
183         split_is_marker = FALSE;
184 
185     g_free(credit_formula);
186     g_free(debit_formula);
187     return split_is_marker;
188 }
189 
190 /**
191  * @return caller-owned.
192  **/
193 GHashTable*
gnc_sx_instance_get_variables_for_parser(GHashTable * instance_var_hash)194 gnc_sx_instance_get_variables_for_parser(GHashTable *instance_var_hash)
195 {
196     GHashTable *parser_vars;
197     parser_vars = g_hash_table_new(g_str_hash, g_str_equal);
198     g_hash_table_foreach(instance_var_hash, (GHFunc)_sx_var_to_raw_numeric, parser_vars);
199     return parser_vars;
200 }
201 
202 int
gnc_sx_parse_vars_from_formula(const char * formula,GHashTable * var_hash,gnc_numeric * result)203 gnc_sx_parse_vars_from_formula(const char *formula,
204                                GHashTable *var_hash,
205                                gnc_numeric *result)
206 {
207     gnc_numeric num;
208     char *errLoc = NULL;
209     int toRet = 0;
210     GHashTable *parser_vars;
211 
212     // convert var_hash -> variables for the parser.
213     parser_vars = gnc_sx_instance_get_variables_for_parser(var_hash);
214 
215     num = gnc_numeric_zero();
216     if (!gnc_exp_parser_parse_separate_vars(formula, &num, &errLoc, parser_vars))
217     {
218         toRet = -1;
219     }
220 
221     // convert back.
222     g_hash_table_foreach(parser_vars, (GHFunc)_var_numeric_to_sx_var, var_hash);
223     g_hash_table_destroy(parser_vars);
224 
225     if (result != NULL)
226     {
227         *result = num;
228     }
229 
230     return toRet;
231 }
232 
233 static GncSxVariable*
gnc_sx_variable_new(gchar * name)234 gnc_sx_variable_new(gchar *name)
235 {
236     GncSxVariable *var = g_new0(GncSxVariable, 1);
237     var->name = g_strdup(name);
238     var->value = gnc_numeric_error(GNC_ERROR_ARG);
239     var->editable = TRUE;
240     return var;
241 }
242 
243 GncSxVariable*
gnc_sx_variable_new_full(gchar * name,gnc_numeric value,gboolean editable)244 gnc_sx_variable_new_full(gchar *name, gnc_numeric value, gboolean editable)
245 {
246     GncSxVariable *var = gnc_sx_variable_new(name);
247     var->value = value;
248     var->editable = editable;
249     return var;
250 }
251 
252 static GncSxVariable*
gnc_sx_variable_new_copy(GncSxVariable * to_copy)253 gnc_sx_variable_new_copy(GncSxVariable *to_copy)
254 {
255     GncSxVariable *var = gnc_sx_variable_new(to_copy->name);
256     var->value = to_copy->value;
257     var->editable = to_copy->editable;
258     return var;
259 }
260 
261 void
gnc_sx_variable_free(GncSxVariable * var)262 gnc_sx_variable_free(GncSxVariable *var)
263 {
264     g_free(var->name);
265     g_free(var);
266 }
267 
268 static inline gchar*
var_name_from_commodities(gnc_commodity * split_c,gnc_commodity * txn_c)269 var_name_from_commodities(gnc_commodity* split_c, gnc_commodity* txn_c)
270 {
271     const gchar* split_m = gnc_commodity_get_mnemonic(split_c);
272     const gchar* txn_m = gnc_commodity_get_mnemonic(txn_c);
273     gchar* var_name = g_strdup_printf ("%s -> %s",
274                                        split_m ? split_m : "(null)",
275                                        txn_m ? txn_m : "(null)");
276 
277     DEBUG("var_name is %s", var_name);
278     return var_name;
279 }
280 
281 static gint
_get_vars_helper(Transaction * txn,void * var_hash_data)282 _get_vars_helper(Transaction *txn, void *var_hash_data)
283 {
284     GHashTable *var_hash = (GHashTable*)var_hash_data;
285     GList *split_list;
286     Split *s;
287     gchar *credit_formula = NULL;
288     gchar *debit_formula = NULL;
289     gnc_commodity *txn_cmdty = get_transaction_currency(NULL, NULL, txn);
290 
291     split_list = xaccTransGetSplitList(txn);
292     if (split_list == NULL)
293     {
294         return 1;
295     }
296 
297     for ( ; split_list; split_list = split_list->next)
298     {
299         gnc_commodity *split_cmdty = NULL;
300         GncGUID *acct_guid = NULL;
301         Account *acct;
302         gboolean split_is_marker = TRUE;
303 
304         s = (Split*)split_list->data;
305 
306         qof_instance_get (QOF_INSTANCE (s),
307 			  "sx-account", &acct_guid,
308 			  "sx-credit-formula", &credit_formula,
309 			  "sx-debit-formula", &debit_formula,
310 			  NULL);
311         acct = xaccAccountLookup(acct_guid, gnc_get_current_book());
312         guid_free (acct_guid);
313         split_cmdty = xaccAccountGetCommodity(acct);
314         // existing... ------------------------------------------
315 	if (credit_formula && strlen(credit_formula) != 0)
316 	{
317 	    gnc_sx_parse_vars_from_formula(credit_formula, var_hash, NULL);
318 	    split_is_marker = FALSE;
319 	}
320 	if (debit_formula && strlen(debit_formula) != 0)
321 	{
322 	    gnc_sx_parse_vars_from_formula(debit_formula, var_hash, NULL);
323 	    split_is_marker = FALSE;
324 	}
325 	g_free (credit_formula);
326 	g_free (debit_formula);
327 
328         if (split_is_marker)
329             continue;
330 
331         if (! gnc_commodity_equal(split_cmdty, txn_cmdty))
332         {
333             GncSxVariable *var;
334             gchar *var_name;
335 
336             var_name = var_name_from_commodities(split_cmdty, txn_cmdty);
337             var = gnc_sx_variable_new(var_name);
338             g_hash_table_insert(var_hash, g_strdup(var->name), var);
339         }
340     }
341 
342     return 0;
343 }
344 
345 Account*
gnc_sx_get_template_transaction_account(const SchedXaction * sx)346 gnc_sx_get_template_transaction_account(const SchedXaction *sx)
347 {
348     Account *template_root, *sx_template_acct;
349     char sx_guid_str[GUID_ENCODING_LENGTH+1];
350 
351     template_root = gnc_book_get_template_root(gnc_get_current_book());
352     guid_to_string_buff(xaccSchedXactionGetGUID(sx), sx_guid_str);
353     sx_template_acct = gnc_account_lookup_by_name(template_root, sx_guid_str);
354     return sx_template_acct;
355 }
356 
357 void
gnc_sx_get_variables(SchedXaction * sx,GHashTable * var_hash)358 gnc_sx_get_variables(SchedXaction *sx, GHashTable *var_hash)
359 {
360     Account *sx_template_acct = gnc_sx_get_template_transaction_account(sx);
361     xaccAccountForEachTransaction(sx_template_acct, _get_vars_helper, var_hash);
362 }
363 
364 static void
_set_var_to_random_value(gchar * key,GncSxVariable * var,gpointer unused_user_data)365 _set_var_to_random_value(gchar *key, GncSxVariable *var, gpointer unused_user_data)
366 {
367     /* This is used by dialog-sx-editor to plug in values as a simplistic way to
368      * check balances. One possible variable is the number of periods in a
369      * interest or future value calculation where the variable is used as an
370      * exponent, so we want the numbers to be monotonically > 0 and not so large
371      * that they'll cause overflows.
372      */
373     var->value = gnc_numeric_create(g_random_int_range(1, 1000), 1);
374 }
375 
376 void
gnc_sx_randomize_variables(GHashTable * vars)377 gnc_sx_randomize_variables(GHashTable *vars)
378 {
379     g_hash_table_foreach(vars, (GHFunc)_set_var_to_random_value, NULL);
380 }
381 
382 static void
_clone_sx_var_hash_entry(gpointer key,gpointer value,gpointer user_data)383 _clone_sx_var_hash_entry(gpointer key, gpointer value, gpointer user_data)
384 {
385     GHashTable *to = (GHashTable*)user_data;
386     GncSxVariable *to_copy = (GncSxVariable*)value;
387     GncSxVariable *var = gnc_sx_variable_new_copy(to_copy);
388     g_hash_table_insert(to, g_strdup(key), var);
389 }
390 
391 static GncSxInstance*
gnc_sx_instance_new(GncSxInstances * parent,GncSxInstanceState state,GDate * date,void * temporal_state,gint sequence_num)392 gnc_sx_instance_new(GncSxInstances *parent, GncSxInstanceState state, GDate *date, void *temporal_state, gint sequence_num)
393 {
394     GncSxInstance *rtn = g_new0(GncSxInstance, 1);
395     rtn->parent = parent;
396     rtn->orig_state = state;
397     rtn->state = state;
398     g_date_clear(&rtn->date, 1);
399     rtn->date = *date;
400     rtn->temporal_state = gnc_sx_clone_temporal_state(temporal_state);
401 
402     if (! parent->variable_names_parsed)
403     {
404         parent->variable_names = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)gnc_sx_variable_free);
405         gnc_sx_get_variables(parent->sx, parent->variable_names);
406         g_hash_table_foreach(parent->variable_names, (GHFunc)_wipe_parsed_sx_var, NULL);
407         parent->variable_names_parsed = TRUE;
408     }
409 
410     rtn->variable_bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)gnc_sx_variable_free);
411     g_hash_table_foreach(parent->variable_names, _clone_sx_var_hash_entry, rtn->variable_bindings);
412 
413     {
414         int instance_i_value;
415         gnc_numeric i_num;
416         GncSxVariable *as_var;
417 
418         instance_i_value = gnc_sx_get_instance_count(rtn->parent->sx, rtn->temporal_state);
419         i_num = gnc_numeric_create(instance_i_value, 1);
420         as_var = gnc_sx_variable_new_full("i", i_num, FALSE);
421 
422         g_hash_table_insert(rtn->variable_bindings, g_strdup("i"), as_var);
423     }
424 
425     return rtn;
426 }
427 
428 static gint
_compare_GncSxVariables(gconstpointer a,gconstpointer b)429 _compare_GncSxVariables(gconstpointer a, gconstpointer b)
430 {
431     return strcmp(((const GncSxVariable*)a)->name, ((const GncSxVariable*)b)->name);
432 }
433 
434 static void
_build_list_from_hash_elts(gpointer key,gpointer value,gpointer user_data)435 _build_list_from_hash_elts(gpointer key, gpointer value, gpointer user_data)
436 {
437     GList **list = (GList**)user_data;
438     *list = g_list_insert_sorted(*list, value, _compare_GncSxVariables);
439 }
440 
441 GList *
gnc_sx_instance_get_variables(GncSxInstance * inst)442 gnc_sx_instance_get_variables(GncSxInstance *inst)
443 {
444     GList *vars = NULL;
445     g_hash_table_foreach(inst->variable_bindings, _build_list_from_hash_elts, &vars);
446     return vars;
447 }
448 
449 static GncSxInstances*
_gnc_sx_gen_instances(gpointer * data,gpointer user_data)450 _gnc_sx_gen_instances(gpointer *data, gpointer user_data)
451 {
452     GncSxInstances *instances = g_new0(GncSxInstances, 1);
453     SchedXaction *sx = (SchedXaction*)data;
454     const GDate *range_end = (const GDate*)user_data;
455     GDate creation_end, remind_end;
456     GDate cur_date;
457     SXTmpStateData *temporal_state = gnc_sx_create_temporal_state(sx);
458 
459     instances->sx = sx;
460 
461     creation_end = *range_end;
462     g_date_add_days(&creation_end, xaccSchedXactionGetAdvanceCreation(sx));
463     remind_end = creation_end;
464     g_date_add_days(&remind_end, xaccSchedXactionGetAdvanceReminder(sx));
465 
466     /* postponed */
467     {
468         GList *postponed = gnc_sx_get_defer_instances(sx);
469         for ( ; postponed != NULL; postponed = postponed->next)
470         {
471             GDate inst_date;
472             int seq_num;
473             GncSxInstance *inst;
474 
475             g_date_clear(&inst_date, 1);
476             inst_date = xaccSchedXactionGetNextInstance(sx, postponed->data);
477             seq_num = gnc_sx_get_instance_count(sx, postponed->data);
478             inst = gnc_sx_instance_new(instances, SX_INSTANCE_STATE_POSTPONED,
479                                        &inst_date, postponed->data, seq_num);
480             instances->instance_list =
481                 g_list_append(instances->instance_list, inst);
482             gnc_sx_destroy_temporal_state(temporal_state);
483             temporal_state = gnc_sx_clone_temporal_state(postponed->data);
484             gnc_sx_incr_temporal_state(sx, temporal_state);
485         }
486     }
487 
488     /* to-create */
489     g_date_clear(&cur_date, 1);
490     cur_date = xaccSchedXactionGetNextInstance(sx, temporal_state);
491     instances->next_instance_date = cur_date;
492     while (g_date_valid(&cur_date) && g_date_compare(&cur_date, &creation_end) <= 0)
493     {
494         GncSxInstance *inst;
495         int seq_num;
496         seq_num = gnc_sx_get_instance_count(sx, temporal_state);
497         inst = gnc_sx_instance_new(instances, SX_INSTANCE_STATE_TO_CREATE,
498                                    &cur_date, temporal_state, seq_num);
499         instances->instance_list = g_list_append(instances->instance_list, inst);
500         gnc_sx_incr_temporal_state(sx, temporal_state);
501         cur_date = xaccSchedXactionGetNextInstance(sx, temporal_state);
502     }
503 
504     /* reminders */
505     while (g_date_valid(&cur_date) &&
506            g_date_compare(&cur_date, &remind_end) <= 0)
507     {
508         GncSxInstance *inst;
509         int seq_num;
510         seq_num = gnc_sx_get_instance_count(sx, temporal_state);
511         inst = gnc_sx_instance_new(instances, SX_INSTANCE_STATE_REMINDER,
512                                    &cur_date, temporal_state, seq_num);
513         instances->instance_list = g_list_append(instances->instance_list,
514                                                  inst);
515         gnc_sx_incr_temporal_state(sx, temporal_state);
516         cur_date = xaccSchedXactionGetNextInstance(sx, temporal_state);
517     }
518 
519     return instances;
520 }
521 
522 GncSxInstanceModel*
gnc_sx_get_current_instances(void)523 gnc_sx_get_current_instances(void)
524 {
525     GDate now;
526     g_date_clear(&now, 1);
527     gnc_gdate_set_time64 (&now, gnc_time (NULL));
528     return gnc_sx_get_instances(&now, FALSE);
529 }
530 
531 GncSxInstanceModel*
gnc_sx_get_instances(const GDate * range_end,gboolean include_disabled)532 gnc_sx_get_instances(const GDate *range_end, gboolean include_disabled)
533 {
534     GList *all_sxes = gnc_book_get_schedxactions(gnc_get_current_book())->sx_list;
535     GncSxInstanceModel *instances;
536 
537     g_assert(range_end != NULL);
538     g_assert(g_date_valid(range_end));
539 
540     instances = gnc_sx_instance_model_new();
541     instances->include_disabled = include_disabled;
542     instances->range_end = *range_end;
543 
544     if (include_disabled)
545     {
546         instances->sx_instance_list = gnc_g_list_map(all_sxes, (GncGMapFunc)_gnc_sx_gen_instances, (gpointer)range_end);
547     }
548     else
549     {
550         GList *sx_iter = g_list_first(all_sxes);
551         GList *enabled_sxes = NULL;
552 
553         for (; sx_iter != NULL; sx_iter = sx_iter->next)
554         {
555             SchedXaction *sx = (SchedXaction*)sx_iter->data;
556             if (xaccSchedXactionGetEnabled(sx))
557             {
558                 enabled_sxes = g_list_prepend (enabled_sxes, sx);
559             }
560         }
561         enabled_sxes = g_list_reverse (enabled_sxes);
562         instances->sx_instance_list = gnc_g_list_map(enabled_sxes, (GncGMapFunc)_gnc_sx_gen_instances, (gpointer)range_end);
563         g_list_free(enabled_sxes);
564     }
565 
566     return instances;
567 }
568 static GncSxInstanceModel*
gnc_sx_instance_model_new(void)569 gnc_sx_instance_model_new(void)
570 {
571     return GNC_SX_INSTANCE_MODEL(g_object_new(GNC_TYPE_SX_INSTANCE_MODEL, NULL));
572 }
573 
574 GType
gnc_sx_instance_model_get_type(void)575 gnc_sx_instance_model_get_type(void)
576 {
577     static GType type = 0;
578     if (type == 0)
579     {
580         static const GTypeInfo info =
581             {
582                 sizeof (GncSxInstanceModelClass),
583                 NULL,   /* base_init */
584                 NULL,   /* base_finalize */
585                 (GClassInitFunc)gnc_sx_instance_model_class_init,   /* class_init */
586                 NULL,   /* class_finalize */
587                 NULL,   /* class_data */
588                 sizeof (GncSxInstanceModel),
589                 0,      /* n_preallocs */
590                 (GInstanceInitFunc)gnc_sx_instance_model_init    /* instance_init */
591             };
592         type = g_type_register_static (G_TYPE_OBJECT,
593                                        "GncSxInstanceModelType",
594                                        &info, 0);
595     }
596     return type;
597 }
598 
599 static void
gnc_sx_instance_model_dispose(GObject * object)600 gnc_sx_instance_model_dispose(GObject *object)
601 {
602     GncSxInstanceModel *model;
603     g_return_if_fail(object != NULL);
604     model = GNC_SX_INSTANCE_MODEL(object);
605 
606     g_return_if_fail(!model->disposed);
607     model->disposed = TRUE;
608 
609     qof_event_unregister_handler(model->qof_event_handler_id);
610 
611     G_OBJECT_CLASS(parent_class)->dispose(object);
612 }
613 
614 static void
gnc_sx_instance_free(GncSxInstance * instance)615 gnc_sx_instance_free(GncSxInstance *instance)
616 {
617     gnc_sx_destroy_temporal_state(instance->temporal_state);
618 
619     if (instance->variable_bindings != NULL)
620     {
621         g_hash_table_destroy(instance->variable_bindings);
622     }
623     instance->variable_bindings = NULL;
624 
625     g_free(instance);
626 }
627 
628 static void
gnc_sx_instances_free(GncSxInstances * instances)629 gnc_sx_instances_free(GncSxInstances *instances)
630 {
631     GList *instance_iter;
632 
633     if (instances->variable_names != NULL)
634     {
635         g_hash_table_destroy(instances->variable_names);
636     }
637     instances->variable_names = NULL;
638 
639     instances->sx = NULL;
640 
641     for (instance_iter = instances->instance_list; instance_iter != NULL; instance_iter = instance_iter->next)
642     {
643         GncSxInstance *inst = (GncSxInstance*)instance_iter->data;
644         gnc_sx_instance_free(inst);
645     }
646     g_list_free(instances->instance_list);
647     instances->instance_list = NULL;
648 
649     g_free(instances);
650 }
651 
652 static void
gnc_sx_instance_model_finalize(GObject * object)653 gnc_sx_instance_model_finalize (GObject *object)
654 {
655     GncSxInstanceModel *model;
656     GList *sx_list_iter;
657 
658     g_return_if_fail(object != NULL);
659 
660     model = GNC_SX_INSTANCE_MODEL(object);
661     for (sx_list_iter = model->sx_instance_list; sx_list_iter != NULL; sx_list_iter = sx_list_iter->next)
662     {
663         GncSxInstances *instances = (GncSxInstances*)sx_list_iter->data;
664         gnc_sx_instances_free(instances);
665     }
666     g_list_free(model->sx_instance_list);
667     model->sx_instance_list = NULL;
668 
669     G_OBJECT_CLASS(parent_class)->finalize(object);
670 }
671 
672 static void
gnc_sx_instance_model_class_init(GncSxInstanceModelClass * klass)673 gnc_sx_instance_model_class_init (GncSxInstanceModelClass *klass)
674 {
675     GObjectClass *object_class = G_OBJECT_CLASS(klass);
676 
677     parent_class = g_type_class_peek_parent(klass);
678 
679     object_class->dispose = gnc_sx_instance_model_dispose;
680     object_class->finalize = gnc_sx_instance_model_finalize;
681 
682     klass->removing_signal_id =
683         g_signal_new("removing",
684                      GNC_TYPE_SX_INSTANCE_MODEL,
685                      G_SIGNAL_RUN_FIRST,
686                      0, /* class offset */
687                      NULL, /* accumulator */
688                      NULL, /* accum data */
689                      g_cclosure_marshal_VOID__POINTER,
690                      G_TYPE_NONE,
691                      1,
692                      G_TYPE_POINTER);
693 
694     klass->updated_signal_id =
695         g_signal_new("updated",
696                      GNC_TYPE_SX_INSTANCE_MODEL,
697                      G_SIGNAL_RUN_FIRST,
698                      0, /* class offset */
699                      NULL, /* accumulator */
700                      NULL, /* accum data */
701                      g_cclosure_marshal_VOID__POINTER,
702                      G_TYPE_NONE,
703                      1,
704                      G_TYPE_POINTER);
705 
706     klass->added_signal_id =
707         g_signal_new("added",
708                      GNC_TYPE_SX_INSTANCE_MODEL,
709                      G_SIGNAL_RUN_FIRST,
710                      0, /* class offset */
711                      NULL, /* accumulator */
712                      NULL, /* accum data */
713                      g_cclosure_marshal_VOID__POINTER,
714                      G_TYPE_NONE,
715                      1,
716                      G_TYPE_POINTER);
717 }
718 
719 static void
gnc_sx_instance_model_init(GTypeInstance * instance,gpointer klass)720 gnc_sx_instance_model_init(GTypeInstance *instance, gpointer klass)
721 {
722     GncSxInstanceModel *inst = (GncSxInstanceModel*)instance;
723 
724     g_date_clear(&inst->range_end, 1);
725     inst->sx_instance_list = NULL;
726     inst->qof_event_handler_id = qof_event_register_handler(_gnc_sx_instance_event_handler, inst);
727 }
728 
729 static gint
_gnc_sx_instance_find_by_sx(GncSxInstances * in_list_instances,SchedXaction * sx_to_find)730 _gnc_sx_instance_find_by_sx(GncSxInstances *in_list_instances, SchedXaction *sx_to_find)
731 {
732     if (in_list_instances->sx == sx_to_find)
733         return 0;
734     return -1;
735 }
736 
737 static void
_gnc_sx_instance_event_handler(QofInstance * ent,QofEventId event_type,gpointer user_data,gpointer evt_data)738 _gnc_sx_instance_event_handler(QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data)
739 {
740     GncSxInstanceModel *instances = GNC_SX_INSTANCE_MODEL(user_data);
741 
742     /* selection rules {
743     //   (gnc_collection_get_schedxaction_list(book), GNC_EVENT_ITEM_ADDED)
744     //   (gnc_collection_get_schedxaction_list(book), GNC_EVENT_ITEM_REMOVED)
745     //   (GNC_IS_SX(ent), QOF_EVENT_MODIFIED)
746     // } */
747     if (!(GNC_IS_SX(ent) || GNC_IS_SXES(ent)))
748         return;
749 
750     if (GNC_IS_SX(ent))
751     {
752         SchedXaction *sx;
753         gboolean sx_is_in_model = FALSE;
754 
755         sx = GNC_SX(ent);
756         // only send `updated` if it's actually in the model
757         sx_is_in_model = (g_list_find_custom(instances->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx) != NULL);
758         if (event_type & QOF_EVENT_MODIFY)
759         {
760             if (sx_is_in_model)
761             {
762                 if (instances->include_disabled || xaccSchedXactionGetEnabled(sx))
763                 {
764                     g_signal_emit_by_name(instances, "updated", (gpointer)sx);
765                 }
766                 else
767                 {
768                     /* the sx was enabled but is now disabled */
769                     g_signal_emit_by_name(instances, "removing", (gpointer)sx);
770                 }
771             }
772             else
773             {
774                 /* determine if this is a legitimate SX or just a "one-off" / being created */
775                 GList *all_sxes = gnc_book_get_schedxactions(gnc_get_current_book())->sx_list;
776                 if (g_list_find(all_sxes, sx) && (!instances->include_disabled && xaccSchedXactionGetEnabled(sx)))
777                 {
778                     /* it's moved from disabled to enabled, add the instances */
779                     instances->sx_instance_list
780                         = g_list_append(instances->sx_instance_list,
781                                         _gnc_sx_gen_instances((gpointer)sx, (gpointer) & instances->range_end));
782                     g_signal_emit_by_name(instances, "added", (gpointer)sx);
783                 }
784             }
785         }
786         /* else { unsupported event type; ignore } */
787     }
788     else if (GNC_IS_SXES(ent))
789     {
790         SchedXaction *sx = GNC_SX(evt_data);
791 
792         if (event_type & GNC_EVENT_ITEM_REMOVED)
793         {
794             GList *instances_link;
795             instances_link = g_list_find_custom(instances->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
796             if (instances_link != NULL)
797             {
798                 g_signal_emit_by_name(instances, "removing", (gpointer)sx);
799             }
800             else if (instances->include_disabled)
801             {
802                 g_warning("could not remove instances that do not exist in the model");
803             }
804         }
805         else if (event_type & GNC_EVENT_ITEM_ADDED)
806         {
807             if (instances->include_disabled || xaccSchedXactionGetEnabled(sx))
808             {
809                 /* generate instances, add to instance list, emit update. */
810                 instances->sx_instance_list
811                     = g_list_append(instances->sx_instance_list,
812                                     _gnc_sx_gen_instances((gpointer)sx, (gpointer) & instances->range_end));
813                 g_signal_emit_by_name(instances, "added", (gpointer)sx);
814             }
815         }
816         /* else { g_critical("unsupported event type [%d]\n", event_type); } */
817     }
818 }
819 
820 typedef struct _HashListPair
821 {
822     GHashTable *hash;
823     GList *list;
824 } HashListPair;
825 
826 static void
_find_unreferenced_vars(gchar * key,gpointer value,HashListPair * cb_pair)827 _find_unreferenced_vars(gchar *key,
828                         gpointer value,
829                         HashListPair *cb_pair)
830 {
831     if (cb_pair->hash ==  NULL ||
832         !g_hash_table_lookup_extended(cb_pair->hash, key, NULL, NULL))
833     {
834         DEBUG("variable [%s] not found", key);
835         cb_pair->list = g_list_append(cb_pair->list, key);
836     }
837 }
838 
839 void
gnc_sx_instance_model_update_sx_instances(GncSxInstanceModel * model,SchedXaction * sx)840 gnc_sx_instance_model_update_sx_instances(GncSxInstanceModel *model, SchedXaction *sx)
841 {
842     GncSxInstances *existing, *new_instances;
843     GList *link;
844 
845     link = g_list_find_custom(model->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
846     if (link == NULL)
847     {
848         g_critical("couldn't find sx [%p]\n", sx);
849         return;
850     }
851 
852     // merge the new instance data into the existing structure, mutating as little as possible.
853     existing = (GncSxInstances*)link->data;
854     new_instances = _gnc_sx_gen_instances((gpointer)sx, &model->range_end);
855     existing->sx = new_instances->sx;
856     existing->next_instance_date = new_instances->next_instance_date;
857     {
858         GList *existing_iter, *new_iter;
859         gboolean existing_remain, new_remain;
860 
861         // step through the lists pairwise, and retain the existing
862         // instance if the dates align, as soon as they don't stop and
863         // cleanup.
864         existing_iter = existing->instance_list;
865         new_iter = new_instances->instance_list;
866         for (; existing_iter != NULL && new_iter != NULL; existing_iter = existing_iter->next, new_iter = new_iter->next)
867         {
868             GncSxInstance *existing_inst, *new_inst;
869             gboolean same_instance_date;
870             existing_inst = (GncSxInstance*)existing_iter->data;
871             new_inst = (GncSxInstance*)new_iter->data;
872 
873             same_instance_date = g_date_compare(&existing_inst->date, &new_inst->date) == 0;
874             if (!same_instance_date)
875                 break;
876         }
877 
878         existing_remain = (existing_iter != NULL);
879         new_remain = (new_iter != NULL);
880 
881         if (existing_remain)
882         {
883             // delete excess
884             gnc_g_list_cut(&existing->instance_list, existing_iter);
885             g_list_foreach(existing_iter, (GFunc)gnc_sx_instance_free, NULL);
886         }
887 
888         if (new_remain)
889         {
890             // append new
891             GList *new_iter_iter;
892             gnc_g_list_cut(&new_instances->instance_list, new_iter);
893 
894             for (new_iter_iter = new_iter; new_iter_iter != NULL; new_iter_iter = new_iter_iter->next)
895             {
896                 GncSxInstance *inst = (GncSxInstance*)new_iter_iter->data;
897                 inst->parent = existing;
898                 existing->instance_list = g_list_append(existing->instance_list, new_iter_iter->data);
899             }
900             g_list_free(new_iter);
901         }
902     }
903 
904     // handle variables
905     {
906         GList *removed_var_names = NULL, *added_var_names = NULL;
907         GList *inst_iter = NULL;
908 
909         if (existing->variable_names != NULL)
910         {
911             HashListPair removed_cb_data;
912             removed_cb_data.hash = new_instances->variable_names;
913             removed_cb_data.list = NULL;
914             g_hash_table_foreach(existing->variable_names, (GHFunc)_find_unreferenced_vars, &removed_cb_data);
915             removed_var_names = removed_cb_data.list;
916         }
917         DEBUG("%d removed variables", g_list_length(removed_var_names));
918 
919         if (new_instances->variable_names != NULL)
920         {
921             HashListPair added_cb_data;
922             added_cb_data.hash = existing->variable_names;
923             added_cb_data.list = NULL;
924             g_hash_table_foreach(new_instances->variable_names, (GHFunc)_find_unreferenced_vars, &added_cb_data);
925             added_var_names = added_cb_data.list;
926         }
927         DEBUG("%d added variables", g_list_length(added_var_names));
928 
929         if (existing->variable_names != NULL)
930         {
931             g_hash_table_destroy(existing->variable_names);
932         }
933         existing->variable_names = new_instances->variable_names;
934         new_instances->variable_names = NULL;
935 
936         for (inst_iter = existing->instance_list; inst_iter != NULL; inst_iter = inst_iter->next)
937         {
938             GList *var_iter;
939             GncSxInstance *inst = (GncSxInstance*)inst_iter->data;
940 
941             for (var_iter = removed_var_names; var_iter != NULL; var_iter = var_iter->next)
942             {
943                 gchar *to_remove_key = (gchar*)var_iter->data;
944                 g_hash_table_remove(inst->variable_bindings, to_remove_key);
945             }
946 
947             for (var_iter = added_var_names; var_iter != NULL; var_iter = var_iter->next)
948             {
949                 gchar *to_add_key = (gchar*)var_iter->data;
950                 if (!g_hash_table_lookup_extended(
951                         inst->variable_bindings, to_add_key, NULL, NULL))
952                 {
953                     GncSxVariable *parent_var
954                         = g_hash_table_lookup(existing->variable_names, to_add_key);
955                     GncSxVariable *var_copy;
956 
957                     g_assert(parent_var != NULL);
958                     var_copy = gnc_sx_variable_new_copy(parent_var);
959                     g_hash_table_insert(inst->variable_bindings, g_strdup(to_add_key), var_copy);
960                 }
961             }
962         }
963     }
964     gnc_sx_instances_free(new_instances);
965 }
966 
967 void
gnc_sx_instance_model_remove_sx_instances(GncSxInstanceModel * model,SchedXaction * sx)968 gnc_sx_instance_model_remove_sx_instances(GncSxInstanceModel *model, SchedXaction *sx)
969 {
970     GList *instance_link = NULL;
971 
972     instance_link = g_list_find_custom(model->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
973     if (instance_link == NULL)
974     {
975         g_warning("instance not found!\n");
976         return;
977     }
978 
979     model->sx_instance_list = g_list_remove_link(model->sx_instance_list, instance_link);
980     gnc_sx_instances_free((GncSxInstances*)instance_link->data);
981 }
982 
983 static void
increment_sx_state(GncSxInstance * inst,GDate ** last_occur_date,int * instance_count,int * remain_occur_count)984 increment_sx_state(GncSxInstance *inst, GDate **last_occur_date, int *instance_count, int *remain_occur_count)
985 {
986     if (!g_date_valid(*last_occur_date)
987         || (g_date_valid(*last_occur_date)
988             && g_date_compare(*last_occur_date, &inst->date) <= 0))
989     {
990         *last_occur_date = &inst->date;
991     }
992 
993     *instance_count = gnc_sx_get_instance_count(inst->parent->sx, inst->temporal_state) + 1;
994 
995     if (*remain_occur_count > 0)
996     {
997         *remain_occur_count -= 1;
998     }
999 }
1000 
1001 static gboolean
_get_template_split_account(const SchedXaction * sx,const Split * template_split,Account ** split_acct,GList ** creation_errors)1002 _get_template_split_account(const SchedXaction* sx,
1003 			    const Split *template_split,
1004 			    Account **split_acct,
1005 			    GList **creation_errors)
1006 {
1007     gboolean success = TRUE;
1008     GncGUID *acct_guid = NULL;
1009     qof_instance_get (QOF_INSTANCE (template_split),
1010 		      "sx-account", &acct_guid,
1011 		      NULL);
1012     *split_acct = xaccAccountLookup(acct_guid, gnc_get_current_book());
1013     if (!*split_acct && sx && creation_errors)
1014     {
1015         char guid_str[GUID_ENCODING_LENGTH+1];
1016 /* Translators: A list of error messages from the Scheduled Transactions (SX).
1017    They might appear in their editor or in "Since last run".                  */
1018         gchar* err = N_("Unknown account for guid [%s], cancelling SX [%s] creation.");
1019         guid_to_string_buff((const GncGUID*)acct_guid, guid_str);
1020         REPORT_ERROR(creation_errors, err, guid_str, xaccSchedXactionGetName(sx));
1021         success = FALSE;
1022     }
1023 
1024     guid_free (acct_guid);
1025     return success;
1026 }
1027 
1028 static void
_get_sx_formula_value(const SchedXaction * sx,const Split * template_split,gnc_numeric * numeric,GList ** creation_errors,const char * formula_key,const char * numeric_key,GHashTable * variable_bindings)1029 _get_sx_formula_value(const SchedXaction* sx,
1030 		      const Split *template_split,
1031 		      gnc_numeric *numeric,
1032 		      GList **creation_errors,
1033 		      const char *formula_key,
1034 		      const char* numeric_key,
1035 		      GHashTable *variable_bindings)
1036 {
1037 
1038     char *formula_str = NULL, *parseErrorLoc = NULL;
1039     gnc_numeric *numeric_val = NULL;
1040     qof_instance_get (QOF_INSTANCE (template_split),
1041 		      formula_key, &formula_str,
1042 		      numeric_key, &numeric_val,
1043 		      NULL);
1044 
1045     if ((variable_bindings == NULL ||
1046          g_hash_table_size (variable_bindings) == 0) &&
1047         numeric_val != NULL &&
1048 	gnc_numeric_check(*numeric_val) == GNC_ERROR_OK &&
1049 	!gnc_numeric_zero_p(*numeric_val))
1050     {
1051         /* If there are no variables to parse and we had a valid numeric stored
1052          * then we can skip parsing the formula, which might save some
1053          * localization problems with separators. */
1054 	numeric->num = numeric_val->num;
1055 	numeric->denom = numeric_val->denom;
1056         return;
1057     }
1058 
1059     if (formula_str != NULL && strlen(formula_str) != 0)
1060     {
1061         GHashTable *parser_vars = NULL;
1062         if (variable_bindings)
1063         {
1064             parser_vars = gnc_sx_instance_get_variables_for_parser(variable_bindings);
1065         }
1066         if (!gnc_exp_parser_parse_separate_vars(formula_str,
1067                                                 numeric,
1068                                                 &parseErrorLoc,
1069                                                 parser_vars))
1070         {
1071             gchar *err = N_("Error parsing SX [%s] key [%s]=formula [%s] at [%s]: %s.");
1072             REPORT_ERROR(creation_errors, err,
1073                     xaccSchedXactionGetName(sx),
1074                     formula_key,
1075                     formula_str,
1076                     parseErrorLoc,
1077                     gnc_exp_parser_error_string());
1078        }
1079 
1080         if (parser_vars != NULL)
1081         {
1082             g_hash_table_destroy(parser_vars);
1083         }
1084     }
1085 }
1086 
1087 static void
_get_credit_formula_value(GncSxInstance * instance,const Split * template_split,gnc_numeric * credit_num,GList ** creation_errors)1088 _get_credit_formula_value(GncSxInstance *instance,
1089                           const Split *template_split, gnc_numeric *credit_num,
1090                           GList **creation_errors)
1091 {
1092     _get_sx_formula_value(instance->parent->sx, template_split, credit_num,
1093                           creation_errors, "sx-credit-formula",
1094                           "sx-credit-numeric", instance->variable_bindings);
1095 }
1096 
1097 static void
_get_debit_formula_value(GncSxInstance * instance,const Split * template_split,gnc_numeric * debit_num,GList ** creation_errors)1098 _get_debit_formula_value(GncSxInstance *instance, const Split *template_split,
1099                          gnc_numeric *debit_num, GList **creation_errors)
1100 {
1101     _get_sx_formula_value(instance->parent->sx, template_split, debit_num,
1102                           creation_errors, "sx-debit-formula",
1103                           "sx-debit-numeric", instance->variable_bindings);
1104 }
1105 
1106 static gnc_numeric
split_apply_formulas(const Split * split,SxTxnCreationData * creation_data)1107 split_apply_formulas (const Split *split, SxTxnCreationData* creation_data)
1108 {
1109     gnc_numeric credit_num = gnc_numeric_zero();
1110     gnc_numeric debit_num = gnc_numeric_zero();
1111     gnc_numeric final;
1112     gint gncn_error;
1113     SchedXaction *sx = creation_data->instance->parent->sx;
1114 
1115     _get_credit_formula_value(creation_data->instance, split, &credit_num,
1116                               creation_data->creation_errors);
1117     _get_debit_formula_value(creation_data->instance, split, &debit_num,
1118                              creation_data->creation_errors);
1119 
1120     final = gnc_numeric_sub_fixed(debit_num, credit_num);
1121 
1122     gncn_error = gnc_numeric_check(final);
1123     if (gncn_error != GNC_ERROR_OK)
1124     {
1125         gchar *err = N_("Error %d in SX [%s] final gnc_numeric value, using 0 instead.");
1126         REPORT_ERROR(creation_data->creation_errors, err,
1127                         gncn_error, xaccSchedXactionGetName(sx));
1128         final = gnc_numeric_zero();
1129     }
1130     return final;
1131 }
1132 
1133 static void
split_apply_exchange_rate(Split * split,GHashTable * bindings,gnc_commodity * txn_cmdty,gnc_commodity * split_cmdty,gnc_numeric * final)1134 split_apply_exchange_rate (Split *split, GHashTable *bindings,
1135                            gnc_commodity *txn_cmdty,
1136                            gnc_commodity *split_cmdty, gnc_numeric *final)
1137 {
1138     gchar *exchange_rate_var_name;
1139     GncSxVariable *exchange_rate_var;
1140     gnc_numeric amt;
1141     gnc_numeric exchange_rate = gnc_numeric_create (1, 1);
1142 
1143     exchange_rate_var_name = var_name_from_commodities(split_cmdty, txn_cmdty);
1144     exchange_rate_var =
1145         (GncSxVariable*)g_hash_table_lookup(bindings,
1146                                             exchange_rate_var_name);
1147 
1148     if (exchange_rate_var != NULL)
1149     {
1150         exchange_rate = exchange_rate_var->value;
1151         DEBUG("exchange_rate is %s", gnc_numeric_to_string (exchange_rate));
1152     }
1153     g_free (exchange_rate_var_name);
1154 
1155     if (!gnc_commodity_is_currency (split_cmdty))
1156         amt = gnc_numeric_div(*final, exchange_rate,
1157                               gnc_commodity_get_fraction (split_cmdty),
1158                               GNC_HOW_RND_ROUND_HALF_UP);
1159     else
1160         amt = gnc_numeric_mul(*final, exchange_rate, 1000,
1161                               GNC_HOW_RND_ROUND_HALF_UP);
1162 
1163 
1164     DEBUG("amount is %s for memo split '%s'", gnc_numeric_to_string (amt),
1165             xaccSplitGetMemo (split));
1166     xaccSplitSetAmount(split, amt); /* marks split dirty */
1167 
1168 }
1169 /* If the template_txn was created from the SX Editor then it has the default
1170  * currency even if none of its splits do; if the template_txn was created from
1171  * a non-currency register then it will be requesting backwards prices. Check
1172  * that the template_txn currency is in at least one split; if it's not a
1173  * currency and one of the splits is, use that currency. If there are no
1174  * currencies at all assume that the user knew what they were doing and return
1175  * the template_transaction's commodity.
1176  *
1177  * Since we're going through the split commodities anyway, check that they all
1178  * have usable values. If we find an error return NULL as a signal to
1179  * create_each_transaction_helper to bail out.
1180  */
1181 
1182 static gnc_commodity*
get_transaction_currency(SxTxnCreationData * creation_data,SchedXaction * sx,Transaction * template_txn)1183 get_transaction_currency(SxTxnCreationData *creation_data,
1184                          SchedXaction *sx, Transaction *template_txn)
1185 {
1186     gnc_commodity *first_currency = NULL, *first_cmdty = NULL;
1187     gboolean err_flag = FALSE, txn_cmdty_in_splits = FALSE;
1188     gnc_commodity *txn_cmdty = xaccTransGetCurrency (template_txn);
1189     GList* txn_splits = xaccTransGetSplitList (template_txn);
1190     GList** creation_errors =
1191         creation_data ? creation_data->creation_errors : NULL;
1192 
1193     if (txn_cmdty)
1194         DEBUG("Template txn currency is %s.",
1195                 gnc_commodity_get_mnemonic (txn_cmdty));
1196     else
1197         DEBUG("No template txn currency.");
1198 
1199     for (;txn_splits; txn_splits = txn_splits->next)
1200     {
1201         Split* t_split = (Split*)txn_splits->data;
1202         Account* split_account = NULL;
1203         gnc_commodity *split_cmdty = NULL;
1204 
1205         if (!_get_template_split_account(sx, t_split, &split_account,
1206                                          creation_errors))
1207         {
1208             err_flag = TRUE;
1209             break;
1210         }
1211         /* Don't consider the commodity of a transaction that has
1212          * neither a credit nor a debit formula. */
1213 
1214         if (split_is_marker(t_split))
1215              continue;
1216 
1217         split_cmdty = xaccAccountGetCommodity (split_account);
1218         if (!txn_cmdty)
1219             txn_cmdty = split_cmdty;
1220         if (!first_cmdty)
1221             first_cmdty = split_cmdty;
1222         if (gnc_commodity_equal (split_cmdty, txn_cmdty))
1223             txn_cmdty_in_splits = TRUE;
1224         if (!first_currency && gnc_commodity_is_currency (split_cmdty))
1225             first_currency = split_cmdty;
1226     }
1227     if (err_flag)
1228     {
1229         g_critical("Error in SX transaction [%s], split missing account: "
1230                    "Creation aborted.", xaccSchedXactionGetName(sx));
1231         return NULL;
1232     }
1233     if (first_currency &&
1234         (!txn_cmdty_in_splits || !gnc_commodity_is_currency (txn_cmdty)))
1235         return first_currency;
1236     if (!txn_cmdty_in_splits)
1237         return first_cmdty;
1238     return txn_cmdty;
1239 }
1240 
1241 static gboolean
create_each_transaction_helper(Transaction * template_txn,void * user_data)1242 create_each_transaction_helper(Transaction *template_txn, void *user_data)
1243 {
1244     Transaction *new_txn;
1245     GList *txn_splits, *template_splits, *node;
1246     Split *copying_split;
1247     SxTxnCreationData *creation_data = (SxTxnCreationData*)user_data;
1248     SchedXaction *sx = creation_data->instance->parent->sx;
1249     gnc_commodity *txn_cmdty = get_transaction_currency (creation_data,
1250                                                          sx, template_txn);
1251 
1252     /* No txn_cmdty means there was a defective split. Bail. */
1253     if (txn_cmdty == NULL)
1254          return FALSE;
1255 
1256     /* FIXME: In general, this should [correctly] deal with errors such
1257        as not finding the appropriate Accounts and not being able to
1258        parse the formula|credit/debit strings. */
1259 
1260     new_txn = xaccTransCloneNoKvp(template_txn);
1261     xaccTransBeginEdit(new_txn);
1262 
1263     DEBUG("creating template txn desc [%s] for sx [%s]",
1264             xaccTransGetDescription(new_txn),
1265             xaccSchedXactionGetName(sx));
1266 
1267 
1268     /* Bug#500427: copy the notes, if any */
1269     if (xaccTransGetNotes(template_txn) != NULL)
1270     {
1271         xaccTransSetNotes(new_txn, g_strdup(xaccTransGetNotes(template_txn)));
1272     }
1273 
1274     xaccTransSetDate(new_txn,
1275                      g_date_get_day(&creation_data->instance->date),
1276                      g_date_get_month(&creation_data->instance->date),
1277                      g_date_get_year(&creation_data->instance->date));
1278 
1279     xaccTransSetDateEnteredSecs(new_txn, gnc_time(NULL));
1280     template_splits = xaccTransGetSplitList(template_txn);
1281     txn_splits = xaccTransGetSplitList(new_txn);
1282     if ((template_splits == NULL) || (txn_splits == NULL))
1283     {
1284         g_critical("transaction w/o splits for sx [%s]",
1285                    xaccSchedXactionGetName(sx));
1286         xaccTransDestroy(new_txn);
1287         xaccTransCommitEdit(new_txn);
1288         return FALSE;
1289     }
1290 
1291     if (txn_cmdty == NULL)
1292     {
1293         xaccTransDestroy(new_txn);
1294         xaccTransCommitEdit(new_txn);
1295         return FALSE;
1296     }
1297     xaccTransSetCurrency(new_txn, txn_cmdty);
1298 
1299     for (;
1300          txn_splits && template_splits;
1301          txn_splits = txn_splits->next, template_splits = template_splits->next)
1302     {
1303         const Split *template_split;
1304         Account *split_acct;
1305         gnc_commodity *split_cmdty = NULL;
1306 
1307         /* FIXME: Ick.  This assumes that the split lists will be ordered
1308            identically. :( They are, but we'd rather not have to count on
1309            it. --jsled */
1310         template_split = (Split*)template_splits->data;
1311         copying_split = (Split*)txn_splits->data;
1312 
1313         _get_template_split_account(sx, template_split, &split_acct,
1314                                     creation_data->creation_errors);
1315 
1316         split_cmdty = xaccAccountGetCommodity(split_acct);
1317         xaccSplitSetAccount(copying_split, split_acct);
1318 
1319         {
1320             gnc_numeric final = split_apply_formulas(template_split,
1321                                                      creation_data);
1322             xaccSplitSetValue(copying_split, final);
1323             DEBUG("value is %s for memo split '%s'",
1324                     gnc_numeric_to_string (final),
1325                     xaccSplitGetMemo (copying_split));
1326             if (! gnc_commodity_equal(split_cmdty, txn_cmdty))
1327             {
1328                 split_apply_exchange_rate(copying_split,
1329                                           creation_data->instance->variable_bindings,
1330                                           txn_cmdty, split_cmdty, &final);
1331             }
1332 
1333             xaccSplitScrub(copying_split);
1334         }
1335     }
1336 
1337 
1338     {
1339 	qof_instance_set (QOF_INSTANCE (new_txn),
1340 			  "from-sched-xaction",
1341 			  xaccSchedXactionGetGUID(creation_data->instance->parent->sx),
1342 			  NULL);
1343     }
1344 
1345     xaccTransCommitEdit(new_txn);
1346 
1347     if (creation_data->created_txn_guids != NULL)
1348     {
1349         *creation_data->created_txn_guids
1350             = g_list_append(*(creation_data->created_txn_guids),
1351                             (gpointer)xaccTransGetGUID(new_txn));
1352     }
1353 
1354     return FALSE;
1355 }
1356 
1357 static void
create_transactions_for_instance(GncSxInstance * instance,GList ** created_txn_guids,GList ** creation_errors)1358 create_transactions_for_instance(GncSxInstance *instance, GList **created_txn_guids, GList **creation_errors)
1359 {
1360     SxTxnCreationData creation_data;
1361     Account *sx_template_account;
1362 
1363     sx_template_account = gnc_sx_get_template_transaction_account(instance->parent->sx);
1364 
1365     creation_data.instance = instance;
1366     creation_data.created_txn_guids = created_txn_guids;
1367     creation_data.creation_errors = creation_errors;
1368     /* Don't update the GUI for every transaction, it can really slow things
1369      * down.
1370      */
1371     qof_event_suspend();
1372     xaccAccountForEachTransaction(sx_template_account,
1373                                   create_each_transaction_helper,
1374                                   &creation_data);
1375     qof_event_resume();
1376 }
1377 
1378 void
gnc_sx_instance_model_effect_change(GncSxInstanceModel * model,gboolean auto_create_only,GList ** created_transaction_guids,GList ** creation_errors)1379 gnc_sx_instance_model_effect_change(GncSxInstanceModel *model,
1380                                     gboolean auto_create_only,
1381                                     GList **created_transaction_guids,
1382                                     GList **creation_errors)
1383 {
1384     GList *iter;
1385 
1386     if (qof_book_is_readonly(gnc_get_current_book()))
1387     {
1388         /* Is the book read-only? Then don't change anything here. */
1389         return;
1390     }
1391 
1392     for (iter = model->sx_instance_list; iter != NULL; iter = iter->next)
1393     {
1394         GList *instance_iter;
1395         GncSxInstances *instances = (GncSxInstances*)iter->data;
1396         GDate *last_occur_date;
1397         gint instance_count = 0;
1398         gint remain_occur_count = 0;
1399 
1400         // If there are no instances, then skip; specifically, skip
1401         // re-setting SchedXaction fields, which will dirty the book
1402         // spuriously.
1403         if (g_list_length(instances->instance_list) == 0)
1404             continue;
1405 
1406         last_occur_date = (GDate*) xaccSchedXactionGetLastOccurDate(instances->sx);
1407         instance_count = gnc_sx_get_instance_count(instances->sx, NULL);
1408         remain_occur_count = xaccSchedXactionGetRemOccur(instances->sx);
1409 
1410         for (instance_iter = instances->instance_list; instance_iter != NULL; instance_iter = instance_iter->next)
1411         {
1412             GncSxInstance *inst = (GncSxInstance*)instance_iter->data;
1413             gboolean sx_is_auto_create;
1414             GList *instance_errors = NULL;
1415 
1416             xaccSchedXactionGetAutoCreate(inst->parent->sx, &sx_is_auto_create, NULL);
1417             if (auto_create_only && !sx_is_auto_create)
1418             {
1419                 if (inst->state != SX_INSTANCE_STATE_TO_CREATE)
1420                 {
1421                     break;
1422                 }
1423                 continue;
1424             }
1425 
1426             if (inst->orig_state == SX_INSTANCE_STATE_POSTPONED
1427                 && inst->state != SX_INSTANCE_STATE_POSTPONED)
1428             {
1429                 // remove from postponed list
1430                 g_assert(inst->temporal_state != NULL);
1431                 gnc_sx_remove_defer_instance(inst->parent->sx,
1432                                              inst->temporal_state);
1433             }
1434 
1435             switch (inst->state)
1436             {
1437                 case SX_INSTANCE_STATE_CREATED:
1438                     // nop: we've already processed this.
1439                     break;
1440                 case SX_INSTANCE_STATE_IGNORED:
1441                     increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
1442                     break;
1443                 case SX_INSTANCE_STATE_POSTPONED:
1444                     if (inst->orig_state != SX_INSTANCE_STATE_POSTPONED)
1445                     {
1446                         gnc_sx_add_defer_instance(instances->sx,
1447                                                   gnc_sx_clone_temporal_state (inst->temporal_state));
1448                     }
1449                     increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
1450                     break;
1451                 case SX_INSTANCE_STATE_TO_CREATE:
1452                     create_transactions_for_instance (inst,
1453                                                       created_transaction_guids,
1454                                                       &instance_errors);
1455                     if (instance_errors == NULL)
1456                     {
1457                         increment_sx_state (inst, &last_occur_date,
1458                                             &instance_count,
1459                                             &remain_occur_count);
1460                         gnc_sx_instance_model_change_instance_state
1461                             (model, inst, SX_INSTANCE_STATE_CREATED);
1462                     }
1463                     else
1464                         *creation_errors = g_list_concat (*creation_errors,
1465                                                           instance_errors);
1466                     break;
1467                 case SX_INSTANCE_STATE_REMINDER:
1468                     // do nothing
1469                     // assert no non-remind instances after this?
1470                     break;
1471                 default:
1472                     g_assert_not_reached();
1473                     break;
1474             }
1475         }
1476 
1477         xaccSchedXactionSetLastOccurDate(instances->sx, last_occur_date);
1478         gnc_sx_set_instance_count(instances->sx, instance_count);
1479         xaccSchedXactionSetRemOccur(instances->sx, remain_occur_count);
1480     }
1481 }
1482 
1483 void
gnc_sx_instance_model_change_instance_state(GncSxInstanceModel * model,GncSxInstance * instance,GncSxInstanceState new_state)1484 gnc_sx_instance_model_change_instance_state(GncSxInstanceModel *model,
1485                                             GncSxInstance *instance,
1486                                             GncSxInstanceState new_state)
1487 {
1488     if (instance->state == new_state)
1489         return;
1490 
1491     instance->state = new_state;
1492 
1493     // ensure 'remind' constraints are met:
1494     {
1495         GList *inst_iter;
1496         inst_iter = g_list_find(instance->parent->instance_list, instance);
1497         g_assert(inst_iter != NULL);
1498         if (instance->state != SX_INSTANCE_STATE_REMINDER)
1499         {
1500             // iterate backwards, making sure reminders are changed to 'postponed'
1501             for (inst_iter = inst_iter->prev; inst_iter != NULL; inst_iter = inst_iter->prev)
1502             {
1503                 GncSxInstance *prev_inst = (GncSxInstance*)inst_iter->data;
1504                 if (prev_inst->state != SX_INSTANCE_STATE_REMINDER)
1505                     continue;
1506                 prev_inst->state = SX_INSTANCE_STATE_POSTPONED;
1507             }
1508         }
1509         else
1510         {
1511             // iterate forward, make sure transactions are set to 'remind'
1512             for (inst_iter = inst_iter->next; inst_iter != NULL; inst_iter = inst_iter->next)
1513             {
1514                 GncSxInstance *next_inst = (GncSxInstance*)inst_iter->data;
1515                 if (next_inst->state == SX_INSTANCE_STATE_REMINDER)
1516                     continue;
1517                 next_inst->state = SX_INSTANCE_STATE_REMINDER;
1518             }
1519         }
1520     }
1521 
1522     g_signal_emit_by_name(model, "updated", (gpointer)instance->parent->sx);
1523 }
1524 
1525 void
gnc_sx_instance_model_set_variable(GncSxInstanceModel * model,GncSxInstance * instance,GncSxVariable * variable,gnc_numeric * new_value)1526 gnc_sx_instance_model_set_variable(GncSxInstanceModel *model,
1527                                    GncSxInstance *instance,
1528                                    GncSxVariable *variable,
1529                                    gnc_numeric *new_value)
1530 {
1531 
1532     if (gnc_numeric_equal(variable->value, *new_value))
1533         return;
1534     variable->value = *new_value;
1535     g_signal_emit_by_name(model, "updated", (gpointer)instance->parent->sx);
1536 }
1537 
1538 static void
_list_from_hash_elts(gpointer key,gpointer value,GList ** result_list)1539 _list_from_hash_elts(gpointer key, gpointer value, GList **result_list)
1540 {
1541     *result_list = g_list_append(*result_list, value);
1542 }
1543 
1544 GList*
gnc_sx_instance_model_check_variables(GncSxInstanceModel * model)1545 gnc_sx_instance_model_check_variables(GncSxInstanceModel *model)
1546 {
1547     GList *rtn = NULL;
1548     GList *sx_iter, *inst_iter, *var_list = NULL, *var_iter;
1549 
1550     for (sx_iter = model->sx_instance_list; sx_iter != NULL; sx_iter = sx_iter->next)
1551     {
1552         GncSxInstances *instances = (GncSxInstances*)sx_iter->data;
1553         for (inst_iter = instances->instance_list; inst_iter != NULL; inst_iter = inst_iter->next)
1554         {
1555             GncSxInstance *inst = (GncSxInstance*)inst_iter->data;
1556 
1557             if (inst->state != SX_INSTANCE_STATE_TO_CREATE)
1558                 continue;
1559 
1560             g_hash_table_foreach(inst->variable_bindings, (GHFunc)_list_from_hash_elts, &var_list);
1561             for (var_iter = var_list; var_iter != NULL; var_iter = var_iter->next)
1562             {
1563                 GncSxVariable *var = (GncSxVariable*)var_iter->data;
1564                 if (gnc_numeric_check(var->value) != GNC_ERROR_OK)
1565                 {
1566                     GncSxVariableNeeded *need = g_new0(GncSxVariableNeeded, 1);
1567                     need->instance = inst;
1568                     need->variable = var;
1569                     rtn = g_list_append(rtn, need);
1570                 }
1571             }
1572             g_list_free(var_list);
1573             var_list = NULL;
1574         }
1575     }
1576     return rtn;
1577 }
1578 
1579 void
gnc_sx_instance_model_summarize(GncSxInstanceModel * model,GncSxSummary * summary)1580 gnc_sx_instance_model_summarize(GncSxInstanceModel *model, GncSxSummary *summary)
1581 {
1582     GList *sx_iter, *inst_iter;
1583 
1584     g_return_if_fail(model != NULL);
1585     g_return_if_fail(summary != NULL);
1586 
1587     summary->need_dialog = FALSE;
1588     summary->num_instances = 0;
1589     summary->num_to_create_instances = 0;
1590     summary->num_auto_create_instances = 0;
1591     summary->num_auto_create_no_notify_instances = 0;
1592 
1593     for (sx_iter = model->sx_instance_list; sx_iter != NULL; sx_iter = sx_iter->next)
1594     {
1595         GncSxInstances *instances = (GncSxInstances*)sx_iter->data;
1596         gboolean sx_is_auto_create = FALSE, sx_notify = FALSE;
1597         xaccSchedXactionGetAutoCreate(instances->sx, &sx_is_auto_create, &sx_notify);
1598         for (inst_iter = instances->instance_list; inst_iter != NULL; inst_iter = inst_iter->next)
1599         {
1600             GncSxInstance *inst = (GncSxInstance*)inst_iter->data;
1601             summary->num_instances++;
1602 
1603             if (inst->state == SX_INSTANCE_STATE_TO_CREATE)
1604             {
1605                 if (sx_is_auto_create)
1606                 {
1607                     if (!sx_notify)
1608                     {
1609                         summary->num_auto_create_no_notify_instances++;
1610                     }
1611                     else
1612                     {
1613                         summary->num_auto_create_instances++;
1614                     }
1615                 }
1616                 else
1617                 {
1618                     summary->num_to_create_instances++;
1619                 }
1620             }
1621         }
1622     }
1623 
1624     // if all the instances are 'auto-create, no-notify', then we don't need
1625     // the dialog.
1626     summary->need_dialog
1627         = (summary->num_instances != 0
1628            && summary->num_auto_create_no_notify_instances != summary->num_instances);
1629 }
1630 
1631 void
gnc_sx_summary_print(const GncSxSummary * summary)1632 gnc_sx_summary_print(const GncSxSummary *summary)
1633 {
1634     PINFO("num_instances: %d", summary->num_instances);
1635     PINFO("num_to_create: %d", summary->num_to_create_instances);
1636     PINFO("num_auto_create_instances: %d", summary->num_auto_create_instances);
1637     PINFO("num_auto_create_no_notify_instances: %d", summary->num_auto_create_no_notify_instances);
1638     PINFO("need dialog? %s", summary->need_dialog ? "true" : "false");
1639 }
1640 
gnc_numeric_free(gpointer data)1641 static void gnc_numeric_free(gpointer data)
1642 {
1643     gnc_numeric *p = (gnc_numeric*) data;
1644     g_free(p);
1645 }
1646 
gnc_g_hash_new_guid_numeric()1647 GHashTable* gnc_g_hash_new_guid_numeric()
1648 {
1649     return g_hash_table_new_full (guid_hash_to_guint, guid_g_hash_table_equal,
1650                                   NULL, gnc_numeric_free);
1651 }
1652 
1653 typedef struct
1654 {
1655     GHashTable *hash;
1656     GList **creation_errors;
1657     const SchedXaction *sx;
1658     gnc_numeric count;
1659 } SxCashflowData;
1660 
add_to_hash_amount(GHashTable * hash,const GncGUID * guid,const gnc_numeric * amount)1661 static void add_to_hash_amount(GHashTable* hash, const GncGUID* guid, const gnc_numeric* amount)
1662 {
1663     /* Do we have a number belonging to this GUID in the hash? If yes,
1664      * modify it in-place; if not, insert the new element into the
1665      * hash. */
1666     gnc_numeric* elem = g_hash_table_lookup(hash, guid);
1667     gchar guidstr[GUID_ENCODING_LENGTH+1];
1668     guid_to_string_buff(guid, guidstr);
1669     if (!elem)
1670     {
1671         elem = g_new0(gnc_numeric, 1);
1672         *elem = gnc_numeric_zero();
1673         g_hash_table_insert(hash, (gpointer) guid, elem);
1674     }
1675 
1676     /* Check input arguments for sanity */
1677     if (gnc_numeric_check(*amount) != GNC_ERROR_OK)
1678     {
1679         g_critical("Oops, the given amount [%s] has the error code %d, at guid [%s].",
1680                    gnc_num_dbg_to_string(*amount),
1681                    gnc_numeric_check(*amount),
1682                    guidstr);
1683         return;
1684     }
1685     if (gnc_numeric_check(*elem) != GNC_ERROR_OK)
1686     {
1687         g_critical("Oops, the account's amount [%s] has the error code %d, at guid [%s].",
1688                    gnc_num_dbg_to_string(*elem),
1689                    gnc_numeric_check(*elem),
1690                    guidstr);
1691         return;
1692     }
1693 
1694     /* Watch out - don't use gnc_numeric_add_fixed here because it
1695      * will refuse to add 1/5+1/10; instead, we have to use the flags
1696      * as given here explicitly. Eventually, add the given amount to
1697      * the entry in the hash. */
1698     *elem = gnc_numeric_add(*elem, *amount,
1699                             GNC_DENOM_AUTO,
1700                             GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_NEVER);
1701 
1702     /* Check for sanity of the output. */
1703     if (gnc_numeric_check(*elem) != GNC_ERROR_OK)
1704     {
1705         g_critical("Oops, after addition at guid [%s] the resulting amount [%s] has the error code %d; added amount = [%s].",
1706                    guidstr,
1707                    gnc_num_dbg_to_string(*elem),
1708                    gnc_numeric_check(*elem),
1709                    gnc_num_dbg_to_string(*amount));
1710         return;
1711     }
1712 
1713     /* In case anyone wants to see this in the debug log. */
1714     DEBUG("Adding to guid [%s] the value [%s]. Value now [%s].",
1715             guidstr,
1716             gnc_num_dbg_to_string(*amount),
1717             gnc_num_dbg_to_string(*elem));
1718 }
1719 
1720 static gboolean
create_cashflow_helper(Transaction * template_txn,void * user_data)1721 create_cashflow_helper(Transaction *template_txn, void *user_data)
1722 {
1723     SxCashflowData *creation_data = user_data;
1724     GList *template_splits;
1725     const gnc_commodity *first_cmdty = NULL;
1726 
1727     DEBUG("Evaluating txn desc [%s] for sx [%s]",
1728             xaccTransGetDescription(template_txn),
1729             xaccSchedXactionGetName(creation_data->sx));
1730 
1731     template_splits = xaccTransGetSplitList(template_txn);
1732 
1733     if (template_splits == NULL)
1734     {
1735         g_critical("transaction w/o splits for sx [%s]",
1736                    xaccSchedXactionGetName(creation_data->sx));
1737         return FALSE;
1738     }
1739 
1740     for (;
1741          template_splits;
1742          template_splits = template_splits->next)
1743     {
1744         Account *split_acct;
1745         const gnc_commodity *split_cmdty = NULL;
1746         const Split *template_split = (const Split*) template_splits->data;
1747 
1748         /* Get the account that should be used for this split. */
1749         if (!_get_template_split_account(creation_data->sx, template_split, &split_acct, creation_data->creation_errors))
1750         {
1751             DEBUG("Could not find account for split");
1752             break;
1753         }
1754 
1755         /* The split's account also has some commodity */
1756         split_cmdty = xaccAccountGetCommodity(split_acct);
1757         if (first_cmdty == NULL)
1758         {
1759             first_cmdty = split_cmdty;
1760             //xaccTransSetCurrency(new_txn, first_cmdty);
1761         }
1762 
1763         {
1764             gnc_numeric credit_num = gnc_numeric_zero();
1765             gnc_numeric debit_num = gnc_numeric_zero();
1766             gnc_numeric final_once, final;
1767             gint gncn_error;
1768 
1769             /* Credit value */
1770             _get_sx_formula_value(creation_data->sx, template_split,
1771 				  &credit_num, creation_data->creation_errors,
1772 				  "sx-credit-formula", "sx-credit-numeric",
1773 				  NULL);
1774             /* Debit value */
1775             _get_sx_formula_value(creation_data->sx, template_split,
1776 				  &debit_num, creation_data->creation_errors,
1777 				  "sx-debit-formula", "sx-debit-numeric", NULL);
1778 
1779             /* The resulting cash flow number: debit minus credit,
1780              * multiplied with the count factor. */
1781             final_once = gnc_numeric_sub_fixed( debit_num, credit_num );
1782             /* Multiply with the count factor. */
1783             final = gnc_numeric_mul(final_once, creation_data->count,
1784                                     gnc_numeric_denom(final_once),
1785                                     GNC_HOW_RND_ROUND_HALF_UP);
1786 
1787             gncn_error = gnc_numeric_check(final);
1788             if (gncn_error != GNC_ERROR_OK)
1789             {
1790                 gchar* err = N_("Error %d in SX [%s] final gnc_numeric value, using 0 instead.");
1791                 REPORT_ERROR(creation_data->creation_errors, err,
1792                              gncn_error, xaccSchedXactionGetName(creation_data->sx));
1793                 final = gnc_numeric_zero();
1794             }
1795 
1796             /* Print error message if we would have needed an exchange rate */
1797             if (! gnc_commodity_equal(split_cmdty, first_cmdty))
1798             {
1799                 gchar *err = N_("No exchange rate available in SX [%s] for %s -> %s, value is zero.");
1800                 REPORT_ERROR(creation_data->creation_errors, err,
1801                              xaccSchedXactionGetName(creation_data->sx),
1802                              gnc_commodity_get_mnemonic(split_cmdty),
1803                              gnc_commodity_get_mnemonic(first_cmdty));
1804                 final = gnc_numeric_zero();
1805             }
1806 
1807             /* And add the resulting value to the hash */
1808             add_to_hash_amount(creation_data->hash, xaccAccountGetGUID(split_acct), &final);
1809         }
1810     }
1811 
1812     return FALSE;
1813 }
1814 
1815 static void
instantiate_cashflow_internal(const SchedXaction * sx,GHashTable * map,GList ** creation_errors,gint count)1816 instantiate_cashflow_internal(const SchedXaction* sx,
1817                               GHashTable* map,
1818                               GList **creation_errors, gint count)
1819 {
1820     SxCashflowData create_cashflow_data;
1821     Account* sx_template_account = gnc_sx_get_template_transaction_account(sx);
1822 
1823     if (!sx_template_account)
1824     {
1825         g_critical("Huh? No template account for the SX %s", xaccSchedXactionGetName(sx));
1826         return;
1827     }
1828 
1829     if (!xaccSchedXactionGetEnabled(sx))
1830     {
1831         DEBUG("Skipping non-enabled SX [%s]",
1832                 xaccSchedXactionGetName(sx));
1833         return;
1834     }
1835 
1836     create_cashflow_data.hash = map;
1837     create_cashflow_data.creation_errors = creation_errors;
1838     create_cashflow_data.sx = sx;
1839     create_cashflow_data.count = gnc_numeric_create(count, 1);
1840 
1841     /* The cash flow numbers are in the transactions of the template
1842      * account, so run this foreach on the transactions. */
1843     xaccAccountForEachTransaction(sx_template_account,
1844                                   create_cashflow_helper,
1845                                   &create_cashflow_data);
1846 }
1847 
1848 typedef struct
1849 {
1850     GHashTable *hash;
1851     GList **creation_errors;
1852     const GDate *range_start;
1853     const GDate *range_end;
1854 } SxAllCashflow;
1855 
instantiate_cashflow_cb(gpointer data,gpointer _user_data)1856 static void instantiate_cashflow_cb(gpointer data, gpointer _user_data)
1857 {
1858     const SchedXaction* sx = (const SchedXaction*) data;
1859     SxAllCashflow* userdata = (SxAllCashflow*) _user_data;
1860     gint count;
1861 
1862     g_assert(sx);
1863     g_assert(userdata);
1864 
1865     /* How often does this particular SX occur in the date range? */
1866     count = gnc_sx_get_num_occur_daterange(sx, userdata->range_start,
1867                                            userdata->range_end);
1868     if (count > 0)
1869     {
1870         /* If it occurs at least once, calculate ("instantiate") its
1871          * cash flow and add it to the result
1872          * g_hash<GUID,gnc_numeric> */
1873         instantiate_cashflow_internal(sx,
1874                                       userdata->hash,
1875                                       userdata->creation_errors,
1876                                       count);
1877     }
1878 }
1879 
gnc_sx_all_instantiate_cashflow(GList * all_sxes,const GDate * range_start,const GDate * range_end,GHashTable * map,GList ** creation_errors)1880 void gnc_sx_all_instantiate_cashflow(GList *all_sxes,
1881                                      const GDate *range_start, const GDate *range_end,
1882                                      GHashTable* map, GList **creation_errors)
1883 {
1884     SxAllCashflow userdata;
1885     userdata.hash = map;
1886     userdata.creation_errors = creation_errors;
1887     userdata.range_start = range_start;
1888     userdata.range_end = range_end;
1889 
1890     /* The work is done in the callback for each SX */
1891     g_list_foreach(all_sxes, instantiate_cashflow_cb, &userdata);
1892 }
1893 
1894 
gnc_sx_all_instantiate_cashflow_all(GDate range_start,GDate range_end)1895 GHashTable* gnc_sx_all_instantiate_cashflow_all(GDate range_start, GDate range_end)
1896 {
1897     GHashTable *result_map = gnc_g_hash_new_guid_numeric();
1898     GList *all_sxes = gnc_book_get_schedxactions(gnc_get_current_book())->sx_list;
1899     gnc_sx_all_instantiate_cashflow(all_sxes,
1900                                     &range_start, &range_end,
1901                                     result_map, NULL);
1902     return result_map;
1903 }
1904