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