This commit was generated by cvs2svn to compensate for changes in r4888,
[freeside.git] / FS / FS / part_svc.pm
index e7f205d..2587347 100644 (file)
@@ -1,8 +1,9 @@
 package FS::part_svc;
 
 use strict;
 package FS::part_svc;
 
 use strict;
-use vars qw( @ISA );
+use vars qw( @ISA $DEBUG );
 use FS::Record qw( qsearch qsearchs fields dbh );
 use FS::Record qw( qsearch qsearchs fields dbh );
+use FS::Schema qw( dbdef );
 use FS::part_svc_column;
 use FS::part_export;
 use FS::export_svc;
 use FS::part_svc_column;
 use FS::part_export;
 use FS::export_svc;
@@ -10,6 +11,8 @@ use FS::cust_svc;
 
 @ISA = qw(FS::Record);
 
 
 @ISA = qw(FS::Record);
 
+$DEBUG = 1;
+
 =head1 NAME
 
 FS::part_svc - Object methods for part_svc objects
 =head1 NAME
 
 FS::part_svc - Object methods for part_svc objects
@@ -64,7 +67,7 @@ database, see L<"insert">.
 
 sub table { 'part_svc'; }
 
 
 sub table { 'part_svc'; }
 
-=item insert [ EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF ] ] 
+=item insert [ EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF [ , JOB ] ] ] 
 
 Adds this service definition to the database.  If there is an error, returns
 the error, otherwise returns false.
 
 Adds this service definition to the database.  If there is an error, returns
 the error, otherwise returns false.
@@ -76,7 +79,7 @@ the part_svc_column table appropriately (see L<FS::part_svc_column>).
 
 =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
 
 
 =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>.
 
-=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null, `D' for default, or `F' for fixed.  For virtual fields, can also be 'X' for excluded.
+=item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null or empty (no default), `D' for default, `F' for fixed (unchangeable), `M' for manual selection from inventory, or `A' for automatic selection from inventory.  For virtual fields, can also be 'X' for excluded.
 
 =back
 
 
 =back
 
@@ -87,6 +90,8 @@ EXTRA_FIELDS_ARRAYREF also.
 If EXPORTNUMS_HASHREF is specified (keys are exportnums and values are
 boolean), the appopriate export_svc records will be inserted.
 
 If EXPORTNUMS_HASHREF is specified (keys are exportnums and values are
 boolean), the appopriate export_svc records will be inserted.
 
+TODOC: JOB
+
 =cut
 
 sub insert {
 =cut
 
 sub insert {
@@ -98,6 +103,8 @@ sub insert {
     my $exportnums = shift;
     @exportnums = grep $exportnums->{$_}, keys %$exportnums;
   }
     my $exportnums = shift;
     @exportnums = grep $exportnums->{$_}, keys %$exportnums;
   }
+  my $job = '';
+  $job = shift if @_;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -135,7 +142,8 @@ sub insert {
     } );
 
     my $flag = $self->getfield($svcdb.'__'.$field.'_flag');
     } );
 
     my $flag = $self->getfield($svcdb.'__'.$field.'_flag');
-    if ( uc($flag) =~ /^([DFX])$/ ) {
+    #if ( uc($flag) =~ /^([DFMAX])$/ ) {
+    if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it
       $part_svc_column->setfield('columnflag', $1);
       $part_svc_column->setfield('columnvalue',
         $self->getfield($svcdb.'__'.$field)
       $part_svc_column->setfield('columnflag', $1);
       $part_svc_column->setfield('columnvalue',
         $self->getfield($svcdb.'__'.$field)
@@ -156,13 +164,14 @@ sub insert {
   }
 
   # add export_svc records
   }
 
   # add export_svc records
-
+  my $slice = 100/scalar(@exportnums) if @exportnums;
+  my $done = 0;
   foreach my $exportnum ( @exportnums ) {
     my $export_svc = new FS::export_svc ( {
       'exportnum' => $exportnum,
       'svcpart'   => $self->svcpart,
     } );
   foreach my $exportnum ( @exportnums ) {
     my $export_svc = new FS::export_svc ( {
       'exportnum' => $exportnum,
       'svcpart'   => $self->svcpart,
     } );
-    $error = $export_svc->insert;
+    $error = $export_svc->insert($job, $slice*$done++, $slice);
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
       return $error;
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;
       return $error;
@@ -185,7 +194,7 @@ sub delete {
 # check & make sure the svcpart isn't in cust_svc or pkg_svc (in any packages)?
 }
 
 # check & make sure the svcpart isn't in cust_svc or pkg_svc (in any packages)?
 }
 
-=item replace OLD_RECORD [ '1.3-COMPAT' [ , EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF ] ] ]
+=item replace OLD_RECORD [ '1.3-COMPAT' [ , EXTRA_FIELDS_ARRAYREF [ , EXPORTNUMS_HASHREF [ , JOB ] ] ] ]
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
@@ -194,10 +203,26 @@ TODOC: 1.3-COMPAT
 
 TODOC: EXTRA_FIELDS_ARRAYREF (same as insert method)
 
 
 TODOC: EXTRA_FIELDS_ARRAYREF (same as insert method)
 
+TODOC: JOB
+
 =cut
 
 sub replace {
   my ( $new, $old ) = ( shift, shift );
 =cut
 
 sub replace {
   my ( $new, $old ) = ( shift, shift );
+  my $compat = '';
+  my @fields = ();
+  my $exportnums;
+  my $job = '';
+  if ( @_ && $_[0] eq '1.3-COMPAT' ) {
+    shift;
+    $compat = '1.3';
+    @fields = @{shift(@_)} if @_;
+    $exportnums = @_ ? shift : '';
+    $job = shift if @_;
+  } else {
+    return 'non-1.3-COMPAT interface not yet written';
+    #not yet implemented
+  }
 
   return "Can't change svcdb for an existing service definition!"
     unless $old->svcdb eq $new->svcdb;
 
   return "Can't change svcdb for an existing service definition!"
     unless $old->svcdb eq $new->svcdb;
@@ -219,11 +244,7 @@ sub replace {
     return $error;
   }
 
     return $error;
   }
 
-  if ( @_ && $_[0] eq '1.3-COMPAT' ) {
-    shift;
-    my @fields = ();
-    @fields = @{shift(@_)} if @_;
-    my $exportnums = @_ ? shift : '';
+  if ( $compat eq '1.3' ) {
 
    # maintain part_svc_column records
 
 
    # maintain part_svc_column records
 
@@ -240,7 +261,8 @@ sub replace {
       } );
 
       my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
       } );
 
       my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
-      if ( uc($flag) =~ /^([DFX])$/ ) {
+      #if ( uc($flag) =~ /^([DFMAX])$/ ) {
+      if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it
         $part_svc_column->setfield('columnflag', $1);
         $part_svc_column->setfield('columnvalue',
           $new->getfield($svcdb.'__'.$field)
         $part_svc_column->setfield('columnflag', $1);
         $part_svc_column->setfield('columnvalue',
           $new->getfield($svcdb.'__'.$field)
@@ -264,6 +286,7 @@ sub replace {
     if ( $exportnums ) {
 
       #false laziness w/ edit/process/agent_type.cgi
     if ( $exportnums ) {
 
       #false laziness w/ edit/process/agent_type.cgi
+      my @new_export_svc = ();
       foreach my $part_export ( qsearch('part_export', {}) ) {
         my $exportnum = $part_export->exportnum;
         my $hashref = {
       foreach my $part_export ( qsearch('part_export', {}) ) {
         my $exportnum = $part_export->exportnum;
         my $hashref = {
@@ -279,14 +302,26 @@ sub replace {
             return $error;
           }
         } elsif ( ! $export_svc && $exportnums->{$exportnum} ) {
             return $error;
           }
         } elsif ( ! $export_svc && $exportnums->{$exportnum} ) {
-          $export_svc = new FS::export_svc ( $hashref );
-          $error = $export_svc->insert;
+          push @new_export_svc, new FS::export_svc ( $hashref );
+        }
+
+      }
+
+      my $slice = 100/scalar(@new_export_svc) if @new_export_svc;
+      my $done = 0;
+      foreach my $export_svc (@new_export_svc) {
+        $error = $export_svc->insert($job, $slice*$done++, $slice);
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return $error;
+        }
+        if ( $job ) {
+          $error = $job->update_statustext( int( $slice * $done ) );
           if ( $error ) {
             $dbh->rollback if $oldAutoCommit;
             return $error;
           }
         }
           if ( $error ) {
             $dbh->rollback if $oldAutoCommit;
             return $error;
           }
         }
-        
       }
 
     }
       }
 
     }
@@ -312,7 +347,6 @@ and replace methods.
 
 sub check {
   my $self = shift;
 
 sub check {
   my $self = shift;
-  my $recref = $self->hashref;
 
   my $error;
   $error=
 
   my $error;
   $error=
@@ -323,8 +357,9 @@ sub check {
   ;
   return $error if $error;
 
   ;
   return $error if $error;
 
-  my @fields = eval { fields( $recref->{svcdb} ) }; #might die
-  return "Unknown svcdb!" unless @fields;
+  my @fields = eval { fields( $self->svcdb ) }; #might die
+  return "Unknown svcdb: ". $self->svcdb. " (Error: $@)"
+    unless @fields;
 
   $self->SUPER::check;
 }
 
   $self->SUPER::check;
 }
@@ -360,8 +395,8 @@ sub all_part_svc_column {
 
 =item part_export [ EXPORTTYPE ]
 
 
 =item part_export [ EXPORTTYPE ]
 
-Returns all exports (see L<FS::part_export>) for this service, or, if an
-export type is specified, only returns exports of the given type.
+Returns a list of all exports (see L<FS::part_export>) for this service, or,
+if an export type is specified, only returns exports of the given type.
 
 =cut
 
 
 =cut
 
@@ -373,15 +408,85 @@ sub part_export {
     qsearch('export_svc', { 'svcpart' => $self->svcpart } );
 }
 
     qsearch('export_svc', { 'svcpart' => $self->svcpart } );
 }
 
-=item cust_svc
+=item part_export_usage
+
+Returns a list of any exports (see L<FS::part_export>) for this service that
+are capable of reporting usage information.
+
+=cut
+
+sub part_export_usage {
+  my $self = shift;
+  grep $_->can('usage_sessions'), $self->part_export;
+}
+
+=item cust_svc [ PKGPART ] 
+
+Returns a list of associated customer services (FS::cust_svc records).
 
 
-Returns a list of associated FS::cust_svc records.
+If a PKGPART is specified, returns the customer services which are contained
+within packages of that type (see L<FS::part_pkg>).  If PKGPARTis specified as
+B<0>, returns unlinked customer services.
 
 =cut
 
 sub cust_svc {
   my $self = shift;
 
 =cut
 
 sub cust_svc {
   my $self = shift;
-  qsearch('cust_svc', { 'svcpart' => $self->svcpart } );
+
+  my $hashref = { 'svcpart' => $self->svcpart };
+
+  my( $addl_from, $extra_sql ) = ( '', '' );
+  if ( @_ ) {
+    my $pkgpart = shift;
+    if ( $pkgpart =~ /^(\d+)$/ ) {
+      $addl_from = 'LEFT JOIN cust_pkg USING ( pkgnum )';
+      $extra_sql = "AND pkgpart = $1";
+    } elsif ( $pkgpart eq '0' ) {
+      $hashref->{'pkgnum'} = '';
+    }
+  }
+
+  qsearch({
+    'table'     => 'cust_svc',
+    'addl_from' => $addl_from,
+    'hashref'   => $hashref,
+    'extra_sql' => $extra_sql,
+  });
+}
+
+=item num_cust_svc [ PKGPART ] 
+
+Returns the number of associated customer services (FS::cust_svc records).
+
+If a PKGPART is specified, returns the number of customer services which are
+contained within packages of that type (see L<FS::part_pkg>).  If PKGPART
+is specified as B<0>, returns the number of unlinked customer services.
+
+=cut
+
+sub num_cust_svc {
+  my $self = shift;
+
+  my @param = ( $self->svcpart );
+
+  my( $join, $and ) = ( '', '' );
+  if ( @_ ) {
+    my $pkgpart = shift;
+    if ( $pkgpart ) {
+      $join = 'LEFT JOIN cust_pkg USING ( pkgnum )';
+      $and = 'AND pkgpart = ?';
+      push @param, $pkgpart;
+    } elsif ( $pkgpart eq '0' ) {
+      $and = 'AND pkgnum IS NULL';
+    }
+  }
+
+  my $sth = dbh->prepare(
+    "SELECT COUNT(*) FROM cust_svc $join WHERE svcpart = ? $and"
+  ) or die dbh->errstr;
+  $sth->execute(@param)
+    or die $sth->errstr;
+  $sth->fetchrow_arrayref->[0];
 }
 
 =item svc_x
 }
 
 =item svc_x
@@ -395,8 +500,147 @@ sub svc_x {
   map { $_->svc_x } $self->cust_svc;
 }
 
   map { $_->svc_x } $self->cust_svc;
 }
 
+
 =back
 
 =back
 
+=head1 SUBROUTINES
+
+=over 4
+
+=item process
+
+Job-queue processor for web interface adds/edits
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process {
+  my $job = shift;
+
+  my $param = thaw(decode_base64(shift));
+  warn Dumper($param) if $DEBUG;
+
+  my $old = qsearchs('part_svc', { 'svcpart' => $param->{'svcpart'} }) 
+    if $param->{'svcpart'};
+
+  $param->{'svc_acct__usergroup'} =
+    ref($param->{'svc_acct__usergroup'})
+      ? join(',', @{$param->{'svc_acct__usergroup'}} )
+      : $param->{'svc_acct__usergroup'};
+  
+  my $new = new FS::part_svc ( {
+    map {
+      $_ => $param->{$_};
+  #  } qw(svcpart svc svcdb)
+    } ( fields('part_svc'),
+        map { my $svcdb = $_;
+              my @fields = fields($svcdb);
+              push @fields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
+
+              map {
+                    if ( $param->{ $svcdb.'__'.$_.'_flag' } =~ /^[MA]$/ ) {
+                      $param->{ $svcdb.'__'.$_ } =
+                        delete( $param->{ $svcdb.'__'.$_.'_classnum' } );
+                    }
+                    ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' );
+                  }
+                  @fields;
+
+            } grep defined( dbdef->table($_) ),
+                   qw( svc_acct svc_domain svc_forward svc_www svc_broadband
+                       svc_phone svc_external
+                     )
+      )
+  } );
+  
+  my %exportnums =
+    map { $_->exportnum => ( $param->{'exportnum'.$_->exportnum} || '') }
+        qsearch('part_export', {} );
+
+  my $error;
+  if ( $param->{'svcpart'} ) {
+    $error = $new->replace( $old,
+                            '1.3-COMPAT',
+                            [ 'usergroup' ],
+                            \%exportnums,
+                            $job
+                          );
+  } else {
+    $error = $new->insert( [ 'usergroup' ],
+                           \%exportnums,
+                           $job,
+                         );
+    $param->{'svcpart'} = $new->getfield('svcpart');
+  }
+
+  die "$error\n" if $error;
+}
+
+=item process_bulk_cust_svc
+
+Job-queue processor for web interface bulk customer service changes
+
+=cut
+
+use Storable qw(thaw);
+use Data::Dumper;
+use MIME::Base64;
+sub process_bulk_cust_svc {
+  my $job = shift;
+
+  my $param = thaw(decode_base64(shift));
+  warn Dumper($param) if $DEBUG;
+
+  my $old_part_svc =
+    qsearchs('part_svc', { 'svcpart' => $param->{'old_svcpart'} } );
+
+  die "Must select a new service definition\n" unless $param->{'new_svcpart'};
+
+  #the rest should be abstracted out to to its own subroutine?
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  local( $FS::cust_svc::ignore_quantity ) = 1;
+
+  my $total = $old_part_svc->num_cust_svc( $param->{'pkgpart'} );
+
+  my $n = 0;
+  foreach my $old_cust_svc ( $old_part_svc->cust_svc( $param->{'pkgpart'} ) ) {
+
+    my $new_cust_svc = new FS::cust_svc { $old_cust_svc->hash };
+
+    $new_cust_svc->svcpart( $param->{'new_svcpart'} );
+    my $error = $new_cust_svc->replace($old_cust_svc);
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      die "$error\n" if $error;
+    }
+
+    $error = $job->update_statustext( int( 100 * ++$n / $total ) );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      die $error if $error;
+    }
+
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
 =head1 BUGS
 
 Delete is unimplemented.
 =head1 BUGS
 
 Delete is unimplemented.
@@ -404,7 +648,7 @@ Delete is unimplemented.
 The list of svc_* tables is hardcoded.  When svc_acct_pop is renamed, this
 should be fixed.
 
 The list of svc_* tables is hardcoded.  When svc_acct_pop is renamed, this
 should be fixed.
 
-all_part_svc_column method should be documented
+all_part_svc_column methods should be documented
 
 =head1 SEE ALSO
 
 
 =head1 SEE ALSO