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