#!/usr/bin/perl -w
#
# $Header: /var/cvs/batts/bin/batts,v 1.62 2001/11/29 23:15:13 lasser Exp $
#
# BATTS is the Barnhard Associates Trouble Ticket System.
# This is the rewritten, code-sharing BATTS interface.
#
# BATTS is copyright (C) 2001 Jon Lasser <jon@cluestickconsulting.com>
#
#   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, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
#   02111-1307 USA
#
# $Log: batts,v $
# Revision 1.62  2001/11/29 23:15:13  lasser
# Documentation continues apace; GPL included and relevant info included
# at top of BATTS programs
#
# Revision 1.61  2001/11/29 19:20:54  lasser
# Implemented mailing of ticket resolutions to customer. Default is on,
# use --nomail to disable.
#
# Revision 1.60  2001/11/29 17:38:43  lasser
# Fix argument-processing bugs
#
# Revision 1.59  2001/11/28 20:14:17  lasser
# accept new sendmail option
#
# Revision 1.58  2001/11/28 18:26:51  lasser
# Make it work mostly, even in /etc/smrsh --- still problems with calling sendmail
# and thus full and pretty From: header support.
#
# Revision 1.57  2001/11/28 16:53:17  lasser
# Proper headers on the return mail, changes to configuration file stuff
#
# Revision 1.56  2001/11/28 14:43:53  lasser
# If there are log entries for a ticket, mention this when showing the
# ticket.
#
# Revision 1.55  2001/11/28 14:18:32  lasser
# requisite improvements to the ticket-via-email interface.
#
# Revision 1.54  2001/11/26 22:27:53  lasser
# All commands except shell from initial spec completed
#
# Revision 1.53  2001/11/26 21:51:44  lasser
# more bugs fixed
#
# Revision 1.52  2001/11/26 20:55:58  lasser
# bugs fixed from variable renaming
#
# Revision 1.51  2001/11/26 20:37:22  lasser
# Great Variable Renaming completed, but not tested.
#
# Revision 1.50  2001/11/26 17:53:37  lasser
# fixed the update-ticket logic, started clarifying all variable names
#
# Revision 1.49  2001/11/25 02:00:27  lasser
# quoting fixed, separated routines that take input from stdin from their
# effects, resolve and close now resolve subsidary tickets
#
# Revision 1.48  2001/11/22 02:24:33  lasser
# text display of event logs
#
# Revision 1.47  2001/11/21 22:50:39  lasser
# all log bits done except non-HTML display
#
# Revision 1.46  2001/11/21 21:18:21  lasser
# yesterday's changes
#
# Revision 1.45  2001/11/20 23:46:34  lasser
# Added requestor for tickets, and appropriate features
#
# Revision 1.44  2001/11/20 22:24:07  lasser
# added longdesc and shortdesc editing
#
# Revision 1.43  2001/11/20 21:35:04  lasser
# added category-ticket, cleaned up cruft in customer-ticket
#
# Revision 1.42  2001/11/20 19:55:14  lasser
# added basic-level support for billing codes
#
# Revision 1.41  2001/11/20 17:17:48  lasser
# fix too-many-spaces problem in ticket descriptions and resolutions
#
# Revision 1.40  2001/11/20 17:02:47  lasser
# implemented status-ticket, set assigned status when assigning a ticket
#
# Revision 1.39  2001/11/20 16:04:47  lasser
# capitalization and spacing bugfixes in user creation and via email
#
# Revision 1.38  2001/11/20 15:46:59  lasser
# fix capitalization issues
#
# Revision 1.37  2001/11/20 04:19:58  lasser
# removed basically-useless person_id function
#
# Revision 1.36  2001/11/20 03:44:33  lasser
# Revert to transitional DTD
#
# Revision 1.35  2001/11/20 03:42:36  lasser
# use non-transitional DTD
#
# Revision 1.34  2001/11/20 03:37:27  lasser
# Fully compliant HTML tables
#
# Revision 1.33  2001/11/20 03:26:23  lasser
# work on producing valid HTML
#
# Revision 1.32  2001/11/20 00:10:14  lasser
# Remove deprecated commands, finish alphebetization
#
# Revision 1.31  2001/11/19 23:47:13  lasser
# alphebetization of public functions in all three places
#
# Revision 1.30  2001/11/19 22:32:09  lasser
# updates, etc.
#
# Revision 1.29  2001/11/19 22:10:51  lasser
# all ticket-summary printing now uses the generic version
#
# Revision 1.28  2001/11/19 21:41:39  lasser
# Better customer support bits
#
# Revision 1.27  2001/11/19 20:58:11  lasser
# changes to customer table
#
# Revision 1.26  2001/11/19 20:46:50  lasser
# more ticket layout change fixes
#
# Revision 1.25  2001/11/19 20:32:47  lasser
# change to person table structure
#
# Revision 1.24  2001/11/19 20:25:59  lasser
# Change batts to work with new table layout
#
# Revision 1.23  2001/11/19 19:33:53  lasser
# Fix quoting bugs in new ticket creation
#
# Revision 1.22  2001/11/19 18:04:53  lasser
# prettier HTML output; start of changes to ticket-via-email
#
# Revision 1.21  2001/11/19 17:54:28  lasser
# html-my assimilated; thus the end of phase one.
#
# Revision 1.20  2001/11/17 00:03:55  lasser
# ticket-new assimilated
#
# Revision 1.19  2001/11/16 22:36:35  lasser
# person-new assimilated
#
# Revision 1.18  2001/11/16 22:28:11  lasser
# person-name integrated
#
# Revision 1.17  2001/11/16 22:11:52  lasser
# assign-ticket integrated
#
# Revision 1.16  2001/11/16 21:59:24  lasser
# Add a help command
#
# Revision 1.15  2001/11/16 21:32:54  lasser
# Subroutines now correctly prototyped, serious bug in commands requiring
# arguments now fixed.
#
# Revision 1.14  2001/11/16 21:10:39  lasser
# Redid main command loop to work off of a table
#
# Revision 1.13  2001/11/16 19:17:47  lasser
# customer-ticket
#
# Revision 1.12  2001/11/16 18:44:43  lasser
# I am become customer-new, creator of businesses.
#
# Revision 1.11  2001/11/15 22:36:58  lasser
# it becomes show-my too
#
# Revision 1.10  2001/11/15 20:23:18  lasser
# added close-ticket
#
# Revision 1.9  2001/11/15 19:25:45  lasser
# main program taking on the attributes of show-new and show-open
#
# Revision 1.8  2001/11/15 18:42:33  lasser
# Added show-ticket to batts
#
# Revision 1.7  2001/11/15 18:05:15  lasser
# batts now operates as resolve-ticket too.
#
# Revision 1.6  2001/11/15 17:54:24  lasser
# Added a show-status routine
#
# Revision 1.5  2001/11/15 06:42:07  lasser
# Print usage statements too
#
# Revision 1.4  2001/11/15 06:29:17  lasser
# Added priority-ticket features to main batts program
#
# Revision 1.3  2001/11/15 06:17:56  lasser
# code cleanup
#
# Revision 1.2  2001/11/15 06:06:53  lasser
# Change priority to hash from array, fix associated printing function.
#
# Revision 1.1  2001/11/14 22:21:02  lasser
# Beginning of the master script; show-priorities merged in.
#

# Set up what we can use
use strict;
use Getopt::Long;
use Mail::Internet;
use DBI;

# Define our subroutines here

# internal subroutines
sub clear_refticket($);		# Clear reference ticket info
sub db_connect();		# Connect to the database
sub html_event_footer();	# print out bottom of HTML event list
sub html_event_header();	# print out top of HTML event list
sub html_event_line(@);		# print out one line of HTML event list
sub html_summary_footer();	# print out bottom of HTML ticket list
sub html_summary_header();	# print out the top of HTML ticket list
sub html_summary_line(@);	# print out one line of HTML ticket list
sub load_billing();		# load data from db
sub load_category();		# load data from db
sub load_customer();		# load data from db
sub load_people();		# load data from db
sub load_priorities();		# load data from db
sub load_status();		# load data from db
sub log_print($);		# print out log entries from query
sub set_refticket($$);		# Set the reference ticket for a ticket
sub summary_print($);		# print out ticket summary from query
sub text_event_footer();	# print out bottom of text ticket list
sub text_event_header();	# print out top of text ticket list
sub text_event_line(@);		# print out one line of text ticket list
sub usage($);			# build an appropriate usage statement

# user commands
sub assign_ticket($$);		# assign a ticket to a person
sub billing_new($);		# create a new billing code
sub category_new($);		# create a new category
sub category_ticket($$);	# set a ticket's category
sub close_ticket($);		# close a resolved ticket
sub customer_new($$);		# add a new customer to the database
sub customer_ticket($$);	# set the customer on a ticket
sub duplicate_ticket($$);	# Mark a ticket as a duplicate
sub help();			# print slightly longer help message
sub log_me($$$$$);		# Log an event associated with a ticket
sub log_stdin($$$$);		# Log an event associated with a ticket
sub log_end($);			# End the timer on a log event
sub longdesc_ticket($$);	# set the long description of a ticket
sub longdesc_ticket_stdin($);	# set the long description of a ticket
sub mysql();			# run MySQL as user batts with the db
sub pending_on($$);		# Mark ticket 1 as pending on ticket 2
sub person_new($$$$);		# create a new person
sub person_username($$);	# change the name of a person
sub priority_ticket($$);	# Change the priority of a given ticket
sub requestor_ticket($$);	# set the requestor of a ticket
sub resolve_ticket($$);		# Resolve a given ticket
sub resolve_ticket_stdin($);	# Resolve a given ticket, res from STDIN
sub resolve_when($$);		# Mark ticket 1 as resolved when 2 is
sub shortdesc_ticket($$);	# set the short description of a ticket
sub show_billing();		# display short list of billing codes
sub show_cat_ticket($);		# display all open tickets in a category
sub show_categories();		# display short list of categories
sub show_commands();		# display short list of commands
sub show_cust_ticket($);	# display a customer's list of tickets
sub show_customers();		# display alphabetical list of customers
sub show_log($);		# display a single log entry
sub show_my($);			# display short list of my open tickets
sub show_new();			# display short list of all new tickets
sub show_open();		# display short list of all open tickets
sub show_people();		# display alphabetical list of all users
sub show_priorities();		# display sorted list of priorities
sub show_status();		# display list of status codes
sub show_ticket($);		# long-form display of a ticket
sub show_ticket_log($);		# display all log entries for a ticket
sub status_ticket($$);		# set the status of a ticket
sub ticket_new($$$);		# Create a new ticket
sub ticket_new_stdin($$);	# Create a new ticket
sub touch($);			# update last-modified time on a ticket

########################################################################
########################################################################
#
# Variable declaration
#
########################################################################
########################################################################

# Configuration settings and command-line options
my ($is_html) = "";			# no HTML output by default
my ($mail_resolution) = "1";		# default mail ticket resolution
my ($default_config) = "/etc/battsrc";	# Our default configuration file
my ($config_file);			# command-line config file
my ($db_config_file) = "/etc/batts.myrc"; # Our database config file
my ($dsn) = "DBI:mysql:batts";		# DB DSN line, can be overridden
my ($return_address);			# Return address for mail
my ($sendmail);				# Invocation of sendmail

# Database standard stuff
my ($dbh);
my ($host_name, $user_name, $password) = (undef, undef, undef);
my (@ary);

my (%routines);			# Table of routines
my ($cmdname) = $0;		# How were we called?

my (@billing, %billing);	# Table of billing codes
my (@category, %category);	# Table of categories
my (@customer, %customer);	# Table of customers
my (@people, %people);		# Table of people --- see load_people
				# for documentation!
my (@priority, %priority);	# Table of priorities, to be read from
				# the database
my (@status, %status);		# Table of status codes, aka resolutions

########################################################################
########################################################################
#
# Format definitions
#
########################################################################
########################################################################
format SHORTDISPLAY_TOP =
Ticket  Created by                Assigned to  Status           Priority
------------------------------------------------------------------------
.


format SHORTDISPLAY =
@<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<< @<<<<<<<<<<@>>>>>>>>>>>>>
$ary[0],$ary[1],                  $ary[2],     $ary[4],   $ary[3]
        @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$ary[5]
------------------------------------------------------------------------
.

########################################################################
########################################################################
#
# Table of routines and arguments
#
########################################################################
########################################################################

# The format is calling-name => [\&function-name, number-of-args, usage]

%routines=(
	"assign-ticket"		=> [\&assign_ticket, 2,
				    "<ticket-number> <username>"],
	"billing-new"		=> [\&billing_new, 1,
				    "<billable-name>"],
	"category-new"		=> [\&category_new, 1,
				    "<category-name>"],
	"category-ticket"	=> [\&category_ticket, 2,
				 "<ticket-number> <category-name>"],
	"close-ticket"		=> [\&close_ticket, 1,
				    "<ticket-number>"],
	"customer-new"		=> [\&customer_new, 2,
			    "<customer-username> <customer-fullname>"],
	"customer-ticket"	=> [\&customer_ticket, 2,
				 "<ticket-number> <customer-username>"],
	"duplicate-ticket"	=> [\&duplicate_ticket, 2,
				  "<duplicate-ticket> <master-ticket>"],
	"help"			=> [\&help, 0, ""],
	"longdesc-ticket"	=> [\&longdesc_ticket_stdin, 1,
				    "<ticket-number>"],
	"log"			=> [\&log_stdin, 4,
	     "<ticket-id> <person-username> <billable-code> <public?>"],
	"log-end"		=> [\&log_end, 1, "<log-id>"],
	"mysql"			=> [\&mysql, 0, ""],
	"pending-on"		=> [\&pending_on, 2,
				    "<ticket-number> <master-ticket>"],
	"person-fullname"	=> [\&person_fullname, 2,
				    "<username> <fullname>"],
	"person-new"		=> [\&person_new, 4,
		"<username> <full name> <email> <customer-username>"],
	"person-username"	=> [\&person_username, 2,
				    "<e-mail> <username>"],
	"priority-ticket"	=> [\&priority_ticket, 2,
				    "<ticket-number> <priority-name>"],
	"requestor-ticket"	=> [\&requestor_ticket, 2,
				   "<ticket-number> <person-username>"],
	"resolve-ticket"	=> [\&resolve_ticket_stdin, 1,
				    "<ticket-number>"],
	"resolve-when"		=> [\&resolve_when, 2,
				  "<ticket-id> <master-ticket>"],
	"shortdesc-ticket"	=> [\&shortdesc_ticket, 2,
			    "<ticket-number> <short-description>"],
	"show-billing"		=> [\&show_billing, 0, ""],
	"show-cat-ticket"	=> [\&show_cat_ticket, 1,
					"[--html] <category-name>"],
	"show-categories"	=> [\&show_categories, 0, ""],
	"show-commands"		=> [\&show_commands, 0, ""],
	"show-cust-ticket"	=> [\&show_cust_ticket, 1,
					"[--html] <customer-username>"],
	"show-customers"	=> [\&show_customers, 0, ""],
	"show-log"		=> [\&show_log, 1,
					"[--html] <event-id>"],
	"show-my"		=> [\&show_my, 1,
					"[--html] <username>"],
	"show-new"		=> [\&show_new, 0, "[--html]"],
	"show-open"		=> [\&show_open, 0, "[--html]"],
	"show-people"		=> [\&show_people, 0, ""],
	"show-priorities"	=> [\&show_priorities, 0, ""],
	"show-status"		=> [\&show_status, 0, ""],
	"show-ticket"		=> [\&show_ticket, 1,
				    "<ticket-number>"],
	"show-ticket-log"	=> [\&show_ticket_log, 1,
					"[--html] <ticket-number>"],
	"status-ticket"		=> [\&status_ticket, 2,
				    "<ticket-number> <status>"],
	"ticket-new"		=> [\&ticket_new_stdin, 2,
				    "<e-mail> <short-description>"],
	"touch"			=> [\&touch, 1, "<ticket-number>"]
);

########################################################################
########################################################################
#
# Main routine starts here
#
########################################################################
########################################################################

########################################################################
########################################################################
# Take in all config variables, command-line options, etc.

# First read in the standard default configuration
{ package Settings; do "$default_config" }

# Next read in our user-specific options
if (defined $ENV{HOME}) {
	{ package Settings; do "$ENV{HOME}/.battsrc" };
}

# Then process the command line...
GetOptions(
	"dbconfig|d=s"	=> \$db_config_file,
	"dsn=s"		=> \$dsn,
	"host|h=s"	=> \$host_name,
	"user|u=s" 	=> \$user_name,
	"password|p:s"	=> \$password,
	"return|r=s"	=> \$return_address,
	"html!"		=> \$is_html,
	"mail!"		=> \$mail_resolution,
	"config|c=s"	=> \$config_file,
	) or exit(1);

# And now we can read in a user-specified configuration file.
if ($config_file) {
	{ package Settings; do "$config_file" }
}

# Now set variables that we care about based on this
if (defined $Settings::db_config_file) {
	$db_config_file = $Settings::db_config_file;
}

if (defined $Settings::dsn) {
	$dsn = $Settings::dsn;
}
if (defined $Settings::is_html) {
	$is_html = $Settings::is_html;
}
if (defined $Settings::return_address) {
	$return_address = $Settings::return_address;
}
if (defined $Settings::sendmail) {
	$sendmail = $Settings::sendmail;
}

# strip out the executable's path, leaving only the basename
$cmdname =~ s#^.*/##;

# Die if we don't have our necessary configuration variables set
$return_address || die "No return address for mail provided.\n";

# If called generically, make our first argument the new command
# Unless we don't have any arguments, in which case assume that
# a BATTS shell is requested.
if ($cmdname =~ /batts/) {
	if ($#ARGV > -1) {
		$cmdname = $ARGV[0];
		shift;
	} else {
		$cmdname = "shell";
	}
}

########################################################################
# Connect to the database
db_connect;

# Load our priority and status tables
load_billing;
load_category;
load_customer;
load_people;
load_priorities;
load_status;

# If the command doesn't exist in the table or has the wrong number
# of arguments, quit now
if ((! defined $routines{$cmdname}) or
	(@ARGV != $routines{$cmdname}[1])) {
	print STDERR "Usage: " . usage($cmdname) . "\n";
	exit 1;
}

# Otherwise, run the command
$routines{$cmdname}[0](@ARGV);

# Clean up and exit
$dbh->disconnect;
exit 0;



########################################################################
########################################################################
#
# clear_refticket: Erase the refticket for the given ticket
#
########################################################################
########################################################################
sub clear_refticket($) {

	my ($ticket_id) = $_[0];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	return $dbh->do (qq{
		UPDATE tickets
		SET refticket=NULL
		WHERE ticket_id=$ticket_id
	});
}


########################################################################
########################################################################
#
# db_connect: Subroutine to connect to the database
#
########################################################################
########################################################################
sub db_connect() {


# If given -p or --password, but no password given, grab it from
# a real terminal:
	if (defined ($password) && !$password) {
		# turn off echoing but don't interfere with STDIN
		open (TTY, "/dev/tty") or die "Cannot open terminal\n";
		system("stty -echo < /dev/tty");
		print STDERR "Enter password: ";
		chomp ($password = <TTY>);
		system ("stty echo < /dev/tty");
		close (TTY);
		print STDERR "\n";
	}

	# Prepare the rest of the DSN
	$dsn .= ":hostname=$host_name" if $host_name;
	$dsn .= ";mysql_read_default_file=$db_config_file";

	# Connect to the database
	$dbh = DBI->connect($dsn, $user_name, $password,
		{ RaiseError => 1 });
}


########################################################################
########################################################################
# html_event_footer: print out the bottom of HTML event list
########################################################################
########################################################################
sub html_event_footer()
{
    print(qq{
      </TBODY>
    </TABLE>
  </BODY>
</HTML>
});
}

########################################################################
########################################################################
#
# html_event_header: print the top of HTML event list
#
########################################################################
########################################################################
sub html_event_header()
{
print(qq{

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
  <HEAD>
     <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    <TITLE>Log of Events</TITLE>
  </HEAD>
  <BODY>
    <H1>Log of Events</H1>

    <TABLE BORDER="0" CELLSPACING="1" CELLPADDING="3">
      <THEAD>
	<TR>
          <TD WIDTH="10%"><STRONG>Event</STRONG></TD>
          <TD WIDTH="10%"><STRONG>Ticket</STRONG></TD>
          <TD WIDTH="17%"><STRONG>Logged By</STRONG></TD>
          <TD WIDTH="18%"><STRONG>Billing Code</STRONG></TD>
          <TD WIDTH="20%"><STRONG>Start Time</STRONG></TD>
          <TD WIDTH="20%"><STRONG>End Time</STRONG></TD>
	  <TD WIDTH="5%"><STRONG>Private?</STRONG></TD>
	</TR>
	<TR>
          <TD WIDTH="100%" COLSPAN="5" <HR NOSHADE></TD>
	</TR>
      </THEAD>
      <TBODY>
});
}


########################################################################
########################################################################
#
# html_event_line: print one log entry
#
########################################################################
########################################################################
sub html_event_line(@)
{
print(qq{
	<TR>
          <TD>$_[0]</TD>
          <TD>$_[1]</TD>
          <TD>$_[2]</TD>
          <TD>$_[3]</TD>
          <TD>$_[5]</TD>
          <TD>$_[4]</TD>
          <TD>$_[7]</TD>
	</TR>
	<TR>
          <TD COLSPAN="5">$_[6]<HR NOSHADE></TD>
	</TR>
});
}


########################################################################
########################################################################
#
# html_summary_footer: print out the bottom of HTML ticket list
#
########################################################################
########################################################################
sub html_summary_footer()
{
    print(qq{
      </TBODY>
    </TABLE>
  </BODY>
</HTML>
});
}


########################################################################
########################################################################
#
# html_summary_header: print the top of a list of HTML tickets
#
########################################################################
########################################################################
sub html_summary_header()
{
print(qq{

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
  <HEAD>
     <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    <TITLE>Summary of Tickets</TITLE>
  </HEAD>
  <BODY>
    <H1>Summary of Tickets</H1>

    <TABLE BORDER="0" CELLSPACING="1" CELLPADDING="3">
      <THEAD>
	<TR>
          <TD WIDTH="10%"><STRONG>Ticket</STRONG></TD>
          <TD WIDTH="30%"><STRONG>Created by</STRONG></TD>
          <TD WIDTH="20%"><STRONG>Assigned to</STRONG></TD>
          <TD WIDTH="20%"><STRONG>Priority</STRONG></TD>
          <TD WIDTH="20%"><STRONG>Status</STRONG></TD>
	</TR>
	<TR>
          <TD WIDTH="100%" COLSPAN="5" <HR NOSHADE></TD>
	</TR>
      </THEAD>
      <TBODY>
});
}


########################################################################
########################################################################
#
# html_summary_line: print one summary ticket entry
#
########################################################################
########################################################################
sub html_summary_line(@)
{
print(qq{
	<TR>
          <TD>$_[0]</TD>
          <TD>$_[1]</TD>
          <TD>$_[2]</TD>
          <TD>$_[3]</TD>
          <TD>$_[4]</TD>
	</TR>
	<TR>
          <TD COLSPAN="5">$_[5]<HR NOSHADE></TD>
	</TR>
});
}


########################################################################
########################################################################
#
# load_billing: Subroutine to load our list of billing codes from the
#               database
#
########################################################################
########################################################################
sub load_billing() {

	my ($sth);

# Get rid of the warning
	@billing="";

	$sth = $dbh->prepare (qq{
		SELECT billable_id, name
		FROM billing_codes
	});
	$sth->execute;

# We load both a hash and an array, so we can lookup either direction
	while (@ary = $sth->fetchrow_array) {
		$billing[$ary[0]] = $ary[1];
		$billing{$ary[1]} = $ary[0];
	}

# Clean up
	$sth->finish;
}


########################################################################
########################################################################
#
# load_category: Subroutine to load our list of categories from
#                the database
#
########################################################################
########################################################################
sub load_category() {

	my ($sth);

# Get rid of the warning
	@category="";

	$sth = $dbh->prepare (qq{
		SELECT category_id, name
		FROM categories
	});
	$sth->execute;

# We load both a hash and an array, so we can lookup either direction
	while (@ary = $sth->fetchrow_array) {
		$category[$ary[0]] = $ary[1];
		$category{$ary[1]} = $ary[0];
	}

# Clean up
	$sth->finish;
}


########################################################################
########################################################################
#
# load_customer: Subroutine to load our list of customers from
#                the database
#
########################################################################
########################################################################
sub load_customer() {

	my ($sth);

# Get rid of the warning
	@customer="";

	$sth = $dbh->prepare (qq{
		SELECT customer_id, username, fullname
		FROM customers
	});
	$sth->execute;

# We load both a hash and an array, so we can lookup either direction
	while (@ary = $sth->fetchrow_array) {
		$customer[$ary[0]] = $ary[1];
		$customer{$ary[1]}[0] = $ary[0];
		$customer{$ary[1]}[1] = $ary[2];
	}

# Clean up
	$sth->finish;
}


########################################################################
########################################################################
#
# load_people: Subroutine to load our people from the database
#
########################################################################
########################################################################

# The people array just returns the name based on the ID number,
# but the hash (with name as key) returns (id, e-mail, customer-id)
sub load_people() {

	my ($sth);

# Get rid of the warning
	@people="";

	$sth = $dbh->prepare (qq{
		SELECT person_id, username, email, customer, fullname
		FROM people
	});
	$sth->execute;

# We load both a hash and an array, so we can lookup either direction
	while (@ary = $sth->fetchrow_array) {
		$people[$ary[0]] = $ary[1];
		$people{$ary[1]}[0] = $ary[0];
		$people{$ary[1]}[1] = $ary[2];
		$people{$ary[1]}[2] = $ary[3];
		$people{$ary[1]}[3] = $ary[4];
	}

# Clean up
	$sth->finish;
}


########################################################################
########################################################################
#
# load_priorities: Subroutine to load our list of priorities from
#                  the database
#
########################################################################
########################################################################
sub load_priorities() {

	my ($sth);

# Get rid of the warning
	@priority="";

	$sth = $dbh->prepare (qq{
		SELECT priority_id, name
		FROM priorities
	});
	$sth->execute;

# We load both a hash and an array, so we can lookup either direction
	while (@ary = $sth->fetchrow_array) {
		$priority[$ary[0]] = $ary[1];
		$priority{$ary[1]} = $ary[0];
	}

# Clean up
	$sth->finish;
}


########################################################################
########################################################################
#
# load_status: Subroutine to load our list of status codes from
#              the database
#
########################################################################
########################################################################
sub load_status() {

	my ($sth);

# Get rid of the warning
	@status="";

	$sth = $dbh->prepare (qq{
		SELECT resolution_id, name
		FROM resolutions
	});
	$sth->execute;

# We load both a hash and an array, so we can lookup either direction
	while (@ary = $sth->fetchrow_array) {
		$status[$ary[0]] = $ary[1];
		$status{$ary[1]} = $ary[0];
	}

# Clean up
	$sth->finish;
}


########################################################################
########################################################################
#
# log_print: display event log based on query passed
#
########################################################################
########################################################################
sub log_print($) {

	my ($query) = @_;

	my ($sth);

	# Set our output formats
	if ($is_html) {
		html_event_header;
	} else {
		text_event_header;
	}

	$sth = $dbh->prepare($query);
	$sth->execute();

	# print all matching events
	while (@ary = $sth->fetchrow_array()) {
        	if ($is_html) {
			html_event_line(@ary);
		} else {
			text_event_line(@ary);
		}
        }

	# Clean up
	$sth->finish();
	if ($is_html) {
		html_event_footer;
	} else {
		text_event_footer;
	}

	return 0;
}

########################################################################
########################################################################
#
# set_refticket: Set the refticket for the given ticket
#
########################################################################
########################################################################
sub set_refticket($$) {

	my ($ticket_id) = $_[0];
	my ($refticket) = $_[1];

	if (($ticket_id < 1) or ($refticket < 1)) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	return $dbh->do (qq{
		UPDATE tickets
		SET refticket=$refticket
		WHERE ticket_id=$ticket_id
	});
}



########################################################################
########################################################################
#
# summary_print: display short-form tickets based on query passed
#
########################################################################
########################################################################
sub summary_print($) {

	my ($query) = @_;
	my ($sth);

	# Set our output formats
	if ($is_html) {
		html_summary_header;
	} else {
		$~ = "SHORTDISPLAY";
		$^ = "SHORTDISPLAY_TOP";
	}

	$sth = $dbh->prepare($query);
	$sth->execute();

	# print all matching tickets
	while (@ary = $sth->fetchrow_array()) {
        	if ($is_html) {
			html_summary_line(@ary);
		} else {
			write;
		}
        }

	# Clean up
	$sth->finish();
	if ($is_html) {
		html_summary_footer;
	} else {
		$~ = "STDOUT";
		$^ = "STDOUT_TOP";
	}

	return 0;
}


########################################################################
########################################################################
#
# text_event_footer: print out text event log footer
#
########################################################################
########################################################################
sub text_event_footer() {
	# We don't do anything here now
}

########################################################################
########################################################################
#
# text_event_header: print out text event log header
#
########################################################################
########################################################################
sub text_event_header() {

	print "Event  Ticket Logged By    Billing Code Start Time     ";
	print "End Time  Private\n";
	print "------------------------------------";
	print "------------------------------------\n";

}

########################################################################
########################################################################
#
# text_event_line: print out a text event log entry
#
########################################################################
########################################################################
sub text_event_line(@) {

	printf "%06d %06d %-12s %-12s %-14s %-14s %1s\n",
	   $_[0], $_[1], $_[2], $_[3], $_[5], $_[4], $_[7];
	print "$_[6]\n";
	print "------------------------------------";
	print "------------------------------------\n";
}


########################################################################
########################################################################
#
# update_tickets: update tickets based on the incoming ticket info
#
########################################################################
########################################################################
sub update_tickets($$) {

	my ($sth);
	my ($refticket) = $_[0];
	my ($restext) = $_[1];

	my ($logmessage);

	# resolve tickets that are resolved when or duplicate of this
	$logmessage = $dbh->quote("Resolved when $refticket resolved.");

	$sth = $dbh->prepare (qq{
		SELECT ticket_id
		FROM tickets
		WHERE refticket=$refticket
		AND ((resolution=$status{'resolved when'})
		  OR (resolution=$status{'duplicate of'}))
	});
	$sth->execute();

	while (@ary = $sth->fetchrow_array()) {
		log_me($ary[0], 'unassigned', 'informational', "N",
		    $logmessage);
		clear_refticket($ary[0]);
		resolve_ticket($ary[0],$restext);
	}
	$sth->finish();

	# set assigned all tickets pending on this ticket
    $logmessage = $dbh->quote("Status reset when $refticket resolved.");
	$sth = $dbh->prepare (qq{
		SELECT ticket_id, assignedto
		FROM tickets
		WHERE refticket=$refticket
		AND resolution=$status{'pending on'}
	});
	$sth->execute();

	while (@ary = $sth->fetchrow_array()) {
		clear_refticket($ary[0]);
		log_me($ary[0], 'unassigned', 'informational', "N",
		        $logmessage);

		if ($ary[1] = $people{'unassigned'}) {
			status_ticket($ary[0], 'new');
		} else {
			status_ticket($ary[0], 'assigned');
		}
	}

	$sth->finish();
	return;
}


########################################################################
########################################################################
#
# usage: print out the error messages from the table.
#
########################################################################
########################################################################
sub usage($) {

	my ($routine) = $_[0];

	my ($usage);		# The usage statement

	# Pull the statement from the routines table, if we have it
	if (defined $routines{$routine}) {
		$usage = $routine . " " . $routines{$routine}[2];
	} else {
		$usage = "batts <command> <arguments>";
	}

	# Print the error message and exit
	return $usage;
}


########################################################################
########################################################################
########################################################################
# External routines begin here
########################################################################
########################################################################
########################################################################


########################################################################
########################################################################
#
# assign_ticket: set the customer on a ticket
#
########################################################################
########################################################################
sub assign_ticket($$) {

	my ($ticket_id) = $_[0];
	my ($assignee) = $_[1];

	my ($sth);

	my ($count) = 0;			# counter variable

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	if (! defined $people{$assignee}) {
		print STDERR "No person \"$assignee\" is known\n";
		return -1;
	}

	return $dbh->do (qq{
		UPDATE tickets
		SET assignedto=$people{$assignee}[0],
		    resolution=$status{'assigned'}
		WHERE ticket_id=$ticket_id
	});
}


########################################################################
########################################################################
#
# billing_new: add a new billing code to the database
#
########################################################################
########################################################################
sub billing_new($) {

	my ($new_code) = $_[0];

	if (defined $billing{$new_code}) {
		print STDERR "Billing $new_code already in database\n";
		return -1;
	}

	return $dbh->do (qq{
		INSERT INTO billing_codes VALUES (NULL, "$new_code")
	});
}

########################################################################
########################################################################
#
# category_new: add a new category to the database
#
########################################################################
########################################################################
sub category_new($) {

	my ($new_category) = $_[0];

	if (defined $category{$new_category}) {
	    print STDERR "Category $new_category already in database\n";
	    return -1;
	}

	return $dbh->do (qq{
		INSERT INTO categories VALUES (NULL, "$new_category")
	});
}


########################################################################
########################################################################
#
# category_ticket: set the category on a ticket
#
########################################################################
########################################################################
sub category_ticket($$) {

	my ($ticket_id) = $_[0];
	my ($category_name) = $_[1];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	if (! defined $category{$category_name}) {
		print STDERR "No category $category_name is known\n";
		return -1;
	}

	return $dbh->do (qq{
		UPDATE tickets
		SET category=$category{$category_name}
		WHERE ticket_id=$ticket_id
	});
}


########################################################################
########################################################################
#
# close_ticket: close a ticket
#
########################################################################
########################################################################
sub close_ticket($) {

	my ($ticket_id) = $_[0];
	my ($sth);

	my ($count) = 0;		# loop counter variable
	my ($restext);			# text of the matching tickets

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	# Find all matching tickets --- there can be only one
	$sth = $dbh->prepare (qq{
        	SELECT restext
        	FROM tickets
        	WHERE ticket_id=$ticket_id
        	AND resolution=$status{'resolved'}
	});

	$sth->execute();
	while (@ary = $sth->fetchrow_array()) { 
		$count++;
		$restext=$ary[0];
	}
	$sth->finish();
	
	if ($count < 1) {
        	print STDERR "Ticket $ticket_id is not resolved.\n";
		return -1;
	} elsif ($count > 1) {
        	print STDERR "Multiple tickets $ticket_id.\n";
		return -1;
	}

	# OK, we only have one ticket. Change the status code in the
	# ticket now.
	$dbh->do (qq{
        	UPDATE tickets 
        	SET resolution=$status{'closed'}
        	WHERE ticket_id=$ticket_id
	});

	# Now change tickets depending on this ticket
	update_tickets($ticket_id, $restext);

	return;
}

########################################################################
########################################################################
#
# customer_new: add a new customer to the database
#
########################################################################
########################################################################
sub customer_new($$) {
	my ($customer_username) = $_[0];
	my ($customer_fullname) = $_[1];
	
	my ($quoted_username);
	my ($quoted_fullname);
	
	if (defined $customer{$customer_username}) {
  print STDERR "Customer \"$customer_username\" already in database\n";
		return -1;
	}

	$quoted_username=$dbh->quote($customer_username);
	$quoted_fullname=$dbh->quote($customer_fullname);
	return $dbh->do (qq{
		INSERT INTO customers VALUES
			(NULL, $quoted_username, $quoted_fullname)
	});
}

########################################################################
########################################################################
#
# customer_ticket: set the customer on a ticket
#
########################################################################
########################################################################
sub customer_ticket($$) {

	my ($ticket_id) = $_[0];
	my ($customer_name) = $_[1];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	if (! defined $customer{$customer_name}) {
	    print STDERR "No customer \"$customer_name\" is known\n";
	    return -1;
	}

	return $dbh->do (qq{
		UPDATE tickets
		SET customer=$customer{$customer_name}[0]
		WHERE ticket_id=$ticket_id
	});
}


########################################################################
########################################################################
#
# duplicate_ticket: mark a ticket as a duplicate of another ticket
#
########################################################################
########################################################################
sub duplicate_ticket($$) {

	my ($duplicate_ticket) = $_[0];
	my ($master_ticket) = $_[1];

	# Do we have a valid ticket number?
	if (($duplicate_ticket < 1) or ($master_ticket < 1)) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	status_ticket($duplicate_ticket, "duplicate of");
	set_refticket($duplicate_ticket, $master_ticket);
}


########################################################################
########################################################################
#
# help: Subroutine to print out a list of commands and usage statements
#
########################################################################
########################################################################
sub help() {
	my ($loop);		# loop variable

	foreach $loop (sort keys %routines) {
		print "$loop:\n\t" . usage($loop) . "\n";
	}
}


########################################################################
########################################################################
#
# log_me: log an event associated with a ticket
#
########################################################################
########################################################################
sub log_me($$$$$) {

	my ($ticket_id) = $_[0];
	my ($username) = $_[1];
	my ($billcode) = $_[2];
	my ($is_private) = $_[3];
	my ($quoted_logmessage) = $_[4];

	my ($sth);

	my ($event_id);			# New ID for this event
	my ($private);			# Is this log entry private?

	# Do we have a valid ticket number?
	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	# Do we have a matching username?
	if (! defined $people{$username}) {
		print STDERR "No such person $username\n";
		return -1;
	}

	# Do we have a matching billing code?
	if (! defined $billing{$billcode}) {
		print STDERR "No such billing code $billcode\n";
		return -1;
	}

	# The private? field should be either Y or N. First, let's
	# uppercase it as just the first character
	$private = uc(substr($is_private, 0, 1));

	if (($private ne "Y") && ($private ne "N")) {
		print STDERR "<private?> must be Y or N\n";
		return -1;
	}

	# Create the !@#$% ticket already
	$dbh->do (qq{
		INSERT INTO events VALUES(
			NULL,
			$ticket_id,
			$people{$username}[0],
			$billing{$billcode},
			NOW(),
			NOW(),
			$quoted_logmessage,
			"$private")
	});

	# Now we need the ID of the generated event
	$sth = $dbh->prepare(qq{
		SELECT event_id
		FROM events
		WHERE ticket=$ticket_id
		  AND person=$people{$username}[0]
		  AND billable=$billing{$billcode}
		  AND logmessage=$quoted_logmessage
		  AND private="$private"
	});
	$sth->execute();
	$event_id = $sth->fetchrow_array();
	$sth->finish();

	$event_id= sprintf("%06d", $event_id);
	print STDERR ("New log entry created: $event_id\n");
	return $event_id;

}

########################################################################
########################################################################
#
# log_stdin: log an event associated with a ticket, taking the text
#	     from STDIN
#
########################################################################
########################################################################
sub log_stdin($$$$) {

	my ($ticket_id) = $_[0];
	my ($username) = $_[1];
	my ($billcode) = $_[2];
	my ($is_private) = $_[3];

	my (@logtext);			# long description of problem
	my ($quoted_logtext) = "";	# One-line rendition of logtext

	# Grab the log text
	@logtext=<STDIN>;

	# Validation of our data performed by the log($$$$$) routine
	$quoted_logtext=$dbh->quote("@logtext");

	return log_me($ticket_id, $username, $billcode, $is_private, 
		$quoted_logtext);

}

########################################################################
########################################################################
#
# log_end: set the end-time on a log event
#
########################################################################
########################################################################
sub log_end($) {

	my ($log_id) = $_[0];

	if ($log_id < 1) {
		print STDERR
			"Log ID number must be a positive integer!\n";
		return -1;
	}

	return $dbh->do (qq{
		UPDATE events
		SET endtime=NOW()
		WHERE event_id=$log_id
	});
}

########################################################################
########################################################################
#
# longdesc_ticket: set the long description of a ticket
#
########################################################################
########################################################################
sub longdesc_ticket($$) {

	my ($ticket_id) = $_[0];
	my ($quoted_longdesc) = $_[1];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	return $dbh->do (qq{
        	UPDATE tickets 
        	SET longdesc=$quoted_longdesc
        	WHERE ticket_id=$ticket_id
	});

}


########################################################################
########################################################################
#
# longdesc_ticket_stdin: set the long description of a ticket
#
########################################################################
########################################################################
sub longdesc_ticket_stdin($) {

	my ($ticket_id) = $_[0];

	my (@longdesc);
	my ($quoted_longdesc)="";

	@longdesc=<STDIN>;
	$quoted_longdesc=$dbh->quote("@longdesc");

	return longdesc_ticket($ticket_id, $quoted_longdesc);
}



########################################################################
########################################################################
#
# mysql: drop into mysql
#
########################################################################
########################################################################
sub mysql() {
	return system ("/usr/bin/mysql",
		"--defaults-file=$db_config_file", "batts");

}

########################################################################
########################################################################
#
# pending_on: mark a ticket as pending on another ticket
#
########################################################################
########################################################################
sub pending_on($$) {

	my ($ticket_id) = $_[0];
	my ($master_ticket) = $_[1];

	# Do we have a valid ticket number?
	if (($ticket_id < 1) or ($master_ticket < 1)) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	status_ticket($ticket_id, "pending on");
	set_refticket($ticket_id, $master_ticket);
}

########################################################################
########################################################################
#
# person_fullname: change the username of a person in the database
#
########################################################################
########################################################################
sub person_fullname($$) {

	my ($username) = $_[0];
	my ($fullname) = $_[1];

	my ($quoted_username);
	my ($quoted_fullname);

	if (! defined $people{$username}) {
		print STDERR "Person \"$username\" not in database\n";
		return -1;
	}

	$quoted_username = $dbh->quote($username);
	$quoted_fullname = $dbh->quote($fullname);

	return $dbh->do (qq{
		UPDATE people SET fullname=$quoted_fullname
		WHERE username=$quoted_username
	});
}


########################################################################
########################################################################
#
# person_new: add a new person to the database
#
########################################################################
########################################################################
sub person_new($$$$) {

	my ($person_username)=$_[0];
	my ($person_fullname)=$_[1];
	my ($person_email)=$_[2];
	my ($customer_name)=$_[3];

	my ($quoted_fullname);

	$person_username = lc($person_username);
	$person_email = lc($person_email);
	$quoted_fullname = $dbh->quote("$person_fullname");

	if (defined $people{$person_username}) {
       print STDERR "Person \"$person_username\" already in database\n";
		return -1;
	}

	if (! defined $customer{$customer_name}) {
	     print STDERR "No such customer \"$customer_name\" known\n";
		return -1;
	}

	$dbh->do (qq{
	    INSERT INTO people VALUES
		(NULL, "$person_username", $quoted_fullname,
		 "$person_email", "$customer{$customer_name}[0]")
	});

	return 0;
}


########################################################################
########################################################################
#
# person_username: change the username of a person in the database
#
########################################################################
########################################################################
sub person_username($$) {

	my ($email) = $_[0];
	my ($username) = $_[1];

	$username = lc($username);
	$email = lc($email);

	if (defined $people{$username}) {
	      print STDERR "Person \"$username\" already in database\n";
		return -1;
	}

	return $dbh->do (qq{
		UPDATE people SET username="$username"
	           WHERE email="$email"
	});
}


########################################################################
########################################################################
#
# priority_ticket: Subroutine to change the priority of a ticket
#
########################################################################
########################################################################
sub priority_ticket($$) {

	my ($ticket_id) = $_[0];
	my ($priority_name) = $_[1];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	if (! defined $priority{$priority_name}) {
		print STDERR "No such priority $priority_name\n";
		return -1;
	}
	
	return $dbh->do (qq{
        	UPDATE tickets 
        	SET priority=$priority{$priority_name}
        	WHERE ticket_id=$ticket_id
	});
}


########################################################################
########################################################################
#
# requestor_ticket: Subroutine to change the requesting user of a ticket
#
########################################################################
########################################################################
sub requestor_ticket($$) {

	my ($ticket_id) = $_[0];
	my ($requestor) = $_[1];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	if (! defined $people{$requestor}) {
		print STDERR "No such person $requestor\n";
		return -1;
	}
	
	return $dbh->do (qq{
        	UPDATE tickets 
        	SET requestor=$people{$requestor}[0]
        	WHERE ticket_id=$ticket_id
	});
}


########################################################################
########################################################################
#
# resolve_ticket: Subroutine to resolve a ticket
#
########################################################################
########################################################################
sub resolve_ticket($$) {

	my ($mail);
	my ($ticket_id) = $_[0];
	my ($quoted_restext) = $_[1];
	
	my ($dequoted_restext);
	my ($person_id);
	my ($sth);
	my ($subject);

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	$dbh->do (qq{
        	UPDATE tickets 
        	SET resolution=$status{'resolved'},
		    restext=$quoted_restext
        	WHERE ticket_id=$ticket_id
	});

	# Update other tickets depending on this one
	update_tickets($ticket_id, $quoted_restext);

	# If the $mail_resolution option is not set, exit now
	return if (! $mail_resolution);

	# Now we need to get the information from the ticket.
	$sth = $dbh->prepare(qq{
		SELECT requestor,shortdesc, restext
		FROM tickets
		WHERE ticket_id=$ticket_id
	});
	$sth->execute();

	# this will only really execute once, so it's sort of silly
	while (@ary=$sth->fetchrow_array()) {
		$person_id=$ary[0];
		$subject=$ary[1];
		$dequoted_restext=$ary[2];
	}

	# Make the ticket ID pretty
	$ticket_id= sprintf("%06d", $ticket_id);

	# Fix up our subject
	$subject="Re: [$ticket_id] $subject";

	# And our body

	# Make up our response
	$mail= new Mail::Internet;
	$mail->body($dequoted_restext);
	$mail->head->replace("Subject", $subject);
	$mail->head->replace("From", $return_address);
	$mail->head->replace ("To", $people{$people[$person_id]}[1]);

	# Mail::Internet's send('sendmail') bit doesn't work, so we must
	open (SENDMAIL, "| $sendmail")
		or die ("Can't open sendmail!\n");
	$mail->print(\*SENDMAIL);
	close(SENDMAIL) or die ("I can't close sendmail!\n");
	
	return;
}


########################################################################
########################################################################
#
# resolve_ticket_stdin: resolve a ticket, taking the text from stdin
#
########################################################################
########################################################################
sub resolve_ticket_stdin($) {

	my ($ticket_id) = $_[0];

	my ($quoted_resolution) = "";
	my (@resolution_text);

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	# Get the text for the resolution field from STDIN
	@resolution_text=<STDIN>;
	$quoted_resolution=$dbh->quote("@resolution_text");

	return resolve_ticket($ticket_id, $quoted_resolution);
}


########################################################################
########################################################################
#
# resolve_when: mark a ticket as resolved when another ticket is
#
########################################################################
########################################################################
sub resolve_when($$) {

	my ($ticket_id) = $_[0];
	my ($master_ticket) = $_[1];

	# Do we have a valid ticket number?
	if (($ticket_id < 1) or ($master_ticket < 1)) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	status_ticket($ticket_id, "resolved when");
	set_refticket($ticket_id, $master_ticket);
}


########################################################################
########################################################################
#
# shortdesc_ticket: set the short description of a ticket
#
########################################################################
########################################################################
sub shortdesc_ticket($$) {

	my ($ticket_id) = $_[0];
	my ($shortdesc) = $_[1];

	my ($quoted_shortdesc);

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	# Protect quotes in the short description
	$quoted_shortdesc=$dbh->quote("$shortdesc");

	return $dbh->do (qq{
        	UPDATE tickets 
        	SET shortdesc=$quoted_shortdesc
        	WHERE ticket_id=$ticket_id
	});

}


########################################################################
########################################################################
#
# show_billing: print out a list of billing codes
#
########################################################################
########################################################################
sub show_billing() {
	print join "\n", sort keys %billing;
	print "\n";
}


########################################################################
########################################################################
#
# show_cat_ticket: display open tickets in a category
#
########################################################################
########################################################################
sub show_cat_ticket($) {

	my ($category_name) = $_[0];

	my ($category_id);		# ID number of category
	my ($query);			# prepped query

	# Get category ID from lookup
	if (! defined $category{$category_name}) {
		print STDERR "Category $category_name does not exist\n";
		return -1;
	}
	$category_id = $category{$category_name};
	
	$query=qq{
		SELECT tickets.ticket_id, creators.email,
		  assignees.username,
		  priorities.name, resolutions.name, tickets.shortdesc
		FROM tickets, priorities, resolutions,
		  people AS creators,
		  people AS assignees
		WHERE tickets.creator = creators.person_id
  		  AND tickets.assignedto = assignees.person_id
  		  AND tickets.priority = priorities.priority_id
  		  AND tickets.resolution = resolutions.resolution_id
		  AND tickets.resolution != $status{'closed'}
		  AND tickets.resolution != $status{'resolved'}
		  AND tickets.resolution != $status{'duplicate of'}
  		  AND tickets.category = $category_id
		ORDER BY tickets.priority, tickets.lastmodified,
		  tickets.ticket_id
	};

	summary_print($query);

	return 0;
}

########################################################################
########################################################################
#
# show_categories: Subroutine to print out a list of categories
#
########################################################################
########################################################################
sub show_categories() {
	print join "\n", sort keys %category;
	print "\n";
}


########################################################################
########################################################################
#
# show_commands: Subroutine to print out a list of commands
#
########################################################################
########################################################################
sub show_commands() {
	print join "\n", sort keys %routines;
	print "\n";
}


########################################################################
########################################################################
#
# show_cust_ticket: display tickets created by a customer
#
########################################################################
########################################################################
sub show_cust_ticket($) {

	my ($custname) = $_[0];

	my ($custid);			# ID number of customer
	my ($query);			# prepped query

	# Get person ID from lookup
	if (! defined $customer{$custname}) {
		print STDERR "Customer ID $custname does not exist\n";
		return -1;
	}
	$custid = $customer{$custname}[0];
	
	$query=qq{
		SELECT tickets.ticket_id, creators.email,
		  assignees.username,
		  priorities.name, resolutions.name, tickets.shortdesc
		FROM tickets, priorities, resolutions,
		  people AS creators,
		  people AS assignees
		WHERE tickets.creator = creators.person_id
  		  AND tickets.assignedto = assignees.person_id
  		  AND tickets.priority = priorities.priority_id
  		  AND tickets.resolution = resolutions.resolution_id
		  AND tickets.resolution != $status{'closed'}
		  AND tickets.resolution != $status{'resolved'}
		  AND tickets.resolution != $status{'duplicate of'}
  		  AND tickets.customer = $custid
		ORDER BY tickets.priority, tickets.lastmodified,
		  tickets.ticket_id
	};

	summary_print($query);

	return 0;
}


########################################################################
########################################################################
#
# show_customers: Subroutine to print out a list of customers
#
########################################################################
########################################################################
sub show_customers() {

	foreach (sort keys %customer) {
	print "$_ : $customer{$_}[1]\n";
	}

}

########################################################################
########################################################################
#
# show_log: Subroutine to print out a single log entry
#
########################################################################
########################################################################
sub show_log($) {

	my ($event_id) = $_[0];

	my ($query);		# Our SQL query

	# If our log request is for an event < 1, get rid of it
	if ($event_id < 1) {
		print STDERR "Requested log entry < 1!\n";
		return -1;
	}

	$query=qq{
		SELECT event_id, ticket, people.username,
		  billing_codes.name, endtime, starttime, logmessage,
		  private
		FROM events, people, billing_codes
		WHERE events.person = people.person_id
		  AND events.billable = billing_codes.billable_id
		  AND event_id="$event_id"
	};

	log_print($query);

	return 0;
}


########################################################################
########################################################################
#
# show_my: display tickets created by or belonging to a user
#
########################################################################
########################################################################
sub show_my($) {

	my ($username) = $_[0];

	my ($person);			# ID number of individual
	my ($query);			# query to pass off

	# If person not in tables, get rid of it.
	if (! defined $people{$username}) {
		print STDERR "No person $username in tables\n";
		return -1
	}

	$person = $people{$username}[0];

	# Set our output formats
	$query=qq{
		SELECT tickets.ticket_id, creators.email,
		  assignees.username,
		  priorities.name, resolutions.name, tickets.shortdesc
		FROM tickets, priorities, resolutions,
		  people AS creators,
		  people AS assignees
		WHERE tickets.creator = creators.person_id
  		  AND tickets.assignedto = assignees.person_id
  		  AND tickets.priority = priorities.priority_id
  		  AND tickets.resolution = resolutions.resolution_id
		  AND tickets.resolution != $status{'closed'}
		  AND tickets.resolution != $status{'resolved'}
		  AND tickets.resolution != $status{'duplicate of'}
  		  AND ((tickets.creator LIKE "$person") 
		    OR (tickets.assignedto LIKE "$person"))
		ORDER BY tickets.priority, tickets.lastmodified,
		  tickets.ticket_id
	};

	summary_print($query);

	return 0;
}



########################################################################
########################################################################
#
# show_new: Display short form for all new tickets
#
########################################################################
########################################################################
sub show_new() {

	my ($query);		# Our query to send out

	$query = qq{
		SELECT tickets.ticket_id, creators.email,
		  assignees.username,
		  priorities.name, resolutions.name, tickets.shortdesc
		FROM tickets, priorities, resolutions,
		  people AS creators,
		  people AS assignees
		WHERE tickets.creator = creators.person_id
  		  AND tickets.assignedto = assignees.person_id
  		  AND tickets.priority = priorities.priority_id
  		  AND tickets.resolution = resolutions.resolution_id
		  AND tickets.resolution = $status{'new'}
		ORDER BY tickets.creationtime, tickets.lastmodified,
		  tickets.ticket_id
	};

	summary_print($query);
	return 0;
}


########################################################################
########################################################################
#
# show_open: Display short form for all open tickets
#
########################################################################
########################################################################
sub show_open() {

	my ($query);			# our query to run

	$query=qq{
		SELECT tickets.ticket_id, creators.email,
		  assignees.username,
		  priorities.name, resolutions.name, tickets.shortdesc
		FROM tickets, priorities, resolutions,
		  people AS creators,
		  people AS assignees
		WHERE tickets.creator = creators.person_id
  		  AND tickets.assignedto = assignees.person_id
  		  AND tickets.priority = priorities.priority_id
  		  AND tickets.resolution = resolutions.resolution_id
		  AND tickets.resolution != $status{'closed'}
		  AND tickets.resolution != $status{'resolved'}
		  AND tickets.resolution != $status{'duplicate of'}
		ORDER BY tickets.priority, tickets.lastmodified,
		  tickets.ticket_id
	};

	summary_print($query);
	return 0;
}

########################################################################
########################################################################
#
# show_people: Subroutine to print out a list of people
#
########################################################################
########################################################################
sub show_people() {
	my ($loop);

	foreach $loop (sort keys %people) {
	print "$loop : $people{$loop}[1] : $people{$loop}[3] : $customer[$people{$loop}[2]]\n";
	}

}

########################################################################
########################################################################
#
# show_priorities: Subroutine to print out a list of priorities
#
########################################################################
########################################################################
sub show_priorities() {
	my ($loop) = 1;

	while ($priority[$loop]) {
		print "$priority[$loop]\n";
		$loop++;
	}

}


########################################################################
########################################################################
#
# show_status: Subroutine to print out a list of status codes
#
########################################################################
########################################################################
sub show_status() {
	my ($loop) = 1;

	while ($status[$loop]) {
		print "$status[$loop]\n";
		$loop++;
	}

}


########################################################################
########################################################################
#
# show_ticket: Subroutine to print out a ticket (long-form)
#
########################################################################
########################################################################
sub show_ticket($) {

	my ($ticket_id) = $_[0];

	my ($sth);

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	# issue query
	$sth = $dbh->prepare ("
	SELECT creators.username, creators.email, customers.username,
	  assignees.username, assignees.email, categories.name,
	  priorities.name, tickets.creationtime, tickets.lastmodified,
	  tickets.shortdesc, tickets.longdesc, tickets.refticket,
	  resolutions.name, tickets.restext, requestor.username
	FROM tickets, customers, categories, priorities, resolutions,
	  people AS creators,
	  people AS requestor,
	  people AS assignees
	WHERE tickets.customer = customers.customer_id
	  AND tickets.creator = creators.person_id
	  AND tickets.requestor = requestor.person_id
	  AND tickets.assignedto = assignees.person_id
	  AND tickets.category = categories.category_id
	  AND tickets.priority = priorities.priority_id
	  AND tickets.resolution = resolutions.resolution_id
	  AND tickets.ticket_id = $ticket_id
	");
	$sth->execute();

	# print all matching tickets
	while (@ary = $sth->fetchrow_array()) {
		print ("Ticket Number $ticket_id:\n");
		print ("Created by $ary[0] <$ary[1]> on $ary[7]\n");

		if (($ary[14] ne 'unassigned') &&
			($ary[14] ne $ary[0])) {
print ("Current ticket requestor is $ary[14] <$people{$ary[14]}[1]>\n");
		}

		print ("Ticket customer is $ary[2]\n");
		print ("Assigned to $ary[3] <$ary[4]>; ");
		print ("last modified on $ary[8]\n");
		print ("Status is $ary[12] ");
		if (defined $ary[11]) { print ("$ary[11]\n"); }
               		else { print ("\n"); }
		print ("Category: $ary[5]\t\t\tPriority: $ary[6]\n\n");
		print ("$ary[9]\n\n$ary[10]\n");
		if (defined $ary[13]) {
			print ("Resolution: $ary[13]\n");
		}
	}

	# Clean up
	$sth->finish();

	$sth=$dbh->prepare(qq{
		SELECT event_id FROM events
		WHERE events.ticket="$ticket_id"
	});
	$sth->execute();
	if (@ary = $sth->fetchrow_array()) {
		print "There are log entries for this ticket.\n";
	}
	$sth->finish();

	return 0;
}


########################################################################
########################################################################
#
# show_ticket_log: Subroutine to print out the log for a ticket
#
########################################################################
########################################################################
sub show_ticket_log($) {

	my ($ticket_id) = $_[0];

	my ($query);		# Our SQL query

	# If our log request is for an ticket < 1, get rid of it
	if ($ticket_id < 1) {
		print STDERR "Requested ticket number < 1!\n";
		return -1;
	}

	$query=qq{
		SELECT event_id, ticket, people.username,
		  billing_codes.name, endtime, starttime, logmessage,
		  private
		FROM events, people, billing_codes
		WHERE events.person = people.person_id
		  AND events.billable = billing_codes.billable_id
		  AND events.ticket="$ticket_id"
	};

	log_print($query);

	return 0;
}


########################################################################
########################################################################
#
# status_ticket: set the status of a ticket to the given priority
#
########################################################################
########################################################################
sub status_ticket($$) {

	my $ticket_id = $_[0];
	my $statusname = $_[1];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}


	if (! defined $status{$statusname}) {
      print STDERR "Status $statusname does not exist in our tables.\n";
		return -1;
	}

	return $dbh->do (qq{
        	UPDATE tickets 
        	SET resolution=$status{$statusname}
        	WHERE ticket_id=$ticket_id
	});

}


########################################################################
########################################################################
#
# ticket_new: Create a new ticket
#
########################################################################
########################################################################
sub ticket_new($$$) {

	my ($email) = $_[0];
	my ($shortdesc) = $_[1];
	my ($quoted_longdesc) = $_[2];

	my ($sth);

	my ($loop);			# travel through the hash
	my ($found) = 0;		# Have we found our answer yet?
	my ($answer) = "";		# What is that answer?
	my ($quoted_shortdesc);		# safe for the database
	my ($ticket_id);		# nice copy of new ticket number

	# All of our numerous sanity checks
	#
	if (! defined $category{'unsorted'}) {
	       print STDERR "No category \"unsorted\" in database!\n";
	       return -1;
	}

	if (! defined $people{'unassigned'}) {
		print STDERR "No person \"unassigned\" in database!\n";
		return -1;
	}

	if (! defined $priority{'medium'}) {
		print STDERR "No priority \"medium\" in database!\n";
		return -1;
	}

	if (! defined $status{'new'}) {
		print STDERR "No status \"new\" in database!\n";
		return -1;
	}

	if (! defined $customer{'unknown'}) {
		print STDERR "No customer \"unknown\" in database!\n";
		return -1;
	}

	# OK, now that we're sane, find the matching customer e-mail
	foreach $loop (keys %people) {
		if ($people{$loop}[1] =~ /$email/) {
			$found=1;
			$answer=$loop;
		}
	}

	# if not found, we'll create a new person and try again
	if (! $found) {
		person_new("tempname", "New Person", $email, "unknown");
		load_people;
		print STDERR "User ID 'tempname' created for $email\n";
		print STDERR "You should probably change that.\n";
		return ticket_new($email, $shortdesc, $quoted_longdesc);
	}

	# Protect quotes in the short description
	$quoted_shortdesc=$dbh->quote($shortdesc);

	# Create the !@#$% ticket already
	$dbh->do (qq{
		INSERT INTO tickets VALUES(
			NULL,
			$people{$answer}[0],
			$people{$answer}[0],
			$people{$answer}[2],
			$category{'unsorted'},
			$priority{'medium'},
			NULL,
			NULL,
			$people{'unassigned'}[0],
			$quoted_shortdesc,
			$quoted_longdesc,
			NULL,
			$status{'new'},
			NULL)
	});

	# Now we need the ID of the new ticket
	$sth = $dbh->prepare(qq{
		SELECT ticket_id
		FROM tickets
		WHERE resolution=$status{'new'}
		  AND assignedto=$people{'unassigned'}[0]
		  AND longdesc=$quoted_longdesc
	});
	$sth->execute();
	$ticket_id = $sth->fetchrow_array();
	$sth->finish();

	$ticket_id= sprintf("%06d", $ticket_id);
	print STDERR ("New ticket created: $ticket_id\n");
	return $ticket_id;
}


########################################################################
########################################################################
#
# ticket_new_stdin: Create a new ticket, long desc from stdin
#
########################################################################
########################################################################
sub ticket_new_stdin($$) {

	my ($email) = $_[0];
	my ($shortdesc) = $_[1];

	my (@longdesc);			# long description of problem
	my ($quoted_longdesc) = "";	# quote-safe version of same

	# Get the long description, with proper quoting for SQL
	@longdesc=<STDIN>;
	$quoted_longdesc=$dbh->quote("@longdesc");

	return ticket_new($email, $shortdesc, $quoted_longdesc);
}


########################################################################
########################################################################
#
# touch: set the timestamp on a ticket
#
########################################################################
########################################################################
sub touch($) {

	my ($ticket_id) = $_[0];

	if ($ticket_id < 1) {
		print STDERR
			"Ticket number must be a positive integer!\n";
		return -1;
	}

	return $dbh->do (qq{
		UPDATE tickets
		SET lastmodified=NOW()
		WHERE ticket_id=$ticket_id
	});
}
