diff options
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/Conf.pm | 8 | ||||
-rw-r--r-- | FS/FS/Record.pm | 4 | ||||
-rw-r--r-- | FS/FS/cust_main.pm | 177 | ||||
-rw-r--r-- | FS/FS/cust_svc.pm | 6 | ||||
-rw-r--r-- | FS/FS/part_export.pm | 30 | ||||
-rw-r--r-- | FS/FS/part_export/cp.pm | 56 | ||||
-rw-r--r-- | FS/FS/part_export/forward_shellcommands.pm | 24 | ||||
-rw-r--r-- | FS/FS/part_export/shellcommands.pm | 14 | ||||
-rw-r--r-- | FS/FS/part_export/sqlmail.pm | 3 | ||||
-rw-r--r-- | FS/FS/part_export/sqlradius.pm | 23 | ||||
-rw-r--r-- | FS/FS/part_export/sqlradius_withdomain.pm | 12 | ||||
-rw-r--r-- | FS/FS/part_pkg.pm | 11 | ||||
-rw-r--r-- | FS/FS/svc_acct.pm | 17 | ||||
-rw-r--r-- | FS/FS/svc_acct_pop.pm | 13 | ||||
-rw-r--r-- | FS/FS/svc_domain.pm | 2 | ||||
-rw-r--r-- | FS/MANIFEST | 30 | ||||
-rwxr-xr-x | FS/bin/freeside-overdue | 196 | ||||
-rwxr-xr-x | FS/bin/freeside-setup | 6 | ||||
-rwxr-xr-x | FS/bin/freeside-sqlradius-reset | 4 | ||||
-rw-r--r-- | FS/t/part_export-sqlradius_withdomain.t | 5 |
20 files changed, 302 insertions, 339 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 84b3c26ed..706ebe720 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1032,6 +1032,14 @@ httemplate/docs/config.html }, { + 'key' => 'radius-ip', + 'section' => '', + 'description' => 'RADIUS attribute for IP addresses.', + 'type' => 'select', + 'select_enum' => [ 'Framed-IP-Address', 'Framed-Address' ], + }, + + { 'key' => 'svc_acct-alldomains', 'section' => '', 'description' => 'Allow accounts to select any domain in the database. Normally accounts can only select from the domain set in the service definition and those purchased by the customer.', diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 9a724feac..02fd4e390 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -426,11 +426,11 @@ sub AUTOLOAD { $field =~ s/.*://; if ( defined($value) ) { confess "errant AUTOLOAD $field for $self (arg $value)" - unless $self->can('setfield'); + unless ref($self) && $self->can('setfield'); $self->setfield($field,$value); } else { confess "errant AUTOLOAD $field for $self (no args)" - unless $self->can('getfield'); + unless ref($self) && $self->can('getfield'); $self->getfield($field); } } diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 7e2ff388e..4302c504a 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -4,7 +4,12 @@ use strict; use vars qw( @ISA $conf $Debug $import ); use Safe; use Carp; -use Time::Local qw(timelocal_nocheck); +BEGIN { + eval "use Time::Local;"; + die "Time::Local version 1.05 required with Perl versions before 5.6" + if $] < 5.006 && !defined($Time::Local::VERSION); + eval "use Time::Local qw(timelocal timelocal_nocheck);"; +} use Date::Format; #use Date::Manip; use Business::CreditCard; @@ -301,23 +306,11 @@ sub insert { } } - #false laziness with sub replace - my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert($self->getfield('last'), $self->company); + $error = $self->queue_fuzzyfiles_update; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "queueing job (transaction rolled back): $error"; - } - - if ( defined $self->dbdef_table->column('ship_last') && $self->ship_last ) { - $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert($self->getfield('last'), $self->company); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "queueing job (transaction rolled back): $error"; - } + return "updating fuzzy search cache: $error"; } - #eslaf $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -520,34 +513,47 @@ sub replace { if ( $self->payby =~ /^(CARD|CHEK|LECB)$/ && grep { $self->get($_) ne $old->get($_) } qw(payinfo paydate payname) ) { - # card/check info has changed, want to retry realtime_card invoice events - #false laziness w/collect - foreach my $cust_bill_event ( - grep { - #$_->part_bill_event->plan eq 'realtime-card' - $_->part_bill_event->eventcode =~ - /^\$cust_bill\->realtime_(card|ach|lec)\(\);$/ - && $_->status eq 'done' - && $_->statustext - } - map { $_->cust_bill_event } - grep { $_->cust_bill_event } - $self->open_cust_bill - - ) { - my $error = $cust_bill_event->retry; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error scheduling invoice events for retry: $error"; - } + # card/check/lec info has changed, want to retry realtime_ invoice events + my $error = $self->retry_realtime; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - #eslaf + } + $error = $self->queue_fuzzyfiles_update; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "updating fuzzy search cache: $error"; } - #false laziness with sub insert + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + +=item queue_fuzzyfiles_update + +Used by insert & replace to update the fuzzy search cache + +=cut + +sub queue_fuzzyfiles_update { + my $self = shift; + + 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; + my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert($self->getfield('last'), $self->company); + my $error = $queue->insert($self->getfield('last'), $self->company); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; @@ -555,13 +561,12 @@ sub replace { if ( defined $self->dbdef_table->column('ship_last') && $self->ship_last ) { $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert($self->getfield('last'), $self->company); + $error = $queue->insert($self->getfield('ship_last'), $self->ship_company); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; } } - #eslaf $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -1271,7 +1276,10 @@ invoice_time - Use this time when deciding when to print invoices and late notices on those invoices. The default is now. It is specified as a UNIX timestamp; see L<perlfunc/"time">). Also see L<Time::Local> and L<Date::Parse> for conversion functions. -retry_card - Retry cards even when not scheduled by invoice events. +retry - Retry card/echeck/LEC transactions even when not scheduled by invoice +events. + +retry_card - Deprecated alias for 'retry' batch_card - This option is deprecated. See the invoice events web interface to control whether cards are batched or run against a realtime gateway. @@ -1305,26 +1313,16 @@ sub collect { return ''; } - if ( exists($options{'retry_card'}) && $options{'retry_card'} ) { - #false laziness w/replace - foreach my $cust_bill_event ( - grep { - #$_->part_bill_event->plan eq 'realtime-card' - $_->part_bill_event->eventcode eq '$cust_bill->realtime_card();' - && $_->status eq 'done' - && $_->statustext - } - map { $_->cust_bill_event } - grep { $_->cust_bill_event } - $self->open_cust_bill - ) { - my $error = $cust_bill_event->retry; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error scheduling invoice events for retry: $error"; - } + if ( exists($options{'retry_card'}) ) { + carp 'retry_card option passed to collect is deprecated; use retry'; + $options{'retry'} ||= $options{'retry_card'}; + } + if ( exists($options{'retry'}) && $options{'retry'} ) { + my $error = $self->retry_realtime; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - #eslaf } foreach my $cust_bill ( $self->cust_bill ) { @@ -1414,6 +1412,60 @@ sub collect { } +=item retry_realtime + +Schedules realtime credit card / electronic check / LEC billing events for +for retry. Useful if card information has changed or manual retry is desired. +The 'collect' method must be called to actually retry the transaction. + +Implementation details: For each of this customer's open invoices, changes +the status of the first "done" (with statustext error) realtime processing +event to "failed". + +=cut + +sub retry_realtime { + my $self = shift; + + 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; + + foreach my $cust_bill ( + grep { $_->cust_bill_event } + $self->open_cust_bill + ) { + my @cust_bill_event = + sort { $a->part_bill_event->seconds <=> $b->part_bill_event->seconds } + grep { + #$_->part_bill_event->plan eq 'realtime-card' + $_->part_bill_event->eventcode =~ + /\$cust_bill\->realtime_(card|ach|lec)$/ + && $_->status eq 'done' + && $_->statustext + } + $cust_bill->cust_bill_event; + next unless @cust_bill_event; + my $error = $cust_bill_event[0]->retry; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error scheduling invoice event for retry: $error"; + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + +} + =item realtime_bop METHOD AMOUNT [ OPTION => VALUE ... ] Runs a realtime credit card, ACH (electronic check) or phone bill transaction @@ -1504,6 +1556,10 @@ sub realtime_bop { ( $content{account_number}, $content{routing_code} ) = split('@', $self->payinfo); $content{bank_name} = $self->payname; + $content{account_type} = 'CHECKING'; + $content{account_name} = $payname; + $content{customer_org} = $self->company ? 'B' : 'I'; + $content{customer_ssn} = $self->ss; } elsif ( $method eq 'LEC' ) { $content{phone} = $self->payinfo; } @@ -2501,4 +2557,3 @@ L<FS::cust_main_invoice>, L<FS::UID>, schema.html from the base documentation. 1; - diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 8ac806519..c0cb6f4e9 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -1,7 +1,7 @@ package FS::cust_svc; use strict; -use vars qw( @ISA ); +use vars qw( @ISA $ignore_quantity ); use Carp qw( cluck ); use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_pkg; @@ -17,6 +17,8 @@ use FS::part_export; @ISA = qw( FS::Record ); +$ignore_quantity = 0; + sub _cache { my $self = shift; my ( $hashref, $cache ) = @_; @@ -229,7 +231,7 @@ sub check { }); return "Already ". scalar(@cust_svc). " ". $part_svc->svc. " services for pkgnum ". $self->pkgnum - if scalar(@cust_svc) >= $quantity; + if scalar(@cust_svc) >= $quantity && (!$ignore_quantity || !$quantity); } ''; #no error diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index 9a1b9d864..ff519969d 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -548,7 +548,7 @@ tie my %shellcommands_options, 'Tie::IxHash', #'machine' => { label=>'Remote machine' }, 'user' => { label=>'Remote username', default=>'root' }, 'useradd' => { label=>'Insert command', - default=>'useradd -d $dir -m -s $shell -u $uid -p $crypt_password $username' + default=>'useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username' #default=>'cp -pr /etc/skel $dir; chown -R $uid.$gid $dir' }, 'useradd_stdin' => { label=>'Insert command STDIN', @@ -564,7 +564,7 @@ tie my %shellcommands_options, 'Tie::IxHash', default=>'', }, 'usermod' => { label=>'Modify command', - default=>'usermod -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username', + default=>'usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username', #default=>'[ -d $old_dir ] && mv $old_dir $new_dir || ( '. # 'chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; '. # 'find . -depth -print | cpio -pdm $new_dir; '. @@ -576,6 +576,9 @@ tie my %shellcommands_options, 'Tie::IxHash', type =>'textarea', default=>'', }, + 'usermod_pwonly' => { label=>'Disallow username changes', + type =>'checkbox', + }, 'suspend' => { label=>'Suspension command', default=>'', }, @@ -613,6 +616,9 @@ tie my %shellcommands_withdomain_options, 'Tie::IxHash', type =>'textarea', #default=>"$_password\n$_password\n", }, + 'usermod_pwonly' => { label=>'Disallow username changes', + type =>'checkbox', + }, 'suspend' => { label=>'Suspension command', default=>'', }, @@ -685,6 +691,12 @@ tie my %sqlradius_options, 'Tie::IxHash', 'password' => { label=>'Database password' }, ; +tie my %sqlradius_withdomain_options, 'Tie::IxHash', + 'datasrc' => { label=>'DBI data source ' }, + 'username' => { label=>'Database username' }, + 'password' => { label=>'Database password' }, +; + tie my %cyrus_options, 'Tie::IxHash', 'server' => { label=>'IMAP server' }, 'username' => { label=>'Admin username' }, @@ -692,7 +704,6 @@ tie my %cyrus_options, 'Tie::IxHash', ; tie my %cp_options, 'Tie::IxHash', - 'host' => { label=>'Hostname' }, 'port' => { label=>'Port number' }, 'username' => { label=>'Username' }, 'password' => { label=>'Password' }, @@ -876,9 +887,9 @@ tie my %forward_shellcommands_options, 'Tie::IxHash', }, 'shellcommands_withdomain' => { - 'desc' => 'Real-time export via remote SSH.', + 'desc' => 'Real-time export via remote SSH (vpopmail, etc.).', 'options' => \%shellcommands_withdomain_options, - 'notes' => 'Run remote commands via SSH. username@domain (rather than just usernames) are considered unique (also see shellcommands). You probably want this if the commands you are running will accept a domain as a parameter, and will allow the same username with different domains. You will need to <a href="../docs/ssh.html">setup SSH for unattended operation</a>.<BR><BR>The following variables are available for interpolation (prefixed with <code>new_</code> or <code>old_</code> for replace operations): <UL><LI><code>$username</code><LI><code>$domain</code><LI><code>$_password</code><LI><code>$quoted_password</code> - unencrypted password quoted for the shell<LI><code>$crypt_password</code> - encrypted password<LI><code>$uid</code><LI><code>$gid</code><LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes)<LI><code>$dir</code> - home directory<LI><code>$shell</code><LI><code>$quota</code><LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.</UL>', + 'notes' => 'Run remote commands via SSH. username@domain (rather than just usernames) are considered unique (also see shellcommands). You probably want this if the commands you are running will accept a domain as a parameter, and will allow the same username with different domains. You will need to <a href="../docs/ssh.html">setup SSH for unattended operation</a>.<BR><BR>Use these buttons for some useful presets:<UL><LI><INPUT TYPE="button" VALUE="vpopmail" onClick=\'this.form.useradd.value = "/home/vpopmail/bin/vadduser $username\\\@$domain $quoted_password"; this.form.useradd_stdin.value = ""; this.form.userdel.value = "/home/vpopmail/bin/vdeluser $username\\\@$domain"; this.form.userdel_stdin.value=""; this.form.usermod.value = "/home/vpopmail/bin/vpasswd $new_username\\\@$new_domain $new_quoted_password"; this.form.usermod_stdin.value = ""; this.form.usermod_pwonly.checked = true;\'></UL>The following variables are available for interpolation (prefixed with <code>new_</code> or <code>old_</code> for replace operations): <UL><LI><code>$username</code><LI><code>$domain</code><LI><code>$_password</code><LI><code>$quoted_password</code> - unencrypted password quoted for the shell<LI><code>$crypt_password</code> - encrypted password<LI><code>$uid</code><LI><code>$gid</code><LI><code>$finger</code> - GECOS, already quoted for the shell (do not add additional quotes)<LI><code>$dir</code> - home directory<LI><code>$shell</code><LI><code>$quota</code><LI>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.</UL>', }, 'ldap' => { @@ -891,7 +902,14 @@ tie my %forward_shellcommands_options, 'Tie::IxHash', 'desc' => 'Real-time export to SQL-backed RADIUS (ICRADIUS, FreeRADIUS)', 'options' => \%sqlradius_options, 'nodomain' => 'Y', - 'notes' => 'Real-time export of radcheck, radreply and usergroup tables to any SQL database for <a href="http://www.freeradius.org/">FreeRADIUS</a> or <a href="http://radius.innercite.com/">ICRADIUS</a>. An existing RADIUS database will be updated in realtime, but you can use <a href="../docs/man/bin/freeside-sqlradius-reset">freeside-sqlradius-reset</a> to delete the entire RADIUS database and repopulate the tables from the Freeside database. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.23/DBI.pm">DBI documentation</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">documentation for your DBD</a> for the exact syntax of a DBI data source. If using <a href="http://www.freeradius.org/">FreeRADIUS</a> 0.5 or above, make sure your <b>op</b> fields are set to allow NULL values.', + 'notes' => 'Real-time export of radcheck, radreply and usergroup tables to any SQL database for <a href="http://www.freeradius.org/">FreeRADIUS</a> or <a href="http://radius.innercite.com/">ICRADIUS</a>. This export does not export RADIUS realms (see also sqlradius_withdomain). An existing RADIUS database will be updated in realtime, but you can use <a href="../docs/man/bin/freeside-sqlradius-reset">freeside-sqlradius-reset</a> to delete the entire RADIUS database and repopulate the tables from the Freeside database. See the <a href="http://search.cpan.org/doc/TIMB/DBI/DBI.pm">DBI documentation</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">documentation for your DBD</a> for the exact syntax of a DBI data source.', + }, + + 'sqlradius_withdomain' => { + 'desc' => 'Real-time export to SQL-backed RADIUS (ICRADIUS, FreeRADIUS) with realms', + 'options' => \%sqlradius_withdomain_options, + 'nodomain' => '', + 'notes' => 'Real-time export of radcheck, radreply and usergroup tables to any SQL database for <a href="http://www.freeradius.org/">FreeRADIUS</a> or <a href="http://radius.innercite.com/">ICRADIUS</a>. This export exports domains to RADIUS realms (see also sqlradius). An existing RADIUS database will be updated in realtime, but you can use <a href="../docs/man/bin/freeside-sqlradius-reset">freeside-sqlradius-reset</a> to delete the entire RADIUS database and repopulate the tables from the Freeside database. See the <a href="http://search.cpan.org/doc/TIMB/DBI/DBI.pm">DBI documentation</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">documentation for your DBD</a> for the exact syntax of a DBI data source.', }, 'sqlmail' => { diff --git a/FS/FS/part_export/cp.pm b/FS/FS/part_export/cp.pm index d998c1d95..c4750dd5d 100644 --- a/FS/FS/part_export/cp.pm +++ b/FS/FS/part_export/cp.pm @@ -10,10 +10,10 @@ sub rebless { shift; } sub _export_insert { my( $self, $svc_acct ) = (shift, shift); $self->cp_queue( $svc_acct->svcnum, 'create_mailbox', - Mailbox => $svc_acct->username, - Password => $svc_acct->_password, - Workgroup => $self->option('workgroup'), - Domain => $svc_acct->domain, + 'Mailbox' => $svc_acct->username, + 'Password' => $svc_acct->_password, + 'Workgroup' => $self->option('workgroup'), + 'Domain' => $svc_acct->domain, ); } @@ -30,8 +30,30 @@ sub _export_replace { sub _export_delete { my( $self, $svc_acct ) = (shift, shift); $self->cp_queue( $svc_acct->svcnum, 'delete_mailbox', - Mailbox => $svc_acct->username, - Domain => $svc_acct->domain, + 'Mailbox' => $svc_acct->username, + 'Domain' => $svc_acct->domain, + ); +} + +sub _export_suspend { + my( $self, $svc_acct ) = (shift, shift); + $self->cp_queue( $svc_acct->svcnum, 'set_mailbox_status', + 'Mailbox' => $svc_acct->username, + 'Domain' => $svc_acct->domain, + 'OTHER' => 'T', + 'OTHER_SUSPEND' => 'T', + ); +} + +sub _export_unsuspend { + my( $self, $svc_acct ) = (shift, shift); + $self->cp_queue( $svc_acct->svcnum, 'set_mailbox_status', + 'Mailbox' => $svc_acct->username, + 'Domain' => $svc_acct->domain, + 'PAYMENT' => 'F', + 'OTHER' => 'F', + 'OTHER_SUSPEND' => 'F', + 'OTHER_BOUNCE' => 'F', ); } @@ -42,7 +64,7 @@ sub cp_queue { 'job' => 'FS::part_export::cp::cp_command', }; $queue->insert( - $self->option('host'), + $self->machine, $self->option('port'), $self->option('username'), $self->option('password'), @@ -69,20 +91,22 @@ sub cp_command { #subroutine, not method ); } - my $other = 'F'; + #my $other = 'F'; if ( $new_password =~ /^\*SUSPENDED\* (.*)$/ ) { $new_password = $1; - $other = 'T'; + # $other = 'T'; } - cp_command($host, $port, $username, $password, 'set_mailbox_status', - Domain => $domain, - Mailbox => $new_username, - Other => $other, - Other_Bounce => $other, - ); + #cp_command($host, $port, $username, $password, $login_domain, + # 'set_mailbox_status', + # Domain => $domain, + # Mailbox => $new_username, + # Other => $other, + # Other_Bounce => $other, + #); if ( $old_password ne $new_password ) { - cp_command($host, $port, $username, $password, 'change_mailbox', + cp_command($host, $port, $username, $password, $login_domain, + 'change_mailbox', Domain => $domain, Mailbox => $new_username, Password => $new_password, diff --git a/FS/FS/part_export/forward_shellcommands.pm b/FS/FS/part_export/forward_shellcommands.pm index 43d558a69..f6fcb6062 100644 --- a/FS/FS/part_export/forward_shellcommands.pm +++ b/FS/FS/part_export/forward_shellcommands.pm @@ -29,13 +29,13 @@ sub _export_command { ${$_} = $svc_forward->getfield($_) foreach $svc_forward->fields; } - my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $self->srcsvc } ); + my $svc_acct = $svc_forward->srcsvc_acct; $username = $svc_acct->username; $domain = $svc_acct->domain; - if ($self->dstsvc) { - $destination = $self->dstsvc_acct->email; + if ($svc_forward->dstsvc_acct) { + $destination = $svc_forward->dstsvc_acct->email; } else { - $destination = $self->dst; + $destination = $svc_forward->dst; } #done setting variables for the command @@ -59,22 +59,22 @@ sub _export_replace { ${"new_$_"} = $new->getfield($_) foreach $new->fields; } - my $old_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $self->srcsvc } ); + my $old_svc_acct = $old->srcsvc_acct; $old_username = $old_svc_acct->username; $old_domain = $old_svc_acct->domain; - if ($self->dstsvc) { - $old_destination = $self->dstsvc_acct->email; + if ($old->dstsvc_acct) { + $old_destination = $old->dstsvc_acct->email; } else { - $old_destination = $self->dst; + $old_destination = $old->dst; } - my $new_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $self->srcsvc } ); + my $new_svc_acct = $new->srcsvc_acct; $new_username = $new_svc_acct->username; $new_domain = $new_svc_acct->domain; - if ($self->dstsvc) { - $new_destination = $self->dstsvc_acct->email; + if ($new->dstsvc) { + $new_destination = $new->dstsvc_acct->email; } else { - $new_destination = $self->dst; + $new_destination = $new->dst; } #done setting variables for the command diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index f592a838a..edc944009 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -75,12 +75,24 @@ sub _export_replace { ${"new_$_"} = $new->getfield($_) foreach $new->fields; } $new_finger = shell_quote $new_finger; - $quoted_new__password = shell_quote $new__password; + $quoted_new__password = shell_quote $new__password; #old, wrong? + $new_quoted_password = shell_quote $new__password; #new, better? $old_domain = $old->domain; $new_domain = $new->domain; $new_crypt_password = ''; #surpress "used only once" warnings $new_crypt_password = crypt( $new->_password, $saltset[int(rand(64))].$saltset[int(rand(64))]); + if ( $self->option('usermod_pwonly') ) { + my $error = ''; + if ( $old_username ne $new_username ) { + $error ||= "can't change username"; + } + if ( $old_domain ne $new_domain ) { + $error ||= "can't change domain"; + } + return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')' + if $error; + } $self->shellcommands_queue( $new->svcnum, user => $self->option('user')||'root', host => $self->machine, diff --git a/FS/FS/part_export/sqlmail.pm b/FS/FS/part_export/sqlmail.pm index 64f72df07..8ccad3c7e 100644 --- a/FS/FS/part_export/sqlmail.pm +++ b/FS/FS/part_export/sqlmail.pm @@ -1,9 +1,10 @@ package FS::part_export::sqlmail; use vars qw(@ISA); +use Digest::MD5 qw(md5_hex); use FS::Record qw(qsearchs); use FS::part_export; -use Digest::MD5 qw(md5_hex); +use FS::svc_domain; @ISA = qw(FS::part_export); diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 60450ee63..8a8f9beba 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -8,6 +8,11 @@ use FS::part_export; sub rebless { shift; } +sub export_username { + my($self, $svc_acct) = (shift, shift); + $svc_acct->username; +} + sub _export_insert { my($self, $svc_acct) = (shift, shift); @@ -16,14 +21,14 @@ sub _export_insert { my %attrib = $svc_acct->$method(); next unless keys %attrib; my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert', - $table, $svc_acct->username, %attrib ); + $table, $self->export_username($svc_acct), %attrib ); return $err_or_queue unless ref($err_or_queue); } my @groups = $svc_acct->radius_groups; if ( @groups ) { my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'usergroup_insert', - $svc_acct->username, @groups ); + $self->export_username($svc_acct), @groups ); return $err_or_queue unless ref($err_or_queue); } ''; @@ -44,9 +49,9 @@ sub _export_replace { my $dbh = dbh; my $jobnum = ''; - if ( $old->username ne $new->username ) { + if ( $self->export_username($old) ne $self->export_username($new) ) { my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'rename', - $new->username, $old->username ); + $self->export_username($new), $self->export_username($old) ); unless ( ref($err_or_queue) ) { $dbh->rollback if $oldAutoCommit; return $err_or_queue; @@ -63,7 +68,7 @@ sub _export_replace { } keys %new ) { my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert', - $table, $new->username, %new ); + $table, $self->export_username($new), %new ); unless ( ref($err_or_queue) ) { $dbh->rollback if $oldAutoCommit; return $err_or_queue; @@ -80,7 +85,7 @@ sub _export_replace { my @del = grep { !exists $new{$_} } keys %old; if ( @del ) { my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'attrib_delete', - $table, $new->username, @del ); + $table, $self->export_username($new), @del ); unless ( ref($err_or_queue) ) { $dbh->rollback if $oldAutoCommit; return $err_or_queue; @@ -109,7 +114,7 @@ sub _export_replace { if ( @delgroups ) { my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_delete', - $new->username, @delgroups ); + $self->export_username($new), @delgroups ); unless ( ref($err_or_queue) ) { $dbh->rollback if $oldAutoCommit; return $err_or_queue; @@ -125,7 +130,7 @@ sub _export_replace { if ( @newgroups ) { my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'usergroup_insert', - $new->username, @newgroups ); + $self->export_username($new), @newgroups ); unless ( ref($err_or_queue) ) { $dbh->rollback if $oldAutoCommit; return $err_or_queue; @@ -147,7 +152,7 @@ sub _export_replace { sub _export_delete { my( $self, $svc_acct ) = (shift, shift); my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'delete', - $svc_acct->username ); + $self->export_username($svc_acct) ); ref($err_or_queue) ? '' : $err_or_queue; } diff --git a/FS/FS/part_export/sqlradius_withdomain.pm b/FS/FS/part_export/sqlradius_withdomain.pm new file mode 100644 index 000000000..1c8f38c9d --- /dev/null +++ b/FS/FS/part_export/sqlradius_withdomain.pm @@ -0,0 +1,12 @@ +package FS::part_export::sqlradius_withdomain; + +use vars qw(@ISA); +use FS::part_export::sqlradius; + +@ISA = qw(FS::part_export::sqlradius); + +sub export_username { + my($self, $svc_acct) = (shift, shift); + $svc_acct->email; +} + diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 60b0e01f9..6525864c4 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -267,11 +267,12 @@ SVCDB is specified and does not match the svcdb of the service definition, sub svcpart { my $self = shift; - my $svcdb = shift; - my @pkg_svc = $self->pkg_svc; - return '' if scalar(@pkg_svc) != 1 - || $pkg_svc[0]->quantity != 1 - || ( $svcdb && $pkg_svc[0]->part_svc->svcdb ne $svcdb ); + my $svcdb = scalar(@_) ? shift : ''; + my @pkg_svc = grep { + $_->quantity == 1 + && ( $svcdb eq $_->part_svc->svcdb || !$svcdb ) + } $self->pkg_svc; + return '' if scalar(@pkg_svc) != 1; $pkg_svc[0]->svcpart; } diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 497e59c94..5b8107fc8 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -9,7 +9,7 @@ use vars qw( @ISA $DEBUG $me $conf $username_uppercase $welcome_template $welcome_from $welcome_subject $welcome_mimetype $smtpmachine - $radius_password + $radius_password $radius_ip $dirhash @saltset @pw_set ); use Carp; @@ -68,6 +68,7 @@ $FS::UID::callback{'FS::svc_acct'} = sub { } $smtpmachine = $conf->config('smtpmachine'); $radius_password = $conf->config('radius-password') || 'Password'; + $radius_ip = $conf->config('radius-ip') || 'Framed-IP-Address'; }; @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' ); @@ -603,6 +604,8 @@ error, returns the error, otherwise returns false. Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>). +Calls any export-specific suspend hooks. + =cut sub suspend { @@ -627,6 +630,8 @@ an error, returns the error, otherwise returns false. Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>). +Calls any export-specific unsuspend hooks. + =cut sub unsuspend { @@ -783,12 +788,14 @@ sub check { $recref->{quota} = $1; unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) { - unless ( $recref->{slipip} eq '0e0' ) { + if ( $recref->{slipip} eq '' ) { + $recref->{slipip} = ''; + } elsif ( $recref->{slipip} eq '0e0' ) { + $recref->{slipip} = '0e0'; + } else { $recref->{slipip} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ or return "Illegal slipip: ". $self->slipip; $recref->{slipip} = $1; - } else { - $recref->{slipip} = '0e0'; } } @@ -860,7 +867,7 @@ sub radius_reply { ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) ); } grep { /^radius_/ && $self->getfield($_) } fields( $self->table ); if ( $self->slipip && $self->slipip ne '0e0' ) { - $reply{'Framed-IP-Address'} = $self->slipip; + $reply{$radius_ip} = $self->slipip; } %reply; } diff --git a/FS/FS/svc_acct_pop.pm b/FS/FS/svc_acct_pop.pm index 3c9ea0130..196ab7ebb 100644 --- a/FS/FS/svc_acct_pop.pm +++ b/FS/FS/svc_acct_pop.pm @@ -142,8 +142,7 @@ sub popselector { function popstate_changed(what) { state = what.options[what.selectedIndex].text; - for (var i = what.form.popnum.length;i > 0;i--) - what.form.popnum.options[i] = null; + what.form.popnum.options.length = 0 what.form.popnum.options[0] = new Option("", "", false, true); END @@ -167,7 +166,13 @@ END $text .= '</SELECT>'; #callback? return 3 html pieces? #'</TD><TD>'; $text .= qq!<SELECT NAME="popnum" SIZE=1><OPTION> !; - foreach my $pop ( @svc_acct_pop ) { + my @initial_select; + if ( scalar(@svc_acct_pop) > 100 ) { + @initial_select = qsearchs( 'svc_acct_pop', { 'popnum' => $popnum } ); + } else { + @initial_select = @svc_acct_pop; + } + foreach my $pop ( @initial_select ) { $text .= qq!<OPTION VALUE="!. $pop->popnum. '"'. ( ( $popnum && $pop->popnum == $popnum ) ? ' SELECTED' : '' ). ">". $pop->text; @@ -182,7 +187,7 @@ END =head1 VERSION -$Id: svc_acct_pop.pm,v 1.7 2002-04-10 13:42:48 ivan Exp $ +$Id: svc_acct_pop.pm,v 1.9 2003-07-04 01:37:46 ivan Exp $ =head1 BUGS diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index 2e8866a56..32b94563d 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -324,7 +324,7 @@ sub check { } #if ( $recref->{domain} =~ /^([\w\-\.]{1,22})\.(com|net|org|edu)$/ ) { - if ( $recref->{domain} =~ /^([\w\-]{1,22})\.(com|net|org|edu)$/ ) { + if ( $recref->{domain} =~ /^([\w\-]{1,63})\.(com|net|org|edu)$/ ) { $recref->{domain} = "$1.$2"; # hmmmmmmmm. } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)$/ ) { diff --git a/FS/MANIFEST b/FS/MANIFEST index 272b5b731..846f37310 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -3,31 +3,31 @@ MANIFEST MANIFEST.SKIP Makefile.PL README -bin/freeside-bill -bin/freeside-daily -bin/freeside-email -bin/freeside-queued bin/freeside-addoutsource bin/freeside-addoutsourceuser +bin/freeside-adduser +bin/freeside-apply-credits +bin/freeside-bill +bin/freeside-cc-receipts-report +bin/freeside-count-active-customers +bin/freeside-credit-report +bin/freeside-daily bin/freeside-deloutsource bin/freeside-deloutsourceuser -bin/freeside-apply-credits -bin/freeside-adduser bin/freeside-deluser -bin/freeside-setup -bin/freeside-setinvoice -bin/freeside-overdue +bin/freeside-email +bin/freeside-expiration-alerter +bin/freeside-queued bin/freeside-radgroup bin/freeside-receivables-report +bin/freeside-reexport bin/freeside-selfservice-server +bin/freeside-setinvoice +bin/freeside-setup bin/freeside-sqlradius-radacctd bin/freeside-sqlradius-reset bin/freeside-sqlradius-seconds bin/freeside-tax-report -bin/freeside-cc-receipts-report -bin/freeside-credit-report -bin/freeside-expiration-alerter -bin/freeside-reexport FS.pm FS/CGI.pm FS/InitHandler.pm @@ -130,6 +130,7 @@ t/Misc.t t/Record.t t/UID.t t/Msgcat.t +t/SearchCache.t t/cust_bill.t t/cust_bill_event.t t/cust_bill_pay.t @@ -146,6 +147,7 @@ t/cust_pay_batch.t t/cust_pkg.t t/cust_refund.t t/cust_svc.t +t/cust_tax_exempt.t t/domain_record.t t/nas.t t/part_bill_event.t @@ -190,6 +192,6 @@ t/svc_www.t t/type_pkgs.t t/queue.t t/queue_arg.t +t/queue_depend.t t/msgcat.t t/raddb.t -t/cust_tax_exempt.t diff --git a/FS/bin/freeside-overdue b/FS/bin/freeside-overdue deleted file mode 100755 index 116245f9c..000000000 --- a/FS/bin/freeside-overdue +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use vars qw( $days_to_pay $cust_main $cust_pkg - $cust_svc $svc_acct ); -use Getopt::Std; -use FS::cust_main; -use FS::cust_pkg; -use FS::cust_svc; -use FS::svc_acct; -use FS::Record qw(qsearch qsearchs); -use FS::UID qw(adminsuidsetup); - -&untaint_argv; -my %opt; -getopts('ed:qpl:scbyoi', \%opt); -my $user = shift or die &usage; - -adminsuidsetup $user; - -my $now = time; #eventually take a time option like freeside-bill -my ($sec,$min,$hour,$mday,$mon,$year) = - (localtime($now) )[0,1,2,3,4,5]; -$mon++; -$year += 1900; - -foreach $cust_main ( qsearch('cust_main',{} ) ) { - - my ( $eyear, $emon, $eday ) = ( 2037, 12, 31 ); - if ( $cust_main->paydate =~ /^(\d{4})\-(\d{1,2})\-(\d{1,2})$/ - && $cust_main->payby eq 'BILL') { - ( $eyear, $emon, $eday ) = ( $1, $2, $3 ); - } - - if ( ( $opt{d} - && $cust_main->balance_date(time - $opt{d} * 86400) > 0 - && qsearchs( 'cust_pkg', { 'custnum' => $cust_main->custnum, - 'susp' => "" } ) ) - || ( $opt{e} - && $cust_main->payby eq 'BILL' - && ( $eyear < $year - || ( $eyear == $year && $emon < $mon ) ) ) - ) { - - unless ( $opt{q} ) { - print $cust_main->custnum, "\t", - $cust_main->last, "\t", $cust_main->first, "\t", - $cust_main->balance_date(time-$opt{d} * 86400); - } - - if ( $opt{p} && ! grep { $_ eq 'POST' } $cust_main->invoicing_list ) { - print "\n\tAdding postal invoicing" unless $opt{q}; - my @invoicing_list = $cust_main->invoicing_list; - push @invoicing_list, 'POST'; - $cust_main->invoicing_list(\@invoicing_list); - } - - if ( $opt{l} ) { - print "\n\tCharging late fee of \$$opt{l}" unless $opt{q}; - my $error = $cust_main->charge($opt{l}, 'Late fee'); - # comment or plandata with info so we don't redo the same late fee every - # day - } - - foreach $cust_pkg ( qsearch( 'cust_pkg', - { 'custnum' => $cust_main->custnum } ) ) { - - if ($opt{s}) { - print "\n\tSuspending pkgnum " . $cust_pkg->pkgnum unless $opt{q}; - $cust_pkg->suspend; - } - - if ($opt{c}) { - print "\n\tCancelling pkgnum " . $cust_pkg->pkgnum unless $opt{q}; - $cust_pkg->cancel; - } - - } - - if ( $opt{b} ) { - print "\n\tBilling" unless $opt{q}; - my $error = $cust_main->bill('time'=>$now); - warn "Error billing, customer #" . $cust_main->custnum . - ":" . $error if $error; - } - - if ( $opt{y} ) { - print "\n\tApplying outstanding payments and credits" unless $opt{q}; - $cust_main->apply_payments; - $cust_main->apply_credits; - } - - if ( $opt{o} ) { - print "\n\tCollecting" unless $opt{q}; - my $error = $cust_main->collect( - 'invoice_time' => $now, - 'batch_card' => $opt{i} ? 'no' : 'yes', - 'force_print' => 'yes', - ); - warn "Error collecting from customer #" . $cust_main->custnum. ":$error" - if $error; - } - - print "\n" unless $opt{q}; - - } - -} - -sub untaint_argv { - foreach $_ ( $[ .. $#ARGV ) { - $ARGV[$_] =~ /^([\w\-\/\.]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; - $ARGV[$_]=$1; - } -} - -sub usage { - die "Usage:\n\n freeside-overdue [ -e ] [ -d days ] [ -q ] [ -p ] [ -l amount ] [ -s ] [ -c ] [ -b ] [ -y ] [ -o [ -i ] ] user\n"; -} - - -=head1 NAME - -freeside-overdue - Perform actions on overdue and/or expired accounts. - -=head1 SYNOPSIS - - freeside-overdue [ -e ] [ -d days ] [ -q ] [ -p ] [ -l amount ] [ -s ] [ -c ] [ -b ] [ -y ] [ -o [ -i ] ] user - -=head1 DESCRIPTION - -This script is deprecated in 1.4.0. You should use freeside-daily and invoice -events instead. - -Performs actions on overdue and/or expired accounts. - -Selection options (at least one selection option is required): - - -d: Customers with a balance due on invoices older than the supplied number - of days. Requires an integer argument. - - -e: Customers with a billing expiration date in the past. - -Action options: - - -q: Be quiet (by default, selected accounts are printed). - - -p: Add postal invoicing to the relevant customers. - - -l: Add a charge of the given amount to the relevant customers. - - -s: Suspend accounts. - - -c: Cancel accounts. - - -b: Bill customers (create invoices) - - -y: Apply unapplied payments and credits - - -o: Collect from customers (charge cards, print invoices) - - -i: real-time billing (as opposed to batch billing). only relevant - for credit cards. - - user: From the mapsecrets file - see config.html from the base documentation - -=head1 CRONTAB - -Example crontab entries: - -# suspend expired accounts -20 4 * * * freeside-overdue -e -s user - -# quietly add postal invoicing to customers over 30 days past due -20 4 * * * freeside-overdue -d 30 -p -q user - -# suspend accounts and charge a $10.23 fee for customers over 60 days past due -20 4 * * * freeside-overdue -d 60 -s -l 10.23 user - -# cancel accounts over 90 days past due -20 4 * * * freeside-overdue -d 90 -c user - -=head1 ORIGINAL AUTHORS - -Original disable-overdue version by mw/kwh: Mark W.? and Kristian Hoffmann ? - -Ivan seems to be turning it into the "do-everything" CLI. - -=head1 BUGS - -Hell now that this is the do-everything CLI it should have --longoptions - -=cut - -1; - diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index 518a2ad42..734744efe 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -373,7 +373,7 @@ sub tables_hash_hack { ], 'primary_key' => 'invnum', 'unique' => [], - 'index' => [ ['custnum'] ], + 'index' => [ ['custnum'], ['_date'] ], }, 'cust_bill_event' => { @@ -1074,7 +1074,7 @@ sub tables_hash_hack { 'addr_block' => { 'columns' => [ - 'blocknum', 'int', '', '', + 'blocknum', 'serial', '', '', 'routernum', 'int', '', '', 'ip_gateway', 'varchar', '', 15, 'ip_netmask', 'int', '', '', @@ -1086,7 +1086,7 @@ sub tables_hash_hack { 'part_sb_field' => { 'columns' => [ - 'sbfieldpart', 'int', '', '', + 'sbfieldpart', 'serial', '', '', 'svcpart', 'int', '', '', 'name', 'varchar', '', $char_d, 'length', 'int', '', '', diff --git a/FS/bin/freeside-sqlradius-reset b/FS/bin/freeside-sqlradius-reset index 9d3a6a700..74f90a582 100755 --- a/FS/bin/freeside-sqlradius-reset +++ b/FS/bin/freeside-sqlradius-reset @@ -12,7 +12,9 @@ adminsuidsetup $user; #my $machine = shift or die &usage; -my @exports = qsearch('part_export', { 'exporttype' => 'sqlradius' } ); +my @exports = qsearch('part_export', { exporttype=>'sqlradius' } ); +push @exports, qsearch('part_export', { exporttype=>'sqlradius_withdomain' } ); + foreach my $export ( @exports ) { my $icradius_dbh = DBI->connect( diff --git a/FS/t/part_export-sqlradius_withdomain.t b/FS/t/part_export-sqlradius_withdomain.t new file mode 100644 index 000000000..504bf679f --- /dev/null +++ b/FS/t/part_export-sqlradius_withdomain.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::part_export::sqlradius_withdomain; +$loaded=1; +print "ok 1\n"; |