fix payment lookup when revoking batch payments, #18548 and #21117
[freeside.git] / FS / FS / cust_bill_event.pm
1 package FS::cust_bill_event;
2
3 use strict;
4 use vars qw( @ISA $DEBUG );
5 use FS::Record qw( qsearch qsearchs );
6 use FS::cust_main_Mixin;
7 use FS::cust_bill;
8 use FS::part_bill_event;
9
10 @ISA = qw(FS::cust_main_Mixin FS::Record);
11
12 $DEBUG = 0;
13
14 =head1 NAME
15
16 FS::cust_bill_event - Object methods for cust_bill_event records
17
18 =head1 SYNOPSIS
19
20   use FS::cust_bill_event;
21
22   $record = new FS::cust_bill_event \%hash;
23   $record = new FS::cust_bill_event { 'column' => 'value' };
24
25   $error = $record->insert;
26
27   $error = $new_record->replace($old_record);
28
29   $error = $record->delete;
30
31   $error = $record->check;
32
33 =head1 DESCRIPTION
34
35 An FS::cust_bill_event object represents an complete invoice event.
36 FS::cust_bill_event inherits from FS::Record.  The following fields are
37 currently supported:
38
39 =over 4
40
41 =item eventnum
42
43 Primary key
44
45 =item invnum
46
47 Invoice (see L<FS::cust_bill>)
48
49 =item eventpart
50
51 Event definition (see L<FS::part_bill_event>)
52
53 =item _date
54
55 Specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
56 L<Time::Local> and L<Date::Parse> for conversion functions.
57
58 =item status
59
60 Event status: B<done> or B<failed>
61
62 =item statustext
63
64 Additional status detail (i.e. error message)
65
66 =back
67
68 =head1 METHODS
69
70 =over 4
71
72 =item new HASHREF
73
74 Creates a new completed invoice event.  To add the compelted invoice event to
75 the database, see L<"insert">.
76
77 Note that this stores the hash reference, not a distinct copy of the hash it
78 points to.  You can ask the object for a copy with the I<hash> method.
79
80 =cut
81
82 # the new method can be inherited from FS::Record, if a table method is defined
83
84 sub table { 'cust_bill_event'; }
85
86 sub cust_linked { $_[0]->cust_main_custnum; } 
87 sub cust_unlinked_msg {
88   my $self = shift;
89   "WARNING: can't find cust_main.custnum ". $self->custnum.
90   ' (cust_bill.invnum '. $self->invnum. ')';
91 }
92
93 =item insert
94
95 Adds this record to the database.  If there is an error, returns the error,
96 otherwise returns false.
97
98 =cut
99
100 # the insert method can be inherited from FS::Record
101
102 =item delete
103
104 Delete this record from the database.
105
106 =cut
107
108 # the delete method can be inherited from FS::Record
109
110 =item replace OLD_RECORD
111
112 Replaces the OLD_RECORD with this one in the database.  If there is an error,
113 returns the error, otherwise returns false.
114
115 =cut
116
117 # the replace method can be inherited from FS::Record
118
119 =item check
120
121 Checks all fields to make sure this is a valid completed invoice event.  If
122 there is an error, returns the error, otherwise returns false.  Called by the
123 insert and replace methods.
124
125 =cut
126
127 # the check method should currently be supplied - FS::Record contains some
128 # data checking routines
129
130 sub check {
131   my $self = shift;
132
133   my $error = $self->ut_numbern('eventnum')
134     || $self->ut_number('invnum')
135     || $self->ut_number('eventpart')
136     || $self->ut_number('_date')
137     || $self->ut_enum('status', [qw( done failed )])
138     || $self->ut_anything('statustext')
139   ;
140
141   return "Unknown eventpart ". $self->eventpart
142     unless my $part_bill_event =
143       qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } );
144
145   return "Unknown invnum ". $self->invnum
146     unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
147
148   $self->SUPER::check;
149 }
150
151 =item part_bill_event
152
153 Returns the invoice event definition (see L<FS::part_bill_event>) for this
154 completed invoice event.
155
156 =cut
157
158 sub part_bill_event {
159   my $self = shift;
160   qsearchs( 'part_bill_event', { 'eventpart' => $self->eventpart } );
161 }
162
163 =item cust_bill
164
165 Returns the invoice (see L<FS::cust_bill>) for this completed invoice event.
166
167 =cut
168
169 sub cust_bill {
170   my $self = shift;
171   qsearchs( 'cust_bill', { 'invnum' => $self->invnum } );
172 }
173
174 =item retry
175
176 Changes the status of this event from B<done> to B<failed>, allowing it to be
177 retried.
178
179 =cut
180
181 sub retry {
182   my $self = shift;
183   return '' unless $self->status eq 'done';
184   my $old = ref($self)->new( { $self->hash } );
185   $self->status('failed');
186   $self->replace($old);
187 }
188
189 =item retryable
190
191 Changes the statustext of this event to B<retriable>, rendering it 
192 retriable (should retry be called).
193
194 =cut
195
196 sub retriable {
197   my $self = shift;
198   return '' unless $self->status eq 'done';
199   my $old = ref($self)->new( { $self->hash } );
200   $self->statustext('retriable');
201   $self->replace($old);
202 }
203
204 =item search_sql_where HASHREF
205
206 Class method which returns an SQL WHERE fragment to search for parameters
207 specified in HASHREF.  Valid parameters are
208
209 =over 4
210
211 =item agentnum
212
213 =item beginning
214
215 An epoch date setting a lower bound for _date values
216
217 =item ending
218
219 An epoch date setting a upper bound for _date values
220
221 =item failed
222
223 Limits the search to failed events if true
224
225 =item payby
226
227 Requires that the search be JOIN'd to part_bill_event # Bug?
228
229 =item invnum 
230
231 =item currentuser
232
233 Specifies the user for agent virtualization
234
235 =back
236
237 =cut
238
239 sub search_sql_where {
240   my ($class, $params) = @_;
241   my @search = ();
242
243   push @search, "agentnum = ". $params->{agentnum} if $params->{agentnum};
244
245   push @search, "cust_bill_event._date >= ". $params->{beginning}
246     if $params->{beginning};
247   push @search, "cust_bill_event._date <= ". $params->{ending}
248     if $params->{ending};
249
250   push @search, "statustext != ''",
251                 "statustext IS NOT NULL",
252                 "statustext != 'N/A'"
253     if $params->{failed};
254
255   push @search, "part_bill_event.payby = '". $params->{payby}. "'"
256     if $params->{payby};
257
258   push @search, "cust_bill_event.invnum = '". $params->{invnum}. "'"
259     if $params->{invnum};
260
261   my $currentuser = $params->{currentuser} || $params->{CurrentUser};
262   if ($currentuser) {
263     my $access_user = qsearchs('access_user', { username => $currentuser });
264     if ($access_user) {
265       push @search, $access_user->agentnums_sql;
266     }else{
267       push @search, "1=0";
268     }
269   }else{
270     push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
271   }
272
273   join(' AND ', @search );
274
275 }
276
277 =back
278
279 =head1 SUBROUTINES
280
281 =over 4
282
283 =item reprint
284
285 =cut
286
287 sub process_reprint {
288   process_re_X('print', @_);
289 }
290
291 =item reemail
292
293 =cut
294
295 sub process_reemail {
296   process_re_X('email', @_);
297 }
298
299 =item refax
300
301 =cut
302
303 sub process_refax {
304   process_re_X('fax', @_);
305 }
306
307 use Storable qw(thaw);
308 use Data::Dumper;
309 use MIME::Base64;
310 sub process_re_X {
311   my( $method, $job ) = ( shift, shift );
312
313   my $param = thaw(decode_base64(shift));
314   warn Dumper($param) if $DEBUG;
315
316   re_X(
317     $method,
318     $param,
319     $job,
320   );
321
322 }
323
324 sub re_X {
325   my($method, $param, $job) = @_;
326
327   my $where = FS::cust_bill_event->search_sql_where($param);
328   $where = " WHERE plan LIKE 'send%'". ( $where ? " AND $where" : "" );
329
330   my $from = 'LEFT JOIN part_bill_event USING ( eventpart )'.
331              'LEFT JOIN cust_bill       USING ( invnum )'.
332              'LEFT JOIN cust_main       USING ( custnum )';
333
334   my @cust_bill_event = qsearch( 'cust_bill_event', {}, '', $where, '', $from );
335
336   my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
337   foreach my $cust_bill_event ( @cust_bill_event ) {
338
339     $cust_bill_event->cust_bill->$method(
340       $cust_bill_event->part_bill_event->templatename
341     );
342
343     if ( $job ) { #progressbar foo
344       $num++;
345       if ( time - $min_sec > $last ) {
346         my $error = $job->update_statustext(
347           int( 100 * $num / scalar(@cust_bill_event) )
348         );
349         die $error if $error;
350         $last = time;
351       }
352     }
353
354   }
355
356   #this doesn't work, but it would be nice
357   #if ( $job ) { #progressbar foo
358   #  my $error = $job->update_statustext(
359   #    scalar(@cust_bill_event). " invoices re-${method}ed"
360   #  );
361   #  die $error if $error;
362   #}
363
364 }
365
366 =back
367
368 =head1 BUGS
369
370 Far too early in the morning.
371
372 =head1 SEE ALSO
373
374 L<FS::part_bill_event>, L<FS::cust_bill>, L<FS::Record>, schema.html from the
375 base documentation.
376
377 =cut
378
379 1;
380