Line data Source code
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 :
50 : enum
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 */
69 270 : G_DEFINE_TYPE(SchedXaction, gnc_schedxaction, QOF_TYPE_INSTANCE)
70 :
71 : static void
72 24 : gnc_schedxaction_init(SchedXaction* sx)
73 : {
74 24 : sx->schedule = NULL;
75 :
76 24 : g_date_clear( &sx->last_date, 1 );
77 24 : g_date_clear( &sx->start_date, 1 );
78 24 : g_date_clear( &sx->end_date, 1 );
79 :
80 24 : sx->enabled = 1;
81 24 : sx->num_occurances_total = 0;
82 24 : sx->autoCreateOption = FALSE;
83 24 : sx->autoCreateNotify = FALSE;
84 24 : sx->advanceCreateDays = 0;
85 24 : sx->advanceRemindDays = 0;
86 24 : sx->instance_num = 0;
87 24 : sx->deferredList = NULL;
88 24 : }
89 :
90 : static void
91 8 : gnc_schedxaction_dispose(GObject *sxp)
92 : {
93 8 : G_OBJECT_CLASS(gnc_schedxaction_parent_class)->dispose(sxp);
94 8 : }
95 :
96 : static void
97 8 : gnc_schedxaction_finalize(GObject* sxp)
98 : {
99 8 : G_OBJECT_CLASS(gnc_schedxaction_parent_class)->finalize(sxp);
100 8 : }
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 : */
108 : static void
109 42 : gnc_schedxaction_get_property (GObject *object,
110 : guint prop_id,
111 : GValue *value,
112 : GParamSpec *pspec)
113 : {
114 : SchedXaction *sx;
115 :
116 42 : g_return_if_fail(GNC_IS_SCHEDXACTION(object));
117 :
118 42 : sx = GNC_SCHEDXACTION(object);
119 42 : switch (prop_id)
120 : {
121 3 : case PROP_NAME:
122 3 : g_value_set_string(value, sx->name);
123 3 : break;
124 3 : case PROP_ENABLED:
125 3 : g_value_set_boolean(value, sx->enabled);
126 3 : break;
127 3 : case PROP_NUM_OCCURANCE:
128 3 : g_value_set_int(value, sx->num_occurances_total);
129 3 : break;
130 3 : case PROP_REM_OCCURANCE:
131 3 : g_value_set_int(value, sx->num_occurances_remain);
132 3 : break;
133 3 : case PROP_AUTO_CREATE:
134 3 : g_value_set_boolean(value, sx->autoCreateOption);
135 3 : break;
136 3 : case PROP_AUTO_CREATE_NOTIFY:
137 3 : g_value_set_boolean(value, sx->autoCreateNotify);
138 3 : break;
139 3 : case PROP_ADVANCE_CREATION_DAYS:
140 3 : g_value_set_int(value, sx->advanceCreateDays);
141 3 : break;
142 3 : case PROP_ADVANCE_REMINDER_DAYS:
143 3 : g_value_set_int(value, sx->advanceRemindDays);
144 3 : break;
145 6 : case PROP_START_DATE:
146 6 : g_value_set_boxed(value, &sx->start_date);
147 6 : break;
148 3 : case PROP_END_DATE:
149 : /* g_value_set_boxed raises a critical error if sx->end_date
150 : * is invalid */
151 3 : if (g_date_valid (&sx->end_date))
152 1 : g_value_set_boxed(value, &sx->end_date);
153 3 : break;
154 3 : case PROP_LAST_OCCURANCE_DATE:
155 : /* g_value_set_boxed raises a critical error if sx->last_date
156 : * is invalid */
157 3 : if (g_date_valid (&sx->last_date))
158 0 : g_value_set_boxed(value, &sx->last_date);
159 3 : break;
160 3 : case PROP_INSTANCE_COUNT:
161 3 : g_value_set_int(value, sx->instance_num);
162 3 : break;
163 3 : case PROP_TEMPLATE_ACCOUNT:
164 3 : g_value_take_object(value, sx->template_acct);
165 3 : break;
166 0 : default:
167 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
168 0 : break;
169 : }
170 : }
171 :
172 : static void
173 34 : gnc_schedxaction_set_property (GObject *object,
174 : guint prop_id,
175 : const GValue *value,
176 : GParamSpec *pspec)
177 : {
178 : SchedXaction *sx;
179 :
180 34 : g_return_if_fail(GNC_IS_SCHEDXACTION(object));
181 :
182 34 : sx = GNC_SCHEDXACTION(object);
183 34 : g_assert (qof_instance_get_editlevel(sx));
184 :
185 34 : switch (prop_id)
186 : {
187 3 : case PROP_NAME:
188 3 : xaccSchedXactionSetName(sx, g_value_get_string(value));
189 3 : break;
190 3 : case PROP_ENABLED:
191 3 : xaccSchedXactionSetEnabled(sx, g_value_get_boolean(value));
192 3 : break;
193 3 : case PROP_NUM_OCCURANCE:
194 3 : xaccSchedXactionSetNumOccur(sx, g_value_get_int(value));
195 3 : break;
196 3 : case PROP_REM_OCCURANCE:
197 3 : xaccSchedXactionSetRemOccur(sx, g_value_get_int(value));
198 3 : break;
199 3 : case PROP_AUTO_CREATE:
200 3 : xaccSchedXactionSetAutoCreate(sx, g_value_get_boolean(value), sx->autoCreateNotify);
201 3 : break;
202 3 : case PROP_AUTO_CREATE_NOTIFY:
203 3 : xaccSchedXactionSetAutoCreate(sx, sx->autoCreateOption, g_value_get_boolean(value));
204 3 : break;
205 3 : case PROP_ADVANCE_CREATION_DAYS:
206 3 : xaccSchedXactionSetAdvanceCreation(sx, g_value_get_int(value));
207 3 : break;
208 3 : case PROP_ADVANCE_REMINDER_DAYS:
209 3 : xaccSchedXactionSetAdvanceReminder(sx, g_value_get_int(value));
210 3 : break;
211 3 : 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 3 : xaccSchedXactionSetStartDate(sx, static_cast<const GDate*>(g_value_get_boxed(value)));
216 3 : break;
217 1 : 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 1 : xaccSchedXactionSetEndDate(sx, static_cast<const GDate*>(g_value_get_boxed(value)));
222 1 : break;
223 0 : 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 0 : xaccSchedXactionSetLastOccurDate(sx, static_cast<const GDate*>(g_value_get_boxed(value)));
228 0 : break;
229 3 : case PROP_INSTANCE_COUNT:
230 3 : gnc_sx_set_instance_count(sx, g_value_get_int(value));
231 3 : break;
232 3 : case PROP_TEMPLATE_ACCOUNT:
233 3 : sx_set_template_account(sx, GNC_ACCOUNT(g_value_get_object(value)));
234 3 : break;
235 0 : default:
236 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
237 0 : break;
238 : }
239 : }
240 :
241 : static void
242 3 : gnc_schedxaction_class_init (SchedXactionClass *klass)
243 : {
244 3 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
245 :
246 3 : gobject_class->dispose = gnc_schedxaction_dispose;
247 3 : gobject_class->finalize = gnc_schedxaction_finalize;
248 3 : gobject_class->set_property = gnc_schedxaction_set_property;
249 3 : gobject_class->get_property = gnc_schedxaction_get_property;
250 :
251 : g_object_class_install_property
252 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : (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 3 : }
383 :
384 : static void
385 24 : xaccSchedXactionInit(SchedXaction *sx, QofBook *book)
386 : {
387 : Account *ra;
388 : const GncGUID *guid;
389 : gchar guidstr[GUID_ENCODING_LENGTH+1];
390 :
391 24 : qof_instance_init_data (&sx->inst, GNC_ID_SCHEDXACTION, book);
392 :
393 : /* create a new template account for our splits */
394 24 : sx->template_acct = xaccMallocAccount(book);
395 24 : guid = qof_instance_get_guid( sx );
396 24 : xaccAccountBeginEdit( sx->template_acct );
397 24 : guid_to_string_buff( guid, guidstr );
398 24 : xaccAccountSetName( sx->template_acct, guidstr);
399 : xaccAccountSetCommodity
400 24 : (sx->template_acct,
401 24 : gnc_commodity_table_lookup( gnc_commodity_table_get_table(book),
402 : GNC_COMMODITY_NS_TEMPLATE, "template") );
403 24 : xaccAccountSetType( sx->template_acct, ACCT_TYPE_BANK );
404 24 : xaccAccountCommitEdit( sx->template_acct );
405 24 : ra = gnc_book_get_template_root( book );
406 24 : gnc_account_append_child( ra, sx->template_acct );
407 24 : }
408 :
409 : SchedXaction*
410 24 : xaccSchedXactionMalloc(QofBook *book)
411 : {
412 : SchedXaction *sx;
413 :
414 24 : g_return_val_if_fail (book, NULL);
415 :
416 24 : sx = GNC_SX(g_object_new(GNC_TYPE_SCHEDXACTION, NULL));
417 24 : xaccSchedXactionInit( sx, book );
418 24 : qof_event_gen( &sx->inst, QOF_EVENT_CREATE , NULL);
419 :
420 24 : return sx;
421 : }
422 :
423 : static void
424 12 : delete_template_trans(SchedXaction *sx)
425 : {
426 12 : std::unordered_set<Transaction*> txns;
427 12 : auto& splits{xaccAccountGetSplits (sx->template_acct)};
428 12 : std::for_each (splits.begin(), splits.end(),
429 0 : [&txns](auto s){ txns.insert (xaccSplitGetParent (s)); });
430 12 : std::for_each (txns.begin(), txns.end(),
431 0 : [](auto t)
432 : {
433 0 : xaccTransBeginEdit (t);
434 0 : xaccTransDestroy (t);
435 0 : xaccTransCommitEdit (t);
436 0 : });
437 24 : return;
438 12 : }
439 :
440 : void
441 8 : sx_set_template_account (SchedXaction *sx, Account *account)
442 : {
443 : Account *old;
444 :
445 8 : old = sx->template_acct;
446 8 : sx->template_acct = account;
447 8 : if (old)
448 : {
449 8 : xaccAccountBeginEdit(old);
450 8 : xaccAccountDestroy(old);
451 : }
452 8 : }
453 :
454 : void
455 8 : xaccSchedXactionDestroy( SchedXaction *sx )
456 : {
457 8 : qof_instance_set_destroying( QOF_INSTANCE(sx), TRUE );
458 8 : gnc_sx_commit_edit( sx );
459 8 : }
460 :
461 : static void
462 8 : xaccSchedXactionFree( SchedXaction *sx )
463 : {
464 8 : if ( sx == NULL ) return;
465 :
466 8 : qof_event_gen( &sx->inst, QOF_EVENT_DESTROY , NULL);
467 :
468 8 : if ( sx->name )
469 8 : g_free( sx->name );
470 :
471 : /*
472 : * we have to delete the transactions in the
473 : * template account ourselves
474 : */
475 :
476 8 : delete_template_trans( sx );
477 :
478 8 : xaccAccountBeginEdit( sx->template_acct );
479 8 : xaccAccountDestroy( sx->template_acct );
480 :
481 8 : g_list_free_full (sx->deferredList, g_free);
482 :
483 : /* a GList of Recurrences */
484 8 : g_list_free_full (sx->schedule, g_free);
485 :
486 : /* qof_instance_release (&sx->inst); */
487 8 : g_object_unref( sx );
488 : }
489 :
490 : /* ============================================================ */
491 :
492 : void
493 145 : gnc_sx_begin_edit (SchedXaction *sx)
494 : {
495 145 : qof_begin_edit (&sx->inst);
496 145 : }
497 :
498 8 : static void sx_free(QofInstance* inst )
499 : {
500 8 : xaccSchedXactionFree( GNC_SX(inst) );
501 8 : }
502 :
503 0 : static void commit_err (QofInstance *inst, QofBackendError errcode)
504 : {
505 0 : g_critical("Failed to commit: %d", errcode);
506 0 : gnc_engine_signal_commit_error( errcode );
507 0 : }
508 :
509 107 : static void commit_done(QofInstance *inst)
510 : {
511 107 : qof_event_gen (inst, QOF_EVENT_MODIFY, NULL);
512 107 : }
513 :
514 : void
515 145 : gnc_sx_commit_edit (SchedXaction *sx)
516 : {
517 145 : if (!qof_commit_edit (QOF_INSTANCE(sx))) return;
518 115 : qof_commit_edit_part2 (&sx->inst, commit_err, commit_done, sx_free);
519 : }
520 :
521 : /* ============================================================ */
522 :
523 : GList*
524 5 : gnc_sx_get_schedule(const SchedXaction *sx)
525 : {
526 5 : return sx->schedule;
527 : }
528 :
529 : void
530 24 : gnc_sx_set_schedule(SchedXaction *sx, GList *schedule)
531 : {
532 24 : g_return_if_fail(sx);
533 24 : gnc_sx_begin_edit(sx);
534 24 : sx->schedule = schedule;
535 24 : qof_instance_set_dirty(&sx->inst);
536 24 : gnc_sx_commit_edit(sx);
537 : }
538 :
539 : gchar *
540 7 : xaccSchedXactionGetName( const SchedXaction *sx )
541 : {
542 7 : return sx->name;
543 : }
544 :
545 : void
546 24 : xaccSchedXactionSetName( SchedXaction *sx, const gchar *newName )
547 : {
548 24 : g_return_if_fail( newName != NULL );
549 24 : gnc_sx_begin_edit(sx);
550 24 : if ( sx->name != NULL )
551 : {
552 0 : g_free( sx->name );
553 0 : sx->name = NULL;
554 : }
555 24 : sx->name = g_strdup( newName );
556 24 : qof_instance_set_dirty(&sx->inst);
557 24 : gnc_sx_commit_edit(sx);
558 : }
559 :
560 : const GDate*
561 2 : xaccSchedXactionGetStartDate(const SchedXaction *sx )
562 : {
563 2 : g_assert (sx);
564 2 : return &sx->start_date;
565 : }
566 :
567 : time64
568 0 : xaccSchedXactionGetStartDateTT(const SchedXaction *sx )
569 : {
570 0 : g_assert (sx);
571 0 : return gdate_to_time64(sx->start_date);
572 : }
573 :
574 : void
575 24 : xaccSchedXactionSetStartDate( SchedXaction *sx, const GDate* newStart )
576 : {
577 24 : 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 0 : g_critical("Invalid Start Date");
585 0 : return;
586 : }
587 24 : gnc_sx_begin_edit(sx);
588 24 : sx->start_date = *newStart;
589 24 : qof_instance_set_dirty(&sx->inst);
590 24 : gnc_sx_commit_edit(sx);
591 : }
592 :
593 : void
594 0 : xaccSchedXactionSetStartDateTT( SchedXaction *sx, const time64 newStart )
595 : {
596 0 : 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 0 : g_critical("Invalid Start Date");
604 0 : return;
605 : }
606 0 : gnc_sx_begin_edit(sx);
607 0 : gnc_gdate_set_time64(&sx->start_date, newStart);
608 0 : qof_instance_set_dirty(&sx->inst);
609 0 : gnc_sx_commit_edit(sx);
610 : }
611 :
612 : gboolean
613 89 : xaccSchedXactionHasEndDate( const SchedXaction *sx )
614 : {
615 89 : return sx != NULL && g_date_valid( &sx->end_date );
616 : }
617 :
618 : const GDate*
619 29 : xaccSchedXactionGetEndDate(const SchedXaction *sx )
620 : {
621 29 : g_assert (sx);
622 29 : return &sx->end_date;
623 : }
624 :
625 : void
626 3 : xaccSchedXactionSetEndDate( 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 6 : if (newEnd == NULL ||
633 3 : (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 0 : g_critical("Bad End Date: Invalid or before Start Date");
641 0 : return;
642 : }
643 :
644 3 : gnc_sx_begin_edit(sx);
645 3 : sx->end_date = *newEnd;
646 3 : qof_instance_set_dirty(&sx->inst);
647 3 : gnc_sx_commit_edit(sx);
648 : }
649 :
650 : const GDate*
651 6 : xaccSchedXactionGetLastOccurDate(const SchedXaction *sx )
652 : {
653 6 : return &sx->last_date;
654 : }
655 :
656 : time64
657 0 : xaccSchedXactionGetLastOccurDateTT(const SchedXaction *sx )
658 : {
659 0 : return gdate_to_time64(sx->last_date);
660 : }
661 :
662 : void
663 6 : xaccSchedXactionSetLastOccurDate(SchedXaction *sx, const GDate* new_last_occur)
664 : {
665 6 : g_return_if_fail (new_last_occur != NULL);
666 6 : if (g_date_valid(&sx->last_date)
667 6 : && g_date_compare(&sx->last_date, new_last_occur) == 0)
668 0 : return;
669 6 : gnc_sx_begin_edit(sx);
670 6 : sx->last_date = *new_last_occur;
671 6 : qof_instance_set_dirty(&sx->inst);
672 6 : gnc_sx_commit_edit(sx);
673 : }
674 :
675 : void
676 0 : xaccSchedXactionSetLastOccurDateTT(SchedXaction *sx, time64 new_last_occur)
677 : {
678 : GDate last_occur;
679 0 : g_return_if_fail (new_last_occur != INT64_MAX);
680 0 : gnc_gdate_set_time64(&last_occur, new_last_occur);
681 0 : if (g_date_valid(&sx->last_date)
682 0 : && g_date_compare(&sx->last_date, &last_occur) == 0)
683 0 : return;
684 0 : gnc_sx_begin_edit(sx);
685 0 : sx->last_date = last_occur;
686 0 : qof_instance_set_dirty(&sx->inst);
687 0 : gnc_sx_commit_edit(sx);
688 : }
689 :
690 : gboolean
691 119 : xaccSchedXactionHasOccurDef( const SchedXaction *sx )
692 : {
693 119 : return ( xaccSchedXactionGetNumOccur( sx ) != 0 );
694 : }
695 :
696 : gint
697 119 : xaccSchedXactionGetNumOccur( const SchedXaction *sx )
698 : {
699 119 : return sx->num_occurances_total;
700 : }
701 :
702 : void
703 8 : xaccSchedXactionSetNumOccur(SchedXaction *sx, gint new_num)
704 : {
705 8 : if (sx->num_occurances_total == new_num)
706 2 : return;
707 6 : gnc_sx_begin_edit(sx);
708 6 : sx->num_occurances_remain = sx->num_occurances_total = new_num;
709 6 : qof_instance_set_dirty(&sx->inst);
710 6 : gnc_sx_commit_edit(sx);
711 : }
712 :
713 : gint
714 4 : xaccSchedXactionGetRemOccur( const SchedXaction *sx )
715 : {
716 4 : return sx->num_occurances_remain;
717 : }
718 :
719 : void
720 12 : xaccSchedXactionSetRemOccur(SchedXaction *sx, gint num_remain)
721 : {
722 : /* FIXME This condition can be tightened up */
723 12 : if (num_remain > sx->num_occurances_total)
724 : {
725 0 : g_warning("number remaining [%d] > total occurrences [%d]",
726 : num_remain, sx->num_occurances_total);
727 : }
728 : else
729 : {
730 12 : if (num_remain == sx->num_occurances_remain)
731 6 : return;
732 6 : gnc_sx_begin_edit(sx);
733 6 : sx->num_occurances_remain = num_remain;
734 6 : qof_instance_set_dirty(&sx->inst);
735 6 : gnc_sx_commit_edit(sx);
736 : }
737 : }
738 :
739 6 : gint gnc_sx_get_num_occur_daterange(const SchedXaction *sx, const GDate* start_date, const GDate* end_date)
740 : {
741 6 : gint result = 0;
742 : SXTmpStateData *tmpState;
743 : gboolean countFirstDate;
744 :
745 : /* SX still active? If not, return now. */
746 6 : if ((xaccSchedXactionHasOccurDef(sx)
747 0 : && xaccSchedXactionGetRemOccur(sx) <= 0)
748 12 : || (xaccSchedXactionHasEndDate(sx)
749 6 : && g_date_compare(xaccSchedXactionGetEndDate(sx), start_date) < 0))
750 : {
751 1 : return result;
752 : }
753 :
754 5 : 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 5 : countFirstDate = !g_date_valid(&tmpState->last_date)
760 5 : || (g_date_compare(&tmpState->last_date, start_date) < 0);
761 :
762 : /* No valid date? SX has never occurred so far. */
763 5 : if (!g_date_valid(&tmpState->last_date))
764 : {
765 : /* SX has never occurred so far */
766 0 : gnc_sx_incr_temporal_state (sx, tmpState);
767 0 : if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0)
768 : {
769 0 : g_free (tmpState);
770 0 : 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 14 : while (g_date_compare(&tmpState->last_date, start_date) < 0)
778 : {
779 9 : gnc_sx_incr_temporal_state (sx, tmpState);
780 9 : if (xaccSchedXactionHasOccurDef(sx) && tmpState->num_occur_rem < 0)
781 : {
782 0 : g_free (tmpState);
783 0 : 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 12 : while (g_date_valid(&tmpState->last_date)
792 9 : && (g_date_compare(&tmpState->last_date, end_date) <= 0)
793 7 : && (!xaccSchedXactionHasEndDate(sx)
794 7 : || g_date_compare(&tmpState->last_date, xaccSchedXactionGetEndDate(sx)) <= 0)
795 21 : && (!xaccSchedXactionHasOccurDef(sx)
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 0 : || tmpState->num_occur_rem >= 0))
800 : {
801 7 : ++result;
802 7 : 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 5 : if (!countFirstDate && result > 0)
808 0 : --result;
809 :
810 5 : g_free (tmpState);
811 5 : return result;
812 : }
813 :
814 : gboolean
815 16 : xaccSchedXactionGetEnabled( const SchedXaction *sx )
816 : {
817 16 : return sx->enabled;
818 : }
819 :
820 : void
821 3 : xaccSchedXactionSetEnabled( SchedXaction *sx, gboolean newEnabled)
822 : {
823 3 : gnc_sx_begin_edit(sx);
824 3 : sx->enabled = newEnabled;
825 3 : qof_instance_set_dirty(&sx->inst);
826 3 : gnc_sx_commit_edit(sx);
827 3 : }
828 :
829 : void
830 8 : xaccSchedXactionGetAutoCreate( const SchedXaction *sx,
831 : gboolean *outAutoCreate,
832 : gboolean *outNotify )
833 : {
834 8 : if (outAutoCreate != NULL)
835 8 : *outAutoCreate = sx->autoCreateOption;
836 8 : if (outNotify != NULL)
837 4 : *outNotify = sx->autoCreateNotify;
838 8 : return;
839 : }
840 :
841 : void
842 10 : xaccSchedXactionSetAutoCreate( SchedXaction *sx,
843 : gboolean newAutoCreate,
844 : gboolean newNotify )
845 : {
846 :
847 10 : gnc_sx_begin_edit(sx);
848 10 : sx->autoCreateOption = newAutoCreate;
849 10 : sx->autoCreateNotify = newNotify;
850 10 : qof_instance_set_dirty(&sx->inst);
851 10 : gnc_sx_commit_edit(sx);
852 10 : return;
853 : }
854 :
855 : gint
856 16 : xaccSchedXactionGetAdvanceCreation( const SchedXaction *sx )
857 : {
858 16 : return sx->advanceCreateDays;
859 : }
860 :
861 : void
862 8 : xaccSchedXactionSetAdvanceCreation( SchedXaction *sx, gint createDays )
863 : {
864 8 : gnc_sx_begin_edit(sx);
865 8 : sx->advanceCreateDays = createDays;
866 8 : qof_instance_set_dirty(&sx->inst);
867 8 : gnc_sx_commit_edit(sx);
868 8 : }
869 :
870 : gint
871 16 : xaccSchedXactionGetAdvanceReminder( const SchedXaction *sx )
872 : {
873 16 : return sx->advanceRemindDays;
874 : }
875 :
876 : void
877 8 : xaccSchedXactionSetAdvanceReminder( SchedXaction *sx, gint reminderDays )
878 : {
879 8 : gnc_sx_begin_edit(sx);
880 8 : sx->advanceRemindDays = reminderDays;
881 8 : qof_instance_set_dirty(&sx->inst);
882 8 : gnc_sx_commit_edit(sx);
883 8 : }
884 :
885 : GDate
886 74 : xaccSchedXactionGetNextInstance (const SchedXaction *sx, SXTmpStateData *tsd)
887 : {
888 : GDate prev_occur, next_occur;
889 :
890 74 : g_date_clear( &prev_occur, 1 );
891 74 : if ( tsd != NULL )
892 74 : 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 74 : 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 37 : prev_occur = sx->start_date;
904 37 : g_date_subtract_days (&prev_occur, 1 );
905 : }
906 :
907 74 : recurrenceListNextInstance(sx->schedule, &prev_occur, &next_occur);
908 :
909 74 : if ( xaccSchedXactionHasEndDate( sx ) )
910 : {
911 16 : const GDate *end_date = xaccSchedXactionGetEndDate( sx );
912 16 : if ( g_date_compare( &next_occur, end_date ) > 0 )
913 : {
914 3 : g_date_clear( &next_occur, 1 );
915 : }
916 : }
917 58 : else if ( xaccSchedXactionHasOccurDef( sx ) )
918 : {
919 12 : if ((tsd && tsd->num_occur_rem == 0) ||
920 0 : (!tsd && sx->num_occurances_remain == 0 ))
921 : {
922 4 : g_date_clear( &next_occur, 1 );
923 : }
924 : }
925 74 : return next_occur;
926 : }
927 :
928 : gint
929 52 : gnc_sx_get_instance_count( const SchedXaction *sx, SXTmpStateData *stateData )
930 : {
931 52 : gint toRet = -1;
932 : SXTmpStateData *tsd;
933 :
934 52 : if ( stateData )
935 : {
936 46 : tsd = (SXTmpStateData*)stateData;
937 46 : toRet = tsd->num_inst;
938 : }
939 : else
940 : {
941 6 : toRet = sx->instance_num;
942 : }
943 :
944 52 : return toRet;
945 : }
946 :
947 : void
948 12 : gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num)
949 : {
950 12 : g_return_if_fail(sx);
951 12 : if (sx->instance_num == instance_num)
952 0 : return;
953 12 : gnc_sx_begin_edit(sx);
954 12 : sx->instance_num = instance_num;
955 12 : qof_instance_set_dirty(&sx->inst);
956 12 : gnc_sx_commit_edit(sx);
957 : }
958 :
959 : GList *
960 0 : xaccSchedXactionGetSplits( const SchedXaction *sx )
961 : {
962 0 : g_return_val_if_fail( sx, NULL );
963 0 : return xaccAccountGetSplitList(sx->template_acct);
964 : }
965 :
966 : static Split *
967 8 : pack_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 8 : split = xaccMallocSplit(book);
976 :
977 8 : 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 8 : gnc_set_num_action(NULL, split, NULL, s_info->get_action());
982 :
983 8 : xaccAccountInsertSplit(parent_acct,
984 : split);
985 :
986 8 : credit_formula = s_info->get_credit_formula ();
987 8 : debit_formula = s_info->get_debit_formula ();
988 8 : acc_guid = qof_entity_get_guid(QOF_INSTANCE(s_info->get_account ()));
989 8 : 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 8 : return split;
996 : }
997 :
998 :
999 : void
1000 4 : xaccSchedXactionSetTemplateTrans (SchedXaction *sx, const TTInfoVec& tt_vec, QofBook *book)
1001 : {
1002 : Transaction *new_trans;
1003 :
1004 4 : g_return_if_fail (book);
1005 :
1006 : /* delete any old transactions, if there are any */
1007 4 : delete_template_trans( sx );
1008 :
1009 16 : for (auto tti : tt_vec)
1010 : {
1011 4 : new_trans = xaccMallocTransaction(book);
1012 :
1013 4 : xaccTransBeginEdit(new_trans);
1014 4 : xaccTransSetDescription(new_trans, tti->get_description());
1015 4 : xaccTransSetDatePostedSecsNormalized(new_trans, gnc_time (NULL));
1016 : /* Set tran-num with gnc_set_num_action which is the same as
1017 : * xaccTransSetNum with these arguments */
1018 4 : gnc_set_num_action (new_trans, NULL, tti->get_num(), NULL);
1019 4 : xaccTransSetNotes (new_trans, tti->get_notes ());
1020 4 : xaccTransSetCurrency (new_trans, tti->get_currency ());
1021 :
1022 24 : for (auto s_info : tti->get_template_splits())
1023 : {
1024 8 : auto new_split = pack_split_info(s_info, sx->template_acct, new_trans, book);
1025 8 : xaccTransAppendSplit(new_trans, new_split);
1026 8 : }
1027 4 : xaccTransCommitEdit(new_trans);
1028 4 : }
1029 : }
1030 :
1031 : SXTmpStateData*
1032 21 : gnc_sx_create_temporal_state(const SchedXaction *sx )
1033 : {
1034 21 : auto toRet = g_new0 (SXTmpStateData, 1);
1035 21 : if (g_date_valid (&(sx->last_date)))
1036 0 : toRet->last_date = sx->last_date;
1037 : else
1038 21 : g_date_set_dmy (&toRet->last_date, 1, static_cast<GDateMonth>(1), 1970);
1039 21 : toRet->num_occur_rem = sx->num_occurances_remain;
1040 21 : toRet->num_inst = sx->instance_num;
1041 21 : return toRet;
1042 : }
1043 :
1044 : void
1045 37 : gnc_sx_incr_temporal_state(const SchedXaction *sx, SXTmpStateData *tsd )
1046 : {
1047 37 : g_return_if_fail(tsd != NULL);
1048 37 : tsd->last_date = xaccSchedXactionGetNextInstance (sx, tsd);
1049 37 : if (xaccSchedXactionHasOccurDef (sx))
1050 : {
1051 4 : --tsd->num_occur_rem;
1052 : }
1053 37 : ++tsd->num_inst;
1054 : }
1055 :
1056 : void
1057 37 : gnc_sx_destroy_temporal_state (SXTmpStateData *tsd)
1058 : {
1059 37 : g_free(tsd);
1060 37 : }
1061 :
1062 : SXTmpStateData*
1063 21 : gnc_sx_clone_temporal_state (SXTmpStateData *tsd)
1064 : {
1065 21 : SXTmpStateData *toRet = NULL;
1066 :
1067 21 : if(tsd)
1068 : {
1069 21 : toRet = g_new(SXTmpStateData, 1);
1070 21 : *toRet = *tsd;
1071 : }
1072 :
1073 21 : return toRet;
1074 : }
1075 :
1076 : static gint
1077 0 : _temporal_state_data_cmp( gconstpointer a, gconstpointer b )
1078 : {
1079 0 : const SXTmpStateData *tsd_a = (SXTmpStateData*)a;
1080 0 : const SXTmpStateData *tsd_b = (SXTmpStateData*)b;
1081 :
1082 0 : if ( !tsd_a && !tsd_b )
1083 0 : return 0;
1084 0 : if (tsd_a == tsd_b)
1085 0 : return 0;
1086 0 : if ( !tsd_a )
1087 0 : return 1;
1088 0 : if ( !tsd_b )
1089 0 : return -1;
1090 0 : return g_date_compare( &tsd_a->last_date,
1091 0 : &tsd_b->last_date );
1092 : }
1093 :
1094 : /**
1095 : * Adds an instance to the deferred list of the SX. Added instances are
1096 : * added in (date-)sorted order.
1097 : **/
1098 : void
1099 0 : gnc_sx_add_defer_instance( SchedXaction *sx, void *deferStateData )
1100 : {
1101 0 : sx->deferredList = g_list_insert_sorted( sx->deferredList,
1102 : deferStateData,
1103 : _temporal_state_data_cmp );
1104 0 : }
1105 :
1106 : /**
1107 : * Removes an instance from the deferred list. The saved SXTmpStateData existed
1108 : * for comparison only, so destroy it.
1109 : **/
1110 : void
1111 0 : gnc_sx_remove_defer_instance( SchedXaction *sx, void *deferStateData )
1112 : {
1113 : GList *found_by_value;
1114 :
1115 0 : found_by_value = g_list_find_custom(
1116 : sx->deferredList, deferStateData, _temporal_state_data_cmp);
1117 0 : if (found_by_value == NULL)
1118 : {
1119 0 : g_warning("unable to find deferred instance");
1120 0 : return;
1121 : }
1122 :
1123 0 : g_free (found_by_value->data);
1124 0 : sx->deferredList = g_list_delete_link(sx->deferredList, found_by_value);
1125 : }
1126 :
1127 : /**
1128 : * Returns the defer list from the SX; this is a (date-)sorted
1129 : * temporal-state-data instance list. The list should not be modified by the
1130 : * caller; use the gnc_sx_{add,remove}_defer_instance() functions to modify
1131 : * the list.
1132 : *
1133 : * @param sx Scheduled transaction
1134 : * @return Defer list which must not be modified by the caller
1135 : **/
1136 : GList*
1137 18 : gnc_sx_get_defer_instances( SchedXaction *sx )
1138 : {
1139 18 : return sx->deferredList;
1140 : }
1141 :
1142 : static void
1143 8 : destroy_sx_on_book_close(QofInstance *ent, gpointer data)
1144 : {
1145 8 : SchedXaction* sx = GNC_SCHEDXACTION(ent);
1146 :
1147 8 : gnc_sx_begin_edit(sx);
1148 8 : xaccSchedXactionDestroy(sx);
1149 8 : }
1150 :
1151 : /**
1152 : * Destroys all SXes in the book because the book is being destroyed.
1153 : *
1154 : * @param book Book being destroyed
1155 : */
1156 : static void
1157 179 : gnc_sx_book_end(QofBook* book)
1158 : {
1159 : QofCollection *col;
1160 :
1161 179 : col = qof_book_get_collection(book, GNC_ID_SCHEDXACTION);
1162 179 : qof_collection_foreach(col, destroy_sx_on_book_close, NULL);
1163 :
1164 : // Now destroy the template root account
1165 179 : gnc_book_set_template_root (book, NULL);
1166 179 : }
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
1175 : static 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 :
1190 : gboolean
1191 83 : SXRegister(void)
1192 : {
1193 : static QofParam params[] =
1194 : {
1195 : {
1196 : GNC_SX_NAME, QOF_TYPE_STRING, (QofAccessFunc)xaccSchedXactionGetName,
1197 : (QofSetterFunc)xaccSchedXactionSetName
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,
1209 : (QofSetterFunc)xaccSchedXactionSetNumOccur
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 83 : qof_class_register(GNC_SX_ID, NULL, params);
1220 83 : return qof_object_register(&SXDesc);
1221 : }
|