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:
48 Optional agent (see L<FS::agent>) for agent-virtualized rates.
58 Creates a new rate plan. To add the rate plan to the database, see L<"insert">.
60 Note that this stores the hash reference, not a distinct copy of the hash it
61 points to. You can ask the object for a copy with the I<hash> method.
65 # the new method can be inherited from FS::Record, if a table method is defined
69 =item insert [ , OPTION => VALUE ... ]
71 Adds this record to the database. If there is an error, returns the error,
72 otherwise returns false.
74 Currently available options are: I<rate_detail>
76 If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
77 objects will have their ratenum field set and will be inserted after this
86 local $SIG{HUP} = 'IGNORE';
87 local $SIG{INT} = 'IGNORE';
88 local $SIG{QUIT} = 'IGNORE';
89 local $SIG{TERM} = 'IGNORE';
90 local $SIG{TSTP} = 'IGNORE';
91 local $SIG{PIPE} = 'IGNORE';
93 my $oldAutoCommit = $FS::UID::AutoCommit;
94 local $FS::UID::AutoCommit = 0;
97 my $error = $self->check;
98 return $error if $error;
100 $error = $self->SUPER::insert;
102 $dbh->rollback if $oldAutoCommit;
106 if ( $options{'rate_detail'} ) {
108 my( $num, $last, $min_sec ) = (0, time, 5); #progressbar foo
110 foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
112 $rate_detail->ratenum($self->ratenum);
113 $error = $rate_detail->insert;
115 $dbh->rollback if $oldAutoCommit;
119 if ( $options{'job'} ) {
121 if ( time - $min_sec > $last ) {
122 my $error = $options{'job'}->update_statustext(
123 int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
126 $dbh->rollback if $oldAutoCommit;
136 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
145 Delete this record from the database.
149 # the delete method can be inherited from FS::Record
151 =item replace OLD_RECORD [ , OPTION => VALUE ... ]
153 Replaces the OLD_RECORD with this one in the database. If there is an error,
154 returns the error, otherwise returns false.
156 Currently available options are: I<rate_detail>
158 If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
159 objects will have their ratenum field set and will be inserted after this
160 record. Any existing rate_detail records associated with this record will be
166 my ($new, $old) = (shift, shift);
169 local $SIG{HUP} = 'IGNORE';
170 local $SIG{INT} = 'IGNORE';
171 local $SIG{QUIT} = 'IGNORE';
172 local $SIG{TERM} = 'IGNORE';
173 local $SIG{TSTP} = 'IGNORE';
174 local $SIG{PIPE} = 'IGNORE';
176 my $oldAutoCommit = $FS::UID::AutoCommit;
177 local $FS::UID::AutoCommit = 0;
180 # my @old_rate_detail = ();
181 # @old_rate_detail = $old->rate_detail if $options{'rate_detail'};
183 my $error = $new->SUPER::replace($old);
185 $dbh->rollback if $oldAutoCommit;
189 # foreach my $old_rate_detail ( @old_rate_detail ) {
191 # my $error = $old_rate_detail->delete;
193 # $dbh->rollback if $oldAutoCommit;
197 # if ( $options{'job'} ) {
199 # if ( time - $min_sec > $last ) {
200 # my $error = $options{'job'}->update_statustext(
201 # int( 50 * $num / scalar( @old_rate_detail ) )
204 # $dbh->rollback if $oldAutoCommit;
212 if ( $options{'rate_detail'} ) {
213 my $sth = $dbh->prepare('DELETE FROM rate_detail WHERE ratenum = ?') or do {
214 $dbh->rollback if $oldAutoCommit;
218 $sth->execute($old->ratenum) or do {
219 $dbh->rollback if $oldAutoCommit;
223 my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
225 foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
227 $rate_detail->ratenum($new->ratenum);
228 $error = $rate_detail->insert;
230 $dbh->rollback if $oldAutoCommit;
234 if ( $options{'job'} ) {
236 if ( time - $min_sec > $last ) {
237 my $error = $options{'job'}->update_statustext(
238 int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
241 $dbh->rollback if $oldAutoCommit;
252 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
259 Checks all fields to make sure this is a valid rate plan. If there is
260 an error, returns the error, otherwise returns false. Called by the insert
269 $self->ut_numbern('ratenum')
270 || $self->ut_text('ratename')
271 #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
273 return $error if $error;
278 =item dest_detail REGIONNUM | RATE_REGION_OBJECTD | HASHREF
280 Returns the rate detail (see L<FS::rate_detail>) for this rate to the
281 specificed destination, or the empty string if no rate can be found for
282 the given destination.
284 Destination can be specified as an FS::rate_detail object or regionnum
285 (see L<FS::rate_detail>), or as a hashref containing the following keys:
289 =item I<countrycode> - required.
291 =item I<phonenum> - required.
293 =item I<weektime> - optional. Specifies a time in seconds from the start
294 of the week, and will return a timed rate (one with a non-null I<ratetimenum>)
295 if one exists at that time. If not, returns a non-timed rate.
297 =item I<cdrtypenum> - optional. Specifies a value for the cdrtypenum
298 field, and will return a rate matching that, if one exists. If not, returns
299 a rate with null cdrtypenum.
306 my( $regionnum, $weektime, $cdrtypenum );
307 if ( ref($_[0]) eq 'HASH' ) {
309 my $countrycode = $_[0]->{'countrycode'};
310 my $phonenum = $_[0]->{'phonenum'};
311 $weektime = $_[0]->{'weektime'};
312 $cdrtypenum = $_[0]->{'cdrtypenum'} || '';
314 #find a rate prefix, first look at most specific, then fewer digits,
315 # finally trying the country code only
316 my $rate_prefix = '';
317 $rate_prefix = qsearchs({
318 'table' => 'rate_prefix',
319 'addl_from' => ' JOIN rate_region USING (regionnum)',
321 'countrycode' => $countrycode,
324 'extra_sql' => ' AND exact_match = \'Y\''
327 for my $len ( reverse(1..10) ) {
328 $rate_prefix = qsearchs('rate_prefix', {
329 'countrycode' => $countrycode,
330 #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) }
331 'npa' => substr($phonenum, 0, $len),
334 $rate_prefix ||= qsearchs('rate_prefix', {
335 'countrycode' => $countrycode,
340 return '' unless $rate_prefix;
342 $regionnum = $rate_prefix->regionnum;
345 $regionnum = ref($_[0]) ? shift->regionnum : shift;
349 'ratenum' => $self->ratenum,
350 'dest_regionnum' => $regionnum,
353 # find all rates matching ratenum, regionnum, cdrtypenum
354 my @details = qsearch( 'rate_detail', {
356 'cdrtypenum' => $cdrtypenum
358 # find all rates maching ratenum, regionnum and null cdrtypenum
359 if ( !@details and $cdrtypenum ) {
360 @details = qsearch( 'rate_detail', {
365 # find one of those matching weektime
366 if ( defined($weektime) ) {
368 my $rate_time = $_->rate_time;
369 $rate_time && $rate_time->contains($weektime)
374 elsif ( @exact > 1 ) {
375 die "overlapping rate_detail times (region $regionnum, time $weektime)\n"
379 # if not found or there is no weektime, find one matching null weektime
381 return $_ if $_->ratetimenum eq '';
389 Returns all region-specific details (see L<FS::rate_detail>) for this rate.
395 qsearch( 'rate_detail', { 'ratenum' => $self->ratenum } );
404 eval "use FS::agent";
406 qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
417 Job-queue processor for web interface adds/edits
421 use Storable qw(thaw);
427 my $param = thaw(decode_base64(shift));
428 warn Dumper($param) if $DEBUG;
430 my $old = qsearchs('rate', { 'ratenum' => $param->{'ratenum'} } )
431 if $param->{'ratenum'};
433 my @rate_detail = map {
435 my $regionnum = $_->regionnum;
436 if ( $param->{"sec_granularity$regionnum"} ) {
438 new FS::rate_detail {
439 'dest_regionnum' => $regionnum,
440 map { $_ => $param->{"$_$regionnum"} }
441 qw( min_included min_charge sec_granularity )
442 #qw( min_included conn_charge conn_sec min_charge sec_granularity )
447 new FS::rate_detail {
448 'dest_regionnum' => $regionnum,
454 'sec_granularity' => '60'
459 } qsearch('rate_region', {} );
461 my $rate = new FS::rate {
462 map { $_ => $param->{$_} }
467 if ( $param->{'ratenum'} ) {
468 warn "$rate replacing $old (". $param->{'ratenum'}. ")\n" if $DEBUG;
470 my @param = ( 'job'=>$job );
471 push @param, 'rate_detail'=>\@rate_detail
472 unless $param->{'preserve_rate_detail'};
474 $error = $rate->replace( $old, @param );
477 warn "inserting $rate\n" if $DEBUG;
478 $error = $rate->insert( 'rate_detail' => \@rate_detail,
481 #$ratenum = $rate->getfield('ratenum');
484 die "$error\n" if $error;
492 L<FS::Record>, schema.html from the base documentation.