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 with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA 02111-1307 USA
28 use C4::AuthoritiesMarc;
29 use C4::ILSDI::Utility;
36 C4::ILS-DI::Services - ILS-DI Services
40 Each function in this module represents an ILS-DI service.
41 They all takes a CGI instance as argument and most of them return a
42 hashref that will be printed by XML::Simple in opac/ilsdi.pl
46 use C4::ILSDI::Services;
52 $out = LookupPatron($cgi);
54 print CGI::header('text/xml');
59 xmldecl => '<?xml version="1.0" encoding="ISO-8859-1" ?>',
60 RootName => 'LookupPatron',
65 =head2 GetAvailability
67 Given a set of biblionumbers or itemnumbers, returns a list with
68 availability of the items associated with the identifiers.
73 list of either biblionumbers or itemnumbers
75 defines the type of record identifier being used in the request,
79 - return_type (Optional)
80 requests a particular level of detail in reporting availability,
84 - return_fmt (Optional)
85 requests a particular format or set of formats in reporting
93 my $out = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
94 $out .= "<dlf:collection\n";
95 $out .= " xmlns:dlf=\"http://diglib.org/ilsdi/1.1\"\n";
96 $out .= " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
97 $out .= " xsi:schemaLocation=\"http://diglib.org/ilsdi/1.1\n";
98 $out .= " http://diglib.org/architectures/ilsdi/schemas/1.1/dlfexpanded.xsd\">\n";
100 foreach $id (split(/ /, $cgi->param('id')))
102 if ($cgi->param('id_type') eq "item")
104 my ($biblionumber, $status, $msg, $location) = Availability($id);
106 $out .= " <dlf:record>\n";
107 $out .= " <dlf:bibliographic id=\"" . ($biblionumber || $id) . "\" />\n";
108 $out .= " <dlf:items>\n";
109 $out .= " <dlf:item id=\"" . $id . "\">\n";
110 $out .= " <dlf:simpleavailability>\n";
111 $out .= " <dlf:identifier>" . $id . "</dlf:identifier>\n";
112 $out .= " <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
113 if ($msg) { $out .= " <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
114 if ($location) { $out .= " <dlf:location>" . $location . "</dlf:location>\n"; }
115 $out .= " </dlf:simpleavailability>\n";
116 $out .= " </dlf:item>\n";
117 $out .= " </dlf:items>\n";
118 $out .= " </dlf:record>\n";
124 my $biblioitem = (GetBiblioItemByBiblioNumber($id, undef))[0];
129 $msg = "Error: could not retrieve availability for this ID";
131 $out .= " <dlf:record>\n";
132 $out .= " <dlf:bibliographic id=\"" . $id . "\" />\n";
133 $out .= " <dlf:simpleavailability>\n";
134 $out .= " <dlf:identifier>" . $id . "</dlf:identifier>\n";
135 $out .= " <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
136 $out .= " <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n";
137 $out .= " </dlf:simpleavailability>\n";
138 $out .= " </dlf:record>\n";
141 $out .= "</dlf:collection>\n";
148 Given a list of biblionumbers, returns a list of record objects that
149 contain bibliographic information, as well as associated holdings and item
150 information. The caller may request a specific metadata schema for the
151 record objects to be returned.
152 This function behaves similarly to HarvestBibliographicRecords and
153 HarvestExpandedRecords in Data Aggregation, but allows quick, real time
154 lookup by bibliographic identifier.
156 You can use OAI-PMH ListRecords instead of this service.
161 list of system record identifiers
163 Defines the metadata schema in which the records are returned,
172 # Check if the schema is supported. For now, GetRecords only supports MARCXML
173 if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML") {
174 $out->{'message'} = "UnsupportedSchema";
179 # Loop over biblionumbers
180 foreach $biblionumber (split(/ /, $cgi->param('id')))
182 # Get the biblioitem from the biblionumber
183 my $biblioitem = (GetBiblioItemByBiblioNumber($biblionumber, undef))[0];
184 if ( not $biblioitem->{'biblionumber'} ) {
185 $biblioitem = "RecordNotFound";
188 # We don't want MARC to be displayed
189 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 $item (@$items)
202 # This hides additionnal XML subfields, we don't need these info
203 delete $item->{'more_subfields_xml'};
204 # Display branch names instead of branch codes
205 $item->{'homebranchname'} = GetBranchName($item->{'homebranch'});
206 $item->{'holdingbranchname'} = GetBranchName($item->{'holdingbranch'});
209 # Hashref building...
210 $biblioitem->{'items'}->{'item'} = $items;
211 $biblioitem->{'reserves'}->{'reserve'} = @reserves[1];
212 $biblioitem->{'issues'}->{'issue'} = $issues;
214 map { $biblioitem->{$_} = encode_entities($biblioitem->{$_},'&') } grep(!/marcxml/, keys %$biblioitem);
216 push @records, $biblioitem;
219 $records->{'record'} = \@records;
224 =head2 GetAuthorityRecords
226 Given a list of authority record identifiers, returns a list of record
227 objects that contain the authority records. The function user may request
228 a specific metadata schema for the record objects.
233 list of authority record identifiers
235 specifies the metadata schema of records to be returned, possible values:
240 sub GetAuthorityRecords {
243 # If the user asks for an unsupported schema, return an error code
244 if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML") {
245 $out->{'message'} = "UnsupportedSchema";
250 # Let's loop over the authority IDs
251 foreach $authid (split(/ /, $cgi->param('id')))
253 # Get the record as XML string, or error code
254 my $record= GetAuthorityXML($authid) || "<record>RecordNotFound</record>";
255 $record =~ s/<\?xml version="1.0" encoding="UTF-8"\?>//go;
264 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
269 an identifier used to look up the patron in Koha
271 the type of the identifier, possible values:
282 # Get the borrower...
283 my $borrower = GetMember($cgi->param('id'), $cgi->param('id_type'));
284 if ( not $borrower->{'borrowernumber'} ) {
285 $out->{'message'} = "PatronNotFound";
290 my $patron->{'id'} = $borrower->{'borrowernumber'};
292 # ...and return his ID
296 =head2 AuthenticatePatron
298 Authenticates a user's login credentials and returns the identifier for
303 - username (Required)
304 user's login identifier
305 - password (Required)
310 sub AuthenticatePatron {
313 # Check if borrower exists, using a C4::ILSDI::Utility function...
314 if ( not (BorrowerExists($cgi->param('username'), $cgi->param('password')))) {
315 $out->{'message'} = "PatronNotFound";
320 my $borrower = GetMember($cgi->param('username'), "userid");
323 my $patron->{'id'} = $borrower->{'borrowernumber'};
325 # ... and return his ID
331 Returns specified information about the patron, based on options in the
332 request. This function can optionally return patron's contact information,
333 fine information, hold request information, and loan information.
337 - patron_id (Required)
339 - show_contact (Optional, default 1)
340 whether or not to return patron's contact information in the response
341 - show_fines (Optional, default 0)
342 whether or not to return fine information in the response
343 - show_holds (Optional, default 0)
344 whether or not to return hold request information in the response
345 - show_loans (Optional, default 0)
346 whether or not to return loan information request information in the response
354 my $borrowernumber = $cgi->param('patron_id');
355 my $borrower = GetMemberDetails($borrowernumber, undef);
356 if ( not $borrower->{'borrowernumber'}) {
357 $out->{'message'} = "PatronNotFound";
361 # Cleaning the borrower hashref
362 $borrower->{'charges'} = $borrower->{'flags'}->{'CHARGES'}->{'amount'};
363 $borrower->{'branchname'} = GetBranchName($borrower->{'branchcode'});
364 delete $borrower->{'flags'};
365 delete $borrower->{'userid'};
366 delete $borrower->{'password'};
368 # Contact fields management
369 if ($cgi->param('show_contact') eq "0") {
370 # Define contact fields
371 my @contactfields = ('email', 'emailpro', 'fax', 'mobile',
372 'phone', 'phonepro', 'streetnumber', 'zipcode', 'city',
373 'streettype', 'B_address', 'B_city', 'B_email', 'B_phone',
374 'B_zipcode', 'address', 'address2', 'altcontactaddress1',
375 'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname',
376 'altcontactphone', 'altcontactsurname', 'altcontactzipcode');
378 foreach my $field (@contactfields) {
379 delete $borrower->{$field};
384 if ($cgi->param('show_fines') eq "1") {
386 for(my $i = 1; my @charge = getcharges($borrowernumber, undef, $i); $i++) {
387 push(@charges, @charge);
389 $borrower->{'fines'}->{'fine'} = \@charges;
392 # Reserves management
393 if ($cgi->param('show_holds') eq "1") {
394 # Get borrower's reserves
395 my @reserves = GetReservesFromBorrowernumber($borrowernumber, undef);
396 foreach my $reserve (@reserves) {
397 # Get additional informations
398 my $item = GetBiblioFromItemNumber($reserve->{'itemnumber'}, undef);
399 my $branchname = GetBranchName($reserve->{'branchcode'});
400 # Remove unwanted fields
401 delete $item->{'marc'};
402 delete $item->{'marcxml'};
403 delete $item->{'more_subfields_xml'};
404 # Add additional fields
405 $reserve->{'item'} = $item;
406 $reserve->{'branchname'} = $branchname;
407 $reserve->{'title'} = (GetBiblio($reserve->{'biblionumber'}))[1]->{'title'};
409 $borrower->{'holds'}->{'hold'} = \@reserves;
413 if ($cgi->param('show_loans') eq "1") {
414 my $issues = GetPendingIssues($borrowernumber);
415 $borrower->{'loans'}->{'loan'} = $issues;
421 =head2 GetPatronStatus
423 Returns a patron's status information.
427 - patron_id (Required)
432 sub GetPatronStatus {
436 my $borrowernumber = $cgi->param('patron_id');
437 my $borrower = GetMemberDetails($borrowernumber, undef);
438 if ( not $borrower->{'borrowernumber'} ) {
439 $out->{'message'} = "PatronNotFound";
444 $patron->{'type'} = $borrower->{'categorycode'};
445 $patron->{'status'} = 0; #TODO
446 $patron->{'expiry'} = $borrower->{'dateexpiry'};
453 Returns information about the services available on a particular item for
458 - patron_id (Required)
467 # Get the member, or return an error code if not found
468 my $borrowernumber = $cgi->param('patron_id');
469 my $borrower = GetMemberDetails($borrowernumber, undef);
470 if ( not $borrower->{'borrowernumber'} ) {
471 $out->{'message'} = "PatronNotFound";
475 # Get the item, or return an error code if not found
476 my $itemnumber = $cgi->param('item_id');
477 my $item = GetItem($itemnumber, undef, undef);
478 if ( not $item->{'itemnumber'} ) {
479 $out->{'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 );
517 # TODO push @availablefor, 'loan';
520 $out->{'AvailableFor'} = \@availablefor;
527 Extends the due date for a borrower's existing issue.
531 - patron_id (Required)
535 - desired_due_date (Required)
536 the date the patron would like the item returned by
543 # Get borrower infos or return an error code
544 my $borrowernumber = $cgi->param('patron_id');
545 my $borrower = GetMemberDetails($borrowernumber, undef);
546 if ( not $borrower->{'borrowernumber'} ) {
547 $out->{'message'} = "PatronNotFound";
551 # Get the item, or return an error code
552 my $itemnumber = $cgi->param('item_id');
553 my $item = GetItem($itemnumber, undef, undef);
554 if ( not $item->{'itemnumber'} ) {
555 $out->{'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);
566 $out->{'renewals'} = $issue->{'renewals'};
567 $out->{'date_due'} = $issue->{'date_due'};
568 $out->{'success'} = @renewal[0];
569 $out->{'error'} = @renewal[1];
576 Creates, for a borrower, a biblio-level hold reserve.
580 - patron_id (Required)
584 - request_location (Required)
585 IP address where the end user request is being placed
586 - pickup_location (Optional)
587 a branch code indicating the location to which to deliver the item for pickup
588 - needed_before_date (Optional)
589 date after which hold request is no longer needed
590 - pickup_expiry_date (Optional)
591 date after which item returned to shelf if item is not picked up
598 # Get the borrower or return an error code
599 my $borrowernumber = $cgi->param('patron_id');
600 my $borrower = GetMemberDetails($borrowernumber, undef);
601 if ( not $borrower->{'borrowernumber'} ) {
602 $out->{'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 $out->{'message'} = "RecordNotFound";
613 my $title = $biblio->{'title'};
615 # Check if the biblio can be reserved
616 my $canbereserved = CanBookBeReserved($borrower, $biblionumber);
617 if ( not $canbereserved ) {
618 $out->{'message'} = "NotHoldable";
623 # Pickup branch management
624 if ($cgi->param('pickup_location')) {
625 $branch = $cgi->param('pickup_location');
626 my $branches = GetBranches();
627 if ( not $branches->{$branch} ) {
628 $out->{'message'} = "LocationNotFound";
631 } else { # if user provide no branch, use his own
632 $branch = $borrower->{'branchcode'};
636 # $branch, $borrowernumber, $biblionumber, $constraint, $bibitems, $priority, $notes, $title, $checkitem, $found
637 AddReserve($branch, $borrowernumber, $biblionumber, 'a', undef, 0, undef, $title, undef, undef);
640 $out->{'title'} = $title;
641 $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 $out->{'message'} = "PatronNotFound";
680 # Get the biblio or return an error code
681 my $biblionumber = $cgi->param('bib_id');
682 my ($count, $biblio) = GetBiblio($biblionumber);
683 if ( not $biblio->{'biblionumber'} ) {
684 $out->{'message'} = "RecordNotFound";
687 my $title = $biblio->{'title'};
689 # Get the item or return an error code
690 my $itemnumber = $cgi->param('item_id');
691 my $item = GetItem($itemnumber, undef, undef);
692 if ( not $item->{'itemnumber'} ) {
693 $out->{'message'} = "RecordNotFound";
697 # if the biblio does not match the item, return an error code
698 if ( $item->{'biblionumber'} ne $biblio->{'biblionumber'} ) {
699 $out->{'message'} = "RecordNotFound";
703 # Check for item disponibility
704 my $canitembereserved = IsAvailableForItemLevelRequest($itemnumber);
705 my $canbookbereserved = CanBookBeReserved($borrower, $biblionumber);
706 if ( (not $canbookbereserved) or not ($canitembereserved) ) {
707 $out->{'message'} = "NotHoldable";
712 # Pickup branch management
713 if ($cgi->param('pickup_location')) {
714 $branch = $cgi->param('pickup_location');
715 my $branches = GetBranches();
716 if ( not $branches->{$branch} ) {
717 $out->{'message'} = "LocationNotFound";
720 } else { # if user provide no branch, use his own
721 $branch = $borrower->{'branchcode'};
727 $rank = '0' unless C4::Context->preference('ReservesNeedReturns');
728 if ( $item->{'holdingbranch'} eq $branch ){
729 $found = 'W' unless C4::Context->preference('ReservesNeedReturns');
733 # $branch, $borrowernumber, $biblionumber, $constraint, $bibitems, $priority, $notes, $title, $checkitem, $found
734 AddReserve($branch, $borrowernumber, $biblionumber, 'a', undef, $rank, undef, $title, $itemnumber, $found);
737 $out->{'title'} = $title;
738 $out->{'pickup_location'} = GetBranchName($branch);
739 # TODO $out->{'date_available'} = '';
746 Cancels an active reserve request for the borrower.
750 - patron_id (Required)
760 # Get the borrower or return an error code
761 my $borrowernumber = $cgi->param('patron_id');
762 my $borrower = GetMemberDetails($borrowernumber, undef);
763 if ( not $borrower->{'borrowernumber'} ) {
764 $out->{'message'} = "PatronNotFound";
768 # Get the item or return an error code
769 my $itemnumber = $cgi->param('item_id');
770 my $item = GetItem($itemnumber, undef, undef);
771 if ( not $item->{'itemnumber'} ) {
772 $out->{'message'} = "RecordNotFound";
776 # Get borrower's reserves
777 my @reserves = GetReservesFromBorrowernumber($borrowernumber, undef);
779 # ...and loop over it to build an array of reserved itemnumbers
780 foreach my $reserve (@reserves) {
781 push @reserveditems, $reserve->{'itemnumber'};
783 # if the item was not reserved by the borrower, returns an error code
784 if ( not grep {$itemnumber eq $_} @reserveditems) {
785 $out->{'message'} = "NotCanceled";
790 CancelReserve($itemnumber, undef, $borrowernumber);
792 $out->{'message'} = "Canceled";