-
- my @cust_bill_pkg = ();
- my @cust_bill_pkg_bundle = ();
- my $sum = 0;
- my $discount_show_always = 0;
-
- foreach my $cust_bill_pkg ( @_ ) {
- $discount_show_always = ($cust_bill_pkg->get('discounts')
- && scalar(@{$cust_bill_pkg->get('discounts')})
- && $conf->exists('discount-show-always'));
- if (scalar(@cust_bill_pkg_bundle) && !$cust_bill_pkg->pkgpart_override) {
- push @cust_bill_pkg, @cust_bill_pkg_bundle
- if ($sum > 0 || ($sum == 0 && $discount_show_always));
- @cust_bill_pkg_bundle = ();
- $sum = 0;
- }
- $sum += $cust_bill_pkg->setup + $cust_bill_pkg->recur;
- push @cust_bill_pkg_bundle, $cust_bill_pkg;
- }
- push @cust_bill_pkg, @cust_bill_pkg_bundle
- if ($sum > 0 || ($sum == 0 && $discount_show_always));
-
- (@cust_bill_pkg);
-
-}
-
-=item calculate_taxes LINEITEMREF TAXHASHREF INVOICE_TIME
-
-This is a weird one. Perhaps it should not even be exposed.
-
-Generates tax line items (see L<FS::cust_bill_pkg>) for this customer.
-Usually used internally by bill method B<bill>.
-
-If there is an error, returns the error, otherwise returns reference to a
-list of line items suitable for insertion.
-
-=over 4
-
-=item LINEITEMREF
-
-An array ref of the line items being billed.
-
-=item TAXHASHREF
-
-A strange beast. The keys to this hash are internal identifiers consisting
-of the name of the tax object type, a space, and its unique identifier ( e.g.
- 'cust_main_county 23' ). The values of the hash are listrefs. The first
-item in the list is the tax object. The remaining items are either line
-items or floating point values (currency amounts).
-
-The taxes are calculated on this entity. Calculated exemption records are
-transferred to the LINEITEMREF items on the assumption that they are related.
-
-Read the source.
-
-=item INVOICE_TIME
-
-This specifies the date appearing on the associated invoice. Some
-jurisdictions (i.e. Texas) have tax exemptions which are date sensitive.
-
-=back
-
-=cut
-
-sub calculate_taxes {
- my ($self, $cust_bill_pkg, $taxlisthash, $invoice_time) = @_;
-
- local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
-
- warn "$me calculate_taxes\n"
- #.Dumper($self, $cust_bill_pkg, $taxlisthash, $invoice_time). "\n"
- if $DEBUG > 2;
-
- my @tax_line_items = ();
-
- # keys are tax names (as printed on invoices / itemdesc )
- # values are listrefs of taxlisthash keys (internal identifiers)
- my %taxname = ();
-
- # keys are taxlisthash keys (internal identifiers)
- # values are (cumulative) amounts
- my %tax = ();
-
- # keys are taxlisthash keys (internal identifiers)
- # values are listrefs of cust_bill_pkg_tax_location hashrefs
- my %tax_location = ();
-
- # keys are taxlisthash keys (internal identifiers)
- # values are listrefs of cust_bill_pkg_tax_rate_location hashrefs
- my %tax_rate_location = ();
-
- foreach my $tax ( keys %$taxlisthash ) {
- my $tax_object = shift @{ $taxlisthash->{$tax} };
- warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
- warn " ". join('/', @{ $taxlisthash->{$tax} } ). "\n" if $DEBUG > 2;
- my $hashref_or_error =
- $tax_object->taxline( $taxlisthash->{$tax},
- 'custnum' => $self->custnum,
- 'invoice_time' => $invoice_time
- );
- return $hashref_or_error unless ref($hashref_or_error);
-
- unshift @{ $taxlisthash->{$tax} }, $tax_object;
-
- my $name = $hashref_or_error->{'name'};
- my $amount = $hashref_or_error->{'amount'};
-
- #warn "adding $amount as $name\n";
- $taxname{ $name } ||= [];
- push @{ $taxname{ $name } }, $tax;
-
- $tax{ $tax } += $amount;
-
- $tax_location{ $tax } ||= [];
- if ( $tax_object->get('pkgnum') || $tax_object->get('locationnum') ) {
- push @{ $tax_location{ $tax } },
- {
- 'taxnum' => $tax_object->taxnum,
- 'taxtype' => ref($tax_object),
- 'pkgnum' => $tax_object->get('pkgnum'),
- 'locationnum' => $tax_object->get('locationnum'),
- 'amount' => sprintf('%.2f', $amount ),
- };
+ my @in = @_;
+
+ my @out = ();
+ my @bundle = ();
+ my $discount_show_always = $conf->exists('discount-show-always');
+ my $show_this = 0;
+
+ # Sort @in the same way we do during invoice rendering, so we can identify
+ # bundles. See FS::Template_Mixin::_items_nontax.
+ @in = sort { $a->pkgnum <=> $b->pkgnum or
+ $a->sdate <=> $b->sdate or
+ ($a->pkgpart_override ? 0 : -1) or
+ ($b->pkgpart_override ? 0 : 1) or
+ $b->hidden cmp $a->hidden or
+ $a->pkgpart_override <=> $b->pkgpart_override
+ } @in;
+
+ # this is a pack-and-deliver pattern. every time there's a cust_bill_pkg
+ # _without_ pkgpart_override, that's the start of the new bundle. if there's
+ # an existing bundle, and it contains a nonzero amount (or a zero amount
+ # that's displayable anyway), push all line items in the bundle.
+ foreach my $cust_bill_pkg ( @in ) {
+
+ if (scalar(@bundle) and !$cust_bill_pkg->pkgpart_override) {
+ # ship out this bundle and reset it
+ if ( $show_this ) {
+ push @out, @bundle;
+ }
+ @bundle = ();
+ $show_this = 0;