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