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