Bug 19532: (follow-up) Fix undef recall_id preventing fulfillment of recall
[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;
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 Internal methods
37
38 =cut
39
40 =head3 add_recall
41
42     my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
43         patron => $patron_object,
44         biblio => $biblio_object,
45         branchcode => $branchcode,
46         item => $item_object,
47         expirationdate => $expirationdate,
48         interface => 'OPAC',
49     });
50
51 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.
52 Interface param is either OPAC or INTRANET
53 Send a RETURN_RECALLED_ITEM notice.
54 Add statistics and logs.
55 #FIXME: Add recallnotes and priority when staff-side recalls is added
56
57 =cut
58
59 sub add_recall {
60     my ( $self, $params ) = @_;
61
62     my $patron = $params->{patron};
63     my $biblio = $params->{biblio};
64     return if ( !defined($patron) or !defined($biblio) );
65     my $branchcode = $params->{branchcode};
66     $branchcode ||= $patron->branchcode;
67     my $item = $params->{item};
68     my $itemnumber = $item ? $item->itemnumber : undef;
69     my $expirationdate = $params->{expirationdate};
70     my $interface = $params->{interface};
71
72     if ( $expirationdate ){
73         my $now = dt_from_string;
74         $expirationdate = dt_from_string($expirationdate)->set({ hour => $now->hour, minute => $now->minute, second => $now->second });
75     }
76
77     my $recall_request = Koha::Recall->new({
78         borrowernumber => $patron->borrowernumber,
79         recalldate => dt_from_string(),
80         biblionumber => $biblio->biblionumber,
81         branchcode => $branchcode,
82         status => 'R',
83         itemnumber => defined $itemnumber ? $itemnumber : undef,
84         expirationdate => $expirationdate,
85         item_level_recall => defined $itemnumber ? 1 : 0,
86     })->store;
87
88     if (defined $recall_request->recall_id){ # successful recall
89         my $recall = Koha::Recalls->find( $recall_request->recall_id );
90
91         # get checkout and adjust due date based on circulation rules
92         my $checkout = $recall->checkout;
93         my $recall_due_date_interval = Koha::CirculationRules->get_effective_rule({
94             categorycode => $checkout->patron->categorycode,
95             itemtype => $checkout->item->effective_itemtype,
96             branchcode => $branchcode,
97             rule_name => 'recall_due_date_interval',
98         });
99         my $due_interval = defined $recall_due_date_interval ? $recall_due_date_interval->rule_value : 5;
100         my $timestamp = dt_from_string( $recall->timestamp );
101         my $due_date = $timestamp->add( days => $due_interval );
102         $checkout->update({ date_due => $due_date });
103
104         # get itemnumber of most relevant checkout if a biblio-level recall
105         unless ( $recall->item_level_recall ) { $itemnumber = $checkout->itemnumber; }
106
107         # send notice to user with recalled item checked out
108         my $letter = C4::Letters::GetPreparedLetter (
109             module => 'circulation',
110             letter_code => 'RETURN_RECALLED_ITEM',
111             branchcode => $recall->branchcode,
112             tables => {
113                 biblio => $biblio->biblionumber,
114                 borrowers => $checkout->borrowernumber,
115                 items => $itemnumber,
116                 issues => $itemnumber,
117             },
118         );
119
120         C4::Message->enqueue( $letter, $checkout->patron->unblessed, 'email' );
121
122         $item = Koha::Items->find( $itemnumber );
123         # add to statistics table
124         UpdateStats({
125             branch => C4::Context->userenv->{'branch'},
126             type => 'recall',
127             itemnumber => $itemnumber,
128             borrowernumber => $recall->borrowernumber,
129             itemtype => $item->effective_itemtype,
130             ccode => $item->ccode,
131         });
132
133         # add action log
134         C4::Log::logaction( 'RECALLS', 'CREATE', $recall->recall_id, "Recall requested by borrower #" . $recall->borrowernumber, $interface ) if ( C4::Context->preference('RecallsLog') );
135
136         return ( $recall, $due_interval, $due_date );
137     }
138
139     # unable to add recall
140     return;
141 }
142
143 =head3 move_recall
144
145     my $message = Koha::Recalls->move_recall({
146         recall_id = $recall_id,
147         action => $action,
148         item => $item_object,
149         borrowernumber => $borrowernumber,
150     });
151
152 A patron is attempting to check out an item that has been recalled by another patron. If the recall is requested/overdue, they have the option of cancelling the recall. If the recall is waiting, they also have the option of reverting the waiting status.
153
154 We can also fulfill the recall here if the recall is placed by this borrower.
155
156 recall_id = ID of the recall to perform the action on
157 action = either cancel or revert
158 item = item object that the patron is attempting to check out
159 borrowernumber = borrowernumber of the patron that is attemptig to check out
160
161 =cut
162
163 sub move_recall {
164     my ( $self, $params ) = @_;
165
166     my $recall_id = $params->{recall_id};
167     my $action = $params->{action};
168     return 'no recall_id provided' if ( !defined $recall_id );
169     my $item = $params->{item};
170     my $borrowernumber = $params->{borrowernumber};
171
172     my $message = 'no action provided';
173
174     if ( $action and $action eq 'cancel' ) {
175         my $recall = Koha::Recalls->find( $recall_id );
176         $recall->set_cancelled;
177         $message = 'cancelled';
178     } elsif ( $action and $action eq 'revert' ) {
179         my $recall = Koha::Recalls->find( $recall_id );
180         $recall->revert_waiting;
181         $message = 'reverted';
182     }
183
184     if ( $message eq 'no action provided' and $item and $item->biblionumber and $borrowernumber ) {
185         # move_recall was not called to revert or cancel, but was called to fulfill
186         my $recall = Koha::Recalls->find({ borrowernumber => $borrowernumber, biblionumber => $item->biblionumber, itemnumber => [ $item->itemnumber, undef ], old => undef });
187         if ( $recall ) {
188             $recall->set_finished;
189             $message = 'fulfilled';
190         }
191     }
192
193     return $message;
194 }
195
196 =head3 _type
197
198 =cut
199
200 sub _type {
201     return 'Recall';
202 }
203
204 =head3 object_class
205
206 =cut
207
208 sub object_class {
209     return 'Koha::Recall';
210 }
211
212 1;