1 package OpenILS::SIP::Item;
2 use strict; use warnings;
4 use Sys::Syslog qw(syslog);
8 use OpenILS::SIP::Transaction;
9 use OpenILS::Application::AppUtils;
10 use OpenILS::Application::Circ::ScriptBuilder;
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';
21 # 1 means read/write Actually, gloves are off. Set what you like.
25 # sip_media_type => 0,
26 sip_item_properties => 0,
27 # magnetic_media => 0,
28 permanent_location => 0,
29 current_location => 0,
35 hold_patron_bcode => 0,
36 hold_patron_name => 0,
53 sub DESTROY { } # keeps AUTOLOAD from catching inherent DESTROY calls
57 my $class = ref($self) or croak "$self is not an object";
62 unless (exists $fields{$name}) {
63 croak "Cannot access '$name' field of class '$class'";
67 # $fields{$name} or croak "Field '$name' of class '$class' is READ ONLY."; # nah, go ahead
68 return $self->{$name} = shift;
70 return $self->{$name};
76 my ($class, $item_id) = @_;
77 my $type = ref($class) || $class;
78 my $self = bless( {}, $type );
80 syslog('LOG_DEBUG', "OILS: Loading item $item_id...");
81 return undef unless $item_id;
83 my $e = OpenILS::SIP->editor();
85 my $copy = $e->search_asset_copy(
87 { barcode => $item_id, deleted => 'f' },
91 acp => [ 'circ_lib', 'call_number', 'status', 'stat_cat_entry_copy_maps' ],
92 acn => [ 'owning_lib', 'record' ],
93 ascecm => [ 'stat_cat', 'stat_cat_entry' ],
100 syslog("LOG_DEBUG", "OILS: Item '%s' : not found", $item_id);
104 my $circ = $e->search_action_circulation([
106 target_copy => $copy->id,
107 stop_fines_time => undef,
108 checkin_time => undef
121 my $user = $circ->usr;
122 my $bc = ($user->card) ? $user->card->barcode : '';
123 $self->{patron} = $bc;
124 $self->{patron_object} = $user;
126 syslog('LOG_DEBUG', "OILS: Open circulation exists on $item_id : user = $bc");
129 $self->{id} = $item_id;
130 $self->{copy} = $copy;
131 $self->{volume} = $copy->call_number;
132 $self->{record} = $copy->call_number->record;
133 $self->{call_number} = $copy->call_number->label;
134 $self->{mods} = $U->record_to_mvr($self->{record}) if $self->{record}->marc;
135 $self->{transit} = $self->fetch_transit;
136 $self->{hold} = $self->fetch_hold;
139 # use the non-translated version of the copy location as the
140 # collection code, since it may be used for additional routing
141 # purposes by the SIP client. Config option?
142 $self->{collection_code} =
143 $e->retrieve_asset_copy_location([
144 $copy->location, {no_i18n => 1}])->name;
147 if($self->{transit}) {
148 $self->{destination_loc} = $self->{transit}->dest->shortname;
150 } elsif($self->{hold}) {
151 $self->{destination_loc} = $self->{hold}->pickup_lib->shortname;
154 syslog("LOG_DEBUG", "OILS: Item('$item_id'): found with title '%s'", $self->title_id);
156 my $config = OpenILS::SIP->config(); # FIXME : will not always match!
157 my $legacy = $config->{implementation_config}->{legacy_script_support} || undef;
159 if( defined $legacy ) {
160 $self->{legacy_script_support} = ($legacy =~ /t(rue)?/io) ? 1 : 0;
161 syslog("LOG_DEBUG", "legacy_script_support is set in SIP config: " . $self->{legacy_script_support});
164 my $lss = OpenSRF::Utils::SettingsClient->new->config_value(
165 apps => 'open-ils.circ',
166 app_settings => 'legacy_script_support'
168 $self->{legacy_script_support} = ($lss =~ /t(rue)?/io) ? 1 : 0;
169 syslog("LOG_DEBUG", "legacy_script_support is set in SRF config: " . $self->{legacy_script_support});
178 my $copy = $self->{copy} or return;
179 my $e = OpenILS::SIP->editor();
181 if ($copy->status->id == OILS_COPY_STATUS_IN_TRANSIT) {
182 my $transit = $e->search_action_transit_copy([
184 target_copy => $copy->id, # NOT barcode ($self->id)
185 dest_recv_time => undef
195 syslog('LOG_WARNING', "OILS: Item(".$copy->barcode.
196 ") status is In Transit, but no action.transit_copy found!") unless $transit;
204 # fetch captured hold.
205 # Assume transit has already beeen fetched
208 my $copy = $self->{copy} or return;
209 my $e = OpenILS::SIP->editor();
211 if( ($copy->status->id == OILS_COPY_STATUS_ON_HOLDS_SHELF) ||
212 ($self->{transit} and $self->{transit}->copy_status == OILS_COPY_STATUS_ON_HOLDS_SHELF) ) {
213 # item has been captured for a hold
215 my $hold = $e->search_action_hold_request([
217 current_copy => $copy->id,
218 capture_time => {'!=' => undef},
219 cancel_time => undef,
220 fulfillment_time => undef
226 ahr => ['pickup_lib']
231 syslog('LOG_WARNING', "OILS: Item(".$copy->barcode.
232 ") is captured for a hold, but there is no matching hold request") unless $hold;
240 sub run_attr_script {
242 return 1 if $self->{ran_script};
243 $self->{ran_script} = 1;
245 if($self->{legacy_script_support}){
247 syslog('LOG_DEBUG', "Legacy script support is ON");
248 my $config = OpenILS::SIP->config();
249 my $path = $config->{implementation_config}->{scripts}->{path};
250 my $item_config_script = $config->{implementation_config}->{scripts}->{item_config};
252 $path = ref($path) eq 'ARRAY' ? $path : [$path];
253 my $path_str = join(", ", @$path);
255 syslog('LOG_DEBUG', "OILS: Script path = [$path_str], Item config script = $item_config_script");
257 my $runner = OpenILS::Application::Circ::ScriptBuilder->build({
258 copy => $self->{copy},
259 editor => OpenILS::SIP->editor(),
262 $runner->add_path($_) for @$path;
263 $runner->load($item_config_script);
265 unless( $self->{item_config_result} = $runner->run ) { # assignment, not comparison
267 warn "Item config script [$path_str : $item_config_script] failed to run: $@\n";
268 syslog('LOG_ERR', "OILS: Item config script [$path_str : $item_config_script] failed to run: $@");
276 # use the in-db circ modifier configuration
277 my $config = {magneticMedia => 'f', SIPMediaType => '001'}; # defaults
278 my $mod = $self->{copy}->circ_modifier;
281 my $mod_obj = OpenILS::SIP->editor()->retrieve_config_circ_modifier($mod);
283 $config->{magneticMedia} = $mod_obj->magnetic_media;
284 $config->{SIPMediaType} = $mod_obj->sip2_media_type;
288 $self->{item_config_result} = { item_config => $config };
300 return 0 unless $self->run_attr_script;
301 my $mag = $self->{item_config_result}->{item_config}->{magneticMedia} || '';
302 syslog('LOG_DEBUG', "OILS: magnetic = $mag");
303 return ($mag and $mag =~ /t(rue)?/io) ? 1 : 0;
308 return 0 unless $self->run_attr_script;
309 my $media = $self->{item_config_result}->{item_config}->{SIPMediaType} || '';
310 syslog('LOG_DEBUG', "OILS: media type = $media");
311 return ($media) ? $media : '001';
316 my $t = ($self->{mods}) ? $self->{mods}->title : $self->{copy}->dummy_title;
317 return OpenILS::SIP::clean_text($t);
320 sub permanent_location {
322 return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
325 sub current_location {
327 return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
336 # 05 Charged; not to be recalled until earliest recall date
339 # 08 Waiting on hold shelf
340 # 09 Waiting to be re-shelved
341 # 10 In transit between library locations
342 # 11 Claimed returned
345 sub sip_circulation_status {
347 my $stat = $self->{copy}->status->id;
349 return '02' if $stat == OILS_COPY_STATUS_ON_ORDER;
350 return '03' if $stat == OILS_COPY_STATUS_AVAILABLE;
351 return '04' if $stat == OILS_COPY_STATUS_CHECKED_OUT;
352 return '06' if $stat == OILS_COPY_STATUS_IN_PROCESS;
353 return '08' if $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF;
354 return '09' if $stat == OILS_COPY_STATUS_RESHELVING;
355 return '10' if $stat == OILS_COPY_STATUS_IN_TRANSIT;
356 return '12' if $stat == OILS_COPY_STATUS_LOST;
357 return '13' if $stat == OILS_COPY_STATUS_MISSING;
362 sub sip_security_marker {
363 return '02'; # FIXME? 00-other; 01-None; 02-Tattle-Tape Security Strip (3M); 03-Whisper Tape (3M)
367 return '01'; # FIXME? 01-09 enumerated in spec. We just use O1-other/unknown.
378 return OpenILS::SIP->config()->{implementation_config}->{currency};
383 return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
391 sub hold_queue_position { # TODO
392 my ($self, $patron_id) = @_;
399 # this should force correct circ fetching
400 require OpenILS::Utils::CStoreEditor;
401 my $e = OpenILS::Utils::CStoreEditor->new(xact => 1);
402 #my $e = OpenILS::SIP->editor();
404 my $circ = $e->search_action_circulation(
405 { target_copy => $self->{copy}->id, checkin_time => undef } )->[0];
410 syslog('LOG_INFO', "OILS: No open circ found for copy");
414 my $due = OpenILS::SIP->format_date($circ->due_date, 'due');
415 syslog('LOG_DEBUG', "OILS: Found item due date = $due");
419 sub recall_date { # TODO
425 # Note: If the held item is in transit, this will be an approximation of shelf
426 # expire time, since the time is not set until the item is checked in at the pickup location
427 my %shelf_expire_setting_cache;
428 sub hold_pickup_date {
430 my $copy = $self->{copy};
431 my $hold = $self->{hold} or return 0;
433 my $date = $hold->shelf_expire_time;
436 # hold has not hit the shelf. create a best guess.
438 my $interval = $shelf_expire_setting_cache{$hold->pickup_lib->id} ||
439 $U->ou_ancestor_setting_value(
440 $hold->pickup_lib->id,
441 'circ.holds.default_shelf_expire_interval');
443 $shelf_expire_setting_cache{$hold->pickup_lib->id} = $interval;
446 my $seconds = OpenSRF::Utils->interval_to_seconds($interval);
447 $date = DateTime->now->add(seconds => $seconds);
448 $date = $date->strftime('%FT%T%z') if $date;
452 return OpenILS::SIP->format_date($date) if $date;
457 # message to display on console
460 return OpenILS::SIP::clean_text($self->{screen_msg}) || '';
467 return OpenILS::SIP::clean_text($self->{print_line}) || '';
471 # An item is available for a patron if
472 # 1) It's not checked out and (there's no hold queue OR patron
473 # is at the front of the queue)
475 # 2) It's checked out to the patron and there's no hold queue
477 my ($self, $for_patron) = @_;
479 my $stat = $self->{copy}->status->id;
481 $stat == OILS_COPY_STATUS_AVAILABLE or
482 $stat == OILS_COPY_STATUS_RESHELVING;
489 my $extra_fields = {};
490 my $c = $self->{copy};
491 foreach my $stat_cat_entry (@{$c->stat_cat_entry_copy_maps}) {
492 my $stat_cat = $stat_cat_entry->stat_cat;
493 next unless ($stat_cat->sip_field);
494 my $value = $stat_cat_entry->stat_cat_entry->value;
495 if(defined $stat_cat->sip_format && length($stat_cat->sip_format) > 0) { # Has a format string?
496 if($stat_cat->sip_format =~ /^\|(.*)\|$/) { # Regex match?
497 if($value =~ /($1)/) { # If we have a match
498 if(defined $2) { # Check to see if they embedded a capture group
499 $value = $2; # If so, use it
501 else { # No embedded capture group?
502 $value = $1; # Use our outer one
506 $value = ''; # Empty string. Will be checked for below.
509 else { # Not a regex match - Try sprintf match (looking for a %s, if any)
510 $value = sprintf($stat_cat->sip_format, $value);
513 next unless length($value) > 0; # No value = no export
514 $value =~ s/\|//g; # Remove all lingering pipe chars for sane output purposes
515 $extra_fields->{ $stat_cat->sip_field } = [] unless (defined $extra_fields->{$stat_cat->sip_field});
516 push(@{$extra_fields->{ $stat_cat->sip_field}}, $value);
518 return $extra_fields;
527 OpenILS::SIP::Item - SIP abstraction layer for OpenILS Items.
531 =head2 owning_lib vs. circ_lib
533 In Evergreen, owning_lib is the org unit that purchased the item, the place to which the item
534 should return after it's done rotating/floating to other branches (via staff intervention),
535 or some combination of those. The owning_lib, however, is not necessarily where the item
536 should be going "right now" or where it should return to by default. That would be the copy
537 circ_lib or the transit destination. (In fact, the item may B<never> go to the owning_lib for
538 its entire existence). In the context of SIP, the circ_lib more accurately describes the item's
539 permanent location, i.e. where it needs to be sent if it's not en route to somewhere else.
541 This confusion extends also to the SIP extension field of "owner". It means that the SIP owner does not
542 correspond to EG's asset.volume.owning_lib, mainly because owning_lib is effectively the "ultimate
543 owner" but not necessarily the "current owner". Because we populate SIP fields with circ_lib, the
544 owning_lib is unused by SIP.