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