1 package FS::Report::Table;
4 use vars qw( @ISA $DEBUG );
6 use Time::Local qw( timelocal );
11 $DEBUG = 0; # turning this on will trace all SQL statements, VERY noisy
12 @ISA = qw( FS::Report );
16 FS::Report::Table - Tables of report data
20 See the more specific report objects, currently only FS::Report::Table::Monthly
24 sub invoiced { #invoiced
25 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
30 LEFT JOIN cust_main USING ( custnum )
31 WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
32 . (%opt ? $self->for_custnum(%opt) : '')
37 sub netsales { #net sales
38 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
40 $self->invoiced($speriod,$eperiod,$agentnum,%opt)
41 - $self->netcredits($speriod,$eperiod,$agentnum,%opt);
47 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
49 $self->payments($speriod, $eperiod, $agentnum, %opt)
50 - $self->refunds( $speriod, $eperiod, $agentnum, %opt);
54 my( $self, $speriod, $eperiod, $agentnum ) = @_;
56 $self->receipts($speriod, $eperiod, $agentnum)
57 - $self->netrefunds( $speriod, $eperiod, $agentnum);
61 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
65 LEFT JOIN cust_main USING ( custnum )
66 WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
67 . (%opt ? $self->for_custnum(%opt) : '')
72 my( $self, $speriod, $eperiod, $agentnum ) = @_;
76 LEFT JOIN cust_main USING ( custnum )
77 WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
82 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
86 LEFT JOIN cust_main USING ( custnum )
87 WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
88 . (%opt ? $self->for_custnum(%opt) : '')
93 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
95 SELECT SUM(cust_credit_bill.amount)
97 LEFT JOIN cust_bill USING ( invnum )
98 LEFT JOIN cust_main USING ( custnum )
99 WHERE ". $self->in_time_period_and_agent( $speriod,
104 . (%opt ? $self->for_custnum(%opt) : '')
108 sub receipts { #net payments
109 my( $self, $speriod, $eperiod, $agentnum ) = @_;
111 SELECT SUM(cust_bill_pay.amount)
113 LEFT JOIN cust_bill USING ( invnum )
114 LEFT JOIN cust_main USING ( custnum )
115 WHERE ". $self->in_time_period_and_agent( $speriod,
124 my( $self, $speriod, $eperiod, $agentnum ) = @_;
126 SELECT SUM(cust_credit_refund.amount)
127 FROM cust_credit_refund
128 LEFT JOIN cust_credit USING ( crednum )
129 LEFT JOIN cust_main USING ( custnum )
130 WHERE ". $self->in_time_period_and_agent( $speriod,
138 #these should be auto-generated or $AUTOLOADed or something
140 my( $self, $speriod, $eperiod, $agentnum ) = @_;
141 $speriod = $self->_subtract_11mo($speriod);
142 $self->invoiced($speriod, $eperiod, $agentnum);
146 my( $self, $speriod, $eperiod, $agentnum ) = @_;
147 $speriod = $self->_subtract_11mo($speriod);
148 $self->netsales($speriod, $eperiod, $agentnum);
152 my( $self, $speriod, $eperiod, $agentnum ) = @_;
153 $speriod = $self->_subtract_11mo($speriod);
154 $self->receipts($speriod, $eperiod, $agentnum);
158 my( $self, $speriod, $eperiod, $agentnum ) = @_;
159 $speriod = $self->_subtract_11mo($speriod);
160 $self->payments($speriod, $eperiod, $agentnum);
164 my( $self, $speriod, $eperiod, $agentnum ) = @_;
165 $speriod = $self->_subtract_11mo($speriod);
166 $self->credits($speriod, $eperiod, $agentnum);
169 sub netcredits_12mo {
170 my( $self, $speriod, $eperiod, $agentnum ) = @_;
171 $speriod = $self->_subtract_11mo($speriod);
172 $self->netcredits($speriod, $eperiod, $agentnum);
176 my( $self, $speriod, $eperiod, $agentnum ) = @_;
177 $speriod = $self->_subtract_11mo($speriod);
178 $self->cashflow($speriod, $eperiod, $agentnum);
181 sub netcashflow_12mo {
182 my( $self, $speriod, $eperiod, $agentnum ) = @_;
183 $speriod = $self->_subtract_11mo($speriod);
184 $self->cashflow($speriod, $eperiod, $agentnum);
188 my( $self, $speriod, $eperiod, $agentnum ) = @_;
189 $speriod = $self->_subtract_11mo($speriod);
190 $self->refunds($speriod, $eperiod, $agentnum);
193 sub netrefunds_12mo {
194 my( $self, $speriod, $eperiod, $agentnum ) = @_;
195 $speriod = $self->_subtract_11mo($speriod);
196 $self->netrefunds($speriod, $eperiod, $agentnum);
200 #not being too bad with the false laziness
202 my($self, $time) = @_;
203 my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
205 if ( $mon < 0 ) { $mon+=12; $year--; }
206 timelocal($sec,$min,$hour,$mday,$mon,$year);
209 sub cust_pkg_setup_cost {
210 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
213 if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
215 $comparison = 'IS NULL';
218 $comparison = "= $1";
220 $where = "AND part_pkg.classnum $comparison";
222 $agentnum ||= $opt{'agentnum'};
224 my $total_sql = " SELECT SUM(part_pkg.setup_cost) ";
225 $total_sql .= " FROM cust_pkg
226 LEFT JOIN cust_main USING ( custnum )
227 LEFT JOIN part_pkg USING ( pkgpart )
230 AND ".$self->in_time_period_and_agent(
231 $speriod, $eperiod, $agentnum, 'cust_pkg.setup');
232 return $self->scalar_sql($total_sql);
235 sub cust_pkg_recur_cost {
236 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
239 if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
241 $comparison = 'IS NULL';
244 $comparison = "= $1";
246 $where = " AND part_pkg.classnum $comparison";
248 $agentnum ||= $opt{'agentnum'};
249 # duplication of in_time_period_and_agent
250 # because we do it a little differently here
251 $where .= " AND cust_main.agentnum = $agentnum" if $agentnum;
253 $FS::CurrentUser::CurrentUser->agentnums_sql('table' => 'cust_main');
255 my $total_sql = " SELECT SUM(part_pkg.recur_cost) ";
256 $total_sql .= " FROM cust_pkg
257 LEFT JOIN cust_main USING ( custnum )
258 LEFT JOIN part_pkg USING ( pkgpart )
261 AND cust_pkg.setup < $eperiod
262 AND (cust_pkg.cancel > $speriod OR cust_pkg.cancel IS NULL)
264 return $self->scalar_sql($total_sql);
268 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
272 if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
274 $comparison = "IS NULL";
276 $comparison = "= $1";
279 if ( $opt{'use_override'} ) {
281 part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
282 override.classnum $comparison AND pkgpart_override IS NOT NULL
285 $where = "AND part_pkg.classnum $comparison";
289 $agentnum ||= $opt{'agentnum'};
292 " SELECT COALESCE( SUM(cust_bill_pkg.setup + cust_bill_pkg.recur), 0 ) ";
295 " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
296 if $opt{average_per_cust_pkg};
300 LEFT JOIN cust_bill USING ( invnum )
301 LEFT JOIN cust_main USING ( custnum )
302 LEFT JOIN cust_pkg USING ( pkgnum )
303 LEFT JOIN part_pkg USING ( pkgpart )
304 LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
307 AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
309 if ($opt{use_usage} && $opt{use_usage} eq 'recurring') {
310 my $total = $self->scalar_sql($total_sql);
311 my $usage = cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
312 return $total-$usage;
313 } elsif ($opt{use_usage} && $opt{use_usage} eq 'usage') {
314 return cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
316 return $self->scalar_sql($total_sql);
320 sub cust_bill_pkg_detail {
321 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
323 my @where = ( "cust_bill_pkg.pkgnum != 0" );
325 if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
327 $comparison = "IS NULL";
329 $comparison = "= $1";
332 if ( $opt{'use_override'} ) {
334 part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
335 override.classnum $comparison AND pkgpart_override IS NOT NULL
338 push @where, "part_pkg.classnum $comparison";
342 if ( $opt{'usageclass'} =~ /^(\d+)$/ ) {
344 $comparison = "IS NULL";
346 $comparison = "= $1";
349 push @where, "cust_bill_pkg_detail.classnum $comparison";
352 $agentnum ||= $opt{'agentnum'};
354 my $where = join( ' AND ', @where );
356 my $total_sql = " SELECT SUM(amount) ";
359 " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
360 if $opt{average_per_cust_pkg};
363 " FROM cust_bill_pkg_detail
364 LEFT JOIN cust_bill_pkg USING ( billpkgnum )
365 LEFT JOIN cust_bill ON cust_bill_pkg.invnum = cust_bill.invnum
366 LEFT JOIN cust_main USING ( custnum )
367 LEFT JOIN cust_pkg ON cust_bill_pkg.pkgnum = cust_pkg.pkgnum
368 LEFT JOIN part_pkg USING ( pkgpart )
369 LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
371 AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
373 $self->scalar_sql($total_sql);
377 sub cust_bill_pkg_discount {
378 my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
381 #my $comparison = '';
382 #if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
384 # $comparison = "IS NULL";
386 # $comparison = "= $1";
389 # if ( $opt{'use_override'} ) {
391 # part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
392 # override.classnum $comparison AND pkgpart_override IS NOT NULL
395 # $where = "part_pkg.classnum $comparison";
399 $agentnum ||= $opt{'agentnum'};
402 " SELECT COALESCE( SUM( cust_bill_pkg_discount.amount ), 0 ) ";
405 # " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
406 # if $opt{average_per_cust_pkg};
409 " FROM cust_bill_pkg_discount
410 LEFT JOIN cust_bill_pkg USING ( billpkgnum )
411 LEFT JOIN cust_bill USING ( invnum )
412 LEFT JOIN cust_main USING ( custnum )
413 WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
414 # LEFT JOIN cust_pkg_discount USING ( pkgdiscountnum )
415 # LEFT JOIN discount USING ( discountnum )
416 # LEFT JOIN cust_pkg USING ( pkgnum )
417 # LEFT JOIN part_pkg USING ( pkgpart )
418 # LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
420 return $self->scalar_sql($total_sql);
424 sub setup_pkg { shift->pkg_field( @_, 'setup' ); }
425 sub susp_pkg { shift->pkg_field( @_, 'susp' ); }
426 sub cancel_pkg { shift->pkg_field( @_, 'cancel'); }
429 my( $self, $speriod, $eperiod, $agentnum, $field ) = @_;
431 SELECT COUNT(*) FROM cust_pkg
432 LEFT JOIN cust_main USING ( custnum )
433 WHERE ". $self->in_time_period_and_agent( $speriod,
442 #this is going to be harder..
444 # my( $self, $speriod, $eperiod, $agentnum ) = @_;
445 # $self->scalar_sql("
446 # SELECT COUNT(*) FROM h_cust_pkg
451 sub in_time_period_and_agent {
452 my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4);
453 my $col = @_ ? shift() : '_date';
455 my $sql = "$col >= $speriod AND $col < $eperiod";
458 $sql .= " AND cust_main.agentnum = $agentnum"
461 #agent virtualization
463 $FS::CurrentUser::CurrentUser->agentnums_sql( 'table'=>'cust_main' );
469 my ( $self, %opt ) = @_;
470 return '' unless $opt{'custnum'};
471 $opt{'custnum'} =~ /^\d+$/ ? " and custnum = $opt{custnum} " : '';
475 my( $self, $sql ) = ( shift, shift );
476 my $sth = dbh->prepare($sql) or die dbh->errstr;
477 warn "FS::Report::Table::Monthly\n$sql\n" if $DEBUG;
479 or die "Unexpected error executing statement $sql: ". $sth->errstr;
480 $sth->fetchrow_arrayref->[0] || 0;
491 L<FS::Report::Table::Monthly>, reports in the web interface.