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 );
+use FS::Misc qw( send_email send_fax );
use FS::cust_main;
use FS::cust_bill_pkg;
use FS::cust_credit;
$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<FS::Misc::send_email>.
+
+=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 { $_ !~ /^(POST|FAX)$/ } $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<FS::cust_main_invoice>.
+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
+ if ( grep { $_ !~ /^(POST|FAX)$/ } @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 { $_ !~ /^(POST|FAX)$/ } @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 { $_ =~ /^(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";
+ }
+
+ if ( grep { $_ eq 'FAX' } @invoicing_list ) { #fax
+ unless ($conf->exists('invoice_latex')) {
+ die 'FAX invoice destination not supported with plain text invoices.'
+ }
+ my $dialstring = $self->cust_main->getfield('fax');
+ #Check $dialstring?
+ my $error = send_fax(docdata => $lpr_data, dialstring => $dialstring);
+ die $error if $error;
+ }
- if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal
- my $lpr = $conf->config('lpr');
- open(LPR, "|$lpr")
- or return "Can't open pipe to $lpr: $!";
- print LPR @print_text;
- close LPR
- or return $! ? "Error closing $lpr: $!"
- : "Exit status $? from $lpr";
}
'';
}
+=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
+
+Like B<send>, 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.
'state' => $cust_main->getfield('state'),
'zip' => $cust_main->getfield('zip'),
'country' => $cust_main->getfield('country'),
- 'cardnum' => $cust_main->getfield('payinfo'),
+ 'cardnum' => $cust_main->payinfo,
'exp' => $cust_main->getfield('paydate'),
'payname' => $cust_main->getfield('payname'),
'amount' => $self->owed,
'';
}
+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.
=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
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 ) {
$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;
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
# 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
@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";
'zip' => _latex_escape($cust_main->zip),
'country' => _latex_escape($cust_main->country),
'footer' => join("\n", $conf->config('invoice_latexfooter') ),
- 'smallfooter' => $conf->config('invoice_latexsmallfooter'),
+ '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') ),
$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'} =
$var;
}
- my $dir = '/tmp'; #! /usr/local/etc/freeside/invoices.datasrc/
- my $unique = int(rand(2**31)); #UGH... use File::Temp or something
+ 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;
- chdir($dir);
- my $file = $self->invnum. ".$unique";
-
- 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;
}
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 (<POSTSCRIPT>) {
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 (<PDF>) {
sub _latex_escape {
my $value = shift;
- $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( length($2) ? "\\$2" : '' )/ge;
+ $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( ( defined($2) && length($2) ) ? "\\$2" : '' )/ge;
$value;
}
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,
+ ],
};
}
#'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),
};
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<FS::Record>, L<FS::cust_main>, L<FS::cust_bill_pay>, L<FS::cust_pay>,