Bug 14004: Add ability to temporarily disable JS/CSS sysprefs
[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         flagsrequired   => { circulate => "circulate_remaining_permissions" },
50         debug           => 1,
51     }
52 );
53
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' };
60     }
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,
82                 },
83             );
84             if ( $letter ) {
85                 my $admin_email_address = $library->branchemail || C4::Context->preference('KohaAdminEmailAddress');
86
87                 C4::Letters::EnqueueLetter(
88                     {   letter                 => $letter,
89                         borrowernumber         => $patron->borrowernumber,
90                         message_transport_type => 'email',
91                         from_address           => $admin_email_address,
92                     }
93                 );
94                 unless ( $patron->notice_email_address ) {
95                     push @messages, {type => 'alert', code => 'no_email_address', };
96                 }
97                 push @messages, { type => 'message', code => 'letter_enqueued' };
98             } else {
99                 push @messages, { type => 'error', code => 'no_template_notice' };
100             }
101         }
102         $hold->cancel;
103         if ( $item->homebranch ne $item->holdingbranch ) {
104             C4::Items::ModItemTransfer( $item->itemnumber, $item->holdingbranch, $item->homebranch );
105         }
106
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 $@;
113             }
114             else {
115                 eval {
116                     C4::Items::ModItem( $assignments, undef, $item->itemnumber );
117                 };
118                 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
119             }
120         }
121
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
125 }
126
127
128 my $today = dt_from_string;
129
130 if ( $startdate ) {
131     $startdate =~ s/^\s+//;
132     $startdate =~ s/\s+$//;
133     $startdate = eval{dt_from_string( $startdate )};
134 }
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 );
139 }
140
141 if ( $enddate ) {
142     $enddate =~ s/^\s+//;
143     $enddate =~ s/\s+$//;
144     $enddate = eval{dt_from_string( $enddate )};
145 }
146 unless ( $enddate ) {
147     #similarly: calculate end date with ConfirmFutureHolds (days)
148     $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
149 }
150
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 });
156
157 $debug and warn $startdate_iso. "\n" . $enddate_iso;
158
159 my @query_params = ();
160
161 if ($startdate_iso) {
162     $sqldatewhere .= " AND reservedate >= ?";
163     push @query_params, $startdate_iso;
164 }
165 if ($enddate_iso) {
166     $sqldatewhere .= " AND reservedate <= ?";
167     push @query_params, $enddate_iso;
168 }
169
170 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
171
172 # Bug 21320
173 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
174     $sqldatewhere .= " AND damaged = 0";
175 }
176
177 my $strsth =
178     "SELECT min(reservedate) as l_reservedate,
179             reserves.reserve_id,
180             reserves.borrowernumber as borrowernumber,
181
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 )
228     ";
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
232
233 if (C4::Context->preference('IndependentBranches')){
234     $strsth .= " AND items.holdingbranch=? ";
235     push @query_params, C4::Context->userenv->{'branch'};
236 }
237 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
238
239 my $sth = $dbh->prepare($strsth);
240 $sth->execute(@query_params);
241
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},
270         }
271     );
272 }
273 $sth->finish;
274
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,
284 );
285
286 output_html_with_http_headers $input, $cookie, $template->output;