[freeside-commits] branch master updated. c2f7d8ba623194ad1fae37b231b2e29b33d05674

Mark Wells mark at 420.am
Tue Jun 3 17:00:12 PDT 2014


The branch, master has been updated
       via  c2f7d8ba623194ad1fae37b231b2e29b33d05674 (commit)
      from  b86687a90410fb7147c172b0a2f8581fb1b9e64e (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 c2f7d8ba623194ad1fae37b231b2e29b33d05674
Author: Mark Wells <mark at freeside.biz>
Date:   Tue Jun 3 16:59:41 2014 -0700

    changes to support new invoice template features, #28080

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index b198598..9404c06 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -1060,6 +1060,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 `$\'',
@@ -4246,6 +4262,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 93445ab..380f895 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -718,7 +718,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")
@@ -773,8 +775,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 4e67cf7..fb02e6b 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1008,7 +1008,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 1b765fa..85c4bac 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -2919,6 +2919,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 b8f1eee..d3eff35 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