rt# 78547 Implement report listing future auto-bill charges
[freeside.git] / httemplate / search / future_autobill.html
1 <%doc>
2
3 Report listing upcoming auto-bill transactions
4
5 Spec requested the ability to run this report with a longer date range,
6 and see which charges will process on which day.  Checkbox multiple_billing_dates
7 enables this functionality.
8
9 Performance:
10 This is a dynamically generated report.  The time this report takes to run
11 will depends on the number of customers.  Installations with a high number
12 of auto-bill customers may find themselves unable to run this report
13 because of browser timeout.  Report could be implemented as a queued job if
14 necessary, to solve the performance problem.
15
16 </%doc>
17 <& elements/grid-report.html,
18   title => 'Upcoming auto-bill transactions',
19   rows => \@rows,
20   cells => \@cells,
21   table_width => "",
22   table_class => 'gridreport',
23   head => '
24     <style type="text/css">
25       table.gridreport { margin: .5em; border: solid 1px #aaa; }
26       th.gridreport { background-color: #ccc; }
27       tr.gridreport:nth-child(even) { background-color: #eee; }
28       tr.gridreport:nth-child(odd)  { background-color: #fff; }
29       td.gridreport { margin: 0 .2em; padding: 0 .4em; }
30     </style>
31   ',
32 &>
33
34 <%init>
35
36 use FS::UID qw( dbh myconnect );
37
38 die "access denied"
39   unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
40
41   my $target_dt;
42   my @target_dates;
43
44   # Work with all date/time operations @ 12 noon
45   my %noon = (
46     hour   => 12,
47     minute => 0,
48     second => 0
49   );
50
51   my $now_dt = DateTime->now;
52   $now_dt = DateTime->new(
53     month => $now_dt->month,
54     day   => $now_dt->day,
55     year  => $now_dt->year,
56     %noon,
57   );
58
59   # Get target date from form
60   if ($cgi->param('target_date')) {
61     my ($mm, $dd, $yy) = split /[\-\/]/,$cgi->param('target_date');
62     $target_dt = DateTime->new(
63       month => $mm,
64       day   => $dd,
65       year  => $yy,
66       %noon,
67     ) if $mm && $dd & $yy;
68
69     # Catch a date from the past: time only travels in one direction
70     $target_dt = undef if $target_dt->epoch < $now_dt->epoch;
71   }
72
73   # without a target date, default to tomorrow
74   unless ($target_dt) {
75     $target_dt = DateTime->from_epoch( epoch => time() + 86400) ;
76     $target_dt = DateTime->new(
77       month => $target_dt->month,
78       day   => $target_dt->day,
79       year  => $target_dt->year,
80       %noon
81     );
82   }
83
84   # If multiple_billing_dates checkbox selected, create a range of dates
85   # from today until the given report date.  Otherwise, use target date only.
86   if ($cgi->param('multiple_billing_dates')) {
87     my $walking_dt = DateTime->from_epoch(epoch => $now_dt->epoch);
88     until ($walking_dt->epoch > $target_dt->epoch) {
89      push @target_dates, $walking_dt->epoch;
90      $walking_dt->add(days => 1);
91     }
92   } else {
93     push @target_dates, $target_dt->epoch;
94   }
95
96   # List all customers with an auto-bill method
97   #
98   # my %cust_payby = map {$_->custnum => $_} qsearch({
99   #   table => 'cust_payby',
100   #   hashref => {
101   #     weight  => { op => '>', value => '0' },
102   #     paydate => { op => '>', value => $target_dt->ymd },
103   #   },
104   #   order_by => " ORDER BY weight DESC ",
105   # });
106
107   # List all customers with an auto-bill method that's not expired
108   my %cust_payby = map {$_->custnum => $_} qsearch({
109     table => 'cust_payby',
110     hashref => {
111       weight  => { op => '>', value => '0' },
112     },
113     order_by => " ORDER BY weight DESC ",
114     extra_sql => " AND ( payby = 'CHEK' OR ( paydate > '".$target_dt->ymd."')) ",
115   });
116
117   my %abreport;
118   my @rows;
119
120   local $@;
121   local $SIG{__DIE__};
122   my $temp_dbh = myconnect();
123   eval { # Creating sandbox dbh where all connections are to be rolled back
124     local $FS::UID::dbh = $temp_dbh;
125     local $FS::UID::AutoCommit = 0;
126
127     # Generate report data into @rows
128     for my $custnum (keys %cust_payby) {
129       my $cust_main = qsearchs('cust_main', {custnum => $custnum});
130
131       # walk forward through billing dates
132       for my $query_epoch (@target_dates) {
133         my $return_bill = [];
134
135         eval { # Don't let an error on one customer crash the report
136           my $error = $cust_main->bill(
137             time           => $query_epoch,
138             return_bill    => $return_bill,
139             no_usage_reset => 1,
140           );
141           die "$error (simulating future billing)" if $error;
142         };
143         warn ("$@: (future_autobill custnum:$custnum)");
144
145         if (@{$return_bill}) {
146           my $inv = $return_bill->[0];
147           push @rows,{
148             name => $cust_main->name,
149             _date => $inv->_date,
150             cells => [
151               { class => 'gridreport', value => $custnum },
152               { class => 'gridreport',
153                 value => '<a href="/view/cust_main.cgi?"'.$custnum.'">'.$cust_main->name.'</a>',
154                 bypass_filter => 1,
155               },
156               { class => 'gridreport', value => $inv->charged, format => 'money' },
157               { class => 'gridreport', value => DateTime->from_epoch(epoch=>$inv->_date)->ymd },
158               { class => 'gridreport', value => ($cust_payby{$custnum}->payby || $cust_payby{$custnum}->paytype) },
159               { class => 'gridreport', value => $cust_payby{$custnum}->paymask },
160             ]
161           };
162         }
163
164       }
165       $temp_dbh->rollback;
166     } # /foreach $custnum
167
168   }; # /eval
169   warn("$@") if $@;
170
171   # Sort output by date, and format for output to grid-report.html
172   my @cells = [
173       # header row
174       { class => 'gridreport', value => '#',       header => 1 },
175       { class => 'gridreport', value => 'Name',    header => 1 },
176       { class => 'gridreport', value => 'Amount',  header => 1 },
177       { class => 'gridreport', value => 'Date',    header => 1 },
178       { class => 'gridreport', value => 'Type',    header => 1 },
179       { class => 'gridreport', value => 'Account', header => 1 },
180     ];
181   push @cells,
182     map  { $_->{cells} }
183     sort { $a->{_date} <=> $b->{_date} || $a->{name} cmp $b->{name} }
184     @rows;
185
186   # grid-report.html requires a parallel @rows parameter to accompany @cells
187   @rows = map { {class => 'gridreport'} } 1..scalar(@cells);
188
189 </%init>