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",
50 flagsrequired => { circulate => "circulate_remaining_permissions" },
56 if ( $op eq 'cancel_reserve' and $reserve_id ) {
57 my $hold = Koha::Holds->find( $reserve_id );
59 my $cancellation_reason = $input->param('cancellation-reason');
60 $hold->cancel({ cancellation_reason => $cancellation_reason });
61 push @messages, { type => 'message', code => 'hold_cancelled' };
63 } elsif ( $op =~ m|^mark_as_lost| ) {
64 my $hold = Koha::Holds->find( $reserve_id );
65 die "wrong reserve_id" unless $hold; # This is a bit rude, but we are not supposed to get a wrong reserve_id
66 my $item = $hold->item;
67 if ( $item and C4::Context->preference('CanMarkHoldsToPullAsLost') =~ m|^allow| ) {
68 my $patron = $hold->borrower;
69 C4::Circulation::LostItem( $item->itemnumber, "pendingreserves" );
70 if ( $op eq 'mark_as_lost_and_notify' and C4::Context->preference('CanMarkHoldsToPullAsLost') eq 'allow_and_notify' ) {
71 my $library = $hold->branch;
72 my $letter = C4::Letters::GetPreparedLetter(
74 letter_code => 'CANCEL_HOLD_ON_LOST',
75 branchcode => $patron->branchcode,
76 lang => $patron->lang,
78 branches => $library->branchcode,
79 borrowers => $patron->borrowernumber,
80 items => $item->itemnumber,
81 biblio => $hold->biblionumber,
82 biblioitems => $hold->biblionumber,
83 reserves => $hold->unblessed,
87 my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress');
89 C4::Letters::EnqueueLetter(
91 borrowernumber => $patron->borrowernumber,
92 message_transport_type => 'email',
93 from_address => $admin_email_address,
96 unless ( $patron->notice_email_address ) {
97 push @messages, {type => 'alert', code => 'no_email_address', };
99 push @messages, { type => 'message', code => 'letter_enqueued' };
101 push @messages, { type => 'error', code => 'no_template_notice' };
105 if ( $item->homebranch ne $item->holdingbranch ) {
106 C4::Items::ModItemTransfer( $item->itemnumber, $item->holdingbranch, $item->homebranch, 'LostReserve' );
109 if ( my $yaml = C4::Context->preference('UpdateItemWhenLostFromHoldList') ) {
110 $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
112 eval { $assignments = YAML::Load($yaml); };
114 warn "Unable to parse UpdateItemWhenLostFromHoldList syspref : $@" if $@;
118 while ( my ( $f, $v ) = each( %$assignments ) ) {
123 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
127 } elsif ( not $item ) {
128 push @messages, { type => 'alert', code => 'hold_placed_at_biblio_level'};
129 } # else the url parameters have been modified and the user is not allowed to continue
133 my $today = dt_from_string;
136 $startdate =~ s/^\s+//;
137 $startdate =~ s/\s+$//;
138 $startdate = eval{dt_from_string( $startdate )};
140 unless ( $startdate ){
141 # changed from delivered range of 10 years-yesterday to 2 days ago-today
142 # Find two days ago for the default shelf pull start date, unless HoldsToPullStartDate sys pref is set.
143 $startdate = $today - DateTime::Duration->new( days => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL );
147 $enddate =~ s/^\s+//;
148 $enddate =~ s/\s+$//;
149 $enddate = eval{dt_from_string( $enddate )};
151 unless ( $enddate ) {
152 #similarly: calculate end date with ConfirmFutureHolds (days)
153 $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
157 my $dbh = C4::Context->dbh;
158 my $sqldatewhere = "";
159 my $startdate_iso = output_pref({ dt => $startdate, dateformat => 'iso', dateonly => 1 });
160 my $enddate_iso = output_pref({ dt => $enddate, dateformat => 'iso', dateonly => 1 });
162 $debug and warn $startdate_iso. "\n" . $enddate_iso;
164 my @query_params = ();
166 if ($startdate_iso) {
167 $sqldatewhere .= " AND reservedate >= ?";
168 push @query_params, $startdate_iso;
171 $sqldatewhere .= " AND reservedate <= ?";
172 push @query_params, $enddate_iso;
175 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
178 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
179 $sqldatewhere .= " AND damaged = 0";
183 "SELECT min(reservedate) as l_reservedate,
185 reserves.borrowernumber as borrowernumber,
187 GROUP_CONCAT(DISTINCT items.holdingbranch
188 ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
189 reserves.biblionumber,
190 reserves.branchcode as l_branch,
194 GROUP_CONCAT(DISTINCT $item_type
195 ORDER BY items.itemnumber SEPARATOR '|') l_item_type,
196 GROUP_CONCAT(DISTINCT items.location
197 ORDER BY items.itemnumber SEPARATOR '|') l_location,
198 GROUP_CONCAT(DISTINCT items.itemcallnumber
199 ORDER BY items.itemnumber SEPARATOR '|') l_itemcallnumber,
200 GROUP_CONCAT(DISTINCT items.enumchron
201 ORDER BY items.itemnumber SEPARATOR '|') l_enumchron,
202 GROUP_CONCAT(DISTINCT items.copynumber
203 ORDER BY items.itemnumber SEPARATOR '|') l_copynumber,
205 biblio.copyrightdate,
206 biblioitems.publicationyear,
212 biblioitems.editionstatement,
213 count(DISTINCT items.itemnumber) as icount,
214 count(DISTINCT reserves.borrowernumber) as rcount,
218 LEFT JOIN items ON items.biblionumber=reserves.biblionumber
219 LEFT JOIN biblio ON reserves.biblionumber=biblio.biblionumber
220 LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
221 LEFT JOIN branchtransfers ON items.itemnumber=branchtransfers.itemnumber
222 LEFT JOIN issues ON items.itemnumber=issues.itemnumber
223 LEFT JOIN borrowers ON reserves.borrowernumber=borrowers.borrowernumber
224 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 )
226 reserves.found IS NULL
228 AND (reserves.itemnumber IS NULL OR reserves.itemnumber = items.itemnumber)
229 AND items.itemnumber NOT IN (SELECT itemnumber FROM branchtransfers where datearrived IS NULL)
230 AND items.itemnumber NOT IN (SELECT itemnumber FROM reserves WHERE found IS NOT NULL AND itemnumber IS NOT NULL)
231 AND issues.itemnumber IS NULL
232 AND reserves.priority <> 0
233 AND reserves.suspend = 0
234 AND notforloan = 0 AND itemlost = 0 AND withdrawn = 0
235 AND ( circulation_rules.rule_value IS NULL OR circulation_rules.rule_value != 0 )
237 # GROUP BY reserves.biblionumber allows only items that are not checked out, else multiples occur when
238 # multiple patrons have a hold on an item
239 #FIXME "found IS NOT NULL AND itemnumber IS NOT NULL" is just a workaround: see BZ 25726
241 if (C4::Context->preference('IndependentBranches')){
242 $strsth .= " AND items.holdingbranch=? ";
243 push @query_params, C4::Context->userenv->{'branch'};
245 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
247 my $sth = $dbh->prepare($strsth);
248 $sth->execute(@query_params);
250 while ( my $data = $sth->fetchrow_hashref ) {
253 reservedate => $data->{l_reservedate},
254 firstname => $data->{firstname} || '',
255 surname => $data->{surname},
256 title => $data->{title},
257 editionstatement => $data->{editionstatement},
258 subtitle => $data->{subtitle},
259 medium => $data->{medium},
260 part_number => $data->{part_number},
261 part_name => $data->{part_name},
262 author => $data->{author},
263 borrowernumber => $data->{borrowernumber},
264 biblionumber => $data->{biblionumber},
265 holdingbranches => [split('\|', $data->{l_holdingbranch})],
266 branch => $data->{l_branch},
267 itemcallnumber => [split('\|', $data->{l_itemcallnumber})],
268 enumchron => [split('\|', $data->{l_enumchron})],
269 copyno => [split('\|', $data->{l_copynumber})],
270 count => $data->{icount},
271 rcount => $data->{rcount},
272 pullcount => $data->{icount} <= $data->{rcount} ? $data->{icount} : $data->{rcount},
273 itemTypes => [split('\|', $data->{l_item_type})],
274 locations => [split('\|', $data->{l_location})],
275 reserve_id => $data->{reserve_id},
276 holdingbranch => $data->{holdingbranch},
277 homebranch => $data->{homebranch},
278 itemnumber => $data->{itemnumber},
279 publicationyear => C4::Context->preference('marcflavour') eq "MARC21" ? $data->{copyrightdate} : $data->{publicationyear},
286 todaysdate => $today,
289 reserveloop => \@reservedata,
290 "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
291 HoldsToPullStartDate => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL,
292 HoldsToPullEndDate => C4::Context->preference('ConfirmFutureHolds') || 0,
293 messages => \@messages,
296 output_html_with_http_headers $input, $cookie, $template->output;