c1632d545324540aa1ee0c7b9a8e56342a8c5112
[koha.git] / C4 / ILSDI / Services.pm
1 package C4::ILSDI::Services;
2
3 # Copyright 2009 SARL Biblibre
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use strict;
21 use warnings;
22
23 use C4::Members;
24 use C4::Items;
25 use C4::Circulation;
26 use C4::Accounts;
27 use C4::Biblio;
28 use C4::Reserves qw(AddReserve CanBookBeReserved CanItemBeReserved IsAvailableForItemLevelRequest);
29 use C4::Context;
30 use C4::AuthoritiesMarc;
31 use XML::Simple;
32 use HTML::Entities;
33 use CGI qw ( -utf8 );
34 use DateTime;
35 use C4::Auth;
36 use C4::Members::Attributes qw(GetBorrowerAttributes);
37
38 use Koha::Biblios;
39 use Koha::Libraries;
40 use Koha::Patrons;
41
42 =head1 NAME
43
44 C4::ILS-DI::Services - ILS-DI Services
45
46 =head1 DESCRIPTION
47
48 Each function in this module represents an ILS-DI service.
49 They all takes a CGI instance as argument and most of them return a 
50 hashref that will be printed by XML::Simple in opac/ilsdi.pl
51
52 =head1 SYNOPSIS
53
54         use C4::ILSDI::Services;
55         use XML::Simple;
56         use CGI qw ( -utf8 );
57
58         my $cgi = new CGI;
59
60         $out = LookupPatron($cgi);
61
62         print CGI::header('text/xml');
63         print XMLout($out,
64                 noattr => 1, 
65                 noescape => 1,
66                 nosort => 1,
67                 xmldecl => '<?xml version="1.0" encoding="UTF-8" ?>',
68                 RootName => 'LookupPatron', 
69                 SuppressEmpty => 1);
70
71 =cut
72
73 =head1 FUNCTIONS
74
75 =head2 GetAvailability
76
77 Given a set of biblionumbers or itemnumbers, returns a list with 
78 availability of the items associated with the identifiers.
79
80 Parameters:
81
82 =head3 id (Required)
83
84 list of either biblionumbers or itemnumbers
85
86 =head3 id_type (Required)
87
88 defines the type of record identifier being used in the request, 
89 possible values:
90
91   - bib
92   - item
93
94 =head3 return_type (Optional)
95
96 requests a particular level of detail in reporting availability, 
97 possible values:
98
99   - bib
100   - item
101
102 =head3 return_fmt (Optional)
103
104 requests a particular format or set of formats in reporting 
105 availability 
106
107 =cut
108
109 sub GetAvailability {
110     my ($cgi) = @_;
111
112     my $out = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
113     $out .= "<dlf:collection\n";
114     $out .= "  xmlns:dlf=\"http://diglib.org/ilsdi/1.1\"\n";
115     $out .= "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
116     $out .= "  xsi:schemaLocation=\"http://diglib.org/ilsdi/1.1\n";
117     $out .= "    http://diglib.org/architectures/ilsdi/schemas/1.1/dlfexpanded.xsd\">\n";
118
119     foreach my $id ( split( / /, $cgi->param('id') ) ) {
120         if ( $cgi->param('id_type') eq "item" ) {
121             my ( $biblionumber, $status, $msg, $location ) = _availability($id);
122
123             $out .= "  <dlf:record>\n";
124             $out .= "    <dlf:bibliographic id=\"" . ( $biblionumber || $id ) . "\" />\n";
125             $out .= "    <dlf:items>\n";
126             $out .= "      <dlf:item id=\"" . $id . "\">\n";
127             $out .= "        <dlf:simpleavailability>\n";
128             $out .= "          <dlf:identifier>" . $id . "</dlf:identifier>\n";
129             $out .= "          <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
130             if ($msg)      { $out .= "          <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
131             if ($location) { $out .= "          <dlf:location>" . $location . "</dlf:location>\n"; }
132             $out .= "        </dlf:simpleavailability>\n";
133             $out .= "      </dlf:item>\n";
134             $out .= "    </dlf:items>\n";
135             $out .= "  </dlf:record>\n";
136         } else {
137             my $status;
138             my $msg;
139             my $items = GetItemnumbersForBiblio($id);
140             if ($items) {
141                 # Open XML
142                 $out .= "  <dlf:record>\n";
143                 $out .= "    <dlf:bibliographic id=\"" .$id. "\" />\n";
144                 $out .= "    <dlf:items>\n";
145                 # We loop over the items to clean them
146                 foreach my $itemnumber (@$items) {
147                     my ( $biblionumber, $status, $msg, $location ) = _availability($itemnumber);
148                     $out .= "      <dlf:item id=\"" . $itemnumber . "\">\n";
149                     $out .= "        <dlf:simpleavailability>\n";
150                     $out .= "          <dlf:identifier>" . $itemnumber . "</dlf:identifier>\n";
151                     $out .= "          <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
152                     if ($msg)      { $out .= "          <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
153                     if ($location) { $out .= "          <dlf:location>" . $location . "</dlf:location>\n"; }
154                     $out .= "        </dlf:simpleavailability>\n";
155                     $out .= "      </dlf:item>\n";
156                 }
157                 # Close XML
158                 $out .= "    </dlf:items>\n";
159                 $out .= "  </dlf:record>\n";
160             } else {
161                 $status = "unknown";
162                 $msg    = "Error: could not retrieve availability for this ID";
163             }
164         }
165     }
166     $out .= "</dlf:collection>\n";
167
168     return $out;
169 }
170
171 =head2 GetRecords
172
173 Given a list of biblionumbers, returns a list of record objects that 
174 contain bibliographic information, as well as associated holdings and item
175 information. The caller may request a specific metadata schema for the 
176 record objects to be returned.
177
178 This function behaves similarly to HarvestBibliographicRecords and 
179 HarvestExpandedRecords in Data Aggregation, but allows quick, real time 
180 lookup by bibliographic identifier.
181
182 You can use OAI-PMH ListRecords instead of this service.
183
184 Parameters:
185
186   - id (Required)
187         list of system record identifiers
188   - id_type (Optional)
189         Defines the metadata schema in which the records are returned, 
190         possible values:
191           - MARCXML
192
193 =cut
194
195 sub GetRecords {
196     my ($cgi) = @_;
197
198     # Check if the schema is supported. For now, GetRecords only supports MARCXML
199     if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
200         return { code => 'UnsupportedSchema' };
201     }
202
203     my @records;
204
205     # Loop over biblionumbers
206     foreach my $biblionumber ( split( / /, $cgi->param('id') ) ) {
207
208         # Get the biblioitem from the biblionumber
209         my $biblioitem = ( GetBiblioItemByBiblioNumber( $biblionumber, undef ) )[0];
210         if ( not $biblioitem->{'biblionumber'} ) {
211             $biblioitem->{code} = "RecordNotFound";
212         }
213
214         my $embed_items = 1;
215         my $record = GetMarcBiblio($biblionumber, $embed_items);
216         if ($record) {
217             $biblioitem->{marcxml} = $record->as_xml_record();
218         }
219
220         # Get most of the needed data
221         my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
222         my $biblio = Koha::Biblios->find( $biblionumber );
223         my $holds  = $biblio->current_holds->unblessed;
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'} = $holds;
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     my $patron = Koha::Patrons->find( $borrowernumber );
380
381     # Cleaning the borrower hashref
382     my $flags = C4::Members::patronflags( $borrower );
383     $borrower->{'charges'} = $flags->{'CHARGES'}->{'amount'};
384     my $library = Koha::Libraries->find( $borrower->{branchcode} );
385     $borrower->{'branchname'} = $library ? $library->branchname : '';
386     delete $borrower->{'userid'};
387     delete $borrower->{'password'};
388
389     # Contact fields management
390     if ( defined $cgi->param('show_contact') && $cgi->param('show_contact') eq "0" ) {
391
392         # Define contact fields
393         my @contactfields = (
394             'email',              'emailpro',           'fax',                 'mobile',          'phone',             'phonepro',
395             'streetnumber',       'zipcode',            'city',                'streettype',      'B_address',         'B_city',
396             'B_email',            'B_phone',            'B_zipcode',           'address',         'address2',          'altcontactaddress1',
397             'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
398         );
399
400         # and delete them
401         foreach my $field (@contactfields) {
402             delete $borrower->{$field};
403         }
404     }
405
406     # Fines management
407     if ( $cgi->param('show_fines') && $cgi->param('show_fines') eq "1" ) {
408         my @charges;
409         for ( my $i = 1 ; my @charge = getcharges( $borrowernumber, undef, $i ) ; $i++ ) {
410             push( @charges, @charge );
411         }
412         $borrower->{'fines'}->{'fine'} = \@charges;
413     }
414
415     # Reserves management
416     if ( $cgi->param('show_holds') && $cgi->param('show_holds') eq "1" ) {
417
418         # Get borrower's reserves
419         my $holds = $patron->holds;
420         while ( my $hold = $holds->next ) {
421
422             my $unblessed_hold = $hold->unblessed;
423             # Get additional informations
424             my $item = GetBiblioFromItemNumber( $hold->itemnumber, undef );
425             my $library = Koha::Libraries->find( $hold->branchcode ); # Should $hold->get_library
426             my $branchname = $library ? $library->branchname : '';
427
428             # Remove unwanted fields
429             delete $item->{'more_subfields_xml'};
430
431             # Add additional fields
432             $unblessed_hold->{item}       = $item;
433             $unblessed_hold->{branchname} = $branchname;
434             $unblessed_hold->{title}      = GetBiblio( $hold->biblionumber )->{'title'}; # Should be $hold->get_biblio
435
436             push @{ $borrower->{holds}{hold} }, $unblessed_hold;
437         }
438     }
439
440     # Issues management
441     if ( $cgi->param('show_loans') && $cgi->param('show_loans') eq "1" ) {
442         my $issues = GetPendingIssues($borrowernumber);
443         foreach my $issue ( @$issues ){
444             $issue->{'issuedate'} = $issue->{'issuedate'}->strftime('%Y-%m-%d %H:%M');
445             $issue->{'date_due'} = $issue->{'date_due'}->strftime('%Y-%m-%d %H:%M');
446         }
447         $borrower->{'loans'}->{'loan'} = $issues;
448     }
449
450     if ( $cgi->param('show_attributes') eq "1" ) {
451         my $attrs = GetBorrowerAttributes( $borrowernumber, 0, 1 );
452         $borrower->{'attributes'} = $attrs;
453     }
454
455     return $borrower;
456 }
457
458 =head2 GetPatronStatus
459
460 Returns a patron's status information.
461
462 Parameters:
463
464   - patron_id (Required)
465         the borrower ID
466
467 =cut
468
469 sub GetPatronStatus {
470     my ($cgi) = @_;
471
472     # Get Member details
473     my $borrowernumber = $cgi->param('patron_id');
474     my $borrower = GetMember( borrowernumber => $borrowernumber );
475     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
476
477     # Return the results
478     return {
479         type   => $$borrower{categorycode},
480         status => 0, # TODO
481         expiry => $$borrower{dateexpiry},
482     };
483 }
484
485 =head2 GetServices
486
487 Returns information about the services available on a particular item for 
488 a particular patron.
489
490 Parameters:
491
492   - patron_id (Required)
493         a borrowernumber
494   - item_id (Required)
495         an itemnumber
496
497 =cut
498
499 sub GetServices {
500     my ($cgi) = @_;
501
502     # Get the member, or return an error code if not found
503     my $borrowernumber = $cgi->param('patron_id');
504     my $borrower = GetMember( borrowernumber => $borrowernumber );
505     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
506
507     my $patron = Koha::Patrons->find( $borrowernumber );
508
509     # Get the item, or return an error code if not found
510     my $itemnumber = $cgi->param('item_id');
511     my $item = GetItem( $itemnumber );
512     return { code => 'RecordNotFound' } unless $$item{itemnumber};
513
514     my @availablefor;
515
516     # Reserve level management
517     my $biblionumber = $item->{'biblionumber'};
518     my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
519     if ($canbookbereserved eq 'OK') {
520         push @availablefor, 'title level hold';
521         my $canitembereserved = IsAvailableForItemLevelRequest($item, $borrower);
522         if ($canitembereserved) {
523             push @availablefor, 'item level hold';
524         }
525     }
526
527     # Reserve cancellation management
528     my $holds = $patron->holds;
529     my @reserveditems;
530     while ( my $hold = $holds->next ) { # FIXME This could be improved
531         push @reserveditems, $hold->itemnumber;
532     }
533     if ( grep { $itemnumber eq $_ } @reserveditems ) {
534         push @availablefor, 'hold cancellation';
535     }
536
537     # Renewal management
538     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
539     if ( $renewal[0] ) {
540         push @availablefor, 'loan renewal';
541     }
542
543     # Issuing management
544     my $barcode = $item->{'barcode'} || '';
545     $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
546     if ($barcode) {
547         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode );
548
549         # TODO push @availablefor, 'loan';
550     }
551
552     my $out;
553     $out->{'AvailableFor'} = \@availablefor;
554
555     return $out;
556 }
557
558 =head2 RenewLoan
559
560 Extends the due date for a borrower's existing issue.
561
562 Parameters:
563
564   - patron_id (Required)
565         a borrowernumber
566   - item_id (Required)
567         an itemnumber
568   - desired_due_date (Required)
569         the date the patron would like the item returned by 
570
571 =cut
572
573 sub RenewLoan {
574     my ($cgi) = @_;
575
576     # Get borrower infos or return an error code
577     my $borrowernumber = $cgi->param('patron_id');
578     my $borrower = GetMember( borrowernumber => $borrowernumber );
579     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
580
581     # Get the item, or return an error code
582     my $itemnumber = $cgi->param('item_id');
583     my $item = GetItem( $itemnumber );
584     return { code => 'RecordNotFound' } unless $$item{itemnumber};
585
586     # Add renewal if possible
587     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
588     if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber ); }
589
590     my $issue = GetItemIssue($itemnumber);
591
592     # Hashref building
593     my $out;
594     $out->{'renewals'} = $issue->{'renewals'};
595     $out->{date_due}   = $issue->{date_due}->strftime('%Y-%m-%d %H:%S');
596     $out->{'success'}  = $renewal[0];
597     $out->{'error'}    = $renewal[1];
598
599     return $out;
600 }
601
602 =head2 HoldTitle
603
604 Creates, for a borrower, a biblio-level hold reserve.
605
606 Parameters:
607
608   - patron_id (Required)
609         a borrowernumber
610   - bib_id (Required)
611         a biblionumber
612   - request_location (Required)
613         IP address where the end user request is being placed
614   - pickup_location (Optional)
615         a branch code indicating the location to which to deliver the item for pickup
616   - needed_before_date (Optional)
617         date after which hold request is no longer needed
618   - pickup_expiry_date (Optional)
619         date after which item returned to shelf if item is not picked up 
620
621 =cut
622
623 sub HoldTitle {
624     my ($cgi) = @_;
625
626     # Get the borrower or return an error code
627     my $borrowernumber = $cgi->param('patron_id');
628     my $borrower = GetMember( borrowernumber => $borrowernumber );
629     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
630
631     # Get the biblio record, or return an error code
632     my $biblionumber = $cgi->param('bib_id');
633     my $biblio = GetBiblio( $biblionumber );
634     return { code => 'RecordNotFound' } unless $$biblio{biblionumber};
635     
636     my $title = $$biblio{title};
637
638     # Check if the biblio can be reserved
639     return { code => 'NotHoldable' } unless CanBookBeReserved( $borrowernumber, $biblionumber ) eq 'OK';
640
641     my $branch;
642
643     # Pickup branch management
644     if ( $cgi->param('pickup_location') ) {
645         $branch = $cgi->param('pickup_location');
646         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
647     } else { # if the request provide no branch, use the borrower's branch
648         $branch = $$borrower{branchcode};
649     }
650
651     # Add the reserve
652     #    $branch,    $borrowernumber, $biblionumber,
653     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
654     #    $title,      $checkitem, $found
655     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
656     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, undef, undef );
657
658     # Hashref building
659     my $out;
660     $out->{'title'}           = $title;
661     my $library = Koha::Libraries->find( $branch );
662     $out->{'pickup_location'} = $library ? $library->branchname : '';
663
664     # TODO $out->{'date_available'}  = '';
665
666     return $out;
667 }
668
669 =head2 HoldItem
670
671 Creates, for a borrower, an item-level hold request on a specific item of 
672 a bibliographic record in Koha.
673
674 Parameters:
675
676   - patron_id (Required)
677         a borrowernumber
678   - bib_id (Required)
679         a biblionumber
680   - item_id (Required)
681         an itemnumber
682   - pickup_location (Optional)
683         a branch code indicating the location to which to deliver the item for pickup
684   - needed_before_date (Optional)
685         date after which hold request is no longer needed
686   - pickup_expiry_date (Optional)
687         date after which item returned to shelf if item is not picked up 
688
689 =cut
690
691 sub HoldItem {
692     my ($cgi) = @_;
693
694     # Get the borrower or return an error code
695     my $borrowernumber = $cgi->param('patron_id');
696     my $borrower = GetMember( borrowernumber => $borrowernumber );
697     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
698
699     # Get the biblio or return an error code
700     my $biblionumber = $cgi->param('bib_id');
701     my $biblio = GetBiblio($biblionumber);
702     return { code => 'RecordNotFound' } unless $$biblio{biblionumber};
703
704     my $title = $$biblio{title};
705
706     # Get the item or return an error code
707     my $itemnumber = $cgi->param('item_id');
708     my $item = GetItem( $itemnumber );
709     return { code => 'RecordNotFound' } unless $$item{itemnumber};
710
711     # If the biblio does not match the item, return an error code
712     return { code => 'RecordNotFound' } if $$item{biblionumber} ne $$biblio{biblionumber};
713
714     # Check for item disponibility
715     my $canitembereserved = C4::Reserves::CanItemBeReserved( $borrowernumber, $itemnumber );
716     my $canbookbereserved = C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber );
717     return { code => 'NotHoldable' } unless $canbookbereserved eq 'OK' and $canitembereserved eq 'OK';
718
719     # Pickup branch management
720     my $branch;
721     if ( $cgi->param('pickup_location') ) {
722         $branch = $cgi->param('pickup_location');
723         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
724     } else { # if the request provide no branch, use the borrower's branch
725         $branch = $$borrower{branchcode};
726     }
727
728     # Add the reserve
729     #    $branch,    $borrowernumber, $biblionumber,
730     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
731     #    $title,      $checkitem, $found
732     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
733     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, $itemnumber, undef );
734
735     # Hashref building
736     my $out;
737     my $library = Koha::Libraries->find( $branch );
738     $out->{'pickup_location'} = $library ? $library->branchname : '';
739
740     # TODO $out->{'date_available'} = '';
741
742     return $out;
743 }
744
745 =head2 CancelHold
746
747 Cancels an active reserve request for the borrower.
748
749 Parameters:
750
751   - patron_id (Required)
752         a borrowernumber
753   - item_id (Required)
754         a reserve_id
755
756 =cut
757
758 sub CancelHold {
759     my ($cgi) = @_;
760
761     # Get the borrower or return an error code
762     my $borrowernumber = $cgi->param('patron_id');
763     my $borrower = GetMember( borrowernumber => $borrowernumber );
764     return { code => 'PatronNotFound' } unless $$borrower{borrowernumber};
765
766     # Get the reserve or return an error code
767     my $reserve_id = $cgi->param('item_id');
768     my $reserve = C4::Reserves::GetReserve($reserve_id);
769     return { code => 'RecordNotFound' } unless $reserve;
770     return { code => 'RecordNotFound' } unless ($reserve->{borrowernumber} == $borrowernumber);
771
772     C4::Reserves::CancelReserve({reserve_id => $reserve_id});
773
774     return { code => 'Canceled' };
775 }
776
777 =head2 _availability
778
779 Returns, for an itemnumber, an array containing availability information.
780
781  my ($biblionumber, $status, $msg, $location) = _availability($id);
782
783 =cut
784
785 sub _availability {
786     my ($itemnumber) = @_;
787     my $item = GetItem( $itemnumber, undef, undef );
788
789     if ( not $item->{'itemnumber'} ) {
790         return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
791     }
792
793     my $biblionumber = $item->{'biblioitemnumber'};
794     my $library = Koha::Libraries->find( $item->{holdingbranch} );
795     my $location = $library ? $library->branchname : '';
796
797     if ( $item->{'notforloan'} ) {
798         return ( $biblionumber, 'not available', 'Not for loan', $location );
799     } elsif ( $item->{'onloan'} ) {
800         return ( $biblionumber, 'not available', 'Checked out', $location );
801     } elsif ( $item->{'itemlost'} ) {
802         return ( $biblionumber, 'not available', 'Item lost', $location );
803     } elsif ( $item->{'withdrawn'} ) {
804         return ( $biblionumber, 'not available', 'Item withdrawn', $location );
805     } elsif ( $item->{'damaged'} ) {
806         return ( $biblionumber, 'not available', 'Item damaged', $location );
807     } else {
808         return ( $biblionumber, 'available', undef, $location );
809     }
810 }
811
812 1;