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