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