RT# 78356 - added speed test fields for broadband service and new modifier to get...
authorChristopher Burger <burgerc@freeside.biz>
Fri, 27 Jul 2018 12:05:10 +0000 (08:05 -0400)
committerChristopher Burger <burgerc@freeside.biz>
Tue, 31 Jul 2018 16:54:22 +0000 (12:54 -0400)
13 files changed:
FS/FS/Schema.pm
FS/FS/cust_pkg.pm
FS/FS/part_export/saisei.pm
FS/FS/part_svc.pm
FS/FS/part_svc_column.pm
FS/FS/svc_broadband.pm
httemplate/browse/part_svc.cgi
httemplate/edit/elements/part_svc_column.html
httemplate/edit/elements/svc_Common.html
httemplate/edit/part_svc.cgi
httemplate/edit/process/elements/process.html
httemplate/edit/process/part_pkg.cgi
httemplate/edit/svc_broadband.cgi

index 6991432..d313db3 100644 (file)
@@ -3324,6 +3324,9 @@ sub tables_hashref {
         'suid',                    'int', 'NULL',        '', '', '',
         'shared_svcnum',           'int', 'NULL',        '', '', '',
         'serviceid',           'varchar', 'NULL',        64, '', '',#srvexport/reportfields
+        'speed_test_up',           'int', 'NULL',        '', '', '',
+        'speed_test_down',         'int', 'NULL',        '', '', '',
+        'speed_test_latency',      'int', 'NULL',        '', '', '',
       ],
       'primary_key' => 'svcnum',
       'unique'      => [ [ 'ip_addr' ], [ 'mac_addr' ] ],
index 3b746fc..360c9d7 100644 (file)
@@ -5969,6 +5969,24 @@ sub fcc_477_count {
 
 }
 
+=item fcc_477_record
+
+Returns a fcc_477 record based on option name.
+
+=cut
+
+sub fcc_477_record {
+  my ($self, $option_name) = @_;
+
+  my $fcc_record = qsearchs({
+    'table'     => 'part_pkg_fcc_option',
+    'hashref'   => { 'pkgpart' => $self->{Hash}->{pkgpart}, 'fccoptionname' => $option_name, },
+  });
+
+  return ( $fcc_record );
+
+}
+
 =item tax_locationnum_sql
 
 Returns an SQL expression for the tax location for a package, based
index 83cb4ff..7707ad9 100644 (file)
@@ -129,9 +129,7 @@ END
 sub _export_insert {
   my ($self, $svc_broadband) = @_;
 
-  my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } );
-  my $rateplan_name = $service_part->{Hash}->{svc};
-  $rateplan_name =~ s/\s/_/g;
+  my $rateplan_name = $self->get_rateplan_name($svc_broadband);
 
   # check for existing rate plan
   my $existing_rateplan;
@@ -218,15 +216,15 @@ sub _export_insert {
 
 sub _export_replace {
   my ($self, $svc_broadband) = @_;
+  $self->_export_insert($svc_broadband);
   return '';
 }
 
 sub _export_delete {
   my ($self, $svc_broadband) = @_;
 
-  my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } );
-  my $rateplan_name = $service_part->{Hash}->{svc};
-  $rateplan_name =~ s/\s/_/g;
+  my $rateplan_name = $self->get_rateplan_name($svc_broadband);
+
   my $username = $svc_broadband->{Hash}->{svcnum};
 
   ## untie host to user
@@ -248,25 +246,49 @@ sub _export_unsuspend {
 sub export_partsvc {
   my ($self, $svc_part) = @_;
 
-  my $rateplan_name = $svc_part->{Hash}->{svc};
-  $rateplan_name =~ s/\s/_/g;
-  my $speeddown = $svc_part->{Hash}->{svc_broadband__speed_down};
-  my $speedup = $svc_part->{Hash}->{svc_broadband__speed_up};
+  my $fcc_477_speeds;
+  if ($svc_part->{Hash}->{svc_broadband__speed_down} eq "down" || $svc_part->{Hash}->{svc_broadband__speed_up} eq "up") {
+    for my $type (qw( down up )) {
+      my $speed_type = "broadband_".$type."stream";
+      foreach my $pkg_svc (FS::Record::qsearch({
+        'table'     => 'pkg_svc',
+        'select'    => 'pkg_svc.*, part_pkg_fcc_option.fccoptionname, part_pkg_fcc_option.optionvalue',
+        'addl_from' => ' LEFT JOIN part_pkg_fcc_option USING (pkgpart) ',
+        'extra_sql' => " WHERE pkg_svc.svcpart = ".$svc_part->{Hash}->{svcpart}." AND pkg_svc.quantity > 0 AND part_pkg_fcc_option.fccoptionname = '".$speed_type."'",
+      })) { $fcc_477_speeds->{
+        $pkg_svc->{Hash}->{pkgpart}}->{$speed_type} = $pkg_svc->{Hash}->{optionvalue} * 1000 unless !$pkg_svc->{Hash}->{optionvalue}; }
+    }
+  }
+  else {
+    $fcc_477_speeds->{1}->{broadband_downstream} = $svc_part->{Hash}->{"svc_broadband__speed_down"};
+    $fcc_477_speeds->{1}->{broadband_upstream} = $svc_part->{Hash}->{"svc_broadband__speed_up"};
+  }
 
-  my $temp_svc = $svc_part->{Hash};
-  my $svc_broadband = {};
-  map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; }  } keys %$temp_svc;
+  foreach my $key (keys %$fcc_477_speeds) {
 
-  # check for existing rate plan
-  my $existing_rateplan;
-  $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
+    $svc_part->{Hash}->{speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
+    $svc_part->{Hash}->{speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
+    $svc_part->{Hash}->{svc_broadband__speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream};
+    $svc_part->{Hash}->{svc_broadband__speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream};
 
-  # Modify the existing rate plan with new service data.
-  $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
+    my $temp_svc = $svc_part->{Hash};
+    my $svc_broadband = {};
+    map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; }  } keys %$temp_svc;
 
-  # if no existing rate plan create one and modify it.
-  $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
-  $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
+    my $rateplan_name = $self->get_rateplan_name($svc_broadband, $svc_part->{Hash}->{svc});
+
+    # check for existing rate plan
+    my $existing_rateplan;
+    $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
+
+    # Modify the existing rate plan with new service data.
+    $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
+
+    # if no existing rate plan create one and modify it.
+    $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
+    $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
+
+  }
 
   return $self->api_error;
 
@@ -311,6 +333,19 @@ sub export_tower_sector {
   return $self->api_error;
 }
 
+## creates the rateplan name
+sub get_rateplan_name {
+  my ($self, $svc_broadband, $svc_name) = @_;
+
+  my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } ) unless $svc_name;
+  my $service_name = $svc_name ? $svc_name : $service_part->{Hash}->{svc};
+
+  my $rateplan_name = $service_name . " " . $svc_broadband->{Hash}->{speed_down} . "-" . $svc_broadband->{Hash}->{speed_up};
+  $rateplan_name =~ s/\s/_/g;
+
+  return $rateplan_name;
+}
+
 =head1 Saisei API
 
 These methods allow access to the Saisei API using the credentials
index 81652c1..b387085 100644 (file)
@@ -869,10 +869,10 @@ sub process {
               map {
                     my $f = $svcdb.'__'.$_;
                     my $flag = $param->{ $f.'_flag' } || ''; #silence warnings
-                    if ( $flag =~ /^[MAH]$/ ) {
+                    if ( $flag =~ /^[MAHP]$/ ) {
                       $param->{ $f } = delete( $param->{ $f.'_classnum' } );
                     }
-                   if ( ( $flag =~ /^[MAHS]$/ or $_ eq 'usergroup' )
+                   if ( ( $flag =~ /^[MAHSP]$/ or $_ eq 'usergroup' )
                          and ref($param->{ $f }) ) {
                       $param->{ $f } = join(',', @{ $param->{ $f } });
                    }
index 75a2dfb..e055af3 100644 (file)
@@ -97,7 +97,7 @@ sub check {
   ;
   return $error if $error;
 
-  $self->columnflag =~ /^([DFSMAHX]?)$/
+  $self->columnflag =~ /^([DFSMAHXP]?)$/
     or return "illegal columnflag ". $self->columnflag;
   $self->columnflag(uc($1));
 
index b94fc69..11e3331 100755 (executable)
@@ -107,8 +107,16 @@ sub table_info {
     'fields' => {
       'svcnum'      => 'Service',
       'description' => 'Descriptive label',
-      'speed_down'  => 'Download speed (Kbps)',
-      'speed_up'    => 'Upload speed (Kbps)',
+      'speed_up'    => {
+                         'label'    => 'Upload speed (Kbps)',
+                         'type'     => 'fcc_477_speed',
+                         'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier',
+                       },
+      'speed_down'  => {
+                         'label'    => 'Download speed (Kbps)',
+                         'type'     => 'fcc_477_speed',
+                         'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier',
+                       },
       'ip_addr'     => 'IP address',
       'blocknum'    => 
       { 'label' => 'Address block',
@@ -148,6 +156,9 @@ sub table_info {
                              disable_inventory => 1,
                            },
       'serviceid' => 'Torrus serviceid', #but is should be hidden
+      'speed_test_up'      => 'Speed test download (Kbps)',
+      'speed_test_down'    => 'Speed test upload (Kbps)',
+      'speed_test_latency' => 'Speed test latency (ms)',
     },
   };
 }
index b947463..222433d 100755 (executable)
@@ -251,6 +251,7 @@ my %flag = (
   'A' => 'Automatically filled in from inventory',
   'H' => 'Selected from hardware class',
   'X' => 'Excluded',
+  'P' => 'From package 477 information',
 );
 
 my %search;
index 93d3b3d..1e1ff79 100644 (file)
@@ -15,6 +15,7 @@ To be called from part_svc.cgi.
 # don't allow the 'inventory' flags (M, A) to be chosen for 
 # fields that aren't free-text
 my $inv_sub = sub { $_[0]->{disable_inventory} || $_[0]->{type} ne 'text' };
+
 tie my %flag, 'Tie::IxHash',
   ''  => { 'desc' => 'No default', 'condition' => sub { 0 } },
   'D' => { 'desc' => 'Default', 
@@ -38,6 +39,9 @@ tie my %flag, 'Tie::IxHash',
   'H' => { 'desc' => 'Select from hardware class',
            'condition' => sub { $_[0]->{type} ne 'select-hardware' },
          },
+  'P' => { 'desc' => 'From package FCC 477 information',
+           'condition' => sub { $_[0]->{type} ne 'fcc_477_speed' }, # get values from package fcc 477 information
+         },
   'X' => { 'desc' => 'Excluded',
            'condition' => sub { 1 }, # obsolete
          },
@@ -203,6 +207,20 @@ that field.
 %       $mode = 'hardware';
 %       $multiple = 0;
 %     }
+%
+%     if ( $def->{'type'} eq 'fcc_477_speed' ) {
+%       if ($field eq 'speed_up') {
+          <SPAN ID="<% $name %>_select">
+            upstream speed
+            <INPUT TYPE="hidden" ID="<% $name %>_select" NAME="<% $name %>_classnum" VALUE="up">
+          </SPAN>
+%       } elsif ($field eq 'speed_down') {
+          <SPAN ID="<% $name %>_select">
+            downstream speed
+            <INPUT TYPE="hidden" ID="<% $name %>_select" NAME="<% $name %>_classnum" VALUE="down">
+          </SPAN>
+%       }
+%     } else {
       <& /elements/select-table.html,
           'field'       => $name.'_classnum',
           'id'          => $name.'_select',
@@ -212,6 +230,7 @@ that field.
           'empty_label' => "Select $mode class",
           'multiple'    => $multiple,
       &>
+%     }
 %   }
     </TD>
     <TD>
index a4e345e..e1c3090 100644 (file)
                                      ];
                    } # shouldn't this be enforced for all 'S' fields?
 
+                   elsif ( $flag eq 'P' ) { #form fcc_477 values
+                     $f->{type}    = 'fixed';
+                     my $cust_pkg = FS::Record::qsearchs({
+                       'table'   => 'cust_pkg',
+                       'hashref' => { 'pkgnum' => $object->{Hash}->{pkgnum} }
+                     });
+                     my $fcc_record = $cust_pkg->fcc_477_record('broadband_'.$columndef->columnvalue.'stream') if $cust_pkg;
+                     $f->{'value'} = $fcc_record->{Hash}->{optionvalue} ? $fcc_record->{Hash}->{optionvalue} * 1000 : '';
+                   } # end 477 values
+
                    if ( $f->{'type'} =~ /^select-svc/ )
                    {
                      $f->{'include_opt_callback'} =
index 6d8a40f..656b04e 100755 (executable)
@@ -107,7 +107,7 @@ function flag_changed(obj) {
         select.multiple = false;
       }
     }
-  } else if ( newflag == 'M' || newflag == 'A' || newflag == 'H' ) {
+  } else if ( newflag == 'M' || newflag == 'A' || newflag == 'H' || newflag == 'P' ) {
     // these all require a class selection
     if ( select ) {
       select.disabled = false;
@@ -120,7 +120,7 @@ function flag_changed(obj) {
   }
   var required = document.getElementById(layer + '__' + field + '_required');
   if (required && !required.disabledinit) {
-    if (newflag == "F") {
+    if (newflag == "F" || newflag =="P") {
       required.checked = false;
       required.disabled = true;
     } else {
index e96fa0c..1175723 100644 (file)
@@ -89,6 +89,9 @@ Example:
    # for use with tables that are FS::option_Common (among other things)
    'args_callback' => sub { my( $cgi, $object ) = @_; },
 
+   # if no errors after package insert or replace will update services attached to package.
+   'update_svc' => sub { my( $cgi, $object ) = @_; },
+
    'debug' => 1, #turns on debugging output
 
    #agent virtualization
@@ -438,6 +441,12 @@ foreach my $value ( @values ) {
     }
   }
 
+  if ( !$error and $opt{'update_svc'} ) {
+    my @args = ();
+    @args = &{ $opt{'args_callback'} }( $cgi, $new ) if $opt{'args_callback'};
+   $error = &{ $opt{'update_svc'} }( $cgi, $new, @args );
+  }
+
   if ( $error ) {
 
     $cgi->param('error', $error);
index 631a26b..7682258 100755 (executable)
@@ -9,6 +9,7 @@
               'edit_ext'          => 'cgi',
               'precheck_callback' => $precheck_callback,
               'args_callback'     => $args_callback,
+              'update_svc'        => $update_svc,
               'process_locale'    => 'pkg',
               'process_m2m'       => \@process_m2m,
           )
@@ -158,6 +159,37 @@ my $args_callback = sub {
 
 };
 
+## update services upon package change.
+my $update_svc = sub {
+  my $cgi = shift @_;
+  my $new = shift @_;
+  my %args = @_;
+  my $error;
+
+  my @svcs = $new->pkg_svc();
+
+  foreach my $svc_part(@svcs) {
+    my @part_svc_column = qsearch('part_svc_column',{ 'svcpart' => $svc_part->{Hash}->{svcpart}, 'columnflag' => 'P' });
+
+    if ($svc_part->{Hash}->{svcdb} eq "svc_broadband" && (keys $args{fcc_options}) && @part_svc_column ) {
+      ## find provisioned services to update
+      my @svc_svcdb = qsearch({
+        'table'     => 'svc_broadband',
+        'select'    => 'svc_broadband.*, cust_svc.svcpart',
+        'addl_from' => 'LEFT JOIN cust_svc USING (svcnum) LEFT JOIN cust_pkg USING (pkgnum)',
+        'extra_sql' => " WHERE cust_svc.svcpart = '".$svc_part->{Hash}->{svcpart}."' AND cust_pkg.pkgpart = '".$svc_part->{Hash}->{pkgpart}."'",
+      });
+      foreach my $svc (@svc_svcdb) {
+        #my $svc_new = $svc;
+        $svc->{Hash}->{speed_down} = $args{fcc_options}->{broadband_downstream} * 1000;
+        $svc->{Hash}->{speed_up} = $args{fcc_options}->{broadband_upstream} * 1000;
+        $error = $svc->replace();
+      }
+    }
+  }
+  return $error;
+};
+
 my $redirect_callback = sub {
   #my( $cgi, $new ) = @_;
   return '' unless $custnum;
index 81c694a..bcf55fe 100644 (file)
@@ -100,7 +100,7 @@ END
 ;
 
 my @fields = (
-  qw( description speed_down speed_up ),
+  qw( description speed_down speed_up speed_test_down speed_test_up speed_test_latency),
   { field=>'sectornum', type=>'select-tower_sector', },
   { field=>'routernum', type=>'select-router_block_ip', 
     include_opt_callback => sub { 
@@ -179,7 +179,6 @@ my $svc_field_callback = sub {
 
   my $columndef = $part_svc->part_svc_column($fieldref->{'field'});
   if ($fieldref->{field} eq 'usergroup' && $columndef->columnflag eq 'F') {
-    
     $fieldref->{'formatted_value'} = 
       [ $object->radius_groups('long_description') ];
   }