mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-25 10:20:18 -06:00
1222 lines
36 KiB
C++
1222 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 <stdint.h>
|
|
|
|
#include "qof.h"
|
|
|
|
#include "Account.h"
|
|
#include "Account.hpp"
|
|
#include "SX-book.h"
|
|
#include "SX-book-p.h"
|
|
#include "SX-ttinfo.hpp"
|
|
#include "SchedXaction.h"
|
|
#include "SchedXaction.hpp"
|
|
#include "Transaction.h"
|
|
#include "gnc-engine.h"
|
|
#include "engine-helpers.h"
|
|
#include "qofinstance-p.h"
|
|
|
|
#include <unordered_set>
|
|
|
|
#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, static_cast<const GDate*>(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, static_cast<const GDate*>(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, static_cast<const GDate*>(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, GNC_ACCOUNT(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 = GNC_SX(g_object_new(GNC_TYPE_SCHEDXACTION, NULL));
|
|
xaccSchedXactionInit( sx, book );
|
|
qof_event_gen( &sx->inst, QOF_EVENT_CREATE , NULL);
|
|
|
|
return sx;
|
|
}
|
|
|
|
static void
|
|
delete_template_trans(SchedXaction *sx)
|
|
{
|
|
std::unordered_set<Transaction*> txns;
|
|
auto& splits{xaccAccountGetSplits (sx->template_acct)};
|
|
std::for_each (splits.begin(), splits.end(),
|
|
[&txns](auto s){ txns.insert (xaccSplitGetParent (s)); });
|
|
std::for_each (txns.begin(), txns.end(),
|
|
[](auto t)
|
|
{
|
|
xaccTransBeginEdit (t);
|
|
xaccTransDestroy (t);
|
|
xaccTransCommitEdit (t);
|
|
});
|
|
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 )
|
|
{
|
|
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 );
|
|
|
|
xaccAccountBeginEdit( sx->template_acct );
|
|
xaccAccountDestroy( sx->template_acct );
|
|
|
|
g_list_free_full (sx->deferredList, g_free);
|
|
|
|
/* a GList of Recurrences */
|
|
g_list_free_full (sx->schedule, g_free);
|
|
|
|
/* 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 <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);
|
|
}
|
|
|
|
void
|
|
xaccSchedXactionSetStartDateTT( SchedXaction *sx, const time64 newStart )
|
|
{
|
|
if ( newStart == INT64_MAX )
|
|
{
|
|
/* 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);
|
|
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 <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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
g_free (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)
|
|
{
|
|
g_free (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;
|
|
|
|
g_free (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 (TTSplitInfoPtr 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, s_info->get_memo());
|
|
|
|
/* Set split-action with gnc_set_num_action which is the same as
|
|
* xaccSplitSetAction with these arguments */
|
|
gnc_set_num_action(NULL, split, NULL, s_info->get_action());
|
|
|
|
xaccAccountInsertSplit(parent_acct,
|
|
split);
|
|
|
|
credit_formula = s_info->get_credit_formula ();
|
|
debit_formula = s_info->get_debit_formula ();
|
|
acc_guid = qof_entity_get_guid(QOF_INSTANCE(s_info->get_account ()));
|
|
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, const TTInfoVec& tt_vec, QofBook *book)
|
|
{
|
|
Transaction *new_trans;
|
|
|
|
g_return_if_fail (book);
|
|
|
|
/* delete any old transactions, if there are any */
|
|
delete_template_trans( sx );
|
|
|
|
for (auto tti : tt_vec)
|
|
{
|
|
new_trans = xaccMallocTransaction(book);
|
|
|
|
xaccTransBeginEdit(new_trans);
|
|
xaccTransSetDescription(new_trans, tti->get_description());
|
|
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, tti->get_num(), NULL);
|
|
xaccTransSetNotes (new_trans, tti->get_notes ());
|
|
xaccTransSetCurrency (new_trans, tti->get_currency ());
|
|
|
|
for (auto s_info : tti->get_template_splits())
|
|
{
|
|
auto 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 )
|
|
{
|
|
auto 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, static_cast<GDateMonth>(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_new(SXTmpStateData, 1);
|
|
*toRet = *tsd;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
g_free (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);
|
|
|
|
// Now destroy the template root account
|
|
gnc_book_set_template_root (book, 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 = ) (void* (*)(QofBook*))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);
|
|
}
|