change calculation method for prepaid income report, #13289
[freeside.git] / httemplate / search / prepaid_income.html
1 <% include("/elements/header.html", 'Prepaid Income (Unearned Revenue) Report') %>
2
3 <% include( '/elements/table-grid.html' ) %>
4
5   <TR>
6 %   if ( scalar(@agentnums) > 1 ) {
7       <TH CLASS="grid" BGCOLOR="#cccccc">Agent</TH>
8 %   }
9     <TH CLASS="grid" BGCOLOR="#cccccc"><% $actual_label %>Unearned Revenue</TH>
10 %   if ( $legacy ) {
11       <TH CLASS="grid" BGCOLOR="#cccccc">Legacy Unearned Revenue</TH>
12 %   }
13   </TR>
14
15 % my $bgcolor1 = '#eeeeee';
16 % my $bgcolor2 = '#ffffff';
17 % my $bgcolor;
18 %
19 % push @agentnums, 0 unless scalar(@agentnums) < 2;
20 % foreach my $agentnum (@agentnums) {  
21 %
22 %   if ( $bgcolor eq $bgcolor1 ) {
23 %     $bgcolor = $bgcolor2;
24 %   } else {
25 %     $bgcolor = $bgcolor1;
26 %   }
27 %
28 %   my $alink = $agentnum ? "$link;agentnum=$agentnum" : $link;
29 %
30 %   my $agent_name = 'Total';
31 %   if ( $agentnum ) {
32 %     my $agent = qsearchs('agent', { 'agentnum' => $agentnum })
33 %       or die "unknown agentnum $agentnum";
34 %     $agent_name = $agent->agent;
35 %   }
36
37     <TR>
38
39 %     if ( scalar(@agentnums) > 1 ) {
40         <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent_name |h %></TD>
41 %     }
42
43       <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $alink %>"><% $money_char %><% $total{$agentnum} %></A></TD>
44
45 %     if ( $legacy ) {
46         <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
47           <% $now == $time ? $money_char.$total_legacy{$agentnum} : '<i>N/A</i>'%>
48         </TD>
49 %     }
50
51     </TR>
52
53 %  }
54
55 </TABLE>
56
57 <BR>
58 <% $actual_label %><% $actual_label ? 'u' : 'U' %>nearned revenue
59 is the amount of unearned revenue
60 <% $actual_label ? 'Freeside has actually' : '' %>
61 invoiced for packages with 
62 <% $cgi->param('include_monthly') ? 'terms extending into the future.'
63                                   : 'longer-than monthly terms.' %>
64
65 % if ( $legacy ) {
66   <BR><BR>
67   Legacy unearned revenue is the amount of unearned revenue represented by 
68   customer packages.  This number may be larger than actual unearned 
69   revenue if you have imported longer-than monthly customer packages from
70   a previous billing system.
71 % }
72
73 <% include('/elements/footer.html') %>
74 <%init>
75
76 die "access denied"
77   unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
78
79 my $conf = new FS::Conf;
80 my $money_char = $conf->config('money_char') || '$';
81
82 my $legacy = $conf->exists('enable_legacy_prepaid_income');
83 my $actual_label = $legacy ? 'Actual ' : '';
84
85 #doesn't yet deal with daily/weekly packages
86
87 my $mode = $cgi->param('mode');
88
89 my $time = time;
90
91 my $now = $cgi->param('date') && parse_datetime($cgi->param('date')) || $time;
92 $now =~ /^(\d+)$/ or die "unparsable date?";
93 $now = $1;
94
95 my $dt = DateTime->from_epoch(epoch => $now, time_zone => 'local');
96 $dt->truncate(to => 'day'); # local midnight on the report day
97 $dt->add(days => 1); # the day after that
98 $dt->subtract(seconds => 1); # the last second of the report day
99 $now = $dt->epoch;
100
101 my $link = "cust_bill_pkg.cgi?nottax=1;unearned_now=$now;mode=$mode";
102
103 if ( $cgi->param('include_monthly') ) {
104   $link .= ';include_monthly=1';
105 }
106
107 my $curuser = $FS::CurrentUser::CurrentUser;
108
109 my $agentnum = '';
110 my @agentnums = ();
111 $agentnum ? ($agentnum) : $curuser->agentnums;
112 if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
113   @agentnums = ($1);
114   #XXX#push @where, "agentnum = $agentnum";
115   #XXX#$link .= ";agentnum=$agentnum";
116 } else {
117   @agentnums = $curuser->agentnums;
118 }
119
120 my @where = ();
121
122 #here is the agent virtualization
123 push @where, $curuser->agentnums_sql( 'table'=>'cust_main' );
124
125 my $status = '';
126 if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
127   $status = $1;
128   $link .= ";status=$status";
129   push @where, FS::cust_main->cust_status_sql . " = '$status'";
130 }
131
132 my %total = ();
133 my %total_legacy = ();
134 foreach my $agentnum (@agentnums) {
135   
136   my $where = join(' AND ', @where, "cust_main.agentnum = $agentnum");
137   $where = "AND $where" if $where;
138
139   my( $total, $total_legacy ) = ( 0, 0 );
140
141   # false laziness
142   my $usage_sql = FS::cust_bill_pkg->usage_sql;
143   my $owed_sql = FS::cust_bill_pkg->owed_sql($now, '', 
144     setuprecur => 'recur', no_usage => 1);
145   my $unearned_base = "(cust_bill_pkg.recur - $usage_sql)";
146   my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS REAL)";
147   my $elapsed  = "GREATEST( $now - cust_bill_pkg.sdate, 0 )";
148   my $remaining = "(1 - $elapsed/$period)";
149
150   my $unearned_sql = "CAST(
151   GREATEST( $unearned_base * $remaining - $owed_sql, 0 )
152     AS DECIMAL(10,2)
153   )";
154
155   my $float = 'REAL'; #'DOUBLE PRECISION';
156   
157   my $select = "SUM( $unearned_sql )";
158
159   if ( !$cgi->param('include_monthly') ) {
160     # all except freq != 0; one-time charges should never be included
161     $where .= "
162                  AND part_pkg.freq != '1'
163                  AND part_pkg.freq NOT LIKE '%h'
164                  AND part_pkg.freq NOT LIKE '%d'
165                  AND part_pkg.freq NOT LIKE '%w'";
166   }
167
168   # $mode actually doesn't matter here, since unpaid invoices have zero
169   # unearned revenue
170
171   my $sql = 
172   "SELECT $select FROM cust_bill_pkg
173                   LEFT JOIN cust_pkg  ON (cust_bill_pkg.pkgnum = cust_pkg.pkgnum)
174                   LEFT JOIN part_pkg  USING ( pkgpart )
175                   LEFT JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
176                WHERE pkgpart > 0
177                  AND cust_bill_pkg.sdate < $now
178                  AND cust_bill_pkg.edate > $now
179                  AND cust_bill_pkg.recur != 0
180                  AND part_pkg.freq != '0'
181                  $where
182              ";
183   my $sth = dbh->prepare($sql) or die dbh->errstr;
184   $sth->execute or die $sth->errstr;
185   my $total = $sth->fetchrow_arrayref->[0];
186
187   $total = sprintf('%.2f', $total);
188   $total{$agentnum} = $total;
189   $total{0} += $total;
190
191   if ( $legacy ) {
192
193     #not yet rewritten in sql, but now not enabled by default
194
195     my @cust_pkg = 
196       grep { $_->part_pkg->recur != 0
197              && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/
198            }
199         qsearch({
200           'select'    => 'cust_pkg.*',
201           'table'     => 'cust_pkg',
202           'addl_from' => ' LEFT JOIN cust_main USING ( custnum ) ',
203           'hashref'   => { 'bill' => { op=>'>', value=>$now } },
204           'extra_sql' => $where,
205         });
206
207     foreach my $cust_pkg ( @cust_pkg ) {
208       my $period = $cust_pkg->bill - $cust_pkg->last_bill;
209    
210       my $elapsed = $now - $cust_pkg->last_bill;
211       $elapsed = 0 if $elapsed < 0;
212    
213       my $remaining = 1 - $elapsed/$period;
214    
215       my $unearned = $remaining * $cust_pkg->part_pkg->recur; #!! only works for flat/legacy
216       $total_legacy += $unearned;
217    
218     }
219
220     $total_legacy = sprintf('%.2f', $total_legacy);
221     $total_legacy{$agentnum} = $total_legacy;
222     $total_legacy{0} += $total_legacy;
223
224   }
225
226 }
227
228 $total{0} = sprintf('%.2f', $total{0});
229 $total_legacy{0} = sprintf('%.2f', $total_legacy{0});
230   
231 </%init>