fix calculation of unused time credit + advance billing, #36726
[freeside.git] / FS / FS / part_pkg / flat.pm
index 22eb698..6ff91b5 100644 (file)
@@ -120,9 +120,7 @@ sub calc_setup {
     push @$details, $self->option( 'additional_info' . $i++ );
   }
 
-  my $quantity = $cust_pkg->quantity || 1;
-
-  my $charge = $quantity * $self->unit_setup($cust_pkg, $sdate, $details);
+  my $charge = $self->unit_setup($cust_pkg, $sdate, $details);
 
   my $discount = 0;
   if ( $charge > 0 ) {
@@ -131,13 +129,16 @@ sub calc_setup {
       delete $param->{'setup_charge'};
   }
 
-  sprintf('%.2f', $charge - $discount);
+  sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) );
 }
 
 sub unit_setup {
   my($self, $cust_pkg, $sdate, $details ) = @_;
-
-  $self->option('setup_fee') || 0;
+  ( exists( $self->{'Hash'}{'_opt_setup_fee'} )
+      ? $self->{'Hash'}{'_opt_setup_fee'}
+      : $self->option('setup_fee', 1) 
+  )
+    || 0;
 }
 
 sub calc_recur {
@@ -162,11 +163,9 @@ sub calc_recur {
     $charge *= $param->{freq_override} if $param->{freq_override};
   }
 
-  my $quantity = $cust_pkg->quantity || 1;
-  $charge *= $quantity;
-
   my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
-  return sprintf('%.2f', $charge - $discount);
+
+  sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) );
 }
 
 sub cutoff_day {
@@ -175,6 +174,12 @@ sub cutoff_day {
   if ( $self->option('sync_bill_date',1) ) {
     my $next_bill = $cust_pkg->cust_main->next_bill_date;
     if ( defined($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];
     }
   }
@@ -183,7 +188,11 @@ sub cutoff_day {
 
 sub base_recur {
   my($self, $cust_pkg, $sdate) = @_;
-  $self->option('recur_fee', 1) || 0;
+  ( exists( $self->{'Hash'}{'_opt_recur_fee'} )
+      ? $self->{'Hash'}{'_opt_recur_fee'}
+      : $self->option('recur_fee', 1) 
+  )
+    || 0;
 }
 
 sub base_recur_permonth {
@@ -201,13 +210,13 @@ sub calc_cancel {
        and $self->option('bill_recur_on_cancel', 1) ) {
     # run another recurring cycle
     return $self->calc_recur(@_);
-  }
-  elsif ( $conf->exists('bill_usage_on_cancel') # should be a package option?
+  } elsif ( $conf->exists('bill_usage_on_cancel') # should be a package option?
           and $self->can('calc_usage') ) {
     # bill for outstanding usage
     return $self->calc_usage(@_);
+  } else {
+    return 'NOTHING'; # numerically zero, but has special meaning
   }
-  0;
 }
 
 sub calc_remain {
@@ -230,24 +239,33 @@ sub calc_remain {
   # Use sdate < $time and edate >= $time because when billing on 
   # cancellation, edate = $time.
   my $credit = 0;
-  foreach my $item ( 
+  foreach my $cust_bill_pkg ( 
     qsearch('cust_bill_pkg', { 
       pkgnum => $cust_pkg->pkgnum,
-      sdate => {op => '<' , value => $time},
       edate => {op => '>=', value => $time},
       recur => {op => '>' , value => 0},
     })
   ) {
     # hack to deal with the weird behavior of edate on package cancellation
-    my $edate = $item->edate;
+    my $edate = $cust_bill_pkg->edate;
     if ( $self->recur_temporality eq 'preceding' ) {
-      $edate = $self->add_freq($item->sdate);
+      $edate = $self->add_freq($cust_bill_pkg->sdate);
     }
-    $credit += ($item->recur - $item->usage) * 
-               ($edate - $time) / ($edate - $item->sdate);
+
+    # this will also get any package charges that are _entirely_ after the
+    # cancellation date (can happen with advance billing). in that case,
+    # use the entire recurring charge:
+    my $amount = $cust_bill_pkg->recur - $cust_bill_pkg->usage;
+
+    # but if the cancellation happens during the interval, prorate it:
+    # (XXX obey prorate_round_day here?)
+    if ( $cust_bill_pkg->sdate < $time ) {
+      $amount = $amount * ($edate - $time) / ($edate - $cust_bill_pkg->sdate);
+    }
+
+    $credit += $amount;
   } 
   sprintf('%.2f', $credit);
-  #sprintf("%.2f", $self->base_recur($cust_pkg, \$time) * ( $next_bill - $time ) / $freq_sec );
 
 }
 
@@ -257,7 +275,16 @@ sub is_free_options {
 
 sub is_prepaid { 0; } #no, we're postpaid
 
-sub can_start_date { ! shift->option('start_1st', 1) }
+sub can_start_date {
+  my $self = shift;
+  my %opt = @_;
+  return 0 if $self->start_on_hold;
+
+  ! $self->option('start_1st', 1) && (   ! $self->option('sync_bill_date',1)
+                                      || ! $self->option('prorate_defer_bill',1)
+                                      || ! $opt{'num_ncancelled_pkgs'}
+                                     ); 
+}
 
 sub can_discount { 1; }