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