mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
This will avoid a ninja-build from picking up a config.h generated by the autotools build (in the root build directory). Picking up the wrong config.h may lead to all kinds of subtle issues if the autotools run was done with different options than the cmake run.
1221 lines
36 KiB
C
1221 lines
36 KiB
C
/********************************************************************\
|
|
* SchedXaction.c -- Scheduled Transaction implementation. *
|
|
* Copyright (C) 2001,2007 Joshua Sled <jsled@asynchronous.org> *
|
|
* *
|
|
* 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 <config.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <string.h>
|
|
|
|
#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 occurances",
|
|
"Total number of occurances 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 occurances remaining",
|
|
"Remaining number of occurances 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 occurence 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 Occurance Date",
|
|
"Date for the last occurance 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);
|
|
|
|
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;
|
|
}
|
|
|
|
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 <rgmerk>.
|
|
* 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);
|
|
}
|
|
|
|
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 permissable 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 <rgmerk>.
|
|
* 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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.
|
|
*/
|
|
if (! g_date_valid( &prev_occur ) && g_date_valid(&sx->start_date))
|
|
{
|
|
/* 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;
|
|
toRet = g_memdup (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 modifiy
|
|
* 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)xaccSchedXactionGetStartDate,
|
|
(QofSetterFunc)xaccSchedXactionSetStartDate
|
|
},
|
|
{
|
|
GNC_SX_LAST_DATE, QOF_TYPE_DATE, (QofAccessFunc)xaccSchedXactionGetLastOccurDate,
|
|
(QofSetterFunc)xaccSchedXactionSetLastOccurDate
|
|
},
|
|
{
|
|
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);
|
|
}
|