1 package C4::ILSDI::Services;
3 # Copyright 2009 SARL Biblibre
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
31 use C4::AuthoritiesMarc;
32 use C4::ILSDI::Utility;
39 C4::ILS-DI::Services - ILS-DI Services
43 Each function in this module represents an ILS-DI service.
44 They all takes a CGI instance as argument and most of them return a
45 hashref that will be printed by XML::Simple in opac/ilsdi.pl
49 use C4::ILSDI::Services;
55 $out = LookupPatron($cgi);
57 print CGI::header('text/xml');
62 xmldecl => '<?xml version="1.0" encoding="ISO-8859-1" ?>',
63 RootName => 'LookupPatron',
68 =head2 GetAvailability
70 Given a set of biblionumbers or itemnumbers, returns a list with
71 availability of the items associated with the identifiers.
76 list of either biblionumbers or itemnumbers
78 defines the type of record identifier being used in the request,
82 - return_type (Optional)
83 requests a particular level of detail in reporting availability,
87 - return_fmt (Optional)
88 requests a particular format or set of formats in reporting
96 my $out = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
97 $out .= "<dlf:collection\n";
98 $out .= " xmlns:dlf=\"http://diglib.org/ilsdi/1.1\"\n";
99 $out .= " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
100 $out .= " xsi:schemaLocation=\"http://diglib.org/ilsdi/1.1\n";
101 $out .= " http://diglib.org/architectures/ilsdi/schemas/1.1/dlfexpanded.xsd\">\n";
103 foreach my $id ( split( / /, $cgi->param('id') ) ) {
104 if ( $cgi->param('id_type') eq "item" ) {
105 my ( $biblionumber, $status, $msg, $location ) = Availability($id);
107 $out .= " <dlf:record>\n";
108 $out .= " <dlf:bibliographic id=\"" . ( $biblionumber || $id ) . "\" />\n";
109 $out .= " <dlf:items>\n";
110 $out .= " <dlf:item id=\"" . $id . "\">\n";
111 $out .= " <dlf:simpleavailability>\n";
112 $out .= " <dlf:identifier>" . $id . "</dlf:identifier>\n";
113 $out .= " <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
114 if ($msg) { $out .= " <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
115 if ($location) { $out .= " <dlf:location>" . $location . "</dlf:location>\n"; }
116 $out .= " </dlf:simpleavailability>\n";
117 $out .= " </dlf:item>\n";
118 $out .= " </dlf:items>\n";
119 $out .= " </dlf:record>\n";
123 my $biblioitem = ( GetBiblioItemByBiblioNumber( $id, undef ) )[0];
128 $msg = "Error: could not retrieve availability for this ID";
130 $out .= " <dlf:record>\n";
131 $out .= " <dlf:bibliographic id=\"" . $id . "\" />\n";
132 $out .= " <dlf:simpleavailability>\n";
133 $out .= " <dlf:identifier>" . $id . "</dlf:identifier>\n";
134 $out .= " <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
135 $out .= " <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n";
136 $out .= " </dlf:simpleavailability>\n";
137 $out .= " </dlf:record>\n";
140 $out .= "</dlf:collection>\n";
147 Given a list of biblionumbers, returns a list of record objects that
148 contain bibliographic information, as well as associated holdings and item
149 information. The caller may request a specific metadata schema for the
150 record objects to be returned.
151 This function behaves similarly to HarvestBibliographicRecords and
152 HarvestExpandedRecords in Data Aggregation, but allows quick, real time
153 lookup by bibliographic identifier.
155 You can use OAI-PMH ListRecords instead of this service.
160 list of system record identifiers
162 Defines the metadata schema in which the records are returned,
171 # Check if the schema is supported. For now, GetRecords only supports MARCXML
172 if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
173 return { message => 'UnsupportedSchema' };
178 # Loop over biblionumbers
179 foreach my $biblionumber ( split( / /, $cgi->param('id') ) ) {
181 # Get the biblioitem from the biblionumber
182 my $biblioitem = ( GetBiblioItemByBiblioNumber( $biblionumber, undef ) )[0];
183 if ( not $biblioitem->{'biblionumber'} ) {
184 $biblioitem = "RecordNotFound";
187 # We don't want MARC to be displayed
188 delete $biblioitem->{'marc'};
190 # nor the XML declaration of MARCXML
191 $biblioitem->{'marcxml'} =~ s/<\?xml version="1.0" encoding="UTF-8"\?>//go;
193 # Get most of the needed data
194 my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
195 my @reserves = GetReservesFromBiblionumber( $biblionumber, undef, undef );
196 my $issues = GetBiblioIssues($biblionumber);
197 my $items = GetItemsByBiblioitemnumber($biblioitemnumber);
199 # We loop over the items to clean them
200 foreach my $item (@$items) {
202 # This hides additionnal XML subfields, we don't need these info
203 delete $item->{'more_subfields_xml'};
205 # Display branch names instead of branch codes
206 $item->{'homebranchname'} = GetBranchName( $item->{'homebranch'} );
207 $item->{'holdingbranchname'} = GetBranchName( $item->{'holdingbranch'} );
210 # Hashref building...
211 $biblioitem->{'items'}->{'item'} = $items;
212 $biblioitem->{'reserves'}->{'reserve'} = $reserves[1];
213 $biblioitem->{'issues'}->{'issue'} = $issues;
215 map { $biblioitem->{$_} = encode_entities( $biblioitem->{$_}, '&' ) } grep( !/marcxml/, keys %$biblioitem );
217 push @records, $biblioitem;
220 return { record => \@records };
223 =head2 GetAuthorityRecords
225 Given a list of authority record identifiers, returns a list of record
226 objects that contain the authority records. The function user may request
227 a specific metadata schema for the record objects.
232 list of authority record identifiers
234 specifies the metadata schema of records to be returned, possible values:
239 sub GetAuthorityRecords {
242 # If the user asks for an unsupported schema, return an error code
243 if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
244 return { message => 'UnsupportedSchema' };
249 # Let's loop over the authority IDs
250 foreach my $authid ( split( / /, $cgi->param('id') ) ) {
252 # Get the record as XML string, or error code
253 my $record = GetAuthorityXML($authid) || "<record>RecordNotFound</record>";
254 $record =~ s/<\?xml version="1.0" encoding="UTF-8"\?>//go;
263 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
268 an identifier used to look up the patron in Koha
270 the type of the identifier, possible values:
281 # Get the borrower...
282 my $borrower = GetMember($cgi->param('id_type') => $cgi->param('id'));
283 if ( not $borrower->{'borrowernumber'} ) {
284 return { message => 'PatronNotFound' };
288 my $patron->{'id'} = $borrower->{'borrowernumber'};
290 # ...and return his ID
294 =head2 AuthenticatePatron
296 Authenticates a user's login credentials and returns the identifier for
301 - username (Required)
302 user's login identifier
303 - password (Required)
308 sub AuthenticatePatron {
311 # Check if borrower exists, using a C4::ILSDI::Utility function...
312 if ( not( BorrowerExists( $cgi->param('username'), $cgi->param('password') ) ) ) {
313 return { message => 'PatronNotFound' };
317 my $borrower = GetMember( userid => $cgi->param('username') );
320 my $patron->{'id'} = $borrower->{'borrowernumber'};
322 # ... and return his ID
328 Returns specified information about the patron, based on options in the
329 request. This function can optionally return patron's contact information,
330 fine information, hold request information, and loan information.
334 - patron_id (Required)
336 - show_contact (Optional, default 1)
337 whether or not to return patron's contact information in the response
338 - show_fines (Optional, default 0)
339 whether or not to return fine information in the response
340 - show_holds (Optional, default 0)
341 whether or not to return hold request information in the response
342 - show_loans (Optional, default 0)
343 whether or not to return loan information request information in the response
351 my $borrowernumber = $cgi->param('patron_id');
352 my $borrower = GetMemberDetails( $borrowernumber, undef );
353 if ( not $borrower->{'borrowernumber'} ) {
354 return { message => 'PatronNotFound' };
357 # Cleaning the borrower hashref
358 $borrower->{'charges'} = $borrower->{'flags'}->{'CHARGES'}->{'amount'};
359 $borrower->{'branchname'} = GetBranchName( $borrower->{'branchcode'} );
360 delete $borrower->{'flags'};
361 delete $borrower->{'userid'};
362 delete $borrower->{'password'};
364 # Contact fields management
365 if ( $cgi->param('show_contact') eq "0" ) {
367 # Define contact fields
368 my @contactfields = (
369 'email', 'emailpro', 'fax', 'mobile', 'phone', 'phonepro',
370 'streetnumber', 'zipcode', 'city', 'streettype', 'B_address', 'B_city',
371 'B_email', 'B_phone', 'B_zipcode', 'address', 'address2', 'altcontactaddress1',
372 'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
376 foreach my $field (@contactfields) {
377 delete $borrower->{$field};
382 if ( $cgi->param('show_fines') eq "1" ) {
384 for ( my $i = 1 ; my @charge = getcharges( $borrowernumber, undef, $i ) ; $i++ ) {
385 push( @charges, @charge );
387 $borrower->{'fines'}->{'fine'} = \@charges;
390 # Reserves management
391 if ( $cgi->param('show_holds') eq "1" ) {
393 # Get borrower's reserves
394 my @reserves = GetReservesFromBorrowernumber( $borrowernumber, undef );
395 foreach my $reserve (@reserves) {
397 # Get additional informations
398 my $item = GetBiblioFromItemNumber( $reserve->{'itemnumber'}, undef );
399 my $branchname = GetBranchName( $reserve->{'branchcode'} );
401 # Remove unwanted fields
402 delete $item->{'marc'};
403 delete $item->{'marcxml'};
404 delete $item->{'more_subfields_xml'};
406 # Add additional fields
407 $reserve->{'item'} = $item;
408 $reserve->{'branchname'} = $branchname;
409 $reserve->{'title'} = ( GetBiblio( $reserve->{'biblionumber'} ) )[1]->{'title'};
411 $borrower->{'holds'}->{'hold'} = \@reserves;
415 if ( $cgi->param('show_loans') eq "1" ) {
416 my $issues = GetPendingIssues($borrowernumber);
417 $borrower->{'loans'}->{'loan'} = $issues;
423 =head2 GetPatronStatus
425 Returns a patron's status information.
429 - patron_id (Required)
434 sub GetPatronStatus {
438 my $borrowernumber = $cgi->param('patron_id');
439 my $borrower = GetMemberDetails( $borrowernumber, undef );
440 if ( not $borrower->{'borrowernumber'} ) {
441 return { message => 'PatronNotFound' };
446 $patron->{'type'} = $borrower->{'categorycode'};
447 $patron->{'status'} = 0; #TODO
448 $patron->{'expiry'} = $borrower->{'dateexpiry'};
455 Returns information about the services available on a particular item for
460 - patron_id (Required)
469 # Get the member, or return an error code if not found
470 my $borrowernumber = $cgi->param('patron_id');
471 my $borrower = GetMemberDetails( $borrowernumber, undef );
472 if ( not $borrower->{'borrowernumber'} ) {
473 return { message => 'PatronNotFound' };
476 # Get the item, or return an error code if not found
477 my $itemnumber = $cgi->param('item_id');
478 my $item = GetItem( $itemnumber, undef, undef );
479 if ( not $item->{'itemnumber'} ) {
480 return { message => 'RecordNotFound' };
485 # Reserve level management
486 my $biblionumber = $item->{'biblionumber'};
487 my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
488 if ($canbookbereserved) {
489 push @availablefor, 'title level hold';
490 my $canitembereserved = IsAvailableForItemLevelRequest($itemnumber);
491 if ($canitembereserved) {
492 push @availablefor, 'item level hold';
496 # Reserve cancellation management
497 my @reserves = GetReservesFromBorrowernumber( $borrowernumber, undef );
499 foreach my $reserve (@reserves) {
500 push @reserveditems, $reserve->{'itemnumber'};
502 if ( grep { $itemnumber eq $_ } @reserveditems ) {
503 push @availablefor, 'hold cancellation';
507 my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
509 push @availablefor, 'loan renewal';
513 my $barcode = $item->{'barcode'} || '';
514 $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
516 my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode );
518 # TODO push @availablefor, 'loan';
522 $out->{'AvailableFor'} = \@availablefor;
529 Extends the due date for a borrower's existing issue.
533 - patron_id (Required)
537 - desired_due_date (Required)
538 the date the patron would like the item returned by
545 # Get borrower infos or return an error code
546 my $borrowernumber = $cgi->param('patron_id');
547 my $borrower = GetMemberDetails( $borrowernumber, undef );
548 if ( not $borrower->{'borrowernumber'} ) {
549 return { message => 'PatronNotFound' };
552 # Get the item, or return an error code
553 my $itemnumber = $cgi->param('item_id');
554 my $item = GetItem( $itemnumber, undef, undef );
555 if ( not $item->{'itemnumber'} ) {
556 return { message => 'RecordNotFound' };
559 # Add renewal if possible
560 my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
561 if ( $renewal[0] ) { AddRenewal( $borrowernumber, $itemnumber ); }
563 my $issue = GetItemIssue($itemnumber);
567 $out->{'renewals'} = $issue->{'renewals'};
568 $out->{'date_due'} = $issue->{'date_due'};
569 $out->{'success'} = $renewal[0];
570 $out->{'error'} = $renewal[1];
577 Creates, for a borrower, a biblio-level hold reserve.
581 - patron_id (Required)
585 - request_location (Required)
586 IP address where the end user request is being placed
587 - pickup_location (Optional)
588 a branch code indicating the location to which to deliver the item for pickup
589 - needed_before_date (Optional)
590 date after which hold request is no longer needed
591 - pickup_expiry_date (Optional)
592 date after which item returned to shelf if item is not picked up
599 # Get the borrower or return an error code
600 my $borrowernumber = $cgi->param('patron_id');
601 my $borrower = GetMemberDetails( $borrowernumber, undef );
602 if ( not $borrower->{'borrowernumber'} ) {
603 return { message => 'PatronNotFound' };
606 # Get the biblio record, or return an error code
607 my $biblionumber = $cgi->param('bib_id');
608 my ( $count, $biblio ) = GetBiblio($biblionumber);
609 if ( not $biblio->{'biblionumber'} ) {
610 return { message => 'RecordNotFound' };
612 my $title = $biblio->{'title'};
614 # Check if the biblio can be reserved
615 my $canbereserved = CanBookBeReserved( $borrower, $biblionumber );
616 if ( not $canbereserved ) {
617 return { message => 'NotHoldable' };
622 # Pickup branch management
623 if ( $cgi->param('pickup_location') ) {
624 $branch = $cgi->param('pickup_location');
625 my $branches = GetBranches();
626 if ( not $branches->{$branch} ) {
627 return { message => 'LocationNotFound' };
629 } else { # if user provide no branch, use his own
630 $branch = $borrower->{'branchcode'};
634 # $branch, $borrowernumber, $biblionumber, $constraint, $bibitems, $priority, $notes, $title, $checkitem, $found
635 AddReserve( $branch, $borrowernumber, $biblionumber, 'a', undef, 0, undef, $title, undef, undef );
639 $out->{'title'} = $title;
640 $out->{'pickup_location'} = GetBranchName($branch);
642 # TODO $out->{'date_available'} = '';
649 Creates, for a borrower, an item-level hold request on a specific item of
650 a bibliographic record in Koha.
654 - patron_id (Required)
660 - pickup_location (Optional)
661 a branch code indicating the location to which to deliver the item for pickup
662 - needed_before_date (Optional)
663 date after which hold request is no longer needed
664 - pickup_expiry_date (Optional)
665 date after which item returned to shelf if item is not picked up
672 # Get the borrower or return an error code
673 my $borrowernumber = $cgi->param('patron_id');
674 my $borrower = GetMemberDetails( $borrowernumber, undef );
675 if ( not $borrower->{'borrowernumber'} ) {
676 return { message => 'PatronNotFound' };
679 # Get the biblio or return an error code
680 my $biblionumber = $cgi->param('bib_id');
681 my ( $count, $biblio ) = GetBiblio($biblionumber);
682 if ( not $biblio->{'biblionumber'} ) {
683 return { message => 'RecordNotFound' };
685 my $title = $biblio->{'title'};
687 # Get the item or return an error code
688 my $itemnumber = $cgi->param('item_id');
689 my $item = GetItem( $itemnumber, undef, undef );
690 if ( not $item->{'itemnumber'} ) {
691 return { message => 'RecordNotFound' };
694 # if the biblio does not match the item, return an error code
695 if ( $item->{'biblionumber'} ne $biblio->{'biblionumber'} ) {
696 return { message => 'RecordNotFound' };
699 # Check for item disponibility
700 my $canitembereserved = IsAvailableForItemLevelRequest($itemnumber);
701 my $canbookbereserved = CanBookBeReserved( $borrower, $biblionumber );
702 if ( ( not $canbookbereserved ) or not($canitembereserved) ) {
703 return { message => 'NotHoldable' };
708 # Pickup branch management
709 if ( $cgi->param('pickup_location') ) {
710 $branch = $cgi->param('pickup_location');
711 my $branches = GetBranches();
712 if ( not $branches->{$branch} ) {
713 return { message => 'LocationNotFound' };
715 } else { # if user provide no branch, use his own
716 $branch = $borrower->{'branchcode'};
723 $rank = '0' unless C4::Context->preference('ReservesNeedReturns');
724 if ( $item->{'holdingbranch'} eq $branch ) {
725 $found = 'W' unless C4::Context->preference('ReservesNeedReturns');
729 # $branch, $borrowernumber, $biblionumber, $constraint, $bibitems, $priority, $notes, $title, $checkitem, $found
730 AddReserve( $branch, $borrowernumber, $biblionumber, 'a', undef, $rank, undef, $title, $itemnumber, $found );
734 $out->{'pickup_location'} = GetBranchName($branch);
736 # TODO $out->{'date_available'} = '';
743 Cancels an active reserve request for the borrower.
747 - patron_id (Required)
757 # Get the borrower or return an error code
758 my $borrowernumber = $cgi->param('patron_id');
759 my $borrower = GetMemberDetails( $borrowernumber, undef );
760 if ( not $borrower->{'borrowernumber'} ) {
761 return { message => 'PatronNotFound' };
764 # Get the item or return an error code
765 my $itemnumber = $cgi->param('item_id');
766 my $item = GetItem( $itemnumber, undef, undef );
767 if ( not $item->{'itemnumber'} ) {
768 return { message => 'RecordNotFound' };
771 # Get borrower's reserves
772 my @reserves = GetReservesFromBorrowernumber( $borrowernumber, undef );
775 # ...and loop over it to build an array of reserved itemnumbers
776 foreach my $reserve (@reserves) {
777 push @reserveditems, $reserve->{'itemnumber'};
780 # if the item was not reserved by the borrower, returns an error code
781 if ( not grep { $itemnumber eq $_ } @reserveditems ) {
782 return { message => 'NotCanceled' };
786 CancelReserve( $itemnumber, undef, $borrowernumber );
788 return { message => 'Canceled' };