multiple payment options, RT#23741
[freeside.git] / FS / FS / cust_bill_void.pm
1 package FS::cust_bill_void;
2 use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::otaker_Mixin FS::Record );
3
4 use strict;
5 use FS::Record qw( qsearch qsearchs dbh fields );
6 use FS::cust_statement;
7 use FS::access_user;
8 use FS::cust_bill_pkg_void;
9 use FS::cust_bill;
10
11 =head1 NAME
12
13 FS::cust_bill_void - Object methods for cust_bill_void records
14
15 =head1 SYNOPSIS
16
17   use FS::cust_bill_void;
18
19   $record = new FS::cust_bill_void \%hash;
20   $record = new FS::cust_bill_void { 'column' => 'value' };
21
22   $error = $record->insert;
23
24   $error = $new_record->replace($old_record);
25
26   $error = $record->delete;
27
28   $error = $record->check;
29
30 =head1 DESCRIPTION
31
32 An FS::cust_bill_void object represents a voided invoice.  FS::cust_bill_void
33 inherits from FS::Record.  The following fields are currently supported:
34
35 =over 4
36
37 =item invnum
38
39 primary key
40
41 =item custnum
42
43 custnum
44
45 =item _date
46
47 _date
48
49 =item charged
50
51 charged
52
53 =item invoice_terms
54
55 invoice_terms
56
57 =item previous_balance
58
59 previous_balance
60
61 =item billing_balance
62
63 billing_balance
64
65 =item closed
66
67 closed
68
69 =item statementnum
70
71 statementnum
72
73 =item agent_invid
74
75 agent_invid
76
77 =item promised_date
78
79 promised_date
80
81 =item void_date
82
83 void_date
84
85 =item reason
86
87 reason
88
89 =item void_usernum
90
91 void_usernum
92
93
94 =back
95
96 =head1 METHODS
97
98 =over 4
99
100 =item new HASHREF
101
102 Creates a new voided invoice.  To add the voided invoice to the database, see L<"insert">.
103
104 Note that this stores the hash reference, not a distinct copy of the hash it
105 points to.  You can ask the object for a copy with the I<hash> method.
106
107 =cut
108
109 sub table { 'cust_bill_void'; }
110 sub notice_name { 'VOIDED Invoice'; }
111 #XXXsub template_conf { 'quotation_'; }
112
113 =item insert
114
115 Adds this record to the database.  If there is an error, returns the error,
116 otherwise returns false.
117
118 =cut
119
120 =item unvoid 
121
122 "Un-void"s this invoice: Deletes the voided invoice from the database and adds
123 back a normal invoice (and related tables).
124
125 =cut
126
127 sub unvoid {
128   my $self = shift;
129
130   local $SIG{HUP} = 'IGNORE';
131   local $SIG{INT} = 'IGNORE';
132   local $SIG{QUIT} = 'IGNORE';
133   local $SIG{TERM} = 'IGNORE';
134   local $SIG{TSTP} = 'IGNORE';
135   local $SIG{PIPE} = 'IGNORE';
136
137   my $oldAutoCommit = $FS::UID::AutoCommit;
138   local $FS::UID::AutoCommit = 0;
139   my $dbh = dbh;
140
141   my $cust_bill = new FS::cust_bill ( {
142     map { $_ => $self->get($_) } fields('cust_bill')
143   } );
144   my $error = $cust_bill->insert;
145   if ( $error ) {
146     $dbh->rollback if $oldAutoCommit;
147     return $error;
148   }
149
150   foreach my $cust_bill_pkg_void ( $self->cust_bill_pkg ) {
151     my $error = $cust_bill_pkg_void->unvoid;
152     if ( $error ) {
153       $dbh->rollback if $oldAutoCommit;
154       return $error;
155     }
156   }
157
158   $error = $self->delete;
159   if ( $error ) {
160     $dbh->rollback if $oldAutoCommit;
161     return $error;
162   }
163
164   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
165
166   '';
167
168 }
169
170 =item delete
171
172 Delete this record from the database.
173
174 =cut
175
176 =item replace OLD_RECORD
177
178 Replaces the OLD_RECORD with this one in the database.  If there is an error,
179 returns the error, otherwise returns false.
180
181 =cut
182
183 =item check
184
185 Checks all fields to make sure this is a valid voided invoice.  If there is
186 an error, returns the error, otherwise returns false.  Called by the insert
187 and replace methods.
188
189 =cut
190
191 sub check {
192   my $self = shift;
193
194   my $error = 
195     $self->ut_number('invnum')
196     || $self->ut_foreign_key('custnum', 'cust_main', 'custnum' )
197     || $self->ut_numbern('_date')
198     || $self->ut_money('charged')
199     || $self->ut_textn('invoice_terms')
200     || $self->ut_moneyn('previous_balance')
201     || $self->ut_moneyn('billing_balance')
202     || $self->ut_enum('closed', [ '', 'Y' ])
203     || $self->ut_foreign_keyn('statementnum', 'cust_statement', 'statementnum')
204     || $self->ut_numbern('agent_invid')
205     || $self->ut_numbern('promised_date')
206     || $self->ut_numbern('void_date')
207     || $self->ut_textn('reason')
208     || $self->ut_numbern('void_usernum')
209   ;
210   return $error if $error;
211
212   $self->void_date(time) unless $self->void_date;
213
214   $self->void_usernum($FS::CurrentUser::CurrentUser->usernum)
215     unless $self->void_usernum;
216
217   $self->SUPER::check;
218 }
219
220 =item display_invnum
221
222 Returns the displayed invoice number for this invoice: agent_invid if
223 cust_bill-default_agent_invid is set and it has a value, invnum otherwise.
224
225 =cut
226
227 sub display_invnum {
228   my $self = shift;
229   my $conf = $self->conf;
230   if ( $conf->exists('cust_bill-default_agent_invid') && $self->agent_invid ){
231     return $self->agent_invid;
232   } else {
233     return $self->invnum;
234   }
235 }
236
237 =item void_access_user
238
239 Returns the voiding employee object (see L<FS::access_user>).
240
241 =cut
242
243 sub void_access_user {
244   my $self = shift;
245   qsearchs('access_user', { 'usernum' => $self->void_usernum } );
246 }
247
248 =item cust_main
249
250 =item cust_bill_pkg
251
252 =cut
253
254 sub cust_bill_pkg { #actually cust_bill_pkg_void objects
255   my $self = shift;
256   qsearch('cust_bill_pkg_void', { invnum=>$self->invnum });
257 }
258
259 =back
260
261 =item cust_pkg
262
263 Returns the packages (see L<FS::cust_pkg>) corresponding to the line items for
264 this invoice.
265
266 =cut
267
268 sub cust_pkg {
269   my $self = shift;
270   my @cust_pkg = map { $_->pkgnum > 0 ? $_->cust_pkg : () }
271                  $self->cust_bill_pkg;
272   my %saw = ();
273   grep { ! $saw{$_->pkgnum}++ } @cust_pkg;
274 }
275
276 =item search_sql_where HASHREF
277
278 Class method which returns an SQL WHERE fragment to search for parameters
279 specified in HASHREF.  Accepts the following parameters for 
280 L<FS::cust_bill::search_sql_where>: C<_date>, C<invnum_min>, C<invnum_max>,
281 C<agentnum>, C<custnum>, C<cust_classnum>, C<refnum>.  Also 
282 accepts the following:
283
284 =over 4
285
286 =item void_date
287
288 Arrayref of start and end date to find invoices voided in a date range.
289
290 =item void_usernum
291
292 User identifier (L<FS::access_user> key) that voided the invoice.
293
294 =back
295
296 =cut
297
298 sub search_sql_where {
299   my($class, $param) = @_;
300
301   my $cust_bill_param = {
302     map { $_ => $param->{$_} }
303     grep { exists($param->{$_}) }
304     qw( _date invnum_min invnum_max agentnum custnum cust_classnum 
305         refnum )
306   };
307   my $search_sql = FS::cust_bill->search_sql_where($cust_bill_param);
308   $search_sql =~ s/cust_bill/cust_bill_void/g;
309   my @search = ($search_sql);
310
311   if ( $param->{void_date} ) {
312     my($beginning, $ending) = @{$param->{void_date}};
313     push @search, "cust_bill_void.void_date >= $beginning",
314                   "cust_bill_void.void_date <  $ending";
315   }
316
317   if ( $param->{void_usernum} =~ /^(\d+)$/ ) {
318     my $usernum = $1;
319     push @search, "cust_bill_void.void_usernum = $1";
320   }
321
322   join(" AND ", @search);
323 }
324
325
326 =item enable_previous
327
328 =cut
329
330 sub enable_previous { 0 }
331
332 =back
333
334 =head1 BUGS
335
336 =head1 SEE ALSO
337
338 L<FS::Record>, schema.html from the base documentation.
339
340 =cut
341
342 1;
343