4 use vars qw( @ISA $DEBUG );
5 use FS::Record qw( qsearch qsearchs dbh fields );
6 #use FS::agent; #causes a weird dep loop in freeside-cdrrated, only needed for v3-style manual FK-checking, so, probably not bother
15 FS::rate - Object methods for rate records
21 $record = new FS::rate \%hash;
22 $record = new FS::rate { 'column' => 'value' };
24 $error = $record->insert;
26 $error = $new_record->replace($old_record);
28 $error = $record->delete;
30 $error = $record->check;
34 An FS::rate object represents an rate plan. FS::rate inherits from
35 FS::Record. The following fields are currently supported:
49 Optional agent (see L<FS::agent>) for agent-virtualized rates.
59 Creates a new rate plan. To add the rate plan to the database, see L<"insert">.
61 Note that this stores the hash reference, not a distinct copy of the hash it
62 points to. You can ask the object for a copy with the I<hash> method.
66 # the new method can be inherited from FS::Record, if a table method is defined
70 =item insert [ , OPTION => VALUE ... ]
72 Adds this record to the database. If there is an error, returns the error,
73 otherwise returns false.
75 Currently available options are: I<rate_detail>
77 If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
78 objects will have their ratenum field set and will be inserted after this
87 local $SIG{HUP} = 'IGNORE';
88 local $SIG{INT} = 'IGNORE';
89 local $SIG{QUIT} = 'IGNORE';
90 local $SIG{TERM} = 'IGNORE';
91 local $SIG{TSTP} = 'IGNORE';
92 local $SIG{PIPE} = 'IGNORE';
94 my $oldAutoCommit = $FS::UID::AutoCommit;
95 local $FS::UID::AutoCommit = 0;
98 my $error = $self->check;
99 return $error if $error;
101 $error = $self->SUPER::insert;
103 $dbh->rollback if $oldAutoCommit;
107 if ( $options{'rate_detail'} ) {
109 my( $num, $last, $min_sec ) = (0, time, 5); #progressbar foo
111 foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
113 $rate_detail->ratenum($self->ratenum);
114 $error = $rate_detail->insert;
116 $dbh->rollback if $oldAutoCommit;
120 if ( $options{'job'} ) {
122 if ( time - $min_sec > $last ) {
123 my $error = $options{'job'}->update_statustext(
124 int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
127 $dbh->rollback if $oldAutoCommit;
137 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
146 Delete this record from the database.
150 # the delete method can be inherited from FS::Record
152 =item replace OLD_RECORD [ , OPTION => VALUE ... ]
154 Replaces the OLD_RECORD with this one in the database. If there is an error,
155 returns the error, otherwise returns false.
157 Currently available options are: I<rate_detail>
159 If I<rate_detail> is set to an array reference of FS::rate_detail objects, the
160 objects will have their ratenum field set and will be inserted after this
161 record. Any existing rate_detail records associated with this record will be
167 my ($new, $old) = (shift, shift);
170 local $SIG{HUP} = 'IGNORE';
171 local $SIG{INT} = 'IGNORE';
172 local $SIG{QUIT} = 'IGNORE';
173 local $SIG{TERM} = 'IGNORE';
174 local $SIG{TSTP} = 'IGNORE';
175 local $SIG{PIPE} = 'IGNORE';
177 my $oldAutoCommit = $FS::UID::AutoCommit;
178 local $FS::UID::AutoCommit = 0;
181 # my @old_rate_detail = ();
182 # @old_rate_detail = $old->rate_detail if $options{'rate_detail'};
184 my $error = $new->SUPER::replace($old);
186 $dbh->rollback if $oldAutoCommit;
190 # foreach my $old_rate_detail ( @old_rate_detail ) {
192 # my $error = $old_rate_detail->delete;
194 # $dbh->rollback if $oldAutoCommit;
198 # if ( $options{'job'} ) {
200 # if ( time - $min_sec > $last ) {
201 # my $error = $options{'job'}->update_statustext(
202 # int( 50 * $num / scalar( @old_rate_detail ) )
205 # $dbh->rollback if $oldAutoCommit;
213 if ( $options{'rate_detail'} ) {
214 my $sth = $dbh->prepare('DELETE FROM rate_detail WHERE ratenum = ?') or do {
215 $dbh->rollback if $oldAutoCommit;
219 $sth->execute($old->ratenum) or do {
220 $dbh->rollback if $oldAutoCommit;
224 my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
226 foreach my $rate_detail ( @{$options{'rate_detail'}} ) {
228 $rate_detail->ratenum($new->ratenum);
229 $error = $rate_detail->insert;
231 $dbh->rollback if $oldAutoCommit;
235 if ( $options{'job'} ) {
237 if ( time - $min_sec > $last ) {
238 my $error = $options{'job'}->update_statustext(
239 int( 100 * $num / scalar( @{$options{'rate_detail'}} ) )
242 $dbh->rollback if $oldAutoCommit;
253 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
260 Checks all fields to make sure this is a valid rate plan. If there is
261 an error, returns the error, otherwise returns false. Called by the insert
270 $self->ut_numbern('ratenum')
271 || $self->ut_text('ratename')
272 #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum')
274 return $error if $error;
279 =item dest_detail REGIONNUM | RATE_REGION_OBJECTD | HASHREF
281 Returns the rate detail (see L<FS::rate_detail>) for this rate to the
282 specificed destination, or the empty string if no rate can be found for
283 the given destination.
285 Destination can be specified as an FS::rate_detail object or regionnum
286 (see L<FS::rate_detail>), or as a hashref containing the following keys:
290 =item I<countrycode> - required.
292 =item I<phonenum> - required.
294 =item I<weektime> - optional. Specifies a time in seconds from the start
295 of the week, and will return a timed rate (one with a non-null I<ratetimenum>)
296 if one exists at that time. If not, returns a non-timed rate.
298 =item I<cdrtypenum> - optional. Specifies a value for the cdrtypenum
299 field, and will return a rate matching that, if one exists. If not, returns
300 a rate with null cdrtypenum.
307 my( $regionnum, $weektime, $cdrtypenum );
308 if ( ref($_[0]) eq 'HASH' ) {
310 my $countrycode = $_[0]->{'countrycode'};
311 my $phonenum = $_[0]->{'phonenum'};
312 $weektime = $_[0]->{'weektime'};
313 $cdrtypenum = $_[0]->{'cdrtypenum'} || '';
315 #find a rate prefix, first look at most specific, then fewer digits,
316 # finally trying the country code only
317 my $rate_prefix = '';
318 $rate_prefix = qsearchs({
319 'table' => 'rate_prefix',
320 'addl_from' => ' JOIN rate_region USING (regionnum)',
322 'countrycode' => $countrycode,
325 'extra_sql' => ' AND exact_match = \'Y\''
328 for my $len ( reverse(1..10) ) {
329 $rate_prefix = qsearchs('rate_prefix', {
330 'countrycode' => $countrycode,
331 #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) }
332 'npa' => substr($phonenum, 0, $len),
335 $rate_prefix ||= qsearchs('rate_prefix', {
336 'countrycode' => $countrycode,
341 return '' unless $rate_prefix;
343 $regionnum = $rate_prefix->regionnum;
346 $regionnum = ref($_[0]) ? shift->regionnum : shift;
350 'ratenum' => $self->ratenum,
351 'dest_regionnum' => $regionnum,
354 # find all rates matching ratenum, regionnum, cdrtypenum
355 my @details = qsearch( 'rate_detail', {
357 'cdrtypenum' => $cdrtypenum
359 # find all rates maching ratenum, regionnum and null cdrtypenum
360 if ( !@details and $cdrtypenum ) {
361 @details = qsearch( 'rate_detail', {
366 # find one of those matching weektime
367 if ( defined($weektime) ) {
369 my $rate_time = $_->rate_time;
370 $rate_time && $rate_time->contains($weektime)
375 elsif ( @exact > 1 ) {
376 die "overlapping rate_detail times (region $regionnum, time $weektime)\n"
380 # if not found or there is no weektime, find one matching null weektime
382 return $_ if $_->ratetimenum eq '';
390 Returns all region-specific details (see L<FS::rate_detail>) for this rate.
396 qsearch( 'rate_detail', { 'ratenum' => $self->ratenum } );
408 Job-queue processor for web interface adds/edits
412 use Storable qw(thaw);
418 my $param = thaw(decode_base64(shift));
419 warn Dumper($param) if $DEBUG;
421 my $old = qsearchs('rate', { 'ratenum' => $param->{'ratenum'} } )
422 if $param->{'ratenum'};
424 my @rate_detail = map {
426 my $regionnum = $_->regionnum;
427 if ( $param->{"sec_granularity$regionnum"} ) {
429 new FS::rate_detail {
430 'dest_regionnum' => $regionnum,
431 map { $_ => $param->{"$_$regionnum"} }
432 qw( min_included min_charge sec_granularity )
433 #qw( min_included conn_charge conn_sec min_charge sec_granularity )
438 new FS::rate_detail {
439 'dest_regionnum' => $regionnum,
445 'sec_granularity' => '60'
450 } qsearch('rate_region', {} );
452 my $rate = new FS::rate {
453 map { $_ => $param->{$_} }
458 if ( $param->{'ratenum'} ) {
459 warn "$rate replacing $old (". $param->{'ratenum'}. ")\n" if $DEBUG;
461 my @param = ( 'job'=>$job );
462 push @param, 'rate_detail'=>\@rate_detail
463 unless $param->{'preserve_rate_detail'};
465 $error = $rate->replace( $old, @param );
468 warn "inserting $rate\n" if $DEBUG;
469 $error = $rate->insert( 'rate_detail' => \@rate_detail,
472 #$ratenum = $rate->getfield('ratenum');
475 die "$error\n" if $error;
483 L<FS::Record>, schema.html from the base documentation.