3ba1b53d4e3128c848b7098153db9c6c2f88e2bc
[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
10 @ISA = qw( Exporter );
11 @EXPORT_OK = qw ( bill );
12
13 sub bill {
14
15   my %opt = @_;
16
17   $FS::cust_main::DEBUG = 1 if $opt{'v'};
18   
19   my %search = ();
20   $search{'payby'}    = $opt{'p'} if $opt{'p'};
21   $search{'agentnum'} = $opt{'a'} if $opt{'a'};
22   
23   #we're at now now (and later).
24   my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T;
25   $time += $opt{'y'} * 86400 if $opt{'y'};
26
27   my $invoice_time = $opt{'n'} ? $^T : $time;
28
29   # select * from cust_main where
30   my $where_pkg = <<"END";
31     0 < ( select count(*) from cust_pkg
32             where cust_main.custnum = cust_pkg.custnum
33               and ( cancel is null or cancel = 0 )
34               and (    setup is null or setup =  0
35                     or bill  is null or bill  <= $time 
36                     or ( expire is not null and expire <= $^T )
37                     or ( adjourn is not null and adjourn <= $^T )
38                   )
39         )
40 END
41   
42   # or
43   my $where_bill_event = <<"END";
44     0 < ( select count(*) from cust_bill
45             where cust_main.custnum = cust_bill.custnum
46               and 0 < charged
47                       - coalesce(
48                                   ( select sum(amount) from cust_bill_pay
49                                       where cust_bill.invnum = cust_bill_pay.invnum )
50                                   ,0
51                                 )
52                       - coalesce(
53                                   ( select sum(amount) from cust_credit_bill
54                                       where cust_bill.invnum = cust_credit_bill.invnum )
55                                   ,0
56                                 )
57               and 0 < ( select count(*) from part_bill_event
58                           where payby = cust_main.payby
59                             and ( disabled is null or disabled = '' )
60                             and seconds <= $time - cust_bill._date
61                             and 0 = ( select count(*) from cust_bill_event
62                                        where cust_bill.invnum = cust_bill_event.invnum
63                                          and part_bill_event.eventpart = cust_bill_event.eventpart
64                                          and status = 'done'
65                                     )
66   
67                       )
68         )
69 END
70   
71   my $extra_sql = ( scalar(%search) ? ' AND ' : ' WHERE ' ). "( $where_pkg OR $where_bill_event )";
72   
73   my @cust_main;
74   if ( @ARGV ) {
75     @cust_main = map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV
76   } else {
77     @cust_main = qsearch('cust_main', \%search, '', $extra_sql );
78   }
79   ;
80   
81   my($cust_main,%saw);
82   foreach $cust_main ( @cust_main ) {
83
84     my $custnum = $cust_main->custnum;
85   
86     # $^T not $time because -d is for pre-printing invoices
87     foreach my $cust_pkg (
88       grep { $_->expire && $_->expire <= $^T } $cust_main->ncancelled_pkgs
89     ) {
90       my $error = $cust_pkg->cancel;
91       warn "Error cancelling expired pkg ". $cust_pkg->pkgnum.
92            " for custnum $custnum: $error"
93         if $error;
94     }
95     # $^T not $time because -d is for pre-printing invoices
96     foreach my $cust_pkg (
97       grep { (    $_->part_pkg->is_prepaid && $_->bill && $_->bill < $^T
98                || $_->adjourn && $_->adjourn <= $^T
99              )
100              && ! $_->susp
101            }
102            $cust_main->ncancelled_pkgs
103     ) {
104       my $action = $cust_pkg->part_pkg->option('recur_action') || 'suspend';
105       my $error = $cust_pkg->$action();
106       warn "Error suspending package ". $cust_pkg->pkgnum.
107            " for custnum $custnum: $error"
108         if $error;
109     }
110   
111     my $error = $cust_main->bill( 'time'         => $time,
112                                   'invoice_time' => $invoice_time,
113                                   'resetup'      => $opt{'s'},
114                                 );
115     warn "Error billing, custnum $custnum: $error" if $error;
116   
117     $error = $cust_main->apply_payments_and_credits;
118     warn "Error applying payments and credits, custnum $custnum: $error"
119       if $error;
120   
121     $error = $cust_main->collect( 'invoice_time' => $time,
122                                   'freq'         => $opt{'freq'},
123                                 );
124     warn "Error collecting, custnum $custnum: $error" if $error;
125   
126   }
127
128 }