- 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;
-
+ my @sections;
+ foreach my $post_total (0,1) {
+ my @these;
+ my $s = $post_total ? \%late_subtotal : \%subtotal;
+ foreach my $locationnum (keys %$s) {
+ foreach my $sectionname (keys %{ $s->{$locationnum} }) {
+ my $section = {
+ 'subtotal' => $s->{$locationnum}{$sectionname},
+ 'post_total' => $post_total,
+ 'sort_weight' => 0,
+ };
+ if ( $locationnum ) {
+ $section->{'locationnum'} = $locationnum;
+ my $location = FS::cust_location->by_key($locationnum);
+ $section->{'description'} = &{ $escape }($location->location_label);
+ # Better ideas? This will roughly group them by proximity,
+ # which alpha sorting on any of the address fields won't.
+ # Sorting by locationnum is meaningless.
+ # We have to sort on _something_ or the order may change
+ # randomly from one invoice to the next, which will confuse
+ # people.
+ $section->{'sort_weight'} = sprintf('%012s',$location->zip) .
+ $locationnum;
+ $section->{'location'} = {
+ map { $_ => &{ $escape }($location->get($_)) }
+ $location->fields
+ };
+ } else {
+ $section->{'category'} = $sectionname;
+ $section->{'description'} = &{ $escape }($sectionname);
+ if ( _pkg_category($_) ) {
+ $section->{'sort_weight'} = _pkg_category($_)->weight;
+ if ( _pkg_category($_)->condense ) {
+ $section = { %$section, $self->_condense_section($opt{format}) };
+ }
+ }
+ }
+ if ( !$post_total and !$not_tax{$locationnum}{$sectionname} ) {
+ # then it's a tax-only section
+ $section->{'summarized'} = 'Y';
+ $section->{'tax_section'} = 'Y';
+ }
+ push @these, $section;
+ } # foreach $sectionname
+ } #foreach $locationnum
+ push @these, @extra_sections if $post_total == 0;
+ # need an alpha sort for location sections, because postal codes can
+ # be non-numeric
+ $sections[ $post_total ] = [ sort {
+ $opt{'by_location'} ?
+ ($a->{sort_weight} cmp $b->{sort_weight}) :
+ ($a->{sort_weight} <=> $b->{sort_weight})
+ } @these ];
+ } #foreach $post_total
+
+ return @sections; # early, late