]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/SIP/Item.pm
SIP Callnumber extensions, POD
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / SIP / Item.pm
1 package OpenILS::SIP::Item;
2 use strict; use warnings;
3
4 use Sys::Syslog qw(syslog);
5 use Carp;
6
7 use OpenILS::SIP;
8 use OpenILS::SIP::Transaction;
9 use OpenILS::Application::AppUtils;
10 use OpenILS::Application::Circ::ScriptBuilder;
11 # use Data::Dumper;
12 use OpenILS::Const qw/:const/;
13 use OpenSRF::Utils qw/:datetime/;
14 use DateTime::Format::ISO8601;
15 use OpenSRF::Utils::SettingsClient;
16 my $U = 'OpenILS::Application::AppUtils';
17
18 my %item_db;
19
20 # 0 means read-only
21 # 1 means read/write    Actually, gloves are off.  Set what you like.
22
23 my %fields = (
24     id => 0,
25 #   sip_media_type     => 0,
26     sip_item_properties => 0,
27 #   magnetic_media     => 0,
28     permanent_location => 0,
29     current_location   => 0,
30 #   print_line         => 1,
31 #   screen_msg         => 1,
32 #   itemnumber         => 0,
33 #   biblionumber       => 0,
34     hold               => 0,
35     hold_patron_bcode  => 0,
36     hold_patron_name   => 0,
37     barcode            => 0,
38     onloan             => 0,
39     collection_code    => 0,
40     destination_loc    => 0,
41     call_number        => 0,
42     enumchron          => 0,
43     location           => 0,
44     author             => 0,
45     title              => 0,
46     copy               => 0,
47     volume             => 0,
48     record             => 0,
49     mods               => 0,
50 );
51
52 our $AUTOLOAD;
53 sub DESTROY { } # keeps AUTOLOAD from catching inherent DESTROY calls
54
55 sub AUTOLOAD {
56     my $self = shift;
57     my $class = ref($self) or croak "$self is not an object";
58     my $name = $AUTOLOAD;
59
60     $name =~ s/.*://;
61
62     unless (exists $fields{$name}) {
63         croak "Cannot access '$name' field of class '$class'";
64     }
65
66     if (@_) {
67         # $fields{$name} or croak "Field '$name' of class '$class' is READ ONLY.";  # nah, go ahead
68         return $self->{$name} = shift;
69     } else {
70         return $self->{$name};
71     }
72 }
73
74
75 sub new {
76     my ($class, $item_id) = @_;
77     my $type = ref($class) || $class;
78     my $self = bless( {}, $type );
79
80     syslog('LOG_DEBUG', "OILS: Loading item $item_id...");
81     return undef unless $item_id;
82
83     my $e = OpenILS::SIP->editor();
84
85     my $copy = $e->search_asset_copy(
86                 [
87                         { barcode => $item_id, deleted => 'f' },
88                         {
89                                 flesh => 3,
90                                 flesh_fields => {
91                                         acp => [ 'circ_lib', 'call_number', 'status' ],
92                                         acn => [ 'owning_lib', 'record' ],
93                                 }
94                         }
95                 ]
96     );
97
98
99     $copy = $copy->[0];
100
101         if(!$copy) {
102                 syslog("LOG_DEBUG", "OILS: Item '%s' : not found", $item_id);
103                 return undef;
104         }
105
106         my ($circ) = $U->fetch_open_circulation($copy->id);
107         if ($circ) {
108                 # if i am checked out, set $self->{patron} to the user's barcode
109                 my $user = $e->retrieve_actor_user(
110                         [
111                                 $circ->usr,
112                                 { flesh => 1, flesh_fields => { "au" => [ 'card' ] } }
113                         ]
114                 );
115
116         my $bc = ($user) ? $user->card->barcode : "";
117         $self->{patron} = $bc;
118         $self->{patron_object} = $user;
119
120         syslog('LOG_DEBUG', "OILS: Open circulation exists on $item_id : user = $bc");
121     }
122
123     $self->{id} = $item_id;
124     $self->{copy}        = $copy;
125     $self->{volume}      = $copy->call_number;
126     $self->{record}      = $copy->call_number->record;
127     $self->{call_number} = $copy->call_number->label;
128     $self->{mods} = $U->record_to_mvr($self->{record}) if $self->{record}->marc;
129
130     if ($copy->status->id == OILS_COPY_STATUS_IN_TRANSIT) {
131         my $transit = $e->search_action_transit_copy([
132             {
133                 target_copy    => $copy->id,    # NOT barcode ($self->id)
134                 dest_recv_time => undef
135             },
136             {
137                 flesh => 1,
138                 flesh_fields => {
139                     atc => [ 'dest' ]
140                 }
141             }
142         ]);
143         # warn "Item transit: " . Dumper($transit) . "\nItem transit->dest: " . Dumper($transit->dest);;
144         if ($transit) {
145             $self->{destination_loc} = $transit->[0]->dest->shortname;
146         } else {
147             syslog('LOG_WARNING', "OILS: Item('$item_id') status is In Transit, but no action.transit_copy found!");
148         }
149     }
150
151     syslog("LOG_DEBUG", "OILS: Item('$item_id'): found with title '%s'", $self->title_id);
152
153     my $config = OpenILS::SIP->config();    # FIXME : will not always match!
154
155     my $legacy = $config->{implementation_config}->{legacy_script_support} || undef;
156     if( defined $legacy ) {
157         $self->{legacy_script_support} = ($legacy =~ /t(rue)?/io) ? 1 : 0;
158         syslog("LOG_DEBUG", "legacy_script_support is set in SIP config: " . $self->{legacy_script_support});
159     } else {
160         my $lss = OpenSRF::Utils::SettingsClient->new->config_value(
161             apps         => 'open-ils.circ',
162             app_settings => 'legacy_script_support'
163         );
164         $self->{legacy_script_support} = ($lss =~ /t(rue)?/io) ? 1 : 0;
165         syslog("LOG_DEBUG", "legacy_script_support is set in SRF config: " . $self->{legacy_script_support});
166     }
167
168     return $self;
169 }
170
171 sub run_attr_script {
172         my $self = shift;
173         return 1 if $self->{ran_script};
174         $self->{ran_script} = 1;
175
176     if($self->{legacy_script_support}){
177
178         syslog('LOG_DEBUG', "Legacy script support is ON");
179         my $config = OpenILS::SIP->config();
180         my $path               = $config->{implementation_config}->{scripts}->{path};
181         my $item_config_script = $config->{implementation_config}->{scripts}->{item_config};
182
183         $path = ref($path) eq 'ARRAY' ? $path : [$path];
184         my $path_str = join(", ", @$path);
185
186         syslog('LOG_DEBUG', "OILS: Script path = [$path_str], Item config script = $item_config_script");
187
188         my $runner = OpenILS::Application::Circ::ScriptBuilder->build({
189             copy   => $self->{copy},
190             editor => OpenILS::SIP->editor(),
191         });
192
193         $runner->add_path($_) for @$path;
194         $runner->load($item_config_script);
195
196         unless( $self->{item_config_result} = $runner->run ) {      # assignment, not comparison
197             $runner->cleanup;
198             warn "Item config script [$path_str : $item_config_script] failed to run: $@\n";
199             syslog('LOG_ERR', "OILS: Item config script [$path_str : $item_config_script] failed to run: $@");
200             return undef;
201         }
202
203         $runner->cleanup;
204
205     } else {
206
207         # use the in-db circ modifier configuration 
208         my $config = {magneticMedia => 'f', SIPMediaType => '001'};     # defaults
209         my $mod = $self->{copy}->circ_modifier;
210
211         if($mod) {
212             my $mod_obj = OpenILS::SIP->editor()->search_config_circ_modifier($mod);
213             if($mod_obj) {
214                 $config->{magneticMedia} = $mod_obj->magnetic_media;
215                 $config->{SIPMediaType}  = $mod_obj->sip2_media_type;
216             }
217         }
218
219         $self->{item_config_result} = { item_config => $config };
220     }
221
222         return 1;
223 }
224
225 sub magnetic_media {
226     my $self = shift;
227     $self->magnetic(@_);
228 }
229 sub magnetic {
230     my $self = shift;
231     return 0 unless $self->run_attr_script;
232     my $mag = $self->{item_config_result}->{item_config}->{magneticMedia} || '';
233     syslog('LOG_DEBUG', "OILS: magnetic = $mag");
234     return ($mag and $mag =~ /t(rue)?/io) ? 1 : 0;
235 }
236
237 sub sip_media_type {
238     my $self = shift;
239     return 0 unless $self->run_attr_script;
240     my $media = $self->{item_config_result}->{item_config}->{SIPMediaType} || '';
241     syslog('LOG_DEBUG', "OILS: media type = $media");
242     return ($media) ? $media : '001';
243 }
244
245 sub title_id {
246     my $self = shift;
247     my $t =  ($self->{mods}) ? $self->{mods}->title : $self->{copy}->dummy_title;
248     return OpenILS::SIP::clean_text($t);
249 }
250
251 sub permanent_location {
252     my $self = shift;
253     return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
254 }
255
256 sub current_location {
257     my $self = shift;
258     return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
259 }
260
261
262 # 2 chars 0-99 
263 # 01 Other
264 # 02 On order
265 # 03 Available
266 # 04 Charged
267 # 05 Charged; not to be recalled until earliest recall date
268 # 06 In process
269 # 07 Recalled
270 # 08 Waiting on hold shelf
271 # 09 Waiting to be re-shelved
272 # 10 In transit between library locations
273 # 11 Claimed returned
274 # 12 Lost
275 # 13 Missing 
276 sub sip_circulation_status {
277     my $self = shift;
278     my $stat = $self->{copy}->status->id;
279
280     return '02' if $stat == OILS_COPY_STATUS_ON_ORDER;
281     return '03' if $stat == OILS_COPY_STATUS_AVAILABLE;
282     return '04' if $stat == OILS_COPY_STATUS_CHECKED_OUT;
283     return '06' if $stat == OILS_COPY_STATUS_IN_PROCESS;
284     return '08' if $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF;
285     return '09' if $stat == OILS_COPY_STATUS_RESHELVING;
286     return '10' if $stat == OILS_COPY_STATUS_IN_TRANSIT;
287     return '12' if $stat == OILS_COPY_STATUS_LOST;
288     return '13' if $stat == OILS_COPY_STATUS_MISSING;
289         
290     return 01;
291 }
292
293 sub sip_security_marker {
294     return '02';    # FIXME? 00-other; 01-None; 02-Tattle-Tape Security Strip (3M); 03-Whisper Tape (3M)
295 }
296
297 sub sip_fee_type {
298     return '01';    # FIXME? 01-09 enumerated in spec.  We just use O1-other/unknown.
299 }
300
301 sub fee {           # TODO
302     my $self = shift;
303     return 0;
304 }
305
306
307 sub fee_currency {
308         my $self = shift;
309         return OpenILS::SIP->config()->{implementation_config}->{currency};
310 }
311
312 sub owner {
313     my $self = shift;
314     return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
315 }
316
317 sub hold_queue {
318     my $self = shift;
319     return [];
320 }
321
322 sub hold_queue_position {       # TODO
323     my ($self, $patron_id) = @_;
324     return 1;
325 }
326
327 sub due_date {
328     my $self = shift;
329
330     # this should force correct circ fetching
331     require OpenILS::Utils::CStoreEditor;
332     my $e = OpenILS::Utils::CStoreEditor->new(xact => 1);
333     #my $e = OpenILS::SIP->editor();
334
335     my $circ = $e->search_action_circulation(
336         { target_copy => $self->{copy}->id, checkin_time => undef } )->[0];
337
338     $e->rollback;
339
340     if( !$circ ) {
341         syslog('LOG_INFO', "OILS: No open circ found for copy");
342         return 0;
343     }
344
345     my $due = OpenILS::SIP->format_date($circ->due_date, 'due');
346     syslog('LOG_DEBUG', "OILS: Found item due date = $due");
347     return $due;
348 }
349
350 sub recall_date {       # TODO
351     my $self = shift;
352     return 0;
353 }
354
355 sub hold_pickup_date {  # TODO
356     my $self = shift;
357     return 0;
358 }
359
360 # message to display on console
361 sub screen_msg {
362     my $self = shift;
363     return OpenILS::SIP::clean_text($self->{screen_msg}) || '';
364 }
365
366
367 # reciept printer
368 sub print_line {
369     my $self = shift;
370     return OpenILS::SIP::clean_text($self->{print_line}) || '';
371 }
372
373
374 # An item is available for a patron if
375 # 1) It's not checked out and (there's no hold queue OR patron
376 #    is at the front of the queue)
377 # OR
378 # 2) It's checked out to the patron and there's no hold queue
379 sub available {
380     my ($self, $for_patron) = @_;
381
382     my $stat = $self->{copy}->status->id;
383     return 1 if 
384         $stat == OILS_COPY_STATUS_AVAILABLE or
385         $stat == OILS_COPY_STATUS_RESHELVING;
386
387     return 0;
388 }
389
390
391 1;
392 __END__
393
394 =head1 NAME
395
396 OpenILS::SIP::Item - SIP abstraction layer for OpenILS Items.
397
398 =head1 DESCRIPTION
399
400 =head2 owning_lib vs. circ_lib
401
402 In Evergreen, owning_lib is the org unit that purchased the item, the place to which the item 
403 should return after it's done rotating/floating to other branches (via staff intervention),
404 or some combination of those.  The owning_lib, however, is not necessarily where the item
405 should be going "right now" or where it should return to by default.  That would be the copy
406 circ_lib or the transit destination.  (In fact, the item may B<never> go to the owning_lib for
407 its entire existence).  In the context of SIP, the circ_lib more accurately describes the item's
408 permanent location, i.e. where it needs to be sent if it's not en route to somewhere else.
409
410 This confusion extends also to the SIP extension field of "owner".  It means that the SIP owner does not 
411 correspond to EG's asset.volume.owning_lib, mainly because owning_lib is effectively the "ultimate
412 owner" but not necessarily the "current owner".  Because we populate SIP fields with circ_lib, the
413 owning_lib is unused by SIP.  
414
415 =head1 TODO
416
417 Holds queue logic
418
419 =cut