Make holds go Available at AcceptItem time
[sitka/iNCIPit.git] / iNCIPit.cgi
1 #! /usr/bin/perl 
2
3 # This file is part of iNCIPit
4 #
5 # iNCIPit is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # iNCIPit is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
13 # License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with iNCIPit. If not, see <http://www.gnu.org/licenses/>.
17
18 use warnings;
19 use strict;
20 use XML::LibXML;
21 use CGI;
22 use HTML::Entities;
23 use CGI::Carp;
24 use OpenSRF::System;
25 use OpenSRF::Utils::SettingsClient;
26 use Digest::MD5 qw/md5_hex/;
27 use OpenILS::Utils::Fieldmapper;
28 use OpenILS::Utils::CStoreEditor qw/:funcs/;
29 use OpenILS::Const qw/:const/;
30 use Scalar::Util qw(reftype blessed);
31 use MARC::Record;
32 use MARC::Field;
33 use MARC::File::XML;
34 use POSIX qw/strftime/;
35 use DateTime;
36 use Config::Tiny;
37
38 my $U = "OpenILS::Application::AppUtils";
39
40 my $conf = load_config( 'iNCIPit.ini' );
41
42 # Set some variables from config (or defaults)
43 my $patron_id_type;
44
45 if ($conf->{behavior}->{patron_id_as_identifier} =~ m/^yes$/i) {
46     $patron_id_type = "id";
47 } else {
48     $patron_id_type = "barcode";
49 }
50
51 # reject non-https access unless configured otherwise
52 unless ($conf->{access}->{permit_plaintext} =~ m/^yes$/i) {
53     unless (defined($ENV{HTTPS}) && $ENV{HTTPS} eq 'on') {
54         print "Content-type: text/plain\n\n";
55         print "Access denied.\n";
56         exit 0;
57     }
58 }
59
60 # TODO: support for multiple load balancer IPs
61 my $lb_ip = $conf->{access}->{load_balancer_ip};
62
63 # if we are behind a load balancer, check to see that the
64 # actual client IP is permitted
65 if ($lb_ip) {
66     my @allowed_ips = split(/ *, */, $conf->{access}->{allowed_client_ips});
67
68     my $forwarded = $ENV{HTTP_X_FORWARDED_FOR};
69     my $ok = 0;
70
71     foreach my $check_ip (@allowed_ips) {
72         $ok = 1 if ($check_ip eq $forwarded);
73     }
74
75     # if we have a load balancer IP and are relying on
76     # X-Forwarded-For, deny requests other than those
77     # from the load balancer
78     # TODO: support for chained X-Forwarded-For -- ignore all but last
79     unless ($ok && $ENV{REMOTE_ADDR} eq $lb_ip) {
80         print "Content-type: text/plain\n\n";
81         print "Access denied.\n";
82         exit 0;
83     }
84 }
85
86 my $cgi = CGI->new();
87
88 my $xml = $cgi->param('POSTDATA') || $cgi->param('XForms:Model');
89
90 # log posted data
91 # XXX: posted ncip message log filename should be in config.
92 open POST_DATA, ">>post_data.txt";
93 print POST_DATA $xml;
94 close POST_DATA;
95
96 # initialize the parser
97 my $parser = new XML::LibXML;
98 my $doc = $parser->load_xml( string => $xml );
99
100 my %session = login();
101
102 if ( defined( $session{authtoken} ) ) {
103     $doc->exists('/NCIPMessage/LookupUser')           ? lookupUser()       : (
104     $doc->exists('/NCIPMessage/ItemRequested')        ? item_request()     : (
105     $doc->exists('/NCIPMessage/ItemShipped')          ? item_shipped()     : (
106     $doc->exists('/NCIPMessage/ItemCheckedOut')       ? item_checked_out() : (
107     $doc->exists('/NCIPMessage/CheckOutItem')         ? check_out_item()   : (
108     $doc->exists('/NCIPMessage/ItemCheckedIn')        ? item_checked_in()  : (
109     $doc->exists('/NCIPMessage/CheckInItem')          ? check_in_item()    : (
110     $doc->exists('/NCIPMessage/ItemReceived')         ? item_received()    : (
111     $doc->exists('/NCIPMessage/AcceptItem')           ? accept_item()      : (
112     $doc->exists('/NCIPMessage/ItemRequestCancelled') ? item_cancelled()   : (
113     $doc->exists('/NCIPMessage/ItemRenewed')          ? item_renew()       : (
114     $doc->exists('/NCIPMessage/RenewItem')            ? renew_item()       :
115     fail("UNKNOWN NCIPMessage")
116     )))))))))));
117
118     logout();
119 } else {
120     fail("Unable to perform action : Unknown Service Request");
121 }
122
123 # load and parse config file
124 sub load_config {
125     my $file = shift;
126
127     my $Config = Config::Tiny->new;
128     $Config = Config::Tiny->read( $file ) ||
129         die( "Error reading config file ", $file, ": ", Config::Tiny->errstr, "\n" );
130     return $Config;
131 }
132
133 # load and parse userpriv_map file, returning a hashref
134 sub load_map_file {
135     my $filename = shift;
136     my $map = {};
137     if (open(my $fh, "<", $filename)) {
138         while (my $entry = <$fh>) {
139             chomp($entry);
140             my ($from, $to) = split(m/:/, $entry);
141             $map->{$from} = $to;
142         }
143         close $fh;
144     }
145     return $map;
146 }
147
148 sub lookup_userpriv {
149     my $input = shift;
150     my $map = shift;
151     if (defined($map->{$input})) { # if we have a mapping for this profile
152         return $map->{$input}; # return value from mapping hash
153     } else {
154         return $input; # return original value
155     }
156 }
157
158 sub lookup_pickup_lib {
159     my $input = shift;
160     my $map = shift;
161     if (defined($map->{$input})) { # if we found this pickup lib
162         return $map->{$input}; # return value from mapping hash
163     } else {
164         return undef; # the original value does us no good -- return undef
165     }
166 }
167
168 sub logit {
169     my ( $msg, $func, $more_info ) = @_;
170     open RESP_DATA, ">>resp_data.txt";
171     print RESP_DATA $msg;
172     print RESP_DATA $more_info unless !$more_info;
173     close RESP_DATA;
174     print $msg || fail($func);
175 }
176
177 sub staff_log {
178     my ( $taiv, $faiv, $more_info ) = @_;
179     my $now = localtime();
180     open STAFF_LOG, ">>staff_data.csv";
181     print STAFF_LOG "$now, $faiv, $taiv, $more_info\n";
182     close STAFF_LOG;
183 }
184
185 sub item_renew {
186     my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemRenewed/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
187     my $faidScheme = HTML::Entities::encode($faidSchemeX);
188     my $faidValue  = $doc->find('/NCIPMessage/ItemRenewed/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
189     my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemRenewed/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
190     my $taidScheme = HTML::Entities::encode($taidSchemeX);
191     my $taidValue  = $doc->find('/NCIPMessage/ItemRenewed/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
192
193     my $pid = $doc->findvalue('/NCIPMessage/ItemRenewed/UniqueUserId/UserIdentifierValue');
194     my $visid = $doc->findvalue('/NCIPMessage/ItemRenewed/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
195     my $due_date = $doc->findvalue('/NCIPMessage/ItemRenewed/DateDue');
196
197     my $r = renewal( $visid, $due_date );
198
199     my $hd = <<ITEMRENEWAL;
200 Content-type: text/xml
201
202
203 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
204 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
205     <ItemRenewedResponse>
206         <ResponseHeader>
207             <FromAgencyId>
208                 <UniqueAgencyId>
209                     <Scheme>$faidScheme</Scheme>
210                     <Value>$faidValue</Value>
211                 </UniqueAgencyId>
212             </FromAgencyId>
213             <ToAgencyId>
214                 <UniqueAgencyId>
215                     <Scheme>$taidScheme</Scheme>
216                     <Value>$taidValue</Value>
217                 </UniqueAgencyId>
218             </ToAgencyId>
219         </ResponseHeader>
220         <UniqueItemId>
221             <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
222         </UniqueItemId>
223     </ItemRenewedResponse>
224 </NCIPMessage> 
225
226 ITEMRENEWAL
227
228     my $more_info = <<MOREINFO;
229
230 VISID             = $visid
231 Desired Due Date     = $due_date
232
233 MOREINFO
234
235     logit( $hd, ( caller(0) )[3], $more_info );
236     staff_log( $taidValue, $faidValue,
237             "ItemRenewal -> Patronid : "
238           . $pid
239           . " | Visid : "
240           . $visid
241           . " | Due Date : "
242           . $due_date );
243 }
244
245 sub renew_item {
246     my $faidSchemeX = $doc->findvalue('/NCIPMessage/RenewItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
247     my $faidScheme = HTML::Entities::encode($faidSchemeX);
248     my $faidValue  = $doc->find('/NCIPMessage/RenewItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
249     my $taidSchemeX = $doc->findvalue('/NCIPMessage/RenewItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
250     my $taidScheme = HTML::Entities::encode($taidSchemeX);
251     my $taidValue  = $doc->find('/NCIPMessage/RenewItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
252
253     my $pid = $doc->findvalue('/NCIPMessage/RenewItem/UniqueUserId/UserIdentifierValue');
254     my $unique_item_id = $doc->findvalue('/NCIPMessage/RenewItem/UniqueItemId/ItemIdentifierValue');
255     my $due_date = $doc->findvalue('/NCIPMessage/RenewItem/DateDue');
256
257     # we are using the UniqueItemId value as a barcode here
258     my $r = renewal( $unique_item_id, $due_date );
259
260     my $hd = <<ITEMRENEWAL;
261 Content-type: text/xml
262
263
264 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
265 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
266     <RenewItemResponse>
267         <ResponseHeader>
268             <FromAgencyId>
269                 <UniqueAgencyId>
270                     <Scheme>$faidScheme</Scheme>
271                     <Value>$faidValue</Value>
272                 </UniqueAgencyId>
273             </FromAgencyId>
274             <ToAgencyId>
275                 <UniqueAgencyId>
276                     <Scheme>$taidScheme</Scheme>
277                     <Value>$taidValue</Value>
278                 </UniqueAgencyId>
279             </ToAgencyId>
280         </ResponseHeader>
281         <UniqueItemId>
282             <ItemIdentifierValue datatype="string">$unique_item_id</ItemIdentifierValue>
283         </UniqueItemId>
284     </RenewItemResponse>
285 </NCIPMessage> 
286
287 ITEMRENEWAL
288
289     my $more_info = <<MOREINFO;
290
291 UNIQUEID             = $unique_item_id
292 Desired Due Date     = $due_date
293
294 MOREINFO
295
296     logit( $hd, ( caller(0) )[3], $more_info );
297     staff_log( $taidValue, $faidValue,
298             "RenewItem -> Patronid : "
299           . $pid
300           . " | Uniqueid: : "
301           . $unique_item_id
302           . " | Due Date : "
303           . $due_date );
304 }
305
306 sub accept_item {
307     my $faidSchemeX = $doc->findvalue('/NCIPMessage/AcceptItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
308     my $faidScheme = HTML::Entities::encode($faidSchemeX);
309     my $faidValue  = $doc->find('/NCIPMessage/AcceptItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
310     my $taidSchemeX = $doc->findvalue('/NCIPMessage/AcceptItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
311     my $taidScheme = HTML::Entities::encode($taidSchemeX);
312     my $taidValue  = $doc->find('/NCIPMessage/AcceptItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
313     my $visid = $doc->findvalue('/NCIPMessage/AcceptItem/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
314     my $request_id = $doc->findvalue('/NCIPMessage/AcceptItem/UniqueRequestId/RequestIdentifierValue') || "unknown";
315     my $patron = $doc->findvalue('/NCIPMessage/AcceptItem/UserOptionalFields/VisibleUserId/VisibleUserIdentifier');
316     my $copy = copy_from_barcode($visid);
317     fail( "accept_item: " . $copy->{textcode} . " $visid" ) unless ( blessed $copy);
318     my $r2 = update_copy( $copy, $conf->{status}->{hold} ); # put into INN-Reach Hold status
319     # We need to find the hold to know the pickup location
320     my $hold = find_hold_on_copy($visid);
321     # Check the copy in to capture for hold -- do it at the pickup_lib
322     # so that the hold becomes Available
323     my $checkin_result = checkin_accept($copy->id, $hold->pickup_lib);
324
325     my $hd = <<ACCEPTITEM;
326 Content-type: text/xml
327
328
329 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
330 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
331     <AcceptItemResponse>
332         <ResponseHeader>
333             <FromAgencyId>
334                 <UniqueAgencyId>
335                     <Scheme>$faidScheme</Scheme>
336                     <Value>$faidValue</Value>
337                 </UniqueAgencyId>
338             </FromAgencyId>
339             <ToAgencyId>
340                 <UniqueAgencyId>
341                     <Scheme>$taidScheme</Scheme>
342                     <Value>$taidValue</Value>
343                 </UniqueAgencyId>
344             </ToAgencyId>
345         </ResponseHeader>
346     <UniqueRequestId>
347             <ItemIdentifierValue datatype="string">$request_id</ItemIdentifierValue>
348         </UniqueRequestId>
349         <UniqueItemId>
350             <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
351         </UniqueItemId>
352     </AcceptItemResponse>
353 </NCIPMessage> 
354
355 ACCEPTITEM
356
357     logit( $hd, ( caller(0) )[3] );
358     staff_log( $taidValue, $faidValue,
359         "AcceptItem -> Request Id : " . $request_id . " | Patron Id : " . $patron . " | Visible Id :" . $visid );
360 }
361
362 sub item_received {
363     my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemReceived/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
364     my $faidScheme = HTML::Entities::encode($faidSchemeX);
365     my $faidValue = $doc->find('/NCIPMessage/ItemReceived/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
366     my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemReceived/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
367     my $taidScheme = HTML::Entities::encode($taidSchemeX);
368     my $taidValue  = $doc->find('/NCIPMessage/ItemReceived/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
369     my $visid = $doc->findvalue('/NCIPMessage/ItemReceived/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
370     my $copy = copy_from_barcode($visid);
371     fail( $copy->{textcode} . " $visid" ) unless ( blessed $copy);
372     my $r1 = checkin($visid) if ( $copy->status == OILS_COPY_STATUS_CHECKED_OUT ); # checkin the item before delete if ItemCheckedIn step was skipped
373     my $r2 = delete_copy($copy);
374
375     my $hd = <<ITEMRECEIVED;
376 Content-type: text/xml
377
378
379 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
380 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
381     <ItemReceivedResponse>
382         <ResponseHeader>
383             <FromAgencyId>
384                 <UniqueAgencyId>
385                     <Scheme>$faidScheme</Scheme>
386                     <Value>$faidValue</Value>
387                 </UniqueAgencyId>
388             </FromAgencyId>
389             <ToAgencyId>
390                 <UniqueAgencyId>
391                     <Scheme>$taidScheme</Scheme>
392                     <Value>$taidValue</Value>
393                 </UniqueAgencyId>
394             </ToAgencyId>
395         </ResponseHeader>
396         <UniqueItemId>
397             <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
398         </UniqueItemId>
399     </ItemReceivedResponse>
400 </NCIPMessage> 
401
402 ITEMRECEIVED
403
404     logit( $hd, ( caller(0) )[3] );
405     staff_log( $taidValue, $faidValue, "ItemReceived -> Visible ID : " . $visid );
406 }
407
408 sub item_cancelled {
409     my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
410     my $faidScheme = HTML::Entities::encode($faidSchemeX);
411     my $faidValue  = $doc->find('/NCIPMessage/ItemRequestCancelled/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
412
413     my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
414     my $taidScheme = HTML::Entities::encode($taidSchemeX);
415     my $taidValue  = $doc->find('/NCIPMessage/ItemRequestCancelled/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
416     my $UniqueItemIdAgencyIdValue = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/UniqueItemId/UniqueAgencyId/Value');
417
418     my $barcode = $doc->findvalue('/NCIPMessage/ItemRequestCancelled/UniqueItemId/ItemIdentifierValue');
419
420     if ( $barcode =~ /^i/ ) {    # delete copy only if barcode is an iNUMBER
421         $barcode .= $faidValue;
422         my $copy = copy_from_barcode($barcode);
423         fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
424         my $r = delete_copy($copy);
425     } else {
426
427         # remove hold!
428         my $copy = copy_from_barcode($barcode);
429         fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
430         my $r = update_copy( $copy, 0 ); # TODO: we need to actually remove the hold, not just reset to available
431     }
432
433     my $hd = <<ITEMREQUESTCANCELLED;
434 Content-type: text/xml
435
436
437 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
438 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
439     <ItemRequestCancelledResponse>
440         <ResponseHeader>
441             <FromAgencyId>
442                 <UniqueAgencyId>
443                     <Scheme>$faidScheme</Scheme>
444                     <Value>$faidValue</Value>
445                 </UniqueAgencyId>
446             </FromAgencyId>
447             <ToAgencyId>
448                 <UniqueAgencyId>
449                     <Scheme>$taidScheme</Scheme>
450                     <Value>$taidValue</Value>
451                 </UniqueAgencyId>
452             </ToAgencyId>
453         </ResponseHeader>
454         <UniqueItemId>
455             <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
456         </UniqueItemId>
457     </ItemRequestCancelledResponse>
458 </NCIPMessage> 
459
460 ITEMREQUESTCANCELLED
461
462     logit( $hd, ( caller(0) )[3] );
463     staff_log( $taidValue, $faidValue,
464         "ItemRequestCancelled -> Barcode : " . $barcode );
465 }
466
467 sub item_checked_in {
468     my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedIn/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
469     my $faidScheme = HTML::Entities::encode($faidSchemeX);
470     my $faidValue  = $doc->find('/NCIPMessage/ItemCheckedIn/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
471     my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedIn/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
472     my $taidScheme = HTML::Entities::encode($taidSchemeX);
473     my $taidValue  = $doc->find('/NCIPMessage/ItemCheckedIn/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
474
475     my $visid = $doc->findvalue('/NCIPMessage/ItemCheckedIn/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
476     my $r = checkin($visid);
477     my $copy = copy_from_barcode($visid);
478     fail( $copy->{textcode} . " $visid" ) unless ( blessed $copy);
479     my $r2 = update_copy( $copy, $conf->{status}->{transit_return} ); # "INN-Reach Transit Return" status
480
481     my $hd = <<ITEMCHECKEDIN;
482 Content-type: text/xml
483
484
485 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
486 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
487     <ItemCheckedInResponse>
488         <ResponseHeader>
489             <FromAgencyId>
490                 <UniqueAgencyId>
491                     <Scheme>$faidScheme</Scheme>
492                     <Value>$faidValue</Value>
493                 </UniqueAgencyId>
494             </FromAgencyId>
495             <ToAgencyId>
496                 <UniqueAgencyId>
497                     <Scheme>$taidScheme</Scheme>
498                     <Value>$taidValue</Value>
499                 </UniqueAgencyId>
500             </ToAgencyId>
501         </ResponseHeader>
502         <UniqueItemId>
503             <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
504         </UniqueItemId>
505     </ItemCheckedInResponse>
506 </NCIPMessage> 
507
508 ITEMCHECKEDIN
509
510     logit( $hd, ( caller(0) )[3] );
511     staff_log( $taidValue, $faidValue, "ItemCheckedIn -> Visible ID : " . $visid );
512 }
513
514 sub item_checked_out {
515     my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedOut/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
516     my $faidScheme = HTML::Entities::encode($faidSchemeX);
517     my $faidValue  = $doc->find('/NCIPMessage/ItemCheckedOut/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
518     my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemCheckedOut/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
519     my $taidScheme = HTML::Entities::encode($taidSchemeX);
520     my $taidValue  = $doc->find('/NCIPMessage/ItemCheckedOut/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
521
522     my $patron_barcode = $doc->findvalue('/NCIPMessage/ItemCheckedOut/UserOptionalFields/VisibleUserId/VisibleUserIdentifier');
523     my $due_date = $doc->findvalue('/NCIPMessage/ItemCheckedOut/DateDue');
524     my $visid = $doc->findvalue('/NCIPMessage/ItemCheckedOut/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
525
526     my $copy = copy_from_barcode($visid);
527     fail( $copy->{textcode} . " $visid" ) unless ( blessed $copy);
528     my $r = update_copy( $copy, 0 ); # seemed like copy had to be available before it could be checked out, so ...
529     my $r1 = checkin($visid) if ( $copy->status == OILS_COPY_STATUS_CHECKED_OUT ); # double posted itemcheckedout messages cause error ... trying to simplify
530     my $r2 = checkout( $visid, $patron_barcode, $due_date );
531
532     my $hd = <<ITEMCHECKEDOUT;
533 Content-type: text/xml
534
535
536 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
537 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
538     <ItemCheckedOutResponse>
539         <ResponseHeader>
540             <FromAgencyId>
541                 <UniqueAgencyId>
542                     <Scheme>$faidScheme</Scheme>
543                     <Value>$faidValue</Value>
544                 </UniqueAgencyId>
545             </FromAgencyId>
546             <ToAgencyId>
547                 <UniqueAgencyId>
548                     <Scheme>$taidScheme</Scheme>
549                     <Value>$taidValue</Value>
550                 </UniqueAgencyId>
551             </ToAgencyId>
552         </ResponseHeader>
553         <UniqueItemId>
554             <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
555         </UniqueItemId>
556     </ItemCheckedOutResponse>
557 </NCIPMessage> 
558
559 ITEMCHECKEDOUT
560
561     logit( $hd, ( caller(0) )[3] );
562     staff_log( $taidValue, $faidValue,
563         "ItemCheckedOut -> Visible Id : " . $visid . " | Patron Barcode : " . $patron_barcode . " | Due Date : " . $due_date );
564 }
565
566 sub check_out_item {
567     my $faidSchemeX = $doc->findvalue('/NCIPMessage/CheckOutItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
568     my $faidScheme = HTML::Entities::encode($faidSchemeX);
569     my $faidValue  = $doc->find('/NCIPMessage/CheckOutItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
570     my $taidSchemeX = $doc->findvalue('/NCIPMessage/CheckOutItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
571     my $taidScheme = HTML::Entities::encode($taidSchemeX);
572     my $taidValue  = $doc->find('/NCIPMessage/CheckOutItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
573
574     my $mdate = $doc->findvalue('/NCIPMessage/CheckOutItem/MandatedAction/DateEventOccurred');
575     # TODO: look up individual accounts for agencies based on barcode prefix + agency identifier
576     my $patron_barcode = $conf->{checkout}->{institutional_patron}; # patron id if patron_id_as_identifier = yes
577
578     # For CheckOutItem and INN-REACH, this value will correspond with our local barcode
579     my $barcode = $doc->findvalue('/NCIPMessage/CheckOutItem/UniqueItemId/ItemIdentifierValue');
580
581     # TODO: watch for possible real ids here?
582     my $due_date = $doc->findvalue('/NCIPMessage/CheckOutItem/DateDue');
583
584     my $copy = copy_from_barcode($barcode);
585     fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
586
587     my $r2 = checkout( $barcode, $patron_barcode, $due_date );
588
589     # TODO: check for checkout exception (like OPEN_CIRCULATION_EXISTS)
590
591     my $hd = <<CHECKOUTITEM;
592 Content-type: text/xml
593
594
595 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
596 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
597     <CheckOutItemResponse>
598         <ResponseHeader>
599             <FromAgencyId>
600                 <UniqueAgencyId>
601                     <Scheme>$faidScheme</Scheme>
602                     <Value>$faidValue</Value>
603                 </UniqueAgencyId>
604             </FromAgencyId>
605             <ToAgencyId>
606                 <UniqueAgencyId>
607                     <Scheme>$taidScheme</Scheme>
608                     <Value>$taidValue</Value>
609                 </UniqueAgencyId>
610             </ToAgencyId>
611         </ResponseHeader>
612         <UniqueItemId>
613             <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
614         </UniqueItemId>
615     </CheckOutItemResponse>
616 </NCIPMessage> 
617
618 CHECKOUTITEM
619
620     logit( $hd, ( caller(0) )[3] );
621     staff_log( $taidValue, $faidValue,
622         "CheckOutItem -> Barcode : " . $barcode . " | Patron Barcode : " . $patron_barcode . " | Due Date : " . $due_date );
623 }
624
625 sub check_in_item {
626     my $faidSchemeX = $doc->findvalue('/NCIPMessage/CheckInItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
627     my $faidScheme = HTML::Entities::encode($faidSchemeX);
628     my $faidValue  = $doc->find('/NCIPMessage/CheckInItem/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
629     my $taidSchemeX = $doc->findvalue('/NCIPMessage/CheckInItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
630     my $taidScheme = HTML::Entities::encode($taidSchemeX);
631     my $taidValue  = $doc->find('/NCIPMessage/CheckInItem/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
632
633     # For CheckInItem and INN-REACH, this value will correspond with our local barcode
634     my $barcode = $doc->findvalue('/NCIPMessage/CheckInItem/UniqueItemId/ItemIdentifierValue');
635     my $r = checkin($barcode);
636     fail($r) if $r =~ /^COPY_NOT_CHECKED_OUT/;
637     # 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
638     ##my $copy = copy_from_barcode($barcode);
639     ##fail($copy->{textcode}." $barcode") unless (blessed $copy);
640     ##  my $r2 = update_copy($copy,0); # Available now 
641
642     my $hd = <<CHECKINITEM;
643 Content-type: text/xml
644
645
646 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
647 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
648     <CheckInItemResponse>
649         <ResponseHeader>
650             <FromAgencyId>
651                 <UniqueAgencyId>
652                     <Scheme>$faidScheme</Scheme>
653                     <Value>$faidValue</Value>
654                 </UniqueAgencyId>
655             </FromAgencyId>
656             <ToAgencyId>
657                 <UniqueAgencyId>
658                     <Scheme>$taidScheme</Scheme>
659                     <Value>$taidValue</Value>
660                 </UniqueAgencyId>
661             </ToAgencyId>
662         </ResponseHeader>
663         <UniqueItemId>
664             <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
665         </UniqueItemId>
666     </CheckInItemResponse>
667 </NCIPMessage> 
668
669 CHECKINITEM
670
671     logit( $hd, ( caller(0) )[3] );
672     staff_log( $taidValue, $faidValue, "CheckInItem -> Barcode : " . $barcode );
673 }
674
675 sub item_shipped {
676     my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemShipped/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
677     my $faidScheme = HTML::Entities::encode($faidSchemeX);
678     my $faidValue  = $doc->find('/NCIPMessage/ItemShipped/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
679     my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemShipped/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
680     my $taidScheme = HTML::Entities::encode($taidSchemeX);
681     my $taidValue  = $doc->find('/NCIPMessage/ItemShipped/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
682
683     my $address = $doc->findvalue('/NCIPMessage/ItemShipped/ShippingInformation/PhysicalAddress/UnstructuredAddress/UnstructuredAddressData');
684
685     my $visid = $doc->findvalue('/NCIPMessage/ItemShipped/ItemOptionalFields/ItemDescription/VisibleItemId/VisibleItemIdentifier') . $faidValue;
686     my $barcode = $doc->findvalue('/NCIPMessage/ItemShipped/UniqueItemId/ItemIdentifierValue') . $faidValue;
687     my $title = $doc->findvalue('/NCIPMessage/ItemShipped/ItemOptionalFields/BibliographicDescription/Title');
688     my $callnumber = $doc->findvalue('/NCIPMessage/ItemShipped/ItemOptionalFields/ItemDescription/CallNumber');
689
690     my $copy = copy_from_barcode($barcode);
691
692     fail( $copy->{textcode} . " $barcode" ) unless ( blessed $copy);
693
694     my $pickup_lib;
695
696     if ($address) {
697         my $pickup_lib_map = load_map_file( $conf->{path}->{pickup_lib_map} );
698
699         if ($pickup_lib_map) {
700             $pickup_lib = lookup_pickup_lib($address, $pickup_lib_map);
701         }
702     }
703
704     if ($pickup_lib) {
705         update_hold_pickup($barcode, $pickup_lib);
706     }
707
708     my $r = update_copy_shipped( $copy, $conf->{status}->{transit}, $visid ); # put copy into INN-Reach Transit status & modify barcode = Visid != tempIIIiNumber
709
710     my $hd = <<ITEMSHIPPED;
711 Content-type: text/xml
712
713
714 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
715 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
716     <ItemShippedResponse>
717         <ResponseHeader>
718             <FromAgencyId>
719                 <UniqueAgencyId>
720                     <Scheme>$faidScheme</Scheme>
721                     <Value>$faidValue</Value>
722                 </UniqueAgencyId>
723             </FromAgencyId>
724             <ToAgencyId>
725                 <UniqueAgencyId>
726                     <Scheme>$taidScheme</Scheme>
727                     <Value>$taidValue</Value>
728                 </UniqueAgencyId>
729             </ToAgencyId>
730         </ResponseHeader>
731         <UniqueItemId>
732             <ItemIdentifierValue datatype="string">$visid</ItemIdentifierValue>
733         </UniqueItemId>
734     </ItemShippedResponse>
735 </NCIPMessage> 
736
737 ITEMSHIPPED
738
739     logit( $hd, ( caller(0) )[3] );
740     staff_log( $taidValue, $faidValue,
741         "ItemShipped -> Visible Id : " . $visid . " | Barcode : " . $barcode . " | Title : " . $title . " | Call Number : " . $callnumber );
742 }
743
744 sub item_request {
745     my $faidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequested/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
746     my $faidScheme = HTML::Entities::encode($faidSchemeX);
747     my $faidValue  = $doc->find('/NCIPMessage/ItemRequested/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
748
749     my $taidSchemeX = $doc->findvalue('/NCIPMessage/ItemRequested/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
750     my $taidScheme = HTML::Entities::encode($taidSchemeX);
751     my $taidValue  = $doc->find('/NCIPMessage/ItemRequested/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
752     my $UniqueItemIdAgencyIdValue = $doc->findvalue('/NCIPMessage/ItemRequested/UniqueItemId/UniqueAgencyId/Value');
753
754     # TODO: should we use the VisibleID for item agency variation of this method call
755
756     my $pid = $doc->findvalue('/NCIPMessage/ItemRequested/UniqueUserId/UserIdentifierValue');
757     my $barcode = $doc->findvalue('/NCIPMessage/ItemRequested/UniqueItemId/ItemIdentifierValue');
758     my $author = $doc->findvalue('/NCIPMessage/ItemRequested/ItemOptionalFields/BibliographicDescription/Author');
759     my $title = $doc->findvalue('/NCIPMessage/ItemRequested/ItemOptionalFields/BibliographicDescription/Title');
760     my $callnumber = $doc->findvalue('/NCIPMessage/ItemRequested/ItemOptionalFields/ItemDescription/CallNumber');
761     my $medium_type = $doc->find('/NCIPMessage/ItemRequested/ItemOptionalFields/BibliographicDescription/MediumType/Value');
762
763     my $r = "default error checking response";
764
765     if ( $barcode =~ /^i/ ) {    # XXX EG is User Agency # create copy only if barcode is an iNUMBER
766         my $copy_status_id = $conf->{status}->{loan_requested}; # INN-Reach Loan Requested - local configured status
767         $barcode .= $faidValue;
768         # 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
769         $r = create_copy( $title, $callnumber, $barcode, 0, $medium_type );
770         my $copy = copy_from_barcode($barcode);
771         my $r2   = place_simple_hold( $copy->id, $pid );
772         my $r3   = update_copy( $copy, $copy_status_id );
773     } else {    # XXX EG is Item Agency
774         unless ( $conf->{behavior}->{no_item_agency_holds} =~ m/^y/i ) {
775             # place hold for user UniqueUserId/UniqueAgencyId/Value = institution account
776             my $copy = copy_from_barcode($barcode);
777             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
778             $r = place_simple_hold( $copy->id, $pid2 );
779             my $r2 = update_copy( $copy, $conf->{status}->{hold} ); # put into INN-Reach Hold status
780         }
781     }
782
783     # Avoid generating invalid XML responses by encoding title/author
784     # TODO: Move away from heredocs for generating XML
785         $title  = HTML::Entities::encode($title);
786         $author = HTML::Entities::encode($author);
787
788     my $hd = <<ITEMREQ;
789 Content-type: text/xml
790
791
792 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
793 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
794     <ItemRequestedResponse>
795         <ResponseHeader>
796             <FromAgencyId>
797                 <UniqueAgencyId>
798                     <Scheme>$faidScheme</Scheme>
799                     <Value>$faidValue</Value>
800                 </UniqueAgencyId>
801             </FromAgencyId>
802             <ToAgencyId>
803                 <UniqueAgencyId>
804                     <Scheme>$taidScheme</Scheme>
805                     <Value>$taidValue</Value>
806                 </UniqueAgencyId>
807             </ToAgencyId>
808         </ResponseHeader>
809         <UniqueUserId>
810             <UniqueAgencyId>
811                 <Scheme datatype="string">$taidScheme</Scheme>
812                 <Value datatype="string">$taidValue</Value>
813             </UniqueAgencyId>
814             <UserIdentifierValue datatype="string">$pid</UserIdentifierValue>
815         </UniqueUserId>
816         <UniqueItemId>
817             <ItemIdentifierValue datatype="string">$barcode</ItemIdentifierValue>
818         </UniqueItemId>
819         <ItemOptionalFields>
820             <BibliographicDescription>
821         <Author datatype="string">$author</Author>
822         <Title datatype="string">$title</Title>
823             </BibliographicDescription>
824             <ItemDescription>
825                 <CallNumber datatype="string">$callnumber</CallNumber>
826             </ItemDescription>
827        </ItemOptionalFields>
828     </ItemRequestedResponse>
829 </NCIPMessage> 
830
831 ITEMREQ
832
833     logit( $hd, ( caller(0) )[3] );
834     staff_log( $taidValue, $faidValue,
835         "ItemRequested -> Barcode : " . $barcode . " | Title : " . $title . " | Call Number : " . $callnumber . " | Patronid :" . $pid );
836 }
837
838 sub lookupUser {
839
840     my $faidScheme = $doc->findvalue('/NCIPMessage/LookupUser/InitiationHeader/FromAgencyId/UniqueAgencyId/Scheme');
841     $faidScheme = HTML::Entities::encode($faidScheme);
842     my $faidValue = $doc->find('/NCIPMessage/LookupUser/InitiationHeader/FromAgencyId/UniqueAgencyId/Value');
843     my $taidScheme = $doc->findvalue('/NCIPMessage/LookupUser/InitiationHeader/ToAgencyId/UniqueAgencyId/Scheme');
844     $taidScheme = HTML::Entities::encode($taidScheme);
845
846     my $taidValue = $doc->find('/NCIPMessage/LookupUser/InitiationHeader/ToAgencyId/UniqueAgencyId/Value');
847     my $id = $doc->findvalue('/NCIPMessage/LookupUser/VisibleUserId/VisibleUserIdentifier');
848
849     my $uidValue;
850
851     if ($patron_id_type eq 'barcode') {
852         $uidValue = user_id_from_barcode($id);
853     } else {
854         $uidValue = $id;
855     }
856
857     if ( !defined($uidValue)
858         || ( ref($uidValue) && reftype($uidValue) eq 'HASH' ) )
859     {
860         do_lookup_user_error_stanza("PATRON_NOT_FOUND : $id");
861         die;
862     }
863
864     my ( $propername, $email, $good_until, $userpriv, $block_stanza ) =
865       ( "name here", "", "good until", "", "" );    # defaults
866
867     my $patron = flesh_user($uidValue);
868
869     #if (blessed($patron)) {
870     my $patron_ok = 1;
871     my @penalties = @{ $patron->standing_penalties };
872
873     if ( $patron->deleted eq 't' ) {
874         do_lookup_user_error_stanza("PATRON_DELETED : $uidValue");
875         die;
876     } elsif ( $patron->barred eq 't' ) {
877         do_lookup_user_error_stanza("PATRON_BARRED : $uidValue");
878         die;
879     } elsif ( $patron->active eq 'f' ) {
880         do_lookup_user_error_stanza("PATRON_INACTIVE : $uidValue");
881         die;
882     }
883
884     elsif ( $#penalties > -1 ) {
885
886 #                my $penalty;
887 #                   foreach $penalty (@penalties) {
888 #                    if (defined($penalty->standing_penalty->block_list)) {
889 #                            my @block_list = split(/\|/, $penalty->standing_penalty->block_list);
890 #                            foreach my $block (@block_list) {
891 #                                foreach my $block_on (@$block_types) {
892 #                                    if ($block eq $block_on) {
893 #                                        $block_stanza .= "\n".$penalty->standing_penalty->name;
894 #                                        $patron_ok = 0;
895 #                                    }
896 #                                    last unless ($patron_ok);
897 #                            }
898 #                                last unless ($patron_ok);
899 #                          }
900 #                     }
901 #                }
902         $block_stanza = qq(
903             <BlockOrTrap>
904                 <UniqueAgencyId>
905                     <Scheme datatype="string">http://just.testing.now</Scheme>
906                     <Value datatype="string">$faidValue</Value>
907                 </UniqueAgencyId>
908                 <BlockOrTrapType>
909                     <Scheme datatype="string">http://just.testing.now</Scheme>
910                     <Value datatype="string">Block Hold</Value>
911                 </BlockOrTrapType>
912             </BlockOrTrap>);
913     }
914
915     if ( defined( $patron->email ) && $conf->{behavior}->{omit_patron_email} !~ m/^y/i ) {
916         $email = qq(
917             <UserAddressInformation>
918                 <ElectronicAddress>
919                     <ElectronicAddressType>
920                         <Scheme datatype="string">http://testing.now</Scheme>
921                         <Value datatype="string">mailto</Value>
922                     </ElectronicAddressType>
923                     <ElectronicAddressData datatype="string">)
924           . HTML::Entities::encode( $patron->email )
925           . qq(</ElectronicAddressData>
926                 </ElectronicAddress>
927             </UserAddressInformation>);
928     }
929
930     $propername = $patron->first_given_name . " " . $patron->family_name;
931     $good_until = $patron->expire_date || "unknown";
932     $userpriv = $patron->profile->name;
933
934     my $userpriv_map = load_map_file( $conf->{path}->{userpriv_map} );
935
936     if ($userpriv_map) {
937         $userpriv = lookup_userpriv($userpriv, $userpriv_map);
938     }
939
940     #} else {
941     #    do_lookup_user_error_stanza("PATRON_NOT_FOUND : $id");
942     #    die;
943     #}
944     my $uniqid = $patron->id;
945     my $visid;
946     if ($patron_id_type eq 'barcode') {
947         $visid = $patron->card->barcode;
948     } else {
949         $visid = $patron->id;
950     }
951     my $hd = <<LOOKUPUSERRESPONSE;
952 Content-type: text/xml
953
954
955 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
956 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
957     <LookupUserResponse>
958         <ResponseHeader>
959             <FromAgencyId>
960                 <UniqueAgencyId>
961                     <Scheme>$taidScheme</Scheme>
962                     <Value>$taidValue</Value>
963                 </UniqueAgencyId>
964             </FromAgencyId>
965             <ToAgencyId>
966                 <UniqueAgencyId>
967                    <Scheme>$faidScheme</Scheme>
968                    <Value>$faidValue</Value>
969                 </UniqueAgencyId>
970             </ToAgencyId>
971         </ResponseHeader>
972         <UniqueUserId>
973             <UniqueAgencyId>
974                 <Scheme>$taidScheme</Scheme>
975                 <Value>$taidValue</Value>
976             </UniqueAgencyId>
977             <UserIdentifierValue>$uniqid</UserIdentifierValue>
978         </UniqueUserId>
979         <UserOptionalFields>
980             <VisibleUserId>
981                 <VisibleUserIdentifierType>
982                     <Scheme datatype="string">http://blah.com</Scheme>
983                     <Value datatype="string">Barcode</Value>
984                 </VisibleUserIdentifierType>
985                 <VisibleUserIdentifier datatype="string">$visid</VisibleUserIdentifier>
986             </VisibleUserId>
987             <NameInformation>
988                 <PersonalNameInformation>
989                     <UnstructuredPersonalUserName datatype="string">$propername</UnstructuredPersonalUserName>
990                 </PersonalNameInformation>
991             </NameInformation>
992             <UserPrivilege>
993                 <UniqueAgencyId>
994                     <Scheme datatype="string">$faidScheme</Scheme>
995                     <Value datatype="string">$faidValue</Value>
996                 </UniqueAgencyId>
997                 <AgencyUserPrivilegeType>
998                     <Scheme datatype="string">http://testing.purposes.only</Scheme>
999                     <Value datatype="string">$userpriv</Value>
1000                 </AgencyUserPrivilegeType>
1001                 <ValidToDate datatype="string">$good_until</ValidToDate>
1002             </UserPrivilege> $email $block_stanza
1003         </UserOptionalFields>
1004    </LookupUserResponse>
1005 </NCIPMessage>
1006
1007 LOOKUPUSERRESPONSE
1008
1009     logit( $hd, ( caller(0) )[3] );
1010     staff_log( $taidValue, $faidValue,
1011             "LookupUser -> Patron Barcode : "
1012           . $id
1013           . " | Patron Id : "
1014           . $uidValue
1015           . " | User Name : "
1016           . $propername
1017           . " | User Priv : "
1018           . $userpriv );
1019 }
1020
1021 sub fail {
1022     my $error_msg =
1023       shift || "THIS IS THE DEFAULT / DO NOT HANG III NCIP RESP MSG";
1024     print "Content-type: text/xml\n\n";
1025
1026     print <<ITEMREQ;
1027 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1028 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1029     <ItemRequestedResponse>
1030         <ResponseHeader>
1031             <FromAgencyId>
1032                 <UniqueAgencyId>
1033                     <Scheme>http://136.181.125.166:6601/IRCIRCD?target=get_scheme_values&amp;scheme=UniqueAgencyId</Scheme>
1034                     <Value></Value>
1035                 </UniqueAgencyId>
1036             </FromAgencyId>
1037             <ToAgencyId>
1038                 <UniqueAgencyId>
1039                     <Scheme>http://136.181.125.166:6601/IRCIRCD?target=get_scheme_values&amp;scheme=UniqueAgencyId</Scheme>
1040                     <Value>$error_msg</Value>
1041                 </UniqueAgencyId>
1042             </ToAgencyId>
1043         </ResponseHeader>
1044     </ItemRequestedResponse>
1045 </NCIPMessage>
1046
1047 ITEMREQ
1048
1049     # XXX: we should log FromAgencyId and ToAgencyId values here, but they are not available to the code at this point
1050     staff_log( '', '',
1051         ( ( caller(0) )[3] . " -> " . $error_msg ) );
1052     die;
1053 }
1054
1055 sub do_lookup_user_error_stanza {
1056
1057     # XXX: we should include FromAgencyId and ToAgencyId values, but they are not available to the code at this point
1058     my $error = shift;
1059     my $hd    = <<LOOKUPPROB;
1060 Content-type: text/xml
1061
1062
1063 <!DOCTYPE NCIPMessage PUBLIC "-//NISO//NCIP DTD Version 1.0//EN" "http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1064 <NCIPMessage version="http://www.niso.org/ncip/v1_0/imp1/dtd/ncip_v1_0.dtd">
1065     <LookupUserResponse>
1066         <ResponseHeader>
1067             <FromAgencyId>
1068                 <UniqueAgencyId>
1069                     <Scheme></Scheme>
1070                     <Value></Value>
1071                 </UniqueAgencyId>
1072             </FromAgencyId>
1073             <ToAgencyId>
1074                 <UniqueAgencyId>
1075                     <Scheme></Scheme>
1076                     <Value></Value>
1077                 </UniqueAgencyId>
1078             </ToAgencyId>
1079         </ResponseHeader>
1080         <Problem>
1081             <ProcessingError>
1082                 <ProcessingErrorType>
1083                     <Scheme>http://www.niso.org/ncip/v1_0/schemes/processingerrortype/lookupuserprocessingerror.scm</Scheme>
1084                     <Value>$error</Value>
1085                 </ProcessingErrorType>
1086                 <ProcessingErrorElement>
1087                     <ElementName>AuthenticationInput</ElementName>
1088                 </ProcessingErrorElement>
1089             </ProcessingError>
1090         </Problem>
1091     </LookupUserResponse>
1092 </NCIPMessage>
1093
1094 LOOKUPPROB
1095
1096     logit( $hd, ( caller(0) )[3] );
1097     # XXX: we should log FromAgencyId and ToAgencyId values here, but they are not available to the code at this point
1098     staff_log( '', '', ( ( caller(0) )[3] . " -> " . $error ) );
1099     die;
1100 }
1101
1102 # Login to the OpenSRF system/Evergreen.
1103 #
1104 # Returns a hash with the authtoken, authtime, and expiration (time in
1105 # seconds since 1/1/1970).
1106 sub login {
1107
1108  # XXX: local opensrf core conf filename should be in config.
1109  # XXX: STAFF account with ncip service related permissions should be in config.
1110     my $bootstrap = '/openils/conf/opensrf_core.xml';
1111     my $uname     = $conf->{auth}->{username};
1112     my $password  = $conf->{auth}->{password};
1113
1114     # Bootstrap the client
1115     OpenSRF::System->bootstrap_client( config_file => $bootstrap );
1116     my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
1117     Fieldmapper->import( IDL => $idl );
1118
1119     # Initialize CStoreEditor:
1120     OpenILS::Utils::CStoreEditor->init;
1121
1122     my $seed = OpenSRF::AppSession->create('open-ils.auth')
1123       ->request( 'open-ils.auth.authenticate.init', $uname )->gather(1);
1124
1125     return undef unless $seed;
1126
1127     my $response = OpenSRF::AppSession->create('open-ils.auth')->request(
1128         'open-ils.auth.authenticate.complete',
1129         {
1130             username => $uname,
1131             password => md5_hex( $seed . md5_hex($password) ),
1132             type     => 'staff'
1133         }
1134     )->gather(1);
1135
1136     return undef unless $response;
1137
1138     my %result;
1139     $result{'authtoken'}  = $response->{payload}->{authtoken};
1140     $result{'authtime'}   = $response->{payload}->{authtime};
1141     $result{'expiration'} = time() + $result{'authtime'}
1142       if ( defined( $result{'authtime'} ) );
1143     return %result;
1144 }
1145
1146 # Check the time versus the session expiration time and login again if
1147 # the session has expired, consequently resetting the session
1148 # paramters. We want to run this before doing anything that requires
1149 # us to have a current session in OpenSRF.
1150 #
1151 # Arguments
1152 # none
1153 #
1154 # Returns
1155 # Nothing
1156 sub check_session_time {
1157     if ( time() > $session{'expiration'} ) {
1158         %session = login();
1159         if ( !%session ) {
1160             die("Failed to reinitialize the session after expiration.");
1161         }
1162     }
1163 }
1164
1165 # Retrieve the logged in user.
1166 #
1167 sub get_session {
1168     my $response =
1169       OpenSRF::AppSession->create('open-ils.auth')
1170       ->request( 'open-ils.auth.session.retrieve', $session{authtoken} )
1171       ->gather(1);
1172     return $response;
1173 }
1174
1175 # Logout/destroy the OpenSRF session
1176 #
1177 # Argument is
1178 # none
1179 #
1180 # Returns
1181 # Does not return anything
1182 sub logout {
1183     if ( time() < $session{'expiration'} ) {
1184         my $response =
1185           OpenSRF::AppSession->create('open-ils.auth')
1186           ->request( 'open-ils.auth.session.delete', $session{authtoken} )
1187           ->gather(1);
1188         if ($response) {
1189
1190             # strong.silent.success
1191             exit(0);
1192         } else {
1193             fail("Logout unsuccessful. Good-bye, anyway.");
1194         }
1195     }
1196 }
1197
1198 sub update_copy {
1199     check_session_time();
1200     my ( $copy, $status_id ) = @_;
1201     my $e = new_editor( authtoken => $session{authtoken} );
1202     return $e->event->{textcode} unless ( $e->checkauth );
1203     $e->xact_begin;
1204     $copy->status($status_id);
1205     return $e->event unless $e->update_asset_copy($copy);
1206     $e->commit;
1207     return 'SUCCESS';
1208 }
1209
1210 # my paranoia re barcode on shipped items using visid for unique value
1211 sub update_copy_shipped {
1212     check_session_time();
1213     my ( $copy, $status_id, $barcode ) = @_;
1214     my $e = new_editor( authtoken => $session{authtoken} );
1215     return $e->event->{textcode} unless ( $e->checkauth );
1216     $e->xact_begin;
1217     $copy->status($status_id);
1218     $copy->barcode($barcode);
1219     return $e->event unless $e->update_asset_copy($copy);
1220     $e->commit;
1221     return 'SUCCESS';
1222 }
1223
1224 # Delete a copy
1225 #
1226 # Argument
1227 # Fieldmapper asset.copy object
1228 #
1229 # Returns
1230 # "SUCCESS" on success
1231 # Event textcode if an error occurs
1232 sub delete_copy {
1233     check_session_time();
1234     my ($copy) = @_;
1235
1236     my $e = new_editor( authtoken => $session{authtoken} );
1237     return $e->event->{textcode} unless ( $e->checkauth );
1238
1239     # Get the calnumber
1240     my $vol = $e->retrieve_asset_call_number( $copy->call_number );
1241     return $e->event->{textcode} unless ($vol);
1242
1243     # Get the biblio.record_entry
1244     my $bre = $e->retrieve_biblio_record_entry( $vol->record );
1245     return $e->event->{textcode} unless ($bre);
1246
1247     # Delete everything in a transaction and rollback if anything fails.
1248     # TODO: I think there is a utility function which handles all this
1249     $e->xact_begin;
1250     my $r;    # To hold results of editor calls
1251     $r = $e->delete_asset_copy($copy);
1252     unless ($r) {
1253         my $lval = $e->event->{textcode};
1254         $e->rollback;
1255         return $lval;
1256     }
1257     my $list =
1258       $e->search_asset_copy( { call_number => $vol->id, deleted => 'f' } );
1259     unless (@$list) {
1260         $r = $e->delete_asset_call_number($vol);
1261         unless ($r) {
1262             my $lval = $e->event->{textcode};
1263             $e->rollback;
1264             return $lval;
1265         }
1266         $list = $e->search_asset_call_number( { record => $bre->id, deleted => 'f' } );
1267         unless (@$list) {
1268             $r = $e->delete_biblio_record_entry($bre);
1269             unless ($r) {
1270                 my $lval = $e->event->{textcode};
1271                 $e->rollback;
1272                 return $lval;
1273             }
1274         }
1275     }
1276     $e->commit;
1277     return 'SUCCESS';
1278 }
1279
1280 # Get asset.copy from asset.copy.barcode.
1281 # Arguments
1282 # copy barcode
1283 #
1284 # Returns
1285 # asset.copy fieldmaper object
1286 # or hash on error
1287 sub copy_from_barcode {
1288     check_session_time();
1289     my ($barcode) = @_;
1290     my $response =
1291       OpenSRF::AppSession->create('open-ils.search')
1292       ->request( 'open-ils.search.asset.copy.find_by_barcode', $barcode )
1293       ->gather(1);
1294     return $response;
1295 }
1296
1297 sub locid_from_barcode {
1298     my ($barcode) = @_;
1299     my $response =
1300       OpenSRF::AppSession->create('open-ils.search')
1301       ->request( 'open-ils.search.biblio.find_by_barcode', $barcode )
1302       ->gather(1);
1303     return $response->{ids}[0];
1304 }
1305
1306 sub bre_id_from_barcode {
1307     check_session_time();
1308     my ($barcode) = @_;
1309     my $response =
1310       OpenSRF::AppSession->create('open-ils.search')
1311       ->request( 'open-ils.search.bib_id.by_barcode', $barcode )
1312       ->gather(1);
1313     return $response;
1314 }
1315
1316 sub holds_for_bre {
1317     check_session_time();
1318     my ($bre_id) = @_;
1319     my $response =
1320       OpenSRF::AppSession->create('open-ils.circ')
1321       ->request( 'open-ils.circ.holds.retrieve_all_from_title', $session{authtoken}, $bre_id )
1322       ->gather(1);
1323     return $response;
1324
1325 }
1326
1327 # Convert a MARC::Record to XML for Evergreen
1328 #
1329 # Copied from Dyrcona's issa framework which copied
1330 # it from MVLC's Safari Load program which copied it
1331 # from some code in the Open-ILS example import scripts.
1332 #
1333 # Argument
1334 # A MARC::Record object
1335 #
1336 # Returns
1337 # String with XML for the MARC::Record as Evergreen likes it
1338 sub convert2marcxml {
1339     my $input = shift;
1340     ( my $xml = $input->as_xml_record() ) =~ s/\n//sog;
1341     $xml =~ s/^<\?xml.+\?\s*>//go;
1342     $xml =~ s/>\s+</></go;
1343     $xml =~ s/\p{Cc}//go;
1344     $xml = $U->entityize($xml);
1345     $xml =~ s/[\x00-\x1f]//go;
1346     return $xml;
1347 }
1348
1349 # Create a copy and marc record
1350 #
1351 # Arguments
1352 # title
1353 # call number
1354 # copy barcode
1355 #
1356 # Returns
1357 # bib id on succes
1358 # event textcode on failure
1359 sub create_copy {
1360     check_session_time();
1361     my ( $title, $callnumber, $barcode, $copy_status_id, $medium_type ) = @_;
1362
1363     my $e = new_editor( authtoken => $session{authtoken} );
1364     return $e->event->{textcode} unless ( $e->checkauth );
1365
1366     my $r = $e->allowed( [ 'CREATE_COPY', 'CREATE_MARC', 'CREATE_VOLUME' ] );
1367     if ( ref($r) eq 'HASH' ) {
1368         return $r->{textcode} . ' ' . $r->{ilsperm};
1369     }
1370
1371     # Check if the barcode exists in asset.copy and bail if it does.
1372     my $list = $e->search_asset_copy( { deleted => 'f', barcode => $barcode } );
1373     if (@$list) {
1374 # in the future, can we update it, if it exists and only if it is an INN-Reach status item ?
1375         $e->finish;
1376         fail( 'BARCODE_EXISTS ! Barcode : ' . $barcode );
1377         die;
1378     }
1379
1380     # Create MARC record
1381     my $record = MARC::Record->new();
1382     $record->encoding('UTF-8');
1383     $record->leader('00881nam a2200193 4500');
1384     my $datespec = strftime( "%Y%m%d%H%M%S.0", localtime );
1385     my @fields = ();
1386     push( @fields, MARC::Field->new( '005', $datespec ) );
1387     push( @fields, MARC::Field->new( '082', '0', '4', 'a' => $callnumber ) );
1388     push( @fields, MARC::Field->new( '245', '0', '0', 'a' => $title ) );
1389     $record->append_fields(@fields);
1390
1391     # Convert the record to XML
1392     my $xml = convert2marcxml($record);
1393
1394     my $bre =
1395       OpenSRF::AppSession->create('open-ils.cat')
1396       ->request( 'open-ils.cat.biblio.record.xml.import',
1397         $session{authtoken}, $xml, 'System Local', 1 )->gather(1);
1398     return $bre->{textcode} if ( ref($bre) eq 'HASH' );
1399
1400     # Create volume record
1401     my $vol =
1402       OpenSRF::AppSession->create('open-ils.cat')
1403       ->request( 'open-ils.cat.call_number.find_or_create', $session{authtoken}, $callnumber, $bre->id, $conf->{volume}->{owning_lib} )
1404       ->gather(1);
1405     return $vol->{textcode} if ( $vol->{textcode} );
1406
1407     # Retrieve the user
1408     my $user = get_session;
1409
1410     # Create copy record
1411     my $copy = Fieldmapper::asset::copy->new();
1412     # XXX CUSTOMIZATION NEEDED XXX
1413     # You will need to either create a circ mod for every expected medium type,
1414     # OR you should create a single circ mod for all requests from the external
1415     # system.
1416     # Adjust these lines as needed.
1417     #    $copy->circ_modifier(qq($medium_type)); # XXX CUSTOMIZATION NEEDED XXX
1418     # OR
1419     $copy->circ_modifier($conf->{copy}->{circ_modifier});
1420     $copy->barcode($barcode);
1421     $copy->call_number( $vol->{acn_id} );
1422     $copy->circ_lib($conf->{copy}->{circ_lib});
1423     $copy->circulate('t');
1424     $copy->holdable('t');
1425     $copy->opac_visible('t');
1426     $copy->deleted('f');
1427     $copy->fine_level(2);
1428     $copy->loan_duration(2);
1429     $copy->location($conf->{copy}->{location});
1430     $copy->status($copy_status_id);
1431     $copy->editor('1');
1432     $copy->creator('1');
1433
1434     $e->xact_begin;
1435     $copy = $e->create_asset_copy($copy);
1436
1437     $e->commit;
1438     return $e->event->{textcode} unless ($r);
1439     return 'SUCCESS';
1440 }
1441
1442 # Checkout a copy to a patron
1443 #
1444 # Arguments
1445 # copy barcode
1446 # patron barcode
1447 #
1448 # Returns
1449 # textcode of the OSRF response.
1450 sub checkout {
1451     check_session_time();
1452     my ( $copy_barcode, $patron_barcode, $due_date ) = @_;
1453
1454     # Check for copy:
1455     my $copy = copy_from_barcode($copy_barcode);
1456     unless ( defined($copy) && blessed($copy) ) {
1457         return 'COPY_BARCODE_NOT_FOUND : ' . $copy_barcode;
1458     }
1459
1460     # Check for user
1461     my $uid;
1462     if ($patron_id_type eq 'barcode') {
1463         $uid = user_id_from_barcode($patron_barcode);
1464     } else {
1465         $uid = $patron_barcode;
1466     }
1467     return 'PATRON_BARCODE_NOT_FOUND : ' . $patron_barcode if ( ref($uid) );
1468
1469     my $response = OpenSRF::AppSession->create('open-ils.circ')->request(
1470         'open-ils.circ.checkout.full.override',
1471         $session{authtoken},
1472         {
1473             copy_barcode => $copy_barcode,
1474             patron_id    => $uid,
1475             due_date     => $due_date
1476         }
1477     )->gather(1);
1478     return $response->{textcode};
1479 }
1480
1481 sub renewal {
1482     check_session_time();
1483     my ( $copy_barcode, $due_date ) = @_;
1484
1485     # Check for copy:
1486     my $copy = copy_from_barcode($copy_barcode);
1487     unless ( defined($copy) && blessed($copy) ) {
1488         return 'COPY_BARCODE_NOT_FOUND : ' . $copy_barcode;
1489     }
1490
1491     my $response = OpenSRF::AppSession->create('open-ils.circ')->request(
1492         'open-ils.circ.renew.override',
1493         $session{authtoken},
1494         {
1495             copy_barcode => $copy_barcode,
1496             due_date     => $due_date
1497         }
1498     )->gather(1);
1499     return $response->{textcode};
1500 }
1501
1502 # Check a copy in
1503 #
1504 # Arguments
1505 # copy barcode
1506 #
1507 # Returns
1508 # "SUCCESS" on success
1509 # textcode of a failed OSRF request
1510 # 'COPY_NOT_CHECKED_OUT' when the copy is not checked out
1511
1512 sub checkin {
1513     check_session_time();
1514     my ($barcode) = @_;
1515
1516     my $copy = copy_from_barcode($barcode);
1517     return $copy->{textcode} unless ( blessed $copy);
1518
1519     return ("COPY_NOT_CHECKED_OUT $barcode")
1520       unless ( $copy->status == OILS_COPY_STATUS_CHECKED_OUT );
1521
1522     my $e = new_editor( authtoken => $session{authtoken} );
1523     return $e->event->{textcode} unless ( $e->checkauth );
1524
1525     my $circ = $e->search_action_circulation(
1526         [ { target_copy => $copy->id, xact_finish => undef } ] )->[0];
1527     my $r =
1528       OpenSRF::AppSession->create('open-ils.circ')
1529       ->request( 'open-ils.circ.checkin.override',
1530         $session{authtoken}, { force => 1, copy_id => $copy->id } )->gather(1);
1531     return 'SUCCESS' if ( $r->{textcode} eq 'ROUTE_ITEM' );
1532     return $r->{textcode};
1533 }
1534
1535 # Check in an copy as part of accept_item
1536 # Intent is for the copy to be captured for
1537 # a hold -- the only hold that should be
1538 # present on the copy
1539
1540 sub checkin_accept {
1541     check_session_time();
1542     my $copy_id = shift;
1543     my $circ_lib = shift;
1544
1545     my $r = OpenSRF::AppSession->create('open-ils.circ')->request(
1546         'open-ils.circ.checkin.override',
1547         $session{authtoken}, { force => 1, copy_id => $copy_id, circ_lib => $circ_lib }
1548     )->gather(1);
1549
1550     return $r->{textcode};
1551 }
1552
1553 # Get actor.usr.id from barcode.
1554 # Arguments
1555 # patron barcode
1556 #
1557 # Returns
1558 # actor.usr.id
1559 # or hash on error
1560 sub user_id_from_barcode {
1561     check_session_time();
1562     my ($barcode) = @_;
1563
1564     my $response;
1565
1566     my $e = new_editor( authtoken => $session{authtoken} );
1567     return $response unless ( $e->checkauth );
1568
1569     my $card = $e->search_actor_card( { barcode => $barcode, active => 't' } );
1570     return $e->event unless ($card);
1571
1572     $response = $card->[0]->usr if (@$card);
1573
1574     $e->finish;
1575
1576     return $response;
1577 }
1578
1579 # Place a simple hold for a patron.
1580 #
1581 # Arguments
1582 # Target object appropriate for type of hold
1583 # Patron for whom the hold is place
1584 #
1585 # Returns
1586 # "SUCCESS" on success
1587 # textcode of a failed OSRF request
1588 # "HOLD_TYPE_NOT_SUPPORTED" if the hold type is not supported
1589 # (Currently only support 'T' and 'C')
1590
1591 # simple hold should be removed and full holds sub should be used instead - pragmatic solution only
1592
1593 sub place_simple_hold {
1594     check_session_time();
1595
1596     #my ($type, $target, $patron, $pickup_ou) = @_;
1597     my ( $target, $patron_id ) = @_;
1598
1599     require $conf->{path}->{oils_header};
1600     use vars qw/ $apputils $memcache $user $authtoken $authtime /;
1601
1602     osrf_connect( $conf->{path}->{opensrf_core} );
1603     oils_login( $conf->{auth}->{username}, $conf->{auth}->{password} );
1604     my $ahr = Fieldmapper::action::hold_request->new();
1605     $ahr->hold_type('C');
1606     # 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.
1607     $ahr->target($target);
1608     $ahr->usr($patron_id);
1609     $ahr->requestor($conf->{hold}->{requestor});
1610     # NOTE: When User Agency, we don't know the pickup location until ItemShipped time
1611     # TODO: When Item Agency and using holds, set this to requested copy's circ lib?
1612     $ahr->pickup_lib($conf->{hold}->{init_pickup_lib});
1613     $ahr->phone_notify(''); # TODO: set this based on usr prefs
1614     $ahr->email_notify(1); # TODO: set this based on usr prefs
1615     $ahr->frozen('t');
1616     my $resp = simplereq( CIRC(), 'open-ils.circ.holds.create', $authtoken, $ahr );
1617     my $e = new_editor( xact => 1, authtoken => $session{authtoken} );
1618     $ahr = $e->retrieve_action_hold_request($resp);    # refresh from db
1619     $ahr->frozen('f');
1620     $e->update_action_hold_request($ahr);
1621     $e->commit;
1622     $U->storagereq( 'open-ils.storage.action.hold_request.copy_targeter', undef, $ahr->id );
1623
1624     #oils_event_die($resp);
1625     my $errors = "";
1626     if ( ref($resp) eq 'ARRAY' ) {
1627         ( $errors .= "error : " . $_->{textcode} ) for @$resp;
1628         return $errors;
1629     } elsif ( ref($resp) ne 'HASH' ) {
1630         return "Hold placed! hold_id = " . $resp . "\n";
1631     }
1632 }
1633
1634 sub find_hold_on_copy {
1635     check_session_time();
1636
1637     my ( $copy_barcode ) = @_;
1638
1639     # start with barcode of item, find bib ID
1640     my $rec = bre_id_from_barcode($copy_barcode);
1641
1642     return undef unless $rec;
1643
1644     # call for holds on that bib
1645     my $holds = holds_for_bre($rec);
1646
1647     # There should only be a single copy hold
1648     my $hold_id = @{$holds->{copy_holds}}[0];
1649
1650     return undef unless $hold_id;
1651
1652     my $hold_details =
1653       OpenSRF::AppSession->create('open-ils.circ')
1654       ->request( 'open-ils.circ.hold.details.retrieve', $session{authtoken}, $hold_id )
1655       ->gather(1);
1656
1657     my $hold = $hold_details->{hold};
1658
1659     return undef unless blessed($hold);
1660
1661     return $hold;
1662 }
1663
1664 sub update_hold_pickup {
1665     check_session_time();
1666
1667     my ( $copy_barcode, $pickup_lib ) = @_;
1668
1669     my $hold = find_hold_on_copy($copy_barcode);
1670
1671     $hold->pickup_lib($pickup_lib);
1672
1673     # update the copy hold with the new pickup lib information
1674     my $result =
1675       OpenSRF::AppSession->create('open-ils.circ')
1676       ->request( 'open-ils.circ.hold.update', $session{authtoken}, $hold )
1677       ->gather(1);
1678
1679     return $result;
1680 }
1681
1682 # Flesh user information
1683 # Arguments
1684 # actor.usr.id
1685 #
1686 # Returns
1687 # fieldmapped, fleshed user or
1688 # event hash on error
1689 sub flesh_user {
1690     check_session_time();
1691     my ($id) = @_;
1692     my $response =
1693       OpenSRF::AppSession->create('open-ils.actor')
1694       ->request( 'open-ils.actor.user.fleshed.retrieve',
1695         $session{'authtoken'}, $id,
1696         [ 'card', 'cards', 'standing_penalties', 'home_ou', 'profile' ] )
1697       ->gather(1);
1698     return $response;
1699 }