+ }
+
+ %pkg_category_cache = ();
+
+ push @$late, map { { 'description' => &{$escape}($_),
+ 'subtotal' => $late_subtotal{$_},
+ 'post_total' => 1,
+ 'sort_weight' => ( _pkg_category($_)
+ ? _pkg_category($_)->weight
+ : 0
+ ),
+ ((_pkg_category($_) && _pkg_category($_)->condense)
+ ? $self->_condense_section($format)
+ : ()
+ ),
+ } }
+ sort _sectionsort keys %late_subtotal;
+
+ my @sections;
+ if ( $summarypage ) {
+ @sections = grep { exists($subtotal{$_}) || ! _pkg_category($_)->disabled }
+ map { $_->categoryname } qsearch('pkg_category', {});
+ push @sections, '' if exists($subtotal{''});
+ } else {
+ @sections = keys %subtotal;
+ }
+
+ my @early = map { { 'description' => &{$escape}($_),
+ 'subtotal' => $subtotal{$_},
+ 'summarized' => $not_tax{$_} ? '' : 'Y',
+ 'tax_section' => $not_tax{$_} ? '' : 'Y',
+ 'sort_weight' => ( _pkg_category($_)
+ ? _pkg_category($_)->weight
+ : 0
+ ),
+ ((_pkg_category($_) && _pkg_category($_)->condense)
+ ? $self->_condense_section($format)
+ : ()
+ ),
+ }
+ } @sections;
+ push @early, @$extra_sections if $extra_sections;
+
+ sort { $a->{sort_weight} <=> $b->{sort_weight} } @early;
+
+}
+
+#helper subs for above
+
+sub _sectionsort {
+ _pkg_category($a)->weight <=> _pkg_category($b)->weight;
+}
+
+sub _pkg_category {
+ my $categoryname = shift;
+ $pkg_category_cache{$categoryname} ||=
+ qsearchs( 'pkg_category', { 'categoryname' => $categoryname } );
+}
+
+my %condensed_format = (
+ 'label' => [ qw( Description Qty Amount ) ],
+ 'fields' => [
+ sub { shift->{description} },
+ sub { shift->{quantity} },
+ sub { my($href, %opt) = @_;
+ ($opt{dollar} || ''). $href->{amount};
+ },
+ ],
+ 'align' => [ qw( l r r ) ],
+ 'span' => [ qw( 5 1 1 ) ], # unitprices?
+ 'width' => [ qw( 10.7cm 1.4cm 1.6cm ) ], # don't like this
+);
+
+sub _condense_section {
+ my ( $self, $format ) = ( shift, shift );
+ ( 'condensed' => 1,
+ map { my $method = "_condensed_$_"; $_ => $self->$method($format) }
+ qw( description_generator
+ header_generator
+ total_generator
+ total_line_generator
+ )
+ );
+}
+
+sub _condensed_generator_defaults {
+ my ( $self, $format ) = ( shift, shift );
+ return ( \%condensed_format, ' ', ' ', ' ', sub { shift } );
+}
+
+my %html_align = (
+ 'c' => 'center',
+ 'l' => 'left',
+ 'r' => 'right',
+);
+
+sub _condensed_header_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ _condensed_generator_defaults($format);
+
+ if ($format eq 'latex') {
+ $prefix = "\\hline\n\\rule{0pt}{2.5ex}\n\\makebox[1.4cm]{}&\n";
+ $suffix = "\\\\\n\\hline";
+ $separator = "&\n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{\\textbf{$d}}}";
+ };
+ } elsif ( $format eq 'html' ) {
+ $prefix = '<th></th>';
+ $suffix = '';
+ $separator = '';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<th align="$html_align{$a}">$d</th>!;
+ };
+ }
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( map { $f->{$_}->[$i] } qw(label align span width) );
+ }
+
+ $prefix. join($separator, @result). $suffix;
+ };
+
+}
+
+sub _condensed_description_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ 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 = '\\\\';
+ $separator = " & \n";
+ $column =
+ 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 = '';
+ $separator = '';
+ $column =
+ 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 $href = shift;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ 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;
+ };
+
+}
+
+sub _condensed_total_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ _condensed_generator_defaults($format);
+ my $style = '';
+
+ if ($format eq 'latex') {
+ $prefix = "& ";
+ $suffix = "\\\\\n";
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '';
+ $suffix = '';
+ $separator = '';
+ $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
+ };
+ }
+
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ # my $r = &{$f->{fields}->[$i]}(@args);
+ # $r .= ' Total' unless $i;
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( &{$f->{fields}->[$i]}(@args). ($i ? '' : ' Total'),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+=item total_line_generator FORMAT
+
+Returns a coderef used for generation of invoice total line items for this
+usage_class. FORMAT is either html or latex
+
+=cut
+
+# should not be used: will have issues with hash element names (description vs
+# total_item and amount vs total_amount -- another array of functions?
+
+sub _condensed_total_line_generator {
+ my ( $self, $format ) = ( shift, shift );
+
+ my ( $f, $prefix, $suffix, $separator, $column ) =
+ _condensed_generator_defaults($format);
+ my $style = '';
+
+ if ($format eq 'latex') {
+ $prefix = "& ";
+ $suffix = "\\\\\n";
+ $separator = " & \n";
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return "\\multicolumn{$s}{$a}{\\makebox[$w][$a]{$d}}";
+ };
+ }elsif ( $format eq 'html' ) {
+ $prefix = '';
+ $suffix = '';
+ $separator = '';
+ $style = 'border-top: 3px solid #000000;border-bottom: 3px solid #000000;';
+ $column =
+ sub { my ($d,$a,$s,$w) = @_;
+ return qq!<td align="$html_align{$a}" style="$style">$d</td>!;
+ };
+ }
+
+
+ sub {
+ my @args = @_;
+ my @result = ();
+
+ foreach (my $i = 0; $f->{label}->[$i]; $i++) {
+ push @result,
+ &{$column}( &{$f->{fields}->[$i]}(@args),
+ map { $f->{$_}->[$i] } qw(align span width)
+ );
+ }
+
+ $prefix. join( $separator, @result ). $suffix;
+ };
+
+}
+
+#sub _items_extra_usage_sections {
+# my $self = shift;
+# my $escape = shift;
+#
+# my %sections = ();
+#
+# my %usage_class = map{ $_->classname, $_ } qsearch('usage_class', {});
+# foreach my $cust_bill_pkg ( $self->cust_bill_pkg )
+# {
+# next unless $cust_bill_pkg->pkgnum > 0;
+#
+# foreach my $section ( keys %usage_class ) {
+#
+# my $usage = $cust_bill_pkg->usage($section);
+#
+# next unless $usage && $usage > 0;
+#
+# $sections{$section} ||= 0;
+# $sections{$section} += $usage;
+#
+# }
+#
+# }
+#
+# map { { 'description' => &{$escape}($_),
+# 'subtotal' => $sections{$_},
+# 'summarized' => '',
+# 'tax_section' => '',
+# }
+# }
+# sort {$usage_class{$a}->weight <=> $usage_class{$b}->weight} keys %sections;
+#
+#}
+
+sub _items_extra_usage_sections {
+ my $self = shift;
+ my $escape = shift;
+ my $format = shift;
+
+ my %sections = ();
+ my %classnums = ();
+ my %lines = ();
+
+ my %usage_class = map { $_->classnum => $_ } qsearch( 'usage_class', {} );
+ foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+ next unless $cust_bill_pkg->pkgnum > 0;
+
+ foreach my $classnum ( keys %usage_class ) {
+ my $section = $usage_class{$classnum}->classname;
+ $classnums{$section} = $classnum;
+
+ foreach my $detail ( $cust_bill_pkg->cust_bill_pkg_detail($classnum) ) {
+ my $amount = $detail->amount;
+ next unless $amount && $amount > 0;
+
+ $sections{$section} ||= { 'subtotal'=>0, 'calls'=>0, 'duration'=>0 };
+ $sections{$section}{amount} += $amount; #subtotal
+ $sections{$section}{calls}++;
+ $sections{$section}{duration} += $detail->duration;
+
+ my $desc = $detail->regionname;
+ my $description = $desc;
+ $description = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($desc) > 50;
+
+ $lines{$section}{$desc} ||= {
+ description => &{$escape}($description),
+ #pkgpart => $part_pkg->pkgpart,
+ pkgnum => $cust_bill_pkg->pkgnum,
+ ref => '',
+ amount => 0,
+ calls => 0,
+ duration => 0,
+ #unit_amount => $cust_bill_pkg->unitrecur,
+ quantity => $cust_bill_pkg->quantity,
+ product_code => 'N/A',
+ ext_description => [],
+ };
+
+ $lines{$section}{$desc}{amount} += $amount;
+ $lines{$section}{$desc}{calls}++;
+ $lines{$section}{$desc}{duration} += $detail->duration;
+
+ }
+ }
+ }
+
+ my %sectionmap = ();
+ foreach (keys %sections) {
+ my $usage_class = $usage_class{$classnums{$_}};
+ $sectionmap{$_} = { 'description' => &{$escape}($_),
+ 'amount' => $sections{$_}{amount}, #subtotal
+ 'calls' => $sections{$_}{calls},
+ 'duration' => $sections{$_}{duration},
+ 'summarized' => '',
+ 'tax_section' => '',
+ 'sort_weight' => $usage_class->weight,
+ ( $usage_class->format
+ ? ( map { $_ => $usage_class->$_($format) }
+ qw( description_generator header_generator total_generator total_line_generator )
+ )
+ : ()
+ ),
+ };
+ }
+
+ my @sections = sort { $a->{sort_weight} <=> $b->{sort_weight} }
+ values %sectionmap;
+
+ my @lines = ();
+ foreach my $section ( keys %lines ) {
+ foreach my $line ( keys %{$lines{$section}} ) {
+ my $l = $lines{$section}{$line};
+ $l->{section} = $sectionmap{$section};
+ $l->{amount} = sprintf( "%.2f", $l->{amount} );
+ #$l->{unit_amount} = sprintf( "%.2f", $l->{unit_amount} );
+ push @lines, $l;