add billco respooling, not re-FTPing, RT#3971
[freeside.git] / FS / FS / cust_bill.pm
index 8cc39ad..61ae6c0 100644 (file)
@@ -226,7 +226,12 @@ Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
 
 sub cust_bill_pkg {
   my $self = shift;
-  qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
+  qsearch(
+    { 'table'    => 'cust_bill_pkg',
+      'hashref'  => { 'invnum' => $self->invnum },
+      'order_by' => 'ORDER BY billpkgnum',
+    }
+  );
 }
 
 =item cust_pkg
@@ -969,6 +974,46 @@ sub fax_invoice {
 
 }
 
+=item ftp_invoice [ TEMPLATENAME ] 
+
+Sends this invoice data via FTP.
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub ftp_invoice {
+  my $self = shift;
+  my $template = scalar(@_) ? shift : '';
+
+  $self->send_csv(
+    'protocol'   => 'ftp',
+    'server'     => $conf->config('cust_bill-ftpserver'),
+    'username'   => $conf->config('cust_bill-ftpusername'),
+    'password'   => $conf->config('cust_bill-ftppassword'),
+    'dir'        => $conf->config('cust_bill-ftpdir'),
+    'format'     => $conf->config('cust_bill-ftpformat'),
+  );
+}
+
+=item spool_invoice [ TEMPLATENAME ] 
+
+Spools this invoice data (see L<FS::spool_csv>)
+
+TEMPLATENAME is unused?
+
+=cut
+
+sub spool_invoice {
+  my $self = shift;
+  my $template = scalar(@_) ? shift : '';
+
+  $self->spool_csv(
+    'format'       => $conf->config('cust_bill-spoolformat'),
+    'agent_spools' => $conf->exists('cust_bill-spoolagent'),
+  );
+}
+
 =item send_if_newest [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
 
 Like B<send>, but only sends the invoice if it is the newest open invoice for
@@ -1626,6 +1671,8 @@ L<Time::Local> and L<Date::Parse> for conversion functions.
 
 cid - 
 
+unsquelch_cdr - overrides any per customer cdr squelching when true
+
 =cut
 
 sub print_generic {
@@ -1992,9 +2039,11 @@ sub print_generic {
   my $adjust_section = { 'description' => 'Credits, Payments, and Adjustments',
                          'subtotal'    => 0 }; # adjusted below
 
+  my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y';
   my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum);
+  my $late_sections = [];
   if ( $multisection ) {
-    push @sections, $self->_items_sections;
+    push @sections, $self->_items_sections( $late_sections );
   }else{
     push @sections, { 'description' => '', 'subtotal' => '' };
   }
@@ -2033,7 +2082,7 @@ sub print_generic {
     push @buf, ['',''];
   }
 
-  foreach my $section (@sections) {
+  foreach my $section (@sections, @$late_sections) {
 
     $section->{'subtotal'} = $other_money_char.
                              sprintf('%.2f', $section->{'subtotal'})
@@ -2049,6 +2098,8 @@ sub print_generic {
     $options{'section'} = $section if $multisection;
     $options{'format'} = $format;
     $options{'escape_function'} = $escape_function;
+    $options{'format_function'} = sub { () } unless $unsquelched;
+    $options{'unsquelched'} = $unsquelched;
 
     foreach my $line_item ( $self->_items_pkg(%options) ) {
       my $detail = {
@@ -2156,8 +2207,8 @@ sub print_generic {
                )
       );
     if ( $multisection ) {
-      $adjust_section->{'pretotal'} = 'New charges total '.
-                                      $total->{'total_amount'};
+      $adjust_section->{'pretotal'} = 'New charges total '. $other_money_char.
+                                      sprintf('%.2f', $self->charged );
     }else{
       push @total_items, $total;
     }
@@ -2269,6 +2320,11 @@ sub print_generic {
     }
   }
 
+  if ( $multisection ) {
+    push @sections, @$late_sections
+      if $unsquelched;
+  }
+
   $invoice_lines = 0;
   my $wasfunc = 0;
   foreach ( grep /invoice_lines\(\d*\)/, @invoice_template ) { #kludgy
@@ -2376,12 +2432,17 @@ when emailing the invoice as part of a multipart/related MIME email.
 =cut
 
 sub print_html {
-  my( $self, $today, $template, $cid ) = @_;
+  my $self = shift;
+  my %params;
+  if ( ref $_[0]  ) {
+    %params = %{ shift() }; 
+  }else{
+    $params{'time'} = shift;
+    $params{'template'} = shift;
+    $params{'cid'} = shift;
+  }
 
-  my %params = ( 'format' => 'html' );
-  $params{'time'} = $today if $today;
-  $params{'template'} = $template if $template;
-  $params{'cid'} = $cid if $cid;
+  $params{'format'} = 'html';
 
   $self->print_generic( %params );
 }
@@ -2528,24 +2589,51 @@ sub invnum_date_pretty {
 
 sub _items_sections {
   my $self = shift;
+  my $late = shift;
 
   my %s = ();
-  foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+  my %l = ();
+
+  foreach my $cust_bill_pkg ( $self->cust_bill_pkg )
+  {
 
     if ( $cust_bill_pkg->pkgnum > 0 ) {
 
-      my $desc = $cust_bill_pkg->part_pkg->categoryname;
+      my $desc = $cust_bill_pkg->section;
+      my $dup_desc = $cust_bill_pkg->duplicate_section;
 
-      $s{$desc} += $cust_bill_pkg->setup
-        if ( $cust_bill_pkg->setup != 0 );
+      if ($cust_bill_pkg->duplicate) {
+        $s{$dup_desc} += $cust_bill_pkg->setup
+          if ( $cust_bill_pkg->setup != 0 );
 
-      $s{$desc} += $cust_bill_pkg->recur
-        if ( $cust_bill_pkg->recur != 0 );
+        $s{$dup_desc} += $cust_bill_pkg->recur
+          if ( $cust_bill_pkg->recur != 0 );
+      }
+
+      if ( $cust_bill_pkg->post_total ) {
+        $l{$desc} += $cust_bill_pkg->setup
+          if ( $cust_bill_pkg->setup != 0 );
+
+        $l{$desc} += $cust_bill_pkg->recur
+          if ( $cust_bill_pkg->recur != 0 );
+
+      } else {
+        $s{$desc} += $cust_bill_pkg->setup
+          if ( $cust_bill_pkg->setup != 0 );
+
+        $s{$desc} += $cust_bill_pkg->recur
+          if ( $cust_bill_pkg->recur != 0 );
+      }
 
     }
 
   }
 
+  push @$late, map { { 'description' => $_,
+                       'subtotal'    => $l{$_},
+                       'post_total'  => 1,
+                   } } sort keys %l;
+
   map { {'description' => $_, 'subtotal' => $s{$_}} } sort keys %s;
 
 }
@@ -2600,11 +2688,12 @@ sub _items_previous {
 sub _items_pkg {
   my $self = shift;
   my %options = @_;
-  my $section = delete $options{'section'};
+  my $section = $options{'section'};
+  my $desc = $section->{'description'};
   my @cust_bill_pkg =
     grep { $_->pkgnum &&
            ( defined($section)
-               ? $_->part_pkg->categoryname eq $section->{'description'}
+               ? ( $_->section eq $desc || $_->duplicate_section eq $desc )
                : 1
            )
          } $self->cust_bill_pkg;
@@ -2633,9 +2722,13 @@ sub _items_cust_bill_pkg {
 
   my $format = $opt{format} || '';
   my $escape_function = $opt{escape_function} || sub { shift };
+  my $format_function = $opt{format_function} || '';
+  my $unsquelched = $opt{unsquelched} || '';
 
   my @b = ();
-  foreach my $cust_bill_pkg ( @$cust_bill_pkg ) {
+  my $last_pkgnum = '';
+  foreach my $cust_bill_pkg ( @$cust_bill_pkg )
+  {
 
     my $cust_pkg = $cust_bill_pkg->cust_pkg;
 
@@ -2643,6 +2736,7 @@ sub _items_cust_bill_pkg {
 
     my %details_opt = ( 'format'          => $format,
                         'escape_function' => $escape_function,
+                        'format_function' => $format_function,
                       );
 
     if ( $cust_bill_pkg->pkgnum > 0 ) {
@@ -2666,11 +2760,19 @@ sub _items_cust_bill_pkg {
           quantity        => $cust_bill_pkg->quantity,
           ext_description => \@d,
         };
+
+        $last_pkgnum = '';
+
       }
 
       if ( $cust_bill_pkg->recur != 0 ) {
 
-        my $description = $desc;
+        my $is_summary =
+          ( $cust_bill_pkg->duplicate && 
+            $opt{section}->{description} ne $cust_bill_pkg->section
+          );
+        my $description = $is_summary ? "Usage charges" : $desc;
+
         unless ( $conf->exists('disable_line_item_date_ranges') ) {
           $description .= " (" . time2str("%x", $cust_bill_pkg->sdate).
                           " - ". time2str("%x", $cust_bill_pkg->edate). ")";
@@ -2678,22 +2780,42 @@ sub _items_cust_bill_pkg {
 
         #at least until cust_bill_pkg has "past" ranges in addition to
         #the "future" sdate/edate ones... see #3032
-        my @d = map &{$escape_function}($_),
-                    $cust_pkg->h_labels_short($self->_date);
+        my @d = ();
+        push @d, map &{$escape_function}($_),
+                       $cust_pkg->h_labels_short($self->_date)
                                               #$cust_bill_pkg->edate,
                                               #$cust_bill_pkg->sdate),
-        push @d, $cust_bill_pkg->details(%details_opt);
+          unless ($cust_bill_pkg->pkgnum eq $last_pkgnum);
 
-        push @b, {
-          description     => $description,
-          #pkgpart         => $part_pkg->pkgpart,
-          pkgnum          => $cust_bill_pkg->pkgnum,
-          amount          => sprintf("%.2f", $cust_bill_pkg->recur),
-          unit_amount     => sprintf("%.2f", $cust_bill_pkg->unitrecur),
-          quantity        => $cust_bill_pkg->quantity,
-          ext_description => \@d,
-        };
+        @d = () if ($cust_bill_pkg->itemdesc || $is_summary);
+        push @d, $cust_bill_pkg->details(%details_opt)
+          unless $is_summary;
 
+        if ($cust_bill_pkg->pkgnum eq $last_pkgnum) {
+
+          $b[$#b]->{amount} =
+            sprintf("%.2f", $b[$#b]->{amount} + $cust_bill_pkg->recur);
+          push @{$b[$#b]->{ext_description}}, @d;
+
+        }else{
+
+          push @b, {
+            description     => $description,
+            #pkgpart         => $part_pkg->pkgpart,
+            pkgnum          => $cust_bill_pkg->pkgnum,
+            amount          => sprintf("%.2f", $cust_bill_pkg->recur),
+            unit_amount     => sprintf("%.2f", $cust_bill_pkg->unitrecur),
+            quantity        => $cust_bill_pkg->quantity,
+            ext_description => \@d,
+          };
+
+        }
+
+        if ($conf->exists('separate_usage') && $cust_bill_pkg->type ne 'U') {
+          $last_pkgnum = '';
+        }else{
+          $last_pkgnum = $cust_bill_pkg->pkgnum;
+        }
       }
 
     } else { #pkgnum tax or one-shot line item (??)
@@ -2713,6 +2835,8 @@ sub _items_cust_bill_pkg {
         };
       }
 
+      $last_pkgnum = '';
+
     }
 
   }
@@ -2781,7 +2905,7 @@ sub _items_payments {
 
 =over 4
 
-=item reprint
+=item process_reprint
 
 =cut
 
@@ -2789,7 +2913,7 @@ sub process_reprint {
   process_re_X('print', @_);
 }
 
-=item reemail
+=item process_reemail
 
 =cut
 
@@ -2797,7 +2921,7 @@ sub process_reemail {
   process_re_X('email', @_);
 }
 
-=item refax
+=item process_refax
 
 =cut
 
@@ -2805,6 +2929,22 @@ sub process_refax {
   process_re_X('fax', @_);
 }
 
+=item process_reftp
+
+=cut
+
+sub process_reftp {
+  process_re_X('ftp', @_);
+}
+
+=item respool
+
+=cut
+
+sub process_respool {
+  process_re_X('spool', @_);
+}
+
 use Storable qw(thaw);
 use Data::Dumper;
 use MIME::Base64;
@@ -2848,6 +2988,8 @@ sub re_X {
     'debug' => 1,
   } );
 
+  $method .= '_invoice' unless $method eq 'email' || $method eq 'print';
+
   warn " $me re_X $method: ". scalar(@cust_bill). " invoices found\n"
     if $DEBUG;