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