8 % if ( $payby{'BILL'} && $curuser->access_right(['Post payment', 'Post check payment' ]) ) {
9 <% $s++ ? ' | ' : '' %>
10 <& /elements/popup_link-cust_main.html,
11 'label' => emt('Enter check payment'),
12 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=BILL",
13 'cust_main' => $cust_main,
14 'actionlabel' => emt('Enter check payment'),
15 'width' => ( $opt{'pkg-balances'} ? 763 : 392),
20 % if ( $payby{'CASH'} && $curuser->access_right(['Post payment', 'Post cash payment']) ) {
21 <% $s++ ? ' | ' : '' %>
22 <& /elements/popup_link-cust_main.html,
23 'label' => emt('Enter cash payment'),
24 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=CASH",
25 'cust_main' => $cust_main,
26 'actionlabel' => emt('Enter cash payment'),
27 'width' => ( $opt{'pkg-balances'} ? 763 : 392),
32 % if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) {
33 <% $s++ ? ' | ' : '' %>
34 <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>"><% mt('Enter Western Union payment') |h %></A>
37 <% $s ? '<BR>' : '' %>
40 % if ( ( $payby{'CARD'} || $payby{'DCRD'} )
41 % && $curuser->access_right(['Process payment', 'Process credit card payment'])
42 % && ! $cust_main->is_encrypted($cust_main->payinfo)
44 <% $s++ ? ' | ' : '' %>
45 <A HREF="<% $p %>misc/payment.cgi?payby=CARD;custnum=<% $custnum %>"><% mt('Process credit card payment') |h %></A>
48 % if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
49 % && $curuser->access_right(['Process payment', 'Process Echeck payment'])
50 % && ! $cust_main->is_encrypted($cust_main->payinfo)
52 <% $s++ ? ' | ' : '' %>
53 <A HREF="<% $p %>misc/payment.cgi?payby=CHEK;custnum=<% $custnum %>"><% mt('Process electronic check (ACH) payment') |h %></A>
56 % if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) {
57 <% $s++ ? ' | ' : '' %>
58 <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline/POS) credit card payment') |h %></A>
61 % if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) {
62 <% $s++ ? ' | ' : '' %>
63 <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCHK;custnum=<% $custnum %>"><% mt('Post manual (offline) electronic check payment') |h %></A>
66 <% $s ? '<BR>' : '' %>
71 % if ( $curuser->access_right('Post credit') ) {
72 <% $s++ ? ' | ' : '' %>
73 <& /elements/popup_link-cust_main.html,
74 'label' => emt('Enter credit'),
75 'action' => "${p}edit/cust_credit.cgi",
76 'cust_main' => $cust_main,
77 'actionlabel' => emt('Enter credit'),
78 'width' => ( $opt{'pkg-balances'} ? 763 : 616),
81 % if ( $curuser->access_right('Credit line items') ) {
82 <% $s++ ? ' | ' : '' %>
83 <& /elements/popup_link-cust_main.html,
84 'label' => emt('Credit line items'),
85 #'action' => "${p}search/cust_bill_pkg.cgi?nottax=1;type=select",
86 'action' => "${p}edit/credit-cust_bill_pkg.html",
87 'cust_main' => $cust_main,
88 'actionlabel' => emt('Credit line items'),
93 <% $s ? '<BR>' : '' %>
98 % if ( $payby{'BILL'} && $curuser->access_right(['Post refund', 'Post check refund']) ) {
99 <% $s++ ? ' | ' : '' %>
100 <& /elements/popup_link-cust_main.html,
101 'label' => emt('Enter check refund'),
102 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=BILL",
103 'cust_main' => $cust_main,
104 'actionlabel' => emt('Enter check refund'),
109 % if ( $payby{'CASH'} && $curuser->access_right(['Post refund', 'Post cash refund']) ) {
110 <% $s++ ? ' | ' : '' %>
111 <& /elements/popup_link-cust_main.html,
112 'label' => emt('Enter cash refund'),
113 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=CASH",
114 'cust_main' => $cust_main,
115 'actionlabel' => emt('Enter cash refund'),
120 %# someday, perhaps. very few gateways let you do unlinked refunds at all.
121 %# Authorize.net makes you sign a special form
123 %# % if ( ( $payby{'CARD'} || $payby{'DCRD'} )
124 %# % && $curuser->access_right('Process refund')
125 %# % && ! $cust_main->is_encrypted($cust_main->payinfo)
127 %# <% $s++ ? ' | ' : '' %>
128 %# <A HREF="<% $p %>misc/refund.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card refund</A>
131 %# % if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
132 %# % && $curuser->access_right('Process refund')
133 %# % && ! $cust_main->is_encrypted($cust_main->payinfo)
135 %# <% $s++ ? ' | ' : '' %>
136 %# <A HREF="<% $p %>misc/refund.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) refund</A>
139 % if ( $payby{'MCRD'} && $curuser->access_right('Post refund') ) {
140 <% $s++ ? ' | ' : '' %>
141 <A HREF="<% $p %>edit/cust_refund.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline/POS) credit card refund') |h %></A>
144 % if ( $payby{'MCHK'} && $curuser->access_right('Post refund') ) {
145 <% $s++ ? ' | ' : '' %>
146 <A HREF="<% $p %>edit/cust_refund.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline) electronic check refund') |h %></A>
150 <TD ALIGN="right" VALIGN="top">
152 %# invoice reports, combined statement
153 % if ( $curuser->access_right('List invoices') ) {
154 % if ( $curuser->access_right('Resend invoices')
155 % && $cust_main->invoicing_list_emailonly ) {
157 <A HREF="<% $p %>misc/email-customer-statement.html?table=cust_main;agent_virt_agentnum=<% $cust_main->agentnum %>;custnum=<% $custnum %>"><% mt('Email statement to this customer') |h %></A>
161 % if ( $num_cust_bill > 0
162 % && $curuser->access_right('View legacy typeset statements')
164 <A HREF="<% $p %>view/cust_main_statement-pdf.cgi?<% $custnum %>"><%
165 mt('Download typeset statement PDF') |h %></A>
168 <A HREF="<% $p %>search/report_cust_bill.html?custnum=<% $custnum %>"><% mt('Invoice reports') |h %></A>
172 %# XXX payments, credits, refund reports
174 %# tax exemption link
176 % my $view_exemptions = $curuser->access_right('View customer tax exemptions');
177 % my $add_adjustment = ( $conf->exists('enable_tax_adjustments')
178 % && $curuser->access_right('Add customer tax adjustment')
180 % if ( $view_exemptions || $add_adjustment ) {
182 % if ( $view_exemptions ) {
183 <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>"><% mt('View tax exemptions') |h %></A>
184 <% $add_adjustment ? '|' : '' %>
187 % if ( $add_adjustment ) {
188 <& /elements/popup_link.html, {
189 'action' => $p.'edit/cust_tax_adjustment.html?custnum='. $cust_main->custnum,
190 'label' => emt('Add tax adjustment'),
191 'actionlabel' => emt('Add tax adjustment'),
196 <A HREF="<% $p %>search/cust_tax_adjustment.html?custnum=<% $custnum %>"><% mt('View tax adjustments') |h %></A>
202 %# batched payment links
204 % if ( ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') )
205 % && $curuser->access_right('View customer batched payments')
208 <% mt('View batched payments:') |h %>
209 % foreach my $status (qw( Queued In-transit Complete All )) {
210 <A HREF="<% $p %>search/cust_pay_batch.cgi?status=<% $status{$status} %>;custnum=<% $custnum %>"><% mt($status) |h %></A>
211 <% $status ne 'All' ? '|' : '' %>
216 %# pending payment links
218 % if ( $curuser->access_right('View customer pending payments')
219 % && scalar($cust_main->cust_pay_pending)
222 <A HREF="<% $p %>search/cust_pay_pending.html?magic=_date;statusNOT=done;custnum=<% $custnum %>"><% mt('View pending payments') |h %></A><BR>
232 <& /elements/table-grid.html &>
233 % my $bgcolor1 = '#eeeeee';
234 % my $bgcolor2 = '#ffffff';
238 <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Date') |h %></TH>
239 <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Description') |h %></TH>
240 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Invoice') |h %></FONT></TH>
241 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Payment') |h %></FONT></TH>
242 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('In-house Credit') |h %></FONT></TH>
243 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Refund') |h %></FONT></TH>
244 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Balance') |h %></FONT></TH>
247 %#display payment history
253 %my $old_history = 0;
256 %foreach my $item ( @history ) {
258 % $lastdate = $item->{'date'};
261 % if ( $item->{'hide'} ) {
262 % $display = ' STYLE="display:none" ';
265 % if ( $bgcolor eq $bgcolor1 ) {
266 % $bgcolor = $bgcolor2;
268 % $bgcolor = $bgcolor1;
271 % my $charge = exists($item->{'charge'})
272 % ? sprintf("$money_char\%.2f", $item->{'charge'})
273 % : exists($item->{'charge_nobal'})
274 % ? sprintf("$money_char\%.2f", $item->{'charge_nobal'})
275 % : exists($item->{'void_charge'})
276 % ? sprintf("<DEL>$money_char\%.2f</DEL>", $item->{'void_charge'})
279 % my $payment = exists($item->{'payment'})
280 % ? sprintf("- $money_char\%.2f", $item->{'payment'})
283 % $payment ||= sprintf( "<DEL>- $money_char\%.2f</DEL>",
284 % $item->{'void_payment'}
286 % if exists($item->{'void_payment'});
288 % my $credit = exists($item->{'credit'})
289 % ? sprintf("- $money_char\%.2f", $item->{'credit'})
292 % $credit ||= sprintf( "<DEL>- $money_char\%.2f</DEL>",
293 % $item->{'void_credit'}
295 % if exists($item->{'void_credit'});
297 % my $refund = exists($item->{'refund'})
298 % ? sprintf("$money_char\%.2f", $item->{'refund'})
301 % my $target = exists($item->{'target'}) ? $item->{'target'} : '';
303 % my $showbalance = $money_char . $item->{'balance'};
304 % $showbalance =~ s/^\$\-/- \$/;
306 <TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>>
307 <TD VALIGN="top" CLASS="grid" BGCOLOR="<% $bgcolor %>">
308 % unless ( !$target || $target{$target}++ ) {
310 <A NAME="<% $target %>">
313 <% time2str($date_format, $item->{'date'}) %>
314 % if ( $target && $target{$target} == 1 ) {
321 <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
322 <% $item->{'desc'} %>
324 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
327 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
330 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
333 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
336 <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
341 % if ( $item->{'balance_forward'} ) {
342 <& .balance_forward_row, $item->{'balance'}, $item->{'date'} &>
351 <SCRIPT TYPE="text/javascript">
353 function show_history () {
354 //alert('showing history!');
356 var balance_forward_row = document.getElementById('balance_forward_row');
358 balance_forward_row.style.display = 'none';
359 for ( var i = 0; i < <% $old_history %>; i++ ) {
360 var oldRow = document.getElementById('old_history'+i);
361 oldRow.style.display = '';
367 <%def .balance_forward_row>
368 % my( $b, $date ) = @_;
369 % ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/- \$/;
371 <TR ID="balance_forward_row">
372 <TD CLASS="grid" BGCOLOR="#dddddd">
373 <% time2str($date_format, $date) %>
376 <TD CLASS="grid" BGCOLOR="#dddddd">
377 <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I>
378 (<A HREF="javascript:void(0);" onClick="show_history();"><% mt('show prior history') |h %></A>)
381 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
382 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
383 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
384 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
385 <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
390 my $conf = new FS::Conf;
391 my $date_format = $conf->config('date_format') || '%m/%d/%Y';
392 my $money_char = $conf->config('money_char') || '$';
396 my( $cust_main ) = @_;
397 my $custnum = $cust_main->custnum;
399 my $curuser = $FS::CurrentUser::CurrentUser;
401 my @payby = grep /\w/, $conf->config('payby');
402 #@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
403 @payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
405 my %payby = map { $_=>1 } @payby;
408 'Queued' => 'O', #Open
410 'Complete' => 'R', #Resolved
420 ( map { $_ => scalar($conf->config($_)) }
421 qw( card_refund-days date_format )
423 ( map { $_ => $conf->exists($_) }
424 qw( deleteinvoices deletepayments deleterefunds pkg-balances
425 cust_credit_bill_pkg-manual cust_bill_pay_pkg-manual
428 'money_char ' => $money_char,
431 ( map { $_ => $curuser->access_right($_) }
433 'View invoices', 'Void invoices', 'Unvoid invoices', 'Delete invoices',
434 'Apply payment', 'Refund credit card payment', 'Refund Echeck payment',
435 'Post refund', 'Post check refund', 'Post cash refund ', 'Refund payment',
436 'Credit card void', 'Echeck void', 'Void payments', 'Unvoid payments',
437 'Delete payment', 'Unapply payment',
438 'Apply credit', 'Delete credit', 'Unapply credit', 'Void credit', 'Unvoid credit',
440 'Billing event reports', 'View customer billing events',
444 #customer information
445 'total_owed' => $cust_main->total_owed,
446 'total_unapplied_refunds' => $cust_main->total_unapplied_refunds,
449 $opt{'date_format'} ||= '%m/%d/%Y';
452 foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) {
454 'date' => $legacy_cust_bill->_date,
456 'num' => $legacy_cust_bill->legacyid,
457 'desc' => include('payment_history/legacy_invoice.html', $legacy_cust_bill, %opt ),
458 'charge_nobal' => $legacy_cust_bill->charged,
463 my $num_cust_bill = 0;
464 foreach my $cust_bill ($cust_main->cust_bill) {
466 'date' => $cust_bill->_date,
468 'num' => $cust_bill->invnum,
469 'desc' => include('payment_history/invoice.html', $cust_bill, %opt ),
470 'charge' => $cust_bill->charged,
476 foreach my $cust_bill_void ($cust_main->cust_bill_void) {
478 'date' => $cust_bill_void->_date,
480 'num' => $cust_bill_void->invnum,
481 'desc' => include('payment_history/voided_invoice.html', $cust_bill_void, %opt ),
482 'void_charge' => $cust_bill_void->charged,
487 foreach my $cust_statement ($cust_main->cust_statement) {
489 'date' => $cust_statement->_date,
491 'num' => $cust_statement->statementnum,
492 'desc' => include('payment_history/statement.html', $cust_statement, %opt ),
493 #'charge' => $cust_bill->charged,
497 #payments (some false laziness w/credits)
498 foreach my $cust_pay ($cust_main->cust_pay) {
500 'date' => $cust_pay->_date,
502 'num' => $cust_pay->paynum,
503 'desc' => include('payment_history/payment.html', $cust_pay, %opt ),
504 'payment' => $cust_pay->paid,
505 #'target' => $target, #XXX
510 foreach my $cust_pay_pending ($cust_main->cust_pay_pending) {
512 'date' => $cust_pay_pending->_date,
514 'num' => $cust_pay_pending->paypendingnum,
515 'desc' => include('payment_history/pending_payment.html', $cust_pay_pending, %opt ),
516 'void_payment' => $cust_pay_pending->paid,
522 foreach my $cust_pay_void ($cust_main->cust_pay_void) {
524 'date' => $cust_pay_void->_date,
526 'num' => $cust_pay_void->paynum,
527 'desc' => include('payment_history/voided_payment.html', $cust_pay_void, %opt ),
528 'void_payment' => $cust_pay_void->paid,
534 foreach my $cust_credit_void ($cust_main->cust_credit_void) {
536 'date' => $cust_credit_void->_date,
538 'num' => $cust_credit_void->paynum,
539 'desc' => include('payment_history/voided_credit.html', $cust_credit_void, %opt ),
540 'void_credit' => $cust_credit_void->amount,
545 foreach my $cust_pay_pending ($cust_main->cust_pay_pending_attempt) {
547 'date' => $cust_pay_pending->_date,
549 'num' => $cust_pay_pending->paypendingnum,
550 'desc' => include('payment_history/attempted_payment.html', $cust_pay_pending, %opt ),
551 'void_payment' => $cust_pay_pending->paid, #??
552 #'target' => $target, #XXX
555 #declined batch payments
556 foreach my $cust_pay_batch (
557 $cust_main->cust_pay_batch(hashref => {status => 'Declined'})
559 my $pay_batch = $cust_pay_batch->pay_batch;
561 'date' => $pay_batch->upload,
563 'num' => $cust_pay_batch->paybatchnum,
564 'desc' => include('payment_history/attempted_batch_payment.html', $cust_pay_batch, %opt),
565 'void_payment' => $cust_pay_batch->amount,
569 #credits (some false laziness w/payments)
570 foreach my $cust_credit ($cust_main->cust_credit) {
572 'date' => $cust_credit->_date,
574 'num' => $cust_credit->crednum,
575 'desc' => include('payment_history/credit.html', $cust_credit, %opt ),
576 'credit' => $cust_credit->amount,
582 foreach my $cust_refund ($cust_main->cust_refund) {
584 'date' => $cust_refund->_date,
586 'num' => $cust_refund->refundnum,
587 'desc' => include('payment_history/refund.html', $cust_refund, %opt),
588 'refund' => $cust_refund->refund,
593 # sort in forward order first, and calculate running balances
594 my $years = $conf->config('payment_history-years') || 2;
595 my $older_than = time - $years * 31556926; #60*60*24*365.2422
598 @history = sort { $a->{date} <=> $b->{date}
599 or $a->{order} <=> $b->{order}
600 or $a->{num} <=> $b->{num}
606 foreach my $item (@history) {
607 $balance += $item->{'charge'} if exists $item->{'charge'};
608 $balance -= $item->{'payment'} if exists $item->{'payment'};
609 $balance -= $item->{'credit'} if exists $item->{'credit'};
610 $balance += $item->{'refund'} if exists $item->{'refund'};
611 $balance = sprintf("%.2f", $balance);
612 $balance =~ s/^\-0\.00$/0.00/;
613 $item->{'balance'} = $balance;
615 if ( $item->{'date'} < $older_than ) {
617 } elsif ( $history[$i-1]->{'hide'} ) {
618 # this is the end of the hidden section
619 $history[$i-1]->{'balance_forward'} = 1;
623 if ( @history and $history[-1]->{'hide'} ) {
624 # then everything is hidden
625 $history[-1]->{'balance_forward'} = 1;
628 # then sort in user-pref order
629 if ( $curuser->option('history_order') eq 'newest' ) {
630 @history = sort { $b->{date} <=> $a->{date}
631 or $b->{order} <=> $a->{order} #or still forward here?
632 or $b->{num} <=> $a->{num}
635 } # else it's already oldest-first, and there are no other options yet
637 sub translate_payby {
638 my ($payby,$payinfo) = (shift,shift);
640 FS::payby->payby2shortname,
641 BILL => $payinfo ? emt('Check #') : '',
642 CHEK => emt('Electronic check '),
643 PREP => emt('Prepaid card '),
644 CARD => emt('Credit card #'),
645 COMP => emt('Complimentary by '),
646 #CASH => emt('Cash'),
647 #WEST => emt('Western Union'),
648 #MCRD => emt('Manual credit card'),
650 $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby;
654 sub translate_payby_refund {
655 my ($payby,$payinfo) = (shift,shift);
657 FS::payby->payby2shortname,
658 BILL => $payinfo ? emt('Check #') : emt('Check'),
659 CHEK => emt('Electronic check '),
660 CARD => emt('Credit card #'),
661 COMP => emt('Complimentary by '),
663 $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby;
667 sub translate_payinfo {
669 my $payby = $object->payby;
670 my $payinfo = $object->payinfo;
672 if ( $payby eq 'CARD' ) {
673 $payinfo = $object->paymask;
674 } elsif ( $payby eq 'CHEK' ) {
675 #false laziness w/payinfo_Mixin::payby_payinfo_pretty, should use that
676 my( $account, $aba ) = split('@', $object->paymask );
677 if ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #blame canada
678 my($branch, $routing) = ($1, $2);
679 $payinfo = emt("Routing [_1], Branch [_2], Acct [_3]",
680 $routing, $branch, $account);
682 $payinfo = emt("Routing [_1], Acct [_2]", $aba, $account);
689 sub areyousure_link {
690 my ($url,$msg,$title,$label) = (shift,shift,shift,shift);
691 ' (<A HREF="javascript:areyousure(\''.$url.'\',\''.$msg.'\')" TITLE="'.$title.'">'.$label.'</A>)';