ef73bf99176315e8d49cfcef399c5b79b008c706
[koha.git] / Koha / Recalls.pm
1 package Koha::Recalls;
2
3 # Copyright 2020 Aleisha Amohia <aleisha@catalyst.net.nz>
4 #
5 # This file is part of Koha.
6 #
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.
11 #
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.
16 #
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>.
19
20 use Modern::Perl;
21
22 use Koha::Database;
23 use Koha::Recall;
24 use Koha::DateUtils qw( dt_from_string );
25
26 use C4::Stats qw( UpdateStats );
27
28 use base qw(Koha::Objects);
29
30 =head1 NAME
31
32 Koha::Recalls - Koha Recalls Object set class
33
34 =head1 API
35
36 =head2 Class methods
37
38 =head3 filter_by_current
39
40     my $current_recalls = $recalls->filter_by_current;
41
42 Returns a new resultset, filtering out finished recalls.
43
44 =cut
45
46 sub filter_by_current {
47     my ($self) = @_;
48
49     return $self->search(
50         {
51             status => [
52                 'in_transit',
53                 'overdue',
54                 'requested',
55                 'waiting',
56             ]
57         }
58     );
59 }
60
61 =head3 filter_by_finished
62
63     my $finished_recalls = $recalls->filter_by_finished;
64
65 Returns a new resultset, filtering out current recalls.
66
67 =cut
68
69 sub filter_by_finished {
70     my ($self) = @_;
71
72     return $self->search(
73         {
74             status => [
75                 'cancelled',
76                 'expired',
77                 'fulfilled',
78             ]
79         }
80     );
81 }
82
83 =head3 add_recall
84
85     my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
86         patron => $patron_object,
87         biblio => $biblio_object,
88         branchcode => $branchcode,
89         item => $item_object,
90         expirationdate => $expirationdate,
91         interface => 'OPAC',
92     });
93
94 Add a new requested recall. We assume at this point that a recall is allowed to be placed on this item or biblio. We are past the checks and are now doing the recall.
95 Interface param is either OPAC or INTRANET
96 Send a RETURN_RECALLED_ITEM notice.
97 Add statistics and logs.
98 #FIXME: Add recallnotes and priority when staff-side recalls is added
99
100 =cut
101
102 sub add_recall {
103     my ( $self, $params ) = @_;
104
105     my $patron = $params->{patron};
106     my $biblio = $params->{biblio};
107     return if ( !defined($patron) or !defined($biblio) );
108     my $branchcode = $params->{branchcode};
109     $branchcode ||= $patron->branchcode;
110     my $item = $params->{item};
111     my $itemnumber = $item ? $item->itemnumber : undef;
112     my $expirationdate = $params->{expirationdate};
113     my $interface = $params->{interface};
114
115     if ( $expirationdate ){
116         my $now = dt_from_string;
117         $expirationdate = dt_from_string($expirationdate)->set({ hour => $now->hour, minute => $now->minute, second => $now->second });
118     }
119
120     my $recall_request = Koha::Recall->new({
121         patron_id => $patron->borrowernumber,
122         created_date => dt_from_string(),
123         biblio_id => $biblio->biblionumber,
124         pickup_library_id => $branchcode,
125         status => 'requested',
126         item_id => defined $itemnumber ? $itemnumber : undef,
127         expiration_date => $expirationdate,
128         item_level => defined $itemnumber ? 1 : 0,
129     })->store;
130
131     if (defined $recall_request->id){ # successful recall
132         my $recall = Koha::Recalls->find( $recall_request->id );
133
134         # get checkout and adjust due date based on circulation rules
135         my $checkout = $recall->checkout;
136         my $recall_due_date_interval = Koha::CirculationRules->get_effective_rule({
137             categorycode => $checkout->patron->categorycode,
138             itemtype => $checkout->item->effective_itemtype,
139             branchcode => $branchcode,
140             rule_name => 'recall_due_date_interval',
141         });
142         my $due_interval = defined $recall_due_date_interval ? $recall_due_date_interval->rule_value : 5;
143         my $timestamp = dt_from_string( $recall->timestamp );
144         my $due_date = $timestamp->add( days => $due_interval );
145         $checkout->update({ date_due => $due_date });
146
147         # get itemnumber of most relevant checkout if a biblio-level recall
148         unless ( $recall->item_level ) { $itemnumber = $checkout->itemnumber; }
149
150         # send notice to user with recalled item checked out
151         my $letter = C4::Letters::GetPreparedLetter (
152             module => 'circulation',
153             letter_code => 'RETURN_RECALLED_ITEM',
154             branchcode => $recall->pickup_library_id,
155             tables => {
156                 biblio => $biblio->biblionumber,
157                 borrowers => $checkout->borrowernumber,
158                 items => $itemnumber,
159                 issues => $itemnumber,
160             },
161         );
162
163         C4::Message->enqueue( $letter, $checkout->patron->unblessed, 'email' );
164
165         $item = Koha::Items->find( $itemnumber );
166         # add to statistics table
167         C4::Stats::UpdateStats({
168             branch => C4::Context->userenv->{'branch'},
169             type => 'recall',
170             itemnumber => $itemnumber,
171             borrowernumber => $recall->patron_id,
172             itemtype => $item->effective_itemtype,
173             ccode => $item->ccode,
174         });
175
176         # add action log
177         C4::Log::logaction( 'RECALLS', 'CREATE', $recall->id, "Recall requested by borrower #" . $recall->patron_id, $interface ) if ( C4::Context->preference('RecallsLog') );
178
179         return ( $recall, $due_interval, $due_date );
180     }
181
182     # unable to add recall
183     return;
184 }
185
186 =head3 move_recall
187
188     my $message = Koha::Recalls->move_recall({
189         recall_id = $recall_id,
190         action => $action,
191         item => $item_object,
192         borrowernumber => $borrowernumber,
193     });
194
195 A patron is attempting to check out an item that has been recalled by another patron.
196 If the recall is requested/overdue, they have the option of cancelling the recall.
197 If the recall is waiting, they also have the option of reverting the waiting status.
198
199 We can also fulfill the recall here if the recall is placed by this borrower.
200
201 recall_id = ID of the recall to perform the action on
202 action = either cancel or revert
203 item = item object that the patron is attempting to check out
204 borrowernumber = borrowernumber of the patron that is attemptig to check out
205
206 =cut
207
208 sub move_recall {
209     my ( $self, $params ) = @_;
210
211     my $recall_id = $params->{recall_id};
212     my $action = $params->{action};
213     return 'no recall_id provided' if ( !defined $recall_id );
214     my $item = $params->{item};
215     my $borrowernumber = $params->{borrowernumber};
216
217     my $message = 'no action provided';
218
219     if ( $action and $action eq 'cancel' ) {
220         my $recall = Koha::Recalls->find( $recall_id );
221         $recall->set_cancelled;
222         $message = 'cancelled';
223     } elsif ( $action and $action eq 'revert' ) {
224         my $recall = Koha::Recalls->find( $recall_id );
225         $recall->revert_waiting;
226         $message = 'reverted';
227     }
228
229     if ( $message eq 'no action provided' and $item and $item->biblionumber and $borrowernumber ) {
230         # move_recall was not called to revert or cancel, but was called to fulfill
231         my $recall = Koha::Recalls->search(
232             {
233                 patron_id => $borrowernumber,
234                 biblio_id => $item->biblionumber,
235                 item_id   => [ $item->itemnumber, undef ],
236                 completed => 0,
237             },
238             { order_by => { -asc => 'created_date' } }
239         )->next;
240         if ( $recall ) {
241             $recall->set_fulfilled;
242             $message = 'fulfilled';
243         }
244     }
245
246     return $message;
247 }
248
249 =head2 Internal methods
250
251 =head3 _type
252
253 =cut
254
255 sub _type {
256     return 'Recall';
257 }
258
259 =head3 object_class
260
261 =cut
262
263 sub object_class {
264     return 'Koha::Recall';
265 }
266
267 1;