X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FTemplate_Mixin.pm;h=bfa03bcdbecbe2559f35b8746c893f44ac5485bf;hb=c2f7d8ba623194ad1fae37b231b2e29b33d05674;hp=00151ae2643b438be39f734fcf960c7a49faff70;hpb=4e082c20bcb6754f6f832cfc95d45198a2b920f5;p=freeside.git diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 00151ae26..bfa03bcdb 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -485,9 +485,14 @@ sub print_generic { 'quotationnum' => $self->quotationnum, 'no_date' => $params{'no_date'}, '_date' => ( $params{'no_date'} ? '' : $self->_date ), + # workaround for inconsistent behavior in the early plain text + # templates; see RT#28271 'date' => ( $params{'no_date'} ? '' - : $self->time2str_local('long', $self->_date, $format) + : ($format eq 'template' + ? $self->_date + : $self->time2str_local('long', $self->_date, $format) + ) ), 'today' => $self->time2str_local('long', $today, $format), 'terms' => $self->terms, @@ -936,7 +941,7 @@ sub print_generic { my $detail = { ref => $line_item->{'pkgnum'}, pkgpart => $line_item->{'pkgpart'}, - quantity => 1, + #quantity => 1, # not really correct section => $previous_section, # which might be $default_section description => &$escape_function($line_item->{'description'}), ext_description => [ map { &$escape_function($_) } @@ -1017,7 +1022,8 @@ sub print_generic { warn "$me searching for line items\n" if $DEBUG > 1; - foreach my $line_item ( $self->_items_pkg(%options) ) { + foreach my $line_item ( $self->_items_pkg(%options), + $self->_items_fee(%options) ) { warn "$me adding line item $line_item\n" if $DEBUG > 1; @@ -1045,6 +1051,7 @@ sub print_generic { $detail->{'edate'} = $line_item->{'edate'}; $detail->{'seconds'} = $line_item->{'seconds'}; $detail->{'svc_label'} = $line_item->{'svc_label'}; + $detail->{'usage_item'} = $line_item->{'usage_item'}; push @detail_items, $detail; push @buf, ( [ $detail->{'description'}, @@ -1384,6 +1391,37 @@ sub print_generic { } $invoice_data{summary_subtotals} = \@summary_subtotals; + # usage subtotals + if ( $conf->exists('usage_class_summary') + and $self->can('_items_usage_class_summary') ) { + my @usage_subtotals = $self->_items_usage_class_summary(escape => $escape_function); + if ( @usage_subtotals ) { + unshift @sections, $usage_subtotals[0]->{section}; + unshift @detail_items, @usage_subtotals; + } + } + + # invoice history "section" (not really a section) + # not to be included in any subtotals, completely independent of + # everything... + if ( $conf->exists('previous_invoice_history') ) { + my %history; + my %monthorder; + foreach my $cust_bill ( $cust_main->cust_bill ) { + # XXX hardcoded format, and currently only 'charged'; add other fields + # if they become necessary + my $date = $self->time2str_local('%b %Y', $cust_bill->_date); + $history{$date} ||= 0; + $history{$date} += $cust_bill->charged; + # just so we have a numeric sort key + $monthorder{$date} ||= $cust_bill->_date; + } + my @sorted_months = sort { $monthorder{$a} <=> $monthorder{$b} } + keys %history; + my @sorted_amounts = map { sprintf('%.2f', $history{$_}) } @sorted_months; + $invoice_data{monthly_history} = [ \@sorted_months, \@sorted_amounts ]; + } + # debugging hook: call this with 'diag' => 1 to just get a hash of # the invoice variables return \%invoice_data if ( $params{'diag'} ); @@ -1860,10 +1898,13 @@ sub _items_sections { my $section = $display->section; my $type = $display->type; - $section = undef unless $opt{by_category}; + # Set $section = undef if we're sectioning by location and this + # line item _has_ a location (i.e. isn't a fee). + $section = undef if $locationnum; + # set this flag if the section is not tax-only $not_tax{$locationnum}{$section} = 1 - unless $cust_bill_pkg->pkgnum == 0; + if $cust_bill_pkg->pkgnum or $cust_bill_pkg->feepart; # there's actually a very important piece of logic buried in here: # incrementing $late_subtotal{$section} CREATES @@ -1899,7 +1940,9 @@ sub _items_sections { } else { # it's a pre-total (normal) section # skip tax items unless they're explicitly included in a section - next if $cust_bill_pkg->pkgnum == 0 && ! $section; + next if $cust_bill_pkg->pkgnum == 0 and + ! $cust_bill_pkg->feepart and + ! $section; if (! $type || $type eq 'S') { $subtotal{$locationnum}{$section} += $cust_bill_pkg->setup @@ -2255,6 +2298,58 @@ sub _items_nontax { grep { $_->pkgnum } $self->cust_bill_pkg; } +sub _items_fee { + my $self = shift; + my %options = @_; + my @cust_bill_pkg = grep { $_->feepart } $self->cust_bill_pkg; + my @items; + foreach my $cust_bill_pkg (@cust_bill_pkg) { + # cache this, so we don't look it up again in every section + my $part_fee = $cust_bill_pkg->get('part_fee') + || $cust_bill_pkg->part_fee; + $cust_bill_pkg->set('part_fee', $part_fee); + if (!$part_fee) { + #die "fee definition not found for line item #".$cust_bill_pkg->billpkgnum."\n"; # might make more sense + warn "fee definition not found for line item #".$cust_bill_pkg->billpkgnum."\n"; + next; + } + if ( exists($options{section}) and exists($options{section}{category}) ) + { + my $categoryname = $options{section}{category}; + # then filter for items that have that section + if ( $part_fee->categoryname ne $categoryname ) { + warn "skipping fee '".$part_fee->itemdesc."'--not in section $categoryname\n" if $DEBUG; + next; + } + } # otherwise include them all in the main section + # XXX what to do when sectioning by location? + + my @ext_desc; + my %base_invnums; # invnum => invoice date + foreach ($cust_bill_pkg->cust_bill_pkg_fee) { + if ($_->base_invnum) { + my $base_bill = FS::cust_bill->by_key($_->base_invnum); + my $base_date = $self->time2str_local('short', $base_bill->_date) + if $base_bill; + $base_invnums{$_->base_invnum} = $base_date || ''; + } + } + foreach (sort keys(%base_invnums)) { + next if $_ == $self->invnum; + push @ext_desc, + $self->mt('from invoice \\#[_1] on [_2]', $_, $base_invnums{$_}); + } + push @items, + { feepart => $cust_bill_pkg->feepart, + amount => sprintf('%.2f', $cust_bill_pkg->setup + $cust_bill_pkg->recur), + description => $part_fee->itemdesc_locale($self->cust_main->locale), + ext_description => \@ext_desc + # sdate/edate? + }; + } + @items; +} + sub _items_pkg { my $self = shift; my %options = @_; @@ -2310,7 +2405,8 @@ sub _taxsort { sub _items_tax { my $self = shift; - my @cust_bill_pkg = sort _taxsort grep { ! $_->pkgnum } $self->cust_bill_pkg; + my @cust_bill_pkg = sort _taxsort grep { ! $_->pkgnum and ! $_->feepart } + $self->cust_bill_pkg; my @items = $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_); if ( $self->conf->exists('always_show_tax') ) { @@ -2401,7 +2497,7 @@ sub _items_cust_bill_pkg { if ( $locationnum ) { # this is a location section; skip packages that aren't at this # service location. - next if $cust_bill_pkg->pkgnum == 0; + next if $cust_bill_pkg->pkgnum == 0; # skips fees... next if $self->cust_pkg_hash->{ $cust_bill_pkg->pkgnum }->locationnum != $locationnum; } @@ -2430,6 +2526,9 @@ sub _items_cust_bill_pkg { @cust_bill_pkg_display = grep { !$_->summary } @cust_bill_pkg_display; } + + my $classname = ''; # package class name, will fill in later + foreach my $display (@cust_bill_pkg_display) { warn "$me _items_cust_bill_pkg considering cust_bill_pkg_display ". @@ -2452,6 +2551,8 @@ sub _items_cust_bill_pkg { warn "$me _items_cust_bill_pkg cust_bill_pkg is quotation_pkg\n" if $DEBUG > 1; + # quotation_pkgs are never fees, so don't worry about the case where + # part_pkg is undefined if ( $cust_bill_pkg->setup != 0 ) { my $description = $desc; @@ -2471,7 +2572,7 @@ sub _items_cust_bill_pkg { }; } - } elsif ( $cust_bill_pkg->pkgnum > 0 ) { + } elsif ( $cust_bill_pkg->pkgnum > 0 ) { # and it's not a quotation_pkg warn "$me _items_cust_bill_pkg cust_bill_pkg is non-tax\n" if $DEBUG > 1; @@ -2488,6 +2589,9 @@ sub _items_cust_bill_pkg { %item_dates = map { $_ => $cust_bill_pkg->$_ } ('sdate', 'edate') unless $part_pkg->option('disable_line_item_date_ranges',1); + # not normally used, but pass this to the template anyway + $classname = $part_pkg->classname; + if ( (!$type || $type eq 'S') && ( $cust_bill_pkg->setup != 0 || $cust_bill_pkg->setup_show_zero @@ -2514,16 +2618,20 @@ sub _items_cust_bill_pkg { my @d = (); my $svc_label; + + # always pass the svc_label through to the template, even if + # not displaying it as an ext_description + my @svc_labels = map &{$escape_function}($_), + $cust_pkg->h_labels_short($self->_date, undef, 'I'); + + $svc_label = $svc_labels[0]; + unless ( $cust_pkg->part_pkg->hide_svc_detail || $cust_bill_pkg->hidden ) { - my @svc_labels = map &{$escape_function}($_), - $cust_pkg->h_labels_short($self->_date, undef, 'I'); push @d, @svc_labels unless $cust_bill_pkg->pkgpart_override; #don't redisplay services - $svc_label = $svc_labels[0]; - my $lnum = $cust_main ? $cust_main->ship_locationnum : $self->prospect_main->locationnum; # show the location label if it's not the customer's default @@ -2576,7 +2684,8 @@ sub _items_cust_bill_pkg { my $is_summary = $display->summary; my $description = $desc; - if ( $type eq 'U' and ($is_summary or $cust_bill_pkg->hidden) ) { + if ( $type eq 'U' and defined($r) ) { + # don't just show the same description as the recur line $description = $self->mt('Usage charges'); } @@ -2596,22 +2705,31 @@ sub _items_cust_bill_pkg { push @dates, $prev->sdate if $prev; push @dates, undef if !$prev; + my @svc_labels = map &{$escape_function}($_), + $cust_pkg->h_labels_short(@dates, 'I'); + $svc_label = $svc_labels[0]; + + # show service labels, unless... + # the package is set not to display them unless ( $part_pkg->hide_svc_detail + # or this is a tax-like line item || $cust_bill_pkg->itemdesc + # or this is a hidden (bundled) line item || $cust_bill_pkg->hidden + # or this is a usage summary line || $is_summary && $type && $type eq 'U' + # or this is a usage line and there's a recurring line + # for the package in the same section (which will + # have service labels already) + || ($type eq 'U' and defined($r)) ) { warn "$me _items_cust_bill_pkg adding service details\n" if $DEBUG > 1; - my @svc_labels = map &{$escape_function}($_), - $cust_pkg->h_labels_short(@dates, 'I'); push @d, @svc_labels unless $cust_bill_pkg->pkgpart_override; #don't redisplay services - $svc_label = $svc_labels[0]; - warn "$me _items_cust_bill_pkg done adding service details\n" if $DEBUG > 1; @@ -2647,7 +2765,7 @@ sub _items_cust_bill_pkg { } } #if svc_acct-usage_seconds - } + } # if we are showing service labels unless ( $is_summary ) { warn "$me _items_cust_bill_pkg adding details\n" @@ -2673,15 +2791,15 @@ sub _items_cust_bill_pkg { $amount = $cust_bill_pkg->usage; } - my $unit_amount = - ( $cust_bill_pkg->unitrecur > 0 ) ? $cust_bill_pkg->unitrecur - : $amount; - if ( !$type || $type eq 'R' ) { warn "$me _items_cust_bill_pkg adding recur\n" if $DEBUG > 1; + my $unit_amount = + ( $cust_bill_pkg->unitrecur > 0 ) ? $cust_bill_pkg->unitrecur + : $amount; + if ( $cust_bill_pkg->hidden ) { $r->{amount} += $amount; $r->{unit_amount} += $unit_amount; @@ -2712,7 +2830,6 @@ sub _items_cust_bill_pkg { # line for the bundle, add this package's total amount and # usage details to it $u->{amount} += $amount; - $u->{unit_amount} += $unit_amount, push @{ $u->{ext_description} }, @d; } elsif ( $amount ) { # create a new usage line @@ -2721,9 +2838,8 @@ sub _items_cust_bill_pkg { pkgpart => $pkgpart, pkgnum => $cust_bill_pkg->pkgnum, amount => $amount, + usage_item => 1, recur_show_zero => $cust_bill_pkg->recur_show_zero, - unit_amount => $unit_amount, - quantity => $cust_bill_pkg->quantity, %item_dates, ext_description => \@d, }; @@ -2732,29 +2848,21 @@ sub _items_cust_bill_pkg { } # recurring or usage with recurring charge - } else { #pkgnum tax or one-shot line item (??) + } else { # taxes and fees warn "$me _items_cust_bill_pkg cust_bill_pkg is tax\n" if $DEBUG > 1; - if ( $cust_bill_pkg->setup != 0 ) { - push @b, { - 'description' => $desc, - 'amount' => sprintf("%.2f", $cust_bill_pkg->setup), - }; - } - if ( $cust_bill_pkg->recur != 0 ) { - push @b, { - 'description' => "$desc (". - $self->time2str_local('short', $cust_bill_pkg->sdate). ' - '. - $self->time2str_local('short', $cust_bill_pkg->edate). ')', - 'amount' => sprintf("%.2f", $cust_bill_pkg->recur), - }; - } + # items of this kind should normally not have sdate/edate. + push @b, { + 'description' => $desc, + 'amount' => sprintf('%.2f', $cust_bill_pkg->setup + + $cust_bill_pkg->recur) + }; - } + } # if quotation / package line item / other line item - } + } # foreach $display $discount_show_always = ($cust_bill_pkg->cust_bill_pkg_discount && $conf->exists('discount-show-always')); @@ -2764,8 +2872,11 @@ sub _items_cust_bill_pkg { foreach ( $s, $r, ($opt{skip_usage} ? () : $u ) ) { if ( $_ ) { $_->{amount} = sprintf( "%.2f", $_->{amount} ), + if exists($_->{amount}); $_->{amount} =~ s/^\-0\.00$/0.00/; - $_->{unit_amount} = sprintf( "%.2f", $_->{unit_amount} ), + $_->{unit_amount} = sprintf('%.2f', $_->{unit_amount}) + if exists($_->{unit_amount}); + push @b, { %$_ } if $_->{amount} != 0 || $discount_show_always