From 23ec9b00410224868d6be1e47a0d72a6c8b3f6f5 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 29 Nov 2003 08:08:36 +0000 Subject: [PATCH] postscript invoice redux --- FS/FS/Conf.pm | 37 ++++++++- FS/FS/cust_bill.pm | 165 ++++++++++++++++++++++++-------------- conf/invoice_latex | 155 +++++++++++++++++++++++++++++++++++ conf/invoice_latexfooter | 5 ++ conf/invoice_latexnotes | 9 +++ httemplate/misc/print-invoice.cgi | 9 ++- httemplate/view/cust_bill-ps.cgi | 13 +++ 7 files changed, 332 insertions(+), 61 deletions(-) create mode 100644 conf/invoice_latex create mode 100644 conf/invoice_latexfooter create mode 100644 conf/invoice_latexnotes create mode 100755 httemplate/view/cust_bill-ps.cgi diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index c69817d49..b30beafaf 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -174,7 +174,7 @@ sub config_items { my $self = shift; #quelle kludge @config_items, - map { + ( map { my $basename = basename($_); $basename =~ /^(.*)$/; $basename = $1; @@ -185,7 +185,19 @@ sub config_items { 'type' => 'textarea', } } glob($self->dir. '/invoice_template_*') - ; + ), + ( map { + my $basename = basename($_); + $basename =~ /^(.*)$/; + $basename = $1; + new FS::ConfItem { + 'key' => $basename, + 'section' => 'billing', + 'description' => 'Alternate LaTeX template for invoices. See the billing documentation for details.', + 'type' => 'textarea', + } + } glob($self->dir. '/invoice_latex_*') + ); } =back @@ -450,6 +462,27 @@ httemplate/docs/config.html 'type' => 'textarea', }, + { + 'key' => 'invoice_latex', + 'section' => 'billing', + 'description' => 'Optional LaTeX template for typeset PostScript invoices.', + 'type' => 'textarea', + }, + + { + 'key' => 'invoice_latexnotes', + 'section' => 'billing', + 'description' => 'Notes section for LaTeX typeset PostScript invoices.', + 'type' => 'textarea', + }, + + { + 'key' => 'invoice_latexfooter', + 'section' => 'billing', + 'description' => 'Footer for LaTeX typeset PostScript invoices.', + 'type' => 'textarea', + }, + { 'key' => 'invoice_default_terms', 'section' => 'billing', diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index aacb50425..9409bc5f6 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -784,14 +784,7 @@ sub print_text { } #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. @@ -920,36 +913,31 @@ sub print_ps { @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' => $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' => join("\n", $conf->config('invoice_latexfooter') ), 'quantity' => 1, - + 'terms' => $conf->config('invoice_default_terms') || 'Payable upon receipt', + 'notes' => join("\n", $conf->config('invoice_latexnotes') ), ); - #$invoice_data{'footer'} =~ s/\n+$//; + $invoice_data{'footer'} =~ s/\n+$//; + $invoice_data{'notes'} =~ s/\n+$//; my $countrydefault = $conf->config('countrydefault') || 'US'; $invoice_data{'country'} = '' if $invoice_data{'country'} eq $countrydefault; @@ -971,8 +959,8 @@ END !~ /^%%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'}; if ( exists $line_item->{'ext_description'} ) { @@ -1004,22 +992,51 @@ END @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'} = $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'} = $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 { @@ -1052,7 +1069,7 @@ END #system('dvips', '-t', 'letter', "$file.dvi", "$file.ps"); system('dvips', '-t', 'letter', "$file.dvi" ); - 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"); @@ -1071,11 +1088,23 @@ END #utility methods for print_* +sub balance_due_msg { + my $self = shift; + my $msg = 'Balance Due'; + 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 ) { @@ -1090,13 +1119,28 @@ sub _items_previous { 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 { @@ -1131,7 +1175,7 @@ sub _items_cust_bill_pkg { @d = $cust_bill_pkg->details if $cust_bill_pkg->recur == 0; push @b, { 'description' => $description, - 'pkgpart' => $part_pkg->pkgpart, + #'pkgpart' => $part_pkg->pkgpart, 'pkgnum' => $cust_pkg->pkgnum, 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup), 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] } @@ -1146,7 +1190,7 @@ sub _items_cust_bill_pkg { '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] } @@ -1193,14 +1237,18 @@ sub _items_credits { #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,[ @@ -1222,10 +1270,11 @@ sub _items_payments { #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; diff --git a/conf/invoice_latex b/conf/invoice_latex new file mode 100644 index 000000000..a89b9a33a --- /dev/null +++ b/conf/invoice_latex @@ -0,0 +1,155 @@ +%% file: Standard Multipage.tex +%% Purpose: Multipage bill template for e-Bills +%% +%% Created by Mark Asplen-Taylor +%% Asplen Management Ltd +%% www.asplen.co.uk +%% +%% Modified for Freeside by Ivan Kohler +%% +%% Changes +%% 0.1 4/12/00 Created +%% 0.2 18/10/01 More fields added +%% 1.0 16/11/01 RELEASED +%% 1.2 16/10/02 Invoice number added +%% 1.3 2/12/02 Logo graphic added +%% 1.4 7/2/03 Multipage headers/footers added +%% n/a 10/12/03 forked for Freeside; checked into CVS +%% + +\documentclass[letterpaper]{article} + +\usepackage{fancyhdr,lastpage,ifthen,longtable,afterpage} +\usepackage{graphicx} % required for logo graphic + +\addtolength{\voffset}{-0.0in} % top margin to top of header +\addtolength{\hoffset}{-0.25in} %left margin on page +\addtolength{\topmargin}{-0.6in} % top margin to top of header +\setlength{\headheight}{1in} % height of header +\setlength{\headsep}{0.5in} % between header and text +\addtolength{\textheight}{-0.4in} % height of main text + +\addtolength{\textheight}{-0.5in} % height of main text +\setlength{\footskip}{0.5in} % bottom of footer from bottom of text + +\addtolength{\textwidth}{2.1in} % width of text +\setlength{\oddsidemargin}{0in} % odd page left margin +\setlength{\evensidemargin}{0in} % even page left margin + +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{1pt} + + % New command for address lines i.e. skip them if blank + +\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}} +\newcommand{\dollar}[1][]{\symbol{36}} % Inserts dollar symbol + +\pagestyle{fancy} + +%% Font options are: +%% bch Bitsream Charter +%% put Utopia +%% phv Adobe Helvetica +%% pnc New Century Schoolbook +%% ptm Times +%% pcr Courier + +\renewcommand{\familydefault}{phv} + +\begin{document} +% +%% Headers and footers defined for the first page +\fancyfoot[CO,CE]{\small{ +\begin{tabular}{c} +$footer +\end{tabular}}} +% +%% The LH Heading comprising logo +%% UNCOMMENT the following FOUR lines and change the path if necssary to provide a logo +\fancyhead[LO,LE]{ +\begin{tabular}{l} +\includegraphics{/usr/local/etc/freeside/logo.eps} +\end{tabular}} +% +%% The Heading comprising isue date, customer ref & INVOICE name +\fancyhead[RO,RE]{ +\begin{tabular}{rcl} +Invoice date & & Invoice number \\ +\vspace{0.2cm} +\textbf{$date} & & \textbf{$invnum} \\\hline +\rule{0pt}{5ex} &~~ \huge{\textsc{Invoice}}& \\ +\vspace{-0.2cm} + & & \\\hline +\end{tabular}} +% +%% Header & footer changes for subsequent pages +% +\afterpage{ \fancyfoot[RO,RE]{\small{\thepage\ of \pageref{LastPage}}} } +\afterpage{ \fancyfoot[CO,CE]{\small{$org_company}} } +\afterpage{ \fancyhead[LO,LE]{\small{}} } +\afterpage{ \fancyhead[RO,RE]{\small{ +\begin{tabular}{ll} +Date & Account number\\ +\textbf{ $date_of_bill} & \textbf{ $customer_ref}\\ +\end{tabular}}} } +% +% +\makebox{ +\begin{minipage}[t]{2.9in} +\vspace{0.20in} +\textbf{$payname}\\ +\addressline{$company} +\addressline{$address1} +\addressline{$address2} +\addressline{$city, $state $zip} +\addressline{$country} +\end{minipage}} +\hfill +\makebox{ +\begin{minipage}[t]{2.5in} +\begin{flushright} +Terms: $terms\\ +$po_line\\ +\end{flushright} +\end{minipage}} +\vspace{0.5cm} +% +\section*{\textsc{Charges}} +\begin{longtable}{|c|l|c|r|r|} +\hline +\rule{0pt}{2.5ex} +\makebox[1.4cm]{\textbf{Ref}} & +\makebox[7.9cm][l]{\textbf{Description}} & +\makebox[1.3cm][c]{\textbf{Quantity}} & +\makebox[2.5cm][r]{\textbf{Unit Price}} & +\makebox[2.5cm][r]{\textbf{Amount}} \\ +\hline +\endfirsthead +\multicolumn{5}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\ +\hline +\rule{0pt}{2.5ex} +\makebox[1.4cm]{\textbf{Ref}} & +\makebox[7.9cm][l]{\textbf{Description}} & +\makebox[1.3cm][c]{\textbf{Quantity}} & +\makebox[2.5cm][r]{\textbf{Unit Price}} & +\makebox[2.5cm][r]{\textbf{Amount}} \\ +\hline +\endhead +\multicolumn{5}{r}{\rule{0pt}{2.5ex}/cont...}\\ +\endfoot +%%TotalDetails + & \multicolumn{3}{l}{$total_item} & $total_amount\\ +%%EndTotalDetails +\hline +\endlastfoot +%%Detail +\rule{0pt}{2.5ex}$ref & +\begin{tabular}{l} +$description\tabularnewline +\end{tabular} +& $quantity & \dollar $amount & \dollar $amount\\\hline +%%EndDetail +\end{longtable} +\vfill +$notes +\end{document} diff --git a/conf/invoice_latexfooter b/conf/invoice_latexfooter new file mode 100644 index 000000000..110b1e6fe --- /dev/null +++ b/conf/invoice_latexfooter @@ -0,0 +1,5 @@ +Ivan Kohler\\ +P.O. Box 1272\\ +Carnelian Bay, CA~~96140\\ +ivan@sisd.com~~~~+1 415 462 1624\\ +Freeside - open-source billing - http://www.sisd.com/freeside\\ diff --git a/conf/invoice_latexnotes b/conf/invoice_latexnotes new file mode 100644 index 000000000..29e173103 --- /dev/null +++ b/conf/invoice_latexnotes @@ -0,0 +1,9 @@ +%% +%% Add any customer specific notes in here +%% +\section*{\textsc{Notes}} +\begin{enumerate} +\item PLEASE NOTE NEW ADDRESS BELOW! +\item Please make your check payable to \textbf{Ivan Kohler}. +\item If you have any questions please email or telephone. +\end{enumerate} diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi index a5500bff2..8c1240c05 100755 --- a/httemplate/misc/print-invoice.cgi +++ b/httemplate/misc/print-invoice.cgi @@ -11,7 +11,14 @@ my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); die "Can't find invoice!\n" unless $cust_bill; open(LPR,"|$lpr") or die "Can't open $lpr: $!"; - print LPR $cust_bill->print_text; #( date ) + + if ( $conf->exists('invoice_latex') ) { + $cust_bill->print_ps; + #print LPR $cust_bill->print_ps; #( date ) + } else { + print LPR $cust_bill->print_text; #( date ) + } + close LPR or die $! ? "Error closing $lpr: $!" : "Exit status $? from $lpr"; diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi new file mode 100755 index 000000000..03340a16f --- /dev/null +++ b/httemplate/view/cust_bill-ps.cgi @@ -0,0 +1,13 @@ +<% + +#untaint invnum +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $invnum = $1; + +my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +die "Invoice #$invnum not found!" unless $cust_bill; + +http_header('Content-Type' => 'application/postscript' ); +%> +<%= $cust_bill->print_ps %> -- 2.11.0