RT #31482 making sure the tax class is not editable after the customer has been billed.
[freeside.git] / FS / FS / phone_avail.pm
1 package FS::phone_avail;
2 use base qw( FS::cust_main_Mixin FS::Record );
3
4 use strict;
5 use vars qw( $DEBUG $me );
6 use FS::Misc::DateTime qw( parse_datetime );
7 use FS::Record qw( qsearch qsearchs dbh );
8 use FS::cust_svc;
9 use FS::msa;
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 =item lata 
178
179 =item msa2msanum
180
181 Translate free-form MSA name to a msa.msanum
182
183 =cut
184
185 sub msa2msanum {
186     my $self = shift;
187     my $msa = shift;
188
189     if ( $msa =~ /(.+[^,])\s+(\w{2}(-\w{2})*)$/ ) {
190       $msa = "$1, $2";
191     }
192
193     my @msas = qsearch('msa', { 'description' => { 'op' => 'ILIKE',
194                                                    'value' => "%$msa%", }
195                               });
196     return 0 unless scalar(@msas);
197     my @msa = grep { $self->msatest($msa,$_->description) } @msas;
198     return 0 unless scalar(@msa) == 1;
199     $msa[0]->msanum;
200 }
201
202 sub msatest {
203     my $self = shift;
204     my ($their,$our) = (shift,shift);
205
206     $their =~ s/^\s+//;
207     $their =~ s/\s+$//;
208     $their =~ s/\s+/ /g;
209     return 1 if $our eq $their;
210
211     my $a = $our;
212     $a =~ s/,.*?$//;
213     return 1 if $a eq $their;
214     return 1 if ($our =~ /^([\w\s]+)-/ && $1 eq $their);
215     0;
216 }
217
218 sub process_batch_import {
219   my $job = shift;
220
221   my $numsub = sub {
222     my( $phone_avail, $value ) = @_;
223     $value =~ s/\D//g;
224     $value =~ /^(\d{3})(\d{3})(\d+)$/ or die "unparsable number $value\n";
225     #( $hash->{npa}, $hash->{nxx}, $hash->{station} ) = ( $1, $2, $3 );
226     $phone_avail->npa($1);
227     $phone_avail->nxx($2);
228     $phone_avail->station($3);
229   };
230
231   my $msasub = sub {
232     my( $phone_avail, $value ) = @_;
233     return '' if !$value;
234     my $msanum = $phone_avail->msa2msanum($value);
235     die "cannot translate MSA ($value) to msanum" unless $msanum;
236     $phone_avail->msanum($msanum);
237   };
238
239   my $opt = { 'table'   => 'phone_avail',
240               'params'  => [ 'availbatch', 'exportnum', 'countrycode', 'ordernum', 'vendor_order_id', 'confirmed' ],
241               'formats' => { 'default' => [ 'state', $numsub, 'name' ],
242                  'bulk' => [ 'state', $numsub, 'name', 'rate_center_abbrev', $msasub, 'latanum' ],
243                },
244                'postinsert_callback' => sub {  
245                     my $record = shift;
246                     if($record->ordernum) {
247                         my $did_order = qsearchs('did_order', 
248                                             { 'ordernum' => $record->ordernum } );
249                         if($did_order && !$did_order->received) {
250                             $did_order->received(time);
251                             $did_order->confirmed(parse_datetime($record->confirmed));
252                             $did_order->vendor_order_id($record->vendor_order_id);
253                             $did_order->replace;
254                         }
255                     }
256                }, 
257             };
258
259   FS::Record::process_batch_import( $job, $opt, @_ );
260 }
261
262 sub flush { # evil direct SQL
263     my $opt = shift;
264
265     if ( $opt->{'ratecenter'} =~ /^[\w\s]+$/
266         && $opt->{'state'} =~ /^[A-Z][A-Z]$/ 
267         && $opt->{'exportnum'} =~ /^\d+$/) {
268     my $sth = dbh->prepare('delete from phone_avail where exportnum = ? '.
269             ' and state = ? and name = ?');
270     $sth->execute($opt->{'exportnum'},$opt->{'state'},$opt->{'ratecenter'})
271         or die $sth->errstr;
272     }
273
274     '';
275 }
276
277 # Used by FS::Upgrade to migrate to a new database.
278 sub _upgrade_data {
279   my ($class, %opts) = @_;
280
281   warn "$me upgrading $class\n" if $DEBUG;
282
283   my $sth = dbh->prepare(
284     'UPDATE phone_avail SET svcnum = NULL
285        WHERE svcnum IS NOT NULL
286          AND 0 = ( SELECT COUNT(*) FROM svc_phone
287                      WHERE phone_avail.svcnum = svc_phone.svcnum )'
288   ) or die dbh->errstr;
289
290   $sth->execute or die $sth->errstr;
291
292 }
293
294 =back
295
296 =head1 BUGS
297
298 Sparse documentation.
299
300 =head1 SEE ALSO
301
302 L<FS::Record>, schema.html from the base documentation.
303
304 =cut
305
306 1;
307