f38365c73230e203c203252fc464e3a8cb4d0f63
[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->as_list;
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             delete $issue->{'more_subfields_xml'};
487             push @checkouts, $issue
488         }
489         $borrower->{'loans'}->{'loan'} = \@checkouts;
490     }
491
492     if ( $cgi->param('show_attributes') eq "1" ) {
493         my $attrs = GetBorrowerAttributes( $borrowernumber, 1 );
494         $borrower->{'attributes'} = $attrs;
495     }
496
497     # Add is expired information
498     $borrower->{'is_expired'} = $patron->is_expired ? 1 : 0;
499
500     return $borrower;
501 }
502
503 =head2 GetPatronStatus
504
505 Returns a patron's status information.
506
507 Parameters:
508
509   - patron_id (Required)
510     the borrower ID
511
512 =cut
513
514 sub GetPatronStatus {
515     my ($cgi) = @_;
516
517     # Get Member details
518     my $borrowernumber = $cgi->param('patron_id');
519     my $patron = Koha::Patrons->find( $borrowernumber );
520     return { code => 'PatronNotFound' } unless $patron;
521
522     # Return the results
523     return {
524         type   => $patron->categorycode,
525         status => 0, # TODO
526         expiry => $patron->dateexpiry,
527     };
528 }
529
530 =head2 GetServices
531
532 Returns information about the services available on a particular item for
533 a particular patron.
534
535 Parameters:
536
537   - patron_id (Required)
538     a borrowernumber
539   - item_id (Required)
540     an itemnumber
541
542 =cut
543
544 sub GetServices {
545     my ($cgi) = @_;
546
547     # Get the member, or return an error code if not found
548     my $borrowernumber = $cgi->param('patron_id');
549     my $patron = Koha::Patrons->find( $borrowernumber );
550     return { code => 'PatronNotFound' } unless $patron;
551
552     my $borrower = $patron->unblessed;
553     # Get the item, or return an error code if not found
554     my $itemnumber = $cgi->param('item_id');
555     my $item = Koha::Items->find($itemnumber);
556     return { code => 'RecordNotFound' } unless $item;
557
558     my @availablefor;
559
560     # Reserve level management
561     my $biblionumber = $item->biblionumber;
562     my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
563     if ($canbookbereserved->{status} eq 'OK') {
564         push @availablefor, 'title level hold';
565         my $canitembereserved = IsAvailableForItemLevelRequest($item, $patron);
566         if ($canitembereserved) {
567             push @availablefor, 'item level hold';
568         }
569     }
570
571     # Reserve cancellation management
572     my $holds = $patron->holds;
573     my @reserveditems;
574     while ( my $hold = $holds->next ) { # FIXME This could be improved
575         push @reserveditems, $hold->itemnumber;
576     }
577     if ( grep { $itemnumber eq $_ } @reserveditems ) {
578         push @availablefor, 'hold cancellation';
579     }
580
581     # Renewal management
582     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
583     if ( $renewal[0] ) {
584         push @availablefor, 'loan renewal';
585     }
586
587     # Issuing management
588     my $barcode = $item->barcode || '';
589     $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
590     if ($barcode) {
591         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $patron, $barcode );
592
593         # TODO push @availablefor, 'loan';
594     }
595
596     my $out;
597     $out->{'AvailableFor'} = \@availablefor;
598
599     return $out;
600 }
601
602 =head2 RenewLoan
603
604 Extends the due date for a borrower's existing issue.
605
606 Parameters:
607
608   - patron_id (Required)
609     a borrowernumber
610   - item_id (Required)
611     an itemnumber
612   - desired_due_date (Required)
613     the date the patron would like the item returned by
614
615 =cut
616
617 sub RenewLoan {
618     my ($cgi) = @_;
619
620     # Get borrower infos or return an error code
621     my $borrowernumber = $cgi->param('patron_id');
622     my $patron = Koha::Patrons->find( $borrowernumber );
623     return { code => 'PatronNotFound' } unless $patron;
624
625     # Get the item, or return an error code
626     my $itemnumber = $cgi->param('item_id');
627     my $item = Koha::Items->find($itemnumber);
628     return { code => 'RecordNotFound' } unless $item;
629
630     # Add renewal if possible
631     my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
632     if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber ); }
633
634     my $issue = $item->checkout;
635     return unless $issue; # FIXME should be handled
636
637     # Hashref building
638     my $out;
639     $out->{'renewals'} = $issue->renewals;
640     $out->{date_due}   = dt_from_string($issue->date_due)->strftime('%Y-%m-%d %H:%M');
641     $out->{'success'}  = $renewal[0];
642     $out->{'error'}    = $renewal[1];
643
644     return $out;
645 }
646
647 =head2 HoldTitle
648
649 Creates, for a borrower, a biblio-level hold reserve.
650
651 Parameters:
652
653   - patron_id (Required)
654     a borrowernumber
655   - bib_id (Required)
656     a biblionumber
657   - request_location (Required)
658     IP address where the end user request is being placed
659   - pickup_location (Optional)
660     a branch code indicating the location to which to deliver the item for pickup
661   - needed_before_date (Optional)
662     date after which hold request is no longer needed
663   - pickup_expiry_date (Optional)
664     date after which item returned to shelf if item is not picked up
665
666 =cut
667
668 sub HoldTitle {
669     my ($cgi) = @_;
670
671     # Get the borrower or return an error code
672     my $borrowernumber = $cgi->param('patron_id');
673     my $patron = Koha::Patrons->find( $borrowernumber );
674     return { code => 'PatronNotFound' } unless $patron;
675
676     # Get the biblio record, or return an error code
677     my $biblionumber = $cgi->param('bib_id');
678     my $biblio = Koha::Biblios->find( $biblionumber );
679     return { code => 'RecordNotFound' } unless $biblio;
680
681     my @hostitems = get_hostitemnumbers_of($biblionumber);
682     my @itemnumbers;
683     if (@hostitems){
684         push(@itemnumbers, @hostitems);
685     }
686
687     my $items = Koha::Items->search({ -or => { biblionumber => $biblionumber, itemnumber => { in => \@itemnumbers } } });
688
689     unless ( $items->count ) {
690         return { code => 'NoItems' };
691     }
692
693     my $title = $biblio ? $biblio->title : '';
694
695     # Check if the biblio can be reserved
696     my $code = CanBookBeReserved( $borrowernumber, $biblionumber )->{status};
697     return { code => $code } unless ( $code eq 'OK' );
698
699     my $branch;
700
701     # Pickup branch management
702     if ( $cgi->param('pickup_location') ) {
703         $branch = $cgi->param('pickup_location');
704         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
705     } else { # if the request provide no branch, use the borrower's branch
706         $branch = $patron->branchcode;
707     }
708
709     my $destination = Koha::Libraries->find($branch);
710     return { code => 'libraryNotPickupLocation' } unless $destination->pickup_location;
711     return { code => 'cannotBeTransferred' } unless $biblio->can_be_transferred({ to => $destination });
712
713     # Add the reserve
714     #    $branch,    $borrowernumber, $biblionumber,
715     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
716     #    $title,      $checkitem, $found
717     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
718     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, undef, undef );
719
720     # Hashref building
721     my $out;
722     $out->{'title'}           = $title;
723     my $library = Koha::Libraries->find( $branch );
724     $out->{'pickup_location'} = $library ? $library->branchname : '';
725
726     # TODO $out->{'date_available'}  = '';
727
728     return $out;
729 }
730
731 =head2 HoldItem
732
733 Creates, for a borrower, an item-level hold request on a specific item of
734 a bibliographic record in Koha.
735
736 Parameters:
737
738   - patron_id (Required)
739     a borrowernumber
740   - bib_id (Required)
741     a biblionumber
742   - item_id (Required)
743     an itemnumber
744   - pickup_location (Optional)
745     a branch code indicating the location to which to deliver the item for pickup
746   - needed_before_date (Optional)
747     date after which hold request is no longer needed
748   - pickup_expiry_date (Optional)
749     date after which item returned to shelf if item is not picked up
750
751 =cut
752
753 sub HoldItem {
754     my ($cgi) = @_;
755
756     # Get the borrower or return an error code
757     my $borrowernumber = $cgi->param('patron_id');
758     my $patron = Koha::Patrons->find( $borrowernumber );
759     return { code => 'PatronNotFound' } unless $patron;
760
761     # Get the biblio or return an error code
762     my $biblionumber = $cgi->param('bib_id');
763     my $biblio = Koha::Biblios->find( $biblionumber );
764     return { code => 'RecordNotFound' } unless $biblio;
765
766     my $title = $biblio ? $biblio->title : '';
767
768     # Get the item or return an error code
769     my $itemnumber = $cgi->param('item_id');
770     my $item = Koha::Items->find($itemnumber);
771     return { code => 'RecordNotFound' } unless $item;
772
773     # If the biblio does not match the item, return an error code
774     return { code => 'RecordNotFound' } if $item->biblionumber ne $biblio->biblionumber;
775
776     # Pickup branch management
777     my $branch;
778     if ( $cgi->param('pickup_location') ) {
779         $branch = $cgi->param('pickup_location');
780         return { code => 'LocationNotFound' } unless Koha::Libraries->find($branch);
781     } else { # if the request provide no branch, use the borrower's branch
782         $branch = $patron->branchcode;
783     }
784
785     # Check for item disponibility
786     my $canitembereserved = C4::Reserves::CanItemBeReserved( $borrowernumber, $itemnumber, $branch )->{status};
787     return { code => $canitembereserved } unless $canitembereserved eq 'OK';
788
789     # Add the reserve
790     #    $branch,    $borrowernumber, $biblionumber,
791     #    $constraint, $bibitems,  $priority, $resdate, $expdate, $notes,
792     #    $title,      $checkitem, $found
793     my $priority= C4::Reserves::CalculatePriority( $biblionumber );
794     AddReserve( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, $itemnumber, undef );
795
796     # Hashref building
797     my $out;
798     my $library = Koha::Libraries->find( $branch );
799     $out->{'pickup_location'} = $library ? $library->branchname : '';
800
801     # TODO $out->{'date_available'} = '';
802
803     return $out;
804 }
805
806 =head2 CancelHold
807
808 Cancels an active reserve request for the borrower.
809
810 Parameters:
811
812   - patron_id (Required)
813         a borrowernumber
814   - item_id (Required)
815         a reserve_id
816
817 =cut
818
819 sub CancelHold {
820     my ($cgi) = @_;
821
822     # Get the borrower or return an error code
823     my $borrowernumber = $cgi->param('patron_id');
824     my $patron = Koha::Patrons->find( $borrowernumber );
825     return { code => 'PatronNotFound' } unless $patron;
826
827     # Get the reserve or return an error code
828     my $reserve_id = $cgi->param('item_id');
829     my $hold = Koha::Holds->find( $reserve_id );
830     return { code => 'RecordNotFound' } unless $hold;
831     return { code => 'RecordNotFound' } unless ($hold->borrowernumber == $borrowernumber);
832
833     $hold->cancel;
834
835     return { code => 'Canceled' };
836 }
837
838 =head2 _availability
839
840 Returns, for an itemnumber, an array containing availability information.
841
842  my ($biblionumber, $status, $msg, $location) = _availability($id);
843
844 =cut
845
846 sub _availability {
847     my ($itemnumber) = @_;
848     my $item = Koha::Items->find($itemnumber);
849
850     unless ( $item ) {
851         return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
852     }
853
854     my $biblionumber = $item->biblioitemnumber;
855     my $library = Koha::Libraries->find( $item->holdingbranch );
856     my $location = $library ? $library->branchname : '';
857
858     if ( $item->notforloan ) {
859         return ( $biblionumber, 'not available', 'Not for loan', $location );
860     } elsif ( $item->onloan ) {
861         return ( $biblionumber, 'not available', 'Checked out', $location );
862     } elsif ( $item->itemlost ) {
863         return ( $biblionumber, 'not available', 'Item lost', $location );
864     } elsif ( $item->withdrawn ) {
865         return ( $biblionumber, 'not available', 'Item withdrawn', $location );
866     } elsif ( $item->damaged ) {
867         return ( $biblionumber, 'not available', 'Item damaged', $location );
868     } else {
869         return ( $biblionumber, 'available', undef, $location );
870     }
871 }
872
873 1;