query optimization for #25459
[freeside.git] / httemplate / graph / cust_bill_pkg.cgi
1 <% include('elements/monthly.html',
2    #Dumper(
3                 'title'        => $title,
4                 'graph_type'   => 'Mountain',
5                 'items'        => \@items,
6                 'params'       => \@params,
7                 'labels'       => \@labels,
8                 'graph_labels' => \@labels,
9                 'colors'       => \@colors,
10                 'links'        => \@links,
11                 'no_graph'     => \@no_graph,
12                 'remove_empty' => 1,
13                 'bottom_total' => 1,
14                 'bottom_link'  => $bottom_link,
15                 'agentnum'     => $agentnum,
16                 'cust_classnum'=> \@cust_classnums,
17              )
18 %>
19 <%init>
20
21 die "access denied"
22   unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
23
24 my $link = "${p}search/cust_bill_pkg.cgi?nottax=1";
25 my $bottom_link = "$link;";
26
27 my $use_usage = $cgi->param('use_usage') || 0;
28 my $use_setup = $cgi->param('use_setup') || 0;
29 my $use_override         = $cgi->param('use_override')         ? 1 : 0;
30 my $average_per_cust_pkg = $cgi->param('average_per_cust_pkg') ? 1 : 0;
31 my $distribute           = $cgi->param('distribute')           ? 1 : 0;
32
33 my %charge_labels = (
34   'SR' => 'setup + recurring',
35   'RU' => 'recurring',
36   'S'  => 'setup',
37   'R'  => 'recurring',
38   'U'  => 'usage',
39 );
40
41 #XXX or virtual
42 my( $agentnum, $sel_agent, $all_agent ) = ('', '', '');
43 if ( $cgi->param('agentnum') eq 'all' ) {
44   $agentnum = 0;
45   $all_agent = 'ALL';
46 }
47 elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
48   $agentnum = $1;
49   $bottom_link .= "agentnum=$agentnum;";
50   $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } );
51   die "agentnum $agentnum not found!" unless $sel_agent;
52 }
53 my $title = $sel_agent ? $sel_agent->agent.' ' : '';
54
55 my( $refnum, $sel_part_referral, $all_part_referral ) = ('', '', '');
56 if ( $cgi->param('refnum') eq 'all' ) {
57   $refnum = 0;
58   $all_part_referral = 'ALL';
59 }
60 elsif ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
61   $refnum = $1;
62   $bottom_link .= "refnum=$refnum;";
63   $sel_part_referral = qsearchs('part_referral', { 'refnum' => $refnum } );
64   die "part_referral $refnum not found!" unless $sel_part_referral;
65 }
66 $title .= $sel_part_referral->referral.' '
67   if $sel_part_referral;
68
69 $title .= 'Sales Report (Gross)';
70 $title .= ', average per customer package'  if $average_per_cust_pkg;
71
72 my @cust_classnums = grep /^\d+$/, $cgi->param('cust_classnum');
73 $bottom_link .= "cust_classnum=$_;" foreach @cust_classnums;
74
75 #classnum (here)
76 # not specified: no longer happens (unless you de-select all classes)
77 # 0: empty class
78 # N: classnum
79 #classnum (link)
80 # not specified: all classes
81 # 0: empty class
82 # N: classnum
83
84 #started out as false lazinessish w/FS::cust_pkg::search_sql (previously search/cust_pkg.cgi), but not much left the sane now after #24776
85
86 my ($class_table, $name_col, $value_col, $class_param);
87 my $all_report_options;
88
89 if ( $cgi->param('class_mode') eq 'report' ) {
90   $class_param = 'report_optionnum'; # CGI param name, also used in the report engine
91   $class_table = 'part_pkg_report_option'; # table containing classes
92   $name_col = 'name'; # the column of that table containing the label
93   $value_col = 'num'; # the column containing the class number
94   # in 'exact' mode we want to run the query in ALL mode.
95   # in 'breakdown' mode want to run the query in ALL mode but using the 
96   # power set of the classes selected.
97   $all_report_options = 1
98     unless $cgi->param('class_agg_break') eq 'aggregate';
99 } else { # class_mode eq 'pkg'
100   $class_param = 'classnum';
101   $class_table = 'pkg_class';
102   $name_col = 'classname';
103   $value_col = 'classnum';
104 }
105
106 my @classnums = sort {$a <=> $b} grep /^\d+$/, $cgi->param($class_param);
107 my @classnames = map { if ( $_ ) {
108                          my $class = qsearchs($class_table, {$value_col=>$_} );
109                          $class->$name_col;
110                        } else {
111                          '(empty class)';
112                        }
113                      }
114                    @classnums;
115 my @not_classnums;
116
117 $bottom_link .= "$class_param=$_;" foreach @classnums;
118
119 if ( $cgi->param('class_agg_break') eq 'aggregate' or
120      $cgi->param('class_agg_break') eq 'exact' ) {
121
122   $title .= ' '. join(', ', @classnames)
123     unless scalar(@classnames) > scalar(qsearch($class_table,{'disabled'=>''}));
124                                  #not efficient for lots of package classes
125
126 } elsif ( $cgi->param('class_agg_break') eq 'breakdown' ) {
127
128   if ( $cgi->param('class_mode') eq 'report' ) {
129     # The new way:
130     # Actually break down all subsets of the (selected) report classes.
131     my @subsets = FS::part_pkg_report_option->subsets(@classnums);
132     warn "SUBSETS:\n".Dumper(\@subsets)."\n\n";
133     my @classnum_space = @classnums;
134     @classnums = @classnames = ();
135     while(@subsets) {
136       my $these = shift @subsets;
137       # applied topology!
138       my $not_these = [ @classnum_space ];
139       my $i = 0;
140       foreach (@$these) {
141         $i++ until $not_these->[$i] == $_;
142         splice($not_these, $i, 1);
143       }
144       push @classnums, $these;
145       push @not_classnums, $not_these;
146       push @classnames, shift @subsets;
147     } #while subsets
148     warn "COMPLEMENTS:\n".Dumper(\@not_classnums)."\n\n";
149   }
150   # else it's 'pkg', i.e. part_pkg.classnum, which is singular on pkgpart
151   # and much simpler
152
153 } else {
154   die "guru meditation #434";
155 }
156
157 #eslaf
158
159 my @items  = ();
160 my @params = ();
161 my @labels = ();
162 my @colors = ();
163 my @links  = ();
164 my @no_graph;
165
166 my @components = ( 'SRU' );
167 # split/omit components as appropriate
168 if ( $use_setup == 1 ) {
169   @components = ( 'S', 'RU' );
170 }
171 elsif ( $use_setup == 2 ) {
172   @components = ( 'RU' );
173 }
174 if ( $use_usage == 1 ) {
175   $components[-1] =~ s/U//; push @components, 'U';
176 }
177 elsif ( $use_usage == 2 ) {
178   $components[-1] =~ s/U//;
179 }
180
181 # Categorization of line items goes
182 # Agent -> Referral -> Package class -> Component (setup/recur/usage)
183 # If per-agent totals are enabled, they go under the Agent level.
184 # There aren't any other kinds of subtotals.
185
186 my $anum = 0;
187 foreach my $agent ( $all_agent || $sel_agent || $FS::CurrentUser::CurrentUser->agents ) {
188
189   my @agent_colors = map { my $col = $cgi->param("agent$anum-color$_");
190                            $col =~ s/^#//;
191                            $col;
192                          }
193                        (0 .. 5);
194   my @colorbuf = ();
195
196   ### fixup the color handling for package classes...
197   ### and usage
198
199   foreach my $part_referral (
200     $all_part_referral ||
201     $sel_part_referral ||
202     qsearch('part_referral', { 'disabled' => '' } ) 
203   ) {
204
205     my @base_params = (
206                         'use_override'         => $use_override,
207                         'average_per_cust_pkg' => $average_per_cust_pkg,
208                         'distribute'           => $distribute,
209                       );
210
211     if ( $cgi->param('class_agg_break') eq 'aggregate' or
212          $cgi->param('class_agg_break') eq 'exact' ) {
213       # the only difference between 'aggregate' and 'exact' is whether
214       # we pass the 'all_report_options' flag.
215
216       foreach my $component ( @components ) {
217
218         push @items, 'cust_bill_pkg';
219
220         push @labels,
221           ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ).
222           ( $all_part_referral || $sel_part_referral ? '' : $part_referral->referral.' ' ).
223           $charge_labels{$component};
224
225         my $row_agentnum = $all_agent || $agent->agentnum;
226         my $row_refnum = $all_part_referral || $part_referral->refnum;
227         my @row_params = (
228                         @base_params,
229                         $class_param => \@classnums,
230                         ($all_agent ? () : ('agentnum' => $row_agentnum) ),
231                         ($all_part_referral ? () : ('refnum' => $row_refnum) ),
232                         'charges'               => $component,
233         );
234
235         # XXX this is very silly.  we should cache it server-side and 
236         # just put a cache identifier in the link
237         my $rowlink = "$link;".
238                       ($all_agent ? '' : "agentnum=$row_agentnum;").
239                       ($all_part_referral ? '' : "refnum=$row_refnum;").
240                       (join('',map {"cust_classnum=$_;"} @cust_classnums)).
241                       "distribute=$distribute;".
242                       "use_override=$use_override;charges=$component;";
243         $rowlink .= "$class_param=$_;" foreach @classnums;
244         if ( $all_report_options ) {
245           push @row_params, 'all_report_options', 1;
246           $rowlink .= 'all_report_options=1';
247         }
248         push @params, \@row_params;
249         push @links, $rowlink;
250
251         @colorbuf = @agent_colors unless @colorbuf;
252         push @colors, shift @colorbuf;
253         push @no_graph, 0;
254
255       } #foreach $component
256
257     } elsif ( $cgi->param('class_agg_break') eq 'breakdown' ) {
258
259       # if we're working with report options, @classnums here contains 
260       # arrays of multiple classnums
261       for (my $i = 0; $i < scalar @classnums; $i++) {
262         my $row_classnum = join(',', @{ $classnums[$i] });
263         my $row_classname = join(', ', @{ $classnames[$i] });
264         my $not_row_classnum = join(',', @{ $not_classnums[$i] });
265         foreach my $component ( @components ) {
266
267           push @items, 'cust_bill_pkg';
268
269           push @labels,
270             ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ).
271             ( $all_part_referral || $sel_part_referral ? '' : $part_referral->referral.' ' ).
272             $row_classname .  ' ' . $charge_labels{$component};
273
274           my $row_agentnum = $all_agent || $agent->agentnum;
275           my $row_refnum = $all_part_referral || $part_referral->refnum;
276           my @row_params = (
277                           @base_params,
278                           $class_param => $row_classnum,
279                           ($all_agent ? () : ('agentnum' => $row_agentnum) ),
280                           ($all_part_referral ? () : ('refnum' => $row_refnum)),
281                           'charges'              => $component,
282           );
283           my $row_link = "$link;".
284                        ($all_agent ? '' : "agentnum=$row_agentnum;").
285                        ($all_part_referral ? '' : "refnum=$row_refnum;").
286                        (join('',map {"cust_classnum=$_;"} @cust_classnums)).
287                        "$class_param=$row_classnum;".
288                        "distribute=$distribute;".
289                        "use_override=$use_override;charges=$component;";
290           if ( $class_param eq 'report_optionnum' ) {
291             push @row_params,
292                           'all_report_options' => 1,
293                           'not_report_optionnum' => $not_row_classnum,
294             ;
295             $row_link .= "all_report_options=1;".
296                          "not_report_optionnum=$not_row_classnum;";
297           }
298           push @params, \@row_params;
299           push @links, $row_link;
300
301           @colorbuf = @agent_colors unless @colorbuf;
302           push @colors, shift @colorbuf;
303           push @no_graph, 0;
304
305         } #foreach $component
306       } #foreach $row_classnum
307
308     } #$cgi->param('class_agg_break')
309
310   } #foreach $part_referral
311
312   if ( $cgi->param('agent_totals') and !$all_agent ) {
313     my $row_agentnum = $agent->agentnum;
314     # Include all components that are anywhere on this report
315     my $component = join('', @components);
316
317     my @row_params = (  'agentnum'              => $row_agentnum,
318                         'cust_classnum'         => \@cust_classnums,
319                         'use_override'          => $use_override,
320                         'average_per_cust_pkg'  => $average_per_cust_pkg,
321                         'distribute'            => $distribute,
322                         'charges'               => $component,
323                      );
324     my $row_link = "$link;".
325                    "agentnum=$row_agentnum;".
326                    "distribute=$distribute;".
327                    "charges=$component;";
328     
329     # package class filters
330     if ( $cgi->param('class_agg_break') eq 'aggregate' ) {
331       push @row_params, $class_param => \@classnums;
332       $row_link .= "$class_param=$_;" foreach @classnums;
333     }
334
335     # refnum filters
336     if ( $sel_part_referral ) {
337       push @row_params, 'refnum' => $sel_part_referral->refnum;
338       $row_link .= "refnum=;".$sel_part_referral->refnum;
339     }
340
341     # customer class filters
342     $row_link .= "cust_classnum=$_;" foreach @cust_classnums;
343
344     push @items, 'cust_bill_pkg';
345     push @labels, mt('[_1] - Subtotal', $agent->agent);
346     push @params, \@row_params;
347     push @links, $row_link;
348     push @colors, '000000'; # better idea?
349     push @no_graph, 1;
350   }
351
352   $anum++;
353
354 }
355
356 #use Data::Dumper;
357 if ( $cgi->param('debug') == 1 ) {
358   $FS::Report::Table::DEBUG = 1;
359 }
360 </%init>