RT#34289: Flag service fields as mandatory
authorJonathan Prykop <jonathan@freeside.biz>
Sat, 25 Apr 2015 03:19:34 +0000 (22:19 -0500)
committerJonathan Prykop <jonathan@freeside.biz>
Sat, 25 Apr 2015 03:19:34 +0000 (22:19 -0500)
FS/FS/Schema.pm
FS/FS/part_svc.pm
FS/FS/part_svc_column.pm
FS/FS/svc_Common.pm
FS/FS/svc_acct.pm
FS/FS/svc_domain.pm
httemplate/browse/part_svc.cgi
httemplate/edit/elements/part_svc_column.html
httemplate/edit/part_svc.cgi

index 7f28e11..42122f7 100644 (file)
@@ -3595,6 +3595,7 @@ sub tables_hashref {
         'columnlabel', 'varchar', 'NULL', $char_d, '', '',
         'columnvalue', 'varchar', 'NULL',     512, '', '', 
         'columnflag',  'char',    'NULL',       1, '', '', 
+        'required',    'char',    'NULL',       1, '', '', 
       ],
       'primary_key'  => 'columnnum',
       'unique'       => [ [ 'svcpart', 'columnname' ] ],
index f56878a..1da30cb 100644 (file)
@@ -95,8 +95,12 @@ the part_svc_column table appropriately (see L<FS::part_svc_column>).
 
 =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
 
+=item I<svcdb>__I<field>_label
+
 =item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null or empty (no default), `D' for default, `F' for fixed (unchangeable), , `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory.  For virtual fields, can also be 'X' for excluded.
 
+=item I<svcdb>__I<field>_required - I<field> should always have a true value
+
 =back
 
 If you want to add part_svc_column records for fields that do not exist as
@@ -145,6 +149,7 @@ sub insert {
   foreach my $field (
     grep { $_ ne 'svcnum'
            && ( defined( $self->getfield($svcdb.'__'.$_.'_flag') )
+                || defined($self->getfield($svcdb.'__'.$_.'_required'))
                 || $self->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
          } (fields($svcdb), @fields)
   ) {
@@ -156,6 +161,7 @@ sub insert {
 
     my $flag  = $self->getfield($svcdb.'__'.$field.'_flag');
     my $label = $self->getfield($svcdb.'__'.$field.'_label');
+    my $required = $self->getfield($svcdb.'__'.$field.'_required') ? 'Y' : '';
     if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
 
       if ( uc($flag) =~ /^([A-Z])$/ ) {
@@ -170,6 +176,8 @@ sub insert {
       $part_svc_column->setfield('columnlabel', $label)
         if $label !~ /^\s*$/;
 
+      $part_svc_column->setfield('required', $required);
+
       if ( $previous ) {
         $error = $part_svc_column->replace($previous);
       } else {
@@ -279,6 +287,7 @@ sub replace {
     foreach my $field (
       grep { $_ ne 'svcnum'
              && ( defined( $new->getfield($svcdb.'__'.$_.'_flag') )
+                  || defined($new->getfield($svcdb.'__'.$_.'_required'))
                   || $new->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ )
            } (fields($svcdb),@fields)
     ) {
@@ -291,6 +300,7 @@ sub replace {
 
       my $flag  = $new->getfield($svcdb.'__'.$field.'_flag');
       my $label = $new->getfield($svcdb.'__'.$field.'_label');
+      my $required = $new->getfield($svcdb.'__'.$field.'_required') ? 'Y' : '';
  
       if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) {
 
@@ -309,6 +319,8 @@ sub replace {
         $part_svc_column->setfield('columnlabel', $label)
           if $label !~ /^\s*$/;
 
+        $part_svc_column->setfield('required', $required);
+
         if ( $previous ) {
           $error = $part_svc_column->replace($previous);
         } else {
@@ -699,6 +711,8 @@ some components specified by "select-.*.html", and a bunch more...
 
 =item select_allow_empty - Used with select_table, adds an empty option
 
+=item required - This field should always have a true value (do not use with type checkbox or disabled)
+
 =back
 
 =cut
@@ -773,7 +787,7 @@ sub process {
                          and ref($param->{ $f }) ) {
                       $param->{ $f } = join(',', @{ $param->{ $f } });
                    }
-                    ( $f, $f.'_flag', $f.'_label' );
+                    ( $f, $f.'_flag', $f.'_label', $f.'_required' );
                   }
                   @fields;
 
index 38ce1fa..75a2dfb 100644 (file)
@@ -45,6 +45,8 @@ fields are currently supported:
 
 =item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, `A' for automatic selection from inventory, or `H' for selection from a hardware class.  For virtual fields, can also be 'X' for excluded.
 
+=item required - column value expected to be true
+
 =back
 
 =head1 METHODS
@@ -91,6 +93,7 @@ sub check {
     || $self->ut_alpha('columnname')
     || $self->ut_textn('columnlabel')
     || $self->ut_anything('columnvalue')
+    || $self->ut_flag('required')
   ;
   return $error if $error;
 
index 8199ba1..b1f9d14 100644 (file)
@@ -152,13 +152,46 @@ sub cust_linked {
 
 Checks the validity of fields in this record.
 
-At present, this does nothing but call FS::Record::check (which, in turn, 
-does nothing but run virtual field checks).
+Only checks fields marked as required in table_info or 
+part_svc_column definition.  Should be invoked by service-specific
+check using SUPER.  Invokes FS::Record::check using SUPER.
 
 =cut
 
 sub check {
   my $self = shift;
+
+  ## Checking required fields
+
+  # get fields marked as required in table_info
+  my $required = {};
+  my $labels = {};
+  my $tinfo = $self->can('table_info') ? $self->table_info : {};
+  my $fields = $tinfo->{'fields'} || {};
+  foreach my $field (keys %$fields) {
+    if (ref($fields->{$field}) && $fields->{$field}->{'required'}) {
+      $required->{$field} = 1;
+      $labels->{$field} = $fields->{$field}->{'label'};
+    }
+  }
+  # add fields marked as required in database
+  foreach my $column (
+    qsearch('part_svc_column',{
+      'svcpart' => $self->svcpart,
+      'required' => 'Y'
+    })
+  ) {
+    $required->{$column->columnname} = 1;
+    $labels->{$column->columnname} = $column->columnlabel;
+  }
+  # do the actual checking
+  foreach my $field (keys %$required) {
+    unless ($self->$field) {
+      my $name = $labels->{$field} || $field;
+      return "Field $name is required\n"
+    }
+  }
+
   $self->SUPER::check;
 }
 
index 452f250..790ac34 100644 (file)
@@ -283,6 +283,7 @@ sub table_info {
                          disable_default => 1,
                          disable_fixed => 1,
                          disable_select => 1,
+                         required => 1,
                        },
         'password_selfchange' => { label => 'Password modification',
                                    type  => 'checkbox',
@@ -310,7 +311,9 @@ sub table_info {
                          type => 'text',
                          disable_inventory => 1,
                        },
-        '_password' => 'Password',
+        '_password' => { label => 'Password',
+                         required => 1
+                       },
         'gid'       => {
                          label    => 'GID',
                         def_info => 'when blank, defaults to UID',
@@ -333,6 +336,7 @@ sub table_info {
                          select_key   => 'svcnum',
                          select_label => 'domain',
                          disable_inventory => 1,
+                         required => 1,
                        },
         'pbxsvc'    => { label => 'PBX',
                          type  => 'select-svc_pbx.html',
index b01d673..78556cf 100644 (file)
@@ -134,7 +134,10 @@ sub table_info {
     'display_weight' => 20,
     'cancel_weight'  => 60,
     'fields' => {
-      'domain' => 'Domain',
+      'domain' => {
+                  label => 'Domain',
+                  required => 1,
+                },
       'parent_svcnum' => { 
                          label => 'Parent domain / Communigate administrator domain',
                          type  => 'select',
index 0d36853..ec5f321 100755 (executable)
@@ -61,6 +61,8 @@ function part_export_areyousure(href) {
 
     <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH>
 
+    <TH CLASS="grid" BGCOLOR="#cccccc" STYLE="font-size: smaller;">Required</TH>
+
   </TR>
 % my $conf = FS::Conf->new;
 % foreach my $part_svc ( @part_svc ) {
@@ -78,6 +80,9 @@ function part_export_areyousure(href) {
 %                $col->columnflag || ( $col->columnlabel !~ /^\S*$/
 %                                      && $col->columnlabel ne $def->{'label'}
 %                                    )
+%                                 || ( $col->required
+%                                      && !$def->{'required'}
+%                                    )
 %              )
 %            }
 %            @dfields ;
@@ -150,7 +155,7 @@ function part_export_areyousure(href) {
     </TD>
 
 %     unless ( @fields ) {
-%       for ( 1..4 ) {  
+%       for ( 1..5 ) {  
          <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD>
 %       }
 %     }
@@ -170,7 +175,6 @@ function part_export_areyousure(href) {
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD>
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $label %></TD>
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD>
-
      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
 % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue);
 % if ( $flag =~ /^[MAH]$/ ) { 
@@ -189,6 +193,11 @@ function part_export_areyousure(href) {
 % }
 
      </TD>
+     <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% if ($part_svc_column->required) {
+       Yes
+% }
+     </TD>
 %     $n1="</TR><TR>";
 %     } #foreach $field
 %   if ( $part_svc->restrict_edit_password ) {
index 2bb4f5e..a6ccaf8 100644 (file)
@@ -77,6 +77,7 @@ that field.
     <TH BGCOLOR="#cccccc">Field</TH>
     <TH BGCOLOR="#cccccc">Label</TH>
     <TH BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>
+    <TH BGCOLOR="#cccccc">Required?</TH>
   </TR>
 % $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now
 % my $i = 0;
@@ -210,9 +211,17 @@ that field.
       &>
 %   }
     </TD>
+    <TD>
+%   if (!$def->{'type'} || !(grep {$_ eq $def->{'type'}} ('checkbox','disabled'))) {
+      <INPUT ID="<% $name.'_required' %>" TYPE="checkbox" NAME="<% $svcdb %>__<% $field %>_required" VALUE="Y" 
+        <% ($part_svc_column->required || $def->{'required'}) ? 'CHECKED' : '' %> 
+        <% $def->{'required'} ? 'DISABLED' : '' %>
+       >
+%   }
+    </TD>
   </TR>
   <TR CLASS="row<%$i%>">
-    <TD COLSPAN=2 CLASS="def_info">
+    <TD COLSPAN=3 CLASS="def_info">
 %   if ( $def->{def_info} ) {
       (<% $def->{def_info} %>)
     </TD>
@@ -228,7 +237,7 @@ that field.
     <TD COLSPAN=3 ALIGN="right">
       <% emt('Require "Provision" access right to edit password') %>
     </TD>
-    <TD>
+    <TD COLSPAN=2>
       <INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y" \
       <% $part_svc->restrict_edit_password ? 'CHECKED' : '' %>>
     </TD>
@@ -244,7 +253,7 @@ that field.
     <TD COLSPAN=3 ALIGN="right">
       <% emt('This service has an attached router') %>
     </TD>
-    <TD>
+    <TD COLSPAN=2>
       <INPUT TYPE="checkbox" NAME="has_router" VALUE="Y" \
       <% $part_svc->has_router ? 'CHECKED' : '' %>>
     </TD>
index 47b020c..7a47f15 100755 (executable)
@@ -101,6 +101,15 @@ function flag_changed(obj) {
       }
     }
   }
+  var required = document.getElementById(layer + '__' + field + '_required');
+  if (required && !required.disabledinit) {
+    if (newflag == "F") {
+      required.checked = false;
+      required.disabled = true;
+    } else {
+      required.disabled = false;
+    }
+  }
 }
 
 window.onload = function() {
@@ -111,6 +120,17 @@ window.onload = function() {
       obj.setAttribute('should_be_multiple', true);
     }
   }
+  var inputs = document.getElementsByTagName('INPUT');
+  for(i = 0; i < inputs.length; i++) {
+    var obj = inputs[i];
+    if (obj.type == 'checkbox') {
+      if ( obj.name.match(/_required$/) ) {
+        if ( obj.disabled ) {
+          obj.disabledinit = 1;
+        }
+      }
+    }
+  }
   for(i = 0; i < selects.length; i++) {
     var obj = selects[i];
     if ( obj.name.match(/_flag$/) ) {