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