#!/usr/bin/perl use FS::Record qw(qsearch qsearchs); use FS::Misc::Getopt; use FS::cdr; use FS::Cursor; our %opt; getopts('f:'); unless ($opt{f}) { die " Usage: cdr-manual-rate -f freesidestatus [ -s startdate ] [ -e enddate ] user "; } $FS::UID::AutoCommit = 1; # because partial completion of this is useful my $where_date; if ($opt{start}) { $where_date = " AND startdate >= $opt{start} "; } if ($opt{end}) { $where_date .= " AND startdate < $opt{end} "; } my $cursor = FS::Cursor->new({ 'table' => 'cdr', 'hashref' => { freesidestatus => $opt{f} }, 'extra_sql' => $where_date, }); our %svc_phone = (); # phonenum => svc_phone our %pkgnum = (); # phonenum => pkgnum our %cust_pkg = (); # pkgnum => cust_pkg our %pkgpart = (); # phonenum => pkgpart our %part_pkg = (); # pkgpart => part_pkg # some stats my $total = 0; my $success = 0; my $notfound = 0; my $failed = 0; while (my $cdr = $cursor->fetch) { $total++; my $cdrdesc = "CDR ". $cdr->acctid.", ".$cdr->src." -> ".$cdr->dst; # borrow CDR-to-package matching code from cdrrated... my $number = $cdr->charged_party || $cdr->src; # strip the prefix from the number 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)); } # find a svc_phone that matches it unless ( $svc_phone{$number} ) { #only phone number matching supported right now my $svc_phone = qsearchs('svc_phone', { 'phonenum' => $number } ); unless ( $svc_phone ) { warn "can't find a phone service for $cdrdesc\n"; $notfound++; next; } $svc_phone{$number} = $svc_phone; } # find the pkgnum unless ( $pkgnum{$number} ) { my $cust_pkg = $svc_phone{$number}->cust_svc->cust_pkg; if (!$cust_pkg) { warn "can't find a package for $cdrdesc\n"; $notfound++; next; } $pkgnum{$number} = $cust_pkg->pkgnum; $cust_pkg{$cust_pkg->pkgnum} ||= $cust_pkg; } unless ( $pkgpart{$number} ) { #get the package, search through the part_pkg and linked for a voip_cdr def w/matching cdrtypenum (or no use_cdrtypenum) my $cust_pkg = $cust_pkg{$pkgnum{$number}}; my @part_pkg; foreach ($cust_pkg->part_pkg->self_and_bill_linked) { if ($_->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 '' ) ) { push @part_pkg, $_; } } if (!@part_pkg) { warn "can't find a voip_cdr package definition for $cdrdesc\n"; $notfound++; next; } elsif (scalar(@part_pkg) > 1) { warn "found more than one package that could rate $cdrdesc\n"; $notfound++; next; } $pkgpart{$number} = $part_pkg[0]->pkgpart; $part_pkg{ $part_pkg[0]->pkgpart } ||= $part_pkg[0]; } # unless $pkgpart{$number} # now actually rate the call. ignore included minutes, since that's a # property of the billing cycle and this call isn't part of a billing # cycle. my $error = $cdr->rate( 'part_pkg' => $part_pkg{ $pkgpart{$number} }, 'cust_pkg' => $cust_pkg{ $pkgnum{$number} }, 'svcnum' => $svc_phone{$number}->svcnum, ); if ( $error ) { warn "can't rate $cdrdesc: $error\n"; $failed++; next; } $error = $cdr->set_status('done'); if ( $error ) { # don't know how this would happen... warn "can't set status on $cdrdesc: $error\n"; $failed++; next; } $success++; } print " Total CDRs: $total Incomplete information: $notfound Failed rating: $failed Successfully rated: $success ";