From 92adead1404db357a11b58f38c2b4403381a2809 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 24 Oct 2008 21:31:38 +0000 Subject: [PATCH] adding prepaid self-service hooks, RT#4100 --- FS/FS/ClientAPI/PrepaidPhone.pm | 196 ++++++++++++++++++++++++++++++++++++++ FS/FS/part_pkg/voip_cdr.pm | 45 ++++----- FS/FS/part_pkg/voip_sqlradacct.pm | 1 + FS/FS/rate.pm | 42 +++++++- 4 files changed, 253 insertions(+), 31 deletions(-) create mode 100644 FS/FS/ClientAPI/PrepaidPhone.pm diff --git a/FS/FS/ClientAPI/PrepaidPhone.pm b/FS/FS/ClientAPI/PrepaidPhone.pm new file mode 100644 index 000000000..2d6297a8a --- /dev/null +++ b/FS/FS/ClientAPI/PrepaidPhone.pm @@ -0,0 +1,196 @@ +package FS::ClientAPI::PrepaidPhone; + +use strict; +#use vars qw($DEBUG $me); +use FS::Record qw(qsearchs); +use FS::rate; +use FS::svc_phone; + +#$DEBUG = 0; +#$me = '[FS::ClientAPI::PrepaidPhone]'; + +#TODO: +# - shared-secret auth? (set a conf value) + +=item call_time HASHREF + +HASHREF contains the following parameters: + +=over 4 + +=item src + +Source number (with countrycode) + +=item dst + +Destination number (with countrycode) + +=back + +Always returns a hashref. If there is an error, the hashref contains a single +"error" key with the error message as a value. Otherwise, returns a hashref +with the following keys: + +=over 4 + +=item custnum + +Empty if no customer is found associated with the number, customer number +otherwise. + +=item seconds + +Number of seconds remaining for a call to destination number + +=back + +=cut + +sub call_time { + my $packet = shift; + + my $src = $packet->{'src'}; + my $dst = $packet->{'dst'}; + + my $number; + #my $conf = new FS::Conf; + #if ( #XXX toll-free? collect? + # $phonenum = $dst; + #} else { #use the src to find the customer + $number = $src; + #} + + my( $countrycode, $phonenum ); + if ( $number #this is an interesting regex to parse out 1&2 digit countrycodes + =~ /^(2[078]|3[0-469]|4[013-9]|5[1-8]|6[0-6]|7|8[1-469]|9[0-58])(\d*)$/ + || $number =~ /^(\d{3})(\d*)$/ + ) + { + $countrycode = $1; + $phonenum = $2; + } else { + return { 'error' => "unparsable number: $number" }; + } + + my $svc_phone = qsearchs('svc_phone', { 'countrycode' => $countrycode, + 'phonenum' => $phonenum, + } + ); + + unless ( $svc_phone ) { + return { 'custnum' => '', + 'seconds' => 0, + #'balance' => 0, + }; + }; + + my $cust_pkg = $svc_phone->cust_svc->cust_pkg; + my $part_pkg = $cust_pkg->part_pkg; + my $cust_main = $cust_pkg->cust_main; + + my %return = ( + 'custnum' => $cust_pkg->custnum, + #'balance' => $cust_pkg->cust_main->balance, + ); + + return \%return unless $part_pkg->plan eq 'voip_cdr' + && $part_pkg->option('rating_method') eq 'prefix'; + + my $rate = qsearchs('rate', { 'ratenum' => $part_pkg->option('ratenum') } ); + + #rate the call and arrive at a max # of seconds for the customer's balance + my $rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode, + 'phonenum' => $phonenum, + }); + + #XXX granularity? included minutes? another day... + + $return{'seconds'} = int(60 * $cust_main->balance / $rate_detail->min_charge); + + return \%return; + +} + +=item call_time_nanpa + +Like I, except countrycode 1 is not required, and all other +countrycodes must be prefixed with 011. + +=cut + +# - everything is assumed to be countrycode 1 unless it starts with 011(ccode) +sub call_time_nanpa { + my $packet = shift; + + foreach (qw( src dst )) { + if ( $packet->{$_} =~ /^011(\d+)/ ) { + $packet->{$_} = $1; + } elsif ( $packet->{$_} !~ /^1/ ) { + $packet->{$_} = '1'.$packet->{$_}; + } + } + + call_time($packet); + +} + +=item phonenum_balance HASHREF + +HASHREF contains the following parameters: + +=over 4 + +=item countrycode + +Optional countrycode. Defaults to 1. + +=item phonenum + +Phone number. + +=back + +Always returns a hashref. If there is an error, the hashref contains a single +"error" key with the error message as a value. Otherwise, returns a hashref +with the following keys: + +=over 4 + +=item custnum + +Empty if no customer is found associated with the number, customer number +otherwise. + +=item balance + +Customer balance. + +=back + +=cut + +sub phonenum_balance { + my $packet = shift; + + my $svc_phone = qsearchs('svc_phone', { + 'countrycode' => ( $packet->{'countrycode'} || 1 ), + 'phonenum' => $packet->{'phonenum'}, + }); + + unless ( $svc_phone ) { + return { 'custnum' => '', + 'balance' => 0, + }; + }; + + my $cust_pkg = $svc_phone->cust_svc->cust_pkg; + + return { + 'custnum' => $cust_pkg->custnum, + 'balance' => $cust_pkg->cust_main->balance, + }; + +} + +1; diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 0ceaac181..1b565f2c4 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -8,8 +8,9 @@ use FS::Conf; use FS::Record qw(qsearchs qsearch); use FS::part_pkg::flat; use FS::cdr; -#use FS::rate; -#use FS::rate_prefix; +use FS::rate; +use FS::rate_prefix; +use FS::rate_detail; @ISA = qw(FS::part_pkg::flat); @@ -125,6 +126,10 @@ tie my %temporalities, 'Tie::IxHash', 'type' => 'checkbox', }, + 'bill_every_call' => { 'name' => 'Generate an invoice immediately for every call. Useful for prepaid.', + 'type' => 'checkbox', + }, + #XXX also have option for an external db # 'cdr_location' => { 'name' => 'CDR database location' # 'type' => 'select', @@ -161,6 +166,7 @@ tie my %temporalities, 'Tie::IxHash', use_duration 411_rewrite output_format summarize_usage usage_section + bill_every_call ) ], 'weight' => 40, @@ -321,32 +327,15 @@ sub calc_recur { warn "rating call $to_or_from +$countrycode $number\n" if $DEBUG; $pretty_destnum = "+$countrycode $number"; - #find a rate prefix, first look at most specific (4 digits) then 3, etc., - # finally trying the country code only - my $rate_prefix = ''; - for my $len ( reverse(1..6) ) { - $rate_prefix = qsearchs('rate_prefix', { - 'countrycode' => $countrycode, - #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) } - 'npa' => substr($number, 0, $len), - } ) and last; - } - $rate_prefix ||= qsearchs('rate_prefix', { - 'countrycode' => $countrycode, - 'npa' => '', - }); - - # - die "Can't find rate for call $to_or_from +$countrycode $number\n" - unless $rate_prefix; - - $regionnum = $rate_prefix->regionnum; - $rate_detail = qsearchs('rate_detail', { - 'ratenum' => $ratenum, - 'dest_regionnum' => $regionnum, - } ); - - $rate_region = $rate_prefix->rate_region; + my $rate = qsearchs('rate', { 'ratenum' => $ratenum }) + or die "ratenum $ratenum not found!"; + + $rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode, + 'phonenum' => $number, + }); + + $rate_region = $rate_detail->dest_region; + $regionnum = $rate_region->regionnum; warn " found rate for regionnum $regionnum ". "and rate detail $rate_detail\n" diff --git a/FS/FS/part_pkg/voip_sqlradacct.pm b/FS/FS/part_pkg/voip_sqlradacct.pm index 49ef515ac..b4f0cf9b5 100644 --- a/FS/FS/part_pkg/voip_sqlradacct.pm +++ b/FS/FS/part_pkg/voip_sqlradacct.pm @@ -13,6 +13,7 @@ use FS::rate_prefix; $DEBUG = 1; %info = ( + 'disabled' => 1, #they're sucked into our CDR table now instead 'name' => 'VoIP rating by plan of CDR records in an SQL RADIUS radacct table', 'shortname' => 'VoIP/telco CDR rating (external RADIUS)', 'fields' => { diff --git a/FS/FS/rate.pm b/FS/FS/rate.pm index c50ca044a..257278a30 100644 --- a/FS/FS/rate.pm +++ b/FS/FS/rate.pm @@ -269,16 +269,52 @@ sub check { $self->SUPER::check; } -=item dest_detail REGIONNUM | RATE_REGION_OBJECTD +=item dest_detail REGIONNUM | RATE_REGION_OBJECTD | HASHREF Returns the rate detail (see L) for this rate to the -specificed destination. +specificed destination. Destination can be specified as an FS::rate_detail +object or regionnum (see L), or as a hashref with two keys: +I and I. =cut sub dest_detail { my $self = shift; - my $regionnum = ref($_[0]) ? shift->regionnum : shift; + + my $regionnum; + if ( ref($_[0]) eq 'HASH' ) { + + my $countrycode = $_->{'countrycode'}; + my $phonenum = $_->{'phonenum'}; + + #find a rate prefix, first look at most specific (4 digits) then 3, etc., + # finally trying the country code only + my $rate_prefix = ''; + for my $len ( reverse(1..6) ) { + $rate_prefix = qsearchs('rate_prefix', { + 'countrycode' => $countrycode, + #'npa' => { op=> 'LIKE', value=> substr($number, 0, $len) } + 'npa' => substr($phonenum, 0, $len), + } ) and last; + } + $rate_prefix ||= qsearchs('rate_prefix', { + 'countrycode' => $countrycode, + 'npa' => '', + }); + + # + #die "Can't find rate for call $to_or_from +$countrycode $number\n" + die "Can't find rate for +$countrycode $phonenum\n" + unless $rate_prefix; + + $regionnum = $rate_prefix->regionnum; + + #$rate_region = $rate_prefix->rate_region; + + } else { + $regionnum = ref($_[0]) ? shift->regionnum : shift; + } + qsearchs( 'rate_detail', { 'ratenum' => $self->ratenum, 'dest_regionnum' => $regionnum, } ); } -- 2.11.0