}
+ if ( $conf->config('invoice_latex') ) {
+ @print_text = $self->print_ps('', $template);
+ }
+
if ( grep { $_ eq 'POST' } @invoicing_list ) { #postal
my $lpr = $conf->config('lpr');
open(LPR, "|$lpr")
my( $self, $method ) = @_;
my $cust_main = $self->cust_main;
- my $amount = $self->owed;
+ my $balance = $cust_main->balance;
+ my $amount = ( $balance < $self->owed ) ? $balance : $self->owed;
+ $amount = sprintf("%.2f", $amount);
+ return "not run (balance $balance)" unless $amount > 0;
my $description = 'Internet Services';
if ( $conf->exists('business-onlinepayment-description') ) {
my $pkg = $part_pkg->pkg;
if ( $cust_bill_pkg->setup != 0 ) {
- push @buf, [ "$pkg Setup",
+ my $description = $pkg;
+ $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->labels;
}
#balance due
- my $balance_due_msg = 'Balance Due';
-
- if ( $conf->config('invoice_default_terms') =~ /^\s*Net\s*(\d+)\s*$/ ) {
- $balance_due_msg .=
- ' - Please pay by '. time2str("%x", $self->_date + ($1*86400) );
- } elsif ( $conf->config('invoice_default_terms') ) {
- $balance_due_msg .= ' - '. $conf->config('invoice_default_terms');
- }
+ my $balance_due_msg = $self->balance_due_msg;
push @buf,['','-----------'];
push @buf,[$balance_due_msg, $money_char.
}
-=item print_ps [ TIME [ , TEMPLATE ] ]
+=item print_latex [ TIME [ , TEMPLATE ] ]
-Returns an postscript invoice, as a scalar.
+Internal method - returns a filename of a filled-in LaTeX template for this
+invoice (Note: add ".tex" to get the actual filename).
+
+See print_ps and print_pdf for methods that return PostScript and PDF output.
TIME an optional value used to control the printing of overdue messages. The
default is now. It isn't the date of the invoice; that's the `_date' field.
=cut
#still some false laziness w/print_text
-sub print_ps {
+sub print_latex {
my( $self, $today, $template ) = @_;
$today ||= time;
@buf = ();
#create the template
- my $templatefile = 'invoice_template_latex';
+ my $templatefile = 'invoice_latex';
$templatefile .= "_$template" if $template;
my @invoice_template = $conf->config($templatefile)
or die "cannot load config file $templatefile";
my %invoice_data = (
- 'invnum' => $self->invnum,
- 'date' => time2str('%b %o, %Y', $self->_date),
- 'agent' => $cust_main->agent->agent,
- 'payname' => $cust_main->payname,
- 'company' => $cust_main->company,
- 'address1' => $cust_main->address1,
- 'address2' => $cust_main->address2,
- 'city' => $cust_main->city,
- 'state' => $cust_main->state,
- 'zip' => $cust_main->zip,
- 'country' => $cust_main->country,
- 'footer' => <<'END', #should come from config value
-Ivan Kohler\\
-1339 Hayes St.\\
-San Francisco, CA~~94117\\
-ivan@sisd.com~~~~+1 415 462 1624\\
-Freeside - open-source billing - http://www.sisd.com/freeside\\
-END
-
+ 'invnum' => $self->invnum,
+ 'date' => time2str('%b %o, %Y', $self->_date),
+ 'agent' => _latex_escape($cust_main->agent->agent),
+ '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),
+ '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') ),
);
- #$invoice_data{'footer'} =~ s/\n+$//;
-
my $countrydefault = $conf->config('countrydefault') || 'US';
$invoice_data{'country'} = '' if $invoice_data{'country'} eq $countrydefault;
+ #do variable substitutions in notes
+ $invoice_data{'notes'} =
+ join("\n",
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ $conf->config('invoice_latexnotes')
+ );
+
+ $invoice_data{'footer'} =~ s/\n+$//;
+ $invoice_data{'smallfooter'} =~ s/\n+$//;
+ $invoice_data{'notes'} =~ s/\n+$//;
+
$invoice_data{'po_line'} =
( $cust_main->payby eq 'BILL' && $cust_main->payinfo )
- ? "Purchase Order #". $cust_main->payinfo
+ ? _latex_escape("Purchase Order #". $cust_main->payinfo)
: '~';
my @line_item = ();
!~ /^%%EndDetail\s*$/ ) {
push @line_item, $line_item_line;
}
- #foreach my $line_item ( $self->_items ) {
- foreach my $line_item ( $self->_items_pkg ) {
+ foreach my $line_item ( $self->_items ) {
+ #foreach my $line_item ( $self->_items_pkg ) {
$invoice_data{'ref'} = $line_item->{'pkgnum'};
- $invoice_data{'description'} = $line_item->{'description'};
+ $invoice_data{'description'} = _latex_escape($line_item->{'description'});
if ( exists $line_item->{'ext_description'} ) {
$invoice_data{'description'} .=
"\\tabularnewline\n~~".
- join("\\tabularnewline\n~~", @{$line_item->{'ext_description'}} );
+ join("\\tabularnewline\n~~", map { _latex_escape($_) } @{$line_item->{'ext_description'}} );
}
$invoice_data{'amount'} = $line_item->{'amount'};
$invoice_data{'product_code'} = $line_item->{'pkgpart'} || 'N/A';
my $taxtotal = 0;
foreach my $tax ( $self->_items_tax ) {
- $invoice_data{'total_item'} = $tax->{'description'};
+ $invoice_data{'total_item'} = _latex_escape($tax->{'description'});
$taxtotal += ( $invoice_data{'total_amount'} = $tax->{'amount'} );
push @total_fill,
map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
@total_item;
}
+ if ( $taxtotal ) {
+ $invoice_data{'total_item'} = 'Sub-total';
+ $invoice_data{'total_amount'} =
+ '\dollar '. sprintf('%.2f', $self->charged - $taxtotal );
+ unshift @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+ }
+
$invoice_data{'total_item'} = '\textbf{Total}';
$invoice_data{'total_amount'} =
- '\textbf{\dollar '. sprintf('%.2f', $self->owed ). '}';
+ '\textbf{\dollar '. sprintf('%.2f', $self->charged + $pr_total ). '}';
push @total_fill,
map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
@total_item;
- if ( $taxtotal ) {
- $invoice_data{'total_item'} = 'Sub-total';
- $invoice_data{'total_amount'} =
- '\dollar '. sprintf('%.2f', $self->owed - $taxtotal );
- unshift @total_fill,
+ #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
+
+ # credits
+ foreach my $credit ( $self->_items_credits ) {
+ $invoice_data{'total_item'} = _latex_escape($credit->{'description'});
+ #$credittotal
+ $invoice_data{'total_amount'} = '-\dollar '. $credit->{'amount'};
+ push @total_fill,
map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
@total_item;
}
+ # payments
+ foreach my $payment ( $self->_items_payments ) {
+ $invoice_data{'total_item'} = _latex_escape($payment->{'description'});
+ #$paymenttotal
+ $invoice_data{'total_amount'} = '-\dollar '. $payment->{'amount'};
+ push @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+ }
+
+ $invoice_data{'total_item'} = '\textbf{'. $self->balance_due_msg. '}';
+ $invoice_data{'total_amount'} =
+ '\textbf{\dollar '. sprintf('%.2f', $self->owed + $pr_total ). '}';
+ push @total_fill,
+ map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b }
+ @total_item;
+
push @filled_in, @total_fill;
} else {
print TEX join("\n", @filled_in ), "\n";
close TEX;
+ return $file;
+
+}
+
+=item print_ps [ TIME [ , TEMPLATE ] ]
+
+Returns an postscript invoice, as a scalar.
+
+TIME an optional value used to control the printing of overdue messages. The
+default is now. It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_ps {
+ my $self = shift;
+
+ my $file = $self->print_latex(@_);
+
#error checking!!
system('pslatex', "$file.tex");
system('pslatex', "$file.tex");
- #system('dvips', '-t', 'letter', "$file.dvi", "$file.ps");
- system('dvips', '-t', 'letter', "$file.dvi" );
+ system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" );
- open(POSTSCRIPT, "<$file.ps") or die "can't open $file.ps: $!\n";
+ open(POSTSCRIPT, "<$file.ps")
+ or die "can't open $file.ps (probable 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>) {
}
+=item print_pdf [ TIME [ , TEMPLATE ] ]
+
+Returns an PDF invoice, as a scalar.
+
+TIME an optional value used to control the printing of overdue messages. The
+default is now. It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_pdf {
+ my $self = shift;
+
+ my $file = $self->print_latex(@_);
+
+ #system('pdflatex', "$file.tex");
+ #system('pdflatex', "$file.tex");
+ #! LaTeX Error: Unknown graphics extension: .eps.
+
+ #error checking!!
+ system('pslatex', "$file.tex");
+ system('pslatex', "$file.tex");
+
+ #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 -");
+
+ open(PDF, "<$file.pdf")
+ or die "can't open $file.pdf (probably error in LaTeX tempalte: $!\n";
+
+ unlink("$file.dvi", "$file.log", "$file.aux", "$file.pdf", "$file.tex");
+
+ my $pdf = '';
+ while (<PDF>) {
+ $pdf .= $_;
+ }
+
+ close PDF;
+
+ return $pdf;
+
+}
+
+# quick subroutine for print_latex
+#
+# There are ten characters that LaTeX treats as special characters, which
+# means that they do not simply typeset themselves:
+# # $ % & ~ _ ^ \ { }
+#
+# TeX ignores blanks following an escaped character; if you want a blank (as
+# in "10% of ..."), you have to "escape" the blank as well ("10\%\ of ...").
+
+sub _latex_escape {
+ my $value = shift;
+ $value =~ s/([#\$%&~_\^{}])( )?/"\\$1". ( length($2) ? "\\$2" : '' )/ge;
+ $value;
+}
+
#utility methods for print_*
+sub balance_due_msg {
+ my $self = shift;
+ my $msg = 'Balance Due';
+ return $msg unless $conf->exists('invoice_default_terms');
+ if ( $conf->config('invoice_default_terms') =~ /^\s*Net\s*(\d+)\s*$/ ) {
+ $msg .= ' - Please pay by '. time2str("%x", $self->_date + ($1*86400) );
+ } elsif ( $conf->config('invoice_default_terms') ) {
+ $msg .= ' - '. $conf->config('invoice_default_terms');
+ }
+ $msg;
+}
+
sub _items {
my $self = shift;
my @display = scalar(@_)
? @_
- : qw( _items_pkg );
+ : qw( _items_previous _items_pkg );
+ #: qw( _items_pkg );
#: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments );
my @b = ();
foreach my $display ( @display ) {
my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
my @b = ();
foreach ( @pr_cust_bill ) {
- push @b, [
- "Previous Balance, Invoice #". $_->invnum.
- " (". time2str("%x",$_->_date). ")",
- $money_char. sprintf("%10.2f",$_->owed)
- ];
+ push @b, {
+ 'description' => 'Previous Balance, Invoice #'. $_->invnum.
+ ' ('. time2str('%x',$_->_date). ')',
+ #'pkgpart' => 'N/A',
+ 'pkgnum' => 'N/A',
+ 'amount' => sprintf("%10.2f", $_->owed),
+ };
}
@b;
+
+ #{
+ # 'description' => 'Previous Balance',
+ # #'pkgpart' => 'N/A',
+ # 'pkgnum' => 'N/A',
+ # 'amount' => sprintf("%10.2f", $pr_total ),
+ # 'ext_description' => [ map {
+ # "Invoice ". $_->invnum.
+ # " (". time2str("%x",$_->_date). ") ".
+ # sprintf("%10.2f", $_->owed)
+ # } @pr_cust_bill ],
+
+ #};
}
sub _items_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 @d = ();
- @d = $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
+ my $description = $pkg;
+ $description .= ' Setup' if $cust_bill_pkg->recur != 0;
+ my @d = @ext_description;
+ push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
push @b, {
- 'description' => "$pkg Setup",
- 'pkgpart' => $part_pkg->pkgpart,
+ '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,
- ],
+ 'ext_description' => \@d,
};
}
'description' => "$pkg (" .
time2str('%x', $cust_bill_pkg->sdate). ' - '.
time2str('%x', $cust_bill_pkg->edate). ')',
- 'pkgpart' => $part_pkg->pkgpart,
+ #'pkgpart' => $part_pkg->pkgpart,
'pkgnum' => $cust_pkg->pkgnum,
'amount' => sprintf("%10.2f", $cust_bill_pkg->recur),
- 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] }
- $cust_pkg->labels ),
+ 'ext_description' => [ @ext_description,
$cust_bill_pkg->details,
],
};
#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);
+ my $reason = $_->cust_credit->reason;
+ #my $reason = substr($_->cust_credit->reason,0,32);
+ #$reason .= '...' if length($reason) < length($_->cust_credit->reason);
$reason = " ($reason) " if $reason;
- push @b,[
- "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".
- $reason,
- $money_char. sprintf("%10.2f",$_->amount)
- ];
+ push @b, {
+ #'description' => 'Credit ref\#'. $_->crednum.
+ # " (". time2str("%x",$_->cust_credit->_date) .")".
+ # $reason,
+ 'description' => 'Credit applied'.
+ time2str("%x",$_->cust_credit->_date). $reason,
+ 'amount' => sprintf("%10.2f",$_->amount),
+ };
}
#foreach ( @cr_cust_credit ) {
# push @buf,[
#something more elaborate if $_->amount ne ->cust_pay->paid ?
- push @b,[
- "Payment received ". time2str("%x",$_->cust_pay->_date ),
- $money_char. sprintf("%10.2f",$_->amount )
- ];
+ push @b, {
+ 'description' => "Payment received ".
+ time2str("%x",$_->cust_pay->_date ),
+ 'amount' => sprintf("%10.2f", $_->amount )
+ };
}
@b;