'description' => 'An alternate ordering of fields for the New Customer and Edit Customer screens.',
     'type'        => 'checkbox',
   },
+
+  {
+    'key'         => 'cust_bill-enable_promised_date',
+    'section'     => 'UI',
+    'description' => 'Enable display/editing of the "promised payment date" field on invoices.',
+    'type'        => 'checkbox',
+  },
   
   {
     'key'         => 'available-locales',
 
         'closed',      'char', 'NULL',  1, '', '', #not yet used much
         'statementnum', 'int', 'NULL', '', '', '', #invoice aggregate statements
         'agent_invid',  'int', 'NULL', '', '', '', #(varchar?) importing legacy
+        'promised_date', @date_type,       '', '',
       ],
       'primary_key' => 'invnum',
       'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum?  huh
 
 
 =item agent_invid - legacy invoice number
 
+=item promised_date - customer promised payment date, for collection
+
 =back
 
 =head1 METHODS
 
   }
 
+  #promised_date - also has an option to accept nulls
+  if ( $param->{promised_date} ) {
+    my($beginning, $ending, $null) = @{$param->{promised_date}};
+
+    push @search, "(( cust_bill.promised_date >= $beginning AND ".
+                    "cust_bill.promised_date <  $ending )" .
+                    ($null ? ' OR cust_bill.promised_date IS NULL ) ' : ')');
+  }
+
   #agent virtualization
   my $curuser = $FS::CurrentUser::CurrentUser;
   if ( $curuser->username eq 'fs_queue'
 
 
   }
 
+  # unset promised payment date if there is one
+  my $cust_bill = $self->cust_bill;
+  if ( $cust_bill->promised_date and $cust_bill->owed <= 0 ) {
+    $cust_bill->set('promised_date', '');
+    my $error = $cust_bill->replace;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+  
   #everything should always be applied to line items in full now... sanity check
   $applied = sprintf('%.2f', $applied);
   unless ( $applied == $self->amount ) {
 
--- /dev/null
+package FS::part_event::Condition::cust_bill_past_promised;
+
+use strict;
+use FS::cust_bill;
+use Time::Local 'timelocal';
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+  'Promised payment date has passed',
+}
+
+sub eventtable_hashref {
+    { 'cust_main' => 0,
+      'cust_bill' => 1,
+      'cust_pkg'  => 0,
+    };
+}
+
+sub option_fields {
+  (
+    'delay' => { label  => 'Delay additional days',
+      type   => 'text',
+      value  => '0',
+    },
+  );
+}
+
+sub condition {
+  # always return true if there is no promised_date
+  my($self, $cust_bill, %opt) = @_;
+
+  my $delay = $self->option('delay') || 0;
+  my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($opt{'time'}))[0..5];
+  my $as_of = timelocal(0,0,0,$mday,$mon,$year) - $delay * 86400;
+  $as_of >= ($cust_bill->promised_date || 0);
+}
+
+sub condition_sql {
+  my( $class, $table, %opt ) = @_;
+  return 'true' if $opt{'driver_name'} ne 'Pg';
+  my $delay = $class->condition_sql_option_integer('delay', 'Pg');
+  my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($opt{'time'}))[0..5];
+  my $as_of = timelocal(0,0,0,$mday,$mon,$year) . " - ($delay * 86400)";
+  "(cust_bill.promised_date IS NULL OR $as_of >= cust_bill.promised_date)";
+}
+
+1;
 
--- /dev/null
+<& /elements/header-popup.html, 'Edit promised date' &>
+<FORM method="POST" action="process/cust_bill-promised_date.html">
+<B><% emt('Invoice #[_1]', $invnum) %></B><BR>
+<% ntable('cccccc',2) %>
+<INPUT TYPE="hidden" NAME="invnum" VALUE="<%$invnum%>">
+<& /elements/tr-input-date-field.html,
+  'promised_date',
+  $cust_bill->promised_date,
+  emt('Promised date'),
+&>
+</TABLE>
+<INPUT TYPE="submit" NAME="submit" VALUE="<% emt('Set date') %>">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+my ($invnum) = $cgi->keywords;
+$invnum =~ /^\d+$/ or die "Illegal invnum";
+my $cust_bill = qsearchs('cust_bill', { invnum => $invnum });
+</%init>
 
--- /dev/null
+<SCRIPT TYPE="text/javascript">window.top.location.reload()</SCRIPT>
+<%init>
+# XXX ACL?
+
+$cgi->param('invnum') =~ /^(\d+)$/
+  or die "Illegal invnum";
+my $invnum = $1;
+
+my $promised_date = '';
+if ( length($cgi->param('promised_date')) ) {
+  $promised_date = parse_datetime($cgi->param('promised_date'))
+    or die "Illegal promised_date";
+}
+
+my $cust_bill = qsearchs('cust_bill', { invnum => $invnum });
+$cust_bill->promised_date($promised_date);
+my $error = $cust_bill->replace;
+die $error if $error; # nothing fancy here
+</%init>
 
     $search{'newest_percust'} = 1;
     $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'";
   }
-  
+
+  # promised date
+  my $start_of_day = timelocal(0, 0, 0, (localtime(time))[3,4,5]);
+  foreach ( $cgi->param('promised_date') ) {
+    # only if at least one box is checked
+    $search{promised_date} ||= [ $start_of_day, $start_of_day, 0 ];
+    if ($_ eq 'past') {
+      # accept everything before today
+      $search{promised_date}[0] = 0;
+    }
+    elsif ( $_ eq 'future' ) {
+      # accept everything after today
+      $search{promised_date}[1] = 4294967295;
+    }
+    elsif ( $_ eq 'null' ) {
+      # accept nulls
+      $search{promised_date}[2] = 1;
+    }
+  }
+
   my $payby_sql = '';
   $payby_sql = ' AND (' . 
     join(' OR ', map { "cust_main.payby = '$_'" } $cgi->param('payby') ) . 
 
   &>
 % }
 
+% if ( $conf->exists('cust_bill-enable_promised_date') ) {
   <TR>
+    
+    <TD ALIGN="right" STYLE="vertical-align:text-top">
+      <% emt('Promised payment date:') %></TD>
+    <TD>
+      <INPUT TYPE="checkbox" NAME="promised_date"   CHECKED VALUE="null">
+        <% emt('None') %> <BR>
+      <INPUT TYPE="checkbox" NAME="promised_date"   CHECKED VALUE="past">
+        <% emt('In the past') %><BR>
+      <INPUT TYPE="checkbox" NAME="promised_date"   CHECKED VALUE="future">
+        <% emt('In the future') %><BR>
+    </TD>
+  </TR>
+% }
+
+<TR>
     <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD>
     <TD><% mt('Show only open invoices') |h %></TD>
   </TR>
   </TR>
 % }
 
+
 </TABLE>
 
 <BR>
 die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('List invoices');
 
+my $conf = new FS::Conf;
+
 my $title = 'Invoice Report';
 #false laziness w/report_cust_pkg.html
 my @title_arg = ();
 
 
 % } 
 
+% if ( $conf->exists('cust_bill-enable_promised_date') ) {
+%   my $onclick = include('/elements/popup_link_onclick.html',
+%      'action'      => $p.'misc/cust_bill-promised_date.html?'.$invnum,
+%      'actionlabel' => emt('Set promised payment date'),
+%      'width'       => 320,
+%      'height'      => 240,
+%   );
+%   $onclick = '<A HREF="#" onclick="'.$onclick.'">';
+%   if ( $cust_bill->promised_date ) {
+%     my $date_format = $conf->config('date_format') || '%b %o, %Y';
+      <% mt('Payment promised by [_1]', 
+            time2str($date_format, $cust_bill->promised_date) ) %>
+      ( <% $onclick %><% mt('change') |h %></A> )
+      <BR><BR>
+%   }
+%   elsif ( $cust_bill->owed > 0 ) {
+    <% $onclick %><% mt('Set promised payment date' ) |h %></A>
+    <BR><BR>
+%   }
+% }
+
 % if ( $curuser->access_right('Resend invoices') ) {
 
     <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>"><% mt('Re-print this invoice') |h %></A>
 
   )
 );
 
+$opt{'date_format'} ||= '%m/%d/%Y';
+
 #legacy invoices
 foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) {
   push @history, {
 
-<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $events %>
+<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $under %>
 <%init>
 
 my( $cust_bill, %opt ) = @_;
 my $curuser = $FS::CurrentUser::CurrentUser;
 
 my $invoice = emt("Invoice #[_1] (Balance [_2])",$cust_bill->display_invnum,$cust_bill->owed);
-$invoice = '<B><FONT SIZE="+1" COLOR="#FF0000">' .
+
+my $under = '';
+if ( $cust_bill->owed > 0 ) {
+  $invoice = '<B><FONT SIZE="+1" COLOR="#FF0000">' .
     emt("Open Invoice #[_1] (Balance [_2])",$cust_bill->display_invnum,$cust_bill->owed) .
-    '</FONT></B>'
-if ( $cust_bill->owed > 0 );
+    '</FONT></B>';
+  if ( $cust_bill->promised_date ) {
+    $under .= '<BR>'. emt('Payment promised on [_1]',
+        time2str($opt{'date_format'}, $cust_bill->promised_date));
+  }
+} #if $cust_bill->owed
 
 my $invnum = $cust_bill->invnum;
 
           || $curuser->access_right('View customer billing events')
         )
    ) {
-  $events =
-    qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?invnum=$invnum!.
-    '">( '.emt('View invoice events').' )</A></FONT>';
+  $under .=
+    qq!<BR><A HREF="${p}search/cust_event.html?invnum=$invnum">( !.
+      emt('View invoice events').' )</A>';
 }
-#
+$under = '<FONT SIZE="-1">'.$under.'</FONT>' if length($under);
 
 </%init>