RT# 78547 Future autobill report - report runs in job queue
authorMitch Jackson <mitch@freeside.biz>
Tue, 18 Sep 2018 01:30:15 +0000 (21:30 -0400)
committerMitch Jackson <mitch@freeside.biz>
Tue, 18 Sep 2018 01:36:08 +0000 (21:36 -0400)
FS/FS/Report/Queued/FutureAutobill.pm [new file with mode: 0644]
FS/FS/UI/Web.pm
httemplate/search/elements/grid-report.html
httemplate/search/future_autobill.html
httemplate/search/report_future_autobill-queued_job.html [new file with mode: 0644]
httemplate/search/report_future_autobill.html

diff --git a/FS/FS/Report/Queued/FutureAutobill.pm b/FS/FS/Report/Queued/FutureAutobill.pm
new file mode 100644 (file)
index 0000000..82c9021
--- /dev/null
@@ -0,0 +1,132 @@
+package FS::Report::Queued::FutureAutobill;
+use strict;
+use warnings;
+use vars qw( $job );
+
+use FS::Conf;
+use FS::cust_main;
+use FS::cust_main::Location;
+use FS::cust_payby;
+use FS::CurrentUser;
+use FS::Log;
+use FS::Mason qw(mason_interps);
+use FS::Record qw( qsearch );
+use FS::UI::Web;
+use FS::UID qw( dbh );
+
+use DateTime;
+use File::Temp;
+use Data::Dumper;
+use HTML::Entities qw( encode_entities );
+
+=head1 NAME
+
+FS::Report::Queued::FutureAutobill - Future Auto-Bill Transactions Report
+
+=head1 DESCRIPTION
+
+Future Autobill report generated within the job queue.
+
+Report results are saved to temp storage as a Mason fragment
+that is rendered by the queued report viewer.
+
+For every customer with a valid auto-bill payment method,
+report runs bill_and_collect() for each day, from today through
+the report target date.  After recording the results, all
+operations are rolled back.
+
+This report relies on the ability to safely run bill_and_collect(),
+with all exports and messaging disabled, and then to roll back the
+results.
+
+=head1 PARAMETERS
+
+C<agentnum>, C<target_date>
+
+=cut
+
+sub make_report {
+  $job = shift;
+  my $param = shift;
+  my $outbuf;
+  my $DEBUG = 0;
+
+  my $time_begin = time();
+
+  my $report_fh = File::Temp->new(
+    TEMPLATE => 'report.future_autobill.XXXXXXXX',
+    DIR      => sprintf( '%s/cache.%s', $FS::Conf::base_dir, $FS::UID::datasrc ),
+    UNLINK   => 0
+  ) or die "Cannot create report file: $!";
+
+  if ( $DEBUG ) {
+    warn Dumper( $job );
+    warn Dumper( $param );
+    warn $report_fh;
+    warn $report_fh->filename;
+  }
+
+  my $curuser = FS::CurrentUser->load_user( $param->{CurrentUser} )
+    or die 'Unable to set report user';
+
+  my ( $fs_interp ) = FS::Mason::mason_interps(
+    'standalone',
+    outbuf => \$outbuf,
+  );
+  $fs_interp->error_mode('fatal');
+  $fs_interp->error_format('text');
+
+  $FS::Mason::Request::QUERY_STRING = sprintf(
+    'target_date=%s&agentnum=%s',
+    encode_entities( $param->{target_date} ),
+    encode_entities( $param->{agentnum} || '' ),
+  );
+  $FS::Mason::Request::FSURL = $param->{RootURL};
+
+  my $mason_request = $fs_interp->make_request(
+    comp => '/search/future_autobill.html'
+  );
+
+  {
+    local $@;
+    eval{ $mason_request->exec() };
+    if ( $@ ) {
+      my $error = ref $@ eq 'HTML::Mason::Exception' ? $@->error : $@;
+
+      my $log = FS::Log->new('FS::Report::Queued::FutureAutobill');
+      $log->error(
+        "Error generating report: $FS::Mason::Request::QUERY_STRING $error"
+      );
+      die $error;
+    }
+  }
+
+  my $report_fn;
+  if ( $report_fh->filename =~ /report\.(future_autobill.+)$/ ) {
+      $report_fn = $1
+  } else {
+    die 'Error parsing report filename '.$report_fh->filename;
+  }
+
+  my $report_title = FS::cust_payby->future_autobill_report_title();
+  my $time_rendered = time() - $time_begin;
+
+  if ( $DEBUG ) {
+    warn "Generated content:\n";
+    warn $outbuf;
+    warn $report_fn;
+    warn $report_title;
+  }
+
+  print $report_fh qq{<% include("/elements/header.html", '$report_title') %>\n};
+  print $report_fh $outbuf;
+  print $report_fh qq{<!-- Time to render report $time_rendered seconds -->};
+  print $report_fh qq{<% include("/elements/footer.html") %>\n};
+
+  die sprintf
+    "<a href=%s/misc/queued_report.html?report=%s>view</a>\n",
+    $param->{RootURL},
+    $report_fn;
+}
+
+1;
index 6cc04b9..5412868 100644 (file)
@@ -743,6 +743,7 @@ use FS::CurrentUser;
 use FS::Record qw(qsearchs);
 use FS::queue;
 use FS::CGI qw(rooturl);
+use FS::Report::Queued::FutureAutobill;
 
 $DEBUG = 0;
 
index b1e5430..efc0097 100644 (file)
@@ -141,13 +141,17 @@ Usage:
   $m->print($output);
 </%perl>
 % } else {
+% unless ( $suppress_header ) {
 <& /elements/header.html, $title &>
+% }
 <% $head %>
 % my $myself = $cgi->self_url;
+% unless ( $suppress_header ) {
 <P ALIGN="right" CLASS="noprint">
 Download full reports<BR>
 as <A HREF="<% "$myself;_type=xls" %>">Excel spreadsheet</A><BR>
 </P>
+% }
 <style type="text/css">
 .report * {
   background-color: #f8f8f8;
@@ -169,8 +173,10 @@ as <A HREF="<% "$myself;_type=xls" %>">Excel spreadsheet</A><BR>
 %     next if !ref($cell); # placeholders
 %     my $td = $cell->{header} ? 'th' : 'td';
 %     my $style = '';
-%     $style .= " rowspan=".$cell->{rowspan} if $cell->{rowspan} > 1;
-%     $style .= " colspan=".$cell->{colspan} if $cell->{colspan} > 1;
+%     $style .= " rowspan=".$cell->{rowspan}
+%       if exists $cell->{rowspan} && $cell->{rowspan} > 1;
+%     $style .= " colspan=".$cell->{colspan}
+%       if exists $cell->{colspan} && $cell->{colspan} > 1;
 %     $style .= ' class="' . $cell->{class} . '"' if $cell->{class};
 % if ($cell->{bypass_filter}) {
       <<%$td%><%$style%>><% $cell->{value} %></<%$td%>>
@@ -182,8 +188,10 @@ as <A HREF="<% "$myself;_type=xls" %>">Excel spreadsheet</A><BR>
 % }
 </table>
 <% $foot %>
+% unless ( $suppress_footer ) {
 <& /elements/footer.html &>
 % }
+% }
 <%args>
 $title
 @rows
@@ -192,4 +200,6 @@ $head => ''
 $foot => ''
 $table_width => "100%"
 $table_class => "report"
+$suppress_header => undef
+$suppress_footer => undef
 </%args>
index d4ad8e5..1f3862f 100644 (file)
@@ -11,7 +11,12 @@ This report relies on the ability to safely run bill_and_collect(),
 with all exports and messaging disabled, and then to roll back the
 results.
 
+This report takes time.  If 200 customers have automatic
+payment methods, and requester is looking one week ahead,
+there will be 1,400 billing and payment cycles simulated
+
 </%doc>
+<h4><% $report_subtitle %></h4>
 <& elements/grid-report.html,
   title => $report_title,
   rows => \@rows,
@@ -27,17 +32,25 @@ results.
       td.gridreport { margin: 0 .2em; padding: 0 .4em; }
     </style>
   ',
+  suppress_header => $job ? 1 : 0,
+  suppress_footer => $job ? 1 : 0,
 &>
 
 <%init>
+  use DateTime;
+  use FS::Misc::Savepoint;
+  use FS::Report::Queued::FutureAutobill;
   use FS::UID qw( dbh );
 
   die "access denied"
     unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
 
+  my $job = $FS::Report::Queued::FutureAutobill::job;
+
+  $job->update_statustext('0,Finding customers') if $job;
+
   my $DEBUG = $cgi->param('DEBUG') || 0;
 
-  my $report_title = FS::cust_payby->future_autobill_report_title;
   my $agentnum = $cgi->param('agentnum')
     if $cgi->param('agentnum') =~ /^\d+/;
 
@@ -60,16 +73,20 @@ results.
 
   # Get target date from form
   if ($cgi->param('target_date')) {
+    # DateTime::Format::DateParse would be better
     my ($mm, $dd, $yy) = split /[\-\/]/,$cgi->param('target_date');
+    ( $yy, $mm, $dd ) = ( $mm, $dd, $yy ) if $mm > 1900;
+
     $target_dt = DateTime->new(
       month  => $mm,
       day    => $dd,
       year   => $yy,
       %noon,
-    ) if $mm && $dd & $yy;
+    ) if $mm && $dd && $yy;
 
     # Catch a date from the past: time only travels in one direction
-    $target_dt = undef if $target_dt->epoch < $now_dt->epoch;
+    $target_dt = undef
+      unless $target_dt && $now_dt && $now_dt <=  $target_dt;
   }
 
   # without a target date, default to tomorrow
@@ -77,6 +94,13 @@ results.
     $target_dt = $now_dt->clone->add( days => 1 );
   }
 
+  my $report_title = FS::cust_payby->future_autobill_report_title;
+  my $report_subtitle = sprintf(
+    '(%s through %s)',
+    $now_dt->mdy('/'),
+    $target_dt->mdy('/'),
+  );
+
   # Create a range of dates from today until the given report date
   #   (leaving the probably useless 'quick-report' mode, but disabled)
   if ( 1 || $cgi->param('multiple_billing_dates')) {
@@ -104,6 +128,9 @@ results.
       . ($agentnum ? "AND cust_main.agentnum = $agentnum" : ''),
   });
 
+  my $completion_target = scalar(keys %cust_payby) * scalar( @target_dates );
+  my $completion_progress = 0;
+
   my $fakebill_time = time();
   my %abreport;
   my @rows;
@@ -125,6 +152,9 @@ results.
     local $FS::cust_main::Billing_Realtime::BOP_TESTING = 1;
     local $FS::cust_main::Billing_Realtime::BOP_TESTING_SUCCESS = 1;
 
+    my $savepoint_label = 'future_autobill';
+    savepoint_create( $savepoint_label );
+
     warn sprintf "Report involves %s customers", scalar keys %cust_payby
       if $DEBUG;
 
@@ -153,8 +183,18 @@ results.
         );
 
         warn "!!! $error (simulating future billing)\n" if $error;
+
+        my $statustext = sprintf(
+            '%s,Simulating upcoming invoices and payments',
+            int( ( ++$completion_progress / $completion_target ) * 100 )
+        );
+        $job->update_statustext( $statustext ) if $job;
+        warn "[ $completion_progress / $completion_target ] $statustext\n"
+          if $DEBUG;
+
       }
 
+
       # Generate report rows from recorded payments in cust_pay
       for my $cust_pay (
         qsearch( cust_pay => {
@@ -206,6 +246,7 @@ results.
       #   locked at a time
 
       warn "-- custnum $custnum -- rollback()\n" if $DEBUG;
+      savepoint_rollback( $savepoint_label );
       dbh->rollback if $oldAutoCommit;
 
     } # /foreach $custnum
diff --git a/httemplate/search/report_future_autobill-queued_job.html b/httemplate/search/report_future_autobill-queued_job.html
new file mode 100644 (file)
index 0000000..d23efb5
--- /dev/null
@@ -0,0 +1,11 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $server = new FS::UI::Web::JSRPC
+  'FS::Report::Queued::FutureAutobill::make_report',
+  $cgi;
+
+</%init>
index ccde299..28f589e 100644 (file)
@@ -1,6 +1,9 @@
 <%doc>
 
-Display date selector for the future_autobill.html report
+Display pre-report page for the Future Auto Bill Transactions report
+
+Report runs in the queue.  Once the report is generated, user is
+redirected to the report results.
 
 </%doc>
 <% include('/elements/header.html', $report_title ) %>
@@ -14,30 +17,43 @@ Display date selector for the future_autobill.html report
 
 % } else {
 
-  <FORM ACTION="future_autobill.html" METHOD="GET">
-  <TABLE>
-  <& /elements/tr-input-date-field.html,
-    {
-      name     => 'target_date',
-      value    => $target_date,
-      label    => emt('Target billing date').': ',
-      required => 1
-    }
-  &>
-
-  <% include('/elements/tr-select-agent.html',
-              'label'         => 'For agent: ',
-              'disable_empty' => 0,
+  <FORM NAME="future_autobill" ID="future_autobill">
+    <TABLE>
+    <& /elements/tr-input-date-field.html,
+      {
+        name     => 'target_date',
+        value    => $target_date,
+        label    => emt('Target billing date').': ',
+        required => 1
+      }
+    &>
+
+    <% include('/elements/tr-select-agent.html',
+                'label'         => 'For agent: ',
+                'disable_empty' => 0,
+              )
+    %>
+    </TABLE>
+    <BR>
+
+    <INPUT ID="future_autobill_submit" TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+  </FORM>
+
+  <% include( '/elements/progress-init.html',
+              'future_autobill',
+              [ qw( agentnum target_date ) ],
+              'report_future_autobill-queued_job.html',
             )
   %>
 
-  </TABLE>
-
-  <BR>
-
-  <INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
-
-  </FORM>
+  <script type="text/javascript">
+    $('#future_autobill').submit( function( event ) {
+      $('#future_autobill').prop( 'disabled', true );
+      $('#future_autobill_submit').prop( 'disabled', true );
+      event.preventDefault();
+      process();
+    });
+  </script>
 
 % }