);
# 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;
)),
#global config
- 'ship_enable' => $conf->exists('invoice-ship_address'),
+ 'ship_enable' => $cust_main->invoice_ship_address || $conf->exists('invoice-ship_address'),
'unitprices' => $conf->exists('invoice-unitprice'),
'smallernotes' => $conf->exists('invoice-smallernotes'),
'smallerfooter' => $conf->exists('invoice-smallerfooter'),
# XXX should be an FS::cust_bill method to set the defaults, instead
# of checking the type here
+ # info from customer's last invoice before this one, for some
+ # summary formats
+ $invoice_data{'last_bill'} = {};
+
my $last_bill = $self->previous_bill;
if ( $last_bill ) {
# ($pr_total is used elsewhere but not as $previous_balance)
$invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total);
- $invoice_data{'last_bill'} = {
- '_date' => $last_bill->_date, #unformatted
- };
+ $invoice_data{'last_bill'}{'_date'} = $last_bill->_date; #unformatted
my (@payments, @credits);
# for formats that itemize previous payments
foreach my $cust_pay ( qsearch('cust_pay', {
$invoice_data{'previous_payments'} = [];
$invoice_data{'previous_credits'} = [];
}
-
- # 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;
}
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)
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 ) {
];
}
-
+
if ( @items_tax ) {
my $total = {};
$total->{'total_item'} = $self->mt('Sub-total');
$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;
}
}
$invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
- push @buf,['','-----------'];
- push @buf,[$self->mt(
- (!$self->enable_previous)
- ? 'Total Charges'
- : 'Total New Charges'
- ),
- $money_char. sprintf("%10.2f",$self->charged) ];
- push @buf,['',''];
-
-
###
# Totals
###
);
my $embolden_function = $embolden_functions{$format};
- if ( $self->can('_items_total') ) { # quotations
-
- $self->_items_total(\@total_items);
+ if ( $multisection ) {
- foreach ( @total_items ) {
- $_->{'total_item'} = &$embolden_function( $_->{'total_item'} );
- $_->{'total_amount'} = &$embolden_function( $other_money_char.
- $_->{'total_amount'}
- );
+ if ( $adjust_section->{'sort_weight'} ) {
+ $adjust_section->{'posttotal'} = $self->mt('Balance Forward').' '.
+ $other_money_char. sprintf("%.2f", ($self->billing_balance || 0) );
+ } else{
+ $adjust_section->{'pretotal'} = $self->mt('New charges total').' '.
+ $other_money_char. sprintf('%.2f', $self->charged );
}
- } else { #normal invoice case
+ }
+
+ if ( $self->can('_items_total') ) { # should always be true now
- # calculate total, possibly including total owed on previous
- # invoices
- my $total = {};
- my $item = 'Total';
- $item = $conf->config('previous_balance-exclude_from_total')
- || 'Total New Charges'
- if $conf->exists('previous_balance-exclude_from_total');
- my $amount = $self->charged;
- if ( $self->enable_previous and !$conf->exists('previous_balance-exclude_from_total') ) {
- $amount += $pr_total;
- }
+ # even for multisection, need plain text version
+
+ my @new_total_items = $self->_items_total;
- $total->{'total_item'} = &$embolden_function($self->mt($item));
- $total->{'total_amount'} =
- &$embolden_function( $other_money_char. sprintf( '%.2f', $amount ) );
- if ( $multisection ) {
- if ( $adjust_section->{'sort_weight'} ) {
- $adjust_section->{'posttotal'} = $self->mt('Balance Forward').' '.
- $other_money_char. sprintf("%.2f", ($self->billing_balance || 0) );
- } else {
- $adjust_section->{'pretotal'} = $self->mt('New charges total').' '.
- $other_money_char. sprintf('%.2f', $self->charged );
- }
- } else {
- push @total_items, $total;
- }
push @buf,['','-----------'];
- push @buf,[$item,
- $money_char.
- sprintf( '%10.2f', $amount )
- ];
- push @buf,['',''];
+
+ foreach ( @new_total_items ) {
+ my ($item, $amount) = ($_->{'total_item'}, $_->{'total_amount'});
+ $_->{'total_item'} = &$embolden_function( $item );
+ $_->{'total_amount'} = &$embolden_function( $other_money_char.$amount );
+ # but if it's multisection, don't append to @total_items. the adjust
+ # section has all this stuff
+ push @total_items, $_ if !$multisection;
+ push @buf, [ $item, $money_char.sprintf('%10.2f',$amount) ];
+ }
+
+ push @buf, [ '', '' ];
# if we're showing previous invoices, also show previous
# credits and payments
and $self->can('_items_credits')
and $self->can('_items_payments') )
{
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
# credits
my $credittotal = 0;
foreach my $credit (
- $self->_items_credits( 'template' => $template, 'trim_len' => 60 )
+ $self->_items_credits( 'template' => $template, 'trim_len' => 40 )
) {
my $total;
$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
if ( $multisection && !$adjust_section->{sort_weight} ) {
$adjust_section->{'posttotal'} = $total->{'total_item'}. ' '.
$total->{'total_amount'};
- }else{
+ } else {
push @total_items, $total;
}
push @buf,['','-----------'];
# invoice history "section" (not really a section)
# not to be included in any subtotals, completely independent of
# everything...
- if ( $conf->exists('previous_invoice_history') ) {
+ if ( $conf->exists('previous_invoice_history') and $cust_main->isa('FS::cust_main') ) {
my %history;
my %monthorder;
foreach my $cust_bill ( $cust_main->cust_bill ) {
sub balance_due_msg {
my $self = shift;
my $msg = $self->mt('Balance Due');
- return $msg unless $self->terms;
- if ( $self->due_date ) {
- $msg .= ' - ' . $self->mt('Please pay by'). ' '.
- $self->due_date2str('short');
- } elsif ( $self->terms ) {
- $msg .= ' - '. $self->terms;
+ return $msg unless $self->terms; # huh?
+ if ( !$self->conf->exists('invoice_show_prior_due_date')
+ or $self->conf->exists('invoice_sections') ) {
+ # if enabled, the due date is shown with Total New Charges (see
+ # _items_total) and not here
+ # (yes, or if invoice_sections is enabled; this is just for compatibility)
+ if ( $self->due_date ) {
+ $msg .= ' - ' . $self->mt('Please pay by'). ' '.
+ $self->due_date2str('short');
+ } elsif ( $self->terms ) {
+ $msg .= ' - '. $self->mt($self->terms);
+ }
}
$msg;
}
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
+ }
+
+ if (!@text) {
+
+ if ( $conf->config($tc.'template') ) {
+
+ warn "$me generating plain text invoice"
+ if $DEBUG;
+
+ # 'print_text' argument is no longer used
+ @text = $self->print_text(\%args);
} else {
- warn "$me not using '${tc}email_pdf_note' in multipart message"
+ warn "$me no plain text version exists; sending empty message body"
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) ];
- }
}
- 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 ) {
+ $related = build MIME::Entity 'Type' => 'multipart/related',
+ 'Encoding' => '7bit';
- 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',
- ;
-
- }
-
- 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
my %base_invnums; # invnum => invoice date
foreach ($cust_bill_pkg->cust_bill_pkg_fee) {
if ($_->base_invnum) {
+ # XXX what if base_bill has been voided?
my $base_bill = FS::cust_bill->by_key($_->base_invnum);
my $base_date = $self->time2str_local('short', $base_bill->_date)
if $base_bill;
}
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?
};
my $multisection = defined($category) || defined($locationnum);
my $discount_show_always = 0;
- my $maxlength = $conf->config('cust_bill-latex_lineitem_maxlength') || 50;
+ my $maxlength = $conf->config('cust_bill-latex_lineitem_maxlength') || 40;
my $cust_main = $self->cust_main;#for per-agent cust_bill-line_item-ate_style
- # and location labels
+
+ # for location labels: use default location on the invoice date
+ my $default_locationnum;
+ if ( $self->custnum ) {
+ my $h_cust_main;
+ my @h_search = FS::h_cust_main->sql_h_search($self->_date);
+ $h_cust_main = qsearchs({
+ 'table' => 'h_cust_main',
+ 'hashref' => { custnum => $self->custnum },
+ 'extra_sql' => $h_search[1],
+ 'addl_from' => $h_search[3],
+ }) || $cust_main;
+ $default_locationnum = $h_cust_main->ship_locationnum;
+ } elsif ( $self->prospectnum ) {
+ my $cust_location = qsearchs('cust_location',
+ { prospectnum => $self->prospectnum,
+ disabled => '' });
+ $default_locationnum = $cust_location->locationnum if $cust_location;
+ }
my @b = (); # accumulator for the line item hashes that we'll return
my ($s, $r, $u, $d) = ( undef, undef, undef, undef );
);
if ( ref($cust_bill_pkg) eq 'FS::quotation_pkg' ) {
+ # XXX this should be pulled out into quotation_pkg
warn "$me _items_cust_bill_pkg cust_bill_pkg is quotation_pkg\n"
if $DEBUG > 1;
push @d, @svc_labels
unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
- my $lnum = $cust_main ? $cust_main->ship_locationnum
- : $self->prospect_main->locationnum;
# show the location label if it's not the customer's default
# location, and we're not grouping items by location already
- if ( $cust_pkg->locationnum != $lnum and !defined($locationnum) ) {
+ if ( $cust_pkg->locationnum != $default_locationnum
+ and !defined($locationnum) ) {
my $loc = $cust_pkg->location_label;
$loc = substr($loc, 0, $maxlength). '...'
if $format eq 'latex' && length($loc) > $maxlength;
warn "$me _items_cust_bill_pkg done adding service details\n"
if $DEBUG > 1;
- my $lnum = $cust_main ? $cust_main->ship_locationnum
- : $self->prospect_main->locationnum;
# show the location label if it's not the customer's default
# location, and we're not grouping items by location already
- if ( $cust_pkg->locationnum != $lnum and !defined($locationnum) ) {
+ if ( $cust_pkg->locationnum != $default_locationnum
+ and !defined($locationnum) ) {
my $loc = $cust_pkg->location_label;
$loc = substr($loc, 0, $maxlength). '...'
if $format eq 'latex' && length($loc) > $maxlength;