);
use FS::Misc::eps2png qw( eps2png );
use FS::Report::FCC_477;
+ use FS::Report::Table;
use FS::Report::Table::Monthly;
+ use FS::Report::Table::Daily;
use FS::TicketSystem;
use FS::NetworkMonitoringSystem;
use FS::Tron qw( tron_lint );
package FS::Report::Table;
use strict;
-use vars qw( @ISA );
+use vars qw( @ISA $DEBUG );
use FS::Report;
+use Time::Local qw( timelocal );
+use FS::UID qw( dbh );
+use FS::Report::Table;
+use FS::CurrentUser;
+$DEBUG = 0; # turning this on will trace all SQL statements, VERY noisy
@ISA = qw( FS::Report );
=head1 NAME
See the more specific report objects, currently only FS::Report::Table::Monthly
+=cut
+
+sub invoiced { #invoiced
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ $self->scalar_sql("
+ SELECT SUM(charged)
+ FROM cust_bill
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ . (%opt ? $self->for_custnum(%opt) : '')
+ );
+
+}
+
+sub netsales { #net sales
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ $self->invoiced($speriod,$eperiod,$agentnum,%opt)
+ - $self->netcredits($speriod,$eperiod,$agentnum,%opt);
+}
+
+#deferred revenue
+
+sub cashflow {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ $self->payments($speriod, $eperiod, $agentnum, %opt)
+ - $self->refunds( $speriod, $eperiod, $agentnum, %opt);
+}
+
+sub netcashflow {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+
+ $self->receipts($speriod, $eperiod, $agentnum)
+ - $self->netrefunds( $speriod, $eperiod, $agentnum);
+}
+
+sub payments {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(paid)
+ FROM cust_pay
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ . (%opt ? $self->for_custnum(%opt) : '')
+ );
+}
+
+sub credits {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(amount)
+ FROM cust_credit
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ );
+}
+
+sub refunds {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(refund)
+ FROM cust_refund
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+ . (%opt ? $self->for_custnum(%opt) : '')
+ );
+}
+
+sub netcredits {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(cust_credit_bill.amount)
+ FROM cust_credit_bill
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ 'cust_bill._date'
+ )
+ . (%opt ? $self->for_custnum(%opt) : '')
+ );
+}
+
+sub receipts { #net payments
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(cust_bill_pay.amount)
+ FROM cust_bill_pay
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ 'cust_bill._date'
+ )
+ );
+}
+
+sub netrefunds {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $self->scalar_sql("
+ SELECT SUM(cust_credit_refund.amount)
+ FROM cust_credit_refund
+ LEFT JOIN cust_credit USING ( crednum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ 'cust_credit._date'
+ )
+ );
+}
+
+#these should be auto-generated or $AUTOLOADed or something
+sub invoiced_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->invoiced($speriod, $eperiod, $agentnum);
+}
+
+sub netsales_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->netsales($speriod, $eperiod, $agentnum);
+}
+
+sub receipts_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->receipts($speriod, $eperiod, $agentnum);
+}
+
+sub payments_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->payments($speriod, $eperiod, $agentnum);
+}
+
+sub credits_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->credits($speriod, $eperiod, $agentnum);
+}
+
+sub netcredits_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->netcredits($speriod, $eperiod, $agentnum);
+}
+
+sub cashflow_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->cashflow($speriod, $eperiod, $agentnum);
+}
+
+sub netcashflow_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->cashflow($speriod, $eperiod, $agentnum);
+}
+
+sub refunds_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->refunds($speriod, $eperiod, $agentnum);
+}
+
+sub netrefunds_12mo {
+ my( $self, $speriod, $eperiod, $agentnum ) = @_;
+ $speriod = $self->_subtract_11mo($speriod);
+ $self->netrefunds($speriod, $eperiod, $agentnum);
+}
+
+
+#not being too bad with the false laziness
+sub _subtract_11mo {
+ my($self, $time) = @_;
+ my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
+ $mon -= 11;
+ if ( $mon < 0 ) { $mon+=12; $year--; }
+ timelocal($sec,$min,$hour,$mday,$mon,$year);
+}
+
+sub cust_pkg_setup_cost {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+ my $where = '';
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = 'IS NULL';
+ }
+ else {
+ $comparison = "= $1";
+ }
+ $where = "AND part_pkg.classnum $comparison";
+ }
+ $agentnum ||= $opt{'agentnum'};
+
+ my $total_sql = " SELECT SUM(part_pkg.setup_cost) ";
+ $total_sql .= " FROM cust_pkg
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ WHERE pkgnum != 0
+ $where
+ AND ".$self->in_time_period_and_agent(
+ $speriod, $eperiod, $agentnum, 'cust_pkg.setup');
+ return $self->scalar_sql($total_sql);
+}
+
+sub cust_pkg_recur_cost {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+ my $where = '';
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = 'IS NULL';
+ }
+ else {
+ $comparison = "= $1";
+ }
+ $where = " AND part_pkg.classnum $comparison";
+ }
+ $agentnum ||= $opt{'agentnum'};
+ # duplication of in_time_period_and_agent
+ # because we do it a little differently here
+ $where .= " AND cust_main.agentnum = $agentnum" if $agentnum;
+ $where .= " AND ".
+ $FS::CurrentUser::CurrentUser->agentnums_sql('table' => 'cust_main');
+
+ my $total_sql = " SELECT SUM(part_pkg.recur_cost) ";
+ $total_sql .= " FROM cust_pkg
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ WHERE pkgnum != 0
+ $where
+ AND cust_pkg.setup < $eperiod
+ AND (cust_pkg.cancel > $speriod OR cust_pkg.cancel IS NULL)
+ ";
+ return $self->scalar_sql($total_sql);
+}
+
+sub cust_bill_pkg {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ my $where = '';
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ if ( $opt{'use_override'} ) {
+ $where = "AND (
+ part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ override.classnum $comparison AND pkgpart_override IS NOT NULL
+ )";
+ } else {
+ $where = "AND part_pkg.classnum $comparison";
+ }
+ }
+
+ $agentnum ||= $opt{'agentnum'};
+
+ my $total_sql =
+ " SELECT COALESCE( SUM(cust_bill_pkg.setup + cust_bill_pkg.recur), 0 ) ";
+
+ $total_sql .=
+ " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
+ if $opt{average_per_cust_pkg};
+
+ $total_sql .=
+ " FROM cust_bill_pkg
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
+ WHERE pkgnum != 0
+ $where
+ AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
+
+ if ($opt{use_usage} && $opt{use_usage} eq 'recurring') {
+ my $total = $self->scalar_sql($total_sql);
+ my $usage = cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
+ return $total-$usage;
+ } elsif ($opt{use_usage} && $opt{use_usage} eq 'usage') {
+ return cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
+ } else {
+ return $self->scalar_sql($total_sql);
+ }
+}
+
+sub cust_bill_pkg_detail {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ my @where = ( "cust_bill_pkg.pkgnum != 0" );
+ my $comparison = '';
+ if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ if ( $opt{'use_override'} ) {
+ push @where, "(
+ part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ override.classnum $comparison AND pkgpart_override IS NOT NULL
+ )";
+ } else {
+ push @where, "part_pkg.classnum $comparison";
+ }
+ }
+
+ if ( $opt{'usageclass'} =~ /^(\d+)$/ ) {
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $comparison = "= $1";
+ }
+
+ push @where, "cust_bill_pkg_detail.classnum $comparison";
+ }
+
+ $agentnum ||= $opt{'agentnum'};
+
+ my $where = join( ' AND ', @where );
+
+ my $total_sql = " SELECT SUM(amount) ";
+
+ $total_sql .=
+ " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
+ if $opt{average_per_cust_pkg};
+
+ $total_sql .=
+ " FROM cust_bill_pkg_detail
+ LEFT JOIN cust_bill_pkg USING ( billpkgnum )
+ LEFT JOIN cust_bill ON cust_bill_pkg.invnum = cust_bill.invnum
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN cust_pkg ON cust_bill_pkg.pkgnum = cust_pkg.pkgnum
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
+ WHERE $where
+ AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
+
+ $self->scalar_sql($total_sql);
+
+}
+
+sub cust_bill_pkg_discount {
+ my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+ #my $where = '';
+ #my $comparison = '';
+ #if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+ # if ( $1 == 0 ) {
+ # $comparison = "IS NULL";
+ # } else {
+ # $comparison = "= $1";
+ # }
+ #
+ # if ( $opt{'use_override'} ) {
+ # $where = "(
+ # part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ # override.classnum $comparison AND pkgpart_override IS NOT NULL
+ # )";
+ # } else {
+ # $where = "part_pkg.classnum $comparison";
+ # }
+ #}
+
+ $agentnum ||= $opt{'agentnum'};
+
+ my $total_sql =
+ " SELECT COALESCE( SUM( cust_bill_pkg_discount.amount ), 0 ) ";
+
+ #$total_sql .=
+ # " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
+ # if $opt{average_per_cust_pkg};
+
+ $total_sql .=
+ " FROM cust_bill_pkg_discount
+ LEFT JOIN cust_bill_pkg USING ( billpkgnum )
+ LEFT JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
+ # LEFT JOIN cust_pkg_discount USING ( pkgdiscountnum )
+ # LEFT JOIN discount USING ( discountnum )
+ # LEFT JOIN cust_pkg USING ( pkgnum )
+ # LEFT JOIN part_pkg USING ( pkgpart )
+ # LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
+
+ return $self->scalar_sql($total_sql);
+
+}
+
+sub setup_pkg { shift->pkg_field( @_, 'setup' ); }
+sub susp_pkg { shift->pkg_field( @_, 'susp' ); }
+sub cancel_pkg { shift->pkg_field( @_, 'cancel'); }
+
+sub pkg_field {
+ my( $self, $speriod, $eperiod, $agentnum, $field ) = @_;
+ $self->scalar_sql("
+ SELECT COUNT(*) FROM cust_pkg
+ LEFT JOIN cust_main USING ( custnum )
+ WHERE ". $self->in_time_period_and_agent( $speriod,
+ $eperiod,
+ $agentnum,
+ "cust_pkg.$field",
+ )
+ );
+
+}
+
+#this is going to be harder..
+#sub unsusp_pkg {
+# my( $self, $speriod, $eperiod, $agentnum ) = @_;
+# $self->scalar_sql("
+# SELECT COUNT(*) FROM h_cust_pkg
+# WHERE
+#
+#}
+
+sub in_time_period_and_agent {
+ my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4);
+ my $col = @_ ? shift() : '_date';
+
+ my $sql = "$col >= $speriod AND $col < $eperiod";
+
+ #agent selection
+ $sql .= " AND cust_main.agentnum = $agentnum"
+ if $agentnum;
+
+ #agent virtualization
+ $sql .= ' AND '.
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table'=>'cust_main' );
+
+ $sql;
+}
+
+sub for_custnum {
+ my ( $self, %opt ) = @_;
+ return '' unless $opt{'custnum'};
+ $opt{'custnum'} =~ /^\d+$/ ? " and custnum = $opt{custnum} " : '';
+}
+
+sub scalar_sql {
+ my( $self, $sql ) = ( shift, shift );
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ warn "FS::Report::Table::Monthly\n$sql\n" if $DEBUG;
+ $sth->execute
+ or die "Unexpected error executing statement $sql: ". $sth->errstr;
+ $sth->fetchrow_arrayref->[0] || 0;
+}
+
+=back
+
=head1 BUGS
Documentation.
--- /dev/null
+package FS::Report::Table::Daily;
+
+use strict;
+use vars qw( @ISA );
+use FS::Report::Table;
+use Time::Local qw( timelocal );
+
+@ISA = qw( FS::Report::Table );
+
+=head1 NAME
+
+FS::Report::Table::Daily - Tables of report data, indexed daily
+
+=head1 SYNOPSIS
+
+ use FS::Report::Table::Daily;
+
+ my $report = new FS::Report::Table::Daily (
+ 'items' => [ 'invoiced', 'netsales', 'credits', 'receipts', ],
+ 'start_month' => 4,
+ 'start_year' => 2000,
+ 'end_month' => 4,
+ 'end_year' => 2020,
+ 'start_day' => 2,
+ 'end_day' => 27,
+ #opt
+ 'agentnum' => 54
+ 'params' => [ [ 'paramsfor', 'item_one' ], [ 'item', 'two' ] ], # ...
+ 'remove_empty' => 1, #collapse empty rows, default 0
+ 'item_labels' => [ ], #useful with remove_empty
+ );
+
+ my $data = $report->data;
+
+=head1 METHODS
+
+=over 4
+
+=item data
+
+Returns a hashref of data (!! describe)
+
+=cut
+
+sub data {
+ my $self = shift;
+
+ my $sday = $self->{'start_day'};
+ my $smonth = $self->{'start_month'};
+ my $syear = $self->{'start_year'};
+ my $eday = $self->{'end_day'};
+ my $emonth = $self->{'end_month'};
+ my $eyear = $self->{'end_year'};
+ my $agentnum = $self->{'agentnum'};
+
+ my %data;
+
+ my $sdate = timelocal(0,0,0,$sday,$smonth-1,$syear);
+ my $edate = timelocal(0,0,0,$eday,$emonth-1,$eyear);
+
+ warn "daily range $sdate $edate\n";
+
+ # XXX: use date_format config for the labels since we have day in the labels now?
+ # XXX: leap seconds / DST
+ while ( $sdate < $edate ) {
+ my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($sdate);
+ $mon++;
+ $year += 1900;
+ warn "label=$mday/$mon/$year\n";
+ push @{$data{label}}, "$mday/$mon/$year";
+
+ my $speriod = $sdate;
+ $sdate += 86400;
+ my $eperiod = $sdate;
+
+ push @{$data{speriod}}, $speriod;
+ push @{$data{eperiod}}, $eperiod;
+
+ my $col = 0;
+ my @items = @{$self->{'items'}};
+ my $i;
+ for ( $i = 0; $i < scalar(@items); $i++ ) {
+ my $item = $items[$i];
+ my @param = $self->{'params'} ? @{ $self->{'params'}[$col] }: ();
+ my $value = $self->$item($speriod, $eperiod, $agentnum, @param);
+ push @{$data{data}->[$col++]}, $value;
+ }
+ }
+
+ #these need to get generalized, sheesh
+ $data{'items'} = $self->{'items'};
+ $data{'item_labels'} = $self->{'item_labels'} || $self->{'items'};
+ $data{'colors'} = $self->{'colors'};
+ $data{'links'} = $self->{'links'} || [];
+
+ if ( $self->{'remove_empty'} ) {
+
+ my $col = 0;
+ #these need to get generalized, sheesh
+ my @newitems = ();
+ my @newlabels = ();
+ my @newdata = ();
+ my @newcolors = ();
+ my @newlinks = ();
+ foreach my $item ( @{$self->{'items'}} ) {
+
+ if ( grep { $_ != 0 } @{$data{'data'}->[$col]} ) {
+ push @newitems, $data{'items'}->[$col];
+ push @newlabels, $data{'item_labels'}->[$col];
+ push @newdata, $data{'data'}->[$col];
+ push @newcolors, $data{'colors'}->[$col];
+ push @newlinks, $data{'links'}->[$col];
+ }
+
+ $col++;
+ }
+
+ $data{'items'} = \@newitems;
+ $data{'item_labels'} = \@newlabels;
+ $data{'data'} = \@newdata;
+ $data{'colors'} = \@newcolors;
+ $data{'links'} = \@newlinks;
+
+ }
+
+ \%data;
+
+}
+
+=back
+
+=head1 BUGS
+
+Documentation.
+
+=head1 SEE ALSO
+
+=cut
+
+1;
package FS::Report::Table::Monthly;
use strict;
-use vars qw( @ISA $DEBUG );
-use Time::Local;
-use FS::UID qw( dbh );
+use vars qw( @ISA );
use FS::Report::Table;
-use FS::CurrentUser;
+use Time::Local qw( timelocal );
@ISA = qw( FS::Report::Table );
-$DEBUG = 0; # turning this on will trace all SQL statements, VERY noisy
=head1 NAME
sub data {
my $self = shift;
- #use Data::Dumper;
- #warn Dumper($self);
-
my $smonth = $self->{'start_month'};
my $syear = $self->{'start_year'};
my $emonth = $self->{'end_month'};
$data{'colors'} = $self->{'colors'};
$data{'links'} = $self->{'links'} || [];
- #use Data::Dumper;
- #warn Dumper(\%data);
-
if ( $self->{'remove_empty'} ) {
- #warn "removing empty rows\n";
-
my $col = 0;
#these need to get generalized, sheesh
my @newitems = ();
}
- #use Data::Dumper;
- #warn Dumper(\%data);
-
\%data;
-
-}
-
-sub invoiced { #invoiced
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
- $self->scalar_sql("
- SELECT SUM(charged)
- FROM cust_bill
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- . (%opt ? $self->for_custnum(%opt) : '')
- );
-
-}
-
-sub netsales { #net sales
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
- $self->invoiced($speriod,$eperiod,$agentnum,%opt)
- - $self->netcredits($speriod,$eperiod,$agentnum,%opt);
-}
-
-#deferred revenue
-
-sub cashflow {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
- $self->payments($speriod, $eperiod, $agentnum, %opt)
- - $self->refunds( $speriod, $eperiod, $agentnum, %opt);
-}
-
-sub netcashflow {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
-
- $self->receipts($speriod, $eperiod, $agentnum)
- - $self->netrefunds( $speriod, $eperiod, $agentnum);
-}
-
-sub payments {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
- $self->scalar_sql("
- SELECT SUM(paid)
- FROM cust_pay
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- . (%opt ? $self->for_custnum(%opt) : '')
- );
-}
-
-sub credits {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $self->scalar_sql("
- SELECT SUM(amount)
- FROM cust_credit
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- );
-}
-
-sub refunds {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
- $self->scalar_sql("
- SELECT SUM(refund)
- FROM cust_refund
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
- . (%opt ? $self->for_custnum(%opt) : '')
- );
-}
-
-sub netcredits {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
- $self->scalar_sql("
- SELECT SUM(cust_credit_bill.amount)
- FROM cust_credit_bill
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- 'cust_bill._date'
- )
- . (%opt ? $self->for_custnum(%opt) : '')
- );
-}
-
-sub receipts { #net payments
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $self->scalar_sql("
- SELECT SUM(cust_bill_pay.amount)
- FROM cust_bill_pay
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- 'cust_bill._date'
- )
- );
-}
-
-sub netrefunds {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $self->scalar_sql("
- SELECT SUM(cust_credit_refund.amount)
- FROM cust_credit_refund
- LEFT JOIN cust_credit USING ( crednum )
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- 'cust_credit._date'
- )
- );
-}
-
-#these should be auto-generated or $AUTOLOADed or something
-sub invoiced_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->invoiced($speriod, $eperiod, $agentnum);
-}
-
-sub netsales_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->netsales($speriod, $eperiod, $agentnum);
-}
-
-sub receipts_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->receipts($speriod, $eperiod, $agentnum);
-}
-
-sub payments_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->payments($speriod, $eperiod, $agentnum);
-}
-
-sub credits_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->credits($speriod, $eperiod, $agentnum);
-}
-
-sub netcredits_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->netcredits($speriod, $eperiod, $agentnum);
-}
-
-sub cashflow_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->cashflow($speriod, $eperiod, $agentnum);
-}
-
-sub netcashflow_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->cashflow($speriod, $eperiod, $agentnum);
-}
-
-sub refunds_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->refunds($speriod, $eperiod, $agentnum);
-}
-
-sub netrefunds_12mo {
- my( $self, $speriod, $eperiod, $agentnum ) = @_;
- $speriod = $self->_subtract_11mo($speriod);
- $self->netrefunds($speriod, $eperiod, $agentnum);
-}
-
-
-#not being too bad with the false laziness
-use Time::Local qw(timelocal);
-sub _subtract_11mo {
- my($self, $time) = @_;
- my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5];
- $mon -= 11;
- if ( $mon < 0 ) { $mon+=12; $year--; }
- timelocal($sec,$min,$hour,$mday,$mon,$year);
-}
-
-sub cust_pkg_setup_cost {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
- my $where = '';
- my $comparison = '';
- if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
- if ( $1 == 0 ) {
- $comparison = 'IS NULL';
- }
- else {
- $comparison = "= $1";
- }
- $where = "AND part_pkg.classnum $comparison";
- }
- $agentnum ||= $opt{'agentnum'};
-
- my $total_sql = " SELECT SUM(part_pkg.setup_cost) ";
- $total_sql .= " FROM cust_pkg
- LEFT JOIN cust_main USING ( custnum )
- LEFT JOIN part_pkg USING ( pkgpart )
- WHERE pkgnum != 0
- $where
- AND ".$self->in_time_period_and_agent(
- $speriod, $eperiod, $agentnum, 'cust_pkg.setup');
- return $self->scalar_sql($total_sql);
-}
-
-sub cust_pkg_recur_cost {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
- my $where = '';
- my $comparison = '';
- if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
- if ( $1 == 0 ) {
- $comparison = 'IS NULL';
- }
- else {
- $comparison = "= $1";
- }
- $where = " AND part_pkg.classnum $comparison";
- }
- $agentnum ||= $opt{'agentnum'};
- # duplication of in_time_period_and_agent
- # because we do it a little differently here
- $where .= " AND cust_main.agentnum = $agentnum" if $agentnum;
- $where .= " AND ".
- $FS::CurrentUser::CurrentUser->agentnums_sql('table' => 'cust_main');
-
- my $total_sql = " SELECT SUM(part_pkg.recur_cost) ";
- $total_sql .= " FROM cust_pkg
- LEFT JOIN cust_main USING ( custnum )
- LEFT JOIN part_pkg USING ( pkgpart )
- WHERE pkgnum != 0
- $where
- AND cust_pkg.setup < $eperiod
- AND (cust_pkg.cancel > $speriod OR cust_pkg.cancel IS NULL)
- ";
- return $self->scalar_sql($total_sql);
-}
-
-sub cust_bill_pkg {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
- my $where = '';
- my $comparison = '';
- if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
- if ( $1 == 0 ) {
- $comparison = "IS NULL";
- } else {
- $comparison = "= $1";
- }
-
- if ( $opt{'use_override'} ) {
- $where = "AND (
- part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
- override.classnum $comparison AND pkgpart_override IS NOT NULL
- )";
- } else {
- $where = "AND part_pkg.classnum $comparison";
- }
- }
-
- $agentnum ||= $opt{'agentnum'};
-
- my $total_sql =
- " SELECT COALESCE( SUM(cust_bill_pkg.setup + cust_bill_pkg.recur), 0 ) ";
-
- $total_sql .=
- " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
- if $opt{average_per_cust_pkg};
-
- $total_sql .=
- " FROM cust_bill_pkg
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN part_pkg USING ( pkgpart )
- LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
- WHERE pkgnum != 0
- $where
- AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
-
- if ($opt{use_usage} && $opt{use_usage} eq 'recurring') {
- my $total = $self->scalar_sql($total_sql);
- my $usage = cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
- return $total-$usage;
- } elsif ($opt{use_usage} && $opt{use_usage} eq 'usage') {
- return cust_bill_pkg_detail(@_); #$speriod, $eperiod, $agentnum, %opt
- } else {
- return $self->scalar_sql($total_sql);
- }
-}
-
-sub cust_bill_pkg_detail {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
- my @where = ( "cust_bill_pkg.pkgnum != 0" );
- my $comparison = '';
- if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
- if ( $1 == 0 ) {
- $comparison = "IS NULL";
- } else {
- $comparison = "= $1";
- }
-
- if ( $opt{'use_override'} ) {
- push @where, "(
- part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
- override.classnum $comparison AND pkgpart_override IS NOT NULL
- )";
- } else {
- push @where, "part_pkg.classnum $comparison";
- }
- }
-
- if ( $opt{'usageclass'} =~ /^(\d+)$/ ) {
- if ( $1 == 0 ) {
- $comparison = "IS NULL";
- } else {
- $comparison = "= $1";
- }
-
- push @where, "cust_bill_pkg_detail.classnum $comparison";
- }
-
- $agentnum ||= $opt{'agentnum'};
-
- my $where = join( ' AND ', @where );
-
- my $total_sql = " SELECT SUM(amount) ";
-
- $total_sql .=
- " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
- if $opt{average_per_cust_pkg};
-
- $total_sql .=
- " FROM cust_bill_pkg_detail
- LEFT JOIN cust_bill_pkg USING ( billpkgnum )
- LEFT JOIN cust_bill ON cust_bill_pkg.invnum = cust_bill.invnum
- LEFT JOIN cust_main USING ( custnum )
- LEFT JOIN cust_pkg ON cust_bill_pkg.pkgnum = cust_pkg.pkgnum
- LEFT JOIN part_pkg USING ( pkgpart )
- LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
- WHERE $where
- AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
-
- $self->scalar_sql($total_sql);
-
-}
-
-sub cust_bill_pkg_discount {
- my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
-
- #my $where = '';
- #my $comparison = '';
- #if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
- # if ( $1 == 0 ) {
- # $comparison = "IS NULL";
- # } else {
- # $comparison = "= $1";
- # }
- #
- # if ( $opt{'use_override'} ) {
- # $where = "(
- # part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
- # override.classnum $comparison AND pkgpart_override IS NOT NULL
- # )";
- # } else {
- # $where = "part_pkg.classnum $comparison";
- # }
- #}
-
- $agentnum ||= $opt{'agentnum'};
-
- my $total_sql =
- " SELECT COALESCE( SUM( cust_bill_pkg_discount.amount ), 0 ) ";
-
- #$total_sql .=
- # " / CASE COUNT(cust_pkg.*) WHEN 0 THEN 1 ELSE COUNT(cust_pkg.*) END "
- # if $opt{average_per_cust_pkg};
-
- $total_sql .=
- " FROM cust_bill_pkg_discount
- LEFT JOIN cust_bill_pkg USING ( billpkgnum )
- LEFT JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum);
- # LEFT JOIN cust_pkg_discount USING ( pkgdiscountnum )
- # LEFT JOIN discount USING ( discountnum )
- # LEFT JOIN cust_pkg USING ( pkgnum )
- # LEFT JOIN part_pkg USING ( pkgpart )
- # LEFT JOIN part_pkg AS override ON pkgpart_override = override.pkgpart
-
- return $self->scalar_sql($total_sql);
-
-}
-
-sub setup_pkg { shift->pkg_field( @_, 'setup' ); }
-sub susp_pkg { shift->pkg_field( @_, 'susp' ); }
-sub cancel_pkg { shift->pkg_field( @_, 'cancel'); }
-
-sub pkg_field {
- my( $self, $speriod, $eperiod, $agentnum, $field ) = @_;
- $self->scalar_sql("
- SELECT COUNT(*) FROM cust_pkg
- LEFT JOIN cust_main USING ( custnum )
- WHERE ". $self->in_time_period_and_agent( $speriod,
- $eperiod,
- $agentnum,
- "cust_pkg.$field",
- )
- );
-
-}
-
-#this is going to be harder..
-#sub unsusp_pkg {
-# my( $self, $speriod, $eperiod, $agentnum ) = @_;
-# $self->scalar_sql("
-# SELECT COUNT(*) FROM h_cust_pkg
-# WHERE
-#
-#}
-
-sub in_time_period_and_agent {
- my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4);
- my $col = @_ ? shift() : '_date';
-
- my $sql = "$col >= $speriod AND $col < $eperiod";
-
- #agent selection
- $sql .= " AND cust_main.agentnum = $agentnum"
- if $agentnum;
-
- #agent virtualization
- $sql .= ' AND '.
- $FS::CurrentUser::CurrentUser->agentnums_sql( 'table'=>'cust_main' );
-
- $sql;
-}
-
-sub for_custnum {
- my ( $self, %opt ) = @_;
- return '' unless $opt{'custnum'};
- $opt{'custnum'} =~ /^\d+$/ ? " and custnum = $opt{custnum} " : '';
-}
-
-sub scalar_sql {
- my( $self, $sql ) = ( shift, shift );
- my $sth = dbh->prepare($sql) or die dbh->errstr;
- warn "FS::Report::Table::Monthly\n$sql\n" if $DEBUG;
- $sth->execute
- or die "Unexpected error executing statement $sql: ". $sth->errstr;
- $sth->fetchrow_arrayref->[0] || 0;
}
=back
%report_financial = (
'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts summary graph' ],
+ 'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ],
'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ],
'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ],
'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ],
'links' => \@links, # or \%link, #opt
'link_fromparam' => 'param_from', #defaults to 'begin'
'link_toparam' => 'param_to', #defaults to 'end'
+ 'daily' => 1, # omit for monthly granularity
#optional, pulled from CGI params if not specified
'start_month' => $smonth,
'end_month' => $emonth,
'end_year' => $eyear,
+
+ #optional, pulled from CGI params if not specified,
+ #only if 'daily' option is given
+ 'start_day' => $sday,
+ 'end_day' => $eday,
+
#optional
'agentnum' => $agentnum,
'nototal' => 1,
'data' => $data->{'data'},
'row_labels' => $data->{'item_labels'},
'graph_labels' => $opt{'graph_labels'} || $data->{'item_labels'},
- 'col_labels' => [ map { my $m = $_; $m =~ s/^(\d+)\//$mon[$1-1] / ; $m }
- @{$data->{label}} ],
+ 'col_labels' => $col_labels,
'axis_labels' => $data->{label},
'colors' => $data->{colors},
'links' => \@links,
$opt{'end_month'} ||= $cgi->param('end_month'); # || $curmon+1;
$opt{'end_year'} ||= $cgi->param('end_year'); # || 1900+$curyear;
-my $report = new FS::Report::Table::Monthly (
-
- 'items' => \@items,
- 'params' => $opt{'params'},
- 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/
- ? $opt{'graph_labels'}
- : $opt{'labels'}
- ),
- 'colors' => $opt{'colors'},
- 'links' => $opt{'links'},
-
- 'start_month' => $opt{'start_month'},
- 'start_year' => $opt{'start_year'},
- 'end_month' => $opt{'end_month'},
- 'end_year' => $opt{'end_year'},
-
- 'agentnum' => $opt{'agentnum'},
- 'remove_empty' => $opt{'remove_empty'},
- 'doublemonths' => $opt{'doublemonths'},
+if ( $opt{'daily'} ) { # daily granularity
+ $opt{'start_day'} ||= $cgi->param('start_day');
+ $opt{'end_day'} ||= $cgi->param('end_day');
+}
+
+my %reportopts = (
+ 'items' => \@items,
+ 'params' => $opt{'params'},
+ 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/
+ ? $opt{'graph_labels'}
+ : $opt{'labels'}
+ ),
+ 'colors' => $opt{'colors'},
+ 'links' => $opt{'links'},
+
+ 'start_day' => $opt{'start_day'},
+ 'start_month' => $opt{'start_month'},
+ 'start_year' => $opt{'start_year'},
+ 'end_day' => $opt{'end_day'},
+ 'end_month' => $opt{'end_month'},
+ 'end_year' => $opt{'end_year'},
+ 'agentnum' => $opt{'agentnum'},
+ 'remove_empty' => $opt{'remove_empty'},
+ 'doublemonths' => $opt{'doublemonths'},
);
+
+my $report;
+$report = new FS::Report::Table::Daily(%reportopts) if $opt{'daily'};
+$report = new FS::Report::Table::Monthly(%reportopts) unless $opt{'daily'};
my $data = $report->data;
+my $col_labels = [ map { my $m = $_; $m =~ s/^(\d+)\//$mon[$1-1] / ; $m }
+ @{$data->{label}} ];
+$col_labels = $data->{label} if $opt{'daily'};
+
my @links;
foreach my $link (@{ $data->{'links'} }) {
my @speriod = @{$data->{'speriod'}};
--- /dev/null
+<% include('elements/monthly.html',
+ 'title' => $agentname.
+ 'Daily Sales, Credits and Receipts Summary',
+ 'items' => \@items,
+ 'labels' => \%label,
+ 'graph_labels' => \%graph_label,
+ 'colors' => \%color,
+ 'links' => \%link,
+ 'agentnum' => $agentnum,
+ 'nototal' => scalar($cgi->param('12mo')),
+ 'daily' => 1,
+ 'start_day' => $smday,
+ 'start_month' => $smon+1,
+ 'start_year' => $syear,
+ 'end_day' => $emday,
+ 'end_month' => $emon+1,
+ 'end_year' => $eyear,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#XXX or virtual
+my( $agentnum, $agent ) = ('', '');
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $agent;
+}
+
+my $agentname = $agent ? $agent->agent.' ' : '';
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+my ($ssec,$smin,$shour,$smday,$smon,$syear,$swday,$syday,$sisdst)
+ = localtime($beginning);
+my ($esec,$emin,$ehour,$emday,$emon,$eyear,$ewday,$eyday,$eisdst)
+ = localtime($ending);
+
+my @items = qw( invoiced netsales
+ credits netcredits
+ payments receipts
+ refunds netrefunds
+ cashflow netcashflow
+ );
+if ( $cgi->param('12mo') == 1 ) {
+ @items = map $_.'_12mo', @items;
+}
+
+my %label = (
+ 'invoiced' => 'Gross Sales',
+ 'netsales' => 'Net Sales',
+ 'credits' => 'Gross Credits',
+ 'netcredits' => 'Net Credits',
+ 'payments' => 'Gross Receipts',
+ 'receipts' => 'Net Receipts',
+ 'refunds' => 'Gross Refunds',
+ 'netrefunds' => 'Net Refunds',
+ 'cashflow' => 'Gross Cashflow',
+ 'netcashflow' => 'Net Cashflow',
+);
+
+my %graph_suffix = (
+ 'invoiced' => ' (invoiced)',
+ 'netsales' => ' (invoiced - applied credits)',
+ 'credits' => ' (credited)',
+ 'netcredits' => ' (applied credits)',
+ 'payments' => ' (payments)',
+ 'receipts' => ' (applied payments)',
+ 'refunds' => ' (refunds)',
+ 'netrefunds' => ' (applied refunds)',
+ 'cashflow' => ' (payments - refunds)',
+ 'netcashflow' => ' (applied payments - applied refunds)',
+);
+my %graph_label = map { $_ => $label{$_}.$graph_suffix{$_} } keys %label;
+
+$label{$_.'_12mo'} = $label{$_}. " (prev 12 months)"
+ foreach keys %label;
+
+$graph_label{$_.'_12mo'} = $graph_label{$_}. " (prev 12 months)"
+ foreach keys %graph_label;
+
+my %color = (
+ 'invoiced' => '9999ff', #light blue
+ 'netsales' => '0000cc', #blue
+ 'credits' => 'ff9999', #light red
+ 'netcredits' => 'cc0000', #red
+ 'payments' => '99cc99', #light green
+ 'receipts' => '00cc00', #green
+ 'refunds' => 'ffcc99', #light orange
+ 'netrefunds' => 'ff9900', #orange
+ 'cashflow' => '99cc33', #light olive
+ 'netcashflow' => '339900', #olive
+);
+$color{$_.'_12mo'} = $color{$_}
+ foreach keys %color;
+
+my %link = (
+ 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;",
+ 'netsales' => "${p}search/cust_bill.html?agentnum=$agentnum;net=1;",
+ 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;",
+ 'netcredits' => "${p}search/cust_credit_bill.html?agentnum=$agentnum;",
+ 'payments' => "${p}search/cust_pay.html?magic=_date;agentnum=$agentnum;",
+ 'receipts' => "${p}search/cust_bill_pay.html?agentnum=$agentnum;",
+ 'refunds' => "${p}search/cust_refund.html?magic=_date;agentnum=$agentnum;",
+ 'netrefunds' => "${p}search/cust_credit_refund.html?agentnum=$agentnum;",
+);
+# XXX link 12mo?
+
+</%init>
--- /dev/null
+<% include('/elements/header.html', 'Daily Sales, Credits and Receipts Summary' ) %>
+
+<FORM ACTION="money_time_daily.cgi" METHOD="GET">
+
+<TABLE>
+
+<% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+%>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>