service dependencies: UI, RT#33685
authorIvan Kohler <ivan@freeside.biz>
Thu, 30 Apr 2015 09:34:51 +0000 (02:34 -0700)
committerIvan Kohler <ivan@freeside.biz>
Thu, 30 Apr 2015 09:34:51 +0000 (02:34 -0700)
12 files changed:
FS/FS/Mason.pm
FS/FS/Schema.pm
FS/FS/part_svc_link.pm [new file with mode: 0644]
FS/MANIFEST
FS/t/part_svc_link.t [new file with mode: 0644]
httemplate/browse/part_svc_link.html [new file with mode: 0644]
httemplate/edit/elements/export_svc.html
httemplate/edit/elements/part_svc_column.html
httemplate/edit/part_svc.cgi
httemplate/edit/part_svc_link.html [new file with mode: 0644]
httemplate/edit/process/part_svc_link.html [new file with mode: 0644]
httemplate/elements/menu.html

index d9b9188..78779d7 100644 (file)
@@ -401,6 +401,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::legacy_cust_history;
   use FS::quotation_pkg_tax;
   use FS::cust_pkg_reason_fee;
+  use FS::part_svc_link;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
index 42122f7..114acb8 100644 (file)
@@ -3618,6 +3618,31 @@ sub tables_hashref {
       'index' => [ ['disabled'] ],
     },
 
+    'part_svc_link' => {
+      'columns' => [
+        'svclinknum',  'serial',   '',      '', '', '',
+        #'linkname',    'varchar', 'NULL', $char_d, '', '',
+        'agentnum',    'int',     'NULL', '', '', '', 
+        'src_svcpart', 'int',      '',      '', '', '',
+        'dst_svcpart', 'int',      '',      '', '', '', 
+        'link_type',   'varchar',  '', $char_d, '', '',
+        'disabled',    'char', 'NULL',   1, '', '', 
+      ],
+      'primary_key'  => 'svclinknum',
+      'unique'       => [ ['agentnum','src_svcpart','dst_svcpart','link_type'] ],
+      'index'        => [ [ 'src_svcpart' ] ],
+      'foreign_keys' => [
+                          { columns    => [ 'src_svcpart' ],
+                            table      => 'part_svc',
+                            references => [ 'svcpart' ]
+                          },
+                          { columns    => [ 'dst_svcpart' ],
+                            table      => 'part_svc',
+                            references => [ 'svcpart' ]
+                          },
+                        ],
+    },
+
     #(this should be renamed to part_pop)
     'svc_acct_pop' => {
       'columns' => [
diff --git a/FS/FS/part_svc_link.pm b/FS/FS/part_svc_link.pm
new file mode 100644 (file)
index 0000000..cf82a90
--- /dev/null
@@ -0,0 +1,249 @@
+package FS::part_svc_link;
+use base qw( FS::Record );
+
+use strict;
+use FS::Record qw( qsearchs ); # qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::part_svc_link - Object methods for part_svc_link records
+
+=head1 SYNOPSIS
+
+  use FS::part_svc_link;
+
+  $record = new FS::part_svc_link \%hash;
+  $record = new FS::part_svc_link { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_svc_link object represents an service definition dependency.
+FS::part_svc_link inherits from FS::Record.  The following fields are currently
+supported:
+
+=over 4
+
+=item svclinknum
+
+primary key
+
+=cut
+
+#=item linkname
+#
+#Dependency name
+
+=item agentnum
+
+Empty for global dependencies, or agentnum (see L<FS::agent>) for
+agent-specific dependencies
+
+=item src_svcpart
+
+Source service definition (see L<FS::part_svc>)
+
+=item dst_svcpart
+
+Destination service definition (see L<FS::part_svc>)
+
+=item link_type
+
+Link type:
+
+=over 4
+
+=cut
+
+# XXX false laziness w/edit/part_svc_link.html
+
+=item part_svc_restrict
+
+In package defintions, require the destination service definition when the
+source service definition is included
+
+=item part_svc_restrict_soft
+
+Soft order block: in package definitions, warn if the destination service
+definition is included without the source service definition
+
+=item cust_svc_provision_restrict
+
+Require the destination service to be provisioned before the source service
+
+=item cust_svc_unprovision_restrict
+
+Require the destination service to be unprovisioned before the source service
+
+=item cust_svc_unprovision_cascade
+
+Automatically unprovision the destination service when the source service is
+unprovisioned
+
+=item cust_svc_suspend_cascade
+
+Suspend the destination service before the source service
+
+=back
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'part_svc_link'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('svclinknum')
+    #|| $self->ut_textn('linkname')
+    || $self->ut_number('src_svcpart')
+    || $self->ut_number('dst_svcpart')
+    || $self->ut_text('link_type')
+    || $self->ut_enum('disabled', [ '', 'Y' ])
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item description
+
+Returns an extended description of this dependency, including.  Exact wording
+depends on I<link_type>.
+
+=cut
+
+sub description {
+  my $self = shift;
+
+  my $src = $self->src_part_svc->svc;
+  my $dst = $self->dst_part_svc->svc;
+
+  #maybe sub-classes with overrides at some point
+  #  (and hooks each place we have manual checks for the various rules)
+  # but this will do for now
+
+  $self->link_type eq 'part_svc_restrict'
+   and return "In package definitions, $dst is required when $src is included";
+
+  $self->link_type eq 'part_svc_restrict_soft'
+   and return "In package definitions, $dst is suggested when $src is included";
+
+  $self->link_type eq 'cust_svc_provision_restrict'
+   and return "Require $dst provisioning before $src";
+
+  $self->link_type eq 'cust_svc_unprovision_restrict'
+   and return "Require $dst unprovisioning before $src";
+
+  $self->link_type eq 'cust_svc_unprovision_cascade'
+   and return "Automatically unprovision $dst when $src is unprovisioned";
+
+  $self->link_type eq 'cust_svc_suspend_cascade'
+   and return "Suspend $dst before $src";
+
+  warn "WARNING: unknown part_svc_link.link_type ". $self->link_type. "\n";
+  return "$src (unknown link_type ". $self->link_type. ") $dst";
+
+}
+
+=item src_part_svc 
+
+Returns the source service definition, as an FS::part_svc object (see
+L<FS::part_svc>).
+
+=cut
+
+sub src_part_svc {
+  my $self = shift;
+  qsearchs('part_svc', { svcpart=>$self->src_svcpart } );
+}
+
+=item src_svc
+
+Returns the source service definition name (part_svc.svc).
+
+=cut
+
+sub src_svc {
+  shift->src_part_svc->svc;
+}
+
+=item dst_part_svc
+
+Returns the destination service definition, as an FS::part_svc object (see
+L<FS::part_svc>).
+
+=cut
+
+
+sub dst_part_svc {
+  my $self = shift;
+  qsearchs('part_svc', { svcpart=>$self->dst_svcpart } );
+}
+
+=item dst_svc
+
+Returns the destination service definition name (part_svc.svc).
+
+=cut
+
+sub dst_svc {
+  shift->src_part_svc->svc;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_svc>, L<FS::Record>
+
+=cut
+
+1;
+
index 575184c..422f69c 100644 (file)
@@ -794,7 +794,7 @@ t/cust_bill_pkg_fee.t
 FS/part_fee_msgcat.pm
 t/part_fee_msgcat.t
 FS/part_fee_usage.pm
-FS/part_fee_usage.t
+t/part_fee_usage.t
 FS/sched_item.pm
 t/sched_item.t
 FS/sched_avail.pm
@@ -841,7 +841,8 @@ t/legacy_cust_history.t
 FS/quotation_pkg_tax.pm
 t/quotation_pkg_tax.t
 FS/h_svc_circuit.pm
-FS/h_svc_circuit.t
 FS/FeeOrigin_Mixin.pm
 FS/cust_pkg_reason_fee.pm
 t/cust_pkg_reason_fee.t
+FS/part_svc_link.pm
+t/part_svc_link.t
diff --git a/FS/t/part_svc_link.t b/FS/t/part_svc_link.t
new file mode 100644 (file)
index 0000000..3cac9ef
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::part_svc_link;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/part_svc_link.html b/httemplate/browse/part_svc_link.html
new file mode 100644 (file)
index 0000000..d31acc6
--- /dev/null
@@ -0,0 +1,32 @@
+<& elements/browse.html,
+     'title'              => 'Service dependencies',
+     'name_singular'      => 'dependency',
+     'menubar'            => [ 'Add a new service dependency' =>
+                                 $p.'edit/part_svc_link.html',
+                             ],
+     'query'              => { 'table'    => 'part_svc_link',
+                               'order_by' => 'ORDER BY src_svcpart',
+                             },
+     'count_query'        => 'SELECT COUNT(*) FROM part_svc_link',
+     'header'             => [ 'Source', 'Dependency', ],
+     'fields'             => [ 'src_svc', 'description', ],
+     'sort_fields'        => [],
+     'links'              => [ $svc_link, $link, ],
+     'disableable'        => 1,
+     'disabled_statuspos' => 1,
+     'agent_virt'         => 1,
+     'agent_null'         => 1,
+     'agent_null_right'   => 'Configuration',
+     #agent_null_right_link'
+     'agent_pos'          => 0,
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $svc_link = [ "${p}edit/part_svc.cgi?", 'src_svcpart' ];
+
+my $link = [ "${p}edit/part_svc_link.html?", 'svclinknum' ];
+
+</%init>
index 5962ae7..1735148 100644 (file)
@@ -39,8 +39,8 @@ function toggle_selectrole() {
   }
 </&>
 </script>
-<& /elements/table.html &>
-  <TR><TH COLSPAN=<% $columns %>>Exports</TH></TR>
+<FONT CLASS="fsinnerbox-title">Exports</FONT>
+<TABLE CLASS="fsinnerbox">
   <TR>
 % # exports
 % foreach my $part_export (@part_export) {
@@ -81,4 +81,4 @@ function toggle_selectrole() {
 %   }
 % }
   </TR>
-</TABLE><BR><BR>
+</TABLE><BR>
index a6ccaf8..23a6deb 100644 (file)
@@ -63,15 +63,14 @@ my %communigate_fields = (
 );
 </%once>
 <INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $svcdb %>">
-<BR><BR>
+<BR>
 %# include export selection
 <& export_svc.html,
   part_svc => $part_svc,
   svcdb => $svcdb
 &>
-For the selected table, you can give fields default or fixed (unchangeable)
-values, or select an inventory class to manually or automatically fill in 
-that field.
+
+<FONT CLASS="fsinnerbox-title">Fields</FONT>
 <& /elements/table-grid.html, cellpadding => 4 &>
   <TR>
     <TH BGCOLOR="#cccccc">Field</TH>
index 7a47f15..a07fc60 100755 (executable)
@@ -187,10 +187,9 @@ window.onload = function() {
 
 <BR>
 
-<BR>
-Table <% $widget->html %>
+<FONT SIZE="+1"><B>Table</B></FONT> <% $widget->html %>
 
-<% include('/elements/footer.html') %>
+<& /elements/footer.html &>
 
 <%init>
 
diff --git a/httemplate/edit/part_svc_link.html b/httemplate/edit/part_svc_link.html
new file mode 100644 (file)
index 0000000..64a99d6
--- /dev/null
@@ -0,0 +1,51 @@
+<& elements/edit.html,
+  'table'         => 'part_svc_link',
+  'name_singular' => 'dependency',
+  'labels'        => \%labels,
+  'fields'        => \@fields,
+&>
+<%init>
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+  { field       => 'agentnum',
+    type        => 'select-agent',
+    empty_label => '(global)',
+  },
+  { field       => 'src_svcpart',
+    type        => 'select-part_svc',
+    empty_label => 'Select service definition',
+  },
+  { field       => 'dst_svcpart',
+    type        => 'select-part_svc',
+    empty_label => 'Select service definition',
+  },
+  { field       => 'link_type',
+    type        => 'select',
+    #XXX false laziness w/part_svc_link POD documentation
+    options     =>[ qw(
+      part_svc_restrict part_svc_restrict_soft
+      cust_svc_provision_restrict cust_svc_unprovision_restrict
+      cust_svc_unprovision_cascade cust_svc_suspend_cascade
+    )],
+    labels      => {
+      part_svc_restrict => 'In package defintions, prevent including the destination service definition unless the source service definition is also included',
+      part_svc_restrict_soft => 'Soft order block: in package definitions, warn if the destination service definition is included without the source service definition',
+      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',
+    },
+  },
+  { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+my %labels = (
+  'svclinknum ' => '',
+  'agentnum'    => 'Agent',
+  'src_svcpart' => 'Source service',
+  'dst_svcpart' => 'Destination service',
+  'link_type'   => 'Dependency type',
+  'disabled'    => 'Disabled'
+);
+</%init>
diff --git a/httemplate/edit/process/part_svc_link.html b/httemplate/edit/process/part_svc_link.html
new file mode 100644 (file)
index 0000000..ffe979d
--- /dev/null
@@ -0,0 +1,5 @@
+<& elements/process.html, table=>'part_svc_link', viewall_dir=>'browse' &>
+<%init>
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+</%init>
index 0aefcd7..e152cb6 100644 (file)
@@ -577,6 +577,7 @@ tie my %config_export_svc, 'Tie::IxHash', ();
 if ( $curuser->access_right('Configuration') ) {
   $config_export_svc{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
   $config_export_svc{'Service classes'} = [ $fsurl.'browse/part_svc_class.html', 'Services classes are user-defined, informational types for services' ];
+  $config_export_svc{'Service dependencies'} = [ $fsurl.'browse/part_svc_link.html', 'Services depencies define rules between service definitions' ];
   $config_export_svc{'Provisioning exports'} = [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ];
 }
 $config_export_svc{'Dialup'}  = [ \%config_dialup, ''    ]