use HTML::Entities;
use Locale::Country;
use FS::UID qw( datasrc );
-use FS::Misc qw( send_email send_fax generate_ps do_print );
+use FS::Misc qw( send_email send_fax generate_ps generate_pdf do_print );
use FS::Record qw( qsearch qsearchs dbh );
use FS::cust_main_Mixin;
use FS::cust_main;
'Encoding' => 'base64',
'Data' => [ $self->print_pdf(@_) ],
'Disposition' => 'attachment',
- 'Filename' => 'invoice.pdf',
+ 'Filename' => 'invoice-'. $self->invnum. '.pdf',
);
}
INVOICE_FROM, if specified, overrides the default email invoice From: address.
+AMOUNT, if specified, only sends the invoice if the total amount owed on this
+invoice and all older invoices is greater than the specified amount.
+
=cut
sub queueable_send {
? shift
: ( $self->_agent_invoice_from || $conf->config('invoice_from') );
+ my $balance_over = ( scalar(@_) && $_[0] !~ /^\s*$/ ) ? shift : 0;
+
+ return ''
+ unless $self->cust_main->total_owed_date($self->_date) > $balance_over;
+
my @invoicing_list = $self->cust_main->invoicing_list;
+ #$self->email_invoice($template, $invoice_from)
$self->email($template, $invoice_from)
if grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list;
+ #$self->print_invoice($template)
$self->print($template)
if grep { $_ eq 'POST' } @invoicing_list; #postal
- $self->fax($template)
+ $self->fax_invoice($template)
if grep { $_ eq 'FAX' } @invoicing_list; #fax
'';
}
+#sub email_invoice {
sub email {
my $self = shift;
my $template = scalar(@_) ? shift : '';
#better to notify this person than silence
@invoicing_list = ($invoice_from) unless @invoicing_list;
+ my $subject = $self->email_subject($template);
+
my $error = send_email(
$self->generate_email(
'from' => $invoice_from,
'to' => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ],
+ 'subject' => $subject,
'template' => $template,
)
);
}
+sub email_subject {
+ my $self = shift;
+
+ #my $template = scalar(@_) ? shift : '';
+ #per-template?
+
+ my $subject = $conf->config('invoice_subject') || 'Invoice';
+
+ my $cust_main = $self->cust_main;
+ my $name = $cust_main->name;
+ my $name_short = $cust_main->name_short;
+ my $invoice_number = $self->invnum;
+ my $invoice_date = $self->_date_pretty;
+
+ eval qq("$subject");
+}
+
=item lpr_data [ TEMPLATENAME ]
Returns the postscript or plaintext for this invoice as an arrayref.
=cut
+#sub print_invoice {
sub print {
my $self = shift;
my $template = scalar(@_) ? shift : '';
do_print $self->lpr_data($template);
}
-=item fax [ TEMPLATENAME ]
+=item fax_invoice [ TEMPLATENAME ]
Faxes this invoice.
=cut
-sub fax {
+sub fax_invoice {
my $self = shift;
my $template = scalar(@_) ? shift : '';
}
+=item ftp_invoice [ TEMPLATENAME ]
+
+Sends this invoice data via FTP.
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub ftp_invoice {
+ my $self = shift;
+ my $template = scalar(@_) ? shift : '';
+
+ $self->send_csv(
+ 'protocol' => 'ftp',
+ 'server' => $conf->config('cust_bill-ftpserver'),
+ 'username' => $conf->config('cust_bill-ftpusername'),
+ 'password' => $conf->config('cust_bill-ftppassword'),
+ 'dir' => $conf->config('cust_bill-ftpdir'),
+ 'format' => $conf->config('cust_bill-ftpformat'),
+ );
+}
+
+=item spool_invoice [ TEMPLATENAME ]
+
+Spools this invoice data (see L<FS::spool_csv>)
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub spool_invoice {
+ my $self = shift;
+ my $template = scalar(@_) ? shift : '';
+
+ $self->spool_csv(
+ 'format' => $conf->config('cust_bill-spoolformat'),
+ 'agent_spools' => $conf->exists('cust_bill-spoolagent'),
+ );
+}
+
=item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
Like B<send>, but only sends the invoice if it is the newest open invoice for
my $taxtotal = 0;
$taxtotal += $_->{'amount'} foreach $self->_items_tax;
- my $duedate = '';
- if ( $conf->exists('invoice_default_terms')
- && $conf->config('invoice_default_terms')=~ /^\s*Net\s*(\d+)\s*$/ ) {
- $duedate = time2str("%m/%d/%Y", $self->_date + ($1*86400) );
- }
+ my $duedate = $self->balance_due_date;
my( $previous_balance, @unused ) = $self->previous; #previous balance
@buf = ();
#previous balance
- foreach ( @pr_cust_bill ) {
- push @buf, [
- "Previous Balance, Invoice #". $_->invnum.
- " (". time2str("%x",$_->_date). ")",
- $money_char. sprintf("%10.2f",$_->owed)
- ];
- }
- if (@pr_cust_bill) {
- push @buf,['','-----------'];
- push @buf,[ 'Total Previous Balance',
- $money_char. sprintf("%10.2f",$pr_total ) ];
- push @buf,['',''];
+ unless ($conf->exists('disable_previous_balance')) {
+ foreach ( @pr_cust_bill ) {
+ push @buf, [
+ "Previous Balance, Invoice #". $_->invnum.
+ " (". time2str("%x",$_->_date). ")",
+ $money_char. sprintf("%10.2f",$_->owed)
+ ];
+ }
+ if (@pr_cust_bill) {
+ push @buf,['','-----------'];
+ push @buf,[ 'Total Previous Balance',
+ $money_char. sprintf("%10.2f",$pr_total ) ];
+ push @buf,['',''];
+ }
}
#new charges
}
push @buf,['','-----------'];
- push @buf,['Total New Charges',
+ push @buf,[ ( $conf->exists('disable_previous_balance')
+ ? 'Total Charges'
+ : 'Total New Charges'),
$money_char. sprintf("%10.2f",$self->charged) ];
push @buf,['',''];
- push @buf,['','-----------'];
- push @buf,['Total Charges',
- $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
- push @buf,['',''];
+ unless ($conf->exists('disable_previous_balance')) {
+ push @buf,['','-----------'];
+ push @buf,['Total Charges',
+ $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
+ push @buf,['',''];
- #credits
- foreach ( $self->cust_credited ) {
+ #credits
+ foreach ( $self->cust_credited ) {
- #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+ #something more elaborate if $_->amount ne $_->cust_credit->credited ?
- my $reason = substr($_->cust_credit->reason,0,32);
- $reason .= '...' if length($reason) < length($_->cust_credit->reason);
- $reason = " ($reason) " if $reason;
- push @buf,[
- "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
- $reason,
- $money_char. sprintf("%10.2f",$_->amount)
- ];
- }
- #foreach ( @cr_cust_credit ) {
- # push @buf,[
- # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
- # $money_char. sprintf("%10.2f",$_->credited)
- # ];
- #}
+ my $reason = substr($_->cust_credit->reason,0,32);
+ $reason .= '...' if length($reason) < length($_->cust_credit->reason);
+ $reason = " ($reason) " if $reason;
+ push @buf,[
+ "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
+ $reason,
+ $money_char. sprintf("%10.2f",$_->amount)
+ ];
+ }
+ #foreach ( @cr_cust_credit ) {
+ # push @buf,[
+ # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
+ # $money_char. sprintf("%10.2f",$_->credited)
+ # ];
+ #}
- #get & print payments
- foreach ( $self->cust_bill_pay ) {
+ #get & print payments
+ foreach ( $self->cust_bill_pay ) {
- #something more elaborate if $_->amount ne ->cust_pay->paid ?
+ #something more elaborate if $_->amount ne ->cust_pay->paid ?
- push @buf,[
- "Payment received ". time2str("%x",$_->cust_pay->_date ),
- $money_char. sprintf("%10.2f",$_->amount )
- ];
- }
+ push @buf,[
+ "Payment received ". time2str("%x",$_->cust_pay->_date ),
+ $money_char. sprintf("%10.2f",$_->amount )
+ ];
+ }
- #balance due
- my $balance_due_msg = $self->balance_due_msg;
+ #balance due
+ my $balance_due_msg = $self->balance_due_msg;
- push @buf,['','-----------'];
- push @buf,[$balance_due_msg, $money_char.
- sprintf("%10.2f", $balance_due ) ];
+ push @buf,['','-----------'];
+ push @buf,[$balance_due_msg, $money_char.
+ sprintf("%10.2f", $balance_due ) ];
+ }
#create the template
$template ||= $self->_agent_template;
'date' => time2str('%b %o, %Y', $self->_date),
'today' => time2str('%b %o, %Y', $today),
'agent' => _latex_escape($cust_main->agent->agent),
+ 'agent_custid' => _latex_escape($cust_main->agent_custid),
'payname' => _latex_escape($cust_main->payname),
'company' => _latex_escape($cust_main->company),
'address1' => _latex_escape($cust_main->address1),
'address2' => _latex_escape($cust_main->address2),
'city' => _latex_escape($cust_main->city),
'state' => _latex_escape($cust_main->state),
+ #'quantity' => 1,
'zip' => _latex_escape($cust_main->zip),
+ 'fax' => _latex_escape($cust_main->fax),
'footer' => join("\n", $conf->config_orbase('invoice_latexfooter', $template) ),
'smallfooter' => join("\n", $conf->config_orbase('invoice_latexsmallfooter', $template) ),
'returnaddress' => $returnaddress,
'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt',
#'notes' => join("\n", $conf->config('invoice_latexnotes') ),
'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
+ 'current_charges' => sprintf('%.2f', $self->charged ),
+ 'previous_balance' => sprintf("%.2f", $pr_total),
+ 'balance' => sprintf("%.2f", $balance_due),
+ 'duedate' => $self->balance_due_date,
+ 'ship_enable' => $conf->exists('invoice-ship_address'),
+ 'unitprices' => $conf->exists('invoice-unitprice'),
);
my $countrydefault = $conf->config('countrydefault') || 'US';
+ my $prefix = $cust_main->has_ship_address ? 'ship_' : '';
+ foreach ( qw( contact company address1 address2 city state zip country fax) ){
+ my $method = $prefix.$_;
+ $invoice_data{"ship_$_"} = _latex_escape($cust_main->$method);
+ }
+ $invoice_data{'ship_country'} = ''
+ if ( $invoice_data{'ship_country'} eq $countrydefault );
+
if ( $cust_main->country eq $countrydefault ) {
$invoice_data{'country'} = '';
} else {
warn "invoice notes: ". $invoice_data{'notes'}. "\n"
if $DEBUG;
+ #do variable substitution in coupon
+ foreach my $include (qw( coupon )) {
+
+ my @inc_src = $conf->config_orbase("invoice_latex$include", $template);
+
+ my $inc_tt = new Text::Template (
+ TYPE => 'ARRAY',
+ SOURCE => [ map "$_\n", @inc_src ],
+ DELIMITERS => [ '[@--', '--@]' ],
+ ) or die "Can't create new Text::Template object: $Text::Template::ERROR";
+
+ unless ( $inc_tt->compile() ) {
+ my $error = "Can't compile $include template: $Text::Template::ERROR\n";
+ warn $error. "Template:\n". join('', map "$_\n", @inc_src);
+ die $error;
+ }
+
+ $invoice_data{$include} = $inc_tt->fill_in( HASH => \%invoice_data );
+
+ $invoice_data{$include} =~ s/\n+$//
+ }
+
$invoice_data{'footer'} =~ s/\n+$//;
$invoice_data{'smallfooter'} =~ s/\n+$//;
$invoice_data{'notes'} =~ s/\n+$//;
!~ /^%%EndDetail\s*$/ ) {
push @line_item, $line_item_line;
}
- foreach my $line_item ( $self->_items ) {
+ foreach my $line_item ( $self->_items ) { #( 'format'=>'latex' ) ) {
#foreach my $line_item ( $self->_items_pkg ) {
$invoice_data{'ref'} = $line_item->{'pkgnum'};
$invoice_data{'description'} =
map _latex_escape($_), @{$line_item->{'ext_description'}}
);
}
- $invoice_data{'amount'} = $line_item->{'amount'};
+ $invoice_data{'amount'} = $line_item->{'amount'};
+ $invoice_data{'unit_amount'} = $line_item->{'unit_amount'};
+ $invoice_data{'quantity'} = $line_item->{'quantity'};
$invoice_data{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
push @filled_in,
map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } @line_item;
$invoice_data{'detail_items'} = \@detail_items;
$invoice_data{'total_items'} = \@total_items;
- foreach my $line_item ( $self->_items ) {
+ my %options = ( 'format' => 'latex', 'escape_function' => \&_latex_escape );
+ foreach my $line_item ( ($conf->exists('disable_previous_balance') ? qw() : $self->_items_previous(%options)), $self->_items_pkg(%options) ) {
my $detail = {
ext_description => [],
};
$detail->{'quantity'} = 1;
$detail->{'description'} = _latex_escape($line_item->{'description'});
if ( exists $line_item->{'ext_description'} ) {
- @{$detail->{'ext_description'}} = map {
- _latex_escape($_);
- } @{$line_item->{'ext_description'}};
+ @{$detail->{'ext_description'}} = @{$line_item->{'ext_description'}};
}
$detail->{'amount'} = $line_item->{'amount'};
+ $detail->{'unit_amount'} = $line_item->{'unit_amount'};
$detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
push @detail_items, $detail;
}
if ( $taxtotal ) {
+ $invoice_data{'taxtotal'} = sprintf('%.2f', $taxtotal);
my $total = {};
$total->{'total_item'} = 'Sub-total';
$total->{'total_amount'} =
'\dollar '. sprintf('%.2f', $self->charged - $taxtotal );
unshift @total_items, $total;
+ }else{
+ $invoice_data{'taxtotal'} = '0.00';
}
{
my $total = {};
$total->{'total_item'} = '\textbf{Total}';
$total->{'total_amount'} =
- '\textbf{\dollar '. sprintf('%.2f', $self->charged + $pr_total ). '}';
+ '\textbf{\dollar '.
+ sprintf( '%.2f',
+ $self->charged + ( $conf->exists('disable_previous_balance')
+ ? 0
+ : $pr_total
+ )
+ ).
+ '}';
push @total_items, $total;
}
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
+ unless ($conf->exists('disable_previous_balance')) {
+ #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
- # credits
- foreach my $credit ( $self->_items_credits ) {
- my $total;
- $total->{'total_item'} = _latex_escape($credit->{'description'});
- #$credittotal
- $total->{'total_amount'} = '-\dollar '. $credit->{'amount'};
- push @total_items, $total;
- }
+ # credits
+ my $credittotal = 0;
+ foreach my $credit ( $self->_items_credits ) {
+ my $total;
+ $total->{'total_item'} = _latex_escape($credit->{'description'});
+ $credittotal += $credit->{'amount'};
+ $total->{'total_amount'} = '-\dollar '. $credit->{'amount'};
+ push @total_items, $total;
+ }
+ $invoice_data{'credittotal'} = sprintf('%.2f', $credittotal);
- # payments
- foreach my $payment ( $self->_items_payments ) {
- my $total = {};
- $total->{'total_item'} = _latex_escape($payment->{'description'});
- #$paymenttotal
- $total->{'total_amount'} = '-\dollar '. $payment->{'amount'};
- push @total_items, $total;
- }
+ # payments
+ my $paymenttotal = 0;
+ foreach my $payment ( $self->_items_payments ) {
+ my $total = {};
+ $total->{'total_item'} = _latex_escape($payment->{'description'});
+ $paymenttotal += $payment->{'amount'};
+ $total->{'total_amount'} = '-\dollar '. $payment->{'amount'};
+ push @total_items, $total;
+ }
+ $invoice_data{'paymenttotal'} = sprintf('%.2f', $paymenttotal);
- {
- my $total;
- $total->{'total_item'} = '\textbf{'. $self->balance_due_msg. '}';
- $total->{'total_amount'} =
- '\textbf{\dollar '. sprintf('%.2f', $self->owed + $pr_total ). '}';
- push @total_items, $total;
+ {
+ my $total;
+ $total->{'total_item'} = '\textbf{'. $self->balance_due_msg. '}';
+ $total->{'total_amount'} =
+ '\textbf{\dollar '. sprintf('%.2f', $self->owed + $pr_total ). '}';
+ push @total_items, $total;
+ }
}
} else {
my $self = shift;
my $file = $self->print_latex(@_);
- FS::Misc::generate_ps($file);
-
+ my $ps = generate_ps($file);
+
+ $ps;
}
=item print_pdf [ TIME [ , TEMPLATE ] ]
my $self = shift;
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.
-
- 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('dvipdf', "$file.dvi", "$file.pdf" );
- 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: $! (error in LaTeX template?)\n";
-
- unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf", "$file.tex");
-
- my $pdf = '';
- while (<PDF>) {
- $pdf .= $_;
- }
-
- close PDF;
-
- return $pdf;
-
+ my $pdf = generate_pdf($file);
+
+ $pdf;
}
=item print_html [ TIME [ , TEMPLATE [ , CID ] ] ]
'date' => time2str('%b %o, %Y', $self->_date),
'today' => time2str('%b %o, %Y', $today),
'agent' => encode_entities($cust_main->agent->agent),
+ 'agent_custid' => encode_entities($cust_main->agent_custid),
'payname' => encode_entities($cust_main->payname),
'company' => encode_entities($cust_main->company),
'address1' => encode_entities($cust_main->address1),
'city' => encode_entities($cust_main->city),
'state' => encode_entities($cust_main->state),
'zip' => encode_entities($cust_main->zip),
+ 'fax' => encode_entities($cust_main->fax),
'terms' => $conf->config('invoice_default_terms')
|| 'Payable upon receipt',
'cid' => $cid,
'template' => $template,
+ 'ship_enable' => $conf->exists('invoice-ship_address'),
+ 'unitprices' => $conf->exists('invoice-unitprice'),
# 'conf_dir' => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
);
+ my $prefix = $cust_main->has_ship_address ? 'ship_' : '';
+ foreach ( qw( contact company address1 address2 city state zip country fax) ){
+ my $method = $prefix.$_;
+ $invoice_data{"ship_$_"} = encode_entities($cust_main->$method);
+ }
+
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) );
+ join("\n", $conf->config_orbase('invoice_htmlreturnaddress', $template) );
} else {
$invoice_data{'returnaddress'} =
join("\n", map {
s/~/ /g;
s/\\\\\*?\s*$/<BR>/;
s/\\hyphenation\{[\w\s\-]+\}//;
+ s/\\([&])/$1/g;
$_;
}
$conf->config_orbase( 'invoice_latexreturnaddress',
} else {
$invoice_data{'notes'} =
join("\n", map {
- s/%%(.*)$/<!-- $1 -->/;
- s/\\section\*\{\\textsc\{(.)(.*)\}\}/<p><b><font size="+1">$1<\/font>\U$2<\/b>/;
- s/\\begin\{enumerate\}/<ol>/;
- s/\\item / <li>/;
- s/\\end\{enumerate\}/<\/ol>/;
- s/\\textbf\{(.*)\}/<b>$1<\/b>/;
- s/\\\\\*/ /;
+ s/%%(.*)$/<!-- $1 -->/g;
+ s/\\section\*\{\\textsc\{(.)(.*)\}\}/<p><b><font size="+1">$1<\/font>\U$2<\/b>/g;
+ s/\\begin\{enumerate\}/<ol>/g;
+ s/\\item / <li>/g;
+ s/\\end\{enumerate\}/<\/ol>/g;
+ s/\\textbf\{(.*)\}/<b>$1<\/b>/g;
+ s/\\\\\*/<br>/g;
+ s/\\dollar ?/\$/g;
+ s/\\#/#/g;
+ s/~/ /g;
$_;
}
$conf->config_orbase('invoice_latexnotes', $template)
my $money_char = $conf->config('money_char') || '$';
- foreach my $line_item ( $self->_items ) {
+ my %options = ( 'format' => 'html', 'escape_function' => \&encode_entities );
+ foreach my $line_item ( ($conf->exists('disable_previous_balance') ? qw() : $self->_items_previous(%options)), $self->_items_pkg(%options) ) {
my $detail = {
ext_description => [],
};
$detail->{'ref'} = $line_item->{'pkgnum'};
$detail->{'description'} = encode_entities($line_item->{'description'});
if ( exists $line_item->{'ext_description'} ) {
- @{$detail->{'ext_description'}} = map {
- encode_entities($_);
- } @{$line_item->{'ext_description'}};
+ @{$detail->{'ext_description'}} = @{$line_item->{'ext_description'}};
}
$detail->{'amount'} = $money_char. $line_item->{'amount'};
$detail->{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
my $total = {};
$total->{'total_item'} = '<b>Total</b>';
$total->{'total_amount'} =
- "<b>$money_char". sprintf('%.2f', $self->charged + $pr_total ). '</b>';
+ "<b>$money_char".
+ sprintf( '%.2f',
+ $self->charged + ( $conf->exists('disable_previous_balance')
+ ? 0
+ : $pr_total
+ )
+ ).
+ '</b>';
push @{$invoice_data{'total_items'}}, $total;
}
- #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
+ unless ($conf->exists('disable_previous_balance')) {
+ #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
- # credits
- foreach my $credit ( $self->_items_credits ) {
- my $total;
- $total->{'total_item'} = encode_entities($credit->{'description'});
- #$credittotal
- $total->{'total_amount'} = "-$money_char". $credit->{'amount'};
- push @{$invoice_data{'total_items'}}, $total;
- }
+ # credits
+ foreach my $credit ( $self->_items_credits ) {
+ my $total;
+ $total->{'total_item'} = encode_entities($credit->{'description'});
+ #$credittotal
+ $total->{'total_amount'} = "-$money_char". $credit->{'amount'};
+ push @{$invoice_data{'total_items'}}, $total;
+ }
- # payments
- foreach my $payment ( $self->_items_payments ) {
- my $total = {};
- $total->{'total_item'} = encode_entities($payment->{'description'});
- #$paymenttotal
- $total->{'total_amount'} = "-$money_char". $payment->{'amount'};
- push @{$invoice_data{'total_items'}}, $total;
- }
+ # payments
+ foreach my $payment ( $self->_items_payments ) {
+ my $total = {};
+ $total->{'total_item'} = encode_entities($payment->{'description'});
+ #$paymenttotal
+ $total->{'total_amount'} = "-$money_char". $payment->{'amount'};
+ push @{$invoice_data{'total_items'}}, $total;
+ }
- {
- my $total;
- $total->{'total_item'} = '<b>'. $self->balance_due_msg. '</b>';
- $total->{'total_amount'} =
- "<b>$money_char". sprintf('%.2f', $self->owed + $pr_total ). '</b>';
- push @{$invoice_data{'total_items'}}, $total;
+ {
+ my $total;
+ $total->{'total_item'} = '<b>'. $self->balance_due_msg. '</b>';
+ $total->{'total_amount'} =
+ "<b>$money_char". sprintf('%.2f', $self->owed + $pr_total ). '</b>';
+ push @{$invoice_data{'total_items'}}, $total;
+ }
}
$html_template->fill_in( HASH => \%invoice_data);
$msg;
}
+sub balance_due_date {
+ my $self = shift;
+ my $duedate = '';
+ if ( $conf->exists('invoice_default_terms')
+ && $conf->config('invoice_default_terms')=~ /^\s*Net\s*(\d+)\s*$/ ) {
+ $duedate = time2str("%m/%d/%Y", $self->_date + ($1*86400) );
+ }
+ $duedate;
+}
+
+=item invnum_date_pretty
+
+Returns a string with the invoice number and date, for example:
+"Invoice #54 (3/20/2008)"
+
+=cut
+
+sub invnum_date_pretty {
+ my $self = shift;
+ 'Invoice #'. $self->invnum. ' ('. $self->_date_pretty. ')';
+}
+
+=item _date_pretty
+
+Returns a string with the date, for example: "3/20/2008"
+
+=cut
+
+sub _date_pretty {
+ my $self = shift;
+ time2str('%x', $self->_date);
+}
+
sub _items {
my $self = shift;
- my @display = scalar(@_)
- ? @_
- : qw( _items_previous _items_pkg );
- #: qw( _items_pkg );
- #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments );
+
+ #my @display = scalar(@_)
+ # ? @_
+ # : qw( _items_previous _items_pkg );
+ # #: qw( _items_pkg );
+ # #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments );
+ my @display = qw( _items_previous _items_pkg );
+
my @b = ();
foreach my $display ( @display ) {
push @b, $self->$display(@_);
sub _items_cust_bill_pkg {
my $self = shift;
my $cust_bill_pkg = shift;
+ my %opt = @_;
+
+ my $format = $opt{format} || '';
+ my $escape_function = $opt{escape_function} || sub { shift };
my @b = ();
foreach my $cust_bill_pkg ( @$cust_bill_pkg ) {
+ my $cust_pkg = $cust_bill_pkg->cust_pkg;
+
my $desc = $cust_bill_pkg->desc;
+ $desc = substr($desc, 0, 50). '...'
+ if $format eq 'latex' && length($desc) > 50;
+
+ my %details_opt = ( 'format' => $format,
+ 'escape_function' => $escape_function,
+ );
if ( $cust_bill_pkg->pkgnum > 0 ) {
if ( $cust_bill_pkg->setup != 0 ) {
+
my $description = $desc;
$description .= ' Setup' if $cust_bill_pkg->recur != 0;
- my @d = $cust_bill_pkg->cust_pkg->h_labels_short($self->_date);
- push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
+
+ my @d = ();
+ push @d, map &{$escape_function}($_),
+ $cust_pkg->h_labels_short($self->_date)
+ unless $cust_pkg->part_pkg->hide_svc_detail;
+
+ push @d, $cust_bill_pkg->details(%details_opt)
+ if $cust_bill_pkg->recur == 0;
+
push @b, {
description => $description,
#pkgpart => $part_pkg->pkgpart,
pkgnum => $cust_bill_pkg->pkgnum,
amount => sprintf("%.2f", $cust_bill_pkg->setup),
+ unit_amount => sprintf("%.2f", $cust_bill_pkg->unitsetup),
+ quantity => $cust_bill_pkg->quantity,
ext_description => \@d,
};
}
if ( $cust_bill_pkg->recur != 0 ) {
+
+ my $description = $desc;
+ unless ( $conf->exists('disable_line_item_date_ranges') ) {
+ $description .= " (" . time2str("%x", $cust_bill_pkg->sdate).
+ " - ". time2str("%x", $cust_bill_pkg->edate). ")";
+ }
+
+ my @d = ();
+
+ #at least until cust_bill_pkg has "past" ranges in addition to
+ #the "future" sdate/edate ones... see #3032
+ push @d, map &{$escape_function}($_),
+ $cust_pkg->h_labels_short($self->_date)
+ #$cust_bill_pkg->edate,
+ #$cust_bill_pkg->sdate),
+ unless $cust_pkg->part_pkg->hide_svc_detail
+ || $cust_bill_pkg->itemdesc;
+
+ push @d, $cust_bill_pkg->details(%details_opt);
+
push @b, {
- description => $desc .
- ( $conf->exists('disable_line_item_date_ranges')
- ? ''
- : " (" .time2str("%x", $cust_bill_pkg->sdate).
- " - ".time2str("%x", $cust_bill_pkg->edate).")"
- ),
+ description => $description,
#pkgpart => $part_pkg->pkgpart,
pkgnum => $cust_bill_pkg->pkgnum,
amount => sprintf("%.2f", $cust_bill_pkg->recur),
- ext_description =>
- [ $cust_bill_pkg->cust_pkg->h_labels_short( $cust_bill_pkg->edate,
- $cust_bill_pkg->sdate),
- $cust_bill_pkg->details,
- ],
+ unit_amount => sprintf("%.2f", $cust_bill_pkg->unitrecur),
+ quantity => $cust_bill_pkg->quantity,
+ ext_description => \@d,
};
+
}
} else { #pkgnum tax or one-shot line item (??)
=over 4
-=item reprint
+=item process_reprint
=cut
process_re_X('print', @_);
}
-=item reemail
+=item process_reemail
=cut
process_re_X('email', @_);
}
-=item refax
+=item process_refax
=cut
process_re_X('fax', @_);
}
+=item process_reftp
+
+=cut
+
+sub process_reftp {
+ process_re_X('ftp', @_);
+}
+
+=item respool
+
+=cut
+
+sub process_respool {
+ process_re_X('spool', @_);
+}
+
use Storable qw(thaw);
use Data::Dumper;
use MIME::Base64;
sub process_re_X {
my( $method, $job ) = ( shift, shift );
- warn "process_re_X $method for job $job\n" if $DEBUG;
+ warn "$me process_re_X $method for job $job\n" if $DEBUG;
my $param = thaw(decode_base64(shift));
warn Dumper($param) if $DEBUG;
sub re_X {
my($method, $job, %param ) = @_;
-# [ 'begin', 'end', 'agentnum', 'open', 'days', 'newest_percust' ],
if ( $DEBUG ) {
warn "re_X $method for job $job with param:\n".
join( '', map { " $_ => ". $param{$_}. "\n" } keys %param );
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 $extra_sql = ' WHERE '. FS::cust_bill->search_sql(\%param);
- 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 )';
+
+ my @cust_bill = qsearch( {
+ #'select' => "cust_bill.*",
+ 'table' => 'cust_bill',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ 'debug' => 1,
+ } );
- my $addl_from = 'left join cust_main using ( custnum )';
+ $method .= '_invoice' unless $method eq 'email' || $method eq 'print';
- 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
- );
+ warn " $me re_X $method: ". scalar(@cust_bill). " invoices found\n"
+ if $DEBUG;
my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
foreach my $cust_bill ( @cust_bill ) {
=back
+=head1 CLASS METHODS
+
+=over 4
+
+=item owed_sql
+
+Returns an SQL fragment to retreive the amount owed (charged minus credited and paid).
+
+=cut
+
+sub owed_sql {
+ my $class = shift;
+ 'charged - '. $class->paid_sql. ' - '. $class->credited_sql;
+}
+
+=item net_sql
+
+Returns an SQL fragment to retreive the net amount (charged minus credited).
+
+=cut
+
+sub net_sql {
+ my $class = shift;
+ 'charged - '. $class->credited_sql;
+}
+
+=item paid_sql
+
+Returns an SQL fragment to retreive the amount paid against this invoice.
+
+=cut
+
+sub paid_sql {
+ #my $class = shift;
+ "( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+ WHERE cust_bill.invnum = cust_bill_pay.invnum )";
+}
+
+=item credited_sql
+
+Returns an SQL fragment to retreive the amount credited against this invoice.
+
+=cut
+
+sub credited_sql {
+ #my $class = shift;
+ "( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+ WHERE cust_bill.invnum = cust_credit_bill.invnum )";
+}
+
+=item search_sql HASHREF
+
+Class method which returns an SQL WHERE fragment to search for parameters
+specified in HASHREF. Valid parameters are
+
+=over 4
+
+=item begin
+
+Epoch date (UNIX timestamp) setting a lower bound for _date values
+
+=item end
+
+Epoch date (UNIX timestamp) setting an upper bound for _date values
+
+=item invnum_min
+
+=item invnum_max
+
+=item agentnum
+
+=item owed
+
+=item net
+
+=item days
+
+=item newest_percust
+
+=back
+
+Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
+
+=cut
+
+sub search_sql {
+ my($class, $param) = @_;
+ if ( $DEBUG ) {
+ warn "$me search_sql called with params: \n".
+ join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n";
+ }
+
+ my @search = ();
+
+ if ( $param->{'begin'} =~ /^(\d+)$/ ) {
+ push @search, "cust_bill._date >= $1";
+ }
+ if ( $param->{'end'} =~ /^(\d+)$/ ) {
+ push @search, "cust_bill._date < $1";
+ }
+ if ( $param->{'invnum_min'} =~ /^(\d+)$/ ) {
+ push @search, "cust_bill.invnum >= $1";
+ }
+ if ( $param->{'invnum_max'} =~ /^(\d+)$/ ) {
+ push @search, "cust_bill.invnum <= $1";
+ }
+ if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
+ push @search, "cust_main.agentnum = $1";
+ }
+
+ push @search, '0 != '. FS::cust_bill->owed_sql
+ if $param->{'open'};
+
+ push @search, '0 != '. FS::cust_bill->net_sql
+ if $param->{'net'};
+
+ push @search, "cust_bill._date < ". (time-86400*$param->{'days'})
+ if $param->{'days'};
+
+ if ( $param->{'newest_percust'} ) {
+
+ #$distinct = 'DISTINCT ON ( cust_bill.custnum )';
+ #$orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC';
+
+ my @newest_where = map { my $x = $_;
+ $x =~ s/\bcust_bill\./newest_cust_bill./g;
+ $x;
+ }
+ grep ! /^cust_main./, @search;
+ my $newest_where = scalar(@newest_where)
+ ? ' AND '. join(' AND ', @newest_where)
+ : '';
+
+
+ push @search, "cust_bill._date = (
+ SELECT(MAX(newest_cust_bill._date)) FROM cust_bill AS newest_cust_bill
+ WHERE newest_cust_bill.custnum = cust_bill.custnum
+ $newest_where
+ )";
+
+ }
+
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ if ( $curuser->username eq 'fs_queue'
+ && $param->{'CurrentUser'} =~ /^(\w+)$/ ) {
+ my $username = $1;
+ my $newuser = qsearchs('access_user', {
+ 'username' => $username,
+ 'disabled' => '',
+ } );
+ if ( $newuser ) {
+ $curuser = $newuser;
+ } else {
+ warn "$me WARNING: (fs_queue) can't find CurrentUser $username\n";
+ }
+ }
+
+ push @search, $curuser->agentnums_sql;
+
+ join(' AND ', @search );
+
+}
+
+=back
+
=head1 BUGS
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 ($=).
-
=head1 SEE ALSO
L<FS::Record>, L<FS::cust_main>, L<FS::cust_bill_pay>, L<FS::cust_pay>,