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