RT# 83450 - fixed rateplan export
[freeside.git] / FS / FS / part_export / globalpops_voip.pm
1 package FS::part_export::globalpops_voip;
2
3 use vars qw(@ISA %info);
4 use Tie::IxHash;
5 use FS::Record qw(qsearch dbh);
6 use FS::part_export;
7 use FS::phone_avail;
8 use Data::Dumper;
9
10 @ISA = qw(FS::part_export);
11
12 tie my %options, 'Tie::IxHash',
13   'login'         => { label=>'VoIP Innovations API login' },
14   'password'      => { label=>'VoIP Innovations API password' },
15   'endpointgroup' => { label=>'VoIP Innovations endpoint group number' },
16   'dry_run'       => { label=>"Test mode - don't actually provision" },
17 ;
18
19 %info = (
20   'svc'     => 'svc_phone',
21   'desc'    => 'Provision phone numbers to VoIP Innovations (formerly GlobalPOPs VoIP)',
22   'options' => \%options,
23   'no_machine' => 1,
24   'notes'   => <<'END'
25 Requires installation of
26 <a href="http://search.cpan.org/dist/Net-GlobalPOPs-MediaServicesAPI">Net::GlobalPOPs::MediaServicesAPI</a>
27 from CPAN.
28 END
29 );
30
31 sub rebless { shift; }
32
33 sub can_get_dids { 1; }
34
35 sub get_dids {
36   my $self = shift;
37   my %opt = ref($_[0]) ? %{$_[0]} : @_;
38
39   my %getdids = ();
40   #  'orderby' => 'npa', #but it doesn't seem to work :/
41
42   if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
43     %getdids = ( 'npa'   => $opt{'areacode'},
44                  'nxx'   => $opt{'exchange'},
45                );
46   } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
47     %getdids = ( 'npa'   => $opt{'areacode'} );
48   } elsif ( $opt{'state'} ) {
49
50     my @avail = qsearch({
51       'table'    => 'phone_avail',
52       'hashref'  => { 'exportnum'   => $self->exportnum,
53                       'countrycode' => '1', #don't hardcode me when gp goes int'l
54                       'state'       => $opt{'state'},
55                     },
56       'order_by' => 'ORDER BY npa',
57     });
58
59     return [ map $_->npa, @avail ] if @avail; #return cached area codes instead
60
61     #otherwise, search for em
62     %getdids = ( 'state' => $opt{'state'} );
63
64   }
65
66   my $dids = $self->gp_command('getDIDs', %getdids);
67
68   if ( $dids->{'type'} eq 'Error' ) {
69     my $error =  "Error running VoIP Innovations getDIDs: ".
70         $dids->{'statuscode'}. ': '. $dids->{'status'}. "\n";
71     warn $error;
72     die $error;
73   }
74
75   my $search = $dids->{'search'};
76
77   if ( $search->{'statuscode'} == 302200 ) {
78     return [];
79   } elsif ( $search->{'statuscode'} != 100 ) {
80
81     my $error = "Error running VoIP Innovations getDIDs: ";
82     if ( $search->{'statuscode'} || $search->{'status'} ) {
83       $error .= $search->{'statuscode'}. ': '. $search->{'status'}. "\n";
84     } else {
85       $error .= Dumper($search);
86     }
87     warn $error;
88     die $error;
89   }
90
91   my @return = ();
92
93   #my $latas = $search->{state}{lata};
94   my %latas;
95   if ( grep $search->{state}{lata}{$_}, qw(name rate_center) ) {
96     %latas = map $search->{state}{lata}{$_},
97                  qw(name rate_center);
98   } else {
99     %latas = %{ $search->{state}{lata} };
100   } 
101
102   foreach my $lata ( keys %latas ) {
103
104     #warn "LATA $lata";
105     
106     #my $l = $latas{$lata};
107     #$l = $l->{rate_center} if exists $l->{rate_center};
108     
109     my $lata_dids = $self->gp_command('getDIDs', %getdids, 'lata'=>$lata);
110     my $lata_search = $lata_dids->{'search'};
111     unless ( $lata_search->{'statuscode'} == 100 ) {
112       die "Error running VoIP Innovations getDIDs: ". $lata_search->{'status'}; #die??
113     }
114    
115     my $l = $lata_search->{state}{lata}{'rate_center'};
116
117     #use Data::Dumper;
118     #warn Dumper($l);
119
120     my %rate_center;
121     if ( grep $l->{$_}, qw(name friendlyname) ) {
122       %rate_center = map $l->{$_},
123                          qw(name friendlyname);
124     } else {
125       %rate_center = %$l;
126     } 
127
128     foreach my $rate_center ( keys %rate_center ) {
129       
130       #warn "rate center $rate_center";
131
132       my $rc = $rate_center{$rate_center}; 
133       $rc = $rc->{friendlyname} if exists $rc->{friendlyname};
134
135       my @r = ();
136       if ( exists($rc->{npa}) ) {
137         @r = ($rc);
138       } else {
139         @r = map { { 'name'=>$_, %{ $rc->{$_} } }; } keys %$rc
140       }
141
142       foreach my $r (@r) {
143
144         my @npa = ();
145         if ( exists($r->{npa}{name}) ) {
146           @npa = ($r->{npa})
147         } else {
148           @npa = map { { 'name'=>$_, %{ $r->{npa}{$_} } } } keys %{ $r->{npa} };
149         }
150
151         foreach my $npa (@npa) {
152
153           if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
154
155             #warn Dumper($npa);
156
157             my $tn = $npa->{nxx}{tn} || $npa->{nxx}{$opt{'exchange'}}{tn};
158
159             my @tn = ref($tn) eq 'ARRAY' ? @$tn : ($tn);
160             #push @return, @tn;
161             push @return,
162               map {
163                     if ( /^\s*(\d{3})(\d{3})(\d{4})\s*$/ ) {
164                       "$1-$2-$3";
165                     } else {
166                       $_;
167                     }
168                   }
169                map { ref($_) eq 'HASH' ? $_->{'content'} : $_ } #tier always 2?
170                @tn;
171
172           } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
173
174             if ( $npa->{nxx}{name} ) {
175               @nxx = ( $npa->{nxx}{name} );
176             } else {
177               @nxx = keys %{ $npa->{nxx} };
178             }
179
180             push @return, map { $r->{name}. ' ('. $npa->{name}. "-$_-XXXX)"; }
181                               @nxx;
182
183           } elsif ( $opt{'state'} ) { #and not other things, then return areacode
184             #my $ac = $npa->{name};
185             #use Data::Dumper;
186             #warn Dumper($r) unless length($ac) == 3;
187
188             push @return, $npa->{name}
189               unless grep { $_ eq $npa->{name} } @return;
190
191           } else {
192             warn "WARNING: returning nothing for get_dids without known options"; #?
193           }
194
195         } #foreach my $npa
196
197       } #foreach my $r
198
199     } #foreach my $rate_center
200
201   } #foreach my $lata
202
203   if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
204     @return = sort { $a cmp $b } @return; #string comparison actually dwiw
205   } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
206     @return = sort { lc($a) cmp lc($b) } @return;
207   } elsif ( $opt{'state'} ) { #and not other things, then return areacode
208
209     #populate cache
210
211     local $SIG{HUP} = 'IGNORE';
212     local $SIG{INT} = 'IGNORE';
213     local $SIG{QUIT} = 'IGNORE';
214     local $SIG{TERM} = 'IGNORE';
215     local $SIG{TSTP} = 'IGNORE';
216     local $SIG{PIPE} = 'IGNORE';
217
218     my $oldAutoCommit = $FS::UID::AutoCommit;
219     local $FS::UID::AutoCommit = 0;
220     my $dbh = dbh;
221
222     my $errmsg = 'WARNING: error populating phone availability cache: ';
223     my $error = '';
224     foreach my $return (@return) {
225       my $phone_avail = new FS::phone_avail {
226         'exportnum'   => $self->exportnum,
227         'countrycode' => '1', #don't hardcode me when gp goes int'l
228         'state'       => $opt{'state'},
229         'npa'         => $return,
230       };
231       $error = $phone_avail->insert();
232       if ( $error ) {
233         warn $errmsg.$error;
234         last;
235       }
236     }
237
238     if ( $error ) {
239       $dbh->rollback if $oldAutoCommit;
240     } else {
241       $dbh->commit or warn $errmsg.$dbh->errstr if $oldAutoCommit;
242     }
243
244     #end populate cache
245
246     #@return = sort { (split(' ', $a))[0] <=> (split(' ', $b))[0] } @return;
247     @return = sort { $a <=> $b } @return;
248   } else {
249     warn "WARNING: returning nothing for get_dids without known options"; #?
250   }
251
252   \@return;
253
254 }
255
256 sub gp_command {
257   my( $self, $command, @args ) = @_;
258
259   eval "use Net::GlobalPOPs::MediaServicesAPI 0.03;";
260   if ( $@ ) {
261     warn $@;
262     die $@;
263   }
264
265   my $gp = Net::GlobalPOPs::MediaServicesAPI->new(
266     'login'    => $self->option('login'),
267     'password' => $self->option('password'),
268     #'debug'    => $debug,
269   );
270
271   $gp->$command(@args);
272 }
273
274
275 sub _export_insert {
276   my( $self, $svc_phone ) = (shift, shift);
277
278   return '' if $self->option('dry_run');
279
280   #we want to provision and catch errors now, not queue
281
282   my $r = $self->gp_command('reserveDID',
283     'did'           => $svc_phone->phonenum,
284     'minutes'       => 1,
285     'endpointgroup' => $self->option('endpointgroup'),
286   );
287
288   my $rdid = $r->{did};
289
290   if ( $rdid->{'statuscode'} != 100 ) {
291     return "Error running VoIP Innovations reserveDID: ".
292            $rdid->{'statuscode'}. ': '. $rdid->{'status'};
293   }
294
295   my $a = $self->gp_command('assignDID',
296     'did'           => $svc_phone->phonenum,
297     'endpointgroup' => $self->option('endpointgroup'),
298     #'rewrite'
299     #'cnam'
300   );
301
302   my $adid = $a->{did};
303
304   if ( $adid->{'statuscode'} != 100 ) {
305     return "Error running VoIP Innovations assignDID: ".
306            $adid->{'statuscode'}. ': '. $adid->{'status'};
307   }
308
309   '';
310 }
311
312 sub _export_replace {
313   my( $self, $new, $old ) = (shift, shift, shift);
314
315   #hmm, what's to change?
316   '';
317 }
318
319 sub _export_delete {
320   my( $self, $svc_phone ) = (shift, shift);
321
322   return '' if $self->option('dry_run');
323
324   #probably okay to queue the deletion...?
325   #but hell, let's do it inline anyway, who wants phone numbers hanging around
326
327   my $r = $self->gp_command('releaseDID',
328     'did'           => $svc_phone->phonenum,
329   );
330
331   my $rdid = $r->{did};
332
333   if ( $rdid->{'statuscode'} != 100 ) {
334     return "Error running VoIP Innovations releaseDID: ".
335            $rdid->{'statuscode'}. ': '. $rdid->{'status'};
336   }
337
338   '';
339 }
340
341 sub _export_suspend {
342   my( $self, $svc_phone ) = (shift, shift);
343   #nop for now
344   '';
345 }
346
347 sub _export_unsuspend {
348   my( $self, $svc_phone ) = (shift, shift);
349   #nop for now
350   '';
351 }
352
353 #hmm, might forgo queueing entirely for most things, data is too much of a pita
354 #sub globalpops_voip_queue {
355 #  my( $self, $svcnum, $method ) = (shift, shift, shift);
356 #  my $queue = new FS::queue {
357 #    'svcnum' => $svcnum,
358 #    'job'    => 'FS::part_export::globalpops_voip::globalpops_voip_command',
359 #  };
360 #  $queue->insert(
361 #    $self->option('login'),
362 #    $self->option('password'),
363 #    $method,
364 #    @_,
365 #  );
366 #}
367
368 sub globalpops_voip_command {
369   my($login, $password, $method, @args) = @_;
370
371   eval "use Net::GlobalPOPs::MediaServicesAPI 0.03;";
372   die $@ if $@;
373
374   my $gp = new Net::GlobalPOPs
375                  'login'    => $login,
376                  'password' => $password,
377                  #'debug'    => 1,
378                ;
379
380   my $return = $gp->$method( @args );
381
382   #$return->{'status'} 
383   #$return->{'statuscode'} 
384
385   die $return->{'status'} if $return->{'statuscode'};
386
387 }
388
389 1;
390