From 978fea1c303551f90c95ae852b6463951b78246b Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 19 May 2008 04:07:02 +0000 Subject: [PATCH] make net receipts clickable... and netreceipts != cashflow, really, so separate those concepts, and cashflow gets gross & net variants. also add gross/net refunds. #3012 --- FS/FS/Report/Table/Monthly.pm | 137 ++++++----- FS/FS/cust_bill_pay.pm | 3 +- FS/FS/cust_credit_refund.pm | 4 +- FS/FS/cust_refund.pm | 4 +- FS/FS/payby.pm | 2 +- FS/FS/payinfo_Mixin.pm | 48 +++- httemplate/graph/money_time.cgi | 58 +++-- httemplate/search/cust_bill_pay.html | 131 +++++++++++ httemplate/search/cust_credit.html | 16 +- httemplate/search/cust_credit_refund.html | 130 +++++++++++ httemplate/search/cust_pay.cgi | 255 +------------------- httemplate/search/cust_refund.html | 7 + httemplate/search/elements/cust_pay_or_refund.html | 258 +++++++++++++++++++++ 13 files changed, 702 insertions(+), 351 deletions(-) create mode 100644 httemplate/search/cust_bill_pay.html create mode 100644 httemplate/search/cust_credit_refund.html create mode 100644 httemplate/search/cust_refund.html create mode 100755 httemplate/search/elements/cust_pay_or_refund.html diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm index 6daa02637..d75f0be79 100644 --- a/FS/FS/Report/Table/Monthly.pm +++ b/FS/FS/Report/Table/Monthly.pm @@ -1,7 +1,7 @@ package FS::Report::Table::Monthly; use strict; -use vars qw( @ISA $expenses_kludge ); +use vars qw( @ISA ); use Time::Local; use FS::UID qw( dbh ); use FS::Report::Table; @@ -9,8 +9,6 @@ use FS::CurrentUser; @ISA = qw( FS::Report::Table ); -$expenses_kludge = 0; - =head1 NAME FS::Report::Table::Monthly - Tables of report data, indexed monthly @@ -142,70 +140,24 @@ sub invoiced { #invoiced sub netsales { #net sales my( $self, $speriod, $eperiod, $agentnum ) = @_; - my $credited = $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' - ) - ); - - #horrible local kludge - my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql(" - SELECT SUM(cust_bill_pkg.setup) - 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 ) - WHERE ". $self->in_time_period_and_agent( $speriod, - $eperiod, - $agentnum, - 'cust_bill._date' - ). " - AND LOWER(part_pkg.pkg) LIKE 'expense _%' - "); - - $self->invoiced($speriod,$eperiod,$agentnum) - $credited - $expenses; + $self->invoiced($speriod,$eperiod,$agentnum) + - $self->credits( $speriod,$eperiod,$agentnum); } #deferred revenue -sub receipts { #cashflow +sub cashflow { my( $self, $speriod, $eperiod, $agentnum ) = @_; - my $refunded = $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) - ); + $self->payments($speriod, $eperiod, $agentnum) + - $self->refunds( $speriod, $eperiod, $agentnum); +} - #horrible local kludge that doesn't even really work right - my $expenses = !$expenses_kludge ? 0 : $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_pay._date' - ). " - AND 0 < ( SELECT COUNT(*) from cust_bill_pkg, cust_pkg, part_pkg - WHERE cust_bill.invnum = cust_bill_pkg.invnum - AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum - AND cust_pkg.pkgpart = part_pkg.pkgpart - AND LOWER(part_pkg.pkg) LIKE 'expense _%' - ) - "); - # my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'"; - - $self->payments($speriod, $eperiod, $agentnum) - $refunded - $expenses; +sub netcashflow { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + + $self->receipts($speriod, $eperiod, $agentnum) + - $self->netrefunds( $speriod, $eperiod, $agentnum); } sub payments { @@ -228,6 +180,16 @@ sub credits { ); } +sub refunds { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $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) + ); +} + sub netcredits { my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" @@ -243,6 +205,36 @@ sub netcredits { ); } +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 ) = @_; @@ -280,6 +272,31 @@ sub netcredits_12mo { $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 { diff --git a/FS/FS/cust_bill_pay.pm b/FS/FS/cust_bill_pay.pm index 74a8bcdd4..b7ba2b711 100644 --- a/FS/FS/cust_bill_pay.pm +++ b/FS/FS/cust_bill_pay.pm @@ -3,11 +3,12 @@ package FS::cust_bill_pay; use strict; use vars qw( @ISA $conf ); use FS::Record qw( qsearchs ); +use FS::cust_main_Mixin; use FS::cust_bill_ApplicationCommon; use FS::cust_bill; use FS::cust_pay; -@ISA = qw( FS::cust_bill_ApplicationCommon ); +@ISA = qw( FS::cust_main_Mixin FS::cust_bill_ApplicationCommon ); #ask FS::UID to run this stuff for us later FS::UID->install_callback( sub { diff --git a/FS/FS/cust_credit_refund.pm b/FS/FS/cust_credit_refund.pm index f237efed2..9fc03f2d3 100644 --- a/FS/FS/cust_credit_refund.pm +++ b/FS/FS/cust_credit_refund.pm @@ -3,11 +3,11 @@ package FS::cust_credit_refund; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch qsearchs dbh ); -#use FS::UID qw(getotaker); +use FS::cust_main_Mixin; use FS::cust_credit; use FS::cust_refund; -@ISA = qw( FS::Record ); +@ISA = qw( FS::cust_main_Mixin FS::Record ); =head1 NAME diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm index 498179553..e075e3ed5 100644 --- a/FS/FS/cust_refund.pm +++ b/FS/FS/cust_refund.pm @@ -11,7 +11,7 @@ use FS::cust_pay_refund; use FS::cust_main; use FS::payinfo_Mixin; -@ISA = qw( FS::Record FS::payinfo_Mixin ); +@ISA = qw( FS::payinfo_Mixin FS::cust_main_Mixin FS::Record ); @encrypted_fields = ('payinfo'); @@ -216,7 +216,7 @@ sub delete { =item replace OLD_RECORD -Currently unimplemented (accounting reasons). +Modifying a refund? Well, don't say I didn't warn you. =cut diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index 6684c95f0..349d266a3 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -124,7 +124,7 @@ sub can_payby { #return "Illegal payby" unless $hash{$payby}; return 0 unless $hash{$payby}; - $table = 'cust_pay' if $table eq 'cust_pay_batch' || $table eq 'cust_refund'; + $table = 'cust_pay' if $table =~ /^cust_(pay_pending|pay_batch|pay_void|refund)$/; return 0 if exists( $hash{$payby}->{$table} ); return 1; diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm index bdfe6c950..fb56371e3 100644 --- a/FS/FS/payinfo_Mixin.pm +++ b/FS/FS/payinfo_Mixin.pm @@ -22,9 +22,7 @@ This class handles the following functions for payinfo... Payment Mask (Generation and Storage) Data Validation (parent checks need to be sure to call this) -Encryption - In the Future (Pull from Record.pm) -Bad Card Stuff - In the Future (Integrate Banned Pay) -Currency - In the Future +Pretty printing =head1 FIELDS @@ -182,11 +180,6 @@ sub mask_payinfo { return $paymask; } -=cut - -sub _mask_payinfo { - my $self = shift; - =item payinfo_check Checks payby and payinfo. @@ -244,9 +237,46 @@ sub payinfo_check { } +=item payby_payinfo_pretty + +Returns payment method and information (suitably masked, if applicable) as +a human-readable string, such as: + + Card #54xxxxxxxxxxxx32 + +or + + Check #119006 + +=cut + +sub payby_payinfo_pretty { + my $self = shift; + if ( $self->payby eq 'CARD' ) { + 'Card #'. $self->paymask; + } elsif ( $self->payby eq 'CHEK' ) { + 'E-check acct#'. $self->payinfo; + } elsif ( $self->payby eq 'BILL' ) { + 'Check #'. $self->payinfo; + } elsif ( $self->payby eq 'PREP' ) { + 'Prepaid card #'. $self->payinfo; + } elsif ( $self->payby eq 'CASH' ) { + 'Cash '. $self->payinfo; + } elsif ( $self->payby eq 'WEST' ) { + 'Western Union'; #. $self->payinfo; + } elsif ( $self->payby eq 'MCRD' ) { + 'Manual credit card'; #. $self->payinfo; + } else { + $self->payby. ' '. $self->payinfo; + } +} + =head1 BUGS -Have to add the future items... +Future items? + Encryption - In the Future (Pull from Record.pm) + Bad Card Stuff - In the Future (Integrate Banned Pay) + Currency - In the Future =head1 SEE ALSO diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi index b996380b6..fbc31c068 100644 --- a/httemplate/graph/money_time.cgi +++ b/httemplate/graph/money_time.cgi @@ -25,27 +25,40 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { my $agentname = $agent ? $agent->agent.' ' : ''; -my @items = qw( invoiced netsales credits netcredits payments receipts ); +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', + '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' => '/Cashflow (payments - refunds)', + '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; @@ -56,12 +69,16 @@ $graph_label{$_.'_12mo'} = $graph_label{$_}. " (previous 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 + '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; @@ -72,6 +89,9 @@ my %link = ( 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;", 'netcredits' => "${p}search/cust_credit_bill.html?agentnum=$agentnum;", 'payments' => "${p}search/cust_pay.cgi?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/search/cust_bill_pay.html b/httemplate/search/cust_bill_pay.html new file mode 100644 index 000000000..3c390e706 --- /dev/null +++ b/httemplate/search/cust_bill_pay.html @@ -0,0 +1,131 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'net payments', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => [ '$%.2f total paid (net)', ], + 'header' => [ 'Net applied', + 'to Invoice', + 'Payment', + 'By', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + sub { $money_char. sprintf('%.2f', shift->amount ) }, + sub { my $cbp = shift; + '#'.$cbp->invnum. ' '. + time2str('%b %d %Y', $cbp->cust_bill_date ). + " ($money_char". + sprintf('%.2f', $cbp->cust_bill_amount). + ")" + }, + sub { my $cbp = shift; + $cbp->cust_pay->payby_payinfo_pretty. ' '. + time2str('%b %d %Y', $cbp->_date ). + " ($money_char". + sprintf('%.2f', $cbp->cust_pay_paid ). + ")" + }, + sub { shift->cust_pay->otaker }, + \&FS::UI::Web::cust_fields, + ], + 'align' => 'rrrl'.FS::UI::Web::cust_aligns(), + 'links' => [ + '', + $cust_bill_link, + $cust_pay_link, + '', + ( map { $_ ne 'Cust. Status' ? $cust_link : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $title = 'Net Payment Search Results'; + +my @search = (); + +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "unknown agentnum $1" unless $agent; + $title = $agent->agent. " $title"; +} + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, "cust_bill._date >= $beginning ", + "cust_bill._date <= $ending"; + +#here is the agent virtualization +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $where = 'WHERE '. join(' AND ', @search); +# +my $count_query = 'SELECT COUNT(*), SUM(amount) + FROM cust_bill_pay + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) '. + $where; + +my $sql_query = { + 'table' => 'cust_bill_pay', + 'select' => join(', ', + 'cust_bill_pay.*', + 'cust_pay.paid AS cust_pay_paid', + 'cust_bill._date AS cust_bill_date', + #'cust_bill.charged AS cust_bill_charged', + 'cust_pay.custnum AS custnum', + 'cust_main.custnum AS cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => $where, + 'addl_from' => 'LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_pay USING ( paynum ) + LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum )', +}; + +my $cust_bill_link = sub { + my $cust_bill_pay = shift; + $cust_bill_pay->invnum + ? [ "${p}view/cust_bill.cgi?", 'invnum' ] + : ''; +}; + +my $cust_pay_link = sub { + my $cust_bill_pay = shift; + $cust_bill_pay->paynum + ? [ "${p}view/cust_pay.html?paynum=", 'paynum' ] + : ''; +}; + +my $cust_link = sub { + my $cust_credit_bill = shift; + $cust_credit_bill->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'cust_main_custnum' ] + : ''; +}; + + diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html index 2c59297be..9a14dceca 100755 --- a/httemplate/search/cust_credit.html +++ b/httemplate/search/cust_credit.html @@ -7,42 +7,42 @@ #'redirect' => $link, 'header' => [ 'Amount', 'Date', - FS::UI::Web::cust_header(), 'By', - 'Reason' + 'Reason', + FS::UI::Web::cust_header(), ], 'fields' => [ #'crednum', sub { sprintf('$%.2f', shift->amount ) }, sub { time2str('%b %d %Y', shift->_date ) }, - \&FS::UI::Web::cust_fields, 'otaker', 'reason', + \&FS::UI::Web::cust_fields, ], #'align' => 'rrrllll', - 'align' => 'rr'.FS::UI::Web::cust_aligns().'ll', + 'align' => 'rrll'.FS::UI::Web::cust_aligns(), 'links' => [ '', '', + '', + '', ( map { $_ ne 'Cust. Status' ? $clink : '' } FS::UI::Web::cust_header() ), - '', - '', ], 'color' => [ '', '', - FS::UI::Web::cust_colors(), '', '', + FS::UI::Web::cust_colors(), ], 'style' => [ '', '', - FS::UI::Web::cust_styles(), '', '', + FS::UI::Web::cust_styles(), ], ) %> diff --git a/httemplate/search/cust_credit_refund.html b/httemplate/search/cust_credit_refund.html new file mode 100644 index 000000000..d9abe2e00 --- /dev/null +++ b/httemplate/search/cust_credit_refund.html @@ -0,0 +1,130 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'net refunds', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => [ '$%.2f total refunded (net)', ], + 'header' => [ 'Net applied', + 'to Credit', + 'Refund', + 'By', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + sub { $money_char. sprintf('%.2f', shift->amount ) }, + sub { my $ccr = shift; + '#'.$ccr->crednum. ' '. + time2str('%b %d %Y', $ccr->cust_credit_date ). + " ($money_char". + sprintf('%.2f', $ccr->cust_credit_amount). + ")" + }, + sub { my $ccr = shift; + time2str('%b %d %Y', $ccr->_date ). + " ($money_char". + sprintf('%.2f', $ccr->cust_refund_refund ). + ")" + }, + sub { shift->cust_refund->otaker }, + \&FS::UI::Web::cust_fields, + ], + 'align' => 'rrrl'.FS::UI::Web::cust_aligns(), + 'links' => [ + '', + '', + '', + '', + ( map { $_ ne 'Cust. Status' ? $cust_link : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $title = 'Net Refund Search Results'; + +my @search = (); + +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "unknown agentnum $1" unless $agent; + $title = $agent->agent. " $title"; +} + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, "cust_credit._date >= $beginning ", + "cust_credit._date <= $ending"; + +#here is the agent virtualization +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $where = 'WHERE '. join(' AND ', @search); +# +my $count_query = 'SELECT COUNT(*), SUM(cust_credit_refund.amount) + FROM cust_credit_refund + LEFT JOIN cust_credit USING ( crednum ) + LEFT JOIN cust_main USING ( custnum ) '. + $where; + +my $sql_query = { + 'table' => 'cust_credit_refund', + 'select' => join(', ', + 'cust_credit_refund.*', + 'cust_refund.refund AS cust_refund_refund', + 'cust_credit._date AS cust_credit_date', + 'cust_credit.amount AS cust_credit_amnount', + 'cust_refund.custnum AS custnum', + 'cust_main.custnum AS cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => $where, + 'addl_from' => 'LEFT JOIN cust_credit USING ( crednum ) + LEFT JOIN cust_refund USING ( refundnum ) + LEFT JOIN cust_main ON ( cust_credit.custnum = cust_main.custnum )', +}; + +#my $cust_credit_link = sub { +# my $cust_credit_refund = shift; +# $cust_credit_refund->crednum +# ? [ "${p}view/cust_credit.cgi?", 'credum' ] +# : ''; +#}; + +#my $cust_refund_link = sub { +# my $cust_credit_refund = shift; +# $cust_credit_refund->refundnum +# ? [ "${p}view/cust_refund.cgi?", 'refundnum' ] +# : ''; +#}; + +my $cust_link = sub { + my $cust_credit_refund = shift; + $cust_credit_refund->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'cust_main_custnum' ] + : ''; +}; + + diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi index 80a611d97..65bd39e19 100755 --- a/httemplate/search/cust_pay.cgi +++ b/httemplate/search/cust_pay.cgi @@ -1,250 +1,7 @@ -<% include( 'elements/search.html', - 'title' => $title, - 'name' => 'payments', - 'query' => $sql_query, - 'count_query' => $count_query, - 'count_addl' => [ '$%.2f total paid', ], - 'header' => [ 'Payment', - 'Amount', - 'Date', - 'By', - FS::UI::Web::cust_header(), - ], - 'fields' => [ - sub { - my $cust_pay = shift; - if ( $cust_pay->payby eq 'CARD' ) { - 'Card #'. $cust_pay->paymask; - } elsif ( $cust_pay->payby eq 'CHEK' ) { - 'E-check acct#'. $cust_pay->payinfo; - } elsif ( $cust_pay->payby eq 'BILL' ) { - 'Check #'. $cust_pay->payinfo; - } elsif ( $cust_pay->payby eq 'PREP' ) { - 'Prepaid card #'. $cust_pay->payinfo; - } elsif ( $cust_pay->payby eq 'CASH' ) { - 'Cash '. $cust_pay->payinfo; - } elsif ( $cust_pay->payby eq 'WEST' ) { - 'Western Union'; #. $cust_pay->payinfo; - } elsif ( $cust_pay->payby eq 'MCRD' ) { - 'Manual credit card'; #. $cust_pay->payinfo; - } else { - $cust_pay->payby. ' '. $cust_pay->payinfo; - } - }, - sub { sprintf('$%.2f', shift->paid ) }, - sub { time2str('%b %d %Y', shift->_date ) }, - sub { my $o = shift->otaker; - $o = 'auto billing' if $o eq 'fs_daily'; - $o = 'customer self-service' if $o eq 'fs_selfservice'; - $o; - }, - \&FS::UI::Web::cust_fields, - ], - #'align' => 'lrrrll', - 'align' => 'rrrc'.FS::UI::Web::cust_aligns(), - 'links' => [ - $link, - $link, - $link, - '', - ( map { $_ ne 'Cust. Status' ? $cust_link : '' } - FS::UI::Web::cust_header() - ), - ], - 'color' => [ - '', - '', - '', - '', - FS::UI::Web::cust_colors(), - ], - 'style' => [ - '', - '', - '', - '', - FS::UI::Web::cust_styles(), - ], - ) +<% include( 'elements/cust_pay_or_refund.html', + 'thing' => 'pay', + 'amount_field' => 'paid', + 'name_singular' => 'payment', + 'name_verb' => 'paid', + ) %> -<%init> - -die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); - -my $title = 'Payment Search Results'; -my( $count_query, $sql_query ); -if ( $cgi->param('magic') ) { - - my @search = (); - my $orderby; - if ( $cgi->param('magic') eq '_date' ) { - - - if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { - push @search, "agentnum = $1"; # $search{'agentnum'} = $1; - my $agent = qsearchs('agent', { 'agentnum' => $1 } ); - die "unknown agentnum $1" unless $agent; - $title = $agent->agent. " $title"; - } - - if ( $cgi->param('payby') ) { - $cgi->param('payby') =~ - /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ - or die "illegal payby ". $cgi->param('payby'); - push @search, "cust_pay.payby = '$1'"; - if ( $3 ) { - - my $cardtype = $3; - - my $search; - if ( $cardtype eq 'VisaMC' ) { - #avoid posix regexes for portability - $search = - " ( ( substring(cust_pay.payinfo from 1 for 1) = '4' ". - " AND substring(cust_pay.payinfo from 1 for 4) != '4936' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49030[2-9]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49033[5-9]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49110[1-2]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49117[4-9]' ". - " AND substring(cust_pay.payinfo from 1 for 6) ". - " NOT SIMILAR TO '49118[1-2]' ". - " )". - " OR substring(cust_pay.payinfo from 1 for 2) = '51' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '52' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '53' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '55' ". - " OR substring(cust_pay.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US - " ) "; - } elsif ( $cardtype eq 'Amex' ) { - $search = - " ( substring(cust_pay.payinfo from 1 for 2 ) = '34' ". - " OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ". - " ) "; - } elsif ( $cardtype eq 'Discover' ) { - $search = - " ( substring(cust_pay.payinfo from 1 for 4 ) = '6011' ". - " OR substring(cust_pay.payinfo from 1 for 2 ) = '65' ". - " OR substring(cust_pay.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN - " ) "; - } elsif ( $cardtype eq 'Maestro' ) { - $search = - " ( substring(cust_pay.payinfo from 1 for 2 ) = '63' ". - " OR substring(cust_pay.payinfo from 1 for 2 ) = '67' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) = '564182' ". - " OR substring(cust_pay.payinfo from 1 for 4 ) = '4936' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49030[2-9]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49033[5-9]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49110[1-2]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49117[4-9]' ". - " OR substring(cust_pay.payinfo from 1 for 6 ) ". - " SIMILAR TO '49118[1-2]' ". - " ) "; - } else { - die "unknown card type $cardtype"; - } - - my $masksearch = $search; - $masksearch =~ s/cust_pay\.payinfo/cust_pay.paymask/gi; - - push @search, - "( $search OR ( cust_pay.paymask IS NOT NULL AND $masksearch ) )"; - - } - } - - if ( $cgi->param('payinfo') ) { - $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ - or die "illegal payinfo ". $cgi->param('payinfo'); - push @search, "cust_pay.payinfo = '$1'"; - } - - my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); - push @search, "_date >= $beginning ", - "_date <= $ending"; - - push @search, FS::UI::Web::parse_lt_gt($cgi, 'paid' ); - - $orderby = '_date'; - - } elsif ( $cgi->param('magic') eq 'paybatch' ) { - - $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/ - or die "illegal paybatch: ". $cgi->param('paybatch'); - - push @search, "paybatch = '$1'"; - - $orderby = "LOWER(company || ' ' || last || ' ' || first )"; - - } else { - die "unknown search magic: ". $cgi->param('magic'); - } - - #here is the agent virtualization - push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; - - my $search = ' WHERE '. join(' AND ', @search); - - $count_query = "SELECT COUNT(*), SUM(paid) ". - "FROM cust_pay LEFT JOIN cust_main USING ( custnum )". - $search; - - $sql_query = { - 'table' => 'cust_pay', - 'select' => join(', ', - 'cust_pay.*', - 'cust_main.custnum as cust_main_custnum', - FS::UI::Web::cust_sql_fields(), - ), - 'hashref' => {}, - 'extra_sql' => "$search ORDER BY $orderby", - 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', - }; - -} else { - - #hmm... is this still used? - - $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo"; - my $payinfo = $1; - - $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby"; - my $payby = $1; - - $count_query = "SELECT COUNT(*), SUM(paid) FROM cust_pay". - " WHERE payinfo = '$payinfo' AND payby = '$payby'". - " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql; - - $sql_query = { - 'table' => 'cust_pay', - 'hashref' => { 'payinfo' => $payinfo, - 'payby' => $payby }, - 'extra_sql' => $FS::CurrentUser::CurrentUser->agentnums_sql. - " ORDER BY _date", - }; - -} - -my $link = ''; -$link = [ "${p}view/cust_pay.html?paynum=", 'paynum' ] - if $FS::CurrentUser::CurrentUser->access_right('View invoices'); #XXX for now - #later# if $FS::CurrentUser::CurrentUser->access_right('View customer payments'); - -my $cust_link = sub { - my $cust_pay = shift; - $cust_pay->cust_main_custnum - ? [ "${p}view/cust_main.cgi?", 'custnum' ] - : ''; -}; - - diff --git a/httemplate/search/cust_refund.html b/httemplate/search/cust_refund.html new file mode 100644 index 000000000..e31e088eb --- /dev/null +++ b/httemplate/search/cust_refund.html @@ -0,0 +1,7 @@ +<% include( 'elements/cust_pay_or_refund.html', + 'thing' => 'refund', + 'amount_field' => 'refund', + 'name_singular' => 'refund', + 'name_verb' => 'refunded', + ) +%> diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html new file mode 100755 index 000000000..cd1e19ded --- /dev/null +++ b/httemplate/search/elements/cust_pay_or_refund.html @@ -0,0 +1,258 @@ +<%doc> + +Examples: + + include( 'elements/cust_pay_or_refund.html', + 'thing' => 'pay', + 'amount_field' => 'paid', + 'name_singular' => 'payment', + 'name_verb' => 'paid', + ) + + include( 'elements/cust_pay_or_refund.html', + 'thing' => 'refund', + 'amount_field' => 'refund', + 'name_singular' => 'refund', + 'name_verb' => 'refunded', + ) + + +<% include( 'search.html', + 'title' => $title, + 'name_singular' => $name_singular, + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => [ '$%.2f total '.$opt{name_verb}, ], + 'header' => [ "\u$name_singular", + 'Amount', + 'Date', + 'By', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'payby_payinfo_pretty', + sub { sprintf('$%.2f', shift->$amount_field() ) }, + sub { time2str('%b %d %Y', shift->_date ) }, + sub { my $o = shift->otaker; + $o = 'auto billing' if $o eq 'fs_daily'; + $o = 'customer self-service' if $o eq 'fs_selfservice'; + $o; + }, + \&FS::UI::Web::cust_fields, + ], + #'align' => 'lrrrll', + 'align' => 'rrrc'.FS::UI::Web::cust_aligns(), + 'links' => [ + $link, + $link, + $link, + '', + ( map { $_ ne 'Cust. Status' ? $cust_link : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +my %opt = @_; + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $thing = $opt{'thing'}; +my $amount_field = $opt{'amount_field'}; +my $name_singular = $opt{'name_singular'}; + +my $title = "\u$name_singular Search Results"; +my( $count_query, $sql_query ); +if ( $cgi->param('magic') ) { + + my @search = (); + my $orderby; + if ( $cgi->param('magic') eq '_date' ) { + + + if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; # $search{'agentnum'} = $1; + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "unknown agentnum $1" unless $agent; + $title = $agent->agent. " $title"; + } + + if ( $cgi->param('payby') ) { + $cgi->param('payby') =~ + /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ + or die "illegal payby ". $cgi->param('payby'); + push @search, "cust_$thing.payby = '$1'"; + if ( $3 ) { + + my $cardtype = $3; + + my $search; + if ( $cardtype eq 'VisaMC' ) { + #avoid posix regexes for portability + $search = + " ( ( substring(cust_$thing.payinfo from 1 for 1) = '4' ". + " AND substring(cust_$thing.payinfo from 1 for 4) != '4936' ". + " AND substring(cust_$thing.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49030[2-9]' ". + " AND substring(cust_$thing.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49033[5-9]' ". + " AND substring(cust_$thing.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49110[1-2]' ". + " AND substring(cust_$thing.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49117[4-9]' ". + " AND substring(cust_$thing.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49118[1-2]' ". + " )". + " OR substring(cust_$thing.payinfo from 1 for 2) = '51' ". + " OR substring(cust_$thing.payinfo from 1 for 2) = '52' ". + " OR substring(cust_$thing.payinfo from 1 for 2) = '53' ". + " OR substring(cust_$thing.payinfo from 1 for 2) = '54' ". + " OR substring(cust_$thing.payinfo from 1 for 2) = '54' ". + " OR substring(cust_$thing.payinfo from 1 for 2) = '55' ". + " OR substring(cust_$thing.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US + " ) "; + } elsif ( $cardtype eq 'Amex' ) { + $search = + " ( substring(cust_$thing.payinfo from 1 for 2 ) = '34' ". + " OR substring(cust_$thing.payinfo from 1 for 2 ) = '37' ". + " ) "; + } elsif ( $cardtype eq 'Discover' ) { + $search = + " ( substring(cust_$thing.payinfo from 1 for 4 ) = '6011' ". + " OR substring(cust_$thing.payinfo from 1 for 2 ) = '65' ". + " OR substring(cust_$thing.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN + " ) "; + } elsif ( $cardtype eq 'Maestro' ) { + $search = + " ( substring(cust_$thing.payinfo from 1 for 2 ) = '63' ". + " OR substring(cust_$thing.payinfo from 1 for 2 ) = '67' ". + " OR substring(cust_$thing.payinfo from 1 for 6 ) = '564182' ". + " OR substring(cust_$thing.payinfo from 1 for 4 ) = '4936' ". + " OR substring(cust_$thing.payinfo from 1 for 6 ) ". + " SIMILAR TO '49030[2-9]' ". + " OR substring(cust_$thing.payinfo from 1 for 6 ) ". + " SIMILAR TO '49033[5-9]' ". + " OR substring(cust_$thing.payinfo from 1 for 6 ) ". + " SIMILAR TO '49110[1-2]' ". + " OR substring(cust_$thing.payinfo from 1 for 6 ) ". + " SIMILAR TO '49117[4-9]' ". + " OR substring(cust_$thing.payinfo from 1 for 6 ) ". + " SIMILAR TO '49118[1-2]' ". + " ) "; + } else { + die "unknown card type $cardtype"; + } + + my $masksearch = $search; + $masksearch =~ s/cust_$thing\.payinfo/cust_$thing.paymask/gi; + + push @search, + "( $search OR ( cust_$thing.paymask IS NOT NULL AND $masksearch ) )"; + + } + } + + if ( $cgi->param('payinfo') ) { + $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ + or die "illegal payinfo ". $cgi->param('payinfo'); + push @search, "cust_$thing.payinfo = '$1'"; + } + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + push @search, "_date >= $beginning ", + "_date <= $ending"; + + push @search, FS::UI::Web::parse_lt_gt($cgi, $amount_field ); + + $orderby = '_date'; + + } elsif ( $cgi->param('magic') eq 'paybatch' ) { + + $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/ + or die "illegal paybatch: ". $cgi->param('paybatch'); + + push @search, "paybatch = '$1'"; + + $orderby = "LOWER(company || ' ' || last || ' ' || first )"; + + } else { + die "unknown search magic: ". $cgi->param('magic'); + } + + #here is the agent virtualization + push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + + my $search = ' WHERE '. join(' AND ', @search); + + $count_query = "SELECT COUNT(*), SUM($amount_field) ". + "FROM cust_$thing LEFT JOIN cust_main USING ( custnum )". + $search; + + $sql_query = { + 'table' => "cust_$thing", + 'select' => join(', ', + "cust_$thing.*", + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => "$search ORDER BY $orderby", + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + }; + +} else { + + #hmm... is this still used? + + $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo"; + my $payinfo = $1; + + $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby"; + my $payby = $1; + + $count_query = "SELECT COUNT(*), SUM($amount_field) FROM cust_$thing". + " WHERE payinfo = '$payinfo' AND payby = '$payby'". + " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql; + + $sql_query = { + 'table' => "cust_$thing", + 'hashref' => { 'payinfo' => $payinfo, + 'payby' => $payby }, + 'extra_sql' => $FS::CurrentUser::CurrentUser->agentnums_sql. + " ORDER BY _date", + }; + +} + +my $link = ''; +if ( $thing eq 'pay' ) { #XXX no refund receipts yet + $link = [ "${p}view/cust_pay.html?paynum=", 'paynum' ] + if $FS::CurrentUser::CurrentUser->access_right('View invoices'); #XXX for now + #later# if $FS::CurrentUser::CurrentUser->access_right('View customer payments'); +} + +my $cust_link = sub { + my $cust_thing = shift; + $cust_thing->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + + -- 2.11.0