ACLs, take three or four or something
[freeside.git] / httemplate / view / cust_main / payment_history.html
1 <%
2   my( $cust_main ) = @_;
3   my $custnum = $cust_main->custnum;
4
5   my $conf = new FS::Conf;
6
7   my $curuser = $FS::CurrentUser::CurrentUser;
8
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 ))
12     unless @payby;
13   my %payby = map { $_=>1 } @payby;
14
15   my $s = 0;
16
17 %>
18
19 <BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
20
21 <% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) { %>
22
23   <%= $s++ ? ' | ' : '' %>
24   <A HREF="<%= $p %>edit/cust_pay.cgi?payby=BILL;custnum=<%= $custnum %>">Post check payment</A>
25
26 <% } %>
27
28 <% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) { %>
29
30   <%= $s++ ? ' | ' : '' %>
31   <A HREF="<%= $p %>edit/cust_pay.cgi?payby=CASH;custnum=<%= $custnum %>">Post cash payment</A>
32
33 <% } %>
34
35 <% if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) { %>
36
37   <%= $s++ ? ' | ' : '' %>
38   <A HREF="<%= $p %>edit/cust_pay.cgi?payby=WEST;custnum=<%= $custnum %>">Post Western Union payment</A>
39
40 <% } %>
41
42 <% if ( ( $payby{'CARD'} || $payby{'DCRD'} )
43         && $curuser->access_right('Process payment')
44       ) {
45 %>
46
47   <%= $s++ ? ' | ' : '' %>
48   <A HREF="<%= $p %>misc/payment.cgi?payby=CARD;custnum=<%= $custnum %>">Process credit card payment</A>
49
50 <% } %>
51
52 <% if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
53         && $curuser->access_right('Process payment')
54       ) {
55 %>
56
57   <%= $s++ ? ' | ' : '' %>
58   <A HREF="<%= $p %>misc/payment.cgi?payby=CHEK;custnum=<%= $custnum %>">Process electronic check (ACH) payment</A>
59
60 <% } %>
61
62 <% if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) { %>
63
64   <%= $s++ ? ' | ' : '' %>
65   <A HREF="<%= $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<%= $custnum %>">Post manual credit card payment</A>
66
67 <% } %>
68
69 <BR>
70
71 <% if ( $curuser->access_right('Post credit') ) { %>
72
73   <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<%= $p %>edit/cust_credit.cgi?<%= $custnum %>', 392, 336, 'cust_credit_popup' ), CAPTION, 'Post credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK )">Post credit</A>
74
75   <BR>
76
77 <% } %>
78
79 <%
80 #get payment history
81 my @history = ();
82
83 #invoices
84 foreach my $cust_bill ($cust_main->cust_bill) {
85   my $pre = ( $cust_bill->owed > 0 )
86               ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open '
87               : '';
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">!
92                : '';
93   push @history, {
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,
99   };
100 }
101
102 #payments (some false laziness w/credits)
103 foreach my $cust_pay ($cust_main->cust_pay) {
104
105   my $payby = $cust_pay->payby;
106
107   my $payinfo;
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";
112   } else {
113     $payinfo = $cust_pay->payinfo;
114   }
115   my @cust_bill_pay = $cust_pay->cust_bill_pay;
116   my @cust_pay_refund = $cust_pay->cust_pay_refund;
117
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)" : '';
129
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?!.
137              $cust_pay->paynum.
138              qq!', 392, 336, 'cust_credit_popup' ), CAPTION, 'Post credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK )">apply</A>)!;
139
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);
150   } else {
151     #complicated
152     $desc = '<BR>';
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 .= '&nbsp;&nbsp;'.
157                  '$'. $app->amount.
158                  ' applied to Invoice #'. $app->invnum.
159                  '<BR>';
160                  #' on '. time2str("%D", $cust_bill_pay->_date).
161       } elsif ( $app->isa('FS::cust_pay_refund') ) {
162         $desc .= '&nbsp;&nbsp;'.
163                  '$'. $app->amount.
164                  ' refunded on'. time2str("%D", $app->_date).
165                  '<BR>';
166       } else {
167         die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund";
168       }
169     }
170     if ( $cust_pay->unapplied > 0 ) {
171       $desc .= '&nbsp;&nbsp;'.
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?!.
175                $cust_pay->paynum. 
176                qq!', 392, 336, 'cust_credit_popup' ), CAPTION, 'Post credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK )">apply</A>)!.
177                '<BR>';
178     }
179   }
180
181   my $refund = '';
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')
188   ) {
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"!.
192               qq!>refund</A>)!;
193   }
194
195   my $void = '';
196   if (    $cust_pay->closed !~ /^Y/i
197        && (    ( $cust_pay->payby eq 'CARD'
198                  && $conf->exists('cc-void')
199                  && $curuser->acccess_right('Credit card void')
200                )
201             || ( $cust_pay->payby eq 'CHEK'
202                  && $conf->exists('echeck-void')
203                  && $curuser->acccess_right('Echeck void')
204                ) 
205           )
206      )
207   {
208     $void = qq! (<A HREF="javascript:areyousure('!.
209             qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum.
210             qq!', 'Are you sure you want to void this payment?')"!.
211             qq! TITLE="Void this payment from the database!.
212               ( $cust_pay->payby =~ /^(CARD|CHEK)$/
213                 ? ' (do not send anything to the payment gateway)'
214                 : '' 
215               ). '"'.
216             qq!>void</A>)!;
217   }
218
219   my $delete = '';
220   if ( $cust_pay->closed !~ /^Y/i
221        && $conf->exists('deletepayments')
222        && $curuser->access_right('Delete payment')
223      )
224   {
225     $delete = qq! (<A HREF="javascript:areyousure('!.
226               qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum.
227               qq!', 'Are you sure you want to delete this payment?')"!.
228               qq! TITLE="Delete this payment from the database completely - not recommended"!.
229               qq!>delete</A>)!;
230   }
231
232   my $unapply = '';
233   if (    $cust_pay->closed !~ /^Y/i
234        && $conf->exists('unapplypayments')
235        && scalar(@cust_bill_pay)           
236        && $curuser->access_right('Unapply payment')
237      )
238   {
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"!.
243                qq!>unapply</A>)!;
244   }
245
246   push @history, {
247     'date'    => $cust_pay->_date,
248     'desc'    => $pre. "Payment$post$info$desc".
249                  "$apply$refund$void$delete$unapply",
250     'payment' => $cust_pay->paid,
251     'target'  => $target,
252   };
253 }
254
255 #voided payments
256 foreach my $cust_pay_void ($cust_main->cust_pay_void) {
257
258   my $payby = $cust_pay_void->payby;
259   my $payinfo = $payby eq 'CARD'
260                   ? $cust_pay_void->payinfo_masked
261                   : $cust_pay_void->payinfo;
262
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)" : '';
268
269   my $unvoid = '';
270   if ( $cust_pay_void->closed !~ /^Y/i
271        && $conf->exists('unvoid')
272        && $curuser->access_right('Unvoid')
273      )
274   {
275     $unvoid = qq! (<A HREF="javascript:areyousure('!.
276               qq!${p}misc/unvoid-cust_pay_void.cgi?!. $cust_pay_void->paynum.
277               qq!', 'Are you sure you want to unvoid this payment?')"!.
278               qq! TITLE="Unvoid this payment from the database!.
279                 ( $cust_pay_void->payby =~ /^(CARD|CHEK)$/
280                   ? ' (do not send anything to the payment gateway)'
281                   : '' 
282                 ). '"'.
283               qq!>unvoid</A>)!;
284   }
285
286   push @history, {
287     'date'   => $cust_pay_void->_date,
288     'desc'   => "<DEL>Payment $info</DEL> <I>voided ".
289                 time2str("%D", $cust_pay_void->void_date).
290                 " by ". $cust_pay_void->otaker. '</i>'. $unvoid,
291     'void_payment' => $cust_pay_void->paid,
292   };
293
294 }
295
296 #credits (some false laziness w/payments)
297 foreach my $cust_credit ($cust_main->cust_credit) {
298
299   my @cust_credit_bill = $cust_credit->cust_credit_bill;
300   my @cust_credit_refund = $cust_credit->cust_credit_refund;
301
302   my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
303   if (    scalar(@cust_credit_bill)   == 0
304        && scalar(@cust_credit_refund) == 0 ) {
305     #completely unapplied
306     $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
307     $post = '</FONT></B>';
308     $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!.
309              $cust_credit->crednum.
310              qq!', 392, 336, 'cust_credit_popup' ), CAPTION, 'Post credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK )">apply</A>)!;
311   } elsif (    scalar(@cust_credit_bill)   == 1
312             && scalar(@cust_credit_refund) == 0
313             && $cust_credit->credited == 0      ) {
314     #applied to one invoice, the usual situation
315     $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum;
316   } elsif (    scalar(@cust_credit_bill)   == 0
317             && scalar(@cust_credit_refund) == 1
318             && $cust_credit->credited == 0      ) {
319     #applied to one refund
320     $desc = ' refunded on '.  time2str("%D", $cust_credit_refund[0]->_date);
321   } else {
322     #complicated
323     $desc = '<BR>';
324     foreach my $app ( sort { $a->_date <=> $b->_date }
325                            ( @cust_credit_bill, @cust_credit_refund ) ) {
326       if ( $app->isa('FS::cust_credit_bill') ) {
327         $desc .= '&nbsp;&nbsp;'.
328                  '$'. $app->amount.
329                  ' applied to Invoice #'. $app->invnum.
330                  '<BR>';
331                  #' on '. time2str("%D", $app->_date).
332       } elsif ( $app->isa('FS::cust_credit_refund') ) {
333         $desc .= '&nbsp;&nbsp;'.
334                  '$'. $app->amount.
335                  ' refunded on'. time2str("%D", $app->_date).
336                  '<BR>';
337       } else {
338         die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund";
339       }
340     }
341     if ( $cust_credit->credited > 0 ) {
342       $desc .= '&nbsp;&nbsp;<B><FONT COLOR="#FF0000">$'.
343                $cust_credit->credited. ' unapplied</FONT></B>'.
344                qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!.
345                $cust_credit->crednum.
346                qq!', 392, 336, 'cust_credit_popup' ), CAPTION, 'Post credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK )">apply</A>)!.
347                '<BR>';
348     }
349   }
350 #
351   my $delete = '';
352   if ( $cust_credit->closed !~ /^Y/i
353        && $conf->exists('deletecredits')
354        && $curuser->access_right('Delete credit')
355      )
356   {
357     $delete = qq! (<A HREF="javascript:areyousure('!.
358               qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum.
359               qq!', 'Are you sure you want to delete this credit?')">!.
360               qq!delete</A>)!;
361   }
362   
363   my $unapply = '';
364   if (    $cust_credit->closed !~ /^Y/i
365        && $conf->exists('unapplycredits')
366        && scalar(@cust_credit_bill)
367        && $curuser->access_right('Unapply credit')
368      )
369   {
370     $unapply = qq! (<A HREF="javascript:areyousure('!.
371                qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum.
372                qq!', 'Are you sure you want to unapply this credit?')">!.
373                qq!unapply</A>)!;
374   }
375   
376   push @history, {
377     'date'   => $cust_credit->_date,
378     'desc'   => $pre. "Credit$post by ". $cust_credit->otaker.
379                 ( $cust_credit->reason
380                     ? ' ('. $cust_credit->reason. ')'
381                     : ''
382                 ).
383                 "$desc$apply$delete$unapply",
384     'credit' => $cust_credit->amount,
385   };
386
387 }
388
389 #refunds
390 foreach my $cust_refund ($cust_main->cust_refund) {
391
392   my $payby = $cust_refund->payby;
393   my $payinfo = $payby eq 'CARD'
394                   ? $cust_refund->payinfo_masked
395                   : $cust_refund->payinfo;
396
397   $payby =~ s/^BILL$/Check #/ if $payinfo;
398   $payby =~ s/^CHEK$/Electronic check /;
399   $payby =~ s/^(CARD|COMP)$/$1 /;
400
401   push @history, {
402     'date'   => $cust_refund->_date,
403     'desc'   => "Refund ($payby$payinfo) by ". $cust_refund->otaker,
404     'refund' => $cust_refund->refund,
405   };
406
407 }
408
409 %>
410
411 <%= include("/elements/table.html") %>
412 <TR>
413   <TH>Date</TH>
414   <TH>Description</TH>
415   <TH><FONT SIZE=-1>Charge</FONT></TH>
416   <TH><FONT SIZE=-1>Payment</FONT></TH>
417   <TH><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
418   <TH><FONT SIZE=-1>Refund</FONT></TH>
419   <TH><FONT SIZE=-1>Balance</FONT></TH>
420 </TR>
421
422 <%
423 #display payment history
424
425 my %target;
426 my $balance = 0;
427 foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
428
429   my $charge  = exists($item->{'charge'})
430                   ? sprintf('$%.2f', $item->{'charge'})
431                   : '';
432   my $payment = exists($item->{'payment'})
433                   ? sprintf('-&nbsp;$%.2f', $item->{'payment'})
434                   : '';
435   $payment ||= sprintf('<DEL>-&nbsp;$%.2f</DEL>', $item->{'void_payment'})
436     if exists($item->{'void_payment'});
437   my $credit  = exists($item->{'credit'})
438                   ? sprintf('-&nbsp;$%.2f', $item->{'credit'})
439                   : '';
440   my $refund  = exists($item->{'refund'})
441                   ? sprintf('$%.2f', $item->{'refund'})
442                   : '';
443
444   my $target = exists($item->{'target'}) ? $item->{'target'} : '';
445
446   $balance += $item->{'charge'}  if exists $item->{'charge'};
447   $balance -= $item->{'payment'} if exists $item->{'payment'};
448   $balance -= $item->{'credit'}  if exists $item->{'credit'};
449   $balance += $item->{'refund'}  if exists $item->{'refund'};
450   $balance = sprintf("%.2f", $balance);
451   $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
452   ( my $showbalance = '$'. $balance ) =~ s/^\$\-/-&nbsp;\$/;
453
454 %>
455
456   <TR>
457     <TD>
458       <% unless ( !$target || $target{$target}++ ) { %>
459         <A NAME="<%= $target %>">
460       <% } %>
461       <%= time2str("%D",$item->{'date'}) %>
462       <% if ( $target && $target{$target} == 1 ) { %>
463         </A>
464       <% } %>
465       </FONT>
466     </TD>
467     <TD><%= $item->{'desc'} %></TD>
468     <TD ALIGN="right"><%= $charge  %></TD>
469     <TD ALIGN="right"><%= $payment %></TD>
470     <TD ALIGN="right"><%= $credit  %></TD>
471     <TD ALIGN="right"><%= $refund  %></TD>
472     <TD ALIGN="right"><%= $showbalance %></TD>
473   </TR>
474
475 <% } %>
476
477 </TABLE>
478