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;
35 C4::ILS-DI::Services - ILS-DI Services
39 Each function in this module represents an ILS-DI service.
40 They all takes a CGI instance as argument and most of them return a
41 hashref that will be printed by XML::Simple in opac/ilsdi.pl
45 use C4::ILSDI::Services;
51 $out = LookupPatron($cgi);
53 print CGI::header('text/xml');
58 xmldecl => '<?xml version="1.0" encoding="ISO-8859-1" ?>',
59 RootName => 'LookupPatron',
64 =head2 GetAvailability
66 Given a set of biblionumbers or itemnumbers, returns a list with
67 availability of the items associated with the identifiers.
72 list of either biblionumbers or itemnumbers
74 defines the type of record identifier being used in the request,
78 - return_type (Optional)
79 requests a particular level of detail in reporting availability,
83 - return_fmt (Optional)
84 requests a particular format or set of formats in reporting
92 my $out = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
93 $out .= "<dlf:collection\n";
94 $out .= " xmlns:dlf=\"http://diglib.org/ilsdi/1.1\"\n";
95 $out .= " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
96 $out .= " xsi:schemaLocation=\"http://diglib.org/ilsdi/1.1\n";
97 $out .= " http://diglib.org/architectures/ilsdi/schemas/1.1/dlfexpanded.xsd\">\n";
99 foreach $id (split(/ /, $cgi->param('id')))
101 if ($cgi->param('id_type') eq "item")
103 my ($biblionumber, $status, $msg, $location) = Availability($id);
105 $out .= " <dlf:record>\n";
106 $out .= " <dlf:bibliographic id=\"" . ($biblionumber || $id) . "\" />\n";
107 $out .= " <dlf:items>\n";
108 $out .= " <dlf:item id=\"" . $id . "\">\n";
109 $out .= " <dlf:simpleavailability>\n";
110 $out .= " <dlf:identifier>" . $id . "</dlf:identifier>\n";
111 $out .= " <dlf:availabilitystatus>" . $status . "</dlf:availabilitystatus>\n";
112 if ($msg) { $out .= " <dlf:availabilitymsg>" . $msg . "</dlf:availabilitymsg>\n"; }
113 if ($location) { $out .= " <dlf:location>" . $location . "</dlf:location>\n"; }
114 $out .= " </dlf:simpleavailability>\n";
115 $out .= " </dlf:item>\n";
116 $out .= " </dlf:items>\n";
117 $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 $out->{'message'} = "UnsupportedSchema";
178 # Loop over biblionumbers
179 foreach $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'};
189 # nor the XML declaration of MARCXML
190 $biblioitem->{'marcxml'} =~ s/<\?xml version="1.0" encoding="UTF-8"\?>//go;
192 # Get most of the needed data
193 my $biblioitemnumber = $biblioitem->{'biblioitemnumber'};
194 my @reserves = GetReservesFromBiblionumber($biblionumber, undef, undef);
195 my $issues = GetBiblioIssues($biblionumber);
196 my $items = GetItemsByBiblioitemnumber($biblioitemnumber);
198 # We loop over the items to clean them
199 foreach $item (@$items)
201 # This hides additionnal XML subfields, we don't need these info
202 delete $item->{'more_subfields_xml'};
203 # Display branch names instead of branch codes
204 $item->{'homebranchname'} = GetBranchName($item->{'homebranch'});
205 $item->{'holdingbranchname'} = GetBranchName($item->{'holdingbranch'});
208 # Hashref building...
209 $biblioitem->{'items'}->{'item'} = $items;
210 $biblioitem->{'reserves'}->{'reserve'} = @reserves[1];
211 $biblioitem->{'issues'}->{'issue'} = $issues;
213 push @records, $biblioitem;
216 $records->{'record'} = \@records;
221 =head2 GetAuthorityRecords
223 Given a list of authority record identifiers, returns a list of record
224 objects that contain the authority records. The function user may request
225 a specific metadata schema for the record objects.
230 list of authority record identifiers
232 specifies the metadata schema of records to be returned, possible values:
237 sub GetAuthorityRecords {
240 # If the user asks for an unsupported schema, return an error code
241 if ( $cgi->param('schema') and $cgi->param('schema') ne "MARCXML") {
242 $out->{'message'} = "UnsupportedSchema";
247 # Let's loop over the authority IDs
248 foreach $authid (split(/ /, $cgi->param('id')))
250 # Get the record as XML string, or error code
251 my $record= GetAuthorityXML($authid) || "<record>RecordNotFound</record>";
252 $record =~ s/<\?xml version="1.0" encoding="UTF-8"\?>//go;
261 Looks up a patron in the ILS by an identifier, and returns the borrowernumber.
266 an identifier used to look up the patron in Koha
268 the type of the identifier, possible values:
279 # Get the borrower...
280 my $borrower = GetMember($cgi->param('id'), $cgi->param('id_type'));
281 if ( not $borrower->{'borrowernumber'} ) {
282 $out->{'message'} = "PatronNotFound";
287 my $patron->{'id'} = $borrower->{'borrowernumber'};
289 # ...and return his ID
293 =head2 AuthenticatePatron
295 Authenticates a user's login credentials and returns the identifier for
300 - username (Required)
301 user's login identifier
302 - password (Required)
307 sub AuthenticatePatron {
310 # Check if borrower exists, using a C4::ILSDI::Utility function...
311 if ( not (BorrowerExists($cgi->param('username'), $cgi->param('password')))) {
312 $out->{'message'} = "PatronNotFound";
317 my $borrower = GetMember($cgi->param('username'), "userid");
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 $out->{'message'} = "PatronNotFound";
358 # Cleaning the borrower hashref
359 $borrower->{'charges'} = $borrower->{'flags'}->{'CHARGES'}->{'amount'};
360 $borrower->{'branchname'} = GetBranchName($borrower->{'branchcode'});
361 delete $borrower->{'flags'};
362 delete $borrower->{'userid'};
363 delete $borrower->{'password'};
365 # Contact fields management
366 if ($cgi->param('show_contact') eq "0") {
367 # Define contact fields
368 my @contactfields = ('email', 'emailpro', 'fax', 'mobile',
369 'phone', 'phonepro', 'streetnumber', 'zipcode', 'city',
370 'streettype', 'B_address', 'B_city', 'B_email', 'B_phone',
371 'B_zipcode', 'address', 'address2', 'altcontactaddress1',
372 'altcontactaddress2', 'altcontactaddress3', 'altcontactfirstname',
373 'altcontactphone', 'altcontactsurname', 'altcontactzipcode');
375 foreach my $field (@contactfields) {
376 delete $borrower->{$field};
381 if ($cgi->param('show_fines') eq "1") {
383 for(my $i = 1; my @charge = getcharges($borrowernumber, undef, $i); $i++) {
384 push(@charges, @charge);
386 $borrower->{'fines'}->{'fine'} = \@charges;
389 # Reserves management
390 if ($cgi->param('show_holds') eq "1") {
391 # Get borrower's reserves
392 my @reserves = GetReservesFromBorrowernumber($borrowernumber, undef);
393 foreach my $reserve (@reserves) {
394 # Get additional informations
395 my $item = GetBiblioFromItemNumber($reserve->{'itemnumber'}, undef);
396 my $branchname = GetBranchName($reserve->{'branchcode'});
397 # Remove unwanted fields
398 delete $item->{'marc'};
399 delete $item->{'marcxml'};
400 delete $item->{'more_subfields_xml'};
401 # Add additional fields
402 $reserve->{'item'} = $item;
403 $reserve->{'branchname'} = $branchname;
404 $reserve->{'title'} = (GetBiblio($reserve->{'biblionumber'}))[1]->{'title'};
406 $borrower->{'holds'}->{'hold'} = \@reserves;
410 if ($cgi->param('show_loans') eq "1") {
411 my $issues = GetPendingIssues($borrowernumber);
412 $borrower->{'loans'}->{'loan'} = $issues;
418 =head2 GetPatronStatus
420 Returns a patron's status information.
424 - patron_id (Required)
429 sub GetPatronStatus {
433 my $borrowernumber = $cgi->param('patron_id');
434 my $borrower = GetMemberDetails($borrowernumber, undef);
435 if ( not $borrower->{'borrowernumber'} ) {
436 $out->{'message'} = "PatronNotFound";
441 $patron->{'type'} = $borrower->{'categorycode'};
442 $patron->{'status'} = 0; #TODO
443 $patron->{'expiry'} = $borrower->{'dateexpiry'};
450 Returns information about the services available on a particular item for
455 - patron_id (Required)
464 # Get the member, or return an error code if not found
465 my $borrowernumber = $cgi->param('patron_id');
466 my $borrower = GetMemberDetails($borrowernumber, undef);
467 if ( not $borrower->{'borrowernumber'} ) {
468 $out->{'message'} = "PatronNotFound";
472 # Get the item, or return an error code if not found
473 my $itemnumber = $cgi->param('item_id');
474 my $item = GetItem($itemnumber, undef, undef);
475 if ( not $item->{'itemnumber'} ) {
476 $out->{'message'} = "RecordNotFound";
482 # Reserve level management
483 my $biblionumber = $item->{'biblionumber'};
484 my $canbookbereserved = CanBookBeReserved($borrower, $biblionumber);
485 if ( $canbookbereserved ) {
486 push @availablefor, 'title level hold';
487 my $canitembereserved = IsAvailableForItemLevelRequest($itemnumber);
488 if ( $canitembereserved ) {
489 push @availablefor, 'item level hold';
493 # Reserve cancellation management
494 my @reserves = GetReservesFromBorrowernumber($borrowernumber, undef);
496 foreach my $reserve (@reserves) {
497 push @reserveditems, $reserve->{'itemnumber'};
499 if ( grep {$itemnumber eq $_} @reserveditems) {
500 push @availablefor, 'hold cancellation';
504 my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
506 push @availablefor, 'loan renewal';
510 my $barcode = $item->{'barcode'} || '';
511 $barcode = barcodedecode($barcode) if( $barcode && C4::Context->preference('itemBarcodeInputFilter'));
513 my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode );
514 # TODO push @availablefor, 'loan';
517 $out->{'AvailableFor'} = \@availablefor;
524 Extends the due date for a borrower's existing issue.
528 - patron_id (Required)
532 - desired_due_date (Required)
533 the date the patron would like the item returned by
540 # Get borrower infos or return an error code
541 my $borrowernumber = $cgi->param('patron_id');
542 my $borrower = GetMemberDetails($borrowernumber, undef);
543 if ( not $borrower->{'borrowernumber'} ) {
544 $out->{'message'} = "PatronNotFound";
548 # Get the item, or return an error code
549 my $itemnumber = $cgi->param('item_id');
550 my $item = GetItem($itemnumber, undef, undef);
551 if ( not $item->{'itemnumber'} ) {
552 $out->{'message'} = "RecordNotFound";
556 # Add renewal if possible
557 my @renewal = CanBookBeRenewed( $borrowernumber, $itemnumber );
558 if (@renewal[0]) { AddRenewal( $borrowernumber, $itemnumber ); }
560 my $issue = GetItemIssue($itemnumber);
563 $out->{'renewals'} = $issue->{'renewals'};
564 $out->{'date_due'} = $issue->{'date_due'};
565 $out->{'success'} = @renewal[0];
566 $out->{'error'} = @renewal[1];
573 Creates, for a borrower, a biblio-level hold reserve.
577 - patron_id (Required)
581 - request_location (Required)
582 IP address where the end user request is being placed
583 - pickup_location (Optional)
584 a branch code indicating the location to which to deliver the item for pickup
585 - needed_before_date (Optional)
586 date after which hold request is no longer needed
587 - pickup_expiry_date (Optional)
588 date after which item returned to shelf if item is not picked up
595 # Get the borrower or return an error code
596 my $borrowernumber = $cgi->param('patron_id');
597 my $borrower = GetMemberDetails($borrowernumber, undef);
598 if ( not $borrower->{'borrowernumber'} ) {
599 $out->{'message'} = "PatronNotFound";
603 # Get the biblio record, or return an error code
604 my $biblionumber = $cgi->param('bib_id');
605 my ($count, $biblio) = GetBiblio($biblionumber);
606 if ( not $biblio->{'biblionumber'} ) {
607 $out->{'message'} = "RecordNotFound";
610 my $title = $biblio->{'title'};
612 # Check if the biblio can be reserved
613 my $canbereserved = CanBookBeReserved($borrower, $biblionumber);
614 if ( not $canbereserved ) {
615 $out->{'message'} = "NotHoldable";
620 # Pickup branch management
621 if ($cgi->param('pickup_location')) {
622 $branch = $cgi->param('pickup_location');
623 my $branches = GetBranches();
624 if ( not $branches->{$branch} ) {
625 $out->{'message'} = "LocationNotFound";
628 } else { # if user provide no branch, use his own
629 $branch = C4::Context->userenv->{'branch'};
633 # $branch, $borrowernumber, $biblionumber, $constraint, $bibitems, $priority, $notes, $title, $checkitem, $found
634 AddReserve($branch, $borrowernumber, $biblionumber, 'a', undef, 0, undef, $title, undef, undef);
637 $out->{'title'} = $title;
638 $out->{'pickup_location'} = GetBranchName($branch);
639 # TODO $out->{'date_available'} = '';
646 Creates, for a borrower, an item-level hold request on a specific item of
647 a bibliographic record in Koha.
651 - patron_id (Required)
657 - pickup_location (Optional)
658 a branch code indicating the location to which to deliver the item for pickup
659 - needed_before_date (Optional)
660 date after which hold request is no longer needed
661 - pickup_expiry_date (Optional)
662 date after which item returned to shelf if item is not picked up
669 # Get the borrower or return an error code
670 my $borrowernumber = $cgi->param('patron_id');
671 my $borrower = GetMemberDetails($borrowernumber, undef);
672 if ( not $borrower->{'borrowernumber'} ) {
673 $out->{'message'} = "PatronNotFound";
677 # Get the biblio or return an error code
678 my $biblionumber = $cgi->param('bib_id');
679 my ($count, $biblio) = GetBiblio($biblionumber);
680 if ( not $biblio->{'biblionumber'} ) {
681 $out->{'message'} = "RecordNotFound";
684 my $title = $biblio->{'title'};
686 # Get the item or return an error code
687 my $itemnumber = $cgi->param('item_id');
688 my $item = GetItem($itemnumber, undef, undef);
689 if ( not $item->{'itemnumber'} ) {
690 $out->{'message'} = "RecordNotFound";
694 # if the biblio does not match the item, return an error code
695 if ( $item->{'biblionumber'} ne $biblio->{'biblionumber'} ) {
696 $out->{'message'} = "RecordNotFound";
700 # Check for item disponibility
701 my $canitembereserved = IsAvailableForItemLevelRequest($itemnumber);
702 my $canbookbereserved = CanBookBeReserved($borrower, $biblionumber);
703 if ( (not $canbookbereserved) or not ($canitembereserved) ) {
704 $out->{'message'} = "NotHoldable";
709 # Pickup branch management
710 if ($cgi->param('pickup_location')) {
711 $branch = $cgi->param('pickup_location');
712 my $branches = GetBranches();
713 if ( not $branches->{$branch} ) {
714 $out->{'message'} = "LocationNotFound";
717 } else { # if user provide no branch, use his own
718 $branch = C4::Context->userenv->{'branch'};
724 $rank = '0' unless C4::Context->preference('ReservesNeedReturns');
725 if ( $item->{'holdingbranch'} eq $branch ){
726 $found = 'W' unless C4::Context->preference('ReservesNeedReturns');
730 # $branch, $borrowernumber, $biblionumber, $constraint, $bibitems, $priority, $notes, $title, $checkitem, $found
731 AddReserve($branch, $borrowernumber, $biblionumber, 'a', undef, $rank, undef, $title, $itemnumber, $found);
734 $out->{'title'} = $title;
735 $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 $out->{'message'} = "PatronNotFound";
765 # Get the item or return an error code
766 my $itemnumber = $cgi->param('item_id');
767 my $item = GetItem($itemnumber, undef, undef);
768 if ( not $item->{'itemnumber'} ) {
769 $out->{'message'} = "RecordNotFound";
773 # Get borrower's reserves
774 my @reserves = GetReservesFromBorrowernumber($borrowernumber, undef);
776 # ...and loop over it to build an array of reserved itemnumbers
777 foreach my $reserve (@reserves) {
778 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 $out->{'message'} = "NotCanceled";
787 CancelReserve($itemnumber, undef, $borrowernumber);
789 $out->{'message'} = "Canceled";