Bug 28785: Don't send SessionRestrictionByIP to template
[koha.git] / C4 / ILSDI / Services.pm
1 package C4::ILSDI::Services;
2
3 # Copyright 2009 SARL Biblibre
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 strict;
21 use warnings;
22
23 use C4::Members;
24 use C4::Items qw( get_hostitemnumbers_of );
25 use C4::Circulation qw( CanBookBeRenewed barcodedecode CanBookBeIssued AddRenewal );
26 use C4::Accounts;
27 use C4::Biblio qw( GetMarcBiblio );
28 use C4::Reserves qw( CanBookBeReserved IsAvailableForItemLevelRequest CalculatePriority AddReserve CanItemBeReserved );
29 use C4::Context;
30 use C4::Auth;
31 use CGI qw ( -utf8 );
32 use DateTime;
33 use C4::Auth;
34 use Koha::DateUtils qw( dt_from_string );
35 use C4::AuthoritiesMarc qw( GetAuthorityXML );
36
37 use Koha::Biblios;
38 use Koha::Checkouts;
39 use Koha::Items;
40 use Koha::Libraries;
41 use Koha::Patrons;
42
43 =head1 NAME
44
45 C4::ILS-DI::Services - ILS-DI Services
46
47 =head1 DESCRIPTION
48
49 Each function in this module represents an ILS-DI service.
50 They all takes a CGI instance as argument and most of them return a
51 hashref that will be printed by XML::Simple in opac/ilsdi.pl
52
53 =head1 SYNOPSIS
54
55     use C4::ILSDI::Services;
56     use XML::Simple;
57     use CGI qw ( -utf8 );
58
59     my $cgi = new CGI;
60
61     $out = LookupPatron($cgi);
62
63     print CGI::header('text/xml');
64     print XMLout($out,
65         noattr => 1,
66         noescape => 1,
67         nosort => 1,
68                 xmldecl => '<?xml version="1.0" encoding="UTF-8" ?>',
69         RootName => 'LookupPatron',
70         SuppressEmpty => 1);
71
72 =cut
73
74 =head1 FUNCTIONS
75
76 =head2 GetAvailability
77
78 Given a set of biblionumbers or itemnumbers, returns a list with
79 availability of the items associated with the identifiers.
80
81 Parameters:
82
83 =head3 id (Required)
84
85 list of either biblionumbers or itemnumbers
86
87 =head3 id_type (Required)
88
89 defines the type of record identifier being used in the request,
90 possible values:
91
92   - bib
93   - item
94
95 =head3 return_type (Optional)
96
97 requests a particular level of detail in reporting availability,
98 possible values:
99
100   - bib
101   - item
102
103 =head3 return_fmt (Optional)
104
105 requests a particular format or set of formats in reporting
106 availability
107
108 =cut
109
110 sub GetAvailability {
111     my ($cgi) = @_;
112
113     my $out = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
114     $out .= "<dlf:collection\n";
115     $out .= "  xmlns:dlf=\"http://diglib.org/ilsdi/1.1\"\n";
116     $out .= "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
117     $out .= "  xsi:schemaLocation=\"http://diglib.org/ilsdi/1.1\n";
118     $out .= "    http://diglib.org/architectures/ilsdi/schemas/1.1/dlfexpanded.xsd\">\n";
119
120     foreach my $id ( split( / /, $cgi->param('id') ) ) {
121         if ( $cgi->param('id_type') eq "item" ) {
122             my ( $biblionumber, $status, $msg, $location ) = _availability($id);
123
124             $out .= "  <dlf:record>\n";
125             $out .= "    <dlf:bibliographic id=\"" . ( $biblionumber || $id ) . "\" />\n";
126             $out .= "    <dlf:items>\n";
127             $out .= "      <dlf:item id=\"" . $id . "\">\n";
128             $out .= "        <dlf:simpleavailability>\n";
129             $out .= "          <dlf:identifier>" . $id . "</dlf:identifier>\n";
130             $out .= "          <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
131             if ($msg)      { $out .= "          <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
132             if ($location) { $out .= "          <dlf:location>" . $location . "</dlf:location>\n"; }
133             $out .= "        </dlf:simpleavailability>\n";
134             $out .= "      </dlf:item>\n";
135             $out .= "    </dlf:items>\n";
136             $out .= "  </dlf:record>\n";
137         } else {
138             my $status;
139             my $msg;
140             my $items = Koha::Items->search({ biblionumber => $id });
141             if ($items->count) {
142                 # Open XML
143                 $out .= "  <dlf:record>\n";
144                 $out .= "    <dlf:bibliographic id=\"" .$id. "\" />\n";
145                 $out .= "    <dlf:items>\n";
146                 # We loop over the items to clean them
147                 while ( my $item = $items->next ) {
148                     my $itemnumber = $item->itemnumber;
149                     my ( $biblionumber, $status, $msg, $location ) = _availability($itemnumber);
150                     $out .= "      <dlf:item id=\"" . $itemnumber . "\">\n";
151                     $out .= "        <dlf:simpleavailability>\n";
152                     $out .= "          <dlf:identifier>" . $itemnumber . "</dlf:identifier>\n";
153                     $out .= "          <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
154                     if ($msg)      { $out .= "          <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
155                     if ($location) { $out .= "          <dlf:location>" . $location . "</dlf:location>\n"; }
156                     $out .= "        </dlf:simpleavailability>\n";
157                     $out .= "      </dlf:item>\n";
158                 }
159                 # Close XML
160                 $out .= "    </dlf:items>\n";
161                 $out .= "  </dlf:record>\n";
162             } else {
163                 $status = "unknown";
164                 $msg    = "Error: could not retrieve availability for this ID";
165             }
166         }
167     }
168     $out .= "</dlf:collection>\n";
169
170     return $out;
171 }
172
173 =head2 GetRecords
174
175 Given a list of biblionumbers, returns a list of record objects that
176 contain bibliographic information, as well as associated holdings and item
177 information. The caller may request a specific metadata schema for the
178 record objects to be returned.
179
180 This function behaves similarly to HarvestBibliographicRecords and
181 HarvestExpandedRecords in Data Aggregation, but allows quick, real time
182 lookup by bibliographic identifier.
183
184 You can use OAI-PMH ListRecords instead of this service.
185
186 Parameters:
187
188   - id (Required)
189     list of system record identifiers
190   - id_type (Optional)
191     Defines the metadata schema in which the records are returned,
192     possible values:
193         - MARCXML
194
195 =cut
196
197 sub GetRecords {
198     my ($cgi) = @_;
199
200     # Check if the schema is supported. For now, GetRecords only supports MARCXML
201     if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
202         return { code => 'UnsupportedSchema' };
203     }
204
205     my @records;
206
207     # Loop over biblionumbers
208     foreach my $biblionumber ( split( / /, $cgi->param('id') ) ) {
209
210         # Get the biblioitem from the biblionumber
211         my $biblio = Koha::Biblios->find( $biblionumber );
212         unless ( $biblio ) {
213             push @records, { code => "RecordNotFound" };
214             next;
215         }
216
217         my $biblioitem = $biblio->biblioitem->unblessed;
218
219         my $embed_items = 1;
220         my $record = GetMarcBiblio({
221             biblionumber => $biblionumber,
222             embed_items  => $embed_items });
223         if ($record) {
224             $biblioitem->{marcxml} = $record->as_xml_record();
225         }
226
227         # Get most of the needed data
228         my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
229         my $checkouts = Koha::Checkouts->search(
230             { biblionumber => $biblionumber },
231             {
232                 join => 'item',
233                 '+select' => ['item.barcode'],
234                 '+as'     => ['barcode'],
235             }
236         )->unblessed;
237         foreach my $checkout (@$checkouts) {
238             delete $checkout->{'borrowernumber'};
239         }
240         my @items            = $biblio->items->as_list;
241
242         $biblioitem->{items}->{item} = [];
243
244         # We loop over the items to clean them
245         foreach my $item (@items) {
246             my %item = %{ $item->unblessed };
247
248             # This hides additionnal XML subfields, we don't need these info
249             delete $item{'more_subfields_xml'};
250
251             # Display branch names instead of branch codes
252             my $home_library    = $item->home_branch;
253             my $holding_library = $item->holding_branch;
254             $item{'homebranchname'}    = $home_library    ? $home_library->branchname    : '';
255             $item{'holdingbranchname'} = $holding_library ? $holding_library->branchname : '';
256
257             if ($item->location) {
258                 my $authorised_value = Koha::AuthorisedValues->find_by_koha_field({ kohafield => 'items.location', authorised_value => $item->location });
259                 if ($authorised_value) {
260                     $item{location_description} = $authorised_value->opac_description;
261                 }
262             }
263
264             if ($item->itype) {
265                 my $itemtype = Koha::ItemTypes->find($item->itype);
266                 if ($itemtype) {
267                     $item{itype_description} = $itemtype->description;
268                 }
269             }
270
271             my $transfer = $item->get_transfer;
272             if ($transfer) {
273                 $item{transfer} = {
274                     datesent => $transfer->datesent,
275                     frombranch => $transfer->frombranch,
276                     tobranch => $transfer->tobranch,
277                 };
278             }
279
280             push @{ $biblioitem->{items}->{item} }, \%item;
281         }
282
283         # Holds
284         my $holds = $biblio->current_holds->unblessed;
285         foreach my $hold (@$holds) {
286             delete $hold->{'borrowernumber'};
287         }
288
289         # Hashref building...
290         $biblioitem->{'reserves'}->{'reserve'} = $holds;
291         $biblioitem->{'issues'}->{'issue'}     = $checkouts;
292
293         push @records, $biblioitem;
294     }
295
296     return { record => \@records };
297 }
298
299 =head2 GetAuthorityRecords
300
301 Given a list of authority record identifiers, returns a list of record
302 objects that contain the authority records. The function user may request
303 a specific metadata schema for the record objects.
304
305 Parameters:
306
307   - id (Required)
308     list of authority record identifiers
309   - schema (Optional)
310     specifies the metadata schema of records to be returned, possible values:
311       - MARCXML
312
313 =cut
314
315 sub GetAuthorityRecords {
316     my ($cgi) = @_;
317
318     # If the user asks for an unsupported schema, return an error code
319     if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
320         return { code => 'UnsupportedSchema' };
321     }
322
323     my @records;
324
325     # Let's loop over the authority IDs
326     foreach my $authid ( split( / /, $cgi->param('id') ) ) {
327
328         # Get the record as XML string, or error code
329         push @records, GetAuthorityXML($authid) || { code => 'RecordNotFound' };
330     }
331
332     return { record => \@records };
333 }
334
335 =head2 LookupPatron
336
337 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
338
339 Parameters:
340
341   - id (Required)
342     an identifier used to look up the patron in Koha
343   - id_type (Optional)
344     the type of the identifier, possible values:
345     - cardnumber
346     - userid
347         - email
348     - borrowernumber
349     - firstname
350         - surname
351
352 =cut
353
354 sub LookupPatron {
355     my ($cgi) = @_;
356
357     my $id      = $cgi->param('id');
358     if(!$id) {
359         return { message => 'PatronNotFound' };
360     }
361
362     my $patrons;
363     my $passed_id_type = $cgi->param('id_type');
364     if($passed_id_type) {
365         $patrons = Koha::Patrons->search( { $passed_id_type => $id } );
366     } else {
367         foreach my $id_type ('cardnumber', 'userid', 'email', 'borrowernumber',
368                      'surname', 'firstname') {
369             $patrons = Koha::Patrons->search( { $id_type => $id } );
370             last if($patrons->count);
371         }
372     }
373     unless ( $patrons->count ) {
374         return { message => 'PatronNotFound' };
375     }
376
377     return { id => $patrons->next->borrowernumber };
378 }
379
380 =head2 AuthenticatePatron
381
382 Authenticates a user's login credentials and returns the identifier for
383 the patron.
384
385 Parameters:
386
387   - username (Required)
388     user's login identifier (userid or cardnumber)
389   - password (Required)
390     user's password
391
392 =cut
393
394 sub AuthenticatePatron {
395     my ($cgi) = @_;
396     my $username = $cgi->param('username');
397     my $password = $cgi->param('password');
398     my ($status, $cardnumber, $userid) = C4::Auth::checkpw( C4::Context->dbh, $username, $password );
399     if ( $status ) {
400         # Track the login
401         C4::Auth::track_login_daily( $userid );
402         # Get the borrower
403         my $patron = Koha::Patrons->find( { userid => $userid } );
404         return { id => $patron->borrowernumber };
405     }
406     else {
407         return { code => 'PatronNotFound' };
408     }
409 }
410
411 =head2 GetPatronInfo
412
413 Returns specified information about the patron, based on options in the
414 request. This function can optionally return patron's contact information,
415 fine information, hold request information, and loan information.
416
417 Parameters:
418
419   - patron_id (Required)
420     the borrowernumber
421   - show_contact (Optional, default 1)
422     whether or not to return patron's contact information in the response
423   - show_fines (Optional, default 0)
424     whether or not to return fine information in the response
425   - show_holds (Optional, default 0)
426     whether or not to return hold request information in the response
427   - show_loans (Optional, default 0)
428     whether or not to return loan information request information in the response
429   - show_attributes (Optional, default 0)
430     whether or not to return additional patron attributes, when enabled the attributes
431     are limited to those marked as opac visible only.
432
433 =cut
434
435 sub GetPatronInfo {
436     my ($cgi) = @_;
437
438     # Get Member details
439     my $borrowernumber = $cgi->param('patron_id');
440     my $patron = Koha::Patrons->find( $borrowernumber );
441     return { code => 'PatronNotFound' } unless $patron;
442
443     # Cleaning the borrower hashref
444     my $borrower = $patron->unblessed;
445     $borrower->{charges} = sprintf "%.02f", $patron->account->non_issues_charges; # FIXME Formatting should not be done here
446     my $library = Koha::Libraries->find( $borrower->{branchcode} );
447     $borrower->{'branchname'} = $library ? $library->branchname : '';
448     delete $borrower->{'userid'};
449     delete $borrower->{'password'};
450
451     # Contact fields management
452     if ( defined $cgi->param('show_contact') && $cgi->param('show_contact') eq "0" ) {
453
454         # Define contact fields
455         my @contactfields = (
456             'email',              'emailpro',           'fax',                 'mobile',          'phone',             'phonepro',
457             'streetnumber',       'zipcode',            'city',                'streettype',      'B_address',         'B_city',
458             'B_email',            'B_phone',            'B_zipcode',           'address',         'address2',          'altcontactaddress1',
459             'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
460         );
461
462         # and delete them
463         foreach my $field (@contactfields) {
464             delete $borrower->{$field};
465         }
466     }
467
468     # Fines management
469     if ( $cgi->param('show_fines') && $cgi->param('show_fines') eq "1" ) {
470         $borrower->{fines}{fine} = $patron->account->lines->unblessed;
471     }
472
473     # Reserves management
474     if ( $cgi->param('show_holds') && $cgi->param('show_holds') eq "1" ) {
475
476         # Get borrower's reserves
477         my $holds = $patron->holds;
478         while ( my $hold = $holds->next ) {
479
480             my ( $item, $biblio, $biblioitem ) = ( {}, {}, {} );
481             # Get additional informations
482             if ( $hold->itemnumber ) {    # item level holds
483                 $item       = Koha::Items->find( $hold->itemnumber );
484                 $biblio     = $item->biblio;
485                 $biblioitem = $biblio->biblioitem;
486
487                 # Remove unwanted fields
488                 $item = $item->unblessed;
489                 delete $item->{more_subfields_xml};
490                 $biblio     = $biblio->unblessed;
491                 $biblioitem = $biblioitem->unblessed;
492             }
493
494             # Add additional fields
495             my $unblessed_hold = $hold->unblessed;
496             $unblessed_hold->{item}       = { %$item, %$biblio, %$biblioitem };
497             my $library = Koha::Libraries->find( $hold->branchcode );
498             my $branchname = $library ? $library->branchname : '';
499             $unblessed_hold->{branchname} = $branchname;
500             $biblio = Koha::Biblios->find( $hold->biblionumber ); # Should be $hold->get_biblio
501             $unblessed_hold->{title} = $biblio ? $biblio->title : ''; # Just in case, but should not be needed
502
503             push @{ $borrower->{holds}{hold} }, $unblessed_hold;
504
505         }
506     }
507
508     # Issues management
509     if ( $cgi->param('show_loans') && $cgi->param('show_loans') eq "1" ) {
510         my $per_page = $cgi->param('loans_per_page');
511         my $page = $cgi->param('loans_page');
512
513         my $pending_checkouts = $patron->pending_checkouts;
514
515         if ($page || $per_page) {
516             $page ||= 1;
517             $per_page ||= 10;
518             $borrower->{total_loans} = $pending_checkouts->count();
519             $pending_checkouts = $pending_checkouts->search(undef, {
520                 rows => $per_page,
521                 page => $page,
522             });
523         }
524
525         my @checkouts;
526         while ( my $c = $pending_checkouts->next ) {
527             # FIXME We should only retrieve what is needed in the template
528             my $issue = $c->unblessed_all_relateds;
529             delete $issue->{'more_subfields_xml'};
530             push @checkouts, $issue
531         }
532         $borrower->{'loans'}->{'loan'} = \@checkouts;
533     }
534
535     my $show_attributes = $cgi->param('show_attributes');
536     if ( $show_attributes && $show_attributes eq "1" ) {
537         # FIXME Regression expected here, we do not retrieve the same field as previously
538         # Waiting for answer on bug 14257 comment 15
539         $borrower->{'attributes'} = [
540             map {
541                 $_->type->opac_display
542                   ? {
543                     %{ $_->unblessed },
544                     %{ $_->type->unblessed },
545                     value             => $_->attribute,   # Backward compatibility
546                     value_description => $_->description, # Awkward retro-compability...
547                   }
548                   : ()
549             } $patron->extended_attributes->search
550         ];
551     }
552
553     # Add is expired information
554     $borrower->{'is_expired'} = $patron->is_expired ? 1 : 0;
555
556     return $borrower;
557 }
558
559 =head2 GetPatronStatus
560
561 Returns a patron's status information.
562
563 Parameters:
564
565   - patron_id (Required)
566     the borrower ID
567
568 =cut
569
570 sub GetPatronStatus {
571     my ($cgi) = @_;
572
573     # Get Member details
574     my $borrowernumber = $cgi->param('patron_id');
575     my $patron = Koha::Patrons->find( $borrowernumber );
576     return { code => 'PatronNotFound' } unless $patron;
577
578     # Return the results
579     return {
580         type   => $patron->categorycode,
581         status => 0, # TODO
582         expiry => $patron->dateexpiry,
583     };
584 }
585
586 =head2 GetServices
587
588 Returns information about the services available on a particular item for
589 a particular patron.
590
591 Parameters:
592
593   - patron_id (Required)
594     a borrowernumber
595   - item_id (Required)
596     an itemnumber
597
598 =cut
599
600 sub GetServices {
601     my ($cgi) = @_;
602
603     # Get the member, or return an error code if not found
604     my $borrowernumber = $cgi->param('patron_id');
605     my $patron = Koha::Patrons->find( $borrowernumber );
606     return { code => 'PatronNotFound' } unless $patron;
607
608     my $borrower = $patron->unblessed;
609     # Get the item, or return an error code if not found
610     my $itemnumber = $cgi->param('item_id');
611     my $item = Koha::Items->find($itemnumber);
612     return { code => 'RecordNotFound' } unless $item;
613
614     my @availablefor;
615
616     # Reserve level management
617     my $biblionumber = $item->biblionumber;
618     my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
619     if ($canbookbereserved->{status} eq 'OK') {
620         push @availablefor, 'title level hold';
621         my $canitembereserved = IsAvailableForItemLevelRequest($item, $patron);
622         if ($canitembereserved) {
623             push @availablefor, 'item level hold';
624         }
625     }
626
627     # Reserve cancellation management
628     my $holds = $patron->holds;
629     my @reserveditems;
630     while ( my $hold = $holds->next ) { # FIXME This could be improved
631         push @reserveditems, $hold->itemnumber;
632     }
633     if ( grep { $itemnumber eq $_ } @reserveditems ) {
634         push @availablefor, 'hold cancellation';
635     }
636
637     # Renewal management
638     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
639     if ( $renewal[0] ) {
640         push @availablefor, 'loan renewal';
641     }
642
643     # Issuing management
644     my $barcode = $item->barcode || '';
645     $barcode = barcodedecode($barcode) if $barcode;
646     if ($barcode) {
647         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $barcode );
648
649         # TODO push @availablefor, 'loan';
650     }
651
652     my $out;
653     $out->{'AvailableFor'} = \@availablefor;
654
655     return $out;
656 }
657
658 =head2 RenewLoan
659
660 Extends the due date for a borrower's existing issue.
661
662 Parameters:
663
664   - patron_id (Required)
665     a borrowernumber
666   - item_id (Required)
667     an itemnumber
668   - desired_due_date (Required)
669     the date the patron would like the item returned by
670
671 =cut
672
673 sub RenewLoan {
674     my ($cgi) = @_;
675
676     # Get borrower infos or return an error code
677     my $borrowernumber = $cgi->param('patron_id');
678     my $patron = Koha::Patrons->find( $borrowernumber );
679     return { code => 'PatronNotFound' } unless $patron;
680
681     # Get the item, or return an error code
682     my $itemnumber = $cgi->param('item_id');
683     my $item = Koha::Items->find($itemnumber);
684     return { code => 'RecordNotFound' } unless $item;
685
686     # Add renewal if possible
687     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
688     if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber, undef, undef, undef, undef, 0 ); }
689
690     my $issue = $item->checkout;
691     return unless $issue; # FIXME should be handled
692
693     # Hashref building
694     my $out;
695     $out->{'renewals'} = $issue->renewals;
696     $out->{date_due}   = dt_from_string($issue->date_due)->strftime('%Y-%m-%d %H:%M');
697     $out->{'success'}  = $renewal[0];
698     $out->{'error'}    = $renewal[1];
699
700     return $out;
701 }
702
703 =head2 HoldTitle
704
705 Creates, for a borrower, a biblio-level hold reserve.
706
707 Parameters:
708
709   - patron_id (Required)
710     a borrowernumber
711   - bib_id (Required)
712     a biblionumber
713   - request_location (Required)
714     IP address where the end user request is being placed
715   - pickup_location (Optional)
716     a branch code indicating the location to which to deliver the item for pickup
717   - start_date (Optional)
718     date after which hold request is no longer needed if the document has not been made available
719   - expiry_date (Optional)
720     date after which item returned to shelf if item is not picked up
721
722 =cut
723
724 sub HoldTitle {
725     my ($cgi) = @_;
726
727     # Get the borrower or return an error code
728     my $borrowernumber = $cgi->param('patron_id');
729     my $patron = Koha::Patrons->find( $borrowernumber );
730     return { code => 'PatronNotFound' } unless $patron;
731
732
733     # If borrower is restricted return an error code
734     return { code => 'PatronRestricted' } if $patron->is_debarred;
735
736     # Check for patron expired, category and syspref settings
737     return { code => 'PatronExpired' } if ($patron->category->effective_BlockExpiredPatronOpacActions && $patron->is_expired);
738
739     # Get the biblio record, or return an error code
740     my $biblionumber = $cgi->param('bib_id');
741     my $biblio = Koha::Biblios->find( $biblionumber );
742     return { code => 'RecordNotFound' } unless $biblio;
743
744     my @hostitems = get_hostitemnumbers_of($biblionumber);
745     my @itemnumbers;
746     if (@hostitems){
747         push(@itemnumbers, @hostitems);
748     }
749
750     my $items = Koha::Items->search({ -or => { biblionumber => $biblionumber, itemnumber => { in => \@itemnumbers } } });
751
752     unless ( $items->count ) {
753         return { code => 'NoItems' };
754     }
755
756     my $title = $biblio ? $biblio->title : '';
757
758     # Check if the biblio can be reserved
759     my $code = CanBookBeReserved( $borrowernumber, $biblionumber )->{status};
760     return { code => $code } unless ( $code eq 'OK' );
761
762     my $branch;
763
764     # Pickup branch management
765     if ( $cgi->param('pickup_location') ) {
766         $branch = $cgi->param('pickup_location');
767         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
768     } else { # if the request provide no branch, use the borrower's branch
769         $branch = $patron->branchcode;
770     }
771
772     my $destination = Koha::Libraries->find($branch);
773     return { code => 'libraryNotPickupLocation' } unless $destination->pickup_location;
774     return { code => 'cannotBeTransferred' } unless $biblio->can_be_transferred({ to => $destination });
775
776     my $resdate;
777     if ( $cgi->param('start_date') ) {
778         $resdate = $cgi->param('start_date');
779     }
780
781     my $expdate;
782     if ( $cgi->param('expiry_date') ) {
783         $expdate = $cgi->param('expiry_date');
784     }
785
786     # Add the reserve
787     #    $branch,    $borrowernumber, $biblionumber,
788     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
789     #    $title,      $checkitem, $found
790     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
791     AddReserve(
792         {
793             branchcode       => $branch,
794             borrowernumber   => $borrowernumber,
795             biblionumber     => $biblionumber,
796             priority         => $priority,
797             reservation_date => $resdate,
798             expiration_date  => $expdate,
799             title            => $title,
800         }
801     );
802
803     # Hashref building
804     my $out;
805     $out->{'title'}           = $title;
806     my $library = Koha::Libraries->find( $branch );
807     $out->{'pickup_location'} = $library ? $library->branchname : '';
808
809     # TODO $out->{'date_available'}  = '';
810
811     return $out;
812 }
813
814 =head2 HoldItem
815
816 Creates, for a borrower, an item-level hold request on a specific item of
817 a bibliographic record in Koha.
818
819 Parameters:
820
821   - patron_id (Required)
822     a borrowernumber
823   - bib_id (Required)
824     a biblionumber
825   - item_id (Required)
826     an itemnumber
827   - pickup_location (Optional)
828     a branch code indicating the location to which to deliver the item for pickup
829   - start_date (Optional)
830     date after which hold request is no longer needed if the item has not been made available
831   - expiry_date (Optional)
832     date after which item returned to shelf if item is not picked up
833
834 =cut
835
836 sub HoldItem {
837     my ($cgi) = @_;
838
839     # Get the borrower or return an error code
840     my $borrowernumber = $cgi->param('patron_id');
841     my $patron = Koha::Patrons->find( $borrowernumber );
842     return { code => 'PatronNotFound' } unless $patron;
843
844     # If borrower is restricted return an error code
845     return { code => 'PatronRestricted' } if $patron->is_debarred;
846
847     # Check for patron expired, category and syspref settings
848     return { code => 'PatronExpired' } if ($patron->category->effective_BlockExpiredPatronOpacActions && $patron->is_expired);
849
850     # Get the biblio or return an error code
851     my $biblionumber = $cgi->param('bib_id');
852     my $biblio = Koha::Biblios->find( $biblionumber );
853     return { code => 'RecordNotFound' } unless $biblio;
854
855     my $title = $biblio ? $biblio->title : '';
856
857     # Get the item or return an error code
858     my $itemnumber = $cgi->param('item_id');
859     my $item = Koha::Items->find($itemnumber);
860     return { code => 'RecordNotFound' } unless $item;
861
862     # If the biblio does not match the item, return an error code
863     return { code => 'RecordNotFound' } if $item->biblionumber ne $biblio->biblionumber;
864
865     # Pickup branch management
866     my $branch;
867     if ( $cgi->param('pickup_location') ) {
868         $branch = $cgi->param('pickup_location');
869         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
870     } else { # if the request provide no branch, use the borrower's branch
871         $branch = $patron->branchcode;
872     }
873
874     # Check for item disponibility
875     my $canitembereserved = C4::Reserves::CanItemBeReserved( $borrowernumber, $itemnumber, $branch )->{status};
876     return { code => $canitembereserved } unless $canitembereserved eq 'OK';
877
878     my $resdate;
879     if ( $cgi->param('start_date') ) {
880         $resdate = $cgi->param('start_date');
881     }
882
883     my $expdate;
884     if ( $cgi->param('expiry_date') ) {
885         $expdate = $cgi->param('expiry_date');
886     }
887
888     # Add the reserve
889     my $priority = C4::Reserves::CalculatePriority($biblionumber);
890     AddReserve(
891         {
892             branchcode       => $branch,
893             borrowernumber   => $borrowernumber,
894             biblionumber     => $biblionumber,
895             priority         => $priority,
896             reservation_date => $resdate,
897             expiration_date  => $expdate,
898             title            => $title,
899             itemnumber       => $itemnumber,
900         }
901     );
902
903     # Hashref building
904     my $out;
905     my $library = Koha::Libraries->find( $branch );
906     $out->{'pickup_location'} = $library ? $library->branchname : '';
907
908     # TODO $out->{'date_available'} = '';
909
910     return $out;
911 }
912
913 =head2 CancelHold
914
915 Cancels an active reserve request for the borrower.
916
917 Parameters:
918
919   - patron_id (Required)
920         a borrowernumber
921   - item_id (Required)
922         a reserve_id
923
924 =cut
925
926 sub CancelHold {
927     my ($cgi) = @_;
928
929     # Get the borrower or return an error code
930     my $borrowernumber = $cgi->param('patron_id');
931     my $patron = Koha::Patrons->find( $borrowernumber );
932     return { code => 'PatronNotFound' } unless $patron;
933
934     # Get the reserve or return an error code
935     my $reserve_id = $cgi->param('item_id');
936     my $hold = Koha::Holds->find( $reserve_id );
937     return { code => 'RecordNotFound' } unless $hold;
938     return { code => 'RecordNotFound' } unless ($hold->borrowernumber == $borrowernumber);
939
940     $hold->cancel;
941
942     return { code => 'Canceled' };
943 }
944
945 =head2 _availability
946
947 Returns, for an itemnumber, an array containing availability information.
948
949  my ($biblionumber, $status, $msg, $location) = _availability($id);
950
951 =cut
952
953 sub _availability {
954     my ($itemnumber) = @_;
955     my $item = Koha::Items->find($itemnumber);
956
957     unless ( $item ) {
958         return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
959     }
960
961     my $biblionumber = $item->biblioitemnumber;
962     my $library = Koha::Libraries->find( $item->holdingbranch );
963     my $location = $library ? $library->branchname : '';
964
965     if ( $item->notforloan ) {
966         return ( $biblionumber, 'not available', 'Not for loan', $location );
967     } elsif ( $item->onloan ) {
968         return ( $biblionumber, 'not available', 'Checked out', $location );
969     } elsif ( $item->itemlost ) {
970         return ( $biblionumber, 'not available', 'Item lost', $location );
971     } elsif ( $item->withdrawn ) {
972         return ( $biblionumber, 'not available', 'Item withdrawn', $location );
973     } elsif ( $item->damaged ) {
974         return ( $biblionumber, 'not available', 'Item damaged', $location );
975     } else {
976         return ( $biblionumber, 'available', undef, $location );
977     }
978 }
979
980 1;