summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Mason.pm1
-rw-r--r--FS/FS/Schema.pm25
-rw-r--r--FS/FS/part_svc_link.pm249
-rw-r--r--FS/MANIFEST5
-rw-r--r--FS/t/part_svc_link.t5
-rw-r--r--httemplate/browse/part_svc_link.html32
-rw-r--r--httemplate/edit/elements/export_svc.html6
-rw-r--r--httemplate/edit/elements/part_svc_column.html7
-rwxr-xr-xhttemplate/edit/part_svc.cgi5
-rw-r--r--httemplate/edit/part_svc_link.html51
-rw-r--r--httemplate/edit/process/part_svc_link.html5
-rw-r--r--httemplate/elements/menu.html1
12 files changed, 380 insertions, 12 deletions
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index d9b91887e..78779d78a 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -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 ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 42122f700..114acb8fd 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -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
index 000000000..cf82a90db
--- /dev/null
+++ b/FS/FS/part_svc_link.pm
@@ -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;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 575184ced..422f69c0e 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -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
index 000000000..3cac9eff2
--- /dev/null
+++ b/FS/t/part_svc_link.t
@@ -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
index 000000000..d31acc646
--- /dev/null
+++ b/httemplate/browse/part_svc_link.html
@@ -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>
diff --git a/httemplate/edit/elements/export_svc.html b/httemplate/edit/elements/export_svc.html
index 5962ae7f8..1735148a4 100644
--- a/httemplate/edit/elements/export_svc.html
+++ b/httemplate/edit/elements/export_svc.html
@@ -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>
diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html
index a6ccaf867..23a6deb56 100644
--- a/httemplate/edit/elements/part_svc_column.html
+++ b/httemplate/edit/elements/part_svc_column.html
@@ -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>
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index 7a47f1550..a07fc6005 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -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
index 000000000..64a99d6f4
--- /dev/null
+++ b/httemplate/edit/part_svc_link.html
@@ -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
index 000000000..ffe979d29
--- /dev/null
+++ b/httemplate/edit/process/part_svc_link.html
@@ -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>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 0aefcd77a..e152cb690 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -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, '' ]