use FS::cust_bill_pay_batch;
use FS::part_bill_event;
use FS::payby;
+use FS::bill_batch;
+use FS::cust_bill_batch;
+use FS::usage_elec qw(most_current_date);
+use FS::cust_bill_pkg_detail; #ugh
@ISA = qw( FS::cust_main_Mixin FS::Record );
$rdate_format = $conf->config('date_format') || '%m/%d/%Y';
} );
+# i think this is cruft
+sub usage_elec{
+ warn "$me: usage_elec has been called\n";
+ my $self = shift;
+ qsearch('usage_elec',{ 'cust_nr' => $self->custnum});
+}
+
=head1 NAME
FS::cust_bill - Object methods for cust_bill records
'notice_name' => $notice_name,
);
- do_print $self->lpr_data(\%opt);
+ if($conf->exists('invoice_print_pdf')) {
+ # Add the invoice to the current batch.
+ $self->batch_invoice(\%opt);
+ }
+ else {
+ do_print $self->lpr_data(\%opt);
+ }
}
=item fax_invoice HASHREF | [ TEMPLATE ]
}
+=item batch_invoice [ HASHREF ]
+
+Place this invoice into the open batch (see C<FS::bill_batch>). If there
+isn't an open batch, one will be created.
+
+=cut
+
+sub batch_invoice {
+ my ($self, $opt) = @_;
+ my $batch = FS::bill_batch->get_open_batch;
+ my $cust_bill_batch = FS::cust_bill_batch->new({
+ batchnum => $batch->batchnum,
+ invnum => $self->invnum,
+ });
+ return $cust_bill_batch->insert($opt);
+}
+
=item ftp_invoice [ TEMPLATENAME ]
Sends this invoice data via FTP.
$params{'time'} = $today if $today;
$params{'template'} = $template if $template;
$params{$_} = $opt{$_}
- foreach grep $opt{$_}, qw( unsquealch_cdr notice_name );
+ foreach grep $opt{$_}, qw(unsquealch_cdr notice_name base ignore_due_date);
$template ||= $self->_agent_template;
Optional options include
+base - a value used for the name of the template. defaults to 'invoice'
template - a value used as a suffix for a configuration template
time - a value used to control the printing of overdue messages. The
die "Unknown format: $format"
unless $format =~ /^(latex|html|template)$/;
+ # this weirdness switches to the most recent invoice under some circumstances
+ if ( $conf->exists('svc_elec_features') && ($params{base} =~ /^rec/i) ) {
+ $self = qsearchs({
+ 'table' => 'cust_bill',
+ 'hashref' => { 'custnum' => $self->custnum },
+ 'order_by' => 'ORDER BY invnum DESC LIMIT 1',
+ });
+ }
+
my $cust_main = $self->cust_main;
$cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
unless $cust_main->payname
#create the template
my $template = $params{template} ? $params{template} : $self->_agent_template;
- my $templatefile = "invoice_$format";
+ my $templatefile = $params{base} || 'invoice'; #base only used for 'rec'
+ $templatefile .= "_$format";
$templatefile .= "_$template"
if length($template);
my @invoice_template = map "$_\n", $conf->config($templatefile)
}
+ my $agentnum = $self->cust_main->agentnum;
+
my %invoice_data = (
#invoice from info
- 'company_name' => scalar( $conf->config('company_name', $self->cust_main->agentnum) ),
- 'company_address' => join("\n", $conf->config('company_address', $self->cust_main->agentnum) ). "\n",
+ 'company_name' => scalar( $conf->config('company_name', $agentnum) ),
+ 'company_address' => join("\n", $conf->config('company_address', $agentnum) ). "\n",
'returnaddress' => $returnaddress,
'agent' => &$escape_function($cust_main->agent->agent),
'notice_name' => ($params{'notice_name'} || 'Invoice'),#escape_function?
'current_charges' => sprintf("%.2f", $self->charged),
'duedate' => $self->due_date2str($rdate_format), #date_format?
+ 'due_date' => $self->due_date2str($rdate_format), #date_format?
+ 'ignore_due_date' => ($params{'ignore_due_date'} || ''),
#customer info
'custnum' => $cust_main->display_custnum,
+ 'phone' => $cust_main->daytime,
'agent_custid' => &$escape_function($cust_main->agent_custid),
( map { $_ => &$escape_function($cust_main->$_()) } qw(
payname company address1 address2 city state zip fax
'smallerfooter' => $conf->exists('invoice-smallerfooter'),
'balance_due_below_line' => $conf->exists('balance_due_below_line'),
+ #layout info -- would be fancy to calc some of this and bury the template
+ # here in the code
+ 'topmargin' => scalar($conf->config('invoice_latextopmargin', $agentnum)),
+ 'headsep' => scalar($conf->config('invoice_latexheadsep', $agentnum)),
+ 'textheight' => scalar($conf->config('invoice_latextextheight', $agentnum)),
+ 'extracouponspace' => scalar($conf->config('invoice_latexextracouponspace', $agentnum)),
+ 'couponfootsep' => scalar($conf->config('invoice_latexcouponfootsep', $agentnum)),
+ 'verticalreturnaddress' => $conf->exists('invoice_latexverticalreturnaddress', $agentnum),
+ 'addresssep' => scalar($conf->config('invoice_latexaddresssep', $agentnum)),
+ 'amountenclosedsep' => scalar($conf->config('invoice_latexcouponamountenclosedsep', $agentnum)),
+ 'coupontoaddresssep' => scalar($conf->config('invoice_latexcoupontoaddresssep', $agentnum)),
+ 'addcompanytoaddress' => $conf->exists('invoice_latexcouponaddcompanytoaddress', $agentnum),
+
# better hang on to conf_dir for a while (for old templates)
'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
);
+ my @last_cust_bill_pkg_details = ();
+ if ($conf->exists('svc_elec_features')) {
+
+ $invoice_data{date} = time2str('%D', $self->_date); # date_format?
+
+ # get the detail records sorted by detailnum
+ # too inefficient?
+ @last_cust_bill_pkg_details =
+ sort { $a->detailnum <=> $b->detailnum }
+ map { $_->cust_bill_pkg_detail }
+ $self->cust_bill_pkg;
+
+ # save a copy of the last if there is one
+ my $last_cust_bill_pkg_detail;
+ if (scalar(@last_cust_bill_pkg_details)) {
+ $last_cust_bill_pkg_detail = pop @last_cust_bill_pkg_details;
+ push @last_cust_bill_pkg_details, $last_cust_bill_pkg_detail;
+ }
+
+ foreach my $method ( qw( last_pay setup_fee prev_read one_time_charge
+ curr_read energy_charge energy_base tdsp gr_fee
+ taxes esiid late_fee average_price
+ meter_multplier meter_number ) )
+ {
+ $invoice_data{$method} = $last_cust_bill_pkg_detail
+ ? $last_cust_bill_pkg_detail->$method
+ : '';
+ }
+
+ foreach my $method ( qw( prev_date curr_date last_pay_date ) )
+ {
+ $invoice_data{$method} =
+ $last_cust_bill_pkg_detail
+ ? time2str('%D', $last_cust_bill_pkg_detail->$method)
+ : '';
+ }
+
+ foreach my $method ( qw( one_time_description pkg_info note ) )
+ {
+ $invoice_data{$method} =
+ $last_cust_bill_pkg_detail
+ ? &$escape_function($last_cust_bill_pkg_detail->$method)
+ : '';
+ }
+
+ $invoice_data{$_} = ''
+ foreach qw( discount2_total discount2_description discount2_pkgnum
+ bill_return_address usage numberOfDays balance rate
+ previousbill_numberOfDays previousbill_totalUsage
+ lastyear_numberOfDays lastyear_totalUsage
+ billed_demand measured_demand );
+
+ if ($last_cust_bill_pkg_detail) {
+ $invoice_data{bill_return_address} =
+ $last_cust_bill_pkg_detail->bill_return_addr;
+ $invoice_data{usage} = $last_cust_bill_pkg_detail->energy_usage;
+ $invoice_data{numberOfDays} = $last_cust_bill_pkg_detail->number_of_days;
+ $invoice_data{balance} =
+ sprintf("%.2f", $last_cust_bill_pkg_detail->balance);
+ $invoice_data{actual_balance} = sprintf("%.2f", $cust_main->balance);
+ $invoice_data{rate} = sprintf("%.6f", $last_cust_bill_pkg_detail->rate);
+ $invoice_data{amount_due} =
+ sprintf("%.2f", $self->charged + $last_cust_bill_pkg_detail->balance);
+ $invoice_data{bill_charged} = $invoice_data{current_charges};
+ $invoice_data{billed_demand} = $last_cust_bill_pkg_detail->demanded_bill;
+ $invoice_data{measured_demand} =
+ $last_cust_bill_pkg_detail->measured_bill;
+ $invoice_data{total_discount1} =
+ sprintf('%.2f', $last_cust_bill_pkg_detail->discount1_total)
+ if $last_cust_bill_pkg_detail->discount1_total
+ }
+
+ if (scalar(@last_cust_bill_pkg_details) > 1) {
+ $invoice_data{previousbill_numberOfDays} =
+ &$escape_function($last_cust_bill_pkg_details[1]->number_of_days);
+ $invoice_data{previousbill_totalUsage} =
+ &$escape_function($last_cust_bill_pkg_details[1]->energy_usage);
+ }
+
+ if (scalar(@last_cust_bill_pkg_details) > 11) {
+ $invoice_data{lastyear_numberOfDays} =
+ &$escape_function($last_cust_bill_pkg_details[11]->number_of_days);
+ $invoice_data{lastyear_totalUsage} =
+ &$escape_function($last_cust_bill_pkg_details[11]->energy_usage);
+ }
+
+ #-ctran 4/11/07 : Manipulatinng the Service address to be input
+ #into latex pdf invoice. The database table cust_main call the
+ #service address as ship address. Here I will combine the ship_address1,
+ #ship_address2, and ship_zip to form service address.
+ #-ctran 4/15/07 : If service address is empty, use address1 form cust_main,
+ #this is the mailing address.
+
+ my ($ship_addr1, $ship_addr2) = ($cust_main->ship_address1,
+ $cust_main->ship_address2);
+ $ship_addr1 .= ", $ship_addr2" if $ship_addr2;
+
+ # we have a total of 30 character for the service address location,
+ # so address will consist 19 chars, zip 9 chars, ', ' 2 chars = 30
+
+ my $service_addrs;
+ if ($ship_addr1) {
+ if ( (length($ship_addr1)) > 30 ) {
+ $service_addrs = substr($ship_addr1,0,28) . "...";
+ } else {
+ $service_addrs = $ship_addr1;
+ }
+ $service_addrs .= ", ".$cust_main->ship_zip if ($cust_main->ship_zip);
+ } else {
+ $service_addrs = substr($cust_main->address1,0,30);
+ }
+ $invoice_data{srvc_addr} = &$escape_function($service_addrs);
+
+ }
+
$invoice_data{finance_section} = '';
if ( $conf->config('finance_pkgclass') ) {
my $pkg_class =
$invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total);
$invoice_data{'balance'} = sprintf("%.2f", $balance_due);
- my $agentnum = $self->cust_main->agentnum;
-
my $summarypage = '';
if ( $conf->exists('invoice_usesummary', $agentnum) ) {
$summarypage = 1;
my $other_money_char = $other_money_chars{$format};
$invoice_data{'dollar'} = $other_money_char;
+ my $dash = $conf->exists('svc_elec_features') ? '*'x20 : '-----------';
+
my @detail_items = ();
my @total_items = ();
my @buf = ();
$invoice_data{'buf'} = \@buf;
$invoice_data{'sections'} = \@sections;
+ # for some kind of statement
+ my @bills = qsearch({
+ 'table' => 'cust_bill',
+ 'hashref' => { 'custnum' => $self->custnum },
+ 'order_by' => 'ORDER BY _date DESC LIMIT 19',
+ });
+ @bills = reverse(@bills);
+
+ #what about multiple details? original code seems not to care
+ my @bill_details = ();
+ push @bill_details,
+ map { $_->cust_bill_pkg_detail }
+ map { $_->cust_bill_pkg }
+ @bills;
+
+ my @pays = reverse( qsearch({ 'table' => 'cust_pay',
+ 'hashref' => { 'custnum' => $self->custnum },
+ 'order_by' => 'ORDER BY _date DESC LIMIT 19',
+ })
+ );
+
+ $invoice_data{'total_bills'} = \@bills;
+ $invoice_data{'total_payments'} = \@pays;
+ $invoice_data{'total_details'} = \@bill_details;
+
my $previous_section = { 'description' => 'Previous Charges',
'subtotal' => $other_money_char.
sprintf('%.2f', $pr_total),
}
if ( @pr_cust_bill && !$conf->exists('disable_previous_balance') ) {
- push @buf, ['','-----------'];
+ push @buf, ['', $dash];
push @buf, [ 'Total Previous Balance',
$money_char. sprintf("%10.2f", $pr_total) ];
push @buf, ['',''];
foreach my $section (@sections, @$late_sections) {
+ # begin some normalization
+ $section->{'subtotal'} = $section->{'amount'}
+ if $multisection
+ && !exists($section->{subtotal})
+ && exists($section->{amount});
+
$invoice_data{finance_amount} = sprintf('%.2f', $section->{'subtotal'} )
if ( $invoice_data{finance_section} &&
$section->{'description'} eq $invoice_data{finance_section} );
sprintf('%.2f', $section->{'subtotal'})
if $multisection;
- # begin some normalization
+ # continue some normalization
$section->{'amount'} = $section->{'subtotal'}
if $multisection;
$detail->{'description'} = &$escape_function($line_item->{'description'});
if ( exists $line_item->{'ext_description'} ) {
@{$detail->{'ext_description'}} = @{$line_item->{'ext_description'}};
+
+ if ($conf->exists('svc_elec_features')) {
+ if ( grep { /DISCOUNT2/i } @{$line_item->{'ext_description'}} ) {
+ $invoice_data{'discount2_total'} = $line_item->{'amount'};
+ $invoice_data{'discount2_pkgnum'} = $detail->{'ref'};
+
+ #want the bare description
+ $invoice_data{'discount2_description'} = &$escape_function($_->desc)
+ foreach $self->cust_bill_pkg_pkgnum($detail->{'ref'});
+ }
+ }
+
}
$detail->{'amount'} = ( $old_latex ? '' : $money_char ).
$line_item->{'amount'};
);
}
+
if ( $section->{'description'} ) {
- push @buf, ( ['','-----------'],
+ push @buf, ( ['', $dash],
[ $section->{'description'}. ' sub-total',
$money_char. sprintf("%10.2f", $section->{'subtotal'})
],
}
$invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
- push @buf,['','-----------'];
+ push @buf,['', $dash];
push @buf,[( $conf->exists('disable_previous_balance')
? 'Total Charges'
: 'Total New Charges'
}else{
push @total_items, $total;
}
- push @buf,['','-----------'];
+ push @buf,['', $dash];
push @buf,[$item,
$money_char.
sprintf( '%10.2f', $amount )
}else{
push @total_items, $total;
}
- push @buf,['','-----------'];
+ push @buf,['', $dash];
push @buf,[$self->balance_due_msg, $money_char.
sprintf("%10.2f", $balance_due ) ];
}
'fields' => [
sub { shift->{description} },
sub { shift->{quantity} },
- sub { shift->{amount} },
+ sub { my($href, %opt) = @_;
+ ($opt{dollar} || ''). $href->{amount};
+ },
],
'align' => [ qw( l r r ) ],
'span' => [ qw( 5 1 1 ) ], # unitprices?
my ( $f, $prefix, $suffix, $separator, $column ) =
_condensed_generator_defaults($format);
+ my $money_char = '$';
if ($format eq 'latex') {
$prefix = "\\hline\n\\multicolumn{1}{c}{\\rule{0pt}{2.5ex}~} &\n";
$suffix = '\\\\';
sub { my ($d,$a,$s,$w) = @_;
return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
};
+ $money_char = '\\dollar';
}elsif ( $format eq 'html' ) {
$prefix = '"><td align="center"></td>';
$suffix = '';
sub { my ($d,$a,$s,$w) = @_;
return qq!<td align="$html_align{$a}">$d</td>!;
};
+ #$money_char = $conf->config('money_char') || '$';
+ $money_char = ''; # this is madness
}
sub {
- my @args = @_;
+ #my @args = @_;
+ my $href = shift;
my @result = ();
foreach (my $i = 0; $f->{label}->[$i]; $i++) {
- push @result, &{$column}( &{$f->{fields}->[$i]}(@args),
- map { $f->{$_}->[$i] } qw(align span width)
- );
+ my $dollar = '';
+ $dollar = $money_char if $i == scalar(@{$f->{label}})-1;
+ push @result,
+ &{$column}( &{$f->{fields}->[$i]}($href, 'dollar' => $dollar),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
}
$prefix. join( $separator, @result ). $suffix;
foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
next unless $cust_bill_pkg->pkgnum > 0;
+ my @header = $cust_bill_pkg->details_header;
+ next unless scalar(@header);
+
foreach my $detail ( $cust_bill_pkg->cust_bill_pkg_detail ) {
my $phonenum = $detail->phonenum;
'duration' => 0,
'sort_weight' => $usage_class{$detail->classnum}->weight,
'phonenum' => $phonenum,
+ 'header' => [ @header ],
};
$sections{"$phonenum $line"}{amount} += $amount; #subtotal
$sections{"$phonenum $line"}{calls}++;
my %sectionmap = ();
my $simple = new FS::usage_class { format => 'simple' }; #bleh
- my $usage_simple = new FS::usage_class { format => 'usage_simple' }; #bleh
foreach ( keys %sections ) {
+ my @header = @{ $sections{$_}{header} || [] };
+ my $usage_simple =
+ new FS::usage_class { format => 'usage_'. (scalar(@header) || 6). 'col' };
my $summary = $sections{$_}{sort_weight} < 0 ? 1 : 0;
my $usage_class = $summary ? $simple : $usage_simple;
my $ending = $summary ? ' usage charges' : '';
+ my %gen_opt = ();
+ unless ($summary) {
+ $gen_opt{label} = [ map{ &{$escape}($_) } @header ];
+ }
$sectionmap{$_} = { 'description' => &{$escape}($_. $ending),
'amount' => $sections{$_}{amount}, #subtotal
'calls' => $sections{$_}{calls},
'sort_weight' => $sections{$_}{sort_weight},
'post_total' => $summary, #inspire pagebreak
(
- ( map { $_ => $usage_class->$_($format) }
+ ( map { $_ => $usage_class->$_($format, %gen_opt) }
qw( description_generator
header_generator
total_generator
}
sub _taxsort {
- return 0 unless $a cmp $b;
- return -1 if $b eq 'Tax';
- return 1 if $a eq 'Tax';
- return -1 if $b eq 'Other surcharges';
- return 1 if $a eq 'Other surcharges';
- $a cmp $b;
+ return 0 unless $a->itemdesc cmp $b->itemdesc;
+ return -1 if $b->itemdesc eq 'Tax';
+ return 1 if $a->itemdesc eq 'Tax';
+ return -1 if $b->itemdesc eq 'Other surcharges';
+ return 1 if $a->itemdesc eq 'Other surcharges';
+ $a->itemdesc cmp $b->itemdesc;
}
sub _items_tax {
=cut
sub owed_sql {
- my $class = shift;
- 'charged - '. $class->paid_sql. ' - '. $class->credited_sql;
+ my ($class, $start, $end) = @_;
+ 'charged - '.
+ $class->paid_sql($start, $end). ' - '.
+ $class->credited_sql($start, $end);
}
=item net_sql
=cut
sub net_sql {
- my $class = shift;
- 'charged - '. $class->credited_sql;
+ my ($class, $start, $end) = @_;
+ 'charged - '. $class->credited_sql($start, $end);
}
=item paid_sql
=cut
sub paid_sql {
- #my $class = shift;
+ my ($class, $start, $end) = @_;
+ $start &&= "AND cust_bill_pay._date <= $start";
+ $end &&= "AND cust_bill_pay._date > $end";
+ $start = '' unless defined($start);
+ $end = '' unless defined($end);
"( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
- WHERE cust_bill.invnum = cust_bill_pay.invnum )";
+ WHERE cust_bill.invnum = cust_bill_pay.invnum $start $end )";
}
=item credited_sql
=cut
sub credited_sql {
- #my $class = shift;
+ my ($class, $start, $end) = @_;
+ $start &&= "AND cust_credit_bill._date <= $start";
+ $end &&= "AND cust_credit_bill._date > $end";
+ $start = '' unless defined($start);
+ $end = '' unless defined($end);
"( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
- WHERE cust_bill.invnum = cust_credit_bill.invnum )";
+ WHERE cust_bill.invnum = cust_credit_bill.invnum $start $end )";
}
=item search_sql_where HASHREF