package FS::cust_bill;
use strict;
-use vars qw( @ISA $conf $invoice_template );
+use vars qw( @ISA $conf $invoice_template $money_char );
+use vars qw( $lpr $invoice_from $smtpmachine );
+use vars qw( $processor );
+use vars qw( $xaction $E_NoErr );
+use vars qw( $bop_processor $bop_login $bop_password $bop_action @bop_options );
use vars qw( $invoice_lines @buf ); #yuck
use Date::Format;
+use Mail::Internet;
+use Mail::Header;
use Text::Template;
use FS::Record qw( qsearch qsearchs );
use FS::cust_main;
use FS::cust_credit;
use FS::cust_pay;
use FS::cust_pkg;
+use FS::cust_credit_bill;
+use FS::cust_pay_batch;
+use FS::cust_bill_event;
@ISA = qw( FS::Record );
#ask FS::UID to run this stuff for us later
$FS::UID::callback{'FS::cust_bill'} = sub {
+
$conf = new FS::Conf;
+
+ $money_char = $conf->config('money_char') || '$';
+
my @invoice_template = $conf->config('invoice_template')
or die "cannot load config file invoice_template";
$invoice_lines = 0;
) or die "can't create new Text::Template object: $Text::Template::ERROR";
$invoice_template->compile()
or die "can't compile template: $Text::Template::ERROR";
+
+ $lpr = $conf->config('lpr');
+ $invoice_from = $conf->config('invoice_from');
+ $smtpmachine = $conf->config('smtpmachine');
+
+ if ( $conf->exists('cybercash3.2') ) {
+ require CCMckLib3_2;
+ #qw($MCKversion %Config InitConfig CCError CCDebug CCDebug2);
+ require CCMckDirectLib3_2;
+ #qw(SendCC2_1Server);
+ require CCMckErrno3_2;
+ #qw(MCKGetErrorMessage $E_NoErr);
+ import CCMckErrno3_2 qw($E_NoErr);
+
+ my $merchant_conf;
+ ($merchant_conf,$xaction)= $conf->config('cybercash3.2');
+ my $status = &CCMckLib3_2::InitConfig($merchant_conf);
+ if ( $status != $E_NoErr ) {
+ warn "CCMckLib3_2::InitConfig error:\n";
+ foreach my $key (keys %CCMckLib3_2::Config) {
+ warn " $key => $CCMckLib3_2::Config{$key}\n"
+ }
+ my($errmsg) = &CCMckErrno3_2::MCKGetErrorMessage($status);
+ die "CCMckLib3_2::InitConfig fatal error: $errmsg\n";
+ }
+ $processor='cybercash3.2';
+ } elsif ( $conf->exists('business-onlinepayment') ) {
+ ( $bop_processor,
+ $bop_login,
+ $bop_password,
+ $bop_action,
+ @bop_options
+ ) = $conf->config('business-onlinepayment');
+ $bop_action ||= 'normal authorization';
+ eval "use Business::OnlinePayment";
+ $processor="Business::OnlinePayment::$bop_processor";
+ }
+
};
=head1 NAME
@cust_pay_objects = $cust_bill->cust_pay;
+ $tax_amount = $record->tax;
+
@lines = $cust_bill->print_text;
@lines = $cust_bill->print_text $time;
=head1 DESCRIPTION
-An FS::cust_bill object represents an invoice. FS::cust_bill inherits from
-FS::Record. The following fields are currently supported:
+An FS::cust_bill object represents an invoice; a declaration that a customer
+owes you money. The specific charges are itemized as B<cust_bill_pkg> records
+(see L<FS::cust_bill_pkg>). FS::cust_bill inherits from FS::Record. The
+following fields are currently supported:
=over 4
=item charged - amount of this invoice
-=item owed - amount still outstanding on this invoice, which is charged minus
-all payments (see L<FS::cust_pay>).
+=item printed - deprecated
-=item printed - how many times this invoice has been printed automatically
-(see L<FS::cust_main/"collect">).
+=item closed - books closed flag, empty or `Y'
=back
Adds this invoice to the database ("Posts" the invoice). If there is an error,
returns the error, otherwise returns false.
-When adding new invoices, owed must be charged (or null, in which case it is
-automatically set to charged).
-
-=cut
-
-sub insert {
- my $self = shift;
-
- $self->owed( $self->charged ) if $self->owed eq '';
- return "owed != charged!"
- unless $self->owed == $self->charged;
-
- $self->SUPER::insert;
-}
-
=item delete
Currently unimplemented. I don't remove invoices because there would then be
=cut
sub delete {
- return "Can't remove invoice!"
+ my $self = shift;
+ return "Can't delete closed invoice" if $self->closed =~ /^Y/i;
+ $self->SUPER::delete(@_);
}
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
-Only owed and printed may be changed. Owed is normally updated by creating and
-inserting a payment (see L<FS::cust_pay>). Printed is normally updated by
-calling the collect method of a customer object (see L<FS::cust_main>).
+Only printed may be changed. printed is normally updated by calling the
+collect method of a customer object (see L<FS::cust_main>).
=cut
#return "Can't change _date!" unless $old->_date eq $new->_date;
return "Can't change _date!" unless $old->_date == $new->_date;
return "Can't change charged!" unless $old->charged == $new->charged;
- return "(New) owed can't be > (new) charged!" if $new->owed > $new->charged;
$new->SUPER::replace($old);
}
|| $self->ut_number('custnum')
|| $self->ut_numbern('_date')
|| $self->ut_money('charged')
- || $self->ut_money('owed')
|| $self->ut_numbern('printed')
+ || $self->ut_enum('closed', [ '', 'Y' ])
;
return $error if $error;
qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
}
+=item cust_bill_event
+
+Returns the completed invoice events (see L<FS::cust_bill_event>) for this
+invoice.
+
+=cut
+
+sub cust_bill_event {
+ my $self = shift;
+ qsearch( 'cust_bill_event', { 'invnum' => $self->invnum } );
+}
+
+
+=item cust_main
+
+Returns the customer (see L<FS::cust_main>) for this invoice.
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
=item cust_credit
-Returns a list consisting of the total previous credited (see
-L<FS::cust_credit>) for this customer, followed by the previous outstanding
-credits (FS::cust_credit objects).
+Depreciated. See the cust_credited method.
+
+ #Returns a list consisting of the total previous credited (see
+ #L<FS::cust_credit>) and unapplied for this customer, followed by the previous
+ #outstanding credits (FS::cust_credit objects).
=cut
sub cust_credit {
- my $self = shift;
- my $total = 0;
- my @cust_credit = sort { $a->_date <=> $b->date }
- grep { $_->credited != 0 && $_->_date < $self->_date }
- qsearch('cust_credit', { 'custnum' => $self->custnum } )
- ;
- foreach (@cust_credit) { $total += $_->credited; }
- $total, @cust_credit;
+ use Carp;
+ croak "FS::cust_bill->cust_credit depreciated; see ".
+ "FS::cust_bill->cust_credit_bill";
+ #my $self = shift;
+ #my $total = 0;
+ #my @cust_credit = sort { $a->_date <=> $b->_date }
+ # grep { $_->credited != 0 && $_->_date < $self->_date }
+ # qsearch('cust_credit', { 'custnum' => $self->custnum } )
+ #;
+ #foreach (@cust_credit) { $total += $_->credited; }
+ #$total, @cust_credit;
}
=item cust_pay
-Returns all payments (see L<FS::cust_pay>) for this invoice.
+Depreciated. See the cust_bill_pay method.
+
+#Returns all payments (see L<FS::cust_pay>) for this invoice.
=cut
sub cust_pay {
+ use Carp;
+ croak "FS::cust_bill->cust_pay depreciated; see FS::cust_bill->cust_bill_pay";
+ #my $self = shift;
+ #sort { $a->_date <=> $b->_date }
+ # qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
+ #;
+}
+
+=item cust_bill_pay
+
+Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice.
+
+=cut
+
+sub cust_bill_pay {
+ my $self = shift;
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } );
+}
+
+=item cust_credited
+
+Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
+
+=cut
+
+sub cust_credited {
my $self = shift;
- sort { $a->_date <=> $b->date }
- qsearch( 'cust_pay', { 'invnum' => $self->invnum } )
+ sort { $a->_date <=> $b->_date }
+ qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } )
;
}
+=item tax
+
+Returns the tax amount (see L<FS::cust_bill_pkg>) for this invoice.
+
+=cut
+
+sub tax {
+ my $self = shift;
+ my $total = 0;
+ my @taxlines = qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum ,
+ 'pkgnum' => 0 } );
+ foreach (@taxlines) { $total += $_->setup; }
+ $total;
+}
+
+=item owed
+
+Returns the amount owed (still outstanding) on this invoice, which is charged
+minus all payment applications (see L<FS::cust_bill_pay>) and credit
+applications (see L<FS::cust_credit_bill>).
+
+=cut
+
+sub owed {
+ my $self = shift;
+ my $balance = $self->charged;
+ $balance -= $_->amount foreach ( $self->cust_bill_pay );
+ $balance -= $_->amount foreach ( $self->cust_credited );
+ $balance = sprintf( "%.2f", $balance);
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $balance;
+}
+
+=item send
+
+Sends this invoice to the destinations configured for this customer: send
+emails or print. See L<FS::cust_main_invoice>.
+
+=cut
+
+sub send {
+ my $self = shift;
+
+ #my @print_text = $cust_bill->print_text; #( date )
+ my @invoicing_list = $self->cust_main->invoicing_list;
+ if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
+ #false laziness w/FS::cust_pay::delete
+ #$ENV{SMTPHOSTS} = $smtpmachine;
+ $ENV{MAILADDRESS} = $invoice_from;
+ my $header = new Mail::Header ( [
+ "From: $invoice_from",
+ "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
+ "Sender: $invoice_from",
+ "Reply-To: $invoice_from",
+ "Date: ". time2str("%a, %d %b %Y %X %z", time),
+ "Subject: Invoice",
+ ] );
+ my $message = new Mail::Internet (
+ 'Header' => $header,
+ 'Body' => [ $self->print_text ], #( date)
+ );
+ $!=0;
+ $message->smtpsend( Host => $smtpmachine )
+ or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
+ or return "(customer # ". $self->custnum. ") can't send invoice email".
+ " to ". join(', ', grep { $_ ne 'POST' } @invoicing_list ).
+ " via server $smtpmachine with SMTP: $!";
+
+ #} elsif ( grep { $_ eq 'POST' } @invoicing_list ) {
+ } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) {
+ open(LPR, "|$lpr")
+ or return "Can't open pipe to $lpr: $!";
+ print LPR $self->print_text; #( date )
+ close LPR
+ or return $! ? "Error closing $lpr: $!"
+ : "Exit status $? from $lpr";
+ }
+
+ '';
+
+}
+
+=item comp
+
+Pays this invoice with a compliemntary payment. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub comp {
+ my $self = shift;
+ my $cust_pay = new FS::cust_pay ( {
+ 'invnum' => $self->invnum,
+ 'paid' => $self->owed,
+ '_date' => '',
+ 'payby' => 'COMP',
+ 'payinfo' => $self->cust_main->payinfo,
+ 'paybatch' => '',
+ } );
+ $cust_pay->insert;
+}
+
+=item realtime_card
+
+Attempts to pay this invoice with a Business::OnlinePayment realtime gateway.
+See http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supproted processors.
+
+=cut
+
+sub realtime_card {
+ my $self = shift;
+ my $cust_main = $self->cust_main;
+ my $amount = $self->owed;
+
+ unless ( $processor =~ /^Business::OnlinePayment::(.*)$/ ) {
+ return "Real-time card processing not enabled (processor $processor)";
+ }
+ my $bop_processor = $1; #hmm?
+
+ my $address = $cust_main->address1;
+ $address .= ", ". $cust_main->address2 if $cust_main->address2;
+
+ #fix exp. date
+ #$cust_main->paydate =~ /^(\d+)\/\d*(\d{2})$/;
+ $cust_main->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my $exp = "$2/$1";
+
+ my($payname, $payfirst, $paylast);
+ if ( $cust_main->payname ) {
+ $payname = $cust_main->payname;
+ $payname =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)$/
+ or do {
+ #$dbh->rollback if $oldAutoCommit;
+ return "Illegal payname $payname";
+ };
+ ($payfirst, $paylast) = ($1, $2);
+ } else {
+ $payfirst = $cust_main->getfield('first');
+ $paylast = $cust_main->getfield('first');
+ $payname = "$payfirst $paylast";
+ }
+
+ my @invoicing_list = grep { $_ ne 'POST' } $cust_main->invoicing_list;
+ if ( $conf->exists('emailinvoiceauto')
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $cust_main->default_invoicing_list;
+ }
+ my $email = $invoicing_list[0];
+
+ my( $action1, $action2 ) = split(/\s*\,\s*/, $bop_action );
+
+ my $transaction =
+ new Business::OnlinePayment( $bop_processor, @bop_options );
+ $transaction->content(
+ 'type' => 'CC',
+ 'login' => $bop_login,
+ 'password' => $bop_password,
+ 'action' => $action1,
+ 'description' => 'Internet Services',
+ 'amount' => $amount,
+ 'invoice_number' => $self->invnum,
+ 'customer_id' => $self->custnum,
+ 'last_name' => $paylast,
+ 'first_name' => $payfirst,
+ 'name' => $payname,
+ 'address' => $address,
+ 'city' => $cust_main->city,
+ 'state' => $cust_main->state,
+ 'zip' => $cust_main->zip,
+ 'country' => $cust_main->country,
+ 'card_number' => $cust_main->payinfo,
+ 'expiration' => $exp,
+ 'referer' => 'http://cleanwhisker.420.am/',
+ 'email' => $email,
+ );
+ $transaction->submit();
+
+ if ( $transaction->is_success() && $action2 ) {
+ my $auth = $transaction->authorization;
+ my $ordernum = $transaction->order_number;
+ #warn "********* $auth ***********\n";
+ #warn "********* $ordernum ***********\n";
+ my $capture =
+ new Business::OnlinePayment( $bop_processor, @bop_options );
+
+ $capture->content(
+ action => $action2,
+ login => $bop_login,
+ password => $bop_password,
+ order_number => $ordernum,
+ amount => $amount,
+ authorization => $auth,
+ description => 'Internet Services',
+ );
+
+ $capture->submit();
+
+ unless ( $capture->is_success ) {
+ my $e = "Authorization sucessful but capture failed, invnum #".
+ $self->invnum. ': '. $capture->result_code.
+ ": ". $capture->error_message;
+ warn $e;
+ return $e;
+ }
+
+ }
+
+ if ( $transaction->is_success() ) {
+
+ my $cust_pay = new FS::cust_pay ( {
+ 'invnum' => $self->invnum,
+ 'paid' => $amount,
+ '_date' => '',
+ 'payby' => 'CARD',
+ 'payinfo' => $cust_main->payinfo,
+ 'paybatch' => "$processor:". $transaction->authorization,
+ } );
+ my $error = $cust_pay->insert;
+ if ( $error ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card debited but database not updated - '.
+ 'error applying payment, invnum #' . $self->invnum.
+ " ($processor): $error";
+ warn $e;
+ return $e;
+ } else {
+ return '';
+ }
+ #} elsif ( $options{'report_badcard'} ) {
+ } else {
+ return "$processor error, invnum #". $self->invnum. ': '.
+ $transaction->result_code. ": ". $transaction->error_message;
+ }
+
+}
+
+=item realtime_card_cybercash
+
+Attempts to pay this invoice with the CyberCash CashRegister realtime gateway.
+
+=cut
+
+sub realtime_card_cybercash {
+ my $self = shift;
+ my $cust_main = $self->cust_main;
+ my $amount = $self->owed;
+
+ return "CyberCash CashRegister real-time card processing not enabled!"
+ unless $processor eq 'cybercash3.2';
+
+ my $address = $cust_main->address1;
+ $address .= ", ". $cust_main->address2 if $cust_main->address2;
+
+ #fix exp. date
+ #$cust_main->paydate =~ /^(\d+)\/\d*(\d{2})$/;
+ $cust_main->paydate =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my $exp = "$2/$1";
+
+ #
+
+ my $paybatch = $self->invnum.
+ '-' . time2str("%y%m%d%H%M%S", time);
+
+ my $payname = $cust_main->payname ||
+ $cust_main->getfield('first').' '.$cust_main->getfield('last');
+
+ my $country = $cust_main->country eq 'US' ? 'USA' : $cust_main->country;
+
+ my @full_xaction = ( $xaction,
+ 'Order-ID' => $paybatch,
+ 'Amount' => "usd $amount",
+ 'Card-Number' => $cust_main->getfield('payinfo'),
+ 'Card-Name' => $payname,
+ 'Card-Address' => $address,
+ 'Card-City' => $cust_main->getfield('city'),
+ 'Card-State' => $cust_main->getfield('state'),
+ 'Card-Zip' => $cust_main->getfield('zip'),
+ 'Card-Country' => $country,
+ 'Card-Exp' => $exp,
+ );
+
+ my %result;
+ %result = &CCMckDirectLib3_2::SendCC2_1Server(@full_xaction);
+
+ if ( $result{'MStatus'} eq 'success' ) { #cybercash smps v.2 or 3
+ my $cust_pay = new FS::cust_pay ( {
+ 'invnum' => $self->invnum,
+ 'paid' => $amount,
+ '_date' => '',
+ 'payby' => 'CARD',
+ 'payinfo' => $cust_main->payinfo,
+ 'paybatch' => "$processor:$paybatch",
+ } );
+ my $error = $cust_pay->insert;
+ if ( $error ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card debited but database not updated - '.
+ 'error applying payment, invnum #' . $self->invnum.
+ " (CyberCash Order-ID $paybatch): $error";
+ warn $e;
+ return $e;
+ } else {
+ return '';
+ }
+# } elsif ( $result{'Mstatus'} ne 'failure-bad-money'
+# || $options{'report_badcard'}
+# ) {
+ } else {
+ return 'Cybercash error, invnum #' .
+ $self->invnum. ':'. $result{'MErrMsg'};
+ }
+
+}
+
+=item batch_card
+
+Adds a payment for this invoice to the pending credit card batch (see
+L<FS::cust_pay_batch>).
+
+=cut
+
+sub batch_card {
+ my $self = shift;
+ my $cust_main = $self->cust_main;
+
+ my $cust_pay_batch = new FS::cust_pay_batch ( {
+ 'invnum' => $self->getfield('invnum'),
+ 'custnum' => $cust_main->getfield('custnum'),
+ 'last' => $cust_main->getfield('last'),
+ 'first' => $cust_main->getfield('first'),
+ 'address1' => $cust_main->getfield('address1'),
+ 'address2' => $cust_main->getfield('address2'),
+ 'city' => $cust_main->getfield('city'),
+ 'state' => $cust_main->getfield('state'),
+ 'zip' => $cust_main->getfield('zip'),
+ 'country' => $cust_main->getfield('country'),
+ 'trancode' => 77,
+ 'cardnum' => $cust_main->getfield('payinfo'),
+ 'exp' => $cust_main->getfield('paydate'),
+ 'payname' => $cust_main->getfield('payname'),
+ 'amount' => $self->owed,
+ } );
+ $cust_pay_batch->insert;
+
+}
+
=item print_text [TIME];
Returns an text invoice, as a list of lines.
unless $cust_main->payname;
my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
- my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
- my $balance_due = $self->owed + $pr_total - $cr_total;
-
- #
+# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
+ #my $balance_due = $self->owed + $pr_total - $cr_total;
+ my $balance_due = $self->owed + $pr_total;
#my @collect = ();
#my($description,$amount);
push @buf, [
"Previous Balance, Invoice #". $_->invnum.
" (". time2str("%x",$_->_date). ")",
- '$'. sprintf("%10.2f",$_->owed)
+ $money_char. sprintf("%10.2f",$_->owed)
];
}
if (@pr_cust_bill) {
push @buf,['','-----------'];
- push @buf,['Total Previous Balance','$' . sprintf("%10.2f",$pr_total ) ];
+ push @buf,[ 'Total Previous Balance',
+ $money_char. sprintf("%10.2f",$pr_total ) ];
push @buf,['',''];
}
my($pkg)=$part_pkg->pkg;
if ( $_->setup != 0 ) {
- push @buf, [ "$pkg Setup",'$' . sprintf("%10.2f",$_->setup) ];
+ push @buf, [ "$pkg Setup", $money_char. sprintf("%10.2f",$_->setup) ];
push @buf,
map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
}
push @buf, [
"$pkg (" . time2str("%x",$_->sdate) . " - " .
time2str("%x",$_->edate) . ")",
- '$' . sprintf("%10.2f",$_->recur)
+ $money_char. sprintf("%10.2f",$_->recur)
];
push @buf,
map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
}
} else { #pkgnum Tax
- push @buf,["Tax",'$' . sprintf("%10.2f",$_->setup) ]
+ push @buf,["Tax", $money_char. sprintf("%10.2f",$_->setup) ]
if $_->setup != 0;
}
}
push @buf,['','-----------'];
push @buf,['Total New Charges',
- '$' . sprintf("%10.2f",$self->charged) ];
+ $money_char. sprintf("%10.2f",$self->charged) ];
push @buf,['',''];
push @buf,['','-----------'];
push @buf,['Total Charges',
- '$' . sprintf("%10.2f",$self->charged + $pr_total) ];
+ $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
push @buf,['',''];
#credits
- foreach ( @cr_cust_credit ) {
+ foreach ( $self->cust_credited ) {
+
+ #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+
+ my $reason = substr($_->cust_credit->reason,0,32);
+ $reason .= '...' if length($reason) < length($_->cust_credit->reason);
+ $reason = " ($reason) " if $reason;
push @buf,[
- "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
- '$' . sprintf("%10.2f",$_->credited)
+ "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
+ $reason,
+ $money_char. sprintf("%10.2f",$_->amount)
];
}
+ #foreach ( @cr_cust_credit ) {
+ # push @buf,[
+ # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
+ # $money_char. sprintf("%10.2f",$_->credited)
+ # ];
+ #}
#get & print payments
- foreach ( $self->cust_pay ) {
+ foreach ( $self->cust_bill_pay ) {
+
+ #something more elaborate if $_->amount ne ->cust_pay->paid ?
+
push @buf,[
- "Payment received ". time2str("%x",$_->_date ),
- '$' . sprintf("%10.2f",$_->paid )
+ "Payment received ". time2str("%x",$_->cust_pay->_date ),
+ $money_char. sprintf("%10.2f",$_->amount )
];
}
#balance due
push @buf,['','-----------'];
- push @buf,['Balance Due','$' .
- sprintf("%10.2f",$self->owed + $pr_total - $cr_total ) ];
+ push @buf,['Balance Due', $money_char.
+ sprintf("%10.2f", $balance_due ) ];
#setup template variables
=head1 VERSION
-$Id: cust_bill.pm,v 1.3 2000-09-20 10:35:21 ivan Exp $
+$Id: cust_bill.pm,v 1.23 2002-03-18 19:49:10 ivan Exp $
=head1 BUGS
=head1 SEE ALSO
-L<FS::Record>, L<FS::cust_main>, L<FS::cust_pay>, L<FS::cust_bill_pkg>,
-L<FS::cust_credit>, schema.html from the base documentation.
+L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill_pay>, L<FS::cust_pay>,
+L<FS::cust_bill_pkg>, L<FS::cust_bill_credit>, schema.html from the base
+documentation.
=cut