use Tie::IxHash module explicitly, don't rely on load order
[freeside.git] / FS / FS / part_pkg / prorate_Mixin.pm
index e8d42b9..a89b54d 100644 (file)
@@ -2,10 +2,18 @@ package FS::part_pkg::prorate_Mixin;
 
 use strict;
 use vars qw( %info );
+use Tie::IxHash;
 use Time::Local qw( timelocal timelocal_nocheck );
 use Date::Format qw( time2str );
 use List::Util qw( min );
 
+tie our %prorate_round_day_opts, 'Tie::IxHash',
+  0   => 'no',
+  1   => 'to the nearest day',
+  2   => 'up to a full day',
+  3   => 'down to a full day',
+;
+
 %info = ( 
   'disabled'  => 1,
   # define all fields that are referenced in this code
@@ -16,8 +24,9 @@ use List::Util qw( min );
                 'type' => 'checkbox',
     },
     'prorate_round_day' => { 
-                'name' => 'When prorating, round to the nearest full day',
-                'type' => 'checkbox',
+                'name' => 'When prorating, round the prorated period',
+                'type' => 'select',
+                'select_options' => \%prorate_round_day_opts,
     },
     'prorate_defer_bill' => {
                 'name' => 'When prorating, defer the first bill until the '.
@@ -183,22 +192,35 @@ sub prorate_setup {
   my $self = shift;
   my ($cust_pkg, $sdate) = @_;
   my @cutoff_days = $self->cutoff_day($cust_pkg);
-  if ( ! $cust_pkg->bill
-      and $self->option('prorate_defer_bill',1)
-      and @cutoff_days
-  ) {
-    my ($mnow, $mend, $mstart) = $self->_endpoints($sdate, @cutoff_days);
-    # If today is the cutoff day, set the next bill and setup both to 
-    # midnight today, so that the customer will be billed normally for a 
-    # month starting today.
-    if ( $mnow - $mstart < 86400 ) {
-      $cust_pkg->setup($mstart);
-      $cust_pkg->bill($mstart);
-    }
-    else {
-      $cust_pkg->bill($mend);
+  if ( @cutoff_days and $self->option('prorate_defer_bill', 1) ) {
+    if ( $cust_pkg->setup ) {
+      # Setup date is already set. Then we're being called indirectly via calc_prorate
+      # to calculate the deferred setup fee. Allow that to happen normally.
+      return 0;
+    } else {
+      # We're going to set the setup date (so that the deferred billing knows when
+      # the package started) and suppress charging the setup fee.
+      if ( $cust_pkg->bill ) {
+        # For some reason (probably user override), the bill date has been set even
+        # though the package isn't billing yet. Start billing as though that was the
+        # start date.
+        $sdate = $cust_pkg->bill;
+        $cust_pkg->setup($cust_pkg->bill);
+      }
+      # Now figure the start and end of the period that contains the start date.
+      my ($mnow, $mend, $mstart) = $self->_endpoints($sdate, @cutoff_days);
+      # If today is the cutoff day, set the next bill and setup both to 
+      # midnight today, so that the customer will be billed normally for a 
+      # month starting today.
+      if ( $mnow - $mstart < 86400 ) {
+        $cust_pkg->setup($mstart);
+        $cust_pkg->bill($mstart);
+      }
+      else {
+        $cust_pkg->bill($mend);
+      }
+      return 1;
     }
-    return 1;
   }
   return 0;
 }
@@ -219,7 +241,8 @@ sub _endpoints {
 
   # only works for freq >= 1 month; probably can't be fixed
   my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5];
-  if( $self->option('prorate_round_day',1) ) {
+  my $rounding_mode = $self->option('prorate_round_day',1);
+  if ( $rounding_mode == 1 ) {
     # If the time is 12:00-23:59, move to the next day by adding 18 
     # hours to $mnow.  Because of DST this can end up from 05:00 to 18:59
     # but it's always within the next day.
@@ -228,6 +251,19 @@ sub _endpoints {
     ($mday,$mon,$year) = (localtime($mnow))[3..5];
     # Then set $mnow to midnight on that day.
     $mnow = timelocal(0,0,0,$mday,$mon,$year);
+  } elsif ( $rounding_mode == 2 ) {
+    # Move the time back to midnight. This increases the length of the
+    # prorate interval.
+    $mnow = timelocal(0,0,0,$mday,$mon,$year);
+    ($mday,$mon,$year) = (localtime($mnow))[3..5];
+  } elsif ( $rounding_mode == 3 ) {
+    # If the time is after midnight, move it forward to the next midnight.
+    # This decreases the length of the prorate interval.
+    if ( $sec > 0 or $min > 0 or $hour > 0 ) {
+      # move to one second before midnight, then tick forward
+      $mnow = timelocal(59,59,23,$mday,$mon,$year) + 1;
+      ($mday,$mon,$year) = (localtime($mnow))[3..5];
+    }
   }
   my $mend;
   my $mstart;