faster (cached) fuzzy searches
[freeside.git] / FS / bin / freeside-bill
1 #!/usr/bin/perl -w
2 # don't take any world-facing input
3 #!/usr/bin/perl -Tw
4
5 use strict;
6 use Fcntl qw(:flock);
7 use Date::Parse;
8 use Getopt::Std;
9 use FS::UID qw(adminsuidsetup);
10 use FS::Record qw(qsearch qsearchs);
11 use FS::cust_main;
12
13 &untaint_argv;  #what it sounds like  (eww)
14 use vars qw($opt_a $opt_c $opt_i $opt_d);
15 getopts("acid:");
16 my $user = shift or die &usage;
17
18 adminsuidsetup $user;
19
20 my %bill_only = map { $_ => 1 } (
21   @ARGV ? @ARGV : ( map $_->custnum, qsearch('cust_main', {} ) )
22 );
23
24 #we're at now now (and later).
25 my($time)= $main::opt_d ? str2time($main::opt_d) : $^T;
26
27 # find packages w/ bill < time && cancel != '', and create corresponding
28 # customer objects
29
30 my($cust_main,%saw);
31 foreach $cust_main (
32   map {
33     unless ( exists $saw{ $_->custnum } && defined $saw{ $_->custnum} ) {
34       $saw{ $_->custnum } = 0; # to avoid 'use of uninitialized value' errors
35     }
36     if (
37       ( $main::opt_a || ( ( $_->getfield('bill') || 0 ) <= $time ) )
38       && $bill_only{ $_->custnum }
39       && !$saw{ $_->custnum }++
40     ) {
41       qsearchs('cust_main',{'custnum'=> $_->custnum } );
42     } else {
43       ();
44     }
45   } ( qsearch('cust_pkg', { 'cancel' => '' }),
46       qsearch('cust_pkg', { 'cancel' => 0  }),
47     )
48 ) {
49
50   # and bill them
51
52   print "Billing customer #" . $cust_main->getfield('custnum') . "\n";
53
54   my($error);
55
56   $error=$cust_main->bill('time'=>$time);
57   warn "Error billing,  customer #" . $cust_main->getfield('custnum') . 
58     ":" . $error if $error;
59
60   if ($main::opt_p) {
61     $cust_main->apply_payments;
62     $error=$cust_main->apply_credits;
63   }
64
65   if ($main::opt_c) {
66     $error=$cust_main->collect('invoice_time'=>$time,
67                                'batch_card' => $main::opt_i ? 'no' : 'yes',
68                               );
69     warn "Error collecting from customer #" . $cust_main->gcustnum.  ":$error"
70       if $error;
71
72     #sleep 1;
73   }
74
75 }
76
77 # subroutines
78
79 sub untaint_argv {
80   foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
81     #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
82     # Date::Parse
83     $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\"";
84     $ARGV[$_]=$1;
85   }
86 }
87
88 sub usage {
89   die "Usage:\n\n  freeside-bill [ -c [ i ] ] [ -d 'date' ] [ -b ] user\n";
90 }
91
92 =head1 NAME
93
94 freeside-bill - Command line (crontab, script) interface to customer billing.
95
96 =head1 SYNOPSIS
97
98   freeside-bill [ -c [ -p ] [ -a ] [ -i ] ] [ -d 'date' ] user [ custnum custnum ... ]
99
100 =head1 DESCRIPTION
101
102 Bills customers.  Searches for customers who are due for billing and calls
103 the bill and collect methods of a cust_main object.  See L<FS::cust_main>.
104
105   -c: Turn on collecting (you probably want this).
106
107   -p: Apply unapplied payments and credits before collecting (you probably want
108       this too)
109
110   -a: Call collect even if there isn't a new invoice (probably a bad idea for
111       daily use)
112
113   -i: real-time billing (as opposed to batch billing).  only relevant
114       for credit cards.
115
116   -d: Pretend it's 'date'.  Date is in any format Date::Parse is happy with,
117       but be careful.
118
119 user: From the mapsecrets file - see config.html from the base documentation
120
121 custnum: if one or more customer numbers are specified, only bills those
122 customers.  Otherwise, bills all customers.
123
124 =head1 VERSION
125
126 $Id: freeside-bill,v 1.9 2001-09-11 00:08:18 ivan Exp $
127
128 =head1 BUGS
129
130 =head1 SEE ALSO
131
132 L<FS::cust_main>, config.html from the base documentation
133
134 =cut
135