Bug 19532: Make recalls.status an ENUM
[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 add_recall
39
40     my ( $recall, $due_interval, $due_date ) = Koha::Recalls->add_recall({
41         patron => $patron_object,
42         biblio => $biblio_object,
43         branchcode => $branchcode,
44         item => $item_object,
45         expirationdate => $expirationdate,
46         interface => 'OPAC',
47     });
48
49 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.
50 Interface param is either OPAC or INTRANET
51 Send a RETURN_RECALLED_ITEM notice.
52 Add statistics and logs.
53 #FIXME: Add recallnotes and priority when staff-side recalls is added
54
55 =cut
56
57 sub add_recall {
58     my ( $self, $params ) = @_;
59
60     my $patron = $params->{patron};
61     my $biblio = $params->{biblio};
62     return if ( !defined($patron) or !defined($biblio) );
63     my $branchcode = $params->{branchcode};
64     $branchcode ||= $patron->branchcode;
65     my $item = $params->{item};
66     my $itemnumber = $item ? $item->itemnumber : undef;
67     my $expirationdate = $params->{expirationdate};
68     my $interface = $params->{interface};
69
70     if ( $expirationdate ){
71         my $now = dt_from_string;
72         $expirationdate = dt_from_string($expirationdate)->set({ hour => $now->hour, minute => $now->minute, second => $now->second });
73     }
74
75     my $recall_request = Koha::Recall->new({
76         borrowernumber => $patron->borrowernumber,
77         recalldate => dt_from_string(),
78         biblionumber => $biblio->biblionumber,
79         branchcode => $branchcode,
80         status => 'requested',
81         itemnumber => defined $itemnumber ? $itemnumber : undef,
82         expirationdate => $expirationdate,
83         item_level_recall => defined $itemnumber ? 1 : 0,
84     })->store;
85
86     if (defined $recall_request->recall_id){ # successful recall
87         my $recall = Koha::Recalls->find( $recall_request->recall_id );
88
89         # get checkout and adjust due date based on circulation rules
90         my $checkout = $recall->checkout;
91         my $recall_due_date_interval = Koha::CirculationRules->get_effective_rule({
92             categorycode => $checkout->patron->categorycode,
93             itemtype => $checkout->item->effective_itemtype,
94             branchcode => $branchcode,
95             rule_name => 'recall_due_date_interval',
96         });
97         my $due_interval = defined $recall_due_date_interval ? $recall_due_date_interval->rule_value : 5;
98         my $timestamp = dt_from_string( $recall->timestamp );
99         my $due_date = $timestamp->add( days => $due_interval );
100         $checkout->update({ date_due => $due_date });
101
102         # get itemnumber of most relevant checkout if a biblio-level recall
103         unless ( $recall->item_level_recall ) { $itemnumber = $checkout->itemnumber; }
104
105         # send notice to user with recalled item checked out
106         my $letter = C4::Letters::GetPreparedLetter (
107             module => 'circulation',
108             letter_code => 'RETURN_RECALLED_ITEM',
109             branchcode => $recall->branchcode,
110             tables => {
111                 biblio => $biblio->biblionumber,
112                 borrowers => $checkout->borrowernumber,
113                 items => $itemnumber,
114                 issues => $itemnumber,
115             },
116         );
117
118         C4::Message->enqueue( $letter, $checkout->patron->unblessed, 'email' );
119
120         $item = Koha::Items->find( $itemnumber );
121         # add to statistics table
122         UpdateStats({
123             branch => C4::Context->userenv->{'branch'},
124             type => 'recall',
125             itemnumber => $itemnumber,
126             borrowernumber => $recall->borrowernumber,
127             itemtype => $item->effective_itemtype,
128             ccode => $item->ccode,
129         });
130
131         # add action log
132         C4::Log::logaction( 'RECALLS', 'CREATE', $recall->recall_id, "Recall requested by borrower #" . $recall->borrowernumber, $interface ) if ( C4::Context->preference('RecallsLog') );
133
134         return ( $recall, $due_interval, $due_date );
135     }
136
137     # unable to add recall
138     return;
139 }
140
141 =head3 move_recall
142
143     my $message = Koha::Recalls->move_recall({
144         recall_id = $recall_id,
145         action => $action,
146         item => $item_object,
147         borrowernumber => $borrowernumber,
148     });
149
150 A patron is attempting to check out an item that has been recalled by another patron.
151 If the recall is requested/overdue, they have the option of cancelling the recall.
152 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->search(
187             {
188                 borrowernumber => $borrowernumber,
189                 biblionumber   => $item->biblionumber,
190                 itemnumber     => [ $item->itemnumber, undef ],
191                 old            => undef
192             },
193             { order_by => { -asc => 'recalldate' } }
194         )->next;
195         if ( $recall ) {
196             $recall->set_fulfilled;
197             $message = 'fulfilled';
198         }
199     }
200
201     return $message;
202 }
203
204 =head2 Internal methods
205
206 =head3 _type
207
208 =cut
209
210 sub _type {
211     return 'Recall';
212 }
213
214 =head3 object_class
215
216 =cut
217
218 sub object_class {
219     return 'Koha::Recall';
220 }
221
222 1;