Bug 13990: (follow-up) silence warnings on testing
[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;
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 C4::Members::Attributes qw(GetBorrowerAttributes);
37 use Koha::DateUtils;
38
39 use Koha::Biblios;
40 use Koha::Checkouts;
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 = GetItemnumbersForBiblio($id);
142             if ($items) {
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                 foreach my $itemnumber (@$items) {
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 $biblioitem = ( GetBiblioItemByBiblioNumber( $biblionumber, undef ) )[0];
212         if ( not $biblioitem->{'biblionumber'} ) {
213             $biblioitem->{code} = "RecordNotFound";
214         }
215
216         my $embed_items = 1;
217         my $record = GetMarcBiblio({
218             biblionumber => $biblionumber,
219             embed_items  => $embed_items });
220         if ($record) {
221             $biblioitem->{marcxml} = $record->as_xml_record();
222         }
223
224         # Get most of the needed data
225         my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
226         my $biblio = Koha::Biblios->find( $biblionumber );
227         my $holds  = $biblio->current_holds->unblessed;
228         my $issues           = GetBiblioIssues($biblionumber);
229         my $items            = GetItemsByBiblioitemnumber($biblioitemnumber);
230
231         # We loop over the items to clean them
232         foreach my $item (@$items) {
233
234             # This hides additionnal XML subfields, we don't need these info
235             delete $item->{'more_subfields_xml'};
236
237             # Display branch names instead of branch codes
238             my $home_library    = Koha::Libraries->find( $item->{homebranch} );
239             my $holding_library = Koha::Libraries->find( $item->{holdingbranch} );
240             $item->{'homebranchname'}    = $home_library    ? $home_library->branchname    : '';
241             $item->{'holdingbranchname'} = $holding_library ? $holding_library->branchname : '';
242         }
243
244         # Hashref building...
245         $biblioitem->{'items'}->{'item'}       = $items;
246         $biblioitem->{'reserves'}->{'reserve'} = $holds;
247         $biblioitem->{'issues'}->{'issue'}     = $issues;
248
249         push @records, $biblioitem;
250     }
251
252     return { record => \@records };
253 }
254
255 =head2 GetAuthorityRecords
256
257 Given a list of authority record identifiers, returns a list of record
258 objects that contain the authority records. The function user may request
259 a specific metadata schema for the record objects.
260
261 Parameters:
262
263   - id (Required)
264     list of authority record identifiers
265   - schema (Optional)
266     specifies the metadata schema of records to be returned, possible values:
267       - MARCXML
268
269 =cut
270
271 sub GetAuthorityRecords {
272     my ($cgi) = @_;
273
274     # If the user asks for an unsupported schema, return an error code
275     if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
276         return { code => 'UnsupportedSchema' };
277     }
278
279     my @records;
280
281     # Let's loop over the authority IDs
282     foreach my $authid ( split( / /, $cgi->param('id') ) ) {
283
284         # Get the record as XML string, or error code
285         push @records, GetAuthorityXML($authid) || { code => 'RecordNotFound' };
286     }
287
288     return { record => \@records };
289 }
290
291 =head2 LookupPatron
292
293 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
294
295 Parameters:
296
297   - id (Required)
298     an identifier used to look up the patron in Koha
299   - id_type (Optional)
300     the type of the identifier, possible values:
301     - cardnumber
302     - userid
303         - email
304     - borrowernumber
305     - firstname
306         - surname
307
308 =cut
309
310 sub LookupPatron {
311     my ($cgi) = @_;
312
313     my $id      = $cgi->param('id');
314     if(!$id) {
315         return { message => 'PatronNotFound' };
316     }
317
318     my $patrons;
319     my $passed_id_type = $cgi->param('id_type');
320     if($passed_id_type) {
321         $patrons = Koha::Patrons->search( { $passed_id_type => $id } );
322     } else {
323         foreach my $id_type ('cardnumber', 'userid', 'email', 'borrowernumber',
324                      'surname', 'firstname') {
325             $patrons = Koha::Patrons->search( { $id_type => $id } );
326             last if($patrons->count);
327         }
328     }
329     unless ( $patrons->count ) {
330         return { message => 'PatronNotFound' };
331     }
332
333     return { id => $patrons->next->borrowernumber };
334 }
335
336 =head2 AuthenticatePatron
337
338 Authenticates a user's login credentials and returns the identifier for
339 the patron.
340
341 Parameters:
342
343   - username (Required)
344     user's login identifier (userid or cardnumber)
345   - password (Required)
346     user's password
347
348 =cut
349
350 sub AuthenticatePatron {
351     my ($cgi) = @_;
352     my $username = $cgi->param('username');
353     my $password = $cgi->param('password');
354     my ($status, $cardnumber, $userid) = C4::Auth::checkpw( C4::Context->dbh, $username, $password );
355     if ( $status ) {
356         # Get the borrower
357         my $patron = Koha::Patrons->find( { cardnumber => $cardnumber } );
358         return { id => $patron->borrowernumber };
359     }
360     else {
361         return { code => 'PatronNotFound' };
362     }
363 }
364
365 =head2 GetPatronInfo
366
367 Returns specified information about the patron, based on options in the
368 request. This function can optionally return patron's contact information,
369 fine information, hold request information, and loan information.
370
371 Parameters:
372
373   - patron_id (Required)
374     the borrowernumber
375   - show_contact (Optional, default 1)
376     whether or not to return patron's contact information in the response
377   - show_fines (Optional, default 0)
378     whether or not to return fine information in the response
379   - show_holds (Optional, default 0)
380     whether or not to return hold request information in the response
381   - show_loans (Optional, default 0)
382     whether or not to return loan information request information in the response
383
384 =cut
385
386 sub GetPatronInfo {
387     my ($cgi) = @_;
388
389     # Get Member details
390     my $borrowernumber = $cgi->param('patron_id');
391     my $patron = Koha::Patrons->find( $borrowernumber );
392     return { code => 'PatronNotFound' } unless $patron;
393
394     # Cleaning the borrower hashref
395     my $borrower = $patron->unblessed;
396     my $flags = C4::Members::patronflags( $borrower );
397     $borrower->{'charges'} = $flags->{'CHARGES'}->{'amount'};
398     my $library = Koha::Libraries->find( $borrower->{branchcode} );
399     $borrower->{'branchname'} = $library ? $library->branchname : '';
400     delete $borrower->{'userid'};
401     delete $borrower->{'password'};
402
403     # Contact fields management
404     if ( defined $cgi->param('show_contact') && $cgi->param('show_contact') eq "0" ) {
405
406         # Define contact fields
407         my @contactfields = (
408             'email',              'emailpro',           'fax',                 'mobile',          'phone',             'phonepro',
409             'streetnumber',       'zipcode',            'city',                'streettype',      'B_address',         'B_city',
410             'B_email',            'B_phone',            'B_zipcode',           'address',         'address2',          'altcontactaddress1',
411             'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
412         );
413
414         # and delete them
415         foreach my $field (@contactfields) {
416             delete $borrower->{$field};
417         }
418     }
419
420     # Fines management
421     if ( $cgi->param('show_fines') && $cgi->param('show_fines') eq "1" ) {
422         my @charges;
423         for ( my $i = 1 ; my @charge = getcharges( $borrowernumber, undef, $i ) ; $i++ ) {
424             push( @charges, @charge );
425         }
426         $borrower->{'fines'}->{'fine'} = \@charges;
427     }
428
429     # Reserves management
430     if ( $cgi->param('show_holds') && $cgi->param('show_holds') eq "1" ) {
431
432         # Get borrower's reserves
433         my $holds = $patron->holds;
434         while ( my $hold = $holds->next ) {
435
436             my ( $item, $biblio, $biblioitem ) = ( {}, {}, {} );
437             # Get additional informations
438             if ( $hold->itemnumber ) {    # item level holds
439                 $item       = Koha::Items->find( $hold->itemnumber );
440                 $biblio     = $item->biblio;
441                 $biblioitem = $biblio->biblioitem;
442
443                 # Remove unwanted fields
444                 $item = $item->unblessed;
445                 delete $item->{more_subfields_xml};
446                 $biblio     = $biblio->unblessed;
447                 $biblioitem = $biblioitem->unblessed;
448             }
449
450             # Add additional fields
451             my $unblessed_hold = $hold->unblessed;
452             $unblessed_hold->{item}       = { %$item, %$biblio, %$biblioitem };
453             my $library = Koha::Libraries->find( $hold->branchcode );
454             my $branchname = $library ? $library->branchname : '';
455             $unblessed_hold->{branchname} = $branchname;
456             $biblio = Koha::Biblios->find( $hold->biblionumber ); # Should be $hold->get_biblio
457             $unblessed_hold->{title} = $biblio ? $biblio->title : ''; # Just in case, but should not be needed
458
459             push @{ $borrower->{holds}{hold} }, $unblessed_hold;
460
461         }
462     }
463
464     # Issues management
465     if ( $cgi->param('show_loans') && $cgi->param('show_loans') eq "1" ) {
466         my $issues = GetPendingIssues($borrowernumber);
467         foreach my $issue ( @$issues ){
468             $issue->{'issuedate'} = $issue->{'issuedate'}->strftime('%Y-%m-%d %H:%M');
469             $issue->{'date_due'} = $issue->{'date_due'}->strftime('%Y-%m-%d %H:%M');
470         }
471         $borrower->{'loans'}->{'loan'} = $issues;
472     }
473
474     if ( $cgi->param('show_attributes') eq "1" ) {
475         my $attrs = GetBorrowerAttributes( $borrowernumber, 0, 1 );
476         $borrower->{'attributes'} = $attrs;
477     }
478
479     return $borrower;
480 }
481
482 =head2 GetPatronStatus
483
484 Returns a patron's status information.
485
486 Parameters:
487
488   - patron_id (Required)
489     the borrower ID
490
491 =cut
492
493 sub GetPatronStatus {
494     my ($cgi) = @_;
495
496     # Get Member details
497     my $borrowernumber = $cgi->param('patron_id');
498     my $patron = Koha::Patrons->find( $borrowernumber );
499     return { code => 'PatronNotFound' } unless $patron;
500
501     # Return the results
502     return {
503         type   => $patron->categorycode,
504         status => 0, # TODO
505         expiry => $patron->dateexpiry,
506     };
507 }
508
509 =head2 GetServices
510
511 Returns information about the services available on a particular item for
512 a particular patron.
513
514 Parameters:
515
516   - patron_id (Required)
517     a borrowernumber
518   - item_id (Required)
519     an itemnumber
520
521 =cut
522
523 sub GetServices {
524     my ($cgi) = @_;
525
526     # Get the member, or return an error code if not found
527     my $borrowernumber = $cgi->param('patron_id');
528     my $patron = Koha::Patrons->find( $borrowernumber );
529     return { code => 'PatronNotFound' } unless $patron;
530
531     my $borrower = $patron->unblessed;
532     # Get the item, or return an error code if not found
533     my $itemnumber = $cgi->param('item_id');
534     my $item = GetItem( $itemnumber );
535     return { code => 'RecordNotFound' } unless $$item{itemnumber};
536
537     my @availablefor;
538
539     # Reserve level management
540     my $biblionumber = $item->{'biblionumber'};
541     my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
542     if ($canbookbereserved eq 'OK') {
543         push @availablefor, 'title level hold';
544         my $canitembereserved = IsAvailableForItemLevelRequest($item, $borrower);
545         if ($canitembereserved) {
546             push @availablefor, 'item level hold';
547         }
548     }
549
550     # Reserve cancellation management
551     my $holds = $patron->holds;
552     my @reserveditems;
553     while ( my $hold = $holds->next ) { # FIXME This could be improved
554         push @reserveditems, $hold->itemnumber;
555     }
556     if ( grep { $itemnumber eq $_ } @reserveditems ) {
557         push @availablefor, 'hold cancellation';
558     }
559
560     # Renewal management
561     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
562     if ( $renewal[0] ) {
563         push @availablefor, 'loan renewal';
564     }
565
566     # Issuing management
567     my $barcode = $item->{'barcode'} || '';
568     $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
569     if ($barcode) {
570         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $barcode );
571
572         # TODO push @availablefor, 'loan';
573     }
574
575     my $out;
576     $out->{'AvailableFor'} = \@availablefor;
577
578     return $out;
579 }
580
581 =head2 RenewLoan
582
583 Extends the due date for a borrower's existing issue.
584
585 Parameters:
586
587   - patron_id (Required)
588     a borrowernumber
589   - item_id (Required)
590     an itemnumber
591   - desired_due_date (Required)
592     the date the patron would like the item returned by
593
594 =cut
595
596 sub RenewLoan {
597     my ($cgi) = @_;
598
599     # Get borrower infos or return an error code
600     my $borrowernumber = $cgi->param('patron_id');
601     my $patron = Koha::Patrons->find( $borrowernumber );
602     return { code => 'PatronNotFound' } unless $patron;
603
604     # Get the item, or return an error code
605     my $itemnumber = $cgi->param('item_id');
606     my $item = GetItem( $itemnumber );
607     return { code => 'RecordNotFound' } unless $$item{itemnumber};
608
609     # Add renewal if possible
610     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
611     if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber ); }
612
613     my $issue = Koha::Checkouts->find( { itemnumber => $itemnumber } ) or return; # FIXME should be handled
614
615     # Hashref building
616     my $out;
617     $out->{'renewals'} = $issue->renewals;
618     $out->{date_due}   = dt_from_string($issue->date_due)->strftime('%Y-%m-%d %H:%S');
619     $out->{'success'}  = $renewal[0];
620     $out->{'error'}    = $renewal[1];
621
622     return $out;
623 }
624
625 =head2 HoldTitle
626
627 Creates, for a borrower, a biblio-level hold reserve.
628
629 Parameters:
630
631   - patron_id (Required)
632     a borrowernumber
633   - bib_id (Required)
634     a biblionumber
635   - request_location (Required)
636     IP address where the end user request is being placed
637   - pickup_location (Optional)
638     a branch code indicating the location to which to deliver the item for pickup
639   - needed_before_date (Optional)
640     date after which hold request is no longer needed
641   - pickup_expiry_date (Optional)
642     date after which item returned to shelf if item is not picked up
643
644 =cut
645
646 sub HoldTitle {
647     my ($cgi) = @_;
648
649     # Get the borrower or return an error code
650     my $borrowernumber = $cgi->param('patron_id');
651     my $patron = Koha::Patrons->find( $borrowernumber );
652     return { code => 'PatronNotFound' } unless $patron;
653
654     # Get the biblio record, or return an error code
655     my $biblionumber = $cgi->param('bib_id');
656     my $biblio = Koha::Biblios->find( $biblionumber );
657     return { code => 'RecordNotFound' } unless $biblio;
658
659     my $title = $biblio ? $biblio->title : '';
660
661     # Check if the biblio can be reserved
662     return { code => 'NotHoldable' } unless CanBookBeReserved( $borrowernumber, $biblionumber ) eq 'OK';
663
664     my $branch;
665
666     # Pickup branch management
667     if ( $cgi->param('pickup_location') ) {
668         $branch = $cgi->param('pickup_location');
669         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
670     } else { # if the request provide no branch, use the borrower's branch
671         $branch = $patron->branchcode;
672     }
673
674     # Add the reserve
675     #    $branch,    $borrowernumber, $biblionumber,
676     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
677     #    $title,      $checkitem, $found
678     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
679     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, undef, undef );
680
681     # Hashref building
682     my $out;
683     $out->{'title'}           = $title;
684     my $library = Koha::Libraries->find( $branch );
685     $out->{'pickup_location'} = $library ? $library->branchname : '';
686
687     # TODO $out->{'date_available'}  = '';
688
689     return $out;
690 }
691
692 =head2 HoldItem
693
694 Creates, for a borrower, an item-level hold request on a specific item of
695 a bibliographic record in Koha.
696
697 Parameters:
698
699   - patron_id (Required)
700     a borrowernumber
701   - bib_id (Required)
702     a biblionumber
703   - item_id (Required)
704     an itemnumber
705   - pickup_location (Optional)
706     a branch code indicating the location to which to deliver the item for pickup
707   - needed_before_date (Optional)
708     date after which hold request is no longer needed
709   - pickup_expiry_date (Optional)
710     date after which item returned to shelf if item is not picked up
711
712 =cut
713
714 sub HoldItem {
715     my ($cgi) = @_;
716
717     # Get the borrower or return an error code
718     my $borrowernumber = $cgi->param('patron_id');
719     my $patron = Koha::Patrons->find( $borrowernumber );
720     return { code => 'PatronNotFound' } unless $patron;
721
722     # Get the biblio or return an error code
723     my $biblionumber = $cgi->param('bib_id');
724     my $biblio = Koha::Biblios->find( $biblionumber );
725     return { code => 'RecordNotFound' } unless $biblio;
726
727     my $title = $biblio ? $biblio->title : '';
728
729     # Get the item or return an error code
730     my $itemnumber = $cgi->param('item_id');
731     my $item = GetItem( $itemnumber );
732     return { code => 'RecordNotFound' } unless $$item{itemnumber};
733
734     # If the biblio does not match the item, return an error code
735     return { code => 'RecordNotFound' } if $$item{biblionumber} ne $biblio->biblionumber;
736
737     # Check for item disponibility
738     my $canitembereserved = C4::Reserves::CanItemBeReserved( $borrowernumber, $itemnumber );
739     my $canbookbereserved = C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber );
740     return { code => 'NotHoldable' } unless $canbookbereserved eq 'OK' and $canitembereserved eq 'OK';
741
742     # Pickup branch management
743     my $branch;
744     if ( $cgi->param('pickup_location') ) {
745         $branch = $cgi->param('pickup_location');
746         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
747     } else { # if the request provide no branch, use the borrower's branch
748         $branch = $patron->branchcode;
749     }
750
751     # Add the reserve
752     #    $branch,    $borrowernumber, $biblionumber,
753     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
754     #    $title,      $checkitem, $found
755     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
756     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, $itemnumber, undef );
757
758     # Hashref building
759     my $out;
760     my $library = Koha::Libraries->find( $branch );
761     $out->{'pickup_location'} = $library ? $library->branchname : '';
762
763     # TODO $out->{'date_available'} = '';
764
765     return $out;
766 }
767
768 =head2 CancelHold
769
770 Cancels an active reserve request for the borrower.
771
772 Parameters:
773
774   - patron_id (Required)
775         a borrowernumber
776   - item_id (Required)
777         a reserve_id
778
779 =cut
780
781 sub CancelHold {
782     my ($cgi) = @_;
783
784     # Get the borrower or return an error code
785     my $borrowernumber = $cgi->param('patron_id');
786     my $patron = Koha::Patrons->find( $borrowernumber );
787     return { code => 'PatronNotFound' } unless $patron;
788
789     # Get the reserve or return an error code
790     my $reserve_id = $cgi->param('item_id');
791     my $hold = Koha::Holds->find( $reserve_id );
792     return { code => 'RecordNotFound' } unless $hold;
793     return { code => 'RecordNotFound' } unless ($hold->borrowernumber == $borrowernumber);
794
795     $hold->cancel;
796
797     return { code => 'Canceled' };
798 }
799
800 =head2 _availability
801
802 Returns, for an itemnumber, an array containing availability information.
803
804  my ($biblionumber, $status, $msg, $location) = _availability($id);
805
806 =cut
807
808 sub _availability {
809     my ($itemnumber) = @_;
810     my $item = GetItem( $itemnumber, undef, undef );
811
812     if ( not $item->{'itemnumber'} ) {
813         return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
814     }
815
816     my $biblionumber = $item->{'biblioitemnumber'};
817     my $library = Koha::Libraries->find( $item->{holdingbranch} );
818     my $location = $library ? $library->branchname : '';
819
820     if ( $item->{'notforloan'} ) {
821         return ( $biblionumber, 'not available', 'Not for loan', $location );
822     } elsif ( $item->{'onloan'} ) {
823         return ( $biblionumber, 'not available', 'Checked out', $location );
824     } elsif ( $item->{'itemlost'} ) {
825         return ( $biblionumber, 'not available', 'Item lost', $location );
826     } elsif ( $item->{'withdrawn'} ) {
827         return ( $biblionumber, 'not available', 'Item withdrawn', $location );
828     } elsif ( $item->{'damaged'} ) {
829         return ( $biblionumber, 'not available', 'Item damaged', $location );
830     } else {
831         return ( $biblionumber, 'available', undef, $location );
832     }
833 }
834
835 1;