b74a23111ae759e33e26bacf17cacba070a6ca55
[freeside.git] / FS / FS / cust_pkg_discount.pm
1 package FS::cust_pkg_discount;
2 use base qw( FS::otaker_Mixin FS::cust_main_Mixin FS::Record );
3
4 use strict;
5 use FS::Record qw( dbh ); # qsearch qsearchs dbh );
6 use FS::discount;
7
8 =head1 NAME
9
10 FS::cust_pkg_discount - Object methods for cust_pkg_discount records
11
12 =head1 SYNOPSIS
13
14   use FS::cust_pkg_discount;
15
16   $record = new FS::cust_pkg_discount \%hash;
17   $record = new FS::cust_pkg_discount { 'column' => 'value' };
18
19   $error = $record->insert;
20
21   $error = $new_record->replace($old_record);
22
23   $error = $record->delete;
24
25   $error = $record->check;
26
27 =head1 DESCRIPTION
28
29 An FS::cust_pkg_discount object represents the application of a discount to a
30 customer package.  FS::cust_pkg_discount inherits from FS::Record.  The
31 following fields are currently supported:
32
33 =over 4
34
35 =item pkgdiscountnum
36
37 primary key
38
39 =item pkgnum
40
41 Customer package (see L<FS::cust_pkg>)
42
43 =item discountnum
44
45 Discount (see L<FS::discount>)
46
47 =item months_used
48
49 months_used
50
51 =item end_date
52
53 end_date
54
55 =item usernum
56
57 order taker, see L<FS::access_user>
58
59
60 =back
61
62 =head1 METHODS
63
64 =over 4
65
66 =item new HASHREF
67
68 Creates a new discount application.  To add the record to the database, see
69  L<"insert">.
70
71 Note that this stores the hash reference, not a distinct copy of the hash it
72 points to.  You can ask the object for a copy with the I<hash> method.
73
74 =cut
75
76 # the new method can be inherited from FS::Record, if a table method is defined
77
78 sub table { 'cust_pkg_discount'; }
79
80 =item insert
81
82 Adds this record to the database.  If there is an error, returns the error,
83 otherwise returns false.
84
85 =cut
86
87 sub insert {
88   #my( $self, %options ) = @_;
89   my $self = shift;
90
91   local $SIG{HUP} = 'IGNORE';
92   local $SIG{INT} = 'IGNORE';
93   local $SIG{QUIT} = 'IGNORE';
94   local $SIG{TERM} = 'IGNORE';
95   local $SIG{TSTP} = 'IGNORE';
96   local $SIG{PIPE} = 'IGNORE';
97
98   my $oldAutoCommit = $FS::UID::AutoCommit;
99   local $FS::UID::AutoCommit = 0;
100   my $dbh = dbh;
101
102   if ( $self->discountnum == -1 ) {
103     my $discount = new FS::discount {
104       '_type'    => $self->_type,
105       'amount'   => $self->amount,
106       'percent'  => $self->percent,
107       'months'   => $self->months,
108       'setup'    => $self->setup,
109       #'linked'   => $self->linked,
110       'disabled' => 'Y',
111     };
112     my $error = $discount->insert;
113     if ( $error ) {
114       $dbh->rollback if $oldAutoCommit;
115       return $error;
116     }
117     $self->discountnum($discount->discountnum);
118   }
119
120   my $error = $self->SUPER::insert; #(@_); #(%options);
121   if ( $error ) {
122     $dbh->rollback if $oldAutoCommit;
123     return $error;
124   }
125
126   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
127   '';
128
129 }
130
131 =item delete
132
133 Delete this record from the database.
134
135 =cut
136
137 # the delete method can be inherited from FS::Record
138
139 =item replace OLD_RECORD
140
141 Replaces the OLD_RECORD with this one in the database.  If there is an error,
142 returns the error, otherwise returns false.
143
144 =cut
145
146 # the replace method can be inherited from FS::Record
147
148 =item check
149
150 Checks all fields to make sure this is a valid discount applciation.  If there
151 is an error, returns the error, otherwise returns false.  Called by the insert
152 and replace methods.
153
154 =cut
155
156 # the check method should currently be supplied - FS::Record contains some
157 # data checking routines
158
159 sub check {
160   my $self = shift;
161
162   my $error = 
163     $self->ut_numbern('pkgdiscountnum')
164     || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
165     || $self->ut_foreign_key('discountnum', 'discount', 'discountnum' )
166     || $self->ut_sfloat('months_used') #actually decimal, but this will do
167     || $self->ut_numbern('end_date')
168     || $self->ut_alphan('otaker')
169     || $self->ut_numbern('usernum')
170     || $self->ut_enum('disabled', [ '', 'Y' ] )
171   ;
172   return $error if $error;
173
174   return "Discount does not apply to setup fees, and package has no recurring"
175     if ! $self->discount->setup && $self->cust_pkg->part_pkg->freq =~ /^0/;
176
177   $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum;
178
179   $self->SUPER::check;
180 }
181
182 =item cust_pkg
183
184 Returns the customer package (see L<FS::cust_pkg>).
185
186 =item discount
187
188 Returns the discount (see L<FS::discount>).
189
190 =item increment_months_used MONTHS
191
192 Increments months_used by the given parameter
193
194 =cut
195
196 sub increment_months_used {
197   my( $self, $used ) = @_;
198   #UPDATE cust_pkg_discount SET months_used = months_used + ?
199   #leaves no history, and billing is mutexed per-customer, so the dum way is ok
200   $self->months_used( $self->months_used + $used );
201   $self->replace();
202 }
203
204 =item decrement_months_used MONTHS
205
206 Decrement months_used by the given parameter
207
208 (Note: as in, extending the length of the discount.  Typically only used to
209 stack/extend a discount when the customer package has one active already.)
210
211 =cut
212
213 sub decrement_months_used {
214   my( $self, $recharged ) = @_;
215   #UPDATE cust_pkg_discount SET months_used = months_used - ?
216   #leaves no history, and billing is mutexed per-customer
217
218   #we're run from part_event/Action/referral_pkg_discount on behalf of a
219   # different customer, so we need to grab this customer's mutex.
220   #   incidentally, that's some inelegant encapsulation breaking shit, and a
221   #   great argument in favor of native-DB trigger history so we can trust
222   #   in normal ACID like the SQL above instead of this
223   $self->cust_pkg->cust_main->select_for_update;
224
225   $self->months_used( $self->months_used - $recharged );
226   $self->replace();
227 }
228
229 =item status
230
231 =cut
232
233 sub status {
234   my $self = shift;
235   my $discount = $self->discount;
236
237   if ( $self->disabled ne 'Y' 
238        and ( ! $discount->months || $self->months_used < $discount->months )
239              #XXX also end date
240      ) {
241     'active';
242   } else {
243     'expired';
244   }
245 }
246
247 # Used by FS::Upgrade to migrate to a new database.
248 sub _upgrade_data {  # class method
249   my ($class, %opts) = @_;
250   $class->_upgrade_otaker(%opts);
251 }
252
253 =back
254
255 =head1 BUGS
256
257 =head1 SEE ALSO
258
259 L<FS::discount>, L<FS::cust_pkg>, L<FS::Record>, schema.html from the base documentation.
260
261 =cut
262
263 1;
264