postscript invoice redux
authorivan <ivan>
Sat, 29 Nov 2003 08:08:36 +0000 (08:08 +0000)
committerivan <ivan>
Sat, 29 Nov 2003 08:08:36 +0000 (08:08 +0000)
FS/FS/Conf.pm
FS/FS/cust_bill.pm
conf/invoice_latex [new file with mode: 0644]
conf/invoice_latexfooter [new file with mode: 0644]
conf/invoice_latexnotes [new file with mode: 0644]
httemplate/misc/print-invoice.cgi
httemplate/view/cust_bill-ps.cgi [new file with mode: 0755]

index c69817d..b30beaf 100644 (file)
@@ -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 <a href="../docs/billing.html">billing documentation</a> 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',
index aacb504..9409bc5 100644 (file)
@@ -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 (file)
index 0000000..a89b9a3
--- /dev/null
@@ -0,0 +1,155 @@
+%% file: Standard Multipage.tex\r
+%% Purpose: Multipage bill template for e-Bills\r
+%% \r
+%% Created by Mark Asplen-Taylor\r
+%% Asplen Management Ltd\r
+%% www.asplen.co.uk\r
+%%\r
+%% Modified for Freeside by Ivan Kohler\r
+%%\r
+%% Changes\r
+%%     0.1     4/12/00 Created\r
+%%     0.2     18/10/01        More fields added\r
+%%     1.0     16/11/01        RELEASED\r
+%%     1.2     16/10/02        Invoice number added\r
+%%     1.3     2/12/02 Logo graphic added\r
+%%     1.4     7/2/03  Multipage headers/footers added\r
+%%      n/a     10/12/03 forked for Freeside; checked into CVS\r
+%%\r
+\r
+\documentclass[letterpaper]{article}\r
+\r
+\usepackage{fancyhdr,lastpage,ifthen,longtable,afterpage}\r
+\usepackage{graphicx}                  % required for logo graphic\r
+\r
+\addtolength{\voffset}{-0.0in} % top margin to top of header\r
+\addtolength{\hoffset}{-0.25in}        %left margin on page\r
+\addtolength{\topmargin}{-0.6in}       % top margin to top of header\r
+\setlength{\headheight}{1in}           % height of header\r
+\setlength{\headsep}{0.5in}            % between header and text\r
+\addtolength{\textheight}{-0.4in}      % height of main text\r
+\r
+\addtolength{\textheight}{-0.5in}      % height of main text\r
+\setlength{\footskip}{0.5in}           % bottom of footer from bottom of text\r
+\r
+\addtolength{\textwidth}{2.1in}        % width of text\r
+\setlength{\oddsidemargin}{0in}        % odd page left margin\r
+\setlength{\evensidemargin}{0in}       % even page left margin\r
+\r
+\renewcommand{\headrulewidth}{0pt}\r
+\renewcommand{\footrulewidth}{1pt}\r
+\r
+                                               % New command for address lines i.e. skip them if blank\r
+\r
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}\r
+\newcommand{\dollar}[1][]{\symbol{36}} % Inserts dollar symbol\r
+\r
+\pagestyle{fancy}\r
+\r
+%% Font options are:\r
+%%     bch     Bitsream Charter\r
+%%     put     Utopia\r
+%%     phv     Adobe Helvetica\r
+%%     pnc     New Century Schoolbook\r
+%%     ptm     Times\r
+%%     pcr     Courier\r
+\r
+\renewcommand{\familydefault}{phv}             \r
+\r
+\begin{document}\r
+%\r
+%%     Headers and footers defined for the first page\r
+\fancyfoot[CO,CE]{\small{\r
+\begin{tabular}{c}\r
+$footer\r
+\end{tabular}}}\r
+%\r
+%%     The LH Heading comprising logo\r
+%%     UNCOMMENT the following FOUR lines and change the path if necssary to provide a logo\r
+\fancyhead[LO,LE]{\r
+\begin{tabular}{l}\r
+\includegraphics{/usr/local/etc/freeside/logo.eps}\r
+\end{tabular}}\r
+%\r
+%%     The Heading comprising isue date, customer ref & INVOICE name\r
+\fancyhead[RO,RE]{\r
+\begin{tabular}{rcl}\r
+Invoice date & & Invoice number \\\r
+\vspace{0.2cm}\r
+\textbf{$date} & & \textbf{$invnum} \\\hline\r
+\rule{0pt}{5ex} &~~ \huge{\textsc{Invoice}}& \\\r
+\vspace{-0.2cm}\r
+ & & \\\hline\r
+\end{tabular}}\r
+%\r
+%%     Header & footer changes for subsequent pages\r
+%\r
+\afterpage{ \fancyfoot[RO,RE]{\small{\thepage\ of \pageref{LastPage}}} }\r
+\afterpage{ \fancyfoot[CO,CE]{\small{$org_company}} }\r
+\afterpage{ \fancyhead[LO,LE]{\small{}} }\r
+\afterpage{ \fancyhead[RO,RE]{\small{\r
+\begin{tabular}{ll}\r
+Date & Account number\\\r
+\textbf{ $date_of_bill} & \textbf{ $customer_ref}\\\r
+\end{tabular}}} }\r
+%\r
+%\r
+\makebox{\r
+\begin{minipage}[t]{2.9in}\r
+\vspace{0.20in}\r
+\textbf{$payname}\\\r
+\addressline{$company}\r
+\addressline{$address1}\r
+\addressline{$address2}\r
+\addressline{$city, $state  $zip}\r
+\addressline{$country}\r
+\end{minipage}}\r
+\hfill\r
+\makebox{\r
+\begin{minipage}[t]{2.5in}\r
+\begin{flushright}\r
+Terms: $terms\\\r
+$po_line\\\r
+\end{flushright}\r
+\end{minipage}}\r
+\vspace{0.5cm}\r
+%\r
+\section*{\textsc{Charges}}\r
+\begin{longtable}{|c|l|c|r|r|}\r
+\hline\r
+\rule{0pt}{2.5ex}\r
+\makebox[1.4cm]{\textbf{Ref}} & \r
+\makebox[7.9cm][l]{\textbf{Description}} & \r
+\makebox[1.3cm][c]{\textbf{Quantity}} & \r
+\makebox[2.5cm][r]{\textbf{Unit Price}} & \r
+\makebox[2.5cm][r]{\textbf{Amount}} \\\r
+\hline\r
+\endfirsthead\r
+\multicolumn{5}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\r
+\hline\r
+\rule{0pt}{2.5ex}\r
+\makebox[1.4cm]{\textbf{Ref}} & \r
+\makebox[7.9cm][l]{\textbf{Description}} & \r
+\makebox[1.3cm][c]{\textbf{Quantity}} & \r
+\makebox[2.5cm][r]{\textbf{Unit Price}} & \r
+\makebox[2.5cm][r]{\textbf{Amount}} \\\r
+\hline\r
+\endhead\r
+\multicolumn{5}{r}{\rule{0pt}{2.5ex}/cont...}\\\r
+\endfoot\r
+%%TotalDetails\r
+ & \multicolumn{3}{l}{$total_item}    & $total_amount\\\r
+%%EndTotalDetails\r
+\hline\r
+\endlastfoot\r
+%%Detail\r
+\rule{0pt}{2.5ex}$ref & \r
+\begin{tabular}{l}\r
+$description\tabularnewline\r
+\end{tabular}\r
+& $quantity & \dollar $amount & \dollar $amount\\\hline\r
+%%EndDetail\r
+\end{longtable}\r
+\vfill\r
+$notes\r
+\end{document}\r
diff --git a/conf/invoice_latexfooter b/conf/invoice_latexfooter
new file mode 100644 (file)
index 0000000..110b1e6
--- /dev/null
@@ -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 (file)
index 0000000..29e1731
--- /dev/null
@@ -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}
index a5500bf..8c1240c 100755 (executable)
@@ -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 (executable)
index 0000000..03340a1
--- /dev/null
@@ -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 %>