Bug 19059: Move C4::Reserves::CancelReserve to Koha::Hold->cancel
[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({
218             biblionumber => $biblionumber,
219             embed_items  => $embed_items });
220         if ($record) {
221             $biblioitem->{marcxml} = $record->as_xml_record();
222         }
223
224         # Get most of the needed data
225         my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
226         my $biblio = Koha::Biblios->find( $biblionumber );
227         my $holds  = $biblio->current_holds->unblessed;
228         my $issues           = GetBiblioIssues($biblionumber);
229         my $items            = GetItemsByBiblioitemnumber($biblioitemnumber);
230
231         # We loop over the items to clean them
232         foreach my $item (@$items) {
233
234             # This hides additionnal XML subfields, we don't need these info
235             delete $item->{'more_subfields_xml'};
236
237             # Display branch names instead of branch codes
238             my $home_library    = Koha::Libraries->find( $item->{homebranch} );
239             my $holding_library = Koha::Libraries->find( $item->{holdingbranch} );
240             $item->{'homebranchname'}    = $home_library    ? $home_library->branchname    : '';
241             $item->{'holdingbranchname'} = $holding_library ? $holding_library->branchname : '';
242         }
243
244         # Hashref building...
245         $biblioitem->{'items'}->{'item'}       = $items;
246         $biblioitem->{'reserves'}->{'reserve'} = $holds;
247         $biblioitem->{'issues'}->{'issue'}     = $issues;
248
249         push @records, $biblioitem;
250     }
251
252     return { record => \@records };
253 }
254
255 =head2 GetAuthorityRecords
256
257 Given a list of authority record identifiers, returns a list of record 
258 objects that contain the authority records. The function user may request 
259 a specific metadata schema for the record objects.
260
261 Parameters:
262
263   - id (Required)
264     list of authority record identifiers
265   - schema (Optional)
266     specifies the metadata schema of records to be returned, possible values:
267       - MARCXML
268
269 =cut
270
271 sub GetAuthorityRecords {
272     my ($cgi) = @_;
273
274     # If the user asks for an unsupported schema, return an error code
275     if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
276         return { code => 'UnsupportedSchema' };
277     }
278
279     my @records;
280
281     # Let's loop over the authority IDs
282     foreach my $authid ( split( / /, $cgi->param('id') ) ) {
283
284         # Get the record as XML string, or error code
285         push @records, GetAuthorityXML($authid) || { code => 'RecordNotFound' };
286     }
287
288     return { record => \@records };
289 }
290
291 =head2 LookupPatron
292
293 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
294
295 Parameters:
296
297   - id (Required)
298         an identifier used to look up the patron in Koha
299   - id_type (Optional)
300         the type of the identifier, possible values:
301         - cardnumber
302         - firstname
303         - userid
304         - borrowernumber
305
306 =cut
307
308 sub LookupPatron {
309     my ($cgi) = @_;
310
311     my $patrons = Koha::Patrons->search( { $cgi->param('id_type') => $cgi->param('id') } );
312     unless ( $patrons->count ) {
313         return { message => 'PatronNotFound' };
314     }
315
316     return { id => $patrons->next->borrowernumber };
317 }
318
319 =head2 AuthenticatePatron
320
321 Authenticates a user's login credentials and returns the identifier for 
322 the patron.
323
324 Parameters:
325
326   - username (Required)
327     user's login identifier (userid or cardnumber)
328   - password (Required)
329     user's password
330
331 =cut
332
333 sub AuthenticatePatron {
334     my ($cgi) = @_;
335     my $username = $cgi->param('username');
336     my $password = $cgi->param('password');
337     my ($status, $cardnumber, $userid) = C4::Auth::checkpw( C4::Context->dbh, $username, $password );
338     if ( $status ) {
339         # Get the borrower
340         my $patron = Koha::Patrons->find( { cardnumber => $cardnumber } );
341         return { id => $patron->borrowernumber };
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 $patron = Koha::Patrons->find( $borrowernumber );
375     return { code => 'PatronNotFound' } unless $patron;
376
377     # Cleaning the borrower hashref
378     my $borrower = $patron->unblessed;
379     my $flags = C4::Members::patronflags( $borrower );
380     $borrower->{'charges'} = $flags->{'CHARGES'}->{'amount'};
381     my $library = Koha::Libraries->find( $borrower->{branchcode} );
382     $borrower->{'branchname'} = $library ? $library->branchname : '';
383     delete $borrower->{'userid'};
384     delete $borrower->{'password'};
385
386     # Contact fields management
387     if ( defined $cgi->param('show_contact') && $cgi->param('show_contact') eq "0" ) {
388
389         # Define contact fields
390         my @contactfields = (
391             'email',              'emailpro',           'fax',                 'mobile',          'phone',             'phonepro',
392             'streetnumber',       'zipcode',            'city',                'streettype',      'B_address',         'B_city',
393             'B_email',            'B_phone',            'B_zipcode',           'address',         'address2',          'altcontactaddress1',
394             'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
395         );
396
397         # and delete them
398         foreach my $field (@contactfields) {
399             delete $borrower->{$field};
400         }
401     }
402
403     # Fines management
404     if ( $cgi->param('show_fines') && $cgi->param('show_fines') eq "1" ) {
405         my @charges;
406         for ( my $i = 1 ; my @charge = getcharges( $borrowernumber, undef, $i ) ; $i++ ) {
407             push( @charges, @charge );
408         }
409         $borrower->{'fines'}->{'fine'} = \@charges;
410     }
411
412     # Reserves management
413     if ( $cgi->param('show_holds') && $cgi->param('show_holds') eq "1" ) {
414
415         # Get borrower's reserves
416         my $holds = $patron->holds;
417         while ( my $hold = $holds->next ) {
418
419             my ( $item, $biblio, $biblioitem ) = ( {}, {}, {} );
420             # Get additional informations
421             if ( $hold->itemnumber ) {    # item level holds
422                 $item       = Koha::Items->find( $hold->itemnumber );
423                 $biblio     = $item->biblio;
424                 $biblioitem = $biblio->biblioitem;
425
426                 # Remove unwanted fields
427                 $item = $item->unblessed;
428                 delete $item->{more_subfields_xml};
429                 $biblio     = $biblio->unblessed;
430                 $biblioitem = $biblioitem->unblessed;
431             }
432
433             # Add additional fields
434             my $unblessed_hold = $hold->unblessed;
435             $unblessed_hold->{item}       = { %$item, %$biblio, %$biblioitem };
436             my $library = Koha::Libraries->find( $hold->branchcode );
437             my $branchname = $library ? $library->branchname : '';
438             $unblessed_hold->{branchname} = $branchname;
439             $biblio = Koha::Biblios->find( $hold->biblionumber ); # Should be $hold->get_biblio
440             $unblessed_hold->{title} = $biblio ? $biblio->title : ''; # Just in case, but should not be needed
441
442             push @{ $borrower->{holds}{hold} }, $unblessed_hold;
443
444         }
445     }
446
447     # Issues management
448     if ( $cgi->param('show_loans') && $cgi->param('show_loans') eq "1" ) {
449         my $issues = GetPendingIssues($borrowernumber);
450         foreach my $issue ( @$issues ){
451             $issue->{'issuedate'} = $issue->{'issuedate'}->strftime('%Y-%m-%d %H:%M');
452             $issue->{'date_due'} = $issue->{'date_due'}->strftime('%Y-%m-%d %H:%M');
453         }
454         $borrower->{'loans'}->{'loan'} = $issues;
455     }
456
457     if ( $cgi->param('show_attributes') eq "1" ) {
458         my $attrs = GetBorrowerAttributes( $borrowernumber, 0, 1 );
459         $borrower->{'attributes'} = $attrs;
460     }
461
462     return $borrower;
463 }
464
465 =head2 GetPatronStatus
466
467 Returns a patron's status information.
468
469 Parameters:
470
471   - patron_id (Required)
472         the borrower ID
473
474 =cut
475
476 sub GetPatronStatus {
477     my ($cgi) = @_;
478
479     # Get Member details
480     my $borrowernumber = $cgi->param('patron_id');
481     my $patron = Koha::Patrons->find( $borrowernumber );
482     return { code => 'PatronNotFound' } unless $patron;
483
484     # Return the results
485     return {
486         type   => $patron->categorycode,
487         status => 0, # TODO
488         expiry => $patron->dateexpiry,
489     };
490 }
491
492 =head2 GetServices
493
494 Returns information about the services available on a particular item for 
495 a particular patron.
496
497 Parameters:
498
499   - patron_id (Required)
500         a borrowernumber
501   - item_id (Required)
502         an itemnumber
503
504 =cut
505
506 sub GetServices {
507     my ($cgi) = @_;
508
509     # Get the member, or return an error code if not found
510     my $borrowernumber = $cgi->param('patron_id');
511     my $patron = Koha::Patrons->find( $borrowernumber );
512     return { code => 'PatronNotFound' } unless $patron;
513
514     my $borrower = $patron->unblessed;
515     # Get the item, or return an error code if not found
516     my $itemnumber = $cgi->param('item_id');
517     my $item = GetItem( $itemnumber );
518     return { code => 'RecordNotFound' } unless $$item{itemnumber};
519
520     my @availablefor;
521
522     # Reserve level management
523     my $biblionumber = $item->{'biblionumber'};
524     my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
525     if ($canbookbereserved eq 'OK') {
526         push @availablefor, 'title level hold';
527         my $canitembereserved = IsAvailableForItemLevelRequest($item, $borrower);
528         if ($canitembereserved) {
529             push @availablefor, 'item level hold';
530         }
531     }
532
533     # Reserve cancellation management
534     my $holds = $patron->holds;
535     my @reserveditems;
536     while ( my $hold = $holds->next ) { # FIXME This could be improved
537         push @reserveditems, $hold->itemnumber;
538     }
539     if ( grep { $itemnumber eq $_ } @reserveditems ) {
540         push @availablefor, 'hold cancellation';
541     }
542
543     # Renewal management
544     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
545     if ( $renewal[0] ) {
546         push @availablefor, 'loan renewal';
547     }
548
549     # Issuing management
550     my $barcode = $item->{'barcode'} || '';
551     $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
552     if ($barcode) {
553         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode );
554
555         # TODO push @availablefor, 'loan';
556     }
557
558     my $out;
559     $out->{'AvailableFor'} = \@availablefor;
560
561     return $out;
562 }
563
564 =head2 RenewLoan
565
566 Extends the due date for a borrower's existing issue.
567
568 Parameters:
569
570   - patron_id (Required)
571         a borrowernumber
572   - item_id (Required)
573         an itemnumber
574   - desired_due_date (Required)
575         the date the patron would like the item returned by 
576
577 =cut
578
579 sub RenewLoan {
580     my ($cgi) = @_;
581
582     # Get borrower infos or return an error code
583     my $borrowernumber = $cgi->param('patron_id');
584     my $patron = Koha::Patrons->find( $borrowernumber );
585     return { code => 'PatronNotFound' } unless $patron;
586
587     # Get the item, or return an error code
588     my $itemnumber = $cgi->param('item_id');
589     my $item = GetItem( $itemnumber );
590     return { code => 'RecordNotFound' } unless $$item{itemnumber};
591
592     # Add renewal if possible
593     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
594     if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber ); }
595
596     my $issue = Koha::Checkouts->find( { itemnumber => $itemnumber } ) or return; # FIXME should be handled
597
598     # Hashref building
599     my $out;
600     $out->{'renewals'} = $issue->renewals;
601     $out->{date_due}   = dt_from_string($issue->date_due)->strftime('%Y-%m-%d %H:%S');
602     $out->{'success'}  = $renewal[0];
603     $out->{'error'}    = $renewal[1];
604
605     return $out;
606 }
607
608 =head2 HoldTitle
609
610 Creates, for a borrower, a biblio-level hold reserve.
611
612 Parameters:
613
614   - patron_id (Required)
615         a borrowernumber
616   - bib_id (Required)
617         a biblionumber
618   - request_location (Required)
619         IP address where the end user request is being placed
620   - pickup_location (Optional)
621         a branch code indicating the location to which to deliver the item for pickup
622   - needed_before_date (Optional)
623         date after which hold request is no longer needed
624   - pickup_expiry_date (Optional)
625         date after which item returned to shelf if item is not picked up 
626
627 =cut
628
629 sub HoldTitle {
630     my ($cgi) = @_;
631
632     # Get the borrower or return an error code
633     my $borrowernumber = $cgi->param('patron_id');
634     my $patron = Koha::Patrons->find( $borrowernumber );
635     return { code => 'PatronNotFound' } unless $patron;
636
637     # Get the biblio record, or return an error code
638     my $biblionumber = $cgi->param('bib_id');
639     my $biblio = Koha::Biblios->find( $biblionumber );
640     return { code => 'RecordNotFound' } unless $biblio;
641
642     my $title = $biblio ? $biblio->title : '';
643
644     # Check if the biblio can be reserved
645     return { code => 'NotHoldable' } unless CanBookBeReserved( $borrowernumber, $biblionumber ) eq 'OK';
646
647     my $branch;
648
649     # Pickup branch management
650     if ( $cgi->param('pickup_location') ) {
651         $branch = $cgi->param('pickup_location');
652         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
653     } else { # if the request provide no branch, use the borrower's branch
654         $branch = $patron->branchcode;
655     }
656
657     # Add the reserve
658     #    $branch,    $borrowernumber, $biblionumber,
659     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
660     #    $title,      $checkitem, $found
661     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
662     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, undef, undef );
663
664     # Hashref building
665     my $out;
666     $out->{'title'}           = $title;
667     my $library = Koha::Libraries->find( $branch );
668     $out->{'pickup_location'} = $library ? $library->branchname : '';
669
670     # TODO $out->{'date_available'}  = '';
671
672     return $out;
673 }
674
675 =head2 HoldItem
676
677 Creates, for a borrower, an item-level hold request on a specific item of 
678 a bibliographic record in Koha.
679
680 Parameters:
681
682   - patron_id (Required)
683         a borrowernumber
684   - bib_id (Required)
685         a biblionumber
686   - item_id (Required)
687         an itemnumber
688   - pickup_location (Optional)
689         a branch code indicating the location to which to deliver the item for pickup
690   - needed_before_date (Optional)
691         date after which hold request is no longer needed
692   - pickup_expiry_date (Optional)
693         date after which item returned to shelf if item is not picked up 
694
695 =cut
696
697 sub HoldItem {
698     my ($cgi) = @_;
699
700     # Get the borrower or return an error code
701     my $borrowernumber = $cgi->param('patron_id');
702     my $patron = Koha::Patrons->find( $borrowernumber );
703     return { code => 'PatronNotFound' } unless $patron;
704
705     # Get the biblio or return an error code
706     my $biblionumber = $cgi->param('bib_id');
707     my $biblio = Koha::Biblios->find( $biblionumber );
708     return { code => 'RecordNotFound' } unless $biblio;
709
710     my $title = $biblio ? $biblio->title : '';
711
712     # Get the item or return an error code
713     my $itemnumber = $cgi->param('item_id');
714     my $item = GetItem( $itemnumber );
715     return { code => 'RecordNotFound' } unless $$item{itemnumber};
716
717     # If the biblio does not match the item, return an error code
718     return { code => 'RecordNotFound' } if $$item{biblionumber} ne $biblio->biblionumber;
719
720     # Check for item disponibility
721     my $canitembereserved = C4::Reserves::CanItemBeReserved( $borrowernumber, $itemnumber );
722     my $canbookbereserved = C4::Reserves::CanBookBeReserved( $borrowernumber, $biblionumber );
723     return { code => 'NotHoldable' } unless $canbookbereserved eq 'OK' and $canitembereserved eq 'OK';
724
725     # Pickup branch management
726     my $branch;
727     if ( $cgi->param('pickup_location') ) {
728         $branch = $cgi->param('pickup_location');
729         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
730     } else { # if the request provide no branch, use the borrower's branch
731         $branch = $patron->branchcode;
732     }
733
734     # Add the reserve
735     #    $branch,    $borrowernumber, $biblionumber,
736     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
737     #    $title,      $checkitem, $found
738     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
739     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, $itemnumber, undef );
740
741     # Hashref building
742     my $out;
743     my $library = Koha::Libraries->find( $branch );
744     $out->{'pickup_location'} = $library ? $library->branchname : '';
745
746     # TODO $out->{'date_available'} = '';
747
748     return $out;
749 }
750
751 =head2 CancelHold
752
753 Cancels an active reserve request for the borrower.
754
755 Parameters:
756
757   - patron_id (Required)
758         a borrowernumber
759   - item_id (Required)
760         a reserve_id
761
762 =cut
763
764 sub CancelHold {
765     my ($cgi) = @_;
766
767     # Get the borrower or return an error code
768     my $borrowernumber = $cgi->param('patron_id');
769     my $patron = Koha::Patrons->find( $borrowernumber );
770     return { code => 'PatronNotFound' } unless $patron;
771
772     # Get the reserve or return an error code
773     my $reserve_id = $cgi->param('item_id');
774     my $hold = Koha::Holds->find( $reserve_id );
775     return { code => 'RecordNotFound' } unless $hold;
776     return { code => 'RecordNotFound' } unless ($hold->borrowernumber == $borrowernumber);
777
778     $hold->cancel;
779
780     return { code => 'Canceled' };
781 }
782
783 =head2 _availability
784
785 Returns, for an itemnumber, an array containing availability information.
786
787  my ($biblionumber, $status, $msg, $location) = _availability($id);
788
789 =cut
790
791 sub _availability {
792     my ($itemnumber) = @_;
793     my $item = GetItem( $itemnumber, undef, undef );
794
795     if ( not $item->{'itemnumber'} ) {
796         return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
797     }
798
799     my $biblionumber = $item->{'biblioitemnumber'};
800     my $library = Koha::Libraries->find( $item->{holdingbranch} );
801     my $location = $library ? $library->branchname : '';
802
803     if ( $item->{'notforloan'} ) {
804         return ( $biblionumber, 'not available', 'Not for loan', $location );
805     } elsif ( $item->{'onloan'} ) {
806         return ( $biblionumber, 'not available', 'Checked out', $location );
807     } elsif ( $item->{'itemlost'} ) {
808         return ( $biblionumber, 'not available', 'Item lost', $location );
809     } elsif ( $item->{'withdrawn'} ) {
810         return ( $biblionumber, 'not available', 'Item withdrawn', $location );
811     } elsif ( $item->{'damaged'} ) {
812         return ( $biblionumber, 'not available', 'Item damaged', $location );
813     } else {
814         return ( $biblionumber, 'available', undef, $location );
815     }
816 }
817
818 1;