X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcdr.pm;h=f7402eeada012aa0682b916d17612c423e90cc2c;hp=b9a4e41db3725a0111baf2d3f02147f3155d69b1;hb=90393980e5f2859ee1e186fa461f48f5129e803e;hpb=40a7b3dc653e099f7bd0bd762b649b04c4432db2 diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index b9a4e41db..f7402eead 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -14,6 +14,7 @@ use FS::cdr_type; use FS::cdr_calltype; use FS::cdr_carrier; use FS::cdr_batch; +use FS::cdr_termination; @ISA = qw(FS::Record); @EXPORT_OK = qw( _cdr_date_parser_maker _cdr_min_parser_maker ); @@ -284,6 +285,10 @@ sub check { # ; # return $error if $error; + for my $f ( grep { $self->$_ =~ /\D/ } qw(startdate answerdate enddate)){ + $self->$f( str2time($self->$f) ); + } + $self->calldate( $self->startdate_sql ) if !$self->calldate && $self->startdate; @@ -319,15 +324,19 @@ sub check { $self->SUPER::check; } -=item is_tollfree +=item is_tollfree [ COLUMN ] - Returns true when the cdr represents a toll free number and false otherwise. +Returns true when the cdr represents a toll free number and false otherwise. + +By default, inspects the dst field, but an optional column name can be passed +to inspect other field. =cut sub is_tollfree { my $self = shift; - ( $self->dst =~ /^(\+?1)?8(8|([02-7])\3)/ ) ? 1 : 0; + my $field = scalar(@_) ? shift : 'dst'; + ( $self->$field() =~ /^(\+?1)?8(8|([02-7])\3)/ ) ? 1 : 0; } =item set_charged_party @@ -356,6 +365,11 @@ sub set_charged_party { if $conf->exists('cdr-charged_party-accountcode-trim_leading_0s'); $self->charged_party( $charged_party ); + } elsif ( $conf->exists('cdr-charged_party-field') ) { + + my $field = $conf->config('cdr-charged_party-field'); + $self->charged_party( $self->$field() ); + } else { if ( $self->is_tollfree ) { @@ -386,11 +400,33 @@ error, otherwise returns false. =cut sub set_status_and_rated_price { - my($self, $status, $rated_price, $svcnum) = @_; - $self->freesidestatus($status); - $self->rated_price($rated_price); - $self->svcnum($svcnum) if $svcnum; - $self->replace(); + my($self, $status, $rated_price, $svcnum, %opt) = @_; + if($opt{'inbound'}) { + my $term = qsearchs('cdr_termination', { + acctid => $self->acctid, + termpart => 1 # inbound + }); + my $error; + if($term) { + warn "replacing existing cdr status (".$self->acctid.")\n" if $term; + $error = $term->delete; + return $error if $error; + } + $term = FS::cdr_termination->new({ + acctid => $self->acctid, + termpart => 1, + rated_price => $rated_price, + status => $status, + svcnum => $svcnum, + }); + return $term->insert; + } + else { + $self->freesidestatus($status); + $self->rated_price($rated_price); + $self->svcnum($svcnum) if $svcnum; + return $self->replace(); + } } =item calldate_unix @@ -503,73 +539,92 @@ my %export_names = ( }, ); -my $duration_sub = sub { - my($cdr, %opt) = @_; - if ( $opt{minutes} ) { - $opt{minutes}. ( $opt{granularity} ? 'm' : ' call' ); - } else { - #config if anyone really wants decimal minutes back - #sprintf('%.2fm', $cdr->billsec / 60 ); - int($cdr->billsec / 60).'m '. ($cdr->billsec % 60).'s'; - } -}; - -my %export_formats = ( - 'simple' => [ - sub { time2str('%D', shift->calldate_unix ) }, #DATE - sub { time2str('%r', shift->calldate_unix ) }, #TIME - 'userfield', #USER - 'dst', #NUMBER_DIALED - $duration_sub, #DURATION - #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE - sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE - ], - 'simple2' => [ - sub { time2str('%D', shift->calldate_unix ) }, #DATE - sub { time2str('%r', shift->calldate_unix ) }, #TIME - #'userfield', #USER - 'src', #called from - 'dst', #NUMBER_DIALED - $duration_sub, #DURATION - #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE - sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE - ], - 'default' => [ - - #DATE - sub { time2str('%D', shift->calldate_unix ) }, - # #time2str("%Y %b %d - %r", $cdr->calldate_unix ), - - #TIME - sub { time2str('%r', shift->calldate_unix ) }, - # time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot - - #DEST ("Number") - sub { my($cdr, %opt) = @_; $opt{pretty_dst} || $cdr->dst; }, - - #REGIONNAME ("Destination") - sub { my($cdr, %opt) = @_; $opt{dst_regionname}; }, - - #DURATION - $duration_sub, - - #PRICE - sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, - - ], -); -$export_formats{'source_default'} = [ 'src', @{ $export_formats{'default'} }, ]; -$export_formats{'accountcode_default'} = - [ @{ $export_formats{'default'} }[0,1], - 'accountcode', - @{ $export_formats{'default'} }[2..5], - ]; +my %export_formats = (); +sub export_formats { + #my $self = shift; + + return %export_formats if keys %export_formats; + + my $conf = new FS::Conf; + my $date_format = $conf->config('date_format') || '%m/%d/%Y'; + + # This is now smarter, and shows the call duration in the + # largest units that accurately reflect the granularity. + my $duration_sub = sub { + my($cdr, %opt) = @_; + my $sec = $opt{seconds} || $cdr->billsec; + if ( length($opt{granularity}) && + $opt{granularity} == 0 ) { #per call + return '1 call'; + } + elsif ( $opt{granularity} == 60 ) {#full minutes + return sprintf("%.0fm",$sec/60); + } + else { #anything else + return sprintf("%dm %ds", $sec/60, $sec%60); + } + }; + + %export_formats = ( + 'simple' => [ + sub { time2str($date_format, shift->calldate_unix ) }, #DATE + sub { time2str('%r', shift->calldate_unix ) }, #TIME + 'userfield', #USER + 'dst', #NUMBER_DIALED + $duration_sub, #DURATION + #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE + sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE + ], + 'simple2' => [ + sub { time2str($date_format, shift->calldate_unix ) }, #DATE + sub { time2str('%r', shift->calldate_unix ) }, #TIME + #'userfield', #USER + 'src', #called from + 'dst', #NUMBER_DIALED + $duration_sub, #DURATION + #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE + sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE + ], + 'default' => [ + + #DATE + sub { time2str($date_format, shift->calldate_unix ) }, + # #time2str("%Y %b %d - %r", $cdr->calldate_unix ), + + #TIME + sub { time2str('%r', shift->calldate_unix ) }, + # time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot + + #DEST ("Number") + sub { my($cdr, %opt) = @_; $opt{pretty_dst} || $cdr->dst; }, + + #REGIONNAME ("Destination") + sub { my($cdr, %opt) = @_; $opt{dst_regionname}; }, + + #DURATION + $duration_sub, + + #PRICE + sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, + + ], + ); + $export_formats{'source_default'} = [ 'src', @{ $export_formats{'default'} }, ]; + $export_formats{'accountcode_default'} = + [ @{ $export_formats{'default'} }[0,1], + 'accountcode', + @{ $export_formats{'default'} }[2..5], + ]; + + %export_formats +} sub downstream_csv { my( $self, %opt ) = @_; my $format = $opt{'format'}; - return "Unknown format $format" unless exists $export_formats{$format}; + my %formats = $self->export_formats; + return "Unknown format $format" unless exists $formats{$format}; #my $conf = new FS::Conf; #$opt{'money_char'} ||= $conf->config('money_char') || '$'; @@ -583,7 +638,7 @@ sub downstream_csv { map { ref($_) ? &{$_}($self, %opt) : $self->$_(); } - @{ $export_formats{$format} }; + @{ $formats{$format} }; my $status = $csv->combine(@columns); die "FS::CDR: error combining ". $csv->error_input(). "into downstream CSV" @@ -719,11 +774,24 @@ sub _cdr_date_parse { if ( $date =~ /^\s*(\d{4})\D(\d{1,2})\D(\d{1,2})\D+(\d{1,2})\D(\d{1,2})\D(\d{1,2})(\D|$)/ ) { ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); - } elsif ( $date =~ /^\s*(\d{1,2})\D(\d{1,2})\D(\d{4})\s+(\d{1,2})\D(\d{1,2})\D(\d{1,2})(\D|$)/ ) { + } elsif ( $date =~ /^\s*(\d{1,2})\D(\d{1,2})\D(\d{4})\s+(\d{1,2})\D(\d{1,2})(?:\D(\d{1,2}))?(\D|$)/ ) { + # 8/26/2010 12:20:01 + # optionally without seconds ($mon, $day, $year, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); + $sec = 0 if !defined($sec); } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d+\.\d+)(\D|$)/ ) { # broadsoft: 20081223201938.314 - ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6); + ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); + } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\d+(\D|$)/ ) { + # Taqua OM: 20050422203450943 + ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); + } elsif ( $date =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ ) { + # WIP: 20100329121420 + ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); + } elsif ( $date =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/) { + # Telos + ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); + $options{gmt} = 1; } else { die "unparsable date: $date"; #maybe we shouldn't die... } @@ -790,6 +858,15 @@ my %import_options = ( { map { $_ => $cdr_info{$_}->{'fixedlength_format'}; } keys %cdr_info }, + + 'format_xml_formats' => + { map { $_ => $cdr_info{$_}->{'xml_format'}; } + keys %cdr_info + }, + + 'format_row_callbacks' => { map { $_ => $cdr_info{$_}->{'row_callback'}; } + keys %cdr_info + }, ); sub _import_options { @@ -840,9 +917,14 @@ sub _upgrade_data { my %cdrbatchnum = (); while (my $row = $sth->fetchrow_arrayref) { - my $cdr_batch = new FS::cdr_batch { 'cdrbatch' => $row->[0] }; - my $error = $cdr_batch->insert; - die $error if $error; + + my $cdr_batch = qsearchs( 'cdr_batch', { 'cdrbatch' => $row->[0] } ); + unless ( $cdr_batch ) { + $cdr_batch = new FS::cdr_batch { 'cdrbatch' => $row->[0] }; + my $error = $cdr_batch->insert; + die $error if $error; + } + $cdrbatchnum{$row->[0]} = $cdr_batch->cdrbatchnum; }