suppress all fractions of days in unearned revenue calculation, #13289
authorMark Wells <mark@freeside.biz>
Tue, 12 Jun 2012 23:34:18 +0000 (16:34 -0700)
committerMark Wells <mark@freeside.biz>
Tue, 12 Jun 2012 23:34:18 +0000 (16:34 -0700)
FS/FS/Mason.pm
FS/FS/Record.pm
httemplate/search/prepaid_income.html
httemplate/search/unearned_detail.html

index de97a19..b0f20ec 100644 (file)
@@ -122,6 +122,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::UID qw( getotaker dbh datasrc driver_name );
   use FS::Record qw( qsearch qsearchs fields dbdef
                     str2time_sql str2time_sql_closing
+                    midnight_sql
                    );
   use FS::Conf;
   use FS::CGI qw(header menubar table itable ntable idiot
index dfc2abf..a93a10a 100644 (file)
@@ -39,6 +39,7 @@ use Tie::IxHash;
 @EXPORT_OK = qw(
   dbh fields hfields qsearch qsearchs dbdef jsearch
   str2time_sql str2time_sql_closing regexp_sql not_regexp_sql concat_sql
+  midnight_sql
 );
 
 $DEBUG = 0;
@@ -3030,7 +3031,7 @@ sub not_regexp_sql {
 
 =item concat_sql [ DRIVER_NAME ] ITEMS_ARRAYREF
 
-Returns the items concatendated based on database type, using "CONCAT()" for
+Returns the items concatenated based on database type, using "CONCAT()" for
 mysql and " || " for Pg and other databases.
 
 You can pass an optional driver name such as "Pg", "mysql" or
@@ -3051,6 +3052,24 @@ sub concat_sql {
 
 }
 
+=item midnight_sql DATE
+
+Returns an SQL expression to convert DATE (a unix timestamp) to midnight 
+on that day in the system timezone, using the default driver name.
+
+=cut
+
+sub midnight_sql {
+  my $driver = driver_name;
+  my $expr = shift;
+  if ( $driver =~ /^mysql/i ) {
+    "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME($expr)))";
+  }
+  else {
+    "EXTRACT( EPOCH FROM DATE(TO_TIMESTAMP($expr)) )";
+  }
+}
+
 =back
 
 =head1 BUGS
index d02345e..ebac5a2 100644 (file)
@@ -56,7 +56,7 @@
 
 <BR>
 <% $actual_label %><% $actual_label ? 'u' : 'U' %>nearned revenue
-is the amount of unearned revenue
+is the as-yet-unearned portion of revenue
 <% $actual_label ? 'Freeside has actually' : '' %>
 invoiced for packages with 
 <% $cgi->param('include_monthly') ? 'terms extending into the future.'
@@ -148,17 +148,20 @@ foreach my $agentnum (@agentnums) {
   } elsif ( $mode eq 'paid' ) {
     $unearned_base = FS::cust_bill_pkg->paid_sql(@opt);
   }
-  my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS REAL)";
-  my $elapsed  = "GREATEST( $now - cust_bill_pkg.sdate, 0 )";
-  my $remaining = "(1 - $elapsed/$period)";
-
+  
+  my $edate_zero = midnight_sql('edate');
+  my $sdate_zero = midnight_sql('sdate');
+  my $period = "CAST( ($edate_zero - $sdate_zero) / 86400.0 AS DECIMAL(10,0) )";
+  my $remaining = "GREATEST(
+    CAST( ($edate_zero - $now) / 86400.0 AS DECIMAL(10,0) ),
+    0)";
+  my $fraction = "$remaining / $period";
+  
   my $unearned_sql = "CAST(
-  GREATEST( $unearned_base * $remaining, 0 )
+  GREATEST( $unearned_base * $fraction, 0 )
     AS DECIMAL(10,2)
   )";
 
-  my $float = 'REAL'; #'DOUBLE PRECISION';
-  
   my $select = "SUM( $unearned_sql )";
 
   if ( !$cgi->param('include_monthly') ) {
@@ -186,7 +189,6 @@ foreach my $agentnum (@agentnums) {
                  $where
              ";
 
-             warn $sql;
   my $sth = dbh->prepare($sql) or die dbh->errstr;
   $sth->execute or die $sth->errstr;
   my $total = $sth->fetchrow_arrayref->[0];
index 831625e..02d514c 100644 (file)
@@ -1,5 +1,6 @@
 <& elements/search.html,
-  'title'       => emt("Unearned revenue - ".ucfirst($unearned_mode)),
+  'title'       => emt("Unearned revenue - ".ucfirst($unearned_mode)) . ' (' .
+                   time2str('%b %d %Y', $unearned) . ')',
   'name'        => emt('line items'),
   'query'       => $query,
   'count_query' => $count_query,
@@ -41,8 +42,8 @@
     date_sub('last_credit'),
     #Charge start
     date_sub('sdate'),
-    #Charge end
-    date_sub('edate'),
+    #Charge end, minus most of a day
+    date_sub('before_edate'),
     #Invoice
     'invnum',
     #Invoice date
@@ -167,11 +168,19 @@ else {
 # whatever we're using as the base, only show rows where it's positive
 push @where, "$unearned_base > 0";
 
-my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS REAL)";
-my $elapsed = "GREATEST( $unearned - cust_bill_pkg.sdate, 0 )";
-my $remaining = "(1 - $elapsed/$period)";
+my $edate_zero = midnight_sql('edate');
+my $sdate_zero = midnight_sql('sdate');
+# $unearned is one second before midnight on the date requested for the report.
 
-$unearned_sql = "CAST( $unearned_base * $remaining AS DECIMAL(10,2) )";
+# suppress partial days for more accounting-like behavior
+my $period = "CAST( ($edate_zero - $sdate_zero) / 86400.0 AS DECIMAL(10,0) )";
+
+my $remaining = "GREATEST( 
+  CAST( ($edate_zero - $unearned) / 86400.0 AS DECIMAL(10,0) ),
+  0)";
+my $fraction = "$remaining / $period";
+
+$unearned_sql = "CAST( $unearned_base * $fraction AS DECIMAL(10,2) )";
 push @select, "$unearned_sql AS unearned_revenue";
 
 # last payment/credit date
@@ -187,6 +196,8 @@ foreach my $x (qw(pay credit)) {
   push @select, "($last_date_sql) AS last_$x";
 }
 
+push @select, '(edate - 82799) AS before_edate';
+
 #no itemdesc
 #no tax report group kludge
 #no tax exemption