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