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