X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=fcc2a65b5f444b9cd13cc16a0c56afa2e2bf0210;hb=refs%2Fheads%2Fsvc_elec_features;hp=8cc84e5f08c35051252c0f2e1361cc800e6ecaa8;hpb=624b2d44625f69d71175c3348cae635d580c890b;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 8cc84e5f0..fcc2a65b5 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -34,6 +34,10 @@ use FS::cust_bill_pay; 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 ); @@ -48,6 +52,13 @@ FS::UID->install_callback( sub { $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 @@ -1300,7 +1311,13 @@ sub print { '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 ] @@ -1346,6 +1363,23 @@ sub fax_invoice { } +=item batch_invoice [ HASHREF ] + +Place this invoice into the open batch (see C). 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. @@ -2026,7 +2060,7 @@ sub print_latex { $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; @@ -2075,6 +2109,7 @@ Non optional options include 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 @@ -2104,6 +2139,15 @@ sub print_generic { 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 @@ -2116,7 +2160,8 @@ sub print_generic { #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) @@ -2299,11 +2344,13 @@ sub print_generic { } + 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), @@ -2316,9 +2363,12 @@ sub print_generic { '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 @@ -2331,6 +2381,19 @@ sub print_generic { '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", @@ -2340,6 +2403,121 @@ sub print_generic { ); + 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 = @@ -2399,8 +2577,6 @@ sub print_generic { $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; @@ -2468,6 +2644,8 @@ sub print_generic { 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 = (); @@ -2478,6 +2656,31 @@ sub print_generic { $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), @@ -2575,7 +2778,7 @@ sub print_generic { } 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, ['','']; @@ -2583,6 +2786,12 @@ sub print_generic { 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} ); @@ -2591,7 +2800,7 @@ sub print_generic { sprintf('%.2f', $section->{'subtotal'}) if $multisection; - # begin some normalization + # continue some normalization $section->{'amount'} = $section->{'subtotal'} if $multisection; @@ -2624,6 +2833,18 @@ sub print_generic { $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'}; @@ -2639,8 +2860,9 @@ sub print_generic { ); } + if ( $section->{'description'} ) { - push @buf, ( ['','-----------'], + push @buf, ( ['', $dash], [ $section->{'description'}. ' sub-total', $money_char. sprintf("%10.2f", $section->{'subtotal'}) ], @@ -2713,7 +2935,7 @@ sub print_generic { } $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal); - push @buf,['','-----------']; + push @buf,['', $dash]; push @buf,[( $conf->exists('disable_previous_balance') ? 'Total Charges' : 'Total New Charges' @@ -2747,7 +2969,7 @@ sub print_generic { }else{ push @total_items, $total; } - push @buf,['','-----------']; + push @buf,['', $dash]; push @buf,[$item, $money_char. sprintf( '%10.2f', $amount ) @@ -2842,7 +3064,7 @@ sub print_generic { }else{ push @total_items, $total; } - push @buf,['','-----------']; + push @buf,['', $dash]; push @buf,[$self->balance_due_msg, $money_char. sprintf("%10.2f", $balance_due ) ]; } @@ -3353,7 +3575,9 @@ my %condensed_format = ( '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? @@ -3427,6 +3651,7 @@ sub _condensed_description_generator { 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 = '\\\\'; @@ -3435,6 +3660,7 @@ sub _condensed_description_generator { sub { my ($d,$a,$s,$w) = @_; return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}"; }; + $money_char = '\\dollar'; }elsif ( $format eq 'html' ) { $prefix = '">'; $suffix = ''; @@ -3443,16 +3669,22 @@ sub _condensed_description_generator { sub { my ($d,$a,$s,$w) = @_; return qq!$d!; }; + #$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; @@ -3697,6 +3929,9 @@ sub _items_svc_phone_sections { 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; @@ -3745,6 +3980,7 @@ sub _items_svc_phone_sections { 'duration' => 0, 'sort_weight' => $usage_class{$detail->classnum}->weight, 'phonenum' => $phonenum, + 'header' => [ @header ], }; $sections{"$phonenum $line"}{amount} += $amount; #subtotal $sections{"$phonenum $line"}{calls}++; @@ -3775,11 +4011,17 @@ sub _items_svc_phone_sections { 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}, @@ -3790,7 +4032,7 @@ sub _items_svc_phone_sections { '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 @@ -3899,12 +4141,12 @@ sub _items_pkg { } 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 { @@ -4341,8 +4583,10 @@ Returns an SQL fragment to retreive the amount owed (charged minus credited and =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 @@ -4352,8 +4596,8 @@ Returns an SQL fragment to retreive the net amount (charged minus credited). =cut sub net_sql { - my $class = shift; - 'charged - '. $class->credited_sql; + my ($class, $start, $end) = @_; + 'charged - '. $class->credited_sql($start, $end); } =item paid_sql @@ -4363,9 +4607,13 @@ Returns an SQL fragment to retreive the amount paid against this invoice. =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 @@ -4375,9 +4623,13 @@ Returns an SQL fragment to retreive the amount credited against this invoice. =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