CDR updates; modularize CDR import formats; add formats for OpenSER, Genband/Tekelec...
authorivan <ivan>
Thu, 17 Jul 2008 23:55:38 +0000 (23:55 +0000)
committerivan <ivan>
Thu, 17 Jul 2008 23:55:38 +0000 (23:55 +0000)
FS/FS/cdr.pm
FS/FS/cdr/asterisk.pm [new file with mode: 0644]
FS/FS/cdr/genband.pm [new file with mode: 0644]
FS/FS/cdr/genband_meetme.pm [new file with mode: 0644]
FS/FS/cdr/nt.pm [new file with mode: 0644]
FS/FS/cdr/openser.pm [new file with mode: 0644]
FS/FS/cdr/simple.pm [new file with mode: 0644]
FS/FS/cdr/taqua.pm [new file with mode: 0644]
FS/FS/cdr/unitel.pm [new file with mode: 0644]

index 1ccf4ae..3b5579c 100644 (file)
@@ -1,7 +1,9 @@
 package FS::cdr;
 
 use strict;
-use vars qw( @ISA );
+use vars qw( @ISA @EXPORT_OK $DEBUG );
+use Exporter;
+use Tie::IxHash;
 use Date::Parse;
 use Date::Format;
 use Time::Local;
@@ -13,6 +15,9 @@ use FS::cdr_carrier;
 use FS::cdr_upstream_rate;
 
 @ISA = qw(FS::Record);
+@EXPORT_OK = qw( _cdr_date_parser_maker );
+
+$DEBUG = 0;
 
 =head1 NAME
 
@@ -501,16 +506,46 @@ as keys (for use with batch_import) and "pretty" format names as values.
 
 =cut
 
-sub import_formats {
-  (
-    'asterisk'       => 'Asterisk',
-    'taqua'          => 'Taqua',
-    'unitel'         => 'Unitel/RSLCOM',
-    'simple'         => 'Simple',
-  );
+#false laziness w/part_pkg & part_export
+
+my %cdr_info;
+foreach my $INC ( @INC ) {
+  warn "globbing $INC/FS/cdr/*.pm\n" if $DEBUG;
+  foreach my $file ( glob("$INC/FS/cdr/*.pm") ) {
+    warn "attempting to load CDR format info from $file\n" if $DEBUG;
+    $file =~ /\/(\w+)\.pm$/ or do {
+      warn "unrecognized file in $INC/FS/cdr/: $file\n";
+      next;
+    };
+    my $mod = $1;
+    my $info = eval "use FS::cdr::$mod; ".
+                    "\\%FS::cdr::$mod\::info;";
+    if ( $@ ) {
+      die "error using FS::cdr::$mod (skipping): $@\n" if $@;
+      next;
+    }
+    unless ( keys %$info ) {
+      warn "no %info hash found in FS::cdr::$mod, skipping\n";
+      next;
+    }
+    warn "got CDR format info from FS::cdr::$mod: $info\n" if $DEBUG;
+    if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
+      warn "skipping disabled CDR format FS::cdr::$mod" if $DEBUG;
+      next;
+    }
+    $cdr_info{$mod} = $info;
+  }
 }
 
-my($tmp_mday, $tmp_mon, $tmp_year);
+tie my %import_formats, 'Tie::IxHash',
+  map  { $_ => $cdr_info{$_}->{'name'} }
+  sort { $cdr_info{$a}->{'weight'} <=> $cdr_info{$b}->{'weight'} }
+  grep { exists($cdr_info{$_}->{'import_fields'}) }
+  keys %cdr_info;
+
+sub import_formats {
+  %import_formats;
+}
 
 sub _cdr_date_parser_maker {
   my $field = shift;
@@ -527,10 +562,18 @@ sub _cdr_date_parse {
 
   return '' unless length($date); #that's okay, it becomes NULL
 
+  my($year, $mon, $day, $hour, $min, $sec);
+
   #$date =~ /^\s*(\d{4})[\-\/]\(\d{1,2})[\-\/](\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})\s*$/
-  $date =~ /^\s*(\d{4})\D(\d{1,2})\D(\d{1,2})\s+(\d{1,2})\D(\d{1,2})\D(\d{1,2})(\D|$)/
-    or die "unparsable date: $date"; #maybe we shouldn't die...
-  my($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+  #taqua  #2007-10-31 08:57:24.113000000
+
+  if ( $date =~ /^\s*(\d{4})\D(\d{1,2})\D(\d{1,2})\s+(\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|$)/ ) {
+    ($mon, $day, $year, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+  } else {
+     die "unparsable date: $date"; #maybe we shouldn't die...
+  }
 
   return '' if $year == 1900 && $mon == 1 && $day == 1
             && $hour == 0    && $min == 0 && $sec == 0;
@@ -538,231 +581,6 @@ sub _cdr_date_parse {
   timelocal($sec, $min, $hour, $day, $mon-1, $year);
 }
 
-#taqua  #2007-10-31 08:57:24.113000000
-
-#http://www.the-asterisk-book.com/unstable/funktionen-cdr.html
-my %amaflags = (
-  DEFAULT       => 0,
-  OMIT          => 1, #asterisk 1.4+
-  IGNORE        => 1, #asterisk 1.2
-  BILLING       => 2, #asterisk 1.4+
-  BILL          => 2, #asterisk 1.2
-  DOCUMENTATION => 3,
-  #? '' => 0,
-);
-
-my %import_formats = (
-  'asterisk' => [
-    'accountcode',
-    'src',
-    'dst',
-    'dcontext',
-    'clid',
-    'channel',
-    'dstchannel',
-    'lastapp',
-    'lastdata',
-    _cdr_date_parser_maker('startdate'),
-    _cdr_date_parser_maker('answerdate'),
-    _cdr_date_parser_maker('enddate'),
-    'duration',
-    'billsec',
-    'disposition',
-    sub { my($cdr, $amaflags) = @_; $cdr->amaflags($amaflags{$amaflags}); },
-    'uniqueid',
-    'userfield',
-  ],
-  'taqua' => [ #some of these are kind arbitrary...
-
-    sub { my($cdr, $field) = @_; },       #XXX interesting RecordType
-             # easy to fix: Can't find cdr.cdrtypenum 1 in cdr_type.cdrtypenum
-
-    sub { my($cdr, $field) = @_; },             #all10#RecordVersion
-    sub { my($cdr, $field) = @_; },       #OrigShelfNumber
-    sub { my($cdr, $field) = @_; },       #OrigCardNumber
-    sub { my($cdr, $field) = @_; },       #OrigCircuit
-    sub { my($cdr, $field) = @_; },       #OrigCircuitType
-    'uniqueid',                           #SequenceNumber
-    'accountcode',                        #SessionNumber
-    'src',                                #CallingPartyNumber
-    'dst',                                #CalledPartyNumber
-    _cdr_date_parser_maker('startdate'),  #CallArrivalTime
-    _cdr_date_parser_maker('enddate'),    #CallCompletionTime
-
-    #Disposition
-    #sub { my($cdr, $d ) = @_; $cdr->disposition( $disposition{$d}): },
-    'disposition',
-                                          #  -1 => '',
-                                          #   0 => '',
-                                          # 100 => '',
-                                          # 101 => '',
-                                          # 102 => '',
-                                          # 103 => '',
-                                          # 104 => '',
-                                          # 105 => '',
-                                          # 201 => '',
-                                          # 203 => '',
-
-    _cdr_date_parser_maker('answerdate'), #DispositionTime
-    sub { my($cdr, $field) = @_; },       #TCAP
-    sub { my($cdr, $field) = @_; },       #OutboundCarrierConnectTime
-    sub { my($cdr, $field) = @_; },       #OutboundCarrierDisconnectTime
-
-    #TermTrunkGroup
-    #it appears channels are actually part of trunk groups, but this data
-    #is interesting and we need a source and destination place to put it
-    'dstchannel',                         #TermTrunkGroup
-
-
-    sub { my($cdr, $field) = @_; },       #TermShelfNumber
-    sub { my($cdr, $field) = @_; },       #TermCardNumber
-    sub { my($cdr, $field) = @_; },       #TermCircuit
-    sub { my($cdr, $field) = @_; },       #TermCircuitType
-    sub { my($cdr, $field) = @_; },       #OutboundCarrierId
-    'charged_party',                      #BillingNumber
-    sub { my($cdr, $field) = @_; },       #SubscriberNumber
-    'lastapp',                            #ServiceName
-    sub { my($cdr, $field) = @_; },       #some weirdness #ChargeTime
-    'lastdata',                           #ServiceInformation
-    sub { my($cdr, $field) = @_; },       #FacilityInfo
-    sub { my($cdr, $field) = @_; },             #all 1900-01-01 0#CallTraceTime
-    sub { my($cdr, $field) = @_; },             #all-1#UniqueIndicator
-    sub { my($cdr, $field) = @_; },             #all-1#PresentationIndicator
-    sub { my($cdr, $field) = @_; },             #empty#Pin
-    sub { my($cdr, $field) = @_; },       #CallType
-    sub { my($cdr, $field) = @_; },           #Balt/empty #OrigRateCenter
-    sub { my($cdr, $field) = @_; },           #Balt/empty #TermRateCenter
-
-    #OrigTrunkGroup
-    #it appears channels are actually part of trunk groups, but this data
-    #is interesting and we need a source and destination place to put it
-    'channel',                            #OrigTrunkGroup
-
-    'userfield',                                #empty#UserDefined
-    sub { my($cdr, $field) = @_; },             #empty#PseudoDestinationNumber
-    sub { my($cdr, $field) = @_; },             #all-1#PseudoCarrierCode
-    sub { my($cdr, $field) = @_; },             #empty#PseudoANI
-    sub { my($cdr, $field) = @_; },             #all-1#PseudoFacilityInfo
-    sub { my($cdr, $field) = @_; },       #OrigDialedDigits
-    sub { my($cdr, $field) = @_; },             #all-1#OrigOutboundCarrier
-    sub { my($cdr, $field) = @_; },       #IncomingCarrierID
-    'dcontext',                           #JurisdictionInfo
-    sub { my($cdr, $field) = @_; },       #OrigDestDigits
-    sub { my($cdr, $field) = @_; },       #huh?#InsertTime
-    sub { my($cdr, $field) = @_; },       #key
-    sub { my($cdr, $field) = @_; },             #empty#AMALineNumber
-    sub { my($cdr, $field) = @_; },             #empty#AMAslpID
-    sub { my($cdr, $field) = @_; },             #empty#AMADigitsDialedWC
-    sub { my($cdr, $field) = @_; },       #OpxOffHook
-    sub { my($cdr, $field) = @_; },       #OpxOnHook
-
-        #acctid - primary key
-  #AUTO #calldate - Call timestamp (SQL timestamp)
-#clid - Caller*ID with text
-        #XXX src - Caller*ID number / Source number
-        #XXX dst - Destination extension
-        #dcontext - Destination context
-        #channel - Channel used
-        #dstchannel - Destination channel if appropriate
-        #lastapp - Last application if appropriate
-        #lastdata - Last application data
-        #startdate - Start of call (UNIX-style integer timestamp)
-        #answerdate - Answer time of call (UNIX-style integer timestamp)
-        #enddate - End time of call (UNIX-style integer timestamp)
-  #HACK#duration - Total time in system, in seconds
-  #HACK#XXX billsec - Total time call is up, in seconds
-        #disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
-#INT amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode.
-        #accountcode - CDR account number to use: account
-
-        #uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
-        #userfield - CDR user-defined field
-
-        #X cdrtypenum - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
-        #XXX charged_party - Service number to be billed
-#upstream_currency - Wholesale currency from upstream
-#X upstream_price - Wholesale price from upstream
-#upstream_rateplanid - Upstream rate plan ID
-#rated_price - Rated (or re-rated) price
-#distance - km (need units field?)
-#islocal - Local - 1, Non Local = 0
-#calltypenum - Type of call - see FS::cdr_calltype
-#X description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc)
-#quantity - Number of items (cdr_type 7&8 only)
-#carrierid - Upstream Carrier ID (see FS::cdr_carrier)
-#upstream_rateid - Upstream Rate ID
-
-        #svcnum - Link to customer service (see FS::cust_svc)
-        #freesidestatus - NULL, done (or something)
-
-  ],
-  'unitel' => [
-    'uniqueid',
-    #'cdr_type',
-    'cdrtypenum',
-    'calldate', # may need massaging?  huh maybe not...
-    #'billsec', #XXX duration and billsec?
-                sub { $_[0]->billsec(  $_[1] );
-                      $_[0]->duration( $_[1] );
-                    },
-    'src',
-    'dst', # XXX needs to have "+61" prepended unless /^\+/ ???
-    'charged_party',
-    'upstream_currency',
-    'upstream_price',
-    'upstream_rateplanid',
-    'distance',
-    'islocal',
-    'calltypenum',
-    'startdate',  #XXX needs massaging
-    'enddate',    #XXX same
-    'description',
-    'quantity',
-    'carrierid',
-    'upstream_rateid',
-  ],
-  'simple' => [
-
-    # 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 );
-        },
-
-  ],
-);
-
-my %import_header = (
-  'simple'         => 1,
-  'taqua'          => 1,
-);
-
 =item batch_import HASHREF
 
 Imports CDR records.  Available options are:
@@ -783,12 +601,26 @@ sub batch_import {
   my $fh = $param->{filehandle};
   my $format = $param->{format};
 
-  return "Unknown format $format" unless exists $import_formats{$format};
-
-  eval "use Text::CSV_XS;";
-  die $@ if $@;
-
-  my $csv = new Text::CSV_XS;
+  return "Unknown format $format"
+    unless exists( $cdr_info{$format} )
+        && exists( $cdr_info{$format}->{'import_fields'} );
+
+  my $info = $cdr_info{$format};
+
+  my $type = exists($info->{'type'}) ? lc($info->{'type'}) : 'csv';
+
+  my $parser;
+  if ( $type eq 'csv' ) {
+    eval "use Text::CSV_XS;";
+    die $@ if $@;
+    my $parser = new Text::CSV_XS;
+  } elsif ( $type eq 'fixedlength' ) {
+    eval "use Parse::FixedLength;";
+    die $@ if $@;
+    my $parser = new Parse::FixedLength $info->{'fixedlength_format'};
+  } else {
+    die "Unknown CDR format type $type for format $format\n";
+  }
 
   my $imported = 0;
   #my $columns;
@@ -804,23 +636,34 @@ sub batch_import {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $header_lines =
-    exists($import_header{$format}) ? $import_header{$format} : 0;
+  my $header_lines = exists($info->{'header'}) ? $info->{'header'} : 0;
 
   my $line;
   while ( defined($line=<$fh>) ) {
 
     next if $header_lines-- > 0; #&& $line =~ /^[\w, "]+$/ 
 
-    $csv->parse($line) or do {
-      $dbh->rollback if $oldAutoCommit;
-      return "can't parse: ". $csv->error_input();
-    };
+    my @columns = ();
+    if ( $type eq 'csv' ) {
+
+      $parser->parse($line) or do {
+        $dbh->rollback if $oldAutoCommit;
+        return "can't parse: ". $parser->error_input();
+      };
+
+      @columns = $parser->fields();
+
+    } elsif ( $type eq 'fixedlength' ) {
+
+      @columns = $parser->parse($line);
+
+    } else {
+      die "Unknown CDR format type $type for format $format\n";
+    }
 
-    my @columns = $csv->fields();
     #warn join('-',@columns);
 
-    if ( $format eq 'simple' ) {
+    if ( $format eq 'simple' ) { #should be a callback or opt in FS::cdr::simple
       @columns = map { s/^ +//; $_; } @columns;
     }
 
@@ -837,7 +680,7 @@ sub batch_import {
         }
 
       }
-      @{ $import_formats{$format} }
+      @{ $info->{'import_fields'} }
     ;
 
     my $cdr = new FS::cdr ( \%cdr );
@@ -848,7 +691,7 @@ sub batch_import {
       &{$sub}($cdr, $data);  # $cdr->&{$sub}($data); 
     }
 
-    if ( $format eq 'taqua' ) {
+    if ( $format eq 'taqua' ) { #should be a callback or opt in FS::cdr::taqua
       if ( $cdr->enddate && $cdr->startdate  ) { #a bit more?
         $cdr->duration( $cdr->enddate - $cdr->startdate  );
       }
diff --git a/FS/FS/cdr/asterisk.pm b/FS/FS/cdr/asterisk.pm
new file mode 100644 (file)
index 0000000..c687962
--- /dev/null
@@ -0,0 +1,44 @@
+package FS::cdr::asterisk;
+
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+#http://www.the-asterisk-book.com/unstable/funktionen-cdr.html
+my %amaflags = (
+  DEFAULT       => 0,
+  OMIT          => 1, #asterisk 1.4+
+  IGNORE        => 1, #asterisk 1.2
+  BILLING       => 2, #asterisk 1.4+
+  BILL          => 2, #asterisk 1.2
+  DOCUMENTATION => 3,
+  #? '' => 0,
+);
+
+%info = (
+  'name'          => 'Asterisk',
+  'weight'        => 10,
+  'import_fields' => [
+    'accountcode',
+    'src',
+    'dst',
+    'dcontext',
+    'clid',
+    'channel',
+    'dstchannel',
+    'lastapp',
+    'lastdata',
+    _cdr_date_parser_maker('startdate'),
+    _cdr_date_parser_maker('answerdate'),
+    _cdr_date_parser_maker('enddate'),
+    'duration',
+    'billsec',
+    'disposition',
+    sub { my($cdr, $amaflags) = @_; $cdr->amaflags($amaflags{$amaflags}); },
+    'uniqueid',
+    'userfield',
+  ],
+);
+
+1;
diff --git a/FS/FS/cdr/genband.pm b/FS/FS/cdr/genband.pm
new file mode 100644 (file)
index 0000000..a509950
--- /dev/null
@@ -0,0 +1,115 @@
+package FS::cdr::genband;
+
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'GenBand (Tekelec)', #'Genband G6 (Tekelec T6000)',
+  'weight'        => 140,
+  'type'          => 'fixedlength',
+  'fixedlength_format' => [qw(
+    Type:2:1:2
+    Sequence:4:3:6
+    OIDCall:30:7:36
+    StartTime:19:37:55
+    AnswerTime:19:56:74
+    EndTime:19:75:93
+    SourceName:30:94:123
+    SourceEndName:30:124:153
+    SourceCallerID:20:154:173
+    SourceCallerName:30:174:203
+    DestinationName:30:204:233
+    DestinationEndName:30:234:263
+    DestCallerID:20:264:283
+    DestCallerIDInfo:30:284:313
+    DialedDigits:30:314:343
+    Billing:30:344:373
+    AuthCode:30:374:403
+    CallDirection:1:404:404
+    ExtendedCall:1:405:405
+    ExternalCall:1:406:406
+    Duration:9:407:415
+    SIPCallID:64:416:479
+    IncomingDigits:30:480:509
+    OutpulsedDigits:30:510:539
+    CarrierIdentificationCode:4:540:543
+    CompletionReason:4:544:547
+    OriginationPartition:30:548:577
+    DestinationPartition:30:578:607
+    BilledSourceDID:20:608:628
+    VideoCall:1:629:630
+  )],
+  'import_fields' => [
+    sub {}, #Type:2:1:2
+    sub {}, #Sequence:4:3:6
+    'uniqueid', #OIDCall:30:7:36
+    _cdr_date_parser_maker('startdate'), #StartTime:19:37:55
+    _cdr_date_parser_maker('answerdate'), #AnswerTime:19:56:74
+    _cdr_date_parser_maker('enddate'), #EndTime:19:75:93
+    #SourceName:30:94:123
+    'channel', #SourceEndName:30:124:153
+    'src', #SourceCallerID:20:154:173
+    'clid', #SourceCallerName:30:174:203
+    #DestinationName:30:204:233
+    'dstchannel', #DestinationEndName:30:234:263
+    'dst', #DestCallerID:20:264:283
+    #DestCallerIDInfo:30:284:313
+    #DialedDigits:30:314:343
+    #Billing:30:344:373
+    #AuthCode:30:374:403
+    #CallDirection:1:404:404
+    #ExtendedCall:1:405:405
+    #ExternalCall:1:406:406
+    'duration', #Duration:9:407:415
+    #SIPCallID:64:416:479
+    #IncomingDigits:30:480:509
+    #OutpulsedDigits:30:510:539
+    #CarrierIdentificationCode:4:540:543
+    #CompletionReason:4:544:547
+    #OriginationPartition:30:548:577
+    #DestinationPartition:30:578:607
+    #BilledSourceDID:20:608:628
+    #VideoCall:1:629:630
+  ],
+);
+#      acctid - primary key
+#       calldate - Call timestamp (SQL timestamp)
+#              clid - Caller*ID with text
+#              src - Caller*ID number / Source number
+#              dst - Destination extension
+#       dcontext - Destination context
+#              channel - Channel used
+#              dstchannel - Destination channel if appropriate
+#       lastapp - Last application if appropriate
+#       lastdata - Last application data
+#              startdate - Start of call (UNIX-style integer timestamp)
+#              answerdate - Answer time of call (UNIX-style integer timestamp)
+#              enddate - End time of call (UNIX-style integer timestamp)
+#              duration - Total time in system, in seconds
+#       billsec - Total time call is up, in seconds
+#       disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+#       amaflags - What flags to use: BILL, IGNORE etc, specified on a per
+#       channel basis like accountcode.
+#       accountcode - CDR account number to use: account
+#              uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
+#       userfield - CDR user-defined field
+#       cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+#       charged_party - Service number to be billed
+#       upstream_currency - Wholesale currency from upstream
+#       upstream_price - Wholesale price from upstream
+#       upstream_rateplanid - Upstream rate plan ID
+#       rated_price - Rated (or re-rated) price
+#       distance - km (need units field?)
+#       islocal - Local - 1, Non Local = 0
+#       calltypenum - Type of call - see FS::cdr_calltype
+#       description - Description (cdr_type 7&8 only) (used for
+#       cust_bill_pkg.itemdesc)
+#       quantity - Number of items (cdr_type 7&8 only)
+#       carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+#       upstream_rateid - Upstream Rate ID
+#       svcnum - Link to customer service (see FS::cust_svc)
+#       freesidestatus - NULL, done (or something)
+
+1;
diff --git a/FS/FS/cdr/genband_meetme.pm b/FS/FS/cdr/genband_meetme.pm
new file mode 100644 (file)
index 0000000..b4414ab
--- /dev/null
@@ -0,0 +1,16 @@
+package FS::cdr::genband_meetme;
+
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'Genband (Tekelec) Meet-Me Conference', #'Genband G6 (Tekelec T6000) Meet-Me Conference Log Records',
+  'weight'        => 145,
+  'disabled'      => 1,
+  'import_fields' => [
+  ],
+);
+
+1;
diff --git a/FS/FS/cdr/nt.pm b/FS/FS/cdr/nt.pm
new file mode 100644 (file)
index 0000000..784a06a
--- /dev/null
@@ -0,0 +1,23 @@
+package FS::cdr::nt;
+
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'NT', #XXX name???
+  'weight'        => 200,
+  'header'        => 1,
+  'import_fields' => [
+    'userfield',  #CallZoneData ???userfield
+    'channel',    #OrigGw
+    'dstchannel', #TermGw
+    'duration',   #Duration
+    'dst',        #CallDTMF
+    'src',        #Ani
+    'startdate',  #DateTimeInt
+  ],
+);
+
+1;
diff --git a/FS/FS/cdr/openser.pm b/FS/FS/cdr/openser.pm
new file mode 100644 (file)
index 0000000..1c2796e
--- /dev/null
@@ -0,0 +1,23 @@
+package FS::cdr::openser;
+
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'OpenSER',
+  'weight'        => 15,
+  'header'        => 1,
+  'import_fields' => [
+    _cdr_date_parser_maker('startdate'),
+    _cdr_date_parser_maker('enddate'),
+    'src',
+    'dst',
+    'duration',
+    'channel',
+    'dstchannel',
+  ],
+);
+
+1;
diff --git a/FS/FS/cdr/simple.pm b/FS/FS/cdr/simple.pm
new file mode 100644 (file)
index 0000000..ab1e3ea
--- /dev/null
@@ -0,0 +1,49 @@
+package FS::cdr::simple;
+
+use vars qw(@ISA %info);
+use FS::cdr;
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'Simple',
+  'weight'        => 20,
+  'header'        => 1,
+  'import_fields' => [
+
+    # 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 );
+        },
+
+  ],
+);
+
+1;
diff --git a/FS/FS/cdr/taqua.pm b/FS/FS/cdr/taqua.pm
new file mode 100644 (file)
index 0000000..587d97d
--- /dev/null
@@ -0,0 +1,136 @@
+package FS::cdr::taqua;
+
+use vars qw(@ISA %info);
+use FS::cdr qw(_cdr_date_parser_maker);
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'Taqua',
+  'weight'        => 130,
+  'header'        => 1,
+  'import_fields' => [  #some of these are kind arbitrary...
+    sub { my($cdr, $field) = @_; },       #XXX interesting RecordType
+             # easy to fix: Can't find cdr.cdrtypenum 1 in cdr_type.cdrtypenum
+
+    sub { my($cdr, $field) = @_; },             #all10#RecordVersion
+    sub { my($cdr, $field) = @_; },       #OrigShelfNumber
+    sub { my($cdr, $field) = @_; },       #OrigCardNumber
+    sub { my($cdr, $field) = @_; },       #OrigCircuit
+    sub { my($cdr, $field) = @_; },       #OrigCircuitType
+    'uniqueid',                           #SequenceNumber
+    'accountcode',                        #SessionNumber
+    'src',                                #CallingPartyNumber
+    'dst',                                #CalledPartyNumber
+    _cdr_date_parser_maker('startdate'),  #CallArrivalTime
+    _cdr_date_parser_maker('enddate'),    #CallCompletionTime
+
+    #Disposition
+    #sub { my($cdr, $d ) = @_; $cdr->disposition( $disposition{$d}): },
+    'disposition',
+                                          #  -1 => '',
+                                          #   0 => '',
+                                          # 100 => '',
+                                          # 101 => '',
+                                          # 102 => '',
+                                          # 103 => '',
+                                          # 104 => '',
+                                          # 105 => '',
+                                          # 201 => '',
+                                          # 203 => '',
+
+    _cdr_date_parser_maker('answerdate'), #DispositionTime
+    sub { my($cdr, $field) = @_; },       #TCAP
+    sub { my($cdr, $field) = @_; },       #OutboundCarrierConnectTime
+    sub { my($cdr, $field) = @_; },       #OutboundCarrierDisconnectTime
+
+    #TermTrunkGroup
+    #it appears channels are actually part of trunk groups, but this data
+    #is interesting and we need a source and destination place to put it
+    'dstchannel',                         #TermTrunkGroup
+
+
+    sub { my($cdr, $field) = @_; },       #TermShelfNumber
+    sub { my($cdr, $field) = @_; },       #TermCardNumber
+    sub { my($cdr, $field) = @_; },       #TermCircuit
+    sub { my($cdr, $field) = @_; },       #TermCircuitType
+    sub { my($cdr, $field) = @_; },       #OutboundCarrierId
+    'charged_party',                      #BillingNumber
+    sub { my($cdr, $field) = @_; },       #SubscriberNumber
+    'lastapp',                            #ServiceName
+    sub { my($cdr, $field) = @_; },       #some weirdness #ChargeTime
+    'lastdata',                           #ServiceInformation
+    sub { my($cdr, $field) = @_; },       #FacilityInfo
+    sub { my($cdr, $field) = @_; },             #all 1900-01-01 0#CallTraceTime
+    sub { my($cdr, $field) = @_; },             #all-1#UniqueIndicator
+    sub { my($cdr, $field) = @_; },             #all-1#PresentationIndicator
+    sub { my($cdr, $field) = @_; },             #empty#Pin
+    sub { my($cdr, $field) = @_; },       #CallType
+    sub { my($cdr, $field) = @_; },           #Balt/empty #OrigRateCenter
+    sub { my($cdr, $field) = @_; },           #Balt/empty #TermRateCenter
+
+    #OrigTrunkGroup
+    #it appears channels are actually part of trunk groups, but this data
+    #is interesting and we need a source and destination place to put it
+    'channel',                            #OrigTrunkGroup
+
+    'userfield',                                #empty#UserDefined
+    sub { my($cdr, $field) = @_; },             #empty#PseudoDestinationNumber
+    sub { my($cdr, $field) = @_; },             #all-1#PseudoCarrierCode
+    sub { my($cdr, $field) = @_; },             #empty#PseudoANI
+    sub { my($cdr, $field) = @_; },             #all-1#PseudoFacilityInfo
+    sub { my($cdr, $field) = @_; },       #OrigDialedDigits
+    sub { my($cdr, $field) = @_; },             #all-1#OrigOutboundCarrier
+    sub { my($cdr, $field) = @_; },       #IncomingCarrierID
+    'dcontext',                           #JurisdictionInfo
+    sub { my($cdr, $field) = @_; },       #OrigDestDigits
+    sub { my($cdr, $field) = @_; },       #huh?#InsertTime
+    sub { my($cdr, $field) = @_; },       #key
+    sub { my($cdr, $field) = @_; },             #empty#AMALineNumber
+    sub { my($cdr, $field) = @_; },             #empty#AMAslpID
+    sub { my($cdr, $field) = @_; },             #empty#AMADigitsDialedWC
+    sub { my($cdr, $field) = @_; },       #OpxOffHook
+    sub { my($cdr, $field) = @_; },       #OpxOnHook
+
+        #acctid - primary key
+  #AUTO #calldate - Call timestamp (SQL timestamp)
+#clid - Caller*ID with text
+        #XXX src - Caller*ID number / Source number
+        #XXX dst - Destination extension
+        #dcontext - Destination context
+        #channel - Channel used
+        #dstchannel - Destination channel if appropriate
+        #lastapp - Last application if appropriate
+        #lastdata - Last application data
+        #startdate - Start of call (UNIX-style integer timestamp)
+        #answerdate - Answer time of call (UNIX-style integer timestamp)
+        #enddate - End time of call (UNIX-style integer timestamp)
+  #HACK#duration - Total time in system, in seconds
+  #HACK#XXX billsec - Total time call is up, in seconds
+        #disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
+#INT amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode.
+        #accountcode - CDR account number to use: account
+
+        #uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
+        #userfield - CDR user-defined field
+
+        #X cdrtypenum - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8)
+        #XXX charged_party - Service number to be billed
+#upstream_currency - Wholesale currency from upstream
+#X upstream_price - Wholesale price from upstream
+#upstream_rateplanid - Upstream rate plan ID
+#rated_price - Rated (or re-rated) price
+#distance - km (need units field?)
+#islocal - Local - 1, Non Local = 0
+#calltypenum - Type of call - see FS::cdr_calltype
+#X description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc)
+#quantity - Number of items (cdr_type 7&8 only)
+#carrierid - Upstream Carrier ID (see FS::cdr_carrier)
+#upstream_rateid - Upstream Rate ID
+
+        #svcnum - Link to customer service (see FS::cust_svc)
+        #freesidestatus - NULL, done (or something)
+  ],
+);
+
+1;
diff --git a/FS/FS/cdr/unitel.pm b/FS/FS/cdr/unitel.pm
new file mode 100644 (file)
index 0000000..74d0840
--- /dev/null
@@ -0,0 +1,38 @@
+package FS::cdr::unitel;
+
+use vars qw(@ISA %info);
+use FS::cdr;
+
+@ISA = qw(FS::cdr);
+
+%info = (
+  'name'          => 'Unitel/RSLCOM',
+  'weight'        => 500,
+  'import_fields' => [
+    'uniqueid',
+    #'cdr_type',
+    'cdrtypenum',
+    'calldate', # may need massaging?  huh maybe not...
+    #'billsec', #XXX duration and billsec?
+                sub { $_[0]->billsec(  $_[1] );
+                      $_[0]->duration( $_[1] );
+                    },
+    'src',
+    'dst', # XXX needs to have "+61" prepended unless /^\+/ ???
+    'charged_party',
+    'upstream_currency',
+    'upstream_price',
+    'upstream_rateplanid',
+    'distance',
+    'islocal',
+    'calltypenum',
+    'startdate',  #XXX needs massaging
+    'enddate',    #XXX same
+    'description',
+    'quantity',
+    'carrierid',
+    'upstream_rateid',
+  ]
+);
+
+1;