Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / cust_pkg.pm
index 27c25da..627a7fc 100644 (file)
@@ -10,7 +10,7 @@ use List::Util qw(max);
 use Tie::IxHash;
 use Time::Local qw( timelocal timelocal_nocheck );
 use MIME::Entity;
-use FS::UID qw( getotaker dbh );
+use FS::UID qw( getotaker dbh driver_name );
 use FS::Misc qw( send_email );
 use FS::Record qw( qsearch qsearchs fields );
 use FS::CurrentUser;
@@ -972,7 +972,16 @@ sub uncancel {
     my $svc_error = $svc_x->insert;
     if ( $svc_error && $options{svc_fatal} ) {
       $dbh->rollback if $oldAutoCommit;
-      return $error;
+      return $svc_error;
+    } else {
+      my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_x->svcnum });
+      if ( $cust_svc ) {
+        my $cs_error = $cust_svc->delete;
+        if ( $cs_error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return $cs_error;
+        }
+      }
     }
     push @svc_errors, $svc_error if $svc_error;
   }
@@ -1180,8 +1189,13 @@ sub suspend {
     $hash{'resume'} = $resume_date;
   }
 
+  $options{options} ||= {};
+
   my $new = new FS::cust_pkg ( \%hash );
-  $error = $new->replace( $self, options => { $self->options } );
+  $error = $new->replace( $self, options => { $self->options,
+                                              %{ $options{options} },
+                                            }
+                        );
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
     return $error;
@@ -2061,7 +2075,7 @@ sub cust_svc {
   }
   if ( $opt{'svcdb'} ) {
     $search{addl_from} = ' LEFT JOIN part_svc USING ( svcpart ) ';
-    $search{hashref}->{svcdb} = $opt{'svcdb'};
+    $search{extra_sql} = ' AND svcdb = '. dbh->quote( $opt{'svcdb'} );
   }
 
   cluck "cust_pkg->cust_svc called" if $DEBUG > 2;
@@ -2195,11 +2209,14 @@ field, I<num_avail>, which specifies the number of available services.
 
 sub available_part_svc {
   my $self = shift;
+
+  my $pkg_quantity = $self->quantity || 1;
+
   grep { $_->num_avail > 0 }
     map {
           my $part_svc = $_->part_svc;
           $part_svc->{'Hash'}{'num_avail'} = #evil encapsulation-breaking
-            $_->quantity - $self->num_cust_svc($_->svcpart);
+            $pkg_quantity * $_->quantity - $self->num_cust_svc($_->svcpart);
 
          # more evil encapsulation breakage
          if($part_svc->{'Hash'}{'num_avail'} > 0) {
@@ -2241,6 +2258,8 @@ sub part_svc {
   my $self = shift;
   my %opt = @_;
 
+  my $pkg_quantity = $self->quantity || 1;
+
   #XXX some sort of sort order besides numeric by svcpart...
   my @part_svc = sort { $a->svcpart <=> $b->svcpart } map {
     my $pkg_svc = $_;
@@ -2248,7 +2267,7 @@ sub part_svc {
     my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart);
     $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #more evil
     $part_svc->{'Hash'}{'num_avail'}    =
-      max( 0, $pkg_svc->quantity - $num_cust_svc );
+      max( 0, $pkg_quantity * $pkg_svc->quantity - $num_cust_svc );
     $part_svc->{'Hash'}{'cust_pkg_svc'} =
         $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : []
       unless exists($opt{summarize_size}) && $opt{summarize_size} > 0
@@ -2607,6 +2626,39 @@ Returns the label of the location object (see L<FS::cust_location>).
 
 #end of subs in location_Mixin.pm now... unfortunately the POD doesn't mixin
 
+=item tax_locationnum
+
+Returns the foreign key to a L<FS::cust_location> object for calculating  
+tax on this package, as determined by the C<tax-pkg_address> and 
+C<tax-ship_address> configuration flags.
+
+=cut
+
+sub tax_locationnum {
+  my $self = shift;
+  my $conf = FS::Conf->new;
+  if ( $conf->exists('tax-pkg_address') ) {
+    return $self->locationnum;
+  }
+  elsif ( $conf->exists('tax-ship_address') ) {
+    return $self->cust_main->ship_locationnum;
+  }
+  else {
+    return $self->cust_main->bill_locationnum;
+  }
+}
+
+=item tax_location
+
+Returns the L<FS::cust_location> object for tax_locationnum.
+
+=cut
+
+sub tax_location {
+  my $self = shift;
+  FS::cust_location->by_key( $self->tax_locationnum )
+}
+
 =item seconds_since TIMESTAMP
 
 Returns the number of seconds all accounts (see L<FS::svc_acct>) in this
@@ -3593,6 +3645,25 @@ sub fcc_477_count {
 
 }
 
+=item tax_locationnum_sql
+
+Returns an SQL expression for the tax location for a package, based
+on the settings of 'tax-pkg_address' and 'tax-ship_address'.
+
+=cut
+
+sub tax_locationnum_sql {
+  my $conf = FS::Conf->new;
+  if ( $conf->exists('tax-pkg_address') ) {
+    'cust_pkg.locationnum';
+  }
+  elsif ( $conf->exists('tax-ship_address') ) {
+    'cust_main.ship_locationnum';
+  }
+  else {
+    'cust_main.bill_locationnum';
+  }
+}
 
 =item location_sql
 
@@ -3680,10 +3751,12 @@ sub _location_sql_where {
   my $or_empty_county   = " OR ( ? = '' AND $table.${prefix}county   IS NULL )";
   my $or_empty_state    = " OR ( ? = '' AND $table.${prefix}state    IS NULL )";
 
+  my $text = (driver_name =~ /^mysql/i) ? 'char' : 'text';
+
 #        ( $table.${prefix}city    = ? $or_empty_city   $ornull )
   "
-        ( $table.district = ? OR ? = '' OR CAST(? AS text) IS NULL )
-    AND ( $table.${prefix}city     = ? OR ? = '' OR CAST(? AS text) IS NULL )
+        ( $table.district = ? OR ? = '' OR CAST(? AS $text) IS NULL )
+    AND ( $table.${prefix}city     = ? OR ? = '' OR CAST(? AS $text) IS NULL )
     AND ( $table.${prefix}county   = ? $or_empty_county $ornull )
     AND ( $table.${prefix}state    = ? $or_empty_state  $ornull )
     AND   $table.${prefix}country  = ?