Bug 17579: Add the Koha::Patron->is_expired
[koha.git] / Koha / Patron.pm
1 package Koha::Patron;
2
3 # Copyright ByWater Solutions 2014
4 # Copyright PTFS Europe 2016
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 3 of the License, or (at your option) any later
11 # version.
12 #
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21 use Modern::Perl;
22
23 use Carp;
24
25 use C4::Context;
26 use C4::Log;
27 use Koha::Database;
28 use Koha::DateUtils;
29 use Koha::Holds;
30 use Koha::Issues;
31 use Koha::OldIssues;
32 use Koha::Patron::Categories;
33 use Koha::Patron::HouseboundProfile;
34 use Koha::Patron::HouseboundRole;
35 use Koha::Patron::Images;
36 use Koha::Patrons;
37 use Koha::Virtualshelves;
38
39 use base qw(Koha::Object);
40
41 =head1 NAME
42
43 Koha::Patron - Koha Patron Object class
44
45 =head1 API
46
47 =head2 Class Methods
48
49 =cut
50
51 =head3 delete
52
53 $patron->delete
54
55 Delete patron's holds, lists and finally the patron.
56
57 Lists owned by the borrower are deleted, but entries from the borrower to
58 other lists are kept.
59
60 =cut
61
62 sub delete {
63     my ($self) = @_;
64
65     my $deleted;
66     $self->_result->result_source->schema->txn_do(
67         sub {
68             # Delete Patron's holds
69             # FIXME Should be $patron->get_holds
70             $_->delete for Koha::Holds->search( { borrowernumber => $self->borrowernumber } );
71
72             # Delete all lists and all shares of this borrower
73             # Consistent with the approach Koha uses on deleting individual lists
74             # Note that entries in virtualshelfcontents added by this borrower to
75             # lists of others will be handled by a table constraint: the borrower
76             # is set to NULL in those entries.
77             # NOTE:
78             # We could handle the above deletes via a constraint too.
79             # But a new BZ report 11889 has been opened to discuss another approach.
80             # Instead of deleting we could also disown lists (based on a pref).
81             # In that way we could save shared and public lists.
82             # The current table constraints support that idea now.
83             # This pref should then govern the results of other routines/methods such as
84             # Koha::Virtualshelf->new->delete too.
85             # FIXME Could be $patron->get_lists
86             $_->delete for Koha::Virtualshelves->search( { owner => $self->borrowernumber } );
87
88             $deleted = $self->SUPER::delete;
89
90             logaction( "MEMBERS", "DELETE", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
91         }
92     );
93     return $deleted;
94 }
95
96
97 =head3 category
98
99 my $patron_category = $patron->category
100
101 Return the patron category for this patron
102
103 =cut
104
105 sub category {
106     my ( $self ) = @_;
107     return Koha::Patron::Category->_new_from_dbic( $self->_result->categorycode );
108 }
109
110 =head3 guarantor
111
112 Returns a Koha::Patron object for this patron's guarantor
113
114 =cut
115
116 sub guarantor {
117     my ( $self ) = @_;
118
119     return unless $self->guarantorid();
120
121     return Koha::Patrons->find( $self->guarantorid() );
122 }
123
124 sub image {
125     my ( $self ) = @_;
126
127     return Koha::Patron::Images->find( $self->borrowernumber )
128 }
129
130 =head3 guarantees
131
132 Returns the guarantees (list of Koha::Patron) of this patron
133
134 =cut
135
136 sub guarantees {
137     my ( $self ) = @_;
138
139     return Koha::Patrons->search( { guarantorid => $self->borrowernumber } );
140 }
141
142 =head3 housebound_profile
143
144 Returns the HouseboundProfile associated with this patron.
145
146 =cut
147
148 sub housebound_profile {
149     my ( $self ) = @_;
150     my $profile = $self->_result->housebound_profile;
151     return Koha::Patron::HouseboundProfile->_new_from_dbic($profile)
152         if ( $profile );
153     return;
154 }
155
156 =head3 housebound_role
157
158 Returns the HouseboundRole associated with this patron.
159
160 =cut
161
162 sub housebound_role {
163     my ( $self ) = @_;
164
165     my $role = $self->_result->housebound_role;
166     return Koha::Patron::HouseboundRole->_new_from_dbic($role) if ( $role );
167     return;
168 }
169
170 =head3 siblings
171
172 Returns the siblings of this patron.
173
174 =cut
175
176 sub siblings {
177     my ( $self ) = @_;
178
179     my $guarantor = $self->guarantor;
180
181     return unless $guarantor;
182
183     return Koha::Patrons->search(
184         {
185             guarantorid => {
186                 '!=' => undef,
187                 '=' => $guarantor->id,
188             },
189             borrowernumber => {
190                 '!=' => $self->borrowernumber,
191             }
192         }
193     );
194 }
195
196 =head3 wants_check_for_previous_checkout
197
198     $wants_check = $patron->wants_check_for_previous_checkout;
199
200 Return 1 if Koha needs to perform PrevIssue checking, else 0.
201
202 =cut
203
204 sub wants_check_for_previous_checkout {
205     my ( $self ) = @_;
206     my $syspref = C4::Context->preference("checkPrevCheckout");
207
208     # Simple cases
209     ## Hard syspref trumps all
210     return 1 if ($syspref eq 'hardyes');
211     return 0 if ($syspref eq 'hardno');
212     ## Now, patron pref trumps all
213     return 1 if ($self->checkprevcheckout eq 'yes');
214     return 0 if ($self->checkprevcheckout eq 'no');
215
216     # More complex: patron inherits -> determine category preference
217     my $checkPrevCheckoutByCat = $self->category->checkprevcheckout;
218     return 1 if ($checkPrevCheckoutByCat eq 'yes');
219     return 0 if ($checkPrevCheckoutByCat eq 'no');
220
221     # Finally: category preference is inherit, default to 0
222     if ($syspref eq 'softyes') {
223         return 1;
224     } else {
225         return 0;
226     }
227 }
228
229 =head3 do_check_for_previous_checkout
230
231     $do_check = $patron->do_check_for_previous_checkout($item);
232
233 Return 1 if the bib associated with $ITEM has previously been checked out to
234 $PATRON, 0 otherwise.
235
236 =cut
237
238 sub do_check_for_previous_checkout {
239     my ( $self, $item ) = @_;
240
241     # Find all items for bib and extract item numbers.
242     my @items = Koha::Items->search({biblionumber => $item->{biblionumber}});
243     my @item_nos;
244     foreach my $item (@items) {
245         push @item_nos, $item->itemnumber;
246     }
247
248     # Create (old)issues search criteria
249     my $criteria = {
250         borrowernumber => $self->borrowernumber,
251         itemnumber => \@item_nos,
252     };
253
254     # Check current issues table
255     my $issues = Koha::Issues->search($criteria);
256     return 1 if $issues->count; # 0 || N
257
258     # Check old issues table
259     my $old_issues = Koha::OldIssues->search($criteria);
260     return $old_issues->count;  # 0 || N
261 }
262
263 =head2 is_debarred
264
265 my $debarment_expiration = $patron->is_debarred;
266
267 Returns the date a patron debarment will expire, or undef if the patron is not
268 debarred
269
270 =cut
271
272 sub is_debarred {
273     my ($self) = @_;
274
275     return unless $self->debarred;
276     return $self->debarred
277       if $self->debarred =~ '^9999'
278       or dt_from_string( $self->debarred ) > dt_from_string;
279     return;
280 }
281
282 =head2 is_expired
283
284 my $is_expired = $patron->is_expired;
285
286 Returns 1 if the patron is expired or 0;
287
288 =cut
289
290 sub is_expired {
291     my ($self) = @_;
292     return 0 unless $self->dateexpiry;
293     return 0 if $self->dateexpiry eq '0000-00-00';
294     return 1 if dt_from_string( $self->dateexpiry ) < dt_from_string;
295     return 0;
296 }
297
298 =head2 update_password
299
300 my $updated = $patron->update_password( $userid, $password );
301
302 Update the userid and the password of a patron.
303 If the userid already exists, returns and let DBIx::Class warns
304 This will add an entry to action_logs if BorrowersLog is set.
305
306 =cut
307
308 sub update_password {
309     my ( $self, $userid, $password ) = @_;
310     eval { $self->userid($userid)->store; };
311     return if $@; # Make sure the userid is not already in used by another patron
312     $self->password($password)->store;
313     logaction( "MEMBERS", "CHANGE PASS", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
314     return 1;
315 }
316
317 =head3 renew_account
318
319 my $new_expiry_date = $patron->renew_account
320
321 Extending the subscription to the expiry date.
322
323 =cut
324
325 sub renew_account {
326     my ($self) = @_;
327     my $date;
328     if ( C4::Context->preference('BorrowerRenewalPeriodBase') eq 'combination' ) {
329         $date = ( dt_from_string gt dt_from_string( $self->dateexpiry ) ) ? dt_from_string : dt_from_string( $self->dateexpiry );
330     } else {
331         $date =
332             C4::Context->preference('BorrowerRenewalPeriodBase') eq 'dateexpiry'
333             ? dt_from_string( $self->dateexpiry )
334             : dt_from_string;
335     }
336     my $expiry_date = $self->category->get_expiry_date($date);
337
338     $self->dateexpiry($expiry_date)->store;
339
340     $self->add_enrolment_fee_if_needed;
341
342     logaction( "MEMBERS", "RENEW", $self->borrowernumber, "Membership renewed" ) if C4::Context->preference("BorrowersLog");
343     return dt_from_string( $expiry_date )->truncate( to => 'day' );
344 }
345
346 =head2 has_overdues
347
348 my $has_overdues = $patron->has_overdues;
349
350 Returns the number of patron's overdues
351
352 =cut
353
354 sub has_overdues {
355     my ($self) = @_;
356     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
357     return $self->_result->issues->search({ date_due => { '<' => $dtf->format_datetime( dt_from_string() ) } })->count;
358 }
359
360 =head2 track_login
361
362     $patron->track_login;
363     $patron->track_login({ force => 1 });
364
365     Tracks a (successful) login attempt.
366     The preference TrackLastPatronActivity must be enabled. Or you
367     should pass the force parameter.
368
369 =cut
370
371 sub track_login {
372     my ( $self, $params ) = @_;
373     return if
374         !$params->{force} &&
375         !C4::Context->preference('TrackLastPatronActivity');
376     $self->lastseen( dt_from_string() )->store;
377 }
378
379 =head2 move_to_deleted
380
381 my $is_moved = $patron->move_to_deleted;
382
383 Move a patron to the deletedborrowers table.
384 This can be done before deleting a patron, to make sure the data are not completely deleted.
385
386 =cut
387
388 sub move_to_deleted {
389     my ($self) = @_;
390     my $patron_infos = $self->unblessed;
391     return Koha::Database->new->schema->resultset('Deletedborrower')->create($patron_infos);
392 }
393
394 =head3 article_requests
395
396 my @requests = $borrower->article_requests();
397 my $requests = $borrower->article_requests();
398
399 Returns either a list of ArticleRequests objects,
400 or an ArtitleRequests object, depending on the
401 calling context.
402
403 =cut
404
405 sub article_requests {
406     my ( $self ) = @_;
407
408     $self->{_article_requests} ||= Koha::ArticleRequests->search({ borrowernumber => $self->borrowernumber() });
409
410     return $self->{_article_requests};
411 }
412
413 =head3 article_requests_current
414
415 my @requests = $patron->article_requests_current
416
417 Returns the article requests associated with this patron that are incomplete
418
419 =cut
420
421 sub article_requests_current {
422     my ( $self ) = @_;
423
424     $self->{_article_requests_current} ||= Koha::ArticleRequests->search(
425         {
426             borrowernumber => $self->id(),
427             -or          => [
428                 { status => Koha::ArticleRequest::Status::Pending },
429                 { status => Koha::ArticleRequest::Status::Processing }
430             ]
431         }
432     );
433
434     return $self->{_article_requests_current};
435 }
436
437 =head3 article_requests_finished
438
439 my @requests = $biblio->article_requests_finished
440
441 Returns the article requests associated with this patron that are completed
442
443 =cut
444
445 sub article_requests_finished {
446     my ( $self, $borrower ) = @_;
447
448     $self->{_article_requests_finished} ||= Koha::ArticleRequests->search(
449         {
450             borrowernumber => $self->id(),
451             -or          => [
452                 { status => Koha::ArticleRequest::Status::Completed },
453                 { status => Koha::ArticleRequest::Status::Canceled }
454             ]
455         }
456     );
457
458     return $self->{_article_requests_finished};
459 }
460
461 =head3 add_enrolment_fee_if_needed
462
463 my $enrolment_fee = $patron->add_enrolment_fee_if_needed;
464
465 Add enrolment fee for a patron if needed.
466
467 =cut
468
469 sub add_enrolment_fee_if_needed {
470     my ($self) = @_;
471     my $enrolment_fee = $self->category->enrolmentfee;
472     if ( $enrolment_fee && $enrolment_fee > 0 ) {
473         # insert fee in patron debts
474         C4::Accounts::manualinvoice( $self->borrowernumber, '', '', 'A', $enrolment_fee );
475     }
476     return $enrolment_fee || 0;
477 }
478
479 =head3 type
480
481 =cut
482
483 sub _type {
484     return 'Borrower';
485 }
486
487 =head1 AUTHOR
488
489 Kyle M Hall <kyle@bywatersolutions.com>
490 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
491
492 =cut
493
494 1;