bugfix for sales report, #27078, from #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     my @classnum_space = @classnums;
133     @classnums = @classnames = ();
134     while(@subsets) {
135       my $these = shift @subsets;
136       # applied topology!
137       my $not_these = [ @classnum_space ];
138       my $i = 0;
139       foreach (@$these) {
140         $i++ until $not_these->[$i] == $_;
141         splice(@$not_these, $i, 1);
142       }
143       push @classnums, $these;
144       push @not_classnums, $not_these;
145       push @classnames, shift @subsets;
146     } #while subsets
147   }
148   # else it's 'pkg', i.e. part_pkg.classnum, which is singular on pkgpart
149   # and much simpler
150
151 } else {
152   die "guru meditation #434";
153 }
154
155 #eslaf
156
157 my @items  = ();
158 my @params = ();
159 my @labels = ();
160 my @colors = ();
161 my @links  = ();
162 my @no_graph;
163
164 my @components = ( 'SRU' );
165 # split/omit components as appropriate
166 if ( $use_setup == 1 ) {
167   @components = ( 'S', 'RU' );
168 }
169 elsif ( $use_setup == 2 ) {
170   @components = ( 'RU' );
171 }
172 if ( $use_usage == 1 ) {
173   $components[-1] =~ s/U//; push @components, 'U';
174 }
175 elsif ( $use_usage == 2 ) {
176   $components[-1] =~ s/U//;
177 }
178
179 # Categorization of line items goes
180 # Agent -> Referral -> Package class -> Component (setup/recur/usage)
181 # If per-agent totals are enabled, they go under the Agent level.
182 # There aren't any other kinds of subtotals.
183
184 my $anum = 0;
185 foreach my $agent ( $all_agent || $sel_agent || $FS::CurrentUser::CurrentUser->agents ) {
186
187   my @agent_colors = map { my $col = $cgi->param("agent$anum-color$_");
188                            $col =~ s/^#//;
189                            $col;
190                          }
191                        (0 .. 5);
192   my @colorbuf = ();
193
194   ### fixup the color handling for package classes...
195   ### and usage
196
197   foreach my $part_referral (
198     $all_part_referral ||
199     $sel_part_referral ||
200     qsearch('part_referral', { 'disabled' => '' } ) 
201   ) {
202
203     my @base_params = (
204                         'use_override'         => $use_override,
205                         'average_per_cust_pkg' => $average_per_cust_pkg,
206                         'distribute'           => $distribute,
207                       );
208
209     if ( $cgi->param('class_agg_break') eq 'aggregate' or
210          $cgi->param('class_agg_break') eq 'exact' ) {
211       # the only difference between 'aggregate' and 'exact' is whether
212       # we pass the 'all_report_options' flag.
213
214       foreach my $component ( @components ) {
215
216         push @items, 'cust_bill_pkg';
217
218         push @labels,
219           ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ).
220           ( $all_part_referral || $sel_part_referral ? '' : $part_referral->referral.' ' ).
221           $charge_labels{$component};
222
223         my $row_agentnum = $all_agent || $agent->agentnum;
224         my $row_refnum = $all_part_referral || $part_referral->refnum;
225         my @row_params = (
226                         @base_params,
227                         $class_param => \@classnums,
228                         ($all_agent ? () : ('agentnum' => $row_agentnum) ),
229                         ($all_part_referral ? () : ('refnum' => $row_refnum) ),
230                         'charges'               => $component,
231         );
232
233         # XXX this is very silly.  we should cache it server-side and 
234         # just put a cache identifier in the link
235         my $rowlink = "$link;".
236                       ($all_agent ? '' : "agentnum=$row_agentnum;").
237                       ($all_part_referral ? '' : "refnum=$row_refnum;").
238                       (join('',map {"cust_classnum=$_;"} @cust_classnums)).
239                       "distribute=$distribute;".
240                       "use_override=$use_override;charges=$component;";
241         $rowlink .= "$class_param=$_;" foreach @classnums;
242         if ( $all_report_options ) {
243           push @row_params, 'all_report_options', 1;
244           $rowlink .= 'all_report_options=1';
245         }
246         push @params, \@row_params;
247         push @links, $rowlink;
248
249         @colorbuf = @agent_colors unless @colorbuf;
250         push @colors, shift @colorbuf;
251         push @no_graph, 0;
252
253       } #foreach $component
254
255     } elsif ( $cgi->param('class_agg_break') eq 'breakdown' ) {
256
257       for (my $i = 0; $i < scalar @classnums; $i++) {
258         my $row_classnum = $classnums[$i];
259         my $row_classname = $classnames[$i];
260         my $not_row_classnum = '';
261         if ( $class_param eq 'report_optionnum' ) {
262           # if we're working with report options, @classnums here contains 
263           # arrays of multiple classnums
264           $row_classnum = join(',', @$row_classnum);
265           $row_classname = join(', ', @$row_classname);
266           $not_row_classnum = join(',', @{ $not_classnums[$i] });
267         }
268         foreach my $component ( @components ) {
269
270           push @items, 'cust_bill_pkg';
271
272           push @labels,
273             ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ).
274             ( $all_part_referral || $sel_part_referral ? '' : $part_referral->referral.' ' ).
275             $row_classname .  ' ' . $charge_labels{$component};
276
277           my $row_agentnum = $all_agent || $agent->agentnum;
278           my $row_refnum = $all_part_referral || $part_referral->refnum;
279           my @row_params = (
280                           @base_params,
281                           $class_param => $row_classnum,
282                           ($all_agent ? () : ('agentnum' => $row_agentnum) ),
283                           ($all_part_referral ? () : ('refnum' => $row_refnum)),
284                           'charges'              => $component,
285           );
286           my $row_link = "$link;".
287                        ($all_agent ? '' : "agentnum=$row_agentnum;").
288                        ($all_part_referral ? '' : "refnum=$row_refnum;").
289                        (join('',map {"cust_classnum=$_;"} @cust_classnums)).
290                        "$class_param=$row_classnum;".
291                        "distribute=$distribute;".
292                        "use_override=$use_override;charges=$component;";
293           if ( $class_param eq 'report_optionnum' ) {
294             push @row_params,
295                           'all_report_options' => 1,
296                           'not_report_optionnum' => $not_row_classnum,
297             ;
298             $row_link .= "all_report_options=1;".
299                          "not_report_optionnum=$not_row_classnum;";
300           }
301           push @params, \@row_params;
302           push @links, $row_link;
303
304           @colorbuf = @agent_colors unless @colorbuf;
305           push @colors, shift @colorbuf;
306           push @no_graph, 0;
307
308         } #foreach $component
309       } #foreach $row_classnum
310
311     } #$cgi->param('class_agg_break')
312
313   } #foreach $part_referral
314
315   if ( $cgi->param('agent_totals') and !$all_agent ) {
316     my $row_agentnum = $agent->agentnum;
317     # Include all components that are anywhere on this report
318     my $component = join('', @components);
319
320     my @row_params = (  'agentnum'              => $row_agentnum,
321                         'cust_classnum'         => \@cust_classnums,
322                         'use_override'          => $use_override,
323                         'average_per_cust_pkg'  => $average_per_cust_pkg,
324                         'distribute'            => $distribute,
325                         'charges'               => $component,
326                      );
327     my $row_link = "$link;".
328                    "agentnum=$row_agentnum;".
329                    "distribute=$distribute;".
330                    "charges=$component;";
331     
332     # package class filters
333     if ( $cgi->param('class_agg_break') eq 'aggregate' ) {
334       push @row_params, $class_param => \@classnums;
335       $row_link .= "$class_param=$_;" foreach @classnums;
336     }
337
338     # refnum filters
339     if ( $sel_part_referral ) {
340       push @row_params, 'refnum' => $sel_part_referral->refnum;
341       $row_link .= "refnum=;".$sel_part_referral->refnum;
342     }
343
344     # customer class filters
345     $row_link .= "cust_classnum=$_;" foreach @cust_classnums;
346
347     push @items, 'cust_bill_pkg';
348     push @labels, mt('[_1] - Subtotal', $agent->agent);
349     push @params, \@row_params;
350     push @links, $row_link;
351     push @colors, '000000'; # better idea?
352     push @no_graph, 1;
353   }
354
355   $anum++;
356
357 }
358
359 #use Data::Dumper;
360 if ( $cgi->param('debug') == 1 ) {
361   $FS::Report::Table::DEBUG = 1;
362 }
363 </%init>