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