start adding package locations, RT#4499
[freeside.git] / FS / FS / cust_pkg.pm
index fd8b73a..03cec75 100644 (file)
@@ -13,6 +13,7 @@ use FS::cust_main_Mixin;
 use FS::cust_svc;
 use FS::part_pkg;
 use FS::cust_main;
+use FS::cust_location;
 use FS::type_pkgs;
 use FS::pkg_svc;
 use FS::cust_bill_pkg;
@@ -104,38 +105,80 @@ inherits from FS::Record.  The following fields are currently supported:
 
 =over 4
 
-=item pkgnum - primary key (assigned automatically for new billing items)
+=item pkgnum
 
-=item custnum - Customer (see L<FS::cust_main>)
+Primary key (assigned automatically for new billing items)
 
-=item pkgpart - Billing item definition (see L<FS::part_pkg>)
+=item custnum
 
-=item setup - date
+Customer (see L<FS::cust_main>)
 
-=item bill - date (next bill date)
+=item pkgpart
+
+Billing item definition (see L<FS::part_pkg>)
+
+=item locationnum
+
+Optional link to package location (see L<FS::location>)
+
+=item setup
+
+date
+
+=item bill
+
+date (next bill date)
+
+=item last_bill
+
+last bill date
+
+=item adjourn
+
+date
+
+=item susp
 
-=item last_bill - last bill date
+date
 
-=item adjourn - date
+=item expire
+
+date
+
+=item cancel
+
+date
+
+=item otaker
+
+order taker (assigned automatically if null, see L<FS::UID>)
+
+=item manual_flag
+
+If this field is set to 1, disables the automatic
+unsuspension of this package when using the B<unsuspendauto> config option.
 
-=item susp - date
+=item quantity
+
+If not set, defaults to 1
+
+=item change_date
 
-=item expire - date
+Date of change from previous package
 
-=item cancel - date
+=item change_pkgnum
 
-=item otaker - order taker (assigned automatically if null, see L<FS::UID>)
+Previous pkgnum
 
-=item manual_flag - If this field is set to 1, disables the automatic
-unsuspension of this package when using the B<unsuspendauto> config file.
+=item change_pkgpart
 
-=item quantity - If not set, defaults to 1
+Previous pkgpart
 
 =back
 
-Note: setup, bill, adjourn, susp, expire and cancel are specified as UNIX timestamps;
-see L<perlfunc/"time">.  Also see L<Time::Local> and L<Date::Parse> for
-conversion functions.
+Note: setup, last_bill, bill, adjourn, susp, expire, cancel and change_date
+are specified as UNIX timestamps; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
 
 =head1 METHODS
 
@@ -223,42 +266,6 @@ sub insert {
   #}
 
   my $conf = new FS::Conf;
-  my $cust_main = $self->cust_main;
-  my $part_pkg = $self->part_pkg;
-  if ( $conf->exists('referral_credit')
-       && $cust_main->referral_custnum
-       && ! $options{'change'}
-       && $part_pkg->freq !~ /^0\D?$/
-     )
-  {
-    my $referring_cust_main = $cust_main->referring_cust_main;
-    if ( $referring_cust_main->status ne 'cancelled' ) {
-      my $error;
-      if ( $part_pkg->freq !~ /^\d+$/ ) {
-        warn 'WARNING: Not crediting customer '. $cust_main->referral_custnum.
-             ' for package '. $self->pkgnum.
-             ' ( customer '. $self->custnum. ')'.
-             ' - One-time referral credits not (yet) available for '.
-             ' packages with '. $part_pkg->freq_pretty. ' frequency';
-      } else {
-
-        my $amount = sprintf( "%.2f", $part_pkg->base_recur / $part_pkg->freq );
-        my $error =
-          $referring_cust_main->
-            credit( $amount,
-                    'Referral credit for '.$cust_main->name,
-                    'reason_type' => $conf->config('referral_credit_type')
-                  );
-        if ( $error ) {
-          $dbh->rollback if $oldAutoCommit;
-          return "Error crediting customer ". $cust_main->referral_custnum.
-               " for referral: $error";
-        }
-
-      }
-
-    }
-  }
 
   if ($conf->config('welcome_letter') && $self->cust_main->num_pkgs == 1) {
     my $queue = new FS::queue {
@@ -433,6 +440,7 @@ sub check {
     $self->ut_numbern('pkgnum')
     || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
     || $self->ut_numbern('pkgpart')
+    || $self->ut_foreign_keyn('locationnum', 'location', 'locationnum')
     || $self->ut_numbern('setup')
     || $self->ut_numbern('bill')
     || $self->ut_numbern('susp')
@@ -549,6 +557,7 @@ sub cancel {
   if ( $options{'reason'} ) {
     $error = $self->insert_reason( 'reason' => $options{'reason'},
                                    'action' => $date ? 'expire' : 'cancel',
+                                   'date'   => $date ? $date : $cancel_time,
                                    'reason_otaker' => $options{'reason_otaker'},
                                  );
     if ( $error ) {
@@ -746,9 +755,12 @@ sub suspend {
     return "Package $pkgnum expires before it would be suspended.";
   }
 
+  my $suspend_time = $options{'time'} || time;
+
   if ( $options{'reason'} ) {
     $error = $self->insert_reason( 'reason' => $options{'reason'},
                                    'action' => $date ? 'adjourn' : 'suspend',
+                                   'date'   => $date ? $date : $suspend_time,
                                    'reason_otaker' => $options{'reason_otaker'},
                                  );
     if ( $error ) {
@@ -758,6 +770,9 @@ sub suspend {
   }
 
   unless ( $date ) {
+
+    my @labels = ();
+
     foreach my $cust_svc (
       qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
     ) {
@@ -777,12 +792,43 @@ sub suspend {
           $dbh->rollback if $oldAutoCommit;
           return $error;
         }
+        my( $label, $value ) = $cust_svc->label;
+        push @labels, "$label: $value";
       }
     }
+
+    my $conf = new FS::Conf;
+    if ( $conf->config('suspend_email_admin') ) {
+      my $error = send_email(
+        'from'    => $conf->config('invoice_from'), #??? well as good as any
+        'to'      => $conf->config('suspend_email_admin'),
+        'subject' => 'FREESIDE NOTIFICATION: Customer package suspended',
+        'body'    => [
+          "This is an automatic message from your Freeside installation\n",
+          "informing you that the following customer package has been suspended:\n",
+          "\n",
+          'Customer: #'. $self->custnum. ' '. $self->cust_main->name. "\n",
+          'Package : #'. $self->pkgnum. " (". $self->part_pkg->pkg_comment. ")\n",
+          ( map { "Service : $_\n" } @labels ),
+        ],
+      );
+
+      if ( $error ) {
+        warn "WARNING: can't send suspension admin email (suspending anyway): ".
+             "$error\n";
+      }
+
+    }
+
   }
 
   my %hash = $self->hash;
-  $date ? ($hash{'adjourn'} = $date) : ($hash{'susp'} = time);
+  if ( $date ) {
+    $hash{'adjourn'} = $date;
+  } else {
+    $hash{'susp'} = $suspend_time;
+  }
   my $new = new FS::cust_pkg ( \%hash );
   $error = $new->replace( $self, options => { $self->options } );
   if ( $error ) {
@@ -1504,7 +1550,8 @@ sub h_labels_short {
     foreach $self->h_labels(@_);
   my @labels;
   foreach my $label ( keys %labels ) {
-    my @values = @{ $labels{$label} };
+    my %seen = ();
+    my @values = grep { ! $seen{$_}++ } @{ $labels{$label} };
     my $num = scalar(@values);
     if ( $num > $max_same_services ) {
       push @labels, "$label ($num)";
@@ -1528,6 +1575,30 @@ sub cust_main {
   qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
 }
 
+=item cust_location
+
+Returns the location object, if any (see L<FS::cust_location>).
+
+=cut
+
+sub cust_location {
+  my $self = shift;
+  return '' unless $self->locationnum;
+  qsearchs( 'cust_main', { 'locationnum' => $self->locationnum } );
+}
+
+=item cust_location_or_main
+
+If this package is associated with a location, returns the locaiton (see
+L<FS::cust_location>), otherwise returns the customer (see L<FS::cust_main>).
+
+=cut
+
+sub cust_location_or_main {
+  my $self = shift;
+  $self->cust_location || $self->cust_main;
+}
+
 =item seconds_since TIMESTAMP
 
 Returns the number of seconds all accounts (see L<FS::svc_acct>) in this