sticky customer notes, RT#29311
[freeside.git] / FS / FS / cust_main.pm
index 43be568..b43d7e3 100644 (file)
@@ -8,6 +8,7 @@ use base qw( FS::cust_main::Packages FS::cust_main::Status
              FS::cust_main::Billing_Discount
              FS::cust_main::Billing_ThirdParty
              FS::cust_main::Location
+             FS::cust_main::Credit_Limit
              FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin
              FS::geocode_Mixin FS::Quotable_Mixin FS::Sales_Mixin
              FS::o2m_Common
@@ -391,7 +392,7 @@ sub insert {
 
     $payby = 'PREP' if $amount;
 
-  } elsif ( $self->payby =~ /^(CASH|WEST|MCRD|PPAL)$/ ) {
+  } elsif ( $self->payby =~ /^(CASH|WEST|MCRD|MCHK|PPAL)$/ ) {
 
     $payby = $1;
     $self->payby('BILL');
@@ -401,11 +402,8 @@ sub insert {
 
   # insert locations
   foreach my $l (qw(bill_location ship_location)) {
-    my $loc = delete $self->hashref->{$l};
-    # XXX if we're moving a prospect's locations, do that here
-    if ( !$loc ) {
-      return "$l not set";
-    }
+
+    my $loc = delete $self->hashref->{$l} or return "$l not set";
     
     if ( !$loc->locationnum ) {
       # warn the location that we're going to insert it with no custnum
@@ -418,8 +416,19 @@ sub insert {
         my $label = $l eq 'ship_location' ? 'service' : 'billing';
         return "$error (in $label location)";
       }
-    }
-    elsif ( ($loc->custnum || 0) > 0 or $loc->prospectnum ) {
+
+    } elsif ( $loc->prospectnum ) {
+
+      $loc->prospectnum('');
+      $loc->set(custnum_pending => 1);
+      my $error = $loc->replace;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        my $label = $l eq 'ship_location' ? 'service' : 'billing';
+        return "$error (moving $label location)";
+      }
+
+    } elsif ( ($loc->custnum || 0) > 0 ) {
       # then it somehow belongs to another customer--shouldn't happen
       $dbh->rollback if $oldAutoCommit;
       return "$l belongs to customer ".$loc->custnum;
@@ -1755,6 +1764,8 @@ sub check {
     || $self->ut_name('first')
     || $self->ut_snumbern('signupdate')
     || $self->ut_snumbern('birthdate')
+    || $self->ut_namen('spouse_last')
+    || $self->ut_namen('spouse_first')
     || $self->ut_snumbern('spouse_birthdate')
     || $self->ut_snumbern('anniversary_date')
     || $self->ut_textn('company')
@@ -2081,6 +2092,7 @@ Returns a list of fields which have ship_ duplicates.
 
 sub addr_fields {
   qw( last first company
+      locationname
       address1 address2 city county state zip country
       latitude longitude
       daytime night fax mobile
@@ -2138,14 +2150,27 @@ sub cust_contact {
 =item unsuspend
 
 Unsuspends all unflagged suspended packages (see L</unflagged_suspended_pkgs>
-and L<FS::cust_pkg>) for this customer.  Always returns a list: an empty list
-on success or a list of errors.
+and L<FS::cust_pkg>) for this customer, except those on hold.
+
+Returns a list: an empty list on success or a list of errors.
 
 =cut
 
 sub unsuspend {
   my $self = shift;
-  grep { $_->unsuspend } $self->suspended_pkgs;
+  grep { ($_->get('setup')) && $_->unsuspend } $self->suspended_pkgs;
+}
+
+=item release_hold
+
+Unsuspends all suspended packages in the on-hold state (those without setup 
+dates) for this customer. 
+
+=cut
+
+sub release_hold {
+  my $self = shift;
+  grep { (!$_->setup) && $_->unsuspend } $self->suspended_pkgs;
 }
 
 =item suspend
@@ -2321,8 +2346,8 @@ Returns all notes (see L<FS::cust_main_note>) for this customer.
 
 sub notes {
   my($self,$orderby_classnum) = (shift,shift);
-  my $orderby = "_DATE DESC";
-  $orderby = "CLASSNUM ASC, $orderby" if $orderby_classnum;
+  my $orderby = "sticky DESC, _date DESC";
+  $orderby = "classnum ASC, $orderby" if $orderby_classnum;
   qsearch( 'cust_main_note',
            { 'custnum' => $self->custnum },
            '',
@@ -2836,7 +2861,7 @@ UNIX timestamps; see L<perlfunc/"time">).  Also see L<Time::Local> and
 L<Date::Parse> for conversion functions.  The empty string can be passed
 to disable that time constraint completely.
 
-Available options are:
+Accepts the same options as L<balance_date_sql>:
 
 =over 4
 
@@ -2844,6 +2869,12 @@ Available options are:
 
 set to true to disregard unapplied credits, payments and refunds outside the specified time period - by default the time period restriction only applies to invoices (useful for reporting, probably a bad idea for event triggering)
 
+=item cutoff
+
+An absolute cutoff time.  Payments, credits, and refunds I<applied> after this 
+time will be ignored.  Note that START_TIME and END_TIME only limit the date 
+range for invoices and I<unapplied> payments, credits, and refunds.
+
 =back
 
 =cut
@@ -3428,9 +3459,10 @@ Old-style:
 
 =cut
 
+#super false laziness w/quotation::charge
 sub charge {
   my $self = shift;
-  my ( $amount, $quantity, $start_date, $classnum );
+  my ( $amount, $setup_cost, $quantity, $start_date, $classnum );
   my ( $pkg, $comment, $additional );
   my ( $setuptax, $taxclass );   #internal taxes
   my ( $taxproduct, $override ); #vendor (CCH) taxes
@@ -3440,6 +3472,7 @@ sub charge {
   my $locationnum;
   if ( ref( $_[0] ) ) {
     $amount     = $_[0]->{amount};
+    $setup_cost = $_[0]->{setup_cost};
     $quantity   = exists($_[0]->{quantity}) ? $_[0]->{quantity} : 1;
     $start_date = exists($_[0]->{start_date}) ? $_[0]->{start_date} : '';
     $no_auto    = exists($_[0]->{no_auto}) ? $_[0]->{no_auto} : '';
@@ -3458,6 +3491,7 @@ sub charge {
     $locationnum = $_[0]->{locationnum} || $self->ship_locationnum;
   } else {
     $amount     = shift;
+    $setup_cost = '';
     $quantity   = 1;
     $start_date = '';
     $pkg        = @_ ? shift : 'One-time charge';
@@ -3488,6 +3522,7 @@ sub charge {
     'setuptax'      => $setuptax,
     'taxclass'      => $taxclass,
     'taxproductnum' => $taxproduct,
+    'setup_cost'    => $setup_cost,
   } );
 
   my %options = ( ( map { ("additional_info$_" => $additional->[$_] ) }
@@ -3776,9 +3811,17 @@ Returns all the payments (see L<FS::cust_pay>) for this customer.
 
 sub cust_pay {
   my $self = shift;
-  return $self->num_cust_pay unless wantarray;
-  sort { $a->_date <=> $b->_date }
-    qsearch( 'cust_pay', { 'custnum' => $self->custnum } )
+  my $opt = ref($_[0]) ? shift : { @_ };
+
+  return $self->num_cust_pay unless wantarray || keys %$opt;
+
+  $opt->{'table'} = 'cust_pay';
+  $opt->{'hashref'}{'custnum'} = $self->custnum;
+
+  map { $_ } #behavior of sort undefined in scalar context
+    sort { $a->_date <=> $b->_date }
+      qsearch($opt);
+
 }
 
 =item num_cust_pay
@@ -3796,6 +3839,22 @@ sub num_cust_pay {
   $sth->fetchrow_arrayref->[0];
 }
 
+=item unapplied_cust_pay
+
+Returns all the unapplied payments (see L<FS::cust_pay>) for this customer.
+
+=cut
+
+sub unapplied_cust_pay {
+  my $self = shift;
+
+  $self->cust_pay(
+    'extra_sql' => ' AND '. FS::cust_pay->unapplied_sql. ' > 0',
+    #@_
+  );
+
+}
+
 =item cust_pay_pkgnum
 
 Returns all the payments (see L<FS::cust_pay>) for this customer's specific
@@ -4216,14 +4275,17 @@ sub cust_statuscolor {
   __PACKAGE__->statuscolors->{$self->cust_status};
 }
 
-=item tickets
+=item tickets [ STATUS ]
 
 Returns an array of hashes representing the customer's RT tickets.
 
+An optional status (or arrayref or hashref of statuses) may be specified.
+
 =cut
 
 sub tickets {
   my $self = shift;
+  my $status = ( @_ && $_[0] ) ? shift : '';
 
   my $num = $conf->config('cust_main-max_tickets') || 10;
   my @tickets = ();
@@ -4231,7 +4293,12 @@ sub tickets {
   if ( $conf->config('ticket_system') ) {
     unless ( $conf->config('ticket_system-custom_priority_field') ) {
 
-      @tickets = @{ FS::TicketSystem->customer_tickets($self->custnum, $num) };
+      @tickets = @{ FS::TicketSystem->customer_tickets( $self->custnum,
+                                                        $num,
+                                                        undef,
+                                                        $status,
+                                                      )
+                  };
 
     } else {
 
@@ -4243,6 +4310,7 @@ sub tickets {
           @{ FS::TicketSystem->customer_tickets( $self->custnum,
                                                  $num - scalar(@tickets),
                                                  $priority,
+                                                 $status,
                                                )
            };
       }
@@ -5053,6 +5121,9 @@ sub queued_bill {
   my $cust_main = qsearchs( 'cust_main', { custnum => $args{'custnum'} } );
   warn 'bill_and_collect custnum#'. $cust_main->custnum. "\n";#log custnum w/pid
 
+  #without this errors don't get rolled back
+  $args{'fatal'} = 1; # runs from job queue, will be caught
+
   $cust_main->bill_and_collect( %args );
 }