1 /********************************************************************\
2 * gnc-budget.c -- Implementation of the top level Budgeting API. *
3 * Copyright (C) 04 sep 2003 Darin Willits <darin@willits.ca> *
4 * Copyright (C) 2005-2006 Chris Shoemaker <c.shoemaker@cox.net> *
5 * *
6 * This program is free software; you can redistribute it and/or *
7 * modify it under the terms of the GNU General Public License as *
8 * published by the Free Software Foundation; either version 2 of *
9 * the License, or (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License*
17 * along with this program; if not, contact: *
18 * *
19 * Free Software Foundation Voice: +1-617-542-5942 *
20 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 * Boston, MA 02110-1301, USA gnu@gnu.org *
22 * *
23 \********************************************************************/
24
25 #include <config.h>
26 #include <glib.h>
27 #include <glib/gprintf.h>
28 #include <glib/gi18n.h>
29 #include <time.h>
30 #include <qof.h>
31 #include <qofbookslots.h>
32 #include <qofinstance-p.h>
33
34 #include "Account.h"
35
36 #include "gnc-budget.h"
37 #include "gnc-commodity.h"
38
39 static QofLogModule log_module = GNC_MOD_ENGINE;
40
41 enum
42 {
43 PROP_0,
44 PROP_NAME, /* Table */
45 PROP_DESCRIPTION, /* Table */
46 PROP_NUM_PERIODS, /* Table */
47 PROP_RUNTIME_0,
48 PROP_RECURRENCE, /* Cached pointer; Recurrence table holds budget guid */
49 };
50
51 struct budget_s
52 {
53 QofInstance inst;
54 };
55
56 typedef struct
57 {
58 QofInstanceClass parent_class;
59 } BudgetClass;
60
61 typedef struct GncBudgetPrivate
62 {
63 /* The name is an arbitrary string assigned by the user. */
64 const gchar *name;
65
66 /* The description is an arbitrary string assigned by the user. */
67 const gchar *description;
68
69 /* Recurrence (period info) for the budget */
70 Recurrence recurrence;
71
72 /* Number of periods */
73 guint num_periods;
74 } GncBudgetPrivate;
75
76 #define GET_PRIVATE(o) \
77 ((GncBudgetPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_BUDGET))
78
79 struct _GncBudgetClass
80 {
81 QofInstanceClass parent_class;
82 };
83
84 /* GObject Initialization */
G_DEFINE_TYPE_WITH_PRIVATE(GncBudget,gnc_budget,QOF_TYPE_INSTANCE)85 G_DEFINE_TYPE_WITH_PRIVATE(GncBudget, gnc_budget, QOF_TYPE_INSTANCE)
86
87 static void
88 gnc_budget_init(GncBudget* budget)
89 {
90 GncBudgetPrivate* priv;
91 GDate *date;
92
93 priv = GET_PRIVATE(budget);
94 priv->name = CACHE_INSERT(_("Unnamed Budget"));
95 priv->description = CACHE_INSERT("");
96
97 priv->num_periods = 12;
98 date = gnc_g_date_new_today ();
99 g_date_subtract_days(date, g_date_get_day(date) - 1);
100 recurrenceSet(&priv->recurrence, 1, PERIOD_MONTH, date, WEEKEND_ADJ_NONE);
101 g_date_free (date);
102 }
103
104 static void
gnc_budget_dispose(GObject * budgetp)105 gnc_budget_dispose (GObject *budgetp)
106 {
107 G_OBJECT_CLASS(gnc_budget_parent_class)->dispose(budgetp);
108 }
109
110 static void
gnc_budget_finalize(GObject * budgetp)111 gnc_budget_finalize(GObject* budgetp)
112 {
113 G_OBJECT_CLASS(gnc_budget_parent_class)->finalize(budgetp);
114 }
115
116 static void
gnc_budget_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)117 gnc_budget_get_property( GObject* object,
118 guint prop_id,
119 GValue* value,
120 GParamSpec* pspec)
121 {
122 GncBudget* budget;
123 GncBudgetPrivate* priv;
124
125 g_return_if_fail(GNC_IS_BUDGET(object));
126
127 budget = GNC_BUDGET(object);
128 priv = GET_PRIVATE(budget);
129 switch ( prop_id )
130 {
131 case PROP_NAME:
132 g_value_set_string(value, priv->name);
133 break;
134 case PROP_DESCRIPTION:
135 g_value_set_string(value, priv->description);
136 break;
137 case PROP_NUM_PERIODS:
138 g_value_set_uint(value, priv->num_periods);
139 break;
140 case PROP_RECURRENCE:
141 /* TODO: Make this a BOXED type */
142 g_value_set_pointer(value, &priv->recurrence);
143 break;
144 default:
145 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
146 break;
147 }
148 }
149
150 static void
gnc_budget_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)151 gnc_budget_set_property( GObject* object,
152 guint prop_id,
153 const GValue* value,
154 GParamSpec* pspec)
155 {
156 GncBudget* budget;
157
158 g_return_if_fail(GNC_IS_BUDGET(object));
159
160 budget = GNC_BUDGET(object);
161 if (prop_id < PROP_RUNTIME_0)
162 g_assert (qof_instance_get_editlevel(budget));
163
164 switch ( prop_id )
165 {
166 case PROP_NAME:
167 gnc_budget_set_name(budget, g_value_get_string(value));
168 break;
169 case PROP_DESCRIPTION:
170 gnc_budget_set_description(budget, g_value_get_string(value));
171 break;
172 case PROP_NUM_PERIODS:
173 gnc_budget_set_num_periods(budget, g_value_get_uint(value));
174 break;
175 case PROP_RECURRENCE:
176 gnc_budget_set_recurrence(budget, g_value_get_pointer(value));
177 break;
178 default:
179 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
180 break;
181 }
182 }
183
184 static void
gnc_budget_class_init(GncBudgetClass * klass)185 gnc_budget_class_init(GncBudgetClass* klass)
186 {
187 GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
188
189 gobject_class->dispose = gnc_budget_dispose;
190 gobject_class->finalize = gnc_budget_finalize;
191 gobject_class->get_property = gnc_budget_get_property;
192 gobject_class->set_property = gnc_budget_set_property;
193
194 g_object_class_install_property(
195 gobject_class,
196 PROP_NAME,
197 g_param_spec_string( "name",
198 "Budget Name",
199 "The name is an arbitrary string "
200 "assigned by the user. It is intended "
201 "to be a short, 5 to 30 character long string "
202 "that is displayed by the GUI as the "
203 "budget mnemonic",
204 NULL,
205 G_PARAM_READWRITE));
206
207 g_object_class_install_property(
208 gobject_class,
209 PROP_DESCRIPTION,
210 g_param_spec_string( "description",
211 "Budget Description",
212 "The description is an arbitrary string "
213 "assigned by the user. It is intended "
214 "to be a longer, 1-5 sentence description of "
215 "what the budget is all about.",
216 NULL,
217 G_PARAM_READWRITE));
218
219 g_object_class_install_property(
220 gobject_class,
221 PROP_NUM_PERIODS,
222 g_param_spec_uint( "num-periods",
223 "Number of Periods",
224 "The number of periods for this budget.",
225 0,
226 G_MAXUINT32,
227 12,
228 G_PARAM_READWRITE));
229
230 g_object_class_install_property(
231 gobject_class,
232 PROP_RECURRENCE,
233 g_param_spec_pointer( "recurrence",
234 "Budget Recurrence",
235 "about.",
236 G_PARAM_READWRITE));
237 }
238
commit_err(QofInstance * inst,QofBackendError errcode)239 static void commit_err (QofInstance *inst, QofBackendError errcode)
240 {
241 PERR ("Failed to commit: %d", errcode);
242 gnc_engine_signal_commit_error( errcode );
243 }
244
245 static void
gnc_budget_free(QofInstance * inst)246 gnc_budget_free(QofInstance *inst)
247 {
248 GncBudget *budget;
249 GncBudgetPrivate* priv;
250
251 if (inst == NULL)
252 return;
253 g_return_if_fail(GNC_IS_BUDGET(inst));
254
255 budget = GNC_BUDGET(inst);
256 priv = GET_PRIVATE(budget);
257
258 /* We first send the message that this object is about to be
259 * destroyed so that any GUI elements can remove it before it is
260 * actually gone. */
261 qof_event_gen( &budget->inst, QOF_EVENT_DESTROY, NULL);
262
263 CACHE_REMOVE(priv->name);
264 CACHE_REMOVE(priv->description);
265
266 /* qof_instance_release (&budget->inst); */
267 g_object_unref(budget);
268 }
269
noop(QofInstance * inst)270 static void noop (QofInstance *inst) {}
271
272 void
gnc_budget_begin_edit(GncBudget * bgt)273 gnc_budget_begin_edit(GncBudget *bgt)
274 {
275 qof_begin_edit(QOF_INSTANCE(bgt));
276 }
277
278 void
gnc_budget_commit_edit(GncBudget * bgt)279 gnc_budget_commit_edit(GncBudget *bgt)
280 {
281 if (!qof_commit_edit(QOF_INSTANCE(bgt))) return;
282 qof_commit_edit_part2(QOF_INSTANCE(bgt), commit_err,
283 noop, gnc_budget_free);
284 }
285
286 GncBudget*
gnc_budget_new(QofBook * book)287 gnc_budget_new(QofBook *book)
288 {
289 GncBudget* budget;
290
291 g_return_val_if_fail(book, NULL);
292
293 ENTER(" ");
294 budget = g_object_new(GNC_TYPE_BUDGET, NULL);
295 qof_instance_init_data (&budget->inst, GNC_ID_BUDGET, book);
296
297 qof_event_gen( &budget->inst, QOF_EVENT_CREATE , NULL);
298
299 LEAVE(" ");
300 return budget;
301 }
302
303 void
gnc_budget_destroy(GncBudget * budget)304 gnc_budget_destroy(GncBudget *budget)
305 {
306 g_return_if_fail(GNC_IS_BUDGET(budget));
307 gnc_budget_begin_edit(budget);
308 qof_instance_set_dirty(&budget->inst);
309 qof_instance_set_destroying(budget, TRUE);
310 gnc_budget_commit_edit(budget);
311 }
312
313 /** Data structure for containing info while cloning budget values */
314 typedef struct
315 {
316 const GncBudget* old_b;
317 GncBudget* new_b;
318 guint num_periods;
319 } CloneBudgetData_t;
320
321 static void
clone_budget_values_cb(Account * a,gpointer user_data)322 clone_budget_values_cb(Account* a, gpointer user_data)
323 {
324 CloneBudgetData_t* data = (CloneBudgetData_t*)user_data;
325 guint i;
326
327 for ( i = 0; i < data->num_periods; ++i )
328 {
329 if ( gnc_budget_is_account_period_value_set(data->old_b, a, i) )
330 {
331 gnc_budget_set_account_period_value(data->new_b, a, i,
332 gnc_budget_get_account_period_value(data->old_b, a, i));
333 }
334 }
335 }
336
337 GncBudget*
gnc_budget_clone(const GncBudget * old_b)338 gnc_budget_clone(const GncBudget* old_b)
339 {
340 GncBudget* new_b;
341 Account* root;
342 CloneBudgetData_t clone_data;
343
344 g_return_val_if_fail(old_b != NULL, NULL);
345
346 ENTER(" ");
347
348 new_b = gnc_budget_new(qof_instance_get_book(old_b));
349 gnc_budget_begin_edit(new_b);
350 gnc_budget_set_name(new_b, gnc_budget_get_name(old_b));
351 gnc_budget_set_description(new_b, gnc_budget_get_description(old_b));
352 gnc_budget_set_recurrence(new_b, gnc_budget_get_recurrence(old_b));
353 gnc_budget_set_num_periods(new_b, gnc_budget_get_num_periods(old_b));
354
355 root = gnc_book_get_root_account(qof_instance_get_book(old_b));
356 clone_data.old_b = old_b;
357 clone_data.new_b = new_b;
358 clone_data.num_periods = gnc_budget_get_num_periods(new_b);
359 gnc_account_foreach_descendant(root, clone_budget_values_cb, &clone_data);
360
361 gnc_budget_commit_edit(new_b);
362
363 LEAVE(" ");
364
365 return new_b;
366 }
367
368 void
gnc_budget_set_name(GncBudget * budget,const gchar * name)369 gnc_budget_set_name(GncBudget* budget, const gchar* name)
370 {
371 GncBudgetPrivate* priv;
372
373 g_return_if_fail(GNC_IS_BUDGET(budget) && name);
374
375 priv = GET_PRIVATE(budget);
376 if ( name == priv->name ) return;
377
378 gnc_budget_begin_edit(budget);
379 CACHE_REPLACE(priv->name, name);
380 qof_instance_set_dirty(&budget->inst);
381 gnc_budget_commit_edit(budget);
382
383 qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, NULL);
384 }
385
386 const gchar*
gnc_budget_get_name(const GncBudget * budget)387 gnc_budget_get_name(const GncBudget* budget)
388 {
389 g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
390 return GET_PRIVATE(budget)->name;
391 }
392
393 void
gnc_budget_set_description(GncBudget * budget,const gchar * description)394 gnc_budget_set_description(GncBudget* budget, const gchar* description)
395 {
396 GncBudgetPrivate* priv;
397
398 g_return_if_fail(GNC_IS_BUDGET(budget));
399 g_return_if_fail(description);
400
401 priv = GET_PRIVATE(budget);
402 if ( description == priv->description ) return;
403 gnc_budget_begin_edit(budget);
404 CACHE_REPLACE(priv->description, description);
405 qof_instance_set_dirty(&budget->inst);
406 gnc_budget_commit_edit(budget);
407
408 qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, NULL);
409 }
410
411 const gchar*
gnc_budget_get_description(const GncBudget * budget)412 gnc_budget_get_description(const GncBudget* budget)
413 {
414 g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
415 return GET_PRIVATE(budget)->description;
416 }
417
418 void
gnc_budget_set_recurrence(GncBudget * budget,const Recurrence * r)419 gnc_budget_set_recurrence(GncBudget *budget, const Recurrence *r)
420 {
421 GncBudgetPrivate* priv;
422
423 g_return_if_fail(budget && r);
424 priv = GET_PRIVATE(budget);
425
426 gnc_budget_begin_edit(budget);
427 priv->recurrence = *r;
428 qof_instance_set_dirty(&budget->inst);
429 gnc_budget_commit_edit(budget);
430
431 qof_event_gen(&budget->inst, QOF_EVENT_MODIFY, NULL);
432 }
433
434 const Recurrence *
gnc_budget_get_recurrence(const GncBudget * budget)435 gnc_budget_get_recurrence(const GncBudget *budget)
436 {
437 g_return_val_if_fail(budget, NULL);
438 return (&GET_PRIVATE(budget)->recurrence);
439 }
440
441 const GncGUID*
gnc_budget_get_guid(const GncBudget * budget)442 gnc_budget_get_guid(const GncBudget* budget)
443 {
444 g_return_val_if_fail(budget, NULL);
445 g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
446 return qof_instance_get_guid(QOF_INSTANCE(budget));
447 }
448
449 void
gnc_budget_set_num_periods(GncBudget * budget,guint num_periods)450 gnc_budget_set_num_periods(GncBudget* budget, guint num_periods)
451 {
452 GncBudgetPrivate* priv;
453
454 g_return_if_fail(GNC_IS_BUDGET(budget));
455
456 priv = GET_PRIVATE(budget);
457 if ( priv->num_periods == num_periods ) return;
458
459 gnc_budget_begin_edit(budget);
460 priv->num_periods = num_periods;
461 qof_instance_set_dirty(&budget->inst);
462 gnc_budget_commit_edit(budget);
463
464 qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, NULL);
465 }
466
467 guint
gnc_budget_get_num_periods(const GncBudget * budget)468 gnc_budget_get_num_periods(const GncBudget* budget)
469 {
470 g_return_val_if_fail(GNC_IS_BUDGET(budget), 0);
471 return GET_PRIVATE(budget)->num_periods;
472 }
473
474 static inline void
make_period_path(const Account * account,guint period_num,char * path1,char * path2)475 make_period_path (const Account *account, guint period_num, char *path1, char *path2)
476 {
477 const GncGUID *guid;
478 gchar *bufend;
479 guid = xaccAccountGetGUID (account);
480 guid_to_string_buff (guid, path1);
481 g_sprintf (path2, "%d", period_num);
482 }
483
484 /* period_num is zero-based */
485 /* What happens when account is deleted, after we have an entry for it? */
486 void
gnc_budget_unset_account_period_value(GncBudget * budget,const Account * account,guint period_num)487 gnc_budget_unset_account_period_value(GncBudget *budget, const Account *account,
488 guint period_num)
489 {
490 gchar path_part_one [GUID_ENCODING_LENGTH + 1];
491 gchar path_part_two [GNC_BUDGET_MAX_NUM_PERIODS_DIGITS];
492
493 g_return_if_fail (budget != NULL);
494 g_return_if_fail (account != NULL);
495 make_period_path (account, period_num, path_part_one, path_part_two);
496
497 gnc_budget_begin_edit(budget);
498 qof_instance_set_kvp (QOF_INSTANCE (budget), NULL, 2, path_part_one, path_part_two);
499 qof_instance_set_dirty(&budget->inst);
500 gnc_budget_commit_edit(budget);
501
502 qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, NULL);
503
504 }
505
506 /* period_num is zero-based */
507 /* What happens when account is deleted, after we have an entry for it? */
508 void
gnc_budget_set_account_period_value(GncBudget * budget,const Account * account,guint period_num,gnc_numeric val)509 gnc_budget_set_account_period_value(GncBudget *budget, const Account *account,
510 guint period_num, gnc_numeric val)
511 {
512 gchar path_part_one [GUID_ENCODING_LENGTH + 1];
513 gchar path_part_two [GNC_BUDGET_MAX_NUM_PERIODS_DIGITS];
514
515 /* Watch out for an off-by-one error here:
516 * period_num starts from 0 while num_periods starts from 1 */
517 if (period_num >= GET_PRIVATE(budget)->num_periods)
518 {
519 PWARN("Period %i does not exist", period_num);
520 return;
521 }
522
523 g_return_if_fail (budget != NULL);
524 g_return_if_fail (account != NULL);
525
526 make_period_path (account, period_num, path_part_one, path_part_two);
527
528 gnc_budget_begin_edit(budget);
529 if (gnc_numeric_check(val))
530 qof_instance_set_kvp (QOF_INSTANCE (budget), NULL, 2, path_part_one, path_part_two);
531 else
532 {
533 GValue v = G_VALUE_INIT;
534 g_value_init (&v, GNC_TYPE_NUMERIC);
535 g_value_set_boxed (&v, &val);
536 qof_instance_set_kvp (QOF_INSTANCE (budget), &v, 2, path_part_one, path_part_two);
537 g_value_unset (&v);
538 }
539 qof_instance_set_dirty(&budget->inst);
540 gnc_budget_commit_edit(budget);
541
542 qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, NULL);
543
544 }
545
546 /* We don't need these here, but maybe they're useful somewhere else?
547 Maybe this should move to Account.h */
548
549 gboolean
gnc_budget_is_account_period_value_set(const GncBudget * budget,const Account * account,guint period_num)550 gnc_budget_is_account_period_value_set(const GncBudget *budget,
551 const Account *account,
552 guint period_num)
553 {
554 GValue v = G_VALUE_INIT;
555 gchar path_part_one [GUID_ENCODING_LENGTH + 1];
556 gchar path_part_two [GNC_BUDGET_MAX_NUM_PERIODS_DIGITS];
557 gconstpointer ptr = NULL;
558
559 g_return_val_if_fail(GNC_IS_BUDGET(budget), FALSE);
560 g_return_val_if_fail(account, FALSE);
561
562 make_period_path (account, period_num, path_part_one, path_part_two);
563 qof_instance_get_kvp (QOF_INSTANCE (budget), &v, 2, path_part_one, path_part_two);
564 if (G_VALUE_HOLDS_BOXED (&v))
565 ptr = g_value_get_boxed (&v);
566 g_value_unset (&v);
567 return (ptr != NULL);
568 }
569
570 gnc_numeric
gnc_budget_get_account_period_value(const GncBudget * budget,const Account * account,guint period_num)571 gnc_budget_get_account_period_value(const GncBudget *budget,
572 const Account *account,
573 guint period_num)
574 {
575 gnc_numeric *numeric = NULL;
576 gnc_numeric retval;
577 gchar path_part_one [GUID_ENCODING_LENGTH + 1];
578 gchar path_part_two [GNC_BUDGET_MAX_NUM_PERIODS_DIGITS];
579 GValue v = G_VALUE_INIT;
580
581 g_return_val_if_fail(GNC_IS_BUDGET(budget), gnc_numeric_zero());
582 g_return_val_if_fail(account, gnc_numeric_zero());
583
584 make_period_path (account, period_num, path_part_one, path_part_two);
585 qof_instance_get_kvp (QOF_INSTANCE (budget), &v, 2, path_part_one, path_part_two);
586 if (G_VALUE_HOLDS_BOXED (&v))
587 numeric = (gnc_numeric*)g_value_get_boxed (&v);
588
589 retval = numeric ? *numeric : gnc_numeric_zero ();
590 g_value_unset (&v);
591 return retval;
592 }
593
594
595 void
gnc_budget_set_account_period_note(GncBudget * budget,const Account * account,guint period_num,const gchar * note)596 gnc_budget_set_account_period_note(GncBudget *budget, const Account *account,
597 guint period_num, const gchar *note)
598 {
599 gchar path_part_one [GUID_ENCODING_LENGTH + 1];
600 gchar path_part_two [GNC_BUDGET_MAX_NUM_PERIODS_DIGITS];
601
602 /* Watch out for an off-by-one error here:
603 * period_num starts from 0 while num_periods starts from 1 */
604 if (period_num >= GET_PRIVATE(budget)->num_periods)
605 {
606 PWARN("Period %i does not exist", period_num);
607 return;
608 }
609
610 g_return_if_fail (budget != NULL);
611 g_return_if_fail (account != NULL);
612
613 make_period_path (account, period_num, path_part_one, path_part_two);
614
615 gnc_budget_begin_edit(budget);
616 if (note == NULL)
617 qof_instance_set_kvp (QOF_INSTANCE (budget), NULL, 3, GNC_BUDGET_NOTES_PATH, path_part_one, path_part_two);
618 else
619 {
620 GValue v = G_VALUE_INIT;
621 g_value_init (&v, G_TYPE_STRING);
622 g_value_set_string (&v, note);
623
624 qof_instance_set_kvp (QOF_INSTANCE (budget), &v, 3, GNC_BUDGET_NOTES_PATH, path_part_one, path_part_two);
625 g_value_unset (&v);
626 }
627 qof_instance_set_dirty(&budget->inst);
628 gnc_budget_commit_edit(budget);
629
630 qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, NULL);
631
632 }
633
634 gchar *
gnc_budget_get_account_period_note(const GncBudget * budget,const Account * account,guint period_num)635 gnc_budget_get_account_period_note(const GncBudget *budget,
636 const Account *account, guint period_num)
637 {
638 gchar path_part_one [GUID_ENCODING_LENGTH + 1];
639 gchar path_part_two [GNC_BUDGET_MAX_NUM_PERIODS_DIGITS];
640 GValue v = G_VALUE_INIT;
641 gchar *retval;
642
643 g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
644 g_return_val_if_fail(account, NULL);
645
646 make_period_path (account, period_num, path_part_one, path_part_two);
647 qof_instance_get_kvp (QOF_INSTANCE (budget), &v, 3, GNC_BUDGET_NOTES_PATH, path_part_one, path_part_two);
648 retval = G_VALUE_HOLDS_STRING (&v) ? g_value_dup_string(&v) : NULL;
649 g_value_unset (&v);
650 return retval;
651 }
652
653 time64
gnc_budget_get_period_start_date(const GncBudget * budget,guint period_num)654 gnc_budget_get_period_start_date(const GncBudget *budget, guint period_num)
655 {
656 g_return_val_if_fail (GNC_IS_BUDGET(budget), 0);
657 return recurrenceGetPeriodTime(&GET_PRIVATE(budget)->recurrence, period_num, FALSE);
658 }
659
660 time64
gnc_budget_get_period_end_date(const GncBudget * budget,guint period_num)661 gnc_budget_get_period_end_date(const GncBudget *budget, guint period_num)
662 {
663 g_return_val_if_fail (GNC_IS_BUDGET(budget), 0);
664 return recurrenceGetPeriodTime(&GET_PRIVATE(budget)->recurrence, period_num, TRUE);
665 }
666
667 gnc_numeric
gnc_budget_get_account_period_actual_value(const GncBudget * budget,Account * acc,guint period_num)668 gnc_budget_get_account_period_actual_value(
669 const GncBudget *budget, Account *acc, guint period_num)
670 {
671 // FIXME: maybe zero is not best error return val.
672 g_return_val_if_fail(GNC_IS_BUDGET(budget) && acc, gnc_numeric_zero());
673 return recurrenceGetAccountPeriodValue(&GET_PRIVATE(budget)->recurrence,
674 acc, period_num);
675 }
676
677 GncBudget*
gnc_budget_lookup(const GncGUID * guid,const QofBook * book)678 gnc_budget_lookup (const GncGUID *guid, const QofBook *book)
679 {
680 QofCollection *col;
681
682 g_return_val_if_fail(guid, NULL);
683 g_return_val_if_fail(book, NULL);
684 col = qof_book_get_collection (book, GNC_ID_BUDGET);
685 return GNC_BUDGET(qof_collection_lookup_entity (col, guid));
686 }
687
just_get_one(QofInstance * ent,gpointer data)688 static void just_get_one(QofInstance *ent, gpointer data)
689 {
690 GncBudget **bgt = (GncBudget**)data;
691 if (bgt && !*bgt) *bgt = GNC_BUDGET(ent);
692 }
693
694 GncBudget*
gnc_budget_get_default(QofBook * book)695 gnc_budget_get_default (QofBook *book)
696 {
697 QofCollection *col;
698 GncBudget *bgt = NULL;
699 GncGUID *default_budget_guid = NULL;
700
701 g_return_val_if_fail(book, NULL);
702
703 qof_instance_get (QOF_INSTANCE (book),
704 "default-budget", &default_budget_guid,
705 NULL);
706 if (default_budget_guid)
707 {
708 col = qof_book_get_collection(book, GNC_ID_BUDGET);
709 bgt = (GncBudget *) qof_collection_lookup_entity(col,
710 default_budget_guid);
711 }
712
713 /* Revert to 2.2.x behavior if the book has no default budget. */
714
715 if ( bgt == NULL )
716 {
717 col = qof_book_get_collection(book, GNC_ID_BUDGET);
718 if (qof_collection_count(col) > 0)
719 {
720 qof_collection_foreach(col, just_get_one, &bgt);
721 }
722 }
723
724 guid_free (default_budget_guid);
725 return bgt;
726 }
727
728 static void
destroy_budget_on_book_close(QofInstance * ent,gpointer data)729 destroy_budget_on_book_close(QofInstance *ent, gpointer data)
730 {
731 GncBudget* bgt = GNC_BUDGET(ent);
732
733 gnc_budget_destroy(bgt);
734 }
735
736 /** Handles book end - frees all budgets from the book
737 *
738 * @param book Book being closed
739 */
740 static void
gnc_budget_book_end(QofBook * book)741 gnc_budget_book_end(QofBook* book)
742 {
743 QofCollection *col;
744
745 col = qof_book_get_collection(book, GNC_ID_BUDGET);
746 qof_collection_foreach(col, destroy_budget_on_book_close, NULL);
747 }
748
749 #ifdef _MSC_VER
750 /* MSVC compiler doesn't have C99 "designated initializers"
751 * so we wrap them in a macro that is empty on MSVC. */
752 # define DI(x) /* */
753 #else
754 # define DI(x) x
755 #endif
756
757 /* Define the QofObject. */
758 static QofObject budget_object_def =
759 {
760 DI(.interface_version = ) QOF_OBJECT_VERSION,
761 DI(.e_type = ) GNC_ID_BUDGET,
762 DI(.type_label = ) "Budget",
763 DI(.create = ) (gpointer)gnc_budget_new,
764 DI(.book_begin = ) NULL,
765 DI(.book_end = ) gnc_budget_book_end,
766 DI(.is_dirty = ) qof_collection_is_dirty,
767 DI(.mark_clean = ) qof_collection_mark_clean,
768 DI(.foreach = ) qof_collection_foreach,
769 DI(.printable = ) (const char * (*)(gpointer)) gnc_budget_get_name,
770 DI(.version_cmp = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
771 };
772
773
774 /* Static wrapper getters for the recurrence params */
gnc_budget_get_rec_pt(const GncBudget * bgt)775 static PeriodType gnc_budget_get_rec_pt(const GncBudget *bgt)
776 {
777 return recurrenceGetPeriodType(&(GET_PRIVATE(bgt)->recurrence));
778 }
gnc_budget_get_rec_mult(const GncBudget * bgt)779 static guint gnc_budget_get_rec_mult(const GncBudget *bgt)
780 {
781 return recurrenceGetMultiplier(&(GET_PRIVATE(bgt)->recurrence));
782 }
gnc_budget_get_rec_time(const GncBudget * bgt)783 static time64 gnc_budget_get_rec_time(const GncBudget *bgt)
784 {
785 return recurrenceGetTime(&(GET_PRIVATE(bgt)->recurrence));
786 }
787
788 /* Register ourselves with the engine. */
gnc_budget_register(void)789 gboolean gnc_budget_register (void)
790 {
791 static QofParam params[] =
792 {
793 {
794 "name", QOF_TYPE_STRING,
795 (QofAccessFunc) gnc_budget_get_name,
796 (QofSetterFunc) gnc_budget_set_name
797 },
798 {
799 "description", QOF_TYPE_STRING,
800 (QofAccessFunc) gnc_budget_get_description,
801 (QofSetterFunc) gnc_budget_set_description
802 },
803 {
804 "recurrence_period_type", QOF_TYPE_INT32,
805 (QofAccessFunc) gnc_budget_get_rec_pt, NULL
806 },
807 /* Signedness problem: Should be unsigned. */
808 {
809 "recurrence_multiplier", QOF_TYPE_INT32,
810 (QofAccessFunc) gnc_budget_get_rec_mult, NULL
811 },
812 /* This is the same way that SchedXaction.c uses QOF_TYPE_DATE
813 but I don't think QOF actually supports a GDate, so I think
814 this is wrong. */
815 {
816 "recurrence_date", QOF_TYPE_DATE,
817 (QofAccessFunc) gnc_budget_get_rec_time, NULL
818 },
819 /* Signedness problem: Should be unsigned. */
820 {
821 "num_periods", QOF_TYPE_INT32,
822 (QofAccessFunc) gnc_budget_get_num_periods,
823 (QofSetterFunc) gnc_budget_set_num_periods
824 },
825 {
826 QOF_PARAM_BOOK, QOF_ID_BOOK,
827 (QofAccessFunc) qof_instance_get_book, NULL
828 },
829 {
830 QOF_PARAM_GUID, QOF_TYPE_GUID,
831 (QofAccessFunc) qof_instance_get_guid, NULL
832 },
833 { NULL },
834 };
835
836 qof_class_register(GNC_ID_BUDGET, (QofSortFunc) NULL, params);
837 return qof_object_register(&budget_object_def);
838 }
839