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