4 use vars qw( @ISA $DEBUG );
5 use FS::Record qw( qsearch qsearchs dbh fields );
14 FS::rate - Object methods for rate records
20 $record = new FS::rate \%hash;
21 $record = new FS::rate { 'column' => 'value' };
23 $error = $record->insert;
25 $error = $new_record->replace($old_record);
27 $error = $record->delete;
29 $error = $record->check;
33 An FS::rate object represents an rate plan. FS::rate inherits from
34 FS::Record. The following fields are currently supported:
38 =item ratenum - primary key
50 Creates a new rate plan. To add the rate plan to the database, see L<"insert">.
52 Note that this stores the hash reference, not a distinct copy of the hash it
53 points to. You can ask the object for a copy with the I<hash> method.
57 # the new method can be inherited from FS::Record, if a table method is defined
61 =item insert [ , OPTION => VALUE ... ]
63 Adds this record to the database. If there is an error, returns the error,
64 otherwise returns false.
66 Currently available options are: I<rate_detail>
68 If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
69 objects will have their ratenum field set and will be inserted after this
78 local $SIG{HUP} = 'IGNORE';
79 local $SIG{INT} = 'IGNORE';
80 local $SIG{QUIT} = 'IGNORE';
81 local $SIG{TERM} = 'IGNORE';
82 local $SIG{TSTP} = 'IGNORE';
83 local $SIG{PIPE} = 'IGNORE';
85 my $oldAutoCommit = $FS::UID::AutoCommit;
86 local $FS::UID::AutoCommit = 0;
89 my $error = $self->check;
90 return $error if $error;
92 $error = $self->SUPER::insert;
94 $dbh->rollback if $oldAutoCommit;
98 if ( $options{'rate_detail'} ) {
100 my( $num, $last, $min_sec ) = (0, time, 5); #progressbar foo
102 foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
104 $rate_detail->ratenum($self->ratenum);
105 $error = $rate_detail->insert;
107 $dbh->rollback if $oldAutoCommit;
111 if ( $options{'job'} ) {
113 if ( time - $min_sec > $last ) {
114 my $error = $options{'job'}->update_statustext(
115 int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
118 $dbh->rollback if $oldAutoCommit;
128 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
137 Delete this record from the database.
141 # the delete method can be inherited from FS::Record
143 =item replace OLD_RECORD [ , OPTION => VALUE ... ]
145 Replaces the OLD_RECORD with this one in the database. If there is an error,
146 returns the error, otherwise returns false.
148 Currently available options are: I<rate_detail>
150 If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
151 objects will have their ratenum field set and will be inserted after this
152 record. Any existing rate_detail records associated with this record will be
158 my ($new, $old) = (shift, shift);
161 local $SIG{HUP} = 'IGNORE';
162 local $SIG{INT} = 'IGNORE';
163 local $SIG{QUIT} = 'IGNORE';
164 local $SIG{TERM} = 'IGNORE';
165 local $SIG{TSTP} = 'IGNORE';
166 local $SIG{PIPE} = 'IGNORE';
168 my $oldAutoCommit = $FS::UID::AutoCommit;
169 local $FS::UID::AutoCommit = 0;
172 # my @old_rate_detail = ();
173 # @old_rate_detail = $old->rate_detail if $options{'rate_detail'};
175 my $error = $new->SUPER::replace($old);
177 $dbh->rollback if $oldAutoCommit;
181 # foreach my $old_rate_detail ( @old_rate_detail ) {
183 # my $error = $old_rate_detail->delete;
185 # $dbh->rollback if $oldAutoCommit;
189 # if ( $options{'job'} ) {
191 # if ( time - $min_sec > $last ) {
192 # my $error = $options{'job'}->update_statustext(
193 # int( 50 * $num / scalar( @old_rate_detail ) )
196 # $dbh->rollback if $oldAutoCommit;
204 if ( $options{'rate_detail'} ) {
205 my $sth = $dbh->prepare('DELETE FROM rate_detail WHERE ratenum = ?') or do {
206 $dbh->rollback if $oldAutoCommit;
210 $sth->execute($old->ratenum) or do {
211 $dbh->rollback if $oldAutoCommit;
215 my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
217 foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
219 $rate_detail->ratenum($new->ratenum);
220 $error = $rate_detail->insert;
222 $dbh->rollback if $oldAutoCommit;
226 if ( $options{'job'} ) {
228 if ( time - $min_sec > $last ) {
229 my $error = $options{'job'}->update_statustext(
230 int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
233 $dbh->rollback if $oldAutoCommit;
244 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
251 Checks all fields to make sure this is a valid rate plan. If there is
252 an error, returns the error, otherwise returns false. Called by the insert
257 # the check method should currently be supplied - FS::Record contains some
258 # data checking routines
264 $self->ut_numbern('ratenum')
265 || $self->ut_text('ratename')
267 return $error if $error;
272 =item dest_detail REGIONNUM | RATE_REGION_OBJECTD | HASHREF
274 Returns the rate detail (see L<FS::rate_detail>) for this rate to the
275 specificed destination, or the empty string if no rate can be found for
276 the given destination.
278 Destination can be specified as an FS::rate_detail object or regionnum
279 (see L<FS::rate_detail>), or as a hashref with two keys: I<countrycode>
282 An optional third key, I<weektime>, will return a timed rate (one with
283 a non-null I<ratetimenum>) if one exists for a call at that time. If
284 no matching timed rate exists, the non-timed rate will be returned.
293 if ( ref($_[0]) eq 'HASH' ) {
295 my $countrycode = $_[0]->{'countrycode'};
296 my $phonenum = $_[0]->{'phonenum'};
297 $weektime = $_[0]->{'weektime'};
299 #find a rate prefix, first look at most specific, then fewer digits,
300 # finally trying the country code only
301 my $rate_prefix = '';
302 for my $len ( reverse(1..10) ) {
303 $rate_prefix = qsearchs('rate_prefix', {
304 'countrycode' => $countrycode,
305 #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) }
306 'npa' => substr($phonenum, 0, $len),
309 $rate_prefix ||= qsearchs('rate_prefix', {
310 'countrycode' => $countrycode,
314 return '' unless $rate_prefix;
316 $regionnum = $rate_prefix->regionnum;
318 #$rate_region = $rate_prefix->rate_region;
321 $regionnum = ref($_[0]) ? shift->regionnum : shift;
324 if(!defined($weektime)) {
325 return qsearchs( 'rate_detail',
326 { 'ratenum' => $self->ratenum,
327 'dest_regionnum' => $regionnum,
332 my @details = grep { my $rate_time = $_->rate_time;
333 $rate_time && $rate_time->contains($weektime) }
334 qsearch( 'rate_detail',
335 { 'ratenum' => $self->ratenum,
336 'dest_regionnum' => $regionnum, } );
338 # this may change at some point
339 return $self->dest_detail($regionnum);
341 elsif(@details == 1) {
345 die "overlapping rate_detail times (region $regionnum, time $weektime)\n";
352 Returns all region-specific details (see L<FS::rate_detail>) for this rate.
358 qsearch( 'rate_detail', { 'ratenum' => $self->ratenum } );
370 Experimental job-queue processor for web interface adds/edits
374 use Storable qw(thaw);
380 my $param = thaw(decode_base64(shift));
381 warn Dumper($param) if $DEBUG;
383 my $old = qsearchs('rate', { 'ratenum' => $param->{'ratenum'} } )
384 if $param->{'ratenum'};
386 my @rate_detail = map {
388 my $regionnum = $_->regionnum;
389 if ( $param->{"sec_granularity$regionnum"} ) {
391 new FS::rate_detail {
392 'dest_regionnum' => $regionnum,
393 map { $_ => $param->{"$_$regionnum"} }
394 qw( min_included min_charge sec_granularity )
395 #qw( min_included conn_charge conn_sec min_charge sec_granularity )
400 new FS::rate_detail {
401 'dest_regionnum' => $regionnum,
407 'sec_granularity' => '60'
412 } qsearch('rate_region', {} );
414 my $rate = new FS::rate {
415 map { $_ => $param->{$_} }
420 if ( $param->{'ratenum'} ) {
421 warn "$rate replacing $old (". $param->{'ratenum'}. ")\n" if $DEBUG;
423 my @param = ( 'job'=>$job );
424 push @param, 'rate_detail'=>\@rate_detail
425 unless $param->{'preserve_rate_detail'};
427 $error = $rate->replace( $old, @param );
430 warn "inserting $rate\n" if $DEBUG;
431 $error = $rate->insert( 'rate_detail' => \@rate_detail,
434 #$ratenum = $rate->getfield('ratenum');
437 die "$error\n" if $error;
445 L<FS::Record>, schema.html from the base documentation.