set fixed values from an explicitly specified svcpart on replace too
[freeside.git] / FS / FS / cust_pkg.pm
index d554d8b..d2a48e9 100644 (file)
@@ -452,7 +452,10 @@ sub unsuspend {
 
   unless ( ! $self->getfield('susp') ) {
     my %hash = $self->hash;
+    my $inactive = time - $hash{'susp'};
     $hash{'susp'} = '';
+    $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive
+      if $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} );
     my $new = new FS::cust_pkg ( \%hash );
     $error = $new->replace($self);
     if ( $error ) {
@@ -499,20 +502,74 @@ sub part_pkg {
     : qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
 }
 
-=item cust_svc
+=item cust_svc [ SVCPART ]
 
 Returns the services for this package, as FS::cust_svc objects (see
-L<FS::cust_svc>)
+L<FS::cust_svc>).  If a svcpart is specified, return only the matching
+services.
 
 =cut
 
 sub cust_svc {
   my $self = shift;
-  if ( $self->{'_svcnum'} ) {
-    values %{ $self->{'_svcnum'}->cache };
-  } else {
-    qsearch ( 'cust_svc', { 'pkgnum' => $self->pkgnum } );
+
+  if ( @_ ) {
+    return qsearch( 'cust_svc', { 'pkgnum'  => $self->pkgnum,
+                                  'svcpart' => shift,          } );
   }
+
+  #if ( $self->{'_svcnum'} ) {
+  #  values %{ $self->{'_svcnum'}->cache };
+  #} else {
+    map  { $_->[0] }
+    sort { $b->[1] cmp $a->[1]  or  $a->[2] <=> $b->[2] } 
+    map {
+          my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $self->pkgpart,
+                                               'svcpart' => $_->svcpart     } );
+          [ $_,
+            $pkg_svc ? $pkg_svc->primary_svc : '',
+            $pkg_svc ? $pkg_svc->quantity : 0,
+          ];
+        }
+    qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } );
+  #}
+
+}
+
+=item num_cust_svc [ SVCPART ]
+
+Returns the number of provisioned services for this package.  If a svcpart is
+specified, counts only the matching services.
+
+=cut
+
+sub num_cust_svc {
+  my $self = shift;
+  my $sql = 'SELECT COUNT(*) FROM cust_svc WHERE pkgnum = ?';
+  $sql .= ' AND svcpart = ?' if @_;
+  my $sth = dbh->prepare($sql) or die dbh->errstr;
+  $sth->execute($self->pkgnum, @_) or die $sth->errstr;
+  $sth->fetchrow_arrayref->[0];
+}
+
+=item available_part_svc 
+
+Returns a list FS::part_svc objects representing services included in this
+package but not yet provisioned.  Each FS::part_svc object also has an extra
+field, I<num_avail>, which specifies the number of available services.
+
+=cut
+
+sub available_part_svc {
+  my $self = shift;
+  grep { $_->num_avail > 0 }
+    map {
+          my $part_svc = $_->part_svc;
+          $part_svc->{'Hash'}{'num_avail'} = #evil encapsulation-breaking
+            $_->quantity - $self->num_cust_svc($_->svcpart);
+          $part_svc;
+        }
+      $self->part_pkg->pkg_svc;
 }
 
 =item labels
@@ -654,7 +711,6 @@ sub transfer {
   my $remaining = 0;
   my $dest;
   my %target;
-  my $pkg_svc;
 
   if (ref ($dest_pkgnum) eq 'FS::cust_pkg') {
     $dest = $dest_pkgnum;
@@ -665,13 +721,11 @@ sub transfer {
 
   return ('Package does not exist: '.$dest_pkgnum) unless $dest;
 
-  foreach $pkg_svc (qsearch('pkg_svc', { pkgpart => $dest->pkgpart })) {
+  foreach my $pkg_svc ( $dest->part_pkg->pkg_svc ) {
     $target{$pkg_svc->svcpart} = $pkg_svc->quantity;
   }
 
-  my $cust_svc;
-
-  foreach $cust_svc ($dest->cust_svc) {
+  foreach my $cust_svc ($dest->cust_svc) {
     $target{$cust_svc->svcpart}--;
   }
 
@@ -682,9 +736,20 @@ sub transfer {
       next if exists $svcpart2svcparts{$svcpart};
       my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
       $svcpart2svcparts{$svcpart} = [
+        map  { $_->[0] }
+        sort { $b->[1] cmp $a->[1]  or  $a->[2] <=> $b->[2] } 
+        map {
+              my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $dest->pkgpart,
+                                                   'svcpart' => $_          } );
+              [ $_,
+                $pkg_svc ? $pkg_svc->primary_svc : '',
+                $pkg_svc ? $pkg_svc->quantity : 0,
+              ];
+            }
+
         grep { $_ != $svcpart }
-          map { $_->svcpart }
-            qsearch('part_svc', { 'svcdb' => $part_svc->svcdb } )
+        map  { $_->svcpart }
+        qsearch('part_svc', { 'svcdb' => $part_svc->svcdb } )
       ];
       warn "alternates for svcpart $svcpart: ".
            join(', ', @{$svcpart2svcparts{$svcpart}}). "\n"
@@ -692,7 +757,7 @@ sub transfer {
     }
   }
 
-  foreach $cust_svc ($self->cust_svc) {
+  foreach my $cust_svc ($self->cust_svc) {
     if($target{$cust_svc->svcpart} > 0) {
       $target{$cust_svc->svcpart}--;
       my $new = new FS::cust_svc {
@@ -716,7 +781,7 @@ sub transfer {
                            } @{$svcpart2svcparts{$cust_svc->svcpart}};
       if ( @alternate ) {
         warn "alternate(s) found\n" if $DEBUG;
-        my $change_svcpart = $alternate[0]; #arbitrary.
+        my $change_svcpart = $alternate[0];
         $target{$change_svcpart}--;
         my $new = new FS::cust_svc {
           svcnum  => $cust_svc->svcnum,