DID inventory/import / bulk DID orders - phase 2, RT12754
[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 msa2msanum
186
187 Translate free-form MSA name to a msa.msanum
188
189 =cut
190
191 sub msa2msanum {
192     my $self = shift;
193     my $msa = shift;
194     my $res = qsearchs('msa', { 'description' => { 'op' => 'ILIKE',
195                                                    'value' => $msa, }
196                               });
197     return 0 unless $res;
198     $res->msanum;
199 }
200
201 sub process_batch_import {
202   my $job = shift;
203
204   my $numsub = sub {
205     my( $phone_avail, $value ) = @_;
206     $value =~ s/\D//g;
207     $value =~ /^(\d{3})(\d{3})(\d+)$/ or die "unparsable number $value\n";
208     #( $hash->{npa}, $hash->{nxx}, $hash->{station} ) = ( $1, $2, $3 );
209     $phone_avail->npa($1);
210     $phone_avail->nxx($2);
211     $phone_avail->station($3);
212   };
213
214   my $msasub = sub {
215     my( $phone_avail, $value ) = @_;
216     my $msanum = $phone_avail->msa2msanum($value);
217     die "cannot translate MSA ($value) to msanum" unless $msanum;
218     $phone_avail->msanum($msanum);
219   };
220
221   my $opt = { 'table'   => 'phone_avail',
222               'params'  => [ 'availbatch', 'exportnum', 'countrycode', 'ordernum', 'vendor_order_id', 'confirmed' ],
223               'formats' => { 'default' => [ 'state', $numsub, 'name' ],
224                  'bulk' => [ 'state', $numsub, 'name', 'rate_center_abbrev', $msasub, 'latanum' ],
225                },
226                'postinsert_callback' => sub {  
227                     my $record = shift;
228                     if($record->ordernum) {
229                         my $did_order = qsearchs('did_order', 
230                                             { 'ordernum' => $record->ordernum } );
231                         if($did_order && !$did_order->received) {
232                             $did_order->received(time);
233                             $did_order->confirmed(parse_datetime($record->confirmed));
234                             $did_order->vendor_order_id($record->vendor_order_id);
235                             $did_order->replace;
236                         }
237                     }
238                }, 
239             };
240
241   FS::Record::process_batch_import( $job, $opt, @_ );
242 }
243
244 sub flush { # evil direct SQL
245     my $opt = shift;
246
247     if ( $opt->{'ratecenter'} =~ /^[\w\s]+$/
248         && $opt->{'state'} =~ /^[A-Z][A-Z]$/ 
249         && $opt->{'exportnum'} =~ /^\d+$/) {
250     my $sth = dbh->prepare('delete from phone_avail where exportnum = ? '.
251             ' and state = ? and name = ?');
252     $sth->execute($opt->{'exportnum'},$opt->{'state'},$opt->{'ratecenter'})
253         or die $sth->errstr;
254     }
255
256     '';
257 }
258
259 # Used by FS::Upgrade to migrate to a new database.
260 sub _upgrade_data {
261   my ($class, %opts) = @_;
262
263   warn "$me upgrading $class\n" if $DEBUG;
264
265   my $sth = dbh->prepare(
266     'UPDATE phone_avail SET svcnum = NULL
267        WHERE svcnum IS NOT NULL
268          AND 0 = ( SELECT COUNT(*) FROM svc_phone
269                      WHERE phone_avail.svcnum = svc_phone.svcnum )'
270   ) or die dbh->errstr;
271
272   $sth->execute or die $sth->errstr;
273
274 }
275
276 =back
277
278 =head1 BUGS
279
280 Sparse documentation.
281
282 =head1 SEE ALSO
283
284 L<FS::Record>, schema.html from the base documentation.
285
286 =cut
287
288 1;
289