service dependencies: cust_svc_suspend_cascade, RT#33685
authorIvan Kohler <ivan@freeside.biz>
Thu, 30 Apr 2015 13:52:43 +0000 (06:52 -0700)
committerIvan Kohler <ivan@freeside.biz>
Thu, 30 Apr 2015 13:52:43 +0000 (06:52 -0700)
FS/FS/cust_pkg.pm
FS/FS/cust_svc.pm
FS/FS/part_svc_link.pm
httemplate/edit/part_svc_link.html

index 07e02f9..9352362 100644 (file)
@@ -1404,31 +1404,34 @@ sub suspend {
       }
     }
 
-    my @labels = ();
-
-    foreach my $cust_svc (
-      qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
-    ) {
-      my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $cust_svc->svcpart } );
-
-      $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
-        $dbh->rollback if $oldAutoCommit;
-        return "Illegal svcdb value in part_svc!";
-      };
-      my $svcdb = $1;
-      require "FS/$svcdb.pm";
-
-      my $svc = qsearchs( $svcdb, { 'svcnum' => $cust_svc->svcnum } );
-      if ($svc) {
-        $error = $svc->suspend;
-        if ( $error ) {
-          $dbh->rollback if $oldAutoCommit;
-          return $error;
-        }
-        my( $label, $value ) = $cust_svc->label;
-        push @labels, "$label: $value";
+    my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
+
+    #attempt ordering ala cust_svc_suspend_cascade (without infinite-looping
+    # on the circular dep case)
+    #  (this is too simple for multi-level deps, we need to use something
+    #   to resolve the DAG properly when possible)
+    my %svcpart = ();
+    $svcpart{$_->svcpart} = 0 foreach @cust_svc;
+    foreach my $svcpart ( keys %svcpart ) {
+      foreach my $part_pkg_link (
+        FS::part_svc_link->by_agentnum($self->cust_main->agentnum,
+                                         src_svcpart => $svcpart,
+                                         link_type => 'cust_svc_suspend_cascade'
+                                      )
+      ) {
+        $svcpart{$part_svc_link->dst_svcpart} = max(
+          $svcpart{$part_svc_link->dst_svcpart},
+          $svcpart{$part_svc_link->src_svcpart} + 1
+        );
       }
     }
+    @cust_svc = sort { $svcpart{ $a->svcpart } <=> $svcpart{ $b->svcpart } }
+                  @cust_svc;
+
+    my @labels = ();
+    foreach my $cust_svc ( @cust_svc ) {
+      $cust_svc->suspend( 'labels_arrayref' => \@labels );
+    }
 
     # suspension fees: if there is a feepart, and it's not an unsuspend fee,
     # and this is not a suspend-before-cancel
index 5922e32..0465580 100644 (file)
@@ -183,6 +183,37 @@ sub delete {
 
 }
 
+=item suspend
+
+Suspends the relevant service by calling the B<suspend> method of the associated
+FS::svc_XXX object (i.e. an FS::svc_acct object or FS::svc_domain object).
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub suspend {
+  my( $self, %opt ) = @_;
+
+  $self->part_svc->svcdb =~ /^([\w\-]+)$/ or return 'Illegal part_svc.svcdb';
+  my $svcdb = $1;
+  require "FS/$svcdb.pm";
+
+  my $svc = qsearchs( $svcdb, { 'svcnum' => $self->svcnum } )
+    or return '';
+
+  $error = $svc->suspend;
+  return $error if $error;
+
+  if ( $opt{labels_arryref} ) {
+    my( $label, $value ) = $self->label;
+    push @{ $opt{labels_arrayref} }, "$label: $value";
+  }
+
+  '';
+
+}
+
 =item cancel
 
 Cancels the relevant service by calling the B<cancel> method of the associated
index a7f1b0f..e8c2cc5 100644 (file)
@@ -88,7 +88,7 @@ unprovisioned
 
 =item cust_svc_suspend_cascade
 
-Suspend the destination service before the source service
+Suspend the destination service after the source service
 
 =back
 
@@ -205,7 +205,7 @@ sub description {
    and return "Automatically unprovision $dst when $src is unprovisioned";
 
   $l eq 'cust_svc_suspend_cascade'
-   and return "Suspend $dst before $src";
+   and return "Suspend $dst after $src";
 
   warn "WARNING: unknown part_svc_link.link_type $l\n";
   return "$src (unknown link_type $l) $dst";
index c8c385d..980b24e 100644 (file)
@@ -35,7 +35,7 @@ my @fields = (
       cust_svc_provision_restrict => 'Require the target service to be provisioned before the source service',
       cust_svc_unprovision_restrict => 'Require the target service to be unprovisioned before the source service',
       cust_svc_unprovision_cascade => 'Automatically unprovision the target service when the source service is unprovisioned',
-      cust_svc_suspend_cascade => 'Suspend the target service before the source service',
+      cust_svc_suspend_cascade => 'Suspend the target service after the source service',
     },
   },
   { field => 'disabled', type => 'checkbox', value => 'Y' }