|
GnuCash 2.4.99
|
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
1.7.4