NENA2 E911 export and batch-oriented exports in general, #14049
[freeside.git] / FS / FS / svc_phone.pm
index 72c609c..4ca8d82 100644 (file)
@@ -1,19 +1,22 @@
 package FS::svc_phone;
+use base qw( FS::svc_Domain_Mixin FS::svc_PBX_Mixin 
+             FS::location_Mixin
+             FS::svc_Common
+           );
 
 use strict;
-use base qw( FS::svc_Domain_Mixin FS::location_Mixin FS::svc_Common );
 use vars qw( $DEBUG $me @pw_set $conf $phone_name_max
              $passwordmin $passwordmax
            );
 use Data::Dumper;
 use Scalar::Util qw( blessed );
 use List::Util qw( min );
+use Tie::IxHash;
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs dbh );
 use FS::PagedSearch qw( psearch );
 use FS::Msgcat qw(gettext);
 use FS::part_svc;
-use FS::phone_device;
 use FS::svc_pbx;
 use FS::svc_domain;
 use FS::cust_location;
@@ -125,6 +128,14 @@ Account number of other provider. See lnp_other_provider.
 See lnp_status. If lnp_status is portin-reject or portout-reject, this is an
 optional reject reason.
 
+=item e911_class
+
+Class of Service for E911 service (per the NENA 2.1 standard).
+
+=item e911_type
+
+Type of Service for E911 service.
+
 =back
 
 =head1 METHODS
@@ -222,6 +233,18 @@ sub table_info {
                        {       label => 'LNP Other Provider Account #', 
                                %dis2 
                        },
+        'e911_class' => {
+                                label => 'E911 Service Class',
+                                type  => 'select-e911_class',
+                                disable_inventory => 1,
+                                multiple => 1,
+                        },
+        'e911_type' => {
+                                label => 'E911 Service Type',
+                                type  => 'select-e911_type',
+                                disable_inventory => 1,
+                                multiple => 1,
+                        },
     },
   };
 }
@@ -239,7 +262,7 @@ Class method which returns an SQL fragment to search for the given string.
 sub search_sql {
   my( $class, $string ) = @_;
 
-  my $conf = new FS::Conf;
+  #my $conf = new FS::Conf;
 
   if ( $conf->exists('svc_phone-allow_alpha_phonenum') ) {
     $string =~ s/\W//g;
@@ -429,11 +452,20 @@ sub replace {
        );
 
   my $error = $new->SUPER::replace($old, %options);
+
+  # if this changed the e911 location, notify exports
+  if ($new->locationnum ne $old->locationnum) {
+    my $new_location = $new->cust_location_or_main;
+    my $old_location = $new->cust_location_or_main;
+    $error ||= $new->export('relocate', $new_location, $old_location);
+  }
+
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error if $error;
   }
 
+
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   ''; #no error
 }
@@ -464,7 +496,11 @@ and replace methods.
 sub check {
   my $self = shift;
 
-  my $conf = new FS::Conf;
+  #my $conf = new FS::Conf;
+
+  my $x = $self->setfixed;
+  return $x unless ref($x);
+  my $part_svc = $x;
 
   my $phonenum = $self->phonenum;
   my $phonenum_check_method;
@@ -546,14 +582,22 @@ sub check {
     return "SIP password must be shorter than $passwordmax characters"
       if length($self->sip_password) > $passwordmax;
 
-  } else { # option for this?
+  } elsif ( $part_svc->part_svc_column('sip_password')->columnflag ne 'F' ) {
 
+    # option for this?
     $self->sip_password(
       join('', map $pw_set[ int(rand $#pw_set) ], (1..min($passwordmax,16)) )
     );
 
   }
 
+  if ($self->e911_class and !exists(e911_classes()->{$self->e911_class})) {
+    return "undefined e911 class '".$self->e911_class."'";
+  }
+  if ($self->e911_type and !exists(e911_types()->{$self->e911_type})) {
+    return "undefined e911 type '".$self->e911_type."'";
+  }
+
   $self->SUPER::check;
 }
 
@@ -648,9 +692,15 @@ sub radius_check {
   my $self = shift;
   my %check = ();
 
-  my $conf = new FS::Conf;
+  #my $conf = new FS::Conf;
 
-  $check{'User-Password'} = $conf->config('svc_phone-radius-default_password');
+  my $password;
+  if ( $conf->config('svc_phone-radius-password') eq 'countrycode_phonenum' ) {
+    $password = $self->countrycode. $self->phonenum;
+  } else {
+    $password = $conf->config('svc_phone-radius-default_password');
+  }
+  $check{'User-Password'} = $password;
 
   %check;
 }
@@ -685,11 +735,6 @@ Returns any FS::phone_device records associated with this service.
 
 =cut
 
-sub phone_device {
-  my $self = shift;
-  qsearch('phone_device', { 'svcnum' => $self->svcnum } );
-}
-
 #override location_Mixin version cause we want to try the cust_pkg location
 #in between us and cust_main
 # XXX what to do in the unlinked case???  return a pseudo-object that returns
@@ -701,6 +746,26 @@ sub cust_location_or_main {
   $cust_pkg ? $cust_pkg->cust_location_or_main : '';
 }
 
+=item phone_name_or_cust
+
+Returns the C<phone_name> field if it has a value, or the package contact
+name if there is one, or the customer contact name.
+
+=cut
+
+sub phone_name_or_cust {
+  my $self = shift;
+  if ( $self->phone_name ) {
+    return $self->phone_name;
+  }
+  my $cust_pkg = $self->cust_svc->cust_pkg or return '';
+  if ( $cust_pkg->contactnum ) {
+    return $cust_pkg->contact->firstlast;
+  } else {
+    return $cust_pkg->cust_main->name_short;
+  }
+}
+
 =item psearch_cdrs OPTIONS
 
 Returns a paged search (L<FS::PagedSearch>) for Call Detail Records 
@@ -717,8 +782,8 @@ Accepts the following options:
 
 =item status => "" (or "processing-tiered", "done"): Return only CDRs with that processing status.
 
-=item inbound => 1: Return CDRs for inbound calls.  With "status", will filter 
-on inbound processing status.
+=item inbound => 1: Return CDRs for inbound calls (that is, those that match
+on 'dst').  With "status", will filter on inbound processing status.
 
 =item default_prefix => "XXX": Also accept the phone number of the service prepended 
 with the chosen prefix.
@@ -729,7 +794,9 @@ with the chosen prefix.
 
 =item calltypenum: Only return CDRs with this call type.
 
-=item disable_src => 1: Only match on "charged_party", not "src".
+=item disable_src => 1: Only match on 'charged_party', not 'src'.
+
+=item disable_charged_party => 1: Only match on 'src', not 'charged_party'.
 
 =item nonzero: Only return CDRs where duration > 0.
 
@@ -769,8 +836,8 @@ sub psearch_cdrs {
 
   } else {
 
-    @fields = ( 'charged_party' );
-    push @fields, 'src' if !$options{'disable_src'};
+    push @fields, 'charged_party' unless $options{'disable_charged_party'};
+    push @fields, 'src' unless $options{'disable_src'};
     $hash{'freesidestatus'} = $options{'status'}
       if exists($options{'status'});
   }
@@ -859,6 +926,67 @@ sub sum_cdrs {
 
 =back
 
+=head1 CLASS METHODS
+
+=over 4
+
+=item e911_classes
+
+Returns a hashref of allowed values and descriptions for the C<e911_class>
+field.
+
+=item e911_types
+
+Returns a hashref of allowed values and descriptions for the C<e911_type>
+field.
+
+=cut
+
+sub e911_classes {
+  tie my %x, 'Tie::IxHash', (
+    1 => 'Residence',
+    2 => 'Business',
+    3 => 'Residence PBX',
+    4 => 'Business PBX',
+    5 => 'Centrex',
+    6 => 'Coin 1 Way out',
+    7 => 'Coin 2 Way',
+    8 => 'Mobile',
+    9 => 'Residence OPX',
+    0 => 'Business OPX',
+    A => 'Customer Operated Coin Telephone',
+    #B => not available
+    G => 'Wireless Phase I',
+    H => 'Wireless Phase II',
+    I => 'Wireless Phase II with Phase I information',
+    V => 'VoIP Services Default',
+    C => 'VoIP Residence',
+    D => 'VoIP Business',
+    E => 'VoIP Coin/Pay Phone',
+    F => 'VoIP Wireless',
+    J => 'VoIP Nomadic',
+    K => 'VoIP Enterprise Services',
+    T => 'Telematics',
+  );
+  \%x;
+}
+
+sub e911_types {
+  tie my %x, 'Tie::IxHash', (
+    0 => 'Not FX nor Non-Published',
+    1 => 'FX in 911 serving area',
+    2 => 'FX outside 911 serving area',
+    3 => 'Non-Published',
+    4 => 'Non-Published FX in serving area',
+    5 => 'Non-Published FX outside serving area',
+    6 => 'Local Ported Number',
+    7 => 'Interim Ported Number',
+  );
+  \%x;
+}
+
+=back
+
 =head1 BUGS
 
 =head1 SEE ALSO