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