summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlevinse <levinse>2010-11-29 22:11:01 +0000
committerlevinse <levinse>2010-11-29 22:11:01 +0000
commit4e1e6d1a69d6c1f08c24859f7ed70b4489b691bf (patch)
tree25e6366f83cc1fa3989d8812e3e8204cf148bce8
parent136200fb9ff797ea0ca44c00b5070129f35f47ce (diff)
svc_dsl, ikano, on-going implementation, initial commit of freeside-pull-dsl, RT7111
-rw-r--r--FS/FS/Schema.pm4
-rw-r--r--FS/FS/part_export/ikano.pm210
-rw-r--r--FS/FS/svc_dsl.pm4
-rwxr-xr-xFS/bin/freeside-pull-dsl71
-rw-r--r--httemplate/edit/svc_dsl.cgi2
-rw-r--r--httemplate/view/svc_dsl.cgi6
6 files changed, 225 insertions, 72 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 281ca6b..e482bed 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 91953e3..444e9b8 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 2c30b00..93d3570 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 0000000..d0aa921
--- /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 4fca0cd..79aeb1a 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 14c1eb2..7bbdca1 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 . " &nbsp; 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>