correct display of prepay discount info with non-monthly packages, #15040
[freeside.git] / FS / FS / cust_pay.pm
index e0c99f8..d98d11e 100644 (file)
@@ -189,29 +189,50 @@ sub insert {
 
   if ( my $credit_type = $conf->config('prepayment_discounts-credit_type') ) {
     if ( my $months = $self->discount_term ) {
-      #hmmm... error handling
-      my ($credit, $savings, $total) = 
-        $cust_main->discount_term_values($months);
+      # XXX this should be moved out somewhere, but discount_term_values
+      # doesn't fit right
+      my ($cust_bill) = ($cust_main->cust_bill)[-1]; # most recent invoice
+      return "can't accept prepayment for an unbilled customer" if !$cust_bill;
+
+      # %billing_pkgs contains this customer's active monthly packages. 
+      # Recurring fees for those packages will be credited and then rebilled 
+      # for the full discount term.  Other packages on the last invoice 
+      # (canceled, non-monthly recurring, or one-time charges) will be 
+      # left as they are.
+      my %billing_pkgs = map { $_->pkgnum => $_ } 
+                         grep { $_->part_pkg->freq eq '1' } 
+                         $cust_main->billing_pkgs;
+      my $credit = 0; # sum of recurring charges from that invoice
+      my $last_bill_date = 0; # the real bill date
+      foreach my $item ( $cust_bill->cust_bill_pkg ) {
+        next if !exists($billing_pkgs{$item->pkgnum}); # skip inactive packages
+        $credit += $item->recur;
+        $last_bill_date = $item->cust_pkg->last_bill 
+          if defined($item->cust_pkg) 
+            and $item->cust_pkg->last_bill > $last_bill_date
+      }
+
       my $cust_credit = new FS::cust_credit {
         'custnum' => $self->custnum,
-        'amount'  => $credit,
+        'amount'  => sprintf('%.2f', $credit),
         'reason'  => 'customer chose to prepay for discount',
       };
       $error = $cust_credit->insert('reason_type' => $credit_type);
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
-        return "error inserting cust_pay: $error";
+        return "error inserting prepayment credit: $error";
       }
-      my @pkgs = $cust_main->_discount_pkgs_and_bill;
-      my $cust_bill = shift(@pkgs);
-      @pkgs = &FS::cust_main::Billing::_discountable_pkgs_at_term($months, @pkgs);
-      $_->bill($_->last_bill) foreach @pkgs;
-      $error = $cust_main->bill( 
-        'recurring_only' => 1,
-        'time'           => $cust_bill->invoice_date,
+      # don't apply it yet
+
+      # bill for the entire term
+      $_->bill($_->last_bill) foreach (values %billing_pkgs);
+      $error = $cust_main->bill(
+        # no recurring_only, we want unbilled packages with start dates to 
+        # get billed
         'no_usage_reset' => 1,
-        'pkg_list'       => \@pkgs,
-        'freq_override'   => $months,
+        'time'           => $last_bill_date, # not $cust_bill->_date
+        'pkg_list'       => [ values %billing_pkgs ],
+        'freq_override'  => $months,
       );
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
@@ -227,6 +248,8 @@ sub insert {
         $dbh->rollback if $oldAutoCommit;
         return "balance after prepay discount attempt: $new_balance";
       }
+      # user friendly: override the "apply only to this invoice" mode
+      $self->invnum('');
       
     }
 
@@ -264,10 +287,41 @@ sub insert {
   }
   #eslaf
 
+  #bill setup fees for voip_cdr bill_every_call packages
+  #some false laziness w/search in freeside-cdrd
+  my $addl_from =
+    'LEFT JOIN part_pkg USING ( pkgpart ) '.
+    "LEFT JOIN part_pkg_option
+       ON ( cust_pkg.pkgpart = part_pkg_option.pkgpart
+            AND part_pkg_option.optionname = 'bill_every_call' )";
+
+  my $extra_sql = " AND plan = 'voip_cdr' AND optionvalue = '1' ".
+                  " AND ( cust_pkg.setup IS NULL OR cust_pkg.setup = 0 ) ";
+
+  my @cust_pkg = qsearch({
+    'table'     => 'cust_pkg',
+    'addl_from' => $addl_from,
+    'hashref'   => { 'custnum' => $self->custnum,
+                     'susp'    => '',
+                     'cancel'  => '',
+                   },
+    'extra_sql' => $extra_sql,
+  });
+
+  if ( @cust_pkg ) {
+    warn "voip_cdr bill_every_call packages found; billing customer\n";
+    my $bill_error = $self->cust_main->bill_and_collect( 'fatal' => 'return' );
+    if ( $bill_error ) {
+      warn "WARNING: Error billing customer: $bill_error\n";
+    }
+  }
+  #end of billing setup fees for voip_cdr bill_every_call packages
+
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
 
   #payment receipt
-  my $trigger = $conf->config('payment_receipt-trigger') || 'cust_pay';
+  my $trigger = $conf->config('payment_receipt-trigger', 
+                              $self->cust_main->agentnum) || 'cust_pay';
   if ( $trigger eq 'cust_pay' ) {
     my $error = $self->send_receipt(
       'manual'    => $options{'manual'},
@@ -401,14 +455,17 @@ sub delete {
 
 }
 
-=item replace OLD_RECORD
+=item replace [ OLD_RECORD ]
 
 You can, but probably shouldn't modify payments...
 
+Replaces the OLD_RECORD with this one in the database, or, if OLD_RECORD is not
+supplied, replaces this record.  If there is an error, returns the error,
+otherwise returns false.
+
 =cut
 
 sub replace {
-  #return "Can't modify payment!"
   my $self = shift;
   return "Can't modify closed payment" if $self->closed =~ /^Y/i;
   $self->SUPER::replace(@_);
@@ -500,7 +557,7 @@ sub send_receipt {
 
   my $conf = new FS::Conf;
 
-  return '' unless $conf->exists('payment_receipt');
+  return '' unless $conf->exists('payment_receipt', $cust_main->agentnum);
 
   my @invoicing_list = $cust_main->invoicing_list_emailonly;
   return '' unless @invoicing_list;
@@ -510,18 +567,18 @@ sub send_receipt {
   my $error = '';
 
   if (    ( exists($opt->{'manual'}) && $opt->{'manual'} )
-       || ! $conf->exists('invoice_html_statement')
+       #|| ! $conf->exists('invoice_html_statement')
        || ! $cust_bill
      )
   {
-
-    if ( $conf->exists('payment_receipt_msgnum')
-         && $conf->config('payment_receipt_msgnum')
-       )
-    {
-      my $msg_template = 
-          FS::msg_template->by_key($conf->config('payment_receipt_msgnum'));
-      $error = $msg_template->send('cust_main'=> $cust_main, 'object'=> $self);
+    my $msgnum = $conf->config('payment_receipt_msgnum', $cust_main->agentnum);
+    if ( $msgnum ) {
+      my $msg_template = FS::msg_template->by_key($msgnum);
+      $error = $msg_template->send(
+        'cust_main'   => $cust_main,
+        'object'      => $self,
+        'from_config' => 'payment_receipt_from',
+      );
 
     } elsif ( $conf->exists('payment_receipt_email') ) {
 
@@ -569,7 +626,7 @@ sub send_receipt {
 
     } else {
 
-      warn "payment_receipt is on, but no payment_receipt_msgnum or invoice_html_statement is configured\n";
+      warn "payment_receipt is on, but no payment_receipt_msgnum\n";
 
     }
 
@@ -581,8 +638,10 @@ sub send_receipt {
     };
 
     $error = $queue->insert(
-      'invnum'   => $cust_bill->invnum,
-      'template' => 'statement',
+      'invnum'      => $cust_bill->invnum,
+      'template'    => 'statement',
+      'notice_name' => 'Statement',
+      'no_coupon'   => 1,
     );
 
   }
@@ -800,9 +859,10 @@ sub _upgrade_data {  #class method
     my $h_cust_pay = $cust_pay->h_search('insert');
     if ( $h_cust_pay ) {
       next if $cust_pay->otaker eq $h_cust_pay->history_user;
-      $cust_pay->otaker($h_cust_pay->history_user);
+      #$cust_pay->otaker($h_cust_pay->history_user);
+      $cust_pay->set('otaker', $h_cust_pay->history_user);
     } else {
-      $cust_pay->otaker('legacy');
+      $cust_pay->set('otaker', 'legacy');
     }
 
     delete $FS::payby::hash{'COMP'}->{cust_pay}; #quelle kludge