1 package FS::Cron::bill;
4 use vars qw( @ISA @EXPORT_OK );
7 use DBI 1.33; #The "clone" method was added in DBI 1.33.
9 use FS::Record qw( qsearch qsearchs );
13 use FS::part_event_condition;
15 @ISA = qw( Exporter );
16 @EXPORT_OK = qw ( bill );
22 my $check_freq = $opt{'check_freq'} || '1d';
25 $debug = 1 if $opt{'v'};
26 $debug = $opt{'l'} if $opt{'l'};
28 $FS::cust_main::DEBUG = $debug;
29 #$FS::cust_event::DEBUG = $opt{'l'} if $opt{'l'};
33 push @search, "( cust_main.archived != 'Y' OR archived IS NULL )"; #disable?
35 push @search, "cust_main.payby = '". $opt{'p'}. "'"
37 push @search, "cust_main.agentnum = ". $opt{'a'}
42 join(' OR ', map "cust_main.custnum = $_", @ARGV ).
47 # generate where_pkg/where_event search clause
50 #we're at now now (and later).
51 my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T;
52 $time += $opt{'y'} * 86400 if $opt{'y'};
54 my $invoice_time = $opt{'n'} ? $^T : $time;
56 # select * from cust_main where
57 my $where_pkg = <<"END";
58 0 < ( select count(*) from cust_pkg
59 where cust_main.custnum = cust_pkg.custnum
60 and ( cancel is null or cancel = 0 )
61 and ( setup is null or setup = 0
62 or bill is null or bill <= $time
63 or ( expire is not null and expire <= $^T )
64 or ( adjourn is not null and adjourn <= $^T )
69 my $where_event = join(' OR ', map {
72 my $join = FS::part_event_condition->join_conditions_sql( $eventtable );
73 my $where = FS::part_event_condition->where_conditions_sql( $eventtable,
78 "0 < ( SELECT COUNT(*) FROM part_event $join
79 WHERE check_freq = '$check_freq'
80 AND eventtable = '$eventtable'
81 AND ( disabled = '' OR disabled IS NULL )
86 if ( $eventtable eq 'cust_main' ) {
89 "0 < ( SELECT COUNT(*) FROM $eventtable
90 WHERE cust_main.custnum = $eventtable.custnum
96 } FS::part_event->eventtables);
98 push @search, "( $where_pkg OR $where_event )";
101 # get a list of custnums
104 warn "searching for customers:\n". join("\n", @search). "\n"
105 if $opt{'v'} || $opt{'l'};
107 my $cursor_dbh = dbh->clone;
110 "DECLARE cron_bill_cursor CURSOR FOR ".
111 " SELECT custnum FROM cust_main WHERE ". join(' AND ', @search)
112 ) or die $cursor_dbh->errstr;
116 my $sth = $cursor_dbh->prepare('FETCH 100 FROM cron_bill_cursor'); #mysql?
118 $sth->execute or die $sth->errstr;
120 my @custnums = map { $_->[0] } @{ $sth->fetchall_arrayref };
122 last unless scalar(@custnums);
125 # for each custnum, queue or make one customer object and bill
126 # (one at a time, to reduce memory footprint with large #s of customers)
129 foreach my $custnum ( @custnums ) {
133 'invoice_time' => $invoice_time,
134 'actual_time' => $^T, #when freeside-bill was started
135 #(not, when using -m, freeside-queued)
136 'check_freq' => $check_freq,
137 'resetup' => ( $opt{'s'} ? $opt{'s'} : 0 ),
143 warn "DRY RUN: would add custnum $custnum for queued_bill\n";
146 #avoid queuing another job if there's one still waiting to run
147 next if qsearch( 'queue', { 'job' => 'FS::cust_main::queued_bill',
148 'custnum' => $custnum,
153 #add job to queue that calls bill_and_collect with options
154 my $queue = new FS::queue {
155 'job' => 'FS::cust_main::queued_bill',
157 'priority' => 99, #don't get in the way of provisioning jobs
159 my $error = $queue->insert( 'custnum'=>$custnum, %args );
165 my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } );
166 $cust_main->bill_and_collect( %args, 'debug' => $debug );
174 $cursor_dbh->commit or die $cursor_dbh->errstr;