ikano, svc_dsl, qual, on-going implementation, RT7111
[freeside.git] / FS / FS / part_export / ikano.pm
1 package FS::part_export::ikano;
2
3 use vars qw(@ISA %info %orderType %orderStatus %loopType $DEBUG $me);
4 use Tie::IxHash;
5 use Date::Format qw( time2str );
6 use FS::Record qw(qsearch qsearchs);
7 use FS::part_export;
8 use FS::svc_dsl;
9 use Data::Dumper;
10
11 @ISA = qw(FS::part_export);
12 $DEBUG = 1;
13 $me= '[' .  __PACKAGE__ . ']';
14
15 tie my %options, 'Tie::IxHash',
16   'keyid'         => { label=>'Ikano keyid' },
17   'username'      => { label=>'Ikano username',
18                         default => 'admin',
19                         },
20   'password'      => { label=>'Ikano password' },
21   'check_networks' => { label => 'Check Networks',
22                     default => 'ATT,BELLCA',
23                     },
24 ;
25
26 %info = (
27   'svc'     => 'svc_dsl',
28   'desc'    => 'Provision DSL to Ikano',
29   'options' => \%options,
30   'notes'   => <<'END'
31 Requires installation of
32 <a href="http://search.cpan.org/dist/Net-Ikano">Net::Ikano</a> from CPAN.
33 END
34 );
35     
36 %orderType = ( 'N' => 'NEW', 'X' => 'CANCEL', 'C' => 'CHANGE' );
37 %orderStatus = ('N' => 'NEW',
38                 'P' => 'PENDING',
39                 'X' => 'CANCELLED',
40                 'C' => 'COMPLETED',
41                 'E' => 'ERROR' );
42 %loopType = ( '' => 'Line-share', '0' => 'Standalone' );
43
44 sub rebless { shift; }
45
46 sub dsl_pull {
47     '';
48 }
49
50 sub dsl_qual {
51     '';
52 }
53
54 sub notes_html {
55     '';
56 }
57
58 sub loop_type_long { # sub, not a method
59     my($svc_dsl) = (shift);
60     return $loopType{$svc_dsl->loop_type};
61 }
62
63 sub status_line {
64     my($self,$svc_dsl) = (shift,shift);
65     return "Ikano ".$orderType{$svc_dsl->vendor_order_type}." order #"
66         . $svc_dsl->vendor_order_id . " (Status: " 
67         . $orderStatus{$svc_dsl->vendor_order_status} . ")";
68 }
69
70 sub ikano_command {
71   my( $self, $command, $args ) = @_;
72
73   eval "use Net::Ikano;";
74   die $@ if $@;
75
76   my $ikano = Net::Ikano->new(
77     'keyid' => $self->option('keyid'),
78     'username'  => $self->option('username'),
79     'password'  => $self->option('password'),
80     'debug'    => 1,
81     #'reqpreviewonly' => 1,
82   );
83
84   $ikano->$command($args);
85 }
86
87 sub valid_order {
88   my( $self, $svc_dsl, $action ) = (shift, shift, shift);
89   
90   warn "$me valid_order action=$action svc_dsl:\n". Dumper($svc_dsl) if $DEBUG;
91
92   # common to all order types/status/loop_type
93   my $error = !($svc_dsl->desired_due_date
94             &&  defined $orderType{$svc_dsl->vendor_order_type}
95             &&  $svc_dsl->first
96             &&  $svc_dsl->last
97             &&  defined $svc_dsl->loop_type
98             &&  $svc_dsl->vendor_qual_id
99             );
100   return 'Missing or invalid order data' if $error;
101   
102   return 'Package does not have an external id configured'
103     if $svc_dsl->cust_svc->cust_pkg->part_pkg->options('externalid',1) eq '';
104
105   return 'No valid qualification for this order' 
106     unless qsearch( 'qual', { 'vendor_qual_id' => $svc_dsl->vendor_qual_id });
107
108   # now go by order type
109   # weird ifs & long lines for readability and ease of understanding - don't change
110   if($svc_dsl->vendor_order_type eq 'N') {
111     if($svc_dsl->pushed) {
112     }
113     else { # unpushed New order - cannot do anything other than push it
114         $error = !($action eq 'insert'
115             &&  length($svc_dsl->vendor_order_id) < 1
116             &&  length($svc_dsl->vendor_order_status) < 1
117             && ( ($svc_dsl->phonenum eq '' && $svc_dsl->loop_type eq '0') # dry
118               || ($svc_dsl->phonenum ne '' && $svc_dsl->loop_type eq '') # line-share
119                )
120             );  
121         return 'Invalid order data' if $error;
122     }
123   }
124   elsif($svc_dsl->vendor_order_type eq 'X') {
125   }
126   elsif($svc_dsl->vendor_order_type eq 'C') {
127   }
128
129  '';
130 }
131
132 sub qual2termsid {
133     my ($self,$vendor_qual_id,$ProductCustomId) = (shift,shift,shift);
134     my $qual = qsearchs( 'qual', { 'vendor_qual_id' => $vendor_qual_id });
135     return '' unless $qual;
136     my %qual_options = $qual->options;
137     while (($optionname, $optionvalue) = each %qual_options) {
138         if ( $optionname =~ /^ikano_Network_(\d+)_ProductGroup_(\d+)_Product_(\d+)_ProductCustomId$/ 
139             && $optionvalue eq $ProductCustomId ) {
140             my $network = $1;
141             my $productgroup = $2;
142             return $qual->option("ikano_Network_".$network."_ProductGroup_".$productgroup."_TermsId");
143         }
144     }
145     '';
146 }
147
148 sub orderstatus_long2short {
149     my ($self,$order_status) = (shift,shift);
150     while (($k, $v) = each %orderStatus) {
151         return $k if $v eq $order_status;
152     }
153     return '';
154 }
155
156 sub _export_insert {
157   my( $self, $svc_dsl ) = (shift, shift);
158
159   my $result = $self->valid_order($svc_dsl,'insert');
160   return $result unless $result eq '';
161
162   my $isp_chg = $svc_dsl->isp_chg eq 'Y' ? 'YES' : 'NO';
163   my $contactTN = $svc_dsl->cust_svc->cust_pkg->cust_main->daytime;
164   $contactTN =~ s/[^0-9]//g;
165
166   my $ProductCustomId = $svc_dsl->cust_svc->cust_pkg->part_pkg->option('externalid',1);
167
168   my $args = {
169         orderType => 'NEW',
170         ProductCustomId => $ProductCustomId,
171         TermsId => $self->qual2termsid($svc_dsl->vendor_qual_id,$ProductCustomId),
172         DSLPhoneNumber => $svc_dsl->loop_type eq '0' ? 'STANDALONE'
173                                                     : $svc_dsl->phonenum,
174         Password => $svc_dsl->password,
175         PrequalId => $svc_dsl->vendor_qual_id,
176         CompanyName => $svc_dsl->company,
177         FirstName => $svc_dsl->first,
178         LastName => $svc_dsl->last,
179         MiddleName => '',
180         ContactMethod => 'PHONE',
181         ContactPhoneNumber => $contactTN,
182         ContactEmail => 'x@x.xx',
183         ContactFax => '',
184         DateToOrder => time2str("%Y-%m-%d",$svc_dsl->desired_due_date),
185         RequestClientIP => '127.0.0.1',
186         IspChange => $isp_chg,
187         IspPrevious => $isp_chg eq 'YES' ? $svc_dsl->isp_prev : '',
188         CurrentProvider => $isp_chg eq 'NO' ? $svc_dsl->isp_prev : '',
189   };
190
191   $result = $self->ikano_command('ORDER',$args); 
192   return $result unless ref($result); # scalar (string) is an error
193
194   # now we're getting an OrderResponse which should have one Order in it
195   warn "$me _export_insert OrderResponse hash:\n".Dumper($result) if $DEBUG;
196   
197   return 'Invalid order response' unless defined $result->{'Order'};
198   $result = $result->{'Order'};
199
200   return 'No order id or status returned' 
201     unless defined $result->{'Status'} && defined $result->{'OrderId'};
202
203   $svc_dsl->pushed(time);
204   $svc_dsl->last_pull((time)+1); 
205   $svc_dsl->vendor_order_id($result->{'OrderId'});
206   $svc_dsl->vendor_order_status($self->orderstatus_long2short($result->{'Status'}));
207   $svc_dsl->username($result->{'Username'});
208   local $FS::svc_Common::noexport_hack = 1;
209   local $FS::UID::AutoCommit = 0;
210   $result = $svc_dsl->replace; 
211   return 'Error setting DSL fields' if $result;
212   '';
213 }
214
215 sub _export_replace {
216   my( $self, $new, $old ) = (shift, shift, shift);
217   '';
218 }
219
220 sub _export_delete {
221   my( $self, $svc_dsl ) = (shift, shift);
222   '';
223 }
224
225 sub _export_suspend {
226   my( $self, $svc_dsl ) = (shift, shift);
227   '';
228 }
229
230 sub _export_unsuspend {
231   my( $self, $svc_dsl ) = (shift, shift);
232   '';
233 }
234
235 1;