doc
[freeside.git] / FS / FS / access_user.pm
index 8cc8b64..44d3bee 100644 (file)
@@ -1,28 +1,19 @@
 package FS::access_user;
 package FS::access_user;
+use base qw( FS::m2m_Common FS::option_Common ); 
 
 use strict;
 
 use strict;
-use vars qw( @ISA $DEBUG $me $conf $htpasswd_file );
+use vars qw( $DEBUG $me );
 use FS::UID;
 use FS::UID;
+use FS::Auth;
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs dbh );
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs dbh );
-use FS::m2m_Common;
-use FS::option_Common;
-use FS::access_user_pref;
-use FS::access_usergroup;
 use FS::agent;
 use FS::agent;
-
-@ISA = qw( FS::m2m_Common FS::option_Common FS::Record );
-#@ISA = qw( FS::m2m_Common FS::option_Common );
+use FS::cust_main;
+use FS::sales;
 
 $DEBUG = 0;
 $me = '[FS::access_user]';
 
 
 $DEBUG = 0;
 $me = '[FS::access_user]';
 
-#kludge htpasswd for now (i hope this bootstraps okay)
-FS::UID->install_callback( sub {
-  $conf = new FS::Conf;
-  $htpasswd_file = $conf->base_dir. '/htpasswd';
-} );
-
 =head1 NAME
 
 FS::access_user - Object methods for access_user records
 =head1 NAME
 
 FS::access_user - Object methods for access_user records
@@ -49,17 +40,37 @@ inherits from FS::Record.  The following fields are currently supported:
 
 =over 4
 
 
 =over 4
 
-=item usernum - primary key
+=item usernum
+
+primary key
+
+=item username
+
+=item _password
+
+=item _password_encoding
+
+Empty or bcrypt
+
+=item last
+
+Last name
 
 
-=item username - 
+=item first
 
 
-=item _password - 
+First name
 
 
-=item last -
+=item user_custnum
 
 
-=item first -
+Master customer for this employee (for commissions)
 
 
-=item disabled - empty or 'Y'
+=item report_salesnum
+
+Default sales person for this employee (for reports)
+
+=item disabled
+
+Empty or 'Y'
 
 =back
 
 
 =back
 
@@ -108,7 +119,6 @@ sub insert {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  $error = $self->htpasswd_kludge();
   if ( $error ) {
     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
     return $error;
   if ( $error ) {
     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
     return $error;
@@ -118,14 +128,7 @@ sub insert {
 
   if ( $error ) {
     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
 
   if ( $error ) {
     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
-
-    #make sure it isn't a dup username?  or you could nuke people's passwords
-    #blah.  really just should do our own login w/cookies
-    #and auth out of the db in the first place
-    #my $hterror = $self->htpasswd_kludge('-D');
-    #$error .= " - additionally received error cleaning up htpasswd file: $hterror"
     return $error;
     return $error;
-
   } else {
     $dbh->commit or die $dbh->errstr if $oldAutoCommit;
     '';
   } else {
     $dbh->commit or die $dbh->errstr if $oldAutoCommit;
     '';
@@ -133,27 +136,6 @@ sub insert {
 
 }
 
 
 }
 
-sub htpasswd_kludge {
-  my $self = shift;
-  
-  #awful kludge to skip setting htpasswd for fs_* users
-  return '' if $self->username =~ /^fs_/;
-
-  unshift @_, '-c' unless -e $htpasswd_file;
-  if ( 
-       system('htpasswd', '-b', @_,
-                          $htpasswd_file,
-                          $self->username,
-                          $self->_password,
-             ) == 0
-     )
-  {
-    return '';
-  } else {
-    return 'htpasswd exited unsucessfully';
-  }
-}
-
 =item delete
 
 Delete this record from the database.
 =item delete
 
 Delete this record from the database.
@@ -174,10 +156,7 @@ sub delete {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $error =
-       $self->SUPER::delete(@_)
-    || $self->htpasswd_kludge('-D')
-  ;
+  my $error = $self->SUPER::delete(@_);
 
   if ( $error ) {
     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
 
   if ( $error ) {
     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
@@ -214,13 +193,11 @@ sub replace {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  if ( $new->_password ne $old->_password ) {
-    my $error = $new->htpasswd_kludge();
-    if ( $error ) {
-      $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
-      return $error;
-    }
-  }
+  return "Must change password when enabling this account"
+    if $old->disabled && !$new->disabled
+    && (      $new->_password =~ /changeme/i
+           || $new->_password eq 'notyet'
+       );
 
   my $error = $new->SUPER::replace($old, @_);
 
 
   my $error = $new->SUPER::replace($old, @_);
 
@@ -251,9 +228,11 @@ sub check {
   my $error = 
     $self->ut_numbern('usernum')
     || $self->ut_alpha_lower('username')
   my $error = 
     $self->ut_numbern('usernum')
     || $self->ut_alpha_lower('username')
-    || $self->ut_text('_password')
-    || $self->ut_text('last')
-    || $self->ut_text('first')
+    || $self->ut_textn('_password')
+    || $self->ut_textn('last')
+    || $self->ut_textn('first')
+    || $self->ut_foreign_keyn('user_custnum', 'cust_main', 'custnum')
+    || $self->ut_foreign_keyn('report_salesnum', 'sales', 'salesnum')
     || $self->ut_enum('disabled', [ '', 'Y' ] )
   ;
   return $error if $error;
     || $self->ut_enum('disabled', [ '', 'Y' ] )
   ;
   return $error if $error;
@@ -269,36 +248,40 @@ Returns a name string for this user: "Last, First".
 
 sub name {
   my $self = shift;
 
 sub name {
   my $self = shift;
-  $self->get('last'). ', '. $self->first;
+  return $self->username
+    if $self->get('last') eq 'Lastname' && $self->first eq 'Firstname'
+    or $self->get('last') eq ''         && $self->first eq '';
+  return $self->get('last'). ', '. $self->first;
 }
 
 }
 
-=item access_usergroup
+=item user_cust_main
 
 
-Returns links to the the groups this user is a part of, as FS::access_usergroup
-objects (see L<FS::access_usergroup>).
+Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
+user.
 
 =cut
 
 
 =cut
 
-sub access_usergroup {
+sub user_cust_main {
   my $self = shift;
   my $self = shift;
-  qsearch( 'access_usergroup', { 'usernum' => $self->usernum } );
+  qsearchs( 'cust_main', { 'custnum' => $self->user_custnum } );
 }
 
 }
 
-#=item access_groups
-#
-#=cut
-#
-#sub access_groups {
-#
-#}
-#
-#=item access_groupnames
-#
-#=cut
-#
-#sub access_groupnames {
-#
-#}
+=item report_sales
+
+Returns the FS::sales object (see L<FS::sales>), if any, for this
+user.
+
+=cut
+
+sub report_sales {
+  my $self = shift;
+  qsearchs( 'sales', { 'salesnum' => $self->report_salesnum } );
+}
+
+=item access_usergroup
+
+Returns links to the the groups this user is a part of, as FS::access_usergroup
+objects (see L<FS::access_usergroup>).
 
 =item agentnums 
 
 
 =item agentnums 
 
@@ -350,6 +333,11 @@ user has the provided access right
 Optional table name in which agentnum is being checked.  Sometimes required to
 resolve 'column reference "agentnum" is ambiguous' errors.
 
 Optional table name in which agentnum is being checked.  Sometimes required to
 resolve 'column reference "agentnum" is ambiguous' errors.
 
+=item viewall_right
+
+All agents will be viewable if the current user has the provided access right.
+Defaults to 'View customers of all agents'.
+
 =back
 
 =cut
 =back
 
 =cut
@@ -360,16 +348,21 @@ sub agentnums_sql {
 
   my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum';
 
 
   my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum';
 
-#  my @agentnums = map { "$agentnum = $_" } $self->agentnums;
-  my @agentnums = ();
-  push @agentnums, "$agentnum IN (". join(',', $self->agentnums). ')';
+  my @or = ();
 
 
-  push @agentnums, "$agentnum IS NULL"
+  my $viewall_right = $opt{'viewall_right'} || 'View customers of all agents';
+  if ( $self->access_right($viewall_right) ) {
+    push @or, "$agentnum IS NOT NULL";
+  } else {
+    push @or, "$agentnum IN (". join(',', $self->agentnums). ')';
+  }
+
+  push @or, "$agentnum IS NULL"
     if $opt{'null'}
     || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) );
 
     if $opt{'null'}
     || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) );
 
-  return ' 1 = 0 ' unless scalar(@agentnums);
-  '( '. join( ' OR ', @agentnums ). ' )';
+  return ' 1 = 0 ' unless scalar(@or);
+  '( '. join( ' OR ', @or ). ' )';
 
 }
 
 
 }
 
@@ -390,10 +383,10 @@ sub agentnum {
   $sth->fetchrow_arrayref->[0];
 }
 
   $sth->fetchrow_arrayref->[0];
 }
 
-=item agents
+=item agents [ HASHREF | OPTION => VALUE ... ]
 
 Returns the list of agents this user can view (via group membership), as
 
 Returns the list of agents this user can view (via group membership), as
-FS::agent objects.
+FS::agent objects.  Accepts the same options as the agentnums_sql method.
 
 =cut
 
 
 =cut
 
@@ -402,7 +395,8 @@ sub agents {
   qsearch({
     'table'     => 'agent',
     'hashref'   => { disabled=>'' },
   qsearch({
     'table'     => 'agent',
     'hashref'   => { disabled=>'' },
-    'extra_sql' => ' AND '. $self->agentnums_sql,
+    'extra_sql' => ' AND '. $self->agentnums_sql(@_),
+    'order_by'  => 'ORDER BY agent',
   });
 }
 
   });
 }
 
@@ -430,7 +424,7 @@ sub access_right {
     unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) {
       warn "$me ACL cache hit for ". join(', ', @$rightname). "\n"
         if $DEBUG;
     unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) {
       warn "$me ACL cache hit for ". join(', ', @$rightname). "\n"
         if $DEBUG;
-      return grep $self->{_ACLcache}{$_}, @$rightname
+      return scalar( grep $self->{_ACLcache}{$_}, @$rightname );
     }
 
     warn "$me ACL cache miss for ". join(', ', @$rightname). "\n"
     }
 
     warn "$me ACL cache miss for ". join(', ', @$rightname). "\n"
@@ -474,7 +468,7 @@ sub access_right {
 
 Returns the default customer view for this user, from the 
 "default_customer_view" user preference, the "cust_main-default_view" config,
 
 Returns the default customer view for this user, from the 
 "default_customer_view" user preference, the "cust_main-default_view" config,
-or the hardcoded default, "jumbo" (may change to "basics" in the near future).
+or the hardcoded default, "basics" (formerly "jumbo" prior to 3.0).
 
 =cut
 
 
 =cut
 
@@ -482,9 +476,85 @@ sub default_customer_view {
   my $self = shift;
 
   $self->option('default_customer_view')
   my $self = shift;
 
   $self->option('default_customer_view')
-    || $conf->config('cust_main-default_view')
-    || 'jumbo'; #'basics' in 1.9.1?
+    || FS::Conf->new->config('cust_main-default_view')
+    || 'basics'; #s/jumbo/basics/ starting with 3.0
+
+}
+
+=item spreadsheet_format [ OVERRIDE ]
+
+Returns a hashref of this user's Excel spreadsheet download settings:
+'extension' (xls or xlsx), 'class' (Spreadsheet::WriteExcel or
+Excel::Writer::XLSX), and 'mime_type'.  If OVERRIDE is 'XLS' or 'XLSX',
+use that instead of the user's setting.
+
+=cut
+
+# is there a better place to put this?
+my %formats = (
+  XLS => {
+    extension => '.xls',
+    class => 'Spreadsheet::WriteExcel',
+    mime_type => 'application/vnd.ms-excel',
+  },
+  XLSX => {
+    extension => '.xlsx',
+    class => 'Excel::Writer::XLSX',
+    mime_type => # it's on wikipedia, it must be true
+      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+  }
+);
+
+sub spreadsheet_format {
+  my $self = shift;
+  my $override = shift;
+
+  my $f =  $override
+        || $self->option('spreadsheet_format') 
+        || FS::Conf->new->config('spreadsheet_format')
+        || 'XLS';
+
+  $formats{$f};
+}
+
+=item is_system_user
+
+Returns true if this user has the name of a known system account.  These 
+users cannot log into the web interface and can't have passwords set.
+
+=cut
+
+sub is_system_user {
+  my $self = shift;
+  return grep { $_ eq $self->username } ( qw(
+    fs_queue
+    fs_daily
+    fs_selfservice
+    fs_signup
+    fs_bootstrap
+    fs_selfserv
+    fs_api
+  ) );
+}
+
+=item change_password NEW_PASSWORD
+
+=cut
+
+sub change_password {
+  #my( $self, $password ) = @_;
+  #FS::Auth->auth_class->change_password( $self, $password );
+  FS::Auth->auth_class->change_password( @_ );
+}
+
+=item change_password_fields NEW_PASSWORD
+
+=cut
 
 
+sub change_password_fields {
+  #my( $self, $password ) = @_;
+  #FS::Auth->auth_class->change_password_fields( $self, $password );
+  FS::Auth->auth_class->change_password_fields( @_ );
 }
 
 =back
 }
 
 =back