Merge branch 'new/security-release-19.11.09' into 19.11.x
[koha.git] / circ / pendingreserves.pl
blob8ea8d25fde4814394621a173fbe6c8be5596e54e
1 #!/usr/bin/perl
3 # Copyright 2000-2002 Katipo Communications
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>.
20 use Modern::Perl;
22 use constant PULL_INTERVAL => 2;
24 use C4::Context;
25 use C4::Output;
26 use CGI qw ( -utf8 );
27 use C4::Auth;
28 use C4::Debug;
29 use C4::Items qw( ModItem ModItemTransfer );
30 use C4::Reserves qw( ModReserveCancelAll );
31 use Koha::Biblios;
32 use Koha::DateUtils;
33 use Koha::Holds;
34 use DateTime::Duration;
36 my $input = new CGI;
37 my $startdate = $input->param('from');
38 my $enddate = $input->param('to');
39 my $theme = $input->param('theme'); # only used if allowthemeoverride is set
40 my $op = $input->param('op') || '';
41 my $borrowernumber = $input->param('borrowernumber');
42 my $reserve_id = $input->param('reserve_id');
44 my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
46 template_name => "circ/pendingreserves.tt",
47 query => $input,
48 type => "intranet",
49 flagsrequired => { circulate => "circulate_remaining_permissions" },
50 debug => 1,
54 my @messages;
55 if ( $op eq 'cancel_reserve' and $reserve_id ) {
56 my $hold = Koha::Holds->find( $reserve_id );
57 if ( $hold ) {
58 $hold->cancel;
59 push @messages, { type => 'message', code => 'hold_cancelled' };
61 } elsif ( $op =~ m|^mark_as_lost| ) {
62 my $hold = Koha::Holds->find( $reserve_id );
63 die "wrong reserve_id" unless $hold; # This is a bit rude, but we are not supposed to get a wrong reserve_id
64 my $item = $hold->item;
65 if ( $item and C4::Context->preference('CanMarkHoldsToPullAsLost') =~ m|^allow| ) {
66 my $patron = $hold->borrower;
67 C4::Circulation::LostItem( $item->itemnumber, "pendingreserves" );
68 if ( $op eq 'mark_as_lost_and_notify' and C4::Context->preference('CanMarkHoldsToPullAsLost') eq 'allow_and_notify' ) {
69 my $library = $hold->branch;
70 my $letter = C4::Letters::GetPreparedLetter(
71 module => 'reserves',
72 letter_code => 'CANCEL_HOLD_ON_LOST',
73 branchcode => $patron->branchcode,
74 lang => $patron->lang,
75 tables => {
76 branches => $library->branchcode,
77 borrowers => $patron->borrowernumber,
78 items => $item->itemnumber,
79 biblio => $hold->biblionumber,
80 biblioitems => $hold->biblionumber,
81 reserves => $hold->unblessed,
84 if ( $letter ) {
85 my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress');
87 C4::Letters::EnqueueLetter(
88 { letter => $letter,
89 borrowernumber => $patron->borrowernumber,
90 message_transport_type => 'email',
91 from_address => $admin_email_address,
94 unless ( $patron->notice_email_address ) {
95 push @messages, {type => 'alert', code => 'no_email_address', };
97 push @messages, { type => 'message', code => 'letter_enqueued' };
98 } else {
99 push @messages, { type => 'error', code => 'no_template_notice' };
102 $hold->cancel;
103 if ( $item->homebranch ne $item->holdingbranch ) {
104 C4::Items::ModItemTransfer( $item->itemnumber, $item->holdingbranch, $item->homebranch );
107 if ( my $yaml = C4::Context->preference('UpdateItemWhenLostFromHoldList') ) {
108 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
109 my $assignments;
110 eval { $assignments = YAML::Load($yaml); };
111 if ($@) {
112 warn "Unable to parse UpdateItemWhenLostFromHoldList syspref : $@" if $@;
114 else {
115 eval {
116 C4::Items::ModItem( $assignments, undef, $item->itemnumber );
118 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
122 } elsif ( not $item ) {
123 push @messages, { type => 'alert', code => 'hold_placed_at_biblio_level'};
124 } # else the url parameters have been modified and the user is not allowed to continue
128 my $today = dt_from_string;
130 if ( $startdate ) {
131 $startdate =~ s/^\s+//;
132 $startdate =~ s/\s+$//;
133 $startdate = eval{dt_from_string( $startdate )};
135 unless ( $startdate ){
136 # changed from delivered range of 10 years-yesterday to 2 days ago-today
137 # Find two days ago for the default shelf pull start date, unless HoldsToPullStartDate sys pref is set.
138 $startdate = $today - DateTime::Duration->new( days => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL );
141 if ( $enddate ) {
142 $enddate =~ s/^\s+//;
143 $enddate =~ s/\s+$//;
144 $enddate = eval{dt_from_string( $enddate )};
146 unless ( $enddate ) {
147 #similarly: calculate end date with ConfirmFutureHolds (days)
148 $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
151 my @reservedata;
152 my $dbh = C4::Context->dbh;
153 my $sqldatewhere = "";
154 my $startdate_iso = output_pref({ dt => $startdate, dateformat => 'iso', dateonly => 1 });
155 my $enddate_iso = output_pref({ dt => $enddate, dateformat => 'iso', dateonly => 1 });
157 $debug and warn $startdate_iso. "\n" . $enddate_iso;
159 my @query_params = ();
161 if ($startdate_iso) {
162 $sqldatewhere .= " AND reservedate >= ?";
163 push @query_params, $startdate_iso;
165 if ($enddate_iso) {
166 $sqldatewhere .= " AND reservedate <= ?";
167 push @query_params, $enddate_iso;
170 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
172 # Bug 21320
173 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
174 $sqldatewhere .= " AND damaged = 0";
177 my $strsth =
178 "SELECT min(reservedate) as l_reservedate,
179 reserves.reserve_id,
180 reserves.borrowernumber as borrowernumber,
182 GROUP_CONCAT(DISTINCT items.holdingbranch
183 ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
184 reserves.biblionumber,
185 reserves.branchcode as l_branch,
186 reserves.itemnumber,
187 items.holdingbranch,
188 items.homebranch,
189 GROUP_CONCAT(DISTINCT $item_type
190 ORDER BY items.itemnumber SEPARATOR '|') l_item_type,
191 GROUP_CONCAT(DISTINCT items.location
192 ORDER BY items.itemnumber SEPARATOR '|') l_location,
193 GROUP_CONCAT(DISTINCT items.itemcallnumber
194 ORDER BY items.itemnumber SEPARATOR '|') l_itemcallnumber,
195 GROUP_CONCAT(DISTINCT items.enumchron
196 ORDER BY items.itemnumber SEPARATOR '|') l_enumchron,
197 GROUP_CONCAT(DISTINCT items.copynumber
198 ORDER BY items.itemnumber SEPARATOR '|') l_copynumber,
199 biblio.title,
200 biblio.subtitle,
201 biblio.medium,
202 biblio.part_number,
203 biblio.part_name,
204 biblio.author,
205 count(DISTINCT items.itemnumber) as icount,
206 count(DISTINCT reserves.borrowernumber) as rcount,
207 borrowers.firstname,
208 borrowers.surname
209 FROM reserves
210 LEFT JOIN items ON items.biblionumber=reserves.biblionumber
211 LEFT JOIN biblio ON reserves.biblionumber=biblio.biblionumber
212 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
213 LEFT JOIN branchtransfers ON items.itemnumber=branchtransfers.itemnumber
214 LEFT JOIN issues ON items.itemnumber=issues.itemnumber
215 LEFT JOIN borrowers ON reserves.borrowernumber=borrowers.borrowernumber
216 LEFT JOIN circulation_rules ON ( items.itype=circulation_rules.itemtype AND rule_name = 'holdallowed' AND circulation_rules.branchcode IS NULL AND circulation_rules.categorycode IS NULL )
217 WHERE
218 reserves.found IS NULL
219 $sqldatewhere
220 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = items.itemnumber)
221 AND items.itemnumber NOT IN (SELECT itemnumber FROM branchtransfers where datearrived IS NULL)
222 AND items.itemnumber NOT IN (SELECT itemnumber FROM reserves WHERE found IS NOT NULL AND itemnumber IS NOT NULL)
223 AND issues.itemnumber IS NULL
224 AND reserves.priority <> 0
225 AND reserves.suspend = 0
226 AND notforloan = 0 AND itemlost = 0 AND withdrawn = 0
227 AND ( circulation_rules.rule_value IS NULL OR circulation_rules.rule_value != 0 )
229 # GROUP BY reserves.biblionumber allows only items that are not checked out, else multiples occur when
230 # multiple patrons have a hold on an item
231 #FIXME "found IS NOT NULL AND itemnumber IS NOT NULL" is just a workaround: see BZ 25726
233 if (C4::Context->preference('IndependentBranches')){
234 $strsth .= " AND items.holdingbranch=? ";
235 push @query_params, C4::Context->userenv->{'branch'};
237 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
239 my $sth = $dbh->prepare($strsth);
240 $sth->execute(@query_params);
242 while ( my $data = $sth->fetchrow_hashref ) {
243 push(
244 @reservedata, {
245 reservedate => $data->{l_reservedate},
246 firstname => $data->{firstname} || '',
247 surname => $data->{surname},
248 title => $data->{title},
249 subtitle => $data->{subtitle},
250 medium => $data->{medium},
251 part_number => $data->{part_number},
252 part_name => $data->{part_name},
253 author => $data->{author},
254 borrowernumber => $data->{borrowernumber},
255 biblionumber => $data->{biblionumber},
256 holdingbranches => [split('\|', $data->{l_holdingbranch})],
257 branch => $data->{l_branch},
258 itemcallnumber => [split('\|', $data->{l_itemcallnumber})],
259 enumchron => [split('\|', $data->{l_enumchron})],
260 copyno => [split('\|', $data->{l_copynumber})],
261 count => $data->{icount},
262 rcount => $data->{rcount},
263 pullcount => $data->{icount} <= $data->{rcount} ? $data->{icount} : $data->{rcount},
264 itemTypes => [split('\|', $data->{l_item_type})],
265 locations => [split('\|', $data->{l_location})],
266 reserve_id => $data->{reserve_id},
267 holdingbranch => $data->{holdingbranch},
268 homebranch => $data->{homebranch},
269 itemnumber => $data->{itemnumber},
273 $sth->finish;
275 $template->param(
276 todaysdate => $today,
277 from => $startdate,
278 to => $enddate,
279 reserveloop => \@reservedata,
280 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
281 HoldsToPullStartDate => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL,
282 HoldsToPullEndDate => C4::Context->preference('ConfirmFutureHolds') || 0,
283 messages => \@messages,
286 output_html_with_http_headers $input, $cookie, $template->output;