X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=e70bb324740ee9502c4bd805bbe82f3b4195916d;hb=30189fa2e5987bafeb4714a83b7f130c568b221c;hp=3b76dd257e6504954791802d3e6b567d3146fd66;hpb=d99c56a3a435d202c4503f6f62895d8111ac41f3;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 3b76dd257..e70bb3247 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -6,6 +6,7 @@ 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 ); @@ -317,7 +318,81 @@ sub owed { $balance; } -=item send [ TEMPLATENAME [ , AGENTNUM ] ] + +=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. @@ -327,12 +402,18 @@ 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 = shift; my $template = scalar(@_) ? shift : ''; - return '' if scalar(@_) && $_[0] && $self->cust_main->agentnum ne 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; @@ -340,23 +421,23 @@ sub send { 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 ], + ) ); 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 die "Can't open pipe to $lpr: $!\n"; @@ -370,6 +451,29 @@ sub send { } +=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. @@ -652,7 +756,7 @@ sub batch_card { '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, @@ -665,24 +769,38 @@ 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'); +} - my $cust_bill_event = qsearchs( 'part_bill_event', +sub _agent_plandata { + my( $self, $option ) = @_; + + my $part_bill_event = qsearchs( 'part_bill_event', { 'payby' => $self->cust_main->payby, 'plan' => 'send_agent', - 'eventcode' => { 'op' => 'LIKE', - 'value' => '_%, '. $self->cust_main->agentnum. ');' }, + 'plandata' => { 'op' => '~', + 'value' => "(^|\n)agentnum ". + $self->cust_main->agentnum. + "(\n|\$)", + }, }, '', 'ORDER BY seconds LIMIT 1' ); - return '' unless $cust_bill_event; + return '' unless $part_bill_event; - if ( $cust_bill_event->eventcode =~ /\(\s*'(.*)'\s*,\s*(\d+)\s*\)\;$/ ) { + if ( $part_bill_event->plandata =~ /^$option (.*)$/m ) { return $1; } else { - warn "can't parse eventcode for agent-specific invoice template"; + warn "can't parse part_bill_event eventpart#". $part_bill_event->eventpart. + " plandata for $option"; return ''; } @@ -699,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 @@ -750,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 ) { @@ -760,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; @@ -946,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 @@ -1142,13 +1264,15 @@ sub print_ps { my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; chdir($dir); - system("pslatex $file.tex >/dev/null 2>&1") == 0 - or die "pslatex $file.tex failed: $!"; - system("pslatex $file.tex >/dev/null 2>&1") == 0 - or die "pslatex $file.tex failed: $!"; + 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 "dbips failed: $!"; + or die "dvips failed"; open(POSTSCRIPT, "<$file.ps") or die "can't open $file.ps: $! (error in LaTeX template?)\n"; @@ -1189,15 +1313,17 @@ sub print_pdf { #system('pdflatex', "$file.tex"); #! LaTeX Error: Unknown graphics extension: .eps. - system("pslatex $file.tex >/dev/null 2>&1") == 0 + my $sfile = shell_quote $file; + + system("pslatex $sfile.tex >/dev/null 2>&1") == 0 or die "pslatex $file.tex failed: $!"; - system("pslatex $file.tex >/dev/null 2>&1") == 0 + 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 ". + "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: $!"; @@ -1229,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; } @@ -1316,45 +1442,32 @@ sub _items_cust_bill_pkg { my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); my $pkg = $part_pkg->pkg; - my %labels; - #tie %labels, 'Tie::IxHash'; - push @{ $labels{$_->[0]} }, $_->[1] foreach $cust_pkg->labels; - my @ext_description; - foreach my $label ( keys %labels ) { - my @values = @{ $labels{$label} }; - my $num = scalar(@values); - if ( $num > 5 ) { - push @ext_description, "$label ($num)"; - } else { - push @ext_description, map { "$label: $_" } @values; - } - } - if ( $cust_bill_pkg->setup != 0 ) { my $description = $pkg; $description .= ' Setup' if $cust_bill_pkg->recur != 0; - my @d = @ext_description; + 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' => \@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' => [ @ext_description, - $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, + ], }; } @@ -1403,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), };