RT# 83450 - fixed rateplan export
[freeside.git] / FS / FS / part_export / huawei_hlr.pm
index fb3b679..9b405d5 100644 (file)
@@ -5,6 +5,8 @@ use Tie::IxHash;
 use FS::Record qw(qsearch qsearchs dbh);
 use FS::part_export;
 use FS::svc_phone;
+use FS::inventory_class;
+use FS::inventory_item;
 use IO::Socket::INET;
 use Data::Dumper;
 
@@ -14,10 +16,25 @@ $DEBUG = 0;
 @ISA = qw(FS::part_export);
 
 tie my %options, 'Tie::IxHash',
-  'opname'    => { label=>'Operator login' },
-  'pwd'       => { label=>'Operator password' },
+  'opname'    => { label=>'Operator login (required)' },
+  'pwd'       => { label=>'Operator password (required)' },
   'tplid'     => { label=>'Template number' },
   'hlrsn'     => { label=>'HLR serial number' },
+  'k4sno'     => { label=>'K4 serial number' },
+  'cardtype'  => { label  => 'Card type (required)',
+                   type   => 'select', 
+                   options=> ['SIM', 'USIM']
+                 },
+  'alg'       => { label  => 'Authentication algorithm (required)',
+                   type   => 'select',
+                   options=> ['COMP128_1',
+                              'COMP128_2',
+                              'COMP128_3',
+                              'MILENAGE' ],
+                 },
+  'opcvalue'  => { label=>'OPC value (for MILENAGE only)' },
+  'opsno'     => { label=>'OP serial number (for MILENAGE only)' },
+  'timeout'   => { label=>'Timeout (seconds)', default => 120 },
   'debug'     => { label=>'Enable debugging', type=>'checkbox' },
 ;
 
@@ -32,17 +49,21 @@ set on the service, and the template must exist.
 END
 );
 
+sub actions {
+  'Import SIMs' => 'misc/part_export/huawei_hlr-import_sim.html'
+}
+
 sub _export_insert {
   my( $self, $svc_phone ) = (shift, shift);
   # svc_phone::check should ensure phonenum and sim_imsi are numeric
   my @command = (
-    'ADD TPLSUB',
     IMSI   => '"'.$svc_phone->sim_imsi.'"',
-    ISDN   => '"'.$svc_phone->phonenum.'"',
+    ISDN   => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"',
     TPLID  => $self->option('tplid'),
   );
   unshift @command, 'HLRSN', $self->option('hlrsn')
     if $self->option('hlrsn');
+  unshift @command, 'ADD TPLSUB';
   my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command);
   ref($err_or_queue) ? '' : $err_or_queue;
 }
@@ -53,7 +74,7 @@ sub _export_replace  {
   if ( $new->sim_imsi ne $old->sim_imsi ) {
     my @command = (
       'MOD IMSI',
-      ISDN    => '"'.$old->phonenum.'"',
+      ISDN    => '"'.$old->countrycode.$old->phonenum.'"',
       IMSI    => '"'.$old->sim_imsi.'"',
       NEWIMSI => '"'.$new->sim_imsi.'"',
     );
@@ -61,11 +82,12 @@ sub _export_replace  {
     return $err_or_queue unless ref $err_or_queue;
     $depend_jobnum = $err_or_queue->jobnum;
   }
-  if ( $new->phonenum ne $old->phonenum ) {
+  if ( $new->countrycode ne $old->countrycode or 
+       $new->phonenum ne $old->phonenum ) {
     my @command = (
       'MOD ISDN',
-      ISDN    => '"'.$old->phonenum.'"',
-      NEWISDN => '"'.$new->phonenum.'"',
+      ISDN    => '"'.$old->countrycode.$old->phonenum.'"',
+      NEWISDN => '"'.$new->countrycode.$new->phonenum.'"',
     );
     my $err_or_queue = $self->queue_command($new->svcnum, @command);
     return $err_or_queue unless ref $err_or_queue;
@@ -94,7 +116,7 @@ sub _export_lock {
   my @command = (
     'MOD LCK',
     IMSI    => '"'.$svc_phone->sim_imsi.'"',
-    ISDN    => '"'.$svc_phone->phonenum.'"',
+    ISDN    => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"',
     IC      => $lockstate,
     OC      => $lockstate,
     GPRSLOCK=> $lockstate,
@@ -107,8 +129,8 @@ sub _export_delete {
   my( $self, $svc_phone ) = (shift, shift);
   my @command = (
     'RMV SUB',
-    IMSI    => '"'.$svc_phone->sim_imsi.'"',
-    ISDN    => '"'.$svc_phone->phonenum.'"',
+    #IMSI    => '"'.$svc_phone->sim_imsi.'"',
+    ISDN    => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"',
   );
   my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command);
   ref($err_or_queue) ? '' : $err_or_queue;
@@ -179,15 +201,16 @@ sub command {
     $string .= shift(@param) . '=' . shift(@param);
     $string .= ',' if @param;
   }
-  $string .= "\n";
+  $string .= "\n;";
   my @result;
   eval { # timeout
     local $SIG{ALRM} = sub { die "timeout\n" };
-    alarm ($self->option('timeout') || 30);
+    alarm ($self->option('timeout') || 120);
     warn "Sending to server:\n$string\n\n" if $DEBUG;
     $socket->print($string);
     warn "Received:\n";
     my $line;
+    local $/ = "\r\n";
     do {
       $line = $socket->getline();
       warn $line if $DEBUG;
@@ -203,11 +226,10 @@ sub command {
     return { error => $@ };
   } else {
     #+++    HLR9820        <date> <time>\n
-    # skip empty lines
     my $header = shift(@result);
-    return { error => 'malformed response: '.$header }
-      unless $header =~ /^\+\+\+/;
-    $return{header} = $header;
+    $header =~ /(\+\+\+.*)/
+      or return { error => 'malformed response: '.$header };
+    $return{header} = $1;
     #SMU    #<serial number>\n
     $return{smu} = shift(@result);
     #%%<command string>%%\n 
@@ -225,4 +247,92 @@ sub command {
   \%return;
 }
 
+sub process_import_sim {
+  my $job = shift;
+  my $param = shift;
+  $param->{'job'} = $job;
+  my $exportnum = delete $param->{'exportnum'};
+  my $export = __PACKAGE__->by_key($exportnum);
+  my $file = delete $param->{'uploaded_files'};
+  $file =~ s/^file://;
+  my $dir = $FS::UID::cache_dir .'/cache.'. $FS::UID::datasrc;
+  open( $param->{'filehandle'}, '<', "$dir/$file" )
+    or die "unable to open '$file'.\n";
+  my $error = $export->import_sim($param);
+}
+
+sub import_sim {
+  # import a SIM list
+  local $FS::UID::AutoCommit = 1; # yes, 1
+  my $self = shift;
+  my $param = shift;
+  my $job = $param->{'job'};
+  my $fh = $param->{'filehandle'};
+  my @lines = $fh->getlines;
+
+  my @command = 'ADD KI';
+  push @command, ('HLRSN', $self->option('hlrsn')) if $self->option('hlrsn');
+
+  my @args = ('OPERTYPE', 'ADD');
+  push @args, ('K4SNO', $self->option('k4sno')) if $self->option('k4sno');
+  push @args, ('CARDTYPE', $self->option('cardtype'),
+               'ALG',      $self->option('alg'));
+  push @args, ('OPCVALUE', $self->option('opcvalue'),
+               'OPSNO',    $self->option('opsno'))
+    if $self->option('alg') eq 'MILENAGE';
+
+  my $agentnum = $param->{'agentnum'};
+  my $classnum = $param->{'classnum'};
+  my $class = FS::inventory_class->by_key($classnum)
+    or die "bad inventory class $classnum\n";
+  my %existing = map { $_->item, 1 } 
+    qsearch('inventory_item', { 'classnum' => $classnum });
+
+  my $socket = $self->login;
+  my $num=0;
+  my $total = scalar(@lines);
+  foreach my $line (@lines) {
+    $num++;
+    $job->update_statustext(int(100*$num/$total).',Provisioning IMSIs...')
+      if $job;
+
+    chomp $line;
+    my ($imsi, $iccid, $pin1, $puk1, $pin2, $puk2, $acc, $ki) = 
+      split(' ', $line);
+    # the only fields we really care about are the IMSI and KI.
+    if ($imsi !~ /^\d{15}$/ or $ki !~ /^[0-9A-Z]{32}$/) {
+      warn "misspelled line in SIM file: $line\n";
+      next;
+    }
+    if ($existing{$imsi}) {
+      warn "IMSI $imsi already in inventory, skipped\n";
+      next;
+    }
+
+    # push IMSI/KI to the HLR
+    my $return = $self->command($socket,
+      @command,
+      'IMSI', qq{"$imsi"},
+      'KIVALUE', qq{"$ki"},
+      @args
+    );
+    if ( $return->{success} ) {
+      # add to inventory
+      my $item = FS::inventory_item->new({
+          'classnum'  => $classnum,
+          'agentnum'  => $agentnum,
+          'item'      => $imsi,
+      });
+      my $error = $item->insert;
+      if ( $error ) {
+        die "IMSI $imsi added to HLR, but not to inventory:\n$error\n";
+      }
+    } else {
+      die "IMSI $imsi could not be added to HLR:\n".$return->{error}."\n";
+    }
+  } #foreach $line
+  $self->logout($socket);
+  return;
+}
+
 1;