diff options
-rw-r--r-- | FS/FS/cust_bill.pm | 224 | ||||
-rw-r--r-- | TODO | 61 | ||||
-rw-r--r-- | htdocs/docs/billing.html | 57 | ||||
-rw-r--r-- | htdocs/docs/config.html | 3 |
4 files changed, 207 insertions, 138 deletions
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 30db4699f..1d0790ef1 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -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 @@ -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. diff --git a/htdocs/docs/billing.html b/htdocs/docs/billing.html index 46ae1fdad..7841bf776 100644 --- a/htdocs/docs/billing.html +++ b/htdocs/docs/billing.html @@ -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> diff --git a/htdocs/docs/config.html b/htdocs/docs/config.html index c500a3336..e42cfd8e1 100644 --- a/htdocs/docs/config.html +++ b/htdocs/docs/config.html @@ -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 `.' |