summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorivan <ivan>2006-06-19 02:33:52 +0000
committerivan <ivan>2006-06-19 02:33:52 +0000
commitc0e8da2f1e89729efa1032241e4239765a296514 (patch)
tree68af748a3dc0c983e3ed255ad1bc05e9bf2eb8e7 /FS
parentbb65b62d3d68ac0788f230fe2e35ebe1913fc0f5 (diff)
agent virtualization, take one (stuff from "inactive" changeset snuck into cust_main.pm and the package reporting changeset in search/cust_pkg.cgi here too)
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/CurrentUser.pm50
-rw-r--r--FS/FS/Record.pm29
-rw-r--r--FS/FS/Schema.pm13
-rw-r--r--FS/FS/UID.pm19
-rw-r--r--FS/FS/access_user.pm51
-rw-r--r--FS/FS/cust_main.pm213
-rw-r--r--FS/FS/part_pkg.pm3
-rw-r--r--FS/MANIFEST1
8 files changed, 277 insertions, 102 deletions
diff --git a/FS/FS/CurrentUser.pm b/FS/FS/CurrentUser.pm
new file mode 100644
index 000000000..13d34167d
--- /dev/null
+++ b/FS/FS/CurrentUser.pm
@@ -0,0 +1,50 @@
+package FS::CurrentUser;
+
+use vars qw($CurrentUser);
+
+#not at compile-time, circular dependancey causes trouble
+#use FS::Record qw(qsearchs);
+#use FS::access_user;
+
+=head1 NAME
+
+FS::CurrentUser - Package representing the current user
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=cut
+
+sub load_user {
+ my( $class, $user ) = @_; #, $pass
+
+ #XXX remove me at some point
+ return "" if $user =~ /^fs_(queue|selfservice)$/;
+
+ #not the best thing in the world...
+ eval "use FS::Record qw(qsearchs);";
+ die $@ if $@;
+ eval "use FS::access_user;";
+ die $@ if $@;
+
+ $CurrentUser = qsearchs('access_user', {
+ 'username' => $user,
+ #'_password' =>
+ } );
+
+ die "unknown user: $user" unless $CurrentUser; # or bad password
+
+ $CurrentUser;
+}
+
+=head1 BUGS
+
+Creepy crawlies
+
+=head1 SEE ALSO
+
+=cut
+
+1;
+
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
index 67de071c6..fa0d2d87e 100644
--- a/FS/FS/Record.pm
+++ b/FS/FS/Record.pm
@@ -29,14 +29,12 @@ $me = '[FS::Record]';
$nowarn_identical = 0;
-my $conf;
my $rsa_module;
my $rsa_loaded;
my $rsa_encrypt;
my $rsa_decrypt;
FS::UID->install_callback( sub {
- $conf = new FS::Conf;
$File::CounterFile::DEFAULT_DIR = "/usr/local/etc/freeside/counters.". datasrc;
} );
@@ -441,6 +439,7 @@ sub qsearch {
}
# Check for encrypted fields and decrypt them.
+ my $conf = new FS::Conf;
if ($conf->exists('encryption') && eval 'defined(@FS::'. $table . '::encrypted_fields)') {
foreach my $record (@return) {
foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
@@ -711,6 +710,7 @@ sub insert {
# Encrypt before the database
+ my $conf = new FS::Conf;
if ($conf->exists('encryption') && defined(eval '@FS::'. $table . 'encrypted_fields')) {
foreach my $field (eval '@FS::'. $table . '::encrypted_fields') {
$self->{'saved'} = $self->getfield($field);
@@ -727,12 +727,18 @@ sub insert {
my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields;
#eslaf
- my $statement = "INSERT INTO $table ( ".
- join( ', ', @real_fields ).
- ") VALUES (".
- join( ', ', @values ).
- ")"
- ;
+ my $statement = "INSERT INTO $table ";
+ if ( @real_fields ) {
+ $statement .=
+ "( ".
+ join( ', ', @real_fields ).
+ ") VALUES (".
+ join( ', ', @values ).
+ ")"
+ ;
+ } else {
+ $statement .= 'DEFAULT VALUES';
+ }
warn "[debug]$me $statement\n" if $DEBUG > 1;
my $sth = dbh->prepare($statement) or return dbh->errstr;
@@ -995,6 +1001,7 @@ sub replace {
return $error if $error;
# Encrypt for replace
+ my $conf = new FS::Conf;
my $saved = {};
if ($conf->exists('encryption') && defined(eval '@FS::'. $new->table . 'encrypted_fields')) {
foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') {
@@ -1635,7 +1642,8 @@ sub virtual_fields {
"WHERE dbtable = '$table'";
my $dbh = dbh;
my $result = $dbh->selectcol_arrayref($query);
- confess $dbh->errstr if $dbh->err;
+ confess "Error executing virtual fields query: $query: ". $dbh->errstr
+ if $dbh->err;
$virtual_fields_cache{$table} = $result;
}
@@ -1788,6 +1796,7 @@ sub encrypt {
my ($self, $value) = @_;
my $encrypted;
+ my $conf = new FS::Conf;
if ($conf->exists('encryption')) {
if ($self->is_encrypted($value)) {
# Return the original value if it isn't plaintext.
@@ -1821,6 +1830,7 @@ sub is_encrypted {
sub decrypt {
my ($self,$value) = @_;
my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted.
+ my $conf = new FS::Conf;
if ($conf->exists('encryption') && $self->is_encrypted($value)) {
$self->loadRSA;
if (ref($rsa_decrypt) =~ /::RSA/) {
@@ -1836,6 +1846,7 @@ sub loadRSA {
#Initialize the Module
$rsa_module = 'Crypt::OpenSSL::RSA'; # The Default
+ my $conf = new FS::Conf;
if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') {
$rsa_module = $conf->config('encryptionmodule');
}
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 17d541e8c..7219274e6 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -16,12 +16,13 @@ use FS::UID qw(datasrc);
$DEBUG = 0;
$me = '[FS::Schema]';
-#ask FS::UID to run this stuff for us later
-FS::UID->install_callback( sub {
- #$conf = new FS::Conf;
- &reload_dbdef("/usr/local/etc/freeside/dbdef.". datasrc)
- unless $setup_hack; #$setup_hack needed now?
-} );
+#hardcoded now...
+##ask FS::UID to run this stuff for us later
+#FS::UID->install_callback( sub {
+# #$conf = new FS::Conf;
+# &reload_dbdef("/usr/local/etc/freeside/dbdef.". datasrc)
+# unless $setup_hack; #$setup_hack needed now?
+#} );
=head1 NAME
diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm
index 83b652b0f..21df9441b 100644
--- a/FS/FS/UID.pm
+++ b/FS/FS/UID.pm
@@ -13,6 +13,7 @@ use Exporter;
use Carp qw(carp croak cluck);
use DBI;
use FS::Conf;
+use FS::CurrentUser;
@ISA = qw(Exporter);
@EXPORT_OK = qw(checkeuid checkruid cgisuidsetup adminsuidsetup forksuidsetup
@@ -87,6 +88,12 @@ sub forksuidsetup {
$dbh = &myconnect;
+ use FS::Schema qw(reload_dbdef);
+ reload_dbdef("/usr/local/etc/freeside/dbdef.$datasrc")
+ unless $FS::Schema::setup_hack;
+
+ FS::CurrentUser->load_user($user);
+
foreach ( keys %callback ) {
&{$callback{$_}};
# breaks multi-database installs # delete $callback{$_}; #run once
@@ -98,7 +105,11 @@ sub forksuidsetup {
}
sub myconnect {
- DBI->connect( getsecrets, {'AutoCommit' => 0, 'ChopBlanks' => 1, } )
+ DBI->connect( getsecrets, { 'AutoCommit' => 0,
+ 'ChopBlanks' => 1,
+ 'ShowErrorStatement' => 1,
+ }
+ )
or die "DBI->connect error: $DBI::errstr\n";
}
@@ -256,10 +267,10 @@ sub getsecrets {
$user = $setuser if $setuser;
die "No user!" unless $user;
my($conf) = new FS::Conf $conf_dir;
- my($line) = grep /^\s*$user\s/, $conf->config('mapsecrets');
+ my($line) = grep /^\s*($user|\*)\s/, $conf->config('mapsecrets');
die "User $user not found in mapsecrets!" unless $line;
- $line =~ /^\s*$user\s+(.*)$/;
- $secrets = $1;
+ $line =~ /^\s*($user|\*)\s+(.*)$/;
+ $secrets = $2;
die "Illegal mapsecrets line for user?!" unless $secrets;
($datasrc, $db_user, $db_pass) = $conf->config($secrets)
or die "Can't get secrets: $secrets: $!\n";
diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
index ca311d3b8..c95d02984 100644
--- a/FS/FS/access_user.pm
+++ b/FS/FS/access_user.pm
@@ -2,7 +2,7 @@ package FS::access_user;
use strict;
use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
+use FS::Record qw( qsearch qsearchs dbh );
use FS::m2m_Common;
use FS::access_usergroup;
@@ -29,7 +29,7 @@ FS::access_user - Object methods for access_user records
=head1 DESCRIPTION
-An FS::access_user object represents an example. FS::access_user inherits from
+An FS::access_user object represents an internal access user. FS::access_user inherits from
FS::Record. The following fields are currently supported:
=over 4
@@ -52,7 +52,7 @@ FS::Record. The following fields are currently supported:
=item new HASHREF
-Creates a new example. To add the example to the database, see L<"insert">.
+Creates a new internal access user. To add the user to the database, see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
@@ -91,7 +91,7 @@ returns the error, otherwise returns false.
=item check
-Checks all fields to make sure this is a valid example. If there is
+Checks all fields to make sure this is a valid internal access user. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
@@ -151,12 +151,51 @@ sub access_usergroup {
#
#}
+=item agentnums
+
+Returns a list of agentnums this user can view (via group membership).
+
+=cut
+
+sub agentnums {
+ my $self = shift;
+ my $sth = dbh->prepare(
+ "SELECT DISTINCT agentnum FROM access_usergroup
+ JOIN access_groupagent USING ( groupnum )
+ WHERE usernum = ?"
+ ) or die dbh->errstr;
+ $sth->execute($self->usernum) or die $sth->errstr;
+ map { $_->[0] } @{ $sth->fetchall_arrayref };
+}
+
+=item agentnums_href
+
+Returns a hashref of agentnums this user can view.
+
+=cut
+
+sub agentnums_href {
+ my $self = shift;
+ { map { $_ => 1 } $self->agentnums };
+}
+
+=item agentnums_sql
+
+Returns an sql fragement to select only agentnums this user can view.
+
+=cut
+
+sub agentnums_sql {
+ my $self = shift;
+ '( '.
+ join( ' OR ', map "agentnum = $_", $self->agentnums ).
+ ' )';
+}
+
=back
=head1 BUGS
-The author forgot to customize this manpage.
-
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 2763c1386..8956d5b26 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -20,6 +20,7 @@ use Date::Parse;
#use Date::Manip;
use String::Approx qw(amatch);
use Business::CreditCard 0.28;
+use Locale::Country;
use FS::UID qw( getotaker dbh );
use FS::Record qw( qsearchs qsearch dbdef );
use FS::Misc qw( send_email );
@@ -79,7 +80,7 @@ sub _cache {
my $self = shift;
my ( $hashref, $cache ) = @_;
if ( exists $hashref->{'pkgnum'} ) {
-# #@{ $self->{'_pkgnum'} } = ();
+ #@{ $self->{'_pkgnum'} } = ();
my $subcache = $cache->subcache( 'pkgnum', 'cust_pkg', $hashref->{custnum});
$self->{'_pkgnum'} = $subcache;
#push @{ $self->{'_pkgnum'} },
@@ -3186,6 +3187,7 @@ This interface may change in the future.
sub invoicing_list {
my( $self, $arrayref ) = @_;
+
if ( $arrayref ) {
my @cust_main_invoice;
if ( $self->custnum ) {
@@ -3220,12 +3222,14 @@ sub invoicing_list {
warn $error if $error;
}
}
+
if ( $self->custnum ) {
map { $_->address }
qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } );
} else {
();
}
+
}
=item check_invoicing_list ARRAYREF
@@ -3303,6 +3307,18 @@ sub invoicing_list_addpost {
$self->invoicing_list(\@invoicing_list);
}
+=item invoicing_list_emailonly
+
+Returns the list of email invoice recipients (invoicing_list without non-email
+destinations such as POST and FAX).
+
+=cut
+
+sub invoicing_list_emailonly {
+ my $self = shift;
+ grep { $_ !~ /^([A-Z]+)$/ } $self->invoicing_list;
+}
+
=item referral_cust_main [ DEPTH [ EXCLUDE_HASHREF ] ]
Returns an array of customers referred by this customer (referral_custnum set
@@ -3600,6 +3616,17 @@ sub ship_contact {
: $self->contact;
}
+=item country_full
+
+Returns this customer's full country name
+
+=cut
+
+sub country_full {
+ my $self = shift;
+ code2country($self->country);
+}
+
=item status
Returns a status string for this customer, currently:
@@ -3610,6 +3637,8 @@ Returns a status string for this customer, currently:
=item active - One or more recurring packages is active
+=item inactive - No active recurring packages, but otherwise unsuspended/uncancelled (the inactive status is new - previously inactive customers were mis-identified as cancelled)
+
=item suspended - All non-cancelled recurring packages are suspended
=item cancelled - All recurring packages are cancelled
@@ -3620,7 +3649,7 @@ Returns a status string for this customer, currently:
sub status {
my $self = shift;
- for my $status (qw( prospect active suspended cancelled )) {
+ for my $status (qw( prospect active inactive suspended cancelled )) {
my $method = $status.'_sql';
my $numnum = ( my $sql = $self->$method() ) =~ s/cust_main\.custnum/?/g;
my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr;
@@ -3635,14 +3664,18 @@ Returns a hex triplet color string for this customer's status.
=cut
-my %statuscolor = (
- 'prospect' => '000000',
- 'active' => '00CC00',
- 'suspended' => 'FF9900',
- 'cancelled' => 'FF0000',
-);
+
sub statuscolor {
my $self = shift;
+
+ my %statuscolor = (
+ 'prospect' => '7e0079', #'000000', #black? naw, purple
+ 'active' => '00CC00', #green
+ 'inactive' => '0000CC', #blue
+ 'suspended' => 'FF9900', #yellow
+ 'cancelled' => 'FF0000', #red
+ );
+
$statuscolor{$self->status};
}
@@ -3659,25 +3692,40 @@ with no packages ever ordered)
=cut
+use vars qw($select_count_pkgs);
+$select_count_pkgs =
+ "SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum";
+
sub prospect_sql { "
- 0 = ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
- )
+ 0 = ( $select_count_pkgs )
"; }
=item active_sql
-Returns an SQL expression identifying active cust_main records.
+Returns an SQL expression identifying active cust_main records (customers with
+no active recurring packages, but otherwise unsuspended/uncancelled).
=cut
sub active_sql { "
- 0 < ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
- AND ". FS::cust_pkg->active_sql. "
+ 0 < ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. "
)
"; }
+=item inactive_sql
+
+Returns an SQL expression identifying inactive cust_main records (customers with
+active recurring packages).
+
+=cut
+
+sub inactive_sql { "
+ 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
+ AND
+ 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " )
+"; }
+
=item susp_sql
=item suspended_sql
@@ -3685,23 +3733,12 @@ Returns an SQL expression identifying suspended cust_main records.
=cut
-#my $recurring_sql = FS::cust_pkg->recurring_sql;
-my $recurring_sql = "
- '0' != ( select freq from part_pkg
- where cust_pkg.pkgpart = part_pkg.pkgpart )
-";
sub suspended_sql { susp_sql(@_); }
sub susp_sql { "
- 0 < ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
- AND $recurring_sql
- AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
- )
- AND 0 = ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
- AND ". FS::cust_pkg->active_sql. "
- )
+ 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " )
+ AND
+ 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " )
"; }
=item cancel_sql
@@ -3712,16 +3749,21 @@ Returns an SQL expression identifying cancelled cust_main records.
=cut
sub cancelled_sql { cancel_sql(@_); }
-sub cancel_sql { "
- 0 < ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
- )
- AND 0 = ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
- AND $recurring_sql
- AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
- )
-"; }
+sub cancel_sql {
+
+ my $recurring_sql = FS::cust_pkg->recurring_sql;
+ #my $recurring_sql = "
+ # '0' != ( select freq from part_pkg
+ # where cust_pkg.pkgpart = part_pkg.pkgpart )
+ #";
+
+ "
+ 0 < ( $select_count_pkgs )
+ AND 0 = ( $select_count_pkgs AND $recurring_sql
+ AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+ )
+ ";
+}
=item uncancel_sql
=item uncancelled_sql
@@ -3732,15 +3774,12 @@ Returns an SQL expression identifying un-cancelled cust_main records.
sub uncancelled_sql { uncancel_sql(@_); }
sub uncancel_sql { "
- ( 0 < ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
+ ( 0 < ( $select_count_pkgs
AND ( cust_pkg.cancel IS NULL
OR cust_pkg.cancel = 0
)
)
- OR 0 = ( SELECT COUNT(*) FROM cust_pkg
- WHERE cust_pkg.custnum = cust_main.custnum
- )
+ OR 0 = ( $select_count_pkgs )
)
"; }
@@ -3802,11 +3841,18 @@ Returns a (possibly empty) array of FS::cust_main objects.
sub smart_search {
my %options = @_;
my $search = delete $options{'search'};
- my @cust_main = ();
+ #here is the agent virtualization
+ my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my @cust_main = ();
if ( $search =~ /^\s*(\d+)\s*$/ ) { # customer # search
- push @cust_main, qsearch('cust_main', { 'custnum' => $1, %options } );
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $1, %options },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualization
+ } );
} elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { #value search
@@ -3820,50 +3866,65 @@ sub smart_search {
if defined dbdef->table('cust_main')->column('ship_last');
$sql .= ' )';
- push @cust_main, qsearch( 'cust_main', \%options, '', $sql );
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => \%options,
+ 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization
+ } );
unless ( @cust_main ) { #no exact match, trying substring/fuzzy
#still some false laziness w/ search/cust_main.cgi
#substring
- push @cust_main, qsearch( 'cust_main',
- { 'last' => { 'op' => 'ILIKE',
- 'value' => "%$q_value%" },
- %options,
- }
- );
- push @cust_main, qsearch( 'cust_main',
- { 'ship_last' => { 'op' => 'ILIKE',
- 'value' => "%$q_value%" },
- %options,
-
- }
- )
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'last' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" },
+ %options,
+ },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualizaiton
+ } );
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'ship_last' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" },
+ %options,
+ },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualization
+ } )
if defined dbdef->table('cust_main')->column('ship_last');
- push @cust_main, qsearch( 'cust_main',
- { 'company' => { 'op' => 'ILIKE',
- 'value' => "%$q_value%" },
- %options,
- }
- );
- push @cust_main, qsearch( 'cust_main',
- { 'ship_company' => { 'op' => 'ILIKE',
- 'value' => "%$q_value%" },
- %options,
- }
- )
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'company' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" },
+ %options,
+ },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualization
+ } );
+ push @cust_main, qsearch( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'ship_company' => { 'op' => 'ILIKE',
+ 'value' => "%$q_value%" },
+ %options,
+ },
+ 'extra_sql' => " AND $agentnums_sql", #agent virtualization
+ } )
if defined dbdef->table('cust_main')->column('ship_last');
#fuzzy
push @cust_main, FS::cust_main->fuzzy_search(
- { 'last' => $value },
- \%options,
+ { 'last' => $value }, #fuzzy hashref
+ \%options, #hashref
+ '', #select
+ " AND $agentnums_sql", #extra_sql #agent virtualization
);
push @cust_main, FS::cust_main->fuzzy_search(
- { 'company' => $value },
- \%options,
+ { 'company' => $value }, #fuzzy hashref
+ \%options, #hashref
+ '', #select
+ " AND $agentnums_sql", #extra_sql #agent virtualization
);
}
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index de4d047de..aa39d890b 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -608,7 +608,8 @@ sub freq_pretty {
my $self = shift;
my $freq = $self->freq;
- my $freqs_href = $self->freqs_href;
+ #my $freqs_href = $self->freqs_href;
+ my $freqs_href = freqs_href();
if ( exists($freqs_href->{$freq}) ) {
$freqs_href->{$freq};
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 098fe4a64..6db82710c 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -345,3 +345,4 @@ t/pay_batch.t
FS/ConfDefaults.pm
t/ConfDefaults.t
FS/m2name_Common.pm
+FS/CurrentUser.pm