Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE...
authorIvan Kohler <ivan@freeside.biz>
Fri, 10 Jul 2015 00:57:01 +0000 (17:57 -0700)
committerIvan Kohler <ivan@freeside.biz>
Fri, 10 Jul 2015 00:57:01 +0000 (17:57 -0700)
14 files changed:
FS/FS/Report/Table.pm
FS/FS/cust_main.pm
httemplate/graph/cust_bill_pkg.cgi
httemplate/graph/report_cust_bill_pkg.html
httemplate/misc/email-customer-statement.html
httemplate/misc/email-customers.html
httemplate/misc/process/email-customer-statement.html [new file with mode: 0644]
httemplate/search/cust_bill_pkg.cgi
httemplate/search/elements/cust_pay_or_refund.html
httemplate/search/elements/grouped-search/core
httemplate/search/elements/grouped-search/html
httemplate/search/elements/grouped-search/html-print
httemplate/view/cust_main/contacts.html
httemplate/view/cust_main/payment_history.html

index eef983d..cba968b 100644 (file)
@@ -439,8 +439,8 @@ sub cust_pkg_recur_cost {
 
 =item cust_bill_pkg: the total package charges on invoice line items.
 
-'charges': limit the type of charges included (setup, recur, usage).
-Should be a string containing one or more of 'S', 'R', or 'U'; if 
+'charges': limit the type of charges included (setup, recur, usage, discount).
+Should be a string containing one or more of 'S', 'R', 'U', or 'D'; if 
 unspecified, defaults to all three.
 
 'classnum': limit to this package class.
@@ -470,6 +470,7 @@ sub cust_bill_pkg {
   $sum += $self->cust_bill_pkg_setup(@_) if $charges{S};
   $sum += $self->cust_bill_pkg_recur(@_) if $charges{R};
   $sum += $self->cust_bill_pkg_detail(@_) if $charges{U};
+  $sum += $self->cust_bill_pkg_discount(@_) if $charges{D};
 
   if ($opt{'average_per_cust_pkg'}) {
     my $count = $self->cust_bill_pkg_count_pkgnum(@_);
@@ -656,47 +657,28 @@ sub cust_bill_pkg_detail {
 }
 
 sub cust_bill_pkg_discount {
-  my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
-  #need to do this the new multi-classnum way if it gets re-enabled
-  #my $where = '';
-  #my $comparison = '';
-  #if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
-  #  if ( $1 == 0 ) {
-  #    $comparison = "IS NULL";
-  #  } else {
-  #    $comparison = "= $1";
-  #  }
-  #
-  #  if ( $opt{'use_override'} ) {
-  #    $where = "(
-  #      part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
-  #      override.classnum $comparison AND pkgpart_override IS NOT NULL
-  #    )";
-  #  } else {
-  #    $where = "part_pkg.classnum $comparison";
-  #  }
-  #}
+  my $self = shift;
+  my ($speriod, $eperiod, $agentnum, %opt) = @_;
+  # apply all the same constraints here as for setup/recur
 
   $agentnum ||= $opt{'agentnum'};
 
-  my $total_sql =
-    " SELECT COALESCE( SUM( cust_bill_pkg_discount.amount ), 0 ) ";
+  my @where = (
+    '(pkgnum != 0 OR feepart IS NOT NULL)',
+    $self->with_classnum($opt{'classnum'}, $opt{'use_override'}),
+    $self->with_report_option(%opt),
+    $self->in_time_period_and_agent($speriod, $eperiod, $agentnum),
+    $self->with_refnum(%opt),
+    $self->with_cust_classnum(%opt)
+  );
 
-  $total_sql .=
-    " FROM cust_bill_pkg_discount
-        LEFT JOIN cust_bill_pkg USING ( billpkgnum )
-        LEFT JOIN cust_bill USING ( invnum )
-        LEFT JOIN cust_main USING ( custnum )
-      WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
-  #      LEFT JOIN cust_pkg_discount USING ( pkgdiscountnum )
-  #      LEFT JOIN discount USING ( discountnum )
-  #      LEFT JOIN cust_pkg USING ( pkgnum )
-  #      LEFT JOIN part_pkg USING ( pkgpart )
-  #      LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
-  
-  return $self->scalar_sql($total_sql);
+  my $total_sql = "SELECT COALESCE(SUM(cust_bill_pkg_discount.amount), 0)
+  FROM cust_bill_pkg_discount
+  JOIN cust_bill_pkg USING (billpkgnum)
+  $cust_bill_pkg_join
+  WHERE " . join(' AND ', grep $_, @where);
 
+  $self->scalar_sql($total_sql);
 }
 
 ##### package churn report #####
index 1afacea..d1e9a22 100644 (file)
@@ -4595,6 +4595,8 @@ sub payment_history {
       'description' => 'Previous balance',
       'amount'      => sprintf("%.2f",$previous),
       'balance'     => sprintf("%.2f",$previous),
+      'date'        => $$opt{'start_date'},
+      'date_pretty' => $self->time2str_local('short', $$opt{'start_date'} ),
     };
     #false laziness with above
     foreach my $key ( qw(amount balance) ) {
index ea7fee9..b5486f4 100644 (file)
@@ -27,6 +27,8 @@ my $bottom_link = "$link;";
 
 my $use_usage = $cgi->param('use_usage') || 0;
 my $use_setup = $cgi->param('use_setup') || 0;
+my $use_discount = $cgi->param('use_discount') || 2;
+
 my $use_override         = $cgi->param('use_override')         ? 1 : 0;
 my $average_per_cust_pkg = $cgi->param('average_per_cust_pkg') ? 1 : 0;
 my $distribute           = $cgi->param('distribute')           ? 1 : 0;
@@ -41,11 +43,13 @@ if ( $average_per_cust_pkg ) {
 }
 
 my %charge_labels = (
+  'SRU'=> 'setup + recurring',
   'SR' => 'setup + recurring',
   'RU' => 'recurring',
   'S'  => 'setup',
   'R'  => 'recurring',
   'U'  => 'usage',
+  'D'  => 'discount',
 );
 
 #XXX or virtual
@@ -186,6 +190,10 @@ elsif ( $use_usage == 2 ) {
   $components[-1] =~ s/U//;
 }
 
+if ( $use_discount == 1 ) {
+  push @components, 'D';
+} # else leave discounts off entirely; never combine them with setup/recur
+
 # Categorization of line items goes
 # Agent -> Referral -> Package class -> Component (setup/recur/usage)
 # If per-agent totals are enabled, they go under the Agent level.
@@ -240,21 +248,26 @@ foreach my $agent ( $all_agent || $sel_agent || $FS::CurrentUser::CurrentUser->a
                         'charges'               => $component,
         );
 
-        # XXX this is very silly.  we should cache it server-side and 
-        # just put a cache identifier in the link
-        my $rowlink = "$link;".
-                      ($all_agent ? '' : "agentnum=$row_agentnum;").
+        my $row_link = "$link;".
+                       "charges=$component;".
+                       "distribute=$distribute;";
+
+        if ( $component eq 'D' ) {
+          # discounts ignore 'charges' and 'distribute'
+          $row_link = "${p}search/cust_bill_pkg_discount.html?";
+        }
+
+        $row_link .=  ($all_agent ? '' : "agentnum=$row_agentnum;").
                       ($all_part_referral ? '' : "refnum=$row_refnum;").
                       (join('',map {"cust_classnum=$_;"} @cust_classnums)).
-                      "distribute=$distribute;".
-                      "use_override=$use_override;charges=$component;";
-        $rowlink .= "$class_param=$_;" foreach @classnums;
+                      "use_override=$use_override;";
+        $row_link .= "$class_param=$_;" foreach @classnums;
         if ( $all_report_options ) {
           push @row_params, 'all_report_options', 1;
-          $rowlink .= 'all_report_options=1';
+          $row_link .= 'all_report_options=1';
         }
         push @params, \@row_params;
-        push @links, $rowlink;
+        push @links, $row_link;
 
         @colorbuf = @agent_colors unless @colorbuf;
         push @colors, shift @colorbuf;
@@ -293,13 +306,22 @@ foreach my $agent ( $all_agent || $sel_agent || $FS::CurrentUser::CurrentUser->a
                           ($all_part_referral ? () : ('refnum' => $row_refnum)),
                           'charges'              => $component,
           );
+
           my $row_link = "$link;".
-                       ($all_agent ? '' : "agentnum=$row_agentnum;").
+                       "charges=$component;".
+                       "distribute=$distribute;";
+
+          if ( $component eq 'D' ) {
+            # discounts ignore 'charges' and 'distribute'
+            $row_link ="${p}search/cust_bill_pkg_discount.html?";
+          }
+
+          $row_link .= ($all_agent ? '' : "agentnum=$row_agentnum;").
                        ($all_part_referral ? '' : "refnum=$row_refnum;").
                        (join('',map {"cust_classnum=$_;"} @cust_classnums)).
                        "$class_param=$row_classnum;".
-                       "distribute=$distribute;".
-                       "use_override=$use_override;charges=$component;";
+                       "use_override=$use_override;";
+
           if ( $class_param eq 'report_optionnum' ) {
             push @row_params,
                           'all_report_options' => 1,
@@ -366,17 +388,6 @@ foreach my $agent ( $all_agent || $sel_agent || $FS::CurrentUser::CurrentUser->a
 
 }
 
-# may be useful at some point...
-#if ( $average_per_cust_pkg ) {
-#  @items = map { ('cust_bill_pkg', 'cust_bill_pkg_count_pkgnum') } @items;
-#  @labels = map { $_, "Packages" } @labels;
-#  @params = map { $_, $_ } @params;
-#  @links = map { $_, $_ } @links;
-#  @colors = map { $_, $_ } @colors;
-#  @no_graph = map { $_, 1 } @no_graph;
-#}
-#
-
 #use Data::Dumper;
 if ( $cgi->param('debug') == 1 ) {
   $FS::Report::Table::DEBUG = 1;
index 76d3a6c..e996714 100644 (file)
@@ -190,6 +190,12 @@ window.onload = class_mode_changed;
     'onchange'=> 'enable_agent_totals',
 &>
 % }
+<& /elements/tr-select.html,
+    'label'   => 'Discounts',
+    'field'   => 'use_discount',
+    'options' => [ 2, 1 ], # 3.x only: make 2 the default
+    'labels'  => { 1 => 'Separate', 2 => 'Do not show' },
+&>
 
 <TR>
   <TD ALIGN="right">Colors</TD>
index 65660f1..92ce1c4 100644 (file)
@@ -5,13 +5,17 @@
       'no_search_fields'  => [ 'start_date', 'end_date' ],
       'alternate_form'    => $alternate_form,
       'post_search_hook'  => $post_search_hook,
+      'acl'               => $acl,
+      'process_url'       => 'process/email-customer-statement.html',
     )
  %>
 
 <%init>
 
+my $acl = 'Resend invoices';
+
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+  unless $FS::CurrentUser::CurrentUser->access_right($acl);
 
 my $alternate_form = sub {
   # this could maaaybe be a separate element, for cleanliness
index 3327303..57f451f 100644 (file)
@@ -10,8 +10,12 @@ This may also be used as an element in other pages, enabling you to provide an
 alternate initial form while using this for search freezing/thawing and 
 preview/send actions, with the following options:
 
+acl - the access right to use (defaults to 'Bulk send customer notices')
+
 form_action - the URL to submit the form to
 
+process_url - the URL for starting the JSRPC process
+
 title - the title of the page
 
 no_search_fields - arrayref of additional fields that are not search parameters
@@ -50,7 +54,7 @@ should be used to set msgnum or from/subject/html_body cgi params
     <% include('/elements/progress-init.html',
                  'OneTrueForm',
                  [ qw( search table from subject html_body text_body msgnum ) ],
-                 'process/email-customers.html',
+                 $process_url,
                  $pdest,
               )
     %>
@@ -90,7 +94,7 @@ should be used to set msgnum or from/subject/html_body cgi params
 
       <INPUT TYPE="hidden" NAME="html_body" VALUE="<% $html_body |h %>">
       <TR>
-        <TD ALIGN="right" VALIGN="top">Message (HTML display): </TD>
+        <TH ALIGN="right" VALIGN="top">Message (HTML display): </TD>
         <TD CLASS="background" ALIGN="left"><% $html_body %></TD>
       </TR>
 
@@ -101,8 +105,11 @@ should be used to set msgnum or from/subject/html_body cgi params
 %                     );
       <INPUT TYPE="hidden" NAME="text_body" VALUE="<% $text_body |h %>">
       <TR>
-        <TD ALIGN="right" VALIGN="top">Message (Text display): </TD>
-        <TD CLASS="background" STYLE="background-color:white" ALIGN="left"><PRE><% $text_body %></PRE></TD>
+        <TH ALIGN="right" VALIGN="top">Message (Text display): </TD>
+        <TD CLASS="background" STYLE="background-color:white" ALIGN="left">
+          <a href="javascript:void(0)" onclick="this.style.display='none'; document.getElementById('email-message-text').style.display=''">click to view</a>
+          <PRE id="email-message-text" style="display: none;"><% $text_body %></PRE>
+        </TD>
       </TR>
 
     </TABLE>
@@ -192,13 +199,16 @@ Template:
 
 my %opt = @_;
 
+$opt{'acl'} ||= 'Bulk send customer notices';
+
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices');
+  unless $FS::CurrentUser::CurrentUser->access_right($opt{'acl'});
 
 my $conf = FS::Conf->new;
 my @no_search_fields = qw( action table from subject html_body text_body popup url );
 
 my $form_action = $opt{'form_action'} || 'email-customers.html';
+my $process_url = $opt{'process_url'} || 'process/email-customers.html';
 my $title = $opt{'title'} || 'Send customer notices';
 push( @no_search_fields, @{$opt{'no_search_fields'}} ) if $opt{'no_search_fields'};
 
diff --git a/httemplate/misc/process/email-customer-statement.html b/httemplate/misc/process/email-customer-statement.html
new file mode 100644 (file)
index 0000000..40a8a70
--- /dev/null
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $server = new FS::UI::Web::JSRPC 'FS::cust_main_Mixin::process_email_search_result', $cgi; 
+
+</%init>
index cbe37bc..8892af1 100644 (file)
@@ -186,8 +186,8 @@ my $conf = new FS::Conf;
 my $money_char = $conf->config('money_char') || '$';
 
 my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
-my @total = ( 'COUNT(*)', 'SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)');
-my @total_desc = ( $money_char.'%.2f total' ); # sprintf strings
+my @total = ( 'COUNT(*)' );
+my @total_desc = ();
 
 my @peritem = ( 'setup', 'recur' );
 my @peritem_desc = ( 'Setup charges', 'Recurring charges' );
@@ -489,14 +489,32 @@ if ( $cgi->param('nottax') ) {
   # setup/recur/usage separation
   my %charges = map { $_ => 1 } split('', $cgi->param('charges') || 'SRU');
 
-  if ( $charges{R} and $charges{U} ) {
+  if ( $charges{S} and $charges{R} and $charges{U} ) {
+    # in this case, show discounts
 
-    # default, don't change @peritem or @total
-    if ( !$charges{S} ) {
-      push @where, 'cust_bill_pkg.recur > 0';
-      $total[1] = "SUM(cust_bill_pkg.recur)";
-      $total_desc[0] = "$money_char%.2f recurring";
-    }
+    $join_pkg .= ' JOIN (
+    SELECT billpkgnum, COALESCE(SUM(amount), 0) AS discounted
+      FROM cust_bill_pkg_discount RIGHT JOIN cust_bill_pkg USING (billpkgnum)
+      GROUP BY billpkgnum
+    ) AS _discount ON (cust_bill_pkg.billpkgnum = _discount.billpkgnum)
+    ';
+    push @select, '_discount.discounted';
+
+    push @peritem, 'discounted';
+    push @peritem_desc, 'Discount';
+    push @total, 'SUM(cust_bill_pkg.setup + cust_bill_pkg.recur + discounted)',
+                 'SUM(discounted)',
+                 'SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)';
+    push @total_desc, "$money_char%.2f gross sales",
+                      "&minus; $money_char%.2f discounted",
+                      "= $money_char%.2f invoiced";
+
+  } elsif ( $charges{R} and $charges{U} ) {
+
+    # hide rows with no recurring fee, and show the sum of recurring fees only
+    push @where, 'cust_bill_pkg.recur > 0';
+    push @total, "SUM(cust_bill_pkg.recur)";
+    push @total_desc, "$money_char%.2f recurring";
 
   } elsif ( $charges{R} and !$charges{U} ) {
 
@@ -505,8 +523,8 @@ if ( $cgi->param('nottax') ) {
     push @select, "($recur_no_usage) AS recur_no_usage";
     $peritem[1] = 'recur_no_usage';
     $peritem_desc[1] = 'Recurring charges (excluding usage)';
-    $total[1] = "SUM($recur_no_usage)";
-    $total_desc[0] = "$money_char%.2f recurring";
+    push @total, "SUM($recur_no_usage)";
+    push @total_desc, "$money_char%.2f recurring";
     if ( !$charges{S} ) {
       push @where, "($recur_no_usage) > 0";
     }
@@ -518,8 +536,8 @@ if ( $cgi->param('nottax') ) {
     # there's already a method named 'usage'
     $peritem[1] = '_usage';
     $peritem_desc[1] = 'Usage charge';
-    $total[1] = "SUM($usage)";
-    $total_desc[0] = "$money_char%.2f usage charges";
+    push @total, "SUM($usage)";
+    push @total_desc, "$money_char%.2f usage charges";
     if ( !$charges{S} ) {
       push @where, "($usage) > 0";
     }
@@ -527,8 +545,8 @@ if ( $cgi->param('nottax') ) {
   } elsif ( $charges{S} ) {
 
     push @where, "cust_bill_pkg.setup > 0";
-    $total[1] = "SUM(cust_bill_pkg.setup)";
-    $total_desc[0] = "$money_char%.2f setup";
+    push @total, "SUM(cust_bill_pkg.setup)";
+    push @total_desc, "$money_char%.2f setup";
 
   } # else huh? you have to have SOME charges
 
@@ -551,10 +569,11 @@ if ( $cgi->param('nottax') ) {
       }
     }
 
-    $total[1] = 'SUM(
+    push @total, 'SUM(
       COALESCE(cust_bill_pkg_tax_rate_location.amount, 
                cust_bill_pkg.setup + cust_bill_pkg.recur)
     )';
+    push @total_desc, "$money_char%.2f total";
 
   } else { # the internal-tax case
 
@@ -564,8 +583,9 @@ if ( $cgi->param('nottax') ) {
     ';
 
     # don't double-count the components of consolidated taxes
-    $total[0] = 'COUNT(DISTINCT cust_bill_pkg.billpkgnum)';
-    $total[1] = 'SUM(cust_bill_pkg_tax_location.amount)';
+    @total = ( 'COUNT(DISTINCT cust_bill_pkg.billpkgnum)',
+               'SUM(cust_bill_pkg_tax_location.amount)' );
+    @total_desc = "$money_char%.2f total";
 
     # taxclass
     if ( $cgi->param('taxclassNULL') ) {
index ce513ab..5808e5f 100755 (executable)
@@ -57,13 +57,14 @@ Examples:
 
                 'group_column'   => 'payby',
                 'group_label'    => 'payby_name',
-                'subtotal'       => { 'paid' => 'sum(paid)' },
+                'subtotal'       => { $opt{name_verb} => "sum($amount_field)" },
                 'subtotal_row'   => [ 'Subtotal',
-                                      sub { sprintf($money, $_[0]->paid) },
+                                      sub { sprintf($money, $_[0]->$amount_field) },
                                     ],
                 'total_row'      => [ '<B>Total</B>',
-                                      sub { sprintf("<B>$money</B>", $_[0]->paid) },
+                                      sub { sprintf("<B>$money</B>", $_[0]->$amount_field) },
                                     ],
+                'show_combined'  => 1,
 &>
 <%init>
 
@@ -181,7 +182,7 @@ push @fields, 'payby_payinfo_pretty',
               sub { sprintf($money, shift->$amount_field() ) },
 ;
 push @link_onclicks, $sub_receipt, '';
-push @sort_fields, '', $amount_field;
+push @sort_fields, 'paysort', $amount_field;
 
 if ( $unapplied ) {
   push @header, emt('Unapplied');
@@ -243,6 +244,7 @@ if ( $cgi->param('magic') ) {
   my @search = ();
   my @select = (
     "$table.*",
+    "( $table.payby || ' ' || coalesce($table.paymask, $table.payinfo) ) AS paysort",
     FS::UI::Web::cust_sql_fields(),
     'cust_main.custnum AS cust_main_custnum',
   );
index fe47fb7..ffa8cee 100644 (file)
@@ -130,6 +130,15 @@ for my $i (0 .. scalar(@groups) - 1) {
   push @queries, $detail_query;
 }
 
+if ( $opt{show_combined} ) {
+  # set up group 0 as a combined view
+  unshift @groups, $totals;
+  unshift @group_labels, 'All ' . PL($opt{name_singular}) .
+                         ' (' . $totals->num_rows . ')';
+  unshift @group_footers, []; # the total footer will suffice
+  unshift @queries, $base_query->clone;
+}
+
 my @total_footer;
 if ($opt{'total_row'}) {
   for( my $col = 0;
index aff17ce..df1471a 100644 (file)
@@ -71,6 +71,23 @@ for (my $i = 0; $i < $group_info->{num}; $i++) {
   push @menubar, $group_info->{group_labels}[$i], ";group=$i";
 }
 
+# not enabled yet; if we need this at some point, enable it on a per-report
+# basis and then disable it for search/cust_pay.html, because it's redundant
+# to see "Check   Check #130108", "Credit card   Card #401...", etc.
+
+## if this is the combined view, add a column for the group key
+#if ( $curr_group == 0 and $opt{'show_combined'} ) {
+#  unshift @{$opt{'header'}}, '';
+#  unshift @{$opt{'fields'}}, $opt{group_label};
+#  unshift @{$opt{'sort_fields'}}, $opt{group_column} if $opt{'sort_fields'};
+#  $opt{'align'} = 'c'.$opt{'align'};
+#  foreach (qw(header2 links link_onclicks color size style cell_style xls_format)) {
+#    if ( $opt{$_} ) {
+#      unshift @{$opt{$_}}, '';
+#    }
+#  }
+#}
+
 </%init>
 
 <& /elements/header.html, $opt{title} &>
@@ -93,6 +110,7 @@ for (my $i = 0; $i < $group_info->{num}; $i++) {
 <A HREF="<% $cgi->self_url %>"><% emt('as Excel spreadsheet') %></A><BR>
 % $cgi->param('type', 'html-print');
 <A HREF="<% $cgi->self_url %>"><% emt('as printable copy') %></A><BR>
+% $cgi->delete('type');
 </P>
 
 <% $pager %>
index c4c3b1e..6d9521b 100644 (file)
@@ -5,7 +5,9 @@ The "printable" view (all groups on one page).
 <%init>
 my %opt = @_;
 
-my $group_info = $m->comp('core', %opt);
+my $group_info = $m->comp('core', %opt,
+  'show_combined' => 0
+);
 my $ncols = scalar(@{ $opt{header} });
 
 my $total_footer = $group_info->{total_footer} || [];
index e810d6f..136f634 100644 (file)
 % } 
 
 <TR>
+% if ( $location->city ) {
   <TD ALIGN="right"><% mt('City') |h %></TD>
   <TD BGCOLOR="#ffffff"><% $location->city |h %></TD>
+% } else {
+  <TD COLSPAN="2"></TD>
+% }
 % if ( $location->county ) {
     <TD ALIGN="right"><% mt('County') |h %></TD>
     <TD BGCOLOR="#ffffff"><% $location->county |h %></TD>
index db2cf4f..69571c7 100644 (file)
 
 %# invoice reports, combined statement
 % if ( $curuser->access_right('List invoices') ) { 
-%   if ( $curuser->access_right('Bulk send customer notices')
+%   if ( $curuser->access_right('Resend invoices')
 %          && $cust_main->invoicing_list_emailonly ) {
 
   <A HREF="<% $p %>misc/email-customer-statement.html?table=cust_main;agent_virt_agentnum=<% $cust_main->agentnum %>;custnum=<% $custnum %>"><% mt('Email statement to this customer') |h %></A>