From db5e7f34b1e17ae6ce8909062537cc2fb98ca30d Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Mon, 25 Feb 2013 17:10:46 -0800 Subject: [PATCH] display inbound CDRs in selfservice, #18316; Taqua caller ID, #18574 --- FS/FS/ClientAPI/MyAccount.pm | 50 ++++++++++++++---- FS/FS/Conf.pm | 35 +++++++++++++ FS/FS/cdr.pm | 14 ++--- FS/FS/cdr/taqua62.pm | 4 +- FS/FS/part_pkg/voip_cdr.pm | 26 ++++++++-- FS/FS/svc_phone.pm | 29 +++++++++++ FS/bin/freeside-cdrrewrited | 60 ++++++++++++++-------- fs_selfservice/FS-SelfService/cgi/selfservice.cgi | 1 + .../FS-SelfService/cgi/view_cdr_details.html | 3 +- fs_selfservice/FS-SelfService/cgi/view_usage.html | 38 ++++++++++++-- 10 files changed, 214 insertions(+), 46 deletions(-) diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index faf377874..38139e11b 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -1723,7 +1723,34 @@ sub list_svcs { } else { $hash{'name'} = $cust_main->name; } + } elsif ( $svcdb eq 'svc_phone' ) { + # could potentially show lots of things... + $hash{'outbound'} = 1; + $hash{'inbound'} = 0; + if ( $part_pkg->plan eq 'voip_inbound' ) { + $hash{'outbound'} = 0; + $hash{'inbound'} = 1; + } elsif ( $part_pkg->option('selfservice_inbound_format') + or $conf->config('selfservice-default_inbound_cdr_format') + ) { + $hash{'inbound'} = 1; + } + foreach (qw(inbound outbound)) { + # hmm...we can't filter by status here, because there might + # not be cdr_terminations at all. have to go by date. + # find all since the last bill date. + # XXX cdr types? we are going to need them. + if ( $hash{$_} ) { + my $sum_cdr = $svc_x->sum_cdrs( + 'inbound' => ( $_ eq 'inbound' ? 1 : 0 ), + 'begin' => ($cust_pkg->last_bill || 0), + 'nonzero' => 1, + ); + $hash{$_} = $sum_cdr->hashref; + } + } } + # elsif ( $svcdb eq 'svc_phone' || $svcdb eq 'svc_port' ) { # %hash = ( # %hash, @@ -1996,7 +2023,7 @@ sub _list_cdr_usage { # we have to return the results all at once... my($svc_phone, $begin, $end, %opt) = @_; map [ $_->downstream_csv(%opt, 'keeparray' => 1) ], - $svc_phone->get_cdrs( 'begin'=>$begin, 'end'=>$end, ); + $svc_phone->get_cdrs( 'begin'=>$begin, 'end'=>$end, %opt ); } sub list_cdr_usage { @@ -2026,18 +2053,21 @@ sub _usage_details { my %callback_opt; my $header = []; if ( $svcdb eq 'svc_phone' ) { - my $format = $cust_pkg->part_pkg->option('output_format') || ''; - $format = '' if $format =~ /^sum_/; - # sensible default if there is no format or it's a summary format - if ( $cust_pkg->part_pkg->plan eq 'voip_inbound' ) { - $format ||= 'source_default'; + my $conf = FS::Conf->new; + my $format = ''; + if ( $p->{inbound} ) { + $format = $cust_pkg->part_pkg->option('selfservice_inbound_format') + || $conf->config('selfservice-default_inbound_cdr_format') + || 'source_default'; $callback_opt{inbound} = 1; + } else { + $format = $cust_pkg->part_pkg->option('selfservice_format') + || $conf->config('selfservice-default_cdr_format') + || 'default'; } - else { - $format ||= 'default'; - } - + $callback_opt{format} = $format; + $callback_opt{use_clid} = 1; $header = [ split(',', FS::cdr::invoice_header($format) ) ]; } diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 93aa84b7a..71b7ddf0a 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -717,6 +717,18 @@ my %batch_gateway_options = ( }, ); +my @cdr_formats = ( + '' => '', + 'default' => 'Default', + 'source_default' => 'Default with source', + 'accountcode_default' => 'Default plus accountcode', + 'description_default' => 'Default with description field as destination', + 'basic' => 'Basic', + 'simple' => 'Simple', + 'simple2' => 'Simple with source', + 'accountcode_simple' => 'Simple with accountcode', +); + # takes the reason class (C, R, S) as an argument sub reason_type_options { my $reason_class = shift; @@ -4760,6 +4772,13 @@ and customer address. Include units.', }, { + 'key' => 'cdr-taqua-callerid_rewrite', + 'section' => 'telephony', + 'description' => 'For the Taqua CDR format, pull Caller ID blocking information from secondary CDRs.', + 'type' => 'checkbox', + }, + + { 'key' => 'cdr-asterisk_australia_rewrite', 'section' => 'telephony', 'description' => 'For Asterisk CDRs, assign CDR type numbers based on Australian conventions.', @@ -5351,6 +5370,22 @@ and customer address. Include units.', }, { + 'key' => 'selfservice-default_cdr_format', + 'section' => 'self-service', + 'description' => 'Format for showing outbound CDRs in self-service. The per-package option overrides this.', + 'type' => 'select', + 'select_hash' => \@cdr_formats, + }, + + { + 'key' => 'selfservice-default_inbound_cdr_format', + 'section' => 'self-service', + 'description' => 'Format for showing inbound CDRs in self-service. The per-package option overrides this. Leave blank to avoid showing these CDRs.', + 'type' => 'select', + 'select_hash' => \@cdr_formats, + }, + + { 'key' => 'logout-timeout', 'section' => 'UI', 'description' => 'If set, automatically log users out of the backoffice after this many minutes.', diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index fedf28aa6..5e986ab50 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -1173,6 +1173,8 @@ sub export_formats { length($price) ? ($opt{money_char} . $price) : ''; }; + my $src_sub = sub { $_[0]->clid || $_[0]->src }; + %export_formats = ( 'simple' => [ sub { time2str($date_format, shift->calldate_unix ) }, #DATE @@ -1187,7 +1189,7 @@ sub export_formats { sub { time2str($date_format, shift->calldate_unix ) }, #DATE sub { time2str('%r', shift->calldate_unix ) }, #TIME #'userfield', #USER - 'src', #called from + $src_sub, #called from 'dst', #NUMBER_DIALED $duration_sub, #DURATION #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE @@ -1196,7 +1198,7 @@ sub export_formats { 'accountcode_simple' => [ sub { time2str($date_format, shift->calldate_unix ) }, #DATE sub { time2str('%r', shift->calldate_unix ) }, #TIME - 'src', #called from + $src_sub, #called from 'accountcode', #NUMBER_DIALED $duration_sub, #DURATION $price_sub, @@ -1204,14 +1206,14 @@ sub export_formats { 'sum_duration' => [ # for summary formats, the CDR is a fictitious object containing the # total billsec and the phone number of the service - 'src', + $src_sub, sub { my($cdr, %opt) = @_; $opt{ratename} }, sub { my($cdr, %opt) = @_; $opt{count} }, sub { my($cdr, %opt) = @_; int($opt{seconds}/60).'m' }, $price_sub, ], 'sum_count' => [ - 'src', + $src_sub, sub { my($cdr, %opt) = @_; $opt{ratename} }, sub { my($cdr, %opt) = @_; $opt{count} }, $price_sub, @@ -1245,7 +1247,7 @@ sub export_formats { $price_sub, ], ); - $export_formats{'source_default'} = [ 'src', @{ $export_formats{'default'} }, ]; + $export_formats{'source_default'} = [ $src_sub, @{ $export_formats{'default'} }, ]; $export_formats{'accountcode_default'} = [ @{ $export_formats{'default'} }[0,1], 'accountcode', @@ -1253,7 +1255,7 @@ sub export_formats { ]; my @default = @{ $export_formats{'default'} }; $export_formats{'description_default'} = - [ 'src', @default[0..2], + [ $src_sub, @default[0..2], sub { my($cdr, %opt) = @_; $cdr->description }, @default[4,5] ]; diff --git a/FS/FS/cdr/taqua62.pm b/FS/FS/cdr/taqua62.pm index 862018e9c..aa9463008 100644 --- a/FS/FS/cdr/taqua62.pm +++ b/FS/FS/cdr/taqua62.pm @@ -20,7 +20,9 @@ use FS::cdr qw(_cdr_date_parser_maker); my($cdr, $field, $conf, $hashref) = @_; $hashref->{skiprow} = 1 unless ($field == 0 && $cdr->disposition == 100 ) #regular CDR - || ($field == 1 && $cdr->lastapp eq 'acctcode'); #accountcode + || ($field == 1 && $cdr->lastapp eq 'acctcode') #accountcode + || ($field == 1 && $cdr->lastapp eq 'CallerId') #CID blocking + ; $cdr->cdrtypenum($field); }, diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index aae51e96c..04098a897 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -51,6 +51,11 @@ tie my %unrateable_opts, 'Tie::IxHash', 2 => 'Flag for later review', ; +tie my %detail_formats, 'Tie::IxHash', + '' => '', + FS::cdr::invoice_formats() +; + %info = ( 'name' => 'VoIP rating by plan of CDR records in an internal (or external) SQL table', 'shortname' => 'VoIP/telco CDR rating (standard)', @@ -211,12 +216,25 @@ tie my %unrateable_opts, 'Tie::IxHash', }, #false laziness w/cdr_termination.pm - 'output_format' => { 'name' => 'CDR invoice display format', + 'output_format' => { 'name' => 'CDR display format for invoices', 'type' => 'select', - 'select_options' => { FS::cdr::invoice_formats() }, + 'select_options' => \%detail_formats, 'default' => 'default', #XXX test }, + 'selfservice_format' => + { 'name' => 'CDR display format for selfservice', + 'type' => 'select', + 'select_options' => \%detail_formats, + 'default' => 'default' + }, + 'selfservice_inbound_format' => + { 'name' => 'Inbound CDR display format for selfservice', + 'type' => 'select', + 'select_options' => \%detail_formats, + 'default' => '' + }, + 'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not): ', }, @@ -297,7 +315,9 @@ tie my %unrateable_opts, 'Tie::IxHash', skip_max_callers use_duration 411_rewrite - output_format usage_mandate summarize_usage usage_section + output_format + selfservice_format selfservice_inbound_format + usage_mandate summarize_usage usage_section bill_every_call bill_inactive_svcs count_available_phones suspend_bill ) diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm index bf610c62b..f28002cc4 100644 --- a/FS/FS/svc_phone.pm +++ b/FS/FS/svc_phone.pm @@ -688,6 +688,8 @@ with the chosen prefix. =item disable_src => 1: Only match on "charged_party", not "src". +=item nonzero: Only return CDRs where duration > 0. + =item by_svcnum: not supported for svc_phone =item billsec_sum: Instead of returning all of the CDRs, return a single @@ -755,6 +757,9 @@ sub psearch_cdrs { if ( $options{'end'} ) { push @where, 'startdate < '. $options{'end'}; } + if ( $options{'nonzero'} ) { + push @where, 'duration > 0'; + } my $extra_sql = ( keys(%hash) ? ' AND ' : ' WHERE ' ). join(' AND ', @where ); @@ -781,6 +786,30 @@ sub get_cdrs { qsearch ( $psearch->{query} ) } +=item sum_cdrs + +Takes the same options as psearch_cdrs, but returns a single row containing +"count" (the number of CDRs) and the sums of the following fields: duration, +billsec, rated_price, rated_seconds, rated_minutes. + +Note that if any calls are not rated, their rated_* fields will be null. +If you want to use those fields, pass the 'status' option to limit to +calls that have been rated. This is intentional; please don't "fix" it. + +=cut + +sub sum_cdrs { + my $self = shift; + my $psearch = $self->psearch_cdrs(@_); + $psearch->{query}->{'select'} = join(',', + 'COUNT(*) AS count', + map { "SUM($_) AS $_" } + qw(duration billsec rated_price rated_seconds rated_minutes) + ); + # hack + $psearch->{query}->{'extra_sql'} =~ s/ ORDER BY.*$//; + qsearchs ( $psearch->{query} ); +} =back diff --git a/FS/bin/freeside-cdrrewrited b/FS/bin/freeside-cdrrewrited index f2c3926fb..16f931fbf 100644 --- a/FS/bin/freeside-cdrrewrited +++ b/FS/bin/freeside-cdrrewrited @@ -30,9 +30,9 @@ die "not running; cdr-asterisk_forward_rewrite, cdr-charged_party_rewrite ". #-- -my %accountcode_unmatch = (); -my $accountcode_retry = 4 * 60 * 60; # 4 hours -my $accountcode_giveup = 4 * 24 * 60 * 60; # 4 days +my %sessionnum_unmatch = (); +my $sessionnum_retry = 4 * 60 * 60; # 4 hours +my $sessionnum_giveup = 4 * 24 * 60 * 60; # 4 days my %cdr_type = map { lc($_->cdrtypename) => $_->cdrtypenum } qsearch('cdr_type',{}); @@ -45,8 +45,8 @@ while (1) { # instead of just doing this search like normal CDRs #hmm :/ - my @recent = grep { ($accountcode_unmatch{$_} + $accountcode_retry) > time } - keys %accountcode_unmatch; + my @recent = grep { ($sessionnum_unmatch{$_} + $sessionnum_retry) > time } + keys %sessionnum_unmatch; my $extra_sql = scalar(@recent) ? ' AND acctid NOT IN ('. join(',', @recent). ') ' : ''; @@ -136,45 +136,62 @@ while (1) { } - if ( $conf->exists('cdr-taqua-accountcode_rewrite') - && $cdr->lastapp eq 'acctcode' && $cdr->cdrtypenum == 1 + if ( $cdr->cdrtypenum == 1 + and $cdr->lastapp + and ( + $conf->exists('cdr-taqua-accountcode_rewrite') or + $conf->exists('cdr-taqua-callerid_rewrite') ) ) { #find the matching CDR - my $primary = qsearchs('cdr', { - 'sessionnum' => $cdr->sessionnum, - 'src' => $cdr->subscriber, - #'accountcode' => '', - }); + my %search = ( 'sessionnum' => $cdr->sessionnum ); + if ( $cdr->lastapp eq 'acctcode' ) { + $search{'src'} = $cdr->subscriber; + } elsif ( $cdr->lastapp eq 'CallerId' ) { + $search{'dst'} = $cdr->subscriber; + } + my $primary = qsearchs('cdr', \%search); unless ( $primary ) { my $cantfind = "can't find primary CDR with session ". $cdr->sessionnum. ", src ". $cdr->subscriber; - if ( $cdr->calldate_unix + $accountcode_giveup < time ) { + if ( $cdr->calldate_unix + $sessionnum_giveup < time ) { warn "ERROR: $cantfind; giving up\n"; - push @status, 'taqua-accountcode-NOTFOUND'; + push @status, 'taqua-sessionnum-NOTFOUND'; $cdr->status('done'); #so it doesn't try to rate - delete $accountcode_unmatch{$cdr->acctid}; #so it doesn't suck mem + delete $sessionnum_unmatch{$cdr->acctid}; #so it doesn't suck mem } else { warn "WARNING: $cantfind; will keep trying\n"; - $accountcode_unmatch{$cdr->acctid} = time; + $sessionnum_unmatch{$cdr->acctid} = time; next; } } else { - $primary->accountcode( $cdr->lastdata ); + if ( $cdr->lastapp eq 'acctcode' ) { + # lastdata contains the dialed account code + $primary->accountcode( $cdr->lastdata ); + push @status, 'taqua-accountcode'; + } elsif ( $cdr->lastapp eq 'CallerId' ) { + # lastdata contains "allowed" or "restricted" + # or case variants thereof + if ( lc($cdr->lastdata) eq 'restricted' ) { + $primary->clid( 'PRIVATE' ); + } + push @status, 'taqua-callerid'; + } else { + warn "unknown Taqua service name: ".$cdr->lastapp."\n"; + } #$primary->freesiderewritestatus( 'taqua-accountcode-primary' ); - my $error = $primary->replace; + my $error = $primary->replace if $primary->modified; if ( $error ) { warn "WARNING: error rewriting primary CDR (will retry): $error\n"; next; } $skip{$primary->acctid} = 1; - push @status, 'taqua-accountcode'; $cdr->status('done'); #so it doesn't try to rate } @@ -214,7 +231,10 @@ sub _shouldrun { $conf->exists('cdr-asterisk_forward_rewrite') || $conf->exists('cdr-asterisk_australia_rewrite') || $conf->exists('cdr-charged_party_rewrite') - || $conf->exists('cdr-taqua-accountcode_rewrite'); + || $conf->exists('cdr-taqua-accountcode_rewrite') + || $conf->exists('cdr-taqua-callerid_rewrite') + || 0 + ; } sub usage { diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index de0ab1a76..f7fe308cf 100755 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -873,6 +873,7 @@ sub view_cdr_details { 'svcnum' => $cgi->param('svcnum'), 'beginning' => $cgi->param('beginning') || '', 'ending' => $cgi->param('ending') || '', + 'inbound' => $cgi->param('inbound') || 0, ); } diff --git a/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html b/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html index b0205ec89..1342c088a 100644 --- a/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html +++ b/fs_selfservice/FS-SelfService/cgi/view_cdr_details.html @@ -1,5 +1,6 @@ <%= $url = "$selfurl?session=$session_id;action="; ''; %> -<%= include('header', 'Call usage for '. +<%= include('header', ($inbound ? 'Dialed calls ' : 'Received calls ') . + ' for '. Date::Format::time2str('%b %o %Y', $beginning). ' - '. Date::Format::time2str('%b %o %Y', $ending) diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage.html b/fs_selfservice/FS-SelfService/cgi/view_usage.html index fd5426a75..f7076684e 100644 --- a/fs_selfservice/FS-SelfService/cgi/view_usage.html +++ b/fs_selfservice/FS-SelfService/cgi/view_usage.html @@ -62,11 +62,22 @@ <%= scalar(@svc_acct) ? '

' : '' %> <%= if ( @svc_phone ) { + %any = (); + for my $dir (qw(outbound inbound)) { + $any{$dir} = grep { $_->{$dir} } @svc_phone; + } $OUT.= 'Call usage

- '; #"Account" ? - #what else? + '; + if ( $any{outbound} ) { + $OUT .= ' + '; + } + if ( $any{inbound} ) { + $OUT .= ' + '; + } $OUT .= ''; } else { $OUT .= ''; @@ -76,10 +87,27 @@ <%= foreach my $svc_phone ( @svc_phone ) { my $link = "${url}view_cdr_details;". "svcnum=$svc_phone->{'svcnum'};beginning=0;ending=0"; - $OUT .= ''; + $OUT .= ''; + # usage summary w/ links + for my $dir (qw(outbound inbound)) { + if ( $dir eq 'inbound' ) { + $link .= ';inbound=1'; + } + if ( $svc_phone->{$dir} ) { + $OUT .= ''; + } elsif ( $any{$dir} ) { + $OUT .= ''; + } } + $OUT .= ''; +} +''; %> <%= scalar(@svc_phone) ? '
NumberNumberDialedReceived
'; - $OUT .= qq!!. $svc_phone->{'label'}. ': '. $svc_phone->{'value'}.''; - $OUT .= '
'. $svc_phone->{'label'}. ': '. $svc_phone->{'value'}; + $OUT .= ''.qq!! . + sprintf('%d calls (%.0f minutes)', + $svc_phone->{$dir}->{'count'}, + $svc_phone->{$dir}->{'duration'} / 60 + ) . + '


' : '' %> -- 2.11.0