r15362 - gnucash/branches/sx-cleanup/src - Move "effect change" and "set sx instance state" methods up from since-last-run to the instance model proper.

Josh Sled jsled at cvs.gnucash.org
Sat Jan 13 17:14:47 EST 2007


Author: jsled
Date: 2007-01-13 17:14:45 -0500 (Sat, 13 Jan 2007)
New Revision: 15362
Trac: http://svn.gnucash.org/trac/changeset/15362

Modified:
   gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.c
   gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.h
   gnucash/branches/sx-cleanup/src/app-utils/test/test-sx.c
   gnucash/branches/sx-cleanup/src/doc/sx.rst
   gnucash/branches/sx-cleanup/src/gnome/dialog-sx-since-last-run.c
Log:
Move "effect change" and "set sx instance state" methods up from since-last-run to the instance model proper.
Extend "update sx instances" method to merge the new instances into the existing instances if possible.


Modified: gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.c
===================================================================
--- gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.c	2007-01-13 22:12:29 UTC (rev 15361)
+++ gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.c	2007-01-13 22:14:45 UTC (rev 15362)
@@ -25,19 +25,22 @@
 #include <glib-object.h>
 
 #include "Account.h"
+#include "SX-book.h"
 #include "SchedXaction.h"
+#include "Scrub.h"
 #include "Split.h"
-#include "SX-book.h"
 #include "Transaction.h"
 #include "gnc-book.h"
 #include "gnc-commodity.h"
 #include "gnc-event.h"
 #include "gnc-exp-parser.h"
 #include "gnc-glib-utils.h"
+#include "gnc-sx-instance-model.h"
 #include "gnc-ui-util.h"
-#include "gnc-sx-instance-model.h"
 #include "qof.h"
 
+static QofLogModule log_module = GNC_MOD_SX;
+
 static GObjectClass *parent_class = NULL;
 
 static void gnc_sx_instance_model_class_init (GncSxInstanceModelClass *klass);
@@ -611,7 +614,7 @@
 void
 gnc_sx_instance_model_update_sx_instances(GncSxInstanceModel *model, SchedXaction *sx)
 {
-     GncSxInstances *new_instances;
+     GncSxInstances *existing, *new_instances;
      GList *link;
 
      link = g_list_find_custom(model->sx_instance_list, sx, (GCompareFunc)_gnc_sx_instance_find_by_sx);
@@ -621,10 +624,58 @@
           printf("couldn't find sx [%p]\n", sx);
           return;
      }
-     gnc_sx_instances_free((GncSxInstances*)link->data);
 
+     // merge the new instance data into the existing structure, mutating as little as possible.
+     existing = (GncSxInstances*)link->data;
      new_instances = _gnc_sx_gen_instances((gpointer)sx, &model->range_end);
-     link->data = new_instances;
+     existing->sx = new_instances->sx;
+     // @fixme: variable names stuff
+     existing->next_instance_date = new_instances->next_instance_date;
+     {
+          GList *existing_iter, *new_iter;
+          gboolean existing_remain, new_remain;
+
+          // step through the lists pairwise, and retain the existing
+          // instance if the dates align, as soon as they don't stop and
+          // cleanup.
+          existing_iter = existing->list;
+          new_iter = new_instances->list;
+          for (; existing_iter != NULL && new_iter != NULL; existing_iter = existing_iter->next, new_iter = new_iter->next)
+          {
+               GncSxInstance *existing_inst, *new_inst;
+               gboolean same_instance_date;
+               existing_inst = (GncSxInstance*)existing_iter->data;
+               new_inst = (GncSxInstance*)new_iter->data;
+
+               same_instance_date = g_date_compare(&existing_inst->date, &new_inst->date) == 0;
+               if (!same_instance_date)
+                    break;
+          }
+
+          existing_remain = (existing_iter != NULL);
+          new_remain = (new_iter != NULL);
+
+          if (existing_remain)
+          {
+               // delete excess
+               gnc_g_list_cut(&existing->list, existing_iter);
+               g_list_foreach(existing_iter, (GFunc)gnc_sx_instance_free, NULL);
+          }
+
+          if (new_remain)
+          {
+               // append new
+               GList *new_iter_iter;
+               gnc_g_list_cut(&new_instances->list, new_iter);
+
+               for (new_iter_iter = new_iter; new_iter_iter != NULL; new_iter_iter = new_iter_iter->next)
+               {
+                    existing->list = g_list_append(existing->list, new_iter_iter->data);
+               }
+               g_list_free(new_iter);
+          }
+     }
+     gnc_sx_instances_free(new_instances);
 }
 
 void
@@ -643,3 +694,431 @@
      model->sx_instance_list = g_list_remove_link(model->sx_instance_list, instance_link);
      gnc_sx_instances_free((GncSxInstances*)instance_link->data);
 }
+
+static void
+increment_sx_state(GncSxInstance *inst, GDate **last_occur_date, int *instance_count, int *remain_occur_count)
+{
+     if (!g_date_valid(*last_occur_date)
+         || (g_date_valid(*last_occur_date)
+             && g_date_compare(*last_occur_date, &inst->date) <= 0))
+     {
+          *last_occur_date = &inst->date;
+     }
+
+     *instance_count = gnc_sx_get_instance_count(inst->parent->sx, inst->temporal_state);
+
+     if (*remain_occur_count > 0)
+     {
+          *remain_occur_count -= 1;
+     }
+}
+
+typedef struct _SxTxnCreationData
+{
+     GncSxInstance *instance;
+     GList **created_txn_guids;
+     GList **creation_errors;
+} SxTxnCreationData;
+
+static gboolean
+_get_template_split_account(GncSxInstance *instance, Split *template_split, Account **split_acct, GList **creation_errors)
+{
+     GUID *acct_guid;
+     kvp_frame *split_kvpf;
+     kvp_value *kvp_val;
+
+     split_kvpf = xaccSplitGetSlots(template_split);
+     /* contains the guid of the split's actual account. */
+     kvp_val = kvp_frame_get_slot_path(split_kvpf,
+                                       GNC_SX_ID,
+                                       GNC_SX_ACCOUNT,
+                                       NULL);
+     if (kvp_val == NULL)
+     {
+          // @@fixme: this should be more of an assert...
+          GString *err = g_string_new("");
+          g_string_printf(err, "Null account kvp value for SX [%s], cancelling creation.",
+                          xaccSchedXactionGetName(instance->parent->sx));
+          *creation_errors = g_list_append(*creation_errors, err);
+          return FALSE;
+     }
+     acct_guid = kvp_value_get_guid( kvp_val );
+     *split_acct = xaccAccountLookup(acct_guid, gnc_get_current_book());
+     if (*split_acct == NULL)
+     {
+          const char *guid_str;
+          GString *err;
+          guid_str = guid_to_string((const GUID*)acct_guid);
+          err = g_string_new("");
+          g_string_printf(err, "Unknown account for guid [%s], cancelling SX [%s] creation.",
+                          guid_str, xaccSchedXactionGetName(instance->parent->sx));
+          g_free((char*)guid_str);
+          *creation_errors = g_list_append(*creation_errors, err);
+          return FALSE;
+     }
+
+     return TRUE;
+}
+
+static void
+_get_sx_formula(GncSxInstance *instance, Split *template_split, gnc_numeric *numeric, GList **creation_errors, const char *formula_key)
+{
+     kvp_frame *split_kvpf;
+     kvp_value *kvp_val;
+     char *formula_str, *parseErrorLoc;
+
+     split_kvpf = xaccSplitGetSlots(template_split);
+     kvp_val = kvp_frame_get_slot_path(split_kvpf,
+                                       GNC_SX_ID,
+                                       formula_key,
+                                       NULL);
+     formula_str = kvp_value_get_string(kvp_val);
+     if (formula_str != NULL && strlen(formula_str) != 0)
+     {
+          GHashTable *parser_vars = gnc_sx_instance_get_variables_for_parser(instance->variable_bindings);
+          if (!gnc_exp_parser_parse_separate_vars(formula_str,
+                                                  numeric,
+                                                  &parseErrorLoc,
+                                                  parser_vars))
+          {
+               GString *err = g_string_new("");
+               g_string_printf(err, "Error parsing SX [%s] key [%s]=formula [%s] at [%s]: %s",
+                               xaccSchedXactionGetName(instance->parent->sx),
+                               formula_key,
+                               formula_str,
+                               parseErrorLoc,
+                               gnc_exp_parser_error_string());
+               *creation_errors = g_list_append(*creation_errors, err);
+          }
+          g_hash_table_destroy(parser_vars);
+     }
+}
+
+static void
+_get_credit_formula(GncSxInstance *instance, Split *template_split, gnc_numeric *credit_num, GList **creation_errors)
+{
+     _get_sx_formula(instance, template_split, credit_num, creation_errors, GNC_SX_CREDIT_FORMULA);
+}
+
+static void
+_get_debit_formula(GncSxInstance *instance, Split *template_split, gnc_numeric *debit_num, GList **creation_errors)
+{
+     _get_sx_formula(instance, template_split, debit_num, creation_errors, GNC_SX_DEBIT_FORMULA);
+}
+
+static gboolean
+create_each_transaction_helper(Transaction *template_txn, void *user_data)
+{
+     Transaction *new_txn;
+     GList *txn_splits, *template_splits;
+     Split *copying_split;
+     gnc_commodity *first_cmdty = NULL;
+     gboolean err_flag = FALSE;
+     SxTxnCreationData *creation_data;
+
+     creation_data = (SxTxnCreationData*)user_data;
+
+     /* FIXME: In general, this should [correctly] deal with errors such
+        as not finding the approrpiate Accounts and not being able to
+        parse the formula|credit/debit strings. */
+
+     new_txn = xaccTransClone(template_txn);
+     xaccTransBeginEdit(new_txn);
+
+     /* clear any copied KVP data */
+     qof_instance_set_slots(QOF_INSTANCE(new_txn), kvp_frame_new());
+
+     xaccTransSetDate(new_txn,
+                      g_date_get_day(&creation_data->instance->date),
+                      g_date_get_month(&creation_data->instance->date),
+                      g_date_get_year(&creation_data->instance->date));
+        
+     /* the accounts and amounts are in the kvp_frames of the splits. */
+     template_splits = xaccTransGetSplitList(template_txn);
+     txn_splits = xaccTransGetSplitList(new_txn);
+     if ((template_splits == NULL) || (txn_splits == NULL))
+     {
+          PERR("\tseen transaction w/o splits. :(");
+          xaccTransDestroy(new_txn);
+          xaccTransCommitEdit(new_txn);
+          return FALSE;
+     }
+
+     for (;
+          txn_splits && template_splits;
+          txn_splits = txn_splits->next, template_splits = template_splits->next)
+     {
+          Split *template_split;
+          Account *split_acct;
+          gnc_commodity *split_cmdty = NULL;
+             
+          /* FIXME: Ick.  This assumes that the split lists will be ordered
+             identically. :( They are, but we'd rather not have to count on
+             it. --jsled */
+          template_split = (Split*)template_splits->data;
+          copying_split = (Split*)txn_splits->data;
+
+          /* clear out any copied Split frame data. */
+          qof_instance_set_slots(QOF_INSTANCE(copying_split), kvp_frame_new());
+
+          if (!_get_template_split_account(creation_data->instance, template_split, &split_acct, creation_data->creation_errors))
+          {
+               err_flag = TRUE;
+               break;
+          }
+             
+          split_cmdty = xaccAccountGetCommodity(split_acct);
+          if (first_cmdty == NULL)
+          {
+               first_cmdty = split_cmdty;
+               xaccTransSetCurrency(new_txn, first_cmdty);
+          }
+
+          xaccAccountBeginEdit(split_acct);
+          xaccAccountInsertSplit(split_acct, copying_split);
+
+          {
+               gnc_numeric credit_num, debit_num, final;
+               gint gncn_error;
+
+               credit_num = gnc_numeric_zero();
+               debit_num = gnc_numeric_zero();
+
+               _get_credit_formula(creation_data->instance, template_split, &credit_num, creation_data->creation_errors);
+               _get_debit_formula(creation_data->instance, template_split, &debit_num, creation_data->creation_errors);
+                       
+               final = gnc_numeric_sub_fixed( debit_num, credit_num );
+                        
+               gncn_error = gnc_numeric_check(final);
+               if (gncn_error != GNC_ERROR_OK) {
+                    GString *err = g_string_new("");
+                    g_string_printf(err, "Error %d in SX [%s] final gnc_numeric value, using 0 instead.", 
+                                    gncn_error,
+                                    xaccSchedXactionGetName(creation_data->instance->parent->sx));
+                    *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
+                    final = gnc_numeric_zero();
+               }
+
+               xaccSplitSetValue(copying_split, final);
+               if (! gnc_commodity_equal(split_cmdty, first_cmdty))
+               {
+                    GString *exchange_rate_var_name = g_string_sized_new(16);
+                    GncSxVariable *exchange_rate_var;
+                    gnc_numeric exchange_rate, amt;
+
+                    /*
+                      GNCPriceDB *price_db = gnc_pricedb_get_db(gnc_get_current_book());
+                      GNCPrice *price;
+
+                      price = gnc_pricedb_lookup_latest(price_db, first_cmdty, split_cmdty);
+                      if (price == NULL)
+                      {
+                      price = gnc_pricedb_lookup_latest(price_db, split_cmdty, first_cmdty);
+                      if (price == NULL)
+                      {
+                      GString *err = g_string_new("");
+                      g_string_printf(err, "could not find pricedb entry for commodity-pair (%s, %s).",
+                      gnc_commodity_get_mnemonic(first_cmdty),
+                      gnc_commodity_get_mnemonic(split_cmdty));
+                      exchange = gnc_numeric_create(1, 1);
+                      *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
+
+                      }
+                      else
+                      {
+                      exchange = gnc_numeric_div(gnc_numeric_create(1,1),
+                      gnc_price_get_value(price),
+                      1000, GNC_HOW_RND_ROUND);
+                      }
+                      }
+                      else
+                      {
+                      exchange = gnc_price_get_value(price);
+                      }
+                    */
+
+                    exchange_rate = gnc_numeric_zero();
+                    g_string_printf(exchange_rate_var_name, "%s -> %s",
+                                    gnc_commodity_get_mnemonic(split_cmdty),
+                                    gnc_commodity_get_mnemonic(first_cmdty));
+                    exchange_rate_var = (GncSxVariable*)g_hash_table_lookup(creation_data->instance->variable_bindings,
+                                                                            exchange_rate_var_name->str);
+                    if (exchange_rate_var != NULL)
+                    {
+                         exchange_rate = exchange_rate_var->value;
+                    }
+                    g_string_free(exchange_rate_var_name, TRUE);
+
+                    amt = gnc_numeric_mul(final, exchange_rate, 1000, GNC_HOW_RND_ROUND);
+                    xaccSplitSetAmount(copying_split, amt);
+               }
+
+               xaccSplitScrub(copying_split);
+          }
+
+          xaccAccountCommitEdit(split_acct);
+     }
+
+     if (err_flag)
+     {
+          PERR("Some error in new transaction creation...");
+          xaccTransDestroy(new_txn);
+          xaccTransCommitEdit(new_txn);
+          return FALSE;
+     }
+
+     {
+          kvp_frame *txn_frame;
+          /* set a kvp-frame element in the transaction indicating and
+           * pointing-to the SX this was created from. */
+          txn_frame = xaccTransGetSlots(new_txn);
+          kvp_frame_set_guid(txn_frame, "from-sched-xaction", xaccSchedXactionGetGUID(creation_data->instance->parent->sx));
+     }
+
+     xaccTransCommitEdit(new_txn);
+
+     if (creation_data->created_txn_guids != NULL)
+     {
+          *creation_data->created_txn_guids
+               = g_list_append(*(creation_data->created_txn_guids), (gpointer)xaccTransGetGUID(new_txn));
+     }
+
+     return TRUE;
+}
+
+static void
+create_transactions_for_instance(GncSxInstance *instance, GList **created_txn_guids, GList **creation_errors)
+{
+     SxTxnCreationData creation_data;
+     Account *sx_template_account;
+
+     sx_template_account = gnc_sx_get_template_transaction_account(instance->parent->sx);
+
+     creation_data.instance = instance;
+     creation_data.created_txn_guids = created_txn_guids;
+     creation_data.creation_errors = creation_errors;
+
+     xaccAccountForEachTransaction(sx_template_account,
+                                   create_each_transaction_helper,
+                                   &creation_data);
+}
+
+void
+gnc_sx_instance_model_effect_change(GncSxInstanceModel *model,
+                                    gboolean auto_create_only,
+                                    GList **created_transaction_guids,
+                                    GList **creation_errors)
+{
+     GList *iter;
+     for (iter = model->sx_instance_list; iter != NULL; iter = iter->next)
+     {
+          GList *instance_iter;
+          GncSxInstances *instances = (GncSxInstances*)iter->data;
+          GDate *last_occur_date;
+          gint instance_count = 0;
+          gint remain_occur_count = 0;
+
+          last_occur_date = xaccSchedXactionGetLastOccurDate(instances->sx);
+          instance_count = gnc_sx_get_instance_count(instances->sx, NULL);
+          remain_occur_count = xaccSchedXactionGetRemOccur(instances->sx);
+
+          for (instance_iter = instances->list; instance_iter != NULL; instance_iter = instance_iter->next)
+          {
+               GncSxInstance *inst = (GncSxInstance*)instance_iter->data;
+               gboolean sx_is_auto_create;
+
+               xaccSchedXactionGetAutoCreate(inst->parent->sx, &sx_is_auto_create, NULL);
+               if (auto_create_only && !sx_is_auto_create)
+               {
+                    if (inst->state != SX_INSTANCE_STATE_TO_CREATE)
+                    {
+                         break;
+                    }
+                    continue;
+               }
+
+               if (inst->orig_state == SX_INSTANCE_STATE_POSTPONED
+                   && inst->state != SX_INSTANCE_STATE_POSTPONED)
+               {
+                    // remove from postponed list
+                    g_assert(inst->temporal_state != NULL);
+                    gnc_sx_remove_defer_instance(inst->parent->sx, inst->temporal_state);
+               }
+
+               switch (inst->state)
+               {
+               case SX_INSTANCE_STATE_CREATED:
+                    // nop: we've already processed this.
+                    break;
+               case SX_INSTANCE_STATE_IGNORED:
+                    increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
+                    break;
+               case SX_INSTANCE_STATE_POSTPONED:
+                    if (inst->orig_state != SX_INSTANCE_STATE_POSTPONED)
+                    {
+                         gnc_sx_add_defer_instance(instances->sx, inst->temporal_state);
+                    }
+                    increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
+                    break;
+               case SX_INSTANCE_STATE_TO_CREATE:
+                    create_transactions_for_instance(inst, created_transaction_guids, creation_errors);
+                    increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
+                    gnc_sx_instance_model_change_instance_state(model, inst, SX_INSTANCE_STATE_CREATED);
+                    break;
+               case SX_INSTANCE_STATE_REMINDER:
+                    // do nothing
+                    // assert no non-remind instances after this?
+                    break;
+               default:
+                    g_assert_not_reached();
+                    break;
+               }
+          }
+          
+          xaccSchedXactionSetLastOccurDate(instances->sx, last_occur_date);
+          gnc_sx_set_instance_count(instances->sx, instance_count);
+          xaccSchedXactionSetRemOccur(instances->sx, remain_occur_count);
+     }
+}
+
+void
+gnc_sx_instance_model_change_instance_state(GncSxInstanceModel *model,
+                                            GncSxInstance *instance,
+                                            GncSxInstanceState new_state)
+{
+     if (instance->state == new_state)
+          return;
+
+     instance->state = new_state;
+
+     // ensure 'remind' constraints are met:
+     {
+          GList *inst_iter;
+          inst_iter = g_list_find(instance->parent->list, instance);
+          g_assert(inst_iter != NULL);
+          if (instance->state != SX_INSTANCE_STATE_REMINDER)
+          {
+               // iterate backwards, making sure reminders are changed to 'postponed'
+               for (inst_iter = inst_iter->prev; inst_iter != NULL; inst_iter = inst_iter->prev)
+               {
+                    GncSxInstance *prev_inst = (GncSxInstance*)inst_iter->data;
+                    if (prev_inst->state != SX_INSTANCE_STATE_REMINDER)
+                         continue;
+                    prev_inst->state = SX_INSTANCE_STATE_POSTPONED;
+               }
+          }
+          else
+          {
+               // iterate forward, make sure transactions are set to 'remind'
+               for (inst_iter = inst_iter->next; inst_iter != NULL; inst_iter = inst_iter->next)
+               {
+                    GncSxInstance *next_inst = (GncSxInstance*)inst_iter->data;
+                    if (next_inst->state == SX_INSTANCE_STATE_REMINDER)
+                         continue;
+                    next_inst->state = SX_INSTANCE_STATE_REMINDER;
+               }
+          }
+     }
+
+     g_signal_emit_by_name(model, "updated", (gpointer)instance->parent->sx);
+}

Modified: gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.h
===================================================================
--- gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.h	2007-01-13 22:12:29 UTC (rev 15361)
+++ gnucash/branches/sx-cleanup/src/app-utils/gnc-sx-instance-model.h	2007-01-13 22:14:45 UTC (rev 15362)
@@ -123,12 +123,21 @@
 Account* gnc_sx_get_template_transaction_account(SchedXaction *sx);
 
 /**
- * @return caller-owned.
+ * @return caller-owned data struct.
  **/
 GHashTable* gnc_sx_instance_get_variables_for_parser(GHashTable *instance_var_hash);
 
 GncSxVariable* gnc_sx_variable_new_full(gchar *name, gnc_numeric value, gboolean editable);
 
+void gnc_sx_instance_model_change_instance_state(GncSxInstanceModel *model,
+                                                 GncSxInstance *instance,
+                                                 GncSxInstanceState new_state);
+
+void gnc_sx_instance_model_effect_change(GncSxInstanceModel *model,
+                                         gboolean auto_create_only,
+                                         GList **created_transaction_guids,
+                                         GList **creation_errors);
+
 /* @@fixme names. */
 void sxsl_get_sx_vars(SchedXaction *sx, GHashTable *var_hash);
 int parse_vars_from_formula(const char *formula, GHashTable *var_hash, gnc_numeric *result);

Modified: gnucash/branches/sx-cleanup/src/app-utils/test/test-sx.c
===================================================================
--- gnucash/branches/sx-cleanup/src/app-utils/test/test-sx.c	2007-01-13 22:12:29 UTC (rev 15361)
+++ gnucash/branches/sx-cleanup/src/app-utils/test/test-sx.c	2007-01-13 22:14:45 UTC (rev 15362)
@@ -99,6 +99,41 @@
      remove_sx(lonely);
 }
 
+static void
+test_state_changes()
+{
+     SchedXaction *foo;
+     GDate *start, *end;
+     GncSxInstanceModel *model;
+     GncSxInstances *insts;
+     GncSxInstance *inst;
+
+     start = g_date_new();
+     g_date_clear(start, 1);
+     g_date_set_time_t(start, time(NULL));
+     
+     end = g_date_new();
+     g_date_clear(end, 1);
+     g_date_set_time_t(end, time(NULL));
+     g_date_add_days(end, 1);
+
+     foo = add_daily_sx("foo", start, NULL, NULL);
+
+     model = gnc_sx_get_instances(end);
+
+     insts = (GncSxInstances*)model->sx_instance_list->data;
+     inst = (GncSxInstance*)insts->list->data;
+     gnc_sx_instance_model_change_instance_state(model, inst, SX_INSTANCE_STATE_REMINDER);
+     success("changed to reminder");
+     gnc_sx_instance_model_change_instance_state(model, inst, SX_INSTANCE_STATE_POSTPONED);
+     success("changed to postponed");
+     gnc_sx_instance_model_change_instance_state(model, inst, SX_INSTANCE_STATE_TO_CREATE);
+     success("changed to to-create");
+
+     g_object_unref(model);
+     remove_sx(foo);
+}
+
 int
 main(int argc, char **argv)
 {
@@ -113,6 +148,7 @@
                test_once();
      }
      test_basic();
+     test_state_changes();
 
      print_test_results();
      exit(get_rv());

Modified: gnucash/branches/sx-cleanup/src/doc/sx.rst
===================================================================
--- gnucash/branches/sx-cleanup/src/doc/sx.rst	2007-01-13 22:12:29 UTC (rev 15361)
+++ gnucash/branches/sx-cleanup/src/doc/sx.rst	2007-01-13 22:14:45 UTC (rev 15362)
@@ -25,7 +25,7 @@
 
 - core
   - [x] sx list -> qof collection
-  - [/] sx engine events
+  - [x] sx engine events
     - [x] sx list collection add/remove -- sx-list GNC_EVENT_ITEM_ADDED, _REMOVED
     - [x] sx modified -- QOF_EVENT_MODIFY
   - [/] sx upcoming instance model
@@ -52,15 +52,16 @@
   - [x] remove clist usage
 
 - sx-from-trans
-  - [ ] convert to GObject
+  - [?] convert to GObject
   - [x] hookup destroy/finalize
 
 - use Recurrence instead of Freq Spec
   - [ ] XML migration, handling
 
 - since-last-run
-  - [ ] move "effect_change" up to app-utils/, test.
-  - [?] add obsolete flag to SxInstanceModel
+  - [x] move "effect_change" up to app-utils/, test.
+  - [x] move state-change up to app-utils
+  - [ ] move variable-setting up to app-utils
   - [x] add reminders, postponed to SxInstanceModel
   - [x] add mutation support to sx instance model
     - [x] state machine

Modified: gnucash/branches/sx-cleanup/src/gnome/dialog-sx-since-last-run.c
===================================================================
--- gnucash/branches/sx-cleanup/src/gnome/dialog-sx-since-last-run.c	2007-01-13 22:12:29 UTC (rev 15361)
+++ gnucash/branches/sx-cleanup/src/gnome/dialog-sx-since-last-run.c	2007-01-13 22:14:45 UTC (rev 15362)
@@ -31,10 +31,6 @@
 #include "dialog-sx-since-last-run.h"
 
 #include "gnc-ui-util.h"
-#include "Split.h"
-#include "Transaction.h"
-#include "Account.h"
-#include "Scrub.h"
 #include "Query.h"
 #include "QueryNew.h"
 #include "gnc-ledger-display.h"
@@ -44,12 +40,10 @@
 #include "gnc-gconf-utils.h"
 #include "gnc-gui-query.h"
 
-static QofLogModule log_module = GNC_MOD_GUI;
+// static QofLogModule log_module = GNC_MOD_GUI;
 
 #define GCONF_SECTION "dialogs/scheduled_trans/since_last_run"
 
-//typedef struct _GncSxSlrTreeModelAdapter GncSxSlrTreeModelAdapter;
-
 struct _GncSxSinceLastRunDialog
 {
      GtkWidget *dialog;
@@ -632,73 +626,8 @@
 void
 gnc_sx_slr_model_change_instance_state(GncSxSlrTreeModelAdapter *model, GncSxInstance *instance, GncSxInstanceState new_state)
 {
-     GtkTreePath *path;
-     GtkTreeIter iter;
-     GList *inst_iter;
-     int indices[2];
-
-     indices[0] = g_list_index(model->instances->sx_instance_list, instance->parent);
-     if (indices[0] == -1)
-          return;
-     indices[1] = g_list_index(instance->parent->list, instance);
-     if (indices[1] == -1)
-          return;
-     path = gtk_tree_path_new_from_indices(indices[0], indices[1], -1);
-     gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
-     gtk_tree_path_free(path);
-
-     instance->state = new_state;
-
-     gtk_tree_store_set(model->real, &iter,
-                        SLR_MODEL_COL_INSTANCE_STATE, gnc_sx_instance_state_names[instance->state],
-                        SLR_MODEL_COL_INSTANCE_STATE_SENSITIVITY, instance->state != SX_INSTANCE_STATE_CREATED,
-                        -1);
-
-     // ensure 'remind' constraints are met
-     inst_iter = g_list_find(instance->parent->list, instance);
-     g_assert(inst_iter != NULL);
-     if (instance->state != SX_INSTANCE_STATE_REMINDER)
-     {
-          // iterate backwards, making sure reminders are changed to 'postponed'
-          for (inst_iter = inst_iter->prev; inst_iter != NULL; inst_iter = inst_iter->prev)
-          {
-               GncSxInstance *prev_inst = (GncSxInstance*)inst_iter->data;
-               indices[1] -= 1;
-               if (prev_inst->state != SX_INSTANCE_STATE_REMINDER)
-                    continue;
-
-               prev_inst->state = SX_INSTANCE_STATE_POSTPONED;
-
-               path = gtk_tree_path_new_from_indices(indices[0], indices[1], -1);
-               gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
-               gtk_tree_path_free(path);
-               gtk_tree_store_set(model->real, &iter,
-                                  SLR_MODEL_COL_INSTANCE_STATE, gnc_sx_instance_state_names[prev_inst->state],
-                                  SLR_MODEL_COL_INSTANCE_STATE_SENSITIVITY, prev_inst->state != SX_INSTANCE_STATE_CREATED,
-                                  -1);
-          }
-     }
-     else
-     {
-          // iterate forward, make sure transactions are set to 'remind'
-          for (inst_iter = inst_iter->next; inst_iter != NULL; inst_iter = inst_iter->next)
-          {
-               GncSxInstance *next_inst = (GncSxInstance*)inst_iter->data;
-               indices[1] += 1;
-               if (next_inst->state == SX_INSTANCE_STATE_REMINDER)
-                    continue;
-
-               next_inst->state = SX_INSTANCE_STATE_REMINDER;
-
-               path = gtk_tree_path_new_from_indices(indices[0], indices[1], -1);
-               gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
-               gtk_tree_path_free(path);
-               gtk_tree_store_set(model->real, &iter,
-                                  SLR_MODEL_COL_INSTANCE_STATE, gnc_sx_instance_state_names[next_inst->state],
-                                  SLR_MODEL_COL_INSTANCE_STATE_SENSITIVITY, next_inst->state != SX_INSTANCE_STATE_CREATED,
-                                  -1);
-          }
-     }
+     // @fixme: pop this out a level.
+     gnc_sx_instance_model_change_instance_state(model->instances, instance, new_state);
 }
 
 /**
@@ -768,18 +697,12 @@
 gsslrtma_updated_cb(GncSxInstanceModel *instances, SchedXaction *updated_sx, gpointer user_data)
 {
      GncSxSlrTreeModelAdapter *model = GNC_SX_SLR_TREE_MODEL_ADAPTER(user_data); 
-     printf("gsslrtma update\n");
 
-     // @@fixme: this should be better about, say, trying to match up changed
-     // instance-state and variable-binding values.  More of a merge
-     // operation than a replace...
-     
      gnc_sx_instance_model_update_sx_instances(instances, updated_sx);
 
+     // this can't be so heavyweight ... it should re-populate the tree store in-place.
      gtk_tree_store_clear(model->real);
      gsslrtma_populate_tree_store(model);
-
-     // gtk_tree_view_expand_all(dialog->instance_view);
 }
 
 static void
@@ -1187,314 +1110,6 @@
      }
 }
 
-static void
-increment_sx_state(GncSxInstance *inst, GDate **last_occur_date, int *instance_count, int *remain_occur_count)
-{
-     if (!g_date_valid(*last_occur_date)
-         || (g_date_valid(*last_occur_date)
-             && g_date_compare(*last_occur_date, &inst->date) <= 0))
-     {
-          *last_occur_date = &inst->date;
-     }
-
-     *instance_count = gnc_sx_get_instance_count(inst->parent->sx, inst->temporal_state);
-
-     if (*remain_occur_count > 0)
-     {
-          *remain_occur_count -= 1;
-     }
-}
-
-typedef struct _SxTxnCreationData
-{
-     GncSxInstance *instance;
-     GList **created_txn_guids;
-     GList **creation_errors;
-} SxTxnCreationData;
-
-static gboolean
-_get_template_split_account(GncSxInstance *instance, Split *template_split, Account **split_acct, GList **creation_errors)
-{
-     GUID *acct_guid;
-     kvp_frame *split_kvpf;
-     kvp_value *kvp_val;
-
-     split_kvpf = xaccSplitGetSlots(template_split);
-     /* contains the guid of the split's actual account. */
-     kvp_val = kvp_frame_get_slot_path(split_kvpf,
-                                       GNC_SX_ID,
-                                       GNC_SX_ACCOUNT,
-                                       NULL);
-     if (kvp_val == NULL)
-     {
-          // @@fixme: this should be more of an assert...
-          GString *err = g_string_new("");
-          g_string_printf(err, "Null account kvp value for SX [%s], cancelling creation.",
-                          xaccSchedXactionGetName(instance->parent->sx));
-          *creation_errors = g_list_append(*creation_errors, err);
-          return FALSE;
-     }
-     acct_guid = kvp_value_get_guid( kvp_val );
-     *split_acct = xaccAccountLookup(acct_guid, gnc_get_current_book());
-     if (*split_acct == NULL)
-     {
-          const char *guid_str;
-          GString *err;
-          guid_str = guid_to_string((const GUID*)acct_guid);
-          err = g_string_new("");
-          g_string_printf(err, "Unknown account for guid [%s], cancelling SX [%s] creation.",
-                          guid_str, xaccSchedXactionGetName(instance->parent->sx));
-          g_free((char*)guid_str);
-          *creation_errors = g_list_append(*creation_errors, err);
-          return FALSE;
-     }
-
-     return TRUE;
-}
-
-static void
-_get_sx_formula(GncSxInstance *instance, Split *template_split, gnc_numeric *numeric, GList **creation_errors, const char *formula_key)
-{
-     kvp_frame *split_kvpf;
-     kvp_value *kvp_val;
-     char *formula_str, *parseErrorLoc;
-
-     split_kvpf = xaccSplitGetSlots(template_split);
-     kvp_val = kvp_frame_get_slot_path(split_kvpf,
-                                       GNC_SX_ID,
-                                       formula_key,
-                                       NULL);
-     formula_str = kvp_value_get_string(kvp_val);
-     if (formula_str != NULL && strlen(formula_str) != 0)
-     {
-          GHashTable *parser_vars = gnc_sx_instance_get_variables_for_parser(instance->variable_bindings);
-          if (!gnc_exp_parser_parse_separate_vars(formula_str,
-                                                  numeric,
-                                                  &parseErrorLoc,
-                                                  parser_vars))
-          {
-               GString *err = g_string_new("");
-               g_string_printf(err, "Error parsing SX [%s] key [%s]=formula [%s] at [%s]: %s",
-                               xaccSchedXactionGetName(instance->parent->sx),
-                               formula_key,
-                               formula_str,
-                               parseErrorLoc,
-                               gnc_exp_parser_error_string());
-               *creation_errors = g_list_append(*creation_errors, err);
-          }
-          g_hash_table_destroy(parser_vars);
-     }
-}
-
-static void
-_get_credit_formula(GncSxInstance *instance, Split *template_split, gnc_numeric *credit_num, GList **creation_errors)
-{
-     _get_sx_formula(instance, template_split, credit_num, creation_errors, GNC_SX_CREDIT_FORMULA);
-}
-
-static void
-_get_debit_formula(GncSxInstance *instance, Split *template_split, gnc_numeric *debit_num, GList **creation_errors)
-{
-     _get_sx_formula(instance, template_split, debit_num, creation_errors, GNC_SX_DEBIT_FORMULA);
-}
-
-static gboolean
-create_each_transaction_helper(Transaction *template_txn, void *user_data)
-{
-     Transaction *new_txn;
-     GList *txn_splits, *template_splits;
-     Split *copying_split;
-     gnc_commodity *first_cmdty = NULL;
-     gboolean err_flag = FALSE;
-     SxTxnCreationData *creation_data;
-
-     creation_data = (SxTxnCreationData*)user_data;
-
-     /* FIXME: In general, this should [correctly] deal with errors such
-        as not finding the approrpiate Accounts and not being able to
-        parse the formula|credit/debit strings. */
-
-     new_txn = xaccTransClone(template_txn);
-     xaccTransBeginEdit(new_txn);
-
-     /* clear any copied KVP data */
-     qof_instance_set_slots(QOF_INSTANCE(new_txn), kvp_frame_new());
-
-     xaccTransSetDate(new_txn,
-                      g_date_get_day(&creation_data->instance->date),
-                      g_date_get_month(&creation_data->instance->date),
-                      g_date_get_year(&creation_data->instance->date));
-        
-     /* the accounts and amounts are in the kvp_frames of the splits. */
-     template_splits = xaccTransGetSplitList(template_txn);
-     txn_splits = xaccTransGetSplitList(new_txn);
-     if ((template_splits == NULL) || (txn_splits == NULL))
-     {
-          PERR("\tseen transaction w/o splits. :(");
-          xaccTransDestroy(new_txn);
-          xaccTransCommitEdit(new_txn);
-          return FALSE;
-     }
-
-     for (;
-          txn_splits && template_splits;
-          txn_splits = txn_splits->next, template_splits = template_splits->next)
-     {
-          Split *template_split;
-          Account *split_acct;
-          gnc_commodity *split_cmdty = NULL;
-             
-          /* FIXME: Ick.  This assumes that the split lists will be ordered
-             identically. :( They are, but we'd rather not have to count on
-             it. --jsled */
-          template_split = (Split*)template_splits->data;
-          copying_split = (Split*)txn_splits->data;
-
-          /* clear out any copied Split frame data. */
-          qof_instance_set_slots(QOF_INSTANCE(copying_split), kvp_frame_new());
-
-          if (!_get_template_split_account(creation_data->instance, template_split, &split_acct, creation_data->creation_errors))
-          {
-               err_flag = TRUE;
-               break;
-          }
-             
-          split_cmdty = xaccAccountGetCommodity(split_acct);
-          if (first_cmdty == NULL)
-          {
-               first_cmdty = split_cmdty;
-               xaccTransSetCurrency(new_txn, first_cmdty);
-          }
-
-          xaccAccountBeginEdit(split_acct);
-          xaccAccountInsertSplit(split_acct, copying_split);
-
-          {
-               gnc_numeric credit_num, debit_num, final;
-               gint gncn_error;
-
-               credit_num = gnc_numeric_zero();
-               debit_num = gnc_numeric_zero();
-
-               _get_credit_formula(creation_data->instance, template_split, &credit_num, creation_data->creation_errors);
-               _get_debit_formula(creation_data->instance, template_split, &debit_num, creation_data->creation_errors);
-                       
-               final = gnc_numeric_sub_fixed( debit_num, credit_num );
-                        
-               gncn_error = gnc_numeric_check(final);
-               if (gncn_error != GNC_ERROR_OK) {
-                    GString *err = g_string_new("");
-                    g_string_printf(err, "Error %d in SX [%s] final gnc_numeric value, using 0 instead.", 
-                                    gncn_error,
-                                    xaccSchedXactionGetName(creation_data->instance->parent->sx));
-                    *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
-                    final = gnc_numeric_zero();
-               }
-
-               xaccSplitSetValue(copying_split, final);
-               if (! gnc_commodity_equal(split_cmdty, first_cmdty))
-               {
-                    GString *exchange_rate_var_name = g_string_sized_new(16);
-                    GncSxVariable *exchange_rate_var;
-                    gnc_numeric exchange_rate, amt;
-
-                    /*
-                      GNCPriceDB *price_db = gnc_pricedb_get_db(gnc_get_current_book());
-                      GNCPrice *price;
-
-                      price = gnc_pricedb_lookup_latest(price_db, first_cmdty, split_cmdty);
-                      if (price == NULL)
-                      {
-                      price = gnc_pricedb_lookup_latest(price_db, split_cmdty, first_cmdty);
-                      if (price == NULL)
-                      {
-                      GString *err = g_string_new("");
-                      g_string_printf(err, "could not find pricedb entry for commodity-pair (%s, %s).",
-                      gnc_commodity_get_mnemonic(first_cmdty),
-                      gnc_commodity_get_mnemonic(split_cmdty));
-                      exchange = gnc_numeric_create(1, 1);
-                      *creation_data->creation_errors = g_list_append(*creation_data->creation_errors, err);
-
-                      }
-                      else
-                      {
-                      exchange = gnc_numeric_div(gnc_numeric_create(1,1),
-                      gnc_price_get_value(price),
-                      1000, GNC_HOW_RND_ROUND);
-                      }
-                      }
-                      else
-                      {
-                      exchange = gnc_price_get_value(price);
-                      }
-                    */
-
-                    exchange_rate = gnc_numeric_zero();
-                    g_string_printf(exchange_rate_var_name, "%s -> %s",
-                                    gnc_commodity_get_mnemonic(split_cmdty),
-                                    gnc_commodity_get_mnemonic(first_cmdty));
-                    exchange_rate_var = (GncSxVariable*)g_hash_table_lookup(creation_data->instance->variable_bindings,
-                                                                            exchange_rate_var_name->str);
-                    if (exchange_rate_var != NULL)
-                    {
-                         exchange_rate = exchange_rate_var->value;
-                    }
-                    g_string_free(exchange_rate_var_name, TRUE);
-
-                    amt = gnc_numeric_mul(final, exchange_rate, 1000, GNC_HOW_RND_ROUND);
-                    xaccSplitSetAmount(copying_split, amt);
-               }
-
-               xaccSplitScrub(copying_split);
-          }
-
-          xaccAccountCommitEdit(split_acct);
-     }
-
-     if (err_flag)
-     {
-          PERR("Some error in new transaction creation...");
-          xaccTransDestroy(new_txn);
-          xaccTransCommitEdit(new_txn);
-          return FALSE;
-     }
-
-     {
-          kvp_frame *txn_frame;
-          /* set a kvp-frame element in the transaction indicating and
-           * pointing-to the SX this was created from. */
-          txn_frame = xaccTransGetSlots(new_txn);
-          kvp_frame_set_guid(txn_frame, "from-sched-xaction", xaccSchedXactionGetGUID(creation_data->instance->parent->sx));
-     }
-
-     xaccTransCommitEdit(new_txn);
-
-     if (creation_data->created_txn_guids != NULL)
-     {
-          *creation_data->created_txn_guids
-               = g_list_append(*(creation_data->created_txn_guids), (gpointer)xaccTransGetGUID(new_txn));
-     }
-
-     return TRUE;
-}
-
-static void
-create_transactions_for_instance(GncSxInstance *instance, GList **created_txn_guids, GList **creation_errors)
-{
-     SxTxnCreationData creation_data;
-     Account *sx_template_account;
-
-     sx_template_account = gnc_sx_get_template_transaction_account(instance->parent->sx);
-
-     creation_data.instance = instance;
-     creation_data.created_txn_guids = created_txn_guids;
-     creation_data.creation_errors = creation_errors;
-
-     xaccAccountForEachTransaction(sx_template_account,
-                                   create_each_transaction_helper,
-                                   &creation_data);
-}
-
 /**
  * @param auto_create_only Will only affect auto-create transactions; the
  * rest of the state will be left alone.
@@ -1505,80 +1120,8 @@
                                GList **created_transaction_guids,
                                GList **creation_errors)
 {
-     GList *list;
-
      g_signal_handler_block(model->instances, model->updated_cb_id);
-
-     for (list = model->instances->sx_instance_list; list != NULL; list = list->next)
-     {
-          GList *instance_list;
-          GncSxInstances *instances = (GncSxInstances*)list->data;
-          GDate *last_occur_date;
-          gint instance_count = 0;
-          gint remain_occur_count = 0;
-
-          last_occur_date = xaccSchedXactionGetLastOccurDate(instances->sx);
-          instance_count = gnc_sx_get_instance_count(instances->sx, NULL);
-          remain_occur_count = xaccSchedXactionGetRemOccur(instances->sx);
-
-          for (instance_list = instances->list; instance_list != NULL; instance_list = instance_list->next)
-          {
-               GncSxInstance *inst = (GncSxInstance*)instance_list->data;
-               gboolean sx_is_auto_create;
-
-               xaccSchedXactionGetAutoCreate(inst->parent->sx, &sx_is_auto_create, NULL);
-               if (auto_create_only && !sx_is_auto_create)
-               {
-                    if (inst->state != SX_INSTANCE_STATE_TO_CREATE)
-                    {
-                         break;
-                    }
-                    continue;
-               }
-
-               if (inst->orig_state == SX_INSTANCE_STATE_POSTPONED
-                   && inst->state != SX_INSTANCE_STATE_POSTPONED)
-               {
-                    // remove from postponed list
-                    g_assert(inst->temporal_state != NULL);
-                    gnc_sx_remove_defer_instance(inst->parent->sx, inst->temporal_state);
-               }
-
-               switch (inst->state)
-               {
-               case SX_INSTANCE_STATE_CREATED:
-                    // nop: we've already processed this.
-                    break;
-               case SX_INSTANCE_STATE_IGNORED:
-                    increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
-                    break;
-               case SX_INSTANCE_STATE_POSTPONED:
-                    if (inst->orig_state != SX_INSTANCE_STATE_POSTPONED)
-                    {
-                         gnc_sx_add_defer_instance(instances->sx, inst->temporal_state);
-                    }
-                    increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
-                    break;
-               case SX_INSTANCE_STATE_TO_CREATE:
-                    create_transactions_for_instance(inst, created_transaction_guids, creation_errors);
-                    increment_sx_state(inst, &last_occur_date, &instance_count, &remain_occur_count);
-                    gnc_sx_slr_model_change_instance_state(model, inst, SX_INSTANCE_STATE_CREATED);
-                    break;
-               case SX_INSTANCE_STATE_REMINDER:
-                    // do nothing
-                    // assert no non-remind instances after this?
-                    break;
-               default:
-                    g_assert_not_reached();
-                    break;
-               }
-          }
-          
-          xaccSchedXactionSetLastOccurDate(instances->sx, last_occur_date);
-          gnc_sx_set_instance_count(instances->sx, instance_count);
-          xaccSchedXactionSetRemOccur(instances->sx, remain_occur_count);
-     }
-
+     gnc_sx_instance_model_effect_change(model->instances, auto_create_only, created_transaction_guids, creation_errors);
      g_signal_handler_unblock(model->instances, model->updated_cb_id);
 }
 



More information about the gnucash-changes mailing list