+=item is_tollfree
+
+ Returns true when the cdr represents a toll free number and false otherwise.
+
+=cut
+
+sub is_tollfree {
+ my $self = shift;
+ ( $self->dst =~ /^(\+?1)?8(8|[02-7]{2})/ ) ? 1 : 0;
+}
+
+=item set_charged_party
+
+If the charged_party field is already set, does nothing. Otherwise:
+
+If the cdr-charged_party-accountcode config option is enabled, sets the
+charged_party to the accountcode.
+
+Otherwise sets the charged_party normally: to the src field in most cases,
+or to the dst field if it is a toll free number.
+
+=cut
+
+sub set_charged_party {
+ my $self = shift;
+
+ unless ( $self->charged_party ) {
+
+ my $conf = new FS::Conf;
+
+ if ( $conf->exists('cdr-charged_party-accountcode') && $self->accountcode ){
+
+ $self->charged_party( $self->accountcode );
+
+ } else {
+
+ if ( $self->is_tollfree ) {
+ $self->charged_party($self->dst);
+ } else {
+ $self->charged_party($self->src);
+ }
+
+ }
+
+ }
+
+}
+
+=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->freesidestatus($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 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
+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_names = (
+ 'convergent' => {},
+ 'simple' => {
+ 'name' => 'Simple',
+ 'invoice_header' => "Date,Time,Name,Destination,Duration,Price",
+ },
+ 'simple2' => {
+ 'name' => 'Simple with source',
+ 'invoice_header' => "Date,Time,Called From,Destination,Duration,Price",
+ #"Date,Time,Name,Called From,Destination,Duration,Price",
+ },
+ 'default' => {
+ 'name' => 'Default',
+ 'invoice_header' => 'Date,Time,Number,Destination,Duration,Price',
+ },
+ 'source_default' => {
+ 'name' => 'Default with source',
+ 'invoice_header' => 'Caller,Date,Time,Number,Destination,Duration,Price',
+ },
+ 'accountcode_default' => {
+ 'name' => 'Default plus accountcode',
+ 'invoice_header' => 'Date,Time,Account,Number,Destination,Duration,Price',
+ },
+);
+
+my $duration_sub = sub {
+ my($cdr, %opt) = @_;
+ if ( $opt{minutes} ) {
+ $opt{minutes}. ( $opt{granularity} ? 'm' : ' call' );
+ } else {
+ sprintf('%.2fm', $cdr->billsec / 60 );
+ }
+};
+
+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
+ ],
+ '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
+ 'dst', #NUMBER_DIALED
+ 'src', #called from
+ $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}; },
+