don't fill up memory with objects for every customer being billed
[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 use FS::part_event;
11 use FS::part_event_condition;
12
13 @ISA = qw( Exporter );
14 @EXPORT_OK = qw ( bill );
15
16 sub bill {
17
18   my %opt = @_;
19
20   my $check_freq = $opt{'check_freq'} || '1d';
21
22   my $debug = 0;
23   $debug = 1 if $opt{'v'};
24   $debug = $opt{'l'} if $opt{'l'};
25  
26   $FS::cust_main::DEBUG = $debug;
27   #$FS::cust_event::DEBUG = $opt{'l'} if $opt{'l'};
28
29   my @search = ();
30
31   push @search, "cust_main.payby    = '". $opt{'p'}. "'"
32     if $opt{'p'};
33   push @search, "cust_main.agentnum =  ". $opt{'a'}
34     if $opt{'a'};
35
36   if ( @ARGV ) {
37     push @search, "( ".
38       join(' OR ', map "cust_main.custnum = $_", @ARGV ).
39     " )";
40   }
41
42   ###
43   # generate where_pkg/where_event search clause
44   ###
45
46   #we're at now now (and later).
47   my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T;
48   $time += $opt{'y'} * 86400 if $opt{'y'};
49
50   my $invoice_time = $opt{'n'} ? $^T : $time;
51
52   # select * from cust_main where
53   my $where_pkg = <<"END";
54     0 < ( select count(*) from cust_pkg
55             where cust_main.custnum = cust_pkg.custnum
56               and ( cancel is null or cancel = 0 )
57               and (    setup is null or setup =  0
58                     or bill  is null or bill  <= $time 
59                     or ( expire is not null and expire <= $^T )
60                     or ( adjourn is not null and adjourn <= $^T )
61                   )
62         )
63 END
64
65   my $where_event = join(' OR ', map {
66     my $eventtable = $_;
67
68     my $join  = FS::part_event_condition->join_conditions_sql(  $eventtable );
69     my $where = FS::part_event_condition->where_conditions_sql( $eventtable,
70                                                                 'time'=>$time,
71                                                               );
72
73     my $are_part_event = 
74       "0 < ( SELECT COUNT(*) FROM part_event $join
75                WHERE check_freq = '$check_freq'
76                  AND eventtable = '$eventtable'
77                  AND ( disabled = '' OR disabled IS NULL )
78                  AND $where
79            )
80       ";
81
82     if ( $eventtable eq 'cust_main' ) { 
83       $are_part_event;
84     } else {
85       "0 < ( SELECT COUNT(*) FROM $eventtable
86                WHERE cust_main.custnum = $eventtable.custnum
87                  AND $are_part_event
88            )
89       ";
90     }
91
92   } FS::part_event->eventtables);
93
94   push @search, "( $where_pkg OR $where_event )";
95
96   ###
97   # get a list of custnums
98   ###
99
100   warn "searching for customers:\n". join("\n", @search). "\n"
101     if $opt{'v'} || $opt{'l'};
102
103   my $sth = dbh->prepare(
104     "SELECT custnum FROM cust_main".
105     " WHERE ". join(' AND ', @search)
106   ) or die dbh->errstr;
107
108   $sth->execute or die $sth->errstr;
109
110   my @custnums = map { $_->[0] } @{ $sth->fetchall_arrayref };
111
112   ###
113   # for each custnum, queue or make one customer object and bill
114   # (one at a time, to reduce memory footprint with large #s of customers)
115   ###
116   
117   foreach my $custnum ( @custnums ) {
118
119     if ( $opt{'m'} ) {
120
121       #add job to queue that calls bill_and_collect with options
122         my $queue = new FS::queue {
123           'job'    => 'FS::cust_main::queued_bill',
124           'secure' => 'Y',
125         };
126         my $error = $queue->insert(
127         'custnum'      => $custnum,
128         'time'         => $time,
129         'invoice_time' => $invoice_time,
130         'check_freq'   => $check_freq,
131         'resetup'      => $opt{'s'} ? $opt{'s'} : 0,
132       );
133
134     } else {
135
136       my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } );
137
138       $cust_main->bill_and_collect(
139         'time'         => $time,
140         'invoice_time' => $invoice_time,
141         'check_freq'   => $check_freq,
142         'resetup'      => $opt{'s'},
143         'debug'        => $debug,
144       );
145
146     }
147
148   }
149
150 }