default to a session cookie instead of setting an explicit timeout, weird timezone...
[freeside.git] / httemplate / view / cust_main / payment_history.html
1 <TABLE>
2   <TR>
3     <TD ALIGN="left">
4
5 %# batched payment links
6
7 % if ( ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') )
8 %      && $curuser->access_right('View customer batched payments')
9 %    )
10 % { 
11     <% mt('View batched payments:') |h %> 
12 %   foreach my $status (qw( Queued In-transit Complete All )) {
13       <A HREF="<% $p %>search/cust_pay_batch.cgi?status=<% $status{$status} %>;custnum=<% $custnum %>"><% mt($status) |h %></A> 
14       <% $status ne 'All' ? '|' : '' %>
15 %   }
16     <BR>
17 % } 
18
19     </TD>
20   </TR>
21   <TR>
22     <TD COLSPAN=2>
23
24 %# and now the table
25
26 <& /elements/table-grid.html &>
27 % my $bgcolor1 = '#eeeeee';
28 %   my $bgcolor2 = '#ffffff';
29 %   my $bgcolor = '';
30
31 <THEAD>
32 <TR>
33   <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Date') |h %></TH>
34   <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Description') |h %></TH>
35   <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Invoice') |h %></FONT></TH>
36   <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Payment') |h %></FONT></TH>
37   <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('In-house Credit') |h %></FONT></TH>
38   <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Refund') |h %></FONT></TH>
39   <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% mt('Balance') |h %></FONT></TH>
40 </TR>
41 </THEAD>
42
43 %#display payment history
44
45 %my %target = ();
46 %
47 %my $hidden = 0;
48 %my $seen = 0;
49 %my $old_history = 0;
50 %my $lastdate = 0;
51 %
52 %foreach my $item ( @history ) {
53 %
54 %  $lastdate = $item->{'date'};
55 %
56 %  my $display = '';
57 %  if ( $item->{'hide'} ) {
58 %    $display = ' STYLE="display:none" ';
59 %  }
60 %
61 %  if ( $bgcolor eq $bgcolor1 ) {
62 %    $bgcolor = $bgcolor2;
63 %  } else {
64 %    $bgcolor = $bgcolor1;
65 %  }
66 %
67 %  my $charge  = exists($item->{'charge'})
68 %                  ? sprintf("$money_char\%.2f", $item->{'charge'})
69 %                  : exists($item->{'charge_nobal'})
70 %                    ? sprintf("$money_char\%.2f", $item->{'charge_nobal'})
71 %                    : exists($item->{'void_charge'})
72 %                      ? sprintf("<DEL>$money_char\%.2f</DEL>", $item->{'void_charge'})
73 %                      : '';
74 %
75 %  my $payment = exists($item->{'payment'})
76 %                  ? sprintf("-&nbsp;$money_char\%.2f", $item->{'payment'})
77 %                  : '';
78 %
79 %  $payment ||= sprintf( "<DEL>-&nbsp;$money_char\%.2f</DEL>",
80 %                        $item->{'void_payment'}
81 %                      )
82 %    if exists($item->{'void_payment'});
83 %
84 %  my $credit  = exists($item->{'credit'})
85 %                  ? sprintf("-&nbsp;$money_char\%.2f", $item->{'credit'})
86 %                  : '';
87 %
88 %  $credit ||= sprintf( "<DEL>-&nbsp;$money_char\%.2f</DEL>",
89 %                       $item->{'void_credit'}
90 %                     )
91 %    if exists($item->{'void_credit'});
92 %
93 %  my $refund  = exists($item->{'refund'})
94 %                  ? sprintf("$money_char\%.2f", $item->{'refund'})
95 %                  : '';
96 %
97 %  my $target = exists($item->{'target'}) ? $item->{'target'} : '';
98 %
99 %  my $showbalance = $money_char . $item->{'balance'};
100 %  $showbalance =~ s/^\$\-/-&nbsp;\$/;
101
102   <TR <% $display ? $display.' ID="old_history'.$old_history++.'"'  : ''%>>
103     <TD VALIGN="top" CLASS="grid" BGCOLOR="<% $bgcolor %>">
104 % unless ( !$target || $target{$target}++ ) { 
105
106         <A NAME="<% $target %>">
107 % } 
108
109       <% time2str($date_format, $item->{'date'}) %>
110 % if ( $target && $target{$target} == 1 ) { 
111
112         </A>
113 % } 
114
115       </FONT>
116     </TD>
117     <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
118       <% $item->{'desc'} %>
119     </TD>
120     <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
121       <% $charge  %>
122     </TD>
123     <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
124       <% $payment %>
125     </TD>
126     <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
127       <% $credit  %>
128     </TD>
129     <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
130       <% $refund  %>
131     </TD>
132     <TD VALIGN="top" ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
133       <% $showbalance %>
134     </TD>
135   </TR>
136
137 % if ( $item->{'balance_forward'} ) {
138 <& .balance_forward_row, $item->{'balance'}, $item->{'date'} &>
139 % }
140 %} # foreach $item
141
142 % if ( $old_history ) {
143 <& .hide_history_row, $old_history++ &>
144 % }
145
146 </TABLE>
147     </TD>
148   </TR>
149 </TABLE>
150
151 <SCRIPT SRC="<% $fsurl %>elements/page_pref.js"></SCRIPT>
152 <SCRIPT TYPE="text/javascript">
153
154 function show_history(show) { // but don't update pref
155   var balance_forward_row = document.getElementById('balance_forward_row');
156
157   balance_forward_row.style.display = show ? 'none' : '';
158   for ( var i = 0; i < <% $old_history %>; i++ ) {
159     var oldRow = document.getElementById('old_history'+i);
160     oldRow.style.display = show ? '' : 'none';
161   }
162 }
163
164 function update_show_history (show) {
165
166   show = show ? 1 : 0;
167   show_history(show);
168   // update user pref (blind post, don't care about the output here)
169   set_page_pref('expand_old_history', '<% $custnum %>', show);
170 }
171
172 $().ready(function() {
173   show_history(<% get_page_pref('expand_old_history', $custnum) %>);
174 });
175
176 </SCRIPT>
177 <%def .balance_forward_row>
178 %  my( $b, $date ) = @_;
179 %  ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
180
181    <TR ID="balance_forward_row">
182      <TD CLASS="grid" BGCOLOR="#dddddd">
183        <% time2str($date_format, $date) %>
184      </TD>
185
186      <TD CLASS="grid" BGCOLOR="#dddddd">
187        <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I>
188        (<A HREF="javascript:void(0);" onClick="update_show_history(true);"><% mt('show prior history') |h %></A>)
189      </TD>
190
191      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
192      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
193      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
194      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
195      <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
196
197    </TR>
198 </%def>
199 <%def .hide_history_row>
200 %  my $num = shift;
201   <TR ID="old_history<% $num %>" STYLE="display: none">
202     <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
203     <TD CLASS="grid" BGCOLOR="#dddddd">
204       <I>(<A HREF="#" onclick="update_show_history(false)"><% mt('hide prior history') |h %></A>)</I>
205     </TD>
206     <TD CLASS="grid" BGCOLOR="#dddddd" COLSPAN=5></TD>
207   </TR>
208 </%def>
209
210 <%shared>
211 my $conf = new FS::Conf;
212 my $date_format = $conf->config('date_format') || '%m/%d/%Y';
213 my $money_char = $conf->config('money_char') || '$';
214 </%shared>
215 <%init>
216
217 my( $cust_main ) = @_;
218 my $custnum = $cust_main->custnum;
219
220 my $curuser = $FS::CurrentUser::CurrentUser;
221
222 my %status = (
223   'Queued'     => 'O', #Open
224   'In-transit' => 'I',
225   'Complete'   => 'R', #Resolved
226   'All'        => '',
227 );
228
229 #get payment history
230 my @history = ();
231
232 my %opt = (
233
234   #config
235   ( map { $_ => scalar($conf->config($_)) }
236         qw( card_refund-days date_format )
237   ),
238   ( map { $_ => $conf->exists($_) } 
239         qw( deletepayments deleterefunds pkg-balances
240             cust_credit_bill_pkg-manual cust_bill_pay_pkg-manual
241           )
242   ),
243   'money_char             ' => $money_char,
244
245   #rights
246   ( map { $_ => $curuser->access_right($_) }
247       (
248         'View invoices', 'Void invoices', 'Unvoid invoices', 'Resend invoices',
249         'Apply payment', 'Refund credit card payment', 'Refund Echeck payment',
250         'Post refund', 'Post check refund', 'Post cash refund ', 'Refund payment',
251         'Credit card void', 'Echeck void', 'Void payments', 'Unvoid payments',
252         'Unapply payment',
253         'Apply credit', 'Unapply credit', 'Void credit', 'Unvoid credit',
254         'Delete refund',
255         'Billing event reports', 'View customer billing events',
256       )
257   ),
258
259   #customer information
260   'total_owed'              => $cust_main->total_owed,
261   'total_unapplied_refunds' => $cust_main->total_unapplied_refunds,
262   'has_email_address'       => scalar($cust_main->invoicing_list_emailonly),
263 );
264
265 $opt{'date_format'} ||= '%m/%d/%Y';
266
267 #legacy invoices
268 foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) {
269   push @history, {
270     'date'   => $legacy_cust_bill->_date,
271     'order'  => 1,
272     'num'    => $legacy_cust_bill->legacyid,
273     'desc'   => include('payment_history/legacy_invoice.html', $legacy_cust_bill, %opt ),
274     'charge_nobal' => $legacy_cust_bill->charged,
275   };
276 }
277
278 #invoices
279 my $num_cust_bill = 0;
280 foreach my $cust_bill ($cust_main->cust_bill) {
281   push @history, {
282     'date'   => $cust_bill->_date,
283     'order'  => 1,
284     'num'    => $cust_bill->invnum,
285     'desc'   => include('payment_history/invoice.html', $cust_bill, %opt ),
286     'charge' => $cust_bill->charged,
287   };
288   $num_cust_bill++;
289 }
290
291 #voided invoices
292 foreach my $cust_bill_void ($cust_main->cust_bill_void) {
293   push @history, {
294     'date'        => $cust_bill_void->_date,
295     'order'       => 0,
296     'num'         => $cust_bill_void->invnum,
297     'desc'        => include('payment_history/voided_invoice.html', $cust_bill_void, %opt ),
298     'void_charge' => $cust_bill_void->charged,
299   };
300 }
301
302 #statements
303 foreach my $cust_statement ($cust_main->cust_statement) {
304   push @history, {
305     'date'   => $cust_statement->_date,
306     'order'  => 2,
307     'num'    => $cust_statement->statementnum,
308     'desc'   => include('payment_history/statement.html', $cust_statement, %opt ),
309     #'charge' => $cust_bill->charged,
310   };
311 }
312
313 #payments (some false laziness w/credits)
314 foreach my $cust_pay ($cust_main->cust_pay) {
315   push @history, {
316     'date'    => $cust_pay->_date,
317     'order'   => 6,
318     'num'     => $cust_pay->paynum,
319     'desc'    => include('payment_history/payment.html', $cust_pay, %opt ),
320     'payment' => $cust_pay->paid,
321     #'target'  => $target, #XXX
322   };
323 }
324
325 #pending payments 
326 foreach my $cust_pay_pending ($cust_main->cust_pay_pending) {
327   push @history, {
328     'date'    => $cust_pay_pending->_date,
329     'order'   => 4,
330     'num'     => $cust_pay_pending->paypendingnum,
331     'desc'    => include('payment_history/pending_payment.html', $cust_pay_pending, %opt ),
332     'void_payment' => $cust_pay_pending->paid, 
333   };
334 }
335
336
337 #voided payments
338 foreach my $cust_pay_void ($cust_main->cust_pay_void) {
339   push @history, {
340     'date'   => $cust_pay_void->_date,
341     'order'  => 3,
342     'num'    => $cust_pay_void->paynum,
343     'desc'   => include('payment_history/voided_payment.html', $cust_pay_void, %opt ),
344     'void_payment' => $cust_pay_void->paid,
345   };
346
347 }
348
349 #voided credits 
350 foreach my $cust_credit_void ($cust_main->cust_credit_void) {
351   push @history, {
352     'date'        => $cust_credit_void->_date,
353     'order'       => 7,
354     'num'         => $cust_credit_void->paynum,
355     'desc'        => include('payment_history/voided_credit.html', $cust_credit_void, %opt ),
356     'void_credit' => $cust_credit_void->amount,
357   };
358 }
359
360 #declined payments
361 foreach my $cust_pay_pending ($cust_main->cust_pay_pending_attempt) {
362   push @history, {
363     'date'    => $cust_pay_pending->_date,
364     'order'   => 5,
365     'num'     => $cust_pay_pending->paypendingnum,
366     'desc'    => include('payment_history/attempted_payment.html', $cust_pay_pending, %opt ),
367     'void_payment' => $cust_pay_pending->paid, #??
368     #'target'  => $target, #XXX
369   };
370 }
371 #declined batch payments
372 foreach my $cust_pay_batch (
373   $cust_main->cust_pay_batch(hashref => {status => 'Declined'})
374 ) {
375   my $pay_batch = $cust_pay_batch->pay_batch;
376   push @history, {
377     'date'    => $pay_batch->upload,
378     'order'   => 5,
379     'num'     => $cust_pay_batch->paybatchnum,
380     'desc'    => include('payment_history/attempted_batch_payment.html', $cust_pay_batch, %opt),
381     'void_payment' => $cust_pay_batch->amount,
382   };
383 }
384
385 #credits (some false laziness w/payments)
386 foreach my $cust_credit ($cust_main->cust_credit) {
387   push @history, {
388     'date'   => $cust_credit->_date,
389     'order'  => 8,
390     'num'    => $cust_credit->crednum,
391     'desc'   => include('payment_history/credit.html', $cust_credit, %opt ),
392     'credit' => $cust_credit->amount,
393   };
394
395 }
396
397 #refunds
398 foreach my $cust_refund ($cust_main->cust_refund) {
399   push @history, {
400     'date'   => $cust_refund->_date,
401     'order'  => 9,
402     'num'    => $cust_refund->refundnum,
403     'desc'   => include('payment_history/refund.html', $cust_refund, %opt),
404     'refund' => $cust_refund->refund,
405   };
406
407 }
408
409 # sort in forward order first, and calculate running balances
410 my $years =  $conf->config('payment_history-years') || 2;
411 my $older_than = time - $years * 31556926; #60*60*24*365.2422
412 my $balance = 0;
413
414 @history = sort {    $a->{date}  <=> $b->{date}
415                   or $a->{order} <=> $b->{order}
416                   or $a->{num}   <=> $b->{num}
417                 }
418              @history;
419
420 my $i = 0;
421 my $balance_forward;
422 foreach my $item (@history) {
423   $balance += $item->{'charge'}  if exists $item->{'charge'};
424   $balance -= $item->{'payment'} if exists $item->{'payment'};
425   $balance -= $item->{'credit'}  if exists $item->{'credit'};
426   $balance += $item->{'refund'}  if exists $item->{'refund'};
427   $balance = sprintf("%.2f", $balance);
428   $balance =~ s/^\-0\.00$/0.00/;
429   $item->{'balance'} = $balance;
430
431   if ( $item->{'date'} < $older_than ) {
432     $item->{'hide'} = 1;
433   } elsif ( $history[$i-1]->{'hide'} ) {
434     # this is the end of the hidden section
435     $history[$i-1]->{'balance_forward'} = 1;
436   }
437   $i++;
438 }
439 if ( @history and $history[-1]->{'hide'} ) {
440   # then everything is hidden
441   $history[-1]->{'balance_forward'} = 1;
442 }
443
444 # then sort in user-pref order
445 if ( $curuser->option('history_order') eq 'newest' ) {
446   @history = sort {    $b->{date}  <=> $a->{date}
447                     or $b->{order} <=> $a->{order} #or still forward here?
448                     or $b->{num}   <=> $a->{num}
449                   }
450                @history;
451 } # else it's already oldest-first, and there are no other options yet
452
453 sub translate_payby {
454     my ($payby,$payinfo) = (shift,shift);
455     my %payby = (
456         FS::payby->payby2shortname,
457         BILL    => $payinfo ? emt('Check #') : '',
458         CHEK    => emt('Electronic check '),
459         PREP    => emt('Prepaid card '),
460         CARD    => emt('Credit card #'),
461         COMP    => emt('Complimentary by '),
462         #CASH    => emt('Cash'),
463         #WEST    => emt('Western Union'),
464         #MCRD    => emt('Manual credit card'),
465     );
466     $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby; 
467     $payby;
468 };
469
470 sub translate_payby_refund {
471     my ($payby,$payinfo) = (shift,shift);
472     my %payby = (
473         FS::payby->payby2shortname,
474         BILL    => $payinfo ? emt('Check #') : emt('Check'),
475         CHEK    => emt('Electronic check '),
476         CARD    => emt('Credit card #'),
477         COMP    => emt('Complimentary by '),
478     );
479     $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby; 
480     $payby;
481 };
482
483 sub translate_payinfo {
484     my $object = shift;
485     my $payby = $object->payby;
486     my $payinfo = $object->payinfo;
487
488     if ( $payby eq 'CARD' ) {
489         $payinfo = $object->paymask;
490     } elsif ( $payby eq 'CHEK' ) {
491         #false laziness w/payinfo_Mixin::payby_payinfo_pretty, should use that
492         my( $account, $aba ) = split('@', $object->paymask );
493         if ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #blame canada
494           my($branch, $routing) = ($1, $2);
495           $payinfo = emt("Routing [_1], Branch [_2], Acct [_3]",
496                          $routing, $branch, $account);
497         } else {
498           $payinfo = emt("Routing [_1], Acct [_2]", $aba, $account);
499         }
500     }
501
502     ($payby,$payinfo);
503 }
504
505 sub areyousure_link {
506     my ($url,$msg,$title,$label) = (shift,shift,shift,shift);
507     ' (<A HREF="javascript:areyousure(\''.$url.'\',\''.$msg.'\')" TITLE="'.$title.'" STYLE="white-space: nowrap;">'.$label.'</A>)';
508 }
509
510 </%init>