[freeside-commits] freeside/FS/FS cdr.pm, 1.83, 1.84 cust_bill_pkg.pm, 1.66, 1.67 detail_format.pm, NONE, 1.1
Mark Wells
mark at wavetail.420.am
Tue Jan 3 13:13:34 PST 2012
- Previous message: [freeside-commits] freeside/FS/FS/detail_format - New directory, NONE, NONE
- Next message: [freeside-commits] freeside/FS/FS/detail_format accountcode_default.pm, NONE, 1.1 basic.pm, NONE, 1.1 default.pm, NONE, 1.1 description_default.pm, NONE, 1.1 simple2.pm, NONE, 1.1 simple.pm, NONE, 1.1 source_default.pm, NONE, 1.1 sum_count.pm, NONE, 1.1 sum_duration.pm, NONE, 1.1 sum_duration_prefix.pm, NONE, 1.1
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
Update of /home/cvs/cvsroot/freeside/FS/FS
In directory wavetail.420.am:/tmp/cvs-serv18734/FS/FS
Modified Files:
cdr.pm cust_bill_pkg.pm
Added Files:
detail_format.pm
Log Message:
detail format refactor, #15535
--- NEW FILE: detail_format.pm ---
package FS::detail_format;
use strict;
use vars qw( $DEBUG );
use FS::Conf;
use FS::cdr;
use FS::cust_bill_pkg_detail;
use Date::Format qw(time2str);
use Text::CSV_XS;
my $me = '[FS::detail_format]';
=head1 NAME
FS::detail_format - invoice detail formatter
=head1 DESCRIPTION
An FS::detail_format object is a converter to create invoice details
(L<FS::cust_bill_pkg_detail>) from call detail records (L<FS::cdr>)
or other usage information. FS::detail_format inherits from nothing.
Subclasses of FS::detail_format represent specific detail formats.
=head1 CLASS METHODS
=over 4
=item new FORMAT, OPTIONS
Returns a new detail formatter. The FORMAT argument is the name of
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.
- 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.
=cut
sub new {
my $class = shift;
if ( $class eq 'FS::detail_format' ) {
my $format = shift
or die "$me format name required";
$class = "FS::detail_format::$format"
unless $format =~ /^FS::detail_format::/;
}
eval "use $class";
die "$me error loading $class: $@" if $@;
my %opt = @_;
my $self = { conf => FS::Conf->new,
csv => Text::CSV_XS->new,
inbound => ($opt{'inbound'} ? 1 : 0),
buffer => ($opt{'buffer'} || []),
};
bless $self, $class;
}
=back
=head1 METHODS
=item inbound VALUE
Set/get the 'inbound' flag.
=cut
sub inbound {
my $self = shift;
$self->{inbound} = ($_[0] > 0) if (@_);
$self->{inbound};
}
=item append CDRS
Takes any number of call detail records (as L<FS::cdr> objects),
formats them, and appends them to the internal buffer.
By default, this simply calls C<single_detail> on each CDR in the
set. Subclasses should override C<append> and maybe C<finish> if
they do not produce detail lines from CDRs in a 1:1 fashion.
The 'billpkgnum', 'invnum', 'pkgnum', and 'phonenum' fields will
be set later.
=cut
sub append {
my $self = shift;
foreach (@_) {
push @{ $self->{buffer} }, $self->single_detail($_);
}
}
=item details
Returns all invoice detail records in the buffer. This will perform
a C<finish> first. Subclasses generally shouldn't override this.
=cut
sub details {
my $self = shift;
$self->finish;
@{ $self->{buffer} }
}
=item finish
Ensures that all invoice details are generated given the CDRs that
have been appended. By default, this does nothing.
=cut
sub finish {}
=item header
Returns a header row for the format, as an L<FS::cust_bill_pkg_detail>
object. By default this has 'format' = 'C', 'detail' = the value
returned by C<header_detail>, and all other fields empty.
This is called after C<finish>, so it can use information from the CDRs.
=cut
sub header {
my $self = shift;
FS::cust_bill_pkg_detail->new(
{ 'format' => 'C', 'detail' => $self->header_detail }
)
}
=item single_detail CDR
Takes a single CDR and returns an invoice detail to describe it.
By default, this maps the following fields from the CDR:
rated_price => amount
rated_classnum => classnum
rated_seconds => duration
rated_regionname => regionname
accountcode => accountcode
startdate => startdate
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.
=cut
sub single_detail {
my $self = shift;
my $cdr = shift;
my @columns = $self->columns($cdr);
my $status = $self->csv->combine(@columns);
die "$me error combining ".$self->csv->error_input."\n"
if !$status;
FS::cust_bill_pkg_detail->new( {
'amount' => $cdr->rated_price,
'classnum' => $cdr->rated_classnum,
'duration' => $cdr->rated_seconds,
'regionname' => $cdr->rated_regionname,
'accountcode' => $cdr->accountcode,
'startdate' => $cdr->startdate,
'format' => 'C',
'detail' => $self->csv->string,
});
}
=item columns CDR
Returns a list of CSV columns (to be shown on the invoice) for
the CDR. This is the method most subclasses should override.
=cut
sub columns {
my $self = shift;
die "$me no columns method in ".ref($self);
}
=item header_detail
Returns the 'detail' field for the header row. This should
probably be a CSV string of column headers for the values returned
by C<columns>.
=cut
sub header_detail {
my $self = shift;
die "$me no header_detail method in ".ref($self);
}
# convenience methods for subclasses
sub conf { $_[0]->{conf} }
sub csv { $_[0]->{csv} }
sub date_format {
my $self = shift;
$self->{date_format} ||= ($self->conf->config('date_format') || '%m/%d/%Y');
}
sub money_char {
my $self = shift;
$self->{money_char} ||= ($self->conf->config('money_char') || '$');
}
#imitate previous behavior for now
sub duration {
my $self = shift;
my $cdr = shift;
my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
my $sec = $object->rated_seconds if $object;
# 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' ) {
'1 call';
}
elsif ( $cdr->rated_granularity eq '60' ) {
sprintf('%dm', ($sec + 59)/60);
}
else {
sprintf('%dm %ds', $sec / 60, $sec % 60);
}
}
sub price {
my $self = shift;
my $cdr = shift;
my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
my $price = $object->rated_price if $object;
length($price) ? $self->money_char . $price : '';
}
1;
Index: cust_bill_pkg.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill_pkg.pm,v
retrieving revision 1.66
retrieving revision 1.67
diff -u -w -d -r1.66 -r1.67
--- cust_bill_pkg.pm 13 Dec 2011 20:40:28 -0000 1.66
+++ cust_bill_pkg.pm 3 Jan 2012 21:13:32 -0000 1.67
@@ -19,6 +19,8 @@
use FS::cust_bill_pkg_tax_rate_location;
use FS::cust_tax_adjustment;
+use List::Util qw(sum);
+
@ISA = qw( FS::cust_main_Mixin FS::Record );
$DEBUG = 0;
@@ -147,30 +149,8 @@
if ( $self->get('details') ) {
foreach my $detail ( @{$self->get('details')} ) {
- my %hash = ();
- if ( ref($detail) ) {
- if ( ref($detail) eq 'ARRAY' ) {
- #carp "this way sucks, use a hash"; #but more useful/friendly
- $hash{'format'} = $detail->[0];
- $hash{'detail'} = $detail->[1];
- $hash{'amount'} = $detail->[2];
- $hash{'classnum'} = $detail->[3];
- $hash{'phonenum'} = $detail->[4];
- $hash{'accountcode'} = $detail->[5];
- $hash{'startdate'} = $detail->[6];
- $hash{'duration'} = $detail->[7];
- $hash{'regionname'} = $detail->[8];
- } elsif ( ref($detail) eq 'HASH' ) {
- %hash = %$detail;
- } else {
- die "unknow detail type ". ref($detail);
- }
- } else {
- $hash{'detail'} = $detail;
- }
- $hash{'billpkgnum'} = $self->billpkgnum;
- my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail \%hash;
- $error = $cust_bill_pkg_detail->insert;
+ $detail->billpkgnum($self->billpkgnum);
+ $error = $detail->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "error inserting cust_bill_pkg_detail: $error";
@@ -351,6 +331,8 @@
;
return $error if $error;
+ $self->regularize_details;
+
#if ( $self->pkgnum != 0 ) { #allow unchecked pkgnum 0 for tax! (add to part_pkg?)
if ( $self->pkgnum > 0 ) { #allow -1 for non-pkg line items and 0 for tax (add to part_pkg?)
return "Unknown pkgnum ". $self->pkgnum
@@ -363,6 +345,50 @@
$self->SUPER::check;
}
+=item regularize_details
+
+Converts the contents of the 'details' pseudo-field to
+L<FS::cust_bill_pkg_detail> objects, if they aren't already.
+
+=cut
+
+sub regularize_details {
+ my $self = shift;
+ if ( $self->get('details') ) {
+ foreach my $detail ( @{$self->get('details')} ) {
+ if ( ref($detail) ne 'FS::cust_bill_pkg_detail' ) {
+ # then turn it into one
+ my %hash = ();
+ if ( ! ref($detail) ) {
+ $hash{'detail'} = 'detail';
+ }
+ elsif ( ref($detail) eq 'HASH' ) {
+ %hash = %$detail;
+ }
+ elsif ( ref($detail) eq 'ARRAY' ) {
+ carp "passing invoice details as arrays is deprecated";
+ #carp "this way sucks, use a hash"; #but more useful/friendly
+ $hash{'format'} = $detail->[0];
+ $hash{'detail'} = $detail->[1];
+ $hash{'amount'} = $detail->[2];
+ $hash{'classnum'} = $detail->[3];
+ $hash{'phonenum'} = $detail->[4];
+ $hash{'accountcode'} = $detail->[5];
+ $hash{'startdate'} = $detail->[6];
+ $hash{'duration'} = $detail->[7];
+ $hash{'regionname'} = $detail->[8];
+ }
+ else {
+ die "unknown detail type ". ref($detail);
+ }
+ $detail = new FS::cust_bill_pkg_detail \%hash;
+ }
+ $detail->billpkgnum($self->billpkgnum) if $self->billpkgnum;
+ }
+ }
+ return;
+}
+
=item cust_pkg
Returns the package (see L<FS::cust_pkg>) for this invoice line item.
@@ -863,29 +889,15 @@
sub usage {
my( $self, $classnum ) = @_;
+ $self->regularize_details;
if ( $self->get('details') ) {
- my $sum = 0;
- foreach my $value (
- map { ref($_) eq 'HASH'
- ? $_->{'amount'}
- : $_->[2]
- }
- grep { ref($_) && ( defined($classnum)
- ? $classnum eq ( ref($_) eq 'HASH'
- ? $_->{'classnum'}
- : $_->[3]
- )
- : 1
- )
- }
+ return sum(
+ map { $_->amount || 0 }
+ grep { !defined($classnum) or $classnum eq $_->classnum }
@{ $self->get('details') }
- ) {
- $sum += $value if $value;
- }
-
- return $sum;
+ );
} else {
@@ -911,16 +923,11 @@
sub usage_classes {
my( $self ) = @_;
+ $self->regularize_details;
if ( $self->get('details') ) {
- my %seen = ();
- foreach my $detail ( grep { ref($_) } @{$self->get('details')} ) {
- $seen{ (ref($detail) eq 'HASH'
- ? $detail->{'classnum'}
- : $detail->[3]) || ''
- } = 1;
- }
+ my %seen = ( map { $_->classnum => 1 } @{ $self->get('details') } );
keys %seen;
} else {
Index: cdr.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cdr.pm,v
retrieving revision 1.83
retrieving revision 1.84
diff -u -w -d -r1.83 -r1.84
--- cdr.pm 3 Jan 2012 07:47:27 -0000 1.83
+++ cdr.pm 3 Jan 2012 21:13:32 -0000 1.84
@@ -10,6 +10,7 @@
use Date::Parse;
use Date::Format;
use Time::Local;
+use List::Util qw( first min );
use FS::UID qw( dbh );
use FS::Conf;
use FS::Record qw( qsearch qsearchs );
@@ -426,7 +427,9 @@
Sets the status and rated price.
-Available options are: inbound, rated_seconds, rated_minutes, rated_classnum, rated_ratename
+Available options are: inbound, rated_pretty_dst, rated_regionname,
+rated_seconds, rated_minutes, rated_granularity, rated_ratedetailnum,
+rated_classnum, rated_ratename.
If there is an error, returns the error, otherwise returns false.
@@ -833,9 +836,11 @@
sub rate_upstream_simple {
my( $self, %opt ) = @_;
- $self->set_status_and_rated_price( 'rated',
+ $self->set_status_and_rated_price(
+ 'rated',
sprintf('%.3f', $self->upstream_price),
$opt{'svcnum'},
+ 'rated_classnum' => $self->calltypenum,
);
}
@@ -874,9 +879,12 @@
sprintf('%.4f', ( $part_pkg->option_cacheable('min_charge') * $charge_min )
+ 0.0000000001 ); #so 1.00005 rounds to 1.0001
- $self->set_status_and_rated_price( 'rated',
+ $self->set_status_and_rated_price(
+ 'rated',
$charge,
$opt{'svcnum'},
+ 'rated_granularity' => $granularity,
+ 'rated_seconds' => $seconds,
);
}
@@ -1021,13 +1029,17 @@
'invoice_header' => 'Caller,Date,Time,Number,Destination,Duration,Price',
},
'sum_duration' => {
- 'name' => 'Summary (one line per service, with duration)',
+ 'name' => 'Summary, one line per service',
'invoice_header' => 'Caller,Rate,Calls,Minutes,Price',
},
'sum_count' => {
- 'name' => 'Summary (one line per service, with count)',
+ 'name' => 'Number of calls, one line per service',
'invoice_header' => 'Caller,Rate,Messages,Price',
},
+ 'sum_duration_prefix' => {
+ 'name' => 'Summary, one line per destination prefix',
+ 'invoice_header' => 'Caller,Rate,Calls,Minutes,Price',
+ },
);
my %export_formats = ();
@@ -1216,6 +1228,8 @@
=cut
+# in the future, load this dynamically from detail_format classes
+
sub invoice_formats {
map { ($_ => $export_names{$_}->{'name'}) }
grep { $export_names{$_}->{'invoice_header'} }
- Previous message: [freeside-commits] freeside/FS/FS/detail_format - New directory, NONE, NONE
- Next message: [freeside-commits] freeside/FS/FS/detail_format accountcode_default.pm, NONE, 1.1 basic.pm, NONE, 1.1 default.pm, NONE, 1.1 description_default.pm, NONE, 1.1 simple2.pm, NONE, 1.1 simple.pm, NONE, 1.1 source_default.pm, NONE, 1.1 sum_count.pm, NONE, 1.1 sum_duration.pm, NONE, 1.1 sum_duration_prefix.pm, NONE, 1.1
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
More information about the freeside-commits
mailing list