package FS::svc_acct;
-
-use strict;
-use base qw( FS::svc_Domain_Mixin
- FS::svc_CGP_Mixin
- FS::svc_CGPRule_Mixin
+use base qw( FS::svc_Domain_Mixin FS::svc_PBX_Mixin
+ FS::svc_CGP_Mixin FS::svc_CGPRule_Mixin
FS::svc_Radius_Mixin
FS::svc_Tower_Mixin
FS::svc_IP_Mixin
- FS::svc_Common );
+ FS::svc_Common
+ );
+
+use strict;
use vars qw( $DEBUG $me $conf $skip_fuzzyfiles
$dir_prefix @shells $usernamemin
$usernamemax $passwordmin $passwordmax
$username_slash $username_equals $username_pound
$username_exclamation
$password_noampersand $password_noexclamation
- $warning_template $warning_from $warning_subject $warning_mimetype
- $warning_cc
+ $warning_msgnum
$smtpmachine
$radius_password $radius_ip
$dirhash
use FS::Msgcat qw(gettext);
use FS::UI::bytecount;
use FS::UI::Web;
+use FS::PagedSearch qw( psearch ); # XXX in v4, replace with FS::Cursor
use FS::part_pkg;
use FS::part_svc;
use FS::svc_acct_pop;
use FS::svc_forward;
use FS::svc_www;
use FS::cdr;
-use FS::acct_snarf;
use FS::tower_sector;
$DEBUG = 0;
$password_noampersand = $conf->exists('password-noexclamation');
$password_noexclamation = $conf->exists('password-noexclamation');
$dirhash = $conf->config('dirhash') || 0;
- if ( $conf->exists('warning_email') ) {
- $warning_template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config('warning_email') ]
- ) or warn "can't create warning email template: $Text::Template::ERROR";
- $warning_from = $conf->config('warning_email-from'); # || 'your-isp-is-dum'
- $warning_subject = $conf->config('warning_email-subject') || 'Warning';
- $warning_mimetype = $conf->config('warning_email-mimetype') || 'text/plain';
- $warning_cc = $conf->config('warning_email-cc');
- } else {
- $warning_template = '';
- $warning_from = '';
- $warning_subject = '';
- $warning_mimetype = '';
- $warning_cc = '';
- }
+ $warning_msgnum = $conf->config('threshold_warning_msgnum');
$smtpmachine = $conf->config('smtpmachine');
$radius_password = $conf->config('radius-password') || 'Password';
$radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address';
'display_weight' => 10,
'cancel_weight' => 50,
'ip_field' => 'slipip',
+ 'manual_require' => 1,
'fields' => {
'dir' => 'Home directory',
'uid' => {
disable_default => 1,
disable_fixed => 1,
disable_select => 1,
+ required => 1,
},
'password_selfchange' => { label => 'Password modification',
type => 'checkbox',
label => 'Quota', #Mail storage limit
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
'file_quota'=> {
label => 'File storage limit',
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
'file_maxnum'=> {
label => 'Number of files limit',
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
'file_maxsize'=> {
label => 'File size limit',
type => 'text',
disable_inventory => 1,
- disable_select => 1,
},
- '_password' => 'Password',
+ '_password' => { label => 'Password',
+ required => 1
+ },
'gid' => {
label => 'GID',
def_info => 'when blank, defaults to UID',
select_key => 'svcnum',
select_label => 'domain',
disable_inventory => 1,
+ required => 1,
},
'pbxsvc' => { label => 'PBX',
type => 'select-svc_pbx.html',
my $msg_template = qsearchs('msg_template', { msgnum => $msgnum });
$error = $msg_template->send('cust_main' => $cust_main,
'object' => $self);
+ #should this do something on error?
}
- else { #!$msgnum
- my ($to,$welcome_template,$welcome_from,$welcome_subject,$welcome_subject_template,$welcome_mimetype)
- = ('','','','','','');
-
- if ( $conf->exists('welcome_email', $agentnum) ) {
- $welcome_template = new Text::Template (
- TYPE => 'ARRAY',
- SOURCE => [ map "$_\n", $conf->config('welcome_email', $agentnum) ]
- ) or warn "can't create welcome email template: $Text::Template::ERROR";
- $welcome_from = $conf->config('welcome_email-from', $agentnum);
- # || 'your-isp-is-dum'
- $welcome_subject = $conf->config('welcome_email-subject', $agentnum)
- || 'Welcome';
- $welcome_subject_template = new Text::Template (
- TYPE => 'STRING',
- SOURCE => $welcome_subject,
- ) or warn "can't create welcome email subject template: $Text::Template::ERROR";
- $welcome_mimetype = $conf->config('welcome_email-mimetype', $agentnum)
- || 'text/plain';
- }
- if ( $welcome_template ) {
- my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list );
- if ( $to ) {
-
- my %hash = (
- 'custnum' => $self->custnum,
- 'username' => $self->username,
- 'password' => $self->_password,
- 'first' => $cust_main->first,
- 'last' => $cust_main->getfield('last'),
- 'pkg' => $cust_pkg->part_pkg->pkg,
- );
- my $wqueue = new FS::queue {
- 'svcnum' => $self->svcnum,
- 'job' => 'FS::svc_acct::send_email'
- };
- my $error = $wqueue->insert(
- 'to' => $to,
- 'from' => $welcome_from,
- 'subject' => $welcome_subject_template->fill_in( HASH => \%hash, ),
- 'mimetype' => $welcome_mimetype,
- 'body' => $welcome_template->fill_in( HASH => \%hash, ),
- );
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error queuing welcome email: $error";
- }
-
- if ( $options{'depend_jobnum'} ) {
- warn "$me depend_jobnum found; adding to welcome email dependancies"
- if $DEBUG;
- if ( ref($options{'depend_jobnum'}) ) {
- warn "$me adding jobs ". join(', ', @{$options{'depend_jobnum'}} ).
- "to welcome email dependancies"
- if $DEBUG;
- push @jobnums, @{ $options{'depend_jobnum'} };
- } else {
- warn "$me adding job $options{'depend_jobnum'} ".
- "to welcome email dependancies"
- if $DEBUG;
- push @jobnums, $options{'depend_jobnum'};
- }
- }
-
- foreach my $jobnum ( @jobnums ) {
- my $error = $wqueue->depend_insert($jobnum);
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "error queuing welcome email job dependancy: $error";
- }
- }
-
- }
-
- } # if $welcome_template
- } # if !$msgnum
}
} # if $cust_pkg
$self->username. '@'. $self->domain(@_);
}
+
=item acct_snarf
Returns an array of FS::acct_snarf records associated with the account.
=cut
-sub acct_snarf {
- my $self = shift;
- qsearch({
- 'table' => 'acct_snarf',
- 'hashref' => { 'svcnum' => $self->svcnum },
- #'order_by' => 'ORDER BY priority ASC',
- });
-}
+# unused as originally intended, but now by Communigate Pro "RPOP"
=item cgp_rpop_hashref
}
}
- if ($warning_template && &{$op2warncondition{$op}}($self, $column, $amount)) {
+ if ($warning_msgnum && &{$op2warncondition{$op}}($self, $column, $amount)) {
my $wqueue = new FS::queue {
'svcnum' => $self->svcnum,
'job' => 'FS::svc_acct::reached_threshold',
};
- my $to = '';
- if ($op eq '-'){
- $to = $warning_cc if &{$op2condition{$op}}($self, $column, $amount);
- }
-
# x_threshold race
my $error = $wqueue->insert(
'svcnum' => $self->svcnum,
'op' => $op,
- 'column' => $column,
- 'to' => $to,
+ 'column' => $column
);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
$self->cust_svc->seconds_since(@_);
}
-=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
-
-Returns the numbers of seconds this account has been online between
-TIMESTAMP_START (inclusive) and TIMESTAMP_END (exclusive), according to an
-external SQL radacct table, specified via sqlradius export. Sessions which
-started in the specified range but are still open are counted from session
-start to the end of the range (unless they are over 1 day old, in which case
-they are presumed missing their stop record and not counted). Also, sessions
-which end in the range but started earlier are counted from the start of the
-range to session end. Finally, sessions which start before the range but end
-after are counted for the entire range.
+=item last_login_text
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
+Returns text describing the time of last login.
=cut
-#note: POD here, implementation in FS::cust_svc
-sub seconds_since_sqlradacct {
+sub last_login_text {
my $self = shift;
- $self->cust_svc->seconds_since_sqlradacct(@_);
+ $self->last_login ? ctime($self->last_login) : 'unknown';
}
-=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
-
-Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
-in this package for sessions ending between TIMESTAMP_START (inclusive) and
-TIMESTAMP_END (exclusive).
+=item psearch_cdrs OPTIONS
-TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
-L<perlfunc/"time">. Also see L<Time::Local> and L<Date::Parse> for conversion
-functions.
+Returns a paged search (L<FS::PagedSearch>) for Call Detail Records
+associated with this service. For svc_acct, "associated with" means that
+either the "src" or the "charged_party" field of the CDR matches the
+"username" field of the service.
=cut
-#note: POD here, implementation in FS::cust_svc
-sub attribute_since_sqlradacct {
- my $self = shift;
- $self->cust_svc->attribute_since_sqlradacct(@_);
-}
+sub psearch_cdrs {
+ my($self, %options) = @_;
+ my @fields;
+ my %hash;
+ my @where;
-=item get_session_history TIMESTAMP_START TIMESTAMP_END
+ my $did = dbh->quote($self->username);
-Returns an array of hash references of this customers login history for the
-given time range. (document this better)
+ my $prefix = $options{'default_prefix'} || ''; #convergent.au '+61'
+ my $prefixdid = dbh->quote($prefix . $self->username);
-=cut
+ my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
-sub get_session_history {
- my $self = shift;
- $self->cust_svc->get_session_history(@_);
-}
-
-=item last_login_text
+ if ( $options{inbound} ) {
+ # these will be selected under their DIDs
+ push @where, "FALSE";
+ }
-Returns text describing the time of last login.
+ my @orwhere;
+ if (!$options{'disable_charged_party'}) {
+ push @orwhere,
+ "charged_party = $did",
+ "charged_party = $prefixdid";
+ }
+ if (!$options{'disable_src'}) {
+ push @orwhere,
+ "src = $did AND charged_party IS NULL",
+ "src = $prefixdid AND charged_party IS NULL";
+ }
+ push @where, '(' . join(' OR ', @orwhere) . ')';
-=cut
+ # $options{'status'} = '' is meaningful; for the rest of them it's not
+ if ( exists $options{'status'} ) {
+ $hash{'freesidestatus'} = $options{'status'};
+ }
+ if ( $options{'cdrtypenum'} ) {
+ $hash{'cdrtypenum'} = $options{'cdrtypenum'};
+ }
+ if ( $options{'calltypenum'} ) {
+ $hash{'calltypenum'} = $options{'calltypenum'};
+ }
+ if ( $options{'begin'} ) {
+ push @where, 'startdate >= '. $options{'begin'};
+ }
+ if ( $options{'end'} ) {
+ push @where, 'startdate < '. $options{'end'};
+ }
+ if ( $options{'nonzero'} ) {
+ push @where, 'duration > 0';
+ }
-sub last_login_text {
- my $self = shift;
- $self->last_login ? ctime($self->last_login) : 'unknown';
+ my $extra_sql = join(' AND ', @where);
+ if ($extra_sql) {
+ if (keys %hash) {
+ $extra_sql = " AND ".$extra_sql;
+ } else {
+ $extra_sql = " WHERE ".$extra_sql;
+ }
+ }
+ return psearch({
+ 'select' => '*',
+ 'table' => 'cdr',
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => "ORDER BY startdate $for_update",
+ });
}
-=item get_cdrs TIMESTAMP_START TIMESTAMP_END [ 'OPTION' => 'VALUE ... ]
+=item get_cdrs (DEPRECATED)
+
+Like psearch_cdrs, but returns all the L<FS::cdr> objects at once, in a
+single list. Arguments are the same as for psearch_cdrs.
=cut
sub get_cdrs {
- my($self, $start, $end, %opt ) = @_;
-
- my $did = $self->username; #yup
-
- my $prefix = $opt{'default_prefix'}; #convergent.au '+61'
-
- my $for_update = $opt{'for_update'} ? 'FOR UPDATE' : '';
-
- #SELECT $for_update * FROM cdr
- # WHERE calldate >= $start #need a conversion
- # AND calldate < $end #ditto
- # AND ( charged_party = "$did"
- # OR charged_party = "$prefix$did" #if length($prefix);
- # OR ( ( charged_party IS NULL OR charged_party = '' )
- # AND
- # ( src = "$did" OR src = "$prefix$did" ) # if length($prefix)
- # )
- # )
- # AND ( freesidestatus IS NULL OR freesidestatus = '' )
-
- my $charged_or_src;
- if ( length($prefix) ) {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR charged_party = '$prefix$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- ( src = '$did' OR src = '$prefix$did' )
- )
- )
- ";
- } else {
- $charged_or_src =
- " AND ( charged_party = '$did'
- OR ( ( charged_party IS NULL OR charged_party = '' )
- AND
- src = '$did'
- )
- )
- ";
-
- }
-
- qsearch(
- 'select' => "$for_update *",
- 'table' => 'cdr',
- 'hashref' => {
- #( freesidestatus IS NULL OR freesidestatus = '' )
- 'freesidestatus' => '',
- },
- 'extra_sql' => $charged_or_src,
-
- );
-
+ my $self = shift;
+ my $psearch = $self->psearch_cdrs(@_);
+ qsearch ( $psearch->{query} )
}
# sub radius_groups has moved to svc_Radius_Mixin
=over 4
-=item send_email
-
-This is the FS::svc_acct job-queue-able version. It still uses
-FS::Misc::send_email under-the-hood.
-
-=cut
-
-sub send_email {
- my %opt = @_;
-
- eval "use FS::Misc qw(send_email)";
- die $@ if $@;
-
- $opt{mimetype} ||= 'text/plain';
- $opt{mimetype} .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/;
-
- my $error = send_email(
- 'from' => $opt{from},
- 'to' => $opt{to},
- 'subject' => $opt{subject},
- 'content-type' => $opt{mimetype},
- 'body' => [ map "$_\n", split("\n", $opt{body}) ],
- );
- die $error if $error;
-}
-
=item check_and_rebuild_fuzzyfiles
=cut
my $error = $svc_acct->replace;
die $error if $error; # email next time, i guess
- if ( $warning_template ) {
- eval "use FS::Misc qw(send_email)";
- die $@ if $@;
+ if ( $warning_msgnum ) {
- my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
- my $cust_main = $cust_pkg->cust_main;
+ my $msg_template = qsearchs('msg_template',{ msgnum => $warning_msgnum });
+ die "Could not load template for threshold_warning_msgnum ($warning_msgnum)" unless $msg_template;
+
+ my $cust_main = $svc_acct->cust_svc->cust_pkg->cust_main;
- my $to = join(', ', grep { $_ !~ /^(POST|FAX)$/ }
- $cust_main->invoicing_list,
- ($opt{'to'} ? $opt{'to'} : ())
- );
-
- my $mimetype = $warning_mimetype;
- $mimetype .= '; charset="iso-8859-1"' unless $opt{mimetype} =~ /charset/;
-
- my $body = $warning_template->fill_in( HASH => {
- 'custnum' => $cust_main->custnum,
- 'username' => $svc_acct->username,
- 'password' => $svc_acct->_password,
- 'first' => $cust_main->first,
- 'last' => $cust_main->getfield('last'),
- 'pkg' => $cust_pkg->part_pkg->pkg,
- 'column' => $opt{'column'},
- 'amount' => $opt{'column'} =~/bytes/
- ? FS::UI::bytecount::display_bytecount($svc_acct->getfield($opt{'column'}))
- : $svc_acct->getfield($opt{'column'}),
- 'threshold' => $opt{'column'} =~/bytes/
- ? FS::UI::bytecount::display_bytecount($threshold)
- : $threshold,
- } );
-
-
- my $error = send_email(
- 'from' => $warning_from,
- 'to' => $to,
- 'subject' => $warning_subject,
- 'content-type' => $mimetype,
- 'body' => [ map "$_\n", split("\n", $body) ],
+ my $to = join(', ', $cust_main->invoicing_list_emailonly );
+
+ my $error = $msg_template->send(
+ cust_main => $cust_main,
+ object => $svc_acct,
+ to => $to,
+ substitutions => {
+ # have to override these, because we changed threshold above
+ 'column' => $opt{'column'},
+ 'amount' => $opt{'column'} =~/bytes/
+ ? FS::UI::bytecount::display_bytecount($svc_acct->getfield($opt{'column'}))
+ : $svc_acct->getfield($opt{'column'}),
+ 'threshold' => $opt{'column'} =~/bytes/
+ ? FS::UI::bytecount::display_bytecount($threshold)
+ : $threshold,
+ },
);
- die $error if $error;
+
+ die "Error sending threshold warning email: $error" if $error;
+
}
}else{
die "unknown op: " . $opt{'op'};