1 package FS::part_export::vitelity;
3 use vars qw(@ISA %info);
5 use FS::Record qw(qsearch dbh);
9 @ISA = qw(FS::part_export);
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 'routesip' => { label=>'routesip (optional sub-account)' },
16 'type' => { label=>'type (optional DID type to order)' },
17 'fax' => { label=>'vfax service', type=>'checkbox' },
18 'restrict_selection' => { type=>'select',
19 label=>'Restrict DID Selection',
20 options=>[ '', 'tollfree', 'non-tollfree' ],
27 'desc' => 'Provision phone numbers to Vitelity',
28 'options' => \%options,
30 Requires installation of
31 <a href="http://search.cpan.org/dist/Net-Vitelity">Net::Vitelity</a>
34 routesip - optional Vitelity sub-account to which newly ordered DIDs will be routed
35 <br>type - optional DID type (perminute, unlimited, or your-pri)
39 sub rebless { shift; }
43 my %opt = ref($_[0]) ? %{$_[0]} : @_;
45 if ( $opt{'tollfree'} ) {
46 # XXX: no caching for now
49 my $command = 'listtollfree';
50 $command = 'listdids' if $self->option('fax');
51 my @tollfree = $self->vitelity_command($command);
54 if (scalar(@tollfree)) {
55 local $SIG{HUP} = 'IGNORE';
56 local $SIG{INT} = 'IGNORE';
57 local $SIG{QUIT} = 'IGNORE';
58 local $SIG{TERM} = 'IGNORE';
59 local $SIG{TSTP} = 'IGNORE';
60 local $SIG{PIPE} = 'IGNORE';
62 my $oldAutoCommit = $FS::UID::AutoCommit;
63 local $FS::UID::AutoCommit = 0;
66 my $errmsg = 'WARNING: error populating phone availability cache: ';
68 foreach my $did ( @tollfree ) {
69 $did =~ /^(\d{3})(\d{3})(\d{4})/ or die "unparsable did $did\n";
70 my($npa, $nxx, $station) = ($1, $2, $3);
73 my $phone_avail = new FS::phone_avail {
74 'exportnum' => $self->exportnum,
75 'countrycode' => '1', # vitelity is US/CA only now
78 'station' => $station,
81 $error = $phone_avail->insert();
83 $dbh->rollback if $oldAutoCommit;
87 $dbh->commit or warn $errmsg.$dbh->errstr if $oldAutoCommit;
91 my @sorted_ret = sort @ret;
94 } elsif ( $opt{'areacode'} && $opt{'exchange'} ) { #return numbers in format NPA-NXX-XXXX
97 map { join('-', $_->npa, $_->nxx, $_->station ) }
99 'table' => 'phone_avail',
100 'hashref' => { 'exportnum' => $self->exportnum,
101 'countrycode' => '1', # vitelity is US/CA only now
102 'npa' => $opt{'areacode'},
103 'nxx' => $opt{'exchange'},
105 'order_by' => 'ORDER BY station',
109 } elsif ( $opt{'areacode'} ) { #return exchanges in format NPA-NXX- literal 'XXXX'
111 # you can't call $->name .... that returns "(unlinked)"
112 # and in any case this is still major abuse of encapsulation, it just happens to work for the other fields
113 @rc = map { $_->{'Hash'}->{name}.' ('. $_->npa. '-'. $_->nxx. '-XXXX)' }
115 'select' => 'DISTINCT npa,nxx,name',
116 'table' => 'phone_avail',
117 'hashref' => { 'exportnum' => $self->exportnum,
118 'countrycode' => '1', # vitelity is US/CA only now
119 'npa' => $opt{'areacode'},
121 'order_by' => 'ORDER BY nxx',
124 @sorted_rc = sort @rc;
125 return [ @sorted_rc ];
127 } elsif ( $opt{'state'} ) { #and not other things, then return areacode
129 #XXX need to flush the cache at some point :/
131 my @avail = qsearch({
132 'select' => 'DISTINCT npa',
133 'table' => 'phone_avail',
134 'hashref' => { 'exportnum' => $self->exportnum,
135 'countrycode' => '1', # vitelity is US/CA only now
136 'state' => $opt{'state'},
138 'order_by' => 'ORDER BY npa',
141 return [ map $_->npa, @avail ] if @avail; #return cached area codes instead
143 #otherwise, search for em
145 my $command = 'listavailratecenters';
146 $command = 'listratecenters' if $self->option('fax');
147 my @ratecenters = $self->vitelity_command( $command,
148 'state' => $opt{'state'},
150 # XXX: Options: type=unlimited OR type=pri
152 if ( $ratecenters[0] eq 'unavailable' || $ratecenters[0] eq 'none' ) {
154 } elsif ( $ratecenters[0] eq 'missingdata' ) {
155 die "missingdata error running Vitelity API"; #die?
158 local $SIG{HUP} = 'IGNORE';
159 local $SIG{INT} = 'IGNORE';
160 local $SIG{QUIT} = 'IGNORE';
161 local $SIG{TERM} = 'IGNORE';
162 local $SIG{TSTP} = 'IGNORE';
163 local $SIG{PIPE} = 'IGNORE';
165 my $oldAutoCommit = $FS::UID::AutoCommit;
166 local $FS::UID::AutoCommit = 0;
169 my $errmsg = 'WARNING: error populating phone availability cache: ';
172 foreach my $ratecenter (@ratecenters) {
174 my $command = 'listlocal';
175 $command = 'listdids' if $self->option('fax');
176 my @dids = $self->vitelity_command( $command,
177 'state' => $opt{'state'},
178 'ratecenter' => $ratecenter,
180 # XXX: Options: type=unlimited OR type=pri
182 if ( $dids[0] eq 'unavailable' || $dids[0] eq 'noneavailable' ) {
184 } elsif ( $dids[0] eq 'missingdata' ) {
185 die "missingdata error running Vitelity API"; #die?
188 foreach my $did ( @dids ) {
189 $did =~ /^(\d{3})(\d{3})(\d{4})/ or die "unparsable did $did\n";
190 my($npa, $nxx, $station) = ($1, $2, $3);
193 my $phone_avail = new FS::phone_avail {
194 'exportnum' => $self->exportnum,
195 'countrycode' => '1', # vitelity is US/CA only now
196 'state' => $opt{'state'},
199 'station' => $station,
200 'name' => $ratecenter,
203 $error = $phone_avail->insert();
205 $dbh->rollback if $oldAutoCommit;
213 $dbh->commit or warn $errmsg.$dbh->errstr if $oldAutoCommit;
215 my @return = sort { $a <=> $b } keys %npa;
216 #@return = sort { (split(' ', $a))[0] <=> (split(' ', $b))[0] } @return;
221 die "get_dids called without state or areacode options";
226 sub vitelity_command {
227 my( $self, $command, @args ) = @_;
229 eval "use Net::Vitelity;";
232 my $vitelity = Net::Vitelity->new(
233 'login' => $self->option('login'),
234 'pass' => $self->option('pass'),
235 'apitype' => $self->option('fax') ? 'fax' : 'api',
239 $vitelity->$command(@args);
243 my( $self, $svc_phone ) = (shift, shift);
245 return '' if $self->option('dry_run');
247 #we want to provision and catch errors now, not queue
249 %vparams = ( 'did' => $svc_phone->phonenum );
250 $vparams{'routesip'} = $self->option('routesip')
251 if defined $self->option('routesip');
252 $vparams{'type'} = $self->option('type')
253 if defined $self->option('type');
255 $command = 'getlocaldid';
256 $success = 'success';
258 # this is OK as Vitelity for now is US/CA only; it's not a hack
259 $command = 'gettollfree' if $vparams{'did'} =~ /^800|^888|^877|^866|^855/;
261 if($self->option('fax')) {
266 my $result = $self->vitelity_command($command,%vparams);
268 if ( $result ne $success ) {
269 return "Error running Vitelity $command: $result";
275 sub _export_replace {
276 my( $self, $new, $old ) = (shift, shift, shift);
279 if( $old->forwarddst ne $new->forwarddst ) {
280 my $result = $self->vitelity_command('callfw',
281 'did' => $old->phonenum,
282 'forward' => $new->forwarddst ? $new->forwarddst : 'none',
284 if ( $result ne 'ok' ) {
285 return "Error running Vitelity callfw: $result";
289 # vfax forwarding emails
290 if( $old->email ne $new->email && $self->option('fax') ) {
291 my $result = $self->vitelity_command('changeemail',
292 'did' => $old->phonenum,
293 'emails' => $new->email ? $new->email : '',
295 if ( $result ne 'ok' ) {
296 return "Error running Vitelity changeemail: $result";
304 my( $self, $svc_phone ) = (shift, shift);
306 return '' if $self->option('dry_run');
308 #probably okay to queue the deletion...?
309 #but hell, let's do it inline anyway, who wants phone numbers hanging around
311 return 'Deleting vfax DIDs is unsupported by Vitelity API' if $self->option('fax');
313 my $result = $self->vitelity_command('removedid',
314 'did' => $svc_phone->phonenum,
317 if ( $result ne 'success' ) {
318 return "Error running Vitelity removedid: $result";
324 sub _export_suspend {
325 my( $self, $svc_phone ) = (shift, shift);
330 sub _export_unsuspend {
331 my( $self, $svc_phone ) = (shift, shift);