/* sched - A program to track personal schedule of events including budget.
   Rick Smereka, Copyright (C) 2004-2006.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   This program will store and calculate your schedule of events.
   Events include budgetary and reminder's. Events are tracked on
   a day to day basis. This program makes it possible to see your
   bank account balance for every day there is an event.

   All data is stored in 'bbuuzzb' tables. Each table is a year's
   worth of data and this program allow you to see the data one
   month at a time.

   As yearend approaches, you need to manually create next year's
   'bbuuzzb' table. The table names have the convention of
   'schedule.nnnn' where 'nnnn' is the year.

   Each time monthly events are listed, they will be displayed in
   the following form:

      c:mmdd.s desc,cat,pl,plb,act,actp

   Where the 'c' is an list event count, 'mmdd' are the month and day
   of the event, 's' is the day sequence count (since there may be
   more than one event per day), 'des' is the event description,
   'cat' is the event category (see below), 'pl' is the planned
   amount, 'plb' is the current planned balance, 'act' is the
   actual amount spent and 'actp' is the current actual account
   balance. The balances are as at after the event has taken
   place.

   This program depends on a category table which you need to set
   up. Every event will belong to one of these categories. The
   name of the category table must be 'schedule.category'.
   The field layout of this category table is as follows:

      1. category number (sequential count)
      2. category description
      3. category type (see below)

   Each category entry has a type which tells this program
   what to do with all the events that belong to this
   category. Here are the event types:

      1 credit
      2 debit
      3 na (reminder)

   You must use the category type number in field 3 of the
   category record (see above).

   This program also uses its own temporary table called
   'schedule.tmp'. Ths table must also already exist.

   Original version for Linux. Feb/2004, Rick Smereka

   Updated program documentation. Modified command prompts.
   Sep/2006, Rick Smereka */

#include "stdhead.h"
#ifndef OS_DOS
#include "flsocket.h"
#include "appinit.h"
#endif
#include "dbmess.h"
#include "dbiocode.h"
#include "dbcomm.h"
#include "dbfxp.h"

#ifdef IPC_TCP
#include "socloc.h"
#include "sloc.h"
#include "slconfig.h"
#include "sliocode.h"
#ifdef OS_WIN32
WSADATA wsaData;
#endif
#endif

/* high level database API changes depending
   on whether we are compiling for stand-alone
   or multiuser */

#ifdef MULTIUSER
#include "dbcs.h"
#include "dbcscfg.h"
#define APNAME "schedm"
#else
#include "dbeng.h"
#include "dblocal.h"
#include "dbengcfg.h"
#include "dblocfg.h"
#define APNAME "sched"
#endif

#define VERSION "1.02.02-2006.09.09"
#define SCHED_NAME "schedule"
#define SCHED_CAT_NAME "schedule.category"
#define SCHED_TMP_NAME "schedule.tmp"
// #define DEBUG 1


// schedule table field definitions

#define SCHED_FIELD_DATE 1 // mmdd.seq
#define SCHED_FIELD_DESC 2
#define SCHED_FIELD_CAT 3
#define SCHED_FIELD_PLANNED 4
#define SCHED_FIELD_ACTUAL 5
#define SCHED_FIELD_PLANNED_BAL 6
#define SCHED_FIELD_ACTUAL_BAL 7

// schedule tmp table field definitions

#define SCHED_TMP_FIELD_DATE 1
#define SCHED_TMP_FIELD_PLANNED_BAL 2
#define SCHED_TMP_FIELD_ACTUAL_BAL 3

// schedule category table field definitions

#define CAT_FIELD_NUM 1
#define CAT_FIELD_DESC 2
#define CAT_FIELD_TYPE 3

// category table counter record value

#define CAT_COUNT_REC "count"

// category table type field values

#define CAT_TYPE_CREDIT 1
#define CAT_TYPE_DEBIT 2
#define CAT_TYPE_NA 0

// fxp scale to all dollar amounts

#define SCHED_FXP_SCALE 2

// event list structure

struct event_list
   {
   char id[10]; // event id
   struct event_list *next;
   };

// null list

#define EVENT_NULL ((struct event_list *)NULL)

// list head

struct event_list *event_head = EVENT_NULL;

// filters for 'get_sched'

#define SCHED_FILTER_ALL 0
#define SCHED_FILTER_BUDGET 1
#define SCHED_FILTER_OTHER 2

int main(void);
void get_date(void);
int c_initialize(void);
int open_tables(void);
int main_menu(void);
void change_date(void);
void get_input(char *, char *);
void add_category(void);
void add_events(void);
void modify_events(void);
void delete_events(void);
int get_event_category(char *, char *);
int get_event_aplanned(char *, char *, char *);
int display_categories(void);
int display_events(int);
int xref_category(char *, int *);
int get_category_count(void);
int summarize_month(void);
void copy_month(void);
int get_next_seq(int);
int get_sched(int, int, int);
struct event_list *ll_locate(int);
int ll_add(char *);
void ll_delete(void);
void close_tables(void);
void term_app(void);

#ifdef IPC_TCP
int c_ipc_socloc_init(void);
#endif

/* global data */

int year_num;
int month_num;
int sched_tid, sched_cat_tid;
int sched_changed, sched_cat_changed;

int main(void)
{
   char mess[75];
   int ret;

   /* build logo string based on platform */

#ifndef MULTIUSER
#ifndef OS_UNIX
   /* single user non-Unix */

   printf("sched Single User for %s Version %s\n", PLATFORM_STRING, VERSION);
#else
   /* single user Unix */

   printf("sched Single User for %s Version %s\n", SUB_PLATFORM_STRING, 
          VERSION);
#endif
#else
#ifndef OS_UNIX
   /* c/s non-Unix */

   printf("schedm Client/Server for %s Version %s\n", PLATFORM_STRING, VERSION);
#else
   /* c/s Unix */

   printf("schedm Client/Server for %s Version %s\n", SUB_PLATFORM_STRING, 
          VERSION);
#endif
#endif

   printf("by Rick Smereka, Copyright (c) 2004-2006\n");
   get_date();

#ifndef OS_DOS
   // register application name with 'appinit'

   if (!appinit_register_name(APNAME))
      {
      printf("%s:fatal error registering app name with appinit\n", APNAME);
      return(0);
      }
#endif

   /* initialize local engine if we are running locally or
      startup the 'socloc' interface (TCP only) and connect to a
      Bbuuzzb database server if we are running in
      client/server mode */

   if (!c_initialize())
      {
      printf("program abort\n");
      term_app();
      return(0);
      }

#ifdef DEBUG
   /* debugging via system logger except for DOS which will use
      the personal logger to create a log file in the current directory */

#ifdef OS_DOS
   sprintf(mess, "%s.log", APNAME);

   if (logman_start(mess, APNAME))
      {
      printf("%s:unable to open log file '%s',program abort\n", APNAME, mess);
      term_app();
      return(0);
      }
#else
   if (logman_start(SYS_LOG_SERVICE_NAME, APNAME))
      {
      printf("%s:unable to start system logging,program abort\n", APNAME);
      term_app();
      return(0);
      }
#endif
#endif

   ret = 0;

   if (open_tables())
      while(!ret)
         ret = main_menu();

   term_app();
   return(0);
}

void get_date(void)
{
   /* Obtain the current year and month from the system clock. */

   time_t tod;
   struct tm *tmbuf;

   tod = time(NULL);
   tmbuf = localtime(&tod);
   month_num =  tmbuf->tm_mon + 1;
   year_num = tmbuf->tm_year + 1900;
}

#ifdef MULTIUSER
int c_initialize(void)
{
   /* Initialize socket communication, form connection to
      'socloc' server (TCP only), update the config list and connect
      to a 'Bbuuzzb' server. Function returns 'TRUE' upon
      success, 'FALSE' otherwise. */

   char mname[] = "c_initialize";
   char mes[128];
   int ret;

#ifdef IPC_TCP
   if (!c_ipc_socloc_init())
      return(FALSE);
#endif

   /* attempt to locate a 'Bbuuzzb' server and connect to it */

   if ((ret = db_initialize()) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:error connecting to the Bbuuzzb server,rc[%s]. "
             "Program abort\n", mname, mes);
      return(FALSE);
      }

   /* display current connected server host name and
      TCP port number */

#ifdef IPC_TCP
   (void)db_get_active(mes, &ret);
   printf("%s:connected to Bbuuzzb server %s on TCP port %d (decimal)\n",
          mname, mes, ret);
#endif

   return(TRUE);
}
#endif

#ifndef MULTIUSER
int c_initialize(void)
{
   /* Start the 'DBENG' local engine, read the config file
      and display the result settings. Function returns
      'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "c_initialize";
   char *mes;
   int ret;

#ifdef DEBUG
#ifdef IPC_TCP
   if (!c_ipc_socloc_init())
      return(FALSE);
#endif
#endif

   if ((mes = (char *)malloc(DBENG_PATH_LIMIT)) == (char *)NULL)
      {
      printf("%s:alloc fail[mes]. Program abort\n", mname);
      return(FALSE);
      }

   ret = db_initialize();

   /* interpret code from init which may be
      a fatal config error */

   switch(ret)
      {
      case DBENG_OK:
         printf("%s:read config file successfully\n", mname);
         break;

      case DBENG_CONFIG_UNABLE_TO_OPEN:
         printf("%s:config file not found, loaded defaults\n", mname);
         break;

      default:
         db_io_code_string(ret, mes);
         printf("%s:fatal config error[%s]\n", mname, mes);
         printf("%s:terminating\n", mname);
         free(mes);
         return(FALSE);
         break;
      };

   (void)db_config_get_tmp_path(mes);
   printf("%s:tmp path is '%s'\n", mname, mes);
   (void)db_config_get_log(mes);
   printf("%s:log is '%s'\n", mname, mes);
   free(mes);
   return(TRUE);
}
#endif

#ifdef IPC_TCP
int c_ipc_socloc_init(void)
{
   /* Initialize socket communication, form connection to
      'socloc' server (TCP only) and update the config list.
      Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "c_ipc_socloc_init";
   char *thelist, mes[128];
   int ret;

   /* startup WinSock (if Windoze) */

#ifdef OS_WIN32
   if (WSAStartup(WINSOCK_VERSION, &wsaData))
      {
      printf("%s:unable to start WinSock. "
            "Program abort\n", mname);
      return(FALSE);
      }
#endif

   /* attempt to connect to a 'socloc' server */

   if ((ret = sloc_initialize()) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:error initializing socloc interface,rc[%s]. "
            "Program abort\n", mname, mes);
      return(FALSE);
      }

   /* get config list from 'socloc' server and update
      the client list with it */

   if ((thelist = (char *)malloc(SL_MAXCOMMAND)) == (char *)NULL)
      {
      printf("%s:alloc fail[thelist]. Program abort\n", mname);
      return(FALSE);
      }

   if ((ret = sloc_config_get_list(thelist)) != SL_OK)
      {
      free(thelist);
      sl_code_string(ret, mes);
      printf("%s:error getting socloc config list,rc[%s]. "
             "Program abort\n", mname, mes);
      return(FALSE);
      }

   if ((ret = sl_config_put_list(thelist)) != SL_OK)
      {
      sl_code_string(ret, mes);
      printf("%s:error putting socloc config list,rc[%s]. ",
             "Program abort\n", mname, mes);
      free(thelist);
      return(FALSE);
      }

   free(thelist);
   return(TRUE);
}
#endif

int open_tables(void)
{
   /* Open application tables. Function returns 'TRUE' upon success,
      'FALSE' otherwise. */

   char mes[128], table[128], mname[] = "open_tables";
   int ret;

   sprintf(table, "%s.%d", SCHED_NAME, year_num);

   if ((ret = db_open(table, &sched_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:open of %s failed[%s]\n", mname, table, mes);
      return(FALSE);
      }

   if ((ret = db_open(SCHED_CAT_NAME, &sched_cat_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:open of %s failed[%s]\n", mname, SCHED_CAT_NAME, mes);
      (void)db_close(sched_tid);
      return(FALSE);
      }
     
   sched_changed = sched_cat_changed = FALSE;
   return(TRUE);
}

int main_menu(void)
{
   /* Display main menu, get a choice and dispatch. Function returns
      zero normally, one on exit request. */

   char mname[] = "main_menu";
   char choice[25];

   // if link list is not empty, delete it

   if (event_head != EVENT_NULL)
      ll_delete();

   printf("using date %d/%02d\n", year_num, month_num);
   printf("1. change current date[yymm]\n");
   printf("2. add new events\n");
   printf("3. modify existing events\n");
   printf("4. summarize month\n");
   printf("5. add categories\n");
   printf("6. display events\n");
   printf("7. delete events\n");
   printf("8. copy month records\n");
   printf("9. exit program\n");
   get_input("9", choice);

   switch(choice[0])
      {
      case '1':
         // change date

         change_date();
   
         // when the date changes, close and re-open tables
  
         close_tables();
         
         if (!open_tables())
            {
            printf("reverting to current year and month\n");
            get_date();
            (void)open_tables();
            }
   
         break;

      case '2':
         add_events();
         break;

      case '3':
         modify_events();
         break;

      case '4':
         summarize_month();
         break;

      case '5':
         add_category();
         break;

      case '6':
         display_events(SCHED_FILTER_ALL);
         break;

      case '7':
         delete_events();
         break;

      case '8':
         copy_month();
         break;

      case '9':
         printf("exit requested\n");
         return(TRUE);

      default:
         printf("unknown menu option\n");
      };

   return(FALSE);
}

void change_date(void)
{
   // Change year and/or month.

   char value[25], buf[25];
   int choice;

   sprintf(buf, "%d", year_num);
   printf("year");
   get_input(buf, value);

   if (!strcmp(value, buf))
      printf("year not changed\n");
   else
      {
      if (!qatoi(value, &choice))
         {
         printf("year is not a number\n");
         return;
         }

      year_num = choice;
      }

   sprintf(buf, "%d", month_num);
   printf("month");
   get_input(buf, value);

   if (!strcmp(buf, value))
      printf("month not changed\n");
   else
      {
      if (!qatoi(value, &choice))
         {
         printf("month is not a number\n");
         return;
         }

      month_num = choice;
      }
}

void get_input(char *def, char *line)
{
   /* Get keyboard input. The 'def' will appy
      if the user simply presses enter. Function
      will call 'term_app' if the user types 'end'. */

   char *ptr, buf[80];

   if (line == (char *)NULL)
      {
      printf("get_input:null[line]\n");
      term_app();
      exit(0);
      }

   printf("[%s,'end' to quit]>", def);
   buf[0] = EOS;
   fgets(buf, 79, stdin);

   if ((ptr = strchr(buf, EOL)) != NULL)
      *ptr = EOS;

   if (!stricmp(buf, "END"))
      {
      printf("exit requested\n");
      term_app();
      exit(0);
      }
        
   if (!strlen(buf))
      {
      strcpy(line, def);
      return;
      }

   strcpy(line, buf);
}

void add_category(void)
{
   // Add category records.

   char buf[256], desc[256], mname[] = "add_category";
   int cat_id, type_int, ret;

   while(TRUE)
      {
      if (!display_categories())
         return;

      printf("description or 'q' to quit adding");
      get_input("q", buf);

      if (buf[0] == 'q' || buf[0] == 'Q')
         return;

      strcpy(desc, buf);
      ret = FALSE;

      while(!ret)
         {
         printf("calc type['d'ebit,'c'redit,'n'a] or 'q' to quit adding");
         get_input("q", buf);

         if (buf[0] == 'q' || buf[0] == 'Q')
            return;

         switch(buf[0])
            {
            case 'c':
            case 'C':
               type_int = CAT_TYPE_CREDIT;
               ret = TRUE;
               break;

            case 'd':
            case 'D':
               type_int = CAT_TYPE_DEBIT;
               ret = TRUE;
               break;

            case 'n':
            case 'N':
               type_int = CAT_TYPE_NA;
               ret = TRUE;
               break;

            default:
               printf("invalid calc type\n");
            };
         }

      if ((cat_id = get_category_count()) == 0)
         return;

      sprintf(buf, "%d", cat_id);

      if ((ret = db_put_field(sched_cat_tid, CAT_FIELD_NUM, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting category id[%s]\n", mname, buf);
         return;
         }

      if ((ret = db_put_field(sched_cat_tid, CAT_FIELD_DESC, desc)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting category description[%s]\n", mname, buf);
         return;
         }

      sprintf(buf, "%d", type_int);

      if ((ret = db_put_field(sched_cat_tid, CAT_FIELD_TYPE, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting category calc type[%s]\n", mname, buf);
         return;
         }

      if ((ret = db_write(sched_cat_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error writing category record[%s]\n", mname, buf);
         return;
         }

      sched_cat_changed = TRUE;
      }
}

void add_events(void)
{
   // Add event records.

   struct fxp *result;
   char buf[256], date[15], desc[256], cat_id[5], mname[] = "add_events";
   char planned[30], actual[30];
   int type_int, ret, modays, seq;

   while(TRUE)
      {
      if (!display_events(SCHED_FILTER_ALL))
         return;

      while(TRUE)
         {
         printf("day of month or 'q' to quit adding");
         get_input("q", buf);

         if (buf[0] == 'q' || buf[0] == 'Q')
            return;

         if (!qatoi(buf, &ret))
            {
            printf("%s:day of month not numeric\n", mname);
            continue;
            }

         modays = days_in_month(year_num, month_num);

         if (ret <= 0)
            {
            printf("%s:out of range day\n", mname);
            continue;
            }

         if (ret > modays)
            {
            printf("%s:out of range day\n", mname);
            continue;
            }

         seq = get_next_seq(ret);
         sprintf(date, "%02d%02d.%d", month_num, ret, seq);
         break;
         }

      printf("description or 'q' to quit adding");
      get_input("q", buf);

      if (buf[0] == 'q' || buf[0] == 'Q')
         return;

      strcpy(desc, buf);

      if (!get_event_category("q", cat_id))
         return;

      // if category type is budget, get planned and actual amounts

      if (!xref_category(cat_id, &type_int))
         return;

      if (type_int == CAT_TYPE_CREDIT || type_int == CAT_TYPE_DEBIT)
         {
         if (!get_event_aplanned("planned", "q", planned))
            return;

         if (!get_event_aplanned("actual", "q", actual))
            return;
         }

      if ((ret = db_new(sched_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error setting up new event record\n", mname);
         return;
         }
   
      if ((ret = db_put_field(sched_tid, SCHED_FIELD_DATE, date)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event date[%s]\n", mname, buf);
         return;
         }

      if ((ret = db_put_field(sched_tid, SCHED_FIELD_DESC, desc)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event desc[%s]\n", mname, buf);
         return;
         }

      if ((ret = db_put_field(sched_tid, SCHED_FIELD_CAT, cat_id)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event category id[%s]\n", mname, buf);
         return;
         }

      if (type_int == CAT_TYPE_CREDIT || type_int == CAT_TYPE_DEBIT)
         {
         if ((result = fxp_iconv(planned)) == FXP_OT_NULL)
            {
            printf("%s:error converting planned value to fxp\n", mname);
            return;
            }

         if ((ret = dbfxp_put(sched_tid, SCHED_FIELD_PLANNED, SCHED_FXP_SCALE, 
             result)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error putting event planned amount[%s]\n", mname, buf);
            return;
            }

         fxp_free(result);

         if ((result = fxp_iconv(actual)) == FXP_OT_NULL)
            {
            printf("%s:error converting actual value to fxp\n", mname);
            return;
            }

         if ((ret = dbfxp_put(sched_tid, SCHED_FIELD_ACTUAL, SCHED_FXP_SCALE, 
             result)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error putting event actual amount[%s]\n", mname, buf);
            return;
            }

         fxp_free(result);
         }

      if ((ret = db_write(sched_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error writing event record[%s]\n", mname, buf);
         return;
         }

      sched_changed = TRUE;
      }
}

void modify_events(void)
{
   // Modify event records.

   struct event_list *the_event;
   struct fxp *result;
   char buf[256], date[15], desc[256], cat_id[5], mname[] = "modify_events";
   char planned[30], actual[30];
   int type_int, ret, modays, seq, cat_typ, record_changed = FALSE;

   while(TRUE)
      {
      if (!display_events(SCHED_FILTER_ALL))
         return;

      while(TRUE)
         {
         printf("event number or 'q' to quit modifying");
         get_input("q", buf);

         if (buf[0] == 'q' || buf[0] == 'Q')
            return;

         if (!qatoi(buf, &ret))
            {
            printf("%s:event number not numeric\n", mname);
            continue;
            }

         if ((the_event = ll_locate(ret)) == EVENT_NULL)
            {
            printf("%s:no such event\n", mname);
            continue;
            }

         if ((ret = db_top(sched_tid)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error going to top of schedule table[%s]\n", mname, buf);
            return;
            }

         // locate the event record in the schedule table

         if ((ret = db_find_field(sched_tid, 0, the_event->id,
             SCHED_FIELD_DATE)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error locating event record[%s]\n", mname, buf);
            return;
            }

         break;
         }

      while(TRUE)
         {
         printf("day of month");
         strncpy(date, &the_event->id[2], 2);
         date[2] = EOS;
         get_input(date, buf);

         if (strcmp(date, buf))
            {
            if (!qatoi(buf, &ret))
               {
               printf("%s:day of month not numeric\n", mname);
               continue;
               }

            modays = days_in_month(year_num, month_num);

            if (ret <= 0)
               {
               printf("%s:out of range day\n", mname);
               continue;
               }

            if (ret > modays)
               {
               printf("%s:out of range day\n", mname);
               continue;
               }

            seq = get_next_seq(ret);
            sprintf(date, "%02d%02d.%d", month_num, ret, seq);

            if ((ret = db_put_field(sched_tid, SCHED_FIELD_DATE, date)) !=
                DBENG_OK)
               {
               db_io_code_string(ret, buf);
               printf("%s:error putting date field into record\n", mname);
               return;
               }

            record_changed = TRUE;
            break;
            }
         else
            break;
         }

      printf("description");

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_DESC, desc)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error getting desc[%s]\n", mname, buf);
         return;
         }

      get_input(desc, buf);

      if (strcmp(buf, desc))
         {
         if ((ret = db_put_field(sched_tid, SCHED_FIELD_DESC, buf)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error putting desc field into record\n", mname);
            return;
            }

         record_changed = TRUE;
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_CAT, cat_id)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error getting category[%s]\n", mname, buf);
         return;
         }

      if (!get_event_category(cat_id, buf))
         return;

      if (!xref_category(cat_id, &cat_typ))
         {
         printf("%s:error getting category type\n", mname);
         return;
         }

      if (strcmp(buf, cat_id))
         {
         if ((ret = db_put_field(sched_tid, SCHED_FIELD_CAT, buf)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error putting category field into record\n", mname);
            return;
            }

         record_changed = TRUE;
         }

      if (cat_typ != CAT_TYPE_NA)
         {
         if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_PLANNED, SCHED_FXP_SCALE,
             &result)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error getting planned field[%s][%s]\n", mname, buf, 
                   date);
            return;
            }

         if (!fxp_oconv(result, SCHED_FXP_SCALE, planned))
            {
            printf("%s:error converting planned field[%s]\n", mname, date);
            fxp_free(result);
            return;
            }

         fxp_free(result);

         if (!get_event_aplanned("planned", planned, buf))
            return;

         if (strcmp(buf, planned))
            {
            if ((result = fxp_iconv(buf)) == FXP_OT_NULL)
               {
               printf("%s:error converting planned value to fxp\n", mname);
               return;
               }

            if ((ret = dbfxp_put(sched_tid, SCHED_FIELD_PLANNED, 
                SCHED_FXP_SCALE, result)) != DBENG_OK)
               {
               db_io_code_string(ret, buf);
               printf("%s:error putting event planned amount[%s]\n", mname, 
                      buf);
               fxp_free(result);
               return;
               }

            fxp_free(result);
            record_changed = TRUE;
            }

         if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_ACTUAL, SCHED_FXP_SCALE,
             &result)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error getting actual field[%s][%s]\n", mname, buf, date);
            return;
            }

         if (!fxp_oconv(result, SCHED_FXP_SCALE, actual))
            {
            printf("%s:error converting actual field[%s]\n", mname, date);
            fxp_free(result);
            return;
            }

         fxp_free(result);

         if (!get_event_aplanned("actual", actual, buf))
            return;

         if (strcmp(buf, actual))
            {
            if ((result = fxp_iconv(buf)) == FXP_OT_NULL)
               {
               printf("%s:error converting actual value to fxp\n", mname);
               return;
               }

            if ((ret = dbfxp_put(sched_tid, SCHED_FIELD_ACTUAL, 
                SCHED_FXP_SCALE, result)) != DBENG_OK)
               {
               db_io_code_string(ret, buf);
               printf("%s:error putting event actual amount[%s]\n", mname, buf);
               fxp_free(result);
               return;
               }

            fxp_free(result);
            record_changed = TRUE;
            }
         }

      if (record_changed)
         {
         if ((ret = db_write(sched_tid)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error writing event record[%s]\n", mname, buf);
            return;
            }

         sched_changed = TRUE;
         }
      }
}

void delete_events(void)
{
   // Delete event records.

   struct event_list *the_event;
   char buf[256], date[15], desc[256], cat_id[5], mname[] = "delete_events";
   int ret, modays, seq, cat_typ, record_changed = FALSE;

   while(TRUE)
      {
      if (!display_events(SCHED_FILTER_ALL))
         return;

      while(TRUE)
         {
         printf("event number or 'q' to quit deleting");
         get_input("q", buf);

         if (buf[0] == 'q' || buf[0] == 'Q')
            return;

         if (!qatoi(buf, &ret))
            {
            printf("%s:event number not numeric\n", mname);
            continue;
            }

         if ((the_event = ll_locate(ret)) == EVENT_NULL)
            {
            printf("%s:no such event\n", mname);
            continue;
            }

         if ((ret = db_top(sched_tid)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error going to top of schedule table[%s]\n", mname, buf);
            return;
            }

         // locate the event record in the schedule table

         if ((ret = db_find_field(sched_tid, 0, the_event->id,
             SCHED_FIELD_DATE)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error locating event record[%s]\n", mname, buf);
            return;
            }

         break;
         }

      if ((ret = db_delete(sched_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error deleteing schedule record[%s]\n", mname, buf);
         return;
         }

      sched_changed = TRUE;
      }
}

int get_event_category(char *def, char *cat)
{
   /* Get the event category id from the user. First display a list of
      valid categories. Prompt default is in 'def'. Function returns
      'TRUE' upon success with the category id in 'cat'. Function
      returns 'FALSE' upon error or quit signal. */

   char cat_id[5], mes[128], mname[] = "get_event_category";
   int ret;

   while(TRUE)
      {
      if (!display_categories())
         return(FALSE);

      if (strlen(def) == 1 && (def[0] == 'q' || def[0] == 'Q'))
         {
         printf("category id or 'q' to quit adding");
         get_input("q", cat_id);

         if (cat_id[0] == 'q' || cat_id[0] == 'Q')
            return(FALSE);
         }
      else
         {
         printf("category id");
         get_input(def, cat_id);
         }

      if ((ret = db_top(sched_cat_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error going to top of category table[%s]\n", mname, mes);
         return(FALSE);
         }

      if ((ret = db_find_field(sched_cat_tid, 0, cat_id, CAT_FIELD_NUM)) !=
          DBENG_OK)
         {
         if (ret == DBENG_EOF)
            printf("%s:category id %s not found\n", mname, cat_id);
         else
            return(FALSE);
         }
      else
         break;
      }

   strcpy(cat, cat_id);
   return(TRUE);
}
   
int get_event_aplanned(char *actplan, char *def, char *out)
{
   /* Get a planned or actual amount from the user. Function returns
      the amount (with zero cents if necessary) in 'out' upon success
      with a 'TRUE' return code. Function returns 'FALSE' otherwise. */

   char buf[25], tmp[25], mname[] = "get_event_aplanned";
   int ret;

   while(TRUE)
      {
      buf[0] = EOS;

      if (strlen(def) == 1 && (def[0] == 'q' || def[0] == 'Q'))
         {
         printf("%s amount or 'q' to quit adding", actplan);
         get_input("q", buf);

         if (buf[0] == 'q' || buf[0] == 'Q')
            return(FALSE);
         }
      else
         {
         printf("%s amount", actplan);
         get_input(def, buf);
         }

      if (ll_words(buf, '.') > 1)
         {
         (void)ll_word(buf, tmp, 1, '.');

         if (!qatoi(tmp, &ret))
            {
            printf("%s:amount is not numeric\n", mname);
            continue;
            }

         (void)ll_word(buf, tmp, 2, '.');

         if (!qatoi(tmp, &ret))
            {
            printf("%s:amount is not numeric\n", mname);
            continue;
            }

         // one decimal place eg: 50.5

         if (strlen(tmp) == 1)
            {
            sprintf(tmp, "%s0", buf);
            strcpy(buf, tmp);
            }

         break;
         }
      else
         {
         // whole dollars

         if (!qatoi(buf, &ret))
            {
            printf("%s:amount is not numeric\n", mname);
            continue;
            }

         sprintf(tmp, "%s.00", buf);
         strcpy(buf, tmp);
         break;
         }
      }

   strcpy(out, buf);
   return(TRUE);
}

int display_categories(void)
{
   /* Display all current categories. Function returns 'TRUE' upon success,
      'FALSE' otherwise. */

   char num[10], desc[128], type[5], mname[] = "display_categories";
   char type_desc[30];
   int c_type, ret;

   if ((ret = db_top(sched_cat_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, desc);
      printf("%s:error going to top of category table[%s]\n", mname, desc);
      return(FALSE);
      }

   // error getting the first record is assumed to be no records,ok

   if ((ret = db_next(sched_cat_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, desc);
      printf("%s:no categories?[%s]\n", mname, desc);
      return(TRUE);
      }

   while(ret == DBENG_OK)
      {
      if ((ret = db_get_field(sched_cat_tid, CAT_FIELD_NUM, num)) != DBENG_OK)
         {
         db_io_code_string(ret, desc);
         printf("%s:error getting category count[%s]\n", mname, desc);
         return(FALSE);
         }

      // ignore counter record

      if (!strcmp(num, CAT_COUNT_REC))
         {
         ret = db_next(sched_cat_tid);
         continue;
         }

      if ((ret = db_get_field(sched_cat_tid, CAT_FIELD_DESC, desc)) != DBENG_OK)
         {
         db_io_code_string(ret, desc);
         printf("%s:error getting category count[%s]\n", mname, desc);
         return(FALSE);
         }

      if ((ret = db_get_field(sched_cat_tid, CAT_FIELD_TYPE, type)) != DBENG_OK)
         {
         db_io_code_string(ret, desc);
         printf("%s:error getting category count[%s]\n", mname, desc);
         return(FALSE);
         }

      if (!qatoi(type, &c_type))
         {
         printf("%s:category[%s] type is not numeric\n", mname, desc);
         return(FALSE);
         }

      switch(c_type)
         {
         case CAT_TYPE_CREDIT:
            sprintf(type_desc, "credit");
            break;

         case CAT_TYPE_DEBIT:
            sprintf(type_desc, "debit");
            break;
   
         case CAT_TYPE_NA:
            sprintf(type_desc, "not applicable");
            break;

         default:
            printf("%s:category[%s] type code is out of range\n", mname, desc);
            return(FALSE);
         };

      printf("id=%s,desc=%s,calc type=%s\n", num, desc, type_desc);
      ret = db_next(sched_cat_tid);
      }

   return(TRUE);
}

int display_events(int typ)
{
   /* Display all events for the current month and build the event link
      list. Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   struct fxp *planned, *actual;
   char date[15], desc[128], cat[5], mname[] = "display_events";
   char planned_st[15], actual_st[15], month[3];
   char planned_bal[15], actual_bal[15];
   int ret, has_bal, cat_typ, cnt = 1;

   // if schedule table has changed, sort it

   if (sched_changed)
      {
      sprintf(cat, "%d", SCHED_FIELD_DATE);
      printf("%s:sorting schedule table\n", mname);
      (void)db_sort(sched_tid, cat);
      sched_changed = FALSE;
      }

   // if link list is not empty, delete it

   if (event_head != EVENT_NULL)
      ll_delete();

   sprintf(month, "%02d", month_num);

   if ((ret = db_top(sched_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, desc);
      printf("%s:error going to top of event table[%s]\n", mname, desc);
      return(FALSE);
      }

   if ((ret = get_sched(typ, sched_tid, month_num)) != DBENG_OK)
      {
      db_io_code_string(ret, desc);
      printf("%s:no existing events?[%s]\n", mname, desc);
      return(TRUE);
      }

   while(ret == DBENG_OK)
      {
      if ((ret = db_get_field(sched_tid, SCHED_FIELD_DATE, date)) != DBENG_OK)
         {
         db_io_code_string(ret, desc);
         printf("%s:error getting date field[%s]\n", mname, desc);
         return(FALSE);
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_DESC, desc)) != DBENG_OK)
         {
         db_io_code_string(ret, desc);
         printf("%s:error getting description field[%s][%s]\n", mname, desc,
                date);
         return(FALSE);
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_CAT, cat)) != DBENG_OK)
         {
         db_io_code_string(ret, desc);
         printf("%s:error getting category field[%s][%s]\n", mname, desc, date);
         return(FALSE);
         }

      if (!xref_category(cat, &cat_typ))
         {
         printf("%s:error getting category type\n", mname);
         return(FALSE);
         }

      if (cat_typ != CAT_TYPE_NA)
         {
         if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_PLANNED, SCHED_FXP_SCALE,
             &planned)) != DBENG_OK)
            {
            db_io_code_string(ret, desc);
            printf("%s:error getting planned field[%s][%s]\n", mname, desc, 
                   date);
            return(FALSE);
            }

         if (!fxp_oconv(planned, SCHED_FXP_SCALE, planned_st))
            {
            printf("%s:error converting planned field[%s]\n", mname, date);
            fxp_free(planned);
            return(FALSE);
            }

         fxp_free(planned);

         if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_ACTUAL, SCHED_FXP_SCALE,
             &actual)) != DBENG_OK)
            {
            db_io_code_string(ret, desc);
            printf("%s:error getting actual field[%s][%s]\n", mname, desc, 
                   date);
            return(FALSE);
            }

         if (!fxp_oconv(actual, SCHED_FXP_SCALE, actual_st))
            {
            printf("%s:error converting actual field[%s]\n", mname, date);
            fxp_free(actual);
            return(FALSE);
            }

         fxp_free(actual);
         has_bal = TRUE;

         // only display balance fields if they are present

         if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_PLANNED_BAL, 
             SCHED_FXP_SCALE, &planned)) != DBENG_OK)
            has_bal = FALSE;

         if (has_bal)
            {
            if (!fxp_oconv(planned, SCHED_FXP_SCALE, planned_bal))
               {
               printf("%s:error converting planned bal field[%s]\n", mname, 
                      date);
               fxp_free(planned);
               return(FALSE);
               }

            fxp_free(planned);

            if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_ACTUAL_BAL, 
                SCHED_FXP_SCALE, &actual)) != DBENG_OK)
               {
               db_io_code_string(ret, desc);
               printf("%s:error getting actual bal field[%s][%s]\n", mname, 
                      desc, date);
               return(FALSE);
               }

            if (!fxp_oconv(actual, SCHED_FXP_SCALE, actual_bal))
               {
               printf("%s:error converting actual bal field[%s]\n", mname, 
                      date);
               fxp_free(actual);
               return(FALSE);
               }

            fxp_free(actual);
            }
         }

      if (!ll_add(date))
         {
         printf("%s:error adding to link list[%s]\n", mname, date);
         return(FALSE);
         }

      if (cat_typ == CAT_TYPE_NA)
         printf("%d:%s,%s,%s\n", cnt++, date, desc, cat);
      else
         {
         if (has_bal)
            printf("%d:%s,%s,%s,%s,%s,%s,%s\n", cnt++, date, desc, cat, 
                   planned_st, planned_bal, actual_st, actual_bal);
         else
            printf("%d:%s,%s,%s,%s,%s\n", cnt++, date, desc, cat, planned_st, 
                   actual_st);
         }
         
      ret = get_sched(typ, sched_tid, month_num);
      }

   return(TRUE);
}

int summarize_month(void)
{
   // Perform calcs on current month and display.

   struct fxp *bal_planned, *bal_actual, *ftmp;
   struct fxp *planned, *actual;
   struct fxp *ptot_credit, *atot_credit, *ptot_debit, *atot_debit;
   char sptot_credit[10], satot_credit[10], sptot_debit[10], satot_debit[10];
   char date[15], cat[5], mes[128], month[3], mname[] = "summarize_month";
   int tmp_tid, found_open_bal, cat_typ, ret;
   int credit_cnt, debit_cnt;

   sprintf(month, "%02d", month_num);

   if ((ret = db_top(sched_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:error going to top of event table[%s]\n", mname, mes);
      return(FALSE);
      }

   // error getting the first record is assumed to be no records,ok

   if ((ret = get_sched(SCHED_FILTER_BUDGET, sched_tid, month_num)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:no existing events?[%s]\n", mname, mes);
      return(TRUE);
      }

   // load planned and actual open balance

   found_open_bal = FALSE;

   while(ret == DBENG_OK)
      {
      if ((ret = db_get_field(sched_tid, SCHED_FIELD_DATE, date)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting date field[%s]\n", mname, mes);
         return(FALSE);
         }

      // open balance is the only record in the month with category 6

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_CAT, cat)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting category field[%s][%s]\n", mname, mes, date);
         return(FALSE);
         }

      if (strcmp(cat, "6"))
         {
         ret = get_sched(SCHED_FILTER_BUDGET, sched_tid, month_num);
         continue;
         }

      if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_PLANNED, SCHED_FXP_SCALE,
          &bal_planned)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting planned field[%s][%s]\n", mname, mes, date);
         return(FALSE);
         }

      if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_ACTUAL, SCHED_FXP_SCALE,
          &bal_actual)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting actual field[%s][%s]\n", mname, mes, date);
         fxp_free(bal_planned);
         return(FALSE);
         }

      found_open_bal = TRUE;
      break;
      }

   // cannot calc if no open balance for the month

   if (!found_open_bal)
      {
      printf("%s:open balance record not found,cannot calculate\n", mname);
      return(FALSE);
      }

   if ((ret = db_top(sched_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:error going to top of event table[%s]\n", mname, mes);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      return(FALSE);
      }

   if ((ret = get_sched(SCHED_FILTER_BUDGET, sched_tid, month_num)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:no existing events?[%s]\n", mname, mes);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      return(FALSE);
      }

   // clear schedule temporary table

   if ((ret = db_clear_table(SCHED_TMP_NAME)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:unable to clear schedule tmp table[%s]\n", mname, mes);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      return(FALSE);
      }

   // open schedule temporary table

   if ((ret = db_open(SCHED_TMP_NAME, &tmp_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, mes);
      printf("%s:unable to open schedule tmp table[%s]\n", mname, mes);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      return(FALSE);
      }

   // initialize all total accumulators

   if ((ptot_credit = fxp_iconv("0.00")) == FXP_OT_NULL)
      {
      printf("%s:error initializing ptot_credit\n", mname);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      return(FALSE);
      }

   if ((atot_credit = fxp_iconv("0.00")) == FXP_OT_NULL)
      {
      printf("%s:error initializing atot_credit\n", mname);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      fxp_free(ptot_credit);
      return(FALSE);
      }

   if ((ptot_debit = fxp_iconv("0.00")) == FXP_OT_NULL)
      {
      printf("%s:error initializing ptot_debit\n", mname);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      fxp_free(ptot_credit);
      fxp_free(atot_credit);
      return(FALSE);
      }

   if ((atot_debit = fxp_iconv("0.00")) == FXP_OT_NULL)
      {
      printf("%s:error initializing atot_debit\n", mname);
      fxp_free(bal_planned);
      fxp_free(bal_actual);
      fxp_free(ptot_credit);
      fxp_free(atot_credit);
      fxp_free(ptot_debit);
      return(FALSE);
      }

   credit_cnt = debit_cnt = 0;

   /* loop through all month event records calculating while ignoring
      the open balance record, write balances to the temporary table */

   while(ret == DBENG_OK)
      {
      if ((ret = db_get_field(sched_tid, SCHED_FIELD_DATE, date)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting date field[%s]\n", mname, mes);
         fxp_free(bal_planned);
         fxp_free(bal_actual);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         (void)db_close(tmp_tid);
         return(FALSE);
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_CAT, cat)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting category field[%s][%s]\n", mname, mes, date);
         fxp_free(bal_planned);
         fxp_free(bal_actual);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         (void)db_close(tmp_tid);
         return(FALSE);
         }

      // ignore balance record

      if (!strcmp(cat, "6"))
         {
         ret = get_sched(SCHED_FILTER_BUDGET, sched_tid, month_num);
         continue;
         }

      // lookup category type

      if (!xref_category(cat, &cat_typ))
         {
         fxp_free(bal_planned);
         fxp_free(bal_actual);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         (void)db_close(tmp_tid);
         return(FALSE);
         }

      if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_PLANNED, SCHED_FXP_SCALE,
          &planned)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting planned field[%s][%s]\n", mname, mes, date);
         fxp_free(bal_planned);
         fxp_free(bal_actual);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         (void)db_close(tmp_tid);
         return(FALSE);
         }

      if ((ret = dbfxp_get(sched_tid, SCHED_FIELD_ACTUAL, SCHED_FXP_SCALE,
          &actual)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting actual field[%s][%s]\n", mname, mes, date);
         fxp_free(bal_planned);
         fxp_free(bal_actual);
         fxp_free(planned);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         (void)db_close(tmp_tid);
         return(FALSE);
         }

      if ((ret = db_put_field(tmp_tid, SCHED_TMP_FIELD_DATE, date)) !=
          DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error putting date field[%s][%s]\n", mname, mes, date);
         fxp_free(bal_planned);
         fxp_free(bal_actual);
         fxp_free(planned);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         (void)db_close(tmp_tid);
         return(FALSE);
         }

      if (cat_typ == CAT_TYPE_CREDIT)
         {
         credit_cnt++;

         if ((ftmp = fxp_add(bal_planned, planned)) == FXP_OT_NULL)
            {
            printf("%s:error adding planned and balance planned\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }

         fxp_free(bal_planned);
         bal_planned = ftmp;

         if ((ftmp = fxp_add(ptot_credit, planned)) == FXP_OT_NULL)
            {
            printf("%s:error adding planned and total credit planned\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }
            
         fxp_free(ptot_credit);
         ptot_credit = ftmp;

         if ((ret = dbfxp_put(tmp_tid, SCHED_TMP_FIELD_PLANNED_BAL, 
             SCHED_FXP_SCALE, bal_planned)) != DBENG_OK)
            {
            db_io_code_string(ret, mes);
            printf("%s:error putting planned balance amount[%s]\n", mname, mes);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }

         if ((ftmp = fxp_add(bal_actual, actual)) == FXP_OT_NULL)
            {
            printf("%s:error adding actual and balance actual\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }

         fxp_free(bal_actual);
         bal_actual = ftmp;

         if ((ftmp = fxp_add(atot_credit, actual)) == FXP_OT_NULL)
            {
            printf("%s:error adding actual and total credit actual\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }
            
         fxp_free(atot_credit);
         atot_credit = ftmp;

         if ((ret = dbfxp_put(tmp_tid, SCHED_TMP_FIELD_ACTUAL_BAL, 
             SCHED_FXP_SCALE, bal_actual)) != DBENG_OK)
            {
            db_io_code_string(ret, mes);
            printf("%s:error putting actual balance amount[%s]\n", mname, mes);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }
         }
      else
         {
         debit_cnt++;

         if ((ftmp = fxp_sub(bal_planned, planned)) == FXP_OT_NULL)
            {
            printf("%s:error subtracting planned and balance planned\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }

         fxp_free(bal_planned);
         bal_planned = ftmp;

         if ((ftmp = fxp_add(ptot_debit, planned)) == FXP_OT_NULL)
            {
            printf("%s:error adding planned and total debit planned\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }
            
         fxp_free(ptot_debit);
         ptot_debit = ftmp;

         if ((ret = dbfxp_put(tmp_tid, SCHED_TMP_FIELD_PLANNED_BAL, 
             SCHED_FXP_SCALE, bal_planned)) != DBENG_OK)
            {
            db_io_code_string(ret, mes);
            printf("%s:error putting planned balance amount[%s]\n", mname, mes);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }

         if ((ftmp = fxp_sub(bal_actual, actual)) == FXP_OT_NULL)
            {
            printf("%s:error subtracting actual and balance actual\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }

         fxp_free(bal_actual);
         bal_actual = ftmp;

         if ((ftmp = fxp_add(atot_debit, actual)) == FXP_OT_NULL)
            {
            printf("%s:error adding actual and total debit actual\n", mname);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }
            
         fxp_free(atot_debit);
         atot_debit = ftmp;

         if ((ret = dbfxp_put(tmp_tid, SCHED_TMP_FIELD_ACTUAL_BAL, 
             SCHED_FXP_SCALE, bal_actual)) != DBENG_OK)
            {
            db_io_code_string(ret, mes);
            printf("%s:error putting actual balance amount[%s]\n", mname, mes);
            fxp_free(bal_planned);
            fxp_free(bal_actual);
            fxp_free(planned);
            fxp_free(actual);
            fxp_free(ptot_credit);
            fxp_free(atot_credit);
            fxp_free(ptot_debit);
            fxp_free(atot_debit);
            (void)db_close(tmp_tid);
            return(FALSE);
            }
         }

      fxp_free(planned);
      fxp_free(actual);

      if ((ret = db_write(tmp_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error writing tmp schedule record[%s]\n", mname, mes);
         fxp_free(bal_planned);
         fxp_free(bal_actual);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         (void)db_close(tmp_tid);
         return(FALSE);
         }

      ret = get_sched(SCHED_FILTER_BUDGET, sched_tid, month_num);
      }

   fxp_free(bal_planned);
   fxp_free(bal_actual);
   (void)db_top(tmp_tid);
   (void)db_next(tmp_tid);
   ret = DBENG_OK;

   // copy temporary table balances to schedule table

   while(ret == DBENG_OK)
      {
      if ((ret = db_get_field(tmp_tid, SCHED_TMP_FIELD_DATE, date)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting date field[%s]\n", mname, mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      if ((ret = db_top(sched_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error going to top of schedule table[%s]\n", mname, mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      if ((ret = db_find_field(sched_tid, 0, date, SCHED_FIELD_DATE)) !=
          DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error locating schedule table record[%s]\n", mname, mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      if ((ret = db_get_field(tmp_tid, SCHED_TMP_FIELD_PLANNED_BAL, mes)) != 
         DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting tmp sched planned bal field[%s]\n", mname, 
                mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      if ((ret = db_put_field(sched_tid, SCHED_FIELD_PLANNED_BAL, mes)) != 
         DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error putting sched planned bal field[%s]\n", mname, mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      if ((ret = db_get_field(tmp_tid, SCHED_TMP_FIELD_ACTUAL_BAL, mes)) != 
         DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting tmp sched actual bal field[%s]\n", mname, 
                mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      if ((ret = db_put_field(sched_tid, SCHED_FIELD_ACTUAL_BAL, mes)) != 
         DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error putting sched actual bal field[%s]\n", mname, mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      if ((ret = db_write(sched_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error writing schedule record[%s]\n", mname, mes);
         (void)db_close(tmp_tid);
         fxp_free(ptot_credit);
         fxp_free(atot_credit);
         fxp_free(ptot_debit);
         fxp_free(atot_debit);
         return(FALSE);
         }

      ret = db_next(tmp_tid);
      }

   (void)db_close(tmp_tid);
   sprintf(mes, "%d", SCHED_FIELD_DATE);
   printf("%s:sorting schedule table\n", mname);
   (void)db_sort(sched_tid, mes);
   (void)display_events(SCHED_FILTER_BUDGET);

   // calc and display monthly summary
   
   if (!fxp_oconv(ptot_credit, SCHED_FXP_SCALE, sptot_credit))
      {
      printf("%s:error converting planned total credit\n", mname);
      fxp_free(ptot_credit);
      fxp_free(atot_credit);
      fxp_free(ptot_debit);
      fxp_free(atot_debit);
      return(FALSE);
      }

   logman("%s:before atot_credit oconv\n", mname);
   fxp_debug(atot_credit);

   if (!fxp_oconv(atot_credit, SCHED_FXP_SCALE, satot_credit))
      {
      printf("%s:error converting actual total credit\n", mname);
      fxp_free(ptot_credit);
      fxp_free(atot_credit);
      fxp_free(ptot_debit);
      fxp_free(atot_debit);
      return(FALSE);
      }

   if (!fxp_oconv(ptot_debit, SCHED_FXP_SCALE, sptot_debit))
      {
      printf("%s:error converting planned total debit\n", mname);
      fxp_free(ptot_credit);
      fxp_free(atot_credit);
      fxp_free(ptot_debit);
      fxp_free(atot_debit);
      return(FALSE);
      }

   if (!fxp_oconv(atot_debit, SCHED_FXP_SCALE, satot_debit))
      {
      printf("%s:error converting actual total debit\n", mname);
      fxp_free(ptot_credit);
      fxp_free(atot_credit);
      fxp_free(ptot_debit);
      fxp_free(atot_debit);
      return(FALSE);
      }

   printf("%s:%d credit event(s),%d debit event(s)\n", mname, credit_cnt,
          debit_cnt);
   printf("%s:totals\n", mname);
   printf("%s:credit:planned=%s,actual=%s\n", mname, sptot_credit, 
          satot_credit);
   printf("%s:debit:planned=%s,actual=%s\n", mname, sptot_debit,
          satot_debit);
   fxp_free(ptot_credit);
   fxp_free(atot_credit);
   fxp_free(ptot_debit);
   fxp_free(atot_debit);
   return(TRUE);
}

void copy_month(void)
{
   // Copy the current month records to a new year/month.

   char *trec, value[25], buf[256], mname[] = "copy_month";
   int dest_year, dest_month, dest_tid, ret;

   // get destination year and month
   // suggest year based on current year unless this is month 12

   if (month_num == 12)
      sprintf(buf, "%d", year_num + 1);
   else
      sprintf(buf, "%d", year_num);

   printf("destination year");
   get_input(buf, value);

   if (!qatoi(value, &dest_year))
      {
      printf("%s:year is not a number\n", mname);
      return;
      }

   // suggest next month

   if (month_num == 12)
      strcpy(buf, "1");
   else
      sprintf(buf, "%d", month_num + 1);

   printf("destination month");
   get_input(buf, value);

   if (!qatoi(value, &dest_month))
      {
      printf("%s:month is not a number\n", mname);
      return;
      }

   // don't allow copy to current year/month

   if (dest_year == year_num && dest_month == month_num)
      {
      printf("%s:cannot copy to current month\n", mname);
      return;
      }

   // open destination table, ok if it is current year

   sprintf(value, "%s.%d", SCHED_NAME, dest_year);

   if ((ret = db_open(value, &dest_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:open of %s failed[%s]\n", mname, value, buf);
      return;
      }

   /* attempt to find existing destination records and ask to delete
      if they exist */

   if ((ret = db_top(dest_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error going to top of event table[%s]\n", mname, buf);
      return;
      }

   if ((ret = get_sched(SCHED_FILTER_ALL, dest_tid, dest_month)) == DBENG_OK)
      {
      if (!getyn("destination month records exist,ok to delete"))
         {
         (void)db_close(dest_tid);
         return;
         }

      while(ret == DBENG_OK)
         {
         if ((ret = db_delete(dest_tid)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error deleting sched record[%s]\n", mname, buf);
            (void)db_close(dest_tid);
            return;
            }

         if ((ret = db_top(dest_tid)) != DBENG_OK)
            {
            db_io_code_string(ret, buf);
            printf("%s:error going to top of event table[%s]\n", mname, buf);
            (void)db_close(dest_tid);
            return;
            }

         ret = get_sched(SCHED_FILTER_ALL, dest_tid, dest_month);
         }
      }

   /* get each source record, change it's ID, zero out balances and write
      it as a destination record */

   if ((ret = db_top(sched_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error going to top of event table[%s]\n", mname, buf);
      (void)db_close(dest_tid);
      return;
      }

   if ((ret = get_sched(SCHED_FILTER_ALL, sched_tid, month_num)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:no existing source events?[%s]\n", mname, buf);
      (void)db_close(dest_tid);
      return;
      }

   sprintf(value, "%02d", dest_month);

   while(ret == DBENG_OK)
      {
      if ((ret = db_new(dest_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error setting up new event record[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_DATE, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error getting date field[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      buf[0] = value[0];
      buf[1] = value[1];

      if ((ret = db_put_field(dest_tid, SCHED_FIELD_DATE, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event date[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_DESC, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error getting desc field[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_put_field(dest_tid, SCHED_FIELD_DESC, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event desc[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_CAT, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error getting category field[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_put_field(dest_tid, SCHED_FIELD_CAT, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event category[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_get_field(sched_tid, SCHED_FIELD_PLANNED, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error getting planned field[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_put_field(dest_tid, SCHED_FIELD_PLANNED, buf)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event planned[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_put_field(dest_tid, SCHED_FIELD_ACTUAL, "000")) != 
          DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event actual value[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_put_field(dest_tid, SCHED_FIELD_PLANNED_BAL, "000")) != 
          DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event planned bal[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_put_field(dest_tid, SCHED_FIELD_ACTUAL_BAL, "000")) != 
          DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error putting event actual bal[%s]\n", mname, buf);
         (void)db_close(dest_tid);
         return;
         }

      if ((ret = db_write(dest_tid)) != DBENG_OK)
         {
         db_io_code_string(ret, buf);
         printf("%s:error writing destination record[%s]\n", mname, buf);
         return;
         }

      ret = get_sched(SCHED_FILTER_ALL, sched_tid, month_num);
      }

   sprintf(buf, "%d", SCHED_FIELD_DATE);
   printf("%s:sorting schedule table\n", mname);
   (void)db_sort(dest_tid, buf);
   (void)db_close(dest_tid);
}

int get_next_seq(int day)
{
   /* Calc the next event sequence number. Function returns the
      sequence number upon success, zero otherwise. */

   struct event_list *rov = event_head;
   char date[15], seq[5];
   int seq_int = 0, tmp;

   // if the link list is empty, start at one

   if (rov == EVENT_NULL)
      return(1);

   sprintf(date, "%02d%02d", month_num, day);

   while(rov != EVENT_NULL)
      {
      if (!strncmp(date, rov->id, 4))
         {
         (void)ll_word(rov->id, seq, 2, '.');
         (void)qatoi(seq, &tmp);

         if (tmp > seq_int)
            seq_int = tmp;
         }

      rov = rov->next;
      }

   seq_int++;
   return(seq_int);
}

int xref_category(char *cat, int *typ)
{
   /* Lookup a category and return the category type. Function
      returns 'TRUE' upon success, 'FALSE' otherwise. */

   char buf[128], mname[] = "xref_category";
   int ret;

   if (typ == (int *)NULL)
      {
      printf("%s:null[typ]\n", mname);
      return(FALSE);
      }

   *typ = -1;

   if ((ret = db_top(sched_cat_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error going to top of category table[%s]\n", mname, buf);
      return(FALSE);
      }
      
   if ((ret = db_find_field(sched_cat_tid, 0, cat, CAT_FIELD_NUM)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error locating category record[%s]\n", mname, buf);
      return(FALSE);
      }

   if ((ret = db_get_field(sched_cat_tid, CAT_FIELD_TYPE, buf)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error getting category type[%s]\n", mname, buf);
      return(0);
      }

   if (!qatoi(buf, &ret))
      {
      printf("%s:category type is not numeric\n", mname);
      return(FALSE);
      }

   *typ = ret;
   return(TRUE);
}

int get_category_count(void)
{
   /* Locate the category table counter record, retrieve the count,
      increment it and re-write the counter record. Function returns
      the category number upon success, zero otherwise. */

   char buf[25], mname[] = "get_category_count";
   int cnt, ret;

   if ((ret = db_top(sched_cat_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error going to top of category table[%s]\n", mname, buf);
      return(0);
      }
      
   if ((ret = db_find_field(sched_cat_tid, 0, CAT_COUNT_REC, 1)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error locating category count record[%s]\n", mname, buf);
      return(0);
      }

   if ((ret = db_get_field(sched_cat_tid, 2, buf)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error getting category count[%s]\n", mname, buf);
      return(0);
      }

   if (!qatoi(buf, &cnt))
      {
      printf("%s:category count is not numeric\n", mname);
      return(0);
      }

   cnt++;
   sprintf(buf, "%d", cnt);

   if ((ret = db_put_field(sched_cat_tid, 2, buf)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error putting category count[%s]\n", mname, buf);
      return(0);
      }

   if ((ret = db_write(sched_cat_tid)) != DBENG_OK)
      {
      db_io_code_string(ret, buf);
      printf("%s:error writing category count record[%s]\n", mname, buf);
      return(0);
      }

   sched_cat_changed = TRUE;
   return(cnt);
}

int get_sched(int filter, int tid, int amonth)
{
   /* Get next record from the schedule table 'tid' and make sure it 
      applies only to the month 'amonth' and apply the 'filter'
      (see 'SCHED_FILTER' types).
      Function returns a 'bbuuzzb' code. */

   char month[3], date[15], mes[128], cat[3], mname[] = "get_sched";
   int ret, cat_typ;

   sprintf(month, "%02d", amonth);

   if ((ret = db_next(tid)) != DBENG_OK)
      return(ret);

   while(ret == DBENG_OK)
      {
      if ((ret = db_get_field(tid, SCHED_FIELD_DATE, date)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting date field[%s]\n", mname, mes);
         return(ret);
         }
   
      // only include current month events

      if (strncmp(month, date, 2))
         {
         ret = db_next(tid);
         continue;
         }

      // if filter is set to all in this month,exit

      if (filter == SCHED_FILTER_ALL)
         return(DBENG_OK);

      if ((ret = db_get_field(tid, SCHED_FIELD_CAT, cat)) != DBENG_OK)
         {
         db_io_code_string(ret, mes);
         printf("%s:error getting category field[%s][%s]\n", mname, mes, date);
         return(ret);
         }
         
      if (!xref_category(cat, &cat_typ))
         {
         printf("%s:error getting category type\n", mname);
         return(DBENG_INVALID_FUNCTION);
         }

      switch(filter)
         {
         case SCHED_FILTER_BUDGET:
            if (cat_typ == CAT_TYPE_CREDIT || cat_typ == CAT_TYPE_DEBIT)
               return(DBENG_OK);

            break;

         case SCHED_FILTER_OTHER:
            if (cat_typ == CAT_TYPE_NA)
               return(DBENG_OK);
   
            break;

         default:
            printf("%s:unknown filter\n", mname);
            return(DBENG_INVALID_FUNCTION);
         };

      ret = db_next(tid);
      }

   return(ret);
}

struct event_list *ll_locate(int seq)
{
   /* Locate an entry in the event link list by its entry or
      sequence number. Function returns a pointer to the entry
      upon success, a NULL pointer (EVENT_NULL) otherwise. */

   struct event_list *rov = event_head;
   int cnt = 1;

   if (rov == EVENT_NULL)
      return(EVENT_NULL);

   if (seq <= 0)
      return(EVENT_NULL);

   while(rov != EVENT_NULL)
      {
      if (cnt == seq)
         return(rov);

      cnt++;
      rov = rov->next;
      }

   // if we get here,out of entries

   return(EVENT_NULL);
}

int ll_add(char *date)
{
   /* Add another node to the event link list. Function returns
      'TRUE' upon success, 'FALSE' otherwise. */

   struct event_list *list_entry;
   struct event_list *rov;
   char mname[] = "ll_add";
   int ret, len, size;

   size = sizeof(struct event_list);

   if ((list_entry = (struct event_list *)malloc(size)) == EVENT_NULL)
      {
      printf("%s:memory fail[list_entry]\n", mname);
      return(FALSE);
      }

   strcpy(list_entry->id, date);
   list_entry->next = EVENT_NULL;

   /* set head of list if this is the first entry */

   if (event_head == EVENT_NULL)
      {
      event_head = list_entry;
      return(TRUE);
      }

   /* find last entry and hook into it */

   rov = event_head;

   while(rov->next != EVENT_NULL)
      rov = rov->next;

   rov->next = list_entry;
   return(TRUE);
}

void ll_delete(void)
{
   // Delete the entire event link list.

   struct event_list *rov, *tmp;

   rov = event_head;

   while(rov != EVENT_NULL)
      {
      tmp = rov->next;
      free(rov);
      rov = tmp;
      }

   event_head = EVENT_NULL;
}

void close_tables(void)
{
   // Sort tables (if changed) and close them.

   char spec[2];

   if (sched_changed)
      {
      sprintf(spec, "%d", SCHED_FIELD_DATE);
      printf("sorting schedule table\n");
      (void)db_sort(sched_tid, spec);
      sched_changed = FALSE;
      }

   if (sched_cat_changed)
      {
      sprintf(spec, "%d", CAT_FIELD_NUM);
      printf("sorting schedule category table\n");
      (void)db_sort(sched_cat_tid, spec);
      sched_cat_changed = FALSE;
      }

   (void)db_close(sched_tid);
   (void)db_close(sched_cat_tid);
}

void term_app(void)
{
   /* Terminate the application. Shutdown all IPC API's
      and exit. */

   close_tables();
   printf("terminating application\n");
   db_end();

   if (logman_is_active())
      logman_end();

#ifdef IPC_TCP
   if (sloc_is_init())
      sloc_term_api();

#ifdef OS_WIN32
   WSACleanup();
#endif
#endif

#ifndef OS_DOS
   (void)appinit_remove_name();
#endif
}
