3 my $custnum = $cust_main->custnum;
5 my $conf = new FS::Conf;
7 my $curuser = $FS::CurrentUser::CurrentUser;
9 my @payby = grep /\w/, $conf->config('payby');
10 #@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
11 @payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
13 my %payby = map { $_=>1 } @payby;
19 <BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
21 <% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) { %>
23 <%= $s++ ? ' | ' : '' %>
24 <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<%= $p %>edit/cust_pay.cgi?popup=1;payby=BILL;custnum=<%= $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter check payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter check payment</A>
28 <% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) { %>
30 <%= $s++ ? ' | ' : '' %>
31 <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<%= $p %>edit/cust_pay.cgi?popup=1;payby=CASH;custnum=<%= $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter cash payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter cash payment</A>
35 <% if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) { %>
37 <%= $s++ ? ' | ' : '' %>
38 <A HREF="<%= $p %>edit/cust_pay.cgi?payby=WEST;custnum=<%= $custnum %>">Enter Western Union payment</A>
42 <% if ( ( $payby{'CARD'} || $payby{'DCRD'} )
43 && $curuser->access_right('Process payment')
47 <%= $s++ ? ' | ' : '' %>
48 <A HREF="<%= $p %>misc/payment.cgi?payby=CARD;custnum=<%= $custnum %>">Process credit card payment</A>
52 <% if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
53 && $curuser->access_right('Process payment')
57 <%= $s++ ? ' | ' : '' %>
58 <A HREF="<%= $p %>misc/payment.cgi?payby=CHEK;custnum=<%= $custnum %>">Process electronic check (ACH) payment</A>
62 <% if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) { %>
64 <%= $s++ ? ' | ' : '' %>
65 <A HREF="<%= $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<%= $custnum %>">Post manual (offline) credit card payment</A>
71 <% if ( $curuser->access_right('Post credit') ) { %>
73 <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<%= $p %>edit/cust_credit.cgi?<%= $custnum %>', 392, 336, 'cust_credit_popup' ), CAPTION, 'Enter credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter credit</A>
84 foreach my $cust_bill ($cust_main->cust_bill) {
85 my $pre = ( $cust_bill->owed > 0 )
86 ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open '
88 my $post = ( $cust_bill->owed > 0 ) ? '</FONT></B>' : '';
89 my $invnum = $cust_bill->invnum;
90 my $link = $curuser->access_right('View invoices')
91 ? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!
94 'date' => $cust_bill->_date,
95 'desc' => $link. $pre.
96 "Invoice #$invnum (Balance \$". $cust_bill->owed. ')'.
97 $post. ( $link ? '</A>' : '' ),
98 'charge' => $cust_bill->charged,
102 #payments (some false laziness w/credits)
103 foreach my $cust_pay ($cust_main->cust_pay) {
105 my $payby = $cust_pay->payby;
108 if ( $payby eq 'CARD' ) {
109 $payinfo = $cust_pay->payinfo_masked;
110 } elsif ( $payby eq 'CHEK' && $cust_pay->payinfo =~ /^(\d+)\@(\d+)$/ ) {
111 $payinfo = "ABA $2, Acct# $1";
113 $payinfo = $cust_pay->payinfo;
115 my @cust_bill_pay = $cust_pay->cust_bill_pay;
116 my @cust_pay_refund = $cust_pay->cust_pay_refund;
118 my $target = "$payby$payinfo";
119 $payby =~ s/^BILL$/Check #/ if $payinfo;
120 $payby =~ s/^CHEK$/Electronic check /;
121 $payby =~ s/^PREP$/Prepaid card /;
122 $payby =~ s/^CARD$/Credit card #/;
123 $payby =~ s/^COMP$/Complimentary by /;
124 $payby =~ s/^CASH$/Cash/;
125 $payby =~ s/^WEST$/Western Union/;
126 $payby =~ s/^MCRD$/Manual credit card/;
127 $payby =~ s/^BILL$//;
128 my $info = $payby ? " ($payby$payinfo)" : '';
130 my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
131 if ( scalar(@cust_bill_pay) == 0
132 && scalar(@cust_pay_refund) == 0 ) {
133 #completely unapplied
134 $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
135 $post = '</FONT></B>';
136 $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!.
138 qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!;
140 } elsif ( scalar(@cust_bill_pay) == 1
141 && scalar(@cust_pay_refund) == 0
142 && $cust_pay->unapplied == 0 ) {
143 #applied to one invoice, the usual situation
144 $desc = ' applied to Invoice #'. $cust_bill_pay[0]->invnum;
145 } elsif ( scalar(@cust_bill_pay) == 0
146 && scalar(@cust_pay_refund) == 1
147 && $cust_pay->unapplied == 0 ) {
148 #applied to one refund
149 $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date);
153 foreach my $app ( sort { $a->_date <=> $b->_date }
154 ( @cust_bill_pay, @cust_pay_refund ) ) {
155 if ( $app->isa('FS::cust_bill_pay') ) {
156 $desc .= ' '.
158 ' applied to Invoice #'. $app->invnum.
160 #' on '. time2str("%D", $cust_bill_pay->_date).
161 } elsif ( $app->isa('FS::cust_pay_refund') ) {
162 $desc .= ' '.
164 ' refunded on '. time2str("%D", $app->_date).
167 die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund";
170 if ( $cust_pay->unapplied > 0 ) {
171 $desc .= ' '.
172 '<B><FONT COLOR="#FF0000">$'.
173 $cust_pay->unapplied. ' unapplied</FONT></B>'.
174 qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!.
176 qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!.
182 my $refund_days = $conf->config('card_refund-days') || 120;
183 if ( $cust_pay->closed !~ /^Y/i
184 && $cust_pay->payby =~ /^(CARD|CHEK)$/
185 && time-$cust_pay->_date < $refund_days*86400
186 && $cust_pay->unrefunded > 0
187 && $curuser->access_right('Refund payment')
189 $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!.
190 qq!paynum=!. $cust_pay->paynum. '"'.
191 qq! TITLE="Send a refund for this payment to the payment gateway"!.
196 if ( $cust_pay->closed !~ /^Y/i
197 && ( ( $cust_pay->payby eq 'CARD'
198 && $curuser->access_right('Credit card void')
200 || ( $cust_pay->payby eq 'CHEK'
201 && $curuser->access_right('Echeck void')
203 || ( $cust_pay->payby !~ /^(CARD|CHEK)$/
204 && $curuser->access_right('Regular void')
209 $void = qq! (<A HREF="javascript:areyousure('!.
210 qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum.
211 qq!', 'Are you sure you want to void this payment?')"!.
212 qq! TITLE="Void this payment from the database!.
213 ( $cust_pay->payby =~ /^(CARD|CHEK)$/
214 ? ' (do not send anything to the payment gateway)'
221 if ( $cust_pay->closed !~ /^Y/i
222 && $conf->exists('deletepayments')
223 && $curuser->access_right('Delete payment')
226 $delete = qq! (<A HREF="javascript:areyousure('!.
227 qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum.
228 qq!', 'Are you sure you want to delete this payment?')"!.
229 qq! TITLE="Delete this payment from the database completely - not recommended"!.
234 if ( $cust_pay->closed !~ /^Y/i
235 && scalar(@cust_bill_pay)
236 && $curuser->access_right('Unapply payment')
239 $unapply = qq! (<A HREF="javascript:areyousure('!.
240 qq!${p}misc/unapply-cust_pay.cgi?!. $cust_pay->paynum.
241 qq!', 'Are you sure you want to unapply this payment?')"!.
242 qq! TITLE="Keep this payment, but dissociate it from the invoices it is currently applied against"!.
247 'date' => $cust_pay->_date,
248 'desc' => $pre. "Payment$post$info$desc".
249 "$apply$refund$void$delete$unapply",
250 'payment' => $cust_pay->paid,
256 foreach my $cust_pay_void ($cust_main->cust_pay_void) {
258 my $payby = $cust_pay_void->payby;
259 my $payinfo = $payby eq 'CARD'
260 ? $cust_pay_void->payinfo_masked
261 : $cust_pay_void->payinfo;
263 $payby =~ s/^BILL$/Check #/ if $payinfo;
264 $payby =~ s/^CHEK$/Electronic check /;
265 $payby =~ s/^BILL$//;
266 $payby =~ s/^(CARD|COMP)$/$1 /;
267 my $info = $payby ? " ($payby$payinfo)" : '';
270 if ( $cust_pay_void->closed !~ /^Y/i
271 && $curuser->access_right('Unvoid')
274 $unvoid = qq! (<A HREF="javascript:areyousure('!.
275 qq!${p}misc/unvoid-cust_pay_void.cgi?!. $cust_pay_void->paynum.
276 qq!', 'Are you sure you want to unvoid this payment?')"!.
277 qq! TITLE="Unvoid this payment from the database!.
278 ( $cust_pay_void->payby =~ /^(CARD|CHEK)$/
279 ? ' (do not send anything to the payment gateway)'
286 'date' => $cust_pay_void->_date,
287 'desc' => "<DEL>Payment $info</DEL> <I>voided ".
288 time2str("%D", $cust_pay_void->void_date).
289 " by ". $cust_pay_void->otaker. '</i>'. $unvoid,
290 'void_payment' => $cust_pay_void->paid,
295 #credits (some false laziness w/payments)
296 foreach my $cust_credit ($cust_main->cust_credit) {
298 my @cust_credit_bill = $cust_credit->cust_credit_bill;
299 my @cust_credit_refund = $cust_credit->cust_credit_refund;
301 my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
302 if ( scalar(@cust_credit_bill) == 0
303 && scalar(@cust_credit_refund) == 0 ) {
304 #completely unapplied
305 $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
306 $post = '</FONT></B>';
307 $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!.
308 $cust_credit->crednum.
309 qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!;
310 } elsif ( scalar(@cust_credit_bill) == 1
311 && scalar(@cust_credit_refund) == 0
312 && $cust_credit->credited == 0 ) {
313 #applied to one invoice, the usual situation
314 $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum;
315 } elsif ( scalar(@cust_credit_bill) == 0
316 && scalar(@cust_credit_refund) == 1
317 && $cust_credit->credited == 0 ) {
318 #applied to one refund
319 $desc = ' refunded on '. time2str("%D", $cust_credit_refund[0]->_date);
323 foreach my $app ( sort { $a->_date <=> $b->_date }
324 ( @cust_credit_bill, @cust_credit_refund ) ) {
325 if ( $app->isa('FS::cust_credit_bill') ) {
326 $desc .= ' '.
328 ' applied to Invoice #'. $app->invnum.
330 #' on '. time2str("%D", $app->_date).
331 } elsif ( $app->isa('FS::cust_credit_refund') ) {
332 $desc .= ' '.
334 ' refunded on '. time2str("%D", $app->_date).
337 die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund";
340 if ( $cust_credit->credited > 0 ) {
341 $desc .= ' <B><FONT COLOR="#FF0000">$'.
342 $cust_credit->credited. ' unapplied</FONT></B>'.
343 qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!.
344 $cust_credit->crednum.
345 qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!.
351 if ( $cust_credit->closed !~ /^Y/i
353 #s'pose deleting a credit isn't bad like deleting a payment
354 # and this needs to be generally available until we have credit voiding..
355 #&& $conf->exists('deletecredits')
357 && $curuser->access_right('Delete credit')
360 $delete = qq! (<A HREF="javascript:areyousure('!.
361 qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum.
362 qq!', 'Are you sure you want to delete this credit?')">!.
367 if ( $cust_credit->closed !~ /^Y/i
368 && scalar(@cust_credit_bill)
369 && $curuser->access_right('Unapply credit')
372 $unapply = qq! (<A HREF="javascript:areyousure('!.
373 qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum.
374 qq!', 'Are you sure you want to unapply this credit?')">!.
379 'date' => $cust_credit->_date,
380 'desc' => $pre. "Credit$post by ". $cust_credit->otaker.
381 ( $cust_credit->reason
382 ? ' ('. $cust_credit->reason. ')'
385 "$desc$apply$delete$unapply",
386 'credit' => $cust_credit->amount,
392 foreach my $cust_refund ($cust_main->cust_refund) {
394 my $payby = $cust_refund->payby;
395 my $payinfo = $payby eq 'CARD'
396 ? $cust_refund->payinfo_masked
397 : $cust_refund->payinfo;
399 $payby =~ s/^BILL$/Check #/ if $payinfo;
400 $payby =~ s/^CHEK$/Electronic check /;
401 $payby =~ s/^(CARD|COMP)$/$1 /;
404 'date' => $cust_refund->_date,
405 'desc' => "Refund ($payby$payinfo) by ". $cust_refund->otaker,
406 'refund' => $cust_refund->refund,
413 <%= include("/elements/table-grid.html") %>
415 <% my $bgcolor1 = '#eeeeee';
416 my $bgcolor2 = '#ffffff';
421 <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
422 <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
423 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Charge</FONT></TH>
424 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment</FONT></TH>
425 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
426 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Refund</FONT></TH>
427 <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Balance</FONT></TH>
431 #display payment history
435 my $money_char = $conf->config('money_char') || '$';
437 my $years = $conf->config('payment_history-years') || 2;
438 my $older_than = time - $years * 31556736; #60*60*24*365.24
443 foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
446 if ( $item->{'date'} < $older_than ) {
447 $display = ' STYLE="display:none" ';
453 if ( $hidden && ! $seen++ ) {
454 ( my $balance_forward = $money_char. $balance ) =~ s/^\$\-/- \$/;
457 <TR ID="balance_forward_row">
458 <TD CLASS="grid" BGCOLOR="#dddddd">
459 <%= time2str("%D",$item->{'date'}) %>
462 <TD CLASS="grid" BGCOLOR="#dddddd">
463 <I>Starting balance on <%= time2str("%D",$item->{'date'}) %></I>
464 (<A HREF="javascript:void(0);" onClick="show_history();">show prior history</A>)
467 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
468 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
469 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
470 <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
471 <TD CLASS="grid" BGCOLOR="#dddddd"><I><%= $balance_forward %></I></TD>
480 if ( $bgcolor eq $bgcolor1 ) {
481 $bgcolor = $bgcolor2;
483 $bgcolor = $bgcolor1;
486 my $charge = exists($item->{'charge'})
487 ? sprintf("$money_char\%.2f", $item->{'charge'})
490 my $payment = exists($item->{'payment'})
491 ? sprintf("- $money_char\%.2f", $item->{'payment'})
494 $payment ||= sprintf( "<DEL>- $money_char\%.2f</DEL>",
495 $item->{'void_payment'}
497 if exists($item->{'void_payment'});
499 my $credit = exists($item->{'credit'})
500 ? sprintf("- $money_char\%.2f", $item->{'credit'})
503 my $refund = exists($item->{'refund'})
504 ? sprintf("$money_char\%.2f", $item->{'refund'})
507 my $target = exists($item->{'target'}) ? $item->{'target'} : '';
509 $balance += $item->{'charge'} if exists $item->{'charge'};
510 $balance -= $item->{'payment'} if exists $item->{'payment'};
511 $balance -= $item->{'credit'} if exists $item->{'credit'};
512 $balance += $item->{'refund'} if exists $item->{'refund'};
513 $balance = sprintf("%.2f", $balance);
514 $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
515 ( my $showbalance = $money_char. $balance ) =~ s/^\$\-/- \$/;
519 <TR <%= $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>>
520 <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>">
521 <% unless ( !$target || $target{$target}++ ) { %>
522 <A NAME="<%= $target %>">
524 <%= time2str("%D",$item->{'date'}) %>
525 <% if ( $target && $target{$target} == 1 ) { %>
530 <TD CLASS="grid" BGCOLOR="<%= $bgcolor %>">
531 <%= $item->{'desc'} %>
533 <TD ALIGN="right" CLASS="grid" BGCOLOR="<%= $bgcolor %>">
536 <TD ALIGN="right" CLASS="grid" BGCOLOR="<%= $bgcolor %>">
539 <TD ALIGN="right" CLASS="grid" BGCOLOR="<%= $bgcolor %>">
542 <TD ALIGN="right" CLASS="grid" BGCOLOR="<%= $bgcolor %>">
545 <TD ALIGN="right" CLASS="grid" BGCOLOR="<%= $bgcolor %>">
554 <SCRIPT TYPE="text/javascript">
556 function show_history () {
557 //alert('showing history!');
559 var balance_forward_row = document.getElementById('balance_forward_row');
561 balance_forward_row.style.display = 'none';
562 for ( var i = 0; i < <%= $old_history %>; i++ ) {
563 var oldRow = document.getElementById('old_history'+i);
564 oldRow.style.display = '';