encryption fixes from huntsberg & jayce
[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 Currently unimplemented (accounting reasons).
170
171 =cut
172
173 sub delete {
174   my $self = shift;
175   return "Can't delete closed refund" if $self->closed =~ /^Y/i;
176   $self->SUPER::delete(@_);
177 }
178
179 =item replace OLD_RECORD
180
181 Currently unimplemented (accounting reasons).
182
183 =cut
184
185 sub replace {
186    return "Can't (yet?) modify cust_refund records!";
187 }
188
189 =item check
190
191 Checks all fields to make sure this is a valid refund.  If there is an error,
192 returns the error, otherwise returns false.  Called by the insert method.
193
194 =cut
195
196 sub check {
197   my $self = shift;
198
199   my $error =
200     $self->ut_numbern('refundnum')
201     || $self->ut_numbern('custnum')
202     || $self->ut_money('refund')
203     || $self->ut_text('reason')
204     || $self->ut_numbern('_date')
205     || $self->ut_textn('paybatch')
206     || $self->ut_enum('closed', [ '', 'Y' ])
207   ;
208   return $error if $error;
209
210   return "refund must be > 0 " if $self->refund <= 0;
211
212   $self->_date(time) unless $self->_date;
213
214   return "unknown cust_main.custnum: ". $self->custnum
215     unless $self->crednum 
216            || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
217
218   $error = $self->payinfo_check;
219   return $error if $error;
220
221   $self->otaker(getotaker);
222
223   $self->SUPER::check;
224 }
225
226 =item cust_credit_refund
227
228 Returns all applications to credits (see L<FS::cust_credit_refund>) for this
229 refund.
230
231 =cut
232
233 sub cust_credit_refund {
234   my $self = shift;
235   sort { $a->_date <=> $b->_date }
236     qsearch( 'cust_credit_refund', { 'refundnum' => $self->refundnum } )
237   ;
238 }
239
240 =item cust_pay_refund
241
242 Returns all applications to payments (see L<FS::cust_pay_refund>) for this
243 refund.
244
245 =cut
246
247 sub cust_pay_refund {
248   my $self = shift;
249   sort { $a->_date <=> $b->_date }
250     qsearch( 'cust_pay_refund', { 'refundnum' => $self->refundnum } )
251   ;
252 }
253
254 =item unapplied
255
256 Returns the amount of this refund that is still unapplied; which is
257 amount minus all credit applications (see L<FS::cust_credit_refund>) and
258 payment applications (see L<FS::cust_pay_refund>).
259
260 =cut
261
262 sub unapplied {
263   my $self = shift;
264   my $amount = $self->refund;
265   $amount -= $_->amount foreach ( $self->cust_credit_refund );
266   $amount -= $_->amount foreach ( $self->cust_pay_refund );
267   sprintf("%.2f", $amount );
268 }
269
270 =item payinfo_masked
271
272 <DEPRICATED> Use $self->paymask
273
274 Returns a "masked" payinfo field with all but the last four characters replaced
275 by 'x'es.  Useful for displaying credit cards.
276
277 =cut
278
279
280 sub payinfo_masked {
281   my $self = shift;
282   return $self->paymask;
283 }
284
285
286 =back
287
288 =head1 BUGS
289
290 Delete and replace methods.  payinfo_masked false laziness with cust_main.pm
291 and cust_pay.pm
292
293 =head1 SEE ALSO
294
295 L<FS::Record>, L<FS::cust_credit>, schema.html from the base documentation.
296
297 =cut
298
299 1;
300