5c0a5da594eb0e79c6eba5ecad728db4ef2fc1a9
[freeside.git] / FS / FS / Cron / bill.pm
1 package FS::Cron::bill;
2
3 use strict;
4 use vars qw( @ISA @EXPORT_OK );
5 use Exporter;
6 use Date::Parse;
7 use FS::UID qw(dbh);
8 use FS::Record qw(qsearchs);
9 use FS::cust_main;
10
11 @ISA = qw( Exporter );
12 @EXPORT_OK = qw ( bill );
13
14 sub bill {
15
16   my %opt = @_;
17
18   my $debug = 0;
19   $debug = 1 if $opt{'v'};
20   $debug = $opt{'l'} if $opt{'l'};
21  
22   $FS::cust_main::DEBUG = $debug;
23   #$FS::cust_event::DEBUG = $opt{'l'} if $opt{'l'};
24
25   my @search = ();
26
27   push @search, "cust_main.archived != 'Y' "; #disable?
28
29   push @search, "cust_main.payby    = '". $opt{'p'}. "'"
30     if $opt{'p'};
31   push @search, "cust_main.agentnum =  ". $opt{'a'}
32     if $opt{'a'};
33
34   if ( @ARGV ) {
35     push @search, "( ".
36       join(' OR ', map "cust_main.custnum = $_", @ARGV ).
37     " )";
38   }
39
40   ###
41   # generate where_pkg / where_bill_event search clause (1.7-style)
42   ###
43
44   #we're at now now (and later).
45   my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T;
46   $time += $opt{'y'} * 86400 if $opt{'y'};
47
48   my $invoice_time = $opt{'n'} ? $^T : $time;
49
50   # select * from cust_main where
51   my $where_pkg = <<"END";
52     0 < ( select count(*) from cust_pkg
53             where cust_main.custnum = cust_pkg.custnum
54               and ( cancel is null or cancel = 0 )
55               and (    setup is null or setup =  0
56                     or bill  is null or bill  <= $time 
57                     or ( expire is not null and expire <= $^T )
58                     or ( adjourn is not null and adjourn <= $^T )
59                   )
60         )
61 END
62   
63   # or
64   my $where_bill_event = <<"END";
65     0 < ( select count(*) from cust_bill
66             where cust_main.custnum = cust_bill.custnum
67               and 0 < charged
68                       - coalesce(
69                                   ( select sum(amount) from cust_bill_pay
70                                       where cust_bill.invnum = cust_bill_pay.invnum )
71                                   ,0
72                                 )
73                       - coalesce(
74                                   ( select sum(amount) from cust_credit_bill
75                                       where cust_bill.invnum = cust_credit_bill.invnum )
76                                   ,0
77                                 )
78               and 0 < ( select count(*) from part_bill_event
79                           where payby = cust_main.payby
80                             and ( disabled is null or disabled = '' )
81                             and seconds <= $time - cust_bill._date
82                             and 0 = ( select count(*) from cust_bill_event
83                                        where cust_bill.invnum = cust_bill_event.invnum
84                                          and part_bill_event.eventpart = cust_bill_event.eventpart
85                                          and status = 'done'
86                                     )
87   
88                       )
89         )
90 END
91   
92   push @search, "( $where_pkg OR $where_bill_event )";
93
94   ###
95   # get a list of custnums
96   ###
97
98   warn "searching for customers:\n". join("\n", @search). "\n"
99     if $opt{'v'} || $opt{'l'};
100
101   my $sth = dbh->prepare(
102     "SELECT custnum FROM cust_main".
103     " WHERE ". join(' AND ', @search)
104   ) or die dbh->errstr;
105
106   $sth->execute or die $sth->errstr;
107
108   my @custnums = map { $_->[0] } @{ $sth->fetchall_arrayref };
109
110   ###
111   # for each custnum, queue or make one customer object and bill
112   # (one at a time, to reduce memory footprint with large #s of customers)
113   ###
114   
115   foreach my $custnum ( @custnums ) {
116   
117     my %args = (
118         'time'         => $time,
119         'invoice_time' => $invoice_time,
120         'actual_time'  => $^T, #when freeside-bill was started
121                                #(not, when using -m, freeside-queued)
122         'resetup'      => ( $opt{'s'} ? $opt{'s'} : 0 ),
123     );
124
125     if ( $opt{'m'} ) {
126
127       #add job to queue that calls bill_and_collect with options
128       my $queue = new FS::queue {
129         'job'      => 'FS::cust_main::queued_bill',
130         'priority' => 99, #don't get in the way of provisioning jobs
131       };
132       my $error = $queue->insert( 'custnum'=>$custnum, %args );
133
134     } else {
135
136       my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } );
137       $cust_main->bill_and_collect( %args, 'debug' => $debug );
138
139     }
140
141   }
142
143 }
144
145 1;