Bug 19972: Make holds to pull list honors item-level_itypes syspref
[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->homebranch,
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 my $strsth =
174     "SELECT min(reservedate) as l_reservedate,
175             reserves.reserve_id,
176             reserves.borrowernumber as borrowernumber,
177
178             GROUP_CONCAT(DISTINCT items.holdingbranch 
179                     ORDER BY items.itemnumber SEPARATOR '|') l_holdingbranch,
180             reserves.biblionumber,
181             reserves.branchcode as l_branch,
182             reserves.itemnumber,
183             items.holdingbranch,
184             items.homebranch,
185             GROUP_CONCAT(DISTINCT $item_type
186                     ORDER BY items.itemnumber SEPARATOR '|') l_item_type,
187             GROUP_CONCAT(DISTINCT items.location 
188                     ORDER BY items.itemnumber SEPARATOR '|') l_location,
189             GROUP_CONCAT(DISTINCT items.itemcallnumber 
190                     ORDER BY items.itemnumber SEPARATOR '<br/>') l_itemcallnumber,
191             GROUP_CONCAT(DISTINCT items.enumchron
192                     ORDER BY items.itemnumber SEPARATOR '<br/>') l_enumchron,
193             GROUP_CONCAT(DISTINCT items.copynumber
194                     ORDER BY items.itemnumber SEPARATOR '<br/>') l_copynumber,
195             biblio.title,
196             biblio.author,
197             count(DISTINCT items.itemnumber) as icount,
198             count(DISTINCT reserves.borrowernumber) as rcount,
199             borrowers.firstname,
200             borrowers.surname
201     FROM  reserves
202         LEFT JOIN items ON items.biblionumber=reserves.biblionumber 
203         LEFT JOIN biblio ON reserves.biblionumber=biblio.biblionumber
204         LEFT JOIN biblioitems ON biblio.biblionumber=biblioitems.biblionumber
205         LEFT JOIN branchtransfers ON items.itemnumber=branchtransfers.itemnumber
206         LEFT JOIN issues ON items.itemnumber=issues.itemnumber
207         LEFT JOIN borrowers ON reserves.borrowernumber=borrowers.borrowernumber
208     WHERE
209     reserves.found IS NULL
210     $sqldatewhere
211     AND (reserves.itemnumber IS NULL OR reserves.itemnumber = items.itemnumber)
212     AND items.itemnumber NOT IN (SELECT itemnumber FROM branchtransfers where datearrived IS NULL)
213     AND items.itemnumber NOT IN (select itemnumber FROM reserves where found IS NOT NULL)
214     AND issues.itemnumber IS NULL
215     AND reserves.priority <> 0 
216     AND reserves.suspend = 0
217     AND notforloan = 0 AND damaged = 0 AND itemlost = 0 AND withdrawn = 0
218     ";
219     # GROUP BY reserves.biblionumber allows only items that are not checked out, else multiples occur when 
220     #    multiple patrons have a hold on an item
221
222
223 if (C4::Context->preference('IndependentBranches')){
224     $strsth .= " AND items.holdingbranch=? ";
225     push @query_params, C4::Context->userenv->{'branch'};
226 }
227 $strsth .= " GROUP BY reserves.biblionumber ORDER BY biblio.title ";
228
229 my $sth = $dbh->prepare($strsth);
230 $sth->execute(@query_params);
231
232 while ( my $data = $sth->fetchrow_hashref ) {
233     my $record = Koha::Biblios->find($data->{biblionumber});
234     if ($record){
235         $data->{subtitle} = [ $record->subtitles ];
236     }
237     push(
238         @reservedata, {
239             reservedate     => $data->{l_reservedate},
240             firstname       => $data->{firstname} || '',
241             surname         => $data->{surname},
242             title           => $data->{title},
243             subtitle        => $data->{subtitle},
244             author          => $data->{author},
245             borrowernumber  => $data->{borrowernumber},
246             biblionumber    => $data->{biblionumber},
247             holdingbranches => [split('\|', $data->{l_holdingbranch})],
248             branch          => $data->{l_branch},
249             itemcallnumber  => $data->{l_itemcallnumber},
250             enumchron       => $data->{l_enumchron},
251             copyno          => $data->{l_copynumber},
252             count           => $data->{icount},
253             rcount          => $data->{rcount},
254             pullcount       => $data->{icount} <= $data->{rcount} ? $data->{icount} : $data->{rcount},
255             itemTypes       => [split('\|', $data->{l_item_type})],
256             locations       => [split('\|', $data->{l_location})],
257             reserve_id      => $data->{reserve_id},
258             holdingbranch   => $data->{holdingbranch},
259             homebranch      => $data->{homebranch},
260             itemnumber      => $data->{itemnumber},
261         }
262     );
263 }
264 $sth->finish;
265
266 $template->param(
267     todaysdate          => $today,
268     from                => $startdate,
269     to                  => $enddate,
270     reserveloop         => \@reservedata,
271     "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1,
272     HoldsToPullStartDate => C4::Context->preference('HoldsToPullStartDate') || PULL_INTERVAL,
273     HoldsToPullEndDate  => C4::Context->preference('ConfirmFutureHolds') || 0,
274     messages            => \@messages,
275 );
276
277 output_html_with_http_headers $input, $cookie, $template->output;