1 package FS::detail_format;
7 use FS::cust_bill_pkg_detail;
8 use Date::Format qw(time2str);
11 my $me = '[FS::detail_format]';
15 FS::detail_format - invoice detail formatter
19 An FS::detail_format object is a converter to create invoice details
20 (L<FS::cust_bill_pkg_detail>) from call detail records (L<FS::cdr>)
21 or other usage information. FS::detail_format inherits from nothing.
23 Subclasses of FS::detail_format represent specific detail formats.
29 =item new FORMAT, OPTIONS
31 Returns a new detail formatter. The FORMAT argument is the name of
36 - buffer: an arrayref to store details into. This may avoid the need for
37 a large copy operation at the end of processing. However, since
38 summary formats will produce nothing until the end of processing,
39 C<finish> must be called after all CDRs have been appended.
41 - inbound: a flag telling the formatter to format CDRs for display to
42 the receiving party, rather than the originator. In this case, the
43 L<FS::cdr_termination> object will be fetched and its values used for
44 rated_price, rated_seconds, rated_minutes, and svcnum. This can be
45 changed with the C<inbound> method.
51 if ( $class eq 'FS::detail_format' ) {
53 or die "$me format name required";
54 $class = "FS::detail_format::$format"
55 unless $format =~ /^FS::detail_format::/;
58 die "$me error loading $class: $@" if $@;
61 my $self = { conf => FS::Conf->new,
62 csv => Text::CSV_XS->new,
63 inbound => ($opt{'inbound'} ? 1 : 0),
64 buffer => ($opt{'buffer'} || []),
75 Set/get the 'inbound' flag.
81 $self->{inbound} = ($_[0] > 0) if (@_);
87 Takes any number of call detail records (as L<FS::cdr> objects),
88 formats them, and appends them to the internal buffer.
90 By default, this simply calls C<single_detail> on each CDR in the
91 set. Subclasses should override C<append> and maybe C<finish> if
92 they do not produce detail lines from CDRs in a 1:1 fashion.
94 The 'billpkgnum', 'invnum', 'pkgnum', and 'phonenum' fields will
102 push @{ $self->{buffer} }, $self->single_detail($_);
108 Returns all invoice detail records in the buffer. This will perform
109 a C<finish> first. Subclasses generally shouldn't override this.
121 Ensures that all invoice details are generated given the CDRs that
122 have been appended. By default, this does nothing.
130 Returns a header row for the format, as an L<FS::cust_bill_pkg_detail>
131 object. By default this has 'format' = 'C', 'detail' = the value
132 returned by C<header_detail>, and all other fields empty.
134 This is called after C<finish>, so it can use information from the CDRs.
141 FS::cust_bill_pkg_detail->new(
142 { 'format' => 'C', 'detail' => $self->header_detail }
146 =item single_detail CDR
148 Takes a single CDR and returns an invoice detail to describe it.
150 By default, this maps the following fields from the CDR:
152 rated_price => amount
153 rated_classnum => classnum
154 rated_seconds => duration
155 rated_regionname => regionname
156 accountcode => accountcode
157 startdate => startdate
159 It then calls C<columns> on the CDR to obtain a list of detail
160 columns, formats them as a CSV string, and stores that in the
169 my @columns = $self->columns($cdr);
170 my $status = $self->csv->combine(@columns);
171 die "$me error combining ".$self->csv->error_input."\n"
174 my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
175 my $price = $object->rated_price if $object;
176 $price = 0 if $cdr->freesidestatus eq 'no-charge';
178 FS::cust_bill_pkg_detail->new( {
180 'classnum' => $cdr->rated_classnum,
181 'duration' => $cdr->rated_seconds,
182 'regionname' => $cdr->rated_regionname,
183 'accountcode' => $cdr->accountcode,
184 'startdate' => $cdr->startdate,
186 'detail' => $self->csv->string,
192 Returns a list of CSV columns (to be shown on the invoice) for
193 the CDR. This is the method most subclasses should override.
199 die "$me no columns method in ".ref($self);
204 Returns the 'detail' field for the header row. This should
205 probably be a CSV string of column headers for the values returned
212 die "$me no header_detail method in ".ref($self);
215 # convenience methods for subclasses
217 sub conf { $_[0]->{conf} }
219 sub csv { $_[0]->{csv} }
223 $self->{date_format} ||= ($self->conf->config('date_format') || '%m/%d/%Y');
228 $self->{money_char} ||= ($self->conf->config('money_char') || '$');
231 #imitate previous behavior for now
236 my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
237 my $sec = $object->rated_seconds if $object;
239 # XXX termination objects don't have rated_granularity so this may
240 # result in inbound CDRs being displayed as min/sec when they shouldn't.
241 # Should probably fix this.
242 if ( $cdr->rated_granularity eq '0' ) {
245 elsif ( $cdr->rated_granularity eq '60' ) {
246 sprintf('%dm', ($sec + 59)/60);
249 sprintf('%dm %ds', $sec / 60, $sec % 60);
256 my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
257 my $price = $object->rated_price if $object;
258 $price = '0.00' if $object->freesidestatus eq 'no-charge';
259 length($price) ? $self->money_char . $price : '';