fix invoice sub-totals, RT#6489
[freeside.git] / FS / FS / cust_main.pm
index 501fca4..506f10b 100644 (file)
@@ -2462,7 +2462,7 @@ An array ref of specific packages (objects) to attempt billing, instead trying a
 
 =item not_pkgpart
 
-A hashref of pkgparts to exclude from this billing run.
+A hashref of pkgparts to exclude from this billing run (can also be specified as a comma-separated scalar).
 
 =item invoice_time
 
@@ -2475,6 +2475,11 @@ typically might mean not charging the normal recurring fee but only usage
 fees since the last billing. Setup charges may be charged.  Not all package
 plans support this feature (they tend to charge 0).
 
+=item invoice_terms
+
+Options terms to be printed on this invocice.  Otherwise, customer-specific
+terms or the default terms are used.
+
 =back
 
 =cut
@@ -2489,6 +2494,10 @@ sub bill {
   my $invoice_time = $options{'invoice_time'} || $time;
 
   $options{'not_pkgpart'} ||= {};
+  $options{'not_pkgpart'} = { map { $_ => 1 }
+                                  split(/\s*,\s*/, $options{'not_pkgpart'})
+                            }
+    unless ref($options{'not_pkgpart'});
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -2782,8 +2791,8 @@ sub bill {
   my @cust_bill = $self->cust_bill;
   my $balance = $self->balance;
   my $previous_balance = scalar(@cust_bill)
-                           ?  $cust_bill[$#cust_bill]->billing_balance
-                           :  0;
+                           ? ( $cust_bill[$#cust_bill]->billing_balance || 0 )
+                           : 0;
 
   $previous_balance += $cust_bill[$#cust_bill]->charged
     if scalar(@cust_bill);
@@ -2797,6 +2806,7 @@ sub bill {
     'charged'             => $charged,
     'billing_balance'     => $balance,
     'previous_balance'    => $previous_balance,
+    'invoice_terms'       => $options{'invoice_terms'},
   } );
   $error = $cust_bill->insert;
   if ( $error ) {
@@ -3036,7 +3046,7 @@ sub _make_lines {
       ###
 
       my $error = 
-        $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg, $options{invoice_time}, $real_pkgpart);
+        $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg, $cust_pkg, $options{invoice_time}, $real_pkgpart, \%options);
       return $error if $error;
 
       push @$cust_bill_pkgs, $cust_bill_pkg;
@@ -3057,6 +3067,7 @@ sub _handle_taxes {
   my $cust_pkg = shift;
   my $invoice_time = shift;
   my $real_pkgpart = shift;
+  my $options = shift;
 
   my %cust_bill_pkg = ();
   my %taxes = ();
@@ -3064,8 +3075,8 @@ sub _handle_taxes {
   my @classes;
   #push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->type eq 'U';
   push @classes, $cust_bill_pkg->usage_classes if $cust_bill_pkg->usage;
-  push @classes, 'setup' if $cust_bill_pkg->setup;
-  push @classes, 'recur' if $cust_bill_pkg->recur;
+  push @classes, 'setup' if ($cust_bill_pkg->setup && !$options->{cancel});
+  push @classes, 'recur' if ($cust_bill_pkg->recur && !$options->{cancel});
 
   if ( $self->tax !~ /Y/i && $self->payby ne 'COMP' ) {
 
@@ -3094,7 +3105,7 @@ sub _handle_taxes {
 
     } else {
 
-      my @loc_keys = qw( state county country );
+      my @loc_keys = qw( city county state country );
       my %taxhash;
       if ( $conf->exists('tax-pkg_address') && $cust_pkg->locationnum ) {
         my $cust_location = $cust_pkg->cust_location;
@@ -3113,7 +3124,7 @@ sub _handle_taxes {
 
       my %taxhash_elim = %taxhash;
 
-      my @elim = qw( taxclass county state );
+      my @elim = qw( taxclass city county state );
       while ( !scalar(@taxes) && scalar(@elim) ) {
         $taxhash_elim{ shift(@elim) } = '';
         @taxes = qsearch( 'cust_main_county', \%taxhash_elim );
@@ -7164,6 +7175,13 @@ New-style, with a hashref of options:
                                     #vendor taxation
                                     'taxproduct' => 2,  #part_pkg_taxproduct
                                     'override'   => {}, #XXX describe
+
+                                    #will be filled in with the new object
+                                    'cust_pkg_ref' => \$cust_pkg,
+
+                                    #generate an invoice immediately
+                                    'bill_now' => 0,
+                                    'invoice_terms' => '', #with these terms
                                   }
                                 );
 
@@ -7179,6 +7197,8 @@ sub charge {
   my ( $pkg, $comment, $additional );
   my ( $setuptax, $taxclass );   #internal taxes
   my ( $taxproduct, $override ); #vendor (CCH) taxes
+  my $cust_pkg_ref = '';
+  my ( $bill_now, $invoice_terms ) = ( 0, '' );
   if ( ref( $_[0] ) ) {
     $amount     = $_[0]->{amount};
     $quantity   = exists($_[0]->{quantity}) ? $_[0]->{quantity} : 1;
@@ -7192,6 +7212,9 @@ sub charge {
     $additional = $_[0]->{additional} || [];
     $taxproduct = $_[0]->{taxproductnum};
     $override   = { '' => $_[0]->{tax_override} };
+    $cust_pkg_ref = exists($_[0]->{cust_pkg_ref}) ? $_[0]->{cust_pkg_ref} : '';
+    $bill_now = exists($_[0]->{bill_now}) ? $_[0]->{bill_now} : '';
+    $invoice_terms = exists($_[0]->{invoice_terms}) ? $_[0]->{invoice_terms} : '';
   } else {
     $amount     = shift;
     $quantity   = 1;
@@ -7220,7 +7243,7 @@ sub charge {
     'plan'          => 'flat',
     'freq'          => 0,
     'disabled'      => 'Y',
-    'classnum'      => $classnum ? $classnum : '',
+    'classnum'      => ( $classnum ? $classnum : '' ),
     'setuptax'      => $setuptax,
     'taxclass'      => $taxclass,
     'taxproductnum' => $taxproduct,
@@ -7263,10 +7286,22 @@ sub charge {
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
+  } elsif ( $cust_pkg_ref ) {
+    ${$cust_pkg_ref} = $cust_pkg;
+  }
+
+  if ( $bill_now ) {
+    my $error = $self->bill( 'invoice_terms' => $invoice_terms,
+                             'pkg_list'      => [ $cust_pkg ],
+                           );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }   
   }
 
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-  '';
+  return '';
 
 }
 
@@ -7305,6 +7340,7 @@ Returns all the invoices (see L<FS::cust_bill>) for this customer.
 
 sub cust_bill {
   my $self = shift;
+  map { $_ } #return $self->num_cust_bill unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch('cust_bill', { 'custnum' => $self->custnum, } )
 }
@@ -7336,6 +7372,7 @@ Returns all the statements (see L<FS::cust_statement>) for this customer.
 
 sub cust_statement {
   my $self = shift;
+  map { $_ } #return $self->num_cust_statement unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch('cust_statement', { 'custnum' => $self->custnum, } )
 }
@@ -7348,6 +7385,7 @@ Returns all the credits (see L<FS::cust_credit>) for this customer.
 
 sub cust_credit {
   my $self = shift;
+  map { $_ } #return $self->num_cust_credit unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
 }
@@ -7361,6 +7399,7 @@ package when using experimental package balances.
 
 sub cust_credit_pkgnum {
   my( $self, $pkgnum ) = @_;
+  map { $_ } #return $self->num_cust_credit_pkgnum($pkgnum) unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch( 'cust_credit', { 'custnum' => $self->custnum,
                               'pkgnum'  => $pkgnum,
@@ -7376,10 +7415,26 @@ Returns all the payments (see L<FS::cust_pay>) for this customer.
 
 sub cust_pay {
   my $self = shift;
+  return $self->num_cust_pay unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch( 'cust_pay', { 'custnum' => $self->custnum } )
 }
 
+=item num_cust_pay
+
+Returns the number of payments (see L<FS::cust_pay>) for this customer.  Also
+called automatically when the cust_pay method is used in a scalar context.
+
+=cut
+
+sub num_cust_pay {
+  my $self = shift;
+  my $sql = "SELECT COUNT(*) FROM cust_pay WHERE custnum = ?";
+  my $sth = dbh->prepare($sql) or die dbh->errstr;
+  $sth->execute($self->custnum) or die $sth->errstr;
+  $sth->fetchrow_arrayref->[0];
+}
+
 =item cust_pay_pkgnum
 
 Returns all the payments (see L<FS::cust_pay>) for this customer's specific
@@ -7389,6 +7444,7 @@ package when using experimental package balances.
 
 sub cust_pay_pkgnum {
   my( $self, $pkgnum ) = @_;
+  map { $_ } #return $self->num_cust_pay_pkgnum($pkgnum) unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch( 'cust_pay', { 'custnum' => $self->custnum,
                            'pkgnum'  => $pkgnum,
@@ -7404,6 +7460,7 @@ Returns all voided payments (see L<FS::cust_pay_void>) for this customer.
 
 sub cust_pay_void {
   my $self = shift;
+  map { $_ } #return $self->num_cust_pay_void unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch( 'cust_pay_void', { 'custnum' => $self->custnum } )
 }
@@ -7416,6 +7473,7 @@ Returns all batched payments (see L<FS::cust_pay_void>) for this customer.
 
 sub cust_pay_batch {
   my $self = shift;
+  map { $_ } #return $self->num_cust_pay_batch unless wantarray;
   sort { $a->paybatchnum <=> $b->paybatchnum }
     qsearch( 'cust_pay_batch', { 'custnum' => $self->custnum } )
 }
@@ -7463,6 +7521,7 @@ Returns all the refunds (see L<FS::cust_refund>) for this customer.
 
 sub cust_refund {
   my $self = shift;
+  map { $_ } #return $self->num_cust_refund unless wantarray;
   sort { $a->_date <=> $b->_date }
     qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
 }