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