improve performance of DID provisioning status report, RT10988
[freeside.git] / FS / FS / phone_avail.pm
1 package FS::phone_avail;
2
3 use strict;
4 use vars qw( @ISA $DEBUG $me );
5 use FS::Record qw( qsearch qsearchs dbh );
6 use FS::cust_svc;
7 use FS::Misc::DateTime qw( parse_datetime );
8
9 @ISA = qw(FS::cust_main_Mixin FS::Record);
10
11 $me = '[FS::phone_avail]';
12 $DEBUG = 0;
13
14 =head1 NAME
15
16 FS::phone_avail - Phone number availability cache
17
18 =head1 SYNOPSIS
19
20   use FS::phone_avail;
21
22   $record = new FS::phone_avail \%hash;
23   $record = new FS::phone_avail { 'column' => 'value' };
24
25   $error = $record->insert;
26
27   $error = $new_record->replace($old_record);
28
29   $error = $record->delete;
30
31   $error = $record->check;
32
33 =head1 DESCRIPTION
34
35 An FS::phone_avail object represents availability of phone service.
36 FS::phone_avail inherits from FS::Record.  The following fields are currently
37 supported:
38
39 =over 4
40
41 =item availnum
42
43 primary key
44
45 =item exportnum
46
47 exportnum
48
49 =item countrycode
50
51 countrycode
52
53 =item state
54
55 state
56
57 =item npa
58
59 npa
60
61 =item nxx
62
63 nxx
64
65 =item station
66
67 station
68
69 =item name
70
71 Optional name
72
73 =item rate_center_abbrev - abbreviated rate center
74
75 =item latanum - LATA #
76
77 =item msanum - MSA #
78
79 =item ordernum - bulk DID order #
80
81 =item svcnum
82
83 =item availbatch
84
85 =back
86
87 =head1 METHODS
88
89 =over 4
90
91 =item new HASHREF
92
93 Creates a new record.  To add the record to the database, see L<"insert">.
94
95 Note that this stores the hash reference, not a distinct copy of the hash it
96 points to.  You can ask the object for a copy with the I<hash> method.
97
98 =cut
99
100 # the new method can be inherited from FS::Record, if a table method is defined
101
102 sub table { 'phone_avail'; }
103
104 =item insert
105
106 Adds this record to the database.  If there is an error, returns the error,
107 otherwise returns false.
108
109 =cut
110
111 # the insert method can be inherited from FS::Record
112
113 =item delete
114
115 Delete this record from the database.
116
117 =cut
118
119 # the delete method can be inherited from FS::Record
120
121 =item replace OLD_RECORD
122
123 Replaces the OLD_RECORD with this one in the database.  If there is an error,
124 returns the error, otherwise returns false.
125
126 =cut
127
128 # the replace method can be inherited from FS::Record
129
130 =item check
131
132 Checks all fields to make sure this is a valid record.  If there is
133 an error, returns the error, otherwise returns false.  Called by the insert
134 and replace methods.
135
136 =cut
137
138 # the check method should currently be supplied - FS::Record contains some
139 # data checking routines
140
141 sub check {
142   my $self = shift;
143
144   my $error = 
145     $self->ut_numbern('availnum')
146     || $self->ut_foreign_key('exportnum', 'part_export', 'exportnum' )
147     || $self->ut_number('countrycode')
148     || $self->ut_alphan('state')
149     || $self->ut_number('npa')
150     || $self->ut_numbern('nxx')
151     || $self->ut_numbern('station')
152     || $self->ut_textn('name')
153     || $self->ut_textn('rate_center_abbrev')
154     || $self->ut_foreign_keyn('latanum', 'lata', 'latanum' )
155     || $self->ut_foreign_keyn('msanum', 'msa', 'msanum' )
156     || $self->ut_foreign_keyn('ordernum', 'did_order', 'ordernum' )
157     || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum' )
158     || $self->ut_textn('availbatch')
159   ;
160   return $error if $error;
161
162   $self->SUPER::check;
163 }
164
165 =item cust_svc
166
167 =cut
168
169 sub cust_svc {
170   my $self = shift;
171   return '' unless $self->svcnum;
172   qsearchs('cust_svc', { 'svcnum' => $self->svcnum });
173 }
174
175 =item part_export
176
177 =cut
178
179 sub part_export {
180   my $self = shift;
181   return '' unless $self->exportnum;
182   qsearchs('part_export', { 'exportnum' => $self->exportnum });
183 }
184
185 =item lata 
186
187 =cut
188
189 sub lata {
190   my $self = shift;
191   return '' unless $self->latanum;
192   qsearchs('lata', { 'latanum' => $self->latanum });
193 }
194
195 =item msa2msanum
196
197 Translate free-form MSA name to a msa.msanum
198
199 =cut
200
201 sub msa2msanum {
202     my $self = shift;
203     my $msa = shift;
204     my $res = qsearchs('msa', { 'description' => { 'op' => 'ILIKE',
205                                                    'value' => $msa, }
206                               });
207     return 0 unless $res;
208     $res->msanum;
209 }
210
211 sub process_batch_import {
212   my $job = shift;
213
214   my $numsub = sub {
215     my( $phone_avail, $value ) = @_;
216     $value =~ s/\D//g;
217     $value =~ /^(\d{3})(\d{3})(\d+)$/ or die "unparsable number $value\n";
218     #( $hash->{npa}, $hash->{nxx}, $hash->{station} ) = ( $1, $2, $3 );
219     $phone_avail->npa($1);
220     $phone_avail->nxx($2);
221     $phone_avail->station($3);
222   };
223
224   my $msasub = sub {
225     my( $phone_avail, $value ) = @_;
226     my $msanum = $phone_avail->msa2msanum($value);
227     die "cannot translate MSA ($value) to msanum" unless $msanum;
228     $phone_avail->msanum($msanum);
229   };
230
231   my $opt = { 'table'   => 'phone_avail',
232               'params'  => [ 'availbatch', 'exportnum', 'countrycode', 'ordernum', 'vendor_order_id', 'confirmed' ],
233               'formats' => { 'default' => [ 'state', $numsub, 'name' ],
234                  'bulk' => [ 'state', $numsub, 'name', 'rate_center_abbrev', $msasub, 'latanum' ],
235                },
236                'postinsert_callback' => sub {  
237                     my $record = shift;
238                     if($record->ordernum) {
239                         my $did_order = qsearchs('did_order', 
240                                             { 'ordernum' => $record->ordernum } );
241                         if($did_order && !$did_order->received) {
242                             $did_order->received(time);
243                             $did_order->confirmed(parse_datetime($record->confirmed));
244                             $did_order->vendor_order_id($record->vendor_order_id);
245                             $did_order->replace;
246                         }
247                     }
248                }, 
249             };
250
251   FS::Record::process_batch_import( $job, $opt, @_ );
252 }
253
254 sub flush { # evil direct SQL
255     my $opt = shift;
256
257     if ( $opt->{'ratecenter'} =~ /^[\w\s]+$/
258         && $opt->{'state'} =~ /^[A-Z][A-Z]$/ 
259         && $opt->{'exportnum'} =~ /^\d+$/) {
260     my $sth = dbh->prepare('delete from phone_avail where exportnum = ? '.
261             ' and state = ? and name = ?');
262     $sth->execute($opt->{'exportnum'},$opt->{'state'},$opt->{'ratecenter'})
263         or die $sth->errstr;
264     }
265
266     '';
267 }
268
269 # Used by FS::Upgrade to migrate to a new database.
270 sub _upgrade_data {
271   my ($class, %opts) = @_;
272
273   warn "$me upgrading $class\n" if $DEBUG;
274
275   my $sth = dbh->prepare(
276     'UPDATE phone_avail SET svcnum = NULL
277        WHERE svcnum IS NOT NULL
278          AND 0 = ( SELECT COUNT(*) FROM svc_phone
279                      WHERE phone_avail.svcnum = svc_phone.svcnum )'
280   ) or die dbh->errstr;
281
282   $sth->execute or die $sth->errstr;
283
284 }
285
286 =back
287
288 =head1 BUGS
289
290 Sparse documentation.
291
292 =head1 SEE ALSO
293
294 L<FS::Record>, schema.html from the base documentation.
295
296 =cut
297
298 1;
299