delete fees, RT#81713
[freeside.git] / FS / FS / part_export.pm
index 8e10ea7..1a8f43d 100644 (file)
@@ -10,6 +10,7 @@ use FS::part_svc;
 use FS::part_export_option;
 use FS::part_export_machine;
 use FS::svc_export_machine;
+use FS::export_cust_svc;
 
 #for export modules, though they should probably just use it themselves
 use FS::queue;
@@ -58,6 +59,12 @@ fields are currently supported:
 
 =item nodomain - blank or "Y" : usernames are exported to this service with no domain
 
+=item default_machine - For exports that require a machine to be selected for
+each service (see L<FS::svc_export_machine>), the one to use as the default.
+
+=item no_suspend - Don't export service suspensions. In the future there may
+be "no_*" options for the other service actions.
+
 =back
 
 =head1 METHODS
@@ -156,11 +163,26 @@ sub delete {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
+  # delete associated export_cust_svc
+  foreach my $export_cust_svc ( 
+    qsearch('export_cust_svc',{ 'exportnum' => $self->exportnum })
+  ) {
+    my $error = $export_cust_svc->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
   # clean up export_nas records
   my $error = $self->process_m2m(
     'link_table'    => 'export_nas',
     'target_table'  => 'nas',
     'params'        => [],
+  ) || $self->process_m2m(
+    'link_table'    => 'export_svc',
+    'target_table'  => 'part_svc',
+    'params'        => [],
   ) || $self->SUPER::delete;
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
@@ -333,6 +355,7 @@ sub check {
     || $self->ut_textn('exportname')
     || $self->ut_domainn('machine')
     || $self->ut_alpha('exporttype')
+    || $self->ut_flag('no_suspend')
   ;
 
   if ( $self->machine eq '_SVC_MACHINE' ) {
@@ -531,15 +554,19 @@ sub default_export_machine {
   die "no default export hostname for export ".$self->exportnum;
 }
 
-#these should probably all go away, just let the subclasses define em
-
 =item export_insert SVC_OBJECT
 
 =cut
 
+# Do not overload!  Overload _export_insert instead
+
 sub export_insert {
   my $self = shift;
   #$self->rebless;
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp "export_insert() suppressed by noexport_hack" if $DEBUG;
+    return;
+  }
   $self->_export_insert(@_);
 }
 
@@ -556,9 +583,15 @@ sub export_insert {
 
 =cut
 
+# Do not overload!  Overload _export_replace instead
+
 sub export_replace {
   my $self = shift;
   #$self->rebless;
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp "export_replace() suppressed by noexport_hack" if $DEBUG;
+    return;
+  }
   $self->_export_replace(@_);
 }
 
@@ -566,9 +599,15 @@ sub export_replace {
 
 =cut
 
+# Do not overload!  Overload _export_delete instead
+
 sub export_delete {
   my $self = shift;
   #$self->rebless;
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp "export_delete() suppressed by noexport_hack" if $DEBUG;
+    return;
+  }
   $self->_export_delete(@_);
 }
 
@@ -576,9 +615,15 @@ sub export_delete {
 
 =cut
 
+# Do not overload!  Overload _export_suspend instead
+
 sub export_suspend {
   my $self = shift;
   #$self->rebless;
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp "export_suspend() suppressed by noexport_hack" if $DEBUG;
+    return;
+  }
   $self->_export_suspend(@_);
 }
 
@@ -586,9 +631,15 @@ sub export_suspend {
 
 =cut
 
+# Do not overload!  Overload _export_unsuspend instead
+
 sub export_unsuspend {
   my $self = shift;
   #$self->rebless;
+  if ( $FS::svc_Common::noexport_hack ) {
+    carp "export_unsuspend() suppressed by noexport_hack" if $DEBUG;
+    return;
+  }
   $self->_export_unsuspend(@_);
 }
 
@@ -626,6 +677,81 @@ sub _export_unsuspend {
   $self->_export_replace( $svc_x, $old );
 }
 
+=item get_remoteid SVC
+
+Returns the remote id for this export for the given service.
+
+=cut
+
+sub get_remoteid {
+  my ($self, $svc_x) = @_;
+
+  my $export_cust_svc = qsearchs('export_cust_svc',{
+    'exportnum' => $self->exportnum,
+    'svcnum' => $svc_x->svcnum
+  });
+
+  return $export_cust_svc ? $export_cust_svc->remoteid : '';
+}
+
+=item set_remoteid SVC VALUE
+
+Sets the remote id for this export for the given service.
+See L<FS::export_cust_svc>.
+
+If value is true, inserts or updates export_cust_svc record.
+If value is false, deletes any existing record.
+
+Returns error message, blank on success.
+
+=cut
+
+sub set_remoteid {
+  my ($self, $svc_x, $value) = @_;
+
+  my $export_cust_svc = qsearchs('export_cust_svc',{
+    'exportnum' => $self->exportnum,
+    'svcnum' => $svc_x->svcnum
+  });
+
+  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 $error = '';
+  if ($value) {
+    if ($export_cust_svc) {
+      $export_cust_svc->set('remoteid',$value);
+      $error = $export_cust_svc->replace;
+    } else {
+      $export_cust_svc = new FS::export_cust_svc {
+        'exportnum' => $self->exportnum,
+        'svcnum' => $svc_x->svcnum,
+        'remoteid' => $value
+      };
+      $error = $export_cust_svc->insert;
+    }
+  } else {
+    if ($export_cust_svc) {
+      $error = $export_cust_svc->delete;
+    } #otherwise, it already doesn't exist
+  }
+
+  if ($oldAutoCommit) {
+    $dbh->rollback if $error;
+    $dbh->commit unless $error;
+  }
+
+  return $error;  
+}
+
 =item export_links SVC_OBJECT ARRAYREF
 
 Adds a list of web elements to ARRAYREF specific to this export and SVC_OBJECT.
@@ -679,6 +805,39 @@ sub info {
   };
 }
 
+=item get_dids SELECTION
+
+Does several things, which is unfortunate. DID phone numbers are organized
+in a sort-of hierarchy: state, areacode, exchange, number. Or, for some 
+vendors: state, region, number. But not always that, either.
+
+SELECTION is one or more field/value pairs specifying parts of the hierarchy
+that have already been selected.  C<get_dids> will then return an arrayref of
+the possible values for the next selection level. Note that these are not
+actual DIDs except at the lowest level.
+
+Generally, 'state' alone will return an array of area codes or region names
+in the state.
+
+'state' and 'areacode' together will return an array of either:
+- exchange strings of the form "New York (212-555-XXXX)"
+- ratecenter names of the form "New York, NY"
+
+These strings are sent back to the UI and offered as options so that the user
+can choose the local calling area they like.
+
+'areacode' and 'exchange', or 'state' and 'ratecenter', or 'region' by itself
+will return an array of actual DID numbers.
+
+Passing 'tollfree' with a true value will override the whole hierarchy and
+return an array of tollfree numbers.
+
+C<get_dids> methods should report errors via die().
+
+=cut
+
+# no stub; can('get_dids') should return false by default
+
 #default fallbacks... FS::part_export::DID_Common ?
 sub can_get_dids { 0; }
 sub get_dids_can_tollfree { 0; }
@@ -688,6 +847,79 @@ sub get_dids_can_edit     { 0; } #don't use without can_manual, otherwise the
                                  # inventory each edit
 sub get_dids_npa_select   { 1; }
 
+# get_dids_npa_select: if true, then prompt to select state, then area code,
+# then city/exchange, then phone number.
+# if false, then prompt to select state (actually province), then "region",
+# then phone number.
+#
+# get_dids_can_manual: if true, then there will be a radio button to enter
+# a phone number manually.
+#
+# get_dids_can_tollfree: if true, then the user will be prompted to choose
+# both a regular and a toll-free number. The export can have a 
+# 'restrict_selection' option to enable only one or the other of those. See
+# part_export/vitelity.pm for an example.
+#
+# get_dids_can_edit: if true, then the user can use the selector again to
+# change the phone number for a service. if false, then they can't (have to
+# reprovision completely).
+
+=item svc_role SVC
+
+Returns the role that SVC occupies with respect to this export, if any.
+This is part of the part_svc's export configuration.
+
+=cut
+
+sub svc_role {
+  my $self = shift;
+  my $svc_x = shift;
+  my $cust_svc = $svc_x->cust_svc or return '';
+  my $export_svc = qsearchs('export_svc', { exportnum => $self->exportnum,
+                                            svcpart   => $cust_svc->svcpart })
+                   or return '';
+  $export_svc->role;
+} 
+
+=item svc_with_role { SVC | PKGNUM }, ROLE
+
+Given a svc_* object SVC or pkgnum PKG, and a role name ROLE, finds the
+service(s) in the same package that are linked to this export with ROLE.
+
+=cut
+
+sub svc_with_role {
+  my $self = shift;
+  my $svc_or_pkgnum = shift;
+  my $role = shift; 
+  my $pkgnum;
+  if ( ref $svc_or_pkgnum ) {
+    $pkgnum = $svc_or_pkgnum->cust_svc->pkgnum or return '';
+  } else {
+    $pkgnum = $svc_or_pkgnum;
+  }
+  my $role_info = $self->info->{roles}->{$role}
+    or die "role '$role' does not exist for export '".$self->exporttype."'\n";
+  my $svcdb = $role_info->{svcdb};
+
+  my @svcs = qsearch({
+    'table'     =>  $svcdb,
+    'addl_from' =>  ' JOIN cust_svc USING (svcnum)' .
+                    ' JOIN export_svc USING (svcpart)',
+    'extra_sql' =>  " WHERE cust_svc.pkgnum = $pkgnum" .
+                    " AND export_svc.exportnum = ".$self->exportnum .
+                    " AND export_svc.role = '$role'",
+  });               
+  if ( $role_info->{multiple} ) {
+    return @svcs;
+  } else {
+    if ( @svcs > 1 ) {
+      warn "multiple $role services in pkgnum $pkgnum; returning the first one.\n";
+    }
+    return $svcs[0];
+  }
+}
+
 =back
 
 =head1 SUBROUTINES