templatable invoices
authorivan <ivan>
Wed, 9 Aug 2000 11:30:41 +0000 (11:30 +0000)
committerivan <ivan>
Wed, 9 Aug 2000 11:30:41 +0000 (11:30 +0000)
FS/FS/cust_bill.pm
TODO
htdocs/docs/billing.html
htdocs/docs/config.html

index 30db469..1d0790e 100644 (file)
@@ -1,8 +1,10 @@
 package FS::cust_bill;
 
 use strict;
-use vars qw( @ISA $conf $add1 $add2 $add3 $add4 );
+use vars qw( @ISA $conf $invoice_template );
+use vars qw( $invoice_lines @buf ); #yuck
 use Date::Format;
+use Text::Template;
 use FS::Record qw( qsearch qsearchs );
 use FS::cust_main;
 use FS::cust_bill_pkg;
@@ -15,7 +17,20 @@ use FS::cust_pkg;
 #ask FS::UID to run this stuff for us later
 $FS::UID::callback{'FS::cust_bill'} = sub { 
   $conf = new FS::Conf;
-  ( $add1, $add2, $add3, $add4 ) = ( $conf->config('address'), '', '', '', '' );
+  my @invoice_template = $conf->config('invoice_template')
+    or die "cannot load config file invoice_template";
+  $invoice_lines = 0;
+  foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy
+    /invoice_lines\((\d+)\)/;
+    $invoice_lines += $1;
+  }
+  die "no invoice_lines() functions in template?" unless $invoice_lines;
+  $invoice_template = new Text::Template (
+    TYPE   => 'ARRAY',
+    SOURCE => [ map "$_\n", @invoice_template ],
+  ) or die "can't create new Text::Template object: $Text::Template::ERROR";
+  $invoice_template->compile()
+    or die "can't compile template: $Text::Template::ERROR";
 };
 
 =head1 NAME
@@ -233,7 +248,7 @@ sub cust_pay {
 
 =item print_text [TIME];
 
-Returns an ASCII invoice, as a list of lines.
+Returns an text invoice, as a list of lines.
 
 TIME an optional value used to control the printing of overdue messages.  The
 default is now.  It isn't the date of the invoice; that's the `_date' field.
@@ -246,7 +261,7 @@ sub print_text {
 
   my( $self, $today ) = ( shift, shift );
   $today ||= time;
-  my $invnum = $self->invnum;
+#  my $invnum = $self->invnum;
   my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
   $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
     unless $cust_main->payname;
@@ -255,48 +270,24 @@ sub print_text {
   my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
   my $balance_due = $self->owed + $pr_total - $cr_total;
 
-  #overdue?
-  my $overdue = ( 
-    $balance_due > 0
-    && $today > $self->_date 
-    && $self->printed > 1
-  );
+  #
 
-  #printing bits here (yuck!)
-
-  my @collect = ();
-
-  my($description,$amount);
-  my(@buf);
-
-  #format address
-  my($l,@address)=(0,'','','','','','','');
-  $address[$l++] =
-    $cust_main->payname.
-      ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
-        ? " (P.O. #". $cust_main->payinfo. ")"
-        : ''
-      )
-  ;
-  $address[$l++]=$cust_main->company if $cust_main->company;
-  $address[$l++]=$cust_main->address1;
-  $address[$l++]=$cust_main->address2 if $cust_main->address2;
-  $address[$l++]=$cust_main->city. ", ". $cust_main->state. "  ".
-                 $cust_main->zip;
-  $address[$l++]=$cust_main->country unless $cust_main->country eq 'US';
+  #my @collect = ();
+  #my($description,$amount);
+  @buf = ();
 
   #previous balance
   foreach ( @pr_cust_bill ) {
-    push @buf, (
+    push @buf, [
       "Previous Balance, Invoice #". $_->invnum. 
                  " (". time2str("%x",$_->_date). ")",
       '$'. sprintf("%10.2f",$_->owed)
-    );
+    ];
   }
   if (@pr_cust_bill) {
-    push @buf,('','-----------');
-    push @buf,('Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) );
-    push @buf,('','');
+    push @buf,['','-----------'];
+    push @buf,['Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) ];
+    push @buf,['',''];
   }
 
   #new charges
@@ -309,117 +300,122 @@ sub print_text {
       my($pkg)=$part_pkg->pkg;
 
       if ( $_->setup != 0 ) {
-        push @buf, ( "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) );
-        push @buf, map { "  ". $_->[0]. ": ". $_->[1], '' } $cust_pkg->labels;
+        push @buf, [ "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) ];
+        push @buf,
+          map { [ "  ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
       }
 
       if ( $_->recur != 0 ) {
-        push @buf, (
+        push @buf, [
           "$pkg (" . time2str("%x",$_->sdate) . " - " .
                                 time2str("%x",$_->edate) . ")",
           '$' . sprintf("%10.2f",$_->recur)
-        );
-        push @buf, map { "  ". $_->[0]. ": ". $_->[1], '' } $cust_pkg->labels;
+        ];
+        push @buf,
+          map { [ "  ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
       }
 
     } else { #pkgnum Tax
-      push @buf,("Tax",'$' . sprintf("%10.2f",$_->setup) ) 
+      push @buf,["Tax",'$' . sprintf("%10.2f",$_->setup) ] 
         if $_->setup != 0;
     }
   }
 
-  push @buf,('','-----------');
-  push @buf,('Total New Charges',
-             '$' . sprintf("%10.2f",$self->charged) );
-  push @buf,('','');
+  push @buf,['','-----------'];
+  push @buf,['Total New Charges',
+             '$' . sprintf("%10.2f",$self->charged) ];
+  push @buf,['',''];
 
-  push @buf,('','-----------');
-  push @buf,('Total Charges',
-             '$' . sprintf("%10.2f",$self->charged + $pr_total) );
-  push @buf,('','');
+  push @buf,['','-----------'];
+  push @buf,['Total Charges',
+             '$' . sprintf("%10.2f",$self->charged + $pr_total) ];
+  push @buf,['',''];
 
   #credits
   foreach ( @cr_cust_credit ) {
-    push @buf,(
+    push @buf,[
       "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
       '$' . sprintf("%10.2f",$_->credited)
-    );
+    ];
   }
 
   #get & print payments
   foreach ( $self->cust_pay ) {
-    push @buf,(
+    push @buf,[
       "Payment received ". time2str("%x",$_->_date ),
       '$' . sprintf("%10.2f",$_->paid )
-    );
+    ];
   }
 
   #balance due
-  push @buf,('','-----------');
-  push @buf,('Balance Due','$' . 
-    sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) );
-
-  #now print
+  push @buf,['','-----------'];
+  push @buf,['Balance Due','$' . 
+    sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) ];
+
+  #setup template variables
+  
+  package FS::cust_bill::_template; #!
+  use vars qw( $invnum $date $page $total_pages @address $overdue @buf );
+
+  $invnum = $self->invnum;
+  $date = $self->_date;
+  $page = 1;
+
+  $total_pages =
+    int( scalar(@FS::cust_bill::buf) / $FS::cust_bill::invoice_lines );
+  $total_pages++
+    if scalar(@FS::cust_bill::buf) % $FS::cust_bill::invoice_lines;
+
+
+  #format address (variable for the template)
+  my $l = 0;
+  @address = ( '', '', '', '', '', '' );
+  package FS::cust_bill; #!
+  $FS::cust_bill::_template::address[$l++] =
+    $cust_main->payname.
+      ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
+        ? " (P.O. #". $cust_main->payinfo. ")"
+        : ''
+      )
+  ;
+  $FS::cust_bill::_template::address[$l++] = $cust_main->company
+    if $cust_main->company;
+  $FS::cust_bill::_template::address[$l++] = $cust_main->address1;
+  $FS::cust_bill::_template::address[$l++] = $cust_main->address2
+    if $cust_main->address2;
+  $FS::cust_bill::_template::address[$l++] =
+    $cust_main->city. ", ". $cust_main->state. "  ".  $cust_main->zip;
+  $FS::cust_bill::_template::address[$l++] = $cust_main->country
+    unless $cust_main->country eq 'US';
+
+  #overdue? (variable for the template)
+  $FS::cust_bill::_template::overdue = ( 
+    $balance_due > 0
+    && $today > $self->_date 
+    && $self->printed > 1
+  );
 
-  my $tot_lines = 50; #should be configurable
-   #header is 17 lines
-  my $tot_pages = int( scalar(@buf) / ( 2 * ( $tot_lines - 17 ) ) );
-  $tot_pages++ if scalar(@buf) % ( 2 * ( $tot_lines - 17 ) );
+  #and subroutine for the template
 
-  my $page = 1;
+  sub FS::cust_bill::_template::invoice_lines {
+    my $lines = shift;
+    map { 
+      scalar(@buf) ? shift @buf : [ '', '' ];
+    }
+    ( 1 .. $lines );
+  }
+    
+  $FS::cust_bill::_template::page = 1;
   my $lines;
+  my @collect;
   while (@buf) {
-    $lines = $tot_lines;
-    my @header = &header(
-      $page, $tot_pages, $self->_date, $self->invnum, @address
+    push @collect, split("\n",
+      $invoice_template->fill_in( PACKAGE => 'FS::cust_bill::_template' )
     );
-    push @collect, @header;
-    $lines -= scalar(@header);
-
-    while ( $lines-- && @buf ) {
-      $description=shift(@buf);
-      $amount=shift(@buf);
-      push @collect, myswrite($description, $amount);
-    }
-    $page++;
-  }
-  while ( $lines-- ) {
-    push @collect, myswrite('', '');
-  }
-
-  return @collect;
-
-  sub header { #17 lines
-    my ( $page, $tot_pages, $date, $invnum, @address ) = @_ ;
-    push @address, '', '', '', '';
-
-    my @return = ();
-    my $i = ' 'x32;
-    push @return,
-      '',
-      $i. 'Invoice',
-      $i. substr("Page $page of $tot_pages".' 'x10, 0, 20).
-        time2str("%x", $date ). "  FS-". $invnum,
-      '',
-      '',
-      $add1,
-      $add2,
-      $add3,
-      $add4,
-      '',
-      splice @address, 0, 7;
-    ;
-    return map $_. "\n", @return;
+    $FS::cust_bill::_template::page++;
   }
 
-  sub myswrite {
-    my $format = <<END;
-  @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<
-END
-    $^A = '';
-    formline( $format, @_ );
-    return $^A;
-  }
+  map "$_\n", @collect;
 
 }
 
@@ -427,7 +423,7 @@ END
 
 =head1 VERSION
 
-$Id: cust_bill.pm,v 1.1 1999-08-04 09:03:53 ivan Exp $
+$Id: cust_bill.pm,v 1.2 2000-08-09 11:30:41 ivan Exp $
 
 =head1 BUGS
 
diff --git a/TODO b/TODO
index 99c0b26..f95014b 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,11 +1,64 @@
-$Id: TODO,v 1.48 2000-07-06 08:57:27 ivan Exp $
+$Id: TODO,v 1.49 2000-08-09 11:30:40 ivan Exp $
 
-If you are interested in helping with any of these, please join the mailing
-list (send a blank message to ivan-freeside-subscribe@sisd.com) to avoid 
-duplication of effort.
+If you are interested in helping with any of these, please join the
+*development* mailing list (send a blank message to
+ivan-freeside-devel-subscribe@sisd.com) to avoid duplication of effort.
 
 ---
 
+http://www.ipmeter.com/ integration would be useful
+
+http://tangram.sourceforge.net/
+Tie::DBI
+
+mmm, http://pootpoot.com/~dlowe/DBIx-Table/
+
+The cybercash links in htdocs/docs/config.html are b0rken.
+
+Yes.  Which is what I've been trying to tell you.  (destination user,
+destination domainname) would be represented by the svcnum of a record in
+svc_acct.  (In retrospect, using the uid instead of the svcnum was a bad
+choice).  (domuser, domain name) would be part of the svc_acct_sm record,
+as domuser and domsvc.
+. 
+Upon further consideration, I'll probably eliminate the svc_acct_sm table
+completely, and just add a field for the svcnum of a domain and the svcnum
+of a destination mailbox to svc_acct (or perhaps a one-to-many
+relationship - multiple svcnum(s) for multiple destination mailboxes.
+hmm.)
+
+> > > > Longer-term, I need to do something about the length of the             
+> > > > column names -                                                          
+> > > > The SQL1992 standard defines 18 character column names,                 
+> > > > which would be a                                                        
+> > > > reasonable goal.                                                        
+> > >                                                                           
+> > > Maybe the thing to do would be to separate these out to separate          
+> > > tables.                                                                   
+> > > then we could simply use something like id, attrib_name,                  
+> > attrib_value                                                                
+> > > to                                                                        
+> > > store all of the radius values.                                           
+> >                                                                             
+> > Hmm, no, that's not quite right.  You don't want to store the actual        
+> > strings in the records for each account.  Probably need a table that        
+> > corresponds to the RADIUS dictionary file, with a list of                   
+> > attributes and                                                              
+> > attribute id's, then reference the attribute by id and not name.            
+> >                                                                             
+> > The more difficult bit is handling the service definitions                  
+> > correctly -                                                                 
+> > part_svc and it's effects.                                                  
+>                                                                               
+> Yes that could be a bit tricky. Perhaps just one "radius" field in            
+> part_svc that held a list of id's from the radius table that applied          
+> that particular service?                                                      
+. 
+No, that wouldn't let you set defaults or fixed values for each attribute,
+like part_svc currently does.
+
+
 hmm - if you delete an account in svc_acct somehow that a mail alias points to,
 svc_acct_sm.export will fail.  make sure this can't be done using
 the web interface.
index 46ae1fd..7841bf7 100644 (file)
@@ -3,19 +3,37 @@
 </head>
 <body>
   <h1>Billing</h1>
-  The <b>freeside-bill</b> script can be run daily to bill all customers.  Usage: bill [ -c [ i ] ] [ -d <i>date</i> ] [ -b ] <i>user</i>
   <ul>
-    <li>-c: Turn on collecting (you probably want this).
-    <li>-i: Real-time billing (as opposed to bacth billing).  Only relevant for credit cards.
-    <li>-d: Pretend it is <i>date</i> (parsed by Date::Parse)
-    <li>-b: N/A
-  </ul>
-  Printing should be configured on your freeside machine to print invoices.
-  <br><br>Batch credit card processing
-  <ul>
-    <li>After this script is run, a credit card batch will be in the <a href="schema.html#cust_pay_batch">cust_pay_batch</a> table.  Export this table to your credit card batching.
-    <li>When your batch completes, erase the cust_pay_batch records in that batch and add any necessary paymants to the <a href="schema.html#cust_pay">cust_pay</a> table.  Example code to add payments is:
-    <pre>use FS::cust_pay;
+    <li>To enable billing, you <b>must</b> create an <a href="config.html#invoice_template">invoice_template</a> configuration file.  An example file is available in the <i>conf/</i> directory of the distribution.  You also need to create an <a href="config.html#lpr">lpr</a> configuration file to enable postal invoices.
+    <ul>
+      <li>Optional: Invoice template customization
+        <ul>
+          <li>See the <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> documentation for details on the substitution language.
+          <li>You <b>must</b> call the invoice_lines() function at least once - pass it a number of lines, and it returns a list of array references, each of two elements: a service description column, and a price column.
+          <li>In addition, the following variables are available:
+            <ul>
+              <li>$invnum - invoice number
+              <li>$date - as a UNIX timestamp (see <a href="http://search.cpan.org/doc/GBARR/TimeDate-1.09/lib/Date/Format.pm">Date::Format</a> for conversion functions).
+              <li>$page - current page
+              <li>$total_pages - total pages
+              <li>@address - A six-element array containing the customer name, company, and address.
+              <li>$overdue - true if this invoice is overdue
+            </ul>
+        </ul>
+    </ul>
+    <li>You can bill individual customers by clicking on the <i>Bill now</i> link on the main customer view.
+    <li> The <b>freeside-bill</b> script can be run daily to bill all customers.  Usage:<pre>bill [ -c [ i ] ] [ -d <i>date</i> ] [ -b ] <i>user</i></pre>
+      <ul>
+        <li>-c: Turn on collecting (you probably want this).
+        <li>-i: Real-time billing (as opposed to bacth billing).  Only relevant for credit cards.
+        <li>-d: Pretend it is <i>date</i> (parsed by <a href="http://search.cpan.org/doc/GBARR/TimeDate-1.09/lib/Date/Parse.pm">Date::Parse</a>)
+        <li>-b: N/A
+      </ul>
+      <br><br>Batch credit card processing
+      <ul>
+        <li>After this script is run, a credit card batch will be in the <a href="schema.html#cust_pay_batch">cust_pay_batch</a> table.  Export this table to your credit card batching.
+        <li>When your batch completes, erase the cust_pay_batch records in that batch and add any necessary paymants to the <a href="schema.html#cust_pay">cust_pay</a> table.  Example code to add payments is:
+        <pre>use FS::cust_pay;
 
 # loop over all records in batch
 
@@ -36,13 +54,14 @@ if ( $error ) {
 # end loop
 </pre>
 All fields except paybatch are contained in the cust_pay_batch table.  You can use paybatch field to track particular batches and/or particular transactions within a batch.<br><br>
-    <li>The <b>freeside-print-batch</b> script can print or email pending credit card batches for manual entry.  Usage: freeside-print-batch [-v] [-p] [-e] [-a] [-d] <i>user</i>
-      <ul>
-        <li>-v: Verbose - Prints records to STDOUT.
-        <li>-p: Print to printer lpr as found in the conf directory.
-        <li>-e: Email output to user found in the Conf email file.
-        <li>-a: Automatically pays all records in cust_pay_batch.  Use -d with this option usually.
-        <li>-d: Delete - Pays account and deletes record from cust_pay_batch.
+        <li>The <b>freeside-print-batch</b> script can print or email pending credit card batches for manual entry.  Usage: freeside-print-batch [-v] [-p] [-e] [-a] [-d] <i>user</i>
+          <ul>
+            <li>-v: Verbose - Prints records to STDOUT.
+            <li>-p: Print to printer lpr as found in the conf directory.
+            <li>-e: Email output to user found in the Conf email file.
+            <li>-a: Automatically pays all records in cust_pay_batch.  Use -d with this option usually.
+            <li>-d: Delete - Pays account and deletes record from cust_pay_batch.
+          </ul>
       </ul>
   </ul>
 </body>
index c500a33..e42cfd8 100644 (file)
@@ -21,7 +21,7 @@ All further configuration files and directories are located in
 `/usr/local/etc/freeside/conf.<i>datasource</i>', for example, 
 `/usr/local/etc/freeside/conf.DBI:Pg:dbname=freeside'
 <ul>
-  <li><a name="address">address</a> - Your company name and address, four lines.
+  <li><a name="address">address</a> - This configuration file is no longer used.  See <a href="#invoice_template">invoice_template</a> instead.
   <li><a name="apacheroot">apacheroot</a> - The directory containing Apache virtual hosts
   <li><a name="apachemachine">apachemachine</a> - A machine with the apacheroot directory and user home directories.  The existance of this file enables setup of virtual host directories, and, in conjunction with the `home' configuration file, symlinks into user home directories.
   <li><a name="apachemachines">apachemachines</a> - Your Apache machines, one per line.  This enables export of `/etc/apache/vhosts.conf', which can be included in your Apache configuration via the <a href="http://www.apache.org/docs/mod/core.html#include">Include</a> directive.
@@ -42,6 +42,7 @@ All further configuration files and directories are located in
   <li><a name="icradius_mysqldest">icradius_mysqldest</a> - Destination directory for the MySQL databases, on the ICRADIUS machines.  Defaults to "/usr/local/var/".
   <li><a name="icradius_mysqlsource">icradius_mysqlsource</a> - Source directory for for the MySQL radcheck table files, on the Freeside machine.  Defaults to "/usr/local/var/freeside".
   <li><a name="invoice_from">invoice_from</a> - Return address on email invoices.
+  <li><a name="invoice_template">invoice_template</a> - Required template file for invoices.  See the <a href="billing.html">section on billing</a> for details.
   <li><a name="lpr">lpr</a> - Print command for paper invoices, for example `lpr -h'.
   <li><a name="maildisablecatchall">maildisablecatchall</a> - The existance of this file will disable the requirement that each virtual domain have a catch-all mailbox.
   <li><a name="mxmachines">mxmachines</a> - MX entries for new domains, weight and machine, one per line, with trailing `.'