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