GnuCash 2.4.99
gnc-sx-instance-model.c
00001 /*
00002  * gnc-sx-instance-model.c
00003  *
00004  * Copyright (C) 2006 Josh Sled <jsled@asynchronous.org>
00005  *
00006  * This program is free software; you can redistribute it and/or
00007  * modify it under the terms of version 2 and/or version 3 of the GNU General Public
00008  * License as published by the Free Software Foundation.
00009  *
00010  * As a special exception, permission is granted to link the binary module
00011  * resultant from this code with the OpenSSL project's "OpenSSL" library (or
00012  * modified versions of it that use the same license as the "OpenSSL"
00013  * library), and distribute the linked executable.  You must obey the GNU
00014  * General Public License in all respects for all of the code used other than
00015  * "OpenSSL". If you modify this file, you may extend this exception to your
00016  * version of the file, but you are not obligated to do so. If you do not
00017  * wish to do so, delete this exception statement from your version of this
00018  * file.
00019  *
00020  * This program is distributed in the hope that it will be useful,
00021  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  * GNU General Public License for more details.
00024  *
00025  * You should have received a copy of the GNU General Public License
00026  * along with this program; if not, contact:
00027  *
00028  * Free Software Foundation           Voice:  +1-617-542-5942
00029  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
00030  * Boston, MA  02110-1301,  USA       gnu@gnu.org
00031  */
00032 
00033 #include "config.h"
00034 #include <glib.h>
00035 #include <glib-object.h>
00036 #include <stdlib.h>
00037 
00038 #include "Account.h"
00039 #include "SX-book.h"
00040 #include "SchedXaction.h"
00041 #include "Scrub.h"
00042 #include "Split.h"
00043 #include "Transaction.h"
00044 #include "gnc-commodity.h"
00045 #include "gnc-event.h"
00046 #include "gnc-exp-parser.h"
00047 #include "gnc-glib-utils.h"
00048 #include "gnc-sx-instance-model.h"
00049 #include "gnc-ui-util.h"
00050 #include "qof.h"
00051 
00052 #undef G_LOG_DOMAIN
00053 #define G_LOG_DOMAIN "gnc.app-utils.sx"
00054 
00055 static GObjectClass *parent_class = NULL;
00056 
00057 static void gnc_sx_instance_model_class_init (GncSxInstanceModelClass *klass);
00058 static void gnc_sx_instance_model_init(GTypeInstance *instance, gpointer klass);
00059 static GncSxInstanceModel* gnc_sx_instance_model_new(void);
00060 
00061 static GncSxInstance* gnc_sx_instance_new(GncSxInstances *parent, GncSxInstanceState state, GDate *date, void *temporal_state, gint sequence_num);
00062 
00063 static gint _get_vars_helper(Transaction *txn, void *var_hash_data);
00064 
00065 static GncSxVariable* gnc_sx_variable_new(gchar *name);
00066 
00067 static void _gnc_sx_instance_event_handler(QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data);
00068 
00069 /* ------------------------------------------------------------ */
00070 
00071 static void
00072 _sx_var_to_raw_numeric(gchar *name, GncSxVariable *var, GHashTable *parser_var_hash)
00073 {
00074     g_hash_table_insert(parser_var_hash, g_strdup(name), &var->value);
00075 }
00076 
00077 static void
00078 _var_numeric_to_sx_var(gchar *name, gnc_numeric *num, GHashTable *sx_var_hash)
00079 {
00080     gpointer p_var;
00081     if (!g_hash_table_lookup_extended(sx_var_hash, name, NULL, &p_var))
00082     {
00083         p_var = (gpointer)gnc_sx_variable_new(name);
00084         g_hash_table_insert(sx_var_hash, g_strdup(name), p_var);
00085     }
00086     ((GncSxVariable*)p_var)->value = *num;
00087 }
00088 
00089 static void
00090 _wipe_parsed_sx_var(gchar *key, GncSxVariable *var, gpointer unused_user_data)
00091 {
00092     var->value = gnc_numeric_error(GNC_ERROR_ARG);
00093 }
00094 
00098 GHashTable*
00099 gnc_sx_instance_get_variables_for_parser(GHashTable *instance_var_hash)
00100 {
00101     GHashTable *parser_vars;
00102     parser_vars = g_hash_table_new(g_str_hash, g_str_equal);
00103     g_hash_table_foreach(instance_var_hash, (GHFunc)_sx_var_to_raw_numeric, parser_vars);
00104     return parser_vars;
00105 }
00106 
00107 int
00108 gnc_sx_parse_vars_from_formula(const char *formula,
00109                                GHashTable *var_hash,
00110                                gnc_numeric *result)
00111 {
00112     gnc_numeric num;
00113     char *errLoc = NULL;
00114     int toRet = 0;
00115     GHashTable *parser_vars;
00116 
00117     // convert var_hash -> variables for the parser.
00118     parser_vars = gnc_sx_instance_get_variables_for_parser(var_hash);
00119 
00120     num = gnc_numeric_zero();
00121     if (!gnc_exp_parser_parse_separate_vars(formula, &num, &errLoc, parser_vars))
00122     {
00123         toRet = -1;
00124     }
00125 
00126     // convert back.
00127     g_hash_table_foreach(parser_vars, (GHFunc)_var_numeric_to_sx_var, var_hash);
00128     g_hash_table_destroy(parser_vars);
00129 
00130     if (result != NULL)
00131     {
00132         *result = num;
00133     }
00134 
00135     return toRet;
00136 }
00137 
00138 static GncSxVariable*
00139 gnc_sx_variable_new(gchar *name)
00140 {
00141     GncSxVariable *var = g_new0(GncSxVariable, 1);
00142     var->name = g_strdup(name);
00143     var->value = gnc_numeric_error(GNC_ERROR_ARG);
00144     var->editable = TRUE;
00145     return var;
00146 }
00147 
00148 GncSxVariable*
00149 gnc_sx_variable_new_full(gchar *name, gnc_numeric value, gboolean editable)
00150 {
00151     GncSxVariable *var = gnc_sx_variable_new(name);
00152     var->value = value;
00153     var->editable = editable;
00154     return var;
00155 }
00156 
00157 static GncSxVariable*
00158 gnc_sx_variable_new_copy(GncSxVariable *to_copy)
00159 {
00160     GncSxVariable *var = gnc_sx_variable_new(to_copy->name);
00161     var->value = to_copy->value;
00162     var->editable = to_copy->editable;
00163     return var;
00164 }
00165 
00166 void
00167 gnc_sx_variable_free(GncSxVariable *var)
00168 {
00169     g_free(var->name);
00170     g_free(var);
00171 }
00172 
00173 static gint
00174 _get_vars_helper(Transaction *txn, void *var_hash_data)
00175 {
00176     GHashTable *var_hash = (GHashTable*)var_hash_data;
00177     GList *split_list;
00178     kvp_frame *kvpf;
00179     kvp_value *kvp_val;
00180     Split *s;
00181     char *str;
00182     gnc_commodity *first_cmdty = NULL;
00183 
00184     split_list = xaccTransGetSplitList(txn);
00185     if (split_list == NULL)
00186     {
00187         return 1;
00188     }
00189 
00190     for ( ; split_list; split_list = split_list->next)
00191     {
00192         gnc_commodity *split_cmdty = NULL;
00193         GncGUID *acct_guid;
00194         Account *acct;
00195 
00196         s = (Split*)split_list->data;
00197         kvpf = xaccSplitGetSlots(s);
00198         kvp_val = kvp_frame_get_slot_path(kvpf,
00199                                           GNC_SX_ID,
00200                                           GNC_SX_ACCOUNT,
00201                                           NULL);
00202         acct_guid = kvp_value_get_guid(kvp_val);
00203         acct = xaccAccountLookup(acct_guid, gnc_get_current_book());
00204         split_cmdty = xaccAccountGetCommodity(acct);
00205         if (first_cmdty == NULL)
00206         {
00207             first_cmdty = split_cmdty;
00208         }
00209 
00210         if (! gnc_commodity_equal(split_cmdty, first_cmdty))
00211         {
00212             GncSxVariable *var;
00213             GString *var_name;
00214             const gchar *split_mnemonic, *first_mnemonic;
00215 
00216             var_name = g_string_sized_new(16);
00217             split_mnemonic = gnc_commodity_get_mnemonic(split_cmdty);
00218             first_mnemonic = gnc_commodity_get_mnemonic(first_cmdty);
00219             g_string_printf(var_name, "%s -> %s",
00220                             split_mnemonic ? split_mnemonic : "(null)",
00221                             first_mnemonic ? first_mnemonic : "(null)");
00222             var = gnc_sx_variable_new(g_strdup(var_name->str));
00223             g_hash_table_insert(var_hash, g_strdup(var->name), var);
00224             g_string_free(var_name, TRUE);
00225         }
00226 
00227         // existing... ------------------------------------------
00228         kvp_val = kvp_frame_get_slot_path(kvpf,
00229                                           GNC_SX_ID,
00230                                           GNC_SX_CREDIT_FORMULA,
00231                                           NULL);
00232         if (kvp_val != NULL)
00233         {
00234             str = kvp_value_get_string(kvp_val);
00235             if (str && strlen(str) != 0)
00236             {
00237                 gnc_sx_parse_vars_from_formula(str, var_hash, NULL);
00238             }
00239         }
00240 
00241         kvp_val = kvp_frame_get_slot_path(kvpf,
00242                                           GNC_SX_ID,
00243                                           GNC_SX_DEBIT_FORMULA,
00244                                           NULL);
00245         if (kvp_val != NULL)
00246         {
00247             str = kvp_value_get_string(kvp_val);
00248             if (str && strlen(str) != 0)
00249             {
00250                 gnc_sx_parse_vars_from_formula(str, var_hash, NULL);
00251             }
00252         }
00253     }
00254 
00255     return 0;
00256 }
00257 
00258 Account*
00259 gnc_sx_get_template_transaction_account(const SchedXaction *sx)
00260 {
00261     Account *template_root, *sx_template_acct;
00262     char sx_guid_str[GUID_ENCODING_LENGTH+1];
00263 
00264     template_root = gnc_book_get_template_root(gnc_get_current_book());
00265     guid_to_string_buff(xaccSchedXactionGetGUID(sx), sx_guid_str);
00266     sx_template_acct = gnc_account_lookup_by_name(template_root, sx_guid_str);
00267     return sx_template_acct;
00268 }
00269 
00270 void
00271 gnc_sx_get_variables(SchedXaction *sx, GHashTable *var_hash)
00272 {
00273     Account *sx_template_acct;
00274     sx_template_acct = gnc_sx_get_template_transaction_account(sx);
00275     xaccAccountForEachTransaction(sx_template_acct, _get_vars_helper, var_hash);
00276 }
00277 
00278 static void
00279 _set_var_to_random_value(gchar *key, GncSxVariable *var, gpointer unused_user_data)
00280 {
00281     var->value = double_to_gnc_numeric(rand() + 2, 1,
00282                                        GNC_NUMERIC_RND_MASK
00283                                        | GNC_HOW_RND_FLOOR);
00284 }
00285 
00286 void
00287 gnc_sx_randomize_variables(GHashTable *vars)
00288 {
00289     g_hash_table_foreach(vars, (GHFunc)_set_var_to_random_value, NULL);
00290 }
00291 
00292 static void
00293 _clone_sx_var_hash_entry(gpointer key, gpointer value, gpointer user_data)
00294 {
00295     GHashTable *to = (GHashTable*)user_data;
00296     GncSxVariable *to_copy = (GncSxVariable*)value;
00297     GncSxVariable *var = gnc_sx_variable_new_copy(to_copy);
00298     g_hash_table_insert(to, g_strdup(key), var);
00299 }
00300 
00301 static GncSxInstance*
00302 gnc_sx_instance_new(GncSxInstances *parent, GncSxInstanceState state, GDate *date, void *temporal_state, gint sequence_num)
00303 {
00304     GncSxInstance *rtn = g_new0(GncSxInstance, 1);
00305     rtn->parent = parent;
00306     rtn->orig_state = state;
00307     rtn->state = state;
00308     g_date_clear(&rtn->date, 1);
00309     rtn->date = *date;
00310     rtn->temporal_state = gnc_sx_clone_temporal_state(temporal_state);
00311 
00312     if (! parent->variable_names_parsed)
00313     {
00314         parent->variable_names = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)gnc_sx_variable_free);
00315         gnc_sx_get_variables(parent->sx, parent->variable_names);
00316         g_hash_table_foreach(parent->variable_names, (GHFunc)_wipe_parsed_sx_var, NULL);
00317         parent->variable_names_parsed = TRUE;
00318     }
00319 
00320     rtn->variable_bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)gnc_sx_variable_free);
00321     g_hash_table_foreach(parent->variable_names, _clone_sx_var_hash_entry, rtn->variable_bindings);
00322 
00323     {
00324         int instance_i_value;
00325         gnc_numeric i_num;
00326         GncSxVariable *as_var;
00327 
00328         instance_i_value = gnc_sx_get_instance_count(rtn->parent->sx, rtn->temporal_state);
00329         i_num = gnc_numeric_create(instance_i_value, 1);
00330         as_var = gnc_sx_variable_new_full("i", i_num, FALSE);
00331 
00332         g_hash_table_insert(rtn->variable_bindings, g_strdup("i"), as_var);
00333     }
00334 
00335     return rtn;
00336 }
00337 
00338 static gint
00339 _compare_GncSxVariables(gconstpointer a, gconstpointer b)
00340 {
00341     return strcmp(((const GncSxVariable*)a)->name, ((const GncSxVariable*)b)->name);
00342 }
00343 
00344 static void
00345 _build_list_from_hash_elts(gpointer key, gpointer value, gpointer user_data)
00346 {
00347     GList **list = (GList**)user_data;
00348     *list = g_list_insert_sorted(*list, value, _compare_GncSxVariables);
00349 }
00350 
00351 GList *
00352 gnc_sx_instance_get_variables(GncSxInstance *inst)
00353 {
00354     GList *vars = NULL;
00355     g_hash_table_foreach(inst->variable_bindings, _build_list_from_hash_elts, &vars);
00356     return vars;
00357 }
00358 
00359 static GncSxInstances*
00360 _gnc_sx_gen_instances(gpointer *data, gpointer user_data)
00361 {
00362     GncSxInstances *instances = g_new0(GncSxInstances, 1);
00363     SchedXaction *sx = (SchedXaction*)data;
00364     const GDate *range_end = (const GDate*)user_data;
00365     GDate creation_end, remind_end;
00366     GDate cur_date;
00367     void *sequence_ctx;
00368 
00369     instances->sx = sx;
00370 
00371     creation_end = *range_end;
00372     g_date_add_days(&creation_end, xaccSchedXactionGetAdvanceCreation(sx));
00373     remind_end = creation_end;
00374     g_date_add_days(&remind_end, xaccSchedXactionGetAdvanceReminder(sx));
00375 
00376     /* postponed */
00377     {
00378         GList *postponed = gnc_sx_get_defer_instances(sx);
00379         for ( ; postponed != NULL; postponed = postponed->next)
00380         {
00381             GDate inst_date;
00382             int seq_num;
00383             GncSxInstance *inst;
00384 
00385             g_date_clear(&inst_date, 1);
00386             inst_date = xaccSchedXactionGetNextInstance(sx, postponed->data);
00387             seq_num = gnc_sx_get_instance_count(sx, postponed->data);
00388             inst = gnc_sx_instance_new(instances, SX_INSTANCE_STATE_POSTPONED, &inst_date, postponed->data, seq_num);
00389             instances->instance_list = g_list_append(instances->instance_list, inst);
00390         }
00391     }
00392 
00393     /* to-create */
00394     g_date_clear(&cur_date, 1);
00395     sequence_ctx = gnc_sx_create_temporal_state(sx);
00396     cur_date = xaccSchedXactionGetInstanceAfter(sx, &cur_date, sequence_ctx);
00397     instances->next_instance_date = cur_date;
00398     while (g_date_valid(&cur_date) && g_date_compare(&cur_date, &creation_end) <= 0)
00399     {
00400         GncSxInstance *inst;
00401         int seq_num;
00402         seq_num = gnc_sx_get_instance_count(sx, sequence_ctx);
00403         inst = gnc_sx_instance_new(instances, SX_INSTANCE_STATE_TO_CREATE, &cur_date, sequence_ctx, seq_num);
00404         instances->instance_list = g_list_append(instances->instance_list, inst);
00405         gnc_sx_incr_temporal_state(sx, sequence_ctx);
00406         cur_date = xaccSchedXactionGetInstanceAfter(sx, &cur_date, sequence_ctx);
00407     }
00408 
00409     /* reminders */
00410     while (g_date_valid(&cur_date) && g_date_compare(&cur_date, &remind_end) <= 0)
00411     {
00412         GncSxInstance *inst;
00413         int seq_num;
00414         seq_num = gnc_sx_get_instance_count(sx, sequence_ctx);
00415         inst = gnc_sx_instance_new(instances, SX_INSTANCE_STATE_REMINDER, &cur_date, sequence_ctx, seq_num);
00416         instances->instance_list = g_list_append(instances->instance_list, inst);
00417         gnc_sx_incr_temporal_state(sx, sequence_ctx);
00418         cur_date = xaccSchedXactionGetInstanceAfter(sx, &cur_date, sequence_ctx);
00419     }
00420 
00421     return instances;
00422 }
00423 
00424 GncSxInstanceModel*
00425 gnc_sx_get_current_instances(void)
00426 {
00427     GDate now;
00428     g_date_clear(&now, 1);
00429     g_date_set_time_t(&now, time(NULL));
00430     return gnc_sx_get_instances(&now, FALSE);
00431 }
00432 
00433 GncSxInstanceModel*
00434 gnc_sx_get_instances(const GDate *range_end, gboolean include_disabled)
00435 {
00436     GList *all_sxes = gnc_book_get_schedxactions(gnc_get_current_book())->sx_list;
00437     GncSxInstanceModel *instances;
00438 
00439     g_assert(range_end != NULL);
00440     g_assert(g_date_valid(range_end));
00441 
00442     instances = gnc_sx_instance_model_new();
00443     instances->include_disabled = include_disabled;
00444     instances->range_end = *range_end;
00445 
00446     if (include_disabled)
00447     {
00448         instances->sx_instance_list = gnc_g_list_map(all_sxes, (GncGMapFunc)_gnc_sx_gen_instances, (gpointer)range_end);
00449     }
00450     else
00451     {
00452         GList *sx_iter = g_list_first(all_sxes);
00453         GList *enabled_sxes = NULL;
00454 
00455         for (; sx_iter != NULL; sx_iter = sx_iter->next)
00456         {
00457             SchedXaction *sx = (SchedXaction*)sx_iter->data;
00458             if (xaccSchedXactionGetEnabled(sx))
00459             {
00460                 enabled_sxes = g_list_append(enabled_sxes, sx);
00461             }
00462         }
00463         instances->sx_instance_list = gnc_g_list_map(enabled_sxes, (GncGMapFunc)_gnc_sx_gen_instances, (gpointer)range_end);
00464         g_list_free(enabled_sxes);
00465     }
00466 
00467     return instances;
00468 }
00469 static GncSxInstanceModel*
00470 gnc_sx_instance_model_new(void)
00471 {
00472     return GNC_SX_INSTANCE_MODEL(g_object_new(GNC_TYPE_SX_INSTANCE_MODEL, NULL));
00473 }
00474 
00475 GType
00476 gnc_sx_instance_model_get_type(void)
00477 {
00478     static GType type = 0;
00479     if (type == 0)
00480     {
00481         static const GTypeInfo info =
00482         {
00483             sizeof (GncSxInstanceModelClass),
00484             NULL,   /* base_init */
00485             NULL,   /* base_finalize */
00486             (GClassInitFunc)gnc_sx_instance_model_class_init,   /* class_init */
00487             NULL,   /* class_finalize */
00488             NULL,   /* class_data */
00489             sizeof (GncSxInstanceModel),
00490             0,      /* n_preallocs */
00491             (GInstanceInitFunc)gnc_sx_instance_model_init    /* instance_init */
00492         };
00493         type = g_type_register_static (G_TYPE_OBJECT,
00494                                        "GncSxInstanceModelType",
00495                                        &info, 0);
00496     }
00497     return type;
00498 }
00499 
00500 static void
00501 gnc_sx_instance_model_dispose(GObject *object)
00502 {
00503     GncSxInstanceModel *model;
00504     g_return_if_fail(object != NULL);
00505     model = GNC_SX_INSTANCE_MODEL(object);
00506 
00507     g_return_if_fail(!model->disposed);
00508     model->disposed = TRUE;
00509 
00510     qof_event_unregister_handler(model->qof_event_handler_id);
00511 
00512     G_OBJECT_CLASS(parent_class)->dispose(object);
00513 }
00514 
00515 static void
00516 gnc_sx_instance_free(GncSxInstance *instance)
00517 {
00518     gnc_sx_destroy_temporal_state(instance->temporal_state);
00519 
00520     if (instance->variable_bindings != NULL)
00521     {
00522         g_hash_table_destroy(instance->variable_bindings);
00523     }
00524     instance->variable_bindings = NULL;
00525 
00526     g_free(instance);
00527 }
00528 
00529 static void
00530 gnc_sx_instances_free(GncSxInstances *instances)
00531 {
00532     GList *instance_iter;
00533 
00534     if (instances->variable_names != NULL)
00535     {
00536         g_hash_table_destroy(instances->variable_names);
00537     }
00538     instances->variable_names = NULL;
00539 
00540     instances->sx = NULL;
00541 
00542     for (instance_iter = instances->instance_list; instance_iter != NULL; instance_iter = instance_iter->next)
00543     {
00544         GncSxInstance *inst = (GncSxInstance*)instance_iter->data;
00545         gnc_sx_instance_free(inst);
00546     }
00547     g_list_free(instances->instance_list);
00548     instances->instance_list = NULL;
00549 
00550     g_free(instances);
00551 }
00552 
00553 static void
00554 gnc_sx_instance_model_finalize (GObject *object)
00555 {
00556     GncSxInstanceModel *model;
00557     GList *sx_list_iter;
00558 
00559     g_return_if_fail(object != NULL);
00560 
00561     model = GNC_SX_INSTANCE_MODEL(object);
00562     for (sx_list_iter = model->sx_instance_list; sx_list_iter != NULL; sx_list_iter = sx_list_iter->next)
00563     {
00564         GncSxInstances *instances = (GncSxInstances*)sx_list_iter->data;
00565         gnc_sx_instances_free(instances);
00566     }
00567     g_list_free(model->sx_instance_list);
00568     model->sx_instance_list = NULL;
00569 
00570     G_OBJECT_CLASS(parent_class)->finalize(object);
00571 }
00572 
00573 static void
00574 gnc_sx_instance_model_class_init (GncSxInstanceModelClass *klass)
00575 {
00576     GObjectClass *object_class = G_OBJECT_CLASS(klass);
00577 
00578     parent_class = g_type_class_peek_parent(klass);
00579 
00580     object_class->dispose = gnc_sx_instance_model_dispose;
00581     object_class->finalize = gnc_sx_instance_model_finalize;
00582 
00583     klass->removing_signal_id =
00584         g_signal_new("removing",
00585                      GNC_TYPE_SX_INSTANCE_MODEL,
00586                      G_SIGNAL_RUN_FIRST,
00587                      0, /* class offset */
00588                      NULL, /* accumulator */
00589                      NULL, /* accum data */
00590                      g_cclosure_marshal_VOID__POINTER,
00591                      G_TYPE_NONE,
00592                      1,
00593                      G_TYPE_POINTER);
00594 
00595     klass->updated_signal_id =
00596         g_signal_new("updated",
00597                      GNC_TYPE_SX_INSTANCE_MODEL,
00598                      G_SIGNAL_RUN_FIRST,
00599                      0, /* class offset */
00600                      NULL, /* accumulator */
00601                      NULL, /* accum data */
00602                      g_cclosure_marshal_VOID__POINTER,
00603                      G_TYPE_NONE,
00604                      1,
00605                      G_TYPE_POINTER);
00606 
00607     klass->added_signal_id =
00608         g_signal_new("added",
00609                      GNC_TYPE_SX_INSTANCE_MODEL,
00610                      G_SIGNAL_RUN_FIRST,
00611                      0, /* class offset */
00612                      NULL, /* accumulator */
00613                      NULL, /* accum data */
00614                      g_cclosure_marshal_VOID__POINTER,
00615                      G_TYPE_NONE,
00616                      1,
00617                      G_TYPE_POINTER);
00618 }
00619 
00620 static void
00621 gnc_sx_instance_model_init(GTypeInstance *instance, gpointer klass)
00622 {
00623     GncSxInstanceModel *inst = (GncSxInstanceModel*)instance;
00624 
00625     g_date_clear(&inst->range_end, 1);
00626     inst->sx_instance_list = NULL;
00627     inst->qof_event_handler_id = qof_event_register_handler(_gnc_sx_instance_event_handler, inst);
00628 }
00629 
00630 static gint
00631 _gnc_sx_instance_find_by_sx(GncSxInstances *in_list_instances, SchedXaction *sx_to_find)
00632 {
00633     if (in_list_instances->sx == sx_to_find)
00634         return 0;
00635     return -1;
00636 }
00637 
00638 static void
00639 _gnc_sx_instance_event_handler(QofInstance *ent, QofEventId event_type, gpointer user_data, gpointer evt_data)
00640 {
00641     GncSxInstanceModel *instances = GNC_SX_INSTANCE_MODEL(user_data);
00642 
00643     /* selection rules {
00644     //   (gnc_collection_get_schedxaction_list(book), GNC_EVENT_ITEM_ADDED)
00645     //   (gnc_collection_get_schedxaction_list(book), GNC_EVENT_ITEM_REMOVED)
00646     //   (GNC_IS_SX(ent), QOF_EVENT_MODIFIED)
00647     // } */
00648     if (!(GNC_IS_SX(ent) || GNC_IS_SXES(ent)))
00649         return;
00650 
00651     if (GNC_IS_SX(ent))
00652     {
00653         SchedXaction *sx;
00654         gboolean sx_is_in_model = FALSE;
00655 
00656         sx = GNC_SX(ent);
00657         // only send `updated` if it's actually in the model
00658         sx_is_in_model = (g_list_find_custom(instances->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx) != NULL);
00659         if (event_type & QOF_EVENT_MODIFY)
00660         {
00661             if (sx_is_in_model)
00662             {
00663                 if (instances->include_disabled || xaccSchedXactionGetEnabled(sx))
00664                 {
00665                     g_signal_emit_by_name(instances, "updated", (gpointer)sx);
00666                 }
00667                 else
00668                 {
00669                     /* the sx was enabled but is now disabled */
00670                     g_signal_emit_by_name(instances, "removing", (gpointer)sx);
00671                 }
00672             }
00673             else
00674             {
00675                 /* determine if this is a legitimate SX or just a "one-off" / being created */
00676                 GList *all_sxes = gnc_book_get_schedxactions(gnc_get_current_book())->sx_list;
00677                 if (g_list_find(all_sxes, sx) && (!instances->include_disabled && xaccSchedXactionGetEnabled(sx)))
00678                 {
00679                     /* it's moved from disabled to enabled, add the instances */
00680                     instances->sx_instance_list
00681                     = g_list_append(instances->sx_instance_list,
00682                                     _gnc_sx_gen_instances((gpointer)sx, (gpointer) & instances->range_end));
00683                     g_signal_emit_by_name(instances, "added", (gpointer)sx);
00684                 }
00685             }
00686         }
00687         /* else { unsupported event type; ignore } */
00688     }
00689     else if (GNC_IS_SXES(ent))
00690     {
00691         SchedXactions *sxes = GNC_SXES(ent);
00692         SchedXaction *sx = GNC_SX(evt_data);
00693 
00694         sxes = NULL;
00695         if (event_type & GNC_EVENT_ITEM_REMOVED)
00696         {
00697             GList *instances_link;
00698             instances_link = g_list_find_custom(instances->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
00699             if (instances_link != NULL)
00700             {
00701                 g_signal_emit_by_name(instances, "removing", (gpointer)sx);
00702             }
00703             else if (instances->include_disabled)
00704             {
00705                 g_warning("could not remove instances that do not exist in the model");
00706             }
00707         }
00708         else if (event_type & GNC_EVENT_ITEM_ADDED)
00709         {
00710             if (instances->include_disabled || xaccSchedXactionGetEnabled(sx))
00711             {
00712                 /* generate instances, add to instance list, emit update. */
00713                 instances->sx_instance_list
00714                 = g_list_append(instances->sx_instance_list,
00715                                 _gnc_sx_gen_instances((gpointer)sx, (gpointer) & instances->range_end));
00716                 g_signal_emit_by_name(instances, "added", (gpointer)sx);
00717             }
00718         }
00719         /* else { g_critical("unsupported event type [%d]\n", event_type); } */
00720     }
00721 }
00722 
00723 typedef struct _HashListPair
00724 {
00725     GHashTable *hash;
00726     GList *list;
00727 } HashListPair;
00728 
00729 static void
00730 _find_unreferenced_vars(gchar *key,
00731                         gpointer value,
00732                         HashListPair *cb_pair)
00733 {
00734     if (!g_hash_table_lookup_extended(cb_pair->hash, key, NULL, NULL))
00735     {
00736         g_debug("variable [%s] not found", key);
00737         cb_pair->list = g_list_append(cb_pair->list, key);
00738     }
00739 }
00740 
00741 void
00742 gnc_sx_instance_model_update_sx_instances(GncSxInstanceModel *model, SchedXaction *sx)
00743 {
00744     GncSxInstances *existing, *new_instances;
00745     GList *link;
00746 
00747     link = g_list_find_custom(model->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
00748     if (link == NULL)
00749     {
00750         g_critical("couldn't find sx [%p]\n", sx);
00751         return;
00752     }
00753 
00754     // merge the new instance data into the existing structure, mutating as little as possible.
00755     existing = (GncSxInstances*)link->data;
00756     new_instances = _gnc_sx_gen_instances((gpointer)sx, &model->range_end);
00757     existing->sx = new_instances->sx;
00758     existing->next_instance_date = new_instances->next_instance_date;
00759     {
00760         GList *existing_iter, *new_iter;
00761         gboolean existing_remain, new_remain;
00762 
00763         // step through the lists pairwise, and retain the existing
00764         // instance if the dates align, as soon as they don't stop and
00765         // cleanup.
00766         existing_iter = existing->instance_list;
00767         new_iter = new_instances->instance_list;
00768         for (; existing_iter != NULL && new_iter != NULL; existing_iter = existing_iter->next, new_iter = new_iter->next)
00769         {
00770             GncSxInstance *existing_inst, *new_inst;
00771             gboolean same_instance_date;
00772             existing_inst = (GncSxInstance*)existing_iter->data;
00773             new_inst = (GncSxInstance*)new_iter->data;
00774 
00775             same_instance_date = g_date_compare(&existing_inst->date, &new_inst->date) == 0;
00776             if (!same_instance_date)
00777                 break;
00778         }
00779 
00780         existing_remain = (existing_iter != NULL);
00781         new_remain = (new_iter != NULL);
00782 
00783         if (existing_remain)
00784         {
00785             // delete excess
00786             gnc_g_list_cut(&existing->instance_list, existing_iter);
00787             g_list_foreach(existing_iter, (GFunc)gnc_sx_instance_free, NULL);
00788         }
00789 
00790         if (new_remain)
00791         {
00792             // append new
00793             GList *new_iter_iter;
00794             gnc_g_list_cut(&new_instances->instance_list, new_iter);
00795 
00796             for (new_iter_iter = new_iter; new_iter_iter != NULL; new_iter_iter = new_iter_iter->next)
00797             {
00798                 GncSxInstance *inst = (GncSxInstance*)new_iter_iter->data;
00799                 inst->parent = existing;
00800                 existing->instance_list = g_list_append(existing->instance_list, new_iter_iter->data);
00801             }
00802             g_list_free(new_iter);
00803         }
00804     }
00805 
00806     // handle variables
00807     {
00808         HashListPair removed_cb_data, added_cb_data;
00809         GList *removed_var_names = NULL, *added_var_names = NULL;
00810         GList *inst_iter = NULL;
00811 
00812         removed_cb_data.hash = new_instances->variable_names;
00813         removed_cb_data.list = NULL;
00814         g_hash_table_foreach(existing->variable_names, (GHFunc)_find_unreferenced_vars, &removed_cb_data);
00815         removed_var_names = removed_cb_data.list;
00816         g_debug("%d removed variables", g_list_length(removed_var_names));
00817 
00818         added_cb_data.hash = existing->variable_names;
00819         added_cb_data.list = NULL;
00820         g_hash_table_foreach(new_instances->variable_names, (GHFunc)_find_unreferenced_vars, &added_cb_data);
00821         added_var_names = added_cb_data.list;
00822         g_debug("%d added variables", g_list_length(added_var_names));
00823 
00824         if (existing->variable_names != NULL)
00825         {
00826             g_hash_table_destroy(existing->variable_names);
00827         }
00828         existing->variable_names = new_instances->variable_names;
00829         new_instances->variable_names = NULL;
00830 
00831         for (inst_iter = existing->instance_list; inst_iter != NULL; inst_iter = inst_iter->next)
00832         {
00833             GList *var_iter;
00834             GncSxInstance *inst = (GncSxInstance*)inst_iter->data;
00835 
00836             for (var_iter = removed_var_names; var_iter != NULL; var_iter = var_iter->next)
00837             {
00838                 gchar *to_remove_key = (gchar*)var_iter->data;
00839                 g_hash_table_remove(inst->variable_bindings, to_remove_key);
00840             }
00841 
00842             for (var_iter = added_var_names; var_iter != NULL; var_iter = var_iter->next)
00843             {
00844                 gchar *to_add_key = (gchar*)var_iter->data;
00845                 if (!g_hash_table_lookup_extended(
00846                             inst->variable_bindings, to_add_key, NULL, NULL))
00847                 {
00848                     GncSxVariable *parent_var
00849                     = g_hash_table_lookup(existing->variable_names, to_add_key);
00850                     GncSxVariable *var_copy;
00851 
00852                     g_assert(parent_var != NULL);
00853                     var_copy = gnc_sx_variable_new_copy(parent_var);
00854                     g_hash_table_insert(inst->variable_bindings, g_strdup(to_add_key), var_copy);
00855                 }
00856             }
00857         }
00858     }
00859     gnc_sx_instances_free(new_instances);
00860 }
00861 
00862 void
00863 gnc_sx_instance_model_remove_sx_instances(GncSxInstanceModel *model, SchedXaction *sx)
00864 {
00865     GList *instance_link = NULL;
00866 
00867     instance_link = g_list_find_custom(model->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
00868     if (instance_link == NULL)
00869     {
00870         g_warning("instance not found!\n");
00871         return;
00872     }
00873 
00874     model->sx_instance_list = g_list_remove_link(model->sx_instance_list, instance_link);
00875     gnc_sx_instances_free((GncSxInstances*)instance_link->data);
00876 }
00877 
00878 static void
00879 increment_sx_state(GncSxInstance *inst, GDate **last_occur_date, int *instance_count, int *remain_occur_count)
00880 {
00881     if (!g_date_valid(*last_occur_date)
00882             || (g_date_valid(*last_occur_date)
00883                 && g_date_compare(*last_occur_date, &inst->date) <= 0))
00884     {
00885         *last_occur_date = &inst->date;
00886     }
00887 
00888     *instance_count = gnc_sx_get_instance_count(inst->parent->sx, inst->temporal_state) + 1;
00889 
00890     if (*remain_occur_count > 0)
00891     {
00892         *remain_occur_count -= 1;
00893     }
00894 }
00895 
00896 typedef struct _SxTxnCreationData
00897 {
00898     GncSxInstance *instance;
00899     GList **created_txn_guids;
00900     GList **creation_errors;
00901 } SxTxnCreationData;
00902 
00903 static gboolean
00904 _get_template_split_account(const SchedXaction* sx, const Split *template_split, Account **split_acct, GList **creation_errors)
00905 {
00906     GncGUID *acct_guid;
00907     kvp_frame *split_kvpf;
00908     kvp_value *kvp_val;
00909 
00910     split_kvpf = xaccSplitGetSlots(template_split);
00911     /* contains the guid of the split's actual account. */
00912     kvp_val = kvp_frame_get_slot_path(split_kvpf,
00913                                       GNC_SX_ID,
00914                                       GNC_SX_ACCOUNT,
00915                                       NULL);
00916     if (kvp_val == NULL)
00917     {
00918         GString *err = g_string_new("");
00919         g_string_printf(err, "Null account kvp value for SX [%s], cancelling creation.",
00920                         xaccSchedXactionGetName(sx));
00921         g_critical("%s", err->str);
00922         if (creation_errors != NULL)
00923             *creation_errors = g_list_append(*creation_errors, err);
00924         else
00925             g_string_free(err, TRUE);
00926         return FALSE;
00927     }
00928     acct_guid = kvp_value_get_guid( kvp_val );
00929     *split_acct = xaccAccountLookup(acct_guid, gnc_get_current_book());
00930     if (*split_acct == NULL)
00931     {
00932         char guid_str[GUID_ENCODING_LENGTH+1];
00933         GString *err;
00934         guid_to_string_buff((const GncGUID*)acct_guid, guid_str);
00935         err = g_string_new("");
00936         g_string_printf(err, "Unknown account for guid [%s], cancelling SX [%s] creation.",
00937                         guid_str, xaccSchedXactionGetName(sx));
00938         g_critical("%s", err->str);
00939         if (creation_errors != NULL)
00940             *creation_errors = g_list_append(*creation_errors, err);
00941         else
00942             g_string_free(err, TRUE);
00943         return FALSE;
00944     }
00945 
00946     return TRUE;
00947 }
00948 
00949 static void
00950 _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)
00951 {
00952     kvp_frame *split_kvpf;
00953     kvp_value *kvp_val;
00954     char *formula_str, *parseErrorLoc;
00955 
00956     split_kvpf = xaccSplitGetSlots(template_split);
00957 
00958     /* First look up the gnc_numeric value in the template split */
00959     kvp_val = kvp_frame_get_slot_path(split_kvpf,
00960                                       GNC_SX_ID,
00961                                       numeric_key,
00962                                       NULL);
00963     *numeric = kvp_value_get_numeric(kvp_val);
00964     if ((gnc_numeric_check(*numeric) == GNC_ERROR_OK)
00965             && !gnc_numeric_zero_p(*numeric))
00966     {
00967         /* Already a valid non-zero result? Then return and don't
00968          * parse the string. Luckily we avoid any locale problems with
00969          * decimal points here! Phew. */
00970         return;
00971     }
00972 
00973     kvp_val = kvp_frame_get_slot_path(split_kvpf,
00974                                       GNC_SX_ID,
00975                                       formula_key,
00976                                       NULL);
00977     formula_str = kvp_value_get_string(kvp_val);
00978     if (formula_str != NULL && strlen(formula_str) != 0)
00979     {
00980         GHashTable *parser_vars = NULL;
00981         if (variable_bindings)
00982         {
00983             parser_vars = gnc_sx_instance_get_variables_for_parser(variable_bindings);
00984         }
00985         if (!gnc_exp_parser_parse_separate_vars(formula_str,
00986                                                 numeric,
00987                                                 &parseErrorLoc,
00988                                                 parser_vars))
00989         {
00990             GString *err = g_string_new("");
00991             g_string_printf(err, "Error parsing SX [%s] key [%s]=formula [%s] at [%s]: %s",
00992                             xaccSchedXactionGetName(sx),
00993                             formula_key,
00994                             formula_str,
00995                             parseErrorLoc,
00996                             gnc_exp_parser_error_string());
00997             g_critical("%s", err->str);
00998             if (creation_errors != NULL)
00999                 *creation_errors = g_list_append(*creation_errors, err);
01000             else
01001                 g_string_free(err, TRUE);
01002         }
01003 
01004         if (parser_vars != NULL)
01005         {
01006             g_hash_table_destroy(parser_vars);
01007         }
01008     }
01009 }
01010 
01011 static void
01012 _get_credit_formula_value(GncSxInstance *instance, const Split *template_split, gnc_numeric *credit_num, GList **creation_errors)
01013 {
01014     _get_sx_formula_value(instance->parent->sx, template_split, credit_num, creation_errors, GNC_SX_CREDIT_FORMULA, GNC_SX_CREDIT_NUMERIC, instance->variable_bindings);
01015 }
01016 
01017 static void
01018 _get_debit_formula_value(GncSxInstance *instance, const Split *template_split, gnc_numeric *debit_num, GList **creation_errors)
01019 {
01020     _get_sx_formula_value(instance->parent->sx, template_split, debit_num, creation_errors, GNC_SX_DEBIT_FORMULA, GNC_SX_DEBIT_NUMERIC, instance->variable_bindings);
01021 }
01022 
01023 static gboolean
01024 create_each_transaction_helper(Transaction *template_txn, void *user_data)
01025 {
01026     Transaction *new_txn;
01027     GList *txn_splits, *template_splits;
01028     Split *copying_split;
01029     gnc_commodity *first_cmdty = NULL;
01030     gboolean err_flag = FALSE;
01031     SxTxnCreationData *creation_data;
01032 
01033     creation_data = (SxTxnCreationData*)user_data;
01034 
01035     /* FIXME: In general, this should [correctly] deal with errors such
01036        as not finding the approrpiate Accounts and not being able to
01037        parse the formula|credit/debit strings. */
01038 
01039     new_txn = xaccTransClone(template_txn);
01040     xaccTransBeginEdit(new_txn);
01041 
01042     g_debug("creating template txn desc [%s] for sx [%s]",
01043             xaccTransGetDescription(new_txn),
01044             xaccSchedXactionGetName(creation_data->instance->parent->sx));
01045 
01046     /* clear any copied KVP data */
01047     qof_instance_set_slots(QOF_INSTANCE(new_txn), kvp_frame_new());
01048 
01049     /* Bug#500427: copy the notes, if any */
01050     if (xaccTransGetNotes(template_txn) != NULL)
01051     {
01052         xaccTransSetNotes(new_txn, g_strdup(xaccTransGetNotes(template_txn)));
01053     }
01054 
01055     xaccTransSetDate(new_txn,
01056                      g_date_get_day(&creation_data->instance->date),
01057                      g_date_get_month(&creation_data->instance->date),
01058                      g_date_get_year(&creation_data->instance->date));
01059 
01060     /* the accounts and amounts are in the kvp_frames of the splits. */
01061     template_splits = xaccTransGetSplitList(template_txn);
01062     txn_splits = xaccTransGetSplitList(new_txn);
01063     if ((template_splits == NULL) || (txn_splits == NULL))
01064     {
01065         g_critical("transaction w/o splits for sx [%s]",
01066                    xaccSchedXactionGetName(creation_data->instance->parent->sx));
01067         xaccTransDestroy(new_txn);
01068         xaccTransCommitEdit(new_txn);
01069         return FALSE;
01070     }
01071 
01072     for (;
01073             txn_splits && template_splits;
01074             txn_splits = txn_splits->next, template_splits = template_splits->next)
01075     {
01076         const Split *template_split;
01077         Account *split_acct;
01078         gnc_commodity *split_cmdty = NULL;
01079 
01080         /* FIXME: Ick.  This assumes that the split lists will be ordered
01081            identically. :( They are, but we'd rather not have to count on
01082            it. --jsled */
01083         template_split = (Split*)template_splits->data;
01084         copying_split = (Split*)txn_splits->data;
01085 
01086         if (!_get_template_split_account(creation_data->instance->parent->sx, template_split, &split_acct, creation_data->creation_errors))
01087         {
01088             err_flag = TRUE;
01089             break;
01090         }
01091 
01092         /* clear out any copied Split frame data. */
01093         qof_instance_set_slots(QOF_INSTANCE(copying_split), kvp_frame_new());
01094 
01095         split_cmdty = xaccAccountGetCommodity(split_acct);
01096         if (first_cmdty == NULL)
01097         {
01098             first_cmdty = split_cmdty;
01099             xaccTransSetCurrency(new_txn, first_cmdty);
01100         }
01101 
01102         xaccSplitSetAccount(copying_split, split_acct);
01103 
01104         {
01105             gnc_numeric credit_num, debit_num, final;
01106             gint gncn_error;
01107 
01108             credit_num = gnc_numeric_zero();
01109             debit_num = gnc_numeric_zero();
01110 
01111             _get_credit_formula_value(creation_data->instance, template_split, &credit_num, creation_data->creation_errors);
01112             _get_debit_formula_value(creation_data->instance, template_split, &debit_num, creation_data->creation_errors);
01113 
01114             final = gnc_numeric_sub_fixed( debit_num, credit_num );
01115 
01116             gncn_error = gnc_numeric_check(final);
01117             if (gncn_error != GNC_ERROR_OK)
01118             {
01119                 GString *err = g_string_new("");
01120                 g_string_printf(err, "error %d in SX [%s] final gnc_numeric value, using 0 instead",
01121                                 gncn_error, xaccSchedXactionGetName(creation_data->instance->parent->sx));
01122                 g_critical("%s", err->str);
01123                 if (creation_data->creation_errors != NULL)
01124                     *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
01125                 else
01126                     g_string_free(err, TRUE);
01127                 final = gnc_numeric_zero();
01128             }
01129 
01130             xaccSplitSetValue(copying_split, final);
01131             if (! gnc_commodity_equal(split_cmdty, first_cmdty))
01132             {
01133                 GString *exchange_rate_var_name = g_string_sized_new(16);
01134                 GncSxVariable *exchange_rate_var;
01135                 gnc_numeric exchange_rate, amt;
01136 
01137                 /*
01138                   GNCPriceDB *price_db = gnc_pricedb_get_db(gnc_get_current_book());
01139                   GNCPrice *price;
01140 
01141                   price = gnc_pricedb_lookup_latest(price_db, first_cmdty, split_cmdty);
01142                   if (price == NULL)
01143                   {
01144                   price = gnc_pricedb_lookup_latest(price_db, split_cmdty, first_cmdty);
01145                   if (price == NULL)
01146                   {
01147                   GString *err = g_string_new("");
01148                   g_string_printf(err, "could not find pricedb entry for commodity-pair (%s, %s).",
01149                   gnc_commodity_get_mnemonic(first_cmdty),
01150                   gnc_commodity_get_mnemonic(split_cmdty));
01151                   exchange = gnc_numeric_create(1, 1);
01152                   *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
01153 
01154                   }
01155                   else
01156                   {
01157                   exchange = gnc_numeric_div(gnc_numeric_create(1,1),
01158                   gnc_price_get_value(price),
01159                   1000, GNC_HOW_RND_ROUND_HALF_UP);
01160                   }
01161                   }
01162                   else
01163                   {
01164                   exchange = gnc_price_get_value(price);
01165                   }
01166                 */
01167 
01168                 exchange_rate = gnc_numeric_zero();
01169                 g_string_printf(exchange_rate_var_name, "%s -> %s",
01170                                 gnc_commodity_get_mnemonic(split_cmdty),
01171                                 gnc_commodity_get_mnemonic(first_cmdty));
01172                 exchange_rate_var = (GncSxVariable*)g_hash_table_lookup(creation_data->instance->variable_bindings,
01173                                     exchange_rate_var_name->str);
01174                 if (exchange_rate_var != NULL)
01175                 {
01176                     exchange_rate = exchange_rate_var->value;
01177                 }
01178                 g_string_free(exchange_rate_var_name, TRUE);
01179 
01180                 amt = gnc_numeric_mul(final, exchange_rate, 1000, GNC_HOW_RND_ROUND_HALF_UP);
01181                 xaccSplitSetAmount(copying_split, amt);
01182             }
01183 
01184             xaccSplitScrub(copying_split);
01185         }
01186     }
01187 
01188     if (err_flag)
01189     {
01190         g_critical("new transaction creation sx [%s]",
01191                    xaccSchedXactionGetName(creation_data->instance->parent->sx));
01192         xaccTransDestroy(new_txn);
01193         xaccTransCommitEdit(new_txn);
01194         return FALSE;
01195     }
01196 
01197     {
01198         kvp_frame *txn_frame;
01199         txn_frame = xaccTransGetSlots(new_txn);
01200         kvp_frame_set_guid(txn_frame, "from-sched-xaction", xaccSchedXactionGetGUID(creation_data->instance->parent->sx));
01201     }
01202 
01203     xaccTransCommitEdit(new_txn);
01204 
01205     if (creation_data->created_txn_guids != NULL)
01206     {
01207         *creation_data->created_txn_guids
01208         = g_list_append(*(creation_data->created_txn_guids), (gpointer)xaccTransGetGUID(new_txn));
01209     }
01210 
01211     return FALSE;
01212 }
01213 
01214 static void
01215 create_transactions_for_instance(GncSxInstance *instance, GList **created_txn_guids, GList **creation_errors)
01216 {
01217     SxTxnCreationData creation_data;
01218     Account *sx_template_account;
01219 
01220     sx_template_account = gnc_sx_get_template_transaction_account(instance->parent->sx);
01221 
01222     creation_data.instance = instance;
01223     creation_data.created_txn_guids = created_txn_guids;
01224     creation_data.creation_errors = creation_errors;
01225 
01226     xaccAccountForEachTransaction(sx_template_account,
01227                                   create_each_transaction_helper,
01228                                   &creation_data);
01229 }
01230 
01231 void
01232 gnc_sx_instance_model_effect_change(GncSxInstanceModel *model,
01233                                     gboolean auto_create_only,
01234                                     GList **created_transaction_guids,
01235                                     GList **creation_errors)
01236 {
01237     GList *iter;
01238     for (iter = model->sx_instance_list; iter != NULL; iter = iter->next)
01239     {
01240         GList *instance_iter;
01241         GncSxInstances *instances = (GncSxInstances*)iter->data;
01242         GDate *last_occur_date;
01243         gint instance_count = 0;
01244         gint remain_occur_count = 0;
01245 
01246         // If there are no instances, then skip; specifically, skip
01247         // re-setting SchedXaction fields, which will dirty the book
01248         // spuriously.
01249         if (g_list_length(instances->instance_list) == 0)
01250             continue;
01251 
01252         last_occur_date = (GDate*) xaccSchedXactionGetLastOccurDate(instances->sx);
01253         instance_count = gnc_sx_get_instance_count(instances->sx, NULL);
01254         remain_occur_count = xaccSchedXactionGetRemOccur(instances->sx);
01255 
01256         for (instance_iter = instances->instance_list; instance_iter != NULL; instance_iter = instance_iter->next)
01257         {
01258             GncSxInstance *inst = (GncSxInstance*)instance_iter->data;
01259             gboolean sx_is_auto_create;
01260 
01261             xaccSchedXactionGetAutoCreate(inst->parent->sx, &sx_is_auto_create, NULL);
01262             if (auto_create_only && !sx_is_auto_create)
01263             {
01264                 if (inst->state != SX_INSTANCE_STATE_TO_CREATE)
01265                 {
01266                     break;
01267                 }
01268                 continue;
01269             }
01270 
01271             if (inst->orig_state == SX_INSTANCE_STATE_POSTPONED
01272                     && inst->state != SX_INSTANCE_STATE_POSTPONED)
01273             {
01274                 // remove from postponed list
01275                 g_assert(inst->temporal_state != NULL);
01276                 gnc_sx_remove_defer_instance(inst->parent->sx, inst->temporal_state);
01277             }
01278 
01279             switch (inst->state)
01280             {
01281             case SX_INSTANCE_STATE_CREATED:
01282                 // nop: we've already processed this.
01283                 break;
01284             case SX_INSTANCE_STATE_IGNORED:
01285                 increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
01286                 break;
01287             case SX_INSTANCE_STATE_POSTPONED:
01288                 if (inst->orig_state != SX_INSTANCE_STATE_POSTPONED)
01289                 {
01290                     gnc_sx_add_defer_instance(instances->sx, inst->temporal_state);
01291                 }
01292                 increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
01293                 break;
01294             case SX_INSTANCE_STATE_TO_CREATE:
01295                 create_transactions_for_instance(inst, created_transaction_guids, creation_errors);
01296                 increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
01297                 gnc_sx_instance_model_change_instance_state(model, inst, SX_INSTANCE_STATE_CREATED);
01298                 break;
01299             case SX_INSTANCE_STATE_REMINDER:
01300                 // do nothing
01301                 // assert no non-remind instances after this?
01302                 break;
01303             default:
01304                 g_assert_not_reached();
01305                 break;
01306             }
01307         }
01308 
01309         xaccSchedXactionSetLastOccurDate(instances->sx, last_occur_date);
01310         gnc_sx_set_instance_count(instances->sx, instance_count);
01311         xaccSchedXactionSetRemOccur(instances->sx, remain_occur_count);
01312     }
01313 }
01314 
01315 void
01316 gnc_sx_instance_model_change_instance_state(GncSxInstanceModel *model,
01317         GncSxInstance *instance,
01318         GncSxInstanceState new_state)
01319 {
01320     if (instance->state == new_state)
01321         return;
01322 
01323     instance->state = new_state;
01324 
01325     // ensure 'remind' constraints are met:
01326     {
01327         GList *inst_iter;
01328         inst_iter = g_list_find(instance->parent->instance_list, instance);
01329         g_assert(inst_iter != NULL);
01330         if (instance->state != SX_INSTANCE_STATE_REMINDER)
01331         {
01332             // iterate backwards, making sure reminders are changed to 'postponed'
01333             for (inst_iter = inst_iter->prev; inst_iter != NULL; inst_iter = inst_iter->prev)
01334             {
01335                 GncSxInstance *prev_inst = (GncSxInstance*)inst_iter->data;
01336                 if (prev_inst->state != SX_INSTANCE_STATE_REMINDER)
01337                     continue;
01338                 prev_inst->state = SX_INSTANCE_STATE_POSTPONED;
01339             }
01340         }
01341         else
01342         {
01343             // iterate forward, make sure transactions are set to 'remind'
01344             for (inst_iter = inst_iter->next; inst_iter != NULL; inst_iter = inst_iter->next)
01345             {
01346                 GncSxInstance *next_inst = (GncSxInstance*)inst_iter->data;
01347                 if (next_inst->state == SX_INSTANCE_STATE_REMINDER)
01348                     continue;
01349                 next_inst->state = SX_INSTANCE_STATE_REMINDER;
01350             }
01351         }
01352     }
01353 
01354     g_signal_emit_by_name(model, "updated", (gpointer)instance->parent->sx);
01355 }
01356 
01357 void
01358 gnc_sx_instance_model_set_variable(GncSxInstanceModel *model,
01359                                    GncSxInstance *instance,
01360                                    GncSxVariable *variable,
01361                                    gnc_numeric *new_value)
01362 {
01363 
01364     if (gnc_numeric_equal(variable->value, *new_value))
01365         return;
01366     variable->value = *new_value;
01367     g_signal_emit_by_name(model, "updated", (gpointer)instance->parent->sx);
01368 }
01369 
01370 static void
01371 _list_from_hash_elts(gpointer key, gpointer value, GList **result_list)
01372 {
01373     *result_list = g_list_append(*result_list, value);
01374 }
01375 
01376 GList*
01377 gnc_sx_instance_model_check_variables(GncSxInstanceModel *model)
01378 {
01379     GList *rtn = NULL;
01380     GList *sx_iter, *inst_iter, *var_list = NULL, *var_iter;
01381 
01382     for (sx_iter = model->sx_instance_list; sx_iter != NULL; sx_iter = sx_iter->next)
01383     {
01384         GncSxInstances *instances = (GncSxInstances*)sx_iter->data;
01385         for (inst_iter = instances->instance_list; inst_iter != NULL; inst_iter = inst_iter->next)
01386         {
01387             GncSxInstance *inst = (GncSxInstance*)inst_iter->data;
01388 
01389             if (inst->state != SX_INSTANCE_STATE_TO_CREATE)
01390                 continue;
01391 
01392             g_hash_table_foreach(inst->variable_bindings, (GHFunc)_list_from_hash_elts, &var_list);
01393             for (var_iter = var_list; var_iter != NULL; var_iter = var_iter->next)
01394             {
01395                 GncSxVariable *var = (GncSxVariable*)var_iter->data;
01396                 if (gnc_numeric_check(var->value) != GNC_ERROR_OK)
01397                 {
01398                     GncSxVariableNeeded *need = g_new0(GncSxVariableNeeded, 1);
01399                     need->instance = inst;
01400                     need->variable = var;
01401                     rtn = g_list_append(rtn, need);
01402                 }
01403             }
01404             g_list_free(var_list);
01405             var_list = NULL;
01406         }
01407     }
01408     return rtn;
01409 }
01410 
01411 void
01412 gnc_sx_instance_model_summarize(GncSxInstanceModel *model, GncSxSummary *summary)
01413 {
01414     GList *sx_iter, *inst_iter;
01415 
01416     g_return_if_fail(model != NULL);
01417     g_return_if_fail(summary != NULL);
01418 
01419     summary->need_dialog = FALSE;
01420     summary->num_instances = 0;
01421     summary->num_to_create_instances = 0;
01422     summary->num_auto_create_instances = 0;
01423     summary->num_auto_create_no_notify_instances = 0;
01424 
01425     for (sx_iter = model->sx_instance_list; sx_iter != NULL; sx_iter = sx_iter->next)
01426     {
01427         GncSxInstances *instances = (GncSxInstances*)sx_iter->data;
01428         gboolean sx_is_auto_create = FALSE, sx_notify = FALSE;
01429         xaccSchedXactionGetAutoCreate(instances->sx, &sx_is_auto_create, &sx_notify);
01430         for (inst_iter = instances->instance_list; inst_iter != NULL; inst_iter = inst_iter->next)
01431         {
01432             GncSxInstance *inst = (GncSxInstance*)inst_iter->data;
01433             summary->num_instances++;
01434 
01435             if (inst->state == SX_INSTANCE_STATE_TO_CREATE)
01436             {
01437                 if (sx_is_auto_create)
01438                 {
01439                     if (!sx_notify)
01440                     {
01441                         summary->num_auto_create_no_notify_instances++;
01442                     }
01443                     else
01444                     {
01445                         summary->num_auto_create_instances++;
01446                     }
01447                 }
01448                 else
01449                 {
01450                     summary->num_to_create_instances++;
01451                 }
01452             }
01453         }
01454     }
01455 
01456     // if all the instances are 'auto-create, no-notify', then we don't need
01457     // the dialog.
01458     summary->need_dialog
01459     = (summary->num_instances != 0
01460        && summary->num_auto_create_no_notify_instances != summary->num_instances);
01461 }
01462 
01463 void
01464 gnc_sx_summary_print(const GncSxSummary *summary)
01465 {
01466     g_message("num_instances: %d", summary->num_instances);
01467     g_message("num_to_create: %d", summary->num_to_create_instances);
01468     g_message("num_auto_create_instances: %d", summary->num_auto_create_instances);
01469     g_message("num_auto_create_no_notify_instances: %d", summary->num_auto_create_no_notify_instances);
01470     g_message("need dialog? %s", summary->need_dialog ? "true" : "false");
01471 }
01472 
01473 static void gnc_numeric_free(gpointer data)
01474 {
01475     gnc_numeric *p = (gnc_numeric*) data;
01476     g_free(p);
01477 }
01478 
01479 GHashTable* gnc_g_hash_new_guid_numeric()
01480 {
01481     return g_hash_table_new_full (guid_hash_to_guint, guid_g_hash_table_equal,
01482                                   NULL, gnc_numeric_free);
01483 }
01484 
01485 typedef struct
01486 {
01487     GHashTable *hash;
01488     GList **creation_errors;
01489     const SchedXaction *sx;
01490     gnc_numeric count;
01491 } SxCashflowData;
01492 
01493 static void add_to_hash_amount(GHashTable* hash, const GncGUID* guid, const gnc_numeric* amount)
01494 {
01495     /* Do we have a number belonging to this GUID in the hash? If yes,
01496      * modify it in-place; if not, insert the new element into the
01497      * hash. */
01498     gnc_numeric* elem = g_hash_table_lookup(hash, guid);
01499     if (!elem)
01500     {
01501         elem = g_new0(gnc_numeric, 1);
01502         *elem = gnc_numeric_zero();
01503         g_hash_table_insert(hash, (gpointer) guid, elem);
01504     }
01505 
01506     /* Check input arguments for sanity */
01507     if (gnc_numeric_check(*amount) != GNC_ERROR_OK)
01508     {
01509         g_critical("Oops, the given amount [%s] has the error code %d, at guid [%s].",
01510                    gnc_num_dbg_to_string(*amount),
01511                    gnc_numeric_check(*amount),
01512                    guid_to_string(guid));
01513         return;
01514     }
01515     if (gnc_numeric_check(*elem) != GNC_ERROR_OK)
01516     {
01517         g_critical("Oops, the account's amount [%s] has the error code %d, at guid [%s].",
01518                    gnc_num_dbg_to_string(*elem),
01519                    gnc_numeric_check(*elem),
01520                    guid_to_string(guid));
01521         return;
01522     }
01523 
01524     /* Watch out - don't use gnc_numeric_add_fixed here because it
01525      * will refuse to add 1/5+1/10; instead, we have to use the flags
01526      * as given here explicitly. Eventually, add the given amount to
01527      * the entry in the hash. */
01528     *elem = gnc_numeric_add(*elem, *amount,
01529                             GNC_DENOM_AUTO,
01530                             GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_NEVER);
01531 
01532     /* Check for sanity of the output. */
01533     if (gnc_numeric_check(*elem) != GNC_ERROR_OK)
01534     {
01535         g_critical("Oops, after addition at guid [%s] the resulting amount [%s] has the error code %d; added amount = [%s].",
01536                    guid_to_string(guid),
01537                    gnc_num_dbg_to_string(*elem),
01538                    gnc_numeric_check(*elem),
01539                    gnc_num_dbg_to_string(*amount));
01540         return;
01541     }
01542 
01543     /* In case anyone wants to see this in the debug log. */
01544     g_debug("Adding to guid [%s] the value [%s]. Value now [%s].",
01545             guid_to_string(guid),
01546             gnc_num_dbg_to_string(*amount),
01547             gnc_num_dbg_to_string(*elem));
01548 }
01549 
01550 static gboolean
01551 create_cashflow_helper(Transaction *template_txn, void *user_data)
01552 {
01553     SxCashflowData *creation_data = user_data;
01554     GList *template_splits;
01555     gboolean err_flag = FALSE;
01556     const gnc_commodity *first_cmdty = NULL;
01557 
01558     g_debug("Evaluating txn desc [%s] for sx [%s]",
01559             xaccTransGetDescription(template_txn),
01560             xaccSchedXactionGetName(creation_data->sx));
01561 
01562     /* The accounts and amounts are in the kvp_frames of the
01563      * splits. Hence, we iterate over all splits of this
01564      * transaction. */
01565     template_splits = xaccTransGetSplitList(template_txn);
01566 
01567     if (template_splits == NULL)
01568     {
01569         g_critical("transaction w/o splits for sx [%s]",
01570                    xaccSchedXactionGetName(creation_data->sx));
01571         return FALSE;
01572     }
01573 
01574     for (;
01575             template_splits;
01576             template_splits = template_splits->next)
01577     {
01578         Account *split_acct;
01579         const gnc_commodity *split_cmdty = NULL;
01580         const Split *template_split = (const Split*) template_splits->data;
01581 
01582         /* Get the account that should be used for this split. */
01583         if (!_get_template_split_account(creation_data->sx, template_split, &split_acct, creation_data->creation_errors))
01584         {
01585             g_debug("Could not find account for split");
01586             err_flag = TRUE;
01587             break;
01588         }
01589 
01590         /* The split's account also has some commodity */
01591         split_cmdty = xaccAccountGetCommodity(split_acct);
01592         if (first_cmdty == NULL)
01593         {
01594             first_cmdty = split_cmdty;
01595             //xaccTransSetCurrency(new_txn, first_cmdty);
01596         }
01597 
01598         {
01599             gnc_numeric credit_num = gnc_numeric_zero();
01600             gnc_numeric debit_num = gnc_numeric_zero();
01601             gnc_numeric final_once, final;
01602             gint gncn_error;
01603 
01604             /* Credit value */
01605             _get_sx_formula_value(creation_data->sx, template_split, &credit_num, creation_data->creation_errors, GNC_SX_CREDIT_FORMULA, GNC_SX_CREDIT_NUMERIC, NULL);
01606             /* Debit value */
01607             _get_sx_formula_value(creation_data->sx, template_split, &debit_num, creation_data->creation_errors, GNC_SX_DEBIT_FORMULA, GNC_SX_DEBIT_NUMERIC, NULL);
01608 
01609             /* The resulting cash flow number: debit minus credit,
01610              * multiplied with the count factor. */
01611             final_once = gnc_numeric_sub_fixed( debit_num, credit_num );
01612             /* Multiply with the count factor. */
01613             final = gnc_numeric_mul(final_once, creation_data->count,
01614                                     gnc_numeric_denom(final_once),
01615                                     GNC_HOW_RND_ROUND_HALF_UP);
01616 
01617             gncn_error = gnc_numeric_check(final);
01618             if (gncn_error != GNC_ERROR_OK)
01619             {
01620                 GString *err = g_string_new("");
01621                 g_string_printf(err, "error %d in SX [%s] final gnc_numeric value, using 0 instead",
01622                                 gncn_error, xaccSchedXactionGetName(creation_data->sx));
01623                 g_critical("%s", err->str);
01624                 if (creation_data->creation_errors != NULL)
01625                     *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
01626                 else
01627                     g_string_free(err, TRUE);
01628                 final = gnc_numeric_zero();
01629             }
01630 
01631             /* Print error message if we would have needed an exchange rate */
01632             if (! gnc_commodity_equal(split_cmdty, first_cmdty))
01633             {
01634                 GString *err = g_string_new("");
01635                 g_string_printf(err, "No exchange rate available in SX [%s] for %s -> %s, value is zero",
01636                                 xaccSchedXactionGetName(creation_data->sx),
01637                                 gnc_commodity_get_mnemonic(split_cmdty),
01638                                 gnc_commodity_get_mnemonic(first_cmdty));
01639                 g_critical("%s", err->str);
01640                 if (creation_data->creation_errors != NULL)
01641                     *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
01642                 else
01643                     g_string_free(err, TRUE);
01644                 final = gnc_numeric_zero();
01645             }
01646 
01647             /* And add the resulting value to the hash */
01648             add_to_hash_amount(creation_data->hash, xaccAccountGetGUID(split_acct), &final);
01649         }
01650     }
01651 
01652     return FALSE;
01653 }
01654 
01655 static void
01656 instantiate_cashflow_internal(const SchedXaction* sx,
01657                               GHashTable* map,
01658                               GList **creation_errors, gint count)
01659 {
01660     SxCashflowData create_cashflow_data;
01661     Account* sx_template_account = gnc_sx_get_template_transaction_account(sx);
01662 
01663     if (!sx_template_account)
01664     {
01665         g_critical("Huh? No template account for the SX %s", xaccSchedXactionGetName(sx));
01666         return;
01667     }
01668 
01669     if (!xaccSchedXactionGetEnabled(sx))
01670     {
01671         g_debug("Skipping non-enabled SX [%s]",
01672                 xaccSchedXactionGetName(sx));
01673         return;
01674     }
01675 
01676     create_cashflow_data.hash = map;
01677     create_cashflow_data.creation_errors = creation_errors;
01678     create_cashflow_data.sx = sx;
01679     create_cashflow_data.count = gnc_numeric_create(count, 1);
01680 
01681     /* The cash flow numbers are in the transactions of the template
01682      * account, so run this foreach on the transactions. */
01683     xaccAccountForEachTransaction(sx_template_account,
01684                                   create_cashflow_helper,
01685                                   &create_cashflow_data);
01686 }
01687 
01688 typedef struct
01689 {
01690     GHashTable *hash;
01691     GList **creation_errors;
01692     const GDate *range_start;
01693     const GDate *range_end;
01694 } SxAllCashflow;
01695 
01696 static void instantiate_cashflow_cb(gpointer data, gpointer _user_data)
01697 {
01698     const SchedXaction* sx = (const SchedXaction*) data;
01699     SxAllCashflow* userdata = (SxAllCashflow*) _user_data;
01700     gint count;
01701 
01702     g_assert(sx);
01703     g_assert(userdata);
01704 
01705     /* How often does this particular SX occur in the date range? */
01706     count = gnc_sx_get_num_occur_daterange(sx, userdata->range_start,
01707                                            userdata->range_end);
01708     if (count > 0)
01709     {
01710         /* If it occurs at least once, calculate ("instantiate") its
01711          * cash flow and add it to the result
01712          * g_hash<GUID,gnc_numeric> */
01713         instantiate_cashflow_internal(sx,
01714                                       userdata->hash,
01715                                       userdata->creation_errors,
01716                                       count);
01717     }
01718 }
01719 
01720 void gnc_sx_all_instantiate_cashflow(GList *all_sxes,
01721                                      const GDate *range_start, const GDate *range_end,
01722                                      GHashTable* map, GList **creation_errors)
01723 {
01724     SxAllCashflow userdata;
01725     userdata.hash = map;
01726     userdata.creation_errors = creation_errors;
01727     userdata.range_start = range_start;
01728     userdata.range_end = range_end;
01729 
01730     /* The work is done in the callback for each SX */
01731     g_list_foreach(all_sxes, instantiate_cashflow_cb, &userdata);
01732 }
01733 
01734 
01735 GHashTable* gnc_sx_all_instantiate_cashflow_all(GDate range_start, GDate range_end)
01736 {
01737     GHashTable *result_map = gnc_g_hash_new_guid_numeric();
01738     GList *all_sxes = gnc_book_get_schedxactions(gnc_get_current_book())->sx_list;
01739     gnc_sx_all_instantiate_cashflow(all_sxes,
01740                                     &range_start, &range_end,
01741                                     result_map, NULL);
01742     return result_map;
01743 }
01744 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines