X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=f090978ca933b9dd24cd48f39c275349bdf6df93;hb=d7ca7fe2132e626d6f4e65905f7592ce1789e39c;hp=fe5a653c068deb7653bd2b84d36036d03e7bc60a;hpb=d33c75b60d9cb9f7155635dc2cd25307f06d947f;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index fe5a653c0..f090978ca 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -20,10 +20,13 @@ use FS::cust_pkg; use FS::cust_credit_bill; use FS::cust_pay_batch; use FS::cust_bill_event; +use FS::part_pkg; +use FS::cust_bill_pay; +use FS::part_bill_event; @ISA = qw( FS::Record ); -$DEBUG = 1; +$DEBUG = 0; #ask FS::UID to run this stuff for us later FS::UID->install_callback( sub { @@ -343,57 +346,200 @@ Returns an argument list to be passed to L. =cut +use MIME::Entity; + sub generate_email { my $self = shift; my %args = @_; - my $mimeparts; - if ($conf->exists('invoice_email_pdf')) { - #warn "[FS::cust_bill::send] creating PDF attachment"; - #mime parts arguments a la MIME::Entity->build(). - $mimeparts = [ - { - 'Type' => 'application/pdf', - 'Encoding' => 'base64', - 'Data' => [ $self->print_pdf('', $args{'template'}) ], - 'Disposition' => 'attachment', - 'Filename' => 'invoice.pdf', - }, - ]; - } + my $me = '[FS::cust_bill::generate_email]'; - my $email_text; - if ($conf->exists('invoice_email_pdf') - and scalar($conf->config('invoice_email_pdf_note'))) { + my %return = ( + 'from' => $args{'from'}, + 'subject' => (($args{'subject'}) ? $args{'subject'} : 'Invoice'), + ); - #warn "[FS::cust_bill::send] using 'invoice_email_pdf_note'"; - $email_text = [ map { $_ . "\n" } $conf->config('invoice_email_pdf_note') ]; + if (ref($args{'to'} eq 'ARRAY')) { + $return{'to'} = $args{'to'}; } else { - #warn "[FS::cust_bill::send] not using 'invoice_email_pdf_note'"; - if (ref($args{'print_text'}) eq 'ARRAY') { - $email_text = $args{'print_text'}; + $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ } + $self->cust_main->invoicing_list + ]; + } + + if ( $conf->exists('invoice_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' + ; + + my $data; + if ( $conf->exists('invoice_email_pdf') + and scalar($conf->config('invoice_email_pdf_note')) ) { + + warn "$me using 'invoice_email_pdf_note' in multipart message" + if $DEBUG; + $data = [ map { $_ . "\n" } + $conf->config('invoice_email_pdf_note') + ]; + } else { - $email_text = [ $self->print_text('', $args{'template'}) ]; + + warn "$me not using 'invoice_email_pdf_note' in multipart message" + if $DEBUG; + if ( ref($args{'print_text'}) eq 'ARRAY' ) { + $data = $args{'print_text'}; + } else { + $data = [ $self->print_text('', $args{'template'}) ]; + } + } - } - my @invoicing_list; - if (ref($args{'to'} eq 'ARRAY')) { - @invoicing_list = @{$args{'to'}}; + $alternative->attach( + 'Type' => 'text/plain', + #'Encoding' => 'quoted-printable', + 'Encoding' => '7bit', + 'Data' => $data, + 'Disposition' => 'inline', + ); + + $args{'from'} =~ /\@([\w\.\-]+)/ or $1 = 'example.com'; + my $content_id = join('.', rand()*(2**32), $$, time). "\@$1"; + + my $image = build MIME::Entity + 'Type' => 'image/png', + 'Encoding' => 'base64', + 'Path' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc/logo.png", + 'Filename' => 'logo.png', + 'Content-ID' => "<$content_id>", + ; + + $alternative->attach( + 'Type' => 'text/html', + 'Encoding' => 'quoted-printable', + 'Data' => [ '', + ' ', + ' ', + ' '. encode_entities($return{'subject'}), + ' ', + ' ', + ' ', + $self->print_html('', $args{'template'}, $content_id), + ' ', + '', + ], + 'Disposition' => 'inline', + #'Filename' => 'invoice.pdf', + ); + + if ( $conf->exists('invoice_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); + + my $pdf = build MIME::Entity $self->mimebuild_pdf('', $args{'template'}); + + $return{'mimeparts'} = [ $related, $pdf ]; + + } else { + + #no other attachment: + # multipart/related + # multipart/alternative + # text/plain + # text/html + # image/png + + $return{'content-type'} = 'multipart/related'; + $return{'mimeparts'} = [ $alternative, $image ]; + $return{'type'} = 'multipart/alternative'; #Content-Type of first part... + #$return{'disposition'} = 'inline'; + + } + } else { - @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->invoicing_list; + + if ( $conf->exists('invoice_email_pdf') ) { + warn "$me creating PDF attachment" + if $DEBUG; + + #mime parts arguments a la MIME::Entity->build(). + $return{'mimeparts'} = [ + { $self->mimebuild_pdf('', $args{'template'}) } + ]; + } + + if ( $conf->exists('invoice_email_pdf') + and scalar($conf->config('invoice_email_pdf_note')) ) { + + warn "$me using 'invoice_email_pdf_note'" + if $DEBUG; + $return{'body'} = [ map { $_ . "\n" } + $conf->config('invoice_email_pdf_note') + ]; + + } else { + + warn "$me not using 'invoice_email_pdf_note'" + if $DEBUG; + if ( ref($args{'print_text'}) eq 'ARRAY' ) { + $return{'body'} = $args{'print_text'}; + } else { + $return{'body'} = [ $self->print_text('', $args{'template'}) ]; + } + + } + } - return ( - 'from' => $args{'from'}, - 'to' => [ @invoicing_list ], - 'subject' => (($args{'subject'}) ? $args{'subject'} : 'Invoice'), - 'body' => $email_text, - 'mimeparts' => $mimeparts, - ); + %return; +} + +=item mimebuild_pdf + +Returns a list suitable for passing to MIME::Entity->build(), representing +this invoice as PDF attachment. + +=cut +sub mimebuild_pdf { + my $self = shift; + ( + 'Type' => 'application/pdf', + 'Encoding' => 'base64', + 'Data' => [ $self->print_pdf(@_) ], + 'Disposition' => 'attachment', + 'Filename' => 'invoice.pdf', + ); } =item send [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ] @@ -414,62 +560,121 @@ sub send { my $self = shift; my $template = scalar(@_) ? shift : ''; return 'N/A' if scalar(@_) && $_[0] && $self->cust_main->agentnum != shift; + my $invoice_from = scalar(@_) ? shift : ( $self->_agent_invoice_from || $conf->config('invoice_from') ); - my @print_text = $self->print_text('', $template); my @invoicing_list = $self->cust_main->invoicing_list; - if ( grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list ) { - #email + $self->email($template, $invoice_from) + if grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list; - #better to notify this person than silence - @invoicing_list = ($invoice_from) unless @invoicing_list; + $self->print($template) + if grep { $_ eq 'POST' } @invoicing_list; #postal - my $error = send_email( - $self->generate_email( - 'from' => $invoice_from, - 'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ], - 'print_text' => [ @print_text ], - ) - ); - die "can't email invoice: $error\n" if $error; - #die "$error\n" if $error; + $self->fax($template) + if grep { $_ eq 'FAX' } @invoicing_list; #fax - } + ''; - if ( grep { $_ =~ /^(POST|FAX)$/ } @invoicing_list ) { - my $lpr_data; - if ($conf->config('invoice_latex')) { - $lpr_data = [ $self->print_ps('', $template) ]; - } else { - $lpr_data = \@print_text; - } +} - if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal - my $lpr = $conf->config('lpr'); - open(LPR, "|$lpr") - or die "Can't open pipe to $lpr: $!\n"; - print LPR @{$lpr_data}; - close LPR - or die $! ? "Error closing $lpr: $!\n" - : "Exit status $? from $lpr\n"; - } +=item email [ TEMPLATENAME [ , INVOICE_FROM ] ] - if ( grep { $_ eq 'FAX' } @invoicing_list ) { #fax - die 'FAX invoice destination not supported with plain text invoices.' - unless $conf->exists('invoice_latex'); - my $dialstring = $self->cust_main->getfield('fax'); - #Check $dialstring? - my $error = send_fax(docdata => $lpr_data, dialstring => $dialstring); - die $error if $error; - } +Emails this invoice. - } +TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. - ''; +INVOICE_FROM, if specified, overrides the default email invoice From: address. + +=cut + +sub email { + my $self = shift; + my $template = scalar(@_) ? shift : ''; + my $invoice_from = + scalar(@_) + ? shift + : ( $self->_agent_invoice_from || $conf->config('invoice_from') ); + + my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } + $self->cust_main->invoicing_list; + + #better to notify this person than silence + @invoicing_list = ($invoice_from) unless @invoicing_list; + + my $error = send_email( + $self->generate_email( + 'from' => $invoice_from, + 'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ], + 'template' => $template, + ) + ); + die "can't email invoice: $error\n" if $error; + #die "$error\n" if $error; + +} + +=item lpr_data [ TEMPLATENAME ] + +Returns the postscript or plaintext for this invoice. + +TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. + +=cut + +sub lpr_data { + my( $self, $template) = @_; + $conf->exists('invoice_latex') + ? [ $self->print_ps('', $template) ] + : [ $self->print_text('', $template) ]; +} + +=item print [ TEMPLATENAME ] + +Prints this invoice. + +TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. + +=cut + +sub print { + my $self = shift; + my $template = scalar(@_) ? shift : ''; + + my $lpr = $conf->config('lpr'); + open(LPR, "|$lpr") + or die "Can't open pipe to $lpr: $!\n"; + print LPR @{ $self->lpr_data($template) }; + close LPR + or die $! ? "Error closing $lpr: $!\n" + : "Exit status $? from $lpr\n"; +} + +=item fax [ TEMPLATENAME ] + +Faxes this invoice. + +TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. + +=cut + +sub fax { + my $self = shift; + my $template = scalar(@_) ? shift : ''; + + die 'FAX invoice destination not (yet?) supported with plain text invoices.' + unless $conf->exists('invoice_latex'); + + my $dialstring = $self->cust_main->getfield('fax'); + #Check $dialstring? + + my $error = send_fax( 'docdata' => $self->lpr_data($template), + 'dialstring' => $dialstring, + ); + die $error if $error; } @@ -880,7 +1085,7 @@ sub print_text { ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes ) { - if ( $cust_bill_pkg->pkgnum ) { + if ( $cust_bill_pkg->pkgnum > 0 ) { my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); @@ -1164,10 +1369,10 @@ sub print_latex { $invoice_data{'country'} = _latex_escape(code2country($cust_main->country)); } - #do variable substitutions in notes $invoice_data{'notes'} = join("\n", - map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } +# #do variable substitutions in notes +# map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } $conf->config_orbase('invoice_latexnotes', $template) ); warn "invoice notes: ". $invoice_data{'notes'}. "\n" @@ -1497,7 +1702,7 @@ sub print_pdf { } -=item print_html [ TIME [ , TEMPLATE ] ] +=item print_html [ TIME [ , TEMPLATE [ , CID ] ] ] Returns an HTML invoice, as a scalar. @@ -1506,76 +1711,13 @@ default is now. It isn't the date of the invoice; that's the `_date' field. It is specified as a UNIX timestamp; see L. Also see L and L for conversion functions. -=cut +CID is a MIME Content-ID used to create a "cid:" URL for the logo image, used +when emailing the invoice as part of a multipart/related MIME email. -#sub print_html { -# my $self = shift; -# -# my $file = $self->print_latex(@_); -# -# my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; -# chdir($dir); -# -# my $sfile = shell_quote $file; -# -# system("htlatex $sfile.tex") == 0 -# or die "hlatex $file.tex failed; is hlatex installed, or see $file.log for details?\n"; -# #system("ltoh $sfile.tex") == 0 -# # or die "ltoh $file.tex failed; is hlatex installed, or see $file.log for details?\n"; -# -# open(HTML, "<$file.html") -# or die "can't open $file.html: $! (error in LaTeX template?)\n"; -# -# #unlink("$file.dvi", "$file.log", "$file.aux", "$file.html", "$file.tex"); -# -# my $html = ''; -# while () { -# -# s///; -## s/print_latex(@_); -# -# my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; -# chdir($dir); -# -# my $sfile = shell_quote $file; -# -# system("htlatex $sfile.tex") == 0 -# or die "hlatex $file.tex failed; is hlatex installed, or see $file.log for details?\n"; -# #system("ltoh $sfile.tex") == 0 -# # or die "ltoh $file.tex failed; is hlatex installed, or see $file.log for details?\n"; -# -# open(CSS, "<$file.css") -# or die "can't open $file.html: $! (error in LaTeX template?)\n"; -# -# unlink("$file.dvi", "$file.log", "$file.aux", "$file.html", "$file.tex"); -# -# my $css = ''; -# while () { -# $css .= $_; -# } -# -# close CSS; -# -# return $css; -# -#} +=cut sub print_html { - my( $self, $today, $template ) = @_; + my( $self, $today, $template, $cid ) = @_; $today ||= time; my $cust_main = $self->cust_main; @@ -1598,17 +1740,10 @@ sub print_html { $html_template->compile() or die 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR; - my $returnaddress = $conf->exists('invoice_htmlreturnaddress') - ? join("\n", $conf->config('invoice_htmlreturnaddress') ) - : join("\n", map { s/~/ /g; s/\\\\\*?\s*$/
/; } - $conf->config('invoice_latexreturnaddress') - ); - warn $conf->config('invoice_latexreturnaddress'); - warn $returnaddress; - my %invoice_data = ( 'invnum' => $self->invnum, 'date' => time2str('%b %o, %Y', $self->_date), + 'today' => time2str('%b %o, %Y', $today), 'agent' => encode_entities($cust_main->agent->agent), 'payname' => encode_entities($cust_main->payname), 'company' => encode_entities($cust_main->company), @@ -1617,15 +1752,23 @@ sub print_html { 'city' => encode_entities($cust_main->city), 'state' => encode_entities($cust_main->state), 'zip' => encode_entities($cust_main->zip), -# 'footer' => join("\n", $conf->config('invoice_latexfooter') ), -# 'smallfooter' => join("\n", $conf->config('invoice_latexsmallfooter') ), - 'returnaddress' => $returnaddress, 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt', - #'notes' => join("\n", $conf->config('invoice_latexnotes') ), + 'cid' => $cid, # 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc", ); + $invoice_data{'returnaddress'} = $conf->exists('invoice_htmlreturnaddress') + ? join("\n", $conf->config('invoice_htmlreturnaddress') ) + : join("\n", map { + s/~/ /g; + s/\\\\\*?\s*$/
/; + s/\\hyphenation\{[\w\s\-]+\}//; + $_; + } + $conf->config('invoice_latexreturnaddress') + ); + my $countrydefault = $conf->config('countrydefault') || 'US'; if ( $cust_main->country eq $countrydefault ) { $invoice_data{'country'} = ''; @@ -1634,8 +1777,20 @@ sub print_html { encode_entities(code2country($cust_main->country)); } - my $countrydefault = $conf->config('countrydefault') || 'US'; - $invoice_data{'country'} = '' if $invoice_data{'country'} eq $countrydefault; + $invoice_data{'notes'} = + length($conf->config_orbase('invoice_htmlnotes', $template)) + ? join("\n", $conf->config_orbase('invoice_htmlnotes', $template) ) + : join("\n", map { + s/%%(.*)$//; + s/\\section\*\{\\textsc\{(.)(.*)\}\}/

$1<\/font>\U$2<\/b>/; + s/\\begin\{enumerate\}/

    /; + s/\\item /
  1. /; + s/\\end\{enumerate\}/<\/ol>/; + s/\\textbf\{(.*)\}/$1<\/b>/; + $_; + } + $conf->config_orbase('invoice_latexnotes', $template) + ); # #do variable substitutions in notes # $invoice_data{'notes'} = @@ -1643,11 +1798,13 @@ sub print_html { # map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } # $conf->config_orbase('invoice_latexnotes', $suffix) # ); -# -# $invoice_data{'footer'} =~ s/\n+$//; -# $invoice_data{'smallfooter'} =~ s/\n+$//; -# $invoice_data{'notes'} =~ s/\n+$//; -# + + $invoice_data{'footer'} = $conf->exists('invoice_htmlfooter') + ? join("\n", $conf->config('invoice_htmlfooter') ) + : join("\n", map { s/~/ /g; s/\\\\\*?\s*$/
    /; $_; } + $conf->config('invoice_latexfooter') + ); + $invoice_data{'po_line'} = ( $cust_main->payby eq 'BILL' && $cust_main->payinfo ) ? encode_entities("Purchase Order #". $cust_main->payinfo) @@ -1823,7 +1980,7 @@ sub _items_cust_bill_pkg { my @b = (); foreach my $cust_bill_pkg ( @$cust_bill_pkg ) { - if ( $cust_bill_pkg->pkgnum ) { + if ( $cust_bill_pkg->pkgnum > 0 ) { my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } );