LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-budget.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 79.4 % 330 262
Test Date: 2026-11-02 18:19:43 Functions: 86.5 % 52 45
Legend: Lines:     hit not hit

            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              : }
        

Generated by: LCOV version 2.0-1