## root/source/sched.c

/* [<][>][^][v][top][bottom][index][help] */

## DEFINITIONS

This source file includes following definitions.

1. main
2. get_date
3. c_initialize
4. c_initialize
5. c_ipc_socloc_init
6. open_tables
7. main_menu
8. change_date
9. get_input
10. add_category
11. add_events
12. modify_events
13. delete_events
14. get_event_category
15. get_event_aplanned
16. display_categories
17. display_events
18. summarize_month
19. copy_month
20. get_next_seq
21. xref_category
22. get_category_count
23. get_sched
24. ll_locate
25. ll_add
26. ll_delete
27. close_tables
28. term_app

    /* 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
    }

/* [<][>][^][v][top][bottom][index][help] */