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
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.
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.
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>.
29 use C4
::Reserves
qw(AddReserve GetReservesFromBiblionumber GetReservesFromBorrowernumber CanBookBeReserved CanItemBeReserved IsAvailableForItemLevelRequest);
31 use C4
::AuthoritiesMarc
;
40 C4::ILS-DI::Services - ILS-DI Services
44 Each function in this module represents an ILS-DI service.
45 They all takes a CGI instance as argument and most of them return a
46 hashref that will be printed by XML::Simple in opac/ilsdi.pl
50 use C4::ILSDI::Services;
56 $out = LookupPatron($cgi);
58 print CGI::header('text/xml');
63 xmldecl => '<?xml version="1.0" encoding="UTF-8" ?>',
64 RootName => 'LookupPatron',
71 =head2 GetAvailability
73 Given a set of biblionumbers or itemnumbers, returns a list with
74 availability of the items associated with the identifiers.
80 list of either biblionumbers or itemnumbers
82 =head3 id_type (Required)
84 defines the type of record identifier being used in the request,
90 =head3 return_type (Optional)
92 requests a particular level of detail in reporting availability,
98 =head3 return_fmt (Optional)
100 requests a particular format or set of formats in reporting
105 sub GetAvailability
{
108 my $out = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
109 $out .= "<dlf:collection\n";
110 $out .= " xmlns:dlf=\"http://diglib.org/ilsdi/1.1\"\n";
111 $out .= " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
112 $out .= " xsi:schemaLocation=\"http://diglib.org/ilsdi/1.1\n";
113 $out .= " http://diglib.org/architectures/ilsdi/schemas/1.1/dlfexpanded.xsd\">\n";
115 foreach my $id ( split( / /, $cgi->param('id') ) ) {
116 if ( $cgi->param('id_type') eq "item" ) {
117 my ( $biblionumber, $status, $msg, $location ) = _availability
($id);
119 $out .= " <dlf:record>\n";
120 $out .= " <dlf:bibliographic id=\"" . ( $biblionumber || $id ) . "\" />\n";
121 $out .= " <dlf:items>\n";
122 $out .= " <dlf:item id=\"" . $id . "\">\n";
123 $out .= " <dlf:simpleavailability>\n";
124 $out .= " <dlf:identifier>" . $id . "</dlf:identifier>\n";
125 $out .= " <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
126 if ($msg) { $out .= " <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
127 if ($location) { $out .= " <dlf:location>" . $location . "</dlf:location>\n"; }
128 $out .= " </dlf:simpleavailability>\n";
129 $out .= " </dlf:item>\n";
130 $out .= " </dlf:items>\n";
131 $out .= " </dlf:record>\n";
135 my $biblioitem = ( GetBiblioItemByBiblioNumber
( $id, undef ) )[0];
140 $msg = "Error: could not retrieve availability for this ID";
142 $out .= " <dlf:record>\n";
143 $out .= " <dlf:bibliographic id=\"" . $id . "\" />\n";
144 $out .= " <dlf:simpleavailability>\n";
145 $out .= " <dlf:identifier>" . $id . "</dlf:identifier>\n";
146 $out .= " <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
147 $out .= " <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n";
148 $out .= " </dlf:simpleavailability>\n";
149 $out .= " </dlf:record>\n";
152 $out .= "</dlf:collection>\n";
159 Given a list of biblionumbers, returns a list of record objects that
160 contain bibliographic information, as well as associated holdings and item
161 information. The caller may request a specific metadata schema for the
162 record objects to be returned.
164 This function behaves similarly to HarvestBibliographicRecords and
165 HarvestExpandedRecords in Data Aggregation, but allows quick, real time
166 lookup by bibliographic identifier.
168 You can use OAI-PMH ListRecords instead of this service.
173 list of system record identifiers
175 Defines the metadata schema in which the records are returned,
184 # Check if the schema is supported. For now, GetRecords only supports MARCXML
185 if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
186 return { code
=> 'UnsupportedSchema' };
191 # Loop over biblionumbers
192 foreach my $biblionumber ( split( / /, $cgi->param('id') ) ) {
194 # Get the biblioitem from the biblionumber
195 my $biblioitem = ( GetBiblioItemByBiblioNumber
( $biblionumber, undef ) )[0];
196 if ( not $biblioitem->{'biblionumber'} ) {
197 $biblioitem->{code
} = "RecordNotFound";
201 my $record = GetMarcBiblio
($biblionumber, $embed_items);
203 $biblioitem->{marcxml
} = $record->as_xml_record();
206 # We don't want MARC to be displayed
207 delete $biblioitem->{'marc'};
209 # Get most of the needed data
210 my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
211 my $reserves = GetReservesFromBiblionumber
({ biblionumber
=> $biblionumber });
212 my $issues = GetBiblioIssues
($biblionumber);
213 my $items = GetItemsByBiblioitemnumber
($biblioitemnumber);
215 # We loop over the items to clean them
216 foreach my $item (@
$items) {
218 # This hides additionnal XML subfields, we don't need these info
219 delete $item->{'more_subfields_xml'};
221 # Display branch names instead of branch codes
222 $item->{'homebranchname'} = GetBranchName
( $item->{'homebranch'} );
223 $item->{'holdingbranchname'} = GetBranchName
( $item->{'holdingbranch'} );
226 # Hashref building...
227 $biblioitem->{'items'}->{'item'} = $items;
228 $biblioitem->{'reserves'}->{'reserve'} = $reserves;
229 $biblioitem->{'issues'}->{'issue'} = $issues;
231 push @records, $biblioitem;
234 return { record
=> \
@records };
237 =head2 GetAuthorityRecords
239 Given a list of authority record identifiers, returns a list of record
240 objects that contain the authority records. The function user may request
241 a specific metadata schema for the record objects.
246 list of authority record identifiers
248 specifies the metadata schema of records to be returned, possible values:
253 sub GetAuthorityRecords
{
256 # If the user asks for an unsupported schema, return an error code
257 if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML" ) {
258 return { code
=> 'UnsupportedSchema' };
263 # Let's loop over the authority IDs
264 foreach my $authid ( split( / /, $cgi->param('id') ) ) {
266 # Get the record as XML string, or error code
267 push @records, GetAuthorityXML
($authid) || { code
=> 'RecordNotFound' };
270 return { record
=> \
@records };
275 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
280 an identifier used to look up the patron in Koha
282 the type of the identifier, possible values:
293 # Get the borrower...
294 my $borrower = GetMember
($cgi->param('id_type') => $cgi->param('id'));
295 if ( not $borrower->{'borrowernumber'} ) {
296 return { message
=> 'PatronNotFound' };
300 my $patron->{'id'} = $borrower->{'borrowernumber'};
301 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
303 # ...and return his ID
307 =head2 AuthenticatePatron
309 Authenticates a user's login credentials and returns the identifier for
314 - username (Required)
315 user's login identifier (userid or cardnumber)
316 - password (Required)
321 sub AuthenticatePatron
{
323 my ($status, $cardnumber, $userid) = C4
::Auth
::checkpw
( C4
::Context
->dbh, $cgi->param('username'), $cgi->param('password') );
326 my $borrower = GetMember
( cardnumber
=> $cardnumber );
327 my $patron->{'id'} = $borrower->{'borrowernumber'};
331 return { code
=> 'PatronNotFound' };
337 Returns specified information about the patron, based on options in the
338 request. This function can optionally return patron's contact information,
339 fine information, hold request information, and loan information.
343 - patron_id (Required)
345 - show_contact (Optional, default 1)
346 whether or not to return patron's contact information in the response
347 - show_fines (Optional, default 0)
348 whether or not to return fine information in the response
349 - show_holds (Optional, default 0)
350 whether or not to return hold request information in the response
351 - show_loans (Optional, default 0)
352 whether or not to return loan information request information in the response
360 my $borrowernumber = $cgi->param('patron_id');
361 my $borrower = GetMemberDetails
( $borrowernumber );
362 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
364 # Cleaning the borrower hashref
365 $borrower->{'charges'} = $borrower->{'flags'}->{'CHARGES'}->{'amount'};
366 $borrower->{'branchname'} = GetBranchName
( $borrower->{'branchcode'} );
367 delete $borrower->{'flags'};
368 delete $borrower->{'userid'};
369 delete $borrower->{'password'};
371 # Contact fields management
372 if ( $cgi->param('show_contact') eq "0" ) {
374 # Define contact fields
375 my @contactfields = (
376 'email', 'emailpro', 'fax', 'mobile', 'phone', 'phonepro',
377 'streetnumber', 'zipcode', 'city', 'streettype', 'B_address', 'B_city',
378 'B_email', 'B_phone', 'B_zipcode', 'address', 'address2', 'altcontactaddress1',
379 'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname', 'altcontactphone', 'altcontactsurname', 'altcontactzipcode'
383 foreach my $field (@contactfields) {
384 delete $borrower->{$field};
389 if ( $cgi->param('show_fines') eq "1" ) {
391 for ( my $i = 1 ; my @charge = getcharges
( $borrowernumber, undef, $i ) ; $i++ ) {
392 push( @charges, @charge );
394 $borrower->{'fines'}->{'fine'} = \
@charges;
397 # Reserves management
398 if ( $cgi->param('show_holds') eq "1" ) {
400 # Get borrower's reserves
401 my @reserves = GetReservesFromBorrowernumber
( $borrowernumber, undef );
402 foreach my $reserve (@reserves) {
404 # Get additional informations
405 my $item = GetBiblioFromItemNumber
( $reserve->{'itemnumber'}, undef );
406 my $branchname = GetBranchName
( $reserve->{'branchcode'} );
408 # Remove unwanted fields
409 delete $item->{'marc'};
410 delete $item->{'marcxml'};
411 delete $item->{'more_subfields_xml'};
413 # Add additional fields
414 $reserve->{'item'} = $item;
415 $reserve->{'branchname'} = $branchname;
416 $reserve->{'title'} = GetBiblio
( $reserve->{'biblionumber'} )->{'title'};
418 $borrower->{'holds'}->{'hold'} = \
@reserves;
422 if ( $cgi->param('show_loans') eq "1" ) {
423 my $issues = GetPendingIssues
($borrowernumber);
424 foreach my $issue ( @
$issues ){
425 $issue->{'issuedate'} = $issue->{'issuedate'}->strftime('%Y-%m-%d %H:%M');
426 $issue->{'date_due'} = $issue->{'date_due'}->strftime('%Y-%m-%d %H:%M');
428 $borrower->{'loans'}->{'loan'} = $issues;
434 =head2 GetPatronStatus
436 Returns a patron's status information.
440 - patron_id (Required)
445 sub GetPatronStatus
{
449 my $borrowernumber = $cgi->param('patron_id');
450 my $borrower = GetMemberDetails
( $borrowernumber );
451 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
455 type
=> $$borrower{categorycode
},
457 expiry
=> $$borrower{dateexpiry
},
463 Returns information about the services available on a particular item for
468 - patron_id (Required)
477 # Get the member, or return an error code if not found
478 my $borrowernumber = $cgi->param('patron_id');
479 my $borrower = GetMemberDetails
( $borrowernumber );
480 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
482 # Get the item, or return an error code if not found
483 my $itemnumber = $cgi->param('item_id');
484 my $item = GetItem
( $itemnumber );
485 return { code
=> 'RecordNotFound' } unless $$item{itemnumber
};
489 # Reserve level management
490 my $biblionumber = $item->{'biblionumber'};
491 my $canbookbereserved = CanBookBeReserved
( $borrower, $biblionumber );
492 if ($canbookbereserved eq 'OK') {
493 push @availablefor, 'title level hold';
494 my $canitembereserved = IsAvailableForItemLevelRequest
($item, $borrower);
495 if ($canitembereserved) {
496 push @availablefor, 'item level hold';
500 # Reserve cancellation management
501 my @reserves = GetReservesFromBorrowernumber
( $borrowernumber, undef );
503 foreach my $reserve (@reserves) {
504 push @reserveditems, $reserve->{'itemnumber'};
506 if ( grep { $itemnumber eq $_ } @reserveditems ) {
507 push @availablefor, 'hold cancellation';
511 my @renewal = CanBookBeRenewed
( $borrowernumber, $itemnumber );
513 push @availablefor, 'loan renewal';
517 my $barcode = $item->{'barcode'} || '';
518 $barcode = barcodedecode
($barcode) if ( $barcode && C4
::Context
->preference('itemBarcodeInputFilter') );
520 my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued
( $borrower, $barcode );
522 # TODO push @availablefor, 'loan';
526 $out->{'AvailableFor'} = \
@availablefor;
533 Extends the due date for a borrower's existing issue.
537 - patron_id (Required)
541 - desired_due_date (Required)
542 the date the patron would like the item returned by
549 # Get borrower infos or return an error code
550 my $borrowernumber = $cgi->param('patron_id');
551 my $borrower = GetMemberDetails
( $borrowernumber );
552 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
554 # Get the item, or return an error code
555 my $itemnumber = $cgi->param('item_id');
556 my $item = GetItem
( $itemnumber );
557 return { code
=> 'RecordNotFound' } unless $$item{itemnumber
};
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
}->strftime('%Y-%m-%d %H:%S');
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 );
602 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
604 # Get the biblio record, or return an error code
605 my $biblionumber = $cgi->param('bib_id');
606 my $biblio = GetBiblio
( $biblionumber );
607 return { code
=> 'RecordNotFound' } unless $$biblio{biblionumber
};
609 my $title = $$biblio{title
};
611 # Check if the biblio can be reserved
612 return { code
=> 'NotHoldable' } unless CanBookBeReserved
( $borrowernumber, $biblionumber ) eq 'OK';
616 # Pickup branch management
617 if ( $cgi->param('pickup_location') ) {
618 $branch = $cgi->param('pickup_location');
619 my $branches = GetBranches
;
620 return { code
=> 'LocationNotFound' } unless $$branches{$branch};
621 } else { # if the request provide no branch, use the borrower's branch
622 $branch = $$borrower{branchcode
};
626 # $branch, $borrowernumber, $biblionumber,
627 # $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
628 # $title, $checkitem, $found
629 my $priority= C4
::Reserves
::CalculatePriority
( $biblionumber );
630 AddReserve
( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, undef, undef );
634 $out->{'title'} = $title;
635 $out->{'pickup_location'} = GetBranchName
($branch);
637 # TODO $out->{'date_available'} = '';
644 Creates, for a borrower, an item-level hold request on a specific item of
645 a bibliographic record in Koha.
649 - patron_id (Required)
655 - pickup_location (Optional)
656 a branch code indicating the location to which to deliver the item for pickup
657 - needed_before_date (Optional)
658 date after which hold request is no longer needed
659 - pickup_expiry_date (Optional)
660 date after which item returned to shelf if item is not picked up
667 # Get the borrower or return an error code
668 my $borrowernumber = $cgi->param('patron_id');
669 my $borrower = GetMemberDetails
( $borrowernumber );
670 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
672 # Get the biblio or return an error code
673 my $biblionumber = $cgi->param('bib_id');
674 my $biblio = GetBiblio
($biblionumber);
675 return { code
=> 'RecordNotFound' } unless $$biblio{biblionumber
};
677 my $title = $$biblio{title
};
679 # Get the item or return an error code
680 my $itemnumber = $cgi->param('item_id');
681 my $item = GetItem
( $itemnumber );
682 return { code
=> 'RecordNotFound' } unless $$item{itemnumber
};
684 # If the biblio does not match the item, return an error code
685 return { code
=> 'RecordNotFound' } if $$item{biblionumber
} ne $$biblio{biblionumber
};
687 # Check for item disponibility
688 my $canitembereserved = C4
::Reserves
::CanItemBeReserved
( $borrowernumber, $itemnumber );
689 my $canbookbereserved = C4
::Reserves
::CanBookBeReserved
( $borrowernumber, $biblionumber );
690 return { code
=> 'NotHoldable' } unless $canbookbereserved eq 'OK' and $canitembereserved eq 'OK';
692 # Pickup branch management
694 if ( $cgi->param('pickup_location') ) {
695 $branch = $cgi->param('pickup_location');
696 my $branches = GetBranches
();
697 return { code
=> 'LocationNotFound' } unless $$branches{$branch};
698 } else { # if the request provide no branch, use the borrower's branch
699 $branch = $$borrower{branchcode
};
703 # $branch, $borrowernumber, $biblionumber,
704 # $constraint, $bibitems, $priority, $resdate, $expdate, $notes,
705 # $title, $checkitem, $found
706 my $priority= C4
::Reserves
::CalculatePriority
( $biblionumber );
707 AddReserve
( $branch, $borrowernumber, $biblionumber, undef, $priority, undef, undef, undef, $title, $itemnumber, undef );
711 $out->{'pickup_location'} = GetBranchName
($branch);
713 # TODO $out->{'date_available'} = '';
720 Cancels an active reserve request for the borrower.
724 - patron_id (Required)
734 # Get the borrower or return an error code
735 my $borrowernumber = $cgi->param('patron_id');
736 my $borrower = GetMemberDetails
( $borrowernumber );
737 return { code
=> 'PatronNotFound' } unless $$borrower{borrowernumber
};
739 # Get the reserve or return an error code
740 my $reserve_id = $cgi->param('item_id');
741 my $reserve = C4
::Reserves
::GetReserve
($reserve_id);
742 return { code
=> 'RecordNotFound' } unless $reserve;
743 return { code
=> 'RecordNotFound' } unless ($reserve->{borrowernumber
} == $borrowernumber);
745 C4
::Reserves
::CancelReserve
({reserve_id
=> $reserve_id});
747 return { code
=> 'Canceled' };
752 Returns, for an itemnumber, an array containing availability information.
754 my ($biblionumber, $status, $msg, $location) = _availability($id);
759 my ($itemnumber) = @_;
760 my $item = GetItem
( $itemnumber, undef, undef );
762 if ( not $item->{'itemnumber'} ) {
763 return ( undef, 'unknown', 'Error: could not retrieve availability for this ID', undef );
766 my $biblionumber = $item->{'biblioitemnumber'};
767 my $location = GetBranchName
( $item->{'holdingbranch'} );
769 if ( $item->{'notforloan'} ) {
770 return ( $biblionumber, 'not available', 'Not for loan', $location );
771 } elsif ( $item->{'onloan'} ) {
772 return ( $biblionumber, 'not available', 'Checked out', $location );
773 } elsif ( $item->{'itemlost'} ) {
774 return ( $biblionumber, 'not available', 'Item lost', $location );
775 } elsif ( $item->{'withdrawn'} ) {
776 return ( $biblionumber, 'not available', 'Item withdrawn', $location );
777 } elsif ( $item->{'damaged'} ) {
778 return ( $biblionumber, 'not available', 'Item damaged', $location );
780 return ( $biblionumber, 'available', undef, $location );