Branch data 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 : 452 : PeriodData (const char* note, std::optional<gnc_numeric> opt_value)
67 : 904 : : note (note)
68 : 452 : , 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 : 26384 : 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 : 48 : gnc_budget_get_name(const GncBudget* budget)
405 : : {
406 : 48 : g_return_val_if_fail(GNC_IS_BUDGET(budget), nullptr);
407 : 48 : 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 : 386 : gnc_budget_get_num_periods(const GncBudget* budget)
489 : : {
490 : 386 : g_return_val_if_fail(GNC_IS_BUDGET(budget), 0);
491 : 386 : return GET_PRIVATE(budget)->num_periods;
492 : : }
493 : :
494 : : static inline StringVec
495 : 951 : make_period_data_path (const Account *account, guint period_num)
496 : : {
497 : 951 : gnc::GUID acct_guid {*(xaccAccountGetGUID (account))};
498 : 3804 : return { acct_guid.to_string(), std::to_string (period_num) };
499 : 951 : }
500 : :
501 : : static inline StringVec
502 : 461 : make_period_note_path (const Account *account, guint period_num)
503 : : {
504 : 1383 : StringVec path { GNC_BUDGET_NOTES_PATH };
505 : 461 : StringVec data_path { make_period_data_path (account, period_num) };
506 : 461 : std::move (data_path.begin(), data_path.end(), std::back_inserter (path));
507 : 922 : return path;
508 : 1844 : }
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 : : /* Watch out for an off-by-one error here:
545 : : * period_num starts from 0 while num_periods starts from 1 */
546 : 39 : if (period_num >= GET_PRIVATE(budget)->num_periods)
547 : : {
548 : 1 : PWARN("Period %i does not exist", period_num);
549 : 1 : return;
550 : : }
551 : :
552 : 38 : g_return_if_fail (budget != nullptr);
553 : 38 : g_return_if_fail (account != nullptr);
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 : 5027 : gnc_budget_is_account_period_value_set (const GncBudget *budget,
580 : : const Account *account,
581 : : guint period_num)
582 : : {
583 : 5027 : g_return_val_if_fail (period_num < GET_PRIVATE(budget)->num_periods, false);
584 : 5027 : return get_perioddata (budget, account, period_num).opt_value.has_value();
585 : : }
586 : :
587 : : gnc_numeric
588 : 2240 : gnc_budget_get_account_period_value (const GncBudget *budget,
589 : : const Account *account,
590 : : guint period_num)
591 : : {
592 : 2240 : g_return_val_if_fail (period_num < GET_PRIVATE(budget)->num_periods,
593 : : gnc_numeric_zero());
594 : 2240 : auto& data = get_perioddata (budget, account, period_num);
595 : :
596 : 2240 : 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 : : /* Watch out for an off-by-one error here:
604 : : * period_num starts from 0 while num_periods starts from 1 */
605 : 9 : if (period_num >= GET_PRIVATE(budget)->num_periods)
606 : : {
607 : 0 : PWARN("Period %i does not exist", period_num);
608 : 0 : return;
609 : : }
610 : :
611 : 9 : g_return_if_fail (budget != nullptr);
612 : 9 : g_return_if_fail (account != nullptr);
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 : 356 : gnc_budget_get_account_period_note (const GncBudget *budget,
640 : : const Account *account, guint period_num)
641 : : {
642 : 356 : g_return_val_if_fail (period_num < GET_PRIVATE(budget)->num_periods, nullptr);
643 : 356 : auto& data = get_perioddata (budget, account, period_num);
644 : 356 : return data.note.empty () ? nullptr : data.note.c_str();
645 : : }
646 : :
647 : : time64
648 : 2078 : gnc_budget_get_period_start_date(const GncBudget *budget, guint period_num)
649 : : {
650 : 2078 : g_return_val_if_fail (GNC_IS_BUDGET(budget), 0);
651 : 2078 : return recurrenceGetPeriodTime(&GET_PRIVATE(budget)->recurrence, period_num, FALSE);
652 : : }
653 : :
654 : : time64
655 : 704 : gnc_budget_get_period_end_date(const GncBudget *budget, guint period_num)
656 : : {
657 : 704 : g_return_val_if_fail (GNC_IS_BUDGET(budget), 0);
658 : 704 : return recurrenceGetPeriodTime(&GET_PRIVATE(budget)->recurrence, period_num, TRUE);
659 : : }
660 : :
661 : : gnc_numeric
662 : 2119 : 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 : 2119 : g_return_val_if_fail(GNC_IS_BUDGET(budget) && acc, gnc_numeric_zero());
667 : 2119 : return recurrenceGetAccountPeriodValue(&GET_PRIVATE(budget)->recurrence,
668 : 2119 : acc, period_num);
669 : : }
670 : :
671 : : static PeriodData&
672 : 7670 : get_perioddata (const GncBudget *budget, const Account *account, guint period_num)
673 : : {
674 : 7670 : GncBudgetPrivate *priv = GET_PRIVATE (budget);
675 : :
676 : 7670 : if (period_num >= priv->num_periods)
677 : 0 : throw std::out_of_range("period_num >= num_periods");
678 : :
679 : 7670 : auto& vec = priv->acct_map[account];
680 : :
681 : 7670 : if (vec.empty())
682 : : {
683 : 59 : auto budget_kvp { QOF_INSTANCE (budget)->kvp_data };
684 : 59 : vec.reserve (priv->num_periods);
685 : :
686 : 511 : for (guint i = 0; i < priv->num_periods; i++)
687 : : {
688 : 452 : auto kval1 { budget_kvp->get_slot (make_period_data_path (account, i)) };
689 : 452 : auto kval2 { budget_kvp->get_slot (make_period_note_path (account, i)) };
690 : :
691 : 452 : auto is_set = kval1 && kval1->get_type() == KvpValue::Type::NUMERIC;
692 : 452 : auto num = is_set ? std::make_optional (kval1->get<gnc_numeric>()) : std::nullopt;
693 : 452 : auto note = (kval2 && kval2->get_type() == KvpValue::Type::STRING) ?
694 : 0 : kval2->get<const char*>() : "";
695 : :
696 : 452 : vec.emplace_back (note, num);
697 : : }
698 : : }
699 : :
700 : 7670 : 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 : 9 : static void just_get_one(QofInstance *ent, gpointer data)
715 : : {
716 : 9 : GncBudget **bgt = (GncBudget**)data;
717 : 9 : if (bgt && !*bgt) *bgt = GNC_BUDGET(ent);
718 : 9 : }
719 : :
720 : : GncBudget*
721 : 23 : gnc_budget_get_default (QofBook *book)
722 : : {
723 : : QofCollection *col;
724 : 23 : GncBudget *bgt = nullptr;
725 : 23 : GncGUID *default_budget_guid = nullptr;
726 : :
727 : 23 : g_return_val_if_fail(book, nullptr);
728 : :
729 : 23 : qof_instance_get (QOF_INSTANCE (book),
730 : : "default-budget", &default_budget_guid,
731 : : nullptr);
732 : 23 : 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 : 23 : if ( bgt == nullptr )
742 : : {
743 : 21 : col = qof_book_get_collection(book, GNC_ID_BUDGET);
744 : 21 : if (qof_collection_count(col) > 0)
745 : : {
746 : 9 : qof_collection_foreach(col, just_get_one, &bgt);
747 : : }
748 : : }
749 : :
750 : 23 : guid_free (default_budget_guid);
751 : 23 : 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 : 153 : gnc_budget_book_end(QofBook* book)
768 : : {
769 : : QofCollection *col;
770 : :
771 : 153 : col = qof_book_get_collection(book, GNC_ID_BUDGET);
772 : 153 : qof_collection_foreach(col, destroy_budget_on_book_close, nullptr);
773 : 153 : }
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 : 81 : 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 : 81 : qof_class_register(GNC_ID_BUDGET, (QofSortFunc) nullptr, params);
863 : 81 : return qof_object_register(&budget_object_def);
864 : : }
|