From 20950bb21ee5dd8839a05dfcd58efa0a98e48e5a Mon Sep 17 00:00:00 2001 From: levinse Date: Fri, 13 May 2011 20:03:20 +0000 Subject: [PATCH] create a Sales, Credits, and Receipts report broken down by day, RT11470 --- FS/FS/Mason.pm | 2 + FS/FS/Report/Table.pm | 470 ++++++++++++++++++++++++- FS/FS/Report/Table/Daily.pm | 140 ++++++++ FS/FS/Report/Table/Monthly.pm | 479 +------------------------- httemplate/elements/menu.html | 1 + httemplate/graph/elements/monthly.html | 61 ++-- httemplate/graph/money_time_daily.cgi | 111 ++++++ httemplate/graph/report_money_time_daily.html | 26 ++ 8 files changed, 791 insertions(+), 499 deletions(-) create mode 100644 FS/FS/Report/Table/Daily.pm create mode 100644 httemplate/graph/money_time_daily.cgi create mode 100644 httemplate/graph/report_money_time_daily.html diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index e74f44eb1..07e650a41 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -133,7 +133,9 @@ if ( -e $addl_handler_use_file ) { ); 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 ); diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm index 9f636fa43..113bf8d9e 100644 --- a/FS/FS/Report/Table.pm +++ b/FS/FS/Report/Table.pm @@ -1,9 +1,14 @@ 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 @@ -14,6 +19,469 @@ FS::Report::Table - Tables of report data 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. diff --git a/FS/FS/Report/Table/Daily.pm b/FS/FS/Report/Table/Daily.pm new file mode 100644 index 000000000..5bbd6e4e7 --- /dev/null +++ b/FS/FS/Report/Table/Daily.pm @@ -0,0 +1,140 @@ +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; diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm index 2d8dd7ee9..f57fb064b 100644 --- a/FS/FS/Report/Table/Monthly.pm +++ b/FS/FS/Report/Table/Monthly.pm @@ -1,14 +1,11 @@ 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 @@ -46,9 +43,6 @@ Returns a hashref of data (!! describe) 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'}; @@ -104,13 +98,8 @@ sub data { $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 = (); @@ -139,471 +128,7 @@ sub data { } - #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 diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index c186aa26a..e28beb760 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -307,6 +307,7 @@ if($curuser->access_right('Financial reports')) { %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', '' ], diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html index de2b2e9d4..a451ea8ff 100644 --- a/httemplate/graph/elements/monthly.html +++ b/httemplate/graph/elements/monthly.html @@ -19,6 +19,7 @@ Example: '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, @@ -26,6 +27,12 @@ Example: '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, @@ -42,8 +49,7 @@ Example: '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, @@ -83,28 +89,41 @@ $opt{'start_year'} ||= $cgi->param('start_year'); # || 1899+$curyear; $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'}}; diff --git a/httemplate/graph/money_time_daily.cgi b/httemplate/graph/money_time_daily.cgi new file mode 100644 index 000000000..4d16ff871 --- /dev/null +++ b/httemplate/graph/money_time_daily.cgi @@ -0,0 +1,111 @@ +<% 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? + + diff --git a/httemplate/graph/report_money_time_daily.html b/httemplate/graph/report_money_time_daily.html new file mode 100644 index 000000000..1e1b45d4e --- /dev/null +++ b/httemplate/graph/report_money_time_daily.html @@ -0,0 +1,26 @@ +<% include('/elements/header.html', 'Daily Sales, Credits and Receipts Summary' ) %> + +
+ + + +<% include( '/elements/tr-input-beginning_ending.html' ) %> + +<% include('/elements/tr-select-agent.html', + 'label' => 'For agent: ', + 'disable_empty' => 0, + ) +%> + +
+ +
+
+ +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + + -- 2.11.0