Bug 17317: ILSDI: Getavailability method with id_type=bib implementation
[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 GetReservesFromBiblionumber GetReservesFromBorrowernumber 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
38 use Koha::Libraries;
39
40 =head1 NAME
41
42 C4::ILS-DI::Services - ILS-DI Services
43
44 =head1 DESCRIPTION
45
46 Each function in this module represents an ILS-DI service.
47 They all takes a CGI instance as argument and most of them return a 
48 hashref that will be printed by XML::Simple in opac/ilsdi.pl
49
50 =head1 SYNOPSIS
51
52         use C4::ILSDI::Services;
53         use XML::Simple;
54         use CGI qw ( -utf8 );
55
56         my $cgi = new CGI;
57
58         $out = LookupPatron($cgi);
59
60         print CGI::header('text/xml');
61         print XMLout($out,
62                 noattr => 1, 
63                 noescape => 1,
64                 nosort => 1,
65                 xmldecl => '<?xml version="1.0" encoding="UTF-8" ?>',
66                 RootName => 'LookupPatron', 
67                 SuppressEmpty => 1);
68
69 =cut
70
71 =head1 FUNCTIONS
72
73 =head2 GetAvailability
74
75 Given a set of biblionumbers or itemnumbers, returns a list with 
76 availability of the items associated with the identifiers.
77
78 Parameters:
79
80 =head3 id (Required)
81
82 list of either biblionumbers or itemnumbers
83
84 =head3 id_type (Required)
85
86 defines the type of record identifier being used in the request, 
87 possible values:
88
89   - bib
90   - item
91
92 =head3 return_type (Optional)
93
94 requests a particular level of detail in reporting availability, 
95 possible values:
96
97   - bib
98   - item
99
100 =head3 return_fmt (Optional)
101
102 requests a particular format or set of formats in reporting 
103 availability 
104
105 =cut
106
107 sub GetAvailability {
108     my ($cgi) = @_;
109
110     my $out = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
111     $out .= "<dlf:collection\n";
112     $out .= "  xmlns:dlf=\"http://diglib.org/ilsdi/1.1\"\n";
113     $out .= "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
114     $out .= "  xsi:schemaLocation=\"http://diglib.org/ilsdi/1.1\n";
115     $out .= "    http://diglib.org/architectures/ilsdi/schemas/1.1/dlfexpanded.xsd\">\n";
116
117     foreach my $id ( split( / /, $cgi->param('id') ) ) {
118         if ( $cgi->param('id_type') eq "item" ) {
119             my ( $biblionumber, $status, $msg, $location ) = _availability($id);
120
121             $out .= "  <dlf:record>\n";
122             $out .= "    <dlf:bibliographic id=\"" . ( $biblionumber || $id ) . "\" />\n";
123             $out .= "    <dlf:items>\n";
124             $out .= "      <dlf:item id=\"" . $id . "\">\n";
125             $out .= "        <dlf:simpleavailability>\n";
126             $out .= "          <dlf:identifier>" . $id . "</dlf:identifier>\n";
127             $out .= "          <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
128             if ($msg)      { $out .= "          <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
129             if ($location) { $out .= "          <dlf:location>" . $location . "</dlf:location>\n"; }
130             $out .= "        </dlf:simpleavailability>\n";
131             $out .= "      </dlf:item>\n";
132             $out .= "    </dlf:items>\n";
133             $out .= "  </dlf:record>\n";
134         } else {
135             my $status;
136             my $msg;
137             my $biblioitem = ( GetBiblioItemByBiblioNumber( $id, undef ) )[0];
138             if ($biblioitem) {
139                 my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
140                 my $items            = (GetItemsByBiblioitemnumber($biblioitemnumber))[0];
141
142                 # We loop over the items to clean them
143                 my $itemnumber;
144                 foreach my $item (@$items) {
145                     $itemnumber=$item->{'itemnumber'};
146                     my ( $biblionumber, $status, $msg, $location ) = _availability($itemnumber);
147                     $out .= "  <dlf:record>\n";
148                     $out .= "    <dlf:bibliographic id=\"" . ( $biblionumber || $itemnumber ) . "\" />\n";
149                     $out .= "    <dlf:items>\n";
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                     $out .= "    </dlf:items>\n";
159                     $out .= "  </dlf:record>\n";
160                 }
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($biblionumber, $embed_items);
217         if ($record) {
218             $biblioitem->{marcxml} = $record->as_xml_record();
219         }
220
221         # Get most of the needed data
222         my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
223         my $reserves         = GetReservesFromBiblionumber({ biblionumber => $biblionumber });
224         my $issues           = GetBiblioIssues($biblionumber);
225         my $items            = GetItemsByBiblioitemnumber($biblioitemnumber);
226
227         # We loop over the items to clean them
228         foreach my $item (@$items) {
229
230             # This hides additionnal XML subfields, we don't need these info
231             delete $item->{'more_subfields_xml'};
232
233             # Display branch names instead of branch codes
234             my $home_library    = Koha::Libraries->find( $item->{homebranch} );
235             my $holding_library = Koha::Libraries->find( $item->{holdingbranch} );
236             $item->{'homebranchname'}    = $home_library    ? $home_library->branchname    : '';
237             $item->{'holdingbranchname'} = $holding_library ? $holding_library->branchname : '';
238         }
239
240         # Hashref building...
241         $biblioitem->{'items'}->{'item'}       = $items;
242         $biblioitem->{'reserves'}->{'reserve'} = $reserves;
243         $biblioitem->{'issues'}->{'issue'}     = $issues;
244
245         push @records, $biblioitem;
246     }
247
248     return { record => \@records };
249 }
250
251 =head2 GetAuthorityRecords
252
253 Given a list of authority record identifiers, returns a list of record 
254 objects that contain the authority records. The function user may request 
255 a specific metadata schema for the record objects.
256
257 Parameters:
258
259   - id (Required)
260     list of authority record identifiers
261   - schema (Optional)
262     specifies the metadata schema of records to be returned, possible values:
263       - MARCXML
264
265 =cut
266
267 sub GetAuthorityRecords {
268     my ($cgi) = @_;
269
270     # If the user asks for an unsupported schema, return an error code
271     if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
272         return { code => 'UnsupportedSchema' };
273     }
274
275     my @records;
276
277     # Let's loop over the authority IDs
278     foreach my $authid ( split( / /, $cgi->param('id') ) ) {
279
280         # Get the record as XML string, or error code
281         push @records, GetAuthorityXML($authid) || { code => 'RecordNotFound' };
282     }
283
284     return { record => \@records };
285 }
286
287 =head2 LookupPatron
288
289 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
290
291 Parameters:
292
293   - id (Required)
294         an identifier used to look up the patron in Koha
295   - id_type (Optional)
296         the type of the identifier, possible values:
297         - cardnumber
298         - firstname
299         - userid
300         - borrowernumber
301
302 =cut
303
304 sub LookupPatron {
305     my ($cgi) = @_;
306
307     # Get the borrower...
308     my $borrower = GetMember($cgi->param('id_type') => $cgi->param('id'));
309     if ( not $borrower->{'borrowernumber'} ) {
310         return { message => 'PatronNotFound' };
311     }
312
313     # Build the hashref
314     my $patron->{'id'} = $borrower->{'borrowernumber'};
315     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
316
317     # ...and return his ID
318     return $patron;
319 }
320
321 =head2 AuthenticatePatron
322
323 Authenticates a user's login credentials and returns the identifier for 
324 the patron.
325
326 Parameters:
327
328   - username (Required)
329     user's login identifier (userid or cardnumber)
330   - password (Required)
331     user's password
332
333 =cut
334
335 sub AuthenticatePatron {
336     my ($cgi) = @_;
337     my $username = $cgi->param('username');
338     my $password = $cgi->param('password');
339     my ($status, $cardnumber, $userid) = C4::Auth::checkpw( C4::Context->dbh, $username, $password );
340     if ( $status ) {
341         # Get the borrower
342         my $borrower = GetMember( cardnumber => $cardnumber );
343         my $patron->{'id'} = $borrower->{'borrowernumber'};
344         return $patron;
345     }
346     else {
347         return { code => 'PatronNotFound' };
348     }
349 }
350
351 =head2 GetPatronInfo
352
353 Returns specified information about the patron, based on options in the 
354 request. This function can optionally return patron's contact information, 
355 fine information, hold request information, and loan information.
356
357 Parameters:
358
359   - patron_id (Required)
360         the borrowernumber
361   - show_contact (Optional, default 1)
362         whether or not to return patron's contact information in the response
363   - show_fines (Optional, default 0)
364         whether or not to return fine information in the response
365   - show_holds (Optional, default 0)
366         whether or not to return hold request information in the response
367   - show_loans (Optional, default 0)
368         whether or not to return loan information request information in the response 
369
370 =cut
371
372 sub GetPatronInfo {
373     my ($cgi) = @_;
374
375     # Get Member details
376     my $borrowernumber = $cgi->param('patron_id');
377     my $borrower = GetMember( borrowernumber => $borrowernumber );
378     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
379
380     # Cleaning the borrower hashref
381     my $flags = C4::Members::patronflags( $borrower );
382     $borrower->{'charges'}    = $flags>{'CHARGES'}->{'amount'};
383     my $library = Koha::Libraries->find( $borrower->{branchcode} );
384     $borrower->{'branchname'} = $library ? $library->branchname : '';
385     delete $borrower->{'userid'};
386     delete $borrower->{'password'};
387
388     # Contact fields management
389     if ( defined $cgi->param('show_contact') && $cgi->param('show_contact') eq "0" ) {
390
391         # Define contact fields
392         my @contactfields = (
393             'email',              'emailpro',           'fax',                 'mobile',          'phone',             'phonepro',
394             'streetnumber',       'zipcode',            'city',                'streettype',      'B_address',         'B_city',
395             'B_email',            'B_phone',            'B_zipcode',           'address',         'address2',          'altcontactaddress1',
396             'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
397         );
398
399         # and delete them
400         foreach my $field (@contactfields) {
401             delete $borrower->{$field};
402         }
403     }
404
405     # Fines management
406     if ( $cgi->param('show_fines') && $cgi->param('show_fines') eq "1" ) {
407         my @charges;
408         for ( my $i = 1 ; my @charge = getcharges( $borrowernumber, undef, $i ) ; $i++ ) {
409             push( @charges, @charge );
410         }
411         $borrower->{'fines'}->{'fine'} = \@charges;
412     }
413
414     # Reserves management
415     if ( $cgi->param('show_holds') && $cgi->param('show_holds') eq "1" ) {
416
417         # Get borrower's reserves
418         my @reserves = GetReservesFromBorrowernumber( $borrowernumber, undef );
419         foreach my $reserve (@reserves) {
420
421             # Get additional informations
422             my $item = GetBiblioFromItemNumber( $reserve->{'itemnumber'}, undef );
423             my $library = Koha::Libraries->find( $reserve->{branchcode} );
424             my $branchname = $library ? $library->branchname : '';
425
426             # Remove unwanted fields
427             delete $item->{'marcxml'};
428             delete $item->{'more_subfields_xml'};
429
430             # Add additional fields
431             $reserve->{'item'}       = $item;
432             $reserve->{'branchname'} = $branchname;
433             $reserve->{'title'}      = GetBiblio( $reserve->{'biblionumber'} )->{'title'};
434         }
435         $borrower->{'holds'}->{'hold'} = \@reserves;
436     }
437
438     # Issues management
439     if ( $cgi->param('show_loans') && $cgi->param('show_loans') eq "1" ) {
440         my $issues = GetPendingIssues($borrowernumber);
441         foreach my $issue ( @$issues ){
442             $issue->{'issuedate'} = $issue->{'issuedate'}->strftime('%Y-%m-%d %H:%M');
443             $issue->{'date_due'} = $issue->{'date_due'}->strftime('%Y-%m-%d %H:%M');
444         }
445         $borrower->{'loans'}->{'loan'} = $issues;
446     }
447
448     if ( $cgi->param('show_attributes') eq "1" ) {
449         my $attrs = GetBorrowerAttributes( $borrowernumber, 0, 1 );
450         $borrower->{'attributes'} = $attrs;
451     }
452
453     return $borrower;
454 }
455
456 =head2 GetPatronStatus
457
458 Returns a patron's status information.
459
460 Parameters:
461
462   - patron_id (Required)
463         the borrower ID
464
465 =cut
466
467 sub GetPatronStatus {
468     my ($cgi) = @_;
469
470     # Get Member details
471     my $borrowernumber = $cgi->param('patron_id');
472     my $borrower = GetMember( borrowernumber => $borrowernumber );
473     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
474
475     # Return the results
476     return {
477         type   => $$borrower{categorycode},
478         status => 0, # TODO
479         expiry => $$borrower{dateexpiry},
480     };
481 }
482
483 =head2 GetServices
484
485 Returns information about the services available on a particular item for 
486 a particular patron.
487
488 Parameters:
489
490   - patron_id (Required)
491         a borrowernumber
492   - item_id (Required)
493         an itemnumber
494 =cut
495
496 sub GetServices {
497     my ($cgi) = @_;
498
499     # Get the member, or return an error code if not found
500     my $borrowernumber = $cgi->param('patron_id');
501     my $borrower = GetMember( borrowernumber => $borrowernumber );
502     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
503
504     # Get the item, or return an error code if not found
505     my $itemnumber = $cgi->param('item_id');
506     my $item = GetItem( $itemnumber );
507     return { code => 'RecordNotFound' } unless $$item{itemnumber};
508
509     my @availablefor;
510
511     # Reserve level management
512     my $biblionumber = $item->{'biblionumber'};
513     my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
514     if ($canbookbereserved eq 'OK') {
515         push @availablefor, 'title level hold';
516         my $canitembereserved = IsAvailableForItemLevelRequest($item, $borrower);
517         if ($canitembereserved) {
518             push @availablefor, 'item level hold';
519         }
520     }
521
522     # Reserve cancellation management
523     my @reserves = GetReservesFromBorrowernumber( $borrowernumber, undef );
524     my @reserveditems;
525     foreach my $reserve (@reserves) {
526         push @reserveditems, $reserve->{'itemnumber'};
527     }
528     if ( grep { $itemnumber eq $_ } @reserveditems ) {
529         push @availablefor, 'hold cancellation';
530     }
531
532     # Renewal management
533     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
534     if ( $renewal[0] ) {
535         push @availablefor, 'loan renewal';
536     }
537
538     # Issuing management
539     my $barcode = $item->{'barcode'} || '';
540     $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
541     if ($barcode) {
542         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode );
543
544         # TODO push @availablefor, 'loan';
545     }
546
547     my $out;
548     $out->{'AvailableFor'} = \@availablefor;
549
550     return $out;
551 }
552
553 =head2 RenewLoan
554
555 Extends the due date for a borrower's existing issue.
556
557 Parameters:
558
559   - patron_id (Required)
560         a borrowernumber
561   - item_id (Required)
562         an itemnumber
563   - desired_due_date (Required)
564         the date the patron would like the item returned by 
565
566 =cut
567
568 sub RenewLoan {
569     my ($cgi) = @_;
570
571     # Get borrower infos or return an error code
572     my $borrowernumber = $cgi->param('patron_id');
573     my $borrower = GetMember( borrowernumber => $borrowernumber );
574     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
575
576     # Get the item, or return an error code
577     my $itemnumber = $cgi->param('item_id');
578     my $item = GetItem( $itemnumber );
579     return { code => 'RecordNotFound' } unless $$item{itemnumber};
580
581     # Add renewal if possible
582     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
583     if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber ); }
584
585     my $issue = GetItemIssue($itemnumber);
586
587     # Hashref building
588     my $out;
589     $out->{'renewals'} = $issue->{'renewals'};
590     $out->{date_due}   = $issue->{date_due}->strftime('%Y-%m-%d %H:%S');
591     $out->{'success'}  = $renewal[0];
592     $out->{'error'}    = $renewal[1];
593
594     return $out;
595 }
596
597 =head2 HoldTitle
598
599 Creates, for a borrower, a biblio-level hold reserve.
600
601 Parameters:
602
603   - patron_id (Required)
604         a borrowernumber
605   - bib_id (Required)
606         a biblionumber
607   - request_location (Required)
608         IP address where the end user request is being placed
609   - pickup_location (Optional)
610         a branch code indicating the location to which to deliver the item for pickup
611   - needed_before_date (Optional)
612         date after which hold request is no longer needed
613   - pickup_expiry_date (Optional)
614         date after which item returned to shelf if item is not picked up 
615
616 =cut
617
618 sub HoldTitle {
619     my ($cgi) = @_;
620
621     # Get the borrower or return an error code
622     my $borrowernumber = $cgi->param('patron_id');
623     my $borrower = GetMember( borrowernumber => $borrowernumber );
624     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
625
626     # Get the biblio record, or return an error code
627     my $biblionumber = $cgi->param('bib_id');
628     my $biblio = GetBiblio( $biblionumber );
629     return { code => 'RecordNotFound' } unless $$biblio{biblionumber};
630     
631     my $title = $$biblio{title};
632
633     # Check if the biblio can be reserved
634     return { code => 'NotHoldable' } unless CanBookBeReserved( $borrowernumber, $biblionumber ) eq 'OK';
635
636     my $branch;
637
638     # Pickup branch management
639     if ( $cgi->param('pickup_location') ) {
640         $branch = $cgi->param('pickup_location');
641         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
642     } else { # if the request provide no branch, use the borrower's branch
643         $branch = $$borrower{branchcode};
644     }
645
646     # Add the reserve
647     #    $branch,    $borrowernumber, $biblionumber,
648     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
649     #    $title,      $checkitem, $found
650     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
651     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, undef, undef );
652
653     # Hashref building
654     my $out;
655     $out->{'title'}           = $title;
656     my $library = Koha::Libraries->find( $branch );
657     $out->{'pickup_location'} = $library ? $library->branchname : '';
658
659     # TODO $out->{'date_available'}  = '';
660
661     return $out;
662 }
663
664 =head2 HoldItem
665
666 Creates, for a borrower, an item-level hold request on a specific item of 
667 a bibliographic record in Koha.
668
669 Parameters:
670
671   - patron_id (Required)
672         a borrowernumber
673   - bib_id (Required)
674         a biblionumber
675   - item_id (Required)
676         an itemnumber
677   - pickup_location (Optional)
678         a branch code indicating the location to which to deliver the item for pickup
679   - needed_before_date (Optional)
680         date after which hold request is no longer needed
681   - pickup_expiry_date (Optional)
682         date after which item returned to shelf if item is not picked up 
683
684 =cut
685
686 sub HoldItem {
687     my ($cgi) = @_;
688
689     # Get the borrower or return an error code
690     my $borrowernumber = $cgi->param('patron_id');
691     my $borrower = GetMember( borrowernumber => $borrowernumber );
692     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
693
694     # Get the biblio or return an error code
695     my $biblionumber = $cgi->param('bib_id');
696     my $biblio = GetBiblio($biblionumber);
697     return { code => 'RecordNotFound' } unless $$biblio{biblionumber};
698
699     my $title = $$biblio{title};
700
701     # Get the item or return an error code
702     my $itemnumber = $cgi->param('item_id');
703     my $item = GetItem( $itemnumber );
704     return { code => 'RecordNotFound' } unless $$item{itemnumber};
705
706     # If the biblio does not match the item, return an error code
707     return { code => 'RecordNotFound' } if $$item{biblionumber} ne $$biblio{biblionumber};
708
709     # Check for item disponibility
710     my $canitembereserved = C4::Reserves::CanItemBeReserved( $borrowernumber, $itemnumber );
711     my $canbookbereserved = C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber );
712     return { code => 'NotHoldable' } unless $canbookbereserved eq 'OK' and $canitembereserved eq 'OK';
713
714     # Pickup branch management
715     my $branch;
716     if ( $cgi->param('pickup_location') ) {
717         $branch = $cgi->param('pickup_location');
718         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
719     } else { # if the request provide no branch, use the borrower's branch
720         $branch = $$borrower{branchcode};
721     }
722
723     # Add the reserve
724     #    $branch,    $borrowernumber, $biblionumber,
725     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
726     #    $title,      $checkitem, $found
727     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
728     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, $itemnumber, undef );
729
730     # Hashref building
731     my $out;
732     my $library = Koha::Libraries->find( $branch );
733     $out->{'pickup_location'} = $library ? $library->branchname : '';
734
735     # TODO $out->{'date_available'} = '';
736
737     return $out;
738 }
739
740 =head2 CancelHold
741
742 Cancels an active reserve request for the borrower.
743
744 Parameters:
745
746   - patron_id (Required)
747         a borrowernumber
748   - item_id (Required)
749         a reserve_id
750
751 =cut
752
753 sub CancelHold {
754     my ($cgi) = @_;
755
756     # Get the borrower or return an error code
757     my $borrowernumber = $cgi->param('patron_id');
758     my $borrower = GetMember( borrowernumber => $borrowernumber );
759     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
760
761     # Get the reserve or return an error code
762     my $reserve_id = $cgi->param('item_id');
763     my $reserve = C4::Reserves::GetReserve($reserve_id);
764     return { code => 'RecordNotFound' } unless $reserve;
765     return { code => 'RecordNotFound' } unless ($reserve->{borrowernumber} == $borrowernumber);
766
767     C4::Reserves::CancelReserve({reserve_id => $reserve_id});
768
769     return { code => 'Canceled' };
770 }
771
772 =head2 _availability
773
774 Returns, for an itemnumber, an array containing availability information.
775
776  my ($biblionumber, $status, $msg, $location) = _availability($id);
777
778 =cut
779
780 sub _availability {
781     my ($itemnumber) = @_;
782     my $item = GetItem( $itemnumber, undef, undef );
783
784     if ( not $item->{'itemnumber'} ) {
785         return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
786     }
787
788     my $biblionumber = $item->{'biblioitemnumber'};
789     my $library = Koha::Libraries->find( $item->{holdingbranch} );
790     my $location = $library ? $library->branchname : '';
791
792     if ( $item->{'notforloan'} ) {
793         return ( $biblionumber, 'not available', 'Not for loan', $location );
794     } elsif ( $item->{'onloan'} ) {
795         return ( $biblionumber, 'not available', 'Checked out', $location );
796     } elsif ( $item->{'itemlost'} ) {
797         return ( $biblionumber, 'not available', 'Item lost', $location );
798     } elsif ( $item->{'withdrawn'} ) {
799         return ( $biblionumber, 'not available', 'Item withdrawn', $location );
800     } elsif ( $item->{'damaged'} ) {
801         return ( $biblionumber, 'not available', 'Item damaged', $location );
802     } else {
803         return ( $biblionumber, 'available', undef, $location );
804     }
805 }
806
807 1;