+=item set_status_and_rated_price STATUS [ RATED_PRICE ]
+
+Sets the status to the provided string. If there is an error, returns the
+error, otherwise returns false.
+
+=cut
+
+sub set_status_and_rated_price {
+ my($self, $status, $rated_price) = @_;
+ $self->status($status);
+ $self->rated_price($rated_price);
+ $self->replace();
+}
+
+=item calldate_unix
+
+Parses the calldate in SQL string format and returns a UNIX timestamp.
+
+=cut
+
+sub calldate_unix {
+ str2time(shift->calldate);
+}
+
+=item cdr_carrier
+
+Returns the FS::cdr_carrier object associated with this CDR, or false if no
+carrierid is defined.
+
+=cut
+
+my %carrier_cache = ();
+
+sub cdr_carrier {
+ my $self = shift;
+ return '' unless $self->carrierid;
+ $carrier_cache{$self->carrierid} ||=
+ qsearchs('cdr_carrier', { 'carrierid' => $self->carrierid } );
+}
+
+=item carriername
+
+Returns the carrier name (see L<FS::cdr_carrier>), or the empty string if
+no FS::cdr_carrier object is assocated with this CDR.
+
+=cut
+
+sub carriername {
+ my $self = shift;
+ my $cdr_carrier = $self->cdr_carrier;
+ $cdr_carrier ? $cdr_carrier->carriername : '';
+}
+
+=item cdr_calltype
+
+Returns the FS::cdr_calltype object associated with this CDR, or false if no
+calltypenum is defined.
+
+=cut
+
+my %calltype_cache = ();
+
+sub cdr_calltype {
+ my $self = shift;
+ return '' unless $self->calltypenum;
+ $calltype_cache{$self->calltypenum} ||=
+ qsearchs('cdr_calltype', { 'calltypenum' => $self->calltypenum } );
+}
+
+=item calltypename
+
+Returns the call type name (see L<FS::cdr_calltype>), or the empty string if
+no FS::cdr_calltype object is assocated with this CDR.
+
+=cut
+
+sub calltypename {
+ my $self = shift;
+ my $cdr_calltype = $self->cdr_calltype;
+ $cdr_calltype ? $cdr_calltype->calltypename : '';
+}
+
+=item cdr_upstream_rate
+
+Returns the upstream rate mapping (see L<FS::cdr_upstream_rate>), or the empty
+string if no FS::cdr_upstream_rate object is associated with this CDR.
+
+=cut
+
+sub cdr_upstream_rate {
+ my $self = shift;
+ return '' unless $self->upstream_rateid;
+ qsearchs('cdr_upstream_rate', { 'upstream_rateid' => $self->upstream_rateid })
+ or '';
+}
+
+=item _convergent_format COLUMN [ COUNTRYCODE ]
+
+Returns the number in COLUMN formatted as follows:
+
+If the country code does not match COUNTRYCODE (default "61"), it is returned
+unchanged.
+
+If the country code does match COUNTRYCODE (default "61"), it is removed. In
+addiiton, "0" is prepended unless the number starts with 13, 18 or 19. (???)
+
+=cut
+
+sub _convergent_format {
+ my( $self, $field ) = ( shift, shift );
+ my $countrycode = scalar(@_) ? shift : '61'; #+61 = australia
+ #my $number = $self->$field();
+ my $number = $self->get($field);
+ #if ( $number =~ s/^(\+|011)$countrycode// ) {
+ if ( $number =~ s/^\+$countrycode// ) {
+ $number = "0$number"
+ unless $number =~ /^1[389]/; #???
+ }
+ $number;
+}
+
+=item downstream_csv [ OPTION => VALUE, ... ]
+
+=cut
+
+my %export_formats = (
+ 'convergent' => [
+ 'carriername', #CARRIER
+ sub { shift->_convergent_format('src') }, #SERVICE_NUMBER
+ sub { shift->_convergent_format('charged_party') }, #CHARGED_NUMBER
+ sub { time2str('%Y-%m-%d', shift->calldate_unix ) }, #DATE
+ sub { time2str('%T', shift->calldate_unix ) }, #TIME
+ 'billsec', #'duration', #DURATION
+ sub { shift->_convergent_format('dst') }, #NUMBER_DIALED
+ '', #XXX add (from prefixes in most recent email) #FROM_DESC
+ '', #XXX add (from prefixes in most recent email) #TO_DESC
+ 'calltypename', #CLASS_CODE
+ 'rated_price', #PRICE
+ sub { shift->rated_price ? 'Y' : 'N' }, #RATED
+ '', #OTHER_INFO
+ ],
+);
+
+sub downstream_csv {
+ my( $self, %opt ) = @_;
+
+ my $format = $opt{'format'}; # 'convergent';
+ return "Unknown format $format" unless exists $export_formats{$format};
+
+ eval "use Text::CSV_XS;";
+ die $@ if $@;
+ my $csv = new Text::CSV_XS;
+
+ my @columns =
+ map {
+ ref($_) ? &{$_}($self) : $self->$_();
+ }
+ @{ $export_formats{$format} };
+
+ my $status = $csv->combine(@columns);
+ die "FS::CDR: error combining ". $csv->error_input(). "into downstream CSV"
+ unless $status;
+
+ $csv->string;
+
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item batch_import
+
+=cut
+
+my %import_formats = (