fix big in RADIUS session viewing when using an ignored-accounting export
[freeside.git] / FS / FS / cust_credit.pm
index e868537..8ec255b 100644 (file)
@@ -1,13 +1,25 @@
 package FS::cust_credit;
 
 use strict;
 package FS::cust_credit;
 
 use strict;
-use vars qw( @ISA );
-use FS::UID qw( getotaker );
-use FS::Record qw( qsearchs );
+use vars qw( @ISA $conf $unsuspendauto );
+use Date::Format;
+use FS::UID qw( dbh getotaker );
+use FS::Record qw( qsearch qsearchs );
+use FS::Misc qw(send_email);
 use FS::cust_main;
 use FS::cust_main;
+use FS::cust_refund;
+use FS::cust_credit_bill;
 
 @ISA = qw( FS::Record );
 
 
 @ISA = qw( FS::Record );
 
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::cust_credit'} = sub { 
+
+  $conf = new FS::Conf;
+  $unsuspendauto = $conf->exists('unsuspendauto');
+
+};
+
 =head1 NAME
 
 FS::cust_credit - Object methods for cust_credit records
 =head1 NAME
 
 FS::cust_credit - Object methods for cust_credit records
@@ -41,9 +53,6 @@ FS::Record.  The following fields are currently supported:
 
 =item amount - amount of the credit
 
 
 =item amount - amount of the credit
 
-=item credited - how much of this credit that is still outstanding, which is
-amount minus all refunds (see L<FS::cust_refund>).
-
 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
 L<Time::Local> and L<Date::Parse> for conversion functions.
 
 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
 L<Time::Local> and L<Date::Parse> for conversion functions.
 
@@ -51,6 +60,8 @@ L<Time::Local> and L<Date::Parse> for conversion functions.
 
 =item reason - text
 
 
 =item reason - text
 
+=item closed - books closed flag, empty or `Y'
+
 =back
 
 =head1 METHODS
 =back
 
 =head1 METHODS
@@ -70,24 +81,46 @@ sub table { 'cust_credit'; }
 Adds this credit to the database ("Posts" the credit).  If there is an error,
 returns the error, otherwise returns false.
 
 Adds this credit to the database ("Posts" the credit).  If there is an error,
 returns the error, otherwise returns false.
 
-When adding new invoices, credited must be amount (or null, in which case it is
-automatically set to amount).
-
 =cut
 
 sub insert {
   my $self = shift;
 
 =cut
 
 sub insert {
   my $self = shift;
 
-  my $error;
-  return $error if $error = $self->ut_money('credited')
-                         || $self->ut_money('amount');
-
-  $self->credited($self->amount) if $self->credited == 0
-                                 || $self->credited eq '';
-  return "credited != amount!"
-    unless $self->credited == $self->amount;
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $cust_main = qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+  my $old_balance = $cust_main->balance;
+
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "error inserting $self: $error";
+  }
+
+  #false laziness w/ cust_credit::insert
+  if ( $unsuspendauto && $old_balance && $cust_main->balance <= 0 ) {
+    my @errors = $cust_main->unsuspend;
+    #return 
+    # side-fx with nested transactions?  upstack rolls back?
+    warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
+         join(' / ', @errors)
+      if @errors;
+  }
+  #eslaf
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
 
 
-  $self->SUPER::insert;
 }
 
 =item delete
 }
 
 =item delete
@@ -97,30 +130,80 @@ Currently unimplemented.
 =cut
 
 sub delete {
 =cut
 
 sub delete {
-  return "Can't remove credit!"
+  my $self = shift;
+  return "Can't delete closed credit" if $self->closed =~ /^Y/i;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  foreach my $cust_credit_bill ( $self->cust_credit_bill ) {
+    my $error = $cust_credit_bill->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  my $error = $self->SUPER::delete(@_);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( $conf->config('deletecredits') ne '' ) {
+
+    my $cust_main = qsearchs('cust_main',{ 'custnum' => $self->custnum });
+
+    my $error = send_email(
+      'from'    => $conf->config('invoice_from'), #??? well as good as any
+      'to'      => $conf->config('deletecredits'),
+      'subject' => 'FREESIDE NOTIFICATION: Credit deleted',
+      'body'    => [
+        "This is an automatic message from your Freeside installation\n",
+        "informing you that the following credit has been deleted:\n",
+        "\n",
+        'crednum: '. $self->crednum. "\n",
+        'custnum: '. $self->custnum.
+          " (". $cust_main->last. ", ". $cust_main->first. ")\n",
+        'amount: $'. sprintf("%.2f", $self->amount). "\n",
+        'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n",
+        'reason: '. $self->reason. "\n",
+      ],
+    );
+
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't send credit deletion notification: $error";
+    }
+
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
 }
 
 =item replace OLD_RECORD
 
 }
 
 =item replace OLD_RECORD
 
-Replaces the OLD_RECORD with this one in the database.  If there is an error,
-returns the error, otherwise returns false.
-
-Only credited may be changed.  Credited is normally updated by creating and
-inserting a refund (see L<FS::cust_refund>).
+Credits may not be modified; there would then be no record the credit was ever
+posted.
 
 =cut
 
 sub replace {
 
 =cut
 
 sub replace {
-  my ( $new, $old ) = ( shift, shift );
-
-  return "Can't change custnum!" unless $old->custnum == $new->custnum;
-  #return "Can't change date!" unless $old->_date eq $new->_date;
-  return "Can't change date!" unless $old->_date == $new->_date;
-  return "Can't change amount!" unless $old->amount == $new->amount;
-  return "(New) credited can't be > (new) amount!"
-    if $new->credited > $new->amount;
-
-  $new->SUPER::replace($old);
+  #return "Can't modify credit!"
+  my $self = shift;
+  return "Can't modify closed credit" if $self->closed =~ /^Y/i;
+  $self->SUPER::replace(@_);
 }
 
 =item check
 }
 
 =item check
@@ -139,11 +222,13 @@ sub check {
     || $self->ut_number('custnum')
     || $self->ut_numbern('_date')
     || $self->ut_money('amount')
     || $self->ut_number('custnum')
     || $self->ut_numbern('_date')
     || $self->ut_money('amount')
-    || $self->ut_money('credited')
-    || $self->ut_textn('reason');
+    || $self->ut_textn('reason')
+    || $self->ut_enum('closed', [ '', 'Y' ])
   ;
   return $error if $error;
 
   ;
   return $error if $error;
 
+  return "amount must be > 0 " if $self->amount <= 0;
+
   return "Unknown customer"
     unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
 
   return "Unknown customer"
     unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
 
@@ -151,22 +236,92 @@ sub check {
 
   $self->otaker(getotaker);
 
 
   $self->otaker(getotaker);
 
-  ''; #no error
+  $self->SUPER::check;
 }
 
 }
 
-=back
+=item cust_refund
+
+Depreciated.  See the cust_credit_refund method.
+
+#Returns all refunds (see L<FS::cust_refund>) for this credit.
 
 
-=head1 VERSION
+=cut
+
+sub cust_refund {
+  use Carp;
+  croak "FS::cust_credit->cust_pay depreciated; see ".
+        "FS::cust_credit->cust_credit_refund";
+  #my $self = shift;
+  #sort { $a->_date <=> $b->_date }
+  #  qsearch( 'cust_refund', { 'crednum' => $self->crednum } )
+  #;
+}
 
 
-$Id: cust_credit.pm,v 1.2 2001-02-11 17:17:39 ivan Exp $
+=item cust_credit_refund
+
+Returns all refund applications (see L<FS::cust_credit_refund>) for this credit.
+
+=cut
+
+sub cust_credit_refund {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit_refund', { 'crednum' => $self->crednum } )
+  ;
+}
+
+=item cust_credit_bill
+
+Returns all application to invoices (see L<FS::cust_credit_bill>) for this
+credit.
+
+=cut
+
+sub cust_credit_bill {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit_bill', { 'crednum' => $self->crednum } )
+  ;
+}
+
+=item credited
+
+Returns the amount of this credit that is still outstanding; which is
+amount minus all refund applications (see L<FS::cust_credit_refund>) and
+applications to invoices (see L<FS::cust_credit_bill>).
+
+=cut
+
+sub credited {
+  my $self = shift;
+  my $amount = $self->amount;
+  $amount -= $_->amount foreach ( $self->cust_credit_refund );
+  $amount -= $_->amount foreach ( $self->cust_credit_bill );
+  sprintf( "%.2f", $amount );
+}
+
+=item cust_main
+
+Returns the customer (see L<FS::cust_main>) for this credit.
+
+=cut
+
+sub cust_main {
+  my $self = shift;
+  qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+
+=back
 
 =head1 BUGS
 
 
 =head1 BUGS
 
-The delete method.
+The delete method.  The replace method.
 
 =head1 SEE ALSO
 
 
 =head1 SEE ALSO
 
-L<FS::Record>, L<FS::cust_refund>, L<FS::cust_bill>, schema.html from the base
+L<FS::Record>, L<FS::cust_credit_refund>, L<FS::cust_refund>,
+L<FS::cust_credit_bill> L<FS::cust_bill>, schema.html from the base
 documentation.
 
 =cut
 documentation.
 
 =cut