Bug 32256: (follow-up) Use correct patron category
[koha.git] / opac / sco / sco-main.pl
1 #!/usr/bin/perl
2 #
3 # This code has been modified by Trendsetters (originally from opac-user.pl)
4 # This code has been modified by rch
5 # Parts Copyright 2010-2011, ByWater Solutions (those related to username/password auth)
6 #
7 # This file is part of Koha.
8 #
9 # Koha is free software; you can redistribute it and/or modify it
10 # under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # Koha is distributed in the hope that it will be useful, but
15 # WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with Koha; if not, see <http://www.gnu.org/licenses>.
21
22 # We're going to authenticate a self-check user.  we'll add a flag to borrowers 'selfcheck'
23 #
24 # We're not in a controlled environment; we never trust the user.
25 #
26 # The checkout permission comes form the CGI cookie/session of a staff user.
27 # The patron is not really logging in here in the same way as they do on the
28 # rest of the OPAC.  So don't confuse loggedinuser with the patron user.
29 # The patron id/cardnumber is retrieved from the JWT
30
31 use Modern::Perl;
32
33 use CGI qw ( -utf8 );
34
35 use C4::Auth qw( in_iprange get_template_and_user checkpw );
36 use C4::Circulation qw( barcodedecode AddReturn CanBookBeIssued AddIssue CanBookBeRenewed AddRenewal );
37 use C4::Reserves;
38 use C4::Output qw( output_html_with_http_headers );
39 use C4::Members;
40 use Koha::DateUtils qw( dt_from_string );
41 use Koha::Acquisition::Currencies;
42 use Koha::Items;
43 use Koha::Patrons;
44 use Koha::Patron::Images;
45 use Koha::Patron::Messages;
46 use Koha::Plugins;
47 use Koha::Token;
48 use Koha::CookieManager;
49
50 my $query = CGI->new;
51
52 unless (C4::Context->preference('WebBasedSelfCheck')) {
53     # redirect to OPAC home if self-check is not enabled
54     print $query->redirect("/cgi-bin/koha/opac-main.pl");
55     exit;
56 }
57
58 unless ( in_iprange(C4::Context->preference('SelfCheckAllowByIPRanges')) ) {
59     # redirect to OPAC home if self-checkout not permitted from current IP
60     print $query->redirect("/cgi-bin/koha/opac-main.pl");
61     exit;
62 }
63
64 $query->param(-name=>'sco_user_login',-values=>[1]);
65
66 my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
67     {
68         template_name   => "sco/sco-main.tt",
69         flagsrequired   => { self_check => "self_checkout_module" },
70         query           => $query,
71         type            => "opac",
72     }
73 );
74
75 # Get the self checkout timeout preference, or use 120 seconds as a default
76 my $selfchecktimeout = 120000;
77 if (C4::Context->preference('SelfCheckTimeout')) { 
78     $selfchecktimeout = C4::Context->preference('SelfCheckTimeout') * 1000;
79 }
80 $template->param( SelfCheckTimeout => $selfchecktimeout );
81
82 # Checks policy laid out by SCOAllowCheckin, defaults to 'on' if preference is undefined
83 my $allowselfcheckreturns = 1;
84 if (defined C4::Context->preference('SCOAllowCheckin')) {
85     $allowselfcheckreturns = C4::Context->preference('SCOAllowCheckin');
86 }
87
88 my $issuerid = $loggedinuser;
89 my ( $op, $patronlogin, $patronpw, $barcodestr, $confirmed, $newissues, $load_checkouts ) = (
90     $query->param("op")             || '',
91     $query->param("patronlogin")    || '',
92     $query->param("patronpw")       || '',
93     $query->param("barcode")        || '',
94     $query->param("confirmed")      || '',
95     $query->param("newissues")      || '',
96     $query->param("load_checkouts") || '',
97 );
98
99 my $jwt = $query->cookie('JWT');
100 #FIXME: This needs to be changed to a POSTed logout...
101 if ($op eq "logout") {
102     $template->param( loggedout => 1 );
103     $query->param( patronlogin => undef, patronpw => undef );
104     undef $jwt;
105 }
106
107 my $barcodes = [];
108 if ( $barcodestr ) {
109     push @$barcodes, split( /\s\n/, $barcodestr );
110     $barcodes = [ map { $_ =~ /^\s*$/ ? () : barcodedecode( $_ ) } @$barcodes ];
111 }
112
113 my @newissueslist = split /,/, $newissues;
114 my $issuenoconfirm = 1; #don't need to confirm on issue.
115 my $issuer   = Koha::Patrons->find( $issuerid )->unblessed;
116
117 my $patronid = $jwt ? Koha::Token->new->decode_jwt({ token => $jwt }) : undef;
118 unless ( $patronid ) {
119     if ( C4::Context->preference('SelfCheckoutByLogin') ) {
120         ( undef, $patronid ) = checkpw( $patronlogin, $patronpw );
121     }
122     else {    # People should not do that unless they know what they are doing!
123               # SelfCheckAllowByIPRanges MUST be configured
124         $patronid = $query->param('patronid');
125     }
126     $jwt = Koha::Token->new->generate_jwt({ id => $patronid }) if $patronid;
127 }
128
129 my $patron;
130 if ( $patronid ) {
131     Koha::Plugins->call( 'patron_barcode_transform', \$patronid );
132     $patron = Koha::Patrons->find( { cardnumber => $patronid } );
133 }
134
135 undef $jwt unless $patron;
136
137 my $branch = $issuer->{branchcode};
138 my $confirm_required = 0;
139 my $return_only = 0;
140
141 if ( C4::Context->preference('BatchCheckouts') and $patron ) {
142     my @batch_category_codes = split ',', C4::Context->preference('BatchCheckoutsValidCategories');
143     my $categorycode = $patron->categorycode;
144     if ( $categorycode && grep { $_ eq $categorycode } @batch_category_codes ) {
145         # do nothing - logged in patron is allowed to do batch checkouts
146     } else {
147         # patron category not allowed to do batch checkouts, only allow first barcode
148         while ( scalar @$barcodes > 1 ) {
149             pop @$barcodes;
150         }
151     }
152 } else {
153     # batch checkouts not enabled, only allow first barcode
154     while ( scalar @$barcodes > 1 ) {
155         pop @$barcodes;
156     }
157 }
158
159 if ( $patron && $op eq "cud-returnbook" && $allowselfcheckreturns ) {
160     my $success = 1;
161
162   foreach my $barcode ( @$barcodes ) {
163     my $item = Koha::Items->find( { barcode => $barcode } );
164     if ( $success && C4::Context->preference("CircConfirmItemParts") ) {
165         if ( defined($item)
166             && $item->materials )
167         {
168             $success = 0;
169         }
170     }
171
172     if ($success) {
173         # Patron cannot checkin an item they don't own
174         $success = 0
175           unless $patron->checkouts->find( { itemnumber => $item->itemnumber } );
176     }
177
178     if ( $success ) {
179         ($success) = AddReturn( $barcode, $branch )
180     }
181
182     $template->param(
183         returned => $success,
184         barcode => $barcode
185     );
186   } # foreach barcode in barcodes
187 }
188
189 elsif ( $patron && ( $op eq 'cud-checkout' ) ) {
190   foreach my $barcode ( @$barcodes ) {
191     my $item = Koha::Items->find( { barcode => $barcode } );
192     my $impossible  = {};
193     my $needconfirm = {};
194     ( $impossible, $needconfirm ) = CanBookBeIssued(
195         $patron,
196         $barcode,
197         undef,
198         0,
199         C4::Context->preference("AllowItemsOnHoldCheckoutSCO")
200     );
201     my $issue_error;
202     if ( $confirm_required = scalar keys %$needconfirm ) {
203         for my $error ( qw( UNKNOWN_BARCODE max_loans_allowed ISSUED_TO_ANOTHER NO_MORE_RENEWALS NOT_FOR_LOAN DEBT WTHDRAWN RESTRICTED RESERVED ITEMNOTSAMEBRANCH EXPIRED DEBARRED CARD_LOST GNA INVALID_DATE UNKNOWN_BARCODE TOO_MANY DEBT_GUARANTEES DEBT_GUARANTORS USERBLOCKEDOVERDUE PATRON_CANT PREVISSUE NOT_FOR_LOAN_FORCING ITEM_LOST ADDITIONAL_MATERIALS ) ) {
204             if ( $needconfirm->{$error} ) {
205                 $issue_error = $error;
206                 $confirmed = 0;
207                 last;
208             }
209         }
210     }
211
212     if (scalar keys %$impossible) {
213
214         my $issue_error = (keys %$impossible)[0]; # FIXME This is wrong, we assume only one error and keys are not ordered
215         my $title = ( $item ) ? $item->biblio->title : '';
216
217         $template->param(
218             impossible                => $issue_error,
219             "circ_error_$issue_error" => 1,
220             title                     => $title,
221             hide_main                 => 1,
222         );
223         if ($issue_error eq 'DEBT') {
224             $template->param(DEBT => $impossible->{DEBT});
225         }
226         if ( $issue_error eq "NO_MORE_RENEWALS" ) {
227             $return_only = 1;
228             $template->param(
229                 returnitem => 1,
230                 barcode    => $barcode,
231             );
232         }
233         last;
234     } elsif ( $needconfirm->{RENEW_ISSUE} ){
235         $template->param(
236                 renew               => 1,
237                 barcode             => $barcode,
238                 confirm             => $item->biblio->title,
239                 confirm_renew_issue => 1,
240                 hide_main           => 1,
241         );
242         last;
243     } elsif ( $confirm_required && !$confirmed ) {
244         $template->param(
245             impossible                => 1,
246             "circ_error_$issue_error" => 1,
247             hide_main                 => 1,
248         );
249         if ($issue_error eq 'DEBT') {
250             $template->param(DEBT => $needconfirm->{DEBT});
251         }
252         last;
253     } else {
254         if ( $confirmed || $issuenoconfirm ) {    # we'll want to call getpatroninfo again to get updated issues.
255             my ( $hold_existed, $item );
256             if ( C4::Context->preference('HoldFeeMode') eq 'any_time_is_collected' ) {
257                 # There is no easy way to know if the patron has been charged for this item.
258                 # So we check if a hold existed for this item before the check in
259                 $item = Koha::Items->find({ barcode => $barcode });
260                 $hold_existed = Koha::Holds->search(
261                     {
262                         -and => {
263                             borrowernumber => $patron->borrowernumber,
264                             -or            => {
265                                 biblionumber => $item->biblionumber,
266                                 itemnumber   => $item->itemnumber
267                             }
268                         }
269                     }
270                 )->count;
271             }
272
273             my $new_issue = AddIssue( $patron, $barcode );
274             $template->param( issued => 1, new_issue => $new_issue );
275             push @newissueslist, $barcode unless ( grep /^$barcode$/, @newissueslist );
276
277             if ( $hold_existed ) {
278                 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
279                 $template->param(
280                     # If the hold existed before the check in, let's confirm that the charge line exists
281                     # Note that this should not be needed but since we do not have proper exception handling here we do it this way
282                     patron_has_hold_fee => Koha::Account::Lines->search(
283                         {
284                             borrowernumber  => $patron->borrowernumber,
285                             debit_type_code => 'RESERVE',
286                             description     => $item->biblio->title,
287                             date            => $dtf->format_date(dt_from_string)
288                         }
289                       )->count,
290                 );
291             }
292         } else {
293             $confirm_required = 1;
294             $template->param(
295                 confirm    => "Issuing title: " . $item->biblio->title,
296                 barcode    => $barcode,
297                 hide_main  => 1,
298             );
299         }
300     }
301   } # foreach barcode in barcodes
302 } # $op
303
304 if ( $patron && ( $op eq 'cud-renew' ) ) {
305   foreach my $barcode ( @$barcodes ) {
306     my $item = Koha::Items->find({ barcode => $barcode });
307
308     if ( $patron->checkouts->find( { itemnumber => $item->itemnumber } ) ) {
309         my ($status,$renewerror) = CanBookBeRenewed( $patron, $item->checkout );
310         if ($status) {
311             AddRenewal(
312                 {
313                     borrowernumber => $patron->borrowernumber,
314                     itemnumber     => $item->itemnumber,
315                     seen           => 1
316                 }
317             );
318             push @newissueslist, $barcode;
319             $template->param(
320                 renewed => 1,
321                 barcode => $barcode
322             );
323         }
324     } else {
325         $template->param( renewed => 0 );
326     }
327   } # foreach barcode in barcodes
328 }
329
330 if ( $patron) {
331     my $borrowername = sprintf "%s %s", ($patron->firstname || ''), ($patron->surname || '');
332     my @checkouts;
333     my $pending_checkouts = $patron->pending_checkouts;
334     if ( C4::Context->preference('SCOLoadCheckoutsByDefault') || $load_checkouts ) {
335         while ( my $c = $pending_checkouts->next ) {
336             my $checkout = $c->unblessed_all_relateds;
337             my ( $can_be_renewed, $renew_error ) = CanBookBeRenewed( $patron, $c );
338             $checkout->{can_be_renewed} = $can_be_renewed;    # In the future this will be $checkout->can_be_renewed
339             $checkout->{renew_error}    = $renew_error;
340             $checkout->{overdue}        = $c->is_overdue;
341             push @checkouts, $checkout;
342         }
343     }
344
345     my $show_priority;
346     for ( C4::Context->preference("OPACShowHoldQueueDetails") ) {
347         m/priority/ and $show_priority = 1;
348     }
349
350     my $account = $patron->account;
351     my $total = $account->balance;
352     my $accountlines = $account->lines;
353
354     my $holds = $patron->holds;
355     my $waiting_holds_count = 0;
356
357     while(my $hold = $holds->next) {
358         $waiting_holds_count++ if $hold->is_waiting;
359     }
360
361     $template->param(
362         validuser => 1,
363         borrowername => $borrowername,
364         issues_count => scalar(@checkouts) || $pending_checkouts->count(),
365         ISSUES => \@checkouts,
366         HOLDS => $holds,
367         newissues => join(',',@newissueslist),
368         patronlogin => $patronlogin,
369         patronpw => $patronpw,
370         waiting_holds_count => $waiting_holds_count,
371         noitemlinks => 1 ,
372         load_checkouts => $load_checkouts,
373         borrowernumber => $patron->borrowernumber,
374         SuspendHoldsOpac => C4::Context->preference('SuspendHoldsOpac'),
375         AutoResumeSuspendedHolds => C4::Context->preference('AutoResumeSuspendedHolds'),
376         howpriority   => $show_priority,
377         ACCOUNT_LINES => $accountlines,
378         total => $total,
379     );
380
381     my $patron_messages = Koha::Patron::Messages->search(
382         {
383             borrowernumber => $patron->borrowernumber,
384             message_type => 'B',
385         }
386     );
387     $template->param(
388         patron_messages => $patron_messages,
389         opacnote => $patron->opacnote,
390     );
391
392     $template->param(
393         nofines => 1,
394
395     );
396     if (C4::Context->preference('ShowPatronImageInWebBasedSelfCheck')) {
397         my $patron_image = $patron->image;
398         $template->param(
399             display_patron_image => 1,
400         ) if $patron_image;
401     }
402 } else {
403     $template->param(
404         nouser     => $patronid,
405     );
406 }
407 my $cookie_mgr = Koha::CookieManager->new;
408 $cookie = $cookie_mgr->replace_in_list( $cookie, $query->cookie(
409     -name => 'JWT',
410     -value => $jwt // '',
411     -expires => $jwt ? '+1d' : '',
412     -HttpOnly => 1,
413     -secure => ( C4::Context->https_enabled() ? 1 : 0 ),
414     -sameSite => 'Lax'
415 ));
416 $template->param(patronid => $patronid);
417
418 output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };