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