X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_bill.pm;h=46809f9a0a8c3bd805009d3ad0cf827ac5a27e62;hb=6ed5d51b3a72e2935dd5d084c9e24567150b03ca;hp=a9bb7452b7d530d17215319842e0bd21a8e8f0b8;hpb=6cd950830fa622fa6cbe87895dc58fb1c9868eef;p=freeside.git diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index a9bb7452b..46809f9a0 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -3,6 +3,7 @@ package FS::cust_bill; use strict; use vars qw( @ISA $DEBUG $conf $money_char ); use vars qw( $invoice_lines @buf ); #yuck +use IPC::Run3; use Date::Format; use Text::Template 1.20; use File::Temp 0.14; @@ -10,8 +11,9 @@ use String::ShellQuote; use HTML::Entities; use Locale::Country; use FS::UID qw( datasrc ); -use FS::Record qw( qsearch qsearchs ); use FS::Misc qw( send_email send_fax ); +use FS::Record qw( qsearch qsearchs ); +use FS::cust_main_Mixin; use FS::cust_main; use FS::cust_bill_pkg; use FS::cust_credit; @@ -24,7 +26,7 @@ use FS::part_pkg; use FS::cust_bill_pay; use FS::part_bill_event; -@ISA = qw( FS::Record ); +@ISA = qw( FS::cust_main_Mixin FS::Record ); $DEBUG = 0; @@ -104,6 +106,13 @@ Invoices are normally created by calling the bill method of a customer object sub table { 'cust_bill'; } +sub cust_linked { $_[0]->cust_main_custnum; } +sub cust_unlinked_msg { + my $self = shift; + "WARNING: can't find cust_main.custnum ". $self->custnum. + ' (cust_bill.invnum '. $self->invnum. ')'; +} + =item insert Adds this invoice to the database ("Posts" the invoice). If there is an error, @@ -414,10 +423,21 @@ sub generate_email { $args{'from'} =~ /\@([\w\.\-]+)/ or $1 = 'example.com'; my $content_id = join('.', rand()*(2**32), $$, time). "\@$1"; + my $path = "$FS::UID::conf_dir/conf.$FS::UID::datasrc"; + my $file; + if ( defined($args{'_template'}) && length($args{'_template'}) + && -e "$path/logo_". $args{'_template'}. ".png" + ) + { + $file = "$path/logo_". $args{'_template'}. ".png"; + } else { + $file = "$path/logo.png"; + } + my $image = build MIME::Entity 'Type' => 'image/png', 'Encoding' => 'base64', - 'Path' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc/logo.png", + 'Path' => $file, 'Filename' => 'logo.png', 'Content-ID' => "<$content_id>", ; @@ -544,8 +564,8 @@ sub mimebuild_pdf { =item send [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ] -Sends this invoice to the destinations configured for this customer: send -emails or print. See L. +Sends this invoice to the destinations configured for this customer: sends +email, prints and/or faxes. See L. TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. @@ -623,7 +643,7 @@ sub email { =item lpr_data [ TEMPLATENAME ] -Returns the postscript or plaintext for this invoice. +Returns the postscript or plaintext for this invoice as an arrayref. TEMPLATENAME, if specified, is the name of a suffix for alternate invoices. @@ -649,12 +669,14 @@ sub print { 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"; + + my $outerr = ''; + run3 $lpr, $self->lpr_data($template), \$outerr, \$outerr; + if ( $? ) { + $outerr = ": $outerr" if length($outerr); + die "Error from $lpr (exit status ". ($?>>8). ")$outerr\n"; + } + } =item fax [ TEMPLATENAME ] @@ -807,7 +829,7 @@ sub send_csv { ) or die "can't create csv"; print CSV $csv->string. "\n"; - #new charges (false laziness w/print_text) + #new charges (false laziness w/print_text and _items stuff) foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { my($pkg, $setup, $recur, $sdate, $edate); @@ -1017,7 +1039,9 @@ sub _agent_plandata { 'plan' => 'send_agent', 'plandata' => { 'op' => '~', 'value' => "(^|\n)agentnum ". + '([0-9]*, )*'. $self->cust_main->agentnum. + '(, [0-9]*)*'. "(\n|\$)", }, }, @@ -1048,7 +1072,7 @@ L and L for conversion functions. =cut -#still some false laziness w/print_text +#still some false laziness w/_items stuff (and send_csv) sub print_text { my( $self, $today, $template ) = @_; @@ -1089,50 +1113,49 @@ sub print_text { ( grep { ! $_->pkgnum } $self->cust_bill_pkg ), #then taxes ) { - if ( $cust_bill_pkg->pkgnum > 0 ) { + my $desc = $cust_bill_pkg->desc; - my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); - my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); - my $pkg = $part_pkg->pkg; + if ( $cust_bill_pkg->pkgnum > 0 ) { if ( $cust_bill_pkg->setup != 0 ) { - my $description = $pkg; + my $description = $desc; $description .= ' Setup' if $cust_bill_pkg->recur != 0; push @buf, [ $description, $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ]; push @buf, map { [ " ". $_->[0]. ": ". $_->[1], '' ] } - $cust_pkg->h_labels($self->_date); + $cust_bill_pkg->cust_pkg->h_labels($self->_date); } if ( $cust_bill_pkg->recur != 0 ) { push @buf, [ - "$pkg (" . time2str("%x", $cust_bill_pkg->sdate) . " - " . - time2str("%x", $cust_bill_pkg->edate) . ")", + "$desc (" . time2str("%x", $cust_bill_pkg->sdate) . " - " . + time2str("%x", $cust_bill_pkg->edate) . ")", $money_char. sprintf("%10.2f", $cust_bill_pkg->recur) ]; push @buf, map { [ " ". $_->[0]. ": ". $_->[1], '' ] } - $cust_pkg->h_labels($cust_bill_pkg->edate, $cust_bill_pkg->sdate); + $cust_bill_pkg->cust_pkg->h_labels( $cust_bill_pkg->edate, + $cust_bill_pkg->sdate ); } push @buf, map { [ " $_", '' ] } $cust_bill_pkg->details; } else { #pkgnum tax or one-shot line item - my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc') - ? ( $cust_bill_pkg->itemdesc || 'Tax' ) - : 'Tax'; + if ( $cust_bill_pkg->setup != 0 ) { - push @buf, [ $itemdesc, + push @buf, [ $desc, $money_char. sprintf("%10.2f", $cust_bill_pkg->setup) ]; } if ( $cust_bill_pkg->recur != 0 ) { - push @buf, [ "$itemdesc (". time2str("%x", $cust_bill_pkg->sdate). " - " - . time2str("%x", $cust_bill_pkg->edate). ")", + push @buf, [ "$desc (". time2str("%x", $cust_bill_pkg->sdate). " - " + . time2str("%x", $cust_bill_pkg->edate). ")", $money_char. sprintf("%10.2f", $cust_bill_pkg->recur) ]; } + } + } push @buf,['','-----------']; @@ -1292,7 +1315,7 @@ L and L for conversion functions. =cut -#still some false laziness w/print_text +#still some false laziness w/print_text (mostly print_text should use _items stuff though) sub print_latex { my( $self, $today, $template ) = @_; @@ -1336,11 +1359,10 @@ sub print_latex { } my $returnaddress; - if ( $conf->exists('invoice_latexreturnaddress') - && length($conf->exists('invoice_latexreturnaddress')) - ) - { - $returnaddress = join("\n", $conf->config('invoice_latexreturnaddress') ); + if ( length($conf->config_orbase('invoice_latexreturnaddress', $template)) ) { + $returnaddress = join("\n", + $conf->config_orbase('invoice_latexreturnaddress', $template) + ); } else { $returnaddress = '~'; } @@ -1357,8 +1379,8 @@ sub print_latex { 'city' => _latex_escape($cust_main->city), 'state' => _latex_escape($cust_main->state), 'zip' => _latex_escape($cust_main->zip), - 'footer' => join("\n", $conf->config('invoice_latexfooter') ), - 'smallfooter' => join("\n", $conf->config('invoice_latexsmallfooter') ), + 'footer' => join("\n", $conf->config_orbase('invoice_latexfooter', $template) ), + 'smallfooter' => join("\n", $conf->config_orbase('invoice_latexsmallfooter', $template) ), 'returnaddress' => $returnaddress, 'quantity' => 1, 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt', @@ -1759,19 +1781,29 @@ sub print_html { 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt', 'cid' => $cid, + 'template' => $template, # '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 { + if ( + defined( $conf->config_orbase('invoice_htmlreturnaddress', $template) ) + && length( $conf->config_orbase('invoice_htmlreturnaddress', $template) ) + ) { + $invoice_data{'returnaddress'} = + join("\n", $conf->config('invoice_htmlreturnaddress', $template) ); + } else { + $invoice_data{'returnaddress'} = + join("\n", map { s/~/ /g; s/\\\\\*?\s*$/
/; s/\\hyphenation\{[\w\s\-]+\}//; $_; } - $conf->config('invoice_latexreturnaddress') + $conf->config_orbase( 'invoice_latexreturnaddress', + $template + ) ); + } my $countrydefault = $conf->config('countrydefault') || 'US'; if ( $cust_main->country eq $countrydefault ) { @@ -1781,20 +1813,26 @@ sub print_html { encode_entities(code2country($cust_main->country)); } - $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) - ); + if ( + defined( $conf->config_orbase('invoice_htmlnotes', $template) ) + && length( $conf->config_orbase('invoice_htmlnotes', $template) ) + ) { + $invoice_data{'notes'} = + join("\n", $conf->config_orbase('invoice_htmlnotes', $template) ); + } else { + $invoice_data{'notes'} = + 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'} = @@ -1803,11 +1841,18 @@ sub print_html { # $conf->config_orbase('invoice_latexnotes', $suffix) # ); - $invoice_data{'footer'} = $conf->exists('invoice_htmlfooter') - ? join("\n", $conf->config('invoice_htmlfooter') ) - : join("\n", map { s/~/ /g; s/\\\\\*?\s*$/
      /; $_; } - $conf->config('invoice_latexfooter') + if ( + defined( $conf->config_orbase('invoice_htmlfooter', $template) ) + && length( $conf->config_orbase('invoice_htmlfooter', $template) ) + ) { + $invoice_data{'footer'} = + join("\n", $conf->config_orbase('invoice_htmlfooter', $template) ); + } else { + $invoice_data{'footer'} = + join("\n", map { s/~/ /g; s/\\\\\*?\s*$/
      /; $_; } + $conf->config_orbase('invoice_latexfooter', $template) ); + } $invoice_data{'po_line'} = ( $cust_main->payby eq 'BILL' && $cust_main->payinfo ) @@ -1984,21 +2029,19 @@ sub _items_cust_bill_pkg { my @b = (); foreach my $cust_bill_pkg ( @$cust_bill_pkg ) { - if ( $cust_bill_pkg->pkgnum > 0 ) { + my $desc = $cust_bill_pkg->desc; - my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); - my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); - my $pkg = $part_pkg->pkg; + if ( $cust_bill_pkg->pkgnum > 0 ) { if ( $cust_bill_pkg->setup != 0 ) { - my $description = $pkg; + my $description = $desc; $description .= ' Setup' if $cust_bill_pkg->recur != 0; - my @d = $cust_pkg->h_labels_short($self->_date); + my @d = $cust_bill_pkg->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, + pkgnum => $cust_bill_pkg->pkgnum, amount => sprintf("%.2f", $cust_bill_pkg->setup), ext_description => \@d, }; @@ -2006,33 +2049,31 @@ sub _items_cust_bill_pkg { if ( $cust_bill_pkg->recur != 0 ) { push @b, { - description => "$pkg (" . + description => "$desc (" . time2str('%x', $cust_bill_pkg->sdate). ' - '. time2str('%x', $cust_bill_pkg->edate). ')', #pkgpart => $part_pkg->pkgpart, - pkgnum => $cust_pkg->pkgnum, + pkgnum => $cust_bill_pkg->pkgnum, amount => sprintf("%.2f", $cust_bill_pkg->recur), - ext_description => [ $cust_pkg->h_labels_short($cust_bill_pkg->edate, - $cust_bill_pkg->sdate), - $cust_bill_pkg->details, - ], + ext_description => + [ $cust_bill_pkg->cust_pkg->h_labels_short( $cust_bill_pkg->edate, + $cust_bill_pkg->sdate), + $cust_bill_pkg->details, + ], }; } } else { #pkgnum tax or one-shot line item (??) - my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc') - ? ( $cust_bill_pkg->itemdesc || 'Tax' ) - : 'Tax'; if ( $cust_bill_pkg->setup != 0 ) { push @b, { - 'description' => $itemdesc, + 'description' => $desc, 'amount' => sprintf("%.2f", $cust_bill_pkg->setup), }; } if ( $cust_bill_pkg->recur != 0 ) { push @b, { - 'description' => "$itemdesc (". + 'description' => "$desc (". time2str("%x", $cust_bill_pkg->sdate). ' - '. time2str("%x", $cust_bill_pkg->edate). ')', 'amount' => sprintf("%.2f", $cust_bill_pkg->recur), @@ -2102,6 +2143,122 @@ sub _items_payments { =back +=head1 SUBROUTINES + +=over 4 + +=item reprint + +=cut + +sub process_reprint { + process_re_X('print', @_); +} + +=item reemail + +=cut + +sub process_reemail { + process_re_X('email', @_); +} + +=item refax + +=cut + +sub process_refax { + process_re_X('fax', @_); +} + +use Storable qw(thaw); +use Data::Dumper; +use MIME::Base64; +sub process_re_X { + my( $method, $job ) = ( shift, shift ); + + my $param = thaw(decode_base64(shift)); + warn Dumper($param) if $DEBUG; + + re_X( + $method, + $job, + %$param, + ); + +} + +sub re_X { + my($method, $job, %param ) = @_; +# [ 'begin', 'end', 'agentnum', 'open', 'days', 'newest_percust' ], + + #some false laziness w/search/cust_bill.html + my $distinct = ''; + my $orderby = 'ORDER BY cust_bill._date'; + + my @where; + + if ( $param{'begin'} =~ /^(\d+)$/ ) { + push @where, "cust_bill._date >= $1"; + } + if ( $param{'end'} =~ /^(\d+)$/ ) { + push @where, "cust_bill._date < $1"; + } + if ( $param{'agentnum'} =~ /^(\d+)$/ ) { + push @where, "cust_main.agentnum = $1"; + } + + my $owed = + "charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay + WHERE cust_bill_pay.invnum = cust_bill.invnum ) + - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill + WHERE cust_credit_bill.invnum = cust_bill.invnum )"; + + push @where, "0 != $owed" + if $param{'open'}; + + push @where, "cust_bill._date < ". (time-86400*$param{'days'}) + if $param{'days'}; + + my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; + + my $addl_from = 'left join cust_main using ( custnum )'; + + if ( $param{'newest_percust'} ) { + $distinct = 'DISTINCT ON ( cust_bill.custnum )'; + $orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC'; + #$count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'"; + } + + my @cust_bill = qsearch( 'cust_bill', + {}, + "$distinct cust_bill.*", + $extra_sql, + '', + $addl_from + ); + + my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo + foreach my $cust_bill ( @cust_bill ) { + $cust_bill->$method(); + + if ( $job ) { #progressbar foo + $num++; + if ( time - $min_sec > $last ) { + my $error = $job->update_statustext( + int( 100 * $num / scalar(@cust_bill) ) + ); + die $error if $error; + $last = time; + } + } + + } + +} + +=back + =head1 BUGS The delete method.