add customer fields option with agent, display_custnum, status and name, RT#73721
[freeside.git] / FS / FS / detail_format.pm
index 665afdb..46c6300 100644 (file)
@@ -34,19 +34,22 @@ a subclass.
 
 OPTIONS may contain:
 
-- buffer: an arrayref to store details into.  This may avoid the need for 
-  a large copy operation at the end of processing.  However, since 
-  summary formats will produce nothing until the end of processing, 
-  C<finish> must be called after all CDRs have been appended.
+- buffer: an arrayref to store details into.  This may avoid the need for a
+large copy operation at the end of processing.  However, since summary
+formats will produce nothing until the end of processing, C<finish> must be
+called after all CDRs have been appended.
 
-- inbound: a flag telling the formatter to format CDRs for display to 
-  the receiving party, rather than the originator.  In this case, the 
-  L<FS::cdr_termination> object will be fetched and its values used for
-  rated_price, rated_seconds, rated_minutes, and svcnum.  This can be 
-  changed with the C<inbound> method.
+- inbound: a flag telling the formatter to format CDRs for display to the
+receiving party, rather than the originator.  In this case, the
+L<FS::cdr_termination> object will be fetched and its values used for
+rated_price, rated_seconds, rated_minutes, and svcnum.  This can be changed
+with the C<inbound> method.
 
-- locale: a locale string to use for static text and date formats.  This
-  is optional.
+- locale: a locale string to use for static text and date formats.  This is
+optional.
+
+- rounding: the number of decimal places to show in the amount column. This
+is optional, and defaults to whatever's in the schema (which is 4).
 
 =cut
 
@@ -63,15 +66,16 @@ sub new {
   my %opt = @_;
 
   my $locale = $opt{'locale'} || '';
-  my $conf = FS::Conf->new(locale => $locale);
+  my $conf = FS::Conf->new({ locale => $locale });
   $locale ||= $conf->config('locale') || 'en_US';
 
   my %locale_info = FS::Locales->locale_info($locale);
   my $language_name = $locale_info{'name'};
 
-  my $self = { conf => FS::Conf->new(locale => $locale),
-               csv  => Text::CSV_XS->new,
+  my $self = { conf => FS::Conf->new({ locale => $locale }),
+               csv  => Text::CSV_XS->new({ binary => 1 }),
                inbound  => ($opt{'inbound'} ? 1 : 0),
+               rounding => $opt{'rounding'},
                buffer   => ($opt{'buffer'} || []),
                _lh      => FS::L10N->get_handle($locale),
                _dh      => eval { Date::Language->new($language_name) } ||
@@ -84,6 +88,8 @@ sub new {
 
 =head1 METHODS
 
+=over 4
+
 =item inbound VALUE
 
 Set/get the 'inbound' flag.
@@ -96,6 +102,19 @@ sub inbound {
   $self->{inbound};
 }
 
+=item phonenum VALUE
+
+Set/get the locally meaningful phone number.  This is used to tag call details
+for presentation on certain kinds of invoices.
+
+=cut
+
+sub phonenum {
+  my $self = shift;
+  $self->{phonenum} = shift if @_;
+  $self->{phonenum};
+}
+
 =item append CDRS
 
 Takes any number of call detail records (as L<FS::cdr> objects),
@@ -153,7 +172,7 @@ sub header {
   my $self = shift;
 
   FS::cust_bill_pkg_detail->new(
-    { 'format' => 'C', 'detail' => $self->mt($self->header_detail) }
+    { 'format' => 'C', 'detail' => $self->header_detail }
   )
 }
 
@@ -163,6 +182,7 @@ Takes a single CDR and returns an invoice detail to describe it.
 
 By default, this maps the following fields from the CDR:
 
+acctid            => acctid
 rated_price       => amount
 rated_classnum    => classnum
 rated_seconds     => duration
@@ -170,6 +190,14 @@ rated_regionname  => regionname
 accountcode       => accountcode
 startdate         => startdate
 
+If the formatter is in inbound mode, it will look up a C<cdr_termination>
+record and use rated_price and rated_seconds from that, and acctid will be
+set to null to avoid linking the CDR to the detail record for the inbound
+leg of the call.
+
+'phonenum' is set to the internal C<phonenum> value set on the formatter
+object.
+
 It then calls C<columns> on the CDR to obtain a list of detail
 columns, formats them as a CSV string, and stores that in the 
 'detail' field.
@@ -188,16 +216,20 @@ sub single_detail {
   my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
   my $price = $object->rated_price if $object;
   $price = 0 if $cdr->freesidestatus eq 'no-charge';
+  $price = sprintf('%.*f', $self->{'rounding'}, $price)
+    if $self->{'rounding'} and length($price);
 
   FS::cust_bill_pkg_detail->new( {
+      'acctid'      => ($self->{inbound} ? '' : $cdr->acctid),
       'amount'      => $price,
       'classnum'    => $cdr->rated_classnum,
-      'duration'    => $cdr->rated_seconds,
+      'duration'    => $object->rated_seconds,
       'regionname'  => $cdr->rated_regionname,
       'accountcode' => $cdr->accountcode,
       'startdate'   => $cdr->startdate,
       'format'      => 'C',
       'detail'      => $self->csv->string,
+      'phonenum'    => $self->phonenum,
   });
 }
 
@@ -249,10 +281,7 @@ sub time2str_local {
   $self->{_dh}->time2str(@_);
 }
 
-sub mt {
-  my $self = shift;
-  $self->{_lh}->maketext(@_);
-}
+# header strings are now localized in FS::TemplateItem_Mixin::detail
 
 #imitate previous behavior for now
 
@@ -262,13 +291,11 @@ sub duration {
   my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
   my $sec = $object->rated_seconds if $object;
   $sec ||= 0;
-  # XXX termination objects don't have rated_granularity so this may 
-  # result in inbound CDRs being displayed as min/sec when they shouldn't.
-  # Should probably fix this.
-  if ( $cdr->rated_granularity eq '0' ) {
+  # termination objects now have rated_granularity.
+  if ( $object->rated_granularity eq '0' ) {
     '1 call';
   }
-  elsif ( $cdr->rated_granularity eq '60' ) {
+  elsif ( $object->rated_granularity eq '60' ) {
     sprintf('%dm', ($sec + 59)/60);
   }
   else {
@@ -282,7 +309,15 @@ sub price {
   my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
   my $price = $object->rated_price if $object;
   $price = '0.00' if $object->freesidestatus eq 'no-charge';
-  length($price) ? $self->money_char . $price : '';
+  $price = sprintf('%.*f', $self->{'rounding'}, $price)
+    if $self->{'rounding'};
+  if (length($price)) {
+    $price = sprintf('%.*f', $self->{'rounding'}, $price)
+      if $self->{'rounding'};
+    return $self->money_char . $price;
+  } else {
+    return '';
+  }
 }
 
 1;