multi-currency, RT#21565
[freeside.git] / FS / FS / part_pkg / flat.pm
index d59c694..9737a94 100644 (file)
@@ -6,6 +6,7 @@ use base qw( FS::part_pkg::prorate_Mixin
 
 use strict;
 use vars qw( %info %usage_recharge_fields @usage_recharge_fieldorder );
+use FS::Record qw( qsearch );
 use Tie::IxHash;
 use List::Util qw( min );
 use FS::UI::bytecount;
@@ -70,7 +71,14 @@ tie my %contract_years, 'Tie::IxHash', (
                                     'unsuspending',
                           'type' => 'checkbox',
                         },
-
+    'bill_recur_on_cancel' => {
+                        'name' => 'Bill the last period on cancellation',
+                        'type' => 'checkbox',
+                        },
+    'bill_suspend_as_cancel' => {
+                        'name' => 'Bill immediately upon suspension', #desc?
+                        'type' => 'checkbox',
+                        },
     'externalid' => { 'name'   => 'Optional External ID',
                       'default' => '',
                     },
@@ -81,6 +89,8 @@ tie my %contract_years, 'Tie::IxHash', (
                         start_1st
                         sync_bill_date prorate_defer_bill prorate_round_day
                         suspend_bill unsuspend_adjust_bill
+                        bill_recur_on_cancel
+                        bill_suspend_as_cancel
                         externalid ),
                   ],
   'weight' => 10,
@@ -93,9 +103,9 @@ sub price_info {
     my $setup = $self->option('setup_fee') || 0;
     my $recur = $self->option('recur_fee', 1) || 0;
     my $str = '';
-    $str = $money_char . $setup . ' one-time' if $setup;
+    $str = $money_char . $setup . ($recur ? ' setup ' : ' one-time') if $setup;
     $str .= ', ' if ($setup && $recur);
-    $str .= $money_char . $recur . ' recurring ' if $recur;
+    $str .= $money_char. $recur. '/'. $self->freq_pretty if $recur;
     $str;
 }
 
@@ -112,7 +122,7 @@ sub calc_setup {
 
   my $quantity = $cust_pkg->quantity || 1;
 
-  my $charge = $quantity * $self->unit_setup($cust_pkg, $sdate, $details);
+  my $charge = $quantity * $self->base_setup($cust_pkg, $sdate, $details);
 
   my $discount = 0;
   if ( $charge > 0 ) {
@@ -124,7 +134,7 @@ sub calc_setup {
   sprintf('%.2f', $charge - $discount);
 }
 
-sub unit_setup {
+sub base_setup {
   my($self, $cust_pkg, $sdate, $details ) = @_;
 
   $self->option('setup_fee') || 0;
@@ -138,11 +148,12 @@ sub calc_recur {
   my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
 
   return 0
-    if $self->recur_temporality eq 'preceding' && $last_bill == 0;
+    if $self->recur_temporality eq 'preceding' && !$last_bill;
 
   my $charge = $self->base_recur($cust_pkg, $sdate);
-  if ( my $cutoff_day = $self->cutoff_day($cust_pkg) ) {
-    $charge = $self->calc_prorate(@_, $cutoff_day);
+  # always treat cutoff_day as a list
+  if ( my @cutoff_day = $self->cutoff_day($cust_pkg) ) {
+    $charge = $self->calc_prorate(@_, @cutoff_day);
   }
   elsif ( $param->{freq_override} ) {
     # XXX not sure if this should be mutually exclusive with sync_bill_date.
@@ -151,6 +162,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);
 }
@@ -164,7 +178,7 @@ sub cutoff_day {
       return (localtime($next_bill))[3];
     }
   }
-  return 0;
+  return ();
 }
 
 sub base_recur {
@@ -180,6 +194,22 @@ sub base_recur_permonth {
   sprintf('%.2f', $self->base_recur($cust_pkg) / $self->freq );
 }
 
+sub calc_cancel {
+  my $self = shift;
+  my $conf = new FS::Conf;
+  if ( $self->recur_temporality eq 'preceding'
+       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?
+          and $self->can('calc_usage') ) {
+    # bill for outstanding usage
+    return $self->calc_usage(@_);
+  }
+  0;
+}
+
 sub calc_remain {
   my ($self, $cust_pkg, %options) = @_;
 
@@ -196,19 +226,28 @@ sub calc_remain {
               || ! $next_bill
               || $next_bill < $time;
 
-  my %sec = (
-    'h' =>    3600, # 60 * 60
-    'd' =>   86400, # 60 * 60 * 24
-    'w' =>  604800, # 60 * 60 * 24 * 7
-    'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12 
-  );
-
-  $self->freq =~ /^(\d+)([hdwm]?)$/
-    or die 'unparsable frequency: '. $self->freq;
-  my $freq_sec = $1 * $sec{$2||'m'};
-  return 0 unless $freq_sec;
-
-  sprintf("%.2f", $self->base_recur($cust_pkg, \$time) * ( $next_bill - $time ) / $freq_sec );
+  # Use actual charge for this period, not base_recur (for discounts).
+  # Use sdate < $time and edate >= $time because when billing on 
+  # cancellation, edate = $time.
+  my $credit = 0;
+  foreach my $item ( 
+    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;
+    if ( $self->recur_temporality eq 'preceding' ) {
+      $edate = $self->add_freq($item->sdate);
+    }
+    $credit += ($item->recur - $item->usage) * 
+               ($edate - $time) / ($edate - $item->sdate);
+  } 
+  sprintf('%.2f', $credit);
+  #sprintf("%.2f", $self->base_recur($cust_pkg, \$time) * ( $next_bill - $time ) / $freq_sec );
 
 }