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