7 use FS::Record qw( qsearch qsearchs );
12 @ISA = qw(FS::Record);
16 FS::cdr - Object methods for cdr records
22 $record = new FS::cdr \%hash;
23 $record = new FS::cdr { 'column' => 'value' };
25 $error = $record->insert;
27 $error = $new_record->replace($old_record);
29 $error = $record->delete;
31 $error = $record->check;
35 An FS::cdr object represents an Call Data Record, typically from a telephony
36 system or provider of some sort. FS::cdr inherits from FS::Record. The
37 following fields are currently supported:
41 =item acctid - primary key
43 =item calldate - Call timestamp (SQL timestamp)
45 =item clid - Caller*ID with text
47 =item src - Caller*ID number / Source number
49 =item dst - Destination extension
51 =item dcontext - Destination context
53 =item channel - Channel used
55 =item dstchannel - Destination channel if appropriate
57 =item lastapp - Last application if appropriate
59 =item lastdata - Last application data
61 =item startdate - Start of call (UNIX-style integer timestamp)
63 =item answerdate - Answer time of call (UNIX-style integer timestamp)
65 =item enddate - End time of call (UNIX-style integer timestamp)
67 =item duration - Total time in system, in seconds
69 =item billsec - Total time call is up, in seconds
71 =item disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY
73 =item amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode.
77 #ignore the "omit" and "documentation" AMAs??
78 #AMA = Automated Message Accounting.
79 #default: Sets the system default.
80 #omit: Do not record calls.
81 #billing: Mark the entry for billing
82 #documentation: Mark the entry for documentation.
86 =item accountcode - CDR account number to use: account
88 =item uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
90 =item userfield - CDR user-defined field
92 =item cdr_type - CDR type - see L<FS::cdr_type> (Usage = 1, S&E = 7, OC&C = 8)
94 =item charged_party - Service number to be billed
96 =item upstream_currency - Wholesale currency from upstream
98 =item upstream_price - Wholesale price from upstream
100 =item upstream_rateplanid - Upstream rate plan ID
102 =item distance - km (need units field?)
104 =item islocal - Local - 1, Non Local = 0
106 =item calltypenum - Type of call - see L<FS::cdr_calltype>
108 =item description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc)
110 =item quantity - Number of items (cdr_type 7&8 only)
112 =item carrierid - Upstream Carrier ID (see L<FS::cdr_carrier>)
116 #Telstra =1, Optus = 2, RSL COM = 3
120 =item upstream_rateid - Upstream Rate ID
122 =item svcnum - Link to customer service (see L<FS::cust_svc>)
124 =item freesidestatus - NULL, done, skipped, pushed_downstream (or something)
134 Creates a new CDR. To add the CDR to the database, see L<"insert">.
136 Note that this stores the hash reference, not a distinct copy of the hash it
137 points to. You can ask the object for a copy with the I<hash> method.
141 # the new method can be inherited from FS::Record, if a table method is defined
147 Adds this record to the database. If there is an error, returns the error,
148 otherwise returns false.
152 # the insert method can be inherited from FS::Record
156 Delete this record from the database.
160 # the delete method can be inherited from FS::Record
162 =item replace OLD_RECORD
164 Replaces the OLD_RECORD with this one in the database. If there is an error,
165 returns the error, otherwise returns false.
169 # the replace method can be inherited from FS::Record
173 Checks all fields to make sure this is a valid CDR. If there is
174 an error, returns the error, otherwise returns false. Called by the insert
177 Note: Unlike most types of records, we don't want to "reject" a CDR and we want
178 to process them as quickly as possible, so we allow the database to check most
186 # we don't want to "reject" a CDR like other sorts of input...
188 # $self->ut_numbern('acctid')
189 ## || $self->ut_('calldate')
190 # || $self->ut_text('clid')
191 # || $self->ut_text('src')
192 # || $self->ut_text('dst')
193 # || $self->ut_text('dcontext')
194 # || $self->ut_text('channel')
195 # || $self->ut_text('dstchannel')
196 # || $self->ut_text('lastapp')
197 # || $self->ut_text('lastdata')
198 # || $self->ut_numbern('startdate')
199 # || $self->ut_numbern('answerdate')
200 # || $self->ut_numbern('enddate')
201 # || $self->ut_number('duration')
202 # || $self->ut_number('billsec')
203 # || $self->ut_text('disposition')
204 # || $self->ut_number('amaflags')
205 # || $self->ut_text('accountcode')
206 # || $self->ut_text('uniqueid')
207 # || $self->ut_text('userfield')
208 # || $self->ut_numbern('cdrtypenum')
209 # || $self->ut_textn('charged_party')
210 ## || $self->ut_n('upstream_currency')
211 ## || $self->ut_n('upstream_price')
212 # || $self->ut_numbern('upstream_rateplanid')
213 ## || $self->ut_n('distance')
214 # || $self->ut_numbern('islocal')
215 # || $self->ut_numbern('calltypenum')
216 # || $self->ut_textn('description')
217 # || $self->ut_numbern('quantity')
218 # || $self->ut_numbern('carrierid')
219 # || $self->ut_numbern('upstream_rateid')
220 # || $self->ut_numbern('svcnum')
221 # || $self->ut_textn('freesidestatus')
223 # return $error if $error;
225 #check the foreign keys even?
226 #do we want to outright *reject* the CDR?
228 $self->ut_numbern('acctid')
230 #Usage = 1, S&E = 7, OC&C = 8
231 || $self->ut_foreign_keyn('cdrtypenum', 'cdr_type', 'cdrtypenum' )
233 #the big list in appendix 2
234 || $self->ut_foreign_keyn('calltypenum', 'cdr_calltype', 'calltypenum' )
236 # Telstra =1, Optus = 2, RSL COM = 3
237 || $self->ut_foreign_keyn('carrierid', 'cdr_carrier', 'carrierid' )
239 return $error if $error;
255 'startdate', # XXX will need massaging
268 'calldate', # XXX may need massaging
269 'billsec', #XXX duration and billsec?
270 # sub { $_[0]->billsec( $_[1] );
271 # $_[0]->duration( $_[1] );
278 'upstream_rateplanid',
282 'startdate', # XXX will definitely need massaging
283 'enddate', # XXX same
294 my $fh = $param->{filehandle};
295 my $format = $param->{format};
297 return "Unknown format $format" unless exists $formats{$format};
299 eval "use Text::CSV_XS;";
302 my $csv = new Text::CSV_XS;
307 local $SIG{HUP} = 'IGNORE';
308 local $SIG{INT} = 'IGNORE';
309 local $SIG{QUIT} = 'IGNORE';
310 local $SIG{TERM} = 'IGNORE';
311 local $SIG{TSTP} = 'IGNORE';
312 local $SIG{PIPE} = 'IGNORE';
314 my $oldAutoCommit = $FS::UID::AutoCommit;
315 local $FS::UID::AutoCommit = 0;
319 while ( defined($line=<$fh>) ) {
321 $csv->parse($line) or do {
322 $dbh->rollback if $oldAutoCommit;
323 return "can't parse: ". $csv->error_input();
326 my @columns = $csv->fields();
327 #warn join('-',@columns);
333 my $field_or_sub = $_;
334 if ( ref($field_or_sub) ) {
335 push @later, $field_or_sub, shift(@columns);
338 ( $field_or_sub => shift @columns );
342 @{ $formats{$format} }
345 my $cdr = new FS::cdr ( \%cdr );
347 while ( scalar(@later) ) {
348 my $sub = shift @later;
349 my $data = shift @later;
350 &{$sub}($cdr, $data); # $cdr->&{$sub}($data);
353 my $error = $cdr->insert;
355 $dbh->rollback if $oldAutoCommit;
365 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
367 #might want to disable this if we skip records for any reason...
368 return "Empty file!" unless $imported;
380 L<FS::Record>, schema.html from the base documentation.