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 containing the following keys:
283 =item I<countrycode> - required.
285 =item I<phonenum> - required.
287 =item I<weektime> - optional. Specifies a time in seconds from the start
288 of the week, and will return a timed rate (one with a non-null I<ratetimenum>)
289 if one exists at that time. If not, returns a non-timed rate.
291 =item I<cdrtypenum> - optional. Specifies a value for the cdrtypenum
292 field, and will return a rate matching that, if one exists. If not, returns
293 a rate with null cdrtypenum.
300 my( $regionnum, $weektime, $cdrtypenum );
301 if ( ref($_[0]) eq 'HASH' ) {
303 my $countrycode = $_[0]->{'countrycode'};
304 my $phonenum = $_[0]->{'phonenum'};
305 $weektime = $_[0]->{'weektime'};
306 $cdrtypenum = $_[0]->{'cdrtypenum'} || '';
308 #find a rate prefix, first look at most specific, then fewer digits,
309 # finally trying the country code only
310 my $rate_prefix = '';
311 $rate_prefix = qsearchs({
312 'table' => 'rate_prefix',
313 'addl_from' => ' JOIN rate_region USING (regionnum)',
315 'countrycode' => $countrycode,
318 'extra_sql' => ' AND exact_match = \'Y\''
321 for my $len ( reverse(1..10) ) {
322 $rate_prefix = qsearchs('rate_prefix', {
323 'countrycode' => $countrycode,
324 #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) }
325 'npa' => substr($phonenum, 0, $len),
328 $rate_prefix ||= qsearchs('rate_prefix', {
329 'countrycode' => $countrycode,
334 return '' unless $rate_prefix;
336 $regionnum = $rate_prefix->regionnum;
339 $regionnum = ref($_[0]) ? shift->regionnum : shift;
343 'ratenum' => $self->ratenum,
344 'dest_regionnum' => $regionnum,
347 # find all rates matching ratenum, regionnum, cdrtypenum
348 my @details = qsearch( 'rate_detail', {
350 'cdrtypenum' => $cdrtypenum
352 # find all rates maching ratenum, regionnum and null cdrtypenum
353 if ( !@details and $cdrtypenum ) {
354 @details = qsearch( 'rate_detail', {
359 # find one of those matching weektime
360 if ( defined($weektime) ) {
362 my $rate_time = $_->rate_time;
363 $rate_time && $rate_time->contains($weektime)
368 elsif ( @exact > 1 ) {
369 die "overlapping rate_detail times (region $regionnum, time $weektime)\n"
373 # if not found or there is no weektime, find one matching null weektime
375 return $_ if $_->ratetimenum eq '';
383 Returns all region-specific details (see L<FS::rate_detail>) for this rate.
389 qsearch( 'rate_detail', { 'ratenum' => $self->ratenum } );
401 Job-queue processor for web interface adds/edits
405 use Storable qw(thaw);
411 my $param = thaw(decode_base64(shift));
412 warn Dumper($param) if $DEBUG;
414 my $old = qsearchs('rate', { 'ratenum' => $param->{'ratenum'} } )
415 if $param->{'ratenum'};
417 my @rate_detail = map {
419 my $regionnum = $_->regionnum;
420 if ( $param->{"sec_granularity$regionnum"} ) {
422 new FS::rate_detail {
423 'dest_regionnum' => $regionnum,
424 map { $_ => $param->{"$_$regionnum"} }
425 qw( min_included min_charge sec_granularity )
426 #qw( min_included conn_charge conn_sec min_charge sec_granularity )
431 new FS::rate_detail {
432 'dest_regionnum' => $regionnum,
438 'sec_granularity' => '60'
443 } qsearch('rate_region', {} );
445 my $rate = new FS::rate {
446 map { $_ => $param->{$_} }
451 if ( $param->{'ratenum'} ) {
452 warn "$rate replacing $old (". $param->{'ratenum'}. ")\n" if $DEBUG;
454 my @param = ( 'job'=>$job );
455 push @param, 'rate_detail'=>\@rate_detail
456 unless $param->{'preserve_rate_detail'};
458 $error = $rate->replace( $old, @param );
461 warn "inserting $rate\n" if $DEBUG;
462 $error = $rate->insert( 'rate_detail' => \@rate_detail,
465 #$ratenum = $rate->getfield('ratenum');
468 die "$error\n" if $error;
476 L<FS::Record>, schema.html from the base documentation.