multiple payment options (remove cust_main.payby reporting options), RT#23741
[freeside.git] / FS / FS / cust_bill / Search.pm
1 package FS::cust_bill::Search;
2
3 use strict;
4 use FS::CurrentUser;
5 use FS::UI::Web;
6 use FS::Record qw( qsearchs dbh );
7 use FS::cust_main;
8 use FS::access_user;
9                                                                                 
10 =item search HASHREF                                                            
11                                                                                 
12 (Class method)                                                                  
13                                                                                 
14 Returns a qsearch hash expression to search for parameters specified in HASHREF.
15 In addition to all parameters accepted by search_sql_where, the following
16 additional parameters valid:
17
18 =over 4                                                                         
19
20 =item newest_percust
21
22 =back
23
24 =cut
25
26 sub search {
27   my( $class, $params ) = @_;
28
29   my( $count_query, $count_addl ) = ( '', '' );
30
31   #some false laziness w/cust_bill::re_X
32
33   $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'"
34     if $params->{'newest_percust'};
35
36   my $extra_sql = FS::cust_bill->search_sql_where( $params );
37   $extra_sql = "WHERE $extra_sql" if $extra_sql;
38
39   my $join_cust_main = FS::UI::Web::join_cust_main('cust_bill');
40
41   unless ( $count_query ) {
42     $count_query = 'SELECT COUNT(*), '. join(', ',
43                      map "SUM($_)",
44                          ( 'charged',
45                            FS::cust_bill->net_sql,
46                            FS::cust_bill->owed_sql,
47                          )
48                    );
49     $count_addl = [ '$%.2f invoiced (gross)',
50                     '$%.2f invoiced (net)',
51                     '$%.2f outstanding balance',
52                   ];
53   }
54   $count_query .=  " FROM cust_bill $join_cust_main $extra_sql";
55
56   #$sql_query =
57   +{
58     'table'     => 'cust_bill',
59     'addl_from' => $join_cust_main,
60     'hashref'   => {},
61     'select'    => join(', ',
62                      'cust_bill.*',
63                      #( map "cust_main.$_", qw(custnum last first company) ),
64                      'cust_main.custnum as cust_main_custnum',
65                      FS::UI::Web::cust_sql_fields(),
66                      #$class->owed_sql. ' AS owed',
67                      #$class->net_sql.  ' AS net',
68                      FS::cust_bill->owed_sql. ' AS owed',
69                      FS::cust_bill->net_sql.  ' AS net',
70                    ),
71     'extra_sql' => $extra_sql,
72     'order_by'  => 'ORDER BY '. ( $params->{'order_by'} || 'cust_bill._date' ),
73
74     'count_query' => $count_query,
75     'count_addl'  => $count_addl,
76   };
77
78 }
79
80 =item search_sql_where HASHREF
81
82 Class method which returns an SQL WHERE fragment to search for parameters
83 specified in HASHREF.  Valid parameters are
84
85 =over 4
86
87 =item _date
88
89 List reference of start date, end date, as UNIX timestamps.
90
91 =item invnum_min
92
93 =item invnum_max
94
95 =item agentnum
96
97 =item cust_status
98
99 =item cust_classnum
100
101 List reference
102
103 =item charged
104
105 List reference of charged limits (exclusive).
106
107 =item owed
108
109 List reference of charged limits (exclusive).
110
111 =item open
112
113 flag, return open invoices only
114
115 =item net
116
117 flag, return net invoices only
118
119 =item days
120
121 =item newest_percust
122
123 =item custnum
124
125 Return only invoices belonging to that customer.
126
127 =item cust_classnum
128
129 Limit to that customer class (single value or arrayref).
130
131 =item refnum
132
133 Limit to customers with that advertising source.
134
135 =back
136
137 Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
138
139 =cut
140
141 sub search_sql_where {
142   my($class, $param) = @_;
143   #if ( $cust_bill::DEBUG ) {
144   #  warn "$me search_sql_where called with params: \n".
145   #       join("\n", map { "  $_: ". $param->{$_} } keys %$param ). "\n";
146   #}
147
148   #some false laziness w/cust_bill::re_X
149
150   my @search = ();
151
152   #agentnum
153   if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
154     push @search, "cust_main.agentnum = $1";
155   }
156
157   #refnum
158   if ( $param->{'refnum'} =~ /^(\d+)$/ ) {
159     push @search, "cust_main.refnum = $1";
160   }
161
162   #custnum
163   if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
164     push @search, "cust_bill.custnum = $1";
165   }
166
167   #cust_status
168   if ( $param->{'cust_status'} =~ /^([a-z]+)$/ ) {
169     push @search, FS::cust_main->cust_status_sql . " = '$1' ";
170   }
171
172   #customer classnum (false laziness w/ cust_main/Search.pm)
173   if ( $param->{'cust_classnum'} ) {
174
175     my @classnum = ref( $param->{'cust_classnum'} )
176                      ? @{ $param->{'cust_classnum'} }
177                      :  ( $param->{'cust_classnum'} );
178
179     @classnum = grep /^(\d*)$/, @classnum;
180
181     if ( @classnum ) {
182       push @search, '( '. join(' OR ', map {
183                                              $_ ? "cust_main.classnum = $_"
184                                                 : "cust_main.classnum IS NULL"
185                                            }
186                                            @classnum
187                               ).
188                     ' )';
189     }
190
191   }
192
193   #_date
194   if ( $param->{_date} ) {
195     my($beginning, $ending) = @{$param->{_date}};
196
197     push @search, "cust_bill._date >= $beginning",
198                   "cust_bill._date <  $ending";
199   }
200
201   #invnum
202   if ( $param->{'invnum_min'} =~ /^\s*(\d+)\s*$/ ) {
203     push @search, "cust_bill.invnum >= $1";
204   }
205   if ( $param->{'invnum_max'} =~ /^\s*(\d+)\s*$/ ) {
206     push @search, "cust_bill.invnum <= $1";
207   }
208
209   #charged
210   if ( $param->{charged} ) {
211     my @charged = ref($param->{charged})
212                     ? @{ $param->{charged} }
213                     : ($param->{charged});
214
215     push @search, map { s/^charged/cust_bill.charged/; $_; }
216                       @charged;
217   }
218
219   my $owed_sql = FS::cust_bill->owed_sql;
220
221   #owed
222   if ( $param->{owed} ) {
223     my @owed = ref($param->{owed})
224                  ? @{ $param->{owed} }
225                  : ($param->{owed});
226     push @search, map { s/^owed/$owed_sql/; $_; }
227                       @owed;
228   }
229
230   #open/net flags
231   push @search, "0 != $owed_sql"
232     if $param->{'open'};
233   push @search, '0 != '. FS::cust_bill->net_sql
234     if $param->{'net'};
235
236   #days
237   push @search, "cust_bill._date < ". (time-86400*$param->{'days'})
238     if $param->{'days'};
239
240   #newest_percust
241   if ( $param->{'newest_percust'} ) {
242
243     #$distinct = 'DISTINCT ON ( cust_bill.custnum )';
244     #$orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC';
245
246     my @newest_where = map { my $x = $_;
247                              $x =~ s/\bcust_bill\./newest_cust_bill./g;
248                              $x;
249                            }
250                            grep ! /^cust_main./, @search;
251     my $newest_where = scalar(@newest_where)
252                          ? ' AND '. join(' AND ', @newest_where)
253                          : '';
254
255
256     push @search, "cust_bill._date = (
257       SELECT(MAX(newest_cust_bill._date)) FROM cust_bill AS newest_cust_bill
258         WHERE newest_cust_bill.custnum = cust_bill.custnum
259           $newest_where
260     )";
261
262   }
263
264   #promised_date - also has an option to accept nulls
265   if ( $param->{promised_date} ) {
266     my($beginning, $ending, $null) = @{$param->{promised_date}};
267
268     push @search, "(( cust_bill.promised_date >= $beginning AND ".
269                     "cust_bill.promised_date <  $ending )" .
270                     ($null ? ' OR cust_bill.promised_date IS NULL ) ' : ')');
271   }
272
273   #agent virtualization
274   my $curuser = $FS::CurrentUser::CurrentUser;
275   if ( $curuser->username eq 'fs_queue'
276        && $param->{'CurrentUser'} =~ /^(\w+)$/ ) {
277     my $username = $1;
278     my $newuser = qsearchs('access_user', {
279       'username' => $username,
280       'disabled' => '',
281     } );
282     if ( $newuser ) {
283       $curuser = $newuser;
284     } else {
285       #warn "$me WARNING: (fs_queue) can't find CurrentUser $username\n";
286       warn "[FS::cust_bill::Search] WARNING: (fs_queue) can't find CurrentUser $username\n";
287     }
288   }
289   push @search, $curuser->agentnums_sql;
290
291   join(' AND ', @search );
292
293 }
294
295 1;
296