RT# 83211 - Added service export error test report
[freeside.git] / FS / FS / part_export / saisei.pm
index 26d9ac5..92b18e4 100644 (file)
@@ -62,6 +62,8 @@ tie my %scripts, 'Tie::IxHash',
                                       label     => 'Export provisioned services',
                                       description => 'will export provisioned services of part service with Saisei export attached.',
                                       html_label => '<b>Export provisioned services attached to this export.</b>',
+                                      error_url  => '/edit/part_export.cgi?',
+                                      success_message => 'Saisei export of provisioned services successful',
                                     },
 ;
 
@@ -169,6 +171,7 @@ sub _export_insert {
                       tower.up_rate_limit as tower_upratelimit,
                       tower.down_rate_limit as tower_downratelimit,
                       tower_sector.sectorname,
+                      tower_sector.towernum,
                       tower_sector.up_rate_limit as sector_upratelimit,
                       tower_sector.down_rate_limit as sector_downratelimit ',
       'addl_from' => 'LEFT JOIN tower USING ( towernum )',
@@ -182,6 +185,7 @@ sub _export_insert {
 
     my $tower_opt = {
       'tower_name'           => $tower_name,
+      'tower_num'            => $tower_sector->{Hash}->{towernum},
       'tower_uprate_limit'   => $tower_sector->{Hash}->{tower_upratelimit},
       'tower_downrate_limit' => $tower_sector->{Hash}->{tower_downratelimit},
     };
@@ -194,9 +198,11 @@ sub _export_insert {
 
     my $sector_opt = {
       'tower_name'            => $tower_name,
+      'tower_num'             => $tower_sector->{Hash}->{towernum},
       'sector_name'           => $sector_name,
       'sector_uprate_limit'   => $tower_sector->{Hash}->{sector_upratelimit},
       'sector_downrate_limit' => $tower_sector->{Hash}->{sector_downratelimit},
+      'rateplan'              => $rateplan_name,
     };
     my $accesspoint = process_sector($self, $sector_opt);
     return $self->api_error if $self->{'__saisei_error'};
@@ -330,6 +336,7 @@ sub export_tower_sector {
   $tower_name =~ s/\s/_/g;
   my $tower_opt = {
     'tower_name'           => $tower_name,
+    'tower_num'            => $tower->{Hash}->{towernum},
     'tower_uprate_limit'   => $tower->{Hash}->{up_rate_limit},
     'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
     'modify_existing'      => '1', # modify an existing access point with this info
@@ -351,6 +358,7 @@ sub export_tower_sector {
     $sector_name =~ s/\s/_/g;
     my $sector_opt = {
       'tower_name'            => $tower_name,
+      'tower_num'             => $tower_sector->{Hash}->{towernum},
       'sector_name'           => $sector_name,
       'sector_uprate_limit'   => $tower_sector->{Hash}->{up_rate_limit},
       'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
@@ -417,7 +425,7 @@ sub api_call {
   if ($client->responseCode() eq '200' || $client->responseCode() eq '201') {
     eval { $result = decode_json($client->responseContent()) };
     unless ($result) {
-      $self->{'__saisei_error'} = "Error decoding json from Saisei";
+      $self->{'__saisei_error'} = "There was an error decoding the JSON data from Saisei.  Bad JSON data logged in error log if debug option was set.";
       warn "Saisei RC 201 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
       return;
     }
@@ -425,23 +433,23 @@ sub api_call {
   elsif ($client->responseCode() eq '404') {
     eval { $result = decode_json($client->responseContent()) };
     unless ($result) {
-      $self->{'__saisei_error'} = "Error decoding json from Saisei";
+      $self->{'__saisei_error'} = "There was an error decoding the JSON data from Saisei.  Bad JSON data logged in error log if debug option was set.";
       warn "Saisei RC 404 Response Content is not json\n".$client->responseContent()."\n" if $self->option('debug');
       return;
     }
     ## check if message is for empty hash.
     my($does_not_exist) = $result->{message} =~ /'(.*)' does not exist$/;
-    $self->{'__saisei_error'} = "Error ".$result->{message} unless $does_not_exist;
+    $self->{'__saisei_error'} = "Saisei Error: ".$result->{message} unless $does_not_exist;
     warn "Saisei Response Content is\n".$client->responseContent."\n" if ($self->option('debug') && !$does_not_exist);
     return;
   }
   elsif ($client->responseCode() eq '500') {
-    $self->{'__saisei_error'} = "Can't connect to host during $method , received responce code: " . $client->responseCode() . " and message: " . $client->responseContent();
+    $self->{'__saisei_error'} = "Could not connect to the Saisei export host machine (".$self->{Hash}->{machine}.':'.$self->option('port').") during $method , we received the responce code: " . $client->responseCode();
     warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
     return;
   }
   else {
-    $self->{'__saisei_error'} = "Bad response from server during $method , received responce code: " . $client->responseCode() . " and message: " . $client->responseContent();
+    $self->{'__saisei_error'} = "Received Bad response from server during $method , we received responce code: " . $client->responseCode();
     warn "Saisei Response Content is\n".$client->responseContent."\n" if $self->option('debug');
     return; 
   }
@@ -473,7 +481,7 @@ sub api_get_policies {
 
   my $get_policies = $self->api_call("GET", '/policies/?token=1&order=name&start=0&limit=20&select=name%2Cpercent_rate%2Cassured%2C');
   return if $self->api_error;
-  $self->{'__saisei_error'} = "Did not receive any global policies"
+  $self->{'__saisei_error'} = "Did not receive any global policies from Saisei."
     unless $get_policies;
 
   return $get_policies->{collection};
@@ -553,8 +561,8 @@ Creates a rateplan.
 sub api_create_rateplan {
   my ($self, $svc, $rateplan) = @_;
 
-  $self->{'__saisei_error'} = "No downrate listed for service $rateplan" if !$svc->{Hash}->{speed_down};
-  $self->{'__saisei_error'} = "No uprate listed for service $rateplan" if !$svc->{Hash}->{speed_up};
+  $self->{'__saisei_error'} = "There is no download speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a download speed set for them." if !$svc->{Hash}->{speed_down};
+  $self->{'__saisei_error'} = "There is no upload speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a upload speed set for them." if !$svc->{Hash}->{speed_up};
 
   my $new_rateplan = $self->api_call(
       "PUT", 
@@ -565,7 +573,7 @@ sub api_create_rateplan {
       },
   ) unless $self->{'__saisei_error'};
 
-  $self->{'__saisei_error'} = "Rate Plan not created"
+  $self->{'__saisei_error'} = "Saisei could not create the rate plan $rateplan."
     unless ($new_rateplan || $self->{'__saisei_error'});
 
   return $new_rateplan;
@@ -598,7 +606,7 @@ sub api_modify_rateplan {
       },
     );
 
-    $self->{'__saisei_error'} = "Rate Plan not modified after create"
+    $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name after it was created."
       unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
     
   }
@@ -616,6 +624,9 @@ Modify a existing rateplan.
 sub api_modify_existing_rateplan {
   my ($self,$svc,$rateplan_name) = @_;
 
+  $self->{'__saisei_error'} = "There is no download speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan_name."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a download speed set for them." if !$svc->{Hash}->{speed_down};
+  $self->{'__saisei_error'} = "There is no upload speed set for the service !--service,".$svc->{Hash}->{svcnum}.",".$rateplan_name."--! with host (".$svc->{Hash}->{ip_addr}."). All services that are to be exported to Saisei need to have a upload speed set for them." if !$svc->{Hash}->{speed_up};
+
   my $modified_rateplan = $self->api_call(
     "PUT",
     "/rate_plans/$rateplan_name",
@@ -625,7 +636,7 @@ sub api_modify_existing_rateplan {
     },
   );
 
-    $self->{'__saisei_error'} = "Rate Plan not modified"
+    $self->{'__saisei_error'} = "Saisei could not modify the rate plan $rateplan_name."
       unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
 
   return;
@@ -649,7 +660,7 @@ sub api_create_user {
       },
   );
 
-  $self->{'__saisei_error'} = "User not created"
+  $self->{'__saisei_error'} = "Saisei could not create the user $user"
     unless ($new_user || $self->{'__saisei_error'}); # should never happen
 
   return $new_user;
@@ -674,7 +685,7 @@ sub api_create_accesspoint {
       },
   );
 
-  $self->{'__saisei_error'} = "Access point not created"
+  $self->{'__saisei_error'} = "Saisei could not create the access point $accesspoint"
     unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
   return;
 
@@ -697,7 +708,7 @@ sub api_modify_accesspoint {
     },
   );
 
-  $self->{'__saisei_error'} = "Rate Plan not modified"
+  $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint after it was created."
     unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
 
   return;
@@ -723,7 +734,7 @@ sub api_modify_existing_accesspoint {
     },
   );
 
-    $self->{'__saisei_error'} = "Access point not modified"
+    $self->{'__saisei_error'} = "Saisei could not modify the access point $accesspoint."
       unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
 
   return;
@@ -749,7 +760,7 @@ sub api_add_host_to_user {
       },
   );
 
-  $self->{'__saisei_error'} = "Host not created"
+  $self->{'__saisei_error'} = "Saisei could not create the host $ip"
     unless ($new_host || $self->{'__saisei_error'}); # should never happen
 
   return $new_host;
@@ -768,7 +779,7 @@ sub api_delete_host_to_user {
 
   my $default_rate_plan = $self->api_call("GET", '?token=1&select=default_rate_plan');
     return if $self->api_error;
-  $self->{'__saisei_error'} = "Did not receive a default rate plan"
+  $self->{'__saisei_error'} = "Can not delete the host as Saisei did not return a default rate plan. Please make sure Saisei has a default rateplan setup."
     unless $default_rate_plan;
 
   my $default_rateplan_name = $default_rate_plan->{collection}->[0]->{default_rate_plan}->{link}->{name};
@@ -783,7 +794,7 @@ sub api_delete_host_to_user {
       },
   );
 
-  $self->{'__saisei_error'} = "Host not created"
+  $self->{'__saisei_error'} = "Saisei could not delete the host $ip"
     unless ($delete_host || $self->{'__saisei_error'}); # should never happen
 
   return $delete_host;
@@ -794,7 +805,7 @@ sub process_tower {
   my ($self, $opt) = @_;
 
   if (!$opt->{tower_uprate_limit} || !$opt->{tower_downrate_limit}) {
-    $self->{'__saisei_error'} = "Can not export tower, no up or down rates attached to tower";
+    $self->{'__saisei_error'} = "Could not export tower !--tower,".$opt->{tower_num}.",".$opt->{tower_name}."--! because there was no up or down rates attached to the tower.  Saisei requires a up and down rate be attached to each tower.";
     return { error => $self->api_error, };
   }
 
@@ -828,8 +839,13 @@ sub process_tower {
 sub process_sector {
   my ($self, $opt) = @_;
 
+  if (!$opt->{sector_name} || $opt->{sector_name} eq '_default') {
+    $self->{'__saisei_error'} = "No sector attached to Tower (".$opt->{tower_name}.") for service ".$opt->{'rateplan'}.".  Saisei requires a tower sector to be attached to each service that is exported to Saisei.";
+    return { error => $self->api_error, };
+  }
+
   if (!$opt->{sector_uprate_limit} || !$opt->{sector_downrate_limit}) {
-    $self->{'__saisei_error'} = "Can not export sector, no up or down rates attached to sector";
+    $self->{'__saisei_error'} = "Could not export sector !--tower,".$opt->{tower_num}.",".$opt->{sector_name}."--! because there was no up or down rates attached to the sector.  Saisei requires a up and down rate be attached to each sector.";
     return { error => $self->api_error, };
   }
 
@@ -864,6 +880,21 @@ sub process_sector {
   return $accesspoint;
 }
 
+=head2 require_tower_and_sector
+
+sets whether the service export requires a sector with it's tower.
+
+=cut
+
+sub require_tower_and_sector {
+  1;
+}
+
+sub required_fields {
+  my @fields = ('svc_broadband__ip_addr_required', 'svc_broadband__speed_up_required', 'svc_broadband__speed_down_required', 'svc_broadband__sectornum_required');
+  return @fields;
+}
+
 sub process_virtual_ap {
   my ($self, $opt) = @_;
 
@@ -907,7 +938,7 @@ sub export_provisioned_services {
   my $param = shift;
 
   my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
-  or die "unknown exportnum $param->{export_provisioned_services_exportnum}";
+  or die "You are trying to use an unknown exportnum $param->{export_provisioned_services_exportnum}.  This export does not exist.\n";
   bless $part_export;
 
   my @svcparts = FS::Record::qsearch({
@@ -935,10 +966,13 @@ sub export_provisioned_services {
     if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
     ## check if service exists as host if not export it.
     my $host = api_get_host($part_export, $svc->{Hash}->{ip_addr});
-    die $host->{message} if $host->{message};
+    die ("Please double check your credentials as ".$host->{message}."\n") if $host->{message};
     warn "Exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
     my $export_error = _export_insert($part_export,$svc) unless $host->{collection};
-    die $export_error if $export_error;
+    if ($export_error) {
+      warn "Error exporting service ".$svc->{Hash}->{ip_addr}."\n" if ($part_export->option('debug'));
+      die ("$export_error\n");
+    }
     $process_count++;
   }
 
@@ -946,6 +980,139 @@ sub export_provisioned_services {
 
 }
 
+sub test_export_report {
+  my ($self, $opts) = @_;
+  my @export_error;
+
+  ##  check all part services for export errors
+  my @exports = FS::Record::qsearch('part_export', { 'exporttype' => "saisei", } );
+  my $export_nums = join "', '", map { $_->{Hash}->{exportnum} } @exports;
+
+  my $svc_part_export_error;
+  my @svcparts = FS::Record::qsearch({
+    'table' => 'part_svc',
+    'addl_from' => 'LEFT JOIN export_svc USING ( svcpart  ) ',
+    'extra_sql' => " WHERE export_svc.exportnum in ('".$export_nums."')",
+  });
+  my $part_count = scalar @svcparts;
+
+  my $svc_part_error;
+  foreach (@svcparts) {
+    my $part_error->{'description'} = $_->svc;
+    $part_error->{'link'} = $opts->{'fsurl'}."/edit/part_svc.cgi?".$_->svcpart;
+
+    foreach my $s ('speed_up', 'speed_down') {
+      my $speed = $_->part_svc_column($s);
+      if ($speed->columnflag eq "" || $speed->columnflag eq "D") {
+        $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is not set to be required and can be set while provisioning the service." unless $speed->required eq "Y";
+      }
+      elsif ($speed->columnflag eq "F" || $speed->columnflag eq "S") {
+        $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is set to auto fill while provisioning the service but there is no value set." unless $speed->columnvalue;
+      }
+      elsif ($speed->columnflag eq "P") {
+        my $fcc_speed_name = "broadband_".$speed->columnvalue."stream";
+        foreach my $part_pkg ( FS::Record::qsearchs({
+                                 'table'   => 'part_pkg',
+                                 'addl_from' => 'LEFT JOIN pkg_svc USING ( pkgpart  ) ',
+                                 'extra_sql' => " WHERE pkg_svc.svcpart = ".$_->svcpart,
+                              })) {
+          my $pkglink = '<a href="'.$opts->{'fsurl'}.'/edit/part_pkg.cgi?'.$part_pkg->pkgpart.'"><FONT COLOR="red"><B>'.$part_pkg->pkg.'</B></FONT></a>';
+          $part_error->{'errors'}->{$speed->columnname} = "Field ".$speed->columnname." is set to package FCC 477 information, but package ".$pkglink." does not have FCC ".$fcc_speed_name." set."
+            unless $part_pkg->fcc_option($fcc_speed_name);
+        }
+      }
+    }
+    $part_error->{'errors'}->{'ip_addr'}    = "Field IP Address is not set to required" if $_->part_svc_column("ip_addr")->required ne "Y";
+    $svc_part_error->{$_->svcpart} = $part_error if $part_error->{'errors'};
+  }
+
+  $svc_part_export_error->{"services"}->{'description'} = "Service definitions";
+  $svc_part_export_error->{"services"}->{'count'} = $part_count;
+  $svc_part_export_error->{"services"}->{'errors'} = $svc_part_error if $svc_part_error;
+
+  push @export_error, $svc_part_export_error;
+
+  ##  check all provisioned cust services for export errors
+  my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
+  my $cust_svc_export_error;
+  my @svcs = FS::Record::qsearch({
+    'table' => 'cust_svc',
+    'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum  ) ',
+    'extra_sql' => " WHERE svcpart in ('".$parts."')",
+  }) unless !$parts;
+  my $svc_count = scalar @svcs;
+
+  my $cust_svc_error;
+  foreach (@svcs) {
+    my $svc_error->{'description'} = $_->description;
+    $svc_error->{'link'} = $opts->{'fsurl'}."/edit/svc_broadband.cgi?".$_->svcnum;
+
+    foreach my $s ('speed_up', 'speed_down', 'ip_addr') {
+        $svc_error->{'errors'}->{$s} = "Field ".$s." is not set and is required for this service to be exported to Saisei." unless $_->$s;
+    }
+
+    my $sector = FS::Record::qsearchs({
+        'table' => 'tower_sector',
+        'extra_sql' => " WHERE sectornum = ".$_->sectornum." AND sectorname != '_default'",
+    }) if $_->sectornum;
+    if (!$sector) {
+      $svc_error->{'errors'}->{'sectornum'} = "No tower sector is set for this service. There needs to be a tower and sector set to be exported to Saisei.";
+    }
+    else {
+      foreach my $s ('up_rate_limit', 'down_rate_limit') {
+        $svc_error->{'errors'}->{'sectornum'} = "The sector ".$sector->description." does not have a ".$s." set. The sector needs a ".$s." set to be exported to Saisei."
+          unless $sector->$s;
+      }
+    }
+    $cust_svc_error->{$_->svcnum} = $svc_error if $svc_error->{'errors'};
+  }
+
+  $cust_svc_export_error->{"provisioned_services"}->{'description'} = "Provisioned services";
+  $cust_svc_export_error->{"provisioned_services"}->{'count'} = $svc_count;
+  $cust_svc_export_error->{"provisioned_services"}->{'errors'} = $cust_svc_error if $cust_svc_error;
+
+  push @export_error, $cust_svc_export_error;
+
+
+  ##  check all towers and sectors for export errors
+  my $tower_sector_export_error;
+  my @towers = FS::Record::qsearch({
+    'table' => 'tower',
+  });
+  my $tower_count = scalar @towers;
+
+  my $towers_error;
+  foreach (@towers) {
+    my $tower_error->{'description'} = $_->towername;
+    $tower_error->{'link'} = $opts->{'fsurl'}."/edit/tower.html?".$_->towernum;
+
+    foreach my $s ('up_rate_limit', 'down_rate_limit') {
+        $tower_error->{'errors'}->{$s} = "Field ".$s." is not set for the tower, this is required for this tower to be exported to Saisei." unless $_->$s;
+    }
+
+    my @sectors = FS::Record::qsearch({
+        'table' => 'tower_sector',
+        'extra_sql' => " WHERE towernum = ".$_->towernum." AND sectorname != '_default' AND (up_rate_limit IS NULL OR down_rate_limit IS NULL)",
+    }) if $_->towernum;
+    foreach my $sector (@sectors) {
+      foreach my $s ('up_rate_limit', 'down_rate_limit') {
+        $tower_error->{'errors'}->{'sector_'.$s} = "The sector ".$sector->description." does not have a ".$s." set. The sector needs a ".$s." set to be exported to Saisei."
+          if !$sector->$s;
+      }
+    }
+    $towers_error->{$_->towernum} = $tower_error if $tower_error->{'errors'};
+  }
+
+  $tower_sector_export_error->{"tower_sector"}->{'description'} = "Tower / Sector";
+  $tower_sector_export_error->{"tower_sector"}->{'count'} = $tower_count;
+  $tower_sector_export_error->{"tower_sector"}->{'errors'} = $towers_error if $towers_error;
+
+  push @export_error, $tower_sector_export_error;
+
+  return [@export_error];
+
+}
+
 =head1 SEE ALSO
 
 L<FS::part_export>