[freeside-commits] freeside/FS/FS Schema.pm, 1.93, 1.94 Upgrade.pm, 1.17, 1.18 cust_bill.pm, 1.218, 1.219 cust_bill_pkg.pm, 1.18, 1.19 cust_bill_pkg_detail.pm, 1.4, 1.5 cust_main.pm, 1.353, 1.354 part_pkg.pm, 1.74, 1.75

Jeff Finucane,420,, jeff at wavetail.420.am
Fri Aug 1 21:10:03 PDT 2008


Update of /home/cvs/cvsroot/freeside/FS/FS
In directory wavetail.420.am:/tmp/cvs-serv24332/FS/FS

Modified Files:
	Schema.pm Upgrade.pm cust_bill.pm cust_bill_pkg.pm 
	cust_bill_pkg_detail.pm cust_main.pm part_pkg.pm 
Log Message:
improve CDR usage presentation

Index: cust_bill_pkg_detail.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill_pkg_detail.pm,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- cust_bill_pkg_detail.pm	16 May 2008 19:26:37 -0000	1.4
+++ cust_bill_pkg_detail.pm	2 Aug 2008 04:10:01 -0000	1.5
@@ -1,10 +1,13 @@
 package FS::cust_bill_pkg_detail;
 
 use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
+use vars qw( @ISA $me $DEBUG );
+use FS::Record qw( qsearch qsearchs dbdef );
+use FS::cust_bill_pkg;
 
 @ISA = qw(FS::Record);
+$me = '[ FS::cust_bill_pkg_detail ]';
+$DEBUG = 0;
 
 =head1 NAME
 
@@ -35,9 +38,7 @@
 
 =item detailnum - primary key
 
-=item pkgnum -
-
-=item invnum -
+=item billpkgnum - link to cust_bill_pkg
 
 =item detail - detail description
 
@@ -102,8 +103,7 @@
   my $self = shift;
 
   $self->ut_numbern('detailnum')
-    || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
-    || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum')
+    || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum')
     || $self->ut_enum('format', [ '', 'C' ] )
     || $self->ut_text('detail')
     || $self->SUPER::check
@@ -111,6 +111,65 @@
 
 }
 
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+
+sub _upgrade_data { # class method
+
+  my ($class, %opts) = @_;
+
+  warn "$me upgrading $class\n" if $DEBUG;
+
+  if ( defined( dbdef->table($class->table)->column('billpkgnum') ) &&
+       defined( dbdef->table($class->table)->column('invnum') ) &&
+       defined( dbdef->table($class->table)->column('pkgnum') ) 
+  ) {
+
+    warn "$me Checking for unmigrated invoice line item details\n" if $DEBUG;
+
+    my @cbpd = qsearch({ 'table'   => $class->table,
+                         'hashref' => {},
+                         'extra_sql' => 'WHERE invnum IS NOT NULL AND '.
+                                        'pkgnum IS NOT NULL',
+                      });
+
+    if (scalar(@cbpd)) {
+      warn "$me Found unmigrated invoice line item details\n" if $DEBUG;
+
+      foreach my $cbpd ( @cbpd ) {
+        my $detailnum = $cbpd->detailnum;
+        warn "$me Contemplating detail $detailnum\n" if $DEBUG > 1;
+        my $cust_bill_pkg =
+          qsearchs({ 'table' => 'cust_bill_pkg',
+                     'hashref' => { 'invnum' => $cbpd->invnum,
+                                    'pkgnum' => $cbpd->pkgnum,
+                                  },
+                     'order_by' => 'ORDER BY billpkgnum LIMIT 1',
+                  });
+        if ($cust_bill_pkg) {
+          $cbpd->billpkgnum($cust_bill_pkg->billpkgnum);
+          $cbpd->invnum('');
+          $cbpd->pkgnum('');
+          my $error = $cbpd->replace;
+
+          warn "*** WARNING: error replacing line item detail ".
+               "(cust_bill_pkg_detail) $detailnum: $error ***\n"
+            if $error;
+        } else {
+          warn "Found orphaned line item detail $detailnum during upgrade.\n";
+        }
+
+      } # foreach $cbpd
+
+    } # if @cbpd
+
+  } # if billpkgnum, invnum, and pkgnum columns defined
+
+  '';
+
+}                         
+
 =back
 
 =head1 BUGS

Index: cust_main.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_main.pm,v
retrieving revision 1.353
retrieving revision 1.354
diff -u -d -r1.353 -r1.354
--- cust_main.pm	1 Aug 2008 21:41:08 -0000	1.353
+++ cust_main.pm	2 Aug 2008 04:10:01 -0000	1.354
@@ -2061,6 +2061,7 @@
   $self->select_for_update; #mutex
 
   my @cust_bill_pkg = ();
+  my @appended_cust_bill_pkg = ();
 
   ###
   # find the packages which are due for billing, find out how much they are
@@ -2092,296 +2093,62 @@
     my $old_cust_pkg = new FS::cust_pkg \%hash;
 
     foreach my $part_pkg ( $cust_pkg->part_pkg->self_and_bill_linked ) {
-
-      $cust_pkg->pkgpart($part_pkg->pkgpart); 
-      $cust_pkg->set($_, $hash{$_}) foreach qw( setup last_bill bill );
-  
-      my @details = ();
-
-      my $lineitems = 0;
-
-      ###
-      # bill setup
-      ###
-
-      my $setup = 0;
-      my $unitsetup = 0;
-      if ( ! $cust_pkg->setup &&
-           (
-             ( $conf->exists('disable_setup_suspended_pkgs') &&
-              ! $cust_pkg->getfield('susp')
-            ) || ! $conf->exists('disable_setup_suspended_pkgs')
-           )
-        || $options{'resetup'}
-      ) {
-    
-        warn "    bill setup\n" if $DEBUG > 1;
-        $lineitems++;
-
-        $setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
-        if ( $@ ) {
-          $dbh->rollback if $oldAutoCommit;
-          return "$@ running calc_setup for $cust_pkg\n";
-        }
-
-        $unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh
-
-        $cust_pkg->setfield('setup', $time)
-          unless $cust_pkg->setup;
-              #do need it, but it won't get written to the db
-              #|| $cust_pkg->pkgpart != $real_pkgpart;
-
-      }
-
-      ###
-      # bill recurring fee
-      ### 
-
-      #XXX unit stuff here too
-      my $recur = 0;
-      my $unitrecur = 0;
-      my $sdate;
-      if ( $part_pkg->getfield('freq') ne '0' &&
-           ! $cust_pkg->getfield('susp') &&
-           ( $cust_pkg->getfield('bill') || 0 ) <= $time
-      ) {
-
-        # XXX should this be a package event?  probably.  events are called
-        # at collection time at the moment, though...
-        $part_pkg->reset_usage($cust_pkg, 'debug'=>$DEBUG)
-          if $part_pkg->can('reset_usage');
-          #don't want to reset usage just cause we want a line item??
-          #&& $part_pkg->pkgpart == $real_pkgpart;
-  
-        warn "    bill recur\n" if $DEBUG > 1;
-        $lineitems++;
-  
-        # XXX shared with $recur_prog
-        $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
-  
-        #over two params!  lets at least switch to a hashref for the rest...
-        my %param = ( 'precommit_hooks' => \@precommit_hooks, );
-  
-        $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) };
-        if ( $@ ) {
-          $dbh->rollback if $oldAutoCommit;
-          return "$@ running calc_recur for $cust_pkg\n";
-        }
-
-  
-        #change this bit to use Date::Manip? CAREFUL with timezones (see
-        # mailing list archive)
-        my ($sec,$min,$hour,$mday,$mon,$year) =
-          (localtime($sdate) )[0,1,2,3,4,5];
-    
-        #pro-rating magic - if $recur_prog fiddles $sdate, want to use that
-        # only for figuring next bill date, nothing else, so, reset $sdate again
-        # here
-        $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
-        $cust_pkg->last_bill($sdate);
-    
-        if ( $part_pkg->freq =~ /^\d+$/ ) {
-          $mon += $part_pkg->freq;
-          until ( $mon < 12 ) { $mon -= 12; $year++; }
-        } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) {
-          my $weeks = $1;
-          $mday += $weeks * 7;
-        } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) {
-          my $days = $1;
-          $mday += $days;
-        } elsif ( $part_pkg->freq =~ /^(\d+)h$/ ) {
-          my $hours = $1;
-          $hour += $hours;
-        } else {
-          $dbh->rollback if $oldAutoCommit;
-          return "unparsable frequency: ". $part_pkg->freq;
-        }
-        $cust_pkg->setfield('bill',
-          timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year));
-  
+      my $error =
+        $self->_make_lines( 'part_pkg'            => $part_pkg,
+                            'cust_pkg'            => $cust_pkg,
+                            'precommit_hooks'     => \@precommit_hooks,
+                            'line_items'          => \@cust_bill_pkg,
+                            'appended_line_items' => \@appended_cust_bill_pkg,
+                            'setup'               => \$total_setup,
+                            'recur'               => \$total_recur,
+                            'tax_matrix'          => \%taxlisthash,
+                            'time'                => $time,
+                            'options'             => \%options,
+                          );
+      if ($error) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
       }
 
-      warn "\$setup is undefined" unless defined($setup);
-      warn "\$recur is undefined" unless defined($recur);
-      warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill);
-  
-      ###
-      # If there's line items, create em cust_bill_pkg records
-      # If $cust_pkg has been modified, update it (if we're a real pkgpart)
-      ###
-  
-      if ( $lineitems ) {
-
-        if ( $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) {
-          # hmm.. and if just the options are modified in some weird price plan?
-  
-          warn "  package ". $cust_pkg->pkgnum. " modified; updating\n"
-            if $DEBUG >1;
-  
-          my $error = $cust_pkg->replace( $old_cust_pkg,
-                                          'options' => { $cust_pkg->options },
-                                        );
-          if ( $error ) { #just in case
-            $dbh->rollback if $oldAutoCommit;
-            return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error";
-          }
-        }
-  
-        $setup = sprintf( "%.2f", $setup );
-        $recur = sprintf( "%.2f", $recur );
-        if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
-          $dbh->rollback if $oldAutoCommit;
-          return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
-        }
-        if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
-          $dbh->rollback if $oldAutoCommit;
-          return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
-        }
-  
-        if ( $setup != 0 || $recur != 0 ) {
-  
-          unless ($postal_charge) {
-            $postal_charge = 1;  # try only once
-            my $postal_pkg = $self->charge_postal_fee();
-            if ( $postal_pkg && !ref( $postal_pkg ) ) {
-              $dbh->rollback if $oldAutoCommit;
-              return "can't charge postal invoice fee for customer ".
-                $self->custnum. ": $postal_pkg";
-            }
-            push @cust_pkgs, $postal_pkg if $postal_pkg;
-          }
-
-          warn "    charges (setup=$setup, recur=$recur); adding line items\n"
-            if $DEBUG > 1;
-          my $cust_bill_pkg = new FS::cust_bill_pkg {
-            'pkgnum'    => $cust_pkg->pkgnum,
-            'setup'     => $setup,
-            'unitsetup' => $unitsetup,
-            'recur'     => $recur,
-            'unitrecur' => $unitrecur,
-            'quantity'  => $cust_pkg->quantity,
-            'sdate'     => $sdate,
-            'edate'     => $cust_pkg->bill,
-            'details' => \@details,
-          };
-          $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart)
-            unless $part_pkg->pkgpart == $real_pkgpart;
-          push @cust_bill_pkg, $cust_bill_pkg;
-
-          $total_setup += $setup;
-          $total_recur += $recur;
-  
-          ###
-          # handle taxes
-          ###
-  
-          unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
-  
-            my @taxes = ();
-            my @taxoverrides = $part_pkg->part_pkg_taxoverride;
-            
-            my $prefix = 
-              ( $conf->exists('tax-ship_address') && length($self->ship_last) )
-              ? 'ship_'
-              : '';
-  
-            if ( $conf->exists('enable_taxproducts')
-                 && (scalar(@taxoverrides) || $part_pkg->taxproductnum )
-               )
-            { 
-  
-              my @taxclassnums = ();
-              my $geocode = $self->geocode('cch');
-  
-              if ( scalar( @taxoverrides ) ) {
-                @taxclassnums = map { $_->taxclassnum } @taxoverrides;
-              }elsif ( $part_pkg->taxproductnum ) {
-                @taxclassnums = map { $_->taxclassnum }
-                                $part_pkg->part_pkg_taxrate('cch', $geocode);
-              }
-  
-              my $extra_sql =
-                "AND (".
-                join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")";
-  
-              @taxes = qsearch({ 'table' => 'tax_rate',
-                                 'hashref' => { 'geocode' => $geocode, },
-                                 'extra_sql' => $extra_sql,
-                              })
-                if scalar(@taxclassnums);
-  
-
-            }else{
-  
-              my %taxhash = map { $_ => $self->get("$prefix$_") }
-                                qw( state county country );
-  
-              $taxhash{'taxclass'} = $part_pkg->taxclass;
-  
-              @taxes = qsearch( 'cust_main_county', \%taxhash );
-
-            unless ( @taxes ) {
-              $taxhash{'taxclass'} = '';
-              @taxes =  qsearch( 'cust_main_county', \%taxhash );
-            }
-
-            #one more try at a whole-country tax rate
-            unless ( @taxes ) {
-              $taxhash{$_} = '' foreach qw( state county );
-              @taxes =  qsearch( 'cust_main_county', \%taxhash );
-            }
-
-            } #if $conf->exists('enable_taxproducts') 
-  
-            # maybe eliminate this entirely, along with all the 0% records
-            unless ( @taxes ) {
-              $dbh->rollback if $oldAutoCommit;
-              my $error;
-              if ( $conf->exists('enable_taxproducts') ) { 
-                $error = 
-                  "fatal: can't find tax rate for zip/taxproduct/pkgpart ".
-                  join('/', ( map $self->get("$prefix$_"),
-                                  qw(zip)
-                            ),
-                            $part_pkg->taxproduct_description,
-                            $part_pkg->pkgpart ). "\n";
-              } else {
-                $error = 
-                  "fatal: can't find tax rate for state/county/country/taxclass ".
-                  join('/', ( map $self->get("$prefix$_"),
-                                  qw(state county country)
-                            ),
-                            $part_pkg->taxclass ). "\n";
-              }
-              return $error;
-            }
-    
-            foreach my $tax ( @taxes ) {
-              my $taxname = ref( $tax ). ' '. $tax->taxnum;
-              if ( exists( $taxlisthash{ $taxname } ) ) {
-                push @{ $taxlisthash{ $taxname  } }, $cust_bill_pkg;
-              }else{
-                $taxlisthash{ $taxname } = [ $tax, $cust_bill_pkg ];
-              }
-            }
-
-
-          } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
-
-        } #if $setup != 0 || $recur != 0
-      
-      } #if $cust_pkg->modified
-
     } #foreach my $part_pkg
 
   } #foreach my $cust_pkg
 
+  push @cust_bill_pkg, @appended_cust_bill_pkg;
+
   unless ( @cust_bill_pkg ) { #don't create an invoice w/o line items
     #but do commit any package date cycling that happened
     $dbh->commit or die $dbh->errstr if $oldAutoCommit;
     return '';
   }
 
+  my $postal_pkg = $self->charge_postal_fee();
+  if ( $postal_pkg && !ref( $postal_pkg ) ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "can't charge postal invoice fee for customer ".
+      $self->custnum. ": $postal_pkg";
+  }
+  if ( $postal_pkg ) {
+    foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) {
+      my $error =
+        $self->_make_lines( 'part_pkg'            => $part_pkg,
+                            'cust_pkg'            => $postal_pkg,
+                            'precommit_hooks'     => \@precommit_hooks,
+                            'line_items'          => \@cust_bill_pkg,
+                            'appended_line_items' => \@appended_cust_bill_pkg,
+                            'setup'               => \$total_setup,
+                            'recur'               => \$total_recur,
+                            'tax_matrix'          => \%taxlisthash,
+                            'time'                => $time,
+                            'options'             => \%options,
+                          );
+      if ($error) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+  }
+
   warn "having a look at the taxes we found...\n" if $DEBUG > 2;
   foreach my $tax ( keys %taxlisthash ) {
     my $tax_object = shift @{ $taxlisthash{$tax} };
@@ -2512,6 +2279,327 @@
   ''; #no error
 }
 
+
+sub _make_lines {
+  my ($self, %params) = @_;
+
+  my $part_pkg = $params{part_pkg} or die "no part_pkg specified";
+  my $cust_pkg = $params{cust_pkg} or die "no cust_pkg specified";
+  my $precommit_hooks = $params{precommit_hooks} or die "no package specified";
+  my $cust_bill_pkgs = $params{line_items} or die "no line buffer specified";
+  my $appended_cust_bill_pkg = $params{appended_line_items}
+    or die "no appended line buffer specified";
+  my $total_setup = $params{setup} or die "no setup accumulator specified";
+  my $total_recur = $params{recur} or die "no recur accumulator specified";
+  my $taxlisthash = $params{tax_matrix} or die "no tax accumulator specified";
+  my $time = $params{'time'} or die "no time specified";
+  my (%options) = %{$params{options}};  #hmmm  only for 'resetup'
+
+  my $dbh = dbh;
+  my $real_pkgpart = $cust_pkg->pkgpart;
+  my %hash = $cust_pkg->hash;
+  my $old_cust_pkg = new FS::cust_pkg \%hash;
+
+  $cust_pkg->pkgpart($part_pkg->pkgpart); 
+  $cust_pkg->set($_, $hash{$_}) foreach qw( setup last_bill bill );
+  
+  my @details = ();
+
+  my $lineitems = 0;
+
+  ###
+  # bill setup
+  ###
+
+  my $setup = 0;
+  my $unitsetup = 0;
+  if ( ! $cust_pkg->setup &&
+       (
+         ( $conf->exists('disable_setup_suspended_pkgs') &&
+          ! $cust_pkg->getfield('susp')
+        ) || ! $conf->exists('disable_setup_suspended_pkgs')
+       )
+    || $options{'resetup'}
+  ) {
+    
+    warn "    bill setup\n" if $DEBUG > 1;
+    $lineitems++;
+
+    $setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
+    return "$@ running calc_setup for $cust_pkg\n"
+      if $@;
+
+    $unitsetup = $cust_pkg->part_pkg->unit_setup || $setup; #XXX uuh
+
+    $cust_pkg->setfield('setup', $time)
+      unless $cust_pkg->setup;
+          #do need it, but it won't get written to the db
+          #|| $cust_pkg->pkgpart != $real_pkgpart;
+
+  }
+
+  ###
+  # bill recurring fee
+  ### 
+
+  #XXX unit stuff here too
+  my $recur = 0;
+  my $unitrecur = 0;
+  my $sdate;
+  if ( $part_pkg->getfield('freq') ne '0' &&
+       ! $cust_pkg->getfield('susp') &&
+       ( $cust_pkg->getfield('bill') || 0 ) <= $time
+  ) {
+
+    # XXX should this be a package event?  probably.  events are called
+    # at collection time at the moment, though...
+    $part_pkg->reset_usage($cust_pkg, 'debug'=>$DEBUG)
+      if $part_pkg->can('reset_usage');
+      #don't want to reset usage just cause we want a line item??
+      #&& $part_pkg->pkgpart == $real_pkgpart;
+
+    warn "    bill recur\n" if $DEBUG > 1;
+    $lineitems++;
+
+    # XXX shared with $recur_prog
+    $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+
+    #over two params!  lets at least switch to a hashref for the rest...
+    my %param = ( 'precommit_hooks' => $precommit_hooks, );
+
+    $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) };
+    return "$@ running calc_recur for $cust_pkg\n"
+      if ( $@ );
+
+  
+    #change this bit to use Date::Manip? CAREFUL with timezones (see
+    # mailing list archive)
+    my ($sec,$min,$hour,$mday,$mon,$year) =
+      (localtime($sdate) )[0,1,2,3,4,5];
+    
+    #pro-rating magic - if $recur_prog fiddles $sdate, want to use that
+    # only for figuring next bill date, nothing else, so, reset $sdate again
+    # here
+    $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+    $cust_pkg->last_bill($sdate);
+    
+    if ( $part_pkg->freq =~ /^\d+$/ ) {
+      $mon += $part_pkg->freq;
+      until ( $mon < 12 ) { $mon -= 12; $year++; }
+    } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) {
+      my $weeks = $1;
+      $mday += $weeks * 7;
+    } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) {
+      my $days = $1;
+      $mday += $days;
+    } elsif ( $part_pkg->freq =~ /^(\d+)h$/ ) {
+      my $hours = $1;
+      $hour += $hours;
+    } else {
+      return "unparsable frequency: ". $part_pkg->freq;
+    }
+    $cust_pkg->setfield('bill',
+      timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year));
+
+  }
+
+  warn "\$setup is undefined" unless defined($setup);
+  warn "\$recur is undefined" unless defined($recur);
+  warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill);
+  
+  ###
+  # If there's line items, create em cust_bill_pkg records
+  # If $cust_pkg has been modified, update it (if we're a real pkgpart)
+  ###
+
+  if ( $lineitems ) {
+
+    if ( $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) {
+      # hmm.. and if just the options are modified in some weird price plan?
+  
+      warn "  package ". $cust_pkg->pkgnum. " modified; updating\n"
+        if $DEBUG >1;
+  
+      my $error = $cust_pkg->replace( $old_cust_pkg,
+                                      'options' => { $cust_pkg->options },
+                                    );
+      return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error"
+        if $error; #just in case
+    }
+  
+    $setup = sprintf( "%.2f", $setup );
+    $recur = sprintf( "%.2f", $recur );
+    if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
+      return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
+    }
+    if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
+      return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
+    }
+
+    if ( $setup != 0 || $recur != 0 ) {
+
+      warn "    charges (setup=$setup, recur=$recur); adding line items\n"
+        if $DEBUG > 1;
+      my $cust_bill_pkg = new FS::cust_bill_pkg {
+        'pkgnum'    => $cust_pkg->pkgnum,
+        'setup'     => $setup,
+        'unitsetup' => $unitsetup,
+        'recur'     => $recur,
+        'unitrecur' => $unitrecur,
+        'quantity'  => $cust_pkg->quantity,
+        'sdate'     => $sdate,
+        'edate'     => $cust_pkg->bill,
+        'details'   => \@details,
+      };
+      $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart)
+        unless $part_pkg->pkgpart == $real_pkgpart;
+      push @$cust_bill_pkgs, $cust_bill_pkg;
+
+      $$total_setup += $setup;
+      $$total_recur += $recur;
+
+      ###
+      # handle taxes
+      ###
+
+      unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
+
+        $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg);
+
+      } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
+
+    } #if $setup != 0 || $recur != 0
+      
+  } #if $line_items
+
+  if ( $part_pkg->can('append_cust_bill_pkgs') ) {
+    my %param = ( 'precommit_hooks' => $precommit_hooks, );
+    my ($more_cust_bill_pkgs) =
+      eval { $part_pkg->append_cust_bill_pkgs( $cust_pkg, \$sdate, \%param ) };
+
+    return "$@ running append_cust_bill_pkgs for $cust_pkg\n"
+      if ( $@ );
+    return "$more_cust_bill_pkgs"
+      unless ( ref($more_cust_bill_pkgs) );
+
+    foreach my $cust_bill_pkg ( @{$more_cust_bill_pkgs} ) {
+
+      $cust_bill_pkg->pkgpart_override($part_pkg->pkgpart)
+        unless $part_pkg->pkgpart == $real_pkgpart;
+      push @$appended_cust_bill_pkg, $cust_bill_pkg;
+
+      $$total_setup += $cust_bill_pkg->setup;
+      $$total_recur += $cust_bill_pkg->recur;
+
+      ###
+      # handle taxes
+      ###
+
+      unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
+
+        $self->_handle_taxes($part_pkg, $taxlisthash, $cust_bill_pkg);
+
+      } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
+    }
+  }
+
+}
+
+sub _handle_taxes {
+  my $self = shift;
+  my $part_pkg = shift;
+  my $taxlisthash = shift;
+  my $cust_bill_pkg = shift;
+
+  my @taxes = ();
+  my @taxoverrides = $part_pkg->part_pkg_taxoverride;
+    
+  my $prefix = 
+    ( $conf->exists('tax-ship_address') && length($self->ship_last) )
+    ? 'ship_'
+    : '';
+
+  if ( $conf->exists('enable_taxproducts')
+       && (scalar(@taxoverrides) || $part_pkg->taxproductnum )
+     )
+  { 
+
+    my @taxclassnums = ();
+    my $geocode = $self->geocode('cch');
+
+    if ( scalar( @taxoverrides ) ) {
+      @taxclassnums = map { $_->taxclassnum } @taxoverrides;
+    }elsif ( $part_pkg->taxproductnum ) {
+      @taxclassnums = map { $_->taxclassnum }
+                      $part_pkg->part_pkg_taxrate('cch', $geocode);
+    }
+
+    my $extra_sql =
+      "AND (".
+      join(' OR ', map { "taxclassnum = $_" } @taxclassnums ). ")";
+
+    @taxes = qsearch({ 'table' => 'tax_rate',
+                       'hashref' => { 'geocode' => $geocode, },
+                       'extra_sql' => $extra_sql,
+                    })
+      if scalar(@taxclassnums);
+
+
+  }else{
+
+    my %taxhash = map { $_ => $self->get("$prefix$_") }
+                      qw( state county country );
+
+    $taxhash{'taxclass'} = $part_pkg->taxclass;
+
+    @taxes = qsearch( 'cust_main_county', \%taxhash );
+
+    unless ( @taxes ) {
+      $taxhash{'taxclass'} = '';
+      @taxes =  qsearch( 'cust_main_county', \%taxhash );
+    }
+
+    #one more try at a whole-country tax rate
+    unless ( @taxes ) {
+      $taxhash{$_} = '' foreach qw( state county );
+      @taxes =  qsearch( 'cust_main_county', \%taxhash );
+    }
+
+  } #if $conf->exists('enable_taxproducts') 
+
+  # maybe eliminate this entirely, along with all the 0% records
+  unless ( @taxes ) {
+    my $error;
+    if ( $conf->exists('enable_taxproducts') ) { 
+      $error = 
+        "fatal: can't find tax rate for zip/taxproduct/pkgpart ".
+        join('/', ( map $self->get("$prefix$_"),
+                        qw(zip)
+                  ),
+                  $part_pkg->taxproduct_description,
+                  $part_pkg->pkgpart ). "\n";
+    } else {
+      $error = 
+        "fatal: can't find tax rate for state/county/country/taxclass ".
+        join('/', ( map $self->get("$prefix$_"),
+                        qw(state county country)
+                  ),
+                  $part_pkg->taxclass ). "\n";
+    }
+    return $error;
+  }
+
+  foreach my $tax ( @taxes ) {
+    my $taxname = ref( $tax ). ' '. $tax->taxnum;
+    if ( exists( $taxlisthash->{ $taxname } ) ) {
+      push @{ $taxlisthash->{ $taxname  } }, $cust_bill_pkg;
+    }else{
+      $taxlisthash->{ $taxname } = [ $tax, $cust_bill_pkg ];
+    }
+  }
+
+}
+
 =item collect OPTIONS
 
 (Attempt to) collect money for this customer's outstanding invoices (see

Index: part_pkg.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/part_pkg.pm,v
retrieving revision 1.74
retrieving revision 1.75
diff -u -d -r1.74 -r1.75
--- part_pkg.pm	18 Jul 2008 22:28:09 -0000	1.74
+++ part_pkg.pm	2 Aug 2008 04:10:01 -0000	1.75
@@ -762,7 +762,7 @@
 
 =item bill_part_pkg_link
 
-Returns the associated part_pkg_link records (see L<FS::part_pkg_link).
+Returns the associated part_pkg_link records (see L<FS::part_pkg_link>).
 
 =cut
 
@@ -772,6 +772,8 @@
 
 =item svc_part_pkg_link
 
+Returns the associated part_pkg_link records (see L<FS::part_pkg_link>).
+
 =cut
 
 sub svc_part_pkg_link {

Index: cust_bill.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill.pm,v
retrieving revision 1.218
retrieving revision 1.219
diff -u -d -r1.218 -r1.219
--- cust_bill.pm	29 Jul 2008 20:00:33 -0000	1.218
+++ cust_bill.pm	2 Aug 2008 04:10:00 -0000	1.219
@@ -2687,6 +2687,7 @@
                     $cust_pkg->h_labels_short($self->_date);
                                               #$cust_bill_pkg->edate,
                                               #$cust_bill_pkg->sdate),
+        @d = () if $cust_bill_pkg->itemdesc;
         push @d, $cust_bill_pkg->details(%details_opt);
 
         push @b, {

Index: Schema.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Schema.pm,v
retrieving revision 1.93
retrieving revision 1.94
diff -u -d -r1.93 -r1.94
--- Schema.pm	23 Jul 2008 14:36:13 -0000	1.93
+++ Schema.pm	2 Aug 2008 04:09:59 -0000	1.94
@@ -508,14 +508,15 @@
     'cust_bill_pkg_detail' => {
       'columns' => [
         'detailnum', 'serial', '', '', '', '', 
-        'pkgnum',  'int', '', '', '', '', 
-        'invnum',  'int', '', '', '', '', 
+        'billpkgnum', 'int', 'NULL', '', '', '',        # should not be nullable
+        'pkgnum',  'int', 'NULL', '', '', '',           # deprecated
+        'invnum',  'int', 'NULL', '', '', '',           # deprecated
         'format',  'char', 'NULL', 1, '', '',
         'detail',  'varchar', '', $char_d, '', '', 
       ],
       'primary_key' => 'detailnum',
       'unique' => [],
-      'index' => [ [ 'pkgnum', 'invnum' ] ],
+      'index' => [ [ 'billpkgnum' ], [ 'pkgnum', 'invnum' ] ],
     },
 
     'cust_credit' => {

Index: cust_bill_pkg.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/cust_bill_pkg.pm,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -d -r1.18 -r1.19
--- cust_bill_pkg.pm	4 Jun 2008 17:59:02 -0000	1.18
+++ cust_bill_pkg.pm	2 Aug 2008 04:10:01 -0000	1.19
@@ -55,7 +55,7 @@
 
 =item edate - ending date of recurring fee
 
-=item itemdesc - Line item description (currentlty used only when pkgnum is 0 or -1)
+=item itemdesc - Line item description (overrides normal package description)
 
 =item quantity - If not set, defaults to 1
 
@@ -116,10 +116,9 @@
 
   foreach my $detail ( @{$self->get('details')} ) {
     my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
-      'pkgnum' => $self->pkgnum,
-      'invnum' => $self->invnum,
-      'format' => (ref($detail) ? $detail->[0] : '' ),
-      'detail' => (ref($detail) ? $detail->[1] : $detail ),
+      'billpkgnum' => $self->billpkgnum,
+      'format'     => (ref($detail) ? $detail->[0] : '' ),
+      'detail'     => (ref($detail) ? $detail->[1] : $detail ),
     };
     $error = $cust_bill_pkg_detail->insert;
     if ( $error ) {
@@ -292,9 +291,7 @@
         )
       }
     qsearch ({ 'table'    => 'cust_bill_pkg_detail',
-               'hashref'  => { 'pkgnum' => $self->pkgnum,
-                               'invnum' => $self->invnum,
-                             },
+               'hashref'  => { 'billpkgnum' => $self->billpkgnum },
                'order_by' => 'ORDER BY detailnum',
             });
     #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum });
@@ -313,7 +310,7 @@
   my $self = shift;
 
   if ( $self->pkgnum > 0 ) {
-    $self->part_pkg->pkg;
+    $self->itemdesc || $self->part_pkg->pkg;
   } else {
     $self->itemdesc || 'Tax';
   }

Index: Upgrade.pm
===================================================================
RCS file: /home/cvs/cvsroot/freeside/FS/FS/Upgrade.pm,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -d -r1.17 -r1.18
--- Upgrade.pm	15 Jul 2008 23:25:08 -0000	1.17
+++ Upgrade.pm	2 Aug 2008 04:10:00 -0000	1.18
@@ -103,6 +103,9 @@
     #remove bad pending records
     'cust_pay_pending' => [],
 
+    #replace invnum and pkgnum with billpkgnum
+    'cust_bill_pkg_detail' => [],
+
   ;
 
   \%hash;



More information about the freeside-commits mailing list