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;
#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
=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.
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;
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
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;
}
=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
-$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.
</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
# 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>