diff options
author | ivan <ivan> | 2006-07-12 00:20:23 +0000 |
---|---|---|
committer | ivan <ivan> | 2006-07-12 00:20:23 +0000 |
commit | 1053db7f76169cbbc87840539959a4c362aff242 (patch) | |
tree | 1d1895ce43bb5910a8de5e3ead26b2e179ed268e /FS | |
parent | a8665e44dbd99bd864e48231928405a31cedce5f (diff) |
svc_phone service and CDR billing from imported CDRs
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/AccessRight.pm | 2 | ||||
-rw-r--r-- | FS/FS/Record.pm | 2 | ||||
-rw-r--r-- | FS/FS/Schema.pm | 12 | ||||
-rw-r--r-- | FS/FS/cdr.pm | 83 | ||||
-rw-r--r-- | FS/FS/cust_svc.pm | 24 | ||||
-rw-r--r-- | FS/FS/h_svc_phone.pm | 33 | ||||
-rw-r--r-- | FS/FS/part_pkg/voip_cdr.pm | 146 | ||||
-rw-r--r-- | FS/FS/part_svc.pm | 10 | ||||
-rw-r--r-- | FS/FS/svc_phone.pm | 146 | ||||
-rw-r--r-- | FS/MANIFEST | 3 |
10 files changed, 374 insertions, 87 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index f04779a..797a12a 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -128,6 +128,8 @@ assigned to users and/or groups. 'List packages', 'List services', + 'List rating data', + 'Financial reports', 'Job queue', # these are not currently agent-virtualized diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 9a99aeb..41e0eba 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -1727,7 +1727,7 @@ sub _quote { ( $nullable ? ' NULL' : ' NOT NULL' ). ")\n" if $DEBUG > 2; - if ( $value eq '' && $column_type =~ /^int/ ) { + if ( $value eq '' && $column_type =~ /^(int|numeric)/ ) { if ( $nullable ) { 'NULL'; } else { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 7219274..3e1d68f 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1538,6 +1538,18 @@ sub tables_hashref { 'index' => [], }, + 'svc_phone' => { + 'columns' => [ + 'svcnum', 'int', '', '', '', '', + 'countrycode', 'varchar', '', 3, '', '', + 'phonenum', 'varchar', '', 15, '', '', #12 ? + 'pin', 'varchar', 'NULL', $char_d, '', '', + ], + 'primary_key' => 'svcnum', + 'unique' => [], + 'index' => [ [ 'countrycode', 'phonenum' ] ], + }, + }; #'new_table' => { diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index 5eb0cf3..2f47170 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -4,6 +4,7 @@ use strict; use vars qw( @ISA ); use Date::Parse; use Date::Format; +use Time::Local; use FS::UID qw( dbh ); use FS::Record qw( qsearch qsearchs ); use FS::cdr_type; @@ -224,6 +225,17 @@ sub check { # ; # return $error if $error; + $self->calldate( $self->startdate_sql ) + if !$self->calldate && $self->startdate; + + unless ( $self->charged_party ) { + if ( $self->dst =~ /^(\+?1)?8[02-8]{2}/ ) { + $self->charged_party($self->dst); + } else { + $self->charged_party($self->src); + } + } + #check the foreign keys even? #do we want to outright *reject* the CDR? my $error = @@ -252,7 +264,7 @@ error, otherwise returns false. sub set_status_and_rated_price { my($self, $status, $rated_price) = @_; - $self->status($status); + $self->freesidestatus($status); $self->rated_price($rated_price); $self->replace(); } @@ -267,6 +279,20 @@ sub calldate_unix { str2time(shift->calldate); } +=item startdate_sql + +Parses the startdate in UNIX timestamp format and returns a string in SQL +format. + +=cut + +sub startdate_sql { + my($sec,$min,$hour,$mday,$mon,$year) = localtime(shift->startdate); + $mon++; + $year += 1900; + "$year-$mon-$mday $hour:$min:$sec"; +} + =item cdr_carrier Returns the FS::cdr_carrier object associated with this CDR, or false if no @@ -420,6 +446,8 @@ sub downstream_csv { =cut +my($tmp_mday, $tmp_mon, $tmp_year); + my %import_formats = ( 'asterisk' => [ 'accountcode', @@ -465,7 +493,42 @@ my %import_formats = ( 'quantity', 'carrierid', 'upstream_rateid', - ] + ], + 'ams' => [ + + # Date + sub { my($cdr, $date) = @_; + $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/ + or die "unparsable date: $date"; #maybe we shouldn't die... + #$cdr->startdate( timelocal(0, 0, 0 ,$2, $1-1, $3) ); + ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 ); + }, + + # Time + sub { my($cdr, $time) = @_; + #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); + $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/ + or die "unparsable time: $time"; #maybe we shouldn't die... + #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) ); + $cdr->startdate( + timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year) + ); + }, + + # Source_Number + 'src', + + # Terminating_Number + 'dst', + + # Duration + sub { my($cdr, $min) = @_; + my $sec = sprintf('%.0f', $min * 60 ); + $cdr->billsec( $sec ); + $cdr->duration( $sec ); + }, + + ], ); sub batch_import { @@ -494,10 +557,20 @@ sub batch_import { my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; - + + if ( $format eq 'ams' ) { # and other formats with a header too? + + } + + my $body = 0; my $line; while ( defined($line=<$fh>) ) { + #skip header... + if ( ! $body++ && $format eq 'ams' && $line =~ /^[\w\, ]+$/ ) { + next; + } + $csv->parse($line) or do { $dbh->rollback if $oldAutoCommit; return "can't parse: ". $csv->error_input(); @@ -506,6 +579,10 @@ sub batch_import { my @columns = $csv->fields(); #warn join('-',@columns); + if ( $format eq 'ams' ) { + @columns = map { s/^ +//; $_; } @columns; + } + my @later = (); my %cdr = map { diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index e7afa77..8914e8c 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -13,6 +13,7 @@ use FS::svc_acct; use FS::svc_domain; use FS::svc_forward; use FS::svc_broadband; +use FS::svc_phone; use FS::svc_external; use FS::domain_record; use FS::part_export; @@ -277,6 +278,10 @@ Returns a list consisting of: - The table name (i.e. svc_domain) for this service - svcnum +Usage example: + + my($label, $value, $svcdb) = $cust_svc->label; + =cut sub label { @@ -315,6 +320,8 @@ sub _svc_label { $tag = $domain_record->zone; } elsif ( $svcdb eq 'svc_broadband' ) { $tag = $svc_x->ip_addr; + } elsif ( $svcdb eq 'svc_phone' ) { + $tag = $svc_x->phonenum; #XXX format it better } elsif ( $svcdb eq 'svc_external' ) { my $conf = new FS::Conf; if ( $conf->config('svc_external-display_type') eq 'artera_turbo' ) { @@ -586,30 +593,29 @@ sub get_cdrs_for_update { my $default_prefix = $options{'default_prefix'}; - #Currently CDRs are associated with svc_acct services via a DID in the - #username. This part is rather tenative and still subject to change... - #return () unless $self->svc_x->isa('FS::svc_acct'); - return () unless $self->part_svc->svcdb eq 'svc_acct'; - my $number = $self->svc_x->username; + #CDRs are now associated with svc_phone services via svc_phone.phonenum + #return () unless $self->svc_x->isa('FS::svc_phone'); + return () unless $self->part_svc->svcdb eq 'svc_phone'; + my $number = $self->svc_x->phonenum; my @cdrs = - qsearch( + qsearch( { 'table' => 'cdr', 'hashref' => { 'freesidestatus' => '', 'charged_party' => $number }, 'extra_sql' => 'FOR UPDATE', - ); + } ); if ( length($default_prefix) ) { push @cdrs, - qsearch( + qsearch( { 'table' => 'cdr', 'hashref' => { 'freesidestatus' => '', 'charged_party' => "$default_prefix$number", }, 'extra_sql' => 'FOR UPDATE', - ); + } ); } @cdrs; diff --git a/FS/FS/h_svc_phone.pm b/FS/FS/h_svc_phone.pm new file mode 100644 index 0000000..95898c7 --- /dev/null +++ b/FS/FS/h_svc_phone.pm @@ -0,0 +1,33 @@ +package FS::h_svc_phone; + +use strict; +use vars qw( @ISA ); +use FS::h_Common; +use FS::svc_phone; + +@ISA = qw( FS::h_Common FS::svc_phone ); + +sub table { 'h_svc_phone' }; + +=head1 NAME + +FS::h_svc_phone - Historical phone number objects + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +An FS::h_svc_phone object represents a historical phone number. +FS::h_svc_phone inherits from FS::h_Common and FS::svc_phone. + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::h_Common>, L<FS::svc_phone>, L<FS::Record>, schema.html from the base +documentation. + +=cut + +1; + diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 15af77b..500a1b0 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -102,9 +102,8 @@ sub calc_recur { my $downstream_cdr = ''; - # also look for a specific domain??? (username@telephonedomain) foreach my $cust_svc ( - grep { $_->part_svc->svcdb eq 'svc_acct' } $cust_pkg->cust_svc + grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc ) { foreach my $cdr ( @@ -125,78 +124,85 @@ sub calc_recur { ) { - die "rating_method 'prefix' not yet supported"; - -# ### -# # look up rate details based on called station id -# ### -# -# my $dest = $cdr->dst; -# -# #remove non-phone# stuff and whitespace -# $dest =~ s/\s//g; + ### + # look up rate details based on called station id + # (or calling station id for toll free calls) + ### + + my( $to_or_from, $number ); + if ( $cdr->dst =~ /^(\+?1)?8[02-8]{2}/ ) { #tollfree call + $to_or_from = 'from'; + $number = $cdr->src; + } else { #regular call + $to_or_from = 'to'; + $number = $cdr->dst; + } + + #remove non-phone# stuff and whitespace + $number =~ s/\s//g; # my $proto = ''; # $dest =~ s/^(\w+):// and $proto = $1; #sip: # my $siphost = ''; # $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com -# -# #determine the country code -# my $countrycode; -# if ( $dest =~ /^011(((\d)(\d))(\d))(\d+)$/ -# || $dest =~ /^\+(((\d)(\d))(\d))(\d+)$/ -# ) -# { -# -# my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 ); -# #first look for 1 digit country code -# if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { -# $countrycode = $one; -# $dest = $u1.$u2.$rest; -# } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 -# $countrycode = $two; -# $dest = $u2.$rest; -# } else { #3 digit country code -# $countrycode = $three; -# $dest = $rest; -# } -# -# } else { -# $countrycode = '1'; -# $dest =~ s/^1//;# if length($dest) > 10; -# } -# -# warn "rating call to +$countrycode $dest\n" if $DEBUG; -# $pretty_destnum = "+$countrycode $dest"; -# -# #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($dest, 0, $len) } -# 'npa' => substr($dest, 0, $len), -# } ) and last; -# } -# $rate_prefix ||= qsearchs('rate_prefix', { -# 'countrycode' => $countrycode, -# 'npa' => '', -# }); -# -# die "Can't find rate for call to +$countrycode $dest\n" -# unless $rate_prefix; -# -# $regionnum = $rate_prefix->regionnum; -# $rate_detail = qsearchs('rate_detail', { -# 'ratenum' => $ratenum, -# 'dest_regionnum' => $regionnum, -# } ); -# -# $rate_region = $rate_prefix->rate_region; -# -# warn " found rate for regionnum $regionnum ". -# "and rate detail $rate_detail\n" -# if $DEBUG; + + #determine the country code + my $countrycode; + if ( $number =~ /^011(((\d)(\d))(\d))(\d+)$/ + || $number =~ /^\+(((\d)(\d))(\d))(\d+)$/ + ) + { + + my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 ); + #first look for 1 digit country code + if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { + $countrycode = $one; + $number = $u1.$u2.$rest; + } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 + $countrycode = $two; + $number = $u2.$rest; + } else { #3 digit country code + $countrycode = $three; + $number = $rest; + } + + } else { + $countrycode = '1'; + $number =~ s/^1//;# if length($number) > 10; + } + + 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 $\numbern" + unless $rate_prefix; + + $regionnum = $rate_prefix->regionnum; + $rate_detail = qsearchs('rate_detail', { + 'ratenum' => $ratenum, + 'dest_regionnum' => $regionnum, + } ); + + $rate_region = $rate_prefix->rate_region; + + warn " found rate for regionnum $regionnum ". + "and rate detail $rate_detail\n" + if $DEBUG; } elsif ( $self->option('rating_method') eq 'upstream' ) { diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index 7f79194..2587347 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -347,7 +347,6 @@ and replace methods. sub check { my $self = shift; - my $recref = $self->hashref; my $error; $error= @@ -358,8 +357,9 @@ sub check { ; return $error if $error; - my @fields = eval { fields( $recref->{svcdb} ) }; #might die - return "Unknown svcdb!" unless @fields; + my @fields = eval { fields( $self->svcdb ) }; #might die + return "Unknown svcdb: ". $self->svcdb. " (Error: $@)" + unless @fields; $self->SUPER::check; } @@ -549,7 +549,9 @@ sub process { @fields; } grep defined( dbdef->table($_) ), - qw( svc_acct svc_domain svc_forward svc_www svc_broadband ) + qw( svc_acct svc_domain svc_forward svc_www svc_broadband + svc_phone svc_external + ) ) } ); diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm new file mode 100644 index 0000000..fca3369 --- /dev/null +++ b/FS/FS/svc_phone.pm @@ -0,0 +1,146 @@ +package FS::svc_phone; + +use strict; +use vars qw( @ISA ); +#use FS::Record qw( qsearch qsearchs ); +use FS::svc_Common; + +@ISA = qw( FS::svc_Common ); + +=head1 NAME + +FS::svc_phone - Object methods for svc_phone records + +=head1 SYNOPSIS + + use FS::svc_phone; + + $record = new FS::svc_phone \%hash; + $record = new FS::svc_phone { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + + $error = $record->suspend; + + $error = $record->unsuspend; + + $error = $record->cancel; + +=head1 DESCRIPTION + +An FS::svc_phone object represents a phone number. FS::svc_phone inherits +from FS::Record. The following fields are currently supported: + +=over 4 + +=item svcnum - primary key + +=item countrycode - + +=item phonenum - + +=item pin - + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new phone number. To add the number to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'svc_phone'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item suspend + +Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item unsuspend + +Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item cancel + +Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item check + +Checks all fields to make sure this is a valid phone number. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('svcnum') + || $self->ut_numbern('countrycode') + || $self->ut_number('phonenum') + || $self->ut_numbern('pin') + ; + return $error if $error; + + $self->countrycode(1) unless $self->countrycode; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, +L<FS::cust_pkg>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index 3c315fc..42b6165 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -347,3 +347,6 @@ FS/ConfDefaults.pm t/ConfDefaults.t FS/m2name_Common.pm FS/CurrentUser.pm +FS/svc_phone.pm +t/svc_phone.t +FS/h_svc_phone.pm |