1 package FS::Report::Table::Monthly;
7 use Time::Local qw( timelocal );
9 @ISA = qw( FS::Report::Table );
13 FS::Report::Table::Monthly - Tables of report data, indexed monthly
17 use FS::Report::Table::Monthly;
19 my $report = new FS::Report::Table::Monthly (
20 'items' => [ 'invoiced', 'netsales', 'credits', 'receipts', ],
28 'cust_classnum' => [ 1,2,4 ],
29 'params' => [ [ 'paramsfor', 'item_one' ], [ 'item', 'two' ] ], # ...
30 'remove_empty' => 1, #collapse empty rows, default 0
31 'item_labels' => [ ], #useful with remove_empty
34 my $data = $report->data;
40 C<start_month>, C<start_year>, C<end_month>, and C<end_year> specify the date
41 range to be included in the report. The start and end months are included.
42 Each month's values are summed from midnight on the first of the month to
43 23:59:59 on the last day of the month.
49 =item items: An arrayref of observables to calculate for each month. See
50 L<FS::Report::Table> for a list of observables and their parameters.
52 =item params: An arrayref, parallel to C<items>, of arrayrefs of parameters
53 (in paired name/value form) to be passed to the observables.
55 =item cross_params: Cross-product parameters. This must be an arrayref of
56 arrayrefs of parameters (paired name/value form). This creates an additional
57 "axis" (orthogonal to the time and C<items> axes) in which the item is
58 calculated once with each set of parameters in C<cross_params>. These
59 parameters are merged with those in C<params>. Instead of being nested two
60 levels, C<data> will be nested three levels, with the third level
61 corresponding to this arrayref.
69 =item agentnum: Limit to customers with this agent.
71 =item refnum: Limit to customers with this advertising source.
73 =item cust_classnum: Limit to customers with this classnum; can be an
76 =item remove_empty: Set this to a true value to hide rows that contain
77 only zeroes. The C<indices> array in the returned data will list the item
78 indices that are actually present in the output so that you know what they
79 are. Ignored if C<cross_params> is in effect.
85 C<item_labels>, C<colors>, and C<links> may be specified as arrayrefs
86 parallel to C<items>. Those values will be returned in C<data>, with any
87 hidden rows (due to C<remove_empty>) filtered out, which is the only
88 reason to do this. Now that we have C<indices> it's probably better to
93 The C<data> method runs the report and returns a hashref of the following:
99 Month labels, in MM/YYYY format.
101 =item speriod, eperiod
103 Absolute start and end times of each month, in unix time format.
107 The values passed in as C<items>, with any suppressed rows deleted.
111 The indices of items in the input C<items> list that appear in the result
112 set. Useful for figuring out what they are when C<remove_empty> has deleted
115 =item item_labels, colors, links - see PASS-THROUGH above
119 The actual results. An arrayref corresponding to C<label> (the time axis),
120 containing arrayrefs corresponding to C<items>, containing either numbers
121 or, if C<cross_params> is given, arrayrefs corresponding to C<cross_params>.
128 local $FS::UID::AutoCommit = 0;
131 my $smonth = $self->{'start_month'};
132 my $syear = $self->{'start_year'};
133 my $emonth = $self->{'end_month'};
134 my $eyear = $self->{'end_year'};
135 # whether to extrapolate into the future
136 my $projecting = $self->{'projection'};
139 if ( $eyear < $syear or
140 ($eyear == $syear and $emonth < $smonth) ) {
141 return { error => 'Start month must be before end month' };
144 my $agentnum = $self->{'agentnum'};
145 my $refnum = $self->{'refnum'};
146 my $cust_classnum = $self->{'cust_classnum'} || [];
147 $cust_classnum = [ $cust_classnum ] if !ref($cust_classnum);
151 $self->init_projection;
153 my $thismonth = $smonth;
154 my $thisyear = $syear;
155 while ( $thisyear < $eyear ||
156 ( $thisyear == $eyear and $thismonth <= $emonth )
158 my $speriod = timelocal(0,0,0,1,$thismonth-1,$thisyear);
160 if ( $thismonth == 13 ) { $thisyear++; $thismonth = 1; }
161 my $eperiod = timelocal(0,0,0,1,$thismonth-1,$thisyear);
163 $self->extend_projection($speriod, $eperiod);
169 my $max_year = $eyear;
170 my $max_month = $emonth;
172 while ( $syear < $max_year
173 || ( $syear == $max_year && $smonth < $max_month+1 ) ) {
175 push @{$data{label}}, "$smonth/$syear"; # sprintf?
177 my $speriod = timelocal(0,0,0,1,$smonth-1,$syear);
178 push @{$data{speriod}}, $speriod;
179 if ( ++$smonth == 13 ) { $syear++; $smonth=1; }
180 my $eperiod = timelocal(0,0,0,1,$smonth-1,$syear);
181 push @{$data{eperiod}}, $eperiod;
184 my @items = @{$self->{'items'}};
187 for ( $i = 0; $i < scalar(@items); $i++ ) {
188 my $item = $items[$i];
189 my @param = $self->{'params'} ? @{ $self->{'params'}[$col] }: ();
190 push @param, 'project', $projecting;
191 push @param, 'refnum' => $refnum if $refnum;
192 push @param, 'cust_classnum' => $cust_classnum if @$cust_classnum;
194 if ( $self->{'cross_params'} ) {
196 foreach my $xparam (@{ $self->{'cross_params'} }) {
197 # @$xparam is a list of additional params to merge into the list
198 my $value = $self->$item($speriod, $eperiod, $agentnum,
203 push @{$data{data}->[$col++]}, \@xdata;
205 my $value = $self->$item($speriod, $eperiod, $agentnum, @param);
206 push @{$data{data}->[$col++]}, $value;
211 #these need to get generalized, sheesh
212 $data{'items'} = $self->{'items'};
213 $data{'item_labels'} = $self->{'item_labels'} || $self->{'items'};
214 $data{'colors'} = $self->{'colors'};
215 $data{'links'} = $self->{'links'} || [];
217 if ( !$self->{'cross_params'} and $self->{'remove_empty'} ) {
220 #these need to get generalized, sheesh
221 #(though we now return a list of item indices that are present in the
222 #output, so the front-end code could do this)
229 foreach my $item ( @{$self->{'items'}} ) {
231 if ( grep { $_ != 0 } @{$data{'data'}->[$col]} ) {
232 push @newitems, $data{'items'}->[$col];
233 push @newlabels, $data{'item_labels'}->[$col];
234 push @newdata, $data{'data'}->[$col];
235 push @newcolors, $data{'colors'}->[$col];
236 push @newlinks, $data{'links'}->[$col];
243 $data{'items'} = \@newitems;
244 $data{'item_labels'} = \@newlabels;
245 $data{'data'} = \@newdata;
246 $data{'colors'} = \@newcolors;
247 $data{'links'} = \@newlinks;
248 $data{'indices'} = \@indices;
251 # clean up after ourselves
253 # leave in until development is finished, for diagnostics