[patch 3/8] [budget-engine.diff] Core Budget implementation

c.shoemaker at cox.net c.shoemaker at cox.net
Sat Oct 15 00:18:30 EDT 2005


 * src/engine/Makefile.am:
   src/engine/gnc-budget.[ch]:
    - budget functionality in the engine

 * src/engine/gnc-engine.[ch]
    - add the budget logging module

 * src/engine/gw-engine-spec.scm
    - g-wrap bindings for budget functions needed by guile


 src/engine/Makefile.am        |   16 -
 src/engine/gnc-budget.c       |  642 +++++++++++++++++-------------------------
 src/engine/gnc-budget.h       |  211 +++++--------
 src/engine/gnc-engine.c       |    2 
 src/engine/gnc-engine.h       |   19 +
 src/engine/gw-engine-spec.scm |   83 +++++
 6 files changed, 444 insertions(+), 529 deletions(-)

Index: gnucash/src/engine/Makefile.am
===================================================================
--- gnucash.orig/src/engine/Makefile.am
+++ gnucash/src/engine/Makefile.am
@@ -8,6 +8,7 @@ AM_CFLAGS = \
 	-I${top_srcdir}/src \
 	-I${top_srcdir}/src/gnc-module \
 	-I${top_srcdir}/src/business/business-core/ \
+	-I${top_srcdir}/src/core-utils \
 	${GNUCASH_ENGINE_CFLAGS}
 
 libgncmod_engine_la_SOURCES = \
@@ -28,10 +29,6 @@ libgncmod_engine_la_SOURCES = \
   cap-gains.c \
   cashobjects.c \
   gnc-associate-account.c \
-  gnc-budget-book.c \
-  gnc-budget-cat.c \
-  gnc-budget-period-value.c \
-  gnc-budget-period.c \
   gnc-budget.c \
   gnc-commodity.c \
   gnc-date.c \
@@ -62,7 +59,7 @@ libgncmod_engine_la_SOURCES = \
   qofobject.c \
   qofquery.c \
   qofquerycore.c \
-  qofsession.c 
+  qofsession.c
 
 EXTRA_libgncmod_engine_la_SOURCES = iso-4217-currencies.c
 
@@ -92,10 +89,6 @@ gncinclude_HEADERS = \
   glib-helpers.h \
   gnc-associate-account.h \
   gnc-book.h \
-  gnc-budget-book.h \
-  gnc-budget-cat.h \
-  gnc-budget-period-value.h \
-  gnc-budget-period.h \
   gnc-budget.h \
   gnc-commodity.h \
   gnc-date.h \
@@ -142,11 +135,6 @@ noinst_HEADERS = \
   SX-book.h \
   SX-ttinfo.h \
   TransactionP.h \
-  gnc-budget-book-p.h \
-  gnc-budget-cat-p.h \
-  gnc-budget-period-value-p.h \
-  gnc-budget-period-p.h \
-  gnc-budget-p.h \
   gnc-event-p.h \
   gnc-hooks-scm.h \
   gnc-lot.h \
Index: gnucash/src/engine/gnc-budget.c
===================================================================
--- gnucash.orig/src/engine/gnc-budget.c
+++ gnucash/src/engine/gnc-budget.c
@@ -1,6 +1,7 @@
 /********************************************************************\
- * gnc-budget.c -- Implementation of the top level Budgeting API's.     *
+ * gnc-budget.c -- Implementation of the top level Budgeting API's. *
  * Copyright (C) 04 sep 2003    Darin Willits <darin at willits.ca>    *
+ * Copyright (C) 2005  Chris Shoemaker <c.shoemaker at cox.net>        *
  *                                                                  *
  * This program is free software; you can redistribute it and/or    *
  * modify it under the terms of the GNU General Public License as   *
@@ -21,492 +22,375 @@
  *                                                                  *
 \********************************************************************/
 
-/** @file gnc-budget.c
- *  @brief Implementation of the top level budgeting API's.
- *  @author Created by Darin Willits 04 sep 2003 
- *  @author Copyright (c) 2003 Darin Willits <darin at willits.ca>
- *
- *
- */
-
-// Includes
-#include "config.h"
-#include <stdio.h>
-
-#include "gnc-budget-p.h"
-#include "qofbook.h"
-#include "qofbook-p.h"
-#include "qofid-p.h"
-#include "gnc-engine.h"
-#include "gnc-engine-util.h"
-#include "gnc-event-p.h"
-#include "Group.h"
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "qof.h"
+
 #include "Account.h"
-#include "gnc-budget-period.h"
+#include "Group.h"
 
+#include "gnc-budget.h"
+#include "gnc-event-p.h"
+#include "gnc-commodity.h"
+#include "gnc-trace.h"
+#include "gnc-gdate-utils.h"
+
+static QofLogModule log_module = GNC_MOD_ENGINE;
 
-static void budget_fill_categories(GncBudget* budget);
+struct gnc_budget_private{
+    QofInstance inst;
+
+    gchar* name;
+    gchar* description;
+    Recurrence recurrence;
+    guint  num_periods;
+};
 
-GncBudget* gnc_budget_new(QofBook *book)
+GncBudget*
+gnc_budget_new(QofBook *book)
 {
     GncBudget* budget;
-    GDate date;
-    GString* freqStr;
 
     g_return_val_if_fail(book, NULL);
 
+    ENTER(" ");
     budget = g_new0(GncBudget, 1);
-    
-    
     qof_instance_init (&budget->inst, GNC_ID_BUDGET, book);
-    //budget->entity_table = qof_book_get_entity_table (book);
-    //qof_entity_guid_new (budget->entity_table, &budget->guid);
-    //qof_entity_store( budget->entity_table, budget,
-    //                &budget->guid, GNC_ID_BUDGET );
-    
-    budget->book = book;
-
-    /* Create the default period frequency.*/
-    budget->period_freq = xaccFreqSpecMalloc(book);
-    
-    g_date_clear(&date, 1);
-    g_date_set_time(&date, time(NULL));
-    xaccFreqSpecSetMonthly(budget->period_freq, &date, 1);
-    xaccFreqSpecSetUIType(budget->period_freq, UIFREQ_MONTHLY);
-
-    freqStr = g_string_sized_new(16);
-    xaccFreqSpecGetFreqStr(budget->period_freq, freqStr);
-    //printf("Category Freq: %s\n", freqStr->str);
-
-    
-    
-    /* fill the categories based on the current account hierarchy. 
-     * FIXME: This should probably not be here but rather be a separate 
-     * operation with a public interface.  For now it is convienent.*/
-    budget_fill_categories(budget);
 
+    recurrenceSet(&budget->recurrence, 1, PERIOD_MONTH, NULL);
+
+    gnc_budget_set_name(budget, "Unnamed Budget");
+    gnc_budget_set_description(budget, "");
+    gnc_budget_set_num_periods(budget, 12);
 
     gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_CREATE );
 
+    LEAVE(" ");
     return budget;
 }
 
-void gnc_budget_delete(GncBudget* budget)
+static void
+remove_bgt_line_items(QofEntity *act, gpointer bgt)
+{
+    KvpFrame *frame;
+    const GUID *guid;
+    gchar guidbuf[GUID_ENCODING_LENGTH+1];
+
+    frame = qof_instance_get_slots(QOF_INSTANCE(bgt));
+    guid = qof_entity_get_guid(QOF_ENTITY(act));
+    guid_to_string_buff(guid, guidbuf);
+    kvp_frame_delete(kvp_frame_get_frame(frame, guidbuf));
+}
+
+static void
+gnc_budget_remove_all_line_items(GncBudget *budget)
 {
-    if(budget == NULL){
+    QofBook *book;
+    QofCollection *col;
+
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+
+    book = qof_instance_get_book(QOF_INSTANCE(budget));
+    col = qof_book_get_collection(book, GNC_ID_ACCOUNT);
+    qof_collection_foreach(col, remove_bgt_line_items, (gpointer) budget);
+}
+
+void
+gnc_budget_free(GncBudget* budget)
+{
+    if (budget == NULL)
         return;
-    }
-    
+
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    gnc_budget_remove_all_line_items(budget);
+
     /* We first send the message that this object is about to be
      * destroyed so that any GUI elements can remove it before it is
-     * actually gone.
-     */
+     * actually gone. */
     gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_DESTROY);
-    
-    //qof_entity_remove(budget->entity_table, &budget->guid);
 
-    if(budget->name){
+    if (budget->name)
         g_free(budget->name);
-    }
-    
-    if(budget->description){
+
+    if (budget->description)
         g_free(budget->description);
-    }
 
     qof_instance_release (&budget->inst);
     g_free(budget);
 }
 
-void gnc_budget_set_name(GncBudget* budget, const gchar* name)
+void
+gnc_budget_set_name(GncBudget* budget, const gchar* name)
 {
-    g_return_if_fail( name != NULL );
-    if ( budget->name != NULL ) {
-        g_free( budget->name );
-        budget->name = NULL;
-    }
-    budget->name = g_strdup( name );
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    g_return_if_fail(name);
+
+    if (budget->name)
+        g_free(budget->name);
+    budget->name = g_strdup(name);
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-gchar* gnc_budget_get_name(GncBudget* budget)
+const gchar*
+gnc_budget_get_name(GncBudget* budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
     return budget->name;
 }
 
-void gnc_budget_set_description(GncBudget* budget, const gchar* description)
+void
+gnc_budget_set_description(GncBudget* budget, const gchar* description)
 {
-    g_return_if_fail( description != NULL );
-    if ( budget->description != NULL ) {
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    g_return_if_fail(description);
+
+    if (budget->description)
         g_free( budget->description);
-        budget->description = NULL;
-    }
     budget->description = g_strdup(description);
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-gchar* gnc_budget_get_description(GncBudget* budget)
+const gchar*
+gnc_budget_get_description(GncBudget* budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
     return budget->description;
 }
 
-
-
-
-void gnc_budget_set_period_frequency(GncBudget* budget, FreqSpec* period)
+void
+gnc_budget_set_recurrence(GncBudget *budget, const Recurrence *r)
 {
-    if(budget == NULL){
-        return;
-    }
-    
-    if((budget->period_freq == period) || (period == NULL)){
-        return;
-    }
-
-    if(budget->period_freq != NULL){
-        /* Delete the existing object before setting the new one. */
-        xaccFreqSpecFree(budget->period_freq);
-    }
-
-    budget->period_freq = period;
+    g_return_if_fail(budget && r);
+    budget->recurrence = *r;
+    gnc_engine_gen_event(&budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-FreqSpec* gnc_budget_get_period_frequency(GncBudget* budget)
+const Recurrence *
+gnc_budget_get_recurrence(GncBudget *budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
-
-    return budget->period_freq;
+    g_return_val_if_fail(budget, NULL);
+    return (&budget->recurrence);
 }
 
-
-
-
-void gnc_budget_set_start_date(GncBudget* budget, GDate* date)
+const GUID*
+gnc_budget_get_guid(GncBudget* budget)
 {
-    if(budget == NULL){
-        return;
-    }
-    budget->start_date = *date;
+    g_return_val_if_fail(budget, NULL);
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
+    return qof_entity_get_guid(QOF_ENTITY(budget));
 }
 
-GDate* gnc_budget_get_start_date(GncBudget* budget)
+void
+gnc_budget_set_num_periods(GncBudget* budget, guint num_periods)
 {
-    if(budget == NULL){
-        return NULL;
-    }
-    return &budget->start_date;
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    budget->num_periods = num_periods;
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-
-
-
-
-
-void gnc_budget_set_length_months(GncBudget* budget, gint months)
+guint
+gnc_budget_get_num_periods(GncBudget* budget)
 {
-    if(budget == NULL){
-        return;
-    }
-
-    budget->length = months;
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), 0);
+    return budget->num_periods;
 }
 
-void gnc_budget_set_length_years(GncBudget* budget, gint years)
-{
-    if(budget == NULL){
-        return;
-    }
-
-    budget->length = years * 12;
-}
+#define BUF_SIZE (10 + GUID_ENCODING_LENGTH + \
+   GNC_BUDGET_MAX_NUM_PERIODS_DIGITS)
 
-gint gnc_budget_get_length_months(GncBudget* budget)
+/* period_num is zero-based */
+/* What happens when account is deleted, after we have an entry for it? */
+void
+gnc_budget_set_account_period_value(GncBudget *budget, Account *account,
+                                    guint period_num, gnc_numeric val)
 {
-    if(budget == NULL){
-        return 0;
-    }
+    const GUID *guid;
+    KvpFrame *frame;
+    gchar path[BUF_SIZE];
+    gchar *bufend;
 
-    return budget->length;
-}
+    frame = qof_instance_get_slots(QOF_INSTANCE(budget));
+    guid = xaccAccountGetGUID(account);
+    bufend = guid_to_string_buff(guid, path);
+    g_sprintf(bufend, "/%d", period_num);
 
-gint gnc_budget_get_length_years(GncBudget* budget)
-{
-    if(budget == NULL){
-        return 0;
-    }
+    kvp_frame_set_numeric(frame, path, val);
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 
-    return budget->length / 12;
 }
 
+/* We don't need these here, but maybe they're useful somewhere else?
+   Maybe this should move to Account.h */
+#if 0
+static gpointer
+is_same_commodity(Account *a, gpointer data)
+{
+    gnc_commodity *acct_comm;
+    gnc_commodity *comm;
 
+    g_return_val_if_fail(data, NULL);
+    // What? No type-checking macro?
+    comm = (gnc_commodity *) data;
+    acct_comm = xaccAccountGetCommodity(a);
 
-
-
-
-void gnc_budget_add_inflow_category(GncBudget* budget, GncBudgetCategory* category)
-{
-    if(budget == NULL){
-        return;
-    }
-    gnc_budget_category_add_child(budget->inflow_category, category);
+    return gnc_commodity_equal(comm, acct_comm) ? NULL : data;
 }
 
-void gnc_budget_remove_inflow_category(GncBudget* budget, GncBudgetCategory* category)
+static gboolean
+xaccAccountChildrenHaveSameCommodity(Account *account)
 {
-    if(budget == NULL){
-        return;
-    }
-    gnc_budget_category_remove_child(budget->inflow_category, category);
-}
+    AccountGroup *grp;
+    gpointer different;
+    gnc_commodity *comm;
 
-void gnc_budget_set_inflow_category(GncBudget* budget, GncBudgetCategory* inflow)
-{
-    if(budget == NULL){
-        return;
-    }
-    budget->inflow_category = inflow;
+    comm = xaccAccountGetCommodity(account);
+    grp = xaccAccountGetChildren(account);
+    different = xaccGroupForEachAccount(
+        grp, is_same_commodity, comm, TRUE);
+    return (different == NULL);
 }
+#endif
 
-GncBudgetCategory* gnc_budget_get_inflow_category(GncBudget* budget)
-{
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->inflow_category;
-}
 
-void gnc_budget_set_outflow_category(GncBudget* budget, GncBudgetCategory* outflow)
+/* In order to distinguish between a value of zero and an unset value,
+   this function can return a gnc_numeric with GNC_ERROR_ARG set.
+   Currently, it only does so for placeholder accounts with
+   mixed-commodity subaccounts. */
+gnc_numeric
+gnc_budget_get_account_period_value(GncBudget *budget, Account *account,
+                                    guint period_num)
 {
-    if(budget == NULL){
-        return;
-    }
-    budget->outflow_category = outflow;
-}
+    gnc_numeric numeric;
+    gchar path[BUF_SIZE];
+    gchar *bufend;
+    const GUID *guid;
+    KvpFrame *frame;
 
-GncBudgetCategory* gnc_budget_get_outflow_category(GncBudget* budget)
-{
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->outflow_category;
-}
+    numeric = gnc_numeric_zero();
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), numeric);
+    g_return_val_if_fail(account, numeric);
 
-void gnc_budget_add_outflow_category(GncBudget* budget, GncBudgetCategory* category)
-{
-    if(budget == NULL){
-        return;
-    }
-    gnc_budget_category_add_child(budget->outflow_category, category);
+    /* FIXME? check for unset?  Right now, returns zero on unset. */
+    frame = qof_instance_get_slots(QOF_INSTANCE(budget));
+    guid = xaccAccountGetGUID(account);
+    bufend = guid_to_string_buff(guid, path);
+    g_sprintf(bufend, "/%d", period_num);
+    numeric = kvp_frame_get_numeric(frame, path);
+    return numeric;
 }
 
-void gnc_budget_remove_outflow_category(GncBudget* budget, GncBudgetCategory* category)
+/* If 'end' is true, then get a time just before the beginning of the
+   next period */
+static time_t
+gnc_budget_get_period_time(GncBudget *budget, guint period_num, gboolean end)
 {
-    if(budget == NULL){
-        return;
+    GDate date;
+    recurrenceNthInstance(&(budget->recurrence),
+                          period_num + (end ? 1 : 0), &date);
+    if (end) {
+        g_date_subtract_days(&date, 1);
+        return gnc_timet_get_day_end_gdate(&date);
+    } else {
+        return gnc_timet_get_day_start_gdate(&date);
     }
-    gnc_budget_category_remove_child(budget->inflow_category, category);
-}
 
-QofBook* gnc_budget_get_book(GncBudget* budget)
-{
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->book;
 }
 
-static void free_budget_period(gpointer data, gpointer user_data)
+Timespec
+gnc_budget_get_period_start_date(GncBudget *budget, guint period_num)
 {
-    GncBudgetPeriod* period = data;
-    g_free(period);
+    Timespec ts;
+    timespecFromTime_t(
+        &ts,  gnc_budget_get_period_time(budget, period_num, FALSE));
+    return ts;
 }
 
-void gnc_budget_generate_periods(GncBudget* budget)
+gnc_numeric
+gnc_budget_get_account_period_actual_value(
+    GncBudget *budget, Account *account, guint period_num)
 {
-    GDate currentPeriod, outDate;
-    GDate endDate;
-    GDate periodStartDate;
-    GncBudgetPeriod* budgetPeriod;
+    gnc_numeric numeric, num1, num2;
+    time_t t1, t2;
 
-    if(budget == NULL){
-        return;
-    }
-
-    /* TODO: Add in some error checking such that we don't regenerate
-     * the periods if they are the same as before.  We may still need
-     * to recalculate the values however.
-     */
-    if(budget->period_list != NULL){
-        g_list_foreach(budget->period_list, free_budget_period, NULL);
-        g_list_free(budget->period_list);
-        budget->period_list = NULL;
-    }
-    
-    /* Add the starting period.  Every budget should have at least
-     * one period. */
-    g_date_set_dmy(&periodStartDate, g_date_get_day(&budget->start_date),
-                                     g_date_get_month(&budget->start_date),
-                                     g_date_get_year(&budget->start_date));
-    budgetPeriod = gnc_budget_period_new();
-    gnc_budget_period_set_start_date(budgetPeriod, &periodStartDate);
-    budget->period_list = g_list_append(budget->period_list, budgetPeriod);
- 
-    
-    /* Setup the end Date */
-    g_date_set_dmy(&endDate, g_date_get_day(&budget->start_date),
-                             g_date_get_month(&budget->start_date),
-                             g_date_get_year(&budget->start_date));
-    g_date_add_months(&endDate, budget->length);
-    
-
-    g_date_set_dmy(&currentPeriod, g_date_get_day(&budget->start_date),
-                                  g_date_get_month(&budget->start_date),
-                                  g_date_get_year(&budget->start_date));
-    do{
-        xaccFreqSpecGetNextInstance(budget->period_freq, &currentPeriod, &outDate);
-        if(g_date_valid(&outDate) == FALSE){
-            break;
-        }
-        
-        if(g_date_compare(&outDate, &endDate) >= 0){
-            break;
-        }
-        else{
-            /* Set the end date of the previous period. */
-            gnc_budget_period_set_end_date(budgetPeriod, &outDate);
-
-            /* Create the new period. */
-            budgetPeriod = gnc_budget_period_new();
-            gnc_budget_period_set_start_date(budgetPeriod, &outDate);
-            
-            budget->period_list = g_list_append(budget->period_list, budgetPeriod);
-            g_date_set_dmy(&currentPeriod, g_date_get_day(&outDate),
-                            g_date_get_month(&outDate),
-                            g_date_get_year(&outDate));
-        }
-    }while(1);
-
-    gnc_budget_period_set_end_date(budgetPeriod, &endDate);
-    
-    /* Now we should generate the list of values for each category.*/
-    gnc_budget_category_generate_values(budget->inflow_category);
-    gnc_budget_category_generate_values(budget->outflow_category);
-}
+    // FIXME: maybe zero is not best error return val.
+    g_return_val_if_fail(GNC_IS_BUDGET(budget) && account, gnc_numeric_zero());
+    t1 = gnc_budget_get_period_time(budget, period_num, FALSE);
+    t2 = gnc_budget_get_period_time(budget, period_num, TRUE);
 
+    num1 = xaccAccountGetBalanceAsOfDateInCurrency(
+        account, t1, NULL, TRUE);
+    num2 = xaccAccountGetBalanceAsOfDateInCurrency(
+        account, t2, NULL, TRUE);
 
-gint gnc_budget_get_num_periods(GncBudget* budget)
-{
-    if(budget == NULL){
-        return 0;
-    }
-    return g_list_length(budget->period_list);
+    numeric = gnc_numeric_sub(num2, num1, GNC_DENOM_AUTO,
+                              GNC_HOW_DENOM_FIXED);
+    return numeric;
 }
 
-GList* gnc_budget_get_period_list(GncBudget* budget)
+QofBook*
+gnc_budget_get_book(GncBudget* budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->period_list;
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
+    return qof_instance_get_book(&budget->inst);
 }
 
-
-
-/* ==================================================================== */
-/* Private member functions.
- *
- * ==================================================================== */
-
-static void add_category_from_account(gpointer data, gpointer userdata)
+GList*
+gnc_book_get_budgets(QofBook* book)
 {
-    GncBudget* budget;
-    Account* account;
-    AccountList* children;
-    GncBudgetCategory* category = NULL;
-    gchar* name;
-    AccountGroup* group;
-    int numChildren;
-    GNCAccountType accountType;
-
-    account = data;
-    budget = userdata;
-    
-    if(account == NULL){
-        return;
-    }
-
-    //printf("Adding Category from account\n");
-    //printf("Account: %s\n", xaccAccountGetName(account));
-    group  = xaccAccountGetChildren(account);
-    children = xaccGroupGetAccountList(group);
-    numChildren = xaccGroupGetNumAccounts(group);
-    //printf("NumChildren: %d\n", numChildren);
-
-    if(numChildren == 0){
-        name = xaccAccountGetFullName(account, ':');
-        accountType = xaccAccountGetType(account);
-        
-        category = gnc_budget_category_new(budget->book, budget);
-        gnc_budget_category_set_name(category, name);
-        gnc_budget_category_add_account(category, account);
-                
-        if(accountType == INCOME){
-            //printf("Inflow Category: %s\n", gnc_budget_category_get_name(category));
-            gnc_budget_category_set_type(category, BUDGET_CATEGORY_INFLOW);
-            gnc_budget_category_add_child(budget->inflow_category, category);
-        }
-        else{
-            //printf("Outflow Category: %s\n", gnc_budget_category_get_name(category));
-            gnc_budget_category_set_type(category, BUDGET_CATEGORY_OUTFLOW);
-            gnc_budget_category_add_child(budget->outflow_category, category);
-        }
-        
-        g_free(name);
-    }
+    QofCollection *col;
 
+    g_return_val_if_fail(book, NULL);
+    col = qof_book_get_collection (book, GNC_ID_BUDGET);
+    return qof_collection_get_list(col);
 }
 
-static void budget_fill_categories(GncBudget* budget)
+GncBudget*
+gnc_budget_lookup (const GUID *guid, QofBook *book)
 {
-    AccountList* children;
-    if(budget == NULL){
-        return;
-    }
-    
-    //printf("Creating top level budget categories.\n");
-
-    budget->inflow_category = gnc_budget_category_new(budget->book, budget);
-    gnc_budget_category_set_name(budget->inflow_category, "Inflow");
+    QofCollection *col;
 
-    
-    budget->outflow_category = gnc_budget_category_new(budget->book, budget);
-    gnc_budget_category_set_name(budget->outflow_category, "Outflow");
+    g_return_val_if_fail(guid, NULL);
+    g_return_val_if_fail(book, NULL);
+    col = qof_book_get_collection (book, GNC_ID_BUDGET);
+    return GNC_BUDGET(qof_collection_lookup_entity (col, guid));
+}
 
-    
-    //printf("Filing budget Categories.\n");
-    //children = xaccGroupGetAccountList(gnc_book_get_group(budget->book));
-    children = xaccGroupGetSubAccounts(gnc_book_get_group(budget->book));
-    g_list_foreach(children, add_category_from_account, budget);
+GncBudget*
+gnc_budget_get_default (QofBook *book)
+{
+    GList *list;
+    GncBudget *bgt = NULL;
 
-    //printf("Number of inflow categories: %d\n", 
-            //gnc_budget_category_get_num_children(budget->inflow_category));
-    //printf("Number of outflow categories: %d\n", 
-            //gnc_budget_category_get_num_children(budget->outflow_category));
-}
+    g_return_val_if_fail(book, NULL);
+    list = gnc_book_get_budgets(book);
+    if (g_list_length(list) > 0) {
+        bgt = GNC_BUDGET(list->data);  // Just get the first one.
+        g_list_free(list);
+    }
+    return bgt;
+}
+
+/* Define the QofObject. */
+/* TODO: Eventually, I'm think I'm going to have to check if this struct is
+   complete.  Also, do we need one of those QofParam thingys? */
+static QofObject budget_object_def =
+{
+    interface_version: QOF_OBJECT_VERSION,
+    e_type:            GNC_ID_BUDGET,
+    type_label:        "BUDGET",
+    create:            (gpointer (*)(QofBook *)) gnc_budget_new,
+    book_begin:        NULL,
+    book_end:          NULL,
+    is_dirty:          NULL,
+    mark_clean:        NULL,
+    foreach:           qof_collection_foreach,
+    printable:         NULL,
+    version_cmp:       (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
+};
 
-GncBudget* gnc_budget_lookup (const GUID *guid, QofBook *book)
+/* Register ourselves with the engine. */
+gboolean gnc_budget_register (void)
 {
-    QofCollection *col;
-    
-    if (!guid || !book){
-        return NULL;
-    }
-        
-    col = qof_book_get_collection (book, GNC_ID_BUDGET);
-    return (GncBudget*)qof_collection_lookup_entity (col, guid);
+    return qof_object_register (&budget_object_def);
 }
Index: gnucash/src/engine/gnc-budget.h
===================================================================
--- gnucash.orig/src/engine/gnc-budget.h
+++ gnucash/src/engine/gnc-budget.h
@@ -1,6 +1,7 @@
 /********************************************************************\
- * gnc-budget.h -- Budget public handling routines.                     *
+ * gnc-budget.h -- Budget public handling routines.                 *
  * Copyright (C) 04 sep 2003    Darin Willits <darin at willits.ca>    *
+ * Copyright (C) 2005  Chris Shoemaker <c.shoemaker at cox.net>        *
  *                                                                  *
  * This program is free software; you can redistribute it and/or    *
  * modify it under the terms of the GNU General Public License as   *
@@ -21,39 +22,58 @@
  *                                                                  *
 \********************************************************************/
 
-
-/** @addtogroup Engine
- *     @{ */
-/** @addtogroup Budget Budget objects
- * A budget object contains a list of budget_categories each of 
- * which define an individual budgetable item.  When a budget is first
- * created these catagories will be initialized based on the existing 
- * account hierarchy.
- *     @{ */
-/** @file gnc-budget.h
- *  @brief Public interface for budget objects.
- *  @author Created by Darin Willits 04 sep 2003 
- *  @author Copyright (c) 04 Darin Willits <darin at willits.ca>
- *
- * This file contains the public methods to interact with a budgeting
- * object. 
- * This is pre-alpha code right now.  User beware.
+/*  Design decisions:
+ *
+ *  - The budget values that the user enters (and that are stored) for
+ *  each account are inclusive of any sub-accounts.
+ *
+ *     - Reason: This allows the user to budget an amount for a
+ *     "parent" account, while tracking (e.g.) expenses with more
+ *     detail in sub-accounts.
+ *
+ *     - Implication: when reporting budgeted values for parent
+ *     accounts, just show the stored value, not a sum.
+ *
+ *  - Budget values are always in the account's commodity.
+ *
+ *
+ *
+ *  Accounts with sub-accounts can have a value budgeted.  For those
+ *  accounts,
+ *
+ *    Option 1: when setting or getting budgeted values, the value is
+ *    *always* exclusive of sub-account values.  Pro: consistent
+ *    values in all contexts.  Con: no summary behavior.
+ *
+ *    Option 2: make setting only for account proper, but always
+ *    report summaries. Con: value can change as soon as it is
+ *    entered; forces entry from bottom-up.
+ *
+ *    * Option 3: value is always *inclusive* of sub-accounts, although
+ *    potentially in a different commodity.  Pro: allows top-down
+ *    entry; no auto value update. Con: ?  [ This option was selected. ]
+ *
  */
 
-
 #ifndef __GNC_BUDGET_H__
 #define __GNC_BUDGET_H__
 
 #include <glib.h>
 
 /** The budget data.*/
-typedef struct gncp_budget GncBudget;
+typedef struct gnc_budget_private GncBudget;
 
 #include "guid.h"
 #include "qofbook.h"
-#include "gnc-budget-cat.h"
-#include "FreqSpec.h"
+#include "Account.h"
+#include "Recurrence.h"
+
+#define GNC_IS_BUDGET(obj)  (QOF_CHECK_TYPE((obj), GNC_ID_BUDGET))
+#define GNC_BUDGET(obj)     (QOF_CHECK_CAST((obj), GNC_ID_BUDGET, GncBudget))
 
+#define GNC_BUDGET_MAX_NUM_PERIODS_DIGITS 3 // max num periods == 999
+
+gboolean gnc_budget_register(void);
 
 /**
  * Creates and initializes a Budget.
@@ -61,130 +81,53 @@ typedef struct gncp_budget GncBudget;
 GncBudget *gnc_budget_new(QofBook *book);
 
 /** Deletes the given budget object.*/
-void gnc_budget_delete(GncBudget* budget);
-
+void gnc_budget_free(GncBudget* budget);
 
+const GUID* gnc_budget_get_guid(GncBudget* budget);
+#define gnc_budget_return_guid(X) \
+  (X ? *(qof_entity_get_guid(QOF_ENTITY(X))) : *(guid_null()))
 
-/**  Set the Name of the Budget.
- */
+/** Set/Get the name of the Budget */
 void gnc_budget_set_name(GncBudget* budget, const gchar* name);
+const gchar* gnc_budget_get_name(GncBudget* budget);
 
-/**  Retieve the Name of the Budget.
- */
-gchar* gnc_budget_get_name(GncBudget* budget);
-
-
-
-/**  Set the Description of the Budget.
- */
+/** Set/Get the description of the Budget */
 void gnc_budget_set_description(GncBudget* budget, const gchar* description);
+const gchar* gnc_budget_get_description(GncBudget* budget);
 
-/**  Retieve the Description of the Budget.
- */
-gchar* gnc_budget_get_description(GncBudget* budget);
-
-
-
-/** Set the period frequency.
- */
-void gnc_budget_set_period_frequency(GncBudget* budget, FreqSpec* period);
-
-/** Get the period frequency.
- */
-FreqSpec* gnc_budget_get_period_frequency(GncBudget* budget);
-
-
-
-/** Set the start date*/
-void gnc_budget_set_start_date(GncBudget* budget, GDate* date);
-
-/** Return the start date */
-GDate* gnc_budget_get_start_date(GncBudget* budget);
-
-
-/** Set the Length for this budget in terms of a number of months.
- */
-void gnc_budget_set_length_months(GncBudget* budget, gint months);
+/** Set/Get the number of periods in the Budget */
+void gnc_budget_set_num_periods(GncBudget* budget, guint num_periods);
+guint gnc_budget_get_num_periods(GncBudget* budget);
+
+void gnc_budget_set_recurrence(GncBudget *budget, const Recurrence *r);
+const Recurrence * gnc_budget_get_recurrence(GncBudget *budget);
+
+/** Set/Get the starting date of the Budget */
+void gnc_budget_set_start_date(GncBudget* budget, Timespec date);
+Timespec gnc_budget_get_start_date(GncBudget* budget);
+Timespec gnc_budget_get_period_start_date(GncBudget* budget, guint period_num);
+
+/* Period indices are zero-based. */
+void gnc_budget_set_account_period_value(
+    GncBudget* budget, Account* account, guint period_num, gnc_numeric val);
+
+gnc_numeric gnc_budget_get_account_period_value(
+    GncBudget *budget, Account *account, guint period_num);
+gnc_numeric gnc_budget_get_account_period_actual_value(
+    GncBudget *budget, Account *account, guint period_num);
 
-/** Set the Length for this budget in terms of a number of months.
- */
-void gnc_budget_set_length_years(GncBudget* budget, gint years);
-
-/** Retrieve the Length for this budget in number of months.
- */
-gint gnc_budget_get_length_months(GncBudget* budget);
-
-/** Retrieve the Length for this budget in number of years.
- */
-gint gnc_budget_get_length_years(GncBudget* budget);
-
-
-
-/* Add the inflow category for this budget.  
- * This is a wrapper function to make it easier to add a category
- * as a child of the inflow category.  It's here cause I'm a lazy ass.
- */
-void gnc_budget_add_inflow_category(GncBudget* budget, GncBudgetCategory* inflow);
-
-/* Remove the inflow category for this budget.  
- */
-void gnc_budget_remove_inflow_category(GncBudget* budget, GncBudgetCategory* category);
-
-/* Set the inflow category for this budget.  
- */
-void gnc_budget_set_inflow_category(GncBudget* budget, GncBudgetCategory* inflow);
-
-/** Retrieve the inflow category for this budget. 
- */
-GncBudgetCategory* gnc_budget_get_inflow_category(GncBudget* budget);
-
-
-
-
-/* Add the outflow category for this budget.  
- */
-void gnc_budget_add_outflow_category(GncBudget* budget, GncBudgetCategory* outflow);
-
-/* Remove the outflow category for this budget.  
- */
-void gnc_budget_remove_outflow_category(GncBudget* budget, GncBudgetCategory* inflow);
-
-/* Set the outflow category for this budget.  
- */
-void gnc_budget_set_outflow_category(GncBudget* budget, GncBudgetCategory* category);
-
-/** Retrieve the outflow category for this budget. 
- */
-GncBudgetCategory* gnc_budget_get_outflow_category(GncBudget* budget);
-
-
-
-/** Retrieve the book that this budget is associated with.*/
+/** Get the book that this budget is associated with. */
 QofBook* gnc_budget_get_book(GncBudget* budget);
 
+/* Caller owns the list of all budgets in the book. */
+GList* gnc_book_get_budgets(QofBook* book);
 
-/** Generate the list of periods.
- * This function will use the start date and budget length to generate a 
- * list of budget periods.  The periods are represented by a list of start
- * dates for each period.
- * As well this function regenerates the list of values for each
- * category.
- * */
-void gnc_budget_generate_periods(GncBudget* budget);
-
-/** Get the number of periods.
- */
-gint gnc_budget_get_num_periods(GncBudget* budget);
-
-/* Return the list of periods.
- */
-GList* gnc_budget_get_period_list(GncBudget* budget);
-
+/* Returns some budget in the book, or NULL. */
+GncBudget* gnc_budget_get_default(QofBook *book);
 
-/** Retrieve the budget object associated with the given GUID from 
- * the given book.
- */
+/* Get the budget associated with the given GUID from the given book. */
 GncBudget* gnc_budget_lookup (const GUID *guid, QofBook *book);
+#define  gnc_budget_lookup_direct(g,b) gnc_budget_lookup(&(g),(b))
 
 #endif // __BUDGET_H__
 
Index: gnucash/src/engine/gnc-engine.c
===================================================================
--- gnucash.orig/src/engine/gnc-engine.c
+++ gnucash/src/engine/gnc-engine.c
@@ -31,7 +31,7 @@
 #include "AccountP.h"
 #include "GroupP.h"
 #include "SX-book-p.h"
-#include "gnc-budget-book-p.h"
+#include "gnc-budget.h"
 #include "TransactionP.h"
 #include "gnc-commodity.h"
 #include "gnc-lot-p.h"
Index: gnucash/src/engine/gnc-engine.h
===================================================================
--- gnucash.orig/src/engine/gnc-engine.h
+++ gnucash/src/engine/gnc-engine.h
@@ -62,6 +62,7 @@
 #define GNC_MOD_IMPORT    "gnucash-import-export"
 #define GNC_MOD_DRUID     "gnucash-druids"
 #define GNC_MOD_TEST      "gnucash-tests"
+#define GNC_MOD_BUDGET    "gnucash-budget"
 //@}
 
 /** @brief IDENTIFIERS
@@ -99,13 +100,29 @@
 #define GNC_ID_SPLIT          "Split"
 #define GNC_ID_SCHEDXACTION   "SchedXaction"
 #define GNC_ID_BUDGET         "Budget"
-#define GNC_ID_BUDGET_CATEGORY "BudgetCategory"
 #define GNC_ID_SXTG           "SXTGroup"
 #define GNC_ID_SXTT           "SXTTrans"
 #define GNC_ID_TRANS          "Trans"
                                                                                 
 /* TYPES **********************************************************/
 
+/* CAS: ISTM, it would make more sense to put the typedefs in their
+   corresponding header files, (e.g. Account.h), and to #include all
+   the engine API header files right here.  After all, when I jump to
+   the definition "Account", I want to end up in Account.h, not this
+   file, like I do now.
+
+   Also, as it is now, if I want to use the engine api, I need to
+   include this header, plus all the other engine headers for the
+   types whose functions I call, so this header is providing almost no
+   benefit of aggregation.  But, if it included all the headers I
+   could just include this file.  Or would that cause a massive
+   recompile everytime one engine header changed?
+   Even if including all the headers here doesn't make sense, I think
+   distributing the stuff in the "Types" section does.
+*/
+
+
 /** @brief Account in Gnucash. 
  * This is the typename for an account. The actual structure is
  * defined in the private header AccountP.h, but no one outside the
Index: gnucash/src/engine/gw-engine-spec.scm
===================================================================
--- gnucash.orig/src/engine/gw-engine-spec.scm
+++ gnucash/src/engine/gw-engine-spec.scm
@@ -26,6 +26,7 @@
     "#include <guid.h>\n"
     "#include <Group.h>\n"
     "#include <Query.h>\n"
+    "#include <gnc-budget.h>\n"
     "#include <gnc-commodity.h>\n"
     "#include <gnc-date.h>\n"
     "#include <gnc-engine.h>\n"
@@ -260,6 +261,7 @@
 ;
 (gw:wrap-value ws 'gnc:id-account '<gnc:id-type> "GNC_ID_ACCOUNT")
 (gw:wrap-value ws 'gnc:id-book '<gnc:id-type> "GNC_ID_BOOK")
+(gw:wrap-value ws 'gnc:id-budget '<gnc:id-type> "GNC_ID_BUDGET")
 (gw:wrap-value ws 'gnc:id-lot '<gnc:id-type> "GNC_ID_LOT")
 (gw:wrap-value ws 'gnc:id-price '<gnc:id-type> "GNC_ID_PRICE")
 (gw:wrap-value ws 'gnc:id-split '<gnc:id-type> "GNC_ID_SPLIT")
@@ -2491,6 +2493,87 @@ the timepair representing midday on that
  '(((gw:glist-of (<gw:mchars> callee-owned) callee-owned) choices))
  "Takes a list of installed Finance::Quote souces and records it internally.")
 
+
+;; Budget functions
+
+(gw:wrap-as-wct ws '<gnc:Budget*> "GncBudget *" "const GncBudget *")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-guid
+ '<gnc:guid-scm>
+ "gnc_budget_return_guid"
+ '((<gnc:Budget*> budget))
+ "Gets the guid of the budget")
+
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-lookup
+ '<gnc:Budget*>
+ "gnc_budget_lookup_direct"
+ '((<gnc:guid-scm> guid)
+   (<gnc:Book*> book))
+ "Lookup a budget from its GUID.")
+
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-default
+ '<gnc:Budget*>
+ "gnc_budget_get_default"
+ '((<gnc:Book*> book))
+ "Get the default budget for the book.")
+
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-name
+ '(<gw:mchars> callee-owned const)
+ "gnc_budget_get_name"
+ '((<gnc:Budget*> budget))
+ "Get the brief name for the budget.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-num-periods
+ '<gw:unsigned-int>
+ "gnc_budget_get_num_periods"
+ '((<gnc:Budget*> budget))
+ "Get the number of periods in a budget.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-account-period-value
+ '<gnc:numeric>
+ "gnc_budget_get_account_period_value"
+ '((<gnc:Budget*> budget)
+   (<gnc:Account*> acct)
+   (<gw:unsigned-int> period_num)
+   )
+ "Get the budgeted value for the given account and budget period.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-account-period-actual-value
+ '<gnc:numeric>
+ "gnc_budget_get_account_period_actual_value"
+ '((<gnc:Budget*> budget)
+   (<gnc:Account*> acct)
+   (<gw:unsigned-int> period_num)
+   )
+ "Get the actual account value for the given account and budget period.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-period-start-date
+ '<gnc:time-pair>
+ "gnc_budget_get_period_start_date"
+ '((<gnc:Budget*> budget)
+   (<gw:unsigned-int> period_num)
+   )
+ "Get the date that the given period begins.")
+
 ;;
 ;; gnc-hooks-scm.h
 ;;   (and gnc-hooks.h)

--


More information about the gnucash-patches mailing list