summaryrefslogtreecommitdiff
path: root/FS/bin/freeside-daily
blob: 702d749e81abd0a721231614deb2aab2a7aa705b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/usr/bin/perl -w

use strict;
use Fcntl qw(:flock);
use Date::Parse;
use Getopt::Std;
use FS::UID qw(adminsuidsetup driver_name dbh datasrc);
use FS::Record qw(qsearch qsearchs dbdef);
use FS::Conf;
use FS::cust_main;

&untaint_argv;	#what it sounds like  (eww)
use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y);
getopts("p:a:d:vsy:");
my $user = shift or die &usage;

adminsuidsetup $user;

$FS::cust_main::DEBUG = 1 if $opt_v;

my %search = ();
$search{'payby'}    = $opt_p if $opt_p;
$search{'agentnum'} = $opt_a if $opt_a;

#we're at now now (and later).
my($time)= $opt_d ? str2time($opt_d) : $^T;
$time += $opt_y * 86400 if $opt_y;

# select * from cust_main where
my $where_pkg = <<"END";
  0 < ( select count(*) from cust_pkg
          where cust_main.custnum = cust_pkg.custnum
            and ( cancel is null or cancel = 0 )
            and (    setup is null or setup =  0
                  or bill  is null or bill  <= $time 
                  or ( expire is not null and expire <= $^T )
                )
      )
END

# or
my $where_bill_event = <<"END";
  0 < ( select count(*) from cust_bill
          where cust_main.custnum = cust_bill.custnum
            and 0 < charged
                    - ( select sum(amount) from cust_bill_pay
                          where cust_bill.invnum = cust_bill_pay.invnum )
                    - ( select sum(amount) from cust_credit_bill
                          where cust_bill.invnum = cust_credit_bill.invnum )
            and 0 < ( select count(*) from part_bill_event
                        where payby = cust_main.payby
                          and ( disabled is null or disabled = '' )
                          and seconds <= $time - cust_bill._date
                          and 0 = ( select count(*) from cust_bill_event
                                     where cust_bill.invnum = cust_bill_event.invnum
                                       and part_bill_event.eventpart = cust_bill_event.eventpart
                                       and status = 'done'
                                  )

                    )
      )
END

my $extra_sql = ( scalar(%search) ? ' AND ' : ' WHERE ' ). "$where_pkg OR $where_bill_event";

my @cust_main;
if ( @ARGV ) {
  @cust_main = map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV
} else {
  @cust_main = qsearch('cust_main', \%search, '', $extra_sql );
}
;

my($cust_main,%saw);
foreach $cust_main ( @cust_main ) {

  # $^T not $time because -d is for pre-printing invoices
  foreach my $cust_pkg (
    grep { $_->expire && $_->expire <= $^T } $cust_main->ncancelled_pkgs
  ) {
    my $error = $cust_pkg->cancel;
    warn "Error cancelling expired pkg ". $cust_pkg->pkgnum. " for custnum ".
         $cust_main->custnum. ": $error"
      if $error;
  }

  my $error = $cust_main->bill( 'time'    => $time,
                                'resetup' => $opt_s, );
  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;

}

if ( driver_name eq 'Pg' ) {
  dbh->{AutoCommit} = 1; #so we can vacuum
  foreach my $table ( dbdef->tables ) {
    my $sth = dbh->prepare("VACUUM ANALYZE $table") or die dbh->errstr;
    $sth->execute or die $sth->errstr;
  }
}

my $conf = new FS::Conf;
my $dest = $conf->config('dump-scpdest');
if ( $dest ) {
  datasrc =~ /dbname=([\w\.]+)$/ or die "unparsable datasrc ". datasrc;
  my $database = $1;
  eval "use Net::SCP qw(scp);";
  if ( driver_name eq 'Pg' ) {
    system("pg_dump $database >/var/tmp/$database.sql")
  } else {
    die "database dumps not yet supported for ". driver_name;
  }
  if ( $conf->config('dump-pgpid') ) {
    eval 'use GnuPG';
    my $gpg = new GnuPG;
    $gpg->encrypt( plaintext => "/var/tmp/$database.sql",
                   output    => "/var/tmp/$database.gpg",
                   recipient => $conf->config('dump-pgpid'),
                 );
    chmod 0600, '/var/tmp/$database.gpg';
    scp("/var/tmp/$database.gpg", $dest);
    unlink "/var/tmp/$database.gpg" or die $!;
  } else {
    chmod 0600, '/var/tmp/$database.sql';
    scp("/var/tmp/$database.sql", $dest);
  }
  unlink "/var/tmp/$database.sql" or die $!;
}

# 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' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] [ -v ] 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.

  -y: In addition to -d, which specifies an absolute date, the -y switch
      specifies an offset, in days.  For example, "-y 15" would increment the
      "pretend date" 15 days from whatever was specified by the -d switch
      (or now, if no -d switch was given).

  -p: Only process customers with the specified payby (I<CARD>, I<DCRD>, I<CHEK>, I<DCHK>, I<BILL>, I<COMP>, I<LECB>)

  -a: Only process customers with the specified agentnum

  -s: re-charge setup fees

  -v: enable debugging

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