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