add port option, RT#7051
[freeside.git] / FS / FS / part_export / vitelity.pm
1 package FS::part_export::vitelity;
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
9 @ISA = qw(FS::part_export);
10
11 tie my %options, 'Tie::IxHash',
12   'login'         => { label=>'Vitelity API login' },
13   'pass'          => { label=>'Vitelity API password' },
14   'dry_run'       => { label=>"Test mode - don't actually provision" },
15 ;
16
17 %info = (
18   'svc'     => 'svc_phone',
19   'desc'    => 'Provision phone numbers to Vitelity',
20   'options' => \%options,
21   'notes'   => <<'END'
22 Requires installation of
23 <a href="http://search.cpan.org/dist/Net-Vitelity">Net::Vitelity</a>
24 from CPAN.
25 END
26 );
27
28 sub rebless { shift; }
29
30 sub get_dids {
31   my $self = shift;
32   my %opt = ref($_[0]) ? %{$_[0]} : @_;
33
34   my %search = ();
35   #  'orderby' => 'npa', #but it doesn't seem to work :/
36
37   my $method = '';
38
39   if ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers
40
41     return [
42       map { join('-', $_->npx, $_->nxx, $_->station ) }
43           qsearch({
44             'table'    => 'phone_avail',
45             'hashref'  => { 'exportnum'   => $self->exportnum,
46                             'countrycode' => '1',
47                             'state'       => $opt{'state'},
48                             'npa'         => $opt{'areacode'},
49                             'nxx'         => $opt{'exchange'},
50                           },
51             'order_by' => 'ORDER BY name', #?
52           })
53     ];
54
55   } elsif ( $opt{'areacode'} ) { #return city (npa-nxx-XXXX)
56
57     return [
58       map { $_->name. ' ('. $_->npa. '-'. $_->nxx. '-XXXX)' } 
59           qsearch({
60             'select'   => 'DISTINCT ON ( name, npa, nxx ) *',
61             'table'    => 'phone_avail',
62             'hashref'  => { 'exportnum'   => $self->exportnum,
63                             'countrycode' => '1',
64                             'state'       => $opt{'state'},
65                             'npa'         => $opt{'areacode'},
66                           },
67             'order_by' => 'ORDER BY name', #?
68           })
69     ];
70
71   } elsif ( $opt{'state'} ) { #and not other things, then return areacode
72
73     #XXX need to flush the cache at some point :/
74
75     my @avail = qsearch({
76       'select'   => 'DISTINCT npa',
77       'table'    => 'phone_avail',
78       'hashref'  => { 'exportnum'   => $self->exportnum,
79                       'countrycode' => '1', #don't hardcode me when gp goes intl
80                       'state'       => $opt{'state'},
81                     },
82       'order_by' => 'ORDER BY npa',
83     });
84
85     return [ map $_->npa, @avail ] if @avail; #return cached area codes instead
86
87     #otherwise, search for em
88
89     my @ratecenters = $self->vitelity_command( 'listavailratecenters',
90                                                  'state' => $opt{'state'}, 
91                                              );
92
93     if ( $ratecenters[0] eq 'unavailable' ) {
94       return [];
95     } elsif ( $ratecenters[0] eq 'missingdata' ) {
96       die "missingdata error running Vitelity API"; #die?
97     }
98
99     local $SIG{HUP} = 'IGNORE';
100     local $SIG{INT} = 'IGNORE';
101     local $SIG{QUIT} = 'IGNORE';
102     local $SIG{TERM} = 'IGNORE';
103     local $SIG{TSTP} = 'IGNORE';
104     local $SIG{PIPE} = 'IGNORE';
105
106     my $oldAutoCommit = $FS::UID::AutoCommit;
107     local $FS::UID::AutoCommit = 0;
108     my $dbh = dbh;
109
110     my $errmsg = 'WARNING: error populating phone availability cache: ';
111
112     my %npa = ();
113     foreach my $ratecenter (@ratecenters) {
114
115       my @dids = $self->vitelity_command( 'listlocal',
116                                             'state'      => $opt{'state'},
117                                             'ratecenter' => $ratecenter,
118                                         );
119
120       if ( $dids[0] eq 'unavailable' ) {
121         next;
122       } elsif ( $dids[0] eq 'missingdata' ) {
123         die "missingdata error running Vitelity API"; #die?
124       }
125
126       foreach my $did ( @dids ) {
127         $did =~ /^(\d{3})(\d{3})(\d{4})$/ or die "unparsable did $did\n";
128         my($npa, $nxx, $station) = ($1, $2, $3);
129         $npa{$npa}++;
130
131         my $phone_avail = new FS::phone_avail {
132           'exportnum'   => $self->exportnum,
133           'countrycode' => '1', #don't hardcode me when vitelity goes int'l
134           'state'       => $opt{'state'},
135           'npa'         => $npa,
136           'nxx'         => $nxx,
137           'station'     => $station,
138           'name'        => $ratecenter,
139         };
140
141         $error = $phone_avail->insert();
142         if ( $error ) {
143           $dbh->rollback if $oldAutoCommit;
144           die $errmsg.$error;
145         }
146
147       }
148
149     }
150
151     $dbh->commit or warn $errmsg.$dbh->errstr if $oldAutoCommit;
152
153     my @return = sort { $a <=> $b } keys %npa;
154     #@return = sort { (split(' ', $a))[0] <=> (split(' ', $b))[0] } @return;
155
156     return \@return;
157
158   } else {
159     die "get_dids called without state or areacode options";
160   }
161
162 }
163
164 sub vitelity_command {
165   my( $self, $command, @args ) = @_;
166
167   eval "use Net::Vitelity;";
168   die $@ if $@;
169
170   my $vitelity = Net::Vitelity->new(
171     'login' => $self->option('login'),
172     'pass'  => $self->option('pass'),
173     #'debug'    => $debug,
174   );
175
176   $vitelity->$command(@args);
177 }
178
179 sub _export_insert {
180   my( $self, $svc_phone ) = (shift, shift);
181
182   return '' if $self->option('dry_run');
183
184   #we want to provision and catch errors now, not queue
185
186   my $result = $self->vitelity_command('getlocaldid',
187     'did'           => $svc_phone->phonenum,
188 #XXX
189 #Options: type=perminute OR type=unlimited OR type=your-pri OR
190 #               routesip=route_to_this_subaccount
191   );
192
193   if ( $result ne 'success' ) {
194     return "Error running Vitelity getlocaldid: $result";
195   }
196
197   '';
198 }
199
200 sub _export_replace {
201   my( $self, $new, $old ) = (shift, shift, shift);
202
203   #hmm, what's to change?
204   '';
205 }
206
207 sub _export_delete {
208   my( $self, $svc_phone ) = (shift, shift);
209
210   return '' if $self->option('dry_run');
211
212   #probably okay to queue the deletion...?
213   #but hell, let's do it inline anyway, who wants phone numbers hanging around
214
215   my $result = $self->vitelity_command('removedid',
216     'did'           => $svc_phone->phonenum,
217   );
218
219   if ( $result ne 'success' ) {
220     return "Error running Vitelity getlocaldid: $result";
221   }
222
223   '';
224 }
225
226 sub _export_suspend {
227   my( $self, $svc_phone ) = (shift, shift);
228   #nop for now
229   '';
230 }
231
232 sub _export_unsuspend {
233   my( $self, $svc_phone ) = (shift, shift);
234   #nop for now
235   '';
236 }
237
238 1;
239