fix intro rates packages vs discounts, RT#83503
[freeside.git] / FS / FS / part_pkg / flat.pm
index f23717b..762eceb 100644 (file)
@@ -15,6 +15,7 @@ use Tie::IxHash;
 use List::Util qw( min );
 use FS::UI::bytecount;
 use FS::Conf;
+use Time::Local 'timelocal';
 
 #ask FS::UID to run this stuff for us later
 FS::UID->install_callback( sub {
@@ -56,6 +57,12 @@ tie my %contract_years, 'Tie::IxHash', (
                                     'the customer\'s next bill date',
                           'type' => 'checkbox',
                         },
+    'prorate_defer_change_bill' => {
+                          'name' => 'When synchronizing, defer bill for '.
+                                    'package changes until the customer\'s '.
+                                    'next bill date',
+                          'type' => 'checkbox',
+                        },
     'prorate_round_day' => {
                           'name' => 'When synchronizing, round the prorated '.
                                     'period',
@@ -86,7 +93,8 @@ tie my %contract_years, 'Tie::IxHash', (
   },
   'fieldorder' => [ qw( recur_temporality 
                         start_1st
-                        sync_bill_date prorate_defer_bill prorate_round_day
+                        sync_bill_date prorate_defer_bill
+                        prorate_defer_change_bill prorate_round_day
                         suspend_bill unsuspend_adjust_bill
                         bill_recur_on_cancel
                         bill_suspend_as_cancel
@@ -113,9 +121,9 @@ sub price_info {
 }
 
 sub calc_setup {
-  my($self, $cust_pkg, $sdate, $details, $param ) = @_;
+  my($self, $cust_pkg, $time, $details, $param ) = @_;
 
-  return 0 if $self->prorate_setup($cust_pkg, $sdate);
+  return 0 if $self->prorate_setup($cust_pkg, $time);
 
   my $i = 0;
   my $count = $self->option( 'additional_count', 'quiet' ) || 0;
@@ -123,12 +131,12 @@ sub calc_setup {
     push @$details, $self->option( 'additional_info' . $i++ );
   }
 
-  my $charge = $self->base_setup($cust_pkg, $sdate, $details);
+  my $charge = $self->base_setup($cust_pkg, $time, $details);
 
   my $discount = 0;
   if ( $charge > 0 ) {
       $param->{'setup_charge'} = $charge;
-      $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
+      $discount = $self->calc_discount($cust_pkg, \$time, $details, $param);
       delete $param->{'setup_charge'};
   }
 
@@ -136,7 +144,7 @@ sub calc_setup {
 }
 
 sub base_setup {
-  my($self, $cust_pkg, $sdate, $details ) = @_;
+  my($self, $cust_pkg, $time, $details ) = @_;
   $self->option('setup_fee', 1) || 0;
 }
 
@@ -173,16 +181,36 @@ sub calc_recur {
 sub cutoff_day {
   my $self = shift;
   my $cust_pkg = shift;
+  my $cust_main = $cust_pkg->cust_main;
+  # force it to act like a prorate package, is what this means
+  # because we made a distinction once between prorate and flat packages
+  if ( $cust_main->force_prorate_day  and $cust_main->prorate_day ) {
+     return ( $cust_main->prorate_day );
+  }
   if ( $self->option('sync_bill_date',1) ) {
     my $next_bill = $cust_pkg->cust_main->next_bill_date;
     if ( $next_bill ) {
-      # careful here. if the prorate calculation is going to round to 
-      # the nearest day, this needs to always return the same result
-      if ( $self->option('prorate_round_day', 1) ) {
-        my $hour = (localtime($next_bill))[2];
-        $next_bill += 64800 if $hour >= 12;
-      }
       return (localtime($next_bill))[3];
+    } else {
+      # This is the customer's only active package and hasn't been billed
+      # yet, so set the cutoff day to either today or tomorrow, whichever
+      # would result in a full period after rounding.
+      my $setup = $cust_pkg->setup; # because it's "now"
+      my $rounding_mode = $self->option('prorate_round_day',1);
+      return () if !$setup or !$rounding_mode;
+      my ($sec, $min, $hour, $mday, $mon, $year) = localtime($setup);
+
+      if (   ( $rounding_mode == 1 and $hour >= 12 )
+          or ( $rounding_mode == 3 and ( $sec > 0 or $min > 0 or $hour > 0 ))
+      ) {
+        # then the prorate period will be rounded down to start from
+        # midnight tomorrow, so the cutoff day should be the current day +
+        # 1.
+        $setup = timelocal(59,59,23,$mday,$mon,$year) + 1;
+        $mday = (localtime($setup))[3];
+      }
+      # otherwise, it will be rounded up, so leave the cutoff day at today.
+      return $mday;
     }
   }
   return ();
@@ -225,6 +253,8 @@ sub calc_cancel {
   }
 }
 
+# no longer used; see credit_remaining in FS::cust_pkg
+
 sub calc_remain {
   my ($self, $cust_pkg, %options) = @_;
 
@@ -235,8 +265,6 @@ sub calc_remain {
     $time = time;
   }
 
-  my $sources = $options{'cust_credit_source_bill_pkg'};
-
   my $next_bill = $cust_pkg->getfield('bill') || 0;
 
   return 0 if    ! $self->base_recur($cust_pkg, \$time)
@@ -272,30 +300,17 @@ sub calc_remain {
       $amount = $amount * ($edate - $time) / ($edate - $cust_bill_pkg->sdate);
     }
 
-    # calculate tax adjustment. we're not doing full credit_lineitems here
-    # (e.g. not applying the credit to the past billing of this package)
-    # so just include the adjustment in the source record with the rest
-    # of the credit
-    my %tax_adjust = FS::cust_credit->calculate_tax_adjustment(
-      'custnum'     => $cust_pkg->custnum,
-      'billpkgnums' => [ $cust_bill_pkg->billpkgnum ],
-      'setuprecurs' => [ 'recur' ],
-      'amounts'     => [ $amount ],
-    );
-    $amount += $tax_adjust{taxtotal};
-
-    $amount = sprintf('%.2f', $amount); # ensure that amounts add up right
     $credit += $amount;
 
-    if ( $sources ) {
-      push @$sources,
-        FS::cust_credit_source_bill_pkg->new( {
-          'billpkgnum' => $cust_bill_pkg->billpkgnum,
-          'amount'     => $amount,
-          'currency'   => $cust_bill_pkg->cust_bill->currency,
-        } );
-    }
-  } # foreach $cust_bill_pkg
+    push @{ $options{'cust_credit_source_bill_pkg'} },
+      new FS::cust_credit_source_bill_pkg {
+        'billpkgnum' => $cust_bill_pkg->billpkgnum,
+        'amount'     => sprintf('%.2f', $amount),
+        'currency'   => $cust_bill_pkg->cust_bill->currency,
+      }
+        if $options{'cust_credit_source_bill_pkg'};
+
+  } 
 
   sprintf('%.2f', $credit);