Bug 30952: Fix color of navbar toggle on small screen
[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             {
169                 branch         => C4::Context->userenv->{'branch'},
170                 type           => 'recall',
171                 itemnumber     => $itemnumber,
172                 borrowernumber => $recall->patron_id,
173                 itemtype       => $item->effective_itemtype,
174                 ccode          => $item->ccode,
175                 categorycode   => $checkout->patron->categorycode
176             }
177         );
178
179         # add action log
180         C4::Log::logaction( 'RECALLS', 'CREATE', $recall->id, "Recall requested by borrower #" . $recall->patron_id, $interface ) if ( C4::Context->preference('RecallsLog') );
181
182         return ( $recall, $due_interval, $due_date );
183     }
184
185     # unable to add recall
186     return;
187 }
188
189 =head3 move_recall
190
191     my $message = Koha::Recalls->move_recall({
192         recall_id = $recall_id,
193         action => $action,
194         item => $item_object,
195         borrowernumber => $borrowernumber,
196     });
197
198 A patron is attempting to check out an item that has been recalled by another patron.
199 If the recall is requested/overdue, they have the option of cancelling the recall.
200 If the recall is waiting, they also have the option of reverting the waiting status.
201
202 We can also fulfill the recall here if the recall is placed by this borrower.
203
204 recall_id = ID of the recall to perform the action on
205 action = either cancel or revert
206 item = item object that the patron is attempting to check out
207 borrowernumber = borrowernumber of the patron that is attemptig to check out
208
209 =cut
210
211 sub move_recall {
212     my ( $self, $params ) = @_;
213
214     my $recall_id = $params->{recall_id};
215     my $action = $params->{action};
216     return 'no recall_id provided' if ( !defined $recall_id );
217     my $item = $params->{item};
218     my $borrowernumber = $params->{borrowernumber};
219
220     my $message = 'no action provided';
221
222     if ( $action and $action eq 'cancel' ) {
223         my $recall = Koha::Recalls->find( $recall_id );
224         $recall->set_cancelled;
225         $message = 'cancelled';
226     } elsif ( $action and $action eq 'revert' ) {
227         my $recall = Koha::Recalls->find( $recall_id );
228         $recall->revert_waiting;
229         $message = 'reverted';
230     }
231
232     if ( $message eq 'no action provided' and $item and $item->biblionumber and $borrowernumber ) {
233         # move_recall was not called to revert or cancel, but was called to fulfill
234         my $recall = Koha::Recalls->search(
235             {
236                 patron_id => $borrowernumber,
237                 biblio_id => $item->biblionumber,
238                 item_id   => [ $item->itemnumber, undef ],
239                 completed => 0,
240             },
241             { order_by => { -asc => 'created_date' } }
242         )->next;
243         if ( $recall ) {
244             $recall->set_fulfilled;
245             $message = 'fulfilled';
246         }
247     }
248
249     return $message;
250 }
251
252 =head2 Internal methods
253
254 =head3 _type
255
256 =cut
257
258 sub _type {
259     return 'Recall';
260 }
261
262 =head3 object_class
263
264 =cut
265
266 sub object_class {
267     return 'Koha::Recall';
268 }
269
270 1;