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