Bug 14257 - Add show_attributes to GetPatronInfo
[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::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 ($status, $cardnumber, $userid) = C4::Auth::checkpw( C4::Context->dbh, $cgi->param('username'), $cgi->param('password') );
325     if ( $status ) {
326         # Get the borrower
327         my $borrower = GetMember( cardnumber => $cardnumber );
328         my $patron->{'id'} = $borrower->{'borrowernumber'};
329         return $patron;
330     }
331     else {
332         return { code => 'PatronNotFound' };
333     }
334 }
335
336 =head2 GetPatronInfo
337
338 Returns specified information about the patron, based on options in the 
339 request. This function can optionally return patron's contact information, 
340 fine information, hold request information, and loan information.
341
342 Parameters:
343
344   - patron_id (Required)
345         the borrowernumber
346   - show_contact (Optional, default 1)
347         whether or not to return patron's contact information in the response
348   - show_fines (Optional, default 0)
349         whether or not to return fine information in the response
350   - show_holds (Optional, default 0)
351         whether or not to return hold request information in the response
352   - show_loans (Optional, default 0)
353         whether or not to return loan information request information in the response 
354
355 =cut
356
357 sub GetPatronInfo {
358     my ($cgi) = @_;
359
360     # Get Member details
361     my $borrowernumber = $cgi->param('patron_id');
362     my $borrower = GetMemberDetails( $borrowernumber );
363     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
364
365     # Cleaning the borrower hashref
366     $borrower->{'charges'}    = $borrower->{'flags'}->{'CHARGES'}->{'amount'};
367     $borrower->{'branchname'} = GetBranchName( $borrower->{'branchcode'} );
368     delete $borrower->{'flags'};
369     delete $borrower->{'userid'};
370     delete $borrower->{'password'};
371
372     # Contact fields management
373     if ( $cgi->param('show_contact') eq "0" ) {
374
375         # Define contact fields
376         my @contactfields = (
377             'email',              'emailpro',           'fax',                 'mobile',          'phone',             'phonepro',
378             'streetnumber',       'zipcode',            'city',                'streettype',      'B_address',         'B_city',
379             'B_email',            'B_phone',            'B_zipcode',           'address',         'address2',          'altcontactaddress1',
380             'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
381         );
382
383         # and delete them
384         foreach my $field (@contactfields) {
385             delete $borrower->{$field};
386         }
387     }
388
389     # Fines management
390     if ( $cgi->param('show_fines') eq "1" ) {
391         my @charges;
392         for ( my $i = 1 ; my @charge = getcharges( $borrowernumber, undef, $i ) ; $i++ ) {
393             push( @charges, @charge );
394         }
395         $borrower->{'fines'}->{'fine'} = \@charges;
396     }
397
398     # Reserves management
399     if ( $cgi->param('show_holds') eq "1" ) {
400
401         # Get borrower's reserves
402         my @reserves = GetReservesFromBorrowernumber( $borrowernumber, undef );
403         foreach my $reserve (@reserves) {
404
405             # Get additional informations
406             my $item = GetBiblioFromItemNumber( $reserve->{'itemnumber'}, undef );
407             my $branchname = GetBranchName( $reserve->{'branchcode'} );
408
409             # Remove unwanted fields
410             delete $item->{'marc'};
411             delete $item->{'marcxml'};
412             delete $item->{'more_subfields_xml'};
413
414             # Add additional fields
415             $reserve->{'item'}       = $item;
416             $reserve->{'branchname'} = $branchname;
417             $reserve->{'title'}      = GetBiblio( $reserve->{'biblionumber'} )->{'title'};
418         }
419         $borrower->{'holds'}->{'hold'} = \@reserves;
420     }
421
422     # Issues management
423     if ( $cgi->param('show_loans') eq "1" ) {
424         my $issues = GetPendingIssues($borrowernumber);
425         foreach my $issue ( @$issues ){
426             $issue->{'issuedate'} = $issue->{'issuedate'}->strftime('%Y-%m-%d %H:%M');
427             $issue->{'date_due'} = $issue->{'date_due'}->strftime('%Y-%m-%d %H:%M');
428         }
429         $borrower->{'loans'}->{'loan'} = $issues;
430     }
431
432     if ( $cgi->param('show_attributes') eq "1" ) {
433         my $attrs = GetBorrowerAttributes( $borrowernumber, 0, 1 );
434         $borrower->{'attributes'} = $attrs;
435     }
436
437     return $borrower;
438 }
439
440 =head2 GetPatronStatus
441
442 Returns a patron's status information.
443
444 Parameters:
445
446   - patron_id (Required)
447         the borrower ID
448
449 =cut
450
451 sub GetPatronStatus {
452     my ($cgi) = @_;
453
454     # Get Member details
455     my $borrowernumber = $cgi->param('patron_id');
456     my $borrower = GetMemberDetails( $borrowernumber );
457     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
458
459     # Return the results
460     return {
461         type   => $$borrower{categorycode},
462         status => 0, # TODO
463         expiry => $$borrower{dateexpiry},
464     };
465 }
466
467 =head2 GetServices
468
469 Returns information about the services available on a particular item for 
470 a particular patron.
471
472 Parameters:
473
474   - patron_id (Required)
475         a borrowernumber
476   - item_id (Required)
477         an itemnumber
478 =cut
479
480 sub GetServices {
481     my ($cgi) = @_;
482
483     # Get the member, or return an error code if not found
484     my $borrowernumber = $cgi->param('patron_id');
485     my $borrower = GetMemberDetails( $borrowernumber );
486     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
487
488     # Get the item, or return an error code if not found
489     my $itemnumber = $cgi->param('item_id');
490     my $item = GetItem( $itemnumber );
491     return { code => 'RecordNotFound' } unless $$item{itemnumber};
492
493     my @availablefor;
494
495     # Reserve level management
496     my $biblionumber = $item->{'biblionumber'};
497     my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
498     if ($canbookbereserved eq 'OK') {
499         push @availablefor, 'title level hold';
500         my $canitembereserved = IsAvailableForItemLevelRequest($item, $borrower);
501         if ($canitembereserved) {
502             push @availablefor, 'item level hold';
503         }
504     }
505
506     # Reserve cancellation management
507     my @reserves = GetReservesFromBorrowernumber( $borrowernumber, undef );
508     my @reserveditems;
509     foreach my $reserve (@reserves) {
510         push @reserveditems, $reserve->{'itemnumber'};
511     }
512     if ( grep { $itemnumber eq $_ } @reserveditems ) {
513         push @availablefor, 'hold cancellation';
514     }
515
516     # Renewal management
517     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
518     if ( $renewal[0] ) {
519         push @availablefor, 'loan renewal';
520     }
521
522     # Issuing management
523     my $barcode = $item->{'barcode'} || '';
524     $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
525     if ($barcode) {
526         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode );
527
528         # TODO push @availablefor, 'loan';
529     }
530
531     my $out;
532     $out->{'AvailableFor'} = \@availablefor;
533
534     return $out;
535 }
536
537 =head2 RenewLoan
538
539 Extends the due date for a borrower's existing issue.
540
541 Parameters:
542
543   - patron_id (Required)
544         a borrowernumber
545   - item_id (Required)
546         an itemnumber
547   - desired_due_date (Required)
548         the date the patron would like the item returned by 
549
550 =cut
551
552 sub RenewLoan {
553     my ($cgi) = @_;
554
555     # Get borrower infos or return an error code
556     my $borrowernumber = $cgi->param('patron_id');
557     my $borrower = GetMemberDetails( $borrowernumber );
558     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
559
560     # Get the item, or return an error code
561     my $itemnumber = $cgi->param('item_id');
562     my $item = GetItem( $itemnumber );
563     return { code => 'RecordNotFound' } unless $$item{itemnumber};
564
565     # Add renewal if possible
566     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
567     if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber ); }
568
569     my $issue = GetItemIssue($itemnumber);
570
571     # Hashref building
572     my $out;
573     $out->{'renewals'} = $issue->{'renewals'};
574     $out->{date_due}   = $issue->{date_due}->strftime('%Y-%m-%d %H:%S');
575     $out->{'success'}  = $renewal[0];
576     $out->{'error'}    = $renewal[1];
577
578     return $out;
579 }
580
581 =head2 HoldTitle
582
583 Creates, for a borrower, a biblio-level hold reserve.
584
585 Parameters:
586
587   - patron_id (Required)
588         a borrowernumber
589   - bib_id (Required)
590         a biblionumber
591   - request_location (Required)
592         IP address where the end user request is being placed
593   - pickup_location (Optional)
594         a branch code indicating the location to which to deliver the item for pickup
595   - needed_before_date (Optional)
596         date after which hold request is no longer needed
597   - pickup_expiry_date (Optional)
598         date after which item returned to shelf if item is not picked up 
599
600 =cut
601
602 sub HoldTitle {
603     my ($cgi) = @_;
604
605     # Get the borrower or return an error code
606     my $borrowernumber = $cgi->param('patron_id');
607     my $borrower = GetMemberDetails( $borrowernumber );
608     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
609
610     # Get the biblio record, or return an error code
611     my $biblionumber = $cgi->param('bib_id');
612     my $biblio = GetBiblio( $biblionumber );
613     return { code => 'RecordNotFound' } unless $$biblio{biblionumber};
614     
615     my $title = $$biblio{title};
616
617     # Check if the biblio can be reserved
618     return { code => 'NotHoldable' } unless CanBookBeReserved( $borrowernumber, $biblionumber ) eq 'OK';
619
620     my $branch;
621
622     # Pickup branch management
623     if ( $cgi->param('pickup_location') ) {
624         $branch = $cgi->param('pickup_location');
625         my $branches = GetBranches;
626         return { code => 'LocationNotFound' } unless $$branches{$branch};
627     } else { # if the request provide no branch, use the borrower's branch
628         $branch = $$borrower{branchcode};
629     }
630
631     # Add the reserve
632     #    $branch,    $borrowernumber, $biblionumber,
633     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
634     #    $title,      $checkitem, $found
635     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
636     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, undef, undef );
637
638     # Hashref building
639     my $out;
640     $out->{'title'}           = $title;
641     $out->{'pickup_location'} = GetBranchName($branch);
642
643     # TODO $out->{'date_available'}  = '';
644
645     return $out;
646 }
647
648 =head2 HoldItem
649
650 Creates, for a borrower, an item-level hold request on a specific item of 
651 a bibliographic record in Koha.
652
653 Parameters:
654
655   - patron_id (Required)
656         a borrowernumber
657   - bib_id (Required)
658         a biblionumber
659   - item_id (Required)
660         an itemnumber
661   - pickup_location (Optional)
662         a branch code indicating the location to which to deliver the item for pickup
663   - needed_before_date (Optional)
664         date after which hold request is no longer needed
665   - pickup_expiry_date (Optional)
666         date after which item returned to shelf if item is not picked up 
667
668 =cut
669
670 sub HoldItem {
671     my ($cgi) = @_;
672
673     # Get the borrower or return an error code
674     my $borrowernumber = $cgi->param('patron_id');
675     my $borrower = GetMemberDetails( $borrowernumber );
676     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
677
678     # Get the biblio or return an error code
679     my $biblionumber = $cgi->param('bib_id');
680     my $biblio = GetBiblio($biblionumber);
681     return { code => 'RecordNotFound' } unless $$biblio{biblionumber};
682
683     my $title = $$biblio{title};
684
685     # Get the item or return an error code
686     my $itemnumber = $cgi->param('item_id');
687     my $item = GetItem( $itemnumber );
688     return { code => 'RecordNotFound' } unless $$item{itemnumber};
689
690     # If the biblio does not match the item, return an error code
691     return { code => 'RecordNotFound' } if $$item{biblionumber} ne $$biblio{biblionumber};
692
693     # Check for item disponibility
694     my $canitembereserved = C4::Reserves::CanItemBeReserved( $borrowernumber, $itemnumber );
695     my $canbookbereserved = C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber );
696     return { code => 'NotHoldable' } unless $canbookbereserved eq 'OK' and $canitembereserved eq 'OK';
697
698     # Pickup branch management
699     my $branch;
700     if ( $cgi->param('pickup_location') ) {
701         $branch = $cgi->param('pickup_location');
702         my $branches = GetBranches();
703         return { code => 'LocationNotFound' } unless $$branches{$branch};
704     } else { # if the request provide no branch, use the borrower's branch
705         $branch = $$borrower{branchcode};
706     }
707
708     # Add the reserve
709     #    $branch,    $borrowernumber, $biblionumber,
710     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
711     #    $title,      $checkitem, $found
712     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
713     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, $itemnumber, undef );
714
715     # Hashref building
716     my $out;
717     $out->{'pickup_location'} = GetBranchName($branch);
718
719     # TODO $out->{'date_available'} = '';
720
721     return $out;
722 }
723
724 =head2 CancelHold
725
726 Cancels an active reserve request for the borrower.
727
728 Parameters:
729
730   - patron_id (Required)
731         a borrowernumber
732   - item_id (Required)
733         a reserve_id
734
735 =cut
736
737 sub CancelHold {
738     my ($cgi) = @_;
739
740     # Get the borrower or return an error code
741     my $borrowernumber = $cgi->param('patron_id');
742     my $borrower = GetMemberDetails( $borrowernumber );
743     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
744
745     # Get the reserve or return an error code
746     my $reserve_id = $cgi->param('item_id');
747     my $reserve = C4::Reserves::GetReserve($reserve_id);
748     return { code => 'RecordNotFound' } unless $reserve;
749     return { code => 'RecordNotFound' } unless ($reserve->{borrowernumber} == $borrowernumber);
750
751     C4::Reserves::CancelReserve({reserve_id => $reserve_id});
752
753     return { code => 'Canceled' };
754 }
755
756 =head2 _availability
757
758 Returns, for an itemnumber, an array containing availability information.
759
760  my ($biblionumber, $status, $msg, $location) = _availability($id);
761
762 =cut
763
764 sub _availability {
765     my ($itemnumber) = @_;
766     my $item = GetItem( $itemnumber, undef, undef );
767
768     if ( not $item->{'itemnumber'} ) {
769         return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
770     }
771
772     my $biblionumber = $item->{'biblioitemnumber'};
773     my $location     = GetBranchName( $item->{'holdingbranch'} );
774
775     if ( $item->{'notforloan'} ) {
776         return ( $biblionumber, 'not available', 'Not for loan', $location );
777     } elsif ( $item->{'onloan'} ) {
778         return ( $biblionumber, 'not available', 'Checked out', $location );
779     } elsif ( $item->{'itemlost'} ) {
780         return ( $biblionumber, 'not available', 'Item lost', $location );
781     } elsif ( $item->{'withdrawn'} ) {
782         return ( $biblionumber, 'not available', 'Item withdrawn', $location );
783     } elsif ( $item->{'damaged'} ) {
784         return ( $biblionumber, 'not available', 'Item damaged', $location );
785     } else {
786         return ( $biblionumber, 'available', undef, $location );
787     }
788 }
789
790 1;