3 # Copyright 2020 Aleisha Amohia <aleisha@catalyst.net.nz>
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
23 use Koha::DateUtils qw( dt_from_string );
28 use base qw(Koha::Object);
32 Koha::Recall - Koha Recall Object class
36 =head2 Internal methods
42 my $biblio = $recall->biblio;
44 Returns the related Koha::Biblio object for this recall.
50 my $biblio_rs = $self->_result->biblio;
51 return unless $biblio_rs;
52 return Koha::Biblio->_new_from_dbic( $biblio_rs );
57 my $item = $recall->item;
59 Returns the related Koha::Item object for this recall.
65 my $item_rs = $self->_result->item;
66 return unless $item_rs;
67 return Koha::Item->_new_from_dbic( $item_rs );
72 my $patron = $recall->patron;
74 Returns the related Koha::Patron object for this recall.
80 my $patron_rs = $self->_result->borrower;
81 return unless $patron_rs;
82 return Koha::Patron->_new_from_dbic( $patron_rs );
87 my $library = $recall->library;
89 Returns the related Koha::Library object for this recall.
95 $self->{_library} = Koha::Libraries->find( $self->branchcode );
96 return $self->{_library};
101 my $checkout = $recall->checkout;
103 Returns the related Koha::Checkout object for this recall.
109 $self->{_checkout} ||= Koha::Checkouts->find({ itemnumber => $self->itemnumber });
111 unless ( $self->item_level_recall ) {
112 # Only look at checkouts of items that are allowed to be recalled, and get the oldest one
113 my @items = Koha::Items->search({ biblionumber => $self->biblionumber })->as_list;
116 my $recalls_allowed = Koha::CirculationRules->get_effective_rule({
117 branchcode => C4::Context->userenv->{'branch'},
118 categorycode => $self->patron->categorycode,
119 itemtype => $_->effective_itemtype,
120 rule_name => 'recalls_allowed',
122 if ( defined $recalls_allowed and $recalls_allowed->rule_value > 0 ) {
123 push ( @itemnumbers, $_->itemnumber );
126 my $checkouts = Koha::Checkouts->search({ itemnumber => [ @itemnumbers ] }, { order_by => { -asc => 'date_due' } });
127 $self->{_checkout} = $checkouts->next;
130 return $self->{_checkout};
135 if ( $recall->requested )
137 [% IF recall.requested %]
139 Return true if recall status is requested.
145 my $status = $self->status;
146 return $status && $status eq 'R';
151 if ( $recall->waiting )
153 [% IF recall.waiting %]
155 Return true if recall is awaiting pickup.
161 my $status = $self->status;
162 return $status && $status eq 'W';
167 if ( $recall->overdue )
169 [% IF recall.overdue %]
171 Return true if recall is overdue to be returned.
177 my $status = $self->status;
178 return $status && $status eq 'O';
183 if ( $recall->in_transit )
185 [% IF recall.in_transit %]
187 Return true if recall is in transit.
193 my $status = $self->status;
194 return $status && $status eq 'T';
199 if ( $recall->expired )
201 [% IF recall.expired %]
203 Return true if recall has expired.
209 my $status = $self->status;
210 return $status && $status eq 'E';
215 if ( $recall->cancelled )
217 [% IF recall.cancelled %]
219 Return true if recall has been cancelled.
225 my $status = $self->status;
226 return $status && $status eq 'C';
231 if ( $recall->finished )
233 [% IF recall.finished %]
235 Return true if recall is finished and has been fulfilled.
241 my $status = $self->status;
242 return $status && $status eq 'F';
245 =head3 calc_expirationdate
247 my $expirationdate = $recall->calc_expirationdate;
248 $recall->update({ expirationdate => $expirationdate });
250 Calculate the expirationdate to set based on circulation rules and system preferences.
254 sub calc_expirationdate {
258 if ( $self->item_level_recall ) {
260 } elsif ( $self->checkout ) {
261 $item = $self->checkout->item;
264 my $branchcode = $self->patron->branchcode;
266 $branchcode = C4::Circulation::_GetCircControlBranch( $item->unblessed, $self->patron->unblessed );
269 my $rule = Koha::CirculationRules->get_effective_rule({
270 categorycode => $self->patron->categorycode,
271 branchcode => $branchcode,
272 itemtype => $item ? $item->effective_itemtype : undef,
273 rule_name => 'recall_shelf_time'
276 my $shelf_time = defined $rule ? $rule->rule_value : C4::Context->preference('RecallsMaxPickUpDelay');
278 my $expirationdate = dt_from_string->add( days => $shelf_time );
279 return $expirationdate;
282 =head3 start_transfer
284 my ( $recall, $dotransfer, $messages ) = $recall->start_transfer({ item => $item_object });
286 Set the recall as in transit.
291 my ( $self, $params ) = @_;
293 if ( $self->item_level_recall ) {
294 # already has an itemnumber
295 $self->update({ status => 'T' });
297 my $itemnumber = $params->{item}->itemnumber;
298 $self->update({ status => 'T', itemnumber => $itemnumber });
301 my ( $dotransfer, $messages ) = C4::Circulation::transferbook({ to_branch => $self->branchcode, from_branch => $self->item->holdingbranch, barcode => $self->item->barcode, trigger => 'Recall' });
303 return ( $self, $dotransfer, $messages );
306 =head3 revert_transfer
308 $recall->revert_transfer;
310 If a transfer is cancelled, revert the recall to requested.
314 sub revert_transfer {
317 if ( $self->item_level_recall ) {
318 $self->update({ status => 'R' });
320 $self->update({ status => 'R', itemnumber => undef });
328 $recall->set_waiting({
329 expirationdate => $expirationdate,
333 Set the recall as waiting and update expiration date.
334 Notify the recall requester.
339 my ( $self, $params ) = @_;
342 if ( $self->item_level_recall ) {
343 $itemnumber = $self->itemnumber;
344 $self->update({ status => 'W', waitingdate => dt_from_string, expirationdate => $params->{expirationdate} });
346 # biblio-level recall with no itemnumber. need to set itemnumber
347 $itemnumber = $params->{item}->itemnumber;
348 $self->update({ status => 'W', waitingdate => dt_from_string, expirationdate => $params->{expirationdate}, itemnumber => $itemnumber });
351 # send notice to recaller to pick up item
352 my $letter = C4::Letters::GetPreparedLetter(
353 module => 'circulation',
354 letter_code => 'PICKUP_RECALLED_ITEM',
355 branchcode => $self->branchcode,
358 biblio => $self->biblionumber,
359 borrowers => $self->borrowernumber,
360 items => $itemnumber,
361 recalls => $self->recall_id,
365 C4::Message->enqueue($letter, $self->patron->unblessed, 'email');
370 =head3 revert_waiting
372 $recall->revert_waiting;
374 Revert recall waiting status.
380 if ( $self->item_level_recall ){
381 $self->update({ status => 'R', waitingdate => undef });
383 $self->update({ status => 'R', waitingdate => undef, itemnumber => undef });
388 =head3 should_be_overdue
390 if ( $recall->should_be_overdue ) {
391 $recall->set_overdue;
394 Return true if this recall should be marked overdue
398 sub should_be_overdue {
400 if ( $self->requested and $self->checkout and dt_from_string( $self->checkout->date_due ) <= dt_from_string ) {
408 $recall->set_overdue;
410 Set a recall as overdue when the recall has been requested and the borrower who has checked out the recalled item is late to return it. This can be done manually by the library or by cronjob. The interface is either 'INTRANET' or 'COMMANDLINE' for logging purposes.
415 my ( $self, $params ) = @_;
416 my $interface = $params->{interface} || 'COMMANDLINE';
417 $self->update({ status => 'O' });
418 C4::Log::logaction( 'RECALLS', 'OVERDUE', $self->recall_id, "Recall status set to overdue", $interface ) if ( C4::Context->preference('RecallsLog') );
424 $recall->set_expired({ interface => 'INTRANET' });
426 Set a recall as expired. This may be done manually or by a cronjob, either when the borrower that placed the recall takes more than RecallsMaxPickUpDelay number of days to collect their item, or if the specified expirationdate passes. The interface is either 'INTRANET' or 'COMMANDLINE' for logging purposes.
431 my ( $self, $params ) = @_;
432 my $interface = $params->{interface} || 'COMMANDLINE';
433 $self->update({ status => 'E', old => 1, expirationdate => dt_from_string });
434 C4::Log::logaction( 'RECALLS', 'EXPIRE', $self->recall_id, "Recall expired", $interface ) if ( C4::Context->preference('RecallsLog') );
440 $recall->set_cancelled;
442 Set a recall as cancelled. This may be done manually, either by the borrower that placed the recall, or by the library.
448 $self->update({ status => 'C', old => 1, cancellationdate => dt_from_string });
449 C4::Log::logaction( 'RECALLS', 'CANCEL', $self->recall_id, "Recall cancelled", 'INTRANET' ) if ( C4::Context->preference('RecallsLog') );
455 $recall->set_finished;
457 Set a recall as finished. This should only be called when the item allocated to a recall is checked out to the borrower who requested the recall.
463 $self->update({ status => 'F', old => 1 });
464 C4::Log::logaction( 'RECALLS', 'FULFILL', $self->recall_id, "Recall fulfilled", 'INTRANET' ) if ( C4::Context->preference('RecallsLog') );