/********************************************************************\ * SchedXaction.c -- Scheduled Transaction implementation. * * Copyright (C) 2001,2007 Joshua Sled * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * * * \********************************************************************/ #include #include #include #include #include #include "qof.h" #include "Account.h" #include "SX-book.h" #include "SX-ttinfo.h" #include "SchedXaction.h" #include "Transaction.h" #include "gnc-engine.h" #include "engine-helpers.h" #include "qofinstance-p.h" #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "gnc.engine.sx" enum { PROP_0, PROP_NAME, /* Table */ PROP_ENABLED, /* Table */ PROP_START_DATE, /* Table */ PROP_END_DATE, /* Table */ PROP_LAST_OCCURANCE_DATE, /* Table */ PROP_NUM_OCCURANCE, /* Table */ PROP_REM_OCCURANCE, /* Table */ PROP_AUTO_CREATE, /* Table */ PROP_AUTO_CREATE_NOTIFY, /* Table */ PROP_ADVANCE_CREATION_DAYS, /* Table */ PROP_ADVANCE_REMINDER_DAYS, /* Table */ PROP_INSTANCE_COUNT, /* Table */ PROP_TEMPLATE_ACCOUNT /* Table */ }; /* GObject initialization */ G_DEFINE_TYPE(SchedXaction, gnc_schedxaction, QOF_TYPE_INSTANCE); static void gnc_schedxaction_init(SchedXaction* sx) { sx->schedule = NULL; g_date_clear( &sx->last_date, 1 ); g_date_clear( &sx->start_date, 1 ); g_date_clear( &sx->end_date, 1 ); sx->enabled = 1; sx->num_occurances_total = 0; sx->autoCreateOption = FALSE; sx->autoCreateNotify = FALSE; sx->advanceCreateDays = 0; sx->advanceRemindDays = 0; sx->instance_num = 0; sx->deferredList = NULL; } static void gnc_schedxaction_dispose(GObject *sxp) { G_OBJECT_CLASS(gnc_schedxaction_parent_class)->dispose(sxp); } static void gnc_schedxaction_finalize(GObject* sxp) { G_OBJECT_CLASS(gnc_schedxaction_parent_class)->finalize(sxp); } /* Note that g_value_set_object() refs the object, as does * g_object_get(). But g_object_get() only unrefs once when it disgorges * the object, leaving an unbalanced ref, which leaks. So instead of * using g_value_set_object(), use g_value_take_object() which doesn't * ref the object when used in get_property(). */ static void gnc_schedxaction_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SchedXaction *sx; g_return_if_fail(GNC_IS_SCHEDXACTION(object)); sx = GNC_SCHEDXACTION(object); switch (prop_id) { case PROP_NAME: g_value_set_string(value, sx->name); break; case PROP_ENABLED: g_value_set_boolean(value, sx->enabled); break; case PROP_NUM_OCCURANCE: g_value_set_int(value, sx->num_occurances_total); break; case PROP_REM_OCCURANCE: g_value_set_int(value, sx->num_occurances_remain); break; case PROP_AUTO_CREATE: g_value_set_boolean(value, sx->autoCreateOption); break; case PROP_AUTO_CREATE_NOTIFY: g_value_set_boolean(value, sx->autoCreateNotify); break; case PROP_ADVANCE_CREATION_DAYS: g_value_set_int(value, sx->advanceCreateDays); break; case PROP_ADVANCE_REMINDER_DAYS: g_value_set_int(value, sx->advanceRemindDays); break; case PROP_START_DATE: g_value_set_boxed(value, &sx->start_date); break; case PROP_END_DATE: /* g_value_set_boxed raises a critical error if sx->end_date * is invalid */ if (g_date_valid (&sx->end_date)) g_value_set_boxed(value, &sx->end_date); break; case PROP_LAST_OCCURANCE_DATE: /* g_value_set_boxed raises a critical error if sx->last_date * is invalid */ if (g_date_valid (&sx->last_date)) g_value_set_boxed(value, &sx->last_date); break; case PROP_INSTANCE_COUNT: g_value_set_int(value, sx->instance_num); break; case PROP_TEMPLATE_ACCOUNT: g_value_take_object(value, sx->template_acct); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gnc_schedxaction_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SchedXaction *sx; g_return_if_fail(GNC_IS_SCHEDXACTION(object)); sx = GNC_SCHEDXACTION(object); g_assert (qof_instance_get_editlevel(sx)); switch (prop_id) { case PROP_NAME: xaccSchedXactionSetName(sx, g_value_get_string(value)); break; case PROP_ENABLED: xaccSchedXactionSetEnabled(sx, g_value_get_boolean(value)); break; case PROP_NUM_OCCURANCE: xaccSchedXactionSetNumOccur(sx, g_value_get_int(value)); break; case PROP_REM_OCCURANCE: xaccSchedXactionSetRemOccur(sx, g_value_get_int(value)); break; case PROP_AUTO_CREATE: xaccSchedXactionSetAutoCreate(sx, g_value_get_boolean(value), sx->autoCreateNotify); break; case PROP_AUTO_CREATE_NOTIFY: xaccSchedXactionSetAutoCreate(sx, sx->autoCreateOption, g_value_get_boolean(value)); break; case PROP_ADVANCE_CREATION_DAYS: xaccSchedXactionSetAdvanceCreation(sx, g_value_get_int(value)); break; case PROP_ADVANCE_REMINDER_DAYS: xaccSchedXactionSetAdvanceReminder(sx, g_value_get_int(value)); break; case PROP_START_DATE: /* Note: when passed through a boxed gvalue, the julian value of the date is copied. The date may appear invalid until a function requiring for dmy calculation is called. */ xaccSchedXactionSetStartDate(sx, g_value_get_boxed(value)); break; case PROP_END_DATE: /* Note: when passed through a boxed gvalue, the julian value of the date is copied. The date may appear invalid until a function requiring for dmy calculation is called. */ xaccSchedXactionSetEndDate(sx, g_value_get_boxed(value)); break; case PROP_LAST_OCCURANCE_DATE: /* Note: when passed through a boxed gvalue, the julian value of the date is copied. The date may appear invalid until a function requiring for dmy calculation is called. */ xaccSchedXactionSetLastOccurDate(sx, g_value_get_boxed(value)); break; case PROP_INSTANCE_COUNT: gnc_sx_set_instance_count(sx, g_value_get_int(value)); break; case PROP_TEMPLATE_ACCOUNT: sx_set_template_account(sx, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gnc_schedxaction_class_init (SchedXactionClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = gnc_schedxaction_dispose; gobject_class->finalize = gnc_schedxaction_finalize; gobject_class->set_property = gnc_schedxaction_set_property; gobject_class->get_property = gnc_schedxaction_get_property; g_object_class_install_property (gobject_class, PROP_NAME, g_param_spec_string ("name", "Scheduled Transaction Name", "The name is an arbitrary string " "assigned by the user. It is intended to " "a short, 5 to 30 character long string " "that is displayed by the GUI.", NULL, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ENABLED, g_param_spec_boolean ("enabled", "Enabled", "TRUE if the scheduled transaction is enabled.", TRUE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_NUM_OCCURANCE, g_param_spec_int ("num-occurance", "Number of occurrences", "Total number of occurrences for this scheduled transaction.", 0, G_MAXINT16, 1, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_REM_OCCURANCE, g_param_spec_int ("rem-occurance", "Number of occurrences remaining", "Remaining number of occurrences for this scheduled transaction.", 0, G_MAXINT16, 1, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_AUTO_CREATE, g_param_spec_boolean ("auto-create", "Auto-create", "TRUE if the transaction will be automatically " "created when its time comes.", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_AUTO_CREATE_NOTIFY, g_param_spec_boolean ("auto-create-notify", "Auto-create-notify", "TRUE if the the user will be notified when the transaction " "is automatically created.", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ADVANCE_CREATION_DAYS, g_param_spec_int ("advance-creation-days", "Days in advance to create", "Number of days in advance to create this scheduled transaction.", 0, G_MAXINT16, 0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ADVANCE_REMINDER_DAYS, g_param_spec_int ("advance-reminder-days", "Days in advance to remind", "Number of days in advance to remind about this scheduled transaction.", 0, G_MAXINT16, 0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_START_DATE, g_param_spec_boxed("start-date", "Start Date", "Date for the first occurrence for the scheduled transaction.", G_TYPE_DATE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_END_DATE, g_param_spec_boxed("end-date", "End Date", "Date for the scheduled transaction to end.", G_TYPE_DATE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_LAST_OCCURANCE_DATE, g_param_spec_boxed("last-occurance-date", "Last Occurrence Date", "Date for the last occurrence of the scheduled transaction.", G_TYPE_DATE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_INSTANCE_COUNT, g_param_spec_int ("instance-count", "Instance count", "Number of instances of this scheduled transaction.", 0, G_MAXINT16, 0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_TEMPLATE_ACCOUNT, g_param_spec_object("template-account", "Template account", "Account which holds the template transactions.", GNC_TYPE_ACCOUNT, G_PARAM_READWRITE)); } static void xaccSchedXactionInit(SchedXaction *sx, QofBook *book) { Account *ra; const GncGUID *guid; gchar guidstr[GUID_ENCODING_LENGTH+1]; qof_instance_init_data (&sx->inst, GNC_ID_SCHEDXACTION, book); /* create a new template account for our splits */ sx->template_acct = xaccMallocAccount(book); guid = qof_instance_get_guid( sx ); xaccAccountBeginEdit( sx->template_acct ); guid_to_string_buff( guid, guidstr ); xaccAccountSetName( sx->template_acct, guidstr); xaccAccountSetCommodity (sx->template_acct, gnc_commodity_table_lookup( gnc_commodity_table_get_table(book), GNC_COMMODITY_NS_TEMPLATE, "template") ); xaccAccountSetType( sx->template_acct, ACCT_TYPE_BANK ); xaccAccountCommitEdit( sx->template_acct ); ra = gnc_book_get_template_root( book ); gnc_account_append_child( ra, sx->template_acct ); } SchedXaction* xaccSchedXactionMalloc(QofBook *book) { SchedXaction *sx; g_return_val_if_fail (book, NULL); sx = g_object_new(GNC_TYPE_SCHEDXACTION, NULL); xaccSchedXactionInit( sx, book ); qof_event_gen( &sx->inst, QOF_EVENT_CREATE , NULL); return sx; } static void sxprivTransMapDelete( gpointer data, gpointer user_data ) { Transaction *t = (Transaction *) data; xaccTransBeginEdit( t ); xaccTransDestroy( t ); xaccTransCommitEdit( t ); return; } static void delete_template_trans(SchedXaction *sx) { GList *templ_acct_splits, *curr_split_listref; Split *curr_split; Transaction *split_trans; GList *templ_acct_transactions = NULL; templ_acct_splits = xaccAccountGetSplitList(sx->template_acct); for (curr_split_listref = templ_acct_splits; curr_split_listref; curr_split_listref = curr_split_listref->next) { curr_split = (Split *) curr_split_listref->data; split_trans = xaccSplitGetParent(curr_split); if (! (g_list_find(templ_acct_transactions, split_trans))) { templ_acct_transactions = g_list_prepend(templ_acct_transactions, split_trans); } } g_list_foreach(templ_acct_transactions, sxprivTransMapDelete, NULL); g_list_free (templ_acct_transactions); return; } void sx_set_template_account (SchedXaction *sx, Account *account) { Account *old; old = sx->template_acct; sx->template_acct = account; if (old) { xaccAccountBeginEdit(old); xaccAccountDestroy(old); } } void xaccSchedXactionDestroy( SchedXaction *sx ) { qof_instance_set_destroying( QOF_INSTANCE(sx), TRUE ); gnc_sx_commit_edit( sx ); } static void xaccSchedXactionFree( SchedXaction *sx ) { GList *l; if ( sx == NULL ) return; qof_event_gen( &sx->inst, QOF_EVENT_DESTROY , NULL); if ( sx->name ) g_free( sx->name ); /* * we have to delete the transactions in the * template account ourselves */ delete_template_trans( sx ); /* * xaccAccountDestroy removes the account from * its group for us AFAICT. If shutting down, * the account is being deleted separately. */ if (!qof_book_shutting_down(qof_instance_get_book(sx))) { xaccAccountBeginEdit(sx->template_acct); xaccAccountDestroy(sx->template_acct); } for ( l = sx->deferredList; l; l = l->next ) { gnc_sx_destroy_temporal_state( l->data ); l->data = NULL; } if ( sx->deferredList ) { g_list_free( sx->deferredList ); sx->deferredList = NULL; } /* qof_instance_release (&sx->inst); */ g_object_unref( sx ); } /* ============================================================ */ void gnc_sx_begin_edit (SchedXaction *sx) { qof_begin_edit (&sx->inst); } static void sx_free(QofInstance* inst ) { xaccSchedXactionFree( GNC_SX(inst) ); } static void commit_err (QofInstance *inst, QofBackendError errcode) { g_critical("Failed to commit: %d", errcode); gnc_engine_signal_commit_error( errcode ); } static void commit_done(QofInstance *inst) { qof_event_gen (inst, QOF_EVENT_MODIFY, NULL); } void gnc_sx_commit_edit (SchedXaction *sx) { if (!qof_commit_edit (QOF_INSTANCE(sx))) return; qof_commit_edit_part2 (&sx->inst, commit_err, commit_done, sx_free); } /* ============================================================ */ GList* gnc_sx_get_schedule(const SchedXaction *sx) { return sx->schedule; } void gnc_sx_set_schedule(SchedXaction *sx, GList *schedule) { g_return_if_fail(sx); gnc_sx_begin_edit(sx); sx->schedule = schedule; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } gchar * xaccSchedXactionGetName( const SchedXaction *sx ) { return sx->name; } void xaccSchedXactionSetName( SchedXaction *sx, const gchar *newName ) { g_return_if_fail( newName != NULL ); gnc_sx_begin_edit(sx); if ( sx->name != NULL ) { g_free( sx->name ); sx->name = NULL; } sx->name = g_strdup( newName ); qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } const GDate* xaccSchedXactionGetStartDate(const SchedXaction *sx ) { g_assert (sx); return &sx->start_date; } time64 xaccSchedXactionGetStartDateTT(const SchedXaction *sx ) { g_assert (sx); return gdate_to_time64(sx->start_date); } void xaccSchedXactionSetStartDate( SchedXaction *sx, const GDate* newStart ) { if ( newStart == NULL || !g_date_valid( newStart )) { /* XXX: I reject the bad data - is this the right * thing to do . * This warning is only human readable - the caller * doesn't know the call failed. This is bad */ g_critical("Invalid Start Date"); return; } gnc_sx_begin_edit(sx); sx->start_date = *newStart; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } void xaccSchedXactionSetStartDateTT( SchedXaction *sx, const time64 newStart ) { if ( newStart == INT64_MAX ) { /* XXX: I reject the bad data - is this the right * thing to do . * This warning is only human readable - the caller * doesn't know the call failed. This is bad */ g_critical("Invalid Start Date"); return; } gnc_sx_begin_edit(sx); gnc_gdate_set_time64(&sx->start_date, newStart); qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } gboolean xaccSchedXactionHasEndDate( const SchedXaction *sx ) { return sx != NULL && g_date_valid( &sx->end_date ); } const GDate* xaccSchedXactionGetEndDate(const SchedXaction *sx ) { g_assert (sx); return &sx->end_date; } void xaccSchedXactionSetEndDate( SchedXaction *sx, const GDate *newEnd ) { /* Note that an invalid GDate IS a permissible value: It means that * the SX is to run "forever". See gnc_sxed_save_sx() and * schedXact_editor_populate() in dialog-sx-editor.c. */ if (newEnd == NULL || (g_date_valid(newEnd) && g_date_compare( newEnd, &sx->start_date ) < 0 )) { /* XXX: I reject the bad data - is this the right * thing to do . * This warning is only human readable - the caller * doesn't know the call failed. This is bad */ g_critical("Bad End Date: Invalid or before Start Date"); return; } gnc_sx_begin_edit(sx); sx->end_date = *newEnd; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } const GDate* xaccSchedXactionGetLastOccurDate(const SchedXaction *sx ) { return &sx->last_date; } time64 xaccSchedXactionGetLastOccurDateTT(const SchedXaction *sx ) { return gdate_to_time64(sx->last_date); } void xaccSchedXactionSetLastOccurDate(SchedXaction *sx, const GDate* new_last_occur) { g_return_if_fail (new_last_occur != NULL); if (g_date_valid(&sx->last_date) && g_date_compare(&sx->last_date, new_last_occur) == 0) return; gnc_sx_begin_edit(sx); sx->last_date = *new_last_occur; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } void xaccSchedXactionSetLastOccurDateTT(SchedXaction *sx, time64 new_last_occur) { GDate last_occur; g_return_if_fail (new_last_occur != INT64_MAX); gnc_gdate_set_time64(&last_occur, new_last_occur); if (g_date_valid(&sx->last_date) && g_date_compare(&sx->last_date, &last_occur) == 0) return; gnc_sx_begin_edit(sx); sx->last_date = last_occur; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } gboolean xaccSchedXactionHasOccurDef( const SchedXaction *sx ) { return ( xaccSchedXactionGetNumOccur( sx ) != 0 ); } gint xaccSchedXactionGetNumOccur( const SchedXaction *sx ) { return sx->num_occurances_total; } void xaccSchedXactionSetNumOccur(SchedXaction *sx, gint new_num) { if (sx->num_occurances_total == new_num) return; gnc_sx_begin_edit(sx); sx->num_occurances_remain = sx->num_occurances_total = new_num; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } gint xaccSchedXactionGetRemOccur( const SchedXaction *sx ) { return sx->num_occurances_remain; } void xaccSchedXactionSetRemOccur(SchedXaction *sx, gint num_remain) { /* FIXME This condition can be tightened up */ if (num_remain > sx->num_occurances_total) { g_warning("number remaining [%d] > total occurrences [%d]", num_remain, sx->num_occurances_total); } else { if (num_remain == sx->num_occurances_remain) return; gnc_sx_begin_edit(sx); sx->num_occurances_remain = num_remain; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } } gint gnc_sx_get_num_occur_daterange(const SchedXaction *sx, const GDate* start_date, const GDate* end_date) { gint result = 0; SXTmpStateData *tmpState; gboolean countFirstDate; /* SX still active? If not, return now. */ if ((xaccSchedXactionHasOccurDef(sx) && xaccSchedXactionGetRemOccur(sx) <= 0) || (xaccSchedXactionHasEndDate(sx) && g_date_compare(xaccSchedXactionGetEndDate(sx), start_date) < 0)) { return result; } tmpState = gnc_sx_create_temporal_state (sx); /* Should we count the first valid date we encounter? Only if the * SX has not yet occurred so far, or if its last valid date was * before the start date. */ countFirstDate = !g_date_valid(&tmpState->last_date) || (g_date_compare(&tmpState->last_date, start_date) < 0); /* No valid date? SX has never occurred so far. */ if (!g_date_valid(&tmpState->last_date)) { /* SX has never occurred so far */ gnc_sx_incr_temporal_state (sx, tmpState); if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0) { gnc_sx_destroy_temporal_state (tmpState); return result; } } /* Increase the tmpState until we are in our interval of * interest. Only calculate anything if the sx hasn't already * ended. */ while (g_date_compare(&tmpState->last_date, start_date) < 0) { gnc_sx_incr_temporal_state (sx, tmpState); if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0) { gnc_sx_destroy_temporal_state (tmpState); return result; } } /* Now we are in our interval of interest. Increment the * occurrence date until we are beyond the end of our * interval. Make sure to check for invalid dates here: It means * the SX has ended. */ while (g_date_valid(&tmpState->last_date) && (g_date_compare(&tmpState->last_date, end_date) <= 0) && (!xaccSchedXactionHasEndDate(sx) || g_date_compare(&tmpState->last_date, xaccSchedXactionGetEndDate(sx)) <= 0) && (!xaccSchedXactionHasOccurDef(sx) /* The >=0 (i.e. the ==) is important here, otherwise * we miss the last valid occurrence of a SX which is * limited by num_occur */ || tmpState->num_occur_rem >= 0)) { ++result; gnc_sx_incr_temporal_state (sx, tmpState); } /* If the first valid date shouldn't be counted, decrease the * result number by one. */ if (!countFirstDate && result > 0) --result; gnc_sx_destroy_temporal_state (tmpState); return result; } gboolean xaccSchedXactionGetEnabled( const SchedXaction *sx ) { return sx->enabled; } void xaccSchedXactionSetEnabled( SchedXaction *sx, gboolean newEnabled) { gnc_sx_begin_edit(sx); sx->enabled = newEnabled; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } void xaccSchedXactionGetAutoCreate( const SchedXaction *sx, gboolean *outAutoCreate, gboolean *outNotify ) { if (outAutoCreate != NULL) *outAutoCreate = sx->autoCreateOption; if (outNotify != NULL) *outNotify = sx->autoCreateNotify; return; } void xaccSchedXactionSetAutoCreate( SchedXaction *sx, gboolean newAutoCreate, gboolean newNotify ) { gnc_sx_begin_edit(sx); sx->autoCreateOption = newAutoCreate; sx->autoCreateNotify = newNotify; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); return; } gint xaccSchedXactionGetAdvanceCreation( const SchedXaction *sx ) { return sx->advanceCreateDays; } void xaccSchedXactionSetAdvanceCreation( SchedXaction *sx, gint createDays ) { gnc_sx_begin_edit(sx); sx->advanceCreateDays = createDays; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } gint xaccSchedXactionGetAdvanceReminder( const SchedXaction *sx ) { return sx->advanceRemindDays; } void xaccSchedXactionSetAdvanceReminder( SchedXaction *sx, gint reminderDays ) { gnc_sx_begin_edit(sx); sx->advanceRemindDays = reminderDays; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } GDate xaccSchedXactionGetNextInstance (const SchedXaction *sx, SXTmpStateData *tsd) { GDate prev_occur, next_occur; g_date_clear( &prev_occur, 1 ); if ( tsd != NULL ) prev_occur = tsd->last_date; /* If prev_occur is in the "cleared" state and sx->start_date isn't, then * we're at the beginning. We want to pretend prev_occur is the day before * the start_date in case the start_date is today so that the SX will fire * today. If start_date isn't valid either then the SX will fire anyway, no * harm done. prev_occur cannot be before start_date either. */ if (g_date_valid (&sx->start_date) && (!g_date_valid ( &prev_occur ) || g_date_compare (&prev_occur, &sx->start_date)<0)) { /* We must be at the beginning. */ prev_occur = sx->start_date; g_date_subtract_days (&prev_occur, 1 ); } recurrenceListNextInstance(sx->schedule, &prev_occur, &next_occur); if ( xaccSchedXactionHasEndDate( sx ) ) { const GDate *end_date = xaccSchedXactionGetEndDate( sx ); if ( g_date_compare( &next_occur, end_date ) > 0 ) { g_date_clear( &next_occur, 1 ); } } else if ( xaccSchedXactionHasOccurDef( sx ) ) { if ((tsd && tsd->num_occur_rem == 0) || (!tsd && sx->num_occurances_remain == 0 )) { g_date_clear( &next_occur, 1 ); } } return next_occur; } gint gnc_sx_get_instance_count( const SchedXaction *sx, SXTmpStateData *stateData ) { gint toRet = -1; SXTmpStateData *tsd; if ( stateData ) { tsd = (SXTmpStateData*)stateData; toRet = tsd->num_inst; } else { toRet = sx->instance_num; } return toRet; } void gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num) { g_return_if_fail(sx); if (sx->instance_num == instance_num) return; gnc_sx_begin_edit(sx); sx->instance_num = instance_num; qof_instance_set_dirty(&sx->inst); gnc_sx_commit_edit(sx); } GList * xaccSchedXactionGetSplits( const SchedXaction *sx ) { g_return_val_if_fail( sx, NULL ); return xaccAccountGetSplitList(sx->template_acct); } static Split * pack_split_info (TTSplitInfo *s_info, Account *parent_acct, Transaction *parent_trans, QofBook *book) { Split *split; const gchar *credit_formula; const gchar *debit_formula; const GncGUID *acc_guid; split = xaccMallocSplit(book); xaccSplitSetMemo(split, gnc_ttsplitinfo_get_memo(s_info)); /* Set split-action with gnc_set_num_action which is the same as * xaccSplitSetAction with these arguments */ gnc_set_num_action(NULL, split, NULL, gnc_ttsplitinfo_get_action(s_info)); xaccAccountInsertSplit(parent_acct, split); credit_formula = gnc_ttsplitinfo_get_credit_formula(s_info); debit_formula = gnc_ttsplitinfo_get_debit_formula(s_info); acc_guid = qof_entity_get_guid(QOF_INSTANCE(gnc_ttsplitinfo_get_account(s_info))); qof_instance_set (QOF_INSTANCE (split), "sx-credit-formula", credit_formula, "sx-debit-formula", debit_formula, "sx-account", acc_guid, NULL); return split; } void xaccSchedXactionSetTemplateTrans(SchedXaction *sx, GList *t_t_list, QofBook *book) { Transaction *new_trans; TTInfo *tti; TTSplitInfo *s_info; Split *new_split; GList *split_list; g_return_if_fail (book); /* delete any old transactions, if there are any */ delete_template_trans( sx ); for (; t_t_list != NULL; t_t_list = t_t_list->next) { tti = t_t_list->data; new_trans = xaccMallocTransaction(book); xaccTransBeginEdit(new_trans); xaccTransSetDescription(new_trans, gnc_ttinfo_get_description(tti)); xaccTransSetDatePostedSecsNormalized(new_trans, gnc_time (NULL)); /* Set tran-num with gnc_set_num_action which is the same as * xaccTransSetNum with these arguments */ gnc_set_num_action(new_trans, NULL, gnc_ttinfo_get_num(tti), NULL); xaccTransSetNotes (new_trans, gnc_ttinfo_get_notes (tti)); xaccTransSetCurrency( new_trans, gnc_ttinfo_get_currency(tti) ); for (split_list = gnc_ttinfo_get_template_splits(tti); split_list; split_list = split_list->next) { s_info = split_list->data; new_split = pack_split_info(s_info, sx->template_acct, new_trans, book); xaccTransAppendSplit(new_trans, new_split); } xaccTransCommitEdit(new_trans); } } SXTmpStateData* gnc_sx_create_temporal_state(const SchedXaction *sx ) { SXTmpStateData *toRet = g_new0( SXTmpStateData, 1 ); if (g_date_valid (&(sx->last_date))) toRet->last_date = sx->last_date; else g_date_set_dmy (&(toRet->last_date), 1, 1, 1970); toRet->num_occur_rem = sx->num_occurances_remain; toRet->num_inst = sx->instance_num; return toRet; } void gnc_sx_incr_temporal_state(const SchedXaction *sx, SXTmpStateData *tsd ) { g_return_if_fail(tsd != NULL); tsd->last_date = xaccSchedXactionGetNextInstance (sx, tsd); if (xaccSchedXactionHasOccurDef (sx)) { --tsd->num_occur_rem; } ++tsd->num_inst; } void gnc_sx_destroy_temporal_state (SXTmpStateData *tsd) { g_free(tsd); } SXTmpStateData* gnc_sx_clone_temporal_state (SXTmpStateData *tsd) { SXTmpStateData *toRet = NULL; if(tsd) { toRet = g_malloc(sizeof(SXTmpStateData)); toRet = memcpy (toRet, tsd, sizeof (SXTmpStateData)); } return toRet; } static gint _temporal_state_data_cmp( gconstpointer a, gconstpointer b ) { const SXTmpStateData *tsd_a = (SXTmpStateData*)a; const SXTmpStateData *tsd_b = (SXTmpStateData*)b; if ( !tsd_a && !tsd_b ) return 0; if (tsd_a == tsd_b) return 0; if ( !tsd_a ) return 1; if ( !tsd_b ) return -1; return g_date_compare( &tsd_a->last_date, &tsd_b->last_date ); } /** * Adds an instance to the deferred list of the SX. Added instances are * added in (date-)sorted order. **/ void gnc_sx_add_defer_instance( SchedXaction *sx, void *deferStateData ) { sx->deferredList = g_list_insert_sorted( sx->deferredList, deferStateData, _temporal_state_data_cmp ); } /** * Removes an instance from the deferred list. The saved SXTmpStateData existed * for comparison only, so destroy it. **/ void gnc_sx_remove_defer_instance( SchedXaction *sx, void *deferStateData ) { GList *found_by_value; found_by_value = g_list_find_custom( sx->deferredList, deferStateData, _temporal_state_data_cmp); if (found_by_value == NULL) { g_warning("unable to find deferred instance"); return; } gnc_sx_destroy_temporal_state(found_by_value->data); sx->deferredList = g_list_delete_link(sx->deferredList, found_by_value); } /** * Returns the defer list from the SX; this is a (date-)sorted * temporal-state-data instance list. The list should not be modified by the * caller; use the gnc_sx_{add,remove}_defer_instance() functions to modify * the list. * * @param sx Scheduled transaction * @return Defer list which must not be modified by the caller **/ GList* gnc_sx_get_defer_instances( SchedXaction *sx ) { return sx->deferredList; } static void destroy_sx_on_book_close(QofInstance *ent, gpointer data) { SchedXaction* sx = GNC_SCHEDXACTION(ent); gnc_sx_begin_edit(sx); xaccSchedXactionDestroy(sx); } /** * Destroys all SXes in the book because the book is being destroyed. * * @param book Book being destroyed */ static void gnc_sx_book_end(QofBook* book) { QofCollection *col; col = qof_book_get_collection(book, GNC_ID_SCHEDXACTION); qof_collection_foreach(col, destroy_sx_on_book_close, NULL); } #ifdef _MSC_VER /* MSVC compiler doesn't have C99 "designated initializers" * so we wrap them in a macro that is empty on MSVC. */ # define DI(x) /* */ #else # define DI(x) x #endif static QofObject SXDesc = { DI(.interface_version = ) QOF_OBJECT_VERSION, DI(.e_type = ) GNC_SX_ID, DI(.type_label = ) "Scheduled Transaction", DI(.create = ) (gpointer)xaccSchedXactionMalloc, DI(.book_begin = ) NULL, DI(.book_end = ) gnc_sx_book_end, DI(.is_dirty = ) qof_collection_is_dirty, DI(.mark_clean = ) qof_collection_mark_clean, DI(.foreach = ) qof_collection_foreach, DI(.printable = ) NULL, DI(.version_cmp = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp, }; gboolean SXRegister(void) { static QofParam params[] = { { GNC_SX_NAME, QOF_TYPE_STRING, (QofAccessFunc)xaccSchedXactionGetName, (QofSetterFunc)xaccSchedXactionSetName }, { GNC_SX_START_DATE, QOF_TYPE_DATE, (QofAccessFunc)xaccSchedXactionGetStartDateTT, (QofSetterFunc)xaccSchedXactionSetStartDateTT }, { GNC_SX_LAST_DATE, QOF_TYPE_DATE, (QofAccessFunc)xaccSchedXactionGetLastOccurDateTT, (QofSetterFunc)xaccSchedXactionSetLastOccurDateTT }, { GNC_SX_NUM_OCCUR, QOF_TYPE_INT64, (QofAccessFunc)xaccSchedXactionGetNumOccur, (QofSetterFunc)xaccSchedXactionSetNumOccur }, { GNC_SX_REM_OCCUR, QOF_TYPE_INT64, (QofAccessFunc)xaccSchedXactionGetRemOccur, (QofSetterFunc)xaccSchedXactionSetRemOccur }, { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL }, { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL }, { NULL }, }; qof_class_register(GNC_SX_ID, NULL, params); return qof_object_register(&SXDesc); }