This commit was generated by cvs2svn to compensate for changes in r6252,
[freeside.git] / FS / FS / cust_refund.pm
1 package FS::cust_refund;
2
3 use strict;
4 use vars qw( @ISA @encrypted_fields );
5 use Business::CreditCard;
6 use FS::Record qw( qsearch qsearchs dbh );
7 use FS::UID qw(getotaker);
8 use FS::cust_credit;
9 use FS::cust_credit_refund;
10 use FS::cust_pay_refund;
11 use FS::cust_main;
12 use FS::payinfo_Mixin;
13
14 @ISA = qw( FS::Record FS::payinfo_Mixin );
15
16 @encrypted_fields = ('payinfo');
17
18 =head1 NAME
19
20 FS::cust_refund - Object method for cust_refund objects
21
22 =head1 SYNOPSIS
23
24   use FS::cust_refund;
25
26   $record = new FS::cust_refund \%hash;
27   $record = new FS::cust_refund { 'column' => 'value' };
28
29   $error = $record->insert;
30
31   $error = $new_record->replace($old_record);
32
33   $error = $record->delete;
34
35   $error = $record->check;
36
37 =head1 DESCRIPTION
38
39 An FS::cust_refund represents a refund: the transfer of money to a customer;
40 equivalent to a negative payment (see L<FS::cust_pay>).  FS::cust_refund
41 inherits from FS::Record.  The following fields are currently supported:
42
43 =over 4
44
45 =item refundnum - primary key (assigned automatically for new refunds)
46
47 =item custnum - customer (see L<FS::cust_main>)
48
49 =item refund - Amount of the refund
50
51 =item reason - Reason for the refund
52
53 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
54 L<Time::Local> and L<Date::Parse> for conversion functions.
55
56 =item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
57
58 =item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format)
59
60 =item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
61
62 =item paybatch - text field for tracking card processing
63
64 =item otaker - order taker (assigned automatically, see L<FS::UID>)
65
66 =item closed - books closed flag, empty or `Y'
67
68 =back
69
70 =head1 METHODS
71
72 =over 4
73
74 =item new HASHREF
75
76 Creates a new refund.  To add the refund to the database, see L<"insert">.
77
78 =cut
79
80 sub table { 'cust_refund'; }
81
82 =item insert
83
84 Adds this refund to the database.
85
86 For backwards-compatibility and convenience, if the additional field crednum is
87 defined, an FS::cust_credit_refund record for the full amount of the refund
88 will be created.  Or (this time for convenience and consistancy), if the
89 additional field paynum is defined, an FS::cust_pay_refund record for the full
90 amount of the refund will be created.  In both cases, custnum is optional.
91
92 =cut
93
94 sub insert {
95   my $self = shift;
96
97   local $SIG{HUP} = 'IGNORE';
98   local $SIG{INT} = 'IGNORE';
99   local $SIG{QUIT} = 'IGNORE';
100   local $SIG{TERM} = 'IGNORE';
101   local $SIG{TSTP} = 'IGNORE';
102   local $SIG{PIPE} = 'IGNORE';
103
104   my $oldAutoCommit = $FS::UID::AutoCommit;
105   local $FS::UID::AutoCommit = 0;
106   my $dbh = dbh;
107
108   if ( $self->crednum ) {
109     my $cust_credit = qsearchs('cust_credit', { 'crednum' => $self->crednum } )
110       or do {
111         $dbh->rollback if $oldAutoCommit;
112         return "Unknown cust_credit.crednum: ". $self->crednum;
113       };
114     $self->custnum($cust_credit->custnum);
115   } elsif ( $self->paynum ) {
116     my $cust_pay = qsearchs('cust_pay', { 'paynum' => $self->paynum } )
117       or do {
118         $dbh->rollback if $oldAutoCommit;
119         return "Unknown cust_pay.paynum: ". $self->paynum;
120       };
121     $self->custnum($cust_pay->custnum);
122   }
123
124   my $error = $self->check;
125   return $error if $error;
126
127   $error = $self->SUPER::insert;
128   if ( $error ) {
129     $dbh->rollback if $oldAutoCommit;
130     return $error;
131   }
132
133   if ( $self->crednum ) {
134     my $cust_credit_refund = new FS::cust_credit_refund {
135       'crednum'   => $self->crednum,
136       'refundnum' => $self->refundnum,
137       'amount'    => $self->refund,
138       '_date'     => $self->_date,
139     };
140     $error = $cust_credit_refund->insert;
141     if ( $error ) {
142       $dbh->rollback if $oldAutoCommit;
143       return $error;
144     }
145     #$self->custnum($cust_credit_refund->cust_credit->custnum);
146   } elsif ( $self->paynum ) {
147     my $cust_pay_refund = new FS::cust_pay_refund {
148       'paynum'    => $self->paynum,
149       'refundnum' => $self->refundnum,
150       'amount'    => $self->refund,
151       '_date'     => $self->_date,
152     };
153     $error = $cust_pay_refund->insert;
154     if ( $error ) {
155       $dbh->rollback if $oldAutoCommit;
156       return $error;
157     }
158   }
159
160
161   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
162
163   '';
164
165 }
166
167 =item delete
168
169 Unless the closed flag is set, deletes this refund and all associated
170 applications (see L<FS::cust_credit_refund> and L<FS::cust_pay_refund>).
171
172 =cut
173
174 sub delete {
175   my $self = shift;
176   return "Can't delete closed refund" if $self->closed =~ /^Y/i;
177
178   local $SIG{HUP} = 'IGNORE';
179   local $SIG{INT} = 'IGNORE';
180   local $SIG{QUIT} = 'IGNORE';
181   local $SIG{TERM} = 'IGNORE';
182   local $SIG{TSTP} = 'IGNORE';
183   local $SIG{PIPE} = 'IGNORE';
184
185   my $oldAutoCommit = $FS::UID::AutoCommit;
186   local $FS::UID::AutoCommit = 0;
187   my $dbh = dbh;
188
189   foreach my $cust_credit_refund ( $self->cust_credit_refund ) {
190     my $error = $cust_credit_refund->delete;
191     if ( $error ) {
192       $dbh->rollback if $oldAutoCommit;
193       return $error;
194     }
195   }
196
197   foreach my $cust_pay_refund ( $self->cust_pay_refund ) {
198     my $error = $cust_pay_refund->delete;
199     if ( $error ) {
200       $dbh->rollback if $oldAutoCommit;
201       return $error;
202     }
203   }
204
205   my $error = $self->SUPER::delete(@_);
206   if ( $error ) {
207     $dbh->rollback if $oldAutoCommit;
208     return $error;
209   }
210
211   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
212
213   '';
214
215 }
216
217 =item replace OLD_RECORD
218
219 Currently unimplemented (accounting reasons).
220
221 =cut
222
223 sub replace {
224   my $self = shift;
225   $self->SUPER::replace(@_);
226 }
227
228 =item check
229
230 Checks all fields to make sure this is a valid refund.  If there is an error,
231 returns the error, otherwise returns false.  Called by the insert method.
232
233 =cut
234
235 sub check {
236   my $self = shift;
237
238   my $error =
239     $self->ut_numbern('refundnum')
240     || $self->ut_numbern('custnum')
241     || $self->ut_money('refund')
242     || $self->ut_text('reason')
243     || $self->ut_numbern('_date')
244     || $self->ut_textn('paybatch')
245     || $self->ut_enum('closed', [ '', 'Y' ])
246   ;
247   return $error if $error;
248
249   return "refund must be > 0 " if $self->refund <= 0;
250
251   $self->_date(time) unless $self->_date;
252
253   return "unknown cust_main.custnum: ". $self->custnum
254     unless $self->crednum 
255            || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
256
257   $error = $self->payinfo_check;
258   return $error if $error;
259
260   $self->otaker(getotaker);
261
262   $self->SUPER::check;
263 }
264
265 =item cust_credit_refund
266
267 Returns all applications to credits (see L<FS::cust_credit_refund>) for this
268 refund.
269
270 =cut
271
272 sub cust_credit_refund {
273   my $self = shift;
274   sort { $a->_date <=> $b->_date }
275     qsearch( 'cust_credit_refund', { 'refundnum' => $self->refundnum } )
276   ;
277 }
278
279 =item cust_pay_refund
280
281 Returns all applications to payments (see L<FS::cust_pay_refund>) for this
282 refund.
283
284 =cut
285
286 sub cust_pay_refund {
287   my $self = shift;
288   sort { $a->_date <=> $b->_date }
289     qsearch( 'cust_pay_refund', { 'refundnum' => $self->refundnum } )
290   ;
291 }
292
293 =item unapplied
294
295 Returns the amount of this refund that is still unapplied; which is
296 amount minus all credit applications (see L<FS::cust_credit_refund>) and
297 payment applications (see L<FS::cust_pay_refund>).
298
299 =cut
300
301 sub unapplied {
302   my $self = shift;
303   my $amount = $self->refund;
304   $amount -= $_->amount foreach ( $self->cust_credit_refund );
305   $amount -= $_->amount foreach ( $self->cust_pay_refund );
306   sprintf("%.2f", $amount );
307 }
308
309 =back
310
311 =head1 CLASS METHODS
312
313 =over 4
314
315 =item unapplied_sql
316
317 Returns an SQL fragment to retreive the unapplied amount.
318
319 =cut 
320
321 sub unapplied_sql {
322   #my $class = shift;
323
324   "refund
325     - COALESCE( 
326                 ( SELECT SUM(amount) FROM cust_credit_refund
327                     WHERE cust_refund.refundnum = cust_credit_refund.refundnum )
328                 ,0
329               )
330     - COALESCE(
331                 ( SELECT SUM(amount) FROM cust_pay_refund
332                     WHERE cust_refund.refundnum = cust_pay_refund.refundnum )
333                 ,0
334               )
335   ";
336
337 }
338
339 =back
340
341 =head1 BUGS
342
343 Delete and replace methods.
344
345 =head1 SEE ALSO
346
347 L<FS::Record>, L<FS::cust_credit>, schema.html from the base documentation.
348
349 =cut
350
351 1;
352