X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=33552523ae921645fc1f18b11af85a42bc5203ea;hb=7a3b9d7929435d128bbb474f51c60919edae01a8;hp=3c941ca6fb87bf6008ab67198420bc6f731ad318;hpb=5edf279b260fd952457af1ab8489eaec7952204a;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 3c941ca6f..33552523a 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -5,6 +5,8 @@ use vars qw( @ISA $conf $money_char ); use vars qw( $invoice_lines @buf ); #yuck use Date::Format; use Text::Template; +use File::Temp 0.14; +use String::ShellQuote; use FS::UID qw( datasrc ); use FS::Record qw( qsearch qsearchs ); use FS::Misc qw( send_email ); @@ -316,51 +318,162 @@ sub owed { $balance; } -=item send + +=item generate_email PARAMHASH + +PARAMHASH can contain the following: + +=over 4 + +=item from => sender address, required + +=item tempate => alternate template name, optional + +=item print_text => text attachment arrayref, optional + +=item subject => email subject, optional + +=back + +Returns an argument list to be passed to L. + +=cut + +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 $email_text; + if ($conf->exists('invoice_email_pdf') + and scalar($conf->config('invoice_email_pdf_note'))) { + + #warn "[FS::cust_bill::send] using 'invoice_email_pdf_note'"; + $email_text = [ map { $_ . "\n" } $conf->config('invoice_email_pdf_note') ]; + } else { + #warn "[FS::cust_bill::send] not using 'invoice_email_pdf_note'"; + if (ref($args{'print_text'}) eq 'ARRAY') { + $email_text = $args{'print_text'}; + } else { + $email_text = [ $self->print_text('', $args{'template'}) ]; + } + } + + my @invoicing_list; + if (ref($args{'to'} eq 'ARRAY')) { + @invoicing_list = @{$args{'to'}}; + } else { + @invoicing_list = grep { $_ ne 'POST' } $self->cust_main->invoicing_list; + } + + return ( + 'from' => $args{'from'}, + 'to' => [ @invoicing_list ], + 'subject' => (($args{'subject'}) ? $args{'subject'} : 'Invoice'), + 'body' => $email_text, + 'mimeparts' => $mimeparts, + ); + + +} + +=item send [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ] Sends this invoice to the destinations configured for this customer: send emails or print. See L. +TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. + +AGENTNUM, if specified, means that this invoice will only be sent for customers +of the specified agent. + +INVOICE_FROM, if specified, overrides the default email invoice From: address. + =cut sub send { - my($self,$template) = @_; + 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 { $_ ne 'POST' } @invoicing_list or !@invoicing_list ) { #email #better to notify this person than silence - @invoicing_list = ($conf->config('invoice_from')) unless @invoicing_list; + @invoicing_list = ($invoice_from) unless @invoicing_list; my $error = send_email( - 'from' => $conf->config('invoice_from'), - 'to' => [ grep { $_ ne 'POST' } @invoicing_list ], - 'subject' => 'Invoice', - 'body' => \@print_text, + $self->generate_email( + 'from' => $invoice_from, + 'to' => [ grep { $_ ne 'POST' } @invoicing_list ], + 'print_text' => [ @print_text ], + ) ); - return "can't send invoice: $error" if $error; + die "can't email invoice: $error\n" if $error; + #die "$error\n" if $error; } - if ( $conf->config('invoice_latex') ) { - @print_text = $self->print_ps('', $template); - } - if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal + @print_text = $self->print_ps('', $template) + if $conf->config('invoice_latex'); my $lpr = $conf->config('lpr'); open(LPR, "|$lpr") - or return "Can't open pipe to $lpr: $!"; + or die "Can't open pipe to $lpr: $!\n"; print LPR @print_text; close LPR - or return $! ? "Error closing $lpr: $!" - : "Exit status $? from $lpr"; + or die $! ? "Error closing $lpr: $!\n" + : "Exit status $? from $lpr\n"; } ''; } +=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ] + +Like B, but only sends the invoice if it is the newest open invoice for +this customer. + +=cut + +sub send_if_newest { + my $self = shift; + + return '' + if scalar( + grep { $_->owed > 0 } + qsearch('cust_bill', { + 'custnum' => $self->custnum, + #'_date' => { op=>'>', value=>$self->_date }, + 'invnum' => { op=>'>', value=>$self->invnum }, + } ) + ); + + $self->send(@_); +} + =item send_csv OPTIONS Sends invoice as a CSV data-file to a remote host with the specified protocol. @@ -654,6 +767,45 @@ sub batch_card { ''; } +sub _agent_template { + my $self = shift; + $self->_agent_plandata('agent_templatename'); +} + +sub _agent_invoice_from { + my $self = shift; + $self->_agent_plandata('agent_invoice_from'); +} + +sub _agent_plandata { + my( $self, $option ) = @_; + + my $part_bill_event = qsearchs( 'part_bill_event', + { + 'payby' => $self->cust_main->payby, + 'plan' => 'send_agent', + 'plandata' => { 'op' => '~', + 'value' => "(^|\n)agentnum ". + $self->cust_main->agentnum. + "(\n|\$)", + }, + }, + '', + 'ORDER BY seconds LIMIT 1' + ); + + return '' unless $part_bill_event; + + if ( $part_bill_event->plandata =~ /^$option (.*)$/m ) { + return $1; + } else { + warn "can't parse part_bill_event eventpart#". $part_bill_event->eventpart. + " plandata for $option"; + return ''; + } + +} + =item print_text [ TIME [ , TEMPLATE ] ] Returns an text invoice, as a list of lines. @@ -665,14 +817,16 @@ L and L for conversion functions. =cut +#still some false laziness w/print_text sub print_text { my( $self, $today, $template ) = @_; $today ||= time; + # my $invnum = $self->invnum; - my $cust_main = qsearchs('cust_main', { 'custnum', $self->custnum } ); + my $cust_main = $self->cust_main; $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') ) - unless $cust_main->payname && $cust_main->payby ne 'CHEK'; + unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/; my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance # my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits @@ -716,7 +870,8 @@ sub print_text { push @buf, [ $description, $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ]; push @buf, - map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels; + map { [ " ". $_->[0]. ": ". $_->[1], '' ] } + $cust_pkg->h_labels($self->_date); } if ( $cust_bill_pkg->recur != 0 ) { @@ -726,7 +881,8 @@ sub print_text { $money_char. sprintf("%10.2f", $cust_bill_pkg->recur) ]; push @buf, - map { [ " ". $_->[0]. ": ". $_->[1], '' ] } $cust_pkg->labels; + map { [ " ". $_->[0]. ": ". $_->[1], '' ] } + $cust_pkg->h_labels($cust_bill_pkg->edate, $cust_bill_pkg->sdate); } push @buf, map { [ " $_", '' ] } $cust_bill_pkg->details; @@ -798,10 +954,11 @@ sub print_text { sprintf("%10.2f", $balance_due ) ]; #create the template + $template ||= $self->_agent_template; my $templatefile = 'invoice_template'; - $templatefile .= "_$template" if $template; + $templatefile .= "_$template" if length($template); my @invoice_template = $conf->config($templatefile) - or die "cannot load config file $templatefile"; + or die "cannot load config file $templatefile"; $invoice_lines = 0; my $wasfunc = 0; foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy @@ -911,7 +1068,7 @@ sub print_latex { # my $invnum = $self->invnum; my $cust_main = $self->cust_main; $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') ) - unless $cust_main->payname && $cust_main->payby ne 'CHEK'; + unless $cust_main->payname && $cust_main->payby !~ /^(CHEK|DCHK)$/; my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance # my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits @@ -923,8 +1080,10 @@ sub print_latex { @buf = (); #create the template + $template ||= $self->_agent_template; my $templatefile = 'invoice_latex'; - $templatefile .= "_$template" if $template; + my $suffix = length($template) ? "_$template" : ''; + $templatefile .= $suffix; my @invoice_template = $conf->config($templatefile) or die "cannot load config file $templatefile"; @@ -941,6 +1100,7 @@ sub print_latex { 'zip' => _latex_escape($cust_main->zip), 'country' => _latex_escape($cust_main->country), 'footer' => join("\n", $conf->config('invoice_latexfooter') ), + 'smallfooter' => join("\n", $conf->config('invoice_latexsmallfooter') ), 'quantity' => 1, 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt', #'notes' => join("\n", $conf->config('invoice_latexnotes') ), @@ -953,10 +1113,11 @@ sub print_latex { $invoice_data{'notes'} = join("\n", map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } - $conf->config('invoice_latexnotes') + $conf->config_orbase('invoice_latexnotes', $suffix) ); $invoice_data{'footer'} =~ s/\n+$//; + $invoice_data{'smallfooter'} =~ s/\n+$//; $invoice_data{'notes'} =~ s/\n+$//; $invoice_data{'po_line'} = @@ -1070,17 +1231,17 @@ sub print_latex { $var; } - my $dir = '/tmp'; #! /usr/local/etc/freeside/invoices.datasrc/ - my $unique = int(rand(2**31)); #UGH... use File::Temp or something - - chdir($dir); - my $file = $self->invnum. ".$unique"; + my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; + my $fh = new File::Temp( TEMPLATE => 'invoice.'. $self->invnum. '.XXXXXXXX', + DIR => $dir, + SUFFIX => '.tex', + UNLINK => 0, + ) or die "can't open temp file: $!\n"; + print $fh join("\n", @filled_in ), "\n"; + close $fh; - open(TEX,">$file.tex") or die "can't open $file.tex: $!\n"; - print TEX join("\n", @filled_in ), "\n"; - close TEX; - - return $file; + $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename; + return $1; } @@ -1100,17 +1261,23 @@ sub print_ps { my $file = $self->print_latex(@_); - #error checking!! - system('pslatex', "$file.tex"); - system('pslatex', "$file.tex"); - system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ); + my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; + chdir($dir); + + my $sfile = shell_quote $file; + + system("pslatex $sfile.tex >/dev/null 2>&1") == 0 + or die "pslatex $file.tex failed; see $file.log for details?\n"; + system("pslatex $sfile.tex >/dev/null 2>&1") == 0 + or die "pslatex $file.tex failed; see $file.log for details?\n"; + + system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0 + or die "dvips failed"; open(POSTSCRIPT, "<$file.ps") - or die "can't open $file.ps (probable error in LaTeX template): $!\n"; + or die "can't open $file.ps: $! (error in LaTeX template?)\n"; - #rm $file.dvi $file.log $file.aux - unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps"); - #unlink("$file.dvi", "$file.log", "$file.aux"); + unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps", "$file.tex"); my $ps = ''; while () { @@ -1139,21 +1306,32 @@ sub print_pdf { my $file = $self->print_latex(@_); + my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; + chdir($dir); + #system('pdflatex', "$file.tex"); #system('pdflatex', "$file.tex"); #! LaTeX Error: Unknown graphics extension: .eps. - #error checking!! - system('pslatex', "$file.tex"); - system('pslatex', "$file.tex"); + my $sfile = shell_quote $file; + + system("pslatex $sfile.tex >/dev/null 2>&1") == 0 + or die "pslatex $file.tex failed: $!"; + system("pslatex $sfile.tex >/dev/null 2>&1") == 0 + or die "pslatex $file.tex failed: $!"; #system('dvipdf', "$file.dvi", "$file.pdf" ); - system("dvips -q -t letter -f $file.dvi | gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$file.pdf -c save pop -"); + system( + "dvips -q -t letter -f $sfile.dvi ". + "| gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$sfile.pdf ". + " -c save pop -" + ) == 0 + or die "dvips | gs failed: $!"; open(PDF, "<$file.pdf") - or die "can't open $file.pdf (probably error in LaTeX tempalte: $!\n"; + or die "can't open $file.pdf: $! (error in LaTeX template?)\n"; - unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf"); + unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf", "$file.tex"); my $pdf = ''; while () { @@ -1177,7 +1355,7 @@ sub print_pdf { sub _latex_escape { my $value = shift; - $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( length($2) ? "\\$2" : '' )/ge; + $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge; $value; } @@ -1267,32 +1445,29 @@ sub _items_cust_bill_pkg { if ( $cust_bill_pkg->setup != 0 ) { my $description = $pkg; $description .= ' Setup' if $cust_bill_pkg->recur != 0; - my @d = (); - @d = $cust_bill_pkg->details if $cust_bill_pkg->recur == 0; + my @d = $cust_pkg->h_labels_short($self->_date); + push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0; push @b, { - 'description' => $description, - #'pkgpart' => $part_pkg->pkgpart, - 'pkgnum' => $cust_pkg->pkgnum, - 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup), - 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] } - $cust_pkg->labels ), - @d, - ], + description => $description, + #pkgpart => $part_pkg->pkgpart, + pkgnum => $cust_pkg->pkgnum, + amount => sprintf("%10.2f", $cust_bill_pkg->setup), + ext_description => \@d, }; } if ( $cust_bill_pkg->recur != 0 ) { push @b, { - 'description' => "$pkg (" . + description => "$pkg (" . time2str('%x', $cust_bill_pkg->sdate). ' - '. time2str('%x', $cust_bill_pkg->edate). ')', - #'pkgpart' => $part_pkg->pkgpart, - 'pkgnum' => $cust_pkg->pkgnum, - 'amount' => sprintf("%10.2f", $cust_bill_pkg->recur), - 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] } - $cust_pkg->labels ), - $cust_bill_pkg->details, - ], + #pkgpart => $part_pkg->pkgpart, + pkgnum => $cust_pkg->pkgnum, + amount => sprintf("%10.2f", $cust_bill_pkg->recur), + ext_description => [ $cust_pkg->h_labels_short($cust_bill_pkg->edate, + $cust_bill_pkg->sdate), + $cust_bill_pkg->details, + ], }; } @@ -1341,7 +1516,7 @@ sub _items_credits { #'description' => 'Credit ref\#'. $_->crednum. # " (". time2str("%x",$_->cust_credit->_date) .")". # $reason, - 'description' => 'Credit applied'. + 'description' => 'Credit applied '. time2str("%x",$_->cust_credit->_date). $reason, 'amount' => sprintf("%10.2f",$_->amount), }; @@ -1386,9 +1561,6 @@ The delete method. print_text formatting (and some logic :/) is in source, but needs to be slurped in from a file. Also number of lines ($=). -missing print_ps for a nice postscript copy (maybe HylaFAX-cover-page-style -or something similar so the look can be completely customized?) - =head1 SEE ALSO L, L, L, L,