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