leading summary page invoices #RT5086
authorjeff <jeff>
Mon, 5 Oct 2009 00:49:34 +0000 (00:49 +0000)
committerjeff <jeff>
Mon, 5 Oct 2009 00:49:34 +0000 (00:49 +0000)
16 files changed:
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/Upgrade.pm
FS/FS/cust_bill.pm
FS/FS/cust_bill_pkg.pm
FS/FS/cust_bill_pkg_display.pm
FS/FS/cust_main.pm
FS/FS/part_event/Action/cust_bill_fee_percent.pm
FS/FS/part_event/Action/fee.pm
FS/FS/pkg_category.pm
conf/invoice_html
conf/invoice_htmlsummary [new file with mode: 0644]
conf/invoice_latex
conf/invoice_latexsummary [new file with mode: 0644]
httemplate/browse/pkg_category.html
httemplate/edit/pkg_category.html

index 3b37fea..1353df8 100644 (file)
@@ -892,6 +892,13 @@ worry that config_items is freeside-specific and icky.
   },
 
   {
+    'key'         => 'invoice_usesummary',
+    'section'     => 'billing',
+    'description' => 'Indicates that html and latex invoices should be in summary style and make use of invoice_latexsummary.',
+    'type'        => 'checkbox',
+  },
+
+  {
     'key'         => 'invoice_template',
     'section'     => 'billing',
     'description' => 'Text template file for invoices.  Used if no invoice_html template is defined, and also seen by users using non-HTML capable mail clients.  See the <a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Plaintext_invoice_templates">billing documentation</a> for details.',
@@ -923,6 +930,14 @@ worry that config_items is freeside-specific and icky.
   },
 
   {
+    'key'         => 'invoice_htmlsummary',
+    'section'     => 'billing',
+    'description' => 'Summary initial page for HTML invoices.',
+    'type'        => 'textarea',
+    'per_agent'   => 1,
+  },
+
+  {
     'key'         => 'invoice_htmlreturnaddress',
     'section'     => 'billing',
     'description' => 'Return address for HTML invoices.  Defaults to the same data in invoice_latexreturnaddress if not specified.',
@@ -953,6 +968,14 @@ worry that config_items is freeside-specific and icky.
   },
 
   {
+    'key'         => 'invoice_latexsummary',
+    'section'     => 'billing',
+    'description' => 'Summary initial page for LaTeX typeset PostScript invoices.',
+    'type'        => 'textarea',
+    'per_agent'   => 1,
+  },
+
+  {
     'key'         => 'invoice_latexcoupon',
     'section'     => 'billing',
     'description' => 'Remittance coupon for LaTeX typeset PostScript invoices.',
@@ -1005,6 +1028,25 @@ worry that config_items is freeside-specific and icky.
     'type'        => 'checkbox',
   },
 
+  {
+    'key'         => 'finance_pkgclass',
+    'section'     => 'billing',
+    'description' => 'The package class for finance charges',
+    'type'        => 'select-sub',
+    'options_sub' => sub { require FS::Record;
+                           require FS::pkg_class;
+                           map { $_->classnum => $_->classname }
+                               FS::Record::qsearch('pkg_class', {} );
+                        },
+    'option_sub'  => sub { require FS::Record;
+                           require FS::pkg_class;
+                           my $pkg_class = FS::Record::qsearchs(
+                            'pkg_class', { 'classnum'=>shift }
+                          );
+                           $pkg_class ? $pkg_class->classname : '';
+                        },
+  },
+
   { 
     'key'         => 'separate_usage',
     'section'     => 'billing',
index 4af026c..70f3250 100644 (file)
@@ -394,6 +394,8 @@ sub tables_hashref {
         'custnum',      'int',     '', '', '', '', 
         '_date',        @date_type,        '', '', 
         'charged',      @money_type,       '', '', 
+        'previous_balance',   @money_typen, '', '',  #eventually not nullable
+        'billing_balance',    @money_typen, '', '',  #eventually not nullable
         'printed',      'int',     '', '', '', '', 
         'closed',      'char', 'NULL',  1, '', '', 
         'statementnum', 'int', 'NULL', '', '', '',
@@ -2073,6 +2075,7 @@ sub tables_hashref {
       'columns' => [
         'categorynum',   'serial',  '', '', '', '', 
         'categoryname',  'varchar', '', $char_d, '', '', 
+        'weight',         'int', 'NULL',  '', '', '',
         'disabled',      'char', 'NULL',   1, '', '', 
       ],
       'primary_key' => 'categorynum',
index 7aecf45..e5cd5d3 100644 (file)
@@ -135,6 +135,9 @@ sub upgrade_data {
     #change recur_flat and enable_prorate
     'part_pkg_option' => [],
 
+    #add weights to pkg_category
+    'pkg_category' => [],
+
   ;
 
   \%hash;
index 55faa36..eefcc80 100644 (file)
@@ -1968,6 +1968,7 @@ sub print_generic {
                  'smallfooter'   => sub { map "$_", @_ },
                  'returnaddress' => sub { map "$_", @_ },
                  'coupon'        => sub { map "$_", @_ },
+                 'summary'       => sub { map "$_", @_ },
                },
     'html'  => {
                  'notes' =>
@@ -2001,6 +2002,7 @@ sub print_generic {
                      }  @_
                    },
                  'coupon'        => sub { "" },
+                 'summary'       => sub { "" },
                },
     'template' => {
                  'notes' =>
@@ -2031,6 +2033,7 @@ sub print_generic {
                      }  @_
                    },
                  'coupon'        => sub { "" },
+                 'summary'       => sub { "" },
                },
   );
 
@@ -2147,6 +2150,14 @@ sub print_generic {
     'unitprices'      => $conf->exists('invoice-unitprice'),
   );
 
+  $invoice_data{finance_section} = '';
+  if ( $conf->config('finance_pkgclass') ) {
+    my $pkg_class =
+      qsearchs('pkg_class', { classnum => $conf->config('finance_pkgclass') });
+    $invoice_data{finance_section} = $pkg_class->categoryname;
+  } 
+ $invoice_data{finance_amount} = '0.00';
+
   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) ){
@@ -2193,11 +2204,19 @@ sub print_generic {
 #  my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
   #my $balance_due = $self->owed + $pr_total - $cr_total;
   my $balance_due = $self->owed + $pr_total;
+  $invoice_data{'true_previous_balance'} = sprintf("%.2f", $self->previous_balance);
+  $invoice_data{'balance_adjustments'} = sprintf("%.2f", $self->previous_balance - $self->billing_balance);
   $invoice_data{'previous_balance'} = sprintf("%.2f", $pr_total);
   $invoice_data{'balance'} = sprintf("%.2f", $balance_due);
 
   my $agentnum = $self->cust_main->agentnum;
 
+  my $summarypage = '';
+  if ( $conf->exists('invoice_usesummary', $agentnum) ) {
+    $summarypage = 1;
+  }
+  $invoice_data{'summarypage'} = $summarypage;
+
   #do variable substitution in notes, footer, smallfooter
   foreach my $include (qw( notes footer smallfooter coupon )) {
 
@@ -2257,6 +2276,7 @@ sub print_generic {
                             'template' => '',
                           );
   my $other_money_char = $other_money_chars{$format};
+  $invoice_data{'dollar'} = $other_money_char;
 
   my @detail_items = ();
   my @total_items = ();
@@ -2271,21 +2291,27 @@ sub print_generic {
   my $previous_section = { 'description' => 'Previous Charges',
                            'subtotal'    => $other_money_char.
                                             sprintf('%.2f', $pr_total),
+                           'summarized'  => $summarypage ? 'Y' : '',
                          };
 
   my $taxtotal = 0;
   my $tax_section = { 'description' => 'Taxes, Surcharges, and Fees',
-                      'subtotal'    => $taxtotal }; # adjusted below
+                      'subtotal'    => $taxtotal,   # adjusted below
+                      'summarized'  => $summarypage ? 'Y' : '',
+                    };
 
   my $adjusttotal = 0;
   my $adjust_section = { 'description' => 'Credits, Payments, and Adjustments',
-                         'subtotal'    => 0 }; # adjusted below
+                         'subtotal'    => 0,   # adjusted below
+                         'summarized'  => $summarypage ? 'Y' : '',
+                       };
 
   my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y';
   my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum);
   my $late_sections = [];
   if ( $multisection ) {
-    push @sections, $self->_items_sections( $late_sections );
+    push @sections,
+      $self->_items_sections( $late_sections, $summarypage, $escape_function );
   }else{
     push @sections, { 'description' => '', 'subtotal' => '' };
   }
@@ -2330,6 +2356,10 @@ sub print_generic {
 
   foreach my $section (@sections, @$late_sections) {
 
+    $invoice_data{finance_amount} = sprintf('%.2f', $section->{'subtotal'} )
+      if ( $invoice_data{finance_section} &&
+           $section->{'description'} eq $invoice_data{finance_section} );
+
     $section->{'subtotal'} = $other_money_char.
                              sprintf('%.2f', $section->{'subtotal'})
       if $multisection;
@@ -2346,6 +2376,7 @@ sub print_generic {
     $options{'escape_function'} = $escape_function;
     $options{'format_function'} = sub { () } unless $unsquelched;
     $options{'unsquelched'} = $unsquelched;
+    $options{'summary_page'} = $summarypage;
 
     foreach my $line_item ( $self->_items_pkg(%options) ) {
       my $detail = {
@@ -2384,6 +2415,9 @@ sub print_generic {
   
   }
   
+  $invoice_data{current_less_finance} =
+    sprintf('%.2f', $self->charged - $invoice_data{finance_amount} );
+
   if ( $multisection && !$conf->exists('disable_previous_balance') ) {
     unshift @sections, $previous_section if $pr_total;
   }
@@ -2555,7 +2589,11 @@ sub print_generic {
       $total->{'total_item'} = &$embolden_function($self->balance_due_msg);
       $total->{'total_amount'} =
         &$embolden_function(
-          $other_money_char. sprintf('%.2f', $self->owed + $pr_total )
+          $other_money_char. sprintf('%.2f', $summarypage 
+                                               ? $self->charged +
+                                                 $self->billing_balance
+                                               : $self->owed + $pr_total
+                                    )
         );
       if ( $multisection ) {
         $adjust_section->{'posttotal'} = $total->{'total_item'}. ' '.
@@ -2574,6 +2612,49 @@ sub print_generic {
       if $unsquelched;
   }
 
+  my @includelist = ();
+  push @includelist, 'summary' if $summarypage;
+  foreach my $include ( @includelist ) {
+
+    my $inc_file = $conf->key_orbase("invoice_${format}$include", $template);
+    my @inc_src;
+
+    if ( length( $conf->config($inc_file, $agentnum) ) ) {
+
+      @inc_src = $conf->config($inc_file, $agentnum);
+
+    } else {
+
+      $inc_file = $conf->key_orbase("invoice_latex$include", $template);
+
+      my $convert_map = $convert_maps{$format}{$include};
+
+      @inc_src = map { s/\[\@--/$delimiters{$format}[0]/g;
+                       s/--\@\]/$delimiters{$format}[1]/g;
+                       $_;
+                     } 
+                 &$convert_map( $conf->config($inc_file, $agentnum) );
+
+    }
+
+    my $inc_tt = new Text::Template (
+      TYPE       => 'ARRAY',
+      SOURCE     => [ map "$_\n", @inc_src ],
+      DELIMITERS => $delimiters{$format},
+    ) or die "Can't create new Text::Template object: $Text::Template::ERROR";
+
+    unless ( $inc_tt->compile() ) {
+      my $error = "Can't compile $inc_file 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+$//
+      if ($format eq 'latex');
+  }
+
   $invoice_lines = 0;
   my $wasfunc = 0;
   foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
@@ -2850,21 +2931,30 @@ sub _date_pretty {
 sub _items_sections {
   my $self = shift;
   my $late = shift;
+  my $summarypage = shift;
+  my $escape = shift;
 
   my %s = ();
   my %l = ();
+  my %not_tax = ();
 
   foreach my $cust_bill_pkg ( $self->cust_bill_pkg )
   {
 
-    if ( $cust_bill_pkg->pkgnum > 0 ) {
+
       my $usage = $cust_bill_pkg->usage;
 
       foreach my $display ($cust_bill_pkg->cust_bill_pkg_display) {
+        next if ( $display->summary && $summarypage );
+
         my $desc = $display->section;
         my $type = $display->type;
 
-        if ( $display->post_total ) {
+        if ( $cust_bill_pkg->pkgnum > 0 ) {
+          $not_tax{$desc} = 1;
+        }
+
+        if ( $display->post_total && !$summarypage ) {
           if (! $type || $type eq 'S') {
             $l{$desc} += $cust_bill_pkg->setup
               if ( $cust_bill_pkg->setup != 0 );
@@ -2908,16 +2998,29 @@ sub _items_sections {
 
       }
 
-    }
-
   }
 
-  push @$late, map { { 'description' => $_,
+  my %cache = map { $_->categoryname => $_ }
+              qsearch( 'pkg_category', {disabled => 'Y'} );
+  $cache{$_->categoryname} = $_
+    foreach qsearch( 'pkg_category', {disabled => ''} );
+
+  push @$late, map { { 'description' => &{$escape}($_),
                        'subtotal'    => $l{$_},
                        'post_total'  => 1,
-                   } } sort keys %l;
-
-  map { {'description' => $_, 'subtotal' => $s{$_}} } sort keys %s;
+                   } }
+                 sort { $cache{$a}->weight <=> $cache{$b}->weight } keys %l;
+
+  map { { 'description' => &{$escape}($_),
+          'subtotal'    => $s{$_},
+          'summarized'  => $not_tax{$_} ? '' : 'Y',
+          'tax_section' => $not_tax{$_} ? '' : 'Y',
+      } }
+    sort { $cache{$a}->weight <=> $cache{$b}->weight }
+    ( $summarypage
+        ? ( grep { exists($s{$_}) || !$cache{$_}->disabled } keys %cache )
+        : ( keys %s )
+    );
 
 }
 
@@ -2999,6 +3102,7 @@ sub _items_cust_bill_pkg {
   my $format_function = $opt{format_function} || '';
   my $unsquelched = $opt{unsquelched} || '';
   my $section = $opt{section}->{description} if $opt{section};
+  my $summary_page = $opt{summary_page} || '';
 
   my @b = ();
   my ($s, $r, $u) = ( undef, undef, undef );
@@ -3018,6 +3122,7 @@ sub _items_cust_bill_pkg {
                                  ? $_->section eq $section
                                  : 1
                                }
+                          grep { $_->summary || !$summary_page }
                           $cust_bill_pkg->cust_bill_pkg_display
                         )
     {
index 9d7aae2..c8c242e 100644 (file)
@@ -361,7 +361,10 @@ sub part_pkg {
   if ( $self->pkgpart_override ) {
     qsearchs('part_pkg', { 'pkgpart' => $self->pkgpart_override } );
   } else {
-    $self->cust_pkg->part_pkg;
+    my $part_pkg;
+    my $cust_pkg = $self->cust_pkg;
+    $part_pkg = $cust_pkg->part_pkg if $cust_pkg;
+    $part_pkg;
   }
 }
 
index 93c6e87..cf70cbd 100644 (file)
@@ -52,7 +52,12 @@ sub section {
   if ( defined($value) ) {
     $self->setfield('section', $value);
   } else {
-    $self->getfield('section') || $self->cust_bill_pkg->part_pkg->categoryname;
+    my $section = $self->getfield('section');
+    unless ($section) {
+      my $part_pkg = $self->cust_bill_pkg->part_pkg;
+      $section = $part_pkg->categoryname if $part_pkg;
+    }
+    $section;
   }
 }
 
index b5a8f0d..16ab0ee 100644 (file)
@@ -2717,6 +2717,23 @@ sub bill {
     $tax = sprintf('%.2f', $tax );
     $total_setup = sprintf('%.2f', $total_setup+$tax );
   
+    my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
+                                                   'disabled'     => '',
+                                                 },
+                               );
+
+    my @display = ();
+    if ( $pkg_category and
+         $conf->config('invoice_latexsummary') ||
+         $conf->config('invoice_htmlsummary')
+       )
+    {
+
+      my %hash = (  'section' => $pkg_category->categoryname );
+      push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
+
+    }
+
     push @cust_bill_pkg, new FS::cust_bill_pkg {
       'pkgnum'   => 0,
       'setup'    => $tax,
@@ -2724,6 +2741,7 @@ sub bill {
       'sdate'    => '',
       'edate'    => '',
       'itemdesc' => $taxname,
+      'display'  => \@display,
       'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
       'cust_bill_pkg_tax_rate_location' => \@cust_bill_pkg_tax_rate_location,
     };
@@ -2761,11 +2779,24 @@ sub bill {
 
   my $charged = sprintf('%.2f', $total_setup + $total_recur );
 
+  my @cust_bill = $self->cust_bill;
+  my $balance = $self->balance;
+  my $previous_balance = scalar(@cust_bill)
+                           ?  $cust_bill[$#cust_bill]->billing_balance
+                           :  0;
+
+  $previous_balance += $cust_bill[$#cust_bill]->charged
+    if scalar(@cust_bill);
+  #my $balance_adjustments =
+  #  sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged);
+
   #create the new invoice
   my $cust_bill = new FS::cust_bill ( {
-    'custnum' => $self->custnum,
-    '_date'   => ( $invoice_time ),
-    'charged' => $charged,
+    'custnum'             => $self->custnum,
+    '_date'               => ( $invoice_time ),
+    'charged'             => $charged,
+    'billing_balance'     => $balance,
+    'previous_balance'    => $previous_balance,
   } );
   $error = $cust_bill->insert;
   if ( $error ) {
index c853393..b0397d4 100644 (file)
@@ -31,6 +31,8 @@ sub do_action {
   #my $cust_main = $self->cust_main($cust_bill);
   my $cust_main = $cust_bill->cust_main;
 
+  my $conf = new FS::Conf;
+
   my $amount =
     sprintf('%.2f', $cust_bill->owed * $self->option('percent') / 100 );
 
@@ -38,6 +40,7 @@ sub do_action {
     'amount'   => $amount,
     'pkg'      => $self->option('reason'),
     'taxclass' => $self->option('taxclass'),
+    'classnum'   => $conf->config('finance_pkgclass'),
     'setuptax' => $self->option('setuptax'),
   );
 
index c700301..163b4fa 100644 (file)
@@ -26,10 +26,13 @@ sub do_action {
 
   my $cust_main = $self->cust_main($cust_object);
 
+  my $conf = new FS::Conf;
+
   my %charge = (
     'amount'   => $self->option('charge'),
     'pkg'      => $self->option('reason'),
     'taxclass' => $self->option('taxclass'),
+    'classnum' => $conf->config('finance_pkgclass'),
     'setuptax' => $self->option('setuptax'),
   );
 
index 69578c9..0beaf1c 100644 (file)
@@ -1,11 +1,13 @@
 package FS::pkg_category;
 
 use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch );
+use vars qw( @ISA $me $DEBUG );
+use FS::Record qw( qsearch dbh );
 use FS::part_pkg;
 
 @ISA = qw( FS::Record );
+$DEBUG = 0;
+$me = '[FS::pkg_category]';
 
 =head1 NAME
 
@@ -95,10 +97,39 @@ sub check {
 
   $self->ut_numbern('categorynum')
   or $self->ut_text('categoryname')
+  or $self->ut_snumber('weight')
   or $self->SUPER::check;
 
 }
 
+# _ upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+#
+#
+
+sub _upgrade_data {
+  my ($class, %opts) = @_;
+  my $dbh = dbh;
+
+  warn "$me upgrading $class\n" if $DEBUG;
+
+  my @pkg_category =
+    qsearch('pkg_category', { weight => { op => '!=', value => '' } } );
+
+  unless( scalar(@pkg_category) ) {
+    my @pkg_category = qsearch('pkg_category', {} );
+    my $weight = 0;
+    foreach ( sort { $a->description cmp $b->description } @pkg_category ) {
+      $_->weight($weight);
+      my $error = $_->replace;
+      die "error setting pkg_category weight: $error\n" if $error;
+      $weight += 10;
+    }
+  }
+  '';
+}
+
 =back
 
 =head1 BUGS
index d5e24b8..73dcc2e 100644 (file)
@@ -3,6 +3,8 @@
 .invoice_header { font-size: 10pt }
 .invoice_headerright TH { border-top: 2px solid #000000; border-bottom: 2px solid #000000 }
 .invoice_headerright TD { font-size: 10pt; empty-cells: show }
+.invoice_summary TH { border-bottom: 2px solid #000000 }
+.invoice_summary TD { font-size: 10pt; empty-cells: show }
 .invoice_longtable table { cellspacing: none }
 .invoice_longtable TH { border-top: 2px solid #000000; border-bottom: 1px solid #000000; padding-left: none; padding-right: none; font-size: 10pt }
 .invoice_desc TD { border-top: 2px solid #000000; font-weight: bold; font-size: 10pt }
@@ -11,7 +13,7 @@
 .invoice_totaldesc TD { font-size: 10pt; empty-cells: show }
 </STYLE>
 
-<table class="invoice" bgcolor="#ffffff" WIDTH=625 CELLSPACING=8><tr><td>
+<table class="invoice" bgcolor="#ffffff" WIDTH=768 CELLSPACING=8><tr><td>
 
   <table class="invoice_header" width="100%">
     <tr>
     </tr>
 
   </table>
-
+  <%= $summary %>
   <%=
-      foreach my $section ( @sections ) {
-        if ($section->{'pretotal'}) {
+      foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) {
+        if ($section->{'pretotal'} && !$summary) {
           $OUT .=
             '<table width="100%"><tr><td>'.
             '<p align="right"><b><font size="+1">'.
             '<p>'.
             '</td></tr></table>';
         }
-        $OUT .= '<table><tr><td>';
-        if ($section->{'description'}) {
-          $OUT .=
-            '<p><b><font size="+1">'. uc(substr($section->{'description'},0,1)).
-            '</font><font size="+0">'. uc(substr($section->{'description'},1)).
-            '</font></b>'.
-            '<p>';
-        }else{
-          $OUT .=
-            '<p><b><font size="+1">C</font><font size="+0">HARGES</font></b>'.
-            '<p>';
-        }
-        $OUT .= '</td></tr></table>';
+        unless ($section->{'summarized'}) {
+          $OUT .= '<table><tr><td>';
+          if ($section->{'description'}) {
+            $OUT .=
+              '<p><b><font size="+1">'. uc(substr($section->{'description'},0,1)).
+              '</font><font size="+0">'. uc(substr($section->{'description'},1)).
+              '</font></b>'.
+              '<p>';
+          }else{
+            $OUT .=
+              '<p><b><font size="+1">C</font><font size="+0">HARGES</font></b>'.
+              '<p>';
+          }
+          $OUT .= '</td></tr></table>';
 
-        $OUT .=
-          '<table class="invoice_longtable" CELLSPACING=0 WIDTH="100%">'.
-          '<tr>'.
-            '<th align="center">Ref</th>'.
-            '<th align="left">Description</th>'.
-            ( $unitprices 
-                ? '<th align="left">Unit Price</th>'.
-                  '<th align="left">Quantity</th>'
-                : ''
-              ).
-            '<th align="right">Amount</th>'.
-          '</tr>';
-
-        my $lastref = 0;
-        foreach my $line (
-          grep { ( scalar(@sections) > 1 
-                 ? $section->{'description'} eq $_->{'section'}->{'description'}
-                 : 1
-               ) }
-          @detail_items )
-        {
           $OUT .=
-            '<tr class="invoice_desc'.
-              ( ($line->{'ref'} && $line->{'ref'} ne $lastref) ? '' : '_more' ).
-              '">'.
-              '<td align="center">'. 
-              ( $line->{'ref'} ne $lastref ? $line->{'ref'} : '' ). '</td>'.
-              '<td align="left">'. $line->{'description'}. '</td>'.
+            '<table class="invoice_longtable" CELLSPACING=0 WIDTH="100%">'.
+            '<tr>'.
+              '<th align="center">Ref</th>'.
+              '<th align="left">Description</th>'.
               ( $unitprices 
-                  ? '<td align="left">'. $line->{'unit_amount'}. '</td>'.
-                    '<td align="left">'. $line->{'quantity'}. '</td>'
+                  ? '<th align="left">Unit Price</th>'.
+                    '<th align="left">Quantity</th>'
                   : ''
-              ).
-
-              '<td align="right">'. $line->{'amount'}. '</td>'.
-            '</tr>'
-          ;
-          $lastref = $line->{'ref'};
-          if ( @{$line->{'ext_description'} } ) {
-            $OUT .= '<tr class="invoice_extdesc"><td></td><td';
-            $OUT .= $unitprices ? ' colspan=3>' : '>';
-            $OUT .= '<table width="100%">';
-            foreach my $ext_desc ( @{$line->{'ext_description'} } ) {
-              $OUT .=
-                '<tr class="invoice_extdesc">'.
-                  '<td align="left" '.
-                       ( $ext_desc =~ /<\/?TD>/i ? '' : 'colspan=99' ). '>'.
-                    '&nbsp;&nbsp;'. $ext_desc.
-                  '</td>'.
-                '</tr>'
+                ).
+              '<th align="right">Amount</th>'.
+            '</tr>';
+
+          my $lastref = 0;
+          foreach my $line (
+            grep { ( scalar(@sections) > 1 
+                   ? $section->{'description'} eq $_->{'section'}->{'description'}
+                   : 1
+                 ) }
+            @detail_items )
+          {
+            $OUT .=
+              '<tr class="invoice_desc'.
+                ( ($line->{'ref'} && $line->{'ref'} ne $lastref) ? '' : '_more' ).
+                '">'.
+                '<td align="center">'. 
+                ( $line->{'ref'} ne $lastref ? $line->{'ref'} : '' ). '</td>'.
+                '<td align="left">'. $line->{'description'}. '</td>'.
+                ( $unitprices 
+                    ? '<td align="left">'. $line->{'unit_amount'}. '</td>'.
+                      '<td align="left">'. $line->{'quantity'}. '</td>'
+                    : ''
+                ).
+
+                '<td align="right">'. $line->{'amount'}. '</td>'.
+              '</tr>'
+            ;
+            $lastref = $line->{'ref'};
+            if ( @{$line->{'ext_description'} } ) {
+              $OUT .= '<tr class="invoice_extdesc"><td></td><td';
+              $OUT .= $unitprices ? ' colspan=3>' : '>';
+              $OUT .= '<table width="100%">';
+              foreach my $ext_desc ( @{$line->{'ext_description'} } ) {
+                $OUT .=
+                  '<tr class="invoice_extdesc">'.
+                    '<td align="left" '.
+                         ( $ext_desc =~ /<\/?TD>/i ? '' : 'colspan=99' ). '>'.
+                      '&nbsp;&nbsp;'. $ext_desc.
+                    '</td>'.
+                  '</tr>'
+              }
+              $OUT .= '</table></td><td></td></tr>';
             }
-            $OUT .= '</table></td><td></td></tr>';
           }
-        }
 
 
-        if (scalar(@sections) > 1) {
-          my $style = 'border-top: 3px solid #000000;'.
-                      'border-bottom: 3px solid #000000;';
-          $OUT .=
-            '<tr class="invoice_totaldesc">'.
-              qq(<td style="$style">&nbsp;</td>).
-              qq(<td align="left" style="$style"). 
-                ( $unitprices ? ' colspan=3>' : '>' ).
-                $section->{'description'}. ' Total </td>'.
-              qq(<td align="right" style="$style">).
-                $section->{'subtotal'}. '</td>'.
-            '</tr>'
-        ;
-        }
-
+          if (scalar(@sections) > 1) {
+            my $style = 'border-top: 3px solid #000000;'.
+                        'border-bottom: 3px solid #000000;';
+            $OUT .=
+              '<tr class="invoice_totaldesc">'.
+                qq(<td style="$style">&nbsp;</td>).
+                qq(<td align="left" style="$style"). 
+                  ( $unitprices ? ' colspan=3>' : '>' ).
+                  $section->{'description'}. ' Total </td>'.
+                qq(<td align="right" style="$style">).
+                  $section->{'subtotal'}. '</td>'.
+              '</tr>'
+          ;
+          }
+        } 
         if ($section->{'posttotal'}) {
           $OUT .= '<tr><td align="right" colspan=5>';
           $OUT .=
   </table>
   <br><br>
 
-<%= $notes %>
+<%= length($summary) ? '' : $notes %>
 
   <hr NOSHADE SIZE=2 COLOR="#000000">
   <p align="center"><%= $footer %>
diff --git a/conf/invoice_htmlsummary b/conf/invoice_htmlsummary
new file mode 100644 (file)
index 0000000..b158478
--- /dev/null
@@ -0,0 +1,74 @@
+<table>
+  <tr>
+    <td>
+      <table>
+        <tr><td><%= $notes %></td></tr>
+      </table>
+    </td>
+    <td>
+      <table class="invoice_summary">
+        <tr><th colspan=2><br></th></tr>
+        <tr>
+          <td><b><u><br>Summary of Previous Balance and Payments<br></u></b></td>
+          <td></td>
+        </tr>
+        <tr>
+          <td><b>Previous Balance</b></td>
+          <td align="right"><b><%= $dollar.$true_previous_balance %></b></td>
+        </tr>
+        <tr>
+          <td><b>Payments</b></td>
+          <th align="right"><b><%= $dollar.$balance_adjustments %></b></th>
+        </tr>
+        <tr>
+          <td><b>Balance Outstanding</b></td>
+          <td align="right"><b><%= $dollar.sprintf('%.2f', $true_previous_balance - $balance_adjustments) %></b></td>
+        </tr>
+        <tr><th colspan=2><br></th></tr>
+        <tr><td colspan=2><br></td></tr>
+        <tr>
+          <td><b><u>Summary of New Charges</u></b></td>
+          <td></td>
+        </tr>
+        <tr><td colspan=2><br></td></tr>
+        <%= 
+          my ($last) = grep { $_->{tax_section} || !$_->{summarized} and !($finance_section && $_->{'description'} eq $finance_section)} reverse @sections;
+          
+          foreach my $section ( grep { $_->{tax_section} || !$_->{summarized} and !($finance_section && $_->{'description'} eq $finance_section)} @sections ) {
+            $OUT .= '<tr><td><b>'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '</b></td>';
+            my $celltype = ($last == $section) ? 'th' : 'td';
+            $OUT .= qq(<$celltype align="right"><b>). $section->{'subtotal'}. "</b></$celltype></tr>";
+          }
+        %>
+        <tr>
+          <td><b>New Charges Total</b></td>
+          <td align="right"><b><%= $dollar.$current_less_finance %></b></td>
+        </tr>
+        <tr><th colspan=2><br></th></tr>
+        <tr><td colspan=2><br></td></tr>
+        <tr>
+          <td><b><u>Invoice Summary</u></b></td>
+          <td></td>
+        </tr>
+        <tr><td colspan=2><br></td></tr>
+        <tr>
+          <td><b>Previous Past Due Charges</b></td>
+          <td align="right"><b><%= $dollar.sprintf('%.2f', $true_previous_balance - $balance_adjustments) %></b></td>
+        </tr>
+        <tr>
+          <td><b>Finance charges on overdue amount</b></td>
+          <td align="right"><b><%= $dollar.$finance_amount %></b></td>
+        </tr>
+        <tr>
+          <td><b>New Charges</b></td>
+          <th align="right"><b><%= $dollar.$current_less_finance %></b></th>
+        </tr>
+        <tr>
+          <td><b>Total Amount Due</b></td>
+          <td align="right"><b><%= $dollar.sprintf('%.2f', $true_previous_balance + $current_charges - $balance_adjustments) %></b></td>
+        </tr>
+        <tr><th colspan=2><br></th></tr>
+      </table>
+    </td>
+  </tr>
+</table>
index 7facc19..cf684ef 100644 (file)
@@ -19,7 +19,7 @@
 \r
 \documentclass[letterpaper]{article}\r
 \r
-\usepackage{fancyhdr,lastpage,ifthen,fslongtable,afterpage,caption,multirow,bigstrut}\r
+\usepackage{fancyhdr,lastpage,ifthen,array,fslongtable,afterpage,caption,multirow,bigstrut}\r
 \usepackage{graphicx}                  % required for logo graphic\r
 \r
 \addtolength{\voffset}{-0.0cm}         % top margin to top of header\r
@@ -232,83 +232,86 @@ Terms: [@-- $terms --@]\\
 \end{minipage}}\r
 \vspace{1.5cm}\r
 %\r
+[@-- $summary --@]\r
+%\r
 \section*{}\r
 [@--\r
-  foreach my $section ( @sections ) {\r
-    if ($section->{'pretotal'}) {\r
+  foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) {\r
+    if ($section->{'pretotal'} && !$summary) {\r
       $OUT .= '\begin{flushright}';\r
       $OUT .= '\large\textsc{'. $section->{'pretotal'}. '}\\\\';\r
       $OUT .= '\\end{flushright}';\r
     }\r
     $OUT .= '\pagebreak' if $section{'post_total'};\r
-    $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';\r
-    $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'\r
-      if $coupon;\r
-    $OUT .= '\begin{longtable}{cllllllr}';\r
-    $OUT .= '\caption*{ ';\r
-    $OUT .= ($section->{'description'}) ? $section->{'description'}: 'Charges';\r
-    $OUT .= '}\\\\';\r
-    $OUT .= '\FShead';\r
-    $OUT .= '\endfirsthead';\r
-    $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\\';\r
-    $OUT .= '\FShead';\r
-    $OUT .= '\endhead';\r
-    $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\\';\r
-    $OUT .= '\endfoot';\r
-    $OUT .= '\hline';\r
-\r
-    if (scalar(@sections) > 1) {\r
-      $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' .\r
-              '{' . $section->{'subtotal'} . '}' . "\n";\r
-    }\r
-\r
-    #if ($section == $sections[$#sections]) {\r
-      foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {\r
-        $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .\r
-                '{' . $line->{'total_amount'} . '}' . "\n";\r
+    unless ($section->{'summarized'} ) {\r
+      $OUT .= '\captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}}';\r
+      $OUT .= '\ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}}'\r
+        if $coupon;\r
+      $OUT .= '\begin{longtable}{cllllllr}';\r
+      $OUT .= '\caption*{ ';\r
+      $OUT .= ($section->{'description'}) ? $section->{'description'}: 'Charges';\r
+      $OUT .= '}\\\\';\r
+      $OUT .= '\FShead';\r
+      $OUT .= '\endfirsthead';\r
+      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\\';\r
+      $OUT .= '\FShead';\r
+      $OUT .= '\endhead';\r
+      $OUT .= '\multicolumn{7}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\\';\r
+      $OUT .= '\endfoot';\r
+      $OUT .= '\hline';\r
+\r
+      if (scalar(@sections) > 1) {\r
+        $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' .\r
+                '{' . $section->{'subtotal'} . '}' . "\n";\r
       }\r
-    #}\r
-\r
-    $OUT .= '\hline';\r
-    $OUT .= '\endlastfoot';\r
-\r
-    my $lastref = 0;\r
-    foreach my $line (\r
-      grep { ( scalar( @sections ) > 1 \r
-             ? $section->{'description'} eq $_->{'section'}->{'description'}\r
-             : 1\r
-           ) }\r
-      @detail_items )\r
-    {\r
-      my $ext_description = $line->{'ext_description'};\r
+\r
+      #if ($section == $sections[$#sections]) {\r
+        foreach my $line (grep {$_->{section}->{description} eq $section->{description}} @total_items) {\r
+          $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .\r
+                  '{' . $line->{'total_amount'} . '}' . "\n";\r
+        }\r
+      #}\r
+\r
+      $OUT .= '\hline';\r
+      $OUT .= '\endlastfoot';\r
+\r
+      my $lastref = 0;\r
+      foreach my $line (\r
+        grep { ( scalar( @sections ) > 1 \r
+               ? $section->{'description'} eq $_->{'section'}->{'description'}\r
+               : 1\r
+             ) }\r
+        @detail_items )\r
+      {\r
+        my $ext_description = $line->{'ext_description'};\r
   \r
-      # Don't break-up small packages.\r
-      my $rowbreak = @$ext_description < 5 ? '*' : '';\r
+        # Don't break-up small packages.\r
+        my $rowbreak = @$ext_description < 5 ? '*' : '';\r
   \r
-      $OUT .= "\\hline\n" if ($line->{'ref'} && $line->{'ref'} ne $lastref);\r
-      $OUT .= '\FSdesc'.\r
-              '{' . ( $line->{'ref'} ne $lastref ? $line->{'ref'} : '' ) . '}'.\r
-              '{' . $line->{'description'} . '}' .\r
-              '{' . ( $unitprices ? $line->{'unit_amount'} : '' ) . '}'.\r
-              '{' . ( $unitprices ? $line->{'quantity'} : ''  ) . '}' .\r
-              '{' . $line->{'amount'} . "}${rowbreak}\n";\r
-      $lastref = $line->{'ref'};\r
-\r
-      foreach my $ext_desc (@$ext_description) {\r
-        if ( $ext_desc !~ /[^\\]&/ ) {\r
-          $ext_desc = substr($ext_desc, 0, 80) . '...'\r
-            if (length($ext_desc) > 80);\r
-          $ext_desc = '\multicolumn{6}{l}{\small{~~~'. $ext_desc. '}}';\r
-        }else{\r
-          $ext_desc = "~~~$ext_desc";\r
+        $OUT .= "\\hline\n" if ($line->{'ref'} && $line->{'ref'} ne $lastref);\r
+        $OUT .= '\FSdesc'.\r
+                '{' . ( $line->{'ref'} ne $lastref ? $line->{'ref'} : '' ) . '}'.\r
+                '{' . $line->{'description'} . '}' .\r
+                '{' . ( $unitprices ? $line->{'unit_amount'} : '' ) . '}'.\r
+                '{' . ( $unitprices ? $line->{'quantity'} : ''  ) . '}' .\r
+                '{' . $line->{'amount'} . "}${rowbreak}\n";\r
+        $lastref = $line->{'ref'};\r
+\r
+        foreach my $ext_desc (@$ext_description) {\r
+          if ( $ext_desc !~ /[^\\]&/ ) {\r
+            $ext_desc = substr($ext_desc, 0, 80) . '...'\r
+              if (length($ext_desc) > 80);\r
+            $ext_desc = '\multicolumn{6}{l}{\small{~~~'. $ext_desc. '}}';\r
+          }else{\r
+            $ext_desc = "~~~$ext_desc";\r
+          }\r
+          $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";\r
         }\r
-        $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";\r
+\r
       }\r
 \r
+      $OUT .= '\end{longtable}';\r
     }\r
-\r
-    $OUT .= '\end{longtable}';\r
-\r
     if ($section->{'posttotal'}) {\r
       $OUT .= '\begin{flushright}';\r
       $OUT .= '\normalfont\large\bfseries\textsc{'. $section->{'posttotal'}. '}\\\\';\r
@@ -319,7 +322,7 @@ Terms: [@-- $terms --@]\\
 --@]\r
 \vfill\r
 \begin{minipage}[t]{\textwidth}\r
-  [@-- $notes --@]\r
+  [@-- length($summary) ? '' : $notes --@]\r
   [@-- $coupon ? '\ifthenelse{\equal{\thepage}{1}}{\rule{0pt}{\extracouponspace}}{}' : '' --@]\r
 \end{minipage}\r
 \end{document}\r
diff --git a/conf/invoice_latexsummary b/conf/invoice_latexsummary
new file mode 100644 (file)
index 0000000..a181ee4
--- /dev/null
@@ -0,0 +1,45 @@
+\begin{tabular}{ll}
+\begin{minipage}{6.4cm}
+\begin{tabular}{m{0cm}m{6.4cm}}
+\rule{0cm}{10cm}&\begin{minipage}{6cm}[@-- $notes --@]\end{minipage}\\
+\end{tabular}
+\end{minipage} &
+\rule{2cm}{0cm}
+\begin{minipage}{12.8cm}
+\begin{tabular}{lr}
+\hline
+&\\
+\textbf{\underline{Summary of Previous Balance and Payments}} & \\
+&\\
+\textbf{Previous Balance}&\textbf{\dollar[@-- $true_previous_balance --@]}\\
+\textbf{Payments}&\textbf{\dollar[@-- $balance_adjustments --@]}\\
+\cline{2-2}
+\textbf{Balance Outstanding}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\
+&\\
+\hline
+&\\
+\textbf{\underline{Summary of New Charges}} & \\
+&\\
+[@--
+  foreach my $section ( grep { $_->{tax_section} || !$_->{summarized} and !($finance_section && $_->{'description'} eq $finance_section)} @sections ) {
+    $OUT .= '\textbf{'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '}';
+    $OUT .= '&\textbf{'. $section->{'subtotal'}. '}\\\\';
+  }
+  $OUT .= '\cline{2-2}';
+--@]
+\textbf{New Charges Total}&\textbf{\dollar[@-- $current_less_finance --@]}\\
+&\\
+\hline
+&\\
+\textbf{\underline{Invoice Summary}} & \\
+& \\
+\textbf{Previous Past Due Charges}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\
+\textbf{Finance charges on overdue amount}&\textbf{\dollar[@-- $finance_amount --@]}\\
+\textbf{New Charges}&\textbf{\dollar[@-- $current_less_finance --@]}\\
+\cline{2-2}
+\textbf{Total Amount Due}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance + $current_charges - $balance_adjustments) --@]}\\
+&\\
+\hline
+\end{tabular}
+\end{minipage} \\
+\end{tabular}
index 20bf1a8..e85c0dd 100644 (file)
@@ -9,9 +9,9 @@
                                     'extra_sql' => 'ORDER BY categorynum',
                                   },
                  'count_query' => $count_query,
-                 'header'      => [ '#', 'Category' ],
-                 'fields'      => [ 'categorynum', 'categoryname' ],
-                 'links'       => [ $link, $link ],
+                 'header'      => [ '#', 'Category', 'Weight' ],
+                 'fields'      => [ 'categorynum', 'categoryname', 'weight' ],
+                 'links'       => [ $link, $link, $link ],
              )
 %>
 
index fdc8da6..a07dc58 100644 (file)
@@ -3,11 +3,13 @@
               'table'  => 'pkg_category',
               'fields' => [
                             'categoryname',
+                            'weight',
                             { field=>'disabled', type=>'checkbox', value=>'Y', },
                           ],
               'labels' => { 
                             'categorynum'  => 'Category number',
                             'categoryname' => 'Category name',
+                            'weight'       => 'Weight',
                             'disabled'  => 'Disable category',
                           },
               'viewall_dir' => 'browse',