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