GnuCash c935c2f+
Loading...
Searching...
No Matches
SchedXaction.cpp
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 "Account.hpp"
35#include "SX-book.h"
36#include "SX-book-p.h"
37#include "SX-ttinfo.hpp"
38#include "SchedXaction.h"
39#include "SchedXaction.hpp"
40#include "Transaction.h"
41#include "gnc-engine.h"
42#include "engine-helpers.h"
43#include "qofinstance-p.h"
44
45#include <unordered_set>
46
47#undef G_LOG_DOMAIN
48#define G_LOG_DOMAIN "gnc.engine.sx"
49
50enum
51{
52 PROP_0,
53 PROP_NAME, /* Table */
54 PROP_ENABLED, /* Table */
55 PROP_START_DATE, /* Table */
56 PROP_END_DATE, /* Table */
57 PROP_LAST_OCCURANCE_DATE, /* Table */
58 PROP_NUM_OCCURANCE, /* Table */
59 PROP_REM_OCCURANCE, /* Table */
60 PROP_AUTO_CREATE, /* Table */
61 PROP_AUTO_CREATE_NOTIFY, /* Table */
62 PROP_ADVANCE_CREATION_DAYS, /* Table */
63 PROP_ADVANCE_REMINDER_DAYS, /* Table */
64 PROP_INSTANCE_COUNT, /* Table */
65 PROP_TEMPLATE_ACCOUNT /* Table */
66};
67
68/* GObject initialization */
69G_DEFINE_TYPE(SchedXaction, gnc_schedxaction, QOF_TYPE_INSTANCE)
70
71static void
72gnc_schedxaction_init(SchedXaction* sx)
73{
74 sx->schedule = NULL;
75
76 g_date_clear( &sx->last_date, 1 );
77 g_date_clear( &sx->start_date, 1 );
78 g_date_clear( &sx->end_date, 1 );
79
80 sx->enabled = 1;
81 sx->num_occurances_total = 0;
82 sx->autoCreateOption = FALSE;
83 sx->autoCreateNotify = FALSE;
84 sx->advanceCreateDays = 0;
85 sx->advanceRemindDays = 0;
86 sx->instance_num = 0;
87 sx->deferredList = NULL;
88}
89
90static void
91gnc_schedxaction_dispose(GObject *sxp)
92{
93 G_OBJECT_CLASS(gnc_schedxaction_parent_class)->dispose(sxp);
94}
95
96static void
97gnc_schedxaction_finalize(GObject* sxp)
98{
99 G_OBJECT_CLASS(gnc_schedxaction_parent_class)->finalize(sxp);
100}
101
102/* Note that g_value_set_object() refs the object, as does
103 * g_object_get(). But g_object_get() only unrefs once when it disgorges
104 * the object, leaving an unbalanced ref, which leaks. So instead of
105 * using g_value_set_object(), use g_value_take_object() which doesn't
106 * ref the object when used in get_property().
107 */
108static void
109gnc_schedxaction_get_property (GObject *object,
110 guint prop_id,
111 GValue *value,
112 GParamSpec *pspec)
113{
114 SchedXaction *sx;
115
116 g_return_if_fail(GNC_IS_SCHEDXACTION(object));
117
118 sx = GNC_SCHEDXACTION(object);
119 switch (prop_id)
120 {
121 case PROP_NAME:
122 g_value_set_string(value, sx->name);
123 break;
124 case PROP_ENABLED:
125 g_value_set_boolean(value, sx->enabled);
126 break;
127 case PROP_NUM_OCCURANCE:
128 g_value_set_int(value, sx->num_occurances_total);
129 break;
130 case PROP_REM_OCCURANCE:
131 g_value_set_int(value, sx->num_occurances_remain);
132 break;
133 case PROP_AUTO_CREATE:
134 g_value_set_boolean(value, sx->autoCreateOption);
135 break;
136 case PROP_AUTO_CREATE_NOTIFY:
137 g_value_set_boolean(value, sx->autoCreateNotify);
138 break;
139 case PROP_ADVANCE_CREATION_DAYS:
140 g_value_set_int(value, sx->advanceCreateDays);
141 break;
142 case PROP_ADVANCE_REMINDER_DAYS:
143 g_value_set_int(value, sx->advanceRemindDays);
144 break;
145 case PROP_START_DATE:
146 g_value_set_boxed(value, &sx->start_date);
147 break;
148 case PROP_END_DATE:
149 /* g_value_set_boxed raises a critical error if sx->end_date
150 * is invalid */
151 if (g_date_valid (&sx->end_date))
152 g_value_set_boxed(value, &sx->end_date);
153 break;
154 case PROP_LAST_OCCURANCE_DATE:
155 /* g_value_set_boxed raises a critical error if sx->last_date
156 * is invalid */
157 if (g_date_valid (&sx->last_date))
158 g_value_set_boxed(value, &sx->last_date);
159 break;
160 case PROP_INSTANCE_COUNT:
161 g_value_set_int(value, sx->instance_num);
162 break;
163 case PROP_TEMPLATE_ACCOUNT:
164 g_value_take_object(value, sx->template_acct);
165 break;
166 default:
167 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
168 break;
169 }
170}
171
172static void
173gnc_schedxaction_set_property (GObject *object,
174 guint prop_id,
175 const GValue *value,
176 GParamSpec *pspec)
177{
178 SchedXaction *sx;
179
180 g_return_if_fail(GNC_IS_SCHEDXACTION(object));
181
182 sx = GNC_SCHEDXACTION(object);
183 g_assert (qof_instance_get_editlevel(sx));
184
185 switch (prop_id)
186 {
187 case PROP_NAME:
188 xaccSchedXactionSetName(sx, g_value_get_string(value));
189 break;
190 case PROP_ENABLED:
191 xaccSchedXactionSetEnabled(sx, g_value_get_boolean(value));
192 break;
193 case PROP_NUM_OCCURANCE:
194 xaccSchedXactionSetNumOccur(sx, g_value_get_int(value));
195 break;
196 case PROP_REM_OCCURANCE:
197 xaccSchedXactionSetRemOccur(sx, g_value_get_int(value));
198 break;
199 case PROP_AUTO_CREATE:
200 xaccSchedXactionSetAutoCreate(sx, g_value_get_boolean(value), sx->autoCreateNotify);
201 break;
202 case PROP_AUTO_CREATE_NOTIFY:
203 xaccSchedXactionSetAutoCreate(sx, sx->autoCreateOption, g_value_get_boolean(value));
204 break;
205 case PROP_ADVANCE_CREATION_DAYS:
206 xaccSchedXactionSetAdvanceCreation(sx, g_value_get_int(value));
207 break;
208 case PROP_ADVANCE_REMINDER_DAYS:
209 xaccSchedXactionSetAdvanceReminder(sx, g_value_get_int(value));
210 break;
211 case PROP_START_DATE:
212 /* Note: when passed through a boxed gvalue, the julian value of the date is copied.
213 The date may appear invalid until a function requiring for dmy calculation is
214 called. */
215 xaccSchedXactionSetStartDate(sx, static_cast<const GDate*>(g_value_get_boxed(value)));
216 break;
217 case PROP_END_DATE:
218 /* Note: when passed through a boxed gvalue, the julian value of the date is copied.
219 The date may appear invalid until a function requiring for dmy calculation is
220 called. */
221 xaccSchedXactionSetEndDate(sx, static_cast<const GDate*>(g_value_get_boxed(value)));
222 break;
223 case PROP_LAST_OCCURANCE_DATE:
224 /* Note: when passed through a boxed gvalue, the julian value of the date is copied.
225 The date may appear invalid until a function requiring for dmy calculation is
226 called. */
227 xaccSchedXactionSetLastOccurDate(sx, static_cast<const GDate*>(g_value_get_boxed(value)));
228 break;
229 case PROP_INSTANCE_COUNT:
230 gnc_sx_set_instance_count(sx, g_value_get_int(value));
231 break;
232 case PROP_TEMPLATE_ACCOUNT:
233 sx_set_template_account(sx, GNC_ACCOUNT(g_value_get_object(value)));
234 break;
235 default:
236 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
237 break;
238 }
239}
240
241static void
242gnc_schedxaction_class_init (SchedXactionClass *klass)
243{
244 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
245
246 gobject_class->dispose = gnc_schedxaction_dispose;
247 gobject_class->finalize = gnc_schedxaction_finalize;
248 gobject_class->set_property = gnc_schedxaction_set_property;
249 gobject_class->get_property = gnc_schedxaction_get_property;
250
251 g_object_class_install_property
252 (gobject_class,
253 PROP_NAME,
254 g_param_spec_string ("name",
255 "Scheduled Transaction Name",
256 "The name is an arbitrary string "
257 "assigned by the user. It is intended to "
258 "a short, 5 to 30 character long string "
259 "that is displayed by the GUI.",
260 NULL,
261 G_PARAM_READWRITE));
262
263 g_object_class_install_property
264 (gobject_class,
265 PROP_ENABLED,
266 g_param_spec_boolean ("enabled",
267 "Enabled",
268 "TRUE if the scheduled transaction is enabled.",
269 TRUE,
270 G_PARAM_READWRITE));
271
272 g_object_class_install_property
273 (gobject_class,
274 PROP_NUM_OCCURANCE,
275 g_param_spec_int ("num-occurance",
276 "Number of occurrences",
277 "Total number of occurrences for this scheduled transaction.",
278 0,
279 G_MAXINT16,
280 1,
281 G_PARAM_READWRITE));
282
283 g_object_class_install_property
284 (gobject_class,
285 PROP_REM_OCCURANCE,
286 g_param_spec_int ("rem-occurance",
287 "Number of occurrences remaining",
288 "Remaining number of occurrences for this scheduled transaction.",
289 0,
290 G_MAXINT16,
291 1,
292 G_PARAM_READWRITE));
293
294 g_object_class_install_property
295 (gobject_class,
296 PROP_AUTO_CREATE,
297 g_param_spec_boolean ("auto-create",
298 "Auto-create",
299 "TRUE if the transaction will be automatically "
300 "created when its time comes.",
301 FALSE,
302 G_PARAM_READWRITE));
303
304 g_object_class_install_property
305 (gobject_class,
306 PROP_AUTO_CREATE_NOTIFY,
307 g_param_spec_boolean ("auto-create-notify",
308 "Auto-create-notify",
309 "TRUE if the the user will be notified when the transaction "
310 "is automatically created.",
311 FALSE,
312 G_PARAM_READWRITE));
313
314 g_object_class_install_property
315 (gobject_class,
316 PROP_ADVANCE_CREATION_DAYS,
317 g_param_spec_int ("advance-creation-days",
318 "Days in advance to create",
319 "Number of days in advance to create this scheduled transaction.",
320 0,
321 G_MAXINT16,
322 0,
323 G_PARAM_READWRITE));
324
325 g_object_class_install_property
326 (gobject_class,
327 PROP_ADVANCE_REMINDER_DAYS,
328 g_param_spec_int ("advance-reminder-days",
329 "Days in advance to remind",
330 "Number of days in advance to remind about this scheduled transaction.",
331 0,
332 G_MAXINT16,
333 0,
334 G_PARAM_READWRITE));
335
336 g_object_class_install_property
337 (gobject_class,
338 PROP_START_DATE,
339 g_param_spec_boxed("start-date",
340 "Start Date",
341 "Date for the first occurrence for the scheduled transaction.",
342 G_TYPE_DATE,
343 G_PARAM_READWRITE));
344
345 g_object_class_install_property
346 (gobject_class,
347 PROP_END_DATE,
348 g_param_spec_boxed("end-date",
349 "End Date",
350 "Date for the scheduled transaction to end.",
351 G_TYPE_DATE,
352 G_PARAM_READWRITE));
353
354 g_object_class_install_property
355 (gobject_class,
356 PROP_LAST_OCCURANCE_DATE,
357 g_param_spec_boxed("last-occurance-date",
358 "Last Occurrence Date",
359 "Date for the last occurrence of the scheduled transaction.",
360 G_TYPE_DATE,
361 G_PARAM_READWRITE));
362
363 g_object_class_install_property
364 (gobject_class,
365 PROP_INSTANCE_COUNT,
366 g_param_spec_int ("instance-count",
367 "Instance count",
368 "Number of instances of this scheduled transaction.",
369 0,
370 G_MAXINT16,
371 0,
372 G_PARAM_READWRITE));
373
374 g_object_class_install_property
375 (gobject_class,
376 PROP_TEMPLATE_ACCOUNT,
377 g_param_spec_object("template-account",
378 "Template account",
379 "Account which holds the template transactions.",
380 GNC_TYPE_ACCOUNT,
381 G_PARAM_READWRITE));
382}
383
384static void
385xaccSchedXactionInit(SchedXaction *sx, QofBook *book)
386{
387 Account *ra;
388 const GncGUID *guid;
389 gchar guidstr[GUID_ENCODING_LENGTH+1];
390
391 qof_instance_init_data (&sx->inst, GNC_ID_SCHEDXACTION, book);
392
393 /* create a new template account for our splits */
394 sx->template_acct = xaccMallocAccount(book);
395 guid = qof_instance_get_guid( sx );
396 xaccAccountBeginEdit( sx->template_acct );
397 guid_to_string_buff( guid, guidstr );
398 xaccAccountSetName( sx->template_acct, guidstr);
400 (sx->template_acct,
401 gnc_commodity_table_lookup( gnc_commodity_table_get_table(book),
402 GNC_COMMODITY_NS_TEMPLATE, "template") );
403 xaccAccountSetType( sx->template_acct, ACCT_TYPE_BANK );
404 xaccAccountCommitEdit( sx->template_acct );
405 ra = gnc_book_get_template_root( book );
406 gnc_account_append_child( ra, sx->template_acct );
407}
408
409SchedXaction*
411{
412 SchedXaction *sx;
413
414 g_return_val_if_fail (book, NULL);
415
416 sx = GNC_SX(g_object_new(GNC_TYPE_SCHEDXACTION, NULL));
417 xaccSchedXactionInit( sx, book );
418 qof_event_gen( &sx->inst, QOF_EVENT_CREATE , NULL);
419
420 return sx;
421}
422
423static void
424delete_template_trans(SchedXaction *sx)
425{
426 std::unordered_set<Transaction*> txns;
427 auto& splits{xaccAccountGetSplits (sx->template_acct)};
428 std::for_each (splits.begin(), splits.end(),
429 [&txns](auto s){ txns.insert (xaccSplitGetParent (s)); });
430 std::for_each (txns.begin(), txns.end(),
431 [](auto t)
432 {
433 xaccTransBeginEdit (t);
434 xaccTransDestroy (t);
435 xaccTransCommitEdit (t);
436 });
437 return;
438}
439
440void
441sx_set_template_account (SchedXaction *sx, Account *account)
442{
443 Account *old;
444
445 old = sx->template_acct;
446 sx->template_acct = account;
447 if (old)
448 {
451 }
452}
453
454void
455xaccSchedXactionDestroy( SchedXaction *sx )
456{
457 qof_instance_set_destroying( QOF_INSTANCE(sx), TRUE );
458 gnc_sx_commit_edit( sx );
459}
460
461static void
462xaccSchedXactionFree( SchedXaction *sx )
463{
464 if ( sx == NULL ) return;
465
466 qof_event_gen( &sx->inst, QOF_EVENT_DESTROY , NULL);
467
468 if ( sx->name )
469 g_free( sx->name );
470
471 /*
472 * we have to delete the transactions in the
473 * template account ourselves
474 */
475
476 delete_template_trans( sx );
477
478 xaccAccountBeginEdit( sx->template_acct );
479 xaccAccountDestroy( sx->template_acct );
480
481 g_list_free_full (sx->deferredList, g_free);
482
483 /* a GList of Recurrences */
484 g_list_free_full (sx->schedule, g_free);
485
486 /* qof_instance_release (&sx->inst); */
487 g_object_unref( sx );
488}
489
490/* ============================================================ */
491
492void
493gnc_sx_begin_edit (SchedXaction *sx)
494{
495 qof_begin_edit (&sx->inst);
496}
497
498static void sx_free(QofInstance* inst )
499{
500 xaccSchedXactionFree( GNC_SX(inst) );
501}
502
503static void commit_err (QofInstance *inst, QofBackendError errcode)
504{
505 g_critical("Failed to commit: %d", errcode);
506 gnc_engine_signal_commit_error( errcode );
507}
508
509static void commit_done(QofInstance *inst)
510{
511 qof_event_gen (inst, QOF_EVENT_MODIFY, NULL);
512}
513
514void
515gnc_sx_commit_edit (SchedXaction *sx)
516{
517 if (!qof_commit_edit (QOF_INSTANCE(sx))) return;
518 qof_commit_edit_part2 (&sx->inst, commit_err, commit_done, sx_free);
519}
520
521/* ============================================================ */
522
523GList*
524gnc_sx_get_schedule(const SchedXaction *sx)
525{
526 return sx->schedule;
527}
528
529void
530gnc_sx_set_schedule(SchedXaction *sx, GList *schedule)
531{
532 g_return_if_fail(sx);
533 gnc_sx_begin_edit(sx);
534 sx->schedule = schedule;
535 qof_instance_set_dirty(&sx->inst);
536 gnc_sx_commit_edit(sx);
537}
538
539gchar *
540xaccSchedXactionGetName( const SchedXaction *sx )
541{
542 return sx->name;
543}
544
545void
546xaccSchedXactionSetName( SchedXaction *sx, const gchar *newName )
547{
548 g_return_if_fail( newName != NULL );
549 gnc_sx_begin_edit(sx);
550 if ( sx->name != NULL )
551 {
552 g_free( sx->name );
553 sx->name = NULL;
554 }
555 sx->name = g_strdup( newName );
556 qof_instance_set_dirty(&sx->inst);
557 gnc_sx_commit_edit(sx);
558}
559
560const GDate*
561xaccSchedXactionGetStartDate(const SchedXaction *sx )
562{
563 g_assert (sx);
564 return &sx->start_date;
565}
566
567time64
568xaccSchedXactionGetStartDateTT(const SchedXaction *sx )
569{
570 g_assert (sx);
571 return gdate_to_time64(sx->start_date);
572}
573
574void
575xaccSchedXactionSetStartDate( SchedXaction *sx, const GDate* newStart )
576{
577 if ( newStart == NULL || !g_date_valid( newStart ))
578 {
579 /* XXX: I reject the bad data - is this the right
580 * thing to do <rgmerk>.
581 * This warning is only human readable - the caller
582 * doesn't know the call failed. This is bad
583 */
584 g_critical("Invalid Start Date");
585 return;
586 }
587 gnc_sx_begin_edit(sx);
588 sx->start_date = *newStart;
589 qof_instance_set_dirty(&sx->inst);
590 gnc_sx_commit_edit(sx);
591}
592
593void
594xaccSchedXactionSetStartDateTT( SchedXaction *sx, const time64 newStart )
595{
596 if ( newStart == INT64_MAX )
597 {
598 /* XXX: I reject the bad data - is this the right
599 * thing to do <rgmerk>.
600 * This warning is only human readable - the caller
601 * doesn't know the call failed. This is bad
602 */
603 g_critical("Invalid Start Date");
604 return;
605 }
606 gnc_sx_begin_edit(sx);
607 gnc_gdate_set_time64(&sx->start_date, newStart);
608 qof_instance_set_dirty(&sx->inst);
609 gnc_sx_commit_edit(sx);
610}
611
612gboolean
613xaccSchedXactionHasEndDate( const SchedXaction *sx )
614{
615 return sx != NULL && g_date_valid( &sx->end_date );
616}
617
618const GDate*
619xaccSchedXactionGetEndDate(const SchedXaction *sx )
620{
621 g_assert (sx);
622 return &sx->end_date;
623}
624
625void
626xaccSchedXactionSetEndDate( SchedXaction *sx, const GDate *newEnd )
627{
628/* Note that an invalid GDate IS a permissible value: It means that
629 * the SX is to run "forever". See gnc_sxed_save_sx() and
630 * schedXact_editor_populate() in dialog-sx-editor.c.
631 */
632 if (newEnd == NULL ||
633 (g_date_valid(newEnd) && g_date_compare( newEnd, &sx->start_date ) < 0 ))
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("Bad End Date: Invalid or before Start Date");
641 return;
642 }
643
644 gnc_sx_begin_edit(sx);
645 sx->end_date = *newEnd;
646 qof_instance_set_dirty(&sx->inst);
647 gnc_sx_commit_edit(sx);
648}
649
650const GDate*
651xaccSchedXactionGetLastOccurDate(const SchedXaction *sx )
652{
653 return &sx->last_date;
654}
655
656time64
657xaccSchedXactionGetLastOccurDateTT(const SchedXaction *sx )
658{
659 return gdate_to_time64(sx->last_date);
660}
661
662void
663xaccSchedXactionSetLastOccurDate(SchedXaction *sx, const GDate* new_last_occur)
664{
665 g_return_if_fail (new_last_occur != NULL);
666 if (g_date_valid(&sx->last_date)
667 && g_date_compare(&sx->last_date, new_last_occur) == 0)
668 return;
669 gnc_sx_begin_edit(sx);
670 sx->last_date = *new_last_occur;
671 qof_instance_set_dirty(&sx->inst);
672 gnc_sx_commit_edit(sx);
673}
674
675void
676xaccSchedXactionSetLastOccurDateTT(SchedXaction *sx, time64 new_last_occur)
677{
678 GDate last_occur;
679 g_return_if_fail (new_last_occur != INT64_MAX);
680 gnc_gdate_set_time64(&last_occur, new_last_occur);
681 if (g_date_valid(&sx->last_date)
682 && g_date_compare(&sx->last_date, &last_occur) == 0)
683 return;
684 gnc_sx_begin_edit(sx);
685 sx->last_date = last_occur;
686 qof_instance_set_dirty(&sx->inst);
687 gnc_sx_commit_edit(sx);
688}
689
690gboolean
691xaccSchedXactionHasOccurDef( const SchedXaction *sx )
692{
693 return ( xaccSchedXactionGetNumOccur( sx ) != 0 );
694}
695
696gint
697xaccSchedXactionGetNumOccur( const SchedXaction *sx )
698{
699 return sx->num_occurances_total;
700}
701
702void
703xaccSchedXactionSetNumOccur(SchedXaction *sx, gint new_num)
704{
705 if (sx->num_occurances_total == new_num)
706 return;
707 gnc_sx_begin_edit(sx);
708 sx->num_occurances_remain = sx->num_occurances_total = new_num;
709 qof_instance_set_dirty(&sx->inst);
710 gnc_sx_commit_edit(sx);
711}
712
713gint
714xaccSchedXactionGetRemOccur( const SchedXaction *sx )
715{
716 return sx->num_occurances_remain;
717}
718
719void
720xaccSchedXactionSetRemOccur(SchedXaction *sx, gint num_remain)
721{
722 /* FIXME This condition can be tightened up */
723 if (num_remain > sx->num_occurances_total)
724 {
725 g_warning("number remaining [%d] > total occurrences [%d]",
726 num_remain, sx->num_occurances_total);
727 }
728 else
729 {
730 if (num_remain == sx->num_occurances_remain)
731 return;
732 gnc_sx_begin_edit(sx);
733 sx->num_occurances_remain = num_remain;
734 qof_instance_set_dirty(&sx->inst);
735 gnc_sx_commit_edit(sx);
736 }
737}
738
739gint gnc_sx_get_num_occur_daterange(const SchedXaction *sx, const GDate* start_date, const GDate* end_date)
740{
741 gint result = 0;
742 SXTmpStateData *tmpState;
743 gboolean countFirstDate;
744
745 /* SX still active? If not, return now. */
747 && xaccSchedXactionGetRemOccur(sx) <= 0)
748 || (xaccSchedXactionHasEndDate(sx)
749 && g_date_compare(xaccSchedXactionGetEndDate(sx), start_date) < 0))
750 {
751 return result;
752 }
753
754 tmpState = gnc_sx_create_temporal_state (sx);
755
756 /* Should we count the first valid date we encounter? Only if the
757 * SX has not yet occurred so far, or if its last valid date was
758 * before the start date. */
759 countFirstDate = !g_date_valid(&tmpState->last_date)
760 || (g_date_compare(&tmpState->last_date, start_date) < 0);
761
762 /* No valid date? SX has never occurred so far. */
763 if (!g_date_valid(&tmpState->last_date))
764 {
765 /* SX has never occurred so far */
766 gnc_sx_incr_temporal_state (sx, tmpState);
767 if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0)
768 {
769 g_free (tmpState);
770 return result;
771 }
772 }
773
774 /* Increase the tmpState until we are in our interval of
775 * interest. Only calculate anything if the sx hasn't already
776 * ended. */
777 while (g_date_compare(&tmpState->last_date, start_date) < 0)
778 {
779 gnc_sx_incr_temporal_state (sx, tmpState);
780 if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0)
781 {
782 g_free (tmpState);
783 return result;
784 }
785 }
786
787 /* Now we are in our interval of interest. Increment the
788 * occurrence date until we are beyond the end of our
789 * interval. Make sure to check for invalid dates here: It means
790 * the SX has ended. */
791 while (g_date_valid(&tmpState->last_date)
792 && (g_date_compare(&tmpState->last_date, end_date) <= 0)
793 && (!xaccSchedXactionHasEndDate(sx)
794 || g_date_compare(&tmpState->last_date, xaccSchedXactionGetEndDate(sx)) <= 0)
796 /* The >=0 (i.e. the ==) is important here, otherwise
797 * we miss the last valid occurrence of a SX which is
798 * limited by num_occur */
799 || tmpState->num_occur_rem >= 0))
800 {
801 ++result;
802 gnc_sx_incr_temporal_state (sx, tmpState);
803 }
804
805 /* If the first valid date shouldn't be counted, decrease the
806 * result number by one. */
807 if (!countFirstDate && result > 0)
808 --result;
809
810 g_free (tmpState);
811 return result;
812}
813
814gboolean
815xaccSchedXactionGetEnabled( const SchedXaction *sx )
816{
817 return sx->enabled;
818}
819
820void
821xaccSchedXactionSetEnabled( SchedXaction *sx, gboolean newEnabled)
822{
823 gnc_sx_begin_edit(sx);
824 sx->enabled = newEnabled;
825 qof_instance_set_dirty(&sx->inst);
826 gnc_sx_commit_edit(sx);
827}
828
829void
830xaccSchedXactionGetAutoCreate( const SchedXaction *sx,
831 gboolean *outAutoCreate,
832 gboolean *outNotify )
833{
834 if (outAutoCreate != NULL)
835 *outAutoCreate = sx->autoCreateOption;
836 if (outNotify != NULL)
837 *outNotify = sx->autoCreateNotify;
838 return;
839}
840
841void
842xaccSchedXactionSetAutoCreate( SchedXaction *sx,
843 gboolean newAutoCreate,
844 gboolean newNotify )
845{
846
847 gnc_sx_begin_edit(sx);
848 sx->autoCreateOption = newAutoCreate;
849 sx->autoCreateNotify = newNotify;
850 qof_instance_set_dirty(&sx->inst);
851 gnc_sx_commit_edit(sx);
852 return;
853}
854
855gint
856xaccSchedXactionGetAdvanceCreation( const SchedXaction *sx )
857{
858 return sx->advanceCreateDays;
859}
860
861void
862xaccSchedXactionSetAdvanceCreation( SchedXaction *sx, gint createDays )
863{
864 gnc_sx_begin_edit(sx);
865 sx->advanceCreateDays = createDays;
866 qof_instance_set_dirty(&sx->inst);
867 gnc_sx_commit_edit(sx);
868}
869
870gint
871xaccSchedXactionGetAdvanceReminder( const SchedXaction *sx )
872{
873 return sx->advanceRemindDays;
874}
875
876void
877xaccSchedXactionSetAdvanceReminder( SchedXaction *sx, gint reminderDays )
878{
879 gnc_sx_begin_edit(sx);
880 sx->advanceRemindDays = reminderDays;
881 qof_instance_set_dirty(&sx->inst);
882 gnc_sx_commit_edit(sx);
883}
884
885GDate
887{
888 GDate prev_occur, next_occur;
889
890 g_date_clear( &prev_occur, 1 );
891 if ( tsd != NULL )
892 prev_occur = tsd->last_date;
893
894 /* If prev_occur is in the "cleared" state and sx->start_date isn't, then
895 * we're at the beginning. We want to pretend prev_occur is the day before
896 * the start_date in case the start_date is today so that the SX will fire
897 * today. If start_date isn't valid either then the SX will fire anyway, no
898 * harm done. prev_occur cannot be before start_date either.
899 */
900 if (g_date_valid (&sx->start_date) && (!g_date_valid ( &prev_occur ) || g_date_compare (&prev_occur, &sx->start_date)<0))
901 {
902 /* We must be at the beginning. */
903 prev_occur = sx->start_date;
904 g_date_subtract_days (&prev_occur, 1 );
905 }
906
907 recurrenceListNextInstance(sx->schedule, &prev_occur, &next_occur);
908
909 if ( xaccSchedXactionHasEndDate( sx ) )
910 {
911 const GDate *end_date = xaccSchedXactionGetEndDate( sx );
912 if ( g_date_compare( &next_occur, end_date ) > 0 )
913 {
914 g_date_clear( &next_occur, 1 );
915 }
916 }
917 else if ( xaccSchedXactionHasOccurDef( sx ) )
918 {
919 if ((tsd && tsd->num_occur_rem == 0) ||
920 (!tsd && sx->num_occurances_remain == 0 ))
921 {
922 g_date_clear( &next_occur, 1 );
923 }
924 }
925 return next_occur;
926}
927
928gint
929gnc_sx_get_instance_count( const SchedXaction *sx, SXTmpStateData *stateData )
930{
931 gint toRet = -1;
932 SXTmpStateData *tsd;
933
934 if ( stateData )
935 {
936 tsd = (SXTmpStateData*)stateData;
937 toRet = tsd->num_inst;
938 }
939 else
940 {
941 toRet = sx->instance_num;
942 }
943
944 return toRet;
945}
946
947void
948gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num)
949{
950 g_return_if_fail(sx);
951 if (sx->instance_num == instance_num)
952 return;
953 gnc_sx_begin_edit(sx);
954 sx->instance_num = instance_num;
955 qof_instance_set_dirty(&sx->inst);
956 gnc_sx_commit_edit(sx);
957}
958
959GList *
960xaccSchedXactionGetSplits( const SchedXaction *sx )
961{
962 g_return_val_if_fail( sx, NULL );
963 return xaccAccountGetSplitList(sx->template_acct);
964}
965
966static Split *
967pack_split_info (TTSplitInfoPtr s_info, Account *parent_acct,
968 Transaction *parent_trans, QofBook *book)
969{
970 Split *split;
971 const gchar *credit_formula;
972 const gchar *debit_formula;
973 const GncGUID *acc_guid;
974
975 split = xaccMallocSplit(book);
976
977 xaccSplitSetMemo(split, s_info->get_memo());
978
979 /* Set split-action with gnc_set_num_action which is the same as
980 * xaccSplitSetAction with these arguments */
981 gnc_set_num_action(NULL, split, NULL, s_info->get_action());
982
983 xaccAccountInsertSplit(parent_acct,
984 split);
985
986 credit_formula = s_info->get_credit_formula ();
987 debit_formula = s_info->get_debit_formula ();
988 acc_guid = qof_entity_get_guid(QOF_INSTANCE(s_info->get_account ()));
989 qof_instance_set (QOF_INSTANCE (split),
990 "sx-credit-formula", credit_formula,
991 "sx-debit-formula", debit_formula,
992 "sx-account", acc_guid,
993 NULL);
994
995 return split;
996}
997
998
999void
1000xaccSchedXactionSetTemplateTrans (SchedXaction *sx, const TTInfoVec& tt_vec, QofBook *book)
1001{
1002 Transaction *new_trans;
1003
1004 g_return_if_fail (book);
1005
1006 /* delete any old transactions, if there are any */
1007 delete_template_trans( sx );
1008
1009 for (auto tti : tt_vec)
1010 {
1011 new_trans = xaccMallocTransaction(book);
1012
1013 xaccTransBeginEdit(new_trans);
1014 xaccTransSetDescription(new_trans, tti->get_description());
1016 /* Set tran-num with gnc_set_num_action which is the same as
1017 * xaccTransSetNum with these arguments */
1018 gnc_set_num_action (new_trans, NULL, tti->get_num(), NULL);
1019 xaccTransSetNotes (new_trans, tti->get_notes ());
1020 xaccTransSetCurrency (new_trans, tti->get_currency ());
1021
1022 for (auto s_info : tti->get_template_splits())
1023 {
1024 auto new_split = pack_split_info(s_info, sx->template_acct, new_trans, book);
1025 xaccTransAppendSplit(new_trans, new_split);
1026 }
1027 xaccTransCommitEdit(new_trans);
1028 }
1029}
1030
1032gnc_sx_create_temporal_state(const SchedXaction *sx )
1033{
1034 auto toRet = g_new0 (SXTmpStateData, 1);
1035 if (g_date_valid (&(sx->last_date)))
1036 toRet->last_date = sx->last_date;
1037 else
1038 g_date_set_dmy (&toRet->last_date, 1, static_cast<GDateMonth>(1), 1970);
1039 toRet->num_occur_rem = sx->num_occurances_remain;
1040 toRet->num_inst = sx->instance_num;
1041 return toRet;
1042}
1043
1044void
1045gnc_sx_incr_temporal_state(const SchedXaction *sx, SXTmpStateData *tsd )
1046{
1047 g_return_if_fail(tsd != NULL);
1048 tsd->last_date = xaccSchedXactionGetNextInstance (sx, tsd);
1050 {
1051 --tsd->num_occur_rem;
1052 }
1053 ++tsd->num_inst;
1054}
1055
1056void
1058{
1059 g_free(tsd);
1060}
1061
1064{
1065 SXTmpStateData *toRet = NULL;
1066
1067 if(tsd)
1068 {
1069 toRet = g_new(SXTmpStateData, 1);
1070 *toRet = *tsd;
1071 }
1072
1073 return toRet;
1074}
1075
1076static gint
1077_temporal_state_data_cmp( gconstpointer a, gconstpointer b )
1078{
1079 const SXTmpStateData *tsd_a = (SXTmpStateData*)a;
1080 const SXTmpStateData *tsd_b = (SXTmpStateData*)b;
1081
1082 if ( !tsd_a && !tsd_b )
1083 return 0;
1084 if (tsd_a == tsd_b)
1085 return 0;
1086 if ( !tsd_a )
1087 return 1;
1088 if ( !tsd_b )
1089 return -1;
1090 return g_date_compare( &tsd_a->last_date,
1091 &tsd_b->last_date );
1092}
1093
1098void
1099gnc_sx_add_defer_instance( SchedXaction *sx, void *deferStateData )
1100{
1101 sx->deferredList = g_list_insert_sorted( sx->deferredList,
1102 deferStateData,
1103 _temporal_state_data_cmp );
1104}
1105
1110void
1111gnc_sx_remove_defer_instance( SchedXaction *sx, void *deferStateData )
1112{
1113 GList *found_by_value;
1114
1115 found_by_value = g_list_find_custom(
1116 sx->deferredList, deferStateData, _temporal_state_data_cmp);
1117 if (found_by_value == NULL)
1118 {
1119 g_warning("unable to find deferred instance");
1120 return;
1121 }
1122
1123 g_free (found_by_value->data);
1124 sx->deferredList = g_list_delete_link(sx->deferredList, found_by_value);
1125}
1126
1136GList*
1138{
1139 return sx->deferredList;
1140}
1141
1142static void
1143destroy_sx_on_book_close(QofInstance *ent, gpointer data)
1144{
1145 SchedXaction* sx = GNC_SCHEDXACTION(ent);
1146
1147 gnc_sx_begin_edit(sx);
1149}
1150
1156static void
1157gnc_sx_book_end(QofBook* book)
1158{
1159 QofCollection *col;
1160
1161 col = qof_book_get_collection(book, GNC_ID_SCHEDXACTION);
1162 qof_collection_foreach(col, destroy_sx_on_book_close, NULL);
1163
1164 // Now destroy the template root account
1165 gnc_book_set_template_root (book, NULL);
1166}
1167
1168#ifdef _MSC_VER
1169/* MSVC compiler doesn't have C99 "designated initializers"
1170 * so we wrap them in a macro that is empty on MSVC. */
1171# define DI(x) /* */
1172#else
1173# define DI(x) x
1174#endif
1175static QofObject SXDesc =
1176{
1177 DI(.interface_version = ) QOF_OBJECT_VERSION,
1178 DI(.e_type = ) GNC_SX_ID,
1179 DI(.type_label = ) "Scheduled Transaction",
1180 DI(.create = ) (void* (*)(QofBook*))xaccSchedXactionMalloc,
1181 DI(.book_begin = ) NULL,
1182 DI(.book_end = ) gnc_sx_book_end,
1183 DI(.is_dirty = ) qof_collection_is_dirty,
1184 DI(.mark_clean = ) qof_collection_mark_clean,
1185 DI(.foreach = ) qof_collection_foreach,
1186 DI(.printable = ) NULL,
1187 DI(.version_cmp = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
1188};
1189
1190gboolean
1192{
1193 static QofParam params[] =
1194 {
1195 {
1196 GNC_SX_NAME, QOF_TYPE_STRING, (QofAccessFunc)xaccSchedXactionGetName,
1198 },
1199 {
1200 GNC_SX_START_DATE, QOF_TYPE_DATE, (QofAccessFunc)xaccSchedXactionGetStartDateTT,
1201 (QofSetterFunc)xaccSchedXactionSetStartDateTT
1202 },
1203 {
1204 GNC_SX_LAST_DATE, QOF_TYPE_DATE, (QofAccessFunc)xaccSchedXactionGetLastOccurDateTT,
1205 (QofSetterFunc)xaccSchedXactionSetLastOccurDateTT
1206 },
1207 {
1208 GNC_SX_NUM_OCCUR, QOF_TYPE_INT64, (QofAccessFunc)xaccSchedXactionGetNumOccur,
1210 },
1211 {
1212 GNC_SX_REM_OCCUR, QOF_TYPE_INT64, (QofAccessFunc)xaccSchedXactionGetRemOccur,
1213 (QofSetterFunc)xaccSchedXactionSetRemOccur
1214 },
1215 { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
1216 { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
1217 { NULL },
1218 };
1219 qof_class_register(GNC_SX_ID, NULL, params);
1220 return qof_object_register(&SXDesc);
1221}
Account handling public routines.
Account public routines (C++ api)
Anchor Scheduled Transaction info in a book.
Scheduled Transactions public handling routines.
API for Transactions and Splits (journal entries)
All type declarations for the whole Gnucash engine.
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition Account.cpp:1516
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition Account.cpp:1475
void gnc_account_append_child(Account *new_parent, Account *child)
This function will remove from the child account any pre-existing parent relationship,...
Definition Account.cpp:2836
void xaccAccountSetName(Account *acc, const char *str)
Set the account's name.
Definition Account.cpp:2458
void xaccAccountDestroy(Account *acc)
The xaccAccountDestroy() routine can be used to get rid of an account.
Definition Account.cpp:1590
void xaccAccountSetType(Account *acc, GNCAccountType tip)
Set the account's type.
Definition Account.cpp:2437
Account * xaccMallocAccount(QofBook *book)
Constructor.
Definition Account.cpp:1270
void xaccAccountSetCommodity(Account *acc, gnc_commodity *com)
Set the account's commodity.
Definition Account.cpp:2678
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition qofbackend.h:58
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition qofbook.cpp:521
gpointer(* QofAccessFunc)(gpointer object, const QofParam *param)
The QofAccessFunc defines an arbitrary function pointer for access functions.
Definition qofclass.h:123
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition qofclass.h:130
void qof_class_register(QofIdTypeConst obj_name, QofSortFunc default_sort_function, const QofParam *params)
This function registers a new object class with the Qof subsystem.
Definition qofclass.cpp:86
gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book)
Returns the commodity table associated with a book.
void gnc_gdate_set_time64(GDate *gd, time64 time)
Set a GDate to a time64.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
time64 gdate_to_time64(GDate d)
Turns a GDate into a time64, returning the first second of the day.
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
#define xaccTransAppendSplit(t, s)
Add a split to the transaction.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
Transaction * xaccMallocTransaction(QofBook *book)
The xaccMallocTransaction() will malloc memory and initialize it.
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
This function sets the posted date of the transaction, specified by a time64 (see ctime(3)).
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction.
#define xaccAccountInsertSplit(acc, s)
The xaccAccountInsertSplit() method will insert the indicated split into the indicated account.
Definition Account.h:1069
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
void xaccTransSetNotes(Transaction *trans, const char *notes)
Sets the transaction Notes.
SplitList * xaccAccountGetSplitList(const Account *acc)
The xaccAccountGetSplitList() routine returns a pointer to a GList of the splits in the account.
Definition Account.cpp:3949
gboolean qof_collection_is_dirty(const QofCollection *col)
Return value of 'dirty' flag on collection.
Definition qofid.cpp:232
void qof_event_gen(QofInstance *entity, QofEventId event_id, gpointer event_data)
Invoke all registered event handlers using the given arguments.
Definition qofevent.cpp:231
#define GUID_ENCODING_LENGTH
Number of characters needed to encode a guid as a string not including the null terminator.
Definition guid.h:84
gchar * guid_to_string_buff(const GncGUID *guid, gchar *str)
The guid_to_string_buff() routine puts a null-terminated string encoding of the id into the memory po...
Definition guid.cpp:208
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
const GncGUID * qof_instance_get_guid(gconstpointer inst)
Return the GncGUID of this instance.
int qof_instance_version_cmp(const QofInstance *left, const QofInstance *right)
Compare two instances, based on their last update times.
void qof_instance_set(QofInstance *inst, const gchar *first_prop,...)
Wrapper for g_object_set Group setting multiple parameters in a single begin/commit/rollback.
void qof_instance_init_data(QofInstance *inst, QofIdType type, QofBook *book)
Initialise the settings associated with an instance.
const GncGUID * qof_entity_get_guid(gconstpointer ent)
void qof_collection_mark_clean(QofCollection *)
reset value of dirty flag
Definition qofid.cpp:238
#define QOF_OBJECT_VERSION
Defines the version of the core object object registration interface.
Definition qofobject.h:63
gboolean qof_object_register(const QofObject *object)
Register new types of object objects.
#define QOF_PARAM_BOOK
"Known" Object Parameters – all objects must support these
Definition qofquery.h:108
SXTmpStateData * gnc_sx_clone_temporal_state(SXTmpStateData *tsd)
Allocates and returns a one-by-one copy of the given temporal state.
GList * gnc_sx_get_defer_instances(SchedXaction *sx)
Returns the defer list from the SX; this is a (date-)sorted temporal-state-data instance list.
gint gnc_sx_get_instance_count(const SchedXaction *sx, SXTmpStateData *stateData)
Get the instance count.
void gnc_sx_set_schedule(SchedXaction *sx, GList *schedule)
GDate xaccSchedXactionGetNextInstance(const SchedXaction *sx, SXTmpStateData *tsd)
Returns the next occurrence of a scheduled transaction.
gint gnc_sx_get_num_occur_daterange(const SchedXaction *sx, const GDate *start_date, const GDate *end_date)
Calculates and returns the number of occurrences of the given SX in the given date range (inclusive).
Account * gnc_book_get_template_root(const QofBook *book)
Returns the template group from the book.
Definition SX-book.cpp:65
void gnc_sx_remove_defer_instance(SchedXaction *sx, void *deferStateData)
Removes an instance from the deferred list.
void gnc_sx_add_defer_instance(SchedXaction *sx, void *deferStateData)
Adds an instance to the deferred list of the SX.
gboolean xaccSchedXactionHasOccurDef(const SchedXaction *sx)
Returns true if the scheduled transaction has a defined number of occurrences, false if not.
const GDate * xaccSchedXactionGetEndDate(const SchedXaction *sx)
Returns invalid date when there is no end-date specified.
SchedXaction * xaccSchedXactionMalloc(QofBook *book)
Creates and initializes a scheduled transaction.
GList * gnc_sx_get_schedule(const SchedXaction *sx)
gboolean SXRegister(void)
QOF registration.
SXTmpStateData * gnc_sx_create_temporal_state(const SchedXaction *sx)
Allocates a new SXTmpStateData object and fills it with the current state of the given sx.
void xaccSchedXactionSetName(SchedXaction *sx, const gchar *newName)
A copy of the name is made.
void xaccSchedXactionSetEndDate(SchedXaction *sx, const GDate *newEnd)
Set to an invalid GDate to turn off 'end-date' definition.
void gnc_sx_destroy_temporal_state(SXTmpStateData *tsd)
Frees the given stateDate object.
void gnc_sx_incr_temporal_state(const SchedXaction *sx, SXTmpStateData *tsd)
Calculates the next occurrence of the given SX and stores that occurrence in the remporalStateDate.
void xaccSchedXactionDestroy(SchedXaction *sx)
Cleans up and frees a SchedXaction and its associated data.
void xaccSchedXactionSetNumOccur(SchedXaction *sx, gint new_num)
Set to '0' to turn off number-of-occurrences definition.
void gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num)
Sets the instance count to something other than the default.
void xaccSplitSetMemo(Split *split, const char *memo)
The memo is an arbitrary string associated with a split.
Split * xaccMallocSplit(QofBook *book)
Constructor.
gboolean qof_commit_edit_part2(QofInstance *inst, void(*on_error)(QofInstance *, QofBackendError), void(*on_done)(QofInstance *), void(*on_free)(QofInstance *))
part2 – deal with the backend
gboolean qof_commit_edit(QofInstance *inst)
commit_edit helpers
gboolean qof_begin_edit(QofInstance *inst)
begin_edit
STRUCTS.
The type used to store guids in C.
Definition guid.h:75
QofBook reference.
Definition qofbook-p.hpp:47
Just the variable temporal bits from the SX structure.