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> |