[freeside-commits] branch FREESIDE_3_BRANCH updated. 16453ebdacf7dc86d045a40eab8427414d6488e4
Mark Wells
mark at 420.am
Tue Jun 3 17:00:07 PDT 2014
The branch, FREESIDE_3_BRANCH has been updated
via 16453ebdacf7dc86d045a40eab8427414d6488e4 (commit)
from 2a5840941e01e1d6b83ad5f9e1039eb844dc5071 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 16453ebdacf7dc86d045a40eab8427414d6488e4
Author: Mark Wells <mark at freeside.biz>
Date: Tue Jun 3 16:58:48 2014 -0700
changes to support new invoice template features, #28080
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 7ede999..941417f 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -1046,6 +1046,7 @@ sub reason_type_options {
'%m/%d/%Y' => 'MM/DD/YYYY',
'%d/%m/%Y' => 'DD/MM/YYYY',
'%Y/%m/%d' => 'YYYY/MM/DD',
+ '%e %b %Y' => 'DD Mon YYYY',
],
'per_locale' => 1,
},
@@ -1576,6 +1577,13 @@ and customer address. Include units.',
# 'per_agent' => 1,
#},
+ {
+ 'key' => 'usage_class_summary',
+ 'section' => 'invoicing',
+ 'description' => 'Summarize total usage by usage class in a separate section.',
+ 'type' => 'checkbox',
+ },
+
{
'key' => 'usage_class_as_a_section',
'section' => 'invoicing',
@@ -1691,6 +1699,14 @@ and customer address. Include units.',
},
{
+ 'key' => 'papersize',
+ 'section' => 'billing',
+ 'description' => 'Invoice paper size. Default is "letter" (U.S. standard). The LaTeX template must be configured to match this size.',
+ 'type' => 'select',
+ 'select_enum' => [ qw(letter a4) ],
+ },
+
+ {
'key' => 'money_char',
'section' => '',
'description' => 'Currency symbol - defaults to `$\'',
@@ -4253,6 +4269,16 @@ and customer address. Include units.',
},
{
+ 'key' => 'previous_invoice_history',
+ 'section' => 'invoicing',
+ 'description' => 'Show a month-by-month history of the customer\'s '.
+ 'billing amounts. This requires template '.
+ 'modification and is currently not supported on the '.
+ 'stock template.',
+ 'type' => 'checkbox',
+ },
+
+ {
'key' => 'balance_due_below_line',
'section' => 'invoicing',
'description' => 'Place the balance due message below a line. Only meaningful when when invoice_sections is false.',
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index c598507..bede413 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -715,7 +715,9 @@ sub generate_ps {
_pslatex($file);
- system('dvips', '-q', '-t', 'letter', "$file.dvi", '-o', "$file.ps" ) == 0
+ my $papersize = $conf->config('papersize') || 'letter';
+
+ system('dvips', '-q', '-t', $papersize, "$file.dvi", '-o', "$file.ps" ) == 0
or die "dvips failed";
open(POSTSCRIPT, "<$file.ps")
@@ -770,8 +772,10 @@ sub generate_pdf {
my $sfile = shell_quote $file;
#system('dvipdf', "$file.dvi", "$file.pdf" );
+ my $papersize = $conf->config('papersize') || 'letter';
+
system(
- "dvips -q -t letter -f $sfile.dvi ".
+ "dvips -q -f $sfile.dvi -t $papersize ".
"| gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$sfile.pdf ".
" -c save pop -"
) == 0
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 95de553..b22f27c 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -801,7 +801,7 @@ sub tables_hashref {
'cust_bill_pkg_detail' => {
'columns' => [
- 'detailnum', 'serial', '', '', '', '',
+ 'detailnum', 'serial', '', '', '', '',
'billpkgnum', 'int', 'NULL', '', '', '', # should not be nullable
'pkgnum', 'int', 'NULL', '', '', '', # deprecated
'invnum', 'int', 'NULL', '', '', '', # deprecated
diff --git a/FS/FS/TemplateItem_Mixin.pm b/FS/FS/TemplateItem_Mixin.pm
index fa20c24..6ae3364 100644
--- a/FS/FS/TemplateItem_Mixin.pm
+++ b/FS/FS/TemplateItem_Mixin.pm
@@ -128,20 +128,21 @@ sub time_period_pretty {
Returns an array of detail information for the invoice line item.
-Currently available options are: I<format>, I<escape_function> and
-I<format_function>.
+Options may include:
-If I<format> is set to html or latex then the array members are improved
-for tabular appearance in those environments if possible.
+I<format>: set to 'html' or 'latex' to have the detail lines formatted for
+inclusion in an HTML table (wrapped in <tr> and <td> elements) or LaTeX table
+(delimited with & and \\ operators).
-If I<escape_function> is set then the array members are processed by this
+I<escape_function>: if present, then the array elements are processed by this
function before being returned.
-I<format_function> overrides the normal HTML or LaTeX function for returning
-formatted CDRs. It can be set to a subroutine which returns an empty list
-to skip usage detail:
+I<format_function>: overrides the normal HTML or LaTeX function for returning
+formatted CDRs.
- 'format_function' => sub { () },
+I<no_usage>: excludes call detail records. The method will still return
+some special-case records like prorate details, and manually created package
+details.
=cut
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index e0ea6ab..bfa03bc 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -1051,6 +1051,7 @@ sub print_generic {
$detail->{'edate'} = $line_item->{'edate'};
$detail->{'seconds'} = $line_item->{'seconds'};
$detail->{'svc_label'} = $line_item->{'svc_label'};
+ $detail->{'usage_item'} = $line_item->{'usage_item'};
push @detail_items, $detail;
push @buf, ( [ $detail->{'description'},
@@ -1390,6 +1391,37 @@ sub print_generic {
}
$invoice_data{summary_subtotals} = \@summary_subtotals;
+ # usage subtotals
+ if ( $conf->exists('usage_class_summary')
+ and $self->can('_items_usage_class_summary') ) {
+ my @usage_subtotals = $self->_items_usage_class_summary(escape => $escape_function);
+ if ( @usage_subtotals ) {
+ unshift @sections, $usage_subtotals[0]->{section};
+ unshift @detail_items, @usage_subtotals;
+ }
+ }
+
+ # invoice history "section" (not really a section)
+ # not to be included in any subtotals, completely independent of
+ # everything...
+ if ( $conf->exists('previous_invoice_history') ) {
+ my %history;
+ my %monthorder;
+ foreach my $cust_bill ( $cust_main->cust_bill ) {
+ # XXX hardcoded format, and currently only 'charged'; add other fields
+ # if they become necessary
+ my $date = $self->time2str_local('%b %Y', $cust_bill->_date);
+ $history{$date} ||= 0;
+ $history{$date} += $cust_bill->charged;
+ # just so we have a numeric sort key
+ $monthorder{$date} ||= $cust_bill->_date;
+ }
+ my @sorted_months = sort { $monthorder{$a} <=> $monthorder{$b} }
+ keys %history;
+ my @sorted_amounts = map { sprintf('%.2f', $history{$_}) } @sorted_months;
+ $invoice_data{monthly_history} = [ \@sorted_months, \@sorted_amounts ];
+ }
+
# debugging hook: call this with 'diag' => 1 to just get a hash of
# the invoice variables
return \%invoice_data if ( $params{'diag'} );
@@ -2494,6 +2526,9 @@ sub _items_cust_bill_pkg {
@cust_bill_pkg_display = grep { !$_->summary }
@cust_bill_pkg_display;
}
+
+ my $classname = ''; # package class name, will fill in later
+
foreach my $display (@cust_bill_pkg_display) {
warn "$me _items_cust_bill_pkg considering cust_bill_pkg_display ".
@@ -2554,6 +2589,9 @@ sub _items_cust_bill_pkg {
%item_dates = map { $_ => $cust_bill_pkg->$_ } ('sdate', 'edate')
unless $part_pkg->option('disable_line_item_date_ranges',1);
+ # not normally used, but pass this to the template anyway
+ $classname = $part_pkg->classname;
+
if ( (!$type || $type eq 'S')
&& ( $cust_bill_pkg->setup != 0
|| $cust_bill_pkg->setup_show_zero
@@ -2580,16 +2618,20 @@ sub _items_cust_bill_pkg {
my @d = ();
my $svc_label;
+
+ # always pass the svc_label through to the template, even if
+ # not displaying it as an ext_description
+ my @svc_labels = map &{$escape_function}($_),
+ $cust_pkg->h_labels_short($self->_date, undef, 'I');
+
+ $svc_label = $svc_labels[0];
+
unless ( $cust_pkg->part_pkg->hide_svc_detail
|| $cust_bill_pkg->hidden )
{
- my @svc_labels = map &{$escape_function}($_),
- $cust_pkg->h_labels_short($self->_date, undef, 'I');
push @d, @svc_labels
unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
- $svc_label = $svc_labels[0];
-
my $lnum = $cust_main ? $cust_main->ship_locationnum
: $self->prospect_main->locationnum;
# show the location label if it's not the customer's default
@@ -2663,6 +2705,10 @@ sub _items_cust_bill_pkg {
push @dates, $prev->sdate if $prev;
push @dates, undef if !$prev;
+ my @svc_labels = map &{$escape_function}($_),
+ $cust_pkg->h_labels_short(@dates, 'I');
+ $svc_label = $svc_labels[0];
+
# show service labels, unless...
# the package is set not to display them
unless ( $part_pkg->hide_svc_detail
@@ -2682,12 +2728,8 @@ sub _items_cust_bill_pkg {
warn "$me _items_cust_bill_pkg adding service details\n"
if $DEBUG > 1;
- my @svc_labels = map &{$escape_function}($_),
- $cust_pkg->h_labels_short(@dates, 'I');
push @d, @svc_labels
unless $cust_bill_pkg->pkgpart_override; #don't redisplay services
- $svc_label = $svc_labels[0];
-
warn "$me _items_cust_bill_pkg done adding service details\n"
if $DEBUG > 1;
@@ -2796,6 +2838,7 @@ sub _items_cust_bill_pkg {
pkgpart => $pkgpart,
pkgnum => $cust_bill_pkg->pkgnum,
amount => $amount,
+ usage_item => 1,
recur_show_zero => $cust_bill_pkg->recur_show_zero,
%item_dates,
ext_description => \@d,
diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm
index c2be4f2..4126d5f 100644
--- a/FS/FS/cdr.pm
+++ b/FS/FS/cdr.pm
@@ -338,7 +338,7 @@ sub check {
#check the foreign keys even?
#do we want to outright *reject* the CDR?
my $error =
- $self->ut_numbern('acctid')
+ $self->ut_numbern('acctid');
#add a config option to turn these back on if someone needs 'em
#
@@ -350,7 +350,7 @@ sub check {
#
# # Telstra =1, Optus = 2, RSL COM = 3
# || $self->ut_foreign_keyn('carrierid', 'cdr_carrier', 'carrierid' )
- ;
+
return $error if $error;
$self->SUPER::check;
@@ -1210,6 +1210,10 @@ my %export_names = (
'name' => 'Summary, one line per destination prefix',
'invoice_header' => 'Caller,Rate,Calls,Minutes,Price',
},
+ 'sum_count_class' => {
+ 'name' => 'Summary, one line per usage class',
+ 'invoice_header' => 'Caller,Class,Calls,Price',
+ },
);
my %export_formats = ();
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index 3a5007a..cfefc95 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -2977,6 +2977,49 @@ sub _items_svc_phone_sections {
}
+=sub _items_usage_class_summary OPTIONS
+
+Returns a list of detail items summarizing the usage charges on this
+invoice. Each one will have 'amount', 'description' (the usage charge name),
+and 'usage_classnum'.
+
+OPTIONS can include 'escape' (a function to escape the descriptions).
+
+=cut
+
+sub _items_usage_class_summary {
+ my $self = shift;
+ my %opt = @_;
+
+ my $escape = $opt{escape} || sub { $_[0] };
+ my $invnum = $self->invnum;
+ my @classes = qsearch({
+ 'table' => 'usage_class',
+ 'select' => 'classnum, classname, SUM(amount) AS amount',
+ 'addl_from' => ' LEFT JOIN cust_bill_pkg_detail USING (classnum)' .
+ ' LEFT JOIN cust_bill_pkg USING (billpkgnum)',
+ 'extra_sql' => " WHERE cust_bill_pkg.invnum = $invnum".
+ ' GROUP BY classnum, classname, weight'.
+ ' HAVING (usage_class.disabled IS NULL OR SUM(amount) > 0)'.
+ ' ORDER BY weight ASC',
+ });
+ my @l;
+ my $section = {
+ description => &{$escape}($self->mt('Usage Summary')),
+ no_subtotal => 1,
+ usage_section => 1,
+ };
+ foreach my $class (@classes) {
+ push @l, {
+ 'description' => &{$escape}($class->classname),
+ 'amount' => sprintf('%.2f', $class->amount),
+ 'usage_classnum' => $class->classnum,
+ 'section' => $section,
+ };
+ }
+ return @l;
+}
+
sub _items_previous {
my $self = shift;
my $conf = $self->conf;
diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm
index 46f6e17..d0cbdbe 100644
--- a/FS/FS/cust_bill_pkg_detail.pm
+++ b/FS/FS/cust_bill_pkg_detail.pm
@@ -86,27 +86,15 @@ sub table { 'cust_bill_pkg_detail'; }
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
-=cut
-
-# the insert method can be inherited from FS::Record
-
=item delete
Delete this record from the database.
-=cut
-
-# the delete method can be inherited from FS::Record
-
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
-=cut
-
-# the replace method can be inherited from FS::Record
-
=item check
Checks all fields to make sure this is a valid line item detail. If there is
@@ -145,6 +133,7 @@ sub check {
|| $self->ut_text('detail')
|| $self->ut_foreign_keyn('classnum', 'usage_class', 'classnum')
|| $self->$phonenum_check_method('phonenum')
+ || $self->ut_numbern('startdate')
|| $self->SUPER::check
;
@@ -237,6 +226,18 @@ sub formatted {
;
}
+=item cust_bill_pkg
+
+Returns the L<FS::cust_bill_pkg> object (the invoice line item) that
+this detail belongs to.
+
+=cut
+
+sub cust_bill_pkg {
+ my $self = shift;
+ my $billpkgnum = $self->billpkgnum or return '';
+ FS::cust_bill_pkg->by_key($billpkgnum);
+}
# Used by FS::Upgrade to migrate to a new database schema
sub _upgrade_schema { # class method
diff --git a/FS/FS/detail_format.pm b/FS/FS/detail_format.pm
index c90d313..b072ff5 100644
--- a/FS/FS/detail_format.pm
+++ b/FS/FS/detail_format.pm
@@ -98,6 +98,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),
@@ -165,21 +178,15 @@ Takes a single CDR and returns an invoice detail to describe it.
By default, this maps the following fields from the CDR:
-=over 4
+rated_price => amount
+rated_classnum => classnum
+rated_seconds => duration
+rated_regionname => regionname
+accountcode => accountcode
+startdate => startdate
-=item rated_price => amount
-
-=item rated_classnum => classnum
-
-=item rated_seconds => duration
-
-=item rated_regionname => regionname
-
-=item accountcode => accountcode
-
-=item startdate => startdate
-
-=back
+'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
@@ -209,6 +216,7 @@ sub single_detail {
'startdate' => $cdr->startdate,
'format' => 'C',
'detail' => $self->csv->string,
+ 'phonenum' => $self->phonenum,
});
}
diff --git a/FS/FS/detail_format/sum_count_class.pm b/FS/FS/detail_format/sum_count_class.pm
new file mode 100644
index 0000000..749d452
--- /dev/null
+++ b/FS/FS/detail_format/sum_count_class.pm
@@ -0,0 +1,93 @@
+package FS::detail_format::sum_count_class;
+
+use strict;
+use vars qw( $DEBUG );
+use base qw(FS::detail_format);
+use FS::Record qw(qsearchs);
+use FS::cust_svc;
+use FS::svc_Common; # for label
+
+$DEBUG = 0;
+
+sub name { 'Summary, one line per service and usage class' };
+
+sub header_detail {
+ my $self = shift;
+ if ( $self->{inbound} ) {
+ 'Destination,Charge Class,Quantity,Price'
+ }
+ else {
+ 'Source,Charge Class,Quantity,Price'
+ }
+}
+
+sub append {
+ my $self = shift;
+ my $svcnums = ($self->{svcnums} ||= {});
+ my $acctids = $self->{acctids} ||= {};
+ foreach my $cdr (@_) {
+ my $object = $self->{inbound} ? $cdr->cdr_termination(1) : $cdr;
+ my $svcnum = $object->svcnum; # yes, $object->svcnum.
+
+ my $subtotal = ($svcnums->{$svcnum}->{$cdr->rated_classnum} ||=
+ { count => 0, duration => 0, amount => 0 });
+ $subtotal->{count}++;
+ $subtotal->{duration} += $object->rated_seconds;
+ $subtotal->{amount} += $object->rated_price
+ if $object->freesidestatus ne 'no-charge';
+
+ my $these_acctids = $acctids->{$cdr->rated_classnum} ||= [];
+ push @$these_acctids, $cdr->acctid;
+ }
+}
+
+sub finish {
+ my $self = shift;
+ my $svcnums = $self->{svcnums};
+ my $buffer = $self->{buffer};
+ foreach my $svcnum (keys %$svcnums) {
+
+ my $classnums = $svcnums->{$svcnum};
+
+ my $cust_svc = qsearchs('cust_svc', { svcnum => $svcnum })
+ or die "svcnum #$svcnum not found";
+ my $phonenum = $cust_svc->svc_x->label;
+ warn "processing $phonenum\n" if $DEBUG;
+
+ foreach my $classnum (keys %$classnums) {
+ my $subtotal = $classnums->{$classnum};
+ next if $subtotal->{amount} < 0.01;
+ my $classname = ($classnum ?
+ FS::usage_class->by_key($classnum)->classname :
+ '');
+ $self->csv->combine(
+ $phonenum,
+ $classname,
+ $subtotal->{count},
+ $self->money_char . sprintf('%.02f',$subtotal->{amount}),
+ );
+
+ warn "adding detail: ".$self->csv->string."\n" if $DEBUG;
+
+ push @$buffer, FS::cust_bill_pkg_detail->new({
+ amount => $subtotal->{amount},
+ format => 'C',
+ classnum => $classnum,
+ duration => $subtotal->{duration},
+ phonenum => $phonenum,
+ accountcode => '', #ignored in this format
+ startdate => '', #could use the earliest startdate in the bunch?
+ regionname => '',
+ detail => $self->csv->string,
+ acctid => $self->{acctids}->{$classnum},
+ });
+ } #foreach $classnum
+ } #foreach $svcnum
+
+ # supposedly the compiler is smart enough to do this in place
+ @$buffer = sort { $a->{Hash}->{phonenum} cmp $b->{Hash}->{phonenum} or
+ $a->{Hash}->{classnum} <=> $b->{Hash}->{classnum} }
+ @$buffer;
+}
+
+1;
diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm
index d7bf16d..3791e56 100644
--- a/FS/FS/part_pkg/voip_cdr.pm
+++ b/FS/FS/part_pkg/voip_cdr.pm
@@ -465,6 +465,15 @@ sub calc_usage {
#my @invoice_details_sort;
+ # for tagging invoice details
+ my $phonenum;
+ if ( $svc_table eq 'svc_phone' ) {
+ $phonenum = $svc_x->phonenum;
+ } elsif ( $svc_table eq 'svc_pbx' ) {
+ $phonenum = $svc_x->title;
+ }
+ $formatter->phonenum($phonenum);
+
#first rate any outstanding CDRs not yet rated
# XXX eventually use an FS::Cursor for this
my $cdr_search = $svc_x->psearch_cdrs(%options);
-----------------------------------------------------------------------
Summary of changes:
FS/FS/Conf.pm | 26 +++++++++
FS/FS/Misc.pm | 8 ++-
FS/FS/Schema.pm | 2 +-
FS/FS/TemplateItem_Mixin.pm | 19 +++----
FS/FS/Template_Mixin.pm | 59 +++++++++++++++++---
FS/FS/cdr.pm | 8 ++-
FS/FS/cust_bill.pm | 43 +++++++++++++++
FS/FS/cust_bill_pkg_detail.pm | 25 ++++-----
FS/FS/detail_format.pm | 36 ++++++++-----
FS/FS/detail_format/sum_count_class.pm | 93 ++++++++++++++++++++++++++++++++
FS/FS/part_pkg/voip_cdr.pm | 9 ++++
11 files changed, 280 insertions(+), 48 deletions(-)
create mode 100644 FS/FS/detail_format/sum_count_class.pm
More information about the freeside-commits
mailing list