1 /********************************************************************\
2  * SchedXaction.c -- Scheduled Transaction implementation.          *
3  * Copyright (C) 2001,2007 Joshua Sled <jsled@asynchronous.org>     *
4  *                                                                  *
5  * This program is free software; you can redistribute it and/or    *
6  * modify it under the terms of the GNU General Public License as   *
7  * published by the Free Software Foundation; either version 2 of   *
8  * the License, or (at your option) any later version.              *
9  *                                                                  *
10  * This program is distributed in the hope that it will be useful,  *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
13  * GNU General Public License for more details.                     *
14  *                                                                  *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact:                        *
17  *                                                                  *
18  * Free Software Foundation           Voice:  +1-617-542-5942       *
19  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
20  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
21  *                                                                  *
22 \********************************************************************/
23 
24 #include <config.h>
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <string.h>
29 #include <stdint.h>
30 
31 #include "qof.h"
32 
33 #include "Account.h"
34 #include "SX-book.h"
35 #include "SX-ttinfo.h"
36 #include "SchedXaction.h"
37 #include "Transaction.h"
38 #include "gnc-engine.h"
39 #include "engine-helpers.h"
40 #include "qofinstance-p.h"
41 
42 #undef G_LOG_DOMAIN
43 #define G_LOG_DOMAIN "gnc.engine.sx"
44 
45 enum
46 {
47     PROP_0,
48     PROP_NAME,				/* Table */
49     PROP_ENABLED,			/* Table */
50     PROP_START_DATE,			/* Table */
51     PROP_END_DATE,			/* Table */
52     PROP_LAST_OCCURANCE_DATE,		/* Table */
53     PROP_NUM_OCCURANCE,			/* Table */
54     PROP_REM_OCCURANCE,			/* Table */
55     PROP_AUTO_CREATE,			/* Table */
56     PROP_AUTO_CREATE_NOTIFY,		/* Table */
57     PROP_ADVANCE_CREATION_DAYS,		/* Table */
58     PROP_ADVANCE_REMINDER_DAYS,		/* Table */
59     PROP_INSTANCE_COUNT,		/* Table */
60     PROP_TEMPLATE_ACCOUNT		/* Table */
61 };
62 
63 /* GObject initialization */
64 G_DEFINE_TYPE(SchedXaction, gnc_schedxaction, QOF_TYPE_INSTANCE);
65 
66 static void
gnc_schedxaction_init(SchedXaction * sx)67 gnc_schedxaction_init(SchedXaction* sx)
68 {
69     sx->schedule = NULL;
70 
71     g_date_clear( &sx->last_date, 1 );
72     g_date_clear( &sx->start_date, 1 );
73     g_date_clear( &sx->end_date, 1 );
74 
75     sx->enabled = 1;
76     sx->num_occurances_total = 0;
77     sx->autoCreateOption = FALSE;
78     sx->autoCreateNotify = FALSE;
79     sx->advanceCreateDays = 0;
80     sx->advanceRemindDays = 0;
81     sx->instance_num = 0;
82     sx->deferredList = NULL;
83 }
84 
85 static void
gnc_schedxaction_dispose(GObject * sxp)86 gnc_schedxaction_dispose(GObject *sxp)
87 {
88     G_OBJECT_CLASS(gnc_schedxaction_parent_class)->dispose(sxp);
89 }
90 
91 static void
gnc_schedxaction_finalize(GObject * sxp)92 gnc_schedxaction_finalize(GObject* sxp)
93 {
94     G_OBJECT_CLASS(gnc_schedxaction_parent_class)->finalize(sxp);
95 }
96 
97 /* Note that g_value_set_object() refs the object, as does
98  * g_object_get(). But g_object_get() only unrefs once when it disgorges
99  * the object, leaving an unbalanced ref, which leaks. So instead of
100  * using g_value_set_object(), use g_value_take_object() which doesn't
101  * ref the object when used in get_property().
102  */
103 static void
gnc_schedxaction_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)104 gnc_schedxaction_get_property (GObject         *object,
105                                guint            prop_id,
106                                GValue          *value,
107                                GParamSpec      *pspec)
108 {
109     SchedXaction *sx;
110 
111     g_return_if_fail(GNC_IS_SCHEDXACTION(object));
112 
113     sx = GNC_SCHEDXACTION(object);
114     switch (prop_id)
115     {
116     case PROP_NAME:
117         g_value_set_string(value, sx->name);
118         break;
119     case PROP_ENABLED:
120         g_value_set_boolean(value, sx->enabled);
121         break;
122     case PROP_NUM_OCCURANCE:
123         g_value_set_int(value, sx->num_occurances_total);
124         break;
125     case PROP_REM_OCCURANCE:
126         g_value_set_int(value, sx->num_occurances_remain);
127         break;
128     case PROP_AUTO_CREATE:
129         g_value_set_boolean(value, sx->autoCreateOption);
130         break;
131     case PROP_AUTO_CREATE_NOTIFY:
132         g_value_set_boolean(value, sx->autoCreateNotify);
133         break;
134     case PROP_ADVANCE_CREATION_DAYS:
135         g_value_set_int(value, sx->advanceCreateDays);
136         break;
137     case PROP_ADVANCE_REMINDER_DAYS:
138         g_value_set_int(value, sx->advanceRemindDays);
139         break;
140     case PROP_START_DATE:
141         g_value_set_boxed(value, &sx->start_date);
142         break;
143     case PROP_END_DATE:
144         /* g_value_set_boxed raises a critical error if sx->end_date
145          * is invalid */
146         if (g_date_valid (&sx->end_date))
147             g_value_set_boxed(value, &sx->end_date);
148         break;
149     case PROP_LAST_OCCURANCE_DATE:
150      /* g_value_set_boxed raises a critical error if sx->last_date
151          * is invalid */
152         if (g_date_valid (&sx->last_date))
153             g_value_set_boxed(value, &sx->last_date);
154         break;
155     case PROP_INSTANCE_COUNT:
156         g_value_set_int(value, sx->instance_num);
157         break;
158     case PROP_TEMPLATE_ACCOUNT:
159         g_value_take_object(value, sx->template_acct);
160         break;
161     default:
162         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
163         break;
164     }
165 }
166 
167 static void
gnc_schedxaction_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)168 gnc_schedxaction_set_property (GObject         *object,
169                                guint            prop_id,
170                                const GValue     *value,
171                                GParamSpec      *pspec)
172 {
173     SchedXaction *sx;
174 
175     g_return_if_fail(GNC_IS_SCHEDXACTION(object));
176 
177     sx = GNC_SCHEDXACTION(object);
178     g_assert (qof_instance_get_editlevel(sx));
179 
180     switch (prop_id)
181     {
182     case PROP_NAME:
183         xaccSchedXactionSetName(sx, g_value_get_string(value));
184         break;
185     case PROP_ENABLED:
186         xaccSchedXactionSetEnabled(sx, g_value_get_boolean(value));
187         break;
188     case PROP_NUM_OCCURANCE:
189         xaccSchedXactionSetNumOccur(sx, g_value_get_int(value));
190         break;
191     case PROP_REM_OCCURANCE:
192         xaccSchedXactionSetRemOccur(sx, g_value_get_int(value));
193         break;
194     case PROP_AUTO_CREATE:
195         xaccSchedXactionSetAutoCreate(sx, g_value_get_boolean(value), sx->autoCreateNotify);
196         break;
197     case PROP_AUTO_CREATE_NOTIFY:
198         xaccSchedXactionSetAutoCreate(sx, sx->autoCreateOption, g_value_get_boolean(value));
199         break;
200     case PROP_ADVANCE_CREATION_DAYS:
201         xaccSchedXactionSetAdvanceCreation(sx, g_value_get_int(value));
202         break;
203     case PROP_ADVANCE_REMINDER_DAYS:
204         xaccSchedXactionSetAdvanceReminder(sx, g_value_get_int(value));
205         break;
206     case PROP_START_DATE:
207         /* Note: when passed through a boxed gvalue, the julian value of the date is copied.
208            The date may appear invalid until a function requiring for dmy calculation is
209            called. */
210         xaccSchedXactionSetStartDate(sx, g_value_get_boxed(value));
211         break;
212     case PROP_END_DATE:
213         /* Note: when passed through a boxed gvalue, the julian value of the date is copied.
214            The date may appear invalid until a function requiring for dmy calculation is
215            called. */
216         xaccSchedXactionSetEndDate(sx, g_value_get_boxed(value));
217         break;
218     case PROP_LAST_OCCURANCE_DATE:
219         /* Note: when passed through a boxed gvalue, the julian value of the date is copied.
220            The date may appear invalid until a function requiring for dmy calculation is
221            called. */
222         xaccSchedXactionSetLastOccurDate(sx, g_value_get_boxed(value));
223         break;
224     case PROP_INSTANCE_COUNT:
225         gnc_sx_set_instance_count(sx, g_value_get_int(value));
226         break;
227     case PROP_TEMPLATE_ACCOUNT:
228         sx_set_template_account(sx, g_value_get_object(value));
229         break;
230     default:
231         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
232         break;
233     }
234 }
235 
236 static void
gnc_schedxaction_class_init(SchedXactionClass * klass)237 gnc_schedxaction_class_init (SchedXactionClass *klass)
238 {
239     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
240 
241     gobject_class->dispose = gnc_schedxaction_dispose;
242     gobject_class->finalize = gnc_schedxaction_finalize;
243     gobject_class->set_property = gnc_schedxaction_set_property;
244     gobject_class->get_property = gnc_schedxaction_get_property;
245 
246     g_object_class_install_property
247     (gobject_class,
248      PROP_NAME,
249      g_param_spec_string ("name",
250                           "Scheduled Transaction Name",
251                           "The name is an arbitrary string "
252                           "assigned by the user.  It is intended to "
253                           "a short, 5 to 30 character long string "
254                           "that is displayed by the GUI.",
255                           NULL,
256                           G_PARAM_READWRITE));
257 
258     g_object_class_install_property
259     (gobject_class,
260      PROP_ENABLED,
261      g_param_spec_boolean ("enabled",
262                            "Enabled",
263                            "TRUE if the scheduled transaction is enabled.",
264                            TRUE,
265                            G_PARAM_READWRITE));
266 
267     g_object_class_install_property
268     (gobject_class,
269      PROP_NUM_OCCURANCE,
270      g_param_spec_int ("num-occurance",
271                        "Number of occurrences",
272                        "Total number of occurrences for this scheduled transaction.",
273                        0,
274                        G_MAXINT16,
275                        1,
276                        G_PARAM_READWRITE));
277 
278     g_object_class_install_property
279     (gobject_class,
280      PROP_REM_OCCURANCE,
281      g_param_spec_int ("rem-occurance",
282                        "Number of occurrences remaining",
283                        "Remaining number of occurrences for this scheduled transaction.",
284                        0,
285                        G_MAXINT16,
286                        1,
287                        G_PARAM_READWRITE));
288 
289     g_object_class_install_property
290     (gobject_class,
291      PROP_AUTO_CREATE,
292      g_param_spec_boolean ("auto-create",
293                            "Auto-create",
294                            "TRUE if the transaction will be automatically "
295                            "created when its time comes.",
296                            FALSE,
297                            G_PARAM_READWRITE));
298 
299     g_object_class_install_property
300     (gobject_class,
301      PROP_AUTO_CREATE_NOTIFY,
302      g_param_spec_boolean ("auto-create-notify",
303                            "Auto-create-notify",
304                            "TRUE if the the user will be notified when the transaction "
305                            "is automatically created.",
306                            FALSE,
307                            G_PARAM_READWRITE));
308 
309     g_object_class_install_property
310     (gobject_class,
311      PROP_ADVANCE_CREATION_DAYS,
312      g_param_spec_int ("advance-creation-days",
313                        "Days in advance to create",
314                        "Number of days in advance to create this scheduled transaction.",
315                        0,
316                        G_MAXINT16,
317                        0,
318                        G_PARAM_READWRITE));
319 
320     g_object_class_install_property
321     (gobject_class,
322      PROP_ADVANCE_REMINDER_DAYS,
323      g_param_spec_int ("advance-reminder-days",
324                        "Days in advance to remind",
325                        "Number of days in advance to remind about this scheduled transaction.",
326                        0,
327                        G_MAXINT16,
328                        0,
329                        G_PARAM_READWRITE));
330 
331     g_object_class_install_property
332     (gobject_class,
333      PROP_START_DATE,
334      g_param_spec_boxed("start-date",
335                         "Start Date",
336                         "Date for the first occurrence for the scheduled transaction.",
337                         G_TYPE_DATE,
338                         G_PARAM_READWRITE));
339 
340     g_object_class_install_property
341     (gobject_class,
342      PROP_END_DATE,
343      g_param_spec_boxed("end-date",
344                         "End Date",
345                         "Date for the scheduled transaction to end.",
346                         G_TYPE_DATE,
347                         G_PARAM_READWRITE));
348 
349     g_object_class_install_property
350     (gobject_class,
351      PROP_LAST_OCCURANCE_DATE,
352      g_param_spec_boxed("last-occurance-date",
353                         "Last Occurrence Date",
354                         "Date for the last occurrence of the scheduled transaction.",
355                         G_TYPE_DATE,
356                         G_PARAM_READWRITE));
357 
358     g_object_class_install_property
359     (gobject_class,
360      PROP_INSTANCE_COUNT,
361      g_param_spec_int ("instance-count",
362                        "Instance count",
363                        "Number of instances of this scheduled transaction.",
364                        0,
365                        G_MAXINT16,
366                        0,
367                        G_PARAM_READWRITE));
368 
369     g_object_class_install_property
370     (gobject_class,
371      PROP_TEMPLATE_ACCOUNT,
372      g_param_spec_object("template-account",
373                          "Template account",
374                          "Account which holds the template transactions.",
375                          GNC_TYPE_ACCOUNT,
376                          G_PARAM_READWRITE));
377 }
378 
379 static void
xaccSchedXactionInit(SchedXaction * sx,QofBook * book)380 xaccSchedXactionInit(SchedXaction *sx, QofBook *book)
381 {
382     Account        *ra;
383     const GncGUID *guid;
384     gchar guidstr[GUID_ENCODING_LENGTH+1];
385 
386     qof_instance_init_data (&sx->inst, GNC_ID_SCHEDXACTION, book);
387 
388     /* create a new template account for our splits */
389     sx->template_acct = xaccMallocAccount(book);
390     guid = qof_instance_get_guid( sx );
391     xaccAccountBeginEdit( sx->template_acct );
392     guid_to_string_buff( guid, guidstr );
393     xaccAccountSetName( sx->template_acct, guidstr);
394     xaccAccountSetCommodity
395     (sx->template_acct,
396      gnc_commodity_table_lookup( gnc_commodity_table_get_table(book),
397                                  GNC_COMMODITY_NS_TEMPLATE, "template") );
398     xaccAccountSetType( sx->template_acct, ACCT_TYPE_BANK );
399     xaccAccountCommitEdit( sx->template_acct );
400     ra = gnc_book_get_template_root( book );
401     gnc_account_append_child( ra, sx->template_acct );
402 }
403 
404 SchedXaction*
xaccSchedXactionMalloc(QofBook * book)405 xaccSchedXactionMalloc(QofBook *book)
406 {
407     SchedXaction *sx;
408 
409     g_return_val_if_fail (book, NULL);
410 
411     sx = g_object_new(GNC_TYPE_SCHEDXACTION, NULL);
412     xaccSchedXactionInit( sx, book );
413     qof_event_gen( &sx->inst, QOF_EVENT_CREATE , NULL);
414 
415     return sx;
416 }
417 
418 static void
sxprivTransMapDelete(gpointer data,gpointer user_data)419 sxprivTransMapDelete( gpointer data, gpointer user_data )
420 {
421     Transaction *t = (Transaction *) data;
422     xaccTransBeginEdit( t );
423     xaccTransDestroy( t );
424     xaccTransCommitEdit( t );
425     return;
426 }
427 
428 static void
delete_template_trans(SchedXaction * sx)429 delete_template_trans(SchedXaction *sx)
430 {
431     GList *templ_acct_splits, *curr_split_listref;
432     Split *curr_split;
433     Transaction *split_trans;
434     GList *templ_acct_transactions = NULL;
435 
436     templ_acct_splits
437     = xaccAccountGetSplitList(sx->template_acct);
438 
439     for (curr_split_listref = templ_acct_splits;
440             curr_split_listref;
441             curr_split_listref = curr_split_listref->next)
442     {
443         curr_split = (Split *) curr_split_listref->data;
444         split_trans = xaccSplitGetParent(curr_split);
445         if (! (g_list_find(templ_acct_transactions, split_trans)))
446         {
447             templ_acct_transactions
448             = g_list_prepend(templ_acct_transactions, split_trans);
449         }
450     }
451 
452     g_list_foreach(templ_acct_transactions,
453                    sxprivTransMapDelete,
454                    NULL);
455 
456     g_list_free (templ_acct_transactions);
457     return;
458 }
459 
460 void
sx_set_template_account(SchedXaction * sx,Account * account)461 sx_set_template_account (SchedXaction *sx, Account *account)
462 {
463     Account *old;
464 
465     old = sx->template_acct;
466     sx->template_acct = account;
467     if (old)
468     {
469         xaccAccountBeginEdit(old);
470         xaccAccountDestroy(old);
471     }
472 }
473 
474 void
xaccSchedXactionDestroy(SchedXaction * sx)475 xaccSchedXactionDestroy( SchedXaction *sx )
476 {
477     qof_instance_set_destroying( QOF_INSTANCE(sx), TRUE );
478     gnc_sx_commit_edit( sx );
479 }
480 
481 static void
xaccSchedXactionFree(SchedXaction * sx)482 xaccSchedXactionFree( SchedXaction *sx )
483 {
484     GList *l;
485 
486     if ( sx == NULL ) return;
487 
488     qof_event_gen( &sx->inst, QOF_EVENT_DESTROY , NULL);
489 
490     if ( sx->name )
491         g_free( sx->name );
492 
493     /*
494      * we have to delete the transactions in the
495      * template account ourselves
496      */
497 
498     delete_template_trans( sx );
499 
500     /*
501      * xaccAccountDestroy removes the account from
502      * its group for us AFAICT.  If shutting down,
503      * the account is being deleted separately.
504      */
505 
506     if (!qof_book_shutting_down(qof_instance_get_book(sx)))
507     {
508         xaccAccountBeginEdit(sx->template_acct);
509         xaccAccountDestroy(sx->template_acct);
510     }
511 
512     for ( l = sx->deferredList; l; l = l->next )
513     {
514         gnc_sx_destroy_temporal_state( l->data );
515         l->data = NULL;
516     }
517     if ( sx->deferredList )
518     {
519         g_list_free( sx->deferredList );
520         sx->deferredList = NULL;
521     }
522 
523     /* qof_instance_release (&sx->inst); */
524     g_object_unref( sx );
525 }
526 
527 /* ============================================================ */
528 
529 void
gnc_sx_begin_edit(SchedXaction * sx)530 gnc_sx_begin_edit (SchedXaction *sx)
531 {
532     qof_begin_edit (&sx->inst);
533 }
534 
sx_free(QofInstance * inst)535 static void sx_free(QofInstance* inst )
536 {
537     xaccSchedXactionFree( GNC_SX(inst) );
538 }
539 
commit_err(QofInstance * inst,QofBackendError errcode)540 static void commit_err (QofInstance *inst, QofBackendError errcode)
541 {
542     g_critical("Failed to commit: %d", errcode);
543     gnc_engine_signal_commit_error( errcode );
544 }
545 
commit_done(QofInstance * inst)546 static void commit_done(QofInstance *inst)
547 {
548     qof_event_gen (inst, QOF_EVENT_MODIFY, NULL);
549 }
550 
551 void
gnc_sx_commit_edit(SchedXaction * sx)552 gnc_sx_commit_edit (SchedXaction *sx)
553 {
554     if (!qof_commit_edit (QOF_INSTANCE(sx))) return;
555     qof_commit_edit_part2 (&sx->inst, commit_err, commit_done, sx_free);
556 }
557 
558 /* ============================================================ */
559 
560 GList*
gnc_sx_get_schedule(const SchedXaction * sx)561 gnc_sx_get_schedule(const SchedXaction *sx)
562 {
563     return sx->schedule;
564 }
565 
566 void
gnc_sx_set_schedule(SchedXaction * sx,GList * schedule)567 gnc_sx_set_schedule(SchedXaction *sx, GList *schedule)
568 {
569     g_return_if_fail(sx);
570     gnc_sx_begin_edit(sx);
571     sx->schedule = schedule;
572     qof_instance_set_dirty(&sx->inst);
573     gnc_sx_commit_edit(sx);
574 }
575 
576 gchar *
xaccSchedXactionGetName(const SchedXaction * sx)577 xaccSchedXactionGetName( const SchedXaction *sx )
578 {
579     return sx->name;
580 }
581 
582 void
xaccSchedXactionSetName(SchedXaction * sx,const gchar * newName)583 xaccSchedXactionSetName( SchedXaction *sx, const gchar *newName )
584 {
585     g_return_if_fail( newName != NULL );
586     gnc_sx_begin_edit(sx);
587     if ( sx->name != NULL )
588     {
589         g_free( sx->name );
590         sx->name = NULL;
591     }
592     sx->name = g_strdup( newName );
593     qof_instance_set_dirty(&sx->inst);
594     gnc_sx_commit_edit(sx);
595 }
596 
597 const GDate*
xaccSchedXactionGetStartDate(const SchedXaction * sx)598 xaccSchedXactionGetStartDate(const SchedXaction *sx )
599 {
600     g_assert (sx);
601     return &sx->start_date;
602 }
603 
604 time64
xaccSchedXactionGetStartDateTT(const SchedXaction * sx)605 xaccSchedXactionGetStartDateTT(const SchedXaction *sx )
606 {
607     g_assert (sx);
608     return gdate_to_time64(sx->start_date);
609 }
610 
611 void
xaccSchedXactionSetStartDate(SchedXaction * sx,const GDate * newStart)612 xaccSchedXactionSetStartDate( SchedXaction *sx, const GDate* newStart )
613 {
614     if ( newStart == NULL || !g_date_valid( newStart ))
615     {
616         /* XXX: I reject the bad data - is this the right
617          * thing to do <rgmerk>.
618          * This warning is only human readable - the caller
619          * doesn't know the call failed.  This is bad
620          */
621         g_critical("Invalid Start Date");
622         return;
623     }
624     gnc_sx_begin_edit(sx);
625     sx->start_date = *newStart;
626     qof_instance_set_dirty(&sx->inst);
627     gnc_sx_commit_edit(sx);
628 }
629 
630 void
xaccSchedXactionSetStartDateTT(SchedXaction * sx,const time64 newStart)631 xaccSchedXactionSetStartDateTT( SchedXaction *sx, const time64 newStart )
632 {
633     if ( newStart == INT64_MAX )
634     {
635         /* XXX: I reject the bad data - is this the right
636          * thing to do <rgmerk>.
637          * This warning is only human readable - the caller
638          * doesn't know the call failed.  This is bad
639          */
640         g_critical("Invalid Start Date");
641         return;
642     }
643     gnc_sx_begin_edit(sx);
644     gnc_gdate_set_time64(&sx->start_date, newStart);
645     qof_instance_set_dirty(&sx->inst);
646     gnc_sx_commit_edit(sx);
647 }
648 
649 gboolean
xaccSchedXactionHasEndDate(const SchedXaction * sx)650 xaccSchedXactionHasEndDate( const SchedXaction *sx )
651 {
652     return sx != NULL && g_date_valid( &sx->end_date );
653 }
654 
655 const GDate*
xaccSchedXactionGetEndDate(const SchedXaction * sx)656 xaccSchedXactionGetEndDate(const SchedXaction *sx )
657 {
658     g_assert (sx);
659     return &sx->end_date;
660 }
661 
662 void
xaccSchedXactionSetEndDate(SchedXaction * sx,const GDate * newEnd)663 xaccSchedXactionSetEndDate( SchedXaction *sx, const GDate *newEnd )
664 {
665 /* Note that an invalid GDate IS a permissible value: It means that
666  * the SX is to run "forever". See gnc_sxed_save_sx() and
667  * schedXact_editor_populate() in dialog-sx-editor.c.
668  */
669     if (newEnd == NULL ||
670         (g_date_valid(newEnd) && g_date_compare( newEnd, &sx->start_date ) < 0 ))
671     {
672         /* XXX: I reject the bad data - is this the right
673          * thing to do <rgmerk>.
674          * This warning is only human readable - the caller
675          * doesn't know the call failed.  This is bad
676          */
677         g_critical("Bad End Date: Invalid or before Start Date");
678         return;
679     }
680 
681     gnc_sx_begin_edit(sx);
682     sx->end_date = *newEnd;
683     qof_instance_set_dirty(&sx->inst);
684     gnc_sx_commit_edit(sx);
685 }
686 
687 const GDate*
xaccSchedXactionGetLastOccurDate(const SchedXaction * sx)688 xaccSchedXactionGetLastOccurDate(const SchedXaction *sx )
689 {
690     return &sx->last_date;
691 }
692 
693 time64
xaccSchedXactionGetLastOccurDateTT(const SchedXaction * sx)694 xaccSchedXactionGetLastOccurDateTT(const SchedXaction *sx )
695 {
696     return gdate_to_time64(sx->last_date);
697 }
698 
699 void
xaccSchedXactionSetLastOccurDate(SchedXaction * sx,const GDate * new_last_occur)700 xaccSchedXactionSetLastOccurDate(SchedXaction *sx, const GDate* new_last_occur)
701 {
702     g_return_if_fail (new_last_occur != NULL);
703     if (g_date_valid(&sx->last_date)
704             && g_date_compare(&sx->last_date, new_last_occur) == 0)
705         return;
706     gnc_sx_begin_edit(sx);
707     sx->last_date = *new_last_occur;
708     qof_instance_set_dirty(&sx->inst);
709     gnc_sx_commit_edit(sx);
710 }
711 
712 void
xaccSchedXactionSetLastOccurDateTT(SchedXaction * sx,time64 new_last_occur)713 xaccSchedXactionSetLastOccurDateTT(SchedXaction *sx, time64 new_last_occur)
714 {
715     GDate last_occur;
716     g_return_if_fail (new_last_occur != INT64_MAX);
717     gnc_gdate_set_time64(&last_occur, new_last_occur);
718     if (g_date_valid(&sx->last_date)
719         && g_date_compare(&sx->last_date, &last_occur) == 0)
720         return;
721     gnc_sx_begin_edit(sx);
722     sx->last_date = last_occur;
723     qof_instance_set_dirty(&sx->inst);
724     gnc_sx_commit_edit(sx);
725 }
726 
727 gboolean
xaccSchedXactionHasOccurDef(const SchedXaction * sx)728 xaccSchedXactionHasOccurDef( const SchedXaction *sx )
729 {
730     return ( xaccSchedXactionGetNumOccur( sx ) != 0 );
731 }
732 
733 gint
xaccSchedXactionGetNumOccur(const SchedXaction * sx)734 xaccSchedXactionGetNumOccur( const SchedXaction *sx )
735 {
736     return sx->num_occurances_total;
737 }
738 
739 void
xaccSchedXactionSetNumOccur(SchedXaction * sx,gint new_num)740 xaccSchedXactionSetNumOccur(SchedXaction *sx, gint new_num)
741 {
742     if (sx->num_occurances_total == new_num)
743         return;
744     gnc_sx_begin_edit(sx);
745     sx->num_occurances_remain = sx->num_occurances_total = new_num;
746     qof_instance_set_dirty(&sx->inst);
747     gnc_sx_commit_edit(sx);
748 }
749 
750 gint
xaccSchedXactionGetRemOccur(const SchedXaction * sx)751 xaccSchedXactionGetRemOccur( const SchedXaction *sx )
752 {
753     return sx->num_occurances_remain;
754 }
755 
756 void
xaccSchedXactionSetRemOccur(SchedXaction * sx,gint num_remain)757 xaccSchedXactionSetRemOccur(SchedXaction *sx, gint num_remain)
758 {
759     /* FIXME This condition can be tightened up */
760     if (num_remain > sx->num_occurances_total)
761     {
762         g_warning("number remaining [%d] > total occurrences [%d]",
763                   num_remain, sx->num_occurances_total);
764     }
765     else
766     {
767         if (num_remain == sx->num_occurances_remain)
768             return;
769         gnc_sx_begin_edit(sx);
770         sx->num_occurances_remain = num_remain;
771         qof_instance_set_dirty(&sx->inst);
772         gnc_sx_commit_edit(sx);
773     }
774 }
775 
gnc_sx_get_num_occur_daterange(const SchedXaction * sx,const GDate * start_date,const GDate * end_date)776 gint gnc_sx_get_num_occur_daterange(const SchedXaction *sx, const GDate* start_date, const GDate* end_date)
777 {
778     gint result = 0;
779     SXTmpStateData *tmpState;
780     gboolean countFirstDate;
781 
782     /* SX still active? If not, return now. */
783     if ((xaccSchedXactionHasOccurDef(sx)
784             && xaccSchedXactionGetRemOccur(sx) <= 0)
785             || (xaccSchedXactionHasEndDate(sx)
786                 && g_date_compare(xaccSchedXactionGetEndDate(sx), start_date) < 0))
787     {
788         return result;
789     }
790 
791     tmpState = gnc_sx_create_temporal_state (sx);
792 
793     /* Should we count the first valid date we encounter? Only if the
794      * SX has not yet occurred so far, or if its last valid date was
795      * before the start date. */
796     countFirstDate = !g_date_valid(&tmpState->last_date)
797                      || (g_date_compare(&tmpState->last_date, start_date) < 0);
798 
799     /* No valid date? SX has never occurred so far. */
800     if (!g_date_valid(&tmpState->last_date))
801     {
802         /* SX has never occurred so far */
803         gnc_sx_incr_temporal_state (sx, tmpState);
804         if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0)
805         {
806             gnc_sx_destroy_temporal_state (tmpState);
807             return result;
808         }
809     }
810 
811     /* Increase the tmpState until we are in our interval of
812      * interest. Only calculate anything if the sx hasn't already
813      * ended. */
814     while (g_date_compare(&tmpState->last_date, start_date) < 0)
815     {
816         gnc_sx_incr_temporal_state (sx, tmpState);
817         if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0)
818         {
819             gnc_sx_destroy_temporal_state (tmpState);
820             return result;
821         }
822     }
823 
824     /* Now we are in our interval of interest. Increment the
825      * occurrence date until we are beyond the end of our
826      * interval. Make sure to check for invalid dates here: It means
827      * the SX has ended. */
828     while (g_date_valid(&tmpState->last_date)
829             && (g_date_compare(&tmpState->last_date, end_date) <= 0)
830             && (!xaccSchedXactionHasEndDate(sx)
831                 || g_date_compare(&tmpState->last_date, xaccSchedXactionGetEndDate(sx)) <= 0)
832             && (!xaccSchedXactionHasOccurDef(sx)
833                 /* The >=0 (i.e. the ==) is important here, otherwise
834                  * we miss the last valid occurrence of a SX which is
835                  * limited by num_occur */
836                 || tmpState->num_occur_rem >= 0))
837     {
838         ++result;
839         gnc_sx_incr_temporal_state (sx, tmpState);
840     }
841 
842     /* If the first valid date shouldn't be counted, decrease the
843      * result number by one. */
844     if (!countFirstDate && result > 0)
845         --result;
846 
847     gnc_sx_destroy_temporal_state (tmpState);
848     return result;
849 }
850 
851 gboolean
xaccSchedXactionGetEnabled(const SchedXaction * sx)852 xaccSchedXactionGetEnabled( const SchedXaction *sx )
853 {
854     return sx->enabled;
855 }
856 
857 void
xaccSchedXactionSetEnabled(SchedXaction * sx,gboolean newEnabled)858 xaccSchedXactionSetEnabled( SchedXaction *sx, gboolean newEnabled)
859 {
860     gnc_sx_begin_edit(sx);
861     sx->enabled = newEnabled;
862     qof_instance_set_dirty(&sx->inst);
863     gnc_sx_commit_edit(sx);
864 }
865 
866 void
xaccSchedXactionGetAutoCreate(const SchedXaction * sx,gboolean * outAutoCreate,gboolean * outNotify)867 xaccSchedXactionGetAutoCreate( const SchedXaction *sx,
868                                gboolean *outAutoCreate,
869                                gboolean *outNotify )
870 {
871     if (outAutoCreate != NULL)
872         *outAutoCreate = sx->autoCreateOption;
873     if (outNotify != NULL)
874         *outNotify     = sx->autoCreateNotify;
875     return;
876 }
877 
878 void
xaccSchedXactionSetAutoCreate(SchedXaction * sx,gboolean newAutoCreate,gboolean newNotify)879 xaccSchedXactionSetAutoCreate( SchedXaction *sx,
880                                gboolean newAutoCreate,
881                                gboolean newNotify )
882 {
883 
884     gnc_sx_begin_edit(sx);
885     sx->autoCreateOption = newAutoCreate;
886     sx->autoCreateNotify = newNotify;
887     qof_instance_set_dirty(&sx->inst);
888     gnc_sx_commit_edit(sx);
889     return;
890 }
891 
892 gint
xaccSchedXactionGetAdvanceCreation(const SchedXaction * sx)893 xaccSchedXactionGetAdvanceCreation( const SchedXaction *sx )
894 {
895     return sx->advanceCreateDays;
896 }
897 
898 void
xaccSchedXactionSetAdvanceCreation(SchedXaction * sx,gint createDays)899 xaccSchedXactionSetAdvanceCreation( SchedXaction *sx, gint createDays )
900 {
901     gnc_sx_begin_edit(sx);
902     sx->advanceCreateDays = createDays;
903     qof_instance_set_dirty(&sx->inst);
904     gnc_sx_commit_edit(sx);
905 }
906 
907 gint
xaccSchedXactionGetAdvanceReminder(const SchedXaction * sx)908 xaccSchedXactionGetAdvanceReminder( const SchedXaction *sx )
909 {
910     return sx->advanceRemindDays;
911 }
912 
913 void
xaccSchedXactionSetAdvanceReminder(SchedXaction * sx,gint reminderDays)914 xaccSchedXactionSetAdvanceReminder( SchedXaction *sx, gint reminderDays )
915 {
916     gnc_sx_begin_edit(sx);
917     sx->advanceRemindDays = reminderDays;
918     qof_instance_set_dirty(&sx->inst);
919     gnc_sx_commit_edit(sx);
920 }
921 
922 GDate
xaccSchedXactionGetNextInstance(const SchedXaction * sx,SXTmpStateData * tsd)923 xaccSchedXactionGetNextInstance (const SchedXaction *sx, SXTmpStateData *tsd)
924 {
925     GDate prev_occur, next_occur;
926 
927     g_date_clear( &prev_occur, 1 );
928     if ( tsd != NULL )
929         prev_occur = tsd->last_date;
930 
931     /* If prev_occur is in the "cleared" state and sx->start_date isn't, then
932      * we're at the beginning. We want to pretend prev_occur is the day before
933      * the start_date in case the start_date is today so that the SX will fire
934      * today. If start_date isn't valid either then the SX will fire anyway, no
935      * harm done. prev_occur cannot be before start_date either.
936      */
937     if (g_date_valid (&sx->start_date) && (!g_date_valid ( &prev_occur ) || g_date_compare (&prev_occur, &sx->start_date)<0))
938     {
939         /* We must be at the beginning. */
940         prev_occur = sx->start_date;
941         g_date_subtract_days (&prev_occur, 1 );
942     }
943 
944     recurrenceListNextInstance(sx->schedule, &prev_occur, &next_occur);
945 
946     if ( xaccSchedXactionHasEndDate( sx ) )
947     {
948         const GDate *end_date = xaccSchedXactionGetEndDate( sx );
949         if ( g_date_compare( &next_occur, end_date ) > 0 )
950         {
951             g_date_clear( &next_occur, 1 );
952         }
953     }
954     else if ( xaccSchedXactionHasOccurDef( sx ) )
955     {
956         if ((tsd && tsd->num_occur_rem == 0) ||
957             (!tsd && sx->num_occurances_remain == 0 ))
958         {
959             g_date_clear( &next_occur, 1 );
960         }
961     }
962     return next_occur;
963 }
964 
965 gint
gnc_sx_get_instance_count(const SchedXaction * sx,SXTmpStateData * stateData)966 gnc_sx_get_instance_count( const SchedXaction *sx, SXTmpStateData *stateData )
967 {
968     gint toRet = -1;
969     SXTmpStateData *tsd;
970 
971     if ( stateData )
972     {
973         tsd = (SXTmpStateData*)stateData;
974         toRet = tsd->num_inst;
975     }
976     else
977     {
978         toRet = sx->instance_num;
979     }
980 
981     return toRet;
982 }
983 
984 void
gnc_sx_set_instance_count(SchedXaction * sx,gint instance_num)985 gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num)
986 {
987     g_return_if_fail(sx);
988     if (sx->instance_num == instance_num)
989         return;
990     gnc_sx_begin_edit(sx);
991     sx->instance_num = instance_num;
992     qof_instance_set_dirty(&sx->inst);
993     gnc_sx_commit_edit(sx);
994 }
995 
996 GList *
xaccSchedXactionGetSplits(const SchedXaction * sx)997 xaccSchedXactionGetSplits( const SchedXaction *sx )
998 {
999     g_return_val_if_fail( sx, NULL );
1000     return xaccAccountGetSplitList(sx->template_acct);
1001 }
1002 
1003 static Split *
pack_split_info(TTSplitInfo * s_info,Account * parent_acct,Transaction * parent_trans,QofBook * book)1004 pack_split_info (TTSplitInfo *s_info, Account *parent_acct,
1005                  Transaction *parent_trans, QofBook *book)
1006 {
1007     Split *split;
1008     const gchar *credit_formula;
1009     const gchar *debit_formula;
1010     const GncGUID *acc_guid;
1011 
1012     split = xaccMallocSplit(book);
1013 
1014     xaccSplitSetMemo(split,
1015                      gnc_ttsplitinfo_get_memo(s_info));
1016 
1017     /* Set split-action with gnc_set_num_action which is the same as
1018      * xaccSplitSetAction with these arguments */
1019     gnc_set_num_action(NULL, split, NULL,
1020                        gnc_ttsplitinfo_get_action(s_info));
1021 
1022     xaccAccountInsertSplit(parent_acct,
1023                            split);
1024 
1025     credit_formula = gnc_ttsplitinfo_get_credit_formula(s_info);
1026     debit_formula = gnc_ttsplitinfo_get_debit_formula(s_info);
1027     acc_guid = qof_entity_get_guid(QOF_INSTANCE(gnc_ttsplitinfo_get_account(s_info)));
1028     qof_instance_set (QOF_INSTANCE (split),
1029 		      "sx-credit-formula", credit_formula,
1030 		      "sx-debit-formula", debit_formula,
1031 		      "sx-account", acc_guid,
1032 		      NULL);
1033 
1034     return split;
1035 }
1036 
1037 
1038 void
xaccSchedXactionSetTemplateTrans(SchedXaction * sx,GList * t_t_list,QofBook * book)1039 xaccSchedXactionSetTemplateTrans(SchedXaction *sx, GList *t_t_list,
1040                                  QofBook *book)
1041 {
1042     Transaction *new_trans;
1043     TTInfo *tti;
1044     TTSplitInfo *s_info;
1045     Split *new_split;
1046     GList *split_list;
1047 
1048     g_return_if_fail (book);
1049 
1050     /* delete any old transactions, if there are any */
1051     delete_template_trans( sx );
1052 
1053     for (; t_t_list != NULL; t_t_list = t_t_list->next)
1054     {
1055         tti = t_t_list->data;
1056 
1057         new_trans = xaccMallocTransaction(book);
1058 
1059         xaccTransBeginEdit(new_trans);
1060 
1061         xaccTransSetDescription(new_trans,
1062                                 gnc_ttinfo_get_description(tti));
1063 
1064         xaccTransSetDatePostedSecsNormalized(new_trans, gnc_time (NULL));
1065 
1066         /* Set tran-num with gnc_set_num_action which is the same as
1067          * xaccTransSetNum with these arguments */
1068         gnc_set_num_action(new_trans, NULL,
1069                         gnc_ttinfo_get_num(tti), NULL);
1070         xaccTransSetNotes (new_trans, gnc_ttinfo_get_notes (tti));
1071         xaccTransSetCurrency( new_trans,
1072                               gnc_ttinfo_get_currency(tti) );
1073 
1074         for (split_list = gnc_ttinfo_get_template_splits(tti);
1075                 split_list;
1076                 split_list = split_list->next)
1077         {
1078             s_info = split_list->data;
1079             new_split = pack_split_info(s_info, sx->template_acct,
1080                                         new_trans, book);
1081             xaccTransAppendSplit(new_trans, new_split);
1082         }
1083         xaccTransCommitEdit(new_trans);
1084     }
1085 }
1086 
1087 SXTmpStateData*
gnc_sx_create_temporal_state(const SchedXaction * sx)1088 gnc_sx_create_temporal_state(const SchedXaction *sx )
1089 {
1090     SXTmpStateData *toRet =
1091 	 g_new0( SXTmpStateData, 1 );
1092     if (g_date_valid (&(sx->last_date)))
1093 	 toRet->last_date       = sx->last_date;
1094     else
1095 	g_date_set_dmy (&(toRet->last_date), 1, 1, 1970);
1096     toRet->num_occur_rem   = sx->num_occurances_remain;
1097     toRet->num_inst   = sx->instance_num;
1098     return toRet;
1099 }
1100 
1101 void
gnc_sx_incr_temporal_state(const SchedXaction * sx,SXTmpStateData * tsd)1102 gnc_sx_incr_temporal_state(const SchedXaction *sx, SXTmpStateData *tsd )
1103 {
1104     g_return_if_fail(tsd != NULL);
1105     tsd->last_date = xaccSchedXactionGetNextInstance (sx, tsd);
1106     if (xaccSchedXactionHasOccurDef (sx))
1107     {
1108         --tsd->num_occur_rem;
1109     }
1110     ++tsd->num_inst;
1111 }
1112 
1113 void
gnc_sx_destroy_temporal_state(SXTmpStateData * tsd)1114 gnc_sx_destroy_temporal_state (SXTmpStateData *tsd)
1115 {
1116     g_free(tsd);
1117 }
1118 
1119 SXTmpStateData*
gnc_sx_clone_temporal_state(SXTmpStateData * tsd)1120 gnc_sx_clone_temporal_state (SXTmpStateData *tsd)
1121 {
1122     SXTmpStateData *toRet = NULL;
1123 
1124     if(tsd)
1125     {
1126         toRet = g_malloc(sizeof(SXTmpStateData));
1127         toRet = memcpy (toRet, tsd, sizeof (SXTmpStateData));
1128     }
1129 
1130     return toRet;
1131 }
1132 
1133 static gint
_temporal_state_data_cmp(gconstpointer a,gconstpointer b)1134 _temporal_state_data_cmp( gconstpointer a, gconstpointer b )
1135 {
1136     const SXTmpStateData *tsd_a = (SXTmpStateData*)a;
1137     const SXTmpStateData *tsd_b = (SXTmpStateData*)b;
1138 
1139     if ( !tsd_a && !tsd_b )
1140         return 0;
1141     if (tsd_a == tsd_b)
1142         return 0;
1143     if ( !tsd_a )
1144         return 1;
1145     if ( !tsd_b )
1146         return -1;
1147     return g_date_compare( &tsd_a->last_date,
1148                            &tsd_b->last_date );
1149 }
1150 
1151 /**
1152  * Adds an instance to the deferred list of the SX.  Added instances are
1153  * added in (date-)sorted order.
1154  **/
1155 void
gnc_sx_add_defer_instance(SchedXaction * sx,void * deferStateData)1156 gnc_sx_add_defer_instance( SchedXaction *sx, void *deferStateData )
1157 {
1158     sx->deferredList = g_list_insert_sorted( sx->deferredList,
1159                        deferStateData,
1160                        _temporal_state_data_cmp );
1161 }
1162 
1163 /**
1164  * Removes an instance from the deferred list. The saved SXTmpStateData existed
1165  * for comparison only, so destroy it.
1166  **/
1167 void
gnc_sx_remove_defer_instance(SchedXaction * sx,void * deferStateData)1168 gnc_sx_remove_defer_instance( SchedXaction *sx, void *deferStateData )
1169 {
1170     GList *found_by_value;
1171 
1172     found_by_value = g_list_find_custom(
1173                          sx->deferredList, deferStateData, _temporal_state_data_cmp);
1174     if (found_by_value == NULL)
1175     {
1176         g_warning("unable to find deferred instance");
1177         return;
1178     }
1179 
1180     gnc_sx_destroy_temporal_state(found_by_value->data);
1181     sx->deferredList = g_list_delete_link(sx->deferredList, found_by_value);
1182 }
1183 
1184 /**
1185  * Returns the defer list from the SX; this is a (date-)sorted
1186  * temporal-state-data instance list.  The list should not be modified by the
1187  * caller; use the gnc_sx_{add,remove}_defer_instance() functions to modify
1188  * the list.
1189  *
1190  * @param sx Scheduled transaction
1191  * @return Defer list which must not be modified by the caller
1192  **/
1193 GList*
gnc_sx_get_defer_instances(SchedXaction * sx)1194 gnc_sx_get_defer_instances( SchedXaction *sx )
1195 {
1196     return sx->deferredList;
1197 }
1198 
1199 static void
destroy_sx_on_book_close(QofInstance * ent,gpointer data)1200 destroy_sx_on_book_close(QofInstance *ent, gpointer data)
1201 {
1202     SchedXaction* sx = GNC_SCHEDXACTION(ent);
1203 
1204     gnc_sx_begin_edit(sx);
1205     xaccSchedXactionDestroy(sx);
1206 }
1207 
1208 /**
1209  * Destroys all SXes in the book because the book is being destroyed.
1210  *
1211  * @param book Book being destroyed
1212  */
1213 static void
gnc_sx_book_end(QofBook * book)1214 gnc_sx_book_end(QofBook* book)
1215 {
1216     QofCollection *col;
1217 
1218     col = qof_book_get_collection(book, GNC_ID_SCHEDXACTION);
1219     qof_collection_foreach(col, destroy_sx_on_book_close, NULL);
1220 }
1221 
1222 #ifdef _MSC_VER
1223 /* MSVC compiler doesn't have C99 "designated initializers"
1224  * so we wrap them in a macro that is empty on MSVC. */
1225 # define DI(x) /* */
1226 #else
1227 # define DI(x) x
1228 #endif
1229 static QofObject SXDesc =
1230 {
1231     DI(.interface_version = ) QOF_OBJECT_VERSION,
1232     DI(.e_type            = ) GNC_SX_ID,
1233     DI(.type_label        = ) "Scheduled Transaction",
1234     DI(.create            = ) (gpointer)xaccSchedXactionMalloc,
1235     DI(.book_begin        = ) NULL,
1236     DI(.book_end          = ) gnc_sx_book_end,
1237     DI(.is_dirty          = ) qof_collection_is_dirty,
1238     DI(.mark_clean        = ) qof_collection_mark_clean,
1239     DI(.foreach           = ) qof_collection_foreach,
1240     DI(.printable         = ) NULL,
1241     DI(.version_cmp       = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
1242 };
1243 
1244 gboolean
SXRegister(void)1245 SXRegister(void)
1246 {
1247     static QofParam params[] =
1248     {
1249         {
1250             GNC_SX_NAME, QOF_TYPE_STRING, (QofAccessFunc)xaccSchedXactionGetName,
1251             (QofSetterFunc)xaccSchedXactionSetName
1252         },
1253         {
1254             GNC_SX_START_DATE, QOF_TYPE_DATE, (QofAccessFunc)xaccSchedXactionGetStartDateTT,
1255             (QofSetterFunc)xaccSchedXactionSetStartDateTT
1256         },
1257         {
1258             GNC_SX_LAST_DATE, QOF_TYPE_DATE, (QofAccessFunc)xaccSchedXactionGetLastOccurDateTT,
1259             (QofSetterFunc)xaccSchedXactionSetLastOccurDateTT
1260         },
1261         {
1262             GNC_SX_NUM_OCCUR, QOF_TYPE_INT64, (QofAccessFunc)xaccSchedXactionGetNumOccur,
1263             (QofSetterFunc)xaccSchedXactionSetNumOccur
1264         },
1265         {
1266             GNC_SX_REM_OCCUR, QOF_TYPE_INT64, (QofAccessFunc)xaccSchedXactionGetRemOccur,
1267             (QofSetterFunc)xaccSchedXactionSetRemOccur
1268         },
1269         { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
1270         { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
1271         { NULL },
1272     };
1273     qof_class_register(GNC_SX_ID, NULL, params);
1274     return qof_object_register(&SXDesc);
1275 }
1276