Bug 26725: (QA follow-up) Add line break and 'the' for readability
[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( 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, 'LostReserve' );
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                     while ( my ( $f, $v ) = each( %$assignments ) ) {
117                         $item->$f($v);
118                     }
119                     $item->store;
120                 };
121                 warn "Unable to modify item itemnumber=" . $item->itemnumber . ": $@" if $@;
122             }
123         }
124
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
128 }
129
130
131 my $today = dt_from_string;
132
133 if ( $startdate ) {
134     $startdate =~ s/^\s+//;
135     $startdate =~ s/\s+$//;
136     $startdate = eval{dt_from_string( $startdate )};
137 }
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 );
142 }
143
144 if ( $enddate ) {
145     $enddate =~ s/^\s+//;
146     $enddate =~ s/\s+$//;
147     $enddate = eval{dt_from_string( $enddate )};
148 }
149 unless ( $enddate ) {
150     #similarly: calculate end date with ConfirmFutureHolds (days)
151     $enddate = $today + DateTime::Duration->new( days => C4::Context->preference('ConfirmFutureHolds') || 0 );
152 }
153
154 my @reservedata;
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 });
159
160 $debug and warn $startdate_iso. "\n" . $enddate_iso;
161
162 my @query_params = ();
163
164 if ($startdate_iso) {
165     $sqldatewhere .= " AND reservedate >= ?";
166     push @query_params, $startdate_iso;
167 }
168 if ($enddate_iso) {
169     $sqldatewhere .= " AND reservedate <= ?";
170     push @query_params, $enddate_iso;
171 }
172
173 my $item_type = C4::Context->preference('item-level_itypes') ? "items.itype" : "biblioitems.itemtype";
174
175 # Bug 21320
176 if ( ! C4::Context->preference('AllowHoldsOnDamagedItems') ) {
177     $sqldatewhere .= " AND damaged = 0";
178 }
179
180 my $strsth =
181     "SELECT min(reservedate) as l_reservedate,
182             reserves.reserve_id,
183             reserves.borrowernumber as borrowernumber,
184
185             GROUP_CONCAT(DISTINCT items.holdingbranch 
186                     ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
187             reserves.biblionumber,
188             reserves.branchcode as l_branch,
189             reserves.itemnumber,
190             items.holdingbranch,
191             items.homebranch,
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,
204             biblio.title,
205             biblio.subtitle,
206             biblio.medium,
207             biblio.part_number,
208             biblio.part_name,
209             biblio.author,
210             biblioitems.editionstatement,
211             count(DISTINCT items.itemnumber) as icount,
212             count(DISTINCT reserves.borrowernumber) as rcount,
213             borrowers.firstname,
214             borrowers.surname
215     FROM  reserves
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 )
223     WHERE
224     reserves.found IS NULL
225     $sqldatewhere
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 )
234     ";
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
238
239 if (C4::Context->preference('IndependentBranches')){
240     $strsth .= " AND items.holdingbranch=? ";
241     push @query_params, C4::Context->userenv->{'branch'};
242 }
243 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
244
245 my $sth = $dbh->prepare($strsth);
246 $sth->execute(@query_params);
247
248 while ( my $data = $sth->fetchrow_hashref ) {
249     push(
250         @reservedata, {
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},
278         }
279     );
280 }
281 $sth->finish;
282
283 $template->param(
284     todaysdate          => $today,
285     from                => $startdate,
286     to                  => $enddate,
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,
292 );
293
294 output_html_with_http_headers $input, $cookie, $template->output;