+ $log->info('finish');
+}
+
+# freeside-daily %opt:
+# -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).
+#
+# -n: When used with "-d" and/or "-y", specifies that invoices should be dated
+# with today's date, regardless of the pretend date used to pre-generate
+# the invoices.
+#
+# -a: Only process customers with the specified agentnum
+#
+# -v: enable debugging
+#
+# -l: debugging level
+
+=item bill_where
+
+Internal function. Returns a WHERE clause to select the set of customers who
+have actionable packages (no setup date, or bill date in the past, or expire
+or adjourn dates in the past) or events (does a complete where_conditions_sql
+scan).
+
+=cut
+
+sub bill_where {
+ my( %opt ) = @_;
+
+ my $time = $opt{'time'};
+ my $invoice_time = $opt{'invoice_time'};
+
+ my $check_freq = $opt{'check_freq'} || '1d';
+
+ my @search = ();
+
+ push @search, "( cust_main.archived != 'Y' OR archived IS NULL )"; #disable?
+
+ push @search, "cust_main.agentnum IN ( ". $opt{'a'}. " ) "
+ if $opt{'a'};
+
+ #it would be useful if i recognized $opt{g} / $not_pkgpart...
+
+ if ( @ARGV ) {
+ push @search, "( ".
+ join(' OR ', map "cust_main.custnum = $_", @ARGV ).
+ " )";
+ }
+
+ ###
+ # generate where_pkg/where_event search clause
+ ###
+
+ my $conf = new FS::Conf;
+ my $billtime = $conf->exists('next-bill-ignore-time') ? day_end($time) : $time;
+
+ # corresponds to perl checks in FS::cust_main::Billing sub bill
+ # ("bill setup" and "bill recurring fee")
+ # select * from cust_main where
+ my $where_pkg = <<"END";
+ EXISTS(
+ SELECT 1 FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart )
+ WHERE cust_main.custnum = cust_pkg.custnum
+ AND ( cancel IS NULL OR cancel = 0 )
+ AND ( ( ( cust_pkg.setup IS NULL OR cust_pkg.setup = 0 )
+ AND ( start_date IS NULL OR start_date = 0
+ OR ( start_date IS NOT NULL AND start_date <= $billtime )
+ )
+ )
+ OR ( freq != '0' AND ( bill IS NULL OR bill <= $billtime ) )
+ OR ( expire IS NOT NULL AND expire <= $^T )
+ OR ( adjourn IS NOT NULL AND adjourn <= $^T )
+ OR ( resume IS NOT NULL AND resume <= $^T )
+ )
+ )
+END
+
+ #some false laziness w/cust_main::Billing due_cust_event
+ my $where_event = join(' OR ', map {
+ my $eventtable = $_;
+
+ # joins and where clauses to test event conditions
+ my $join = FS::part_event_condition->join_conditions_sql( $eventtable,
+ 'time'=>$time );
+ my $where = FS::part_event_condition->where_conditions_sql( $eventtable,
+ 'time'=>$time,
+ );
+ $where = $where ? "AND $where" : '';
+
+ # test to return all applicable part_events (defined on this eventtable,
+ # not disabled, check_freq correct, and all event conditions true)
+ my $are_part_event =
+ "EXISTS ( SELECT 1 FROM part_event $join
+ WHERE check_freq = '$check_freq'
+ AND eventtable = '$eventtable'
+ AND ( disabled = '' OR disabled IS NULL )
+ $where
+ )
+ ";
+
+ if ( $eventtable eq 'cust_main' ) {
+ $are_part_event;
+ } else {
+ my $cust_join = FS::part_event->eventtables_cust_join->{$eventtable}
+ || '';
+ my $custnum = FS::part_event->eventtables_custnum->{$eventtable};
+ "EXISTS ( SELECT 1 FROM $eventtable $cust_join
+ WHERE cust_main.custnum = $custnum
+ AND $are_part_event
+ )
+ ";
+ }
+
+ } FS::part_event->eventtables);
+
+ push @search, "( $where_pkg OR $where_event )";
+
+ warn "searching for customers:\n". join("\n", @search). "\n"
+ if $opt{'v'} || $opt{'l'};
+
+ join(' AND ', @search);
+