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>.
22 use constant PULL_INTERVAL => 2;
29 use C4::Items qw( ModItemTransfer );
30 use C4::Reserves qw( ModReserveCancelAll );
34 use DateTime::Duration;
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",
49 flagsrequired => { circulate => "circulate_remaining_permissions" },
55 if ( $op eq 'cancel_reserve' and $reserve_id ) {
56 my $hold = Koha::Holds->find( $reserve_id );
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(
72 letter_code => 'CANCEL_HOLD_ON_LOST',
73 branchcode => $patron->branchcode,
74 lang => $patron->lang,
76 branches => $library->branchcode,
77 borrowers => $patron->borrowernumber,
78 items => $item->itemnumber,
79 biblio => $hold->biblionumber,
80 biblioitems => $hold->biblionumber,
81 reserves => $hold->unblessed,
85 my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress');
87 C4::Letters::EnqueueLetter(
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' };
99 push @messages, { type => 'error', code => 'no_template_notice' };
103 if ( $item->homebranch ne $item->holdingbranch ) {
104 C4::Items::ModItemTransfer( $item->itemnumber, $item->holdingbranch, $item->homebranch, 'LostReserve' );
107 if ( my $yaml = C4::Context->preference('UpdateItemWhenLostFromHoldList') ) {
108 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
110 eval { $assignments = YAML::Load($yaml); };
112 warn "Unable to parse UpdateItemWhenLostFromHoldList syspref : $@" if $@;
116 while ( my ( $f, $v ) = each( %$assignments ) ) {
121 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
125 } elsif ( not $item ) {
126 push @messages, { type => 'alert', code => 'hold_placed_at_biblio_level'};
127 } # else the url parameters have been modified and the user is not allowed to continue
131 my $today = dt_from_string;
134 $startdate =~ s/^\s+//;
135 $startdate =~ s/\s+$//;
136 $startdate = eval{dt_from_string( $startdate )};
138 unless ( $startdate ){
139 # changed from delivered range of 10 years-yesterday to 2 days ago-today
140 # Find two days ago for the default shelf pull start date, unless HoldsToPullStartDate sys pref is set.
141 $startdate = $today - DateTime::Duration->new( days => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL );
145 $enddate =~ s/^\s+//;
146 $enddate =~ s/\s+$//;
147 $enddate = eval{dt_from_string( $enddate )};
149 unless ( $enddate ) {
150 #similarly: calculate end date with ConfirmFutureHolds (days)
151 $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
155 my $dbh = C4::Context->dbh;
156 my $sqldatewhere = "";
157 my $startdate_iso = output_pref({ dt => $startdate, dateformat => 'iso', dateonly => 1 });
158 my $enddate_iso = output_pref({ dt => $enddate, dateformat => 'iso', dateonly => 1 });
160 $debug and warn $startdate_iso. "\n" . $enddate_iso;
162 my @query_params = ();
164 if ($startdate_iso) {
165 $sqldatewhere .= " AND reservedate >= ?";
166 push @query_params, $startdate_iso;
169 $sqldatewhere .= " AND reservedate <= ?";
170 push @query_params, $enddate_iso;
173 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
176 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
177 $sqldatewhere .= " AND damaged = 0";
181 "SELECT min(reservedate) as l_reservedate,
183 reserves.borrowernumber as borrowernumber,
185 GROUP_CONCAT(DISTINCT items.holdingbranch
186 ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
187 reserves.biblionumber,
188 reserves.branchcode as l_branch,
192 GROUP_CONCAT(DISTINCT $item_type
193 ORDER BY items.itemnumber SEPARATOR '|') l_item_type,
194 GROUP_CONCAT(DISTINCT items.location
195 ORDER BY items.itemnumber SEPARATOR '|') l_location,
196 GROUP_CONCAT(DISTINCT items.itemcallnumber
197 ORDER BY items.itemnumber SEPARATOR '|') l_itemcallnumber,
198 GROUP_CONCAT(DISTINCT items.enumchron
199 ORDER BY items.itemnumber SEPARATOR '|') l_enumchron,
200 GROUP_CONCAT(DISTINCT items.copynumber
201 ORDER BY items.itemnumber SEPARATOR '|') l_copynumber,
202 GROUP_CONCAT(DISTINCT items.barcode
203 ORDER BY items.itemnumber SEPARATOR '|') l_barcode,
210 biblioitems.editionstatement,
211 count(DISTINCT items.itemnumber) as icount,
212 count(DISTINCT reserves.borrowernumber) as rcount,
216 LEFT JOIN items ON items.biblionumber=reserves.biblionumber
217 LEFT JOIN biblio ON reserves.biblionumber=biblio.biblionumber
218 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
219 LEFT JOIN branchtransfers ON items.itemnumber=branchtransfers.itemnumber
220 LEFT JOIN issues ON items.itemnumber=issues.itemnumber
221 LEFT JOIN borrowers ON reserves.borrowernumber=borrowers.borrowernumber
222 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 )
224 reserves.found IS NULL
226 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = items.itemnumber)
227 AND items.itemnumber NOT IN (SELECT itemnumber FROM branchtransfers where datearrived IS NULL)
228 AND items.itemnumber NOT IN (SELECT itemnumber FROM reserves WHERE found IS NOT NULL AND itemnumber IS NOT NULL)
229 AND issues.itemnumber IS NULL
230 AND reserves.priority <> 0
231 AND reserves.suspend = 0
232 AND notforloan = 0 AND itemlost = 0 AND withdrawn = 0
233 AND ( circulation_rules.rule_value IS NULL OR circulation_rules.rule_value != 0 )
235 # GROUP BY reserves.biblionumber allows only items that are not checked out, else multiples occur when
236 # multiple patrons have a hold on an item
237 #FIXME "found IS NOT NULL AND itemnumber IS NOT NULL" is just a workaround: see BZ 25726
239 if (C4::Context->preference('IndependentBranches')){
240 $strsth .= " AND items.holdingbranch=? ";
241 push @query_params, C4::Context->userenv->{'branch'};
243 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
245 my $sth = $dbh->prepare($strsth);
246 $sth->execute(@query_params);
248 while ( my $data = $sth->fetchrow_hashref ) {
251 reservedate => $data->{l_reservedate},
252 firstname => $data->{firstname} || '',
253 surname => $data->{surname},
254 title => $data->{title},
255 editionstatement => $data->{editionstatement},
256 subtitle => $data->{subtitle},
257 medium => $data->{medium},
258 part_number => $data->{part_number},
259 part_name => $data->{part_name},
260 author => $data->{author},
261 borrowernumber => $data->{borrowernumber},
262 biblionumber => $data->{biblionumber},
263 holdingbranches => [split('\|', $data->{l_holdingbranch})],
264 branch => $data->{l_branch},
265 itemcallnumber => [split('\|', $data->{l_itemcallnumber})],
266 enumchron => [split('\|', $data->{l_enumchron})],
267 copyno => [split('\|', $data->{l_copynumber})],
268 barcode => [split('\|', $data->{l_barcode})],
269 count => $data->{icount},
270 rcount => $data->{rcount},
271 pullcount => $data->{icount} <= $data->{rcount} ? $data->{icount} : $data->{rcount},
272 itemTypes => [split('\|', $data->{l_item_type})],
273 locations => [split('\|', $data->{l_location})],
274 reserve_id => $data->{reserve_id},
275 holdingbranch => $data->{holdingbranch},
276 homebranch => $data->{homebranch},
277 itemnumber => $data->{itemnumber},
284 todaysdate => $today,
287 reserveloop => \@reservedata,
288 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
289 HoldsToPullStartDate => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL,
290 HoldsToPullEndDate => C4::Context->preference('ConfirmFutureHolds') || 0,
291 messages => \@messages,
294 output_html_with_http_headers $input, $cookie, $template->output;