From 157e8bdba110b7aac022bd2c2f7b377d3c5b2f85 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 29 Jun 2004 04:02:45 +0000 Subject: [PATCH] add cust_pay_refund table to refund payments --- ANNOUNCE.1.5.0 | 10 ++ FS/FS.pm | 6 +- FS/FS/cust_bill_pay.pm | 69 +++-------- FS/FS/cust_credit_bill.pm | 2 +- FS/FS/cust_credit_refund.pm | 58 +++------ FS/FS/cust_pay.pm | 20 +++- FS/FS/cust_pay_refund.pm | 187 ++++++++++++++++++++++++++++++ FS/FS/cust_refund.pm | 105 ++++++++++------- FS/MANIFEST | 2 + FS/bin/freeside-setup | 15 +++ FS/t/cust_pay_refund.t | 5 + README.1.5.0pre6 | 15 +++ httemplate/docs/upgrade10.html | 20 +++- httemplate/edit/cust_bill_pay.cgi | 8 +- httemplate/edit/process/cust_bill_pay.cgi | 24 +++- httemplate/view/cust_main.cgi | 40 +++++-- 16 files changed, 421 insertions(+), 165 deletions(-) create mode 100644 ANNOUNCE.1.5.0 create mode 100644 FS/FS/cust_pay_refund.pm create mode 100644 FS/t/cust_pay_refund.t create mode 100644 README.1.5.0pre6 diff --git a/ANNOUNCE.1.5.0 b/ANNOUNCE.1.5.0 new file mode 100644 index 000000000..d1c6f233e --- /dev/null +++ b/ANNOUNCE.1.5.0 @@ -0,0 +1,10 @@ +- broadband (dsl/wireless) tracking, etc etc +- Extended description on invoice for time/data charges +- Multiple, named taxes +- */*FIX +- extended reported and graphing +- integrated RT ticketing system +- one-time payments (in signup server too). DCRD and DCHK on-demand payment types +- credit report +- reseller interface +- cust_pay_refund and credit card refund w/supported processor diff --git a/FS/FS.pm b/FS/FS.pm index 6c560d9ea..67cb1f837 100644 --- a/FS/FS.pm +++ b/FS/FS.pm @@ -122,9 +122,11 @@ L - Credit class L - Refund class -L - Refund application class +L - Refund application to credit class -L - Credit invoice application class +L - Credit application to invoice class + +L - Refund application to payment class L - Credit card transaction queue class diff --git a/FS/FS/cust_bill_pay.pm b/FS/FS/cust_bill_pay.pm index c8b5525ea..f0cb13296 100644 --- a/FS/FS/cust_bill_pay.pm +++ b/FS/FS/cust_bill_pay.pm @@ -74,60 +74,11 @@ otherwise returns false. sub insert { my $self = shift; - - local $SIG{HUP} = 'IGNORE'; - local $SIG{INT} = 'IGNORE'; - local $SIG{QUIT} = 'IGNORE'; - local $SIG{TERM} = 'IGNORE'; - local $SIG{TSTP} = 'IGNORE'; - local $SIG{PIPE} = 'IGNORE'; - - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - my $dbh = dbh; - - my $error = $self->check; + my $error = $self->SUPER::insert; return $error if $error; - $error = $self->SUPER::insert; - - my $cust_pay = qsearchs('cust_pay', { 'paynum' => $self->paynum } ) or do { - $dbh->rollback if $oldAutoCommit; - return "unknown cust_pay.paynum: ". $self->paynum; - }; - - my $pay_total = 0; - $pay_total += $_ foreach map { $_->amount } - qsearch('cust_bill_pay', { 'paynum' => $self->paynum } ); - - if ( sprintf("%.2f", $pay_total) > sprintf("%.2f", $cust_pay->paid) ) { - $dbh->rollback if $oldAutoCommit; - return "total cust_bill_pay.amount $pay_total for paynum ". $self->paynum. - " greater than cust_pay.paid ". $cust_pay->paid; - } - - my $cust_bill = $self->cust_bill; - unless ( $cust_bill ) { - $dbh->rollback if $oldAutoCommit; - return "unknown cust_bill.invnum: ". $self->invnum; - }; - - my $bill_total = 0; - $bill_total += $_ foreach map { $_->amount } - qsearch('cust_bill_pay', { 'invnum' => $self->invnum } ); - $bill_total += $_ foreach map { $_->amount } - qsearch('cust_credit_bill', { 'invnum' => $self->invnum } ); - if ( sprintf("%.2f", $bill_total) > sprintf("%.2f", $cust_bill->charged) ) { - $dbh->rollback if $oldAutoCommit; - return "total cust_bill_pay.amount and cust_credit_bill.amount $bill_total". - " for invnum ". $self->invnum. - " greater than cust_bill.charged ". $cust_bill->charged; - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - - if ( $conf->exists('invoice_send_receipts') ) { - my $send_error = $cust_bill->send; + if ( $conf->exists('invoice_send_receipts') ) { + my $send_error = $self->cust_bill->send; warn "Error sending receipt: $send_error\n" if $send_error; } @@ -178,9 +129,23 @@ sub check { return $error if $error; return "amount must be > 0" if $self->amount <= 0; + + return "Unknown invoice" + unless my $cust_bill = + qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); + + return "Unknown payment" + unless my $cust_pay = + qsearchs( 'cust_pay', { 'paynum' => $self->paynum } ); $self->_date(time) unless $self->_date; + return "Cannot apply more than remaining value of invoice" + unless $self->amount <= $cust_bill->owed; + + return "Cannot apply more than remaining value of payment" + unless $self->amount <= $cust_pay->unapplied; + $self->SUPER::check; } diff --git a/FS/FS/cust_credit_bill.pm b/FS/FS/cust_credit_bill.pm index bd76c2e1a..0e5885f57 100644 --- a/FS/FS/cust_credit_bill.pm +++ b/FS/FS/cust_credit_bill.pm @@ -38,7 +38,7 @@ FS::cust_credit_bill - Object methods for cust_credit_bill records =head1 DESCRIPTION An FS::cust_credit_bill object represents application of a credit (see -L) to an invoice (see L). FS::cust_credit +L) to an invoice (see L). FS::cust_credit_bill inherits from FS::Record. The following fields are currently supported: =over 4 diff --git a/FS/FS/cust_credit_refund.pm b/FS/FS/cust_credit_refund.pm index d0deae2f3..ff2454d9f 100644 --- a/FS/FS/cust_credit_refund.pm +++ b/FS/FS/cust_credit_refund.pm @@ -70,43 +70,9 @@ otherwise returns false. sub insert { my $self = shift; - - local $SIG{HUP} = 'IGNORE'; - local $SIG{INT} = 'IGNORE'; - local $SIG{QUIT} = 'IGNORE'; - local $SIG{TERM} = 'IGNORE'; - local $SIG{TSTP} = 'IGNORE'; - local $SIG{PIPE} = 'IGNORE'; - - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - my $dbh = dbh; - - my $error = $self->check; + my $error = $self->SUPER::insert; return $error if $error; - $error = $self->SUPER::insert; - - my $cust_refund = - qsearchs('cust_refund', { 'refundnum' => $self->refundnum } ) - or do { - $dbh->rollback if $oldAutoCommit; - return "unknown cust_refund.refundnum: ". $self->refundnum - }; - - my $refund_total = 0; - $refund_total += $_ foreach map { $_->amount } - qsearch('cust_credit_refund', { 'refundnum' => $self->refundnum } ); - - if ( $refund_total > $cust_refund->refund ) { - $dbh->rollback if $oldAutoCommit; - return "total cust_credit_refund.amount $refund_total for refundnum ". - $self->refundnum. - " greater than cust_refund.refund ". $cust_refund->refund; - } - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - ''; } @@ -132,8 +98,9 @@ sub replace { =item check -Checks all fields to make sure this is a valid payment. If there is an error, -returns the error, otherwise returns false. Called by the insert method. +Checks all fields to make sure this is a valid refund application. If there is +an error, returns the error, otherwise returns false. Called by the insert +method. =cut @@ -151,10 +118,21 @@ sub check { return "amount must be > 0" if $self->amount <= 0; + return "unknown cust_credit.crednum: ". $self->crednum + unless my $cust_credit = + qsearchs( 'cust_credit', { 'crednum' => $self->crednum } ); + + return "Unknown refund" + unless my $cust_refund = + qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } ); + $self->_date(time) unless $self->_date; - return "unknown cust_credit.crednum: ". $self->crednum - unless qsearchs( 'cust_credit', { 'crednum' => $self->crednum } ); + return "Cannot apply more than remaining value of credit" + unless $self->amount <= $cust_credit->credited; + + return "Cannot apply more than remaining value of refund" + unless $self->amount <= $cust_refund->unapplied; $self->SUPER::check; } @@ -185,7 +163,7 @@ sub cust_credit { =head1 VERSION -$Id: cust_credit_refund.pm,v 1.10 2003-08-05 00:20:41 khoff Exp $ +$Id: cust_credit_refund.pm,v 1.11 2004-06-29 04:02:44 ivan Exp $ =head1 BUGS diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index ba9924f99..111de05da 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -9,6 +9,7 @@ use FS::Record qw( dbh qsearch qsearchs dbh ); use FS::Misc qw(send_email); use FS::cust_bill; use FS::cust_bill_pay; +use FS::cust_pay_refund; use FS::cust_main; @ISA = qw( FS::Record ); @@ -371,10 +372,26 @@ sub cust_bill_pay { ; } +=item cust_pay_refund + +Returns all applications of refunds (see L) to this +payment. + +=cut + +sub cust_pay_refund { + my $self = shift; + sort { $a->_date <=> $b->_date } + qsearch( 'cust_pay_refund', { 'paynum' => $self->paynum } ) + ; +} + + =item unapplied Returns the amount of this payment that is still unapplied; which is -paid minus all payment applications (see L). +paid minus all payment applications (see L) and refund +applications (see L). =cut @@ -382,6 +399,7 @@ sub unapplied { my $self = shift; my $amount = $self->paid; $amount -= $_->amount foreach ( $self->cust_bill_pay ); + $amount -= $_->amount foreach ( $self->cust_pay_refund ); sprintf("%.2f", $amount ); } diff --git a/FS/FS/cust_pay_refund.pm b/FS/FS/cust_pay_refund.pm new file mode 100644 index 000000000..55a5eb778 --- /dev/null +++ b/FS/FS/cust_pay_refund.pm @@ -0,0 +1,187 @@ +package FS::cust_pay_refund; + +use strict; +use vars qw( @ISA ); #$conf ); +use FS::UID qw( getotaker ); +use FS::Record qw( qsearchs ); # qsearch ); +use FS::cust_main; +use FS::cust_pay; +use FS::cust_refund; + +@ISA = qw( FS::Record ); + +#ask FS::UID to run this stuff for us later +#FS::UID->install_callback( sub { +# $conf = new FS::Conf; +#} ); + +=head1 NAME + +FS::cust_pay_refund - Object methods for cust_pay_refund records + +=head1 SYNOPSIS + + use FS::cust_pay_refund; + + $record = new FS::cust_pay_refund \%hash; + $record = new FS::cust_pay_refund { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_pay_refund object represents application of a refund (see +L) to an payment (see L). FS::cust_pay_refund +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item payrefundnum - primary key + +=item paynum - credit being applied + +=item refundnum - invoice to which credit is applied (see L) + +=item amount - amount of the credit applied + +=item _date - specified as a UNIX timestamp; see L. Also see +L and L for conversion functions. + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new cust_pay_refund. To add the cust_pay_refund to the database, +see L<"insert">. + +=cut + +sub table { 'cust_pay_refund'; } + +=item insert + +Adds this cust_pay_refund to the database. If there is an error, returns the +error, otherwise returns false. + +=cut + +sub insert { + my $self = shift; + my $error = $self->SUPER::insert(@_); + return $error if $error; + + ''; +} + +=item delete + +=cut + +sub delete { + my $self = shift; + return "Can't apply refund to closed payment" + if $self->cust_pay->closed =~ /^Y/i; + return "Can't apply closed refund" + if $self->cust_refund->closed =~ /^Y/i; + $self->SUPER::delete(@_); +} + +=item replace OLD_RECORD + +Application of refunds to payments may not be modified. + +=cut + +sub replace { + return "Can't modify application of a refund to payment!" +} + +=item check + +Checks all fields to make sure this is a valid refund application to a payment. +If there is an error, returns the error, otherwise returns false. Called by +the insert and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('payrefundnum') + || $self->ut_number('paynum') + || $self->ut_number('refundnum') + || $self->ut_numbern('_date') + || $self->ut_money('amount') + ; + return $error if $error; + + return "amount must be > 0" if $self->amount <= 0; + + return "Unknown payment" + unless my $cust_pay = + qsearchs( 'cust_pay', { 'paynum' => $self->paynum } ); + + return "Unknown refund" + unless my $cust_refund = + qsearchs( 'cust_refund', { 'refundnum' => $self->refundnum } ); + + $self->_date(time) unless $self->_date; + + return 'Cannot apply ($'. $self->amount. ') more than'. + ' remaining value of refund ($'. $cust_refund->unapplied. ')' + unless $self->amount <= $cust_refund->unapplied; + + return "Cannot apply more than remaining value of payment" + unless $self->amount <= $cust_pay->unapplied; + + $self->SUPER::check; +} + +=item sub cust_credit + +Returns the credit (see L) + +=cut + +sub cust_credit { + my $self = shift; + qsearchs( 'cust_credit', { 'crednum' => $self->crednum } ); +} + +=item cust_bill + +Returns the invoice (see L) + +=cut + +sub cust_bill { + my $self = shift; + qsearchs( 'cust_bill', { 'invnum' => $self->invnum } ); +} + +=back + +=head1 BUGS + +The delete method. + +=head1 SEE ALSO + +L, L, L, L, +schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm index d60c01061..b9f48db5c 100644 --- a/FS/FS/cust_refund.pm +++ b/FS/FS/cust_refund.pm @@ -3,10 +3,11 @@ package FS::cust_refund; use strict; use vars qw( @ISA ); use Business::CreditCard; -use FS::Record qw( qsearchs dbh ); +use FS::Record qw( qsearch qsearchs dbh ); use FS::UID qw(getotaker); use FS::cust_credit; use FS::cust_credit_refund; +use FS::cust_pay_refund; use FS::cust_main; @ISA = qw( FS::Record ); @@ -78,7 +79,9 @@ Adds this refund to the database. For backwards-compatibility and convenience, if the additional field crednum is defined, an FS::cust_credit_refund record for the full amount of the refund -will be created. In this case, custnum is optional. +will be created. Or (this time for convenience and consistancy), if the +additional field paynum is defined, an FS::cust_pay_refund record for the full +amount of the refund will be created. In both cases, custnum is optional. =cut @@ -103,6 +106,13 @@ sub insert { return "Unknown cust_credit.crednum: ". $self->crednum; }; $self->custnum($cust_credit->custnum); + } elsif ( $self->paynum ) { + my $cust_pay = qsearchs('cust_pay', { 'paynum' => $self->paynum } ) + or do { + $dbh->rollback if $oldAutoCommit; + return "Unknown cust_pay.paynum: ". $self->paynum; + }; + $self->custnum($cust_pay->custnum); } my $error = $self->check; @@ -127,57 +137,20 @@ sub insert { return $error; } #$self->custnum($cust_credit_refund->cust_credit->custnum); - } - - - $dbh->commit or die $dbh->errstr if $oldAutoCommit; - - ''; - -} - -sub upgrade_replace { #1.3.x->1.4.x - my $self = shift; - - local $SIG{HUP} = 'IGNORE'; - local $SIG{INT} = 'IGNORE'; - local $SIG{QUIT} = 'IGNORE'; - local $SIG{TERM} = 'IGNORE'; - local $SIG{TSTP} = 'IGNORE'; - local $SIG{PIPE} = 'IGNORE'; - - my $oldAutoCommit = $FS::UID::AutoCommit; - local $FS::UID::AutoCommit = 0; - my $dbh = dbh; - - my $error = $self->check; - return $error if $error; - - my %new = $self->hash; - my $new = FS::cust_refund->new(\%new); - - if ( $self->crednum ) { - my $cust_credit_refund = new FS::cust_credit_refund { - 'crednum' => $self->crednum, + } elsif ( $self->paynum ) { + my $cust_pay_refund = new FS::cust_pay_refund { + 'paynum' => $self->paynum, 'refundnum' => $self->refundnum, 'amount' => $self->refund, '_date' => $self->_date, }; - $error = $cust_credit_refund->insert; + $error = $cust_pay_refund->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; } - $new->custnum($cust_credit_refund->cust_credit->custnum); - } else { - die; } - $error = $new->SUPER::replace($self); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } $dbh->commit or die $dbh->errstr if $oldAutoCommit; @@ -263,6 +236,52 @@ sub check { $self->SUPER::check; } +=item cust_credit_refund + +Returns all applications to credits (see L) for this +refund. + +=cut + +sub cust_credit_refund { + my $self = shift; + sort { $a->_date <=> $b->_date } + qsearch( 'cust_credit_refund', { 'refundnum' => $self->refundnum } ) + ; +} + +=item cust_pay_refund + +Returns all applications to payments (see L) for this +refund. + +=cut + +sub cust_pay_refund { + my $self = shift; + sort { $a->_date <=> $b->_date } + qsearch( 'cust_pay_refund', { 'refundnum' => $self->refundnum } ) + ; +} + +=item unapplied + +Returns the amount of this refund that is still unapplied; which is +amount minus all credit applications (see L) and +payment applications (see L). + +=cut + +sub unapplied { + my $self = shift; + my $amount = $self->refund; + $amount -= $_->amount foreach ( $self->cust_credit_refund ); + $amount -= $_->amount foreach ( $self->cust_pay_refund ); + sprintf("%.2f", $amount ); +} + + + =item payinfo_masked Returns a "masked" payinfo field with all but the last four characters replaced diff --git a/FS/MANIFEST b/FS/MANIFEST index 24fc1d1bc..4e78e7220 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -59,6 +59,7 @@ FS/cust_pay.pm FS/cust_bill_event.pm FS/cust_bill_pay.pm FS/cust_pay_batch.pm +FS/cust_pay_refund.pm FS/cust_pkg.pm FS/cust_refund.pm FS/cust_credit_refund.pm @@ -148,6 +149,7 @@ t/cust_main_county.t t/cust_main_invoice.t t/cust_pay.t t/cust_pay_batch.t +t/cust_pay_refund.t t/cust_pkg.t t/cust_refund.t t/cust_svc.t diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index 522c0a1a2..33b3465e0 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -1115,6 +1115,21 @@ sub tables_hash_hack { 'index' => [], }, + 'cust_pay_refund' => { + 'columns' => [ + 'payrefundnum', 'serial', '', '', + 'paynum', 'int', '', '', + 'refundnum', 'int', '', '', + '_date', @date_type, + 'amount', @money_type, + ], + 'primary_key' => 'payrefundnum', + 'unique' => [], + 'index' => [ ['paynum'], ['refundnum'] ], + }, + + + ); %tables; diff --git a/FS/t/cust_pay_refund.t b/FS/t/cust_pay_refund.t new file mode 100644 index 000000000..85d6c2316 --- /dev/null +++ b/FS/t/cust_pay_refund.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_pay_refund; +$loaded=1; +print "ok 1\n"; diff --git a/README.1.5.0pre6 b/README.1.5.0pre6 new file mode 100644 index 000000000..ba9129fab --- /dev/null +++ b/README.1.5.0pre6 @@ -0,0 +1,15 @@ +CREATE TABLE cust_pay_refund ( + payrefundnum serial NOT NULL, + paynum int NOT NULL, + refundnum int NOT NULL, + _date int NOT NULL, + amount decimal(10,2) NOT NULL, + PRIMARY KEY (payrefundnum) +); +CREATE INDEX cust_pay_refund1 ON cust_pay_refund(paynum); +CREATE INDEX cust_pay_refund2 ON cust_pay_refund(refundnum); + +dbdef-create username +create-history-tables username cust_pay_refund +dbdef-create username + diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html index c2b88be32..1d099646f 100644 --- a/httemplate/docs/upgrade10.html +++ b/httemplate/docs/upgrade10.html @@ -113,8 +113,8 @@ select setval('public.part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from par Or on Pg versions that don't support DROP CONSTRAINT and ADD PRIMARY KEY (tested on 7.1 and 7.2 so far): DROP INDEX part_pkg_temp_pkey; CREATE UNIQUE INDEX part_pkg_pkey ON part_pkg (pkgpart); -7.1?: select setval('part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) ); -7.2: select setval('part_pkg_pkgpart_seq', ( select max(pkgpart) from part_pkg) ); +probably this one?: select setval('part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) ); +probably not this one?: select setval('part_pkg_pkgpart_seq', ( select max(pkgpart) from part_pkg) ); CREATE TABLE h_part_pkg_temp ( historynum serial NOT NULL, @@ -148,9 +148,19 @@ select setval('public.h_part_pkg_temp_historynum_seq', ( select max(historynum) Or on Pg versions that don't support DROP CONSTRAINT and ADD PRIMARY KEY (tested on 7.1 and 7.2 so far): DROP INDEX h_part_pkg_temp_pkey; CREATE UNIQUE INDEX h_part_pkg_pkey ON h_part_pkg (historynum); -7.1?: select setval('h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) ); -7.2: select setval('h_part_pkg_historynum_seq', ( select max(historynum) from h_part_pkg) ); - +probably this one?: select setval('h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) ); +probably not this one?: select setval('h_part_pkg_historynum_seq', ( select max(historynum) from h_part_pkg) ); + +CREATE TABLE cust_pay_refund ( + payrefundnum serial NOT NULL, + paynum int NOT NULL, + refundnum int NOT NULL, + _date int NOT NULL, + amount decimal(10,2) NOT NULL, + PRIMARY KEY (payrefundnum) +); +CREATE INDEX cust_pay_refund1 ON cust_pay_refund(paynum); +CREATE INDEX cust_pay_refund2 ON cust_pay_refund(refundnum); DROP INDEX cust_bill_pkg1; diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi index 8cdf4509a..24bce308a 100755 --- a/httemplate/edit/cust_bill_pay.cgi +++ b/httemplate/edit/cust_bill_pay.cgi @@ -59,10 +59,10 @@ foreach my $cust_bill ( @cust_bill ) { END } -# if ( cust_bill == "Refund" ) { -# what.form.amount.value = "$credited"; -# } print < END @@ -75,7 +75,7 @@ foreach my $cust_bill ( @cust_bill ) { ' - '. time2str("%D",$cust_bill->_date). ' - $'. $cust_bill->owed; } -#print qq!