use strict;
use vars qw( @ISA $conf $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::UID qw( datasrc );
use FS::Record qw( qsearch qsearchs );
+use FS::Misc qw( send_email );
use FS::cust_main;
use FS::cust_bill_pkg;
use FS::cust_credit;
@ISA = qw( FS::Record );
#ask FS::UID to run this stuff for us later
-$FS::UID::callback{'FS::cust_bill'} = sub {
-
+FS::UID->install_callback( sub {
$conf = new FS::Conf;
-
$money_char = $conf->config('money_char') || '$';
-
- $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
$self->printed(0) if $self->printed eq '';
- ''; #no error
+ $self->SUPER::check;
}
=item previous
sub send {
my($self,$template) = @_;
-
- #my @print_text = $cust_bill->print_text; #( date )
+ my @print_text = $self->print_text('', $template);
my @invoicing_list = $self->cust_main->invoicing_list;
- if ( grep { $_ ne 'POST' } @invoicing_list ) { #email invoice
- #false laziness w/FS::cust_pay::delete & fs_signup_server && ::realtime_card
- #$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('', $template) ], #( date)
+
+ if ( grep { $_ ne 'POST' } @invoicing_list or !@invoicing_list ) { #email
+
+ #better to notify this person than silence
+ @invoicing_list = ($conf->config('invoice_from')) unless @invoicing_list;
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from'),
+ 'to' => [ grep { $_ ne 'POST' } @invoicing_list ],
+ 'subject' => 'Invoice',
+ 'body' => \@print_text,
);
- $!=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 ) {
+ return "can't send invoice: $error" if $error;
+
+ }
+
+ if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal
+ my $lpr = $conf->config('lpr');
open(LPR, "|$lpr")
or return "Can't open pipe to $lpr: $!";
- print LPR $self->print_text; #( date )
+ print LPR @print_text;
close LPR
or return $! ? "Error closing $lpr: $!"
: "Exit status $? from $lpr";
}
+=item send_csv OPTIONS
+
+Sends invoice as a CSV data-file to a remote host with the specified protocol.
+
+Options are:
+
+protocol - currently only "ftp"
+server
+username
+password
+dir
+
+The file will be named "N-YYYYMMDDHHMMSS.csv" where N is the invoice number
+and YYMMDDHHMMSS is a timestamp.
+
+The fields of the CSV file is as follows:
+
+record_type, invnum, custnum, _date, charged, first, last, company, address1, address2, city, state, zip, country, pkg, setup, recur, sdate, edate
+
+=over 4
+
+=item record type - B<record_type> is either C<cust_bill> or C<cust_bill_pkg>
+
+If B<record_type> is C<cust_bill>, this is a primary invoice record. The
+last five fields (B<pkg> through B<edate>) are irrelevant, and all other
+fields are filled in.
+
+If B<record_type> is C<cust_bill_pkg>, this is a line item record. Only the
+first two fields (B<record_type> and B<invnum>) and the last five fields
+(B<pkg> through B<edate>) are filled in.
+
+=item invnum - invoice number
+
+=item custnum - customer number
+
+=item _date - invoice date
+
+=item charged - total invoice amount
+
+=item first - customer first name
+
+=item last - customer first name
+
+=item company - company name
+
+=item address1 - address line 1
+
+=item address2 - address line 1
+
+=item city
+
+=item state
+
+=item zip
+
+=item country
+
+=item pkg - line item description
+
+=item setup - line item setup fee (one or both of B<setup> and B<recur> will be defined)
+
+=item recur - line item recurring fee (one or both of B<setup> and B<recur> will be defined)
+
+=item sdate - start date for recurring fee
+
+=item edate - end date for recurring fee
+
+=back
+
+=cut
+
+sub send_csv {
+ my($self, %opt) = @_;
+
+ #part one: create file
+
+ my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill";
+ mkdir $spooldir, 0700 unless -d $spooldir;
+
+ my $file = $spooldir. '/'. $self->invnum. time2str('-%Y%m%d%H%M%S.csv', time);
+
+ open(CSV, ">$file") or die "can't open $file: $!";
+
+ eval "use Text::CSV_XS";
+ die $@ if $@;
+
+ my $csv = Text::CSV_XS->new({'always_quote'=>1});
+
+ my $cust_main = $self->cust_main;
+
+ $csv->combine(
+ 'cust_bill',
+ $self->invnum,
+ $self->custnum,
+ time2str("%x", $self->_date),
+ sprintf("%.2f", $self->charged),
+ ( map { $cust_main->getfield($_) }
+ qw( first last company address1 address2 city state zip country ) ),
+ map { '' } (1..5),
+ ) or die "can't create csv";
+ print CSV $csv->string. "\n";
+
+ #new charges (false laziness w/print_text)
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+
+ my($pkg, $setup, $recur, $sdate, $edate);
+ if ( $cust_bill_pkg->pkgnum ) {
+
+ ($pkg, $setup, $recur, $sdate, $edate) = (
+ $cust_bill_pkg->cust_pkg->part_pkg->pkg,
+ ( $cust_bill_pkg->setup != 0
+ ? sprintf("%.2f", $cust_bill_pkg->setup )
+ : '' ),
+ ( $cust_bill_pkg->recur != 0
+ ? sprintf("%.2f", $cust_bill_pkg->recur )
+ : '' ),
+ time2str("%x", $cust_bill_pkg->sdate),
+ time2str("%x", $cust_bill_pkg->edate),
+ );
+
+ } else { #pkgnum tax
+ next unless $cust_bill_pkg->setup != 0;
+ my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
+ ? ( $cust_bill_pkg->itemdesc || 'Tax' )
+ : 'Tax';
+ ($pkg, $setup, $recur, $sdate, $edate) =
+ ( $itemdesc, sprintf("%10.2f",$cust_bill_pkg->setup), '', '', '' );
+ }
+
+ $csv->combine(
+ 'cust_bill_pkg',
+ $self->invnum,
+ ( map { '' } (1..11) ),
+ ($pkg, $setup, $recur, $sdate, $edate)
+ ) or die "can't create csv";
+ print CSV $csv->string. "\n";
+
+ }
+
+ close CSV or die "can't close CSV: $!";
+
+ #part two: upload it
+
+ my $net;
+ if ( $opt{protocol} eq 'ftp' ) {
+ eval "use Net::FTP;";
+ die $@ if $@;
+ $net = Net::FTP->new($opt{server}) or die @$;
+ } else {
+ die "unknown protocol: $opt{protocol}";
+ }
+
+ $net->login( $opt{username}, $opt{password} )
+ or die "can't FTP to $opt{username}\@$opt{server}: login error: $@";
+
+ $net->binary or die "can't set binary mode";
+
+ $net->cwd($opt{dir}) or die "can't cwd to $opt{dir}";
+
+ $net->put($file) or die "can't put $file: $!";
+
+ $net->quit;
+
+ unlink $file;
+
+}
+
=item comp
Pays this invoice with a compliemntary payment. If there is an error,
=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.
+Attempts to pay this invoice with a credit card payment via a
+Business::OnlinePayment realtime gateway. See
+http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supported 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 $description = 'Internet Services';
- if ( $conf->exists('business-onlinepayment-description') ) {
- my $dtempl = $conf->config('business-onlinepayment-description');
-
- my $agent = $self->cust_main->agent->agent;
- my $pkgs = join(', ',
- map { $_->cust_pkg->part_pkg->pkg }
- grep { $_->pkgnum } $self->cust_bill_pkg
- );
- $description = eval qq("$dtempl");
-
- }
-
- my $transaction =
- new Business::OnlinePayment( $bop_processor, @bop_options );
- $transaction->content(
- 'type' => 'CC',
- 'login' => $bop_login,
- 'password' => $bop_password,
- 'action' => $action1,
- 'description' => $description,
- '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 => $description,
- );
-
- $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;
- }
+ $self->realtime_bop( 'CC', @_ );
+}
- }
+=item realtime_ach
- 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 {
+Attempts to pay this invoice with an electronic check (ACH) payment via a
+Business::OnlinePayment realtime gateway. See
+http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supported processors.
- my $perror = "$processor error, invnum #". $self->invnum. ': '.
- $transaction->result_code. ": ". $transaction->error_message;
-
- if ( $conf->exists('emaildecline')
- && grep { $_ ne 'POST' } $cust_main->invoicing_list
- ) {
- my @templ = $conf->config('declinetemplate');
- my $template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", @templ ],
- ) or return "($perror) can't create template: $Text::Template::ERROR";
- $template->compile()
- or return "($perror) can't compile template: $Text::Template::ERROR";
-
- my $error = $transaction->error_message;
-
- #false laziness w/FS::cust_pay::delete & fs_signup_server && ::send
- $ENV{MAILADDRESS} = $invoice_from;
- my $header = new Mail::Header ( [
- "From: $invoice_from",
- "To: ". join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list ),
- "Sender: $invoice_from",
- "Reply-To: $invoice_from",
- "Date: ". time2str("%a, %d %b %Y %X %z", time),
- "Subject: Your credit card could not be processed",
- ] );
- my $message = new Mail::Internet (
- 'Header' => $header,
- 'Body' => [ $template->fill_in() ],
- );
- $!=0;
- $message->smtpsend( Host => $smtpmachine )
- or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
- or return "($perror) (customer # ". $self->custnum.
- ") can't send card decline email to ".
- join(', ', grep { $_ ne 'POST' } $cust_main->invoicing_list ).
- " via server $smtpmachine with SMTP: $!";
- }
-
- return $perror;
- }
+=cut
+sub realtime_ach {
+ my $self = shift;
+ $self->realtime_bop( 'ECHECK', @_ );
}
-=item realtime_card_cybercash
+=item realtime_lec
-Attempts to pay this invoice with the CyberCash CashRegister realtime gateway.
+Attempts to pay this invoice with phone bill (LEC) payment via a
+Business::OnlinePayment realtime gateway. See
+http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment
+for supported processors.
=cut
-sub realtime_card_cybercash {
+sub realtime_lec {
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";
+ $self->realtime_bop( 'LEC', @_ );
+}
- #
+sub realtime_bop {
+ my( $self, $method ) = @_;
- my $paybatch = $self->invnum.
- '-' . time2str("%y%m%d%H%M%S", time);
+ my $cust_main = $self->cust_main;
+ my $amount = $self->owed;
- my $payname = $cust_main->payname ||
- $cust_main->getfield('first').' '.$cust_main->getfield('last');
+ my $description = 'Internet Services';
+ if ( $conf->exists('business-onlinepayment-description') ) {
+ my $dtempl = $conf->config('business-onlinepayment-description');
- my $country = $cust_main->country eq 'US' ? 'USA' : $cust_main->country;
+ my $agent_obj = $cust_main->agent
+ or die "can't retreive agent for $cust_main (agentnum ".
+ $cust_main->agentnum. ")";
+ my $agent = $agent_obj->agent;
+ my $pkgs = join(', ',
+ map { $_->cust_pkg->part_pkg->pkg }
+ grep { $_->pkgnum } $self->cust_bill_pkg
+ );
+ $description = eval qq("$dtempl");
+ }
- 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,
+ $cust_main->realtime_bop($method, $amount,
+ 'description' => $description,
+ 'invnum' => $self->invnum,
);
- 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
'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;
+ my $error = $cust_pay_batch->insert;
+ die $error if $error;
+ '';
}
=item print_text [TIME];
# 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;
+ unless $cust_main->payname && $cust_main->payby ne 'CHEK';
my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
}
#new charges
- foreach ( $self->cust_bill_pkg ) {
+ foreach my $cust_bill_pkg (
+ ( grep { $_->pkgnum } $self->cust_bill_pkg ), #packages first
+ ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes
+ ) {
- if ( $_->pkgnum ) {
+ if ( $cust_bill_pkg->pkgnum ) {
- my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } );
- my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart});
- my($pkg)=$part_pkg->pkg;
+ my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } );
+ my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } );
+ my $pkg = $part_pkg->pkg;
- if ( $_->setup != 0 ) {
- push @buf, [ "$pkg Setup", $money_char. sprintf("%10.2f",$_->setup) ];
+ if ( $cust_bill_pkg->setup != 0 ) {
+ push @buf, [ "$pkg Setup",
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
push @buf,
map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
}
- if ( $_->recur != 0 ) {
+ if ( $cust_bill_pkg->recur != 0 ) {
push @buf, [
- "$pkg (" . time2str("%x",$_->sdate) . " - " .
- time2str("%x",$_->edate) . ")",
- $money_char. sprintf("%10.2f",$_->recur)
+ "$pkg (" . time2str("%x", $cust_bill_pkg->sdate) . " - " .
+ time2str("%x", $cust_bill_pkg->edate) . ")",
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
];
push @buf,
map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels;
}
- } else { #pkgnum Tax
- push @buf,["Tax", $money_char. sprintf("%10.2f",$_->setup) ]
- if $_->setup != 0;
+ push @buf, map { [ " $_", '' ] } $cust_bill_pkg->details;
+
+ } else { #pkgnum tax or one-shot line item
+ my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
+ ? ( $cust_bill_pkg->itemdesc || 'Tax' )
+ : 'Tax';
+ if ( $cust_bill_pkg->setup != 0 ) {
+ push @buf, [ $itemdesc,
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ];
+ }
+ if ( $cust_bill_pkg->recur != 0 ) {
+ push @buf, [ "$itemdesc (". time2str("%x", $cust_bill_pkg->sdate). " - "
+ . time2str("%x", $cust_bill_pkg->edate). ")",
+ $money_char. sprintf("%10.2f", $cust_bill_pkg->recur)
+ ];
+ }
}
}
or die "cannot load config file $templatefile";
$invoice_lines = 0;
my $wasfunc = 0;
- foreach ( grep /invoice_lines\(\d+\)/, @invoice_template ) { #kludgy
- /invoice_lines\((\d+)\)/;
- $invoice_lines += $1;
+ foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
+ /invoice_lines\((\d*)\)/;
+ $invoice_lines += $1 || scalar(@buf);
$wasfunc=1;
}
die "no invoice_lines() functions in template?" unless $wasfunc;
#setup template variables
package FS::cust_bill::_template; #!
- use vars qw( $invnum $date $page $total_pages @address $overdue @buf );
+ use vars qw( $invnum $date $page $total_pages @address $overdue @buf $agent );
$invnum = $self->invnum;
$date = $self->_date;
$page = 1;
+ $agent = $self->cust_main->agent->agent;
if ( $FS::cust_bill::invoice_lines ) {
$total_pages =
# );
#and subroutine for the template
-
sub FS::cust_bill::_template::invoice_lines {
- my $lines = shift or return @buf;
+ my $lines = shift || scalar(@buf);
map {
scalar(@buf) ? shift @buf : [ '', '' ];
}
( 1 .. $lines );
}
-
#and fill it in
$FS::cust_bill::_template::page = 1;
my $lines;
=back
-=head1 VERSION
-
-$Id: cust_bill.pm,v 1.29 2002-04-16 10:47:34 ivan Exp $
-
=head1 BUGS
The delete method.