diff options
Diffstat (limited to 'FS/bin')
-rw-r--r-- | FS/bin/freeside-adduser | 57 | ||||
-rwxr-xr-x | FS/bin/freeside-apply-credits | 21 | ||||
-rwxr-xr-x | FS/bin/freeside-bill | 128 | ||||
-rwxr-xr-x | FS/bin/freeside-daily | 90 | ||||
-rwxr-xr-x | FS/bin/freeside-email | 61 | ||||
-rwxr-xr-x | FS/bin/freeside-overdue | 193 | ||||
-rw-r--r-- | FS/bin/freeside-queued | 174 | ||||
-rw-r--r-- | FS/bin/freeside-setinvoice | 42 |
8 files changed, 766 insertions, 0 deletions
diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser new file mode 100644 index 000000000..9d424634b --- /dev/null +++ b/FS/bin/freeside-adduser @@ -0,0 +1,57 @@ +#!/usr/bin/perl -w +# +# $Id: freeside-adduser,v 1.4 2002-02-06 14:58:05 ivan Exp $ + +use strict; +use vars qw($opt_h $opt_c $opt_s); +use Getopt::Std; + +my $FREESIDE_CONF = "/usr/local/etc/freeside"; + +getopts("ch:s:"); +die &usage if $opt_c && ! $opt_h; +my $user = shift or die &usage; + +if ( $opt_h ) { + my @args = ( 'htpasswd' ); + push @args, '-c' if $opt_c; + push @args, $opt_h, $user; + system(@args) == 0 or die "htpasswd failed: $?"; +} + +my $secretfile = $opt_s || 'secrets'; + +open(MAPSECRETS,">>$FREESIDE_CONF/mapsecrets") + or die "can't open $FREESIDE_CONF/mapsecrets: $!"; +print MAPSECRETS "$user $secretfile\n"; +close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!"; + +sub usage { + die "Usage:\n\n freeside-adduser [ -h htpasswd_file [ -c ] ] [ -s secretfile ] username" +} + +=head1 NAME + +freeside-adduser - Command line interface to add (freeside) users. + +=head1 SYNOPSIS + + freeside-adduser [ -h htpasswd_file [ -c ] ] [ -s secretfile ] username + +=head1 DESCRIPTION + +Adds a user to the Freeside billing system. This is for adding users (internal +sales/tech folks) to the web interface, not for adding customer accounts. + + -h: Also call htpasswd for this user with the given filename + + -c: Passed to htpasswd + + -s: Specify an alternate secret file + +=head1 SEE ALSO + +L<htpasswd>, base Freeside documentation + +=cut + diff --git a/FS/bin/freeside-apply-credits b/FS/bin/freeside-apply-credits new file mode 100755 index 000000000..ea6a7bdd0 --- /dev/null +++ b/FS/bin/freeside-apply-credits @@ -0,0 +1,21 @@ +#!/usr/bin/perl -Tw + +use strict; +use vars qw( $user $cust_main @customers ); +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch); +use FS::cust_main; + +$user = shift or die &usage; +&adminsuidsetup( $user ); + +my @customers = qsearch('cust_main', {} ); +die "No customers" unless (scalar(@customers) > 0); + +foreach $cust_main (@customers) { + print "Applying credits for customer #". $cust_main->custnum; + $cust_main->apply_credits; +} + + + diff --git a/FS/bin/freeside-bill b/FS/bin/freeside-bill new file mode 100755 index 000000000..49ad4a768 --- /dev/null +++ b/FS/bin/freeside-bill @@ -0,0 +1,128 @@ +#!/usr/bin/perl -w +# don't take any world-facing input +#!/usr/bin/perl -Tw + +use strict; +use Fcntl qw(:flock); +use Date::Parse; +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch qsearchs); +use FS::cust_main; + +&untaint_argv; #what it sounds like (eww) +use vars qw($opt_a $opt_c $opt_d $opt_p); +getopts("acd:p"); +my $user = shift or die &usage; + +adminsuidsetup $user; + +my %bill_only = map { $_ => 1 } ( + @ARGV ? @ARGV : ( map $_->custnum, qsearch('cust_main', {} ) ) +); + +#we're at now now (and later). +my($time)= $opt_d ? str2time($opt_d) : $^T; + +# find packages w/ bill < time && cancel != '', and create corresponding +# customer objects + +my($cust_main,%saw); +foreach $cust_main ( + map { + unless ( exists $saw{ $_->custnum } && defined $saw{ $_->custnum} ) { + $saw{ $_->custnum } = 0; # to avoid 'use of uninitialized value' errors + } + if ( + ( $opt_a || ( ( $_->getfield('bill') || 0 ) <= $time ) ) + && $bill_only{ $_->custnum } + && !$saw{ $_->custnum }++ + ) { + qsearchs('cust_main',{'custnum'=> $_->custnum } ); + } else { + (); + } + } ( qsearch('cust_pkg', { 'cancel' => '' }), + qsearch('cust_pkg', { 'cancel' => 0 }), + ) +) { + + # and bill them + + print "Billing customer #" . $cust_main->getfield('custnum') . "\n"; + + my($error); + + $error=$cust_main->bill('time'=>$time); + warn "Error billing, customer #" . $cust_main->getfield('custnum') . + ":" . $error if $error; + + if ($opt_p) { + $cust_main->apply_payments; + $cust_main->apply_credits; + } + + if ($opt_c) { + $error=$cust_main->collect( 'invoice_time' => $time); + warn "Error collecting from customer #" . $cust_main->custnum. ":$error" + if $error; + + #sleep 1; + } + +} + +# subroutines + +sub untaint_argv { + foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV + #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + # Date::Parse + $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + $ARGV[$_]=$1; + } +} + +sub usage { + die "Usage:\n\n freeside-bill [ -c [ -p ] ] [ -d 'date' ] user [ custnum custnum ... ]\n"; +} + +=head1 NAME + +freeside-bill - Command line (crontab, script) interface to customer billing. + +=head1 SYNOPSIS + + freeside-bill [ -c [ -p ] [ -a ] ] [ -d 'date' ] user [ custnum custnum ... ] + +=head1 DESCRIPTION + +This script is deprecated in 1.4.0. You should use freeside-daily instead. + +Bills customers. Searches for customers who are due for billing and calls +the bill and collect methods of a cust_main object. See L<FS::cust_main>. + + -c: Turn on collecting (you probably want this). + + -p: Apply unapplied payments and credits before collecting (you probably want + this too) + + -a: Call collect even if there isn't a new invoice (probably a bad idea for + daily use) + + -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with, + but be careful. + +user: From the mapsecrets file - see config.html from the base documentation + +custnum: if one or more customer numbers are specified, only bills those +customers. Otherwise, bills all customers. + +=head1 BUGS + +=head1 SEE ALSO + +L<freeside-daily>, L<FS::cust_main>, config.html from the base documentation + +=cut + diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily new file mode 100755 index 000000000..8d839cb21 --- /dev/null +++ b/FS/bin/freeside-daily @@ -0,0 +1,90 @@ +#!/usr/bin/perl -w + +use strict; +use Fcntl qw(:flock); +use Date::Parse; +use Getopt::Std; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch qsearchs); +use FS::cust_main; + +&untaint_argv; #what it sounds like (eww) +use vars qw($opt_d); +getopts("d:"); +my $user = shift or die &usage; + +adminsuidsetup $user; + +my @cust_main = @ARGV + ? map { qsearchs('cust_main', { custnum => $_ } ) } @ARGV + : qsearch('cust_main', {} ) +; + +#we're at now now (and later). +my($time)= $opt_d ? str2time($opt_d) : $^T; + +my($cust_main,%saw); +foreach $cust_main ( @cust_main ) { + + my $error; + + $error = $cust_main->bill( 'time' => $time ); + warn "Error billing, custnum ". $cust_main->custnum. ": $error" if $error; + + $cust_main->apply_payments; + $cust_main->apply_credits; + + $error=$cust_main->collect( 'invoice_time' => $time ); + warn "Error collecting, custnum". $cust_main->custnum. ": $error" if $error; + +} + +# subroutines + +sub untaint_argv { + foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV + #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + # Date::Parse + $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + $ARGV[$_]=$1; + } +} + +sub usage { + die "Usage:\n\n freeside-daily [ -d 'date' ] user [ custnum custnum ... ]\n"; +} + +=head1 NAME + +freeside-daily - Run daily billing and invoice collection events. + +=head1 SYNOPSIS + + freeside-daily [ -d 'date' ] user [ custnum custnum ... ] + +=head1 DESCRIPTION + +Bills customers and runs invoice collection events. Should be run from +crontab daily. + +This script replaces freeside-bill from 1.3.1. + +Bills customers. Searches for customers who are due for billing and calls +the bill and collect methods of a cust_main object. See L<FS::cust_main>. + + -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with, + but be careful. + +user: From the mapsecrets file - see config.html from the base documentation + +custnum: if one or more customer numbers are specified, only bills those +customers. Otherwise, bills all customers. + +=head1 BUGS + +=head1 SEE ALSO + +L<FS::cust_main>, config.html from the base documentation + +=cut + diff --git a/FS/bin/freeside-email b/FS/bin/freeside-email new file mode 100755 index 000000000..c7ff41114 --- /dev/null +++ b/FS/bin/freeside-email @@ -0,0 +1,61 @@ +#!/usr/bin/perl -Tw + +use strict; +use FS::UID qw(adminsuidsetup); +use FS::Conf; +use FS::Record qw(qsearch); +use FS::svc_acct; + +&untaint_argv; #what it sounds like (eww) +my $user = shift or die &usage; + +adminsuidsetup $user; + +my $conf = new FS::Conf; +my $domain = $conf->config('domain'); + +my @svc_acct = qsearch('svc_acct', {}); +my @usernames = map $_->username, @svc_acct; +my @emails = map "$_\@$domain", @usernames; + +print join("\n", @emails), "\n"; + +# subroutines + +sub untaint_argv { + foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV + #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + # Date::Parse + $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + $ARGV[$_]=$1; + } +} + +sub usage { + die "Usage:\n\n freeside-email user\n"; +} + +=head1 NAME + +freeside-email - Prints email addresses of all users on STDOUT + +=head1 SYNOPSIS + + freeside-email user + +=head1 DESCRIPTION + +Prints the email addresses of all customers on STDOUT, separated by newlines. + +user: From the mapsecrets file - see config.html from the base documentation + +=head1 VERSION + +$Id: freeside-email,v 1.1 2001-05-15 07:52:34 ivan Exp $ + +=head1 BUGS + +=head1 SEE ALSO + +=cut + diff --git a/FS/bin/freeside-overdue b/FS/bin/freeside-overdue new file mode 100755 index 000000000..db99e62b4 --- /dev/null +++ b/FS/bin/freeside-overdue @@ -0,0 +1,193 @@ +#!/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 + +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-queued b/FS/bin/freeside-queued new file mode 100644 index 000000000..87e3cb422 --- /dev/null +++ b/FS/bin/freeside-queued @@ -0,0 +1,174 @@ +#!/usr/bin/perl -w + +use strict; +use vars qw( $log_file $sigterm $sigint ); +use subs qw( _die _logmsg ); +use Fcntl qw(:flock); +use POSIX qw(setsid); +use Date::Format; +use IO::File; +use FS::UID qw(adminsuidsetup forksuidsetup driver_name); +use FS::Record qw(qsearchs); +use FS::queue; + +# no autoloading just yet +use FS::cust_main; +use FS::svc_acct; +use Net::SSH; + +my $pid_file = '/var/run/freeside-queued.pid'; + +my $user = shift or die &usage; + +&daemonize; + +sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; } +$SIG{CHLD} = \&REAPER; + + $sigterm = 0; + $sigint = 0; +$SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $sigint++; }; +$SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $sigterm++; }; + +$> = $FS::UID::freeside_uid unless $>; +$< = $>; +$ENV{HOME} = (getpwuid($>))[7]; #for ssh +adminsuidsetup $user; + +$log_file = "/usr/local/etc/freeside/queuelog.". $FS::UID::datasrc; + +$SIG{__DIE__} = \&_die; +$SIG{__WARN__} = \&_logmsg; + +warn "freeside-queued starting\n"; + +while (1) { + + my $job = qsearchs( + 'queue', + { 'status' => 'new' }, + '', + driver_name =~ /^mysql$/i + ? 'ORDER BY jobnum LIMIT 1 FOR UPDATE' + : 'ORDER BY jobnum FOR UPDATE LIMIT 1' + ) or do { + sleep 5; + next; + }; + + my %hash = $job->hash; + $hash{'status'} = 'locked'; + my $ljob = new FS::queue ( \%hash ); + my $error = $ljob->replace($job); + die $error if $error; + + my @args = $ljob->args; + + # number of children limit? + defined( my $pid = fork ) or do { + warn "WARNING: can't fork: $!\n"; + my %hash = $job->hash; + $hash{'status'} = 'failed'; + my $ljob = new FS::queue ( \%hash ); + my $error = $ljob->replace($job); + die $error if $error; + }; + + unless ( $pid ) { #kid time + + #get new db handles + $FS::UID::dbh->{InactiveDestroy} = 1; + $FS::svc_acct::icradius_dbh->{InactiveDestroy} = 1 + if $FS::svc_acct::icradius_dbh; + forksuidsetup($user); + + my $eval = "&". $ljob->job. '(@args);'; + warn "running $eval"; + eval $eval; + if ( $@ ) { + warn "job $eval failed"; + my %hash = $ljob->hash; + $hash{'status'} = 'failed'; + my $fjob = new FS::queue( \%hash ); + my $error = $fjob->replace($ljob); + die $error if $error; + } else { + $ljob->delete; + } + + exit; + #end-of-kid + } + +} continue { + if ( $sigterm ) { + warn "received TERM signal; exiting\n"; + exit; + } + if ( $sigint ) { + warn "received INT signal; exiting\n"; + exit; + } +} + +sub usage { + die "Usage:\n\n freeside-queued user\n"; +} + +sub _die { + my $msg = shift; + unlink $pid_file if -e $pid_file; + _logmsg($msg); +} + +sub _logmsg { + chomp( my $msg = shift ); + my $log = new IO::File ">>$log_file"; + flock($log, LOCK_EX); + seek($log, 0, 2); + print $log "[". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n"; + flock($log, LOCK_UN); + close $log; +} + +sub daemonize { + + chdir "/" or die "Can't chdir to /: $!"; + open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; + defined(my $pid = fork) or die "Can't fork: $!"; + if ( $pid ) { + print "freeside-queued started with pid $pid\n"; #logging to $log_file\n"; + exit unless $pid_file; + my $pidfh = new IO::File ">$pid_file" or exit; + print $pidfh "$pid\n"; + exit; + } + open STDOUT, '>/dev/null' + or die "Can't write to /dev/null: $!"; + setsid or die "Can't start a new session: $!"; + open STDERR, '>&STDOUT' or die "Can't dup stdout: $!"; + +} + +=head1 NAME + +freeside-queued - Job queue daemon + +=head1 SYNOPSIS + + freeside-queued user + +=head1 DESCRIPTION + +Job queue daemon. Should be running at all times. + +user: from the mapsecrets file - see config.html from the base documentation + +=head1 VERSION + +=head1 BUGS + +=head1 SEE ALSO + +=cut + diff --git a/FS/bin/freeside-setinvoice b/FS/bin/freeside-setinvoice new file mode 100644 index 000000000..708e2fa30 --- /dev/null +++ b/FS/bin/freeside-setinvoice @@ -0,0 +1,42 @@ +#!/usr/bin/perl + +use strict; +use FS::UID qw(adminsuidsetup); +use FS::Conf; +use FS::Record qw(qsearch qsearchs); +use FS::cust_main; +use FS::svc_acct; + +&untaint_argv; #what it sounds like (eww) +my $user = shift or die &usage; + +adminsuidsetup $user; + +foreach my $cust_main ( + grep { ! scalar($_->invoicing_list) } + qsearch( 'cust_main', {} ) +) { + my @dest; + my @cust_pkg = $cust_main->ncancelled_pkgs; + foreach my $cust_pkg ( @cust_pkg ) { + foreach my $cust_svc ( $cust_pkg->cust_svc ) { + my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $cust_svc->svcnum } ); + push @dest, $svc_acct->svcnum if $svc_acct; + } + } + push @dest, 'POST' unless @dest; + $cust_main->invoicing_list(\@dest); +} + +sub untaint_argv { + foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV + $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + $ARGV[$_]=$1; + } +} + +sub usage { + die "Usage:\n\n freeside-setinvoice user\n"; +} + + |