4 use vars qw( @ISA $conf $money_char );
5 use vars qw( $lpr $invoice_from $smtpmachine );
6 use vars qw( $processor );
7 use vars qw( $xaction $E_NoErr );
8 use vars qw( $bop_processor $bop_login $bop_password $bop_action @bop_options );
9 use vars qw( $invoice_lines @buf ); #yuck
11 use Mail::Internet 1.44;
14 use FS::Record qw( qsearch qsearchs );
16 use FS::cust_bill_pkg;
20 use FS::cust_credit_bill;
21 use FS::cust_pay_batch;
22 use FS::cust_bill_event;
24 @ISA = qw( FS::Record );
26 #ask FS::UID to run this stuff for us later
27 $FS::UID::callback{'FS::cust_bill'} = sub {
31 $money_char = $conf->config('money_char') || '$';
33 $lpr = $conf->config('lpr');
34 $invoice_from = $conf->config('invoice_from');
35 $smtpmachine = $conf->config('smtpmachine');
37 if ( $conf->exists('cybercash3.2') ) {
39 #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
40 require CCMckDirectLib3_2;
42 require CCMckErrno3_2;
43 #qw(MCKGetErrorMessage $E_NoErr);
44 import CCMckErrno3_2 qw($E_NoErr);
47 ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
48 my $status = &CCMckLib3_2::InitConfig($merchant_conf);
49 if ( $status != $E_NoErr ) {
50 warn "CCMckLib3_2::InitConfig error:\n";
51 foreach my $key (keys %CCMckLib3_2::Config) {
52 warn " $key => $CCMckLib3_2::Config{$key}\n"
54 my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
55 die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
57 $processor='cybercash3.2';
58 } elsif ( $conf->exists('business-onlinepayment') ) {
64 ) = $conf->config('business-onlinepayment');
65 $bop_action ||= 'normal authorization';
66 eval "use Business::OnlinePayment";
67 $processor="Business::OnlinePayment::$bop_processor";
74 FS::cust_bill - Object methods for cust_bill records
80 $record = new FS::cust_bill \%hash;
81 $record = new FS::cust_bill { 'column' => 'value' };
83 $error = $record->insert;
85 $error = $new_record->replace($old_record);
87 $error = $record->delete;
89 $error = $record->check;
91 ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
93 @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
95 ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
97 @cust_pay_objects = $cust_bill->cust_pay;
99 $tax_amount = $record->tax;
101 @lines = $cust_bill->print_text;
102 @lines = $cust_bill->print_text $time;
106 An FS::cust_bill object represents an invoice; a declaration that a customer
107 owes you money. The specific charges are itemized as B<cust_bill_pkg> records
108 (see L<FS::cust_bill_pkg>). FS::cust_bill inherits from FS::Record. The
109 following fields are currently supported:
113 =item invnum - primary key (assigned automatically for new invoices)
115 =item custnum - customer (see L<FS::cust_main>)
117 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
118 L<Time::Local> and L<Date::Parse> for conversion functions.
120 =item charged - amount of this invoice
122 =item printed - deprecated
124 =item closed - books closed flag, empty or `Y'
134 Creates a new invoice. To add the invoice to the database, see L<"insert">.
135 Invoices are normally created by calling the bill method of a customer object
136 (see L<FS::cust_main>).
140 sub table { 'cust_bill'; }
144 Adds this invoice to the database ("Posts" the invoice). If there is an error,
145 returns the error, otherwise returns false.
149 Currently unimplemented. I don't remove invoices because there would then be
150 no record you ever posted this invoice (which is bad, no?)
156 return "Can't delete closed invoice" if $self->closed =~ /^Y/i;
157 $self->SUPER::delete(@_);
160 =item replace OLD_RECORD
162 Replaces the OLD_RECORD with this one in the database. If there is an error,
163 returns the error, otherwise returns false.
165 Only printed may be changed. printed is normally updated by calling the
166 collect method of a customer object (see L<FS::cust_main>).
171 my( $new, $old ) = ( shift, shift );
172 return "Can't change custnum!" unless $old->custnum == $new->custnum;
173 #return "Can't change _date!" unless $old->_date eq $new->_date;
174 return "Can't change _date!" unless $old->_date == $new->_date;
175 return "Can't change charged!" unless $old->charged == $new->charged;
177 $new->SUPER::replace($old);
182 Checks all fields to make sure this is a valid invoice. If there is an error,
183 returns the error, otherwise returns false. Called by the insert and replace
192 $self->ut_numbern('invnum')
193 || $self->ut_number('custnum')
194 || $self->ut_numbern('_date')
195 || $self->ut_money('charged')
196 || $self->ut_numbern('printed')
197 || $self->ut_enum('closed', [ '', 'Y' ])
199 return $error if $error;
201 return "Unknown customer"
202 unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
204 $self->_date(time) unless $self->_date;
206 $self->printed(0) if $self->printed eq '';
213 Returns a list consisting of the total previous balance for this customer,
214 followed by the previous outstanding invoices (as FS::cust_bill objects also).
221 my @cust_bill = sort { $a->_date <=> $b->_date }
222 grep { $_->owed != 0 && $_->_date < $self->_date }
223 qsearch( 'cust_bill', { 'custnum' => $self->custnum } )
225 foreach ( @cust_bill ) { $total += $_->owed; }
231 Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
237 qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
240 =item cust_bill_event
242 Returns the completed invoice events (see L<FS::cust_bill_event>) for this
247 sub cust_bill_event {
249 qsearch( 'cust_bill_event', { 'invnum' => $self->invnum } );
255 Returns the customer (see L<FS::cust_main>) for this invoice.
261 qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
266 Depreciated. See the cust_credited method.
268 #Returns a list consisting of the total previous credited (see
269 #L<FS::cust_credit>) and unapplied for this customer, followed by the previous
270 #outstanding credits (FS::cust_credit objects).
276 croak "FS::cust_bill->cust_credit depreciated; see ".
277 "FS::cust_bill->cust_credit_bill";
280 #my @cust_credit = sort { $a->_date <=> $b->_date }
281 # grep { $_->credited != 0 && $_->_date < $self->_date }
282 # qsearch('cust_credit', { 'custnum' => $self->custnum } )
284 #foreach (@cust_credit) { $total += $_->credited; }
285 #$total, @cust_credit;
290 Depreciated. See the cust_bill_pay method.
292 #Returns all payments (see L<FS::cust_pay>) for this invoice.
298 croak "FS::cust_bill->cust_pay depreciated; see FS::cust_bill->cust_bill_pay";
300 #sort { $a->_date <=> $b->_date }
301 # qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
307 Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice.
313 sort { $a->_date <=> $b->_date }
314 qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } );
319 Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
325 sort { $a->_date <=> $b->_date }
326 qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } )
332 Returns the tax amount (see L<FS::cust_bill_pkg>) for this invoice.
339 my @taxlines = qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum ,
341 foreach (@taxlines) { $total += $_->setup; }
347 Returns the amount owed (still outstanding) on this invoice, which is charged
348 minus all payment applications (see L<FS::cust_bill_pay>) and credit
349 applications (see L<FS::cust_credit_bill>).
355 my $balance = $self->charged;
356 $balance -= $_->amount foreach ( $self->cust_bill_pay );
357 $balance -= $_->amount foreach ( $self->cust_credited );
358 $balance = sprintf( "%.2f", $balance);
359 $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
365 Sends this invoice to the destinations configured for this customer: send
366 emails or print. See L<FS::cust_main_invoice>.
371 my($self,$template) = @_;
373 #my @print_text = $cust_bill->print_text; #( date )
374 my @invoicing_list = $self->cust_main->invoicing_list;
375 if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
376 #false laziness w/FS::cust_pay::delete & fs_signup_server && ::realtime_card
377 #$ENV{SMTPHOSTS} = $smtpmachine;
378 $ENV{MAILADDRESS} = $invoice_from;
379 my $header = new Mail::Header ( [
380 "From: $invoice_from",
381 "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
382 "Sender: $invoice_from",
383 "Reply-To: $invoice_from",
384 "Date: ". time2str("%a, %d %b %Y %X %z", time),
387 my $message = new Mail::Internet (
389 'Body' => [ $self->print_text('', $template) ], #( date)
392 $message->smtpsend( Host => $smtpmachine )
393 or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
394 or return "(customer # ". $self->custnum. ") can't send invoice email".
395 " to ". join(', ', grep { $_ ne 'POST' } @invoicing_list ).
396 " via server $smtpmachine with SMTP: $!";
398 #} elsif ( grep { $_ eq 'POST' } @invoicing_list ) {
399 } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
401 or return "Can't open pipe to $lpr: $!";
402 print LPR $self->print_text; #( date )
404 or return $! ? "Error closing $lpr: $!"
405 : "Exit status $? from $lpr";
414 Pays this invoice with a compliemntary payment. If there is an error,
415 returns the error, otherwise returns false.
421 my $cust_pay = new FS::cust_pay ( {
422 'invnum' => $self->invnum,
423 'paid' => $self->owed,
426 'payinfo' => $self->cust_main->payinfo,
434 Attempts to pay this invoice with a Business::OnlinePayment realtime gateway.
435 See http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
436 for supproted processors.
442 my $cust_main = $self->cust_main;
443 my $amount = $self->owed;
445 unless ( $processor =~ /^Business::OnlinePayment::(.*)$/ ) {
446 return "Real-time card processing not enabled (processor $processor)";
448 my $bop_processor = $1; #hmm?
450 my $address = $cust_main->address1;
451 $address .= ", ". $cust_main->address2 if $cust_main->address2;
454 #$cust_main->paydate =~ /^(\d+)\/\d*(\d{2})$/;
455 $cust_main->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
458 my($payname, $payfirst, $paylast);
459 if ( $cust_main->payname ) {
460 $payname = $cust_main->payname;
461 $payname =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
463 #$dbh->rollback if $oldAutoCommit;
464 return "Illegal payname $payname";
466 ($payfirst, $paylast) = ($1, $2);
468 $payfirst = $cust_main->getfield('first');
469 $paylast = $cust_main->getfield('last');
470 $payname = "$payfirst $paylast";
473 my @invoicing_list = grep { $_ ne 'POST' } $cust_main->invoicing_list;
474 if ( $conf->exists('emailinvoiceauto')
475 || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
476 push @invoicing_list, $cust_main->default_invoicing_list;
478 my $email = $invoicing_list[0];
480 my( $action1, $action2 ) = split(/\s*\,\s*/, $bop_action );
482 my $description = 'Internet Services';
483 if ( $conf->exists('business-onlinepayment-description') ) {
484 my $dtempl = $conf->config('business-onlinepayment-description');
486 my $agent_obj = $cust_main->agent
487 or die "can't retreive agent for $cust_main (agentnum ".
488 $cust_main->agentnum. ")";
489 my $agent = $agent_obj->agent;
490 my $pkgs = join(', ',
491 map { $_->cust_pkg->part_pkg->pkg }
492 grep { $_->pkgnum } $self->cust_bill_pkg
494 $description = eval qq("$dtempl");
499 new Business::OnlinePayment( $bop_processor, @bop_options );
500 $transaction->content(
502 'login' => $bop_login,
503 'password' => $bop_password,
504 'action' => $action1,
505 'description' => $description,
507 'invoice_number' => $self->invnum,
508 'customer_id' => $self->custnum,
509 'last_name' => $paylast,
510 'first_name' => $payfirst,
512 'address' => $address,
513 'city' => $cust_main->city,
514 'state' => $cust_main->state,
515 'zip' => $cust_main->zip,
516 'country' => $cust_main->country,
517 'card_number' => $cust_main->payinfo,
518 'expiration' => $exp,
519 'referer' => 'http://cleanwhisker.420.am/',
521 'phone' => $cust_main->daytime || $cust_main->night,
523 $transaction->submit();
525 if ( $transaction->is_success() && $action2 ) {
526 my $auth = $transaction->authorization;
527 my $ordernum = $transaction->order_number;
529 #warn "********* $auth ***********\n";
530 #warn "********* $ordernum ***********\n";
532 new Business::OnlinePayment( $bop_processor, @bop_options );
537 password => $bop_password,
538 order_number => $ordernum,
540 authorization => $auth,
541 description => $description,
546 unless ( $capture->is_success ) {
547 my $e = "Authorization sucessful but capture failed, invnum #".
548 $self->invnum. ': '. $capture->result_code.
549 ": ". $capture->error_message;
556 if ( $transaction->is_success() ) {
558 my $cust_pay = new FS::cust_pay ( {
559 'invnum' => $self->invnum,
563 'payinfo' => $cust_main->payinfo,
564 'paybatch' => "$processor:". $transaction->authorization,
566 my $error = $cust_pay->insert;
568 # gah, even with transactions.
569 my $e = 'WARNING: Card debited but database not updated - '.
570 'error applying payment, invnum #' . $self->invnum.
571 " ($processor): $error";
577 #} elsif ( $options{'report_badcard'} ) {
580 my $perror = "$processor error, invnum #". $self->invnum. ': '.
581 $transaction->result_code. ": ". $transaction->error_message;
583 if ( $conf->exists('emaildecline')
584 && grep { $_ ne 'POST' } $cust_main->invoicing_list
586 my @templ = $conf->config('declinetemplate');
587 my $template = new Text::Template (
589 SOURCE => [ map "$_\n", @templ ],
590 ) or return "($perror) can't create template: $Text::Template::ERROR";
592 or return "($perror) can't compile template: $Text::Template::ERROR";
594 my $templ_hash = { error => $transaction->error_message };
596 #false laziness w/FS::cust_pay::delete & fs_signup_server && ::send
597 $ENV{MAILADDRESS} = $invoice_from;
598 my $header = new Mail::Header ( [
599 "From: $invoice_from",
600 "To: ". join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list ),
601 "Sender: $invoice_from",
602 "Reply-To: $invoice_from",
603 "Date: ". time2str("%a, %d %b %Y %X %z", time),
604 "Subject: Your credit card could not be processed",
606 my $message = new Mail::Internet (
608 'Body' => [ $template->fill_in(HASH => $templ_hash) ],
611 $message->smtpsend( Host => $smtpmachine )
612 or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
613 or return "($perror) (customer # ". $self->custnum.
614 ") can't send card decline email to ".
615 join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list ).
616 " via server $smtpmachine with SMTP: $!";
624 =item realtime_card_cybercash
626 Attempts to pay this invoice with the CyberCash CashRegister realtime gateway.
630 sub realtime_card_cybercash {
632 my $cust_main = $self->cust_main;
633 my $amount = $self->owed;
635 return "CyberCash CashRegister real-time card processing not enabled!"
636 unless $processor eq 'cybercash3.2';
638 my $address = $cust_main->address1;
639 $address .= ", ". $cust_main->address2 if $cust_main->address2;
642 #$cust_main->paydate =~ /^(\d+)\/\d*(\d{2})$/;
643 $cust_main->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
648 my $paybatch = $self->invnum.
649 '-' . time2str("%y%m%d%H%M%S", time);
651 my $payname = $cust_main->payname ||
652 $cust_main->getfield('first').' '.$cust_main->getfield('last');
654 my $country = $cust_main->country eq 'US' ? 'USA' : $cust_main->country;
656 my @full_xaction = ( $xaction,
657 'Order-ID' => $paybatch,
658 'Amount' => "usd $amount",
659 'Card-Number' => $cust_main->getfield('payinfo'),
660 'Card-Name' => $payname,
661 'Card-Address' => $address,
662 'Card-City' => $cust_main->getfield('city'),
663 'Card-State' => $cust_main->getfield('state'),
664 'Card-Zip' => $cust_main->getfield('zip'),
665 'Card-Country' => $country,
670 %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
672 if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
673 my $cust_pay = new FS::cust_pay ( {
674 'invnum' => $self->invnum,
678 'payinfo' => $cust_main->payinfo,
679 'paybatch' => "$processor:$paybatch",
681 my $error = $cust_pay->insert;
683 # gah, even with transactions.
684 my $e = 'WARNING: Card debited but database not updated - '.
685 'error applying payment, invnum #' . $self->invnum.
686 " (CyberCash Order-ID $paybatch): $error";
692 # } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
693 # || $options{'report_badcard'}
696 return 'Cybercash error, invnum #' .
697 $self->invnum. ':'. $result{'MErrMsg'};
704 Adds a payment for this invoice to the pending credit card batch (see
705 L<FS::cust_pay_batch>).
711 my $cust_main = $self->cust_main;
713 my $cust_pay_batch = new FS::cust_pay_batch ( {
714 'invnum' => $self->getfield('invnum'),
715 'custnum' => $cust_main->getfield('custnum'),
716 'last' => $cust_main->getfield('last'),
717 'first' => $cust_main->getfield('first'),
718 'address1' => $cust_main->getfield('address1'),
719 'address2' => $cust_main->getfield('address2'),
720 'city' => $cust_main->getfield('city'),
721 'state' => $cust_main->getfield('state'),
722 'zip' => $cust_main->getfield('zip'),
723 'country' => $cust_main->getfield('country'),
725 'cardnum' => $cust_main->getfield('payinfo'),
726 'exp' => $cust_main->getfield('paydate'),
727 'payname' => $cust_main->getfield('payname'),
728 'amount' => $self->owed,
730 my $error = $cust_pay_batch->insert;
731 die $error if $error;
736 =item print_text [TIME];
738 Returns an text invoice, as a list of lines.
740 TIME an optional value used to control the printing of overdue messages. The
741 default is now. It isn't the date of the invoice; that's the `_date' field.
742 It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
743 L<Time::Local> and L<Date::Parse> for conversion functions.
749 my( $self, $today, $template ) = @_;
751 # my $invnum = $self->invnum;
752 my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } );
753 $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
754 unless $cust_main->payname;
756 my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
757 # my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
758 #my $balance_due = $self->owed + $pr_total - $cr_total;
759 my $balance_due = $self->owed + $pr_total;
762 #my($description,$amount);
766 foreach ( @pr_cust_bill ) {
768 "Previous Balance, Invoice #". $_->invnum.
769 " (". time2str("%x",$_->_date). ")",
770 $money_char. sprintf("%10.2f",$_->owed)
774 push @buf,['','-----------'];
775 push @buf,[ 'Total Previous Balance',
776 $money_char. sprintf("%10.2f",$pr_total ) ];
781 foreach ( $self->cust_bill_pkg ) {
785 my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
786 my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
787 my($pkg)=$part_pkg->pkg;
789 if ( $_->setup != 0 ) {
790 push @buf, [ "$pkg Setup", $money_char. sprintf("%10.2f",$_->setup) ];
792 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
795 if ( $_->recur != 0 ) {
797 "$pkg (" . time2str("%x",$_->sdate) . " - " .
798 time2str("%x",$_->edate) . ")",
799 $money_char. sprintf("%10.2f",$_->recur)
802 map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
806 push @buf,["Tax", $money_char. sprintf("%10.2f",$_->setup) ]
811 push @buf,['','-----------'];
812 push @buf,['Total New Charges',
813 $money_char. sprintf("%10.2f",$self->charged) ];
816 push @buf,['','-----------'];
817 push @buf,['Total Charges',
818 $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
822 foreach ( $self->cust_credited ) {
824 #something more elaborate if $_->amount ne $_->cust_credit->credited ?
826 my $reason = substr($_->cust_credit->reason,0,32);
827 $reason .= '...' if length($reason) < length($_->cust_credit->reason);
828 $reason = " ($reason) " if $reason;
830 "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
832 $money_char. sprintf("%10.2f",$_->amount)
835 #foreach ( @cr_cust_credit ) {
837 # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
838 # $money_char. sprintf("%10.2f",$_->credited)
842 #get & print payments
843 foreach ( $self->cust_bill_pay ) {
845 #something more elaborate if $_->amount ne ->cust_pay->paid ?
848 "Payment received ". time2str("%x",$_->cust_pay->_date ),
849 $money_char. sprintf("%10.2f",$_->amount )
854 push @buf,['','-----------'];
855 push @buf,['Balance Due', $money_char.
856 sprintf("%10.2f", $balance_due ) ];
859 my $templatefile = 'invoice_template';
860 $templatefile .= "_$template" if $template;
861 my @invoice_template = $conf->config($templatefile)
862 or die "cannot load config file $templatefile";
865 foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy
866 /invoice_lines\((\d+)\)/;
867 $invoice_lines += $1;
870 die "no invoice_lines() functions in template?" unless $wasfunc;
871 my $invoice_template = new Text::Template (
873 SOURCE => [ map "$_\n", @invoice_template ],
874 ) or die "can't create new Text::Template object: $Text::Template::ERROR";
875 $invoice_template->compile()
876 or die "can't compile template: $Text::Template::ERROR";
878 #setup template variables
879 package FS::cust_bill::_template; #!
880 use vars qw( $invnum $date $page $total_pages @address $overdue @buf );
882 $invnum = $self->invnum;
883 $date = $self->_date;
886 if ( $FS::cust_bill::invoice_lines ) {
888 int( scalar(@FS::cust_bill::buf) / $FS::cust_bill::invoice_lines );
890 if scalar(@FS::cust_bill::buf) % $FS::cust_bill::invoice_lines;
895 #format address (variable for the template)
897 @address = ( '', '', '', '', '', '' );
898 package FS::cust_bill; #!
899 $FS::cust_bill::_template::address[$l++] =
901 ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
902 ? " (P.O. #". $cust_main->payinfo. ")"
906 $FS::cust_bill::_template::address[$l++] = $cust_main->company
907 if $cust_main->company;
908 $FS::cust_bill::_template::address[$l++] = $cust_main->address1;
909 $FS::cust_bill::_template::address[$l++] = $cust_main->address2
910 if $cust_main->address2;
911 $FS::cust_bill::_template::address[$l++] =
912 $cust_main->city. ", ". $cust_main->state. " ". $cust_main->zip;
913 $FS::cust_bill::_template::address[$l++] = $cust_main->country
914 unless $cust_main->country eq 'US';
916 # #overdue? (variable for the template)
917 # $FS::cust_bill::_template::overdue = (
919 # && $today > $self->_date
920 ## && $self->printed > 1
921 # && $self->printed > 0
924 #and subroutine for the template
926 sub FS::cust_bill::_template::invoice_lines {
927 my $lines = shift or return @buf;
929 scalar(@buf) ? shift @buf : [ '', '' ];
936 $FS::cust_bill::_template::page = 1;
940 push @collect, split("\n",
941 $invoice_template->fill_in( PACKAGE => 'FS::cust_bill::_template' )
943 $FS::cust_bill::_template::page++;
946 map "$_\n", @collect;
954 $Id: cust_bill.pm,v 1.37 2002-06-07 20:33:27 khoff Exp $
960 print_text formatting (and some logic :/) is in source, but needs to be
961 slurped in from a file. Also number of lines ($=).
963 missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style
964 or something similar so the look can be completely customized?)
968 L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill_pay>, L<FS::cust_pay>,
969 L<FS::cust_bill_pkg>, L<FS::cust_bill_credit>, schema.html from the base