promised payment date for invoices, #13554
authormark <mark>
Thu, 8 Dec 2011 21:13:17 +0000 (21:13 +0000)
committermark <mark>
Thu, 8 Dec 2011 21:13:17 +0000 (21:13 +0000)
12 files changed:
FS/FS/Conf.pm
FS/FS/Schema.pm
FS/FS/cust_bill.pm
FS/FS/cust_bill_ApplicationCommon.pm
FS/FS/part_event/Condition/cust_bill_past_promised.pm [new file with mode: 0644]
httemplate/misc/cust_bill-promised_date.html [new file with mode: 0644]
httemplate/misc/process/cust_bill-promised_date.html [new file with mode: 0644]
httemplate/search/cust_bill.html
httemplate/search/report_cust_bill.html
httemplate/view/cust_bill.cgi
httemplate/view/cust_main/payment_history.html
httemplate/view/cust_main/payment_history/invoice.html

index 3987fac..fcdcd57 100644 (file)
@@ -4681,6 +4681,13 @@ and customer address. Include units.',
     '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',
index 51daf44..18df708 100644 (file)
@@ -532,6 +532,7 @@ sub tables_hashref {
         '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
index ef6dc7b..41ff6aa 100644 (file)
@@ -144,6 +144,8 @@ Specific use cases
 
 =item agent_invid - legacy invoice number
 
+=item promised_date - customer promised payment date, for collection
+
 =back
 
 =head1 METHODS
@@ -5622,6 +5624,15 @@ sub search_sql_where {
 
   }
 
+  #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'
index afb90f4..cadb8a7 100644 (file)
@@ -435,6 +435,17 @@ sub apply_to_lineitems {
 
   }
 
+  # 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 ) {
diff --git a/FS/FS/part_event/Condition/cust_bill_past_promised.pm b/FS/FS/part_event/Condition/cust_bill_past_promised.pm
new file mode 100644 (file)
index 0000000..e861cb4
--- /dev/null
@@ -0,0 +1,48 @@
+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;
diff --git a/httemplate/misc/cust_bill-promised_date.html b/httemplate/misc/cust_bill-promised_date.html
new file mode 100644 (file)
index 0000000..7b7b960
--- /dev/null
@@ -0,0 +1,19 @@
+<& /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>
diff --git a/httemplate/misc/process/cust_bill-promised_date.html b/httemplate/misc/process/cust_bill-promised_date.html
new file mode 100644 (file)
index 0000000..298b130
--- /dev/null
@@ -0,0 +1,19 @@
+<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>
index 4117112..813f9b8 100755 (executable)
@@ -128,7 +128,26 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) {
     $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') ) . 
index 0f0d91b..51618fb 100644 (file)
   &>
 % }
 
+% 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>
@@ -49,6 +65,7 @@
   </TR>
 % }
 
+
 </TABLE>
 
 <BR>
@@ -62,6 +79,8 @@
 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 = ();
index 2ce294e..a8b4ac1 100755 (executable)
 
 % } 
 
+% 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) ) %>
+      (&nbsp;<% $onclick %><% mt('change') |h %></A>&nbsp;)
+      <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>
index 467c3bc..63708e6 100644 (file)
@@ -398,6 +398,8 @@ my %opt = (
   )
 );
 
+$opt{'date_format'} ||= '%m/%d/%Y';
+
 #legacy invoices
 foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) {
   push @history, {
index d7ee004..3028f0f 100644 (file)
@@ -1,4 +1,4 @@
-<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $events %>
+<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $under %>
 <%init>
 
 my( $cust_bill, %opt ) = @_;
@@ -8,10 +8,17 @@ my $conf = new FS::Conf;
 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;
 
@@ -34,10 +41,10 @@ if ( $cust_bill->num_cust_event
           || $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>