event refactor, landing on HEAD!
[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::Record qw(qsearch qsearchs);
8 use FS::cust_main;
9 use FS::part_event;
10 use FS::part_event_condition;
11
12 @ISA = qw( Exporter );
13 @EXPORT_OK = qw ( bill );
14
15 sub bill {
16
17   my %opt = @_;
18
19   my $check_freq = $opt{'check_freq'} || '1d';
20
21   $FS::cust_main::DEBUG = 1 if $opt{'v'};
22   $FS::cust_main::DEBUG = $opt{'l'} if $opt{'l'};
23   #$FS::cust_event::DEBUG = $opt{'l'} if $opt{'l'};
24   
25   my %search = ();
26   $search{'payby'}    = $opt{'p'} if $opt{'p'};
27   $search{'agentnum'} = $opt{'a'} if $opt{'a'};
28   
29   #we're at now now (and later).
30   my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T;
31   $time += $opt{'y'} * 86400 if $opt{'y'};
32
33   my $invoice_time = $opt{'n'} ? $^T : $time;
34
35   # select * from cust_main where
36   my $where_pkg = <<"END";
37     0 < ( select count(*) from cust_pkg
38             where cust_main.custnum = cust_pkg.custnum
39               and ( cancel is null or cancel = 0 )
40               and (    setup is null or setup =  0
41                     or bill  is null or bill  <= $time 
42                     or ( expire is not null and expire <= $^T )
43                     or ( adjourn is not null and adjourn <= $^T )
44                   )
45         )
46 END
47
48   my $where_event = join(' OR ', map {
49     my $eventtable = $_;
50
51     my $join  = FS::part_event_condition->join_conditions_sql(  $eventtable );
52     my $where = FS::part_event_condition->where_conditions_sql( $eventtable,
53                                                                 'time'=>$time,
54                                                               );
55
56     my $are_part_event = 
57       "0 < ( SELECT COUNT(*) FROM part_event $join
58                WHERE check_freq = '$check_freq'
59                  AND eventtable = '$eventtable'
60                  AND ( disabled = '' OR disabled IS NULL )
61                  AND $where
62            )
63       ";
64
65     if ( $eventtable eq 'cust_main' ) { 
66       $are_part_event;
67     } else {
68       "0 < ( SELECT COUNT(*) FROM $eventtable
69                WHERE cust_main.custnum = $eventtable.custnum
70                  AND $are_part_event
71            )
72       ";
73     }
74
75   } FS::part_event->eventtables);
76
77   my $extra_sql = ( scalar(%search) ? ' AND ' : ' WHERE ' ).
78                   "( $where_pkg OR $where_event )";
79
80   my @cust_main;
81   if ( @ARGV ) {
82     @cust_main = map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV
83   } else {
84
85     warn "searching for customers:\n".
86          join("\n", map "  $_ => ".$search{$_}, keys %search). "\n".
87          "  $extra_sql\n"
88       if $opt{'v'} || $opt{'l'};
89
90     @cust_main = qsearch({
91       'table'     => 'cust_main',
92       'hashref'   => \%search,
93       'extra_sql' => $extra_sql,
94     });
95
96   }
97   
98   my($cust_main,%saw);
99   foreach $cust_main ( @cust_main ) {
100
101     if ( $opt{'m'} ) {
102
103       die "XXX multi-process mode not yet completed";
104       #add job to queue that calls bill_and_collect with options
105
106     } else {
107
108       $cust_main->bill_and_collect(
109         'time'         => $time,
110         'invoice_time' => $invoice_time,
111         'check_freq'   => $check_freq,
112         'resetup'      => $opt{'s'},
113       );
114
115     }
116
117   }
118
119 }