X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FTemplate_Mixin.pm;h=99019e91e588ed3401c56a14642eec4b236dead2;hb=0af65f4c957f0f658d9a539effd5d61375f6a0b9;hp=c8b419d1c5222e8e731e283bfc2bb92e43f12266;hpb=861c90163e1b7c564ef7905d657b3cf16e7a328d;p=freeside.git diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index c8b419d1c..99019e91e 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -19,7 +19,7 @@ use HTML::Entities; use Cwd; use FS::UID; use FS::Misc qw( send_email ); -use FS::Record qw( qsearch qsearchs ); +use FS::Record qw( qsearch qsearchs dbh ); use FS::Conf; use FS::Misc qw( generate_ps generate_pdf ); use FS::pkg_category; @@ -936,9 +936,10 @@ sub print_generic { if $DEBUG > 1; my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y'; - my $multisection = $conf->exists($tc.'sections', $cust_main->agentnum) || - $conf->exists($tc.'sections_by_location', $cust_main->agentnum); + my $multisection = $self->has_sections; $invoice_data{'multisection'} = $multisection; + my $section_with_taxes = 1 + if $conf->exists('invoice_sections_with_taxes'); my $late_sections; my $extra_sections = []; my $extra_lines = (); @@ -1156,14 +1157,27 @@ sub print_generic { if ( $invoice_data{finance_section} && $section->{'description'} eq $invoice_data{finance_section} ); - $section->{'subtotal'} = $other_money_char. - sprintf('%.2f', $section->{'subtotal'}) - if $multisection; + if ( $multisection ) { + + if ( ref($section->{'subtotal'}) ) { + + $section->{'subtotal'} = + sprintf("$other_money_char%.2f to $other_money_char%.2f", + $section->{'subtotal'}[0], + $section->{'subtotal'}[1] + ); + + } else { + + $section->{'subtotal'} = $other_money_char. + sprintf('%.2f', $section->{'subtotal'}) + + } - # continue some normalization - $section->{'amount'} = $section->{'subtotal'} - if $multisection; + # continue some normalization + $section->{'amount'} = $section->{'subtotal'} + } if ( $section->{'description'} ) { push @buf, ( [ &$escape_function($section->{'description'}), '' ], @@ -1188,6 +1202,9 @@ sub print_generic { warn "$me searching for line items\n" if $DEBUG > 1; + my %section_tax_lines; + my %seen_tax_lines; + foreach my $line_item ( $self->_items_pkg(%options), $self->_items_fee(%options) ) { @@ -1211,10 +1228,55 @@ sub print_generic { $line_item->{'unit_amount'} = $money_char.$line_item->{'unit_amount'}; } $line_item->{'ext_description'} ||= []; - + + if ( $section_with_taxes && ref $line_item->{pkg_tax} ) { + for my $line_tax ( @{$ line_item->{pkg_tax} } ) { + + # It is rarely possible for the same tax record to be presented here + # multiple times. See cust_bill_pkg::_pkg_tax_list for more info + next if $seen_tax_lines{ $line_tax->{billpkgtaxlocationnum} }; + $seen_tax_lines{ $line_tax->{billpkgtaxlocationnum} } = 1; + + $section_tax_lines{ $line_tax->{taxname} } += $line_tax->{amount}; + } + } + push @detail_items, $line_item; } + # If conf flag invoice_sections_with_taxes: + # - Add @detail_items for taxes into each section + # - Update section subtotal to include taxes + if ( $section_with_taxes && %section_tax_lines ) { + for my $taxname ( keys %section_tax_lines ) { + + push @detail_items, { + section => $section, + amount => sprintf($money_char."%.2f",$section_tax_lines{$taxname}), + description => &$escape_function($taxname), + }; + + # Append taxes to total. If line format resembles "$5.00 to $12.00" + # append to the second value. + if ($section->{subtotal} =~ /to/) { + my @subtotal = split /\s/, $section->{subtotal}; + $subtotal[2] =~ s/[^\d\.]//g; + $subtotal[2] = sprintf( + $money_char."%.2f", + ( $subtotal[2] + $section_tax_lines{$taxname} ) + ); + $section->{subtotal} = join ' ', @subtotal; + } else { + $section->{subtotal} =~ s/[^\d\.]//g; + $section->{subtotal} = sprintf( + $money_char . "%.2f", + ( $section->{subtotal} + $section_tax_lines{$taxname} ) + ); + } + + } + } + if ( $section->{'description'} ) { push @buf, ( ['','-----------'], [ $section->{'description'}. ' sub-total', @@ -1315,11 +1377,19 @@ sub print_generic { $tax_section->{'description'} = $self->mt($tax_description); $tax_section->{'summarized'} = ''; - # append it if it's not already there - if ( !grep $tax_section, @sections ) { + if ( $conf->exists('invoice_sections_with_taxes')) { + + # remove tax section if taxes are itemized within other sections + @sections = grep{ $_ ne $tax_section } @sections; + + } elsif ( !grep $tax_section, @sections ) { + + # append it if it's not already there push @sections, $tax_section; push @summary_subtotals, $tax_section; + } + } } else { @@ -1362,7 +1432,16 @@ sub print_generic { foreach ( @new_total_items ) { my ($item, $amount) = ($_->{'total_item'}, $_->{'total_amount'}); $_->{'total_item'} = &$embolden_function( $item ); + + if ( ref($amount) ) { + $_->{'total_amount'} = &$embolden_function( + $other_money_char.$amount->[0]. ' to '. + $other_money_char.$amount->[1] + ); + } else { $_->{'total_amount'} = &$embolden_function( $other_money_char.$amount ); + } + # but if it's multisection, don't append to @total_items. the adjust # section has all this stuff push @total_items, $_ if !$multisection; @@ -2085,6 +2164,7 @@ Returns an argument list to be passed to L. =cut use MIME::Entity; +use Encode; sub generate_email { @@ -2152,7 +2232,7 @@ sub generate_email { if $DEBUG; # 'print_text' argument is no longer used - @text = $self->print_text(\%args); + @text = map Encode::encode_utf8($_), $self->print_text(\%args); } else { @@ -2247,7 +2327,7 @@ sub generate_email { ' ', ' ', ' ', - $html, + Encode::encode_utf8($html), ' ', '', ], @@ -3050,11 +3130,15 @@ sub _items_fee { my $desc = $part_fee->itemdesc_locale($self->cust_main->locale); # but not escape the base description line + my @pkg_tax = $cust_bill_pkg->_pkg_tax_list + if $self->conf->exists('invoice_sections_with_taxes'); + push @items, { feepart => $cust_bill_pkg->feepart, amount => sprintf('%.2f', $cust_bill_pkg->setup + $cust_bill_pkg->recur), description => $desc, - ext_description => \@ext_desc + pkg_tax => \@pkg_tax, + ext_description => \@ext_desc, # sdate/edate? }; } @@ -3155,6 +3239,8 @@ which does something complicated. preref_callback: coderef run for each line item, code should return HTML to be displayed before that line item (quotations only) +section_with_taxes: Look up and include applied taxes for each record + Returns a list of hashrefs, each of which may contain: pkgnum, description, amount, unit_amount, quantity, pkgpart, _is_setup, and @@ -3290,6 +3376,9 @@ sub _items_cust_bill_pkg { 'no_usage' => $opt{'no_usage'}, ); + my @pkg_tax = $cust_bill_pkg->_pkg_tax_list + if $self->conf->exists('invoice_sections_with_taxes'); + if ( ref($cust_bill_pkg) eq 'FS::quotation_pkg' ) { # XXX this should be pulled out into quotation_pkg @@ -3315,6 +3404,7 @@ sub _items_cust_bill_pkg { 'amount' => sprintf("%.2f", $cust_bill_pkg->setup), 'unit_amount' => sprintf("%.2f", $cust_bill_pkg->unitsetup), 'quantity' => $cust_bill_pkg->quantity, + 'pkg_tax' => \@pkg_tax, 'ext_description' => \@details, 'preref_html' => ( $opt{preref_callback} ? &{ $opt{preref_callback} }( $cust_bill_pkg ) @@ -3330,6 +3420,7 @@ sub _items_cust_bill_pkg { 'amount' => sprintf("%.2f", $cust_bill_pkg->recur), 'unit_amount' => sprintf("%.2f", $cust_bill_pkg->unitrecur), 'quantity' => $cust_bill_pkg->quantity, + 'pkg_tax' => \@pkg_tax, 'ext_description' => \@details, 'preref_html' => ( $opt{preref_callback} ? &{ $opt{preref_callback} }( $cust_bill_pkg ) @@ -3435,6 +3526,7 @@ sub _items_cust_bill_pkg { setup_show_zero => $cust_bill_pkg->setup_show_zero, unit_amount => $cust_bill_pkg->unitsetup, quantity => $cust_bill_pkg->quantity, + pkg_tax => \@pkg_tax, ext_description => \@d, svc_label => ($svc_label || ''), locationnum => $cust_pkg->locationnum, # sure, why not? @@ -3595,6 +3687,7 @@ sub _items_cust_bill_pkg { recur_show_zero => $cust_bill_pkg->recur_show_zero, unit_amount => $unit_amount, quantity => $cust_bill_pkg->quantity, + pkg_tax => \@pkg_tax, %item_dates, ext_description => \@d, svc_label => ($svc_label || ''), @@ -3623,6 +3716,7 @@ sub _items_cust_bill_pkg { amount => $amount, usage_item => 1, recur_show_zero => $cust_bill_pkg->recur_show_zero, + pkg_tax => \@pkg_tax, %item_dates, ext_description => \@d, locationnum => $cust_pkg->locationnum, @@ -3771,4 +3865,66 @@ sub _items_discounts_avail { } +=item has_sections AGENTNUM + +Return true if invoice_sections should be enabled for this bill. + (Inherited by both cust_bill and cust_bill_void) + +Determination: +* False if not an invoice +* True always if conf invoice_sections is enabled +* True always if sections_by_location is enabled +* True if conf invoice_sections_multilocation > 1, + and location_count >= invoice_sections_multilocation +* Else, False + +=cut + +sub has_sections { + my ($self, $agentnum) = @_; + + return 0 unless $self->invnum > 0; + + $agentnum ||= $self->cust_main->agentnum; + return 1 if $self->conf->exists('invoice_sections', $agentnum); + return 1 if $self->conf->exists('sections_by_location', $agentnum); + + my $location_min = $self->conf->config( + 'invoice_sections_multilocation', $agentnum, + ); + + return 1 + if $location_min + && $self->location_count >= $location_min; + + 0; +} + + +=item location_count + +Return the number of locations billed on this invoice + +=cut + +sub location_count { + my ($self) = @_; + return 0 unless $self->invnum; + + # SELECT COUNT( DISTINCT cust_pkg.locationnum ) + # FROM cust_bill_pkg + # LEFT JOIN cust_pkg USING (pkgnum) + # WHERE invnum = 278 + # AND cust_bill_pkg.pkgnum > 0 + + my $result = qsearchs({ + select => 'COUNT(DISTINCT cust_pkg.locationnum) as location_count', + table => 'cust_bill_pkg', + addl_from => 'LEFT JOIN cust_pkg USING (pkgnum)', + extra_sql => 'WHERE invnum = '.dbh->quote( $self->invnum ) + . ' AND cust_bill_pkg.pkgnum > 0' + }); + ref $result ? $result->location_count : 0; +} + 1;