--- /dev/null
+#!/usr/bin/perl -w
+
+use strict;
+use FS::Daemon ':all'; #daemonize1 drop_root daemonize2 myexit logfile sig*
+use FS::UID qw( adminsuidsetup );
+use FS::Record qw( qsearch qsearchs );
+use FS::cdr;
+use FS::svc_phone;
+use FS::part_pkg;
+
+my $user = shift or die &usage;
+
+daemonize1('freeside-cdrrated');
+
+drop_root();
+
+adminsuidsetup($user);
+
+logfile( "%%%FREESIDE_LOG%%%/cdrrated-log.". $FS::UID::datasrc );
+
+daemonize2();
+
+our $conf = new FS::Conf;
+
+die "not running; cdr-prerate conf option is off\n"
+ unless _shouldrun();
+
+#--
+
+my $extra_sql = '';
+my @cdrtypenums = $conf->config('cdr-prerate-cdrtypenums');
+if ( @cdrtypenums ) {
+ $extra_sql .= ' AND cdrtypenum IN ('. join(',', @cdrtypenums ). ')';
+}
+
+our %svcnum = ();
+our %pkgpart = ();
+our %part_pkg = ();
+
+#some false laziness w/freeside-cdrrewrited
+
+while (1) {
+
+ my $found = 0;
+ foreach my $cdr (
+ qsearch( {
+ 'table' => 'cdr',
+ 'hashref' => { 'freesidestatus' => '' },
+ 'extra_sql' => $extra_sql.
+ ' LIMIT 1024'. #arbitrary, but don't eat too much memory
+ ' FOR UPDATE',
+ } )
+
+ ) {
+
+ $found = 1;
+
+ #find the matching service - some weird false laziness w/svc_phone::get_cdrs
+
+ #in charged_party or src
+ #hmm... edge case; get_cdrs rating will match a src if a charged_party is
+ # present #but doesn't match a service...
+ my $number = $cdr->charged_party || $cdr->src;
+
+ #technically default_prefix. phonenum or phonenum (or default_prefix without the + . phonenum)
+ #but for now we're just assuming default_prefix is +1
+ my $prefix = '+1'; #$options{'default_prefix'};
+
+ $number = substr($number, length($prefix))
+ if $prefix eq substr($number, 0, length($prefix));
+ if ( $prefix && $prefix =~ /^\+(\d+)$/ ) {
+ $prefix = $1;
+ $number = substr($number, length($prefix))
+ if $prefix eq substr($number, 0, length($prefix));
+ }
+
+ unless ( $svcnum{$number} ) {
+ #only phone number matching supported right now
+ my $svc_phone = qsearchs('svc_phone', { 'phonenum' => $number } );
+ unless ( $svc_phone ) {
+ #XXX set freesideratestatus or something so we don't keep retrying?
+ next;
+ }
+
+ $svcnum{$number} = $svc_phone->svcnum;
+
+ my $cust_pkg = $svc_phone->cust_svc->cust_pkg;
+ unless ( $cust_pkg ) {
+ #XXX unlinked svc_phone?
+ # warn and also set freesideratestatus or somesuch?
+ next;
+ }
+
+ #get the package, search through the part_pkg and linked for a voip_cdr def w/matching cdrtypenum (or no use_cdrtypenum)
+ my @part_pkg =
+ grep { $_->plan eq 'voip_cdr'
+ && ( ! length($_->option_cacheable('use_cdrtypenum'))
+ || $_->option_cacheable('use_cdrtypenum')
+ eq $cdr->cdrtypenum #eq otherwise 0 matches ''
+ )
+ && ( ! length($_->option_cacheable('ignore_cdrtypenum'))
+ || $_->option_cacheable('ignore_cdrtypenum')
+ ne $cdr->cdrtypenum #ne otherwise 0 matches ''
+ )
+
+ }
+ $cust_pkg->part_pkg->self_and_bill_linked;
+
+ if ( ! @part_pkg ) {
+ #XXX no package for this CDR
+ # warn and also set freesideratestatus or somesuch?
+ # or at least warn
+ next;
+ } elsif ( scalar(@part_pkg) > 1 ) {
+ warn "multiple package could rate CDR ". $cdr->acctid. "\n";
+ # and also set freesideratestatus or somesuch?
+ next;
+ }
+
+ $pkgpart{$number} = $part_pkg[0]->pkgpart;
+ $part_pkg{ $part_pkg[0]->pkgpart } ||= $part_pkg[0];
+
+ }
+
+ #unless ( $part_pkg{$pkgpart{$number}} ) {
+ #}
+
+ #XXX if $part_pkg->option('min_included') then we can't prerate this CDR
+
+ my $error = $cdr->rate(
+ 'part_pkg' => $part_pkg{ $pkgpart{$number} },
+ 'svcnum' => $svcnum{ $number },
+ );
+ if ( $error ) {
+ #XXX ???
+ warn $error;
+ sleep 30;
+ }
+
+ last if sigterm() || sigint();
+
+ }
+
+ myexit() if sigterm() || sigint();
+ sleep 5 unless $found;
+
+}
+
+#--
+
+sub _shouldrun {
+ $conf->exists('cdr-prerate');
+}
+
+sub usage {
+ die "Usage:\n\n freeside-cdrrewrited user\n";
+}
+
+=head1 NAME
+
+freeside-cdrrated - Real-time daemon for CDR rating
+
+=head1 SYNOPSIS
+
+ freeside-cdrrated
+
+=head1 DESCRIPTION
+
+Runs continuously, searches for CDRs and which can be pre-rated, and rates them.
+
+=head1 SEE ALSO
+
+cdr-prerate configuration setting
+
+=cut
+
+1;
+