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 return $self->status eq 'requested';
150 if ( $recall->waiting )
152 [% IF recall.waiting %]
154 Return true if recall is awaiting pickup.
160 return $self->status eq 'waiting';
165 if ( $recall->overdue )
167 [% IF recall.overdue %]
169 Return true if recall is overdue to be returned.
175 return $self->status eq 'overdue';
180 if ( $recall->in_transit )
182 [% IF recall.in_transit %]
184 Return true if recall is in transit.
190 return $self->status eq 'in_transit';
195 if ( $recall->expired )
197 [% IF recall.expired %]
199 Return true if recall has expired.
205 return $self->status eq 'expired';
210 if ( $recall->cancelled )
212 [% IF recall.cancelled %]
214 Return true if recall has been cancelled.
220 return $self->status eq 'cancelled';
225 if ( $recall->fulfilled )
227 [% IF recall.fulfilled %]
229 Return true if the recall has been fulfilled.
235 return $self->status eq 'fulfilled';
238 =head3 calc_expirationdate
240 my $expirationdate = $recall->calc_expirationdate;
241 $recall->update({ expirationdate => $expirationdate });
243 Calculate the expirationdate to set based on circulation rules and system preferences.
247 sub calc_expirationdate {
251 if ( $self->item_level_recall ) {
253 } elsif ( $self->checkout ) {
254 $item = $self->checkout->item;
257 my $branchcode = $self->patron->branchcode;
259 $branchcode = C4::Circulation::_GetCircControlBranch( $item->unblessed, $self->patron->unblessed );
262 my $rule = Koha::CirculationRules->get_effective_rule({
263 categorycode => $self->patron->categorycode,
264 branchcode => $branchcode,
265 itemtype => $item ? $item->effective_itemtype : undef,
266 rule_name => 'recall_shelf_time'
269 my $shelf_time = defined $rule ? $rule->rule_value : C4::Context->preference('RecallsMaxPickUpDelay');
271 my $expirationdate = dt_from_string->add( days => $shelf_time );
272 return $expirationdate;
275 =head3 start_transfer
277 my ( $recall, $dotransfer, $messages ) = $recall->start_transfer({ item => $item_object });
279 Set the recall as in transit.
284 my ( $self, $params ) = @_;
286 if ( $self->item_level_recall ) {
287 # already has an itemnumber
288 $self->update({ status => 'in_transit' });
290 my $itemnumber = $params->{item}->itemnumber;
291 $self->update({ status => 'in_transit', itemnumber => $itemnumber });
294 my ( $dotransfer, $messages ) = C4::Circulation::transferbook({ to_branch => $self->branchcode, from_branch => $self->item->holdingbranch, barcode => $self->item->barcode, trigger => 'Recall' });
296 return ( $self, $dotransfer, $messages );
299 =head3 revert_transfer
301 $recall->revert_transfer;
303 If a transfer is cancelled, revert the recall to requested.
307 sub revert_transfer {
310 if ( $self->item_level_recall ) {
311 $self->update({ status => 'requested' });
313 $self->update({ status => 'requested', itemnumber => undef });
321 $recall->set_waiting(
322 { expirationdate => $expirationdate,
327 Set the recall as waiting and update expiration date.
328 Notify the recall requester.
333 my ( $self, $params ) = @_;
336 if ( $self->item_level_recall ) {
337 $itemnumber = $self->itemnumber;
338 $self->update({ status => 'waiting', waitingdate => dt_from_string, expirationdate => $params->{expirationdate} });
340 # biblio-level recall with no itemnumber. need to set itemnumber
341 $itemnumber = $params->{item}->itemnumber;
342 $self->update({ status => 'waiting', waitingdate => dt_from_string, expirationdate => $params->{expirationdate}, itemnumber => $itemnumber });
345 # send notice to recaller to pick up item
346 my $letter = C4::Letters::GetPreparedLetter(
347 module => 'circulation',
348 letter_code => 'PICKUP_RECALLED_ITEM',
349 branchcode => $self->branchcode,
352 biblio => $self->biblionumber,
353 borrowers => $self->borrowernumber,
354 items => $itemnumber,
355 recalls => $self->recall_id,
359 C4::Message->enqueue($letter, $self->patron->unblessed, 'email');
364 =head3 revert_waiting
366 $recall->revert_waiting;
368 Revert recall waiting status.
374 if ( $self->item_level_recall ){
375 $self->update({ status => 'requested', waitingdate => undef });
377 $self->update({ status => 'requested', waitingdate => undef, itemnumber => undef });
382 =head3 should_be_overdue
384 if ( $recall->should_be_overdue ) {
385 $recall->set_overdue;
388 Return true if this recall should be marked overdue
392 sub should_be_overdue {
394 if ( $self->requested and $self->checkout and dt_from_string( $self->checkout->date_due ) <= dt_from_string ) {
402 $recall->set_overdue;
404 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.
409 my ( $self, $params ) = @_;
410 my $interface = $params->{interface} || 'COMMANDLINE';
411 $self->update({ status => 'overdue' });
412 C4::Log::logaction( 'RECALLS', 'OVERDUE', $self->recall_id, "Recall status set to overdue", $interface ) if ( C4::Context->preference('RecallsLog') );
418 $recall->set_expired({ interface => 'INTRANET' });
420 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.
425 my ( $self, $params ) = @_;
426 my $interface = $params->{interface} || 'COMMANDLINE';
427 $self->update({ status => 'expired', old => 1, expirationdate => dt_from_string });
428 C4::Log::logaction( 'RECALLS', 'EXPIRE', $self->recall_id, "Recall expired", $interface ) if ( C4::Context->preference('RecallsLog') );
434 $recall->set_cancelled;
436 Set a recall as cancelled. This may be done manually, either by the borrower that placed the recall, or by the library.
442 $self->update({ status => 'cancelled', old => 1, cancellationdate => dt_from_string });
443 C4::Log::logaction( 'RECALLS', 'CANCEL', $self->recall_id, "Recall cancelled", 'INTRANET' ) if ( C4::Context->preference('RecallsLog') );
449 $recall->set_fulfilled;
451 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.
457 $self->update({ status => 'fulfilled', old => 1 });
458 C4::Log::logaction( 'RECALLS', 'FULFILL', $self->recall_id, "Recall fulfilled", 'INTRANET' ) if ( C4::Context->preference('RecallsLog') );