);
# but NOT $conf
use vars qw( $invoice_lines @buf ); #yuck
-use List::Util qw(sum);
+use List::Util qw(sum); #can't import first, it conflicts with cust_main.first
use Date::Format;
use Date::Language;
use Text::Template 1.20;
unless $format =~ /^(latex|html|template)$/;
my $cust_main = $self->cust_main || $self->prospect_main;
- $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
- unless $cust_main->payname
- && $cust_main->payby !~ /^(CARD|DCRD|CHEK|DCHK)$/;
my $locale = $params{'locale'} || $cust_main->locale;
'custnum' => $cust_main->display_custnum,
'prospectnum' => $cust_main->prospectnum,
'agent_custid' => &$escape_function($cust_main->agent_custid),
- ( map { $_ => &$escape_function($cust_main->$_()) } qw(
- payname company address1 address2 city state zip fax
- )),
+ ( map { $_ => &$escape_function($cust_main->$_()) }
+ qw( company address1 address2 city state zip fax )
+ ),
+ 'payname' => &$escape_function( $cust_main->invoice_attn
+ || $cust_main->contact_firstlast ),
#global config
'ship_enable' => $conf->exists('invoice-ship_address'),
my @address = ();
$invoice_data{'address'} = \@address;
push @address,
- $cust_main->payname.
- ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
- ? " (P.O. #". $cust_main->payinfo. ")"
- : ''
+ $invoice_data{'payname'}.
+ ( $cust_main->po_number
+ ? " (P.O. #". $cust_main->po_number. ")"
+ : ''
)
;
push @address, $cust_main->company
# (this is used in the summary & on the payment coupon)
$invoice_data{'balance'} = sprintf("%.2f", $balance_due);
- # info from customer's last invoice before this one, for some
- # summary formats
- $invoice_data{'last_bill'} = {};
+ # flag telling this invoice to have a first-page summary
+ my $summarypage = '';
if ( $self->custnum && $self->invnum ) {
+ # XXX should be an FS::cust_bill method to set the defaults, instead
+ # of checking the type here
my $last_bill = $self->previous_bill;
if ( $last_bill ) {
$invoice_data{'previous_payments'} = [];
$invoice_data{'previous_credits'} = [];
}
- } # if this is an invoice
- my $summarypage = '';
- if ( $conf->exists('invoice_usesummary', $agentnum) ) {
- $summarypage = 1;
- }
- $invoice_data{'summarypage'} = $summarypage;
+ # info from customer's last invoice before this one, for some
+ # summary formats
+ $invoice_data{'last_bill'} = {};
+
+ if ( $conf->exists('invoice_usesummary', $agentnum) ) {
+ $invoice_data{'summarypage'} = $summarypage = 1;
+ }
+
+ } # if this is an invoice
warn "$me substituting variables in notes, footer, smallfooter\n"
if $DEBUG > 1;
warn "$me generating sections\n"
if $DEBUG > 1;
- my $taxtotal = 0;
- my $tax_section = { 'description' => $self->mt('Taxes, Surcharges, and Fees'),
- 'subtotal' => $taxtotal, # adjusted below
- 'tax_section' => 1,
- };
- my $tax_weight = _pkg_category($tax_section->{description})
- ? _pkg_category($tax_section->{description})->weight
- : 0;
- $tax_section->{'summarized'} = ''; #why? $summarypage && !$tax_weight ? 'Y' : '';
- $tax_section->{'sort_weight'} = $tax_weight;
-
- my $adjusttotal = 0;
- my $adjust_section = {
- 'description' => $self->mt('Credits, Payments, and Adjustments'),
- 'adjust_section' => 1,
- 'subtotal' => 0, # adjusted below
- };
- my $adjust_weight = _pkg_category($adjust_section->{description})
- ? _pkg_category($adjust_section->{description})->weight
- : 0;
- $adjust_section->{'summarized'} = ''; #why? $summarypage && !$adjust_weight ? 'Y' : '';
- $adjust_section->{'sort_weight'} = $adjust_weight;
-
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);
$previous_section = $default_section;
}
+ my $adjust_section = {
+ 'description' => $self->mt('Credits, Payments, and Adjustments'),
+ 'adjust_section' => 1,
+ 'subtotal' => 0, # adjusted below
+ };
+ my $adjust_weight = _pkg_category($adjust_section->{description})
+ ? _pkg_category($adjust_section->{description})->weight
+ : 0;
+ $adjust_section->{'summarized'} = ''; #why? $summarypage && !$adjust_weight ? 'Y' : '';
+ # Note: 'sort_weight' here is actually a flag telling whether there is an
+ # explicit package category for the adjust section. If so, certain behavior
+ # happens.
+ $adjust_section->{'sort_weight'} = $adjust_weight;
+
+
if ( $multisection ) {
($extra_sections, $extra_lines) =
$self->_items_extra_usage_sections($escape_function_nonbsp, $format)
join(', ', map "$_=>".$line_item->{$_}, keys %$line_item). "\n"
if $DEBUG > 1;
+ push @buf, ( [ $line_item->{'description'},
+ $money_char. sprintf("%10.2f", $line_item->{'amount'}),
+ ],
+ map { [ " ". $_, '' ] } @{$line_item->{'ext_description'}},
+ );
+
$line_item->{'ref'} = $line_item->{'pkgnum'};
$line_item->{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; # mt()?
$line_item->{'section'} = $section;
$line_item->{'ext_description'} ||= [];
push @detail_items, $line_item;
- push @buf, ( [ $line_item->{'description'},
- $money_char. sprintf("%10.2f", $line_item->{'amount'}),
- ],
- map { [ " ". $_, '' ] } @{$line_item->{'ext_description'}},
- );
}
if ( $section->{'description'} ) {
warn "$me adding taxes\n"
if $DEBUG > 1;
+ # create a tax section if we don't yet have one
+ my $tax_description = 'Taxes, Surcharges, and Fees';
+ my $tax_section =
+ List::Util::first { $_->{description} eq $tax_description } @sections;
+ if (!$tax_section) {
+ $tax_section = { 'description' => $tax_description };
+ push @sections, $tax_section if $multisection;
+ }
+ $tax_section->{tax_section} = 1; # mark this section as containing taxes
+ # if this is an existing tax section, we're merging the tax items into it.
+ # grab the taxtotal that's already there, strip the money symbol if any
+ my $taxtotal = $tax_section->{'subtotal'} || 0;
+ $taxtotal =~ s/^\Q$other_money_char\E//;
+
+ # this does nothing
+ #my $tax_weight = _pkg_category($tax_section->{description})
+ # ? _pkg_category($tax_section->{description})->weight
+ # : 0;
+ #$tax_section->{'summarized'} = ''; #why? $summarypage && !$tax_weight ? 'Y' : '';
+ #$tax_section->{'sort_weight'} = $tax_weight;
+
my @items_tax = $self->_items_tax;
foreach my $tax ( @items_tax ) {
$other_money_char. sprintf('%.2f', $self->charged - $taxtotal );
if ( $multisection ) {
- $tax_section->{'subtotal'} = $other_money_char.
- sprintf('%.2f', $taxtotal);
- $tax_section->{'pretotal'} = 'New charges sub-total '.
- $total->{'total_amount'};
- if ( $taxtotal ) {
- push @sections, $tax_section;
- push @summary_subtotals, $tax_section;
+ if ( $taxtotal > 0 ) {
+ $tax_section->{'subtotal'} = $other_money_char.
+ sprintf('%.2f', $taxtotal);
+ $tax_section->{'pretotal'} = 'New charges sub-total '.
+ $total->{'total_amount'};
+ $tax_section->{'description'} = $self->mt($tax_description);
+
+ # append it if it's not already there
+ if ( !grep $tax_section, @sections ) {
+ push @sections, $tax_section;
+ push @summary_subtotals, $tax_section;
+ }
}
+
} else {
unshift @total_items, $total;
}
$money_char. sprintf("%10.2f",$self->charged) ];
push @buf,['',''];
-
###
# Totals
###
$total->{'total_item'} = &$escape_function($credit->{'description'});
$credittotal += $credit->{'amount'};
$total->{'total_amount'} = $minus.$other_money_char.$credit->{'amount'};
- $adjusttotal += $credit->{'amount'};
if ( $multisection ) {
push @detail_items, {
ext_description => [],
$total->{'total_item'} = &$escape_function($payment->{'description'});
$paymenttotal += $payment->{'amount'};
$total->{'total_amount'} = $minus.$other_money_char.$payment->{'amount'};
- $adjusttotal += $payment->{'amount'};
if ( $multisection ) {
push @detail_items, {
ext_description => [],
if ( $multisection ) {
$adjust_section->{'subtotal'} = $other_money_char.
- sprintf('%.2f', $adjusttotal);
+ sprintf('%.2f', $credittotal + $paymenttotal);
+
+ #why this? because {sort_weight} forces the adjust_section to appear
+ #in @extra_sections instead of @sections. obviously.
push @sections, $adjust_section
unless $adjust_section->{sort_weight};
# do not summarize; adjustments there are shown according to
alternate template name, optional
-=item print_text
-
-text attachment arrayref, optional
-
=item subject
email subject, optional
my $tc = $self->template_conf;
- if ( $conf->exists($tc.'html') ) {
+ my @text; # array of lines
+ my $html; # a big string
+ my @related_parts; # will contain the text/HTML alternative, and images
+ my $related; # will contain the multipart/related object
- warn "$me creating HTML/text multipart message"
- if $DEBUG;
+ if ( $conf->exists($tc. 'email_pdf') ) {
+ if ( my $msgnum = $conf->config($tc.'email_pdf_msgnum') ) {
- $return{'nobody'} = 1;
+ warn "$me using '${tc}email_pdf_msgnum' in multipart message"
+ if $DEBUG;
- my $alternative = build MIME::Entity
- 'Type' => 'multipart/alternative',
- #'Encoding' => '7bit',
- 'Disposition' => 'inline'
- ;
+ my $msg_template = FS::msg_template->by_key($msgnum)
+ or die "${tc}email_pdf_msgnum $msgnum not found\n";
+ my %prepared = $msg_template->prepare(
+ cust_main => $self->cust_main,
+ object => $self
+ );
+
+ @text = split(/(?=\n)/, $prepared{'text_body'});
+ $html = $prepared{'html_body'};
- my $data = '';
- if ( $conf->exists($tc. 'email_pdf')
- and scalar($conf->config($tc. 'email_pdf_note')) ) {
+ } elsif ( my @note = $conf->config($tc.'email_pdf_note') ) {
warn "$me using '${tc}email_pdf_note' in multipart message"
if $DEBUG;
- $data = [ map { $_ . "\n" }
- $conf->config($tc.'email_pdf_note')
- ];
+ @text = $conf->config($tc.'email_pdf_note');
+ $html = join('<BR>', @text);
+
+ } # else use the plain text invoice
+ }
- } else {
+ if (!@text) {
- warn "$me not using '${tc}email_pdf_note' in multipart message"
- if $DEBUG;
- if ( ref($args{'print_text'}) eq 'ARRAY' ) {
- $data = $args{'print_text'};
- } elsif ( $conf->exists($tc.'template') ) { #plaintext invoice_template
- $data = [ $self->print_text(\%args) ];
- }
+ warn "$me generating plain text invoice"
+ if $DEBUG;
- }
+ # 'print_text' argument is no longer used
+ @text = $self->print_text(\%args);
- if ( $data ) {
- $alternative->attach(
- 'Type' => 'text/plain',
- 'Encoding' => 'quoted-printable',
- 'Charset' => 'UTF-8',
- #'Encoding' => '7bit',
- 'Data' => $data,
- 'Disposition' => 'inline',
- );
- }
+ }
- my $htmldata;
- my $image = '';
- my $barcode = '';
- if ( $conf->exists($tc.'email_pdf')
- and scalar($conf->config($tc.'email_pdf_note')) ) {
+ my $text_part = build MIME::Entity (
+ 'Type' => 'text/plain',
+ 'Encoding' => 'quoted-printable',
+ 'Charset' => 'UTF-8',
+ #'Encoding' => '7bit',
+ 'Data' => \@text,
+ 'Disposition' => 'inline',
+ );
- $htmldata = join('<BR>', $conf->config($tc.'email_pdf_note') );
+ if (!$html) {
- } else {
+ if ( $conf->exists($tc.'html') ) {
+ warn "$me generating HTML invoice"
+ if $DEBUG;
$args{'from'} =~ /\@([\w\.\-]+)/;
my $from = $1 || 'example.com';
}
my $image_data = $conf->config_binary( $logo, $agentnum);
- $image = build MIME::Entity
+ push @related_parts, build MIME::Entity
'Type' => 'image/png',
'Encoding' => 'base64',
'Data' => $image_data,
if ( ref($self) eq 'FS::cust_bill' && $conf->exists('invoice-barcode') ) {
my $barcode_content_id = join('.', rand()*(2**32), $$, time). "\@$from";
- $barcode = build MIME::Entity
+ push @related_parts, build MIME::Entity
'Type' => 'image/png',
'Encoding' => 'base64',
'Data' => $self->invoice_barcode(0),
$args{'barcode_cid'} = $barcode_content_id;
}
- $htmldata = $self->print_html({ 'cid'=>$content_id, %args });
+ $html = $self->print_html({ 'cid'=>$content_id, %args });
+ }
+
+ }
+
+ if ( $html ) {
+
+ warn "$me creating HTML/text multipart message"
+ if $DEBUG;
+
+ $return{'nobody'} = 1;
+
+ my $alternative = build MIME::Entity
+ 'Type' => 'multipart/alternative',
+ #'Encoding' => '7bit',
+ 'Disposition' => 'inline'
+ ;
+
+ if ( @text ) {
+ $alternative->add_part($text_part);
}
$alternative->attach(
' </title>',
' </head>',
' <body bgcolor="#e8e8e8">',
- $htmldata,
+ $html,
' </body>',
'</html>',
],
#'Filename' => 'invoice.pdf',
);
+ unshift @related_parts, $alternative;
- my @otherparts = ();
- if ( ref($self) eq 'FS::cust_bill' && $cust_main->email_csv_cdr ) {
-
- push @otherparts, build MIME::Entity
- 'Type' => 'text/csv',
- 'Encoding' => '7bit',
- 'Data' => [ map { "$_\n" }
- $self->call_details('prepend_billed_number' => 1)
- ],
- 'Disposition' => 'attachment',
- 'Filename' => 'usage-'. $self->invnum. '.csv',
- ;
-
- }
+ $related = build MIME::Entity 'Type' => 'multipart/related',
+ 'Encoding' => '7bit';
- if ( $conf->exists($tc.'email_pdf') ) {
-
- #attaching pdf too:
- # multipart/mixed
- # multipart/related
- # multipart/alternative
- # text/plain
- # text/html
- # image/png
- # application/pdf
-
- my $related = build MIME::Entity 'Type' => 'multipart/related',
- 'Encoding' => '7bit';
-
- #false laziness w/Misc::send_email
- $related->head->replace('Content-type',
- $related->mime_type.
- '; boundary="'. $related->head->multipart_boundary. '"'.
- '; type=multipart/alternative'
- );
-
- $related->add_part($alternative);
-
- $related->add_part($image) if $image;
-
- my $pdf = build MIME::Entity $self->mimebuild_pdf(\%args);
+ #false laziness w/Misc::send_email
+ $related->head->replace('Content-type',
+ $related->mime_type.
+ '; boundary="'. $related->head->multipart_boundary. '"'.
+ '; type=multipart/alternative'
+ );
- $return{'mimeparts'} = [ $related, $pdf, @otherparts ];
+ $related->add_part($_) foreach @related_parts;
- } else {
+ }
- #no other attachment:
- # multipart/related
- # multipart/alternative
- # text/plain
- # text/html
- # image/png
+ my @otherparts = ();
+ if ( ref($self) eq 'FS::cust_bill' && $cust_main->email_csv_cdr ) {
- $return{'content-type'} = 'multipart/related';
- if ($conf->exists('invoice-barcode') && $barcode) {
- $return{'mimeparts'} = [ $alternative, $image, $barcode, @otherparts ];
- } else {
- $return{'mimeparts'} = [ $alternative, $image, @otherparts ];
- }
- $return{'type'} = 'multipart/alternative'; #Content-Type of first part...
- #$return{'disposition'} = 'inline';
+ push @otherparts, build MIME::Entity
+ 'Type' => 'text/csv',
+ 'Encoding' => '7bit',
+ 'Data' => [ map { "$_\n" }
+ $self->call_details('prepend_billed_number' => 1)
+ ],
+ 'Disposition' => 'attachment',
+ 'Filename' => 'usage-'. $self->invnum. '.csv',
+ ;
- }
-
- } else {
+ }
- if ( $conf->exists($tc.'email_pdf') ) {
- warn "$me creating PDF attachment"
- if $DEBUG;
+ if ( $conf->exists($tc.'email_pdf') ) {
- #mime parts arguments a la MIME::Entity->build().
- $return{'mimeparts'} = [
- { $self->mimebuild_pdf(\%args) }
- ];
- }
-
- if ( $conf->exists($tc.'email_pdf')
- and scalar($conf->config($tc.'email_pdf_note')) ) {
+ #attaching pdf too:
+ # multipart/mixed
+ # multipart/related
+ # multipart/alternative
+ # text/plain
+ # text/html
+ # image/png
+ # application/pdf
- warn "$me using '${tc}email_pdf_note'"
- if $DEBUG;
- $return{'body'} = [ map { $_ . "\n" }
- $conf->config($tc.'email_pdf_note')
- ];
+ my $pdf = build MIME::Entity $self->mimebuild_pdf(\%args);
+ push @otherparts, $pdf;
+ }
+ if (@otherparts) {
+ $return{'content-type'} = 'multipart/mixed'; # of the outer container
+ if ( $html ) {
+ $return{'mimeparts'} = [ $related, @otherparts ];
+ $return{'type'} = 'multipart/related'; # of the first part
} else {
-
- warn "$me not using '${tc}email_pdf_note'"
- if $DEBUG;
- if ( ref($args{'print_text'}) eq 'ARRAY' ) {
- $return{'body'} = $args{'print_text'};
- } else {
- $return{'body'} = [ $self->print_text(\%args) ];
- }
-
+ $return{'mimeparts'} = [ $text_part, @otherparts ];
+ $return{'type'} = 'text/plain';
}
-
+ } elsif ( $html ) { # no PDF or CSV, strip the outer container
+ $return{'mimeparts'} = \@related_parts;
+ $return{'content-type'} = 'multipart/related';
+ $return{'type'} = 'multipart/alternative';
+ } else { # no HTML either
+ $return{'body'} = \@text;
+ $return{'content-type'} = 'text/plain';
}
%return;
$self->_items_cust_bill_pkg([ $self->cust_bill_pkg ])
-The only OPTIONS accepted is 'section', which may point to a hashref
-with a key named 'condensed', which may have a true value. If it
-does, this method tries to merge identical items into items with
-'quantity' equal to the number of items (not the sum of their
-separate quantities, for some reason).
+OPTIONS are passed through to _items_cust_bill_pkg, and should include
+'format' and 'escape_function' at minimum.
+
+To produce items for a specific invoice section, OPTIONS should include
+'section', a hashref containing 'category' and/or 'locationnum' keys.
+
+'section' may also contain a key named 'condensed'. If this is present
+and has a true value, _items_pkg will try to merge identical items into items
+with 'quantity' equal to the number of items (not the sum of their separate
+quantities, for some reason).
=cut
my $self = shift;
my %options = @_;
my @cust_bill_pkg = grep { $_->feepart } $self->cust_bill_pkg;
+ my $escape_function = $options{escape_function};
+
my @items;
foreach my $cust_bill_pkg (@cust_bill_pkg) {
# cache this, so we don't look it up again in every section
}
foreach (sort keys(%base_invnums)) {
next if $_ == $self->invnum;
+ # per convention, we must escape ext_description lines
push @ext_desc,
- $self->mt('from invoice \\#[_1] on [_2]', $_, $base_invnums{$_});
+ &{$escape_function}(
+ $self->mt('from invoice #[_1] on [_2]', $_, $base_invnums{$_})
+ );
}
+ my $desc = $part_fee->itemdesc_locale($self->cust_main->locale);
+ # but not escape the base description line
+
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),
+ description => $desc,
ext_description => \@ext_desc
# sdate/edate?
};
if $cust_bill_pkg->recur != 0
|| $discount_show_always
|| $cust_bill_pkg->recur_show_zero;
- push @b, {
+ #push @b, {
+ # keep it consistent, please
+ $s = {
'pkgnum' => $cust_bill_pkg->pkgpart, #so it displays in Ref
'description' => $description,
'amount' => sprintf("%.2f", $cust_bill_pkg->setup),
};
}
if ( $cust_bill_pkg->recur != 0 ) {
- push @b, {
+ #push @b, {
+ $r = {
'pkgnum' => $cust_bill_pkg->pkgpart, #so it displays in Ref
'description' => "$desc (". $cust_bill_pkg->part_pkg->freq_pretty.")",
'amount' => sprintf("%.2f", $cust_bill_pkg->recur),
} # recurring or usage with recurring charge
- # decide whether to show active discounts here
- if (
- # case 1: we are showing a single line for the package
- ( !$type )
- # case 2: we are showing a setup line for a package that has
- # no base recurring fee
- or ( $type eq 'S' and $cust_bill_pkg->unitrecur == 0 )
- # case 3: we are showing a recur line for a package that has
- # a base recurring fee
- or ( $type eq 'R' and $cust_bill_pkg->unitrecur > 0 )
- ) {
-
- # the line item hashref for the line that will show the original
- # price
- # (use the recur or single line for the package, unless we're
- # showing a setup line for a package with no recurring fee)
- my $active_line = $r;
- if ( $type eq 'S' ) {
- $active_line = $s;
- }
-
- my @discounts = $cust_bill_pkg->cust_bill_pkg_discount;
- # special case: if there are old "discount details" on this line
- # item, don't show discount line items
- if ( FS::cust_bill_pkg_detail->count(
- "detail LIKE 'Includes discount%' AND billpkgnum = " .
- $cust_bill_pkg->billpkgnum
- ) > 0 ) {
- @discounts = ();
- }
- if ( @discounts ) {
- warn "$me _items_cust_bill_pkg including discounts for ".
- $cust_bill_pkg->billpkgnum."\n"
- if $DEBUG;
- my $discount_amount = sum( map {$_->amount} @discounts );
- # if multiple discounts apply to the same package, how to display
- # them? ext_description lines, apparently
- #
- # # discount amounts are negative
- if ( $d and $cust_bill_pkg->hidden ) {
- $d->{amount} -= $discount_amount;
- } else {
- my @ext;
- $d = {
- _is_discount => 1,
- description => $self->mt('Discount'),
- amount => -1 * $discount_amount,
- ext_description => \@ext,
- };
- foreach my $cust_bill_pkg_discount (@discounts) {
- my $discount = $cust_bill_pkg_discount->cust_pkg_discount->discount;
- my $discount_desc = $discount->description_short;
-
- if ($discount->months) {
-
- # calculate months remaining after this invoice
- my $used = FS::Record->scalar_sql(
- 'SELECT SUM(months) FROM cust_bill_pkg_discount
- JOIN cust_bill_pkg USING (billpkgnum)
- JOIN cust_bill USING (invnum)
- WHERE pkgdiscountnum = ? AND _date <= ?',
- $cust_bill_pkg_discount->pkgdiscountnum,
- $self->_date
- );
- $used ||= 0;
- my $remaining = sprintf('%.2f', $discount->months - $used);
- # append "for X months (Y months remaining)"
- $discount_desc .= $self->mt(' for [quant,_1,month] ([quant,_2,month] remaining)',
- $cust_bill_pkg_discount->months,
- $remaining
- );
- } # else it's not time-limited
- push @ext, &{$escape_function}($discount_desc);
- }
- }
-
- # update the active line (before the discount) to show the
- # original price (whether this is a hidden line or not)
- $active_line->{amount} += $discount_amount;
-
- } # if there are any discounts
- } # if this is an appropriate place to show discounts
-
} else { # taxes and fees
warn "$me _items_cust_bill_pkg cust_bill_pkg is tax\n"
} # if quotation / package line item / other line item
+ # decide whether to show active discounts here
+ if (
+ # case 1: we are showing a single line for the package
+ ( !$type )
+ # case 2: we are showing a setup line for a package that has
+ # no base recurring fee
+ or ( $type eq 'S' and $cust_bill_pkg->unitrecur == 0 )
+ # case 3: we are showing a recur line for a package that has
+ # a base recurring fee
+ or ( $type eq 'R' and $cust_bill_pkg->unitrecur > 0 )
+ ) {
+
+ my $item_discount = $cust_bill_pkg->_item_discount;
+ if ( $item_discount ) {
+ # $item_discount->{amount} is negative
+
+ if ( $d and $cust_bill_pkg->hidden ) {
+ $d->{amount} += $item_discount->{amount};
+ } else {
+ $d = $item_discount;
+ $_ = &{$escape_function}($_) foreach @{ $d->{ext_description} };
+ }
+
+ # update the active line (before the discount) to show the
+ # original price (whether this is a hidden line or not)
+ #
+ # quotation discounts keep track of setup and recur; invoice
+ # discounts currently don't
+ if ( exists $item_discount->{setup_amount} ) {
+
+ $s->{amount} -= $item_discount->{setup_amount} if $s;
+ $r->{amount} -= $item_discount->{recur_amount} if $r;
+
+ } else {
+
+ # $active_line is the line item hashref for the line that will
+ # show the original price
+ # (use the recur or single line for the package, unless we're
+ # showing a setup line for a package with no recurring fee)
+ my $active_line = $r;
+ if ( $type eq 'S' ) {
+ $active_line = $s;
+ }
+ $active_line->{amount} -= $item_discount->{amount};
+
+ }
+
+ } # if there are any discounts
+ } # if this is an appropriate place to show discounts
+
} # foreach $display
$discount_show_always = ($cust_bill_pkg->cust_bill_pkg_discount