[freeside-devel] Experimental RPM installation of Freeside (long)

Richard Siddall richard.siddall at elirion.net
Wed Sep 21 14:16:50 PDT 2005


Attached to this e-mail are some files making up the experimental
RPM-based installation of Freeside that I've been working on for about
six months.

Obviously this is not intended to replace the tarball/CPAN install, just
provide the option of doing an RPM-based install on RPM-based systems
for those Freeside users that would prefer that approach.

This started on Fedora Core 3 with Freeside 1.5.0pre6, and has been used
successfully to install 1.5.7 on Fedora Core 2.  I haven't tried it on
any other RPM-based distributions.  Some fixing up was required after
installing the RPMs.

The files are:

install-rpm.html - This gives you an idea of how to install Freeside
from RPMs.  This file does not get installed by the RPM.

freeside.spec - This is the spec file that you use with rpmbuild to
generate the various RPMs.  There's a core Freeside RPM, plus a web
framework front-end RPM and a database back-end RPM.  Just copy all the
sources and the Freeside tarball to your RPM sources directory, and run
rpmbuild.  Only the HTML::Mason and PostgreSQL RPMs have been tested.
You can build RPMs for Apache::ASP and MySQL, but these are untested.

freeside-1.5.7.build.patch - This patch modifies Makefile to have it
create some subdirectories for the HTML documentation, and changes
freeside-deloutsource to use the same database naming as
freeside-addoutsource.

freeside-1.5.7.dbd-pg.patch - This patch is a workaround for a bug in
DBD::Pg.

freeside-1.5.7.mod_perl2.patch - These are some hacks to get
Freeside to work on Apache 2 with mod_perl > 1.29 as that's what comes
with the FC2 distribution.  The Great Mod_Perl 2 Debacle may be
over, but FC2 only comes with mod_perl 1.99.  So, you may want to 
install a newer mod_perl or continue the practice of building and
installing Apache 1.  See:
http://www.sisd.com/cgi-bin/wiki.pl?Freeside_FAQ and note that the first 
Apache2-compatible RT was announced on 28-Aug-2005.
(This patch should be reworked to make Freeside work with either version 
of mod_perl.)

freeside-1.5.7.redhat.patch - This patches freeside-init so it reads the
QUEUED_USER and other settings from /etc/sysconfig/freeside.

freeside-1.5.7.rpm.patch - This is a workaround for a bug in RPM's
handling of here documents in Perl scripts that results in it generating
an incorrect list of dependencies.

freeside-1.5.7.nasport.patch - This adds the ability to bill for excess
hourly usage for selected types of connections only.  I added this as we
needed the ability to charge ISDN customers for overage.  The UI is
crude.  I modified seconds_from_sqlradacct to return a hash indexed by
NASPortType.  I also modified the SQL query as the data set I was using
returned over 40,000 hours when the correct value was more like 1165.
I believe this is due to stale sessions in the data set.  I don't think 
you can guarantee that when Freeside accesses a RADIUS accounting server 
it will encounter a clean data set.  I would not recommend using this as 
is.  I think it still results in an incorrect result for number of hours 
used, but the error is in the customer's favor.

freeside-1.5.7.emailsubject.patch - This adds the ability to configure
the subject lines for invoice, receipt, and decline e-mails.  The
billing system we're switching from includes the company name in invoice
e-mails, so I needed to add this ability to Freeside.  I made all these
configuration items Text::Templates, and am now using values like:
	{ $ispname } - Invoice { $invnum }
I started on trying to pass hashes containing a consistent set of values
to all the templates.  For some reason I wimped out of changing the
cancellation e-mails at all.

freeside-1.5.7.flat_prorate.patch - This passes 'approx' to 
Delta_Format, which only works with Date::Manip >= 5.44.  Without this I 
was getting a divide by zero error when trying to prorate monthly 
packages as Delta_Format was returning zero for the number of days in 
one or three months.

freeside-1.5.7.typo.patch - This corrects a few speling errors.

freeside-asp.conf - This is an untested Apache 2 configuration file for
Freeside under Apache::ASP.  The freeside-apacheasp RPM puts it in
/etc/httpd/conf.d and all you need to do is change the User/Group in
httpd.conf.

freeside-mason.conf - This is an Apache 2 configuration file for
Freeside under HTML::Mason.  The freeside-mason RPM puts it in
/etc/httpd/conf.d and all you need to do is change the User/Group in
httpd.conf to freeside/freeside.

freeside-import - This is a new Perl script to read in one or more
text files containing Freeside object descriptions in a format like
Windows INI files.  It's probably not the best choice for a file format.
You can use freeside-import for a lot of things, including initial
administration and converting from other billing systems.  It doesn't
currently use the Freeside schema description, and it does not include
some of the more exotic objects.  See the Perl API for more details on
the objects.  It's single pass in operation: it reads in the files and
builds a data structure of all the objects, then walks the data
structure calling the Perl API to instantiate the objects in the
database.  If you put text instead of an object number in a key field,
freeside-import does a lookup to find the corresponding object.  It dies
if the lookup fails.  You can use syntax like "==cust_main" to denote
the last created cust_main object (double equals means last created). 
If you want a # in the value string, enclose the string in single or 
double quotes.

freeside-install - This is a new Perl script to complete the RPM
installation.  Originally I was going to take one of Ivan's INSTALL
scripts from the /install directory in the tarball, break it up into
lots of functions, and add command line switches so you could run parts
of the complete installation.  In the end I took the FC3 INSTALL script
and threw out everything but the few steps that a well-structured RPM
shouldn't do: the interactive ones.

init.txt - Sample input to freeside-install to perform "initial
administration"
tax.txt - Sample input to freeside-install to set up some tax rates
products.txt - Sample input to freeside-install to define some services
and packages.
export.txt - Sample input to freeside-install to set up an export and
associate it with a service.
invevent.txt - Sample input to add two invoice events for CARD customers 
to first run the card and then send an invoice reflecting the outcome.

In summary, this implementation of the RPM install is incomplete.  It
takes us a good deal of the way to making Freeside installation on
RPM-based systems as easy as:
	yum install freeside-mason freeside-postgresql
perl-Business-OnlinePayment-AuthorizeNet
	freeside-install <name> (and enter a few passwords)

However, there are still a few rough edges:
- Only tested on one distribution
- No yum/apt repositories with the required packages on them
- You still need to do a bit of fixing up after the above two lines
- There's no support for RT yet
- There's no support for the self-service interface yet (i.e. running
yum install freeside-selfservice-client on an RPM-based client system).
- We haven't figured out how to integrate this into the tarball so that
it works with future Freeside releases but the tarball/CPAN install also
still works.

I'll be interested to see if Ivan thinks any of this is worth committing
to CVS.

Regards,

	Richard Siddall

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://420.am/pipermail/freeside-devel/attachments/20050921/a7b14aa4/install-rpm-0001.html
-------------- next part --------------
#QUEUED_USER=
#RADACCTD_USER=
#
#SELFSERVICE_USER=
#SELFSERVICE_MACHINES=
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/bin/freeside-deloutsource freeside-1.5.7/FS/bin/freeside-deloutsource
--- freeside-1.5.7.orig/FS/bin/freeside-deloutsource	2002-09-20 11:47:58.000000000 -0400
+++ freeside-1.5.7/FS/bin/freeside-deloutsource	2005-08-27 17:59:05.000000000 -0400
@@ -3,9 +3,9 @@
 domain=$1
 
 dropdb $domain && \
-rm -rf /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \
-rm -rf /usr/local/etc/freeside/counters.DBI:Pg:host=localhost\;dbname=$domain && \
-rm -rf /usr/local/etc/freeside/cache.DBI:Pg:host=localhost\;dbname=$domain && \
-rm -rf /usr/local/etc/freeside/export.DBI:Pg:host=localhost\;dbname=$domain && \
-rm /usr/local/etc/freeside/dbdef.DBI:Pg:host=localhost\;dbname=$domain
+rm -rf /usr/local/etc/freeside/conf.DBI:Pg:dbname=$domain && \
+rm -rf /usr/local/etc/freeside/counters.DBI:Pg:dbname=$domain && \
+rm -rf /usr/local/etc/freeside/cache.DBI:Pg:dbname=$domain && \
+rm -rf /usr/local/etc/freeside/export.DBI:Pg:dbname=$domain && \
+rm /usr/local/etc/freeside/dbdef.DBI:Pg:dbname=$domain
 
diff -Naur freeside-1.5.7.orig/Makefile freeside-1.5.7/Makefile
--- freeside-1.5.7.orig/Makefile	2005-07-11 09:01:16.000000000 -0400
+++ freeside-1.5.7/Makefile	2005-08-30 22:04:10.320868163 -0400
@@ -136,8 +136,12 @@
 	[ -e ./httemplate/docs/man ] || mkdir httemplate/docs/man
 	[ -e ./httemplate/docs/man/bin ] || mkdir httemplate/docs/man/bin
 	[ -e ./httemplate/docs/man/FS ] || mkdir httemplate/docs/man/FS
+	[ -e ./httemplate/docs/man/FS/ClientAPI ] || mkdir httemplate/docs/man/FS/ClientAPI
+	[ -e ./httemplate/docs/man/FS/Report ] || mkdir httemplate/docs/man/FS/Report
+	[ -e ./httemplate/docs/man/FS/TicketSystem ] || mkdir httemplate/docs/man/FS/TicketSystem
 	[ -e ./httemplate/docs/man/FS/UI ] || mkdir httemplate/docs/man/FS/UI
 	[ -e ./httemplate/docs/man/FS/part_export ] || mkdir httemplate/docs/man/FS/part_export
+	[ -e ./httemplate/docs/man/FS/part_pkg ] || mkdir httemplate/docs/man/FS/part_pkg
 	chmod a+rx bin/pod2x
 	[ -e DONT_REBUILD_DOCS ] || bin/pod2x
 
@@ -145,8 +149,12 @@
 	[ -e ./httemplate/docs/man ] || mkdir httemplate/docs/man
 	[ -e ./httemplate/docs/man/bin ] || mkdir httemplate/docs/man/bin
 	[ -e ./httemplate/docs/man/FS ] || mkdir httemplate/docs/man/FS
+	[ -e ./httemplate/docs/man/FS/ClientAPI ] || mkdir httemplate/docs/man/FS/ClientAPI
+	[ -e ./httemplate/docs/man/FS/Report ] || mkdir httemplate/docs/man/FS/Report
+	[ -e ./httemplate/docs/man/FS/TicketSystem ] || mkdir httemplate/docs/man/FS/TicketSystem
 	[ -e ./httemplate/docs/man/FS/UI ] || mkdir httemplate/docs/man/FS/UI
 	[ -e ./httemplate/docs/man/FS/part_export ] || mkdir httemplate/docs/man/FS/part_export
+	[ -e ./httemplate/docs/man/FS/part_pkg ] || mkdir httemplate/docs/man/FS/part_pkg
 	bin/pod2x
 
 install-docs: docs
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/FS/Record.pm freeside-1.5.7/FS/FS/Record.pm
--- freeside-1.5.7.orig/FS/FS/Record.pm	2005-05-04 05:20:34.000000000 -0400
+++ freeside-1.5.7/FS/FS/Record.pm	2005-07-26 14:10:31.000000000 -0400
@@ -337,9 +337,9 @@
     if ( $record->{$field} =~ /^\d+(\.\d+)?$/
          && $dbdef->table($table)->column($field)->type =~ /(int|serial)/i
     ) {
-      $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } );
+      $sth->bind_param($bind++, $record->{$field}, SQL_INTEGER );
     } else {
-      $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_VARCHAR } );
+      $sth->bind_param($bind++, $record->{$field}, SQL_VARCHAR );
     }
   }
 
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/bin/freeside-expiration-alerter freeside-1.5.7/FS/bin/freeside-expiration-alerter
--- freeside-1.5.7.orig/FS/bin/freeside-expiration-alerter	2003-04-21 16:53:57.000000000 -0400
+++ freeside-1.5.7/FS/bin/freeside-expiration-alerter	2005-08-29 19:11:41.220781694 -0400
@@ -29,6 +29,7 @@
 my $urgent_time = 15 * 24 * 60 * 60;
 my $panic_time = 5 * 24 * 60 * 60;
 my $window_time = 24 * 60 * 60;
+my $subject = "Unnotified Billing Arrangement Expirations";
 
 &untaint_argv;	#what it sounds like  (eww)
 
@@ -51,6 +52,8 @@
   if $conf->exists('invoice_from');
 $failure_recipient = $conf->config('invoice_from')
   if $conf->exists('invoice_from');
+$subject = $conf->config('alerter_email-subject')
+  if $conf->exists('alerter-email_subject');
 
 
 my(@customers)=qsearch('cust_main',{});
@@ -61,13 +64,16 @@
 
 # Prepare for sending email
 
+my $subject_template = new Text::Template (TYPE => 'STRING', SOURCE => $subject);
+my $mail_subject = $subject_template->fill_in(HASH => { ispname => $conf->config('company_name') });
+
 $ENV{MAILADDRESS} = $mail_sender;
 my $header = new Mail::Header ( [
   "From: Account Processor",
   "To: $failure_recipient",
   "Sender: $mail_sender",
   "Reply-To: $mail_sender",
-  "Subject: Unnotified Billing Arrangement Expirations",
+  "Subject: $mail_subject",
 ] );
 
 my @alerter_template = $conf->config('alerter_template')
diff -Naur freeside-1.5.7.orig/FS/FS/Conf.pm freeside-1.5.7/FS/FS/Conf.pm
--- freeside-1.5.7.orig/FS/FS/Conf.pm	2005-06-30 09:20:33.000000000 -0400
+++ freeside-1.5.7/FS/FS/Conf.pm	2005-08-28 12:18:03.000000000 -0400
@@ -300,6 +300,13 @@
   },
 
   {
+    'key'         => 'alerter_email-subject',
+    'section'     => 'billing',
+    'description' => 'Template for the subject lines of billing method expiration alerts.  See the <a href="../docs/billing.html#invoice_template">billing documentation</a> for details.',
+    'type'        => 'text',
+  },
+
+  {
     'key'         => 'apacheroot',
     'section'     => 'deprecated',
     'description' => '<b>DEPRECATED</b>, add a <i>www_shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead.  The directory containing Apache virtual hosts',
@@ -674,6 +681,13 @@
   },
 
   {
+    'key'         => 'invoice_email-subject',
+    'section'     => 'billing',
+    'description' => 'Template for the e-mail subject of invoices.',
+    'type'        => 'text',
+  },
+
+  {
     'key'         => 'payment_receipt_email',
     'section'     => 'billing',
     'description' => 'Template file for payment receipts.',
@@ -681,6 +695,13 @@
   },
 
   {
+    'key'         => 'receipt_email-subject',
+    'section'     => 'billing',
+    'description' => 'Template for the e-mail subject line of payment receipts.',
+    'type'        => 'text',
+  },
+
+  {
     'key'         => 'lpr',
     'section'     => 'required',
     'description' => 'Print command for paper invoices, for example `lpr -h\'',
@@ -1209,6 +1230,13 @@
   },
 
   {
+    'key'         => 'decline_email-subject',
+    'section'     => 'billing',
+    'description' => 'Template for the subject line of credit card decline emails.',
+    'type'        => 'text',
+  },
+
+  {
     'key'         => 'emaildecline',
     'section'     => 'billing',
     'description' => 'Enable emailing of credit card decline notices.',
diff -Naur freeside-1.5.7.orig/FS/FS/cust_bill.pm freeside-1.5.7/FS/FS/cust_bill.pm
--- freeside-1.5.7.orig/FS/FS/cust_bill.pm	2005-07-09 11:41:18.000000000 -0400
+++ freeside-1.5.7/FS/FS/cust_bill.pm	2005-08-29 17:23:32.000000000 -0400
@@ -356,9 +356,23 @@
 
   my $me = '[FS::cust_bill::generate_email]';
 
+  my $subject = $conf->config('invoice_email-subject');
+  $subject = 'Invoice' if !$subject;
+
+  my $subject_template = new Text::Template(TYPE => 'STRING', SOURCE => $subject);
+  my $templ_hash = {
+    ispname => $conf->config('company_name'),
+    invnum => $self->invnum,
+    date => $self->_date,
+    money_char => $conf->config('money_char') || '$',
+  };
+  foreach (qw/first last company address1 address2 city state zip country/) {
+    $$templ_hash{$_} = $self->cust_main->getfield($_);
+  }
+
   my %return = (
     'from'      => $args{'from'},
-    'subject'   => (($args{'subject'}) ? $args{'subject'} : 'Invoice'),
+    'subject'   => (($args{'subject'}) ? $args{'subject'} : $subject_template->fill_in(HASH => $templ_hash)),
   );
 
   if (ref($args{'to'} eq 'ARRAY')) {
diff -Naur freeside-1.5.7.orig/FS/FS/cust_main.pm freeside-1.5.7/FS/FS/cust_main.pm
--- freeside-1.5.7.orig/FS/FS/cust_main.pm	2005-06-30 08:44:46.000000000 -0400
+++ freeside-1.5.7/FS/FS/cust_main.pm	2005-08-29 19:20:57.241357962 -0400
@@ -2278,12 +2278,24 @@
       $template->compile()
         or return "($perror) can't compile template: $Text::Template::ERROR";
 
-      my $templ_hash = { error => $transaction->error_message };
+      my $subject = $conf->config('decline_email-subject');
+      $subject = 'Your payment could not be processed' if !$subject;
+      my $subject_template = new Text::Template(TYPE => 'STRING', SOURCE => $subject);
+
+      my $templ_hash = {
+        error => $transaction->error_message,
+        ispname => $conf->config('company_name'),
+        money_char => $conf->config('money_char') || '$',
+        name => $self->name,
+      };
+      foreach (qw/first last company address1 address2 city state zip country/) {
+	$$templ_hash{$_} = $self->getfield($_);
+      }
 
       my $error = send_email(
         'from'    => $conf->config('invoice_from'),
         'to'      => [ grep { $_ ne 'POST' } $self->invoicing_list ],
-        'subject' => 'Your payment could not be processed',
+        'subject' => $subject_template->fill_in(HASH => $templ_hash),
         'body'    => [ $template->fill_in(HASH => $templ_hash) ],
       );
 
diff -Naur freeside-1.5.7.orig/FS/FS/cust_pay.pm freeside-1.5.7/FS/FS/cust_pay.pm
--- freeside-1.5.7.orig/FS/FS/cust_pay.pm	2005-06-08 20:18:35.000000000 -0400
+++ freeside-1.5.7/FS/FS/cust_pay.pm	2005-08-29 17:08:10.000000000 -0400
@@ -185,6 +185,10 @@
       return '';
     };
 
+    my $subject = $conf->config('receipt_email-subject');
+    $subject = 'Payment receipt' if !$subject;
+    my $subject_template = new Text::Template(TYPE => 'STRING', SOURCE => $subject);
+
     my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list;
 
     my $payby = $self->payby;
@@ -193,11 +197,7 @@
     $payinfo = $self->payinfo_masked if $payby eq 'CARD' || $payby eq 'CHEK';
     $payby =~ s/^CHEK$/Electronic check/;
 
-    my $error = send_email(
-      'from'    => $conf->config('invoice_from'), #??? well as good as any
-      'to'      => \@invoicing_list,
-      'subject' => 'Payment receipt',
-      'body'    => [ $receipt_template->fill_in( HASH => {
+    my $templ_hash = {
                        'date'    => time2str("%a %B %o, %Y", $self->_date),
                        'name'    => $cust_main->name,
                        'paynum'  => $self->paynum,
@@ -205,7 +205,18 @@
                        'payby'   => ucfirst(lc($payby)),
                        'payinfo' => $payinfo,
                        'balance' => $cust_main->balance,
-                   } ) ],
+                       'ispname' => $conf->config('company_name'),
+                       'money_char' => $conf->config('money_char') || '$',
+                   };
+    foreach (qw/first last company address1 address2 city state zip country/) {
+      $$templ_hash{$_} = $self->cust_main->getfield($_);
+    }
+
+    my $error = send_email(
+      'from'    => $conf->config('invoice_from'), #??? well as good as any
+      'to'      => \@invoicing_list,
+      'subject' => $subject_template->fill_in( HASH => $templ_hash ),
+      'body'    => [ $receipt_template->fill_in( HASH => $templ_hash ) ],
     );
     if ( $error ) {
       warn "can't send payment receipt: $error";
diff -Naur freeside-1.5.7.orig/FS/FS/cust_pkg.pm freeside-1.5.7/FS/FS/cust_pkg.pm
--- freeside-1.5.7.orig/FS/FS/cust_pkg.pm	2005-03-21 17:13:36.000000000 -0500
+++ freeside-1.5.7/FS/FS/cust_pkg.pm	2005-08-29 17:29:38.747520156 -0400
@@ -425,7 +425,7 @@
   my $conf = new FS::Conf;
   my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->invoicing_list;
   if ( !$options{'quiet'} && $conf->exists('emailcancel') && @invoicing_list ) {
-    my $conf = new FS::Conf;
+#    my $conf = new FS::Conf;
     my $error = send_email(
       'from'    => $conf->config('invoice_from'),
       'to'      => \@invoicing_list,
diff -Naur freeside-1.5.7.orig/FS/FS/svc_acct.pm freeside-1.5.7/FS/FS/svc_acct.pm
--- freeside-1.5.7.orig/FS/FS/svc_acct.pm	2005-06-30 09:20:33.000000000 -0400
+++ freeside-1.5.7/FS/FS/svc_acct.pm	2005-08-29 17:10:03.000000000 -0400
@@ -304,19 +304,27 @@
           'svcnum' => $self->svcnum,
           'job'    => 'FS::svc_acct::send_email'
         };
-        my $error = $wqueue->insert(
-          'to'       => $to,
-          'from'     => $welcome_from,
-          'subject'  => $welcome_subject,
-          'mimetype' => $welcome_mimetype,
-          'body'     => $welcome_template->fill_in( HASH => {
+        my $subject_template = new Text::Template(TYPE => 'STRING', SOURCE => $welcome_subject);
+        my $templ_hash = {
                           'custnum'  => $self->custnum,
                           'username' => $self->username,
                           'password' => $self->_password,
-                          'first'    => $cust_main->first,
-                          'last'     => $cust_main->getfield('last'),
+#                          'first'    => $cust_main->first,
+#                          'last'     => $cust_main->getfield('last'),
                           'pkg'      => $cust_pkg->part_pkg->pkg,
-                        } ),
+                          'ispname'  => $conf->config('company_name'),
+                          'money_char' => $conf->config('money_char') || '$',
+                        };
+        foreach (qw/first last company address1 address2 city state zip country/) {
+          $$templ_hash{$_} = $cust_main->getfield($_);
+        }
+
+        my $error = $wqueue->insert(
+          'to'       => $to,
+          'from'     => $welcome_from,
+          'subject'  => $subject_template->fill_in( HASH => $templ_hash ),
+          'mimetype' => $welcome_mimetype,
+          'body'     => $welcome_template->fill_in( HASH => $templ_hash ),
         );
         if ( $error ) {
           $dbh->rollback if $oldAutoCommit;
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/FS/part_pkg/flat.pm freeside-1.5.7/FS/FS/part_pkg/flat.pm
--- freeside-1.5.7.orig/FS/FS/part_pkg/flat.pm	2005-07-09 06:36:43.000000000 -0400
+++ freeside-1.5.7/FS/FS/part_pkg/flat.pm	2005-09-20 14:28:40.285111162 -0400
@@ -58,7 +58,7 @@
   my $next_date = ParseDate("epoch $next_bill");
   my $err;
   my $delta = DateCalc($now_date,$next_date,\$err, 0);
-  my $days_remaining = Delta_Format($delta, 4, "%dh");
+  my $days_remaining = Delta_Format($delta, 'approx', 4, "%dh");
 
   my $frequency = $self->freq;
 
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/FS/CGI.pm freeside-1.5.7/FS/FS/CGI.pm
--- freeside-1.5.7.orig/FS/FS/CGI.pm	2004-12-10 17:28:25.000000000 -0500
+++ freeside-1.5.7/FS/FS/CGI.pm	2005-07-26 22:41:07.000000000 -0400
@@ -91,7 +91,8 @@
       if ( $header =~ /^Content-Type$/ ) {
         $HTML::Mason::Commands::r->content_type($value);
       } else {
-        $HTML::Mason::Commands::r->header_out( $header => $value );
+	# Modified for mod_perl 1.99+
+        $HTML::Mason::Commands::r->headers_out->{ $header } = $value;
       }
     } else {
       die "http_header called in unknown environment";
@@ -168,8 +169,9 @@
 
 sub eidiot {
   warn "eidiot depriciated";
-  $HTML::Mason::Commands::r->send_http_header
-    if defined $HTML::Mason::Commands::r;
+# Disabled for mod_perl 1.99+, should be an eval{}
+#  $HTML::Mason::Commands::r->send_http_header
+#    if defined $HTML::Mason::Commands::r;
   idiot(@_);
   &myexit();
 }
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/bin/freeside-sqlradius-seconds freeside-1.5.7/FS/bin/freeside-sqlradius-seconds
--- freeside-1.5.7.orig/FS/bin/freeside-sqlradius-seconds	2002-12-17 05:42:26.000000000 -0500
+++ freeside-1.5.7/FS/bin/freeside-sqlradius-seconds	2005-09-09 10:45:39.010386201 -0400
@@ -17,7 +17,12 @@
 my $svc_acct = qsearchs( 'svc_acct', { 'username' => $target_user } );
 die "username $target_user not found\n" unless $svc_acct;
 
-print $svc_acct->seconds_since_sqlradacct( $start, $stop ). "\n";
+my %usage = $svc_acct->seconds_since_sqlradacct( $start, $stop );
+my $seconds = 0;
+foreach (keys %usage) {
+  $seconds += $usage{$_};
+}
+print $seconds . "\n";
 
 sub usage {
   die "Usage:\n\n  freeside-sqlradius-seconds freeside_username target_username start_date stop_date\n";
diff -Naur freeside-1.5.7.orig/FS/FS/cust_pkg.pm freeside-1.5.7/FS/FS/cust_pkg.pm
--- freeside-1.5.7.orig/FS/FS/cust_pkg.pm	2005-03-21 17:13:36.000000000 -0500
+++ freeside-1.5.7/FS/FS/cust_pkg.pm	2005-09-08 16:10:06.000000000 -0400
@@ -857,7 +857,7 @@
 sub seconds_since_sqlradacct {
   my($self, $start, $end) = @_;
 
-  my $seconds = 0;
+  my %seconds = ();
 
   foreach my $cust_svc (
     grep {
@@ -866,10 +866,13 @@
         && scalar($part_svc->part_export('sqlradius'));
     } $self->cust_svc
   ) {
-    $seconds += $cust_svc->seconds_since_sqlradacct($start, $end);
+    my %result = $cust_svc->seconds_since_sqlradacct($start, $end);
+    foreach (keys %result) {
+      $seconds{$_} += $result{$_};
+    }
   }
 
-  $seconds;
+  %seconds;
 
 }
 
diff -Naur freeside-1.5.7.orig/FS/FS/cust_svc.pm freeside-1.5.7/FS/FS/cust_svc.pm
--- freeside-1.5.7.orig/FS/FS/cust_svc.pm	2005-06-09 16:16:58.000000000 -0400
+++ freeside-1.5.7/FS/FS/cust_svc.pm	2005-09-08 17:12:12.000000000 -0400
@@ -390,7 +390,7 @@
     unless @part_export;
     #or return undef;
 
-  my $seconds = 0;
+  my %seconds = ();
   foreach my $part_export ( @part_export ) {
 
     next if $part_export->option('ignore_accounting');
@@ -423,65 +423,82 @@
     my $query;
   
     #find closed sessions completely within the given range
-    my $sth = $dbh->prepare("SELECT SUM(acctsessiontime)
+    my $sth = $dbh->prepare("SELECT NASPortType, SUM(acctsessiontime)
                                FROM radacct
                                WHERE UserName = ?
                                  AND $str2time AcctStartTime) >= ?
                                  AND $str2time AcctStopTime ) <  ?
                                  AND $str2time AcctStopTime ) > 0
-                                 AND AcctStopTime IS NOT NULL"
+                                 AND AcctStopTime IS NOT NULL
+                                 AND NASPortType IS NOT NULL
+                                 GROUP BY NASPortType"
     ) or die $dbh->errstr;
     $sth->execute($username, $start, $end) or die $sth->errstr;
-    my $regular = $sth->fetchrow_arrayref->[0];
+    my %regular = ();
+    while (my $h = $sth->fetchrow_arrayref()) {
+      $seconds{$h->[0]} = $h->[1];
+    }
   
     #find open sessions which start in the range, count session start->range end
-    $query = "SELECT SUM( ? - $str2time AcctStartTime ) )
+    $query = "SELECT NASPortType, SUM( ? - $str2time AcctStartTime ) )
                 FROM radacct
                 WHERE UserName = ?
                   AND $str2time AcctStartTime ) >= ?
                   AND $str2time AcctStartTime ) <  ?
                   AND ( ? - $str2time AcctStartTime ) ) < 86400
                   AND (    $str2time AcctStopTime ) = 0
-                                    OR AcctStopTime IS NULL )";
+                                    OR AcctStopTime IS NULL )
+                  AND NASPortType IS NOT NULL
+                  GROUP BY NASPortType";
     $sth = $dbh->prepare($query) or die $dbh->errstr;
     $sth->execute($end, $username, $start, $end, $end)
       or die $sth->errstr. " executing query $query";
-    my $start_during = $sth->fetchrow_arrayref->[0];
+    while (my $h = $sth->fetchrow_arrayref()) {
+#      $seconds{$h->[0]} += $h->[1];
+    }
   
     #find closed sessions which start before the range but stop during,
     #count range start->session end
-    $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime ) - ? ) 
+    $sth = $dbh->prepare("SELECT NASPortType, SUM( $str2time AcctStopTime ) - ? ) 
                             FROM radacct
                             WHERE UserName = ?
                               AND $str2time AcctStartTime ) < ?
                               AND $str2time AcctStopTime  ) >= ?
                               AND $str2time AcctStopTime  ) <  ?
                               AND $str2time AcctStopTime ) > 0
-                              AND AcctStopTime IS NOT NULL"
+                              AND AcctStopTime IS NOT NULL
+                              AND AcctStartTime > 0
+                              AND NASPortType IS NOT NULL
+                              GROUP BY NASPortType"
     ) or die $dbh->errstr;
     $sth->execute($start, $username, $start, $start, $end ) or die $sth->errstr;
-    my $end_during = $sth->fetchrow_arrayref->[0];
+    while (my $h = $sth->fetchrow_arrayref()) {
+#      $seconds{$h->[0]} += $h->[1];
+    }
   
     #find closed (not anymore - or open) sessions which start before the range
     # but stop after, or are still open, count range start->range end
     # don't count open sessions (probably missing stop record)
-    $sth = $dbh->prepare("SELECT COUNT(*)
+    $sth = $dbh->prepare("SELECT NASPortType, COUNT(*)
                             FROM radacct
                             WHERE UserName = ?
                               AND $str2time AcctStartTime ) < ?
-                              AND ( $str2time AcctStopTime ) >= ?
-                                                                  )"
+                              AND $str2time AcctStopTime ) >= ?
+                              AND AcctStartTime > 0
+                              AND NASPortType IS NOT NULL
+                              GROUP BY NASPortType"
                               #      OR AcctStopTime =  0
                               #      OR AcctStopTime IS NULL       )"
     ) or die $dbh->errstr;
     $sth->execute($username, $start, $end ) or die $sth->errstr;
-    my $entire_range = ($end-$start) * $sth->fetchrow_arrayref->[0];
-
-    $seconds += $regular + $end_during + $start_during + $entire_range;
+    while (my $h = $sth->fetchrow_arrayref()) {
+#      my $entire_range = ($end-$start) * $h->[1];
+#      $seconds{$h->[0]} += $entire_range;
+    }
 
   }
 
-  $seconds;
+  %seconds;
 
 }
 
diff -Naur freeside-1.5.7.orig/FS/FS/part_pkg/sqlradacct_hour.pm freeside-1.5.7/FS/FS/part_pkg/sqlradacct_hour.pm
--- freeside-1.5.7.orig/FS/FS/part_pkg/sqlradacct_hour.pm	2005-07-09 06:36:43.000000000 -0400
+++ freeside-1.5.7/FS/FS/part_pkg/sqlradacct_hour.pm	2005-09-08 16:10:06.000000000 -0400
@@ -26,6 +26,10 @@
     'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
                                'default' => 0,
                              },
+    'NASPortTypes' => { 'name' => 'NAS Port Types',
+				'default' => 'Async ISDN Virtual',
+                #                'type' => 'select',
+                             },
     'recur_included_input' => { 'name' => 'Upload megabytes included',
                                 'default' => 0,
                               },
@@ -49,7 +53,7 @@
                               'default' => 0,
                             },
   },
-  'fieldorder' => [qw( setup_fee recur_flat unused_credit recur_included_hours recur_hourly_charge recur_included_input recur_input_charge recur_included_output recur_output_charge recur_included_total recur_total_charge )],
+  'fieldorder' => [qw( setup_fee recur_flat unused_credit recur_included_hours recur_hourly_charge NASPortTypes recur_included_input recur_input_charge recur_included_output recur_output_charge recur_included_total recur_total_charge )],
   #'setup' => 'what.setup_fee.value',
   #'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; my $totalcharge = sprintf(\"%.2f\", \' + what.recur_total_charge.value + \' * $total); my $inputcharge = sprintf(\"%.2f\", \' + what.recur_input_charge.value + \' * $input); my $outputcharge = sprintf(\"%.2f\", \' + what.recur_output_charge.value + \' * $output); my $hourscharge = sprintf(\"%.2f\", \' + what.recur_hourly_charge.value + \' * $hours); if ( \' + what.recur_total_charge.value + \' > 0 ) { push @details, \"Last month\\\'s data \". sprintf(\"%.1f\", $total). \" megs: \\\$$totalcharge\" } if ( \' + what.recur_input_charge.value + \' > 0 ) { push @details, \"Last month\\\'s download \". sprintf(\"%.1f\", $input). \" megs: \\\$$inputcharge\" } if ( \' + what.recur_output_charge.value + \' > 0 ) { push @details, \"Last month\\\'s upload \". sprintf(\"%.1f\", $output). \" megs: \\\$$outputcharge\" } if ( \' + what.recur_hourly_charge.value + \' > 0 ) { push @details, \"Last month\\\'s time \". sprintf(\"%.1f\", $hours). \" hours: \\\$$hourscharge\"; } \' + what.recur_flat.value + \' + $hourscharge + $inputcharge + $outputcharge + $totalcharge ;\'',
   'weight' => 40,
@@ -59,7 +63,12 @@
   my($self, $cust_pkg, $sdate, $details ) = @_;
 
   my $last_bill = $cust_pkg->last_bill;
-  my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $$sdate ) / 3600;
+  my %result = $cust_pkg->seconds_since_sqlradacct($last_bill, $$sdate );
+  my $seconds = 0;
+  foreach (split '\s+', $self->option('NASPortTypes')) {
+    $seconds += $result{$_};
+  }
+  my $hours = $seconds / 3600;
   $hours -= $self->option('recur_included_hours');
   $hours = 0 if $hours < 0;
 
@@ -81,30 +90,37 @@
   $output = 0 if $output < 0;
 
   my $totalcharge =
-    $total  * sprintf('%.2f', $self->option('recur_total_charge'));
+    sprintf('%.2f', $total  * $self->option('recur_total_charge'));
   my $inputcharge =
-    $input  * sprintf('%.2f', $self->option('recur_input_charge'));
+    sprintf('%.2f', $input  * $self->option('recur_input_charge'));
   my $outputcharge = 
-    $output * sprintf('%.2f', $self->option('recur_output_charge'));
+    sprintf('%.2f', $output * $self->option('recur_output_charge'));
 
   my $hourscharge =
-    $hours * sprintf('%.2f', $self->option('recur_hourly_charge'));
+    sprintf('%.2f', $hours * $self->option('recur_hourly_charge'));
+
+#  my $money_char = $conf->config('money_char') || '$';
+  my $money_char = '$';
+
+  if ( $self->option('recur_flat') > 0 ) {
+    push @$details, "Base charge: $money_char" . sprintf('%.2f', $self->option('recur_flat'));
+  }
 
   if ( $self->option('recur_total_charge') > 0 ) {
-    push @$details, "Last month's data ".
-                    sprintf('%.1f', $total). " megs: $totalcharge";
+    push @$details, "Excess data ".
+                    sprintf('%.1f', $total). " megs: $money_char$totalcharge";
   }
   if ( $self->option('recur_input_charge') > 0 ) {
-    push @$details, "Last month's download ".
-                   sprintf('%.1f', $input). " megs: $inputcharge";
+    push @$details, "Excess download ".
+                   sprintf('%.1f', $input). " megs: $money_char$inputcharge";
   }
   if ( $self->option('recur_output_charge') > 0 ) {
-    push @$details, "Last month's upload ".
-                   sprintf('%.1f', $output). " megs: $outputcharge";
+    push @$details, "Excess upload ".
+                   sprintf('%.1f', $output). " megs: $money_char$outputcharge";
   }
   if ( $self->option('recur_hourly_charge')  > 0 ) {
-    push @$details, "Last month\'s time ".
-                   sprintf('%.1f', $hours). " hours: $hourscharge";
+    push @$details, "Excess time ".
+                   sprintf('%.1f', $hours). " hours: $money_char$hourscharge";
   }
 
   $self->option('recur_flat')
diff -Naur freeside-1.5.7.orig/httemplate/view/svc_acct.cgi freeside-1.5.7/httemplate/view/svc_acct.cgi
--- freeside-1.5.7.orig/httemplate/view/svc_acct.cgi	2005-06-08 05:03:06.000000000 -0400
+++ freeside-1.5.7/httemplate/view/svc_acct.cgi	2005-09-08 16:10:06.000000000 -0400
@@ -74,7 +74,11 @@
     %plandata = ();
   }
 
-  my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time );
+  my %usage = $svc_acct->seconds_since_sqlradacct( $last_bill, time );
+  my $seconds = 0;
+  foreach (keys %usage) {
+    $seconds += $usage{$_};
+  }
   my $hour = int($seconds/3600);
   my $min = int( ($seconds%3600) / 60 );
   my $sec = $seconds%60;
@@ -108,6 +112,9 @@
     (no billing cycle available for unaudited account)<BR>
   <% } %>
 
+  <% foreach (sort keys %usage) { %>
+     <%= $_ %>: <B><%= int($usage{$_}/3600) %></B>h <B><%= int(($usage{$_} % 3600) / 60) %></B>m <B><%= $usage{$_} % 60 %></B>s<BR>
+  <% } %>
   Upload: <B><%= sprintf("%.3f", $input) %></B> megabytes<BR>
   Download: <B><%= sprintf("%.3f", $output) %></B> megabytes<BR>
 
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/init.d/freeside-init freeside-1.5.7/init.d/freeside-init
--- freeside-1.5.7.orig/init.d/freeside-init	2005-04-25 05:33:35.000000000 -0400
+++ freeside-1.5.7/init.d/freeside-init	2005-09-08 11:12:21.288590613 -0400
@@ -2,17 +2,11 @@
 #
 # chkconfig: 345 86 16
 # description: Freeside daemons
+# config: /etc/sysconfig/freeside
 
-QUEUED_USER=%%%QUEUED_USER%%%
-
-FREESIDE_PATH="%%%FREESIDE_PATH%%%"
-
-SELFSERVICE_USER=%%%SELFSERVICE_USER%%%
-SELFSERVICE_MACHINES="%%%SELFSERVICE_MACHINES%%%"
-
-#INSTALLSCRIPT/INSTALLSITEBIN from Makefile.PL
-PATH="$PATH:/usr/local/bin"
-export PATH
+if [ -f /etc/sysconfig/freeside ]; then
+        . /etc/sysconfig/freeside
+fi
 
 case "$1" in
   start)
@@ -22,7 +16,7 @@
         echo "done."
 
         echo -n "Starting freeside-sqlradius-radacctd: "
-        freeside-sqlradius-radacctd $QUEUED_USER
+        freeside-sqlradius-radacctd $RADACCTD_USER
         echo "done."
 
         for MACHINE in $SELFSERVICE_MACHINES; do
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/FS/part_export/communigate_pro_singledomain.pm freeside-1.5.7/FS/FS/part_export/communigate_pro_singledomain.pm
--- freeside-1.5.7.orig/FS/FS/part_export/communigate_pro_singledomain.pm	2004-03-24 09:28:47.000000000 -0500
+++ freeside-1.5.7/FS/FS/part_export/communigate_pro_singledomain.pm	2005-07-26 14:09:39.000000000 -0400
@@ -21,8 +21,8 @@
 <a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a>
 mail server.  This is an unusual export to CommuniGate Pro that forces all
 accounts into a single domain.  As CommuniGate Pro supports multipledomains,
-unless you have a specific reason for using this export, you probably want to
-use the communigate_pro export instead.  The
+unless you have a specific reason for using this export, you probably want
+to use the communigate_pro export instead.  The
 <a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a>
 must be installed as CGP::CLI.
 END
-------------- next part --------------
diff -Naur freeside-1.5.7.orig/FS/FS/ConfItem.pm freeside-1.5.7/FS/FS/ConfItem.pm
--- freeside-1.5.7.orig/FS/FS/ConfItem.pm	2001-10-24 11:29:30.000000000 -0400
+++ freeside-1.5.7/FS/FS/ConfItem.pm	2005-09-16 23:26:43.000000000 -0400
@@ -2,7 +2,7 @@
 
 =head1 NAME
 
-FS::ConfItem - Configutaion option meta-data.
+FS::ConfItem - Configuration option meta-data.
 
 =head1 SYNOPSIS
 
diff -Naur freeside-1.5.7.orig/FS/FS/Conf.pm freeside-1.5.7/FS/FS/Conf.pm
--- freeside-1.5.7.orig/FS/FS/Conf.pm	2005-06-30 09:20:33.000000000 -0400
+++ freeside-1.5.7/FS/FS/Conf.pm	2005-09-16 23:25:28.000000000 -0400
@@ -512,7 +512,7 @@
   {
     'key'         => 'erpcdmachines',
     'section'     => 'deprecated',
-    'description' => '<b>DEPRECATED</b>, ERPCD is no longer supported.  Used to be ERPCD authenticaion machines, one per line.  This enables export of `/usr/annex/acp_passwd\' and `/usr/annex/acp_dialup\'',
+    'description' => '<b>DEPRECATED</b>, ERPCD is no longer supported.  Used to be ERPCD authentication machines, one per line.  This enables export of `/usr/annex/acp_passwd\' and `/usr/annex/acp_dialup\'',
     'type'        => 'textarea',
   },
 
@@ -1456,7 +1456,7 @@
   {
     'key'         => 'ticket_system',
     'section'     => '',
-    'description' => 'Ticketing system integraiton.  <b>RT_Internal</b> uses the built-in RT ticketing system (see the <a href="../docs/install-rt">integrated ticketing installation instructions</a>).   <b>RT_External</b> accesses an external RT installation in a separate database (local or remote).',
+    'description' => 'Ticketing system integration.  <b>RT_Internal</b> uses the built-in RT ticketing system (see the <a href="../docs/install-rt">integrated ticketing installation instructions</a>).   <b>RT_External</b> accesses an external RT installation in a separate database (local or remote).',
     'type'        => 'select',
     #'select_enum' => [ '', qw(RT_Internal RT_Libs RT_External) ],
     'select_enum' => [ '', qw(RT_Internal RT_External) ],
diff -Naur freeside-1.5.7.orig/FS/FS/cust_pkg.pm freeside-1.5.7/FS/FS/cust_pkg.pm
--- freeside-1.5.7.orig/FS/FS/cust_pkg.pm	2005-03-21 17:13:36.000000000 -0500
+++ freeside-1.5.7/FS/FS/cust_pkg.pm	2005-09-16 23:26:28.000000000 -0400
@@ -16,7 +16,7 @@
 
 # need to 'use' these instead of 'require' in sub { cancel, suspend, unsuspend,
 # setup }
-# because they load configuraion by setting FS::UID::callback (see TODO)
+# because they load configuration by setting FS::UID::callback (see TODO)
 use FS::svc_acct;
 use FS::svc_domain;
 use FS::svc_www;
@@ -306,7 +306,7 @@
              qsearchs( 'reg_code', { 'code'     => $self->reg_code,
                                      'agentnum' => $self->cust_main->agentnum })
            ) {
-      return "Unknown registraiton code";
+      return "Unknown registration code";
     }
 
   } elsif ( $self->promo_code ) {
diff -Naur freeside-1.5.7.orig/FS/FS/cust_svc.pm freeside-1.5.7/FS/FS/cust_svc.pm
--- freeside-1.5.7.orig/FS/FS/cust_svc.pm	2005-06-09 16:16:58.000000000 -0400
+++ freeside-1.5.7/FS/FS/cust_svc.pm	2005-09-16 23:09:01.000000000 -0400
@@ -200,7 +200,7 @@
 =item check
 
 Checks all fields to make sure this is a valid service.  If there is an error,
-returns the error, otehrwise returns false.  Called by the insert and
+returns the error, otherwise returns false.  Called by the insert and
 replace methods.
 
 =cut
diff -Naur freeside-1.5.7.orig/FS/FS/part_export/artera_turbo.pm freeside-1.5.7/FS/FS/part_export/artera_turbo.pm
--- freeside-1.5.7.orig/FS/FS/part_export/artera_turbo.pm	2005-01-06 15:20:24.000000000 -0500
+++ freeside-1.5.7/FS/FS/part_export/artera_turbo.pm	2005-09-16 23:07:17.000000000 -0400
@@ -43,7 +43,7 @@
 <a href="http://search.cpan.org/dist/Net-Artera">Net::Artera</a>
 from CPAN.  You probably also want to:
 <UL>
-  <LI>In the configuraiton UI section: set the <B>svc_external-skip_manual</B> and <B>svc_external-display_type</B> configuration values.
+  <LI>In the configuration UI section: set the <B>svc_external-skip_manual</B> and <B>svc_external-display_type</B> configuration values.
   <LI>In the message catalog: set <B>svc_external-id</B> to <I>Artera Serial Number</I> and set <B>svc_external-title</B> to <I>Artera Key Code</I>.
 </UL>
 END
diff -Naur freeside-1.5.7.orig/fs_selfservice/FS-SelfService/cgi/provision_list.html freeside-1.5.7/fs_selfservice/FS-SelfService/cgi/provision_list.html
--- freeside-1.5.7.orig/fs_selfservice/FS-SelfService/cgi/provision_list.html	2004-10-17 10:01:47.000000000 -0400
+++ freeside-1.5.7/fs_selfservice/FS-SelfService/cgi/provision_list.html	2005-09-16 22:54:13.000000000 -0400
@@ -41,7 +41,7 @@
     #}
 
     unless ( $cust_svc->{'svcnum'} == $svcnum ) {
-      $OUT .= qq!(<A HREF="javascript:areyousure('${url}delete_svc;svcnum=$cust_svc->{svcnum}', 'This will perminantly delete the $cust_svc->{label}[1] $cust_svc->{label}[0].  Are you sure?')">!.
+      $OUT .= qq!(<A HREF="javascript:areyousure('${url}delete_svc;svcnum=$cust_svc->{svcnum}', 'This will permanently delete the $cust_svc->{label}[1] $cust_svc->{label}[0].  Are you sure?')">!.
               'delete</A>)';
 
     }
diff -Naur freeside-1.5.7.orig/fs_selfservice/FS-SelfService/SelfService.pm freeside-1.5.7/fs_selfservice/FS-SelfService/SelfService.pm
--- freeside-1.5.7.orig/fs_selfservice/FS-SelfService/SelfService.pm	2005-06-08 05:03:06.000000000 -0400
+++ freeside-1.5.7/fs_selfservice/FS-SelfService/SelfService.pm	2005-09-16 23:07:00.000000000 -0400
@@ -1066,7 +1066,7 @@
 
 Note: Resellers can also use the B<signup_info> and B<new_customer> functions
 with their active session, and the B<customer_info> and B<order_pkg> functions
-with their active session and an additonal I<custnum> parameter.
+with their active session and an additional I<custnum> parameter.
 
 =over 4
 
diff -Naur freeside-1.5.7.orig/httemplate/view/cust_main.cgi freeside-1.5.7/httemplate/view/cust_main.cgi
--- freeside-1.5.7.orig/httemplate/view/cust_main.cgi	2004-12-29 07:00:07.000000000 -0500
+++ freeside-1.5.7/httemplate/view/cust_main.cgi	2005-09-16 22:54:26.000000000 -0400
@@ -44,7 +44,7 @@
 
 <%
 
-print qq! | <A HREF="javascript:areyousure('${p}misc/cust_main-cancel.cgi?$custnum', 'Perminantly delete all services and cancel this customer?')">!.
+print qq! | <A HREF="javascript:areyousure('${p}misc/cust_main-cancel.cgi?$custnum', 'Permanently delete all services and cancel this customer?')">!.
       'Cancel this customer</A>'
   if $cust_main->ncancelled_pkgs;
 
-------------- next part --------------
#
# The following enables Freeside's Apache::ASP interface under mod_perl
#

Alias /freeside /var/www/freeside/apacheasp

PerlModule Apache::ASP
PerlRequire /usr/local/etc/freeside/handler.pl
<Directory /var/www/freeside/apacheasp>
<Files ~ (\.cgi|\.html)>
AddHandler perl-script .cgi .html
PerlResponseHandler Apache::ASP
</Files>
PerlSetVar Global /usr/local/etc/freeside/asp-global/
PerlSetVar Debug 2
PerlSetVar RequestBinaryRead Off
# your freeside document root
PerlSetVar IncludesDir /var/www/freeside/apacheasp
#
# Basic authentication
#
AuthName Freeside
AuthType Basic
AuthUserFile /usr/local/etc/freeside/htpasswd
require valid-user
</Directory>

-------------- next part --------------
#!/usr/bin/perl -w
# Import text file into Freeside
# Input is a formatted text file containing basic item definitions
#
use strict;
use Date::Parse;
use FS::UID qw(adminsuidsetup datasrc);
use FS::Record qw(fields qsearch qsearchs);
use FS::cust_main;
use FS::cust_pkg;
use FS::cust_svc;
use FS::svc_acct;
use FS::pkg_svc;

my $user = shift or die &usage;
adminsuidsetup $user;

#use vars qw($cust_main::import);
#$import = 1;

sub usage {
	die "Usage: freeside-import freeside-user file(s)\nWhere:\tfreeside-user is a user created with freeside-adduser\n\tfile(s) are text formatted object lists";
}

# Define tag types that can be used in the data file
# These are essentially FS:: record types and their fields.
my %tags = (
	'agent_type' => [qw/atype/],
	'agent' => [qw/agent typenum/],
	'svc_acct_pop' => [qw/city state ac exch loc/],
	'part_pop_local' => [qw/popnum city state npa nxx/],
	'part_referral' => [qw/referral/],
	'part_svc' => [qw/svc svcdb disabled/],
	'part_svc_column' => [qw/columnnum svcpart columnname columnvalue columnflag/],
	'part_pkg' => [qw/pkg comment setup freq recur setuptax recurtax taxclass plan plandata disabled/],
	'part_pkg_option' => [qw/optionnum pkgpart optionname optionvalue/],
	'pkg_svc' => [qw/pkgpart svcpart quantity primary_svc/],
	'cust_main' => [qw/custnum agentnum refnum first last ss company address1 address2 city county state zip country daytime night fax ship_first ship_last ship_company ship_address1 ship_address2 ship_city ship_county ship_state ship_zip ship_country ship_daytime ship_night ship_fax payby payinfo paydate payname tax otaker comments invoicing/],
	'cust_pkg' => [qw/pkgnum custnum pkgpart setup bill last_bill susp expire cancel otaker manual_flag/],
	# svc_ objects should also include "pkgnum svcpart"
	'svc_acct' => [qw/svcnum username _password sec_phrase popnum uid gid finger dir shell quota slipip seconds domsvc pkgnum svcpart usergroup/],
	'svc_domain' => [qw/svcnum domain catchall pkgnum svcpart action email/],
	'svc_forward' => [qw/svcnum srcsvc dstsvc dst pkgnum svcpart/],
	'svc_www' => [qw /svcnum recnum usersvc pkgnum svcpart/],
	'part_export' => [qw/exportnum machine exporttype nodomain/],
	'part_export_option' => [qw/optionnum exportnum optionname optionvalue/],
	'export_svc' => [qw/exportsvcnum exportnum svcpart/],
	'cust_main_county' => [qw/taxnum state county country tax taxclass exempt_amount/],
	'cust_bill' => [qw/invnum custnum _date charged printed closed/],
	'cust_bill_pkg' => [qw/invnum pkgnum setup recur sdate edate/],
	'cust_pay' => [qw/paynum custnum paid _date payby payinfo paybatch closed/],
	'cust_credit' => [qw/crednum custnum amount _date otaker reason closed/],
	'cust_refund' => [qw/refundnum custnum refund _date payby payinfo paybatch otaker closed/],
	'part_bill_event' => [qw/eventpart payby event eventcode seconds weight plan plandata disabled/],
);

# Define fields that can be looked up in other object types.
# These are fields that should be unique numbers but can be text in the data file
my %lookups = (
	'agent' => {'typenum' => 'agent_type.atype'},
	'svc_acct_pop' => {'popnum' => 'part_pop_local.popnum'},
	'part_svc_column' => {'svcpart' => 'part_svc.svc'},
	'part_pkg_option' => {'pkgpart' => 'part_pkg.pkg'},
	'pkg_svc' => {'pkgpart' => 'part_pkg.pkg', 'svcpart' => 'part_svc.svc'},
	'cust_main' => {'agentnum' => 'agent.agent', 'refnum' => 'part_referral.referral'},
	'cust_pkg' => {'pkgpart' => 'part_pkg.pkg', 'custnum' => 'cust_main.first+last|ship_first+ship_last|company'},
	'svc_acct' => {'pkgnum' => 'cust_main.first+last|ship_first+ship_last|company', 'svcpart' => 'part_svc.svc', 'domsvc' => 'svc_domain.domain'},
	'svc_domain' => {'pkgnum' => 'cust_main.first+last|ship_first+ship_last|company', 'svcpart' => 'part_svc.svc'},
	'svc_forward' => {'pkgnum' => 'cust_main.first+last|ship_first+ship_last|company', 'svcpart' => 'part_svc.svc'},
	'svc_www' => {'recnum' => 'domain_record.reczone+svcnum', 'pkgnum' => 'cust_main.first+last|ship_first+ship_last|company', 'svcpart' => 'part_svc.svc'},
	'part_export_option' => {'exportnum' => 'part_export.machine+exporttype'},
	'export_svc' => {'exportnum' => 'part_export.machine+exporttype', 'svcpart' => 'part_svc.svc'},
	'cust_bill' => {'custnum' => 'cust_main.first+last|ship_first+ship_last|company'},
	'cust_bill_pkg' => {'invnum' => 'cust_bill._date', 'pkgnum' => 'cust_pkg.custnum+pkgpart'},
	'cust_pay' => {'custnum' => 'cust_main.first+last|ship_first+ship_last|company'},
	'cust_credit' => {'custnum' => 'cust_main.first+last|ship_first+ship_last|company'},
	'cust_refund' => {'custnum' => 'cust_main.first+last|ship_first+ship_last|company'},
	'domain_record' => {'svcnum' => 'svc_domain.domain'},
);

my %nolookup = (
	'pkg_svc' => 'pkg_svc',
);

my %conversions = (
	'cust_pkg' => {'setup' => 'date', 'bill' => 'date', 'last_bill' => 'date', 'susp' => 'date', 'expire' => 'date', 'cancel' => 'date'},
#	'part_svc_column' => {'columnname' => 'svc_column'}, # 'columnflag' => 'enum:D|F'
	'svc_acct' => {'usergroup' => 'array'},
	'cust_main' => {'invoicing' => 'array'},
	'cust_bill' => {'_date' => 'date'},
	'cust_bill_pkg' => {'sdate' => 'date', 'edate' => 'date'},
	'cust_pay' => {'_date' => 'date'},
	'cust_credit' => {'_date' => 'date'},
	'cust_refund' => {'_date' => 'date'},
);

my %enumerations = (
	'part_pkg' => {'plan' => [qw/flat flat_delayed prorate subscription flat_comission_cust flat_comission flat_comission_pkg sesmon_hour sesmon_minute sqlradacct_hour/] },
	'part_bill_event' => {'payby' => [qw/CARD DCRD CHEK DCHK LECB BILL COMP/] },
);

my @values;	# Presumably we can bypass memory limitations by tying this to a DBM file.
my %pairs;
my $type;
my $verbose = 1;
my $parse_error = 0;
my $svcdb;
my %lastids;

# Read in the data file and construct a data structure, validating attributes as you go.
while (<ARGV>) {
	next if (/^\s*$/ || /^\s*#/);	# Skip blank lines and comments
	chomp;
	if (/^\s*\[\s*(.*?)\s*\]\s*(#.*)?$/) {
		if ($type) {
			push @values, {$type => {%pairs} };
			%pairs = ();
		}
		$type = $1;
		$type =~ s/-/_/;
		print "[$type]\n" if $verbose;
		report_error("$ARGV $.: $_", "Object $type not known\n\n") if !exists($tags{$type});
	} elsif (/^\s*(.*?)\s*=\s*'(.*?)'\s*(#.*)?$/ ||
		/^\s*(.*?)\s*=\s*"(.*?)"\s*(#.*)?$/ ||
		/^\s*(.*?)\s*=\s*(.*?)\s*(#.*)?$/) {
		my ($col, $val) = ($1, $2);
		$val =~ s/\\n/\n/g;
		print "$col=$val\n" if $verbose;
		$pairs{$col} = $val;
		report_error("$ARGV $.: $_", "Attribute $col not known in definition of $type\n\n") if !grep {grep {$_ eq $col} @$_} $tags{$type};
		$svcdb = $val if $col eq 'svcdb' && $type eq 'part_svc';
		report_error("$ARGV $.: $_", "Service column $col not known in definition of $svcdb\n\n") if $type eq 'part_svc' && $col eq 'columnname' && !grep {grep {$_ eq $col} @$_} $tags{$svcdb};
	}
}
if ($type) {
	push @values, {$type => {%pairs} };
}

die "$parse_error parse errors - can't continue" if $parse_error;

# Should do validation at this point

# !!! TBD !!!

dump_it() if $verbose & 2;
#print_it();
process_it();
print "freeside-import complete\n"; # Sign off to confirm everything went well

sub report_error {
	print STDERR @_;
	$parse_error++;
}

sub dump_it {
# Print out the data structure using Data::Dumper
use Data::Dumper;

print Dumper(@values);
}

# Do something with the data structure
# Should be populating the FS database
# Is currently just printing it out again.
sub print_it {

	foreach my $entry (@values) {
		#print $_->{type} . "\n";
		#foreach ($_->{values}) {
		#	print $_[0] . "=" . $_[1] . "\n";
		#}
		foreach my $type (keys %$entry) {
			print "\t\$record = new FS::$type(\n";
			my $values = $$entry{$type};
			foreach (keys %$values) {
				if (defined($lookups{$type})) {
					my $lookup = $lookups{$type};
					if (defined($$lookup{$_})) {
						my ($table,$field) = split /\./, $$lookup{$_};
						print "\t\t$_ => qsearch(\"$table\", { \"$field\" => \"$$values{$_}\" });\n";
						next;
					}
				}
				print "\t\t$_ => '$$values{$_}',\n";
			}
			print "\t);\n";
			print "\t\$error = \$record->insert();\n\n";
		}
	}
}

sub process_it {

	$FS::cust_main::import = 1;	# Suppress expired credit card errors

	foreach my $entry (@values) {
		#print $_->{type} . "\n";
		#foreach ($_->{values}) {
		#	print $_[0] . "=" . $_[1] . "\n";
		#}
		foreach my $type (keys %$entry) {
	#		print "\t\$record = new FS::$type(\n";
			my $values = $$entry{$type};
			# part_svc_column lookups can be done before working through each attribute in the object
			if ($type eq 'part_svc_column') {
				if ($$values{'columnname'} eq 'domsvc') {
					my $record = qsearchs('svc_domain', { 'domain' => $$values{'columnvalue'} });
					die "Can't find domain $$values{'columnvalue'}" if !$record;
					$$values{'columnvalue'} = get_recordid('svc_domain', $record);
				}
			}
			foreach (keys %$values) {
				if ($$values{$_} =~ /^=\s*(.*)/) {
					if ($1 =~ /^\d+$/) {
						# User supplied a record number "==num"
						$$values{$_} =~ s/^=\s*//;
					} else {
						# User specified last object created "==object_type"
						print "Looking up ID of last $1 created\n";
						$$values{$_} = $lastids{$1};
					}
					next;
				}
				my $value = lookup_recordid($type, $_, $$values{$_});
				if ($value) {
					$$values{$_} = $value;
					next;
				}
				if (defined($conversions{$type})) {
					my $conversion = $conversions{$type};
					if (!defined($$conversion{$_})) {
						next;
					} elsif ($$conversion{$_} eq 'date') {
						$$values{$_} = str2time($$values{$_}) if $$values{$_} !~ /^\s*\d+\s*$/;
					} elsif ($$conversion{$_} eq 'array') {
						$$values{$_} = [split ',', $$values{$_}];
#					} elsif ($$conversion{$_} eq 'svc_column') {
#						$$values{$_} = str2time($$values{$_});
					}
				}
	#			print "\t\t$_ => '$$values{$_}',\n";
			}
	#		print "\t);\n";
	#		print "\t\$error = \$record->insert();\n\n";
			my $record;
			my $oldrec;
			if ($type eq 'agent_type') {
				$record = new FS::agent_type($values);
			} elsif ($type eq 'agent') {
				$record = new FS::agent($values);
			} elsif ($type eq 'svc_acct_pop') {
				$record = new FS::svc_acct_pop($values);
			} elsif ($type eq 'part_pop_local') {
				$record = new FS::part_pop_local($values);
			} elsif ($type eq 'part_referral') {
				$record = new FS::part_referral($values);
			} elsif ($type eq 'part_svc') {
				$record = new FS::part_svc($values);
			} elsif ($type eq 'part_svc_column') {
				$record = new FS::part_svc_column($values);
			} elsif ($type eq 'part_pkg') {
				$record = new FS::part_pkg($values);
			} elsif ($type eq 'part_pkg_option') {
				$record = new FS::part_pkg_option($values);
			} elsif ($type eq 'pkg_svc') {
				# As of 1.5.7, part_pkg inserts a pkg_svc with quantity zero for every known part_svc
				# Therefore, we replace pkg_svc records instead of inserting
				my $oldrec = qsearchs('pkg_svc', {pkgpart => $values->{pkgpart}, svcpart => $values->{svcpart}});
				if ($oldrec) {
#					$values->{pkgsvcnum} = $oldrec->pkgsvcnum;
					$oldrec->delete();
#					print "Replacing record $values->{pkgsvcnum} with pkgpart = $values->{pkgpart}, svcpart = $values->{svcpart}\n";
					print "Deleting record with pkgpart = $values->{pkgpart}, svcpart = $values->{svcpart}\n";
				} else {
					print "Inserting record with pkgpart = $values->{pkgpart}, svcpart = $values->{svcpart}\n";
				}
				$record = new FS::pkg_svc($values);
			} elsif ($type eq 'part_export') {
				$record = new FS::part_export($values);
			} elsif ($type eq 'part_export_option') {
				$record = new FS::part_export_option($values);
			} elsif ($type eq 'export_svc') {
				$record = new FS::export_svc($values);
			} elsif ($type eq 'cust_main_county') {
				$record = new FS::cust_main_county($values);
			} elsif ($type eq 'cust_main') {
				$oldrec = $$values{'invoicing'};
				delete $$values{'invoicing'};
				$record = new FS::cust_main($values);
				my $err = $record->check_invoicing_list($oldrec);
				warn $err if $err;
			} elsif ($type eq 'cust_pkg') {
				$record = new FS::cust_pkg($values);
			} elsif ($type eq 'svc_acct') {
				$record = new FS::svc_acct($values);
			} elsif ($type eq 'svc_domain') {
				$FS::svc_domain::whois_hack = 1;	# Disables domain registrations
				$record = new FS::svc_domain($values);
			} elsif ($type eq 'svc_forward') {
				$record = new FS::svc_forward($values);
			} elsif ($type eq 'svc_www') {
				$record = new FS::svc_www($values);
			} elsif ($type eq 'cust_bill') {
				$record = new FS::cust_bill($values);
			} elsif ($type eq 'cust_bill_pkg') {
				$record = new FS::cust_bill_pkg($values);
			} elsif ($type eq 'cust_pay') {
				$record = new FS::cust_pay($values);
			} elsif ($type eq 'cust_credit') {
				$record = new FS::cust_credit($values);
			} elsif ($type eq 'cust_refund') {
				$record = new FS::cust_refund($values);
			} elsif ($type eq 'part_bill_event') {
				$record = new FS::part_bill_event($values);
			} else {
				die "Unknown type $type: can't create object";
			}
#			my $error = $oldrec ? $record->replace($oldrec) : $record->insert();
			my $error = $type eq 'cust_main' ? $record->insert({}, $oldrec) : $record->insert();
			if ( $error ) {
				warn $record->_dump;
				warn map "$_: ". $$values{$_}. "|\n", keys %$values;
				die $error;
			} else {
				# Update the last created object record number for the current type
				my $id = "";
				$id = get_recordid($type, $record) if !exists($nolookup{$type});
				print "$type - $id: OK\n";
				$lastids{$type} = $id;
				# Apply payments or credits automatically (not everyone may want this).
				if ($type eq 'cust_pay' || $type eq 'cust_credit') {
					my $cust_main = qsearchs('cust_main', {'custnum' => $$values{'custnum'} });
					die "Can't find customer to apply payment/credit: cust_num = $$values{custnum}" unless $record;
					$cust_main->apply_payments if $type eq 'cust_pay';
					$cust_main->apply_credits if $type eq 'cust_credit';
				}
			}
		}
	}
}

sub lookup_recordid {
	my ($type, $attrib, $value) = @_;

	if (defined($lookups{$type}->{$attrib})) {
		my ($table,$field) = split /\./, $lookups{$type}->{$attrib};
		my @vals = split /\+/, $value;
		foreach my $option (split /\|/, $field) {
			my @fields = split /\+/, $option;
			my $searchvals = {};
			foreach my $f (@fields) {
				if (defined($lookups{$table}->{$f})) {
					my @v;
					my @p = split /\+/, $lookups{$table}->{$f};
					foreach (@p) {
						push @v, shift @vals;
					}
					my $v = join '+', @v;
					print "Recursive lookup in $table for $v in fields $f\n";
					$$searchvals{$f} = lookup_recordid($table, $f, $v);
				} else {
					$$searchvals{$f} = shift @vals;
				}
			}
			my $record = qsearchs($table, $searchvals);
			return get_recordid($table, $record) if $record;
		}
		die "Unknown $table: $field = $value";
	}
	undef;
}

sub get_recordid {
	my $table = shift;
	my $record = shift;

	my $value;
	if ($table eq 'agent_type') {
		$value = $record->typenum;
	} elsif ($table eq 'agent') {
		$value = $record->agentnum;
	} elsif ($table eq 'svc_acct_pop') {
		$value = $record->popnum;
	} elsif ($table eq 'part_pop_local') {
		$value = $record->localnum;
	} elsif ($table eq 'part_referral') {
		$value = $record->refnum;
	} elsif ($table eq 'part_svc') {
		$value = $record->svcpart;
	} elsif ($table eq 'part_svc_column') {
		$value = $record->columnnum;
	} elsif ($table eq 'part_pkg') {
		$value = $record->pkgpart;
	} elsif ($table eq 'part_pkg_option') {
		$value = $record->optionnum;
	} elsif ($table eq 'pkg_svc') {
#		die "No unique key in pkg_svc";
		$value = $record->pkgsvcnum;
	} elsif ($table eq 'part_export') {
		$value = $record->exportnum;
	} elsif ($table eq 'part_export_option') {
		$value = $record->optionnum;
	} elsif ($table eq 'export_svc') {
		$value = $record->exportsvcnum;
	} elsif ($table eq 'cust_main_county') {
		$value = $record->taxnum;
	} elsif ($table eq 'cust_main') {
		$value = $record->custnum;
	} elsif ($table eq 'cust_pkg') {
		$value = $record->pkgnum;
	} elsif ($table eq 'svc_acct') {
		$value = $record->svcnum;
	} elsif ($table eq 'svc_domain') {
		$value = $record->svcnum;
	} elsif ($table eq 'svc_forward') {
		$value = $record->svcnum;
	} elsif ($table eq 'svc_www') {
		$value = $record->svcnum;
	} elsif ($table eq 'cust_bill') {
		$value = $record->invnum ;
	} elsif ($table eq 'cust_bill_pkg') {
		$value = $record->invnum;
	} elsif ($table eq 'cust_pay') {
		$value = $record->paynum;
	} elsif ($table eq 'cust_credit') {
		$value = $record->crednum;
	} elsif ($table eq 'cust_refund') {
		$value = $record->refundnum;
	} elsif ($table eq 'part_bill_event') {
		$value = $record->eventpart;
	} elsif ($table eq 'domain_record') {
		$value = $record->recnum;
	} else {
		die "Unknown table $table in lookup";
	}
	$value;
}

=head1 NAME

freeside-import - Command line utility to import text object descriptions into Freeside via Perl API

=head1 SYNOPSIS

  freeside-import user file file ...

=head1 DESCRIPTION

Reads in files containing Freeside object data in Windows-ini style format, does
basic checks for accuracy, then uses the Perl API to create the objects.

Object fields which reference other objects can contain lookups instead of object numbers,
e.g.

   custnum==cust_main # Use last created cust_main
   custnum=Ivan+Kohler # Look up in First+Last

user: From the mapsecrets file - see config.html from the base documentation

file: File containing the object text dump

=head1 BUGS

=head1 SEE ALSO

Perl API, config.html from the base documentation

=cut

-------------- next part --------------
#!/bin/sh
# Modified for FC3/yum

## Create the freeside user account and create the database in PostgreSQL
#add_freeside_user()
#{
##	/usr/sbin/useradd freeside
##	chsh freeside -s /bin/bash
#}

#start_pg()
#{
##	/sbin/chkconfig postgresql on
##	/etc/init.d/postgresql start
#}

create_freeside_pg_user_and_db()
{
	echo "Creating Freeside database user for Pg"
	su postgres -c "createuser -P -A -d freeside"

	su freeside -c "createdb freeside"
}

## Install Freeside's Perl modules, create the configuration, and create the first user
#install_freeside()
#{
##	cd ../../..
##	make install-perl-modules
##	make create-config
#}

add_first_freeside_user()
{
	echo "Creating first Freeside application user"
	freeside-adduser -c -h /usr/local/etc/freeside/htpasswd $USER
	su freeside -c "freeside-setup -s $USER"
	su freeside -c "/usr/share/freeside-1.5.7/populate-msgcat $USER"
#	make deploy
}

USER=$1

# Should check that we're root...

if [ "$USER" = "" ]; then
        echo "usage: fsinstall <first_user>"
        exit 1
fi

create_freeside_pg_user_and_db;

add_first_freeside_user;

exit 0;

-------------- next part --------------
#
# The following enables Freeside's HTML::Mason interface under mod_perl
#

Alias /freeside /var/www/freeside/mason

PerlModule HTML::Mason
PerlRequire /usr/local/etc/freeside/handler.pl
<Directory /var/www/freeside/mason>
<Files ~ (\.cgi|\.html)>
AddHandler perl-script .cgi .html
PerlResponseHandler HTML::Mason
</Files>
AuthName Freeside
AuthType Basic
AuthUserFile /usr/local/etc/freeside/htpasswd
require valid-user
</Directory>

-------------- next part --------------
Summary: Freeside ISP Billing System
Name: freeside
Version: 1.5.7
Release: 2
License: GPL
Group: Applications/Internet
URL: http://www.sisd.com/freeside/
Packager: Richard Siddall <richard.siddall at elirion.net>
Vendor: Freeside
Source: http://www.sisd.com/freeside/%{name}-%{version}.tar.gz
Source1: freeside-mason.conf
Source2: freeside-asp.conf
Source3: freeside-install
Source4: freeside-import
Source5: freeside.sysconfig
Patch: %{name}-%{version}.build.patch
Patch1: %{name}-%{version}.dbd-pg.patch
Patch2: %{name}-%{version}.mod_perl2.patch
Patch3: %{name}-%{version}.redhat.patch
Patch4: %{name}-%{version}.rpm.patch
Patch5: %{name}-%{version}.emailsubject.patch
Patch6: %{name}-%{version}.nasport.patch
Patch7: %{name}-%{version}.flat_prorate.patch
Patch8: %{name}-%{version}.typo.patch
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
BuildArch: noarch
Requires: %{name}-frontend
Requires: %{name}-backend
Requires: tetex-latex
%{?fc1:BuildRequires: httpd}
%{?el3:BuildRequires: httpd}
%{?rh9:BuildRequires: httpd}
%{?rh8:BuildRequires: httpd}
%{?rh7:BuildRequires: apache}
%{?el2:BuildRequires: apache}
%{?rh6:BuildRequires: apache}

%description
Freeside is a flexible ISP billing system written by Ivan Kohler

%package mason
Summary: HTML::Mason interface for %{name}
Group: Applications/Internet
Prefix: /var/www/freeside
Requires: mod_ssl
Requires: perl(HTML::Mason)
Requires: perl(HTML::Mason::ApacheHandler)
Requires: perl(CGI)
Requires: perl(Date::Format)
Requires: perl(Date::Parse)
Requires: perl(Time::Local)
Requires: perl(Time::Duration)
Requires: perl(Tie::IxHash)
Requires: perl(URI::Escape)
Requires: perl(HTML::Entities)
Requires: perl(IO::Handle)
Requires: perl(IO::File)
Requires: perl(IO::Scalar)
Requires: perl(Net::Whois::Raw)
Requires: perl(Text::CSV_XS)
Requires: perl(Spreadsheet::WriteExcel)
Requires: perl(Business::CreditCard)
Requires: perl(String::Approx)
Requires: perl(Chart::LinesPoints)
Requires: perl(HTML::Widgets::SelectLayers)
Requires: perl(FS)
Requires: perl(FS::UID)
Requires: perl(FS::Record)
Requires: perl(FS::Conf)
Requires: perl(FS::CGI)
Requires: perl(FS::UI::Web)
Requires: perl(FS::Msgcat)
Requires: perl(FS::Misc)
Requires: perl(FS::Report::Table::Monthly)
Requires: perl(FS::TicketSystem)
Requires: perl(FS::agent)
Requires: perl(FS::agent_type)
Requires: perl(FS::domain_record)
Requires: perl(FS::cust_bill)
Requires: perl(FS::cust_bill_pay)
Requires: perl(FS::cust_credit)
Requires: perl(FS::cust_credit_bill)
Requires: perl(FS::cust_main)
Requires: perl(FS::cust_main_county)
Requires: perl(FS::cust_pay)
Requires: perl(FS::cust_pkg)
Requires: perl(FS::cust_refund)
Requires: perl(FS::cust_svc)
Requires: perl(FS::nas)
Requires: perl(FS::part_bill_event)
Requires: perl(FS::part_pkg)
Requires: perl(FS::part_referral)
Requires: perl(FS::part_svc)
Requires: perl(FS::part_svc_router)
Requires: perl(FS::part_virtual_field)
Requires: perl(FS::pkg_svc)
Requires: perl(FS::port)
Requires: perl(FS::queue)
Requires: perl(FS::raddb)
Requires: perl(FS::session)
Requires: perl(FS::svc_acct)
Requires: perl(FS::svc_acct_pop)
Requires: perl(FS::svc_domain)
Requires: perl(FS::svc_forward)
Requires: perl(FS::svc_www)
Requires: perl(FS::router)
Requires: perl(FS::addr_block)
Requires: perl(FS::svc_broadband)
Requires: perl(FS::svc_external)
Requires: perl(FS::type_pkgs)
Requires: perl(FS::part_export)
Requires: perl(FS::part_export_option)
Requires: perl(FS::export_svc)
Requires: perl(FS::msgcat)
Requires: perl(FS::rate)
Requires: perl(FS::rate_region)
Requires: perl(FS::rate_prefix)
Requires: perl(FS::XMLRPC)
Requires: perl(MIME::Entity)
Requires: perl(Text::Wrapper)
Requires: perl(CGI::Cookie)
Requires: perl(Time::ParseDate)
Requires: perl(HTML::Scrubber)
Requires: perl(Text::Quoted)
Conflicts: %{name}-apacheasp
Provides: %{name}-frontend
BuildArch: noarch

%description mason
This package includes the HTML::Mason web interface for %{name}.
You should install only one %{name} web interface.

%package apacheasp
Summary: Apache::ASP interface for %{name}
Group: Applications/Internet
Prefix: /var/www/freeside
Requires: mod_ssl
Requires: perl(Apache::ASP)
Requires: perl(CGI)
Requires: perl(Date::Format)
Requires: perl(Date::Parse)
Requires: perl(Time::Local)
Requires: perl(Time::Duration)
Requires: perl(Tie::IxHash)
Requires: perl(URI::Escape)
Requires: perl(HTML::Entities)
Requires: perl(IO::Handle)
Requires: perl(IO::File)
Requires: perl(IO::Scalar)
Requires: perl(Net::Whois::Raw)
Requires: perl(Text::CSV_XS)
Requires: perl(Spreadsheet::WriteExcel)
Requires: perl(Business::CreditCard)
Requires: perl(String::Approx)
Requires: perl(Chart::LinesPoints)
Requires: perl(HTML::Widgets::SelectLayers)
Requires: perl(FS)
Requires: perl(FS::UID)
Requires: perl(FS::Record)
Requires: perl(FS::Conf)
Requires: perl(FS::CGI)
Requires: perl(FS::UI::Web)
Requires: perl(FS::Msgcat)
Requires: perl(FS::Misc)
Requires: perl(FS::Report::Table::Monthly)
Requires: perl(FS::TicketSystem)
Requires: perl(FS::agent)
Requires: perl(FS::agent_type)
Requires: perl(FS::domain_record)
Requires: perl(FS::cust_bill)
Requires: perl(FS::cust_bill_pay)
Requires: perl(FS::cust_credit)
Requires: perl(FS::cust_credit_bill)
Requires: perl(FS::cust_main)
Requires: perl(FS::cust_main_county)
Requires: perl(FS::cust_pay)
Requires: perl(FS::cust_pkg)
Requires: perl(FS::cust_refund)
Requires: perl(FS::cust_svc)
Requires: perl(FS::nas)
Requires: perl(FS::part_bill_event)
Requires: perl(FS::part_pkg)
Requires: perl(FS::part_referral)
Requires: perl(FS::part_svc)
Requires: perl(FS::part_svc_router)
Requires: perl(FS::part_virtual_field)
Requires: perl(FS::pkg_svc)
Requires: perl(FS::port)
Requires: perl(FS::queue)
Requires: perl(FS::raddb)
Requires: perl(FS::session)
Requires: perl(FS::svc_acct)
Requires: perl(FS::svc_acct_pop)
Requires: perl(FS::svc_domain)
Requires: perl(FS::svc_forward)
Requires: perl(FS::svc_www)
Requires: perl(FS::router)
Requires: perl(FS::addr_block)
Requires: perl(FS::svc_broadband)
Requires: perl(FS::svc_external)
Requires: perl(FS::type_pkgs)
Requires: perl(FS::part_export)
Requires: perl(FS::part_export_option)
Requires: perl(FS::export_svc)
Requires: perl(FS::msgcat)
Requires: perl(FS::rate)
Requires: perl(FS::rate_region)
Requires: perl(FS::rate_prefix)
Requires: perl(Data::Dumper)
Conflicts: %{name}-mason
Provides: %{name}-frontend
BuildArch: noarch

%description apacheasp
This package includes the Apache::ASP web interface for %{name}.
You should install only one %{name} web interface.
Please note that this interface is deprecated as future versions of %{name} will use
the HTML::Mason-based RT tracking tool.

%package postgresql
Summary: PostgreSQL backend for %{name}
Group: Applications/Internet
Requires: perl-DBI
Requires: perl-DBD-Pg >= 1.32
Requires: %{name}
Conflicts: %{name}-mysql
Provides: %{name}-backend

%description postgresql
This package includes the PostgreSQL database backend for %{name}.
You should install only one %{name} database backend.
Please note that this RPM does not create the database or database user; it only installs the required drivers.

%package mysql
Summary: MySQL database backend for %{name}
Group: Applications/Internet
Requires: perl-DBI
Requires: perl-DBD-MySQL
Requires: %{name}
Conflicts: %{name}-postgresql
Provides: %{name}-backend

%description mysql
This package includes the MySQL database backend for %{name}.
You should install only one %{name} database backend.
Please note that this RPM does not create the database or database user; it only installs the required drivers.

%prep
%setup
%patch0 -p1
%patch1 -p1
%patch2 -p1
%patch3 -p1
%patch4 -p1
%patch5 -p1
%patch6 -p1
%patch7 -p1
%patch8 -p1
%{__cp} %SOURCE3 FS/bin
%{__cp} %SOURCE4 FS/bin
#%{__rm} -r FS/FS/UI/Gtk.pm
perl -pi -e 's|/usr/local/bin|%{buildroot}%{_bindir}|g' FS/Makefile.PL

%build
# Add freeside user and group if there isn't already such a user
%{__id} freeside 2>/dev/null >/dev/null || /usr/sbin/useradd -s /bin/sh -r freeside
# False laziness...
%{__make} htmlman
echo "Made HTML manuals"
touch htmlman
%{__make} alldocs

cd FS
CFLAGS="$RPM_OPT_FLAGS" perl Makefile.PL PREFIX=$RPM_BUILD_ROOT%{_prefix}
%{__make} OPTIMIZE="$RPM_OPT_FLAGS"
cd ..

%install
%{__rm} -rf %{buildroot}

FREESIDE_DOCUMENT_ROOT=/var/www/freeside
%{__mkdir_p} $RPM_BUILD_ROOT$FREESIDE_DOCUMENT_ROOT/asp
%{__mkdir_p} $RPM_BUILD_ROOT$FREESIDE_DOCUMENT_ROOT/mason

touch install-perl-modules
%{__make} create-config FREESIDE_CONF=$RPM_BUILD_ROOT/usr/local/etc/freeside
%{__rm} install-perl-modules

touch docs
%{__perl} -pi -e "s|%%%%%%FREESIDE_DOCUMENT_ROOT%%%%%%|$FREESIDE_DOCUMENT_ROOT/asp|g" htetc/global.asa
%{__perl} -pi -e "s|%%%%%%FREESIDE_DOCUMENT_ROOT%%%%%%|$FREESIDE_DOCUMENT_ROOT/mason|g" htetc/handler.pl
%{__make} install-docs PREFIX=$RPM_BUILD_ROOT%{_prefix} TEMPLATE=asp FREESIDE_DOCUMENT_ROOT=$RPM_BUILD_ROOT$FREESIDE_DOCUMENT_ROOT/asp ASP_GLOBAL=$RPM_BUILD_ROOT/usr/local/etc/freeside/asp-global
%{__make} install-docs PREFIX=$RPM_BUILD_ROOT%{_prefix} TEMPLATE=mason FREESIDE_DOCUMENT_ROOT=$RPM_BUILD_ROOT$FREESIDE_DOCUMENT_ROOT/mason MASON_HANDLER=$RPM_BUILD_ROOT/usr/local/etc/freeside/handler.pl MASONDATA=$RPM_BUILD_ROOT/usr/local/etc/freeside/masondata
%{__rm} docs

# Install the init script
%{__mkdir_p} $RPM_BUILD_ROOT%{_initrddir}
%{__install} init.d/freeside-init $RPM_BUILD_ROOT%{_initrddir}/freeside

# Install the HTTPD configuration snippets for HTML::Mason and Apache::ASP
%{__mkdir_p} $RPM_BUILD_ROOT/etc/httpd/conf.d
%{__install} %SOURCE1 $RPM_BUILD_ROOT/etc/httpd/conf.d
%{__install} %SOURCE2 $RPM_BUILD_ROOT/etc/httpd/conf.d

# Install all the miscellaneous binaries into /usr/share or similar
%{__mkdir_p} $RPM_BUILD_ROOT%{_datadir}/%{name}-%{version}
%{__install} bin/* $RPM_BUILD_ROOT%{_datadir}/%{name}-%{version}

#%{__mkdir_p} $RPM_BUILD_ROOT%{_bindir}
#%{__install} %SOURCE3 $RPM_BUILD_ROOT%{_bindir}
#%{__install} %SOURCE4 $RPM_BUILD_ROOT%{_bindir}

%{__mkdir_p} $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig
%{__install} %SOURCE5 $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/%{name}

#
cd FS
#make install UNINST=1
eval `perl '-V:installarchlib'`
%{__mkdir_p} $RPM_BUILD_ROOT$installarchlib
%makeinstall PREFIX=$RPM_BUILD_ROOT%{_prefix} UNINST=1
%{__rm} -f `find $RPM_BUILD_ROOT -type f -name perllocal.pod -o -name .packlist`

[ -x %{_libdir}/rpm/brp-compress ] && %{_libdir}/rpm/brp-compress

find $RPM_BUILD_ROOT%{_prefix} -type f -print | \
	grep -v '/usr/local/etc/freeside/conf' | \
	grep -v '/usr/local/etc/freeside/secrets' | \
        sed "s@^$RPM_BUILD_ROOT@@g" > %{name}-%{version}-%{release}-filelist
if [ "$(cat %{name}-%{version}-%{release}-filelist)X" = "X" ] ; then
    echo "ERROR: EMPTY FILE LIST"
    exit 1
fi
cd ..

%pre
if ! %{__id} freeside &>/dev/null; then
	/usr/sbin/useradd -r freeside
fi

%clean
%{__rm} -rf %{buildroot}

%files -f FS/%{name}-%{version}-%{release}-filelist
/etc/rc.d/init.d/freeside
%attr(0644,root,root) %config(noreplace) /etc/sysconfig/freeside
%defattr(-,freeside,freeside,-)
%doc README INSTALL CREDITS GPL
%attr(-,freeside,freeside) %config(noreplace) /usr/local/etc/freeside/conf.*
%attr(-,freeside,freeside) %config(noreplace) /usr/local/etc/freeside/counters.*
%attr(-,freeside,freeside) %config(noreplace) /usr/local/etc/freeside/cache.*
%attr(-,freeside,freeside) %config(noreplace) /usr/local/etc/freeside/export.*
%attr(-,freeside,freeside) %config(noreplace) /usr/local/etc/freeside/secrets
%attr(-,freeside,freeside) %dir /usr/local/etc/freeside

%files apacheasp
%defattr(-, freeside, freeside, 0755)
%attr(0755,freeside,freeside) /var/www/freeside/asp
%attr(-,freeside,freeside) /usr/local/etc/freeside/asp-global
%attr(0644,root,root) /etc/httpd/conf.d/freeside-asp.conf

%files mason
%defattr(-, freeside, freeside, 0755)
%attr(0755,freeside,freeside) /var/www/freeside/mason
%attr(-,freeside,freeside) /usr/local/etc/freeside/handler.pl
%attr(-,freeside,freeside) /usr/local/etc/freeside/masondata
%attr(0644,root,root) /etc/httpd/conf.d/freeside-mason.conf

%files postgresql

%files mysql

%changelog
* Sun Feb 06 2005 Richard Siddall <richard.siddall at elirion.net> - 1.5.0pre6-1
- Initial package
-------------- next part --------------
#[cust_main_county]
#taxnum - primary key (assigned automatically for new tax rates)
#state
#county
#country
#tax - percentage
#taxclass
#exempt_amount
[cust_main_county]
state=CT
country=US
tax=0
taxclass=Web Hosting
exempt_amount=0.00
[cust_main_county]
state=CT
country=US
tax=0
taxclass=Internet Access
exempt_amount=0.00
[cust_main_county]
state=CT
country=US
tax=0
taxclass=Exempt
exempt_amount=0.00
[cust_main_county]
state=CT
country=US
tax=6
taxclass=Standard
exempt_amount=0.00
[cust_main_county]
state=CT
country=US
tax=1
taxclass=Data Processing
exempt_amount=0.00
-------------- next part --------------
[agent-type]
atype=Internal
[agent]
agent=Internal Sales
typenum=Internal
[agent]
agent=Unlisted
typenum=Internal
[part_referral]
referral=Another Customer
[part_referral]
referral=Newspaper Ad.
[part_svc]
svc=Domain
svcdb=svc_domain
[part_pkg]
pkg=Domain
comment=Domain
# You need a domain to create user accounts, whether or not they receive
# e-mail
freq=0
#setuptax - Setup fee tax exempt flag, empty or `Y'
#recurtax - Recurring fee tax exempt flag, empty or `Y'
#taxclass - Tax class flag
plan=flat
#plandata - Price plan data
#disabled - Disabled flag, empty or `Y'
[pkg_svc]
pkgpart=Domain
svcpart=Domain
quantity=1
##primary_svc=
[cust_main]
agentnum=Internal Sales
refnum=Newspaper Ad.
first=Ivan
last=Kohler
#ss - social security number (optional)
company=SISD, Inc.
address1=1234 Truckee Lane
#address2 - (optional)
city=Truckee
#county - (optional, see the FS::cust_main_county manpage)
state=CA
zip=54321
country=USA
daytime=(123) 456-7890
#night - phone (optional)
fax=(555) 555-1212
payby=COMP
payinfo=Ivan
paydate=12/2018
#otaker - order taker (assigned automatically, see the FS::UID manpage)
#comments - comments (optional)
[cust_pkg]
custnum=Ivan+Kohler
pkgpart=Domain
setup=7/1/2004
#bill - date (next bill date)
#susp - date
#expire - date
#cancel - date
#otaker - order taker (assigned automatically if null, see the FS::UID manpage)
#manual_flag - If this field is set to 1, disables the automatic unsuspension of this package when using the unsuspendauto config file.
[svc_domain]
pkgnum=Ivan+Kohler
svcpart=Domain
domain=aol.com
# We'll use this domain for e-mail.  Realms will be handled in the RADIUS
# database
action=M
# catchall - optional svcnum of an svc_acct record, designating an email catchall account.
-------------- next part --------------
# Invoice events for running card and then sending invoice
[part_bill_event]
payby=CARD
event=Run Card
eventcode=$cust_bill->realtime_card();
seconds=0
weight=0
plan=realtime-card
[part_bill_event]
payby=CARD
event=Send Invoice
eventcode=$cust_bill->send();
seconds=0
weight=1
plan=send
-------------- next part --------------
#
# Services - list all your product components here
#
[part_svc]
svc=Dial-up Access w/POP
svcdb=svc_acct
[part_svc_column]
svcpart=Dial-up Access w/POP
columnname=usergroup
columnvalue=dial1
columnflag=D
[part_svc_column]
svcpart=Dial-up Access w/POP
columnname=domsvc
columnvalue=aol.com
columnflag=D
[part_svc_column]
svcpart=Dial-up Access w/POP
columnname=slipip
columnvalue=0e0
columnflag=D
[part_svc_column]
svcpart=Dial-up Access w/POP
columnname=quota
columnvalue=100
columnflag=D
#
# Packages - list all your packages here
#
[part_pkg]
pkg=Dial-up Standard
comment=Standard Dial-up Package
freq=1
#setuptax - Setup fee tax exempt flag, empty or `Y'
#recurtax - Recurring fee tax exempt flag, empty or `Y'
taxclass=Internet Access
plan=flat
#disabled - Disabled flag, empty or `Y'
[part_pkg_option]
pkgpart=Dial-up Standard
optionname=recur_fee
optionvalue=23.95
[part_pkg_option]
pkgpart=Dial-up Standard
optionname=unused_credit
optionvalue=1
[pkg_svc]
pkgpart=Dial-up Standard
svcpart=Dial-up Access w/POP
quantity=1
primary_svc=Y
-------------- next part --------------
#[part_export]
#exportnum - primary key
#machine - Machine name
#exporttype - Export type: svc_acct(sysvshell sysvshell textradius shellcommands shellcommands_withdomain ldap sqlradius sqlradius_withdomain sqlmail cyrus cp infostreet vpopmail) svc_domain(bind bind_slave http sqlmail domain_shellcommands) svc_forward(sqlmail forward_shellcommands) svc_www(www_shellcommands apache)
#nodomain - blank or ``Y'' : usernames are exported to this service with no domain
[part_export]
machine=192.168.1.2
exporttype=sqlradius
#[part_export_option]
#optionnum - primary key
#exportnum - export (see the FS::part_export manpage)
#optionname - option name
#optionvalue - option value
[part_export_option]
exportnum=192.168.1.2+sqlradius
optionname=datasrc
optionvalue=DBI:mysql:radius:192.168.1.2
[part_export_option]
exportnum=192.168.1.2+sqlradius
optionname=username
optionvalue=root
[part_export_option]
exportnum=192.168.1.2+sqlradius
optionname=password
optionvalue=password
#[export_svc]
#exportsvcnum - primary key
#exportnum - export (see the FS::part_export manpage)
#svcpart - service definition (see the FS::part_svc manpage)
[export_svc]
exportnum=192.168.1.2+sqlradius
svcpart=Dial-up Access w/POP


More information about the freeside-devel mailing list