diff options
| -rw-r--r-- | FS/FS/Schema.pm | 4 | ||||
| -rw-r--r-- | FS/FS/part_export/ikano.pm | 210 | ||||
| -rw-r--r-- | FS/FS/svc_dsl.pm | 4 | ||||
| -rwxr-xr-x | FS/bin/freeside-pull-dsl | 71 | ||||
| -rw-r--r-- | httemplate/edit/svc_dsl.cgi | 2 | ||||
| -rw-r--r-- | httemplate/view/svc_dsl.cgi | 6 | 
6 files changed, 225 insertions, 72 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 281ca6ba1..e482bedfa 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1844,8 +1844,8 @@ sub tables_hashref {  	'due_date',     'int', 'NULL',       '', '', '',          'vendor_order_id',              'varchar', 'NULL', $char_d,  '', '',          'vendor_qual_id',              'varchar', 'NULL', $char_d,  '', '', -        'vendor_order_type',   'char', 'NULL',       1,  '', '',  -        'vendor_order_status',   'char', 'NULL',       1,  '', '',  +        'vendor_order_type',   'varchar', 'NULL', $char_d,  '', '', +        'vendor_order_status',   'varchar', 'NULL', $char_d,  '', '',          'first',              'varchar', 'NULL', $char_d,  '', '',          'last',              'varchar', 'NULL', $char_d,  '', '',          'company',              'varchar', 'NULL', $char_d,  '', '', diff --git a/FS/FS/part_export/ikano.pm b/FS/FS/part_export/ikano.pm index 91953e320..444e9b878 100644 --- a/FS/FS/part_export/ikano.pm +++ b/FS/FS/part_export/ikano.pm @@ -1,17 +1,15 @@  package FS::part_export::ikano; -use vars qw(@ISA %info %orderType %orderStatus %loopType $DEBUG $me); +use vars qw(@ISA %info %loopType $me);  use Tie::IxHash;  use Date::Format qw( time2str );  use Date::Parse qw( str2time );  use FS::Record qw(qsearch qsearchs dbh);  use FS::part_export;  use FS::svc_dsl; -use FS::dsl_note;  use Data::Dumper;  @ISA = qw(FS::part_export); -$DEBUG = 1;  $me= '[' .  __PACKAGE__ . ']';  tie my %options, 'Tie::IxHash', @@ -23,6 +21,7 @@ tie my %options, 'Tie::IxHash',    'check_networks' => { label => 'Check Networks',  		    default => 'ATT,BELLCA',  		    }, +  'debug' => { label => 'Debug Mode',  type => 'checkbox' },  ;  %info = ( @@ -35,12 +34,6 @@ Requires installation of  END  ); -%orderType = ( 'N' => 'NEW', 'X' => 'CANCEL', 'C' => 'CHANGE' ); -%orderStatus = ('N' => 'NEW', -		'P' => 'PENDING', -		'X' => 'CANCELLED', -		'C' => 'COMPLETED', -		'E' => 'ERROR' );  %loopType = ( '' => 'Line-share', '0' => 'Standalone' );  sub rebless { shift; } @@ -54,8 +47,8 @@ sub dsl_pull {  # current assumptions of what won't change (from their side):  # vendor_order_id, vendor_qual_id, vendor_order_type, pushed, monitored,  # last_pull, address (from qual), contact info, ProductCustomId -      my($self, $svc_dsl) = (shift, shift); +    $self->loadmod;      my $result = $self->valid_order($svc_dsl,'pull');      return $result unless $result eq ''; @@ -64,7 +57,8 @@ sub dsl_pull {      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 pull OrderResponse hash:\n".Dumper($result) if $DEBUG; +    warn "$me pull OrderResponse hash:\n".Dumper($result)  +	if $self->option('debug');      return 'Invalid order response' unless defined $result->{'Order'};      $result = $result->{'Order'}; @@ -84,13 +78,13 @@ sub dsl_pull {      my $dbh = dbh;      # 1. status  -    my $new_order_status = $self->orderstatus_long2short($result->{'Status'}); -    return 'Invalid new status' if $new_order_status eq ''; -    if($svc_dsl->vendor_order_status ne $new_order_status) { -	$svc_dsl->monitored('')  -	    if ($new_order_status eq 'X' || $new_order_status eq 'C'); -	$svc_dsl->vendor_order_status($new_order_status); -    } +    my $order_status = grep($_ eq $result->{'Status'}, @Net::Ikano::orderStatus) +			    ? $result->{'Status'} : ''; +    return 'Invalid new status' if $order_status eq ''; +    $svc_dsl->vendor_order_status($order_status)  +	if($svc_dsl->vendor_order_status ne $order_status); +    $svc_dsl->monitored('')  +	    if ($order_status eq 'CANCELLED' || $order_status eq 'COMPLETED');      # 2. fields we don't care much about      my %justUpdate = ( 'first' => 'FirstName', @@ -114,9 +108,9 @@ sub dsl_pull {  # TN may change only if sub changes it and New or Change order in Completed status  	my $tn = $product->{'PhoneNumber'};  	if($tn ne $svc_dsl->phonenum) { -	    if( ($svc_dsl->vendor_order_type eq 'N'  -		|| $svc_dsl->vendor_order_type eq 'C') -	       && $svc_dsl->vendor_order_status eq 'C' ) { +	    if( ($svc_dsl->vendor_order_type eq 'NEW'  +		|| $svc_dsl->vendor_order_type eq 'CHANGE') +	       && $svc_dsl->vendor_order_status eq 'COMPLETED' ) {  		$svc_dsl->phonenum($tn);  	    }  	    else { return 'TN has changed in an invalid state'; } @@ -128,10 +122,10 @@ sub dsl_pull {  	    if $product->{'PhoneNumber'} ne 'STANDALONE';  	my $tn = $product->{'VirtualPhoneNumber'};  	if($tn ne $svc_dsl->phonenum) { -	    if( ($svc_dsl->vendor_order_type eq 'N'  -		|| $svc_dsl->vendor_order_type eq 'C') -	      && $svc_dsl->vendor_order_status ne 'C' -	      && $svc_dsl->vendor_order_status ne 'X') { +	    if( ($svc_dsl->vendor_order_type eq 'NEW'  +		|| $svc_dsl->vendor_order_type eq 'CHANGE') +	      && $svc_dsl->vendor_order_status ne 'COMPLETED' +	      && $svc_dsl->vendor_order_status ne 'CANCELLED') {  		$svc_dsl->phonenum($tn);  	    }  	    else { return 'TN has changed in an invalid state'; } @@ -139,39 +133,58 @@ sub dsl_pull {      }      # 4. desired_due_date - may change if manually changed -    if($svc_dsl->vendor_order_type eq 'N'  -	    || $svc_dsl->vendor_order_type eq 'C'){ +    if($svc_dsl->vendor_order_type eq 'NEW'  +	    || $svc_dsl->vendor_order_type eq 'CHANGE'){  	my $f = str2time($product->{'DateToOrder'});  	return 'Invalid DateToOrder' unless $f; -	$svc_dsl->desired_due_date($f) if $svc_dsl->desired_due_date != $f; +	$svc_dsl->desired_due_date($f) if $svc_dsl->desired_due_date ne $f;  	# XXX: optionally sync back to start_date or whatever...       } -    elsif($svc_dsl->vendor_order_type eq 'X'){ +    elsif($svc_dsl->vendor_order_type eq 'CANCEL'){  	my $f = str2time($product->{'DateToDisconnect'});  	return 'Invalid DateToDisconnect' unless $f; -	$svc_dsl->desired_due_date($f) if $svc_dsl->desired_due_date != $f; +	$svc_dsl->desired_due_date($f) if $svc_dsl->desired_due_date ne $f;  	# XXX: optionally sync back to expire or whatever...       }      # 5. due_date -    if($svc_dsl->vendor_order_type eq 'N'  - 	  || $svc_dsl->vendor_order_type eq 'C') { +    if($svc_dsl->vendor_order_type eq 'NEW'  + 	  || $svc_dsl->vendor_order_type eq 'CHANGE') {  	my $f = str2time($product->{'ActivationDate'}); -	if($svc_dsl->vendor_order_status ne 'N') { +	if($svc_dsl->vendor_order_status ne 'NEW' +	    && $svc_dsl->vendor_order_status ne 'CANCELLED') {  	    return 'Invalid ActivationDate' unless $f; -	    $svc_dsl->due_date($f) if $svc_dsl->due_date != $f; +	    $svc_dsl->due_date($f) if $svc_dsl->due_date ne $f;  	}      }      # Ikano API does not implement the proper disconnect date,      # so we can't do anything about it      # 6. staticips - for now just comma-separate them -    my @statics = $result->{'StaticIps'}; -# XXX +    my $tstatics = $result->{'StaticIps'}; +    my @istatics = defined $tstatics ? @$tstatics : (); +    my $ostatics = $svc_dsl->staticips; +    my @ostatics = split(',',$ostatics); +    # more horrible search/sync code below... +    my $staticsChanged = 0; +    foreach $istatic ( @istatics ) { # they have, we don't +	unless ( grep($_ eq $istatic, @ostatics) ) { +	    push @ostatics, $istatic; +	    $staticsChanged = 1; +	} +    } +    for(my $i=0; $i < scalar(@ostatics); $i++) { +	unless ( grep($_ eq $ostatics[$i], @istatics) ) { +	    splice(@ostatics,$i,1); +	    $i--; +	    $staticsChanged = 1; +	} +    } +    $svc_dsl->staticips(join(',',@ostatics)) if $staticsChanged;      # 7. notes - put them into the common format and compare      my $tnotes = $result->{'OrderNotes'};  -    my @tnotes = @$tnotes; +    my @tnotes = defined $tnotes ? @$tnotes : ();      my @inotes = (); # all Ikano OrderNotes as FS::dsl_note objects      my $notesChanged = 0;       foreach $tnote ( @tnotes ) { @@ -260,38 +273,37 @@ sub loop_type_long { # sub, not a method      return $loopType{$svc_dsl->loop_type};  } -sub status_line { -    my($self,$svc_dsl) = (shift,shift); -    return "Ikano ".$orderType{$svc_dsl->vendor_order_type}." order #" -	. $svc_dsl->vendor_order_id . " (Status: "  -	. $orderStatus{$svc_dsl->vendor_order_status} . ")"; -} -  sub ikano_command {    my( $self, $command, $args ) = @_; -  eval "use Net::Ikano;"; -  die $@ if $@; +  $self->loadmod;    my $ikano = Net::Ikano->new(      'keyid' => $self->option('keyid'),      'username'  => $self->option('username'),      'password'  => $self->option('password'), -    'debug'    => 1, -    #'reqpreviewonly' => 1, +    'debug'    => $self->option('debug'),    );    $ikano->$command($args);  } +sub loadmod { +  eval "use Net::Ikano;"; +  die $@ if $@; +} +  sub valid_order {    my( $self, $svc_dsl, $action ) = (shift, shift, shift); +  +  $self->loadmod; -  warn "$me valid_order action=$action svc_dsl:\n". Dumper($svc_dsl) if $DEBUG; +  warn "$me valid_order action=$action svc_dsl:\n". Dumper($svc_dsl) +	if $self->option('debug');    # common to all order types/status/loop_type    my $error = !($svc_dsl->desired_due_date -	    &&  defined $orderType{$svc_dsl->vendor_order_type} +	    &&  grep($_ eq $svc_dsl->vendor_order_type, @Net::Ikano::orderType)  	    &&  $svc_dsl->first  	    &&	$svc_dsl->last  	    &&	defined $svc_dsl->loop_type @@ -307,13 +319,17 @@ sub valid_order {    # now go by order type    # weird ifs & long lines for readability and ease of understanding - don't change -  if($svc_dsl->vendor_order_type eq 'N') { +  if($svc_dsl->vendor_order_type eq 'NEW') {      if($svc_dsl->pushed) { -	$error = !($action eq 'pull' +	$error = !( ($action eq 'pull' || $action eq 'statuschg'  +			|| $action eq 'delete')  	    && 	length($svc_dsl->vendor_order_id) > 0  	    && 	length($svc_dsl->vendor_order_status) > 0  		);  	return 'Invalid order data' if $error; + +	return 'Phone number required for status change' +	    if ($action eq 'statuschg' && length($svc_dsl->phonenum) < 1);      }      else { # unpushed New order - cannot do anything other than push it  	$error = !($action eq 'insert' @@ -326,9 +342,9 @@ sub valid_order {  	return 'Invalid order data' if $error;      }    } -  elsif($svc_dsl->vendor_order_type eq 'X') { +  elsif($svc_dsl->vendor_order_type eq 'CANCEL') {    } -  elsif($svc_dsl->vendor_order_type eq 'C') { +  elsif($svc_dsl->vendor_order_type eq 'CHANGE') {    }   ''; @@ -350,16 +366,11 @@ sub qual2termsid {      '';  } -sub orderstatus_long2short { -    my ($self,$order_status) = (shift,shift); -    my %rorderStatus = reverse %orderStatus; -    return $rorderStatus{$order_status} if exists $rorderStatus{$order_status}; -    ''; -} -  sub _export_insert {    my( $self, $svc_dsl ) = (shift, shift); +  $self->loadmod; +    my $result = $self->valid_order($svc_dsl,'insert');    return $result unless $result eq ''; @@ -396,18 +407,20 @@ sub _export_insert {    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 $DEBUG; +  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 order id or status returned'  -    unless defined $result->{'Status'} && defined $result->{'OrderId'}; +  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($self->orderstatus_long2short($result->{'Status'})); +  $svc_dsl->vendor_order_status($result->{'Status'});    $svc_dsl->username($result->{'Username'});    local $FS::svc_Common::noexport_hack = 1;    local $FS::UID::AutoCommit = 0; @@ -418,22 +431,85 @@ sub _export_insert {  sub _export_replace {    my( $self, $new, $old ) = (shift, shift, shift); +# XXX only supports password changes now, but should return error if  +# another change is attempted? + +  if($new->password ne $old->password) { +      my $result = $self->valid_order($new,'statuschg'); +      return $result unless $result eq ''; +       +      $result = $self->ikano_command('PASSWORDCHANGE', +	    { DSLPhoneNumber => $new->phonenum, +	      NewPassword => $new->password, +	    } );  +      return $result unless ref($result); # scalar (string) is an error + +      return 'Error changing password' unless defined $result->{'Password'} +	&& $result->{'Password'} eq $new->password; +  } +    '';  }  sub _export_delete {    my( $self, $svc_dsl ) = (shift, shift); +   +  my $result = $self->valid_order($svc_dsl,'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 + +  if($svc_dsl->vendor_order_type eq 'NEW') { +    if($svc_dsl->vendor_order_status eq 'NEW'  +	    || $svc_dsl->vendor_order_status eq 'PENDING') { +	my $result = $self->ikano_command('CANCEL',  +	    { OrderId => $svc_dsl->vendor_order_id, } ); +	return $result unless ref($result); # scalar (string) is an error + +	return $self->dsl_pull($svc_dsl); +    } +    else { +	return "Cannot cancel a NEW order unless it's in NEW or PENDING status"; +    } +  } +  else { +    return 'Canceling orders other than NEW orders is not currently implemented'; +  } +    '';  } +sub statuschg { +  my( $self, $svc_dsl, $type ) = (shift, shift, shift); + +  my $result = $self->valid_order($svc_dsl,'statuschg'); +  return $result unless $result eq ''; + +  # get the DSLServiceId +  $result = $self->ikano_command('CUSTOMERLOOKUP', +	{ PhoneNumber => $svc_dsl->phonenum } );  +  return $result unless ref($result); # scalar (string) is an error +  return 'No DSLServiceId found' unless defined $result->{'DSLServiceId'}; +  my $DSLServiceId = $result->{'DSLServiceId'}; + +  $result = $self->ikano_command('ACCOUNTSTATUSCHANGE', +	{ DSLPhoneNumber => $svc_dsl->phonenum, +	  DSLServiceId => $DSLServiceId, +	  type => $type, +	} );  +  return $result unless ref($result); # scalar (string) is an error +  '';  +} +  sub _export_suspend {    my( $self, $svc_dsl ) = (shift, shift); -  ''; +  $self->statuschg($svc_dsl,"SUSPEND");  }  sub _export_unsuspend {    my( $self, $svc_dsl ) = (shift, shift); -  ''; +  $self->statuschg($svc_dsl,"UNSUSPEND");  }  1; diff --git a/FS/FS/svc_dsl.pm b/FS/FS/svc_dsl.pm index 2c30b006e..93d357042 100644 --- a/FS/FS/svc_dsl.pm +++ b/FS/FS/svc_dsl.pm @@ -4,6 +4,8 @@ use strict;  use vars qw( @ISA $conf $DEBUG $me );  use FS::Record qw( qsearch qsearchs );  use FS::svc_Common; +use FS::dsl_note; +use FS::qual;  @ISA = qw( FS::svc_Common );  $DEBUG = 0; @@ -238,7 +240,7 @@ sub check {      || $self->ut_numbern('due_date')      || $self->ut_textn('vendor_order_id')      || $self->ut_textn('vendor_qual_id') -    || $self->ut_alpha('vendor_order_type') +    || $self->ut_alphan('vendor_order_type')      || $self->ut_alphan('vendor_order_status')      || $self->ut_text('first')      || $self->ut_text('last') diff --git a/FS/bin/freeside-pull-dsl b/FS/bin/freeside-pull-dsl new file mode 100755 index 000000000..d0aa921cc --- /dev/null +++ b/FS/bin/freeside-pull-dsl @@ -0,0 +1,71 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Conf; +use FS::Record qw(qsearch qsearchs dbh); +use FS::svc_dsl; +use FS::part_export; +use Data::Dumper; + +&untaint_argv;	#what it sounds like  (eww) +use vars qw(%opt); + +my $user = shift or die &usage; +adminsuidsetup $user; + +my @monitored = qsearch('svc_dsl', { 'monitored' => 'Y' } ); +foreach my $svc_dsl ( @monitored ) { +    my @exports = $svc_dsl->part_svc->part_export_dsl_pull; +    my $svcnum = $svc_dsl->svcnum; +    warn "either zero or more than one DSL-pulling export attached to svcnum " +	. "$svcnum, skipping" if ( scalar(@exports) != 1 ); +    my $export = $exports[0]; +    my $error = $export->dsl_pull($svc_dsl); # this will commit to db by default +    warn "Error pulling DSL svcnum $svcnum: $error" unless $error eq ''; +} + +### +# subroutines +### + +sub untaint_argv { +  foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV +    #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; +    # Date::Parse +    $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; +    $ARGV[$_]=$1; +  } +} + +sub usage { +  die "Usage:\n\n  freeside-pull-dsl \n"; +} + +### +# documentation +### + +=head1 NAME + +freeside-pull-dsl - Pull DSL order data from telcos/vendors for all monitored DSL orders to update + +=head1 SYNOPSIS + +  freeside-pull-dsl user + +=head1 DESCRIPTION + +user - name of an internal Freeside user + +This is still a work in progress - in future may add limiting by exportnum or svcpart or other such stuff. + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::cust_main>, config.html from the base documentation + +=cut + diff --git a/httemplate/edit/svc_dsl.cgi b/httemplate/edit/svc_dsl.cgi index 4fca0cd9f..79aeb1a10 100644 --- a/httemplate/edit/svc_dsl.cgi +++ b/httemplate/edit/svc_dsl.cgi @@ -113,7 +113,7 @@ my $new_cb = sub {  		  value => $vendor_qual_id,  },  		{ field => 'vendor_order_type',   		  type => 'hidden',  -		  value => 'N' }, +		  value => 'NEW' },  		{ field => 'desired_due_date',  		  type => 'fixed',  		  formatted_value =>  diff --git a/httemplate/view/svc_dsl.cgi b/httemplate/view/svc_dsl.cgi index 14c1eb26d..7bbdca1c7 100644 --- a/httemplate/view/svc_dsl.cgi +++ b/httemplate/view/svc_dsl.cgi @@ -51,12 +51,16 @@ my $svc_cb = sub {  	    'last',  	    'company'  ); +    my $status = '';      if($export->exporttype eq 'ikano') {  	push @fields, qw ( username password isp_chg isp_prev staticips ); +	$status = "Ikano " . $svc_x->vendor_order_type . " order #" +		. $svc_x->vendor_order_id . "   Status: "  +		. $svc_x->vendor_order_status;      }      # else add any other export-specific stuff here -    $footer = "<B>".$export->status_line($svc_x)."</B>"; +    $footer = "<B>$status</B>";      $footer .= "<BR><BR><BR><B>Order Notes:</B><BR>".$export->notes_html($svc_x);  };  </%init>  | 
