X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2FTemplate_Mixin.pm;h=9e43c3c5cacb9b970d539dc209aeab7fe210b6fa;hp=d8e46c5ab6423bb4abe00bf9a850e9205be0ad14;hb=d0295833c7e240698bfd7b3a9bddf2024a602253;hpb=5310efb6d40b32ae08ae78553fc8721693925da7 diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index d8e46c5ab..9e43c3c5c 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -7,7 +7,7 @@ use vars qw( $DEBUG $me ); # 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; @@ -316,9 +316,6 @@ sub print_generic { 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; @@ -565,12 +562,14 @@ sub print_generic { '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'), + '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'), @@ -655,10 +654,10 @@ sub print_generic { 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 @@ -698,6 +697,10 @@ sub print_generic { # 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 ) { @@ -758,9 +761,7 @@ sub print_generic { # ($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', { @@ -802,11 +803,7 @@ sub print_generic { $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; } @@ -908,29 +905,6 @@ sub print_generic { 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); @@ -971,6 +945,21 @@ sub print_generic { $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) @@ -1220,6 +1209,27 @@ sub print_generic { 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 ) { @@ -1254,7 +1264,7 @@ sub print_generic { ]; } - + if ( @items_tax ) { my $total = {}; $total->{'total_item'} = $self->mt('Sub-total'); @@ -1262,30 +1272,26 @@ sub print_generic { $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 ### @@ -1297,51 +1303,37 @@ sub print_generic { ); 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 @@ -1349,19 +1341,17 @@ sub print_generic { 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 => [], @@ -1395,7 +1385,6 @@ sub print_generic { $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 => [], @@ -1417,7 +1406,10 @@ sub print_generic { 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 @@ -1440,7 +1432,7 @@ sub print_generic { if ( $multisection && !$adjust_section->{sort_weight} ) { $adjust_section->{'posttotal'} = $total->{'total_item'}. ' '. $total->{'total_amount'}; - }else{ + } else { push @total_items, $total; } push @buf,['','-----------']; @@ -1526,7 +1518,7 @@ sub print_generic { # 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 ) { @@ -1906,12 +1898,18 @@ sub due_date2str { 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; } @@ -2019,10 +2017,6 @@ sender address, required alternate template name, optional -=item print_text - -text attachment arrayref, optional - =item subject email subject, optional @@ -2066,61 +2060,70 @@ sub generate_email { 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('
', @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('
', $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'; @@ -2139,7 +2142,7 @@ sub generate_email { } 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, @@ -2149,7 +2152,7 @@ sub generate_email { 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), @@ -2159,7 +2162,26 @@ sub generate_email { $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( @@ -2172,7 +2194,7 @@ sub generate_email { ' ', ' ', ' ', - $htmldata, + $html, ' ', '', ], @@ -2180,104 +2202,68 @@ sub generate_email { #'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', - ; - - } - - 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; + $related = build MIME::Entity 'Type' => 'multipart/related', + 'Encoding' => '7bit'; - 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; @@ -2794,11 +2780,16 @@ equivalent to $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 @@ -2866,13 +2857,14 @@ sub _items_fee { } foreach (sort keys(%base_invnums)) { next if $_ == $self->invnum; + # per convention, we must escape ext_description lines push @ext_desc, &{$escape_function}( $self->mt('from invoice #[_1] on [_2]', $_, $base_invnums{$_}) ); } my $desc = $part_fee->itemdesc_locale($self->cust_main->locale); - $desc = &{$escape_function}($desc); + # but not escape the base description line push @items, { feepart => $cust_bill_pkg->feepart, @@ -3007,10 +2999,28 @@ sub _items_cust_bill_pkg { 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 ); @@ -3091,6 +3101,7 @@ sub _items_cust_bill_pkg { ); 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; @@ -3194,11 +3205,10 @@ sub _items_cust_bill_pkg { 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; @@ -3296,11 +3306,10 @@ sub _items_cust_bill_pkg { 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;