Bug 17555: Add Koha::Patron->category
[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 update_password
283
284 my $updated = $patron->update_password( $userid, $password );
285
286 Update the userid and the password of a patron.
287 If the userid already exists, returns and let DBIx::Class warns
288 This will add an entry to action_logs if BorrowersLog is set.
289
290 =cut
291
292 sub update_password {
293     my ( $self, $userid, $password ) = @_;
294     eval { $self->userid($userid)->store; };
295     return if $@; # Make sure the userid is not already in used by another patron
296     $self->password($password)->store;
297     logaction( "MEMBERS", "CHANGE PASS", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
298     return 1;
299 }
300
301 =head3 renew_account
302
303 my $new_expiry_date = $patron->renew_account
304
305 Extending the subscription to the expiry date.
306
307 =cut
308
309 sub renew_account {
310     my ($self) = @_;
311     my $date;
312     if ( C4::Context->preference('BorrowerRenewalPeriodBase') eq 'combination' ) {
313         $date = ( dt_from_string gt dt_from_string( $self->dateexpiry ) ) ? dt_from_string : dt_from_string( $self->dateexpiry );
314     } else {
315         $date =
316             C4::Context->preference('BorrowerRenewalPeriodBase') eq 'dateexpiry'
317             ? dt_from_string( $self->dateexpiry )
318             : dt_from_string;
319     }
320     my $expiry_date = $self->category->get_expiry_date($date);
321
322     $self->dateexpiry($expiry_date)->store;
323
324     $self->add_enrolment_fee_if_needed;
325
326     logaction( "MEMBERS", "RENEW", $self->borrowernumber, "Membership renewed" ) if C4::Context->preference("BorrowersLog");
327     return dt_from_string( $expiry_date )->truncate( to => 'day' );
328 }
329
330 =head2 has_overdues
331
332 my $has_overdues = $patron->has_overdues;
333
334 Returns the number of patron's overdues
335
336 =cut
337
338 sub has_overdues {
339     my ($self) = @_;
340     my $dtf = Koha::Database->new->schema->storage->datetime_parser;
341     return $self->_result->issues->search({ date_due => { '<' => $dtf->format_datetime( dt_from_string() ) } })->count;
342 }
343
344 =head2 track_login
345
346     $patron->track_login;
347     $patron->track_login({ force => 1 });
348
349     Tracks a (successful) login attempt.
350     The preference TrackLastPatronActivity must be enabled. Or you
351     should pass the force parameter.
352
353 =cut
354
355 sub track_login {
356     my ( $self, $params ) = @_;
357     return if
358         !$params->{force} &&
359         !C4::Context->preference('TrackLastPatronActivity');
360     $self->lastseen( dt_from_string() )->store;
361 }
362
363 =head2 move_to_deleted
364
365 my $is_moved = $patron->move_to_deleted;
366
367 Move a patron to the deletedborrowers table.
368 This can be done before deleting a patron, to make sure the data are not completely deleted.
369
370 =cut
371
372 sub move_to_deleted {
373     my ($self) = @_;
374     my $patron_infos = $self->unblessed;
375     return Koha::Database->new->schema->resultset('Deletedborrower')->create($patron_infos);
376 }
377
378 =head3 article_requests
379
380 my @requests = $borrower->article_requests();
381 my $requests = $borrower->article_requests();
382
383 Returns either a list of ArticleRequests objects,
384 or an ArtitleRequests object, depending on the
385 calling context.
386
387 =cut
388
389 sub article_requests {
390     my ( $self ) = @_;
391
392     $self->{_article_requests} ||= Koha::ArticleRequests->search({ borrowernumber => $self->borrowernumber() });
393
394     return $self->{_article_requests};
395 }
396
397 =head3 article_requests_current
398
399 my @requests = $patron->article_requests_current
400
401 Returns the article requests associated with this patron that are incomplete
402
403 =cut
404
405 sub article_requests_current {
406     my ( $self ) = @_;
407
408     $self->{_article_requests_current} ||= Koha::ArticleRequests->search(
409         {
410             borrowernumber => $self->id(),
411             -or          => [
412                 { status => Koha::ArticleRequest::Status::Pending },
413                 { status => Koha::ArticleRequest::Status::Processing }
414             ]
415         }
416     );
417
418     return $self->{_article_requests_current};
419 }
420
421 =head3 article_requests_finished
422
423 my @requests = $biblio->article_requests_finished
424
425 Returns the article requests associated with this patron that are completed
426
427 =cut
428
429 sub article_requests_finished {
430     my ( $self, $borrower ) = @_;
431
432     $self->{_article_requests_finished} ||= Koha::ArticleRequests->search(
433         {
434             borrowernumber => $self->id(),
435             -or          => [
436                 { status => Koha::ArticleRequest::Status::Completed },
437                 { status => Koha::ArticleRequest::Status::Canceled }
438             ]
439         }
440     );
441
442     return $self->{_article_requests_finished};
443 }
444
445 =head3 add_enrolment_fee_if_needed
446
447 my $enrolment_fee = $patron->add_enrolment_fee_if_needed;
448
449 Add enrolment fee for a patron if needed.
450
451 =cut
452
453 sub add_enrolment_fee_if_needed {
454     my ($self) = @_;
455     my $enrolment_fee = $self->category->enrolmentfee;
456     if ( $enrolment_fee && $enrolment_fee > 0 ) {
457         # insert fee in patron debts
458         C4::Accounts::manualinvoice( $self->borrowernumber, '', '', 'A', $enrolment_fee );
459     }
460     return $enrolment_fee || 0;
461 }
462
463 =head3 type
464
465 =cut
466
467 sub _type {
468     return 'Borrower';
469 }
470
471 =head1 AUTHOR
472
473 Kyle M Hall <kyle@bywatersolutions.com>
474 Alex Sassmannshausen <alex.sassmannshausen@ptfs-europe.com>
475
476 =cut
477
478 1;