%#display payment history %my %target = (); % %my $hidden = 0; %my $seen = 0; %my $old_history = 0; %my $lastdate = 0; % %foreach my $item ( @history ) { % % $lastdate = $item->{'date'}; % % my $display = ''; % if ( $item->{'hide'} ) { % $display = ' STYLE="display:none" '; % } % % if ( $bgcolor eq $bgcolor1 ) { % $bgcolor = $bgcolor2; % } else { % $bgcolor = $bgcolor1; % } % % my $charge = exists($item->{'charge'}) % ? sprintf("$money_char\%.2f", $item->{'charge'}) % : exists($item->{'charge_nobal'}) % ? sprintf("$money_char\%.2f", $item->{'charge_nobal'}) % : exists($item->{'void_charge'}) % ? sprintf("$money_char\%.2f", $item->{'void_charge'}) % : ''; % % my $payment = exists($item->{'payment'}) % ? sprintf("- $money_char\%.2f", $item->{'payment'}) % : ''; % % $payment ||= sprintf( "- $money_char\%.2f", % $item->{'void_payment'} % ) % if exists($item->{'void_payment'}); % % my $credit = exists($item->{'credit'}) % ? sprintf("- $money_char\%.2f", $item->{'credit'}) % : ''; % % $credit ||= sprintf( "- $money_char\%.2f", % $item->{'void_credit'} % ) % if exists($item->{'void_credit'}); % % my $refund = exists($item->{'refund'}) % ? sprintf("$money_char\%.2f", $item->{'refund'}) % : ''; % % my $target = exists($item->{'target'}) ? $item->{'target'} : ''; % % my $showbalance = $money_char . $item->{'balance'}; % $showbalance =~ s/^\$\-/- \$/; > % if ( $item->{'balance_forward'} ) { <& .balance_forward_row, $item->{'balance'}, $item->{'date'} &> % } %} # foreach $item
%# payment links % my $s = 0; % if ( $payby{'BILL'} && $curuser->access_right(['Post payment', 'Post check payment' ]) ) { <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Enter check payment'), 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=BILL", 'cust_main' => $cust_main, 'actionlabel' => emt('Enter check payment'), 'width' => ( $opt{'pkg-balances'} ? 763 : 392), 'height' => 392, &> % } % if ( $payby{'CASH'} && $curuser->access_right(['Post payment', 'Post cash payment']) ) { <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Enter cash payment'), 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=CASH", 'cust_main' => $cust_main, 'actionlabel' => emt('Enter cash payment'), 'width' => ( $opt{'pkg-balances'} ? 763 : 392), 'height' => 392, &> % } % if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) { <% $s++ ? ' | ' : '' %> <% mt('Enter Western Union payment') |h %> % } <% $s ? '
' : '' %> % $s=0; % if ( ( $payby{'CARD'} || $payby{'DCRD'} ) % && $curuser->access_right(['Process payment', 'Process credit card payment']) % && ! $cust_main->is_encrypted($cust_main->payinfo) % ) { <% $s++ ? ' | ' : '' %> <% mt('Process credit card payment') |h %> % } % if ( ( $payby{'CHEK'} || $payby{'DCHK'} ) % && $curuser->access_right(['Process payment', 'Process Echeck payment']) % && ! $cust_main->is_encrypted($cust_main->payinfo) % ) { <% $s++ ? ' | ' : '' %> <% mt('Process electronic check (ACH) payment') |h %> % } % if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) { <% $s++ ? ' | ' : '' %> <% mt('Post manual (offline/POS) credit card payment') |h %> % } % if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) { <% $s++ ? ' | ' : '' %> <% mt('Post manual (offline) electronic check payment') |h %> % } <% $s ? '
' : '' %> %# credit links % $s=0; % if ( $curuser->access_right('Post credit') ) { <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Enter credit'), 'action' => "${p}edit/cust_credit.cgi", 'cust_main' => $cust_main, 'actionlabel' => emt('Enter credit'), 'width' => ( $opt{'pkg-balances'} ? 763 : 616), &> % } % if ( $curuser->access_right('Credit line items') ) { <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Credit line items'), #'action' => "${p}search/cust_bill_pkg.cgi?nottax=1;type=select", 'action' => "${p}edit/credit-cust_bill_pkg.html", 'cust_main' => $cust_main, 'actionlabel' => emt('Credit line items'), 'width' => 968, #763, 'height' => 575, &> % } <% $s ? '
' : '' %> %# refund links % $s = 0; % if ( $payby{'BILL'} && $curuser->access_right(['Post refund', 'Post check refund']) ) { <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Enter check refund'), 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=BILL", 'cust_main' => $cust_main, 'actionlabel' => emt('Enter check refund'), 'width' => 392, &> % } % if ( $payby{'CASH'} && $curuser->access_right(['Post refund', 'Post cash refund']) ) { <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Enter cash refund'), 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=CASH", 'cust_main' => $cust_main, 'actionlabel' => emt('Enter cash refund'), 'width' => 392, &> % } %# someday, perhaps. very few gateways let you do unlinked refunds at all. %# Authorize.net makes you sign a special form %# %# % if ( ( $payby{'CARD'} || $payby{'DCRD'} ) %# % && $curuser->access_right('Process refund') %# % && ! $cust_main->is_encrypted($cust_main->payinfo) %# % ) { %# <% $s++ ? ' | ' : '' %> %# Process credit card refund %# % } %# %# % if ( ( $payby{'CHEK'} || $payby{'DCHK'} ) %# % && $curuser->access_right('Process refund') %# % && ! $cust_main->is_encrypted($cust_main->payinfo) %# % ) { %# <% $s++ ? ' | ' : '' %> %# Process electronic check (ACH) refund %# % } % if ( $payby{'MCRD'} && $curuser->access_right('Post refund') ) { <% $s++ ? ' | ' : '' %> <% mt('Post manual (offline/POS) credit card refund') |h %> % } % if ( $payby{'MCHK'} && $curuser->access_right('Post refund') ) { <% $s++ ? ' | ' : '' %> <% mt('Post manual (offline) electronic check refund') |h %> % }
%# invoice reports, combined statement % if ( $curuser->access_right('List invoices') ) { % if ( $curuser->access_right('Resend invoices') % && $cust_main->invoicing_list_emailonly ) { <% mt('Email statement to this customer') |h %>
% } % if ( $num_cust_bill > 0 % && $curuser->access_right('View legacy typeset statements') % ) { <% mt('Download typeset statement PDF') |h %>
% } <% mt('Invoice reports') |h %> % }
%# XXX payments, credits, refund reports %# tax exemption link % my $view_exemptions = $curuser->access_right('View customer tax exemptions'); % my $add_adjustment = ( $conf->exists('enable_tax_adjustments') % && $curuser->access_right('Add customer tax adjustment') % ); % if ( $view_exemptions || $add_adjustment ) { % if ( $view_exemptions ) { <% mt('View tax exemptions') |h %> <% $add_adjustment ? '|' : '' %> % } % if ( $add_adjustment ) { <& /elements/popup_link.html, { 'action' => $p.'edit/cust_tax_adjustment.html?custnum='. $cust_main->custnum, 'label' => emt('Add tax adjustment'), 'actionlabel' => emt('Add tax adjustment'), 'height' => 200, } &> | <% mt('View tax adjustments') |h %> % }
% } %# batched payment links % if ( ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') ) % && $curuser->access_right('View customer batched payments') % ) % { <% mt('View batched payments:') |h %> % foreach my $status (qw( Queued In-transit Complete All )) { <% mt($status) |h %> <% $status ne 'All' ? '|' : '' %> % }
% } %# pending payment links % if ( $curuser->access_right('View customer pending payments') % && scalar($cust_main->cust_pay_pending) % ) % { <% mt('View pending payments') |h %>
% }
%# and now the table <& /elements/table-grid.html &> % my $bgcolor1 = '#eeeeee'; % my $bgcolor2 = '#ffffff'; % my $bgcolor = '';
<% mt('Date') |h %> <% mt('Description') |h %> <% mt('Invoice') |h %> <% mt('Payment') |h %> <% mt('In-house Credit') |h %> <% mt('Refund') |h %> <% mt('Balance') |h %>
% unless ( !$target || $target{$target}++ ) { % } <% time2str($date_format, $item->{'date'}) %> % if ( $target && $target{$target} == 1 ) { % } <% $item->{'desc'} %> <% $charge %> <% $payment %> <% $credit %> <% $refund %> <% $showbalance %>
<%def .balance_forward_row> % my( $b, $date ) = @_; % ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/- \$/; <% time2str($date_format, $date) %> <% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %> (<% mt('show prior history') |h %>) <% $balance_forward %> <%shared> my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; my $money_char = $conf->config('money_char') || '$'; <%init> my( $cust_main ) = @_; my $custnum = $cust_main->custnum; my $curuser = $FS::CurrentUser::CurrentUser; my @payby = grep /\w/, $conf->config('payby'); #@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) @payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) unless @payby; my %payby = map { $_=>1 } @payby; my %status = ( 'Queued' => 'O', #Open 'In-transit' => 'I', 'Complete' => 'R', #Resolved 'All' => '', ); #get payment history my @history = (); my %opt = ( #config ( map { $_ => scalar($conf->config($_)) } qw( card_refund-days date_format ) ), ( map { $_ => $conf->exists($_) } qw( deleteinvoices deletepayments deleterefunds pkg-balances cust_credit_bill_pkg-manual cust_bill_pay_pkg-manual ) ), 'money_char ' => $money_char, #rights ( map { $_ => $curuser->access_right($_) } ( 'View invoices', 'Void invoices', 'Unvoid invoices', 'Delete invoices', 'Apply payment', 'Refund credit card payment', 'Refund Echeck payment', 'Post refund', 'Post check refund', 'Post cash refund ', 'Refund payment', 'Credit card void', 'Echeck void', 'Void payments', 'Unvoid payments', 'Delete payment', 'Unapply payment', 'Apply credit', 'Delete credit', 'Unapply credit', 'Void credit', 'Unvoid credit', 'Delete refund', 'Billing event reports', 'View customer billing events', ) ), #customer information 'total_owed' => $cust_main->total_owed, 'total_unapplied_refunds' => $cust_main->total_unapplied_refunds, ); $opt{'date_format'} ||= '%m/%d/%Y'; #legacy invoices foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) { push @history, { 'date' => $legacy_cust_bill->_date, 'order' => 1, 'num' => $legacy_cust_bill->legacyid, 'desc' => include('payment_history/legacy_invoice.html', $legacy_cust_bill, %opt ), 'charge_nobal' => $legacy_cust_bill->charged, }; } #invoices my $num_cust_bill = 0; foreach my $cust_bill ($cust_main->cust_bill) { push @history, { 'date' => $cust_bill->_date, 'order' => 1, 'num' => $cust_bill->invnum, 'desc' => include('payment_history/invoice.html', $cust_bill, %opt ), 'charge' => $cust_bill->charged, }; $num_cust_bill++; } #voided invoices foreach my $cust_bill_void ($cust_main->cust_bill_void) { push @history, { 'date' => $cust_bill_void->_date, 'order' => 0, 'num' => $cust_bill_void->invnum, 'desc' => include('payment_history/voided_invoice.html', $cust_bill_void, %opt ), 'void_charge' => $cust_bill_void->charged, }; } #statements foreach my $cust_statement ($cust_main->cust_statement) { push @history, { 'date' => $cust_statement->_date, 'order' => 2, 'num' => $cust_statement->statementnum, 'desc' => include('payment_history/statement.html', $cust_statement, %opt ), #'charge' => $cust_bill->charged, }; } #payments (some false laziness w/credits) foreach my $cust_pay ($cust_main->cust_pay) { push @history, { 'date' => $cust_pay->_date, 'order' => 6, 'num' => $cust_pay->paynum, 'desc' => include('payment_history/payment.html', $cust_pay, %opt ), 'payment' => $cust_pay->paid, #'target' => $target, #XXX }; } #pending payments foreach my $cust_pay_pending ($cust_main->cust_pay_pending) { push @history, { 'date' => $cust_pay_pending->_date, 'order' => 4, 'num' => $cust_pay_pending->paypendingnum, 'desc' => include('payment_history/pending_payment.html', $cust_pay_pending, %opt ), 'void_payment' => $cust_pay_pending->paid, }; } #voided payments foreach my $cust_pay_void ($cust_main->cust_pay_void) { push @history, { 'date' => $cust_pay_void->_date, 'order' => 3, 'num' => $cust_pay_void->paynum, 'desc' => include('payment_history/voided_payment.html', $cust_pay_void, %opt ), 'void_payment' => $cust_pay_void->paid, }; } #voided credits foreach my $cust_credit_void ($cust_main->cust_credit_void) { push @history, { 'date' => $cust_credit_void->_date, 'order' => 7, 'num' => $cust_credit_void->paynum, 'desc' => include('payment_history/voided_credit.html', $cust_credit_void, %opt ), 'void_credit' => $cust_credit_void->amount, }; } #declined payments foreach my $cust_pay_pending ($cust_main->cust_pay_pending_attempt) { push @history, { 'date' => $cust_pay_pending->_date, 'order' => 5, 'num' => $cust_pay_pending->paypendingnum, 'desc' => include('payment_history/attempted_payment.html', $cust_pay_pending, %opt ), 'void_payment' => $cust_pay_pending->paid, #?? #'target' => $target, #XXX }; } #declined batch payments foreach my $cust_pay_batch ( $cust_main->cust_pay_batch(hashref => {status => 'Declined'}) ) { my $pay_batch = $cust_pay_batch->pay_batch; push @history, { 'date' => $pay_batch->upload, 'order' => 5, 'num' => $cust_pay_batch->paybatchnum, 'desc' => include('payment_history/attempted_batch_payment.html', $cust_pay_batch, %opt), 'void_payment' => $cust_pay_batch->amount, }; } #credits (some false laziness w/payments) foreach my $cust_credit ($cust_main->cust_credit) { push @history, { 'date' => $cust_credit->_date, 'order' => 8, 'num' => $cust_credit->crednum, 'desc' => include('payment_history/credit.html', $cust_credit, %opt ), 'credit' => $cust_credit->amount, }; } #refunds foreach my $cust_refund ($cust_main->cust_refund) { push @history, { 'date' => $cust_refund->_date, 'order' => 9, 'num' => $cust_refund->refundnum, 'desc' => include('payment_history/refund.html', $cust_refund, %opt), 'refund' => $cust_refund->refund, }; } # sort in forward order first, and calculate running balances my $years = $conf->config('payment_history-years') || 2; my $older_than = time - $years * 31556926; #60*60*24*365.2422 my $balance = 0; @history = sort { $a->{date} <=> $b->{date} or $a->{order} <=> $b->{order} or $a->{num} <=> $b->{num} } @history; my $i = 0; my $balance_forward; foreach my $item (@history) { $balance += $item->{'charge'} if exists $item->{'charge'}; $balance -= $item->{'payment'} if exists $item->{'payment'}; $balance -= $item->{'credit'} if exists $item->{'credit'}; $balance += $item->{'refund'} if exists $item->{'refund'}; $balance = sprintf("%.2f", $balance); $balance =~ s/^\-0\.00$/0.00/; $item->{'balance'} = $balance; if ( $item->{'date'} < $older_than ) { $item->{'hide'} = 1; } elsif ( $history[$i-1]->{'hide'} ) { # this is the end of the hidden section $history[$i-1]->{'balance_forward'} = 1; } $i++; } if ( @history and $history[-1]->{'hide'} ) { # then everything is hidden $history[-1]->{'balance_forward'} = 1; } # then sort in user-pref order if ( $curuser->option('history_order') eq 'newest' ) { @history = sort { $b->{date} <=> $a->{date} or $b->{order} <=> $a->{order} #or still forward here? or $b->{num} <=> $a->{num} } @history; } # else it's already oldest-first, and there are no other options yet sub translate_payby { my ($payby,$payinfo) = (shift,shift); my %payby = ( FS::payby->payby2shortname, BILL => $payinfo ? emt('Check #') : '', CHEK => emt('Electronic check '), PREP => emt('Prepaid card '), CARD => emt('Credit card #'), COMP => emt('Complimentary by '), #CASH => emt('Cash'), #WEST => emt('Western Union'), #MCRD => emt('Manual credit card'), ); $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby; $payby; }; sub translate_payby_refund { my ($payby,$payinfo) = (shift,shift); my %payby = ( FS::payby->payby2shortname, BILL => $payinfo ? emt('Check #') : emt('Check'), CHEK => emt('Electronic check '), CARD => emt('Credit card #'), COMP => emt('Complimentary by '), ); $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby; $payby; }; sub translate_payinfo { my $object = shift; my $payby = $object->payby; my $payinfo = $object->payinfo; if ( $payby eq 'CARD' ) { $payinfo = $object->paymask; } elsif ( $payby eq 'CHEK' ) { #false laziness w/payinfo_Mixin::payby_payinfo_pretty, should use that my( $account, $aba ) = split('@', $object->paymask ); if ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #blame canada my($branch, $routing) = ($1, $2); $payinfo = emt("Routing [_1], Branch [_2], Acct [_3]", $routing, $branch, $account); } else { $payinfo = emt("Routing [_1], Acct [_2]", $aba, $account); } } ($payby,$payinfo); } sub areyousure_link { my ($url,$msg,$title,$label) = (shift,shift,shift,shift); ' ('.$label.')'; }