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