GnuCash  5.6-150-g038405b370+
gnc-budget.cpp
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  PeriodData () = default;
66  PeriodData (const char* note, std::optional<gnc_numeric> opt_value)
67  : note (note)
68  , 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;
91 
92 #define GET_PRIVATE(o) \
93  ((GncBudgetPrivate*)gnc_budget_get_instance_private((GncBudget*)o))
94 
96 {
97  QofInstanceClass parent_class;
98 };
99 
100 /* GObject Initialization */
101 G_DEFINE_TYPE_WITH_PRIVATE(GncBudget, gnc_budget, QOF_TYPE_INSTANCE)
102 
103 static void
104 gnc_budget_init(GncBudget* budget)
105 {
106  GncBudgetPrivate* priv;
107  GDate *date;
108 
109  priv = GET_PRIVATE(budget);
110  priv->name = CACHE_INSERT(_("Unnamed Budget"));
111  priv->description = CACHE_INSERT("");
112  new (&priv->acct_map) AcctMap ();
113 
114  priv->num_periods = 12;
115  date = gnc_g_date_new_today ();
116  g_date_subtract_days(date, g_date_get_day(date) - 1);
117  recurrenceSet(&priv->recurrence, 1, PERIOD_MONTH, date, WEEKEND_ADJ_NONE);
118  g_date_free (date);
119 }
120 
121 static void
122 gnc_budget_dispose (GObject *budgetp)
123 {
124  G_OBJECT_CLASS(gnc_budget_parent_class)->dispose(budgetp);
125 }
126 
127 static void
128 gnc_budget_finalize(GObject* budgetp)
129 {
130  G_OBJECT_CLASS(gnc_budget_parent_class)->finalize(budgetp);
131 }
132 
133 static void
134 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  g_return_if_fail(GNC_IS_BUDGET(object));
143 
144  budget = GNC_BUDGET(object);
145  priv = GET_PRIVATE(budget);
146  switch ( prop_id )
147  {
148  case PROP_NAME:
149  g_value_set_string(value, priv->name);
150  break;
151  case PROP_DESCRIPTION:
152  g_value_set_string(value, priv->description);
153  break;
154  case PROP_NUM_PERIODS:
155  g_value_set_uint(value, priv->num_periods);
156  break;
157  case PROP_RECURRENCE:
158  /* TODO: Make this a BOXED type */
159  g_value_set_pointer(value, &priv->recurrence);
160  break;
161  default:
162  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
163  break;
164  }
165 }
166 
167 static void
168 gnc_budget_set_property( GObject* object,
169  guint prop_id,
170  const GValue* value,
171  GParamSpec* pspec)
172 {
173  GncBudget* budget;
174 
175  g_return_if_fail(GNC_IS_BUDGET(object));
176 
177  budget = GNC_BUDGET(object);
178  if (prop_id < PROP_RUNTIME_0)
179  g_assert (qof_instance_get_editlevel(budget));
180 
181  switch ( prop_id )
182  {
183  case PROP_NAME:
184  gnc_budget_set_name(budget, g_value_get_string(value));
185  break;
186  case PROP_DESCRIPTION:
187  gnc_budget_set_description(budget, g_value_get_string(value));
188  break;
189  case PROP_NUM_PERIODS:
190  gnc_budget_set_num_periods(budget, g_value_get_uint(value));
191  break;
192  case PROP_RECURRENCE:
193  gnc_budget_set_recurrence (budget, static_cast<Recurrence*>(g_value_get_pointer(value)));
194  break;
195  default:
196  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
197  break;
198  }
199 }
200 
201 static void
202 gnc_budget_class_init(GncBudgetClass* klass)
203 {
204  GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
205 
206  gobject_class->dispose = gnc_budget_dispose;
207  gobject_class->finalize = gnc_budget_finalize;
208  gobject_class->get_property = gnc_budget_get_property;
209  gobject_class->set_property = gnc_budget_set_property;
210 
211  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  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  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  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 }
255 
256 static void commit_err (QofInstance *inst, QofBackendError errcode)
257 {
258  PERR ("Failed to commit: %d", errcode);
259  gnc_engine_signal_commit_error( errcode );
260 }
261 
262 static void
263 gnc_budget_free(QofInstance *inst)
264 {
265  GncBudget *budget;
266  GncBudgetPrivate* priv;
267 
268  if (inst == nullptr)
269  return;
270  g_return_if_fail(GNC_IS_BUDGET(inst));
271 
272  budget = GNC_BUDGET(inst);
273  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  qof_event_gen( &budget->inst, QOF_EVENT_DESTROY, nullptr);
279 
280  CACHE_REMOVE(priv->name);
281  CACHE_REMOVE(priv->description);
282  priv->acct_map.~AcctMap();
283 
284  /* qof_instance_release (&budget->inst); */
285  g_object_unref(budget);
286 }
287 
288 static void noop (QofInstance *inst) {}
289 
290 void
291 gnc_budget_begin_edit(GncBudget *bgt)
292 {
293  qof_begin_edit(QOF_INSTANCE(bgt));
294 }
295 
296 void
297 gnc_budget_commit_edit(GncBudget *bgt)
298 {
299  if (!qof_commit_edit(QOF_INSTANCE(bgt))) return;
300  qof_commit_edit_part2(QOF_INSTANCE(bgt), commit_err,
301  noop, gnc_budget_free);
302 }
303 
304 GncBudget*
305 gnc_budget_new(QofBook *book)
306 {
307  g_return_val_if_fail(book, nullptr);
308 
309  ENTER(" ");
310 
311  auto budget { static_cast<GncBudget*>(g_object_new(GNC_TYPE_BUDGET, nullptr)) };
312  qof_instance_init_data (&budget->inst, GNC_ID_BUDGET, book);
313 
314  qof_event_gen( &budget->inst, QOF_EVENT_CREATE , nullptr);
315 
316  LEAVE(" ");
317  return budget;
318 }
319 
320 void
321 gnc_budget_destroy(GncBudget *budget)
322 {
323  g_return_if_fail(GNC_IS_BUDGET(budget));
324  gnc_budget_begin_edit(budget);
325  qof_instance_set_dirty(&budget->inst);
326  qof_instance_set_destroying(budget, TRUE);
327  gnc_budget_commit_edit(budget);
328 }
329 
331 typedef struct
332 {
333  const GncBudget* old_b;
334  GncBudget* new_b;
335  guint num_periods;
337 
338 static void
339 clone_budget_values_cb(Account* a, gpointer user_data)
340 {
341  CloneBudgetData_t* data = (CloneBudgetData_t*)user_data;
342  guint i;
343 
344  for ( i = 0; i < data->num_periods; ++i )
345  {
346  if ( gnc_budget_is_account_period_value_set(data->old_b, a, i) )
347  {
348  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 }
353 
354 GncBudget*
355 gnc_budget_clone(const GncBudget* old_b)
356 {
357  GncBudget* new_b;
358  Account* root;
359  CloneBudgetData_t clone_data;
360 
361  g_return_val_if_fail(old_b != nullptr, nullptr);
362 
363  ENTER(" ");
364 
365  new_b = gnc_budget_new(qof_instance_get_book(old_b));
366  gnc_budget_begin_edit(new_b);
367  gnc_budget_set_name(new_b, gnc_budget_get_name(old_b));
368  gnc_budget_set_description(new_b, gnc_budget_get_description(old_b));
369  gnc_budget_set_recurrence(new_b, gnc_budget_get_recurrence(old_b));
370  gnc_budget_set_num_periods(new_b, gnc_budget_get_num_periods(old_b));
371 
372  root = gnc_book_get_root_account(qof_instance_get_book(old_b));
373  clone_data.old_b = old_b;
374  clone_data.new_b = new_b;
375  clone_data.num_periods = gnc_budget_get_num_periods(new_b);
376  gnc_account_foreach_descendant(root, clone_budget_values_cb, &clone_data);
377 
378  gnc_budget_commit_edit(new_b);
379 
380  LEAVE(" ");
381 
382  return new_b;
383 }
384 
385 void
386 gnc_budget_set_name(GncBudget* budget, const gchar* name)
387 {
388  GncBudgetPrivate* priv;
389 
390  g_return_if_fail(GNC_IS_BUDGET(budget) && name);
391 
392  priv = GET_PRIVATE(budget);
393  if ( name == priv->name ) return;
394 
395  gnc_budget_begin_edit(budget);
396  CACHE_REPLACE(priv->name, name);
397  qof_instance_set_dirty(&budget->inst);
398  gnc_budget_commit_edit(budget);
399 
400  qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, nullptr);
401 }
402 
403 const gchar*
404 gnc_budget_get_name(const GncBudget* budget)
405 {
406  g_return_val_if_fail(GNC_IS_BUDGET(budget), nullptr);
407  return GET_PRIVATE(budget)->name;
408 }
409 
410 void
411 gnc_budget_set_description(GncBudget* budget, const gchar* description)
412 {
413  GncBudgetPrivate* priv;
414 
415  g_return_if_fail(GNC_IS_BUDGET(budget));
416  g_return_if_fail(description);
417 
418  priv = GET_PRIVATE(budget);
419  if ( description == priv->description ) return;
420  gnc_budget_begin_edit(budget);
421  CACHE_REPLACE(priv->description, description);
422  qof_instance_set_dirty(&budget->inst);
423  gnc_budget_commit_edit(budget);
424 
425  qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, nullptr);
426 }
427 
428 const gchar*
429 gnc_budget_get_description(const GncBudget* budget)
430 {
431  g_return_val_if_fail(GNC_IS_BUDGET(budget), nullptr);
432  return GET_PRIVATE(budget)->description;
433 }
434 
435 void
436 gnc_budget_set_recurrence(GncBudget *budget, const Recurrence *r)
437 {
438  GncBudgetPrivate* priv;
439 
440  g_return_if_fail(budget && r);
441  priv = GET_PRIVATE(budget);
442 
443  gnc_budget_begin_edit(budget);
444  priv->recurrence = *r;
445  qof_instance_set_dirty(&budget->inst);
446  gnc_budget_commit_edit(budget);
447 
448  qof_event_gen(&budget->inst, QOF_EVENT_MODIFY, nullptr);
449 }
450 
451 const Recurrence *
452 gnc_budget_get_recurrence(const GncBudget *budget)
453 {
454  g_return_val_if_fail(budget, nullptr);
455  return (&GET_PRIVATE(budget)->recurrence);
456 }
457 
458 const GncGUID*
459 gnc_budget_get_guid(const GncBudget* budget)
460 {
461  g_return_val_if_fail(budget, nullptr);
462  g_return_val_if_fail(GNC_IS_BUDGET(budget), nullptr);
463  return qof_instance_get_guid(QOF_INSTANCE(budget));
464 }
465 
466 void
467 gnc_budget_set_num_periods(GncBudget* budget, guint num_periods)
468 {
469  GncBudgetPrivate* priv;
470 
471  g_return_if_fail(GNC_IS_BUDGET(budget));
472  g_return_if_fail(num_periods > 0);
473 
474  priv = GET_PRIVATE(budget);
475  if ( priv->num_periods == num_periods ) return;
476 
477  gnc_budget_begin_edit(budget);
478  priv->num_periods = num_periods;
479  std::for_each (priv->acct_map.begin(), priv->acct_map.end(),
480  [num_periods](auto& it){ it.second.resize(num_periods); });
481  qof_instance_set_dirty(&budget->inst);
482  gnc_budget_commit_edit(budget);
483 
484  qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, nullptr);
485 }
486 
487 guint
488 gnc_budget_get_num_periods(const GncBudget* budget)
489 {
490  g_return_val_if_fail(GNC_IS_BUDGET(budget), 0);
491  return GET_PRIVATE(budget)->num_periods;
492 }
493 
494 static inline StringVec
495 make_period_data_path (const Account *account, guint period_num)
496 {
497  gnc::GUID acct_guid {*(xaccAccountGetGUID (account))};
498  return { acct_guid.to_string(), std::to_string (period_num) };
499 }
500 
501 static inline StringVec
502 make_period_note_path (const Account *account, guint period_num)
503 {
504  StringVec path { GNC_BUDGET_NOTES_PATH };
505  StringVec data_path { make_period_data_path (account, period_num) };
506  std::move (data_path.begin(), data_path.end(), std::back_inserter (path));
507  return path;
508 }
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 gnc_budget_unset_account_period_value(GncBudget *budget, const Account *account,
518  guint period_num)
519 {
520  g_return_if_fail (budget != nullptr);
521  g_return_if_fail (account != nullptr);
522  g_return_if_fail (period_num < GET_PRIVATE(budget)->num_periods);
523 
524  auto& data = get_perioddata (budget, account, period_num);
525  data.opt_value.reset();
526 
527  gnc_budget_begin_edit(budget);
528  auto path = make_period_data_path (account, period_num);
529  auto budget_kvp { QOF_INSTANCE (budget)->kvp_data };
530  delete budget_kvp->set_path (path, nullptr);
531  qof_instance_set_dirty(&budget->inst);
532  gnc_budget_commit_edit(budget);
533 
534  qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, nullptr);
535 
536 }
537 
538 /* period_num is zero-based */
539 /* What happens when account is deleted, after we have an entry for it? */
540 void
541 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  if (period_num >= GET_PRIVATE(budget)->num_periods)
547  {
548  PWARN("Period %i does not exist", period_num);
549  return;
550  }
551 
552  g_return_if_fail (budget != nullptr);
553  g_return_if_fail (account != nullptr);
554 
555  auto& perioddata = get_perioddata (budget, account, period_num);
556  auto budget_kvp { QOF_INSTANCE (budget)->kvp_data };
557  auto path = make_period_data_path (account, period_num);
558 
559  gnc_budget_begin_edit(budget);
560  if (gnc_numeric_check(val))
561  {
562  delete budget_kvp->set_path (path, nullptr);
563  perioddata.opt_value.reset();
564  }
565  else
566  {
567  KvpValue* v = new KvpValue (val);
568  delete budget_kvp->set_path (path, v);
569  perioddata.opt_value = val;
570  }
571  qof_instance_set_dirty(&budget->inst);
572  gnc_budget_commit_edit(budget);
573 
574  qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, nullptr);
575 
576 }
577 
578 gboolean
579 gnc_budget_is_account_period_value_set (const GncBudget *budget,
580  const Account *account,
581  guint period_num)
582 {
583  g_return_val_if_fail (period_num < GET_PRIVATE(budget)->num_periods, false);
584  return get_perioddata (budget, account, period_num).opt_value.has_value();
585 }
586 
587 gnc_numeric
588 gnc_budget_get_account_period_value (const GncBudget *budget,
589  const Account *account,
590  guint period_num)
591 {
592  g_return_val_if_fail (period_num < GET_PRIVATE(budget)->num_periods,
593  gnc_numeric_zero());
594  auto& data = get_perioddata (budget, account, period_num);
595 
596  return data.opt_value.has_value() ? data.opt_value.value() : gnc_numeric_zero();
597 }
598 
599 void
600 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  if (period_num >= GET_PRIVATE(budget)->num_periods)
606  {
607  PWARN("Period %i does not exist", period_num);
608  return;
609  }
610 
611  g_return_if_fail (budget != nullptr);
612  g_return_if_fail (account != nullptr);
613 
614  auto& perioddata = get_perioddata (budget, account, period_num);
615  auto budget_kvp { QOF_INSTANCE (budget)->kvp_data };
616  auto path = make_period_note_path (account, period_num);
617 
618  gnc_budget_begin_edit(budget);
619  if (note == nullptr)
620  {
621  delete budget_kvp->set_path (path, nullptr);
622  perioddata.note.clear ();
623  }
624  else
625  {
626  KvpValue* v = new KvpValue (g_strdup (note));
627 
628  delete budget_kvp->set_path (path, v);
629  perioddata.note = note;
630  }
631  qof_instance_set_dirty(&budget->inst);
632  gnc_budget_commit_edit(budget);
633 
634  qof_event_gen( &budget->inst, QOF_EVENT_MODIFY, nullptr);
635 
636 }
637 
638 const gchar *
639 gnc_budget_get_account_period_note (const GncBudget *budget,
640  const Account *account, guint period_num)
641 {
642  g_return_val_if_fail (period_num < GET_PRIVATE(budget)->num_periods, nullptr);
643  auto& data = get_perioddata (budget, account, period_num);
644  return data.note.empty () ? nullptr : data.note.c_str();
645 }
646 
647 time64
648 gnc_budget_get_period_start_date(const GncBudget *budget, guint period_num)
649 {
650  g_return_val_if_fail (GNC_IS_BUDGET(budget), 0);
651  return recurrenceGetPeriodTime(&GET_PRIVATE(budget)->recurrence, period_num, FALSE);
652 }
653 
654 time64
655 gnc_budget_get_period_end_date(const GncBudget *budget, guint period_num)
656 {
657  g_return_val_if_fail (GNC_IS_BUDGET(budget), 0);
658  return recurrenceGetPeriodTime(&GET_PRIVATE(budget)->recurrence, period_num, TRUE);
659 }
660 
661 gnc_numeric
662 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  g_return_val_if_fail(GNC_IS_BUDGET(budget) && acc, gnc_numeric_zero());
667  return recurrenceGetAccountPeriodValue(&GET_PRIVATE(budget)->recurrence,
668  acc, period_num);
669 }
670 
671 static PeriodData&
672 get_perioddata (const GncBudget *budget, const Account *account, guint period_num)
673 {
674  GncBudgetPrivate *priv = GET_PRIVATE (budget);
675 
676  if (period_num >= priv->num_periods)
677  throw std::out_of_range("period_num >= num_periods");
678 
679  auto& vec = priv->acct_map[account];
680 
681  if (vec.empty())
682  {
683  auto budget_kvp { QOF_INSTANCE (budget)->kvp_data };
684  vec.reserve (priv->num_periods);
685 
686  for (guint i = 0; i < priv->num_periods; i++)
687  {
688  auto kval1 { budget_kvp->get_slot (make_period_data_path (account, i)) };
689  auto kval2 { budget_kvp->get_slot (make_period_note_path (account, i)) };
690 
691  auto is_set = kval1 && kval1->get_type() == KvpValue::Type::NUMERIC;
692  auto num = is_set ? std::make_optional (kval1->get<gnc_numeric>()) : std::nullopt;
693  auto note = (kval2 && kval2->get_type() == KvpValue::Type::STRING) ?
694  kval2->get<const char*>() : "";
695 
696  vec.emplace_back (note, num);
697  }
698  }
699 
700  return vec.at(period_num);
701 }
702 
703 GncBudget*
704 gnc_budget_lookup (const GncGUID *guid, const QofBook *book)
705 {
706  QofCollection *col;
707 
708  g_return_val_if_fail(guid, nullptr);
709  g_return_val_if_fail(book, nullptr);
710  col = qof_book_get_collection (book, GNC_ID_BUDGET);
711  return GNC_BUDGET(qof_collection_lookup_entity (col, guid));
712 }
713 
714 static void just_get_one(QofInstance *ent, gpointer data)
715 {
716  GncBudget **bgt = (GncBudget**)data;
717  if (bgt && !*bgt) *bgt = GNC_BUDGET(ent);
718 }
719 
720 GncBudget*
721 gnc_budget_get_default (QofBook *book)
722 {
723  QofCollection *col;
724  GncBudget *bgt = nullptr;
725  GncGUID *default_budget_guid = nullptr;
726 
727  g_return_val_if_fail(book, nullptr);
728 
729  qof_instance_get (QOF_INSTANCE (book),
730  "default-budget", &default_budget_guid,
731  nullptr);
732  if (default_budget_guid)
733  {
734  col = qof_book_get_collection(book, GNC_ID_BUDGET);
735  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  if ( bgt == nullptr )
742  {
743  col = qof_book_get_collection(book, GNC_ID_BUDGET);
744  if (qof_collection_count(col) > 0)
745  {
746  qof_collection_foreach(col, just_get_one, &bgt);
747  }
748  }
749 
750  guid_free (default_budget_guid);
751  return bgt;
752 }
753 
754 static void
755 destroy_budget_on_book_close(QofInstance *ent, gpointer data)
756 {
757  GncBudget* bgt = GNC_BUDGET(ent);
758 
759  gnc_budget_destroy(bgt);
760 }
761 
766 static void
767 gnc_budget_book_end(QofBook* book)
768 {
769  QofCollection *col;
770 
771  col = qof_book_get_collection(book, GNC_ID_BUDGET);
772  qof_collection_foreach(col, destroy_budget_on_book_close, nullptr);
773 }
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 static PeriodType gnc_budget_get_rec_pt(const GncBudget *bgt)
802 {
803  return recurrenceGetPeriodType(&(GET_PRIVATE(bgt)->recurrence));
804 }
805 static guint gnc_budget_get_rec_mult(const GncBudget *bgt)
806 {
807  return recurrenceGetMultiplier(&(GET_PRIVATE(bgt)->recurrence));
808 }
809 static time64 gnc_budget_get_rec_time(const GncBudget *bgt)
810 {
811  return recurrenceGetTime(&(GET_PRIVATE(bgt)->recurrence));
812 }
813 
814 /* Register ourselves with the engine. */
815 gboolean gnc_budget_register (void)
816 {
817  static QofParam params[] =
818  {
819  {
820  "name", QOF_TYPE_STRING,
821  (QofAccessFunc) gnc_budget_get_name,
823  },
824  {
825  "description", QOF_TYPE_STRING,
826  (QofAccessFunc) gnc_budget_get_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,
850  },
851  {
852  QOF_PARAM_BOOK, QOF_ID_BOOK,
854  },
855  {
856  QOF_PARAM_GUID, QOF_TYPE_GUID,
858  },
859  { nullptr },
860  };
861 
862  qof_class_register(GNC_ID_BUDGET, (QofSortFunc) nullptr, params);
863  return qof_object_register(&budget_object_def);
864 }
void gnc_budget_set_num_periods(GncBudget *budget, guint num_periods)
Set/Get the number of periods in the Budget.
Definition: gnc-budget.cpp:467
int qof_instance_version_cmp(const QofInstance *left, const QofInstance *right)
Compare two instances, based on their last update times.
void gnc_budget_destroy(GncBudget *budget)
Deletes the given budget object.
Definition: gnc-budget.cpp:321
const GncGUID * qof_instance_get_guid(gconstpointer inst)
Return the GncGUID of this instance.
void qof_instance_get(const QofInstance *inst, const gchar *first_prop,...)
Wrapper for g_object_get.
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
gboolean qof_collection_is_dirty(const QofCollection *col)
Return value of &#39;dirty&#39; flag on collection.
Definition: qofid.cpp:255
QofInstance * qof_collection_lookup_entity(const QofCollection *col, const GncGUID *guid)
Find the entity going only from its guid.
Definition: qofid.cpp:212
QofBackendError
The errors that can be reported to the GUI & other front-end users.
Definition: qofbackend.h:57
STRUCTS.
GnuCash Budgets.
GncBudget * gnc_budget_new(QofBook *book)
Creates and initializes a Budget.
Definition: gnc-budget.cpp:305
void qof_class_register(QofIdTypeConst obj_name, QofSortFunc default_sort_function, const QofParam *params)
This function registers a new object class with the Qof subsystem.
Definition: qofclass.cpp:86
STL namespace.
int(* QofSortFunc)(gconstpointer, gconstpointer)
This function is the default sort function for a particular object type.
Definition: qofclass.h:223
#define QOF_OBJECT_VERSION
Defines the version of the core object object registration interface.
Definition: qofobject.h:63
gboolean qof_commit_edit(QofInstance *inst)
commit_edit helpers
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
#define QOF_PARAM_BOOK
"Known" Object Parameters – all objects must support these
Definition: qofquery.h:108
Data structure for containing info while cloning budget values.
Definition: gnc-budget.cpp:331
void(* QofSetterFunc)(gpointer, gpointer)
The QofSetterFunc defines an function pointer for parameter setters.
Definition: qofclass.h:185
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
void qof_instance_init_data(QofInstance *inst, QofIdType type, QofBook *book)
Initialise the settings associated with an instance.
gboolean qof_begin_edit(QofInstance *inst)
begin_edit
#define xaccAccountGetGUID(X)
Definition: Account.h:248
Account handling public routines.
The budget data.
Definition: gnc-budget.cpp:51
time64 gnc_budget_get_period_start_date(const GncBudget *budget, guint period_num)
Get the starting date of the Budget period.
Definition: gnc-budget.cpp:648
GncBudget * gnc_budget_clone(const GncBudget *old_b)
Clones a budget creating a copy.
Definition: gnc-budget.cpp:355
gboolean qof_commit_edit_part2(QofInstance *inst, void(*on_error)(QofInstance *, QofBackendError), void(*on_done)(QofInstance *), void(*on_free)(QofInstance *))
part2 – deal with the backend
gpointer(* QofAccessFunc)(gpointer object, const QofParam *param)
The QofAccessFunc defines an arbitrary function pointer for access functions.
Definition: qofclass.h:178
void qof_collection_mark_clean(QofCollection *)
reset value of dirty flag
Definition: qofid.cpp:261
void gnc_budget_set_name(GncBudget *budget, const gchar *name)
Set/Get the name of the Budget.
Definition: gnc-budget.cpp:386
void gnc_budget_set_description(GncBudget *budget, const gchar *description)
Set/Get the description of the Budget.
Definition: gnc-budget.cpp:411
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
GNCNumericErrorCode gnc_numeric_check(gnc_numeric in)
Check for error signal in value.
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition: qofbook.cpp:521
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
guint qof_collection_count(const QofCollection *col)
return the number of entities in the collection.
Definition: qofid.cpp:244
gboolean qof_object_register(const QofObject *object)
Register new types of object objects.
Definition: qofobject.cpp:299
time64 gnc_budget_get_period_end_date(const GncBudget *budget, guint period_num)
Get the ending date of the Budget period.
Definition: gnc-budget.cpp:655
void qof_event_gen(QofInstance *entity, QofEventId event_id, gpointer event_data)
Invoke all registered event handlers using the given arguments.
Definition: qofevent.cpp:231
The type used to store guids in C.
Definition: guid.h:75
Commodity handling public routines.
GDate * gnc_g_date_new_today()
Returns a newly allocated date of the current clock time, taken from time(2).
Definition: gnc-date.cpp:1225