disable auto-billing of specific customer packages, RT#6378
authorivan <ivan>
Thu, 18 Mar 2010 07:59:52 +0000 (07:59 +0000)
committerivan <ivan>
Thu, 18 Mar 2010 07:59:52 +0000 (07:59 +0000)
14 files changed:
FS/FS/Schema.pm
FS/FS/cust_bill.pm
FS/FS/cust_main.pm
FS/FS/cust_pkg.pm
FS/FS/part_event/Condition/cust_bill_has_noauto.pm [new file with mode: 0644]
FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm [new file with mode: 0644]
FS/FS/part_pkg.pm
httemplate/edit/process/quick-charge.cgi
httemplate/edit/process/quick-cust_pkg.cgi
httemplate/edit/quick-charge.html
httemplate/misc/order_pkg.html
httemplate/view/cust_main/one_time_charge_link.html
httemplate/view/cust_main/order_pkg_link.html
httemplate/view/cust_main/packages/status.html

index 2cfe4b4..ecb3a74 100644 (file)
@@ -1259,6 +1259,7 @@ sub tables_hashref {
         'change_pkgpart',      'int', 'NULL', '', '', '',
         'change_locationnum',  'int', 'NULL', '', '', '',
         'manual_flag',        'char', 'NULL',  1, '', '', 
+        'no_auto',            'char', 'NULL',  1, '', '', 
         'quantity',            'int', 'NULL', '', '', '',
       ],
       'primary_key' => 'pkgnum',
@@ -1435,7 +1436,7 @@ sub tables_hashref {
         'pay_weight',    'real',    'NULL', '', '', '',
         'credit_weight', 'real',    'NULL', '', '', '',
         'agentnum',      'int',     'NULL', '', '', '', 
-
+        'no_auto',      'char',     'NULL',  1, '', '', 
       ],
       'primary_key' => 'pkgpart',
       'unique' => [],
index 28a7257..b2cad50 100644 (file)
@@ -356,11 +356,24 @@ this invoice.
 
 sub cust_pkg {
   my $self = shift;
-  my @cust_pkg = map { $_->cust_pkg } $self->cust_bill_pkg;
+  my @cust_pkg = map { $_->pkgnum > 0 ? $_->cust_pkg : () }
+                     $self->cust_bill_pkg;
   my %saw = ();
   grep { ! $saw{$_->pkgnum}++ } @cust_pkg;
 }
 
+=item no_auto
+
+Returns true if any of the packages (or their definitions) corresponding to the
+line items for this invoice have the no_auto flag set.
+
+=cut
+
+sub no_auto {
+  my $self = shift;
+  grep { $_->no_auto || $_->part_pkg->no_auto } $self->cust_pkg;
+}
+
 =item open_cust_bill_pkg
 
 Returns the open line items for this invoice.
@@ -1899,6 +1912,14 @@ sub realtime_bop {
   $cust_main->realtime_bop($method, $amount,
     'description' => $description,
     'invnum'      => $self->invnum,
+#this didn't do what we want, it just calls apply_payments_and_credits
+#    'apply'       => 1,
+    'apply_to_invoice' => 1,
+ #what we want:
+ #this changes application behavior: auto payments
+                        #triggered against a specific invoice are now applied
+                        #to that invoice instead of oldest open.
+                        #seem okay to me...
   );
 
 }
index 6c2bcf3..2574ca7 100644 (file)
@@ -2696,15 +2696,21 @@ sub bill {
     return $error;
   }
 
-  my @cust_bill_pkg = ();
+  #keep auto-charge and non-auto-charge line items separate
+  my @passes = ( '', 'no_auto' );
+
+  my %cust_bill_pkg = map { $_ => [] } @passes;
 
   ###
   # find the packages which are due for billing, find out how much they are
   # & generate invoice database.
   ###
 
-  my( $total_setup, $total_recur, $postal_charge ) = ( 0, 0, 0 );
-  my %taxlisthash;
+  my %total_setup   = map { my $z = 0; $_ => \$z; } @passes;
+  my %total_recur   = map { my $z = 0; $_ => \$z; } @passes;
+
+  my %taxlisthash = map { $_ => {} } @passes;
+
   my @precommit_hooks = ();
 
   $options{'pkg_list'} ||= [ $self->ncancelled_pkgs ];  #param checks?
@@ -2727,14 +2733,16 @@ sub bill {
 
       $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill );
 
+      my $pass = ($cust_pkg->no_auto || $part_pkg->no_auto) ? 'no_auto' : '';
+
       my $error =
         $self->_make_lines( 'part_pkg'            => $part_pkg,
                             'cust_pkg'            => $cust_pkg,
                             'precommit_hooks'     => \@precommit_hooks,
-                            'line_items'          => \@cust_bill_pkg,
-                            'setup'               => \$total_setup,
-                            'recur'               => \$total_recur,
-                            'tax_matrix'          => \%taxlisthash,
+                            'line_items'          => $cust_bill_pkg{$pass},
+                            'setup'               => $total_setup{$pass},
+                            'recur'               => $total_recur{$pass},
+                            'tax_matrix'          => $taxlisthash{$pass},
                             'time'                => $time,
                             'real_pkgpart'        => $real_pkgpart,
                             'options'             => \%options,
@@ -2748,130 +2756,138 @@ sub bill {
 
   } #foreach my $cust_pkg
 
-  unless ( @cust_bill_pkg ) { #don't create an invoice w/o line items
-    #but do commit any package date cycling that happened
-    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-    return '';
-  }
+  #if the customer isn't on an automatic payby, everything can go on a single
+  #invoice anyway?
+  #if ( $cust_main->payby !~ /^(CARD|CHEK)$/ ) {
+    #merge everything into one list
+  #}
 
-  if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) ||
-         !$conf->exists('postal_invoice-recurring_only')
-     )
-  {
+  foreach my $pass (@passes) { # keys %cust_bill_pkg ) {
 
-    my $postal_pkg = $self->charge_postal_fee();
-    if ( $postal_pkg && !ref( $postal_pkg ) ) {
+    my @cust_bill_pkg = @{ $cust_bill_pkg{$pass} };
 
-      $dbh->rollback if $oldAutoCommit;
-      return "can't charge postal invoice fee for customer ".
-        $self->custnum. ": $postal_pkg";
-
-    } elsif ( $postal_pkg ) {
-
-      my $real_pkgpart = $postal_pkg->pkgpart;
-      foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) {
-        my %postal_options = %options;
-        delete $postal_options{cancel};
-        my $error =
-          $self->_make_lines( 'part_pkg'            => $part_pkg,
-                              'cust_pkg'            => $postal_pkg,
-                              'precommit_hooks'     => \@precommit_hooks,
-                              'line_items'          => \@cust_bill_pkg,
-                              'setup'               => \$total_setup,
-                              'recur'               => \$total_recur,
-                              'tax_matrix'          => \%taxlisthash,
-                              'time'                => $time,
-                              'real_pkgpart'        => $real_pkgpart,
-                              'options'             => \%postal_options,
-                            );
-        if ($error) {
-          $dbh->rollback if $oldAutoCommit;
-          return $error;
-        }
-      }
+    next unless @cust_bill_pkg; #don't create an invoice w/o line items
 
-    }
+    if ( scalar( grep { $_->recur && $_->recur > 0 } @cust_bill_pkg) ||
+           !$conf->exists('postal_invoice-recurring_only')
+       )
+    {
 
-  }
+      my $postal_pkg = $self->charge_postal_fee();
+      if ( $postal_pkg && !ref( $postal_pkg ) ) {
 
-  my $listref_or_error =
-    $self->calculate_taxes( \@cust_bill_pkg, \%taxlisthash, $invoice_time);
+        $dbh->rollback if $oldAutoCommit;
+        return "can't charge postal invoice fee for customer ".
+          $self->custnum. ": $postal_pkg";
+
+      } elsif ( $postal_pkg ) {
+
+        my $real_pkgpart = $postal_pkg->pkgpart;
+        foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) {
+          my %postal_options = %options;
+          delete $postal_options{cancel};
+          my $error =
+            $self->_make_lines( 'part_pkg'            => $part_pkg,
+                                'cust_pkg'            => $postal_pkg,
+                                'precommit_hooks'     => \@precommit_hooks,
+                                'line_items'          => \@cust_bill_pkg,
+                                'setup'               => $total_setup{$pass},
+                                'recur'               => $total_recur{$pass},
+                                'tax_matrix'          => $taxlisthash{$pass},
+                                'time'                => $time,
+                                'real_pkgpart'        => $real_pkgpart,
+                                'options'             => \%postal_options,
+                              );
+          if ($error) {
+            $dbh->rollback if $oldAutoCommit;
+            return $error;
+          }
+        }
 
-  unless ( ref( $listref_or_error ) ) {
-    $dbh->rollback if $oldAutoCommit;
-    return $listref_or_error;
-  }
+      }
 
-  foreach my $taxline ( @$listref_or_error ) {
-    $total_setup = sprintf('%.2f', $total_setup+$taxline->setup );
-    push @cust_bill_pkg, $taxline;
-  }
+    }
 
-  #add tax adjustments
-  warn "adding tax adjustments...\n" if $DEBUG > 2;
-  foreach my $cust_tax_adjustment (
-    qsearch('cust_tax_adjustment', { 'custnum'    => $self->custnum,
-                                     'billpkgnum' => '',
-                                   }
-           )
-  ) {
+    my $listref_or_error =
+      $self->calculate_taxes( \@cust_bill_pkg, $taxlisthash{$pass}, $invoice_time);
 
-    my $tax = sprintf('%.2f', $cust_tax_adjustment->amount );
-
-    my $itemdesc = $cust_tax_adjustment->taxname;
-    $itemdesc = '' if $itemdesc eq 'Tax';
-
-    push @cust_bill_pkg, new FS::cust_bill_pkg {
-      'pkgnum'      => 0,
-      'setup'       => $tax,
-      'recur'       => 0,
-      'sdate'       => '',
-      'edate'       => '',
-      'itemdesc'    => $itemdesc,
-      'itemcomment' => $cust_tax_adjustment->comment,
-      'cust_tax_adjustment' => $cust_tax_adjustment,
-      #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
-    };
+    unless ( ref( $listref_or_error ) ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $listref_or_error;
+    }
 
-  }
+    foreach my $taxline ( @$listref_or_error ) {
+      ${ $total_setup{$pass} } =
+        sprintf('%.2f', ${ $total_setup{$pass} } + $taxline->setup );
+      push @cust_bill_pkg, $taxline;
+    }
 
-  my $charged = sprintf('%.2f', $total_setup + $total_recur );
+    #add tax adjustments
+    warn "adding tax adjustments...\n" if $DEBUG > 2;
+    foreach my $cust_tax_adjustment (
+      qsearch('cust_tax_adjustment', { 'custnum'    => $self->custnum,
+                                       'billpkgnum' => '',
+                                     }
+             )
+    ) {
 
-  my @cust_bill = $self->cust_bill;
-  my $balance = $self->balance;
-  my $previous_balance = scalar(@cust_bill)
-                           ? ( $cust_bill[$#cust_bill]->billing_balance || 0 )
-                           : 0;
+      my $tax = sprintf('%.2f', $cust_tax_adjustment->amount );
+
+      my $itemdesc = $cust_tax_adjustment->taxname;
+      $itemdesc = '' if $itemdesc eq 'Tax';
+
+      push @cust_bill_pkg, new FS::cust_bill_pkg {
+        'pkgnum'      => 0,
+        'setup'       => $tax,
+        'recur'       => 0,
+        'sdate'       => '',
+        'edate'       => '',
+        'itemdesc'    => $itemdesc,
+        'itemcomment' => $cust_tax_adjustment->comment,
+        'cust_tax_adjustment' => $cust_tax_adjustment,
+        #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
+      };
 
-  $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,
-    'billing_balance'     => $balance,
-    'previous_balance'    => $previous_balance,
-    'invoice_terms'       => $options{'invoice_terms'},
-  } );
-  $error = $cust_bill->insert;
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return "can't create invoice for customer #". $self->custnum. ": $error";
-  }
+    my $charged = sprintf('%.2f', ${ $total_setup{$pass} } + ${ $total_recur{$pass} } );
 
-  foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
-    $cust_bill_pkg->invnum($cust_bill->invnum); 
-    my $error = $cust_bill_pkg->insert;
+    my @cust_bill = $self->cust_bill;
+    my $balance = $self->balance;
+    my $previous_balance = scalar(@cust_bill)
+                             ? ( $cust_bill[$#cust_bill]->billing_balance || 0 )
+                             : 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,
+      'billing_balance'     => $balance,
+      'previous_balance'    => $previous_balance,
+      'invoice_terms'       => $options{'invoice_terms'},
+    } );
+    $error = $cust_bill->insert;
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
-      return "can't create invoice line item: $error";
+      return "can't create invoice for customer #". $self->custnum. ": $error";
     }
-  }
-    
+
+    foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+      $cust_bill_pkg->invnum($cust_bill->invnum); 
+      my $error = $cust_bill_pkg->insert;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "can't create invoice line item: $error";
+      }
+    }
+
+  } #foreach my $pass ( keys %cust_bill_pkg )
 
   foreach my $hook ( @precommit_hooks ) { 
     eval {
@@ -7549,12 +7565,14 @@ sub charge {
   my ( $pkg, $comment, $additional );
   my ( $setuptax, $taxclass );   #internal taxes
   my ( $taxproduct, $override ); #vendor (CCH) taxes
+  my $no_auto = '';
   my $cust_pkg_ref = '';
   my ( $bill_now, $invoice_terms ) = ( 0, '' );
   if ( ref( $_[0] ) ) {
     $amount     = $_[0]->{amount};
     $quantity   = exists($_[0]->{quantity}) ? $_[0]->{quantity} : 1;
     $start_date = exists($_[0]->{start_date}) ? $_[0]->{start_date} : '';
+    $no_auto    = exists($_[0]->{no_auto}) ? $_[0]->{no_auto} : '';
     $pkg        = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge';
     $comment    = exists($_[0]->{comment}) ? $_[0]->{comment}
                                            : '$'. sprintf("%.2f",$amount);
@@ -7632,6 +7650,7 @@ sub charge {
     'pkgpart'    => $pkgpart,
     'quantity'   => $quantity,
     'start_date' => $start_date,
+    'no_auto'    => $no_auto,
   } );
 
   $error = $cust_pkg->insert;
index 6e9cae0..0094135 100644 (file)
@@ -525,6 +525,7 @@ sub check {
     || $self->ut_numbern('cancel')
     || $self->ut_numbern('adjourn')
     || $self->ut_numbern('expire')
+    || $self->ut_enum('no_auto', [ '', 'Y' ])
   ;
   return $error if $error;
 
diff --git a/FS/FS/part_event/Condition/cust_bill_has_noauto.pm b/FS/FS/part_event/Condition/cust_bill_has_noauto.pm
new file mode 100644 (file)
index 0000000..6cb94c0
--- /dev/null
@@ -0,0 +1,33 @@
+package FS::part_event::Condition::cust_bill_has_noauto;
+
+use strict;
+use FS::cust_bill;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+  'Invoice ineligible for automatic collection';
+}
+
+sub eventtable_hashref {
+    { 'cust_main' => 0,
+      'cust_bill' => 1,
+      'cust_pkg'  => 0,
+    };
+}
+
+sub condition {
+  #my($self, $cust_bill, %opt) = @_;
+  my($self, $cust_bill) = @_;
+
+  $cust_bill->no_auto;
+}
+
+#sub condition_sql {
+#  my( $class, $table ) = @_;
+#  
+#  my $sql = qq|  |;
+#  return $sql;
+#}
+
+1;
diff --git a/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm b/FS/FS/part_event/Condition/cust_bill_hasnt_noauto.pm
new file mode 100644 (file)
index 0000000..78a6d51
--- /dev/null
@@ -0,0 +1,33 @@
+package FS::part_event::Condition::cust_bill_hasnt_noauto;
+
+use strict;
+use FS::cust_bill;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+  'Invoice eligible for automatic collection';
+}
+
+sub eventtable_hashref {
+    { 'cust_main' => 0,
+      'cust_bill' => 1,
+      'cust_pkg'  => 0,
+    };
+}
+
+sub condition {
+  #my($self, $cust_bill, %opt) = @_;
+  my($self, $cust_bill) = @_;
+
+  ! $cust_bill->no_auto;
+}
+
+#sub condition_sql {
+#  my( $class, $table ) = @_;
+#  
+#  my $sql = qq|  |;
+#  return $sql;
+#}
+
+1;
index 61bfc4d..08c9b87 100644 (file)
@@ -462,6 +462,7 @@ sub check {
     || $self->ut_textn('taxclass')
     || $self->ut_enum('disabled', [ '', 'Y' ] )
     || $self->ut_enum('custom', [ '', 'Y' ] )
+    || $self->ut_enum('no_auto', [ '', 'Y' ])
     #|| $self->ut_moneyn('setup_cost')
     #|| $self->ut_moneyn('recur_cost')
     || $self->ut_floatn('setup_cost')
@@ -908,9 +909,11 @@ sub options {
   map { $_->optionname => $_->optionvalue } $self->part_pkg_option;
 }
 
-=item option OPTIONNAME
+=item option OPTIONNAME [ QUIET ]
 
-Returns the option value for the given name, or the empty string.
+Returns the option value for the given name, or the empty string.  If a true
+value is passed as the second argument, warnings about missing the option
+will be suppressed.
 
 =cut
 
index 827530e..27921b8 100644 (file)
@@ -61,6 +61,7 @@ unless ( $error ) {
                            ? str2time($cgi->param('start_date'))
                            : ''
                        ),
+    'no_auto'       => scalar($cgi->param('no_auto')),
     'pkg'           => scalar($cgi->param('pkg')),
     'setuptax'      => scalar($cgi->param('setuptax')),
     'taxclass'      => scalar($cgi->param('taxclass')),
index a095892..202d0a3 100644 (file)
@@ -57,6 +57,7 @@ my $cust_pkg = new FS::cust_pkg {
                                 ? str2time($cgi->param('start_date'))
                                 : ''
                             ),
+  'no_auto'              => scalar($cgi->param('no_auto')),
   'refnum'               => $refnum,
   'locationnum'          => $locationnum,
   'discountnum'          => $discountnum,
index 64ad3a2..a472915 100644 (file)
@@ -153,6 +153,13 @@ function bill_now_changed (what) {
   });
 </SCRIPT>
 
+% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) {
+%   my $what = lc(FS::payby->shortname($cust_main->payby));
+    <TR>
+      <TD ALIGN="right">Disable automatic <% $what %> charge </TD>
+      <TD COLSPAN=6><INPUT TYPE="checkbox" NAME="no_auto" VALUE="Y"></TD>
+    </TR>
+% }
 
 <TR>
   <TD ALIGN="right">Tax exempt </TD>
index 666099a..33b2bb3 100644 (file)
   });
 </SCRIPT>
 
+% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) {
+%   my $what = lc(FS::payby->shortname($cust_main->payby));
+    <TR>
+      <TH ALIGN="right">Disable automatic <% $what %> charge </TH>
+      <TD COLSPAN=6><INPUT TYPE="checkbox" NAME="no_auto" VALUE="Y"></TD>
+    </TR>
+% }
+
 % if ( $curuser->access_right('Discount customer package') ) {
   <% include('/elements/tr-select-discount.html',
                'element_etc' => 'DISABLED',
@@ -119,7 +127,7 @@ my $cust_main = qsearchs({
 
 my $pkgpart = scalar($cgi->param('pkgpart'));
 
-my $format = "%m/%d/%Y %T %z (%Z)"; #false laziness w/REAL_cust_pkg.cgi?
+my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi?
 my $start_date = $cust_main->next_bill_date;
 $start_date = $start_date ? time2str($format, $start_date) : '';
 
index 4ce8a28..b3defa2 100644 (file)
@@ -80,7 +80,7 @@ function taxoverridequickchargemagic() {
      'actionlabel' => 'One-time charge',
      'color'       => '#333399',
      'width'       => 763,
-     'height'      => 408,
+     'height'      => 460, #more for more room for lines of add'l description?
    })
 %>
 
index 0c53f2a..30c86a7 100644 (file)
@@ -6,7 +6,7 @@
               'cust_main'   => $cust_main,
               'closetext'   => 'Close',
               'width'       => 763,
-              'height'      => 476,
+              'height'      => 538,
           )
 %>
 <%init>
index 7b1b53a..a686843 100644 (file)
@@ -42,6 +42,8 @@
        )
     %>
 
+    <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
     <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
 
 %   unless ( $cust_pkg->get('setup') ) { 
@@ -76,7 +78,9 @@
 
           <% pkg_status_row_colspan( $cust_pkg, 'Not&nbsp;yet&nbsp;billed&nbsp;(one-time&nbsp;charge)', '', 'colspan'=>$colspan, %opt ) %>
 
-         <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+          <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+          <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
 
           <% pkg_status_row_if(
                $cust_pkg,
 
           <% pkg_status_row_colspan($cust_pkg, "Not&nbsp;yet&nbsp;billed&nbsp;($billed_or_prepaid&nbsp;". myfreq($part_pkg). ')', '', 'colspan'=>$colspan, %opt ) %>
 
+          <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
           <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
 
           <% pkg_status_row_if($cust_pkg, 'Start billing', 'start_date', %opt) %>
 
           <% pkg_status_row($cust_pkg, 'Billed', 'setup', %opt) %>
 
+          <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
           <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
 
 %       } else { 
             %>
 %         } 
 
+          <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
           <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
 
           <% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt) %>
@@ -290,6 +300,23 @@ sub pkg_status_row_changed {
   $html;
 }
 
+sub pkg_status_row_noauto {
+  my( $cust_pkg, %opt ) = @_;
+  my $part_pkg = $opt{'part_pkg'};
+  return '' unless $cust_pkg->no_auto || $part_pkg->no_auto;
+
+  #inefficient, should be passed in
+  my $cust_main = $cust_pkg->cust_main;
+
+  return '' unless $cust_main->payby =~ /^(CARD|CHEK)$/;
+  my $what = lc(FS::payby->shortname($cust_main->payby));
+
+  pkg_status_row_colspan( $cust_pkg, "No automatic $what charge", '',
+                          'colspan' => $opt{'colspan'},
+                          #%opt,
+                        );
+}
+
 sub pkg_status_row_discount {
   my( $cust_pkg, %opt ) = @_;