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