qualification address handling changes, RT#7111
[freeside.git] / FS / FS / part_export / ikano.pm
index afed6f4..b04defa 100644 (file)
@@ -40,6 +40,35 @@ END
 
 sub rebless { shift; }
 
+sub external_pkg_map { 1; }
+
+sub location_types {
+  (
+    ''     => '(None)',
+    'APT'  => 'Apartment',
+    'BLDG' => 'Building',
+    'FLR'  => 'Floor',
+    'LOT'  => 'Lot',
+    'RM'   => 'Room',
+    'SLIP' => 'Slip',
+    'SUIT' => 'Suite',
+    'TRLR' => 'Trailer',
+    'UNIT' => 'Unit',
+    'WING' => 'Wing',
+  );
+}
+
+sub location_types_parse {
+  my $class = shift;
+  my %t = $class->location_types;
+  delete $t{''};
+  (
+    (map { $_ => $_ } keys %t),
+    (reverse %t),
+    'STE' => 'SUIT', #USPS
+  );
+}
+
 sub dsl_pull {
 # we distinguish between invalid new data (return error) versus data that
 # has legitimately changed (may eventually execute hooks; now just update)
@@ -50,7 +79,6 @@ sub dsl_pull {
 # vendor_order_id, vendor_qual_id, vendor_order_type, pushed, monitored,
 # last_pull, address (from qual), contact info, ProductCustomId
     my($self, $svc_dsl, $threshold) = (shift, shift, shift);
-    $self->loadmod;
     my $result = $self->valid_order($svc_dsl,'pull');
     return $result unless $result eq '';
 
@@ -257,12 +285,16 @@ sub ikano2fsnote {
      } );
 }
 
+# address always required for Ikano qual, TN optional (assume dry if not given)
 sub qual {
     my($self,$qual) = (shift,shift);
-# address always required for Ikano qual, TN optional (assume dry if not given)
 
-    my %location_hash = $qual->location; 
-    return 'No address provided' unless %location_hash;
+    my %location_hash = $qual->location_hash; 
+    return 'No address provided' unless keys %location_hash;
+
+    warn Dumper(\%location_hash);
+    return 'Location kind is required' unless $location_hash{location_kind};
+
     my $svctn = $qual->phonenum;
 
     my $result = $self->ikano_command('PREQUAL',
@@ -312,7 +344,7 @@ sub qual {
     };
 }
 
-sub qual_html {
+sub qual_result {
     my($self,$qual) = (shift,shift);
     
     my %qual_options = $qual->options;
@@ -324,21 +356,52 @@ sub qual_html {
                && $optionvalue ne '' );
     }
 
-    # XXX: eventually perhaps this should return both the packages a link to
-    # order each package and go to the svc prov with the prequal id filled in
-    # but only if cust, not prospect!
-    my $list = "<B>Qualifying Packages:</B><UL>";
+    my %pkglist = ();
+    my $result = { 'header' => 'Qualifying Packages',
+                  'pkglist' => \%pkglist,
+                };
+
     my @part_pkgs = qsearch( 'part_pkg', { 'disabled' => '' } );
     foreach my $part_pkg ( @part_pkgs ) {
-       my $externalid = $part_pkg->option('externalid',1);
-       if ( $externalid ) {
-           $list .= "<LI>".$part_pkg->pkgpart.": ".$part_pkg->pkg." - "
-               .$part_pkg->comment."</LI>" 
-             if grep( $_ eq $externalid, @externalids );
+       my %vendor_pkg_ids = $part_pkg->vendor_pkg_ids;
+       my $externalid = $vendor_pkg_ids{$self->exportnum} 
+           if defined $vendor_pkg_ids{$self->exportnum};
+       if ( $externalid && grep( $_ eq $externalid, @externalids )) {
+           $pkglist{$part_pkg->pkgpart} = $part_pkg->pkg." - ".$part_pkg->comment;
        }
     }
-    $list .= "</UL>";
-    $list;
+
+    $result;
+}
+
+sub quals_by_cust_and_pkg { 
+    my($self, $custnum, $pkgpart) = (shift,shift,shift);
+
+    die "invalid custnum or pkgpart"
+       unless ($custnum =~ /^\d+$/ && $pkgpart =~ /^\d+$/);
+
+    my $part_pkg = qsearchs('part_pkg', { 'pkgpart' => $pkgpart } );
+    die "no part_pkg found" unless $part_pkg;
+    my %vendor_pkg_ids = $part_pkg->vendor_pkg_ids;
+    my $external_id = $vendor_pkg_ids{$self->exportnum};
+    die "no vendor package id defined on this package" unless $external_id;
+    
+    my $extra_sql = "where custnum = $custnum or locationnum in (select "
+       . "locationnum from cust_location where custnum = $custnum)";
+    my @quals = qsearch( { 'table' => 'qual', 'extra_sql' => $extra_sql, } );
+
+    my @filtered_quals;
+    foreach my $qual ( @quals ) {
+       my %qual_options = $qual->options;
+       my( $optionname, $optionvalue );
+       while (($optionname, $optionvalue) = each %qual_options) {
+          push @filtered_quals, $qual
+             if ( $optionname =~ /^ikano_Network_(\d+)_ProductGroup_(\d+)_Product_(\d+)_ProductCustomId$/
+                   && $optionvalue eq $external_id );
+       }
+    }
+
+    @filtered_quals;
 }
 
 sub notes_html { 
@@ -401,9 +464,10 @@ sub valid_order {
            &&  $svc_dsl->vendor_qual_id
            );
   return 'Missing or invalid order data' if $error;
-  
+  my %vendor_pkg_ids = $svc_dsl->cust_svc->cust_pkg->part_pkg->vendor_pkg_ids;
   return 'Package does not have an external id configured'
-    if $svc_dsl->cust_svc->cust_pkg->part_pkg->options('externalid',1) eq '';
+    unless defined $vendor_pkg_ids{$self->exportnum};
 
   return 'No valid qualification for this order' 
     unless qsearch( 'qual', { 'vendor_qual_id' => $svc_dsl->vendor_qual_id });
@@ -413,7 +477,7 @@ sub valid_order {
   if($svc_dsl->vendor_order_type eq 'NEW') {
     if($svc_dsl->pushed) {
        $error = !( ($action eq 'pull' || $action eq 'statuschg' 
-                       || $action eq 'delete')
+                       || $action eq 'delete' || $action eq 'expire')
            &&  length($svc_dsl->vendor_order_id) > 0
            &&  length($svc_dsl->vendor_order_status) > 0
                );
@@ -461,8 +525,6 @@ sub qual2termsid {
 sub _export_insert {
   my( $self, $svc_dsl ) = (shift, shift);
 
-  $self->loadmod;
-
   my $result = $self->valid_order($svc_dsl,'insert');
   return $result unless $result eq '';
 
@@ -470,7 +532,8 @@ sub _export_insert {
   my $contactTN = $svc_dsl->cust_svc->cust_pkg->cust_main->daytime;
   $contactTN =~ s/[^0-9]//g;
 
-  my $ProductCustomId = $svc_dsl->cust_svc->cust_pkg->part_pkg->option('externalid',1);
+  my %vendor_pkg_ids = $svc_dsl->cust_svc->cust_pkg->part_pkg->vendor_pkg_ids;
+  my $ProductCustomId = $vendor_pkg_ids{$self->exportnum};
 
   my $args = {
        orderType => 'NEW',
@@ -549,7 +612,7 @@ sub _export_delete {
   return $result unless $result eq '';
 
   # for now allow an immediate cancel only on New orders in New/Pending status
-  #XXX: add support for Chance and Cancel orders in New/Pending status later
+  #XXX: add support for Change and Cancel orders in New/Pending status later
 
   if($svc_dsl->vendor_order_type eq 'NEW') {
     if($svc_dsl->vendor_order_status eq 'NEW' 
@@ -570,6 +633,10 @@ sub _export_delete {
        return "Cannot cancel a NEW order unless it's in NEW or PENDING status";
     }
   }
+  elsif($svc_dsl->vendor_order_type eq 'CANCEL') {
+     return 'Cannot cancel a CANCEL order unless expire was set'
+       unless $svc_dsl->cust_svc->cust_pkg->expire > 0;
+  }
   else {
     return 'Canceling orders other than NEW orders is not currently implemented';
   }
@@ -577,6 +644,84 @@ sub _export_delete {
   '';
 }
 
+sub export_expire {
+  my($self, $svc_dsl, $date) = (shift, shift, shift);
+  
+  my $result = $self->valid_order($svc_dsl,'expire');
+  return $result unless $result eq '';
+  
+  # for now allow a proper cancel only on New orders in Completed status
+  #XXX: add support for some other cases in future
+  
+  if($svc_dsl->vendor_order_type eq 'NEW' 
+       && $svc_dsl->vendor_order_status eq 'COMPLETED') {
+    
+         my $contactTN = $svc_dsl->cust_svc->cust_pkg->cust_main->daytime;
+         $contactTN =~ s/[^0-9]//g;
+
+         my %vendor_pkg_ids = $svc_dsl->cust_svc->cust_pkg->part_pkg->vendor_pkg_ids;
+         my $ProductCustomId = $vendor_pkg_ids{$self->exportnum};
+
+         # we are now a cancel order
+         $svc_dsl->desired_due_date($date);
+         $svc_dsl->vendor_order_type('CANCEL');
+
+         my $args = {
+               orderType => 'CANCEL',
+               ProductCustomId => $ProductCustomId,
+               TermsId => $self->qual2termsid($svc_dsl->vendor_qual_id,$ProductCustomId),
+               DSLPhoneNumber => $svc_dsl->loop_type eq '0' ? 'STANDALONE'
+                                                           : $svc_dsl->phonenum,
+               Password => $svc_dsl->password,
+               PrequalId => $svc_dsl->vendor_qual_id,
+               CompanyName => $svc_dsl->company,
+               FirstName => $svc_dsl->first,
+               LastName => $svc_dsl->last,
+               MiddleName => '',
+               ContactMethod => 'PHONE',
+               ContactPhoneNumber => $contactTN,
+               ContactEmail => 'x@x.xx',
+               ContactFax => '',
+               DateToOrder => time2str("%Y-%m-%d",$date),
+               RequestClientIP => '127.0.0.1',
+               IspChange => 'NO',
+               IspPrevious => '',
+               CurrentProvider => '',
+         };
+
+         $args->{'VirtualPhoneNumber'} = $svc_dsl->phonenum 
+           if $svc_dsl->loop_type eq '0';
+
+         $result = $self->ikano_command('ORDER',$args); 
+         return $result unless ref($result); # scalar (string) is an error
+
+         # now we're getting an OrderResponse which should have one Order in it
+         warn "$me _export_insert OrderResponse hash:\n".Dumper($result)
+               if $self->option('debug');
+         
+         return 'Invalid order response' unless defined $result->{'Order'};
+         $result = $result->{'Order'};
+
+         return 'No/invalid order id or status returned' 
+           unless defined $result->{'Status'} && defined $result->{'OrderId'}
+               && grep($_ eq $result->{'Status'}, @Net::Ikano::orderStatus);
+
+         $svc_dsl->pushed(time);
+         $svc_dsl->last_pull((time)+1); 
+         $svc_dsl->vendor_order_id($result->{'OrderId'});
+         $svc_dsl->vendor_order_status($result->{'Status'});
+         $svc_dsl->monitored('Y');
+         local $FS::svc_Common::noexport_hack = 1;
+         $result = $svc_dsl->replace; 
+         return "Error setting DSL fields: $result" if $result;
+  }
+  else {
+    return "Cancelling anything other than NEW orders in COMPLETED status is "
+       . "not currently implemented";
+  }
+ '';
+}
+
 sub statuschg {
   my( $self, $svc_dsl, $type ) = (shift, shift, shift);