Merge branch 'master' of git.freeside.biz:/home/git/freeside
[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 </TABLE>
143     </TD>
144   </TR>
145 </TABLE>
146
147 <SCRIPT TYPE="text/javascript">
148
149 function show_history () {
150   //alert('showing history!');
151
152   var balance_forward_row = document.getElementById('balance_forward_row');
153
154   balance_forward_row.style.display = 'none';
155   for ( var i = 0; i < <% $old_history %>; i++ ) {
156     var oldRow = document.getElementById('old_history'+i);
157     oldRow.style.display = '';
158   }
159
160 }
161
162 </SCRIPT>
163 <%def .balance_forward_row>
164 %  my( $b, $date ) = @_;
165 %  ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
166
167    <TR ID="balance_forward_row">
168      <TD CLASS="grid" BGCOLOR="#dddddd">
169        <% time2str($date_format, $date) %>
170      </TD>
171
172      <TD CLASS="grid" BGCOLOR="#dddddd">
173        <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I>
174        (<A HREF="javascript:void(0);" onClick="show_history();"><% mt('show prior history') |h %></A>)
175      </TD>
176
177      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
178      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
179      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
180      <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
181      <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
182
183    </TR>
184 </%def>
185 <%shared>
186 my $conf = new FS::Conf;
187 my $date_format = $conf->config('date_format') || '%m/%d/%Y';
188 my $money_char = $conf->config('money_char') || '$';
189 </%shared>
190 <%init>
191
192 my( $cust_main ) = @_;
193 my $custnum = $cust_main->custnum;
194
195 my $curuser = $FS::CurrentUser::CurrentUser;
196
197 my %status = (
198   'Queued'     => 'O', #Open
199   'In-transit' => 'I',
200   'Complete'   => 'R', #Resolved
201   'All'        => '',
202 );
203
204 #get payment history
205 my @history = ();
206
207 my %opt = (
208
209   #config
210   ( map { $_ => scalar($conf->config($_)) }
211         qw( card_refund-days date_format )
212   ),
213   ( map { $_ => $conf->exists($_) } 
214         qw( deletepayments deleterefunds pkg-balances
215             cust_credit_bill_pkg-manual cust_bill_pay_pkg-manual
216           )
217   ),
218   'money_char             ' => $money_char,
219
220   #rights
221   ( map { $_ => $curuser->access_right($_) }
222       (
223         'View invoices', 'Void invoices', 'Unvoid invoices', 'Resend invoices',
224         'Apply payment', 'Refund credit card payment', 'Refund Echeck payment',
225         'Post refund', 'Post check refund', 'Post cash refund ', 'Refund payment',
226         'Credit card void', 'Echeck void', 'Void payments', 'Unvoid payments',
227         'Unapply payment',
228         'Apply credit', 'Unapply credit', 'Void credit', 'Unvoid credit',
229         'Delete refund',
230         'Billing event reports', 'View customer billing events',
231       )
232   ),
233
234   #customer information
235   'total_owed'              => $cust_main->total_owed,
236   'total_unapplied_refunds' => $cust_main->total_unapplied_refunds,
237   'has_email_address'       => scalar($cust_main->invoicing_list_emailonly),
238 );
239
240 $opt{'date_format'} ||= '%m/%d/%Y';
241
242 #legacy invoices
243 foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) {
244   push @history, {
245     'date'   => $legacy_cust_bill->_date,
246     'order'  => 1,
247     'num'    => $legacy_cust_bill->legacyid,
248     'desc'   => include('payment_history/legacy_invoice.html', $legacy_cust_bill, %opt ),
249     'charge_nobal' => $legacy_cust_bill->charged,
250   };
251 }
252
253 #invoices
254 my $num_cust_bill = 0;
255 foreach my $cust_bill ($cust_main->cust_bill) {
256   push @history, {
257     'date'   => $cust_bill->_date,
258     'order'  => 1,
259     'num'    => $cust_bill->invnum,
260     'desc'   => include('payment_history/invoice.html', $cust_bill, %opt ),
261     'charge' => $cust_bill->charged,
262   };
263   $num_cust_bill++;
264 }
265
266 #voided invoices
267 foreach my $cust_bill_void ($cust_main->cust_bill_void) {
268   push @history, {
269     'date'        => $cust_bill_void->_date,
270     'order'       => 0,
271     'num'         => $cust_bill_void->invnum,
272     'desc'        => include('payment_history/voided_invoice.html', $cust_bill_void, %opt ),
273     'void_charge' => $cust_bill_void->charged,
274   };
275 }
276
277 #statements
278 foreach my $cust_statement ($cust_main->cust_statement) {
279   push @history, {
280     'date'   => $cust_statement->_date,
281     'order'  => 2,
282     'num'    => $cust_statement->statementnum,
283     'desc'   => include('payment_history/statement.html', $cust_statement, %opt ),
284     #'charge' => $cust_bill->charged,
285   };
286 }
287
288 #payments (some false laziness w/credits)
289 foreach my $cust_pay ($cust_main->cust_pay) {
290   push @history, {
291     'date'    => $cust_pay->_date,
292     'order'   => 6,
293     'num'     => $cust_pay->paynum,
294     'desc'    => include('payment_history/payment.html', $cust_pay, %opt ),
295     'payment' => $cust_pay->paid,
296     #'target'  => $target, #XXX
297   };
298 }
299
300 #pending payments 
301 foreach my $cust_pay_pending ($cust_main->cust_pay_pending) {
302   push @history, {
303     'date'    => $cust_pay_pending->_date,
304     'order'   => 4,
305     'num'     => $cust_pay_pending->paypendingnum,
306     'desc'    => include('payment_history/pending_payment.html', $cust_pay_pending, %opt ),
307     'void_payment' => $cust_pay_pending->paid, 
308   };
309 }
310
311
312 #voided payments
313 foreach my $cust_pay_void ($cust_main->cust_pay_void) {
314   push @history, {
315     'date'   => $cust_pay_void->_date,
316     'order'  => 3,
317     'num'    => $cust_pay_void->paynum,
318     'desc'   => include('payment_history/voided_payment.html', $cust_pay_void, %opt ),
319     'void_payment' => $cust_pay_void->paid,
320   };
321
322 }
323
324 #voided credits 
325 foreach my $cust_credit_void ($cust_main->cust_credit_void) {
326   push @history, {
327     'date'        => $cust_credit_void->_date,
328     'order'       => 7,
329     'num'         => $cust_credit_void->paynum,
330     'desc'        => include('payment_history/voided_credit.html', $cust_credit_void, %opt ),
331     'void_credit' => $cust_credit_void->amount,
332   };
333 }
334
335 #declined payments
336 foreach my $cust_pay_pending ($cust_main->cust_pay_pending_attempt) {
337   push @history, {
338     'date'    => $cust_pay_pending->_date,
339     'order'   => 5,
340     'num'     => $cust_pay_pending->paypendingnum,
341     'desc'    => include('payment_history/attempted_payment.html', $cust_pay_pending, %opt ),
342     'void_payment' => $cust_pay_pending->paid, #??
343     #'target'  => $target, #XXX
344   };
345 }
346 #declined batch payments
347 foreach my $cust_pay_batch (
348   $cust_main->cust_pay_batch(hashref => {status => 'Declined'})
349 ) {
350   my $pay_batch = $cust_pay_batch->pay_batch;
351   push @history, {
352     'date'    => $pay_batch->upload,
353     'order'   => 5,
354     'num'     => $cust_pay_batch->paybatchnum,
355     'desc'    => include('payment_history/attempted_batch_payment.html', $cust_pay_batch, %opt),
356     'void_payment' => $cust_pay_batch->amount,
357   };
358 }
359
360 #credits (some false laziness w/payments)
361 foreach my $cust_credit ($cust_main->cust_credit) {
362   push @history, {
363     'date'   => $cust_credit->_date,
364     'order'  => 8,
365     'num'    => $cust_credit->crednum,
366     'desc'   => include('payment_history/credit.html', $cust_credit, %opt ),
367     'credit' => $cust_credit->amount,
368   };
369
370 }
371
372 #refunds
373 foreach my $cust_refund ($cust_main->cust_refund) {
374   push @history, {
375     'date'   => $cust_refund->_date,
376     'order'  => 9,
377     'num'    => $cust_refund->refundnum,
378     'desc'   => include('payment_history/refund.html', $cust_refund, %opt),
379     'refund' => $cust_refund->refund,
380   };
381
382 }
383
384 # sort in forward order first, and calculate running balances
385 my $years =  $conf->config('payment_history-years') || 2;
386 my $older_than = time - $years * 31556926; #60*60*24*365.2422
387 my $balance = 0;
388
389 @history = sort {    $a->{date}  <=> $b->{date}
390                   or $a->{order} <=> $b->{order}
391                   or $a->{num}   <=> $b->{num}
392                 }
393              @history;
394
395 my $i = 0;
396 my $balance_forward;
397 foreach my $item (@history) {
398   $balance += $item->{'charge'}  if exists $item->{'charge'};
399   $balance -= $item->{'payment'} if exists $item->{'payment'};
400   $balance -= $item->{'credit'}  if exists $item->{'credit'};
401   $balance += $item->{'refund'}  if exists $item->{'refund'};
402   $balance = sprintf("%.2f", $balance);
403   $balance =~ s/^\-0\.00$/0.00/;
404   $item->{'balance'} = $balance;
405
406   if ( $item->{'date'} < $older_than ) {
407     $item->{'hide'} = 1;
408   } elsif ( $history[$i-1]->{'hide'} ) {
409     # this is the end of the hidden section
410     $history[$i-1]->{'balance_forward'} = 1;
411   }
412   $i++;
413 }
414 if ( @history and $history[-1]->{'hide'} ) {
415   # then everything is hidden
416   $history[-1]->{'balance_forward'} = 1;
417 }
418
419 # then sort in user-pref order
420 if ( $curuser->option('history_order') eq 'newest' ) {
421   @history = sort {    $b->{date}  <=> $a->{date}
422                     or $b->{order} <=> $a->{order} #or still forward here?
423                     or $b->{num}   <=> $a->{num}
424                   }
425                @history;
426 } # else it's already oldest-first, and there are no other options yet
427
428 sub translate_payby {
429     my ($payby,$payinfo) = (shift,shift);
430     my %payby = (
431         FS::payby->payby2shortname,
432         BILL    => $payinfo ? emt('Check #') : '',
433         CHEK    => emt('Electronic check '),
434         PREP    => emt('Prepaid card '),
435         CARD    => emt('Credit card #'),
436         COMP    => emt('Complimentary by '),
437         #CASH    => emt('Cash'),
438         #WEST    => emt('Western Union'),
439         #MCRD    => emt('Manual credit card'),
440     );
441     $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby; 
442     $payby;
443 };
444
445 sub translate_payby_refund {
446     my ($payby,$payinfo) = (shift,shift);
447     my %payby = (
448         FS::payby->payby2shortname,
449         BILL    => $payinfo ? emt('Check #') : emt('Check'),
450         CHEK    => emt('Electronic check '),
451         CARD    => emt('Credit card #'),
452         COMP    => emt('Complimentary by '),
453     );
454     $payby = (exists $payby{$payby}) ? $payby{$payby} : $payby; 
455     $payby;
456 };
457
458 sub translate_payinfo {
459     my $object = shift;
460     my $payby = $object->payby;
461     my $payinfo = $object->payinfo;
462
463     if ( $payby eq 'CARD' ) {
464         $payinfo = $object->paymask;
465     } elsif ( $payby eq 'CHEK' ) {
466         #false laziness w/payinfo_Mixin::payby_payinfo_pretty, should use that
467         my( $account, $aba ) = split('@', $object->paymask );
468         if ( $aba =~ /^(\d{5})\.(\d{3})$/ ) { #blame canada
469           my($branch, $routing) = ($1, $2);
470           $payinfo = emt("Routing [_1], Branch [_2], Acct [_3]",
471                          $routing, $branch, $account);
472         } else {
473           $payinfo = emt("Routing [_1], Acct [_2]", $aba, $account);
474         }
475     }
476
477     ($payby,$payinfo);
478 }
479
480 sub areyousure_link {
481     my ($url,$msg,$title,$label) = (shift,shift,shift,shift);
482     ' (<A HREF="javascript:areyousure(\''.$url.'\',\''.$msg.'\')" TITLE="'.$title.'">'.$label.'</A>)';
483 }
484
485 </%init>