4 # Copyright 2012-2013 Midwest Consortium for Library Services
5 # Copyright 2013 Calvin College
6 # contact Dan Wells <dbw2@calvin.edu>
7 # Copyright 2013 Traverse Area District Library,
8 # contact Jeff Godin <jgodin@tadl.org>
11 # This code incorporates code (with modifications) from issa, "a small
12 # command-line client to OpenILS/Evergreen". issa is licensed GPLv2 or (at your
13 # option) any later version of the GPL.
17 # Copyright 2011 Jason J.A. Stephenson <jason@sigio.com>
18 # Portions Copyright 2012 Merrimack Valley Library Consortium
19 # <jstephenson@mvlc.org>
22 # This file is part of iNCIPit
24 # iNCIPit is free software: you can redistribute it and/or modify it
25 # under the terms of the GNU General Public License as published by
26 # the Free Software Foundation, either version 2 of the License, or
27 # (at your option) any later version.
29 # iNCIPit is distributed in the hope that it will be useful, but WITHOUT
30 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
31 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
32 # License for more details.
34 # You should have received a copy of the GNU General Public License
35 # along with iNCIPit. If not, see <http://www.gnu.org/licenses/>.
44 use OpenSRF::Utils::SettingsClient;
45 use Digest::MD5 qw/md5_hex/;
46 use OpenILS::Utils::Fieldmapper;
47 use OpenILS::Utils::CStoreEditor qw/:funcs/;
48 use OpenILS::Const qw/:const/;
49 use Scalar::Util qw(reftype blessed);
53 use POSIX qw/strftime/;
57 my $U = "OpenILS::Application::AppUtils";
59 my $conf = load_config( 'iNCIPit.ini' );
61 # Set some variables from config (or defaults)
64 if ($conf->{behavior}->{patron_id_as_identifier} =~ m/^yes$/i) {
65 $patron_id_type = "id";
67 $patron_id_type = "barcode";
70 # reject non-https access unless configured otherwise
71 unless ($conf->{access}->{permit_plaintext} =~ m/^yes$/i) {
72 unless (defined($ENV{HTTPS}) && $ENV{HTTPS} eq 'on') {
73 print "Content-type: text/plain\n\n";
74 print "Access denied.\n";
79 # TODO: support for multiple load balancer IPs
80 my $lb_ip = $conf->{access}->{load_balancer_ip};
82 # if we are behind a load balancer, check to see that the
83 # actual client IP is permitted
85 my @allowed_ips = split(/ *, */, $conf->{access}->{allowed_client_ips});
87 my $forwarded = $ENV{HTTP_X_FORWARDED_FOR};
90 foreach my $check_ip (@allowed_ips) {
91 $ok = 1 if ($check_ip eq $forwarded);
94 # if we have a load balancer IP and are relying on
95 # X-Forwarded-For, deny requests other than those
96 # from the load balancer
97 # TODO: support for chained X-Forwarded-For -- ignore all but last
98 unless ($ok && $ENV{REMOTE_ADDR} eq $lb_ip) {
99 print "Content-type: text/plain\n\n";
100 print "Access denied.\n";
105 my $cgi = CGI->new();
107 my $xml = $cgi->param('POSTDATA') || $cgi->param('XForms:Model');
110 # XXX: posted ncip message log filename should be in config.
111 open POST_DATA, ">>post_data.txt";
112 print POST_DATA $xml;
115 # initialize the parser
116 my $parser = new XML::LibXML;
117 my $doc = $parser->load_xml( string => $xml );
119 my %session = login();
121 if ( defined( $session{authtoken} ) ) {
122 $doc->exists('/NCIPMessage/LookupUser') ? lookupUser() : (
123 $doc->exists('/NCIPMessage/ItemRequested') ? item_request() : (
124 $doc->exists('/NCIPMessage/ItemShipped') ? item_shipped() : (
125 $doc->exists('/NCIPMessage/ItemCheckedOut') ? item_checked_out() : (
126 $doc->exists('/NCIPMessage/CheckOutItem') ? check_out_item() : (
127 $doc->exists('/NCIPMessage/ItemCheckedIn') ? item_checked_in() : (
128 $doc->exists('/NCIPMessage/CheckInItem') ? check_in_item() : (
129 $doc->exists('/NCIPMessage/ItemReceived') ? item_received() : (
130 $doc->exists('/NCIPMessage/AcceptItem') ? accept_item() : (
131 $doc->exists('/NCIPMessage/ItemRequestCancelled') ? item_cancelled() : (
132 $doc->exists('/NCIPMessage/ItemRenewed') ? item_renew() : (
133 $doc->exists('/NCIPMessage/RenewItem') ? renew_item() :
134 fail("UNKNOWN NCIPMessage")
139 fail("Unable to perform action : Unknown Service Request");
142 # load and parse config file
146 my $Config = Config::Tiny->new;
147 $Config = Config::Tiny->read( $file ) ||
148 die( "Error reading config file ", $file, ": ", Config::Tiny->errstr, "\n" );
152 # load and parse userpriv_map file, returning a hashref
154 my $filename = shift;
156 if (open(my $fh, "<", $filename)) {
157 while (my $entry = <$fh>) {
159 my ($from, $to) = split(m/:/, $entry);
167 sub lookup_userpriv {
170 if (defined($map->{$input})) { # if we have a mapping for this profile
171 return $map->{$input}; # return value from mapping hash
173 return $input; # return original value
177 sub lookup_pickup_lib {
180 if (defined($map->{$input})) { # if we found this pickup lib
181 return $map->{$input}; # return value from mapping hash
183 return undef; # the original value does us no good -- return undef
188 my ( $msg, $func, $more_info ) = @_;
189 open RESP_DATA, ">>resp_data.txt";
190 print RESP_DATA $msg;
191 print RESP_DATA $more_info unless !$more_info;
193 print $msg || fail($func);
197 my ( $taiv, $faiv, $more_info ) = @_;
198 my $now = localtime();
199 open STAFF_LOG, ">>staff_data.csv";
200 print STAFF_LOG "$now, $faiv, $taiv, $more_info\n";
205 my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemRenewed/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
206 my $faidScheme = HTML::Entities::encode($faidSchemeX);
207 my $faidValue = $doc->find('/NCIPMessage/ItemRenewed/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
208 my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemRenewed/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
209 my $taidScheme = HTML::Entities::encode($taidSchemeX);
210 my $taidValue = $doc->find('/NCIPMessage/ItemRenewed/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
212 my $pid = $doc->findvalue('/NCIPMessage/ItemRenewed/UniqueUserId/UserIdentifierValue');
213 my $visid = $doc->findvalue('/NCIPMessage/ItemRenewed/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
214 my $due_date = $doc->findvalue('/NCIPMessage/ItemRenewed/DateDue');
216 my $r = renewal( $visid, $due_date );
218 my $hd = <<ITEMRENEWAL;
219 Content-type: text/xml
222 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
223 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
224 <ItemRenewedResponse>
228 <Scheme>$faidScheme</Scheme>
229 <Value>$faidValue</Value>
234 <Scheme>$taidScheme</Scheme>
235 <Value>$taidValue</Value>
240 <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
242 </ItemRenewedResponse>
247 my $more_info = <<MOREINFO;
250 Desired Due Date = $due_date
254 logit( $hd, ( caller(0) )[3], $more_info );
255 staff_log( $taidValue, $faidValue,
256 "ItemRenewal -> Patronid : "
265 my $faidSchemeX = $doc->findvalue('/NCIPMessage/RenewItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
266 my $faidScheme = HTML::Entities::encode($faidSchemeX);
267 my $faidValue = $doc->find('/NCIPMessage/RenewItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
268 my $taidSchemeX = $doc->findvalue('/NCIPMessage/RenewItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
269 my $taidScheme = HTML::Entities::encode($taidSchemeX);
270 my $taidValue = $doc->find('/NCIPMessage/RenewItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
272 my $pid = $doc->findvalue('/NCIPMessage/RenewItem/UniqueUserId/UserIdentifierValue');
273 my $unique_item_id = $doc->findvalue('/NCIPMessage/RenewItem/UniqueItemId/ItemIdentifierValue');
274 my $due_date = $doc->findvalue('/NCIPMessage/RenewItem/DateDue');
276 # we are using the UniqueItemId value as a barcode here
277 my $r = renewal( $unique_item_id, $due_date );
279 my $hd = <<ITEMRENEWAL;
280 Content-type: text/xml
283 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
284 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
289 <Scheme>$faidScheme</Scheme>
290 <Value>$faidValue</Value>
295 <Scheme>$taidScheme</Scheme>
296 <Value>$taidValue</Value>
301 <ItemIdentifierValue datatype="string">$unique_item_id</ItemIdentifierValue>
308 my $more_info = <<MOREINFO;
310 UNIQUEID = $unique_item_id
311 Desired Due Date = $due_date
315 logit( $hd, ( caller(0) )[3], $more_info );
316 staff_log( $taidValue, $faidValue,
317 "RenewItem -> Patronid : "
326 my $faidSchemeX = $doc->findvalue('/NCIPMessage/AcceptItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
327 my $faidScheme = HTML::Entities::encode($faidSchemeX);
328 my $faidValue = $doc->find('/NCIPMessage/AcceptItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
329 my $taidSchemeX = $doc->findvalue('/NCIPMessage/AcceptItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
330 my $taidScheme = HTML::Entities::encode($taidSchemeX);
331 my $taidValue = $doc->find('/NCIPMessage/AcceptItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
332 my $visid = $doc->findvalue('/NCIPMessage/AcceptItem/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
333 my $request_id = $doc->findvalue('/NCIPMessage/AcceptItem/UniqueRequestId/RequestIdentifierValue') || "unknown";
334 my $patron = $doc->findvalue('/NCIPMessage/AcceptItem/UserOptionalFields/VisibleUserId/VisibleUserIdentifier');
335 my $copy = copy_from_barcode($visid);
336 fail( "accept_item: " . $copy->{textcode} . " $visid" ) unless ( blessed $copy);
337 my $r2 = update_copy( $copy, $conf->{status}->{hold} ); # put into INN-Reach Hold status
338 # We need to find the hold to know the pickup location
339 my $hold = find_hold_on_copy($visid);
340 if (defined $hold && blessed($hold)) {
341 # Check the copy in to capture for hold -- do it at the pickup_lib
342 # so that the hold becomes Available
343 my $checkin_result = checkin_accept($copy->id, $hold->pickup_lib);
345 fail( "accept_item: no hold found for visid " . $visid );
348 my $hd = <<ACCEPTITEM;
349 Content-type: text/xml
352 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
353 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
358 <Scheme>$faidScheme</Scheme>
359 <Value>$faidValue</Value>
364 <Scheme>$taidScheme</Scheme>
365 <Value>$taidValue</Value>
370 <ItemIdentifierValue datatype="string">$request_id</ItemIdentifierValue>
373 <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
375 </AcceptItemResponse>
380 logit( $hd, ( caller(0) )[3] );
381 staff_log( $taidValue, $faidValue,
382 "AcceptItem -> Request Id : " . $request_id . " | Patron Id : " . $patron . " | Visible Id :" . $visid );
386 my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemReceived/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
387 my $faidScheme = HTML::Entities::encode($faidSchemeX);
388 my $faidValue = $doc->find('/NCIPMessage/ItemReceived/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
389 my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemReceived/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
390 my $taidScheme = HTML::Entities::encode($taidSchemeX);
391 my $taidValue = $doc->find('/NCIPMessage/ItemReceived/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
392 my $visid = $doc->findvalue('/NCIPMessage/ItemReceived/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
393 my $copy = copy_from_barcode($visid);
394 fail( $copy->{textcode} . " $visid" ) unless ( blessed $copy);
395 my $r1 = checkin($visid) if ( $copy->status == OILS_COPY_STATUS_CHECKED_OUT ); # checkin the item before delete if ItemCheckedIn step was skipped
396 my $r2 = delete_copy($copy);
398 my $hd = <<ITEMRECEIVED;
399 Content-type: text/xml
402 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
403 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
404 <ItemReceivedResponse>
408 <Scheme>$faidScheme</Scheme>
409 <Value>$faidValue</Value>
414 <Scheme>$taidScheme</Scheme>
415 <Value>$taidValue</Value>
420 <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
422 </ItemReceivedResponse>
427 logit( $hd, ( caller(0) )[3] );
428 staff_log( $taidValue, $faidValue, "ItemReceived -> Visible ID : " . $visid );
432 my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
433 my $faidScheme = HTML::Entities::encode($faidSchemeX);
434 my $faidValue = $doc->find('/NCIPMessage/ItemRequestCancelled/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
436 my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
437 my $taidScheme = HTML::Entities::encode($taidSchemeX);
438 my $taidValue = $doc->find('/NCIPMessage/ItemRequestCancelled/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
439 my $UniqueItemIdAgencyIdValue = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/UniqueItemId/UniqueAgencyId/Value');
441 my $barcode = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/UniqueItemId/ItemIdentifierValue');
443 if ( $barcode =~ /^i/ ) { # delete copy only if barcode is an iNUMBER
444 $barcode .= $faidValue;
445 my $copy = copy_from_barcode($barcode);
446 fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
447 my $r = delete_copy($copy);
451 my $copy = copy_from_barcode($barcode);
452 fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
453 my $r = update_copy( $copy, 0 ); # TODO: we need to actually remove the hold, not just reset to available
456 my $hd = <<ITEMREQUESTCANCELLED;
457 Content-type: text/xml
460 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
461 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
462 <ItemRequestCancelledResponse>
466 <Scheme>$faidScheme</Scheme>
467 <Value>$faidValue</Value>
472 <Scheme>$taidScheme</Scheme>
473 <Value>$taidValue</Value>
478 <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
480 </ItemRequestCancelledResponse>
485 logit( $hd, ( caller(0) )[3] );
486 staff_log( $taidValue, $faidValue,
487 "ItemRequestCancelled -> Barcode : " . $barcode );
490 sub item_checked_in {
491 my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedIn/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
492 my $faidScheme = HTML::Entities::encode($faidSchemeX);
493 my $faidValue = $doc->find('/NCIPMessage/ItemCheckedIn/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
494 my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedIn/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
495 my $taidScheme = HTML::Entities::encode($taidSchemeX);
496 my $taidValue = $doc->find('/NCIPMessage/ItemCheckedIn/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
498 my $visid = $doc->findvalue('/NCIPMessage/ItemCheckedIn/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
499 my $r = checkin($visid);
500 my $copy = copy_from_barcode($visid);
501 fail( $copy->{textcode} . " $visid" ) unless ( blessed $copy);
502 my $r2 = update_copy( $copy, $conf->{status}->{transit_return} ); # "INN-Reach Transit Return" status
504 my $hd = <<ITEMCHECKEDIN;
505 Content-type: text/xml
508 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
509 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
510 <ItemCheckedInResponse>
514 <Scheme>$faidScheme</Scheme>
515 <Value>$faidValue</Value>
520 <Scheme>$taidScheme</Scheme>
521 <Value>$taidValue</Value>
526 <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
528 </ItemCheckedInResponse>
533 logit( $hd, ( caller(0) )[3] );
534 staff_log( $taidValue, $faidValue, "ItemCheckedIn -> Visible ID : " . $visid );
537 sub item_checked_out {
538 my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedOut/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
539 my $faidScheme = HTML::Entities::encode($faidSchemeX);
540 my $faidValue = $doc->find('/NCIPMessage/ItemCheckedOut/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
541 my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedOut/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
542 my $taidScheme = HTML::Entities::encode($taidSchemeX);
543 my $taidValue = $doc->find('/NCIPMessage/ItemCheckedOut/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
545 my $patron_barcode = $doc->findvalue('/NCIPMessage/ItemCheckedOut/UserOptionalFields/VisibleUserId/VisibleUserIdentifier');
546 my $due_date = $doc->findvalue('/NCIPMessage/ItemCheckedOut/DateDue');
547 my $visid = $doc->findvalue('/NCIPMessage/ItemCheckedOut/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
549 my $copy = copy_from_barcode($visid);
550 fail( $copy->{textcode} . " $visid" ) unless ( blessed $copy);
551 my $r = update_copy( $copy, 0 ); # seemed like copy had to be available before it could be checked out, so ...
552 my $r1 = checkin($visid) if ( $copy->status == OILS_COPY_STATUS_CHECKED_OUT ); # double posted itemcheckedout messages cause error ... trying to simplify
553 my $r2 = checkout( $visid, $patron_barcode, $due_date );
555 my $hd = <<ITEMCHECKEDOUT;
556 Content-type: text/xml
559 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
560 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
561 <ItemCheckedOutResponse>
565 <Scheme>$faidScheme</Scheme>
566 <Value>$faidValue</Value>
571 <Scheme>$taidScheme</Scheme>
572 <Value>$taidValue</Value>
577 <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
579 </ItemCheckedOutResponse>
584 logit( $hd, ( caller(0) )[3] );
585 staff_log( $taidValue, $faidValue,
586 "ItemCheckedOut -> Visible Id : " . $visid . " | Patron Barcode : " . $patron_barcode . " | Due Date : " . $due_date );
590 my $faidSchemeX = $doc->findvalue('/NCIPMessage/CheckOutItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
591 my $faidScheme = HTML::Entities::encode($faidSchemeX);
592 my $faidValue = $doc->find('/NCIPMessage/CheckOutItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
593 my $taidSchemeX = $doc->findvalue('/NCIPMessage/CheckOutItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
594 my $taidScheme = HTML::Entities::encode($taidSchemeX);
595 my $taidValue = $doc->find('/NCIPMessage/CheckOutItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
597 my $mdate = $doc->findvalue('/NCIPMessage/CheckOutItem/MandatedAction/DateEventOccurred');
598 # TODO: look up individual accounts for agencies based on barcode prefix + agency identifier
599 my $patron_barcode = $conf->{checkout}->{institutional_patron}; # patron id if patron_id_as_identifier = yes
601 # For CheckOutItem and INN-REACH, this value will correspond with our local barcode
602 my $barcode = $doc->findvalue('/NCIPMessage/CheckOutItem/UniqueItemId/ItemIdentifierValue');
604 # TODO: watch for possible real ids here?
605 my $due_date = $doc->findvalue('/NCIPMessage/CheckOutItem/DateDue');
607 my $copy = copy_from_barcode($barcode);
608 fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
610 my $r2 = checkout( $barcode, $patron_barcode, $due_date );
612 # TODO: check for checkout exception (like OPEN_CIRCULATION_EXISTS)
614 my $hd = <<CHECKOUTITEM;
615 Content-type: text/xml
618 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
619 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
620 <CheckOutItemResponse>
624 <Scheme>$faidScheme</Scheme>
625 <Value>$faidValue</Value>
630 <Scheme>$taidScheme</Scheme>
631 <Value>$taidValue</Value>
636 <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
638 </CheckOutItemResponse>
643 logit( $hd, ( caller(0) )[3] );
644 staff_log( $taidValue, $faidValue,
645 "CheckOutItem -> Barcode : " . $barcode . " | Patron Barcode : " . $patron_barcode . " | Due Date : " . $due_date );
649 my $faidSchemeX = $doc->findvalue('/NCIPMessage/CheckInItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
650 my $faidScheme = HTML::Entities::encode($faidSchemeX);
651 my $faidValue = $doc->find('/NCIPMessage/CheckInItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
652 my $taidSchemeX = $doc->findvalue('/NCIPMessage/CheckInItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
653 my $taidScheme = HTML::Entities::encode($taidSchemeX);
654 my $taidValue = $doc->find('/NCIPMessage/CheckInItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
656 # For CheckInItem and INN-REACH, this value will correspond with our local barcode
657 my $barcode = $doc->findvalue('/NCIPMessage/CheckInItem/UniqueItemId/ItemIdentifierValue');
658 my $r = checkin($barcode);
659 fail($r) if $r =~ /^COPY_NOT_CHECKED_OUT/;
660 # TODO: do we need to do these next steps? checkin() should handle everything, and we want this to end up in 'reshelving'. If we are worried about transits, we should handle (abort) them, not just change the status
661 ##my $copy = copy_from_barcode($barcode);
662 ##fail($copy->{textcode}." $barcode") unless (blessed $copy);
663 ## my $r2 = update_copy($copy,0); # Available now
665 my $hd = <<CHECKINITEM;
666 Content-type: text/xml
669 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
670 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
671 <CheckInItemResponse>
675 <Scheme>$faidScheme</Scheme>
676 <Value>$faidValue</Value>
681 <Scheme>$taidScheme</Scheme>
682 <Value>$taidValue</Value>
687 <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
689 </CheckInItemResponse>
694 logit( $hd, ( caller(0) )[3] );
695 staff_log( $taidValue, $faidValue, "CheckInItem -> Barcode : " . $barcode );
699 my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemShipped/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
700 my $faidScheme = HTML::Entities::encode($faidSchemeX);
701 my $faidValue = $doc->find('/NCIPMessage/ItemShipped/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
702 my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemShipped/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
703 my $taidScheme = HTML::Entities::encode($taidSchemeX);
704 my $taidValue = $doc->find('/NCIPMessage/ItemShipped/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
706 my $address = $doc->findvalue('/NCIPMessage/ItemShipped/ShippingInformation/PhysicalAddress/UnstructuredAddress/UnstructuredAddressData');
708 my $visid = $doc->findvalue('/NCIPMessage/ItemShipped/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
709 my $barcode = $doc->findvalue('/NCIPMessage/ItemShipped/UniqueItemId/ItemIdentifierValue') . $faidValue;
710 my $title = $doc->findvalue('/NCIPMessage/ItemShipped/ItemOptionalFields/BibliographicDescription/Title');
711 my $callnumber = $doc->findvalue('/NCIPMessage/ItemShipped/ItemOptionalFields/ItemDescription/CallNumber');
713 my $copy = copy_from_barcode($barcode);
715 fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
720 my $pickup_lib_map = load_map_file( $conf->{path}->{pickup_lib_map} );
722 if ($pickup_lib_map) {
723 $pickup_lib = lookup_pickup_lib($address, $pickup_lib_map);
728 update_hold_pickup($barcode, $pickup_lib);
731 my $r = update_copy_shipped( $copy, $conf->{status}->{transit}, $visid ); # put copy into INN-Reach Transit status & modify barcode = Visid != tempIIIiNumber
733 my $hd = <<ITEMSHIPPED;
734 Content-type: text/xml
737 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
738 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
739 <ItemShippedResponse>
743 <Scheme>$faidScheme</Scheme>
744 <Value>$faidValue</Value>
749 <Scheme>$taidScheme</Scheme>
750 <Value>$taidValue</Value>
755 <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
757 </ItemShippedResponse>
762 logit( $hd, ( caller(0) )[3] );
763 staff_log( $taidValue, $faidValue,
764 "ItemShipped -> Visible Id : " . $visid . " | Barcode : " . $barcode . " | Title : " . $title . " | Call Number : " . $callnumber );
768 my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequested/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
769 my $faidScheme = HTML::Entities::encode($faidSchemeX);
770 my $faidValue = $doc->find('/NCIPMessage/ItemRequested/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
772 my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequested/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
773 my $taidScheme = HTML::Entities::encode($taidSchemeX);
774 my $taidValue = $doc->find('/NCIPMessage/ItemRequested/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
775 my $UniqueItemIdAgencyIdValue = $doc->findvalue('/NCIPMessage/ItemRequested/UniqueItemId/UniqueAgencyId/Value');
777 # TODO: should we use the VisibleID for item agency variation of this method call
779 my $pid = $doc->findvalue('/NCIPMessage/ItemRequested/UniqueUserId/UserIdentifierValue');
780 my $barcode = $doc->findvalue('/NCIPMessage/ItemRequested/UniqueItemId/ItemIdentifierValue');
781 my $author = $doc->findvalue('/NCIPMessage/ItemRequested/ItemOptionalFields/BibliographicDescription/Author');
782 my $title = $doc->findvalue('/NCIPMessage/ItemRequested/ItemOptionalFields/BibliographicDescription/Title');
783 my $callnumber = $doc->findvalue('/NCIPMessage/ItemRequested/ItemOptionalFields/ItemDescription/CallNumber');
784 my $medium_type = $doc->find('/NCIPMessage/ItemRequested/ItemOptionalFields/BibliographicDescription/MediumType/Value');
786 my $r = "default error checking response";
788 if ( $barcode =~ /^i/ ) { # XXX EG is User Agency # create copy only if barcode is an iNUMBER
789 my $copy_status_id = $conf->{status}->{loan_requested}; # INN-Reach Loan Requested - local configured status
790 $barcode .= $faidValue;
791 # we want our custom status to be then end result, so create the copy with status of "Available, then hold it, then update the status
792 $r = create_copy( $title, $callnumber, $barcode, 0, $medium_type );
793 my $copy = copy_from_barcode($barcode);
794 my $r2 = place_simple_hold( $copy->id, $pid );
795 my $r3 = update_copy( $copy, $copy_status_id );
796 } else { # XXX EG is Item Agency
797 unless ( $conf->{behavior}->{no_item_agency_holds} =~ m/^y/i ) {
798 # place hold for user UniqueUserId/UniqueAgencyId/Value = institution account
799 my $copy = copy_from_barcode($barcode);
800 my $pid2 = 1013459; # XXX CUSTOMIZATION NEEDED XXX # this is the id of a user representing your DCB system, TODO: use agency information to create and link to individual accounts per agency, if needed
801 $r = place_simple_hold( $copy->id, $pid2 );
802 my $r2 = update_copy( $copy, $conf->{status}->{hold} ); # put into INN-Reach Hold status
806 # Avoid generating invalid XML responses by encoding title/author
807 # TODO: Move away from heredocs for generating XML
808 $title = HTML::Entities::encode($title);
809 $author = HTML::Entities::encode($author);
812 Content-type: text/xml
815 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
816 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
817 <ItemRequestedResponse>
821 <Scheme>$faidScheme</Scheme>
822 <Value>$faidValue</Value>
827 <Scheme>$taidScheme</Scheme>
828 <Value>$taidValue</Value>
834 <Scheme datatype="string">$taidScheme</Scheme>
835 <Value datatype="string">$taidValue</Value>
837 <UserIdentifierValue datatype="string">$pid</UserIdentifierValue>
840 <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
843 <BibliographicDescription>
844 <Author datatype="string">$author</Author>
845 <Title datatype="string">$title</Title>
846 </BibliographicDescription>
848 <CallNumber datatype="string">$callnumber</CallNumber>
850 </ItemOptionalFields>
851 </ItemRequestedResponse>
856 logit( $hd, ( caller(0) )[3] );
857 staff_log( $taidValue, $faidValue,
858 "ItemRequested -> Barcode : " . $barcode . " | Title : " . $title . " | Call Number : " . $callnumber . " | Patronid :" . $pid );
863 my $faidScheme = $doc->findvalue('/NCIPMessage/LookupUser/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
864 $faidScheme = HTML::Entities::encode($faidScheme);
865 my $faidValue = $doc->find('/NCIPMessage/LookupUser/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
866 my $taidScheme = $doc->findvalue('/NCIPMessage/LookupUser/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
867 $taidScheme = HTML::Entities::encode($taidScheme);
869 my $taidValue = $doc->find('/NCIPMessage/LookupUser/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
870 my $id = $doc->findvalue('/NCIPMessage/LookupUser/VisibleUserId/VisibleUserIdentifier');
874 if ($patron_id_type eq 'barcode') {
875 $uidValue = user_id_from_barcode($id);
880 if ( !defined($uidValue)
881 || ( ref($uidValue) && reftype($uidValue) eq 'HASH' ) )
883 do_lookup_user_error_stanza("PATRON_NOT_FOUND : $id");
887 my ( $propername, $email, $good_until, $userpriv, $block_stanza ) =
888 ( "name here", "", "good until", "", "" ); # defaults
890 my $patron = flesh_user($uidValue);
892 #if (blessed($patron)) {
894 my @penalties = @{ $patron->standing_penalties };
896 if ( $patron->deleted eq 't' ) {
897 do_lookup_user_error_stanza("PATRON_DELETED : $uidValue");
899 } elsif ( $patron->barred eq 't' ) {
900 do_lookup_user_error_stanza("PATRON_BARRED : $uidValue");
902 } elsif ( $patron->active eq 'f' ) {
903 do_lookup_user_error_stanza("PATRON_INACTIVE : $uidValue");
907 elsif ( $#penalties > -1 ) {
910 # foreach $penalty (@penalties) {
911 # if (defined($penalty->standing_penalty->block_list)) {
912 # my @block_list = split(/\|/, $penalty->standing_penalty->block_list);
913 # foreach my $block (@block_list) {
914 # foreach my $block_on (@$block_types) {
915 # if ($block eq $block_on) {
916 # $block_stanza .= "\n".$penalty->standing_penalty->name;
919 # last unless ($patron_ok);
921 # last unless ($patron_ok);
928 <Scheme datatype="string">http://just.testing.now</Scheme>
929 <Value datatype="string">$faidValue</Value>
932 <Scheme datatype="string">http://just.testing.now</Scheme>
933 <Value datatype="string">Block Hold</Value>
938 if ( defined( $patron->email ) && $conf->{behavior}->{omit_patron_email} !~ m/^y/i ) {
940 <UserAddressInformation>
942 <ElectronicAddressType>
943 <Scheme datatype="string">http://testing.now</Scheme>
944 <Value datatype="string">mailto</Value>
945 </ElectronicAddressType>
946 <ElectronicAddressData datatype="string">)
947 . HTML::Entities::encode( $patron->email )
948 . qq(</ElectronicAddressData>
950 </UserAddressInformation>);
953 $propername = $patron->first_given_name . " " . $patron->family_name;
954 $good_until = $patron->expire_date || "unknown";
955 $userpriv = $patron->profile->name;
957 my $userpriv_map = load_map_file( $conf->{path}->{userpriv_map} );
960 $userpriv = lookup_userpriv($userpriv, $userpriv_map);
964 # do_lookup_user_error_stanza("PATRON_NOT_FOUND : $id");
967 my $uniqid = $patron->id;
969 if ($patron_id_type eq 'barcode') {
970 $visid = $patron->card->barcode;
972 $visid = $patron->id;
974 my $hd = <<LOOKUPUSERRESPONSE;
975 Content-type: text/xml
978 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
979 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
984 <Scheme>$taidScheme</Scheme>
985 <Value>$taidValue</Value>
990 <Scheme>$faidScheme</Scheme>
991 <Value>$faidValue</Value>
997 <Scheme>$taidScheme</Scheme>
998 <Value>$taidValue</Value>
1000 <UserIdentifierValue>$uniqid</UserIdentifierValue>
1002 <UserOptionalFields>
1004 <VisibleUserIdentifierType>
1005 <Scheme datatype="string">http://blah.com</Scheme>
1006 <Value datatype="string">Barcode</Value>
1007 </VisibleUserIdentifierType>
1008 <VisibleUserIdentifier datatype="string">$visid</VisibleUserIdentifier>
1011 <PersonalNameInformation>
1012 <UnstructuredPersonalUserName datatype="string">$propername</UnstructuredPersonalUserName>
1013 </PersonalNameInformation>
1017 <Scheme datatype="string">$faidScheme</Scheme>
1018 <Value datatype="string">$faidValue</Value>
1020 <AgencyUserPrivilegeType>
1021 <Scheme datatype="string">http://testing.purposes.only</Scheme>
1022 <Value datatype="string">$userpriv</Value>
1023 </AgencyUserPrivilegeType>
1024 <ValidToDate datatype="string">$good_until</ValidToDate>
1025 </UserPrivilege> $email $block_stanza
1026 </UserOptionalFields>
1027 </LookupUserResponse>
1032 logit( $hd, ( caller(0) )[3] );
1033 staff_log( $taidValue, $faidValue,
1034 "LookupUser -> Patron Barcode : "
1046 shift || "THIS IS THE DEFAULT / DO NOT HANG III NCIP RESP MSG";
1047 print "Content-type: text/xml\n\n";
1050 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1051 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1052 <ItemRequestedResponse>
1056 <Scheme>http://136.181.125.166:6601/IRCIRCD?target=get_scheme_values&scheme=UniqueAgencyId</Scheme>
1062 <Scheme>http://136.181.125.166:6601/IRCIRCD?target=get_scheme_values&scheme=UniqueAgencyId</Scheme>
1063 <Value>$error_msg</Value>
1067 </ItemRequestedResponse>
1072 # XXX: we should log FromAgencyId and ToAgencyId values here, but they are not available to the code at this point
1074 ( ( caller(0) )[3] . " -> " . $error_msg ) );
1078 sub do_lookup_user_error_stanza {
1080 # XXX: we should include FromAgencyId and ToAgencyId values, but they are not available to the code at this point
1082 my $hd = <<LOOKUPPROB;
1083 Content-type: text/xml
1086 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1087 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1088 <LookupUserResponse>
1105 <ProcessingErrorType>
1106 <Scheme>http://www.niso.org/ncip/v1_0/schemes/processingerrortype/lookupuserprocessingerror.scm</Scheme>
1107 <Value>$error</Value>
1108 </ProcessingErrorType>
1109 <ProcessingErrorElement>
1110 <ElementName>AuthenticationInput</ElementName>
1111 </ProcessingErrorElement>
1114 </LookupUserResponse>
1119 logit( $hd, ( caller(0) )[3] );
1120 # XXX: we should log FromAgencyId and ToAgencyId values here, but they are not available to the code at this point
1121 staff_log( '', '', ( ( caller(0) )[3] . " -> " . $error ) );
1125 # Login to the OpenSRF system/Evergreen.
1127 # Returns a hash with the authtoken, authtime, and expiration (time in
1128 # seconds since 1/1/1970).
1131 # XXX: local opensrf core conf filename should be in config.
1132 # XXX: STAFF account with ncip service related permissions should be in config.
1133 my $bootstrap = '/openils/conf/opensrf_core.xml';
1134 my $uname = $conf->{auth}->{username};
1135 my $password = $conf->{auth}->{password};
1137 # Bootstrap the client
1138 OpenSRF::System->bootstrap_client( config_file => $bootstrap );
1139 my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
1140 Fieldmapper->import( IDL => $idl );
1142 # Initialize CStoreEditor:
1143 OpenILS::Utils::CStoreEditor->init;
1145 my $seed = OpenSRF::AppSession->create('open-ils.auth')
1146 ->request( 'open-ils.auth.authenticate.init', $uname )->gather(1);
1148 return undef unless $seed;
1150 my $response = OpenSRF::AppSession->create('open-ils.auth')->request(
1151 'open-ils.auth.authenticate.complete',
1154 password => md5_hex( $seed . md5_hex($password) ),
1159 return undef unless $response;
1162 $result{'authtoken'} = $response->{payload}->{authtoken};
1163 $result{'authtime'} = $response->{payload}->{authtime};
1164 $result{'expiration'} = time() + $result{'authtime'}
1165 if ( defined( $result{'authtime'} ) );
1169 # Check the time versus the session expiration time and login again if
1170 # the session has expired, consequently resetting the session
1171 # paramters. We want to run this before doing anything that requires
1172 # us to have a current session in OpenSRF.
1179 sub check_session_time {
1180 if ( time() > $session{'expiration'} ) {
1183 die("Failed to reinitialize the session after expiration.");
1188 # Retrieve the logged in user.
1192 OpenSRF::AppSession->create('open-ils.auth')
1193 ->request( 'open-ils.auth.session.retrieve', $session{authtoken} )
1198 # Logout/destroy the OpenSRF session
1204 # Does not return anything
1206 if ( time() < $session{'expiration'} ) {
1208 OpenSRF::AppSession->create('open-ils.auth')
1209 ->request( 'open-ils.auth.session.delete', $session{authtoken} )
1213 # strong.silent.success
1216 fail("Logout unsuccessful. Good-bye, anyway.");
1222 check_session_time();
1223 my ( $copy, $status_id ) = @_;
1224 my $e = new_editor( authtoken => $session{authtoken} );
1225 return $e->event->{textcode} unless ( $e->checkauth );
1227 $copy->status($status_id);
1228 return $e->event unless $e->update_asset_copy($copy);
1233 # my paranoia re barcode on shipped items using visid for unique value
1234 sub update_copy_shipped {
1235 check_session_time();
1236 my ( $copy, $status_id, $barcode ) = @_;
1237 my $e = new_editor( authtoken => $session{authtoken} );
1238 return $e->event->{textcode} unless ( $e->checkauth );
1240 $copy->status($status_id);
1241 $copy->barcode($barcode);
1242 return $e->event unless $e->update_asset_copy($copy);
1250 # Fieldmapper asset.copy object
1253 # "SUCCESS" on success
1254 # Event textcode if an error occurs
1256 check_session_time();
1259 my $e = new_editor( authtoken => $session{authtoken} );
1260 return $e->event->{textcode} unless ( $e->checkauth );
1263 my $vol = $e->retrieve_asset_call_number( $copy->call_number );
1264 return $e->event->{textcode} unless ($vol);
1266 # Get the biblio.record_entry
1267 my $bre = $e->retrieve_biblio_record_entry( $vol->record );
1268 return $e->event->{textcode} unless ($bre);
1270 # Delete everything in a transaction and rollback if anything fails.
1271 # TODO: I think there is a utility function which handles all this
1273 my $r; # To hold results of editor calls
1274 $r = $e->delete_asset_copy($copy);
1276 my $lval = $e->event->{textcode};
1281 $e->search_asset_copy( { call_number => $vol->id, deleted => 'f' } );
1283 $r = $e->delete_asset_call_number($vol);
1285 my $lval = $e->event->{textcode};
1289 $list = $e->search_asset_call_number( { record => $bre->id, deleted => 'f' } );
1291 $r = $e->delete_biblio_record_entry($bre);
1293 my $lval = $e->event->{textcode};
1303 # Get asset.copy from asset.copy.barcode.
1308 # asset.copy fieldmaper object
1310 sub copy_from_barcode {
1311 check_session_time();
1314 OpenSRF::AppSession->create('open-ils.search')
1315 ->request( 'open-ils.search.asset.copy.find_by_barcode', $barcode )
1320 sub locid_from_barcode {
1323 OpenSRF::AppSession->create('open-ils.search')
1324 ->request( 'open-ils.search.biblio.find_by_barcode', $barcode )
1326 return $response->{ids}[0];
1329 sub bre_id_from_barcode {
1330 check_session_time();
1333 OpenSRF::AppSession->create('open-ils.search')
1334 ->request( 'open-ils.search.bib_id.by_barcode', $barcode )
1340 check_session_time();
1343 OpenSRF::AppSession->create('open-ils.circ')
1344 ->request( 'open-ils.circ.holds.retrieve_all_from_title', $session{authtoken}, $bre_id )
1350 # Convert a MARC::Record to XML for Evergreen
1352 # Copied from Dyrcona's issa framework which copied
1353 # it from MVLC's Safari Load program which copied it
1354 # from some code in the Open-ILS example import scripts.
1357 # A MARC::Record object
1360 # String with XML for the MARC::Record as Evergreen likes it
1361 sub convert2marcxml {
1363 ( my $xml = $input->as_xml_record() ) =~ s/\n//sog;
1364 $xml =~ s/^<\?xml.+\?\s*>//go;
1365 $xml =~ s/>\s+</></go;
1366 $xml =~ s/\p{Cc}//go;
1367 $xml = $U->entityize($xml);
1368 $xml =~ s/[\x00-\x1f]//go;
1372 # Create a copy and marc record
1381 # event textcode on failure
1383 check_session_time();
1384 my ( $title, $callnumber, $barcode, $copy_status_id, $medium_type ) = @_;
1386 my $e = new_editor( authtoken => $session{authtoken} );
1387 return $e->event->{textcode} unless ( $e->checkauth );
1389 my $r = $e->allowed( [ 'CREATE_COPY', 'CREATE_MARC', 'CREATE_VOLUME' ] );
1390 if ( ref($r) eq 'HASH' ) {
1391 return $r->{textcode} . ' ' . $r->{ilsperm};
1394 # Check if the barcode exists in asset.copy and bail if it does.
1395 my $list = $e->search_asset_copy( { deleted => 'f', barcode => $barcode } );
1397 # in the future, can we update it, if it exists and only if it is an INN-Reach status item ?
1399 fail( 'BARCODE_EXISTS ! Barcode : ' . $barcode );
1403 # Create MARC record
1404 my $record = MARC::Record->new();
1405 $record->encoding('UTF-8');
1406 $record->leader('00881nam a2200193 4500');
1407 my $datespec = strftime( "%Y%m%d%H%M%S.0", localtime );
1409 push( @fields, MARC::Field->new( '005', $datespec ) );
1410 push( @fields, MARC::Field->new( '082', '0', '4', 'a' => $callnumber ) );
1411 push( @fields, MARC::Field->new( '245', '0', '0', 'a' => $title ) );
1412 $record->append_fields(@fields);
1414 # Convert the record to XML
1415 my $xml = convert2marcxml($record);
1418 OpenSRF::AppSession->create('open-ils.cat')
1419 ->request( 'open-ils.cat.biblio.record.xml.import',
1420 $session{authtoken}, $xml, 'System Local', 1 )->gather(1);
1421 return $bre->{textcode} if ( ref($bre) eq 'HASH' );
1423 # Create volume record
1425 OpenSRF::AppSession->create('open-ils.cat')
1426 ->request( 'open-ils.cat.call_number.find_or_create', $session{authtoken}, $callnumber, $bre->id, $conf->{volume}->{owning_lib} )
1428 return $vol->{textcode} if ( $vol->{textcode} );
1431 my $user = get_session;
1433 # Create copy record
1434 my $copy = Fieldmapper::asset::copy->new();
1435 # XXX CUSTOMIZATION NEEDED XXX
1436 # You will need to either create a circ mod for every expected medium type,
1437 # OR you should create a single circ mod for all requests from the external
1439 # Adjust these lines as needed.
1440 # $copy->circ_modifier(qq($medium_type)); # XXX CUSTOMIZATION NEEDED XXX
1442 $copy->circ_modifier($conf->{copy}->{circ_modifier});
1443 $copy->barcode($barcode);
1444 $copy->call_number( $vol->{acn_id} );
1445 $copy->circ_lib($conf->{copy}->{circ_lib});
1446 $copy->circulate('t');
1447 $copy->holdable('t');
1448 $copy->opac_visible('t');
1449 $copy->deleted('f');
1450 $copy->fine_level(2);
1451 $copy->loan_duration(2);
1452 $copy->location($conf->{copy}->{location});
1453 $copy->status($copy_status_id);
1455 $copy->creator('1');
1458 $copy = $e->create_asset_copy($copy);
1461 return $e->event->{textcode} unless ($r);
1465 # Checkout a copy to a patron
1472 # textcode of the OSRF response.
1474 check_session_time();
1475 my ( $copy_barcode, $patron_barcode, $due_date ) = @_;
1478 my $copy = copy_from_barcode($copy_barcode);
1479 unless ( defined($copy) && blessed($copy) ) {
1480 return 'COPY_BARCODE_NOT_FOUND : ' . $copy_barcode;
1485 if ($patron_id_type eq 'barcode') {
1486 $uid = user_id_from_barcode($patron_barcode);
1488 $uid = $patron_barcode;
1490 return 'PATRON_BARCODE_NOT_FOUND : ' . $patron_barcode if ( ref($uid) );
1492 my $response = OpenSRF::AppSession->create('open-ils.circ')->request(
1493 'open-ils.circ.checkout.full.override',
1494 $session{authtoken},
1496 copy_barcode => $copy_barcode,
1498 due_date => $due_date
1501 return $response->{textcode};
1505 check_session_time();
1506 my ( $copy_barcode, $due_date ) = @_;
1509 my $copy = copy_from_barcode($copy_barcode);
1510 unless ( defined($copy) && blessed($copy) ) {
1511 return 'COPY_BARCODE_NOT_FOUND : ' . $copy_barcode;
1514 my $response = OpenSRF::AppSession->create('open-ils.circ')->request(
1515 'open-ils.circ.renew.override',
1516 $session{authtoken},
1518 copy_barcode => $copy_barcode,
1519 due_date => $due_date
1522 return $response->{textcode};
1531 # "SUCCESS" on success
1532 # textcode of a failed OSRF request
1533 # 'COPY_NOT_CHECKED_OUT' when the copy is not checked out
1536 check_session_time();
1539 my $copy = copy_from_barcode($barcode);
1540 return $copy->{textcode} unless ( blessed $copy);
1542 return ("COPY_NOT_CHECKED_OUT $barcode")
1543 unless ( $copy->status == OILS_COPY_STATUS_CHECKED_OUT );
1545 my $e = new_editor( authtoken => $session{authtoken} );
1546 return $e->event->{textcode} unless ( $e->checkauth );
1548 my $circ = $e->search_action_circulation(
1549 [ { target_copy => $copy->id, xact_finish => undef } ] )->[0];
1551 OpenSRF::AppSession->create('open-ils.circ')
1552 ->request( 'open-ils.circ.checkin.override',
1553 $session{authtoken}, { force => 1, copy_id => $copy->id } )->gather(1);
1554 return 'SUCCESS' if ( $r->{textcode} eq 'ROUTE_ITEM' );
1555 return $r->{textcode};
1558 # Check in an copy as part of accept_item
1559 # Intent is for the copy to be captured for
1560 # a hold -- the only hold that should be
1561 # present on the copy
1563 sub checkin_accept {
1564 check_session_time();
1565 my $copy_id = shift;
1566 my $circ_lib = shift;
1568 my $r = OpenSRF::AppSession->create('open-ils.circ')->request(
1569 'open-ils.circ.checkin.override',
1570 $session{authtoken}, { force => 1, copy_id => $copy_id, circ_lib => $circ_lib }
1573 return $r->{textcode};
1576 # Get actor.usr.id from barcode.
1583 sub user_id_from_barcode {
1584 check_session_time();
1589 my $e = new_editor( authtoken => $session{authtoken} );
1590 return $response unless ( $e->checkauth );
1592 my $card = $e->search_actor_card( { barcode => $barcode, active => 't' } );
1593 return $e->event unless ($card);
1595 $response = $card->[0]->usr if (@$card);
1602 # Place a simple hold for a patron.
1605 # Target object appropriate for type of hold
1606 # Patron for whom the hold is place
1609 # "SUCCESS" on success
1610 # textcode of a failed OSRF request
1611 # "HOLD_TYPE_NOT_SUPPORTED" if the hold type is not supported
1612 # (Currently only support 'T' and 'C')
1614 # simple hold should be removed and full holds sub should be used instead - pragmatic solution only
1616 sub place_simple_hold {
1617 check_session_time();
1619 #my ($type, $target, $patron, $pickup_ou) = @_;
1620 my ( $target, $patron_id ) = @_;
1622 require $conf->{path}->{oils_header};
1623 use vars qw/ $apputils $memcache $user $authtoken $authtime /;
1625 osrf_connect( $conf->{path}->{opensrf_core} );
1626 oils_login( $conf->{auth}->{username}, $conf->{auth}->{password} );
1627 my $ahr = Fieldmapper::action::hold_request->new();
1628 $ahr->hold_type('C');
1629 # The targeter doesn't like our special statuses, and changing the status after the targeter finishes is difficult because it runs asynchronously. Our workaround is to create the hold frozen, unfreeze it, then run the targeter manually.
1630 $ahr->target($target);
1631 $ahr->usr($patron_id);
1632 $ahr->requestor($conf->{hold}->{requestor});
1633 # NOTE: When User Agency, we don't know the pickup location until ItemShipped time
1634 # TODO: When Item Agency and using holds, set this to requested copy's circ lib?
1635 $ahr->pickup_lib($conf->{hold}->{init_pickup_lib});
1636 $ahr->phone_notify(''); # TODO: set this based on usr prefs
1637 $ahr->email_notify(1); # TODO: set this based on usr prefs
1639 my $resp = simplereq( CIRC(), 'open-ils.circ.holds.create', $authtoken, $ahr );
1640 my $e = new_editor( xact => 1, authtoken => $session{authtoken} );
1641 $ahr = $e->retrieve_action_hold_request($resp); # refresh from db
1643 $e->update_action_hold_request($ahr);
1645 $U->storagereq( 'open-ils.storage.action.hold_request.copy_targeter', undef, $ahr->id );
1647 #oils_event_die($resp);
1649 if ( ref($resp) eq 'ARRAY' ) {
1650 ( $errors .= "error : " . $_->{textcode} ) for @$resp;
1652 } elsif ( ref($resp) ne 'HASH' ) {
1653 return "Hold placed! hold_id = " . $resp . "\n";
1657 sub find_hold_on_copy {
1658 check_session_time();
1660 my ( $copy_barcode ) = @_;
1662 # start with barcode of item, find bib ID
1663 my $rec = bre_id_from_barcode($copy_barcode);
1665 return undef unless $rec;
1667 # call for holds on that bib
1668 my $holds = holds_for_bre($rec);
1670 # There should only be a single copy hold
1671 my $hold_id = @{$holds->{copy_holds}}[0];
1673 return undef unless $hold_id;
1676 OpenSRF::AppSession->create('open-ils.circ')
1677 ->request( 'open-ils.circ.hold.details.retrieve', $session{authtoken}, $hold_id )
1680 my $hold = $hold_details->{hold};
1682 return undef unless blessed($hold);
1687 sub update_hold_pickup {
1688 check_session_time();
1690 my ( $copy_barcode, $pickup_lib ) = @_;
1692 my $hold = find_hold_on_copy($copy_barcode);
1694 # return if hold was not found
1695 return undef unless defined($hold) && blessed($hold);
1697 $hold->pickup_lib($pickup_lib);
1699 # update the copy hold with the new pickup lib information
1701 OpenSRF::AppSession->create('open-ils.circ')
1702 ->request( 'open-ils.circ.hold.update', $session{authtoken}, $hold )
1708 # Flesh user information
1713 # fieldmapped, fleshed user or
1714 # event hash on error
1716 check_session_time();
1719 OpenSRF::AppSession->create('open-ils.actor')
1720 ->request( 'open-ils.actor.user.fleshed.retrieve',
1721 $session{'authtoken'}, $id,
1722 [ 'card', 'cards', 'standing_penalties', 'home_ou', 'profile' ] )