add reporting on (and resolution of) stuck pending transactions, RT#4837 (RT#3572)
[freeside.git] / httemplate / search / elements / cust_pay_or_refund.html
1 <%doc>
2
3 Examples:
4
5   include( 'elements/cust_pay_or_refund.html',
6                'thing'         => 'pay',
7                'amount_field'  => 'paid',
8                'name_singular' => 'payment',
9                'name_verb'     => 'paid',
10          )
11
12   include( 'elements/cust_pay_or_refund.html',
13                'thing'         => 'refund',
14                'amount_field'  => 'refund',
15                'name_singular' => 'refund',
16                'name_verb'     => 'refunded',
17          )
18
19   include( 'elements/cust_pay_or_refund.html',
20                'thing'         => 'pay_pending',
21                'amount_field'  => 'paid',
22                'name_singular' => 'pending payment',
23                'name_verb'     => 'pending',
24                'disable_link'  => 1,
25                'disable_by'    => 1,
26                'html_init'     => '',
27                'addl_header'   => [],
28                'addl_fields'   => [],
29           )
30
31 </%doc>
32 <% include( 'search.html',
33                 'title'         => $title,
34                 'name_singular' => $name_singular,
35                 'query'         => $sql_query,
36                 'count_query'   => $count_query,
37                 'count_addl'    => [ '$%.2f total '.$opt{name_verb}, ],
38                 'header'        => [ "\u$name_singular",
39                                      'Amount',
40                                      'Date',
41                                      @header,
42                                      FS::UI::Web::cust_header(),
43                                    ],
44                 'fields'      => [
45                   'payby_payinfo_pretty',
46                   sub { sprintf('$%.2f', shift->$amount_field() ) },
47                   sub { time2str('%b %d %Y', shift->_date ) },
48                   @fields,
49                   \&FS::UI::Web::cust_fields,
50                 ],
51                 #'align' => 'lrrrll',
52                 'align' => 'rrr'.
53                            join('', map 'c', @fields ).
54                            FS::UI::Web::cust_aligns(),
55                 'links' => [
56                   $link,
57                   $link,
58                   $link,
59                   ( map '', @fields ),
60                   ( map { $_ ne 'Cust. Status' ? $cust_link : '' }
61                         FS::UI::Web::cust_header()
62                   ),
63                 ],
64                 'color' => [ 
65                              '',
66                              '',
67                              '',
68                              ( map '', @fields ),
69                              FS::UI::Web::cust_colors(),
70                            ],
71                 'style' => [ 
72                              '',
73                              '',
74                              '',
75                              ( map '', @fields ),
76                              FS::UI::Web::cust_styles(),
77                            ],
78           )
79 %>
80 <%init>
81
82 my %opt = @_;
83
84 my $curuser = $FS::CurrentUser::CurrentUser;
85
86 die "access denied"
87   unless $curuser->access_right('Financial reports');
88
89 my $thing = $opt{'thing'};
90 my $amount_field = $opt{'amount_field'};
91 my $name_singular = $opt{'name_singular'};
92
93 my $title = "\u$name_singular Search Results";
94
95 my @header = ();
96 my @fields = ();
97 unless ( $opt{'disable_by'} ) {
98   push @header, 'By';
99   push @fields, sub {
100                   sub { my $o = shift->otaker;
101                         $o = 'auto billing'          if $o eq 'fs_daily';
102                         $o = 'customer self-service' if $o eq 'fs_selfservice';
103                         $o;
104                       },
105   };
106 }
107
108 push @header, @{ $opt{'addl_header'} }
109   if $opt{'addl_header'};
110 push @fields, @{ $opt{'addl_fields'} }
111   if $opt{'addl_fields'};
112
113 my( $count_query, $sql_query );
114 if ( $cgi->param('magic') ) {
115
116   my @search = ();
117   my $orderby;
118   if ( $cgi->param('magic') eq '_date' ) {
119
120
121     if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
122       push @search, "agentnum = $1"; # $search{'agentnum'} = $1;
123       my $agent = qsearchs('agent', { 'agentnum' => $1 } );
124       die "unknown agentnum $1" unless $agent;
125       $title = $agent->agent. " $title";
126     }
127
128     if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
129       push @search, "custnum = $1";
130     }
131
132     if ( $cgi->param('payby') ) {
133       $cgi->param('payby') =~
134         /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/
135           or die "illegal payby ". $cgi->param('payby');
136       push @search, "cust_$thing.payby = '$1'";
137       if ( $3 ) {
138
139         my $cardtype = $3;
140
141         my $search;
142         if ( $cardtype eq 'VisaMC' ) {
143           #avoid posix regexes for portability
144           $search =
145             " ( (     substring(cust_$thing.payinfo from 1 for 1) = '4'     ".
146             "     AND substring(cust_$thing.payinfo from 1 for 4) != '4936' ".
147             "     AND substring(cust_$thing.payinfo from 1 for 6)           ".
148             "         NOT SIMILAR TO '49030[2-9]'                        ".
149             "     AND substring(cust_$thing.payinfo from 1 for 6)           ".
150             "         NOT SIMILAR TO '49033[5-9]'                        ".
151             "     AND substring(cust_$thing.payinfo from 1 for 6)           ".
152             "         NOT SIMILAR TO '49110[1-2]'                        ".
153             "     AND substring(cust_$thing.payinfo from 1 for 6)           ".
154             "         NOT SIMILAR TO '49117[4-9]'                        ".
155             "     AND substring(cust_$thing.payinfo from 1 for 6)           ".
156             "         NOT SIMILAR TO '49118[1-2]'                        ".
157             "   )".
158             "   OR substring(cust_$thing.payinfo from 1 for 2) = '51' ".
159             "   OR substring(cust_$thing.payinfo from 1 for 2) = '52' ".
160             "   OR substring(cust_$thing.payinfo from 1 for 2) = '53' ".
161             "   OR substring(cust_$thing.payinfo from 1 for 2) = '54' ".
162             "   OR substring(cust_$thing.payinfo from 1 for 2) = '54' ".
163             "   OR substring(cust_$thing.payinfo from 1 for 2) = '55' ".
164             "   OR substring(cust_$thing.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US
165             " ) ";
166         } elsif ( $cardtype eq 'Amex' ) {
167           $search =
168             " (    substring(cust_$thing.payinfo from 1 for 2 ) = '34' ".
169             "   OR substring(cust_$thing.payinfo from 1 for 2 ) = '37' ".
170             " ) ";
171         } elsif ( $cardtype eq 'Discover' ) {
172           $search =
173             " (    substring(cust_$thing.payinfo from 1 for 4 ) = '6011'  ".
174             "   OR substring(cust_$thing.payinfo from 1 for 2 ) = '65'    ".
175             "   OR substring(cust_$thing.payinfo from 1 for 3 ) = '622'   ". #China Union Pay processed as Discover outside CN
176             " ) ";
177         } elsif ( $cardtype eq 'Maestro' ) { 
178           $search =
179             " (    substring(cust_$thing.payinfo from 1 for 2 ) = '63'     ".
180             "   OR substring(cust_$thing.payinfo from 1 for 2 ) = '67'     ".
181             "   OR substring(cust_$thing.payinfo from 1 for 6 ) = '564182' ".
182             "   OR substring(cust_$thing.payinfo from 1 for 4 ) = '4936'   ".
183             "   OR substring(cust_$thing.payinfo from 1 for 6 )            ".
184             "      SIMILAR TO '49030[2-9]'                             ".
185             "   OR substring(cust_$thing.payinfo from 1 for 6 )            ".
186             "      SIMILAR TO '49033[5-9]'                             ".
187             "   OR substring(cust_$thing.payinfo from 1 for 6 )            ".
188             "      SIMILAR TO '49110[1-2]'                             ".
189             "   OR substring(cust_$thing.payinfo from 1 for 6 )            ".
190             "      SIMILAR TO '49117[4-9]'                             ".
191             "   OR substring(cust_$thing.payinfo from 1 for 6 )            ".
192             "      SIMILAR TO '49118[1-2]'                             ".
193             " ) ";
194         } else {
195           die "unknown card type $cardtype";
196         }
197
198         my $masksearch = $search;
199         $masksearch =~ s/cust_$thing\.payinfo/cust_$thing.paymask/gi;
200
201         push @search,
202           "( $search OR ( cust_$thing.paymask IS NOT NULL AND $masksearch ) )";
203
204       }
205     }
206
207     if ( $cgi->param('payinfo') ) {
208       $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/
209         or die "illegal payinfo ". $cgi->param('payinfo');
210       push @search, "cust_$thing.payinfo = '$1'";
211     }
212
213     #for cust_pay_pending...  statusNOT=done
214     if ( $cgi->param('statusNOT') =~ /^(\w+)$/ ) {
215       push @search, "status != '$1'";
216     }
217
218     my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
219     push @search, "_date >= $beginning ",
220                   "_date <= $ending";
221
222     push @search, FS::UI::Web::parse_lt_gt($cgi, $amount_field );
223
224     $orderby = '_date';
225
226   } elsif ( $cgi->param('magic') eq 'paybatch' ) {
227
228     $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/
229       or die "illegal paybatch: ". $cgi->param('paybatch');
230
231     push @search, "paybatch = '$1'";
232
233     $orderby = "LOWER(company || ' ' || last || ' ' || first )";
234
235   } else {
236     die "unknown search magic: ". $cgi->param('magic');
237   }
238
239   #here is the agent virtualization
240   push @search, $curuser->agentnums_sql;
241
242   my $search = ' WHERE '. join(' AND ', @search);
243
244   $count_query = "SELECT COUNT(*), SUM($amount_field) ".
245                  "FROM cust_$thing LEFT JOIN cust_main USING ( custnum )".
246                  $search;
247
248   $sql_query = {
249     'table'     => "cust_$thing",
250     'select'    => join(', ',
251                      "cust_$thing.*",
252                      'cust_main.custnum as cust_main_custnum',
253                      FS::UI::Web::cust_sql_fields(),
254                    ),
255     'hashref'   => {},
256     'extra_sql' => "$search ORDER BY $orderby",
257     'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
258   };
259
260 } else {
261
262   #hmm... is this still used?
263
264   $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo";
265   my $payinfo = $1;
266
267   $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby";
268   my $payby = $1;
269
270   $count_query = "SELECT COUNT(*), SUM($amount_field) FROM cust_$thing".
271                  "  WHERE payinfo = '$payinfo' AND payby = '$payby'".
272                  "  AND ". $curuser->agentnums_sql;
273
274   $sql_query = {
275     'table'     => "cust_$thing",
276     'hashref'   => { 'payinfo' => $payinfo,
277                      'payby'   => $payby    },
278     'extra_sql' => $curuser->agentnums_sql.
279                    " ORDER BY _date",
280   };
281
282 }
283
284 my $link = '';
285 if (    ( $curuser->access_right('View invoices') #XXX for now
286           || $curuser->access_right('View customer payments')
287         )
288      && ! $opt{'disable_link'}
289    )
290 {
291   $link = [ "${p}view/cust_$thing.html?${thing}num=", $thing.'num' ]
292 }
293
294 my $cust_link = sub {
295   my $cust_thing = shift;
296   $cust_thing->cust_main_custnum
297     ? [ "${p}view/cust_main.cgi?", 'custnum' ] 
298     : '';
299 };
300
301 </%init>