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' ],
92 acn => [ 'owning_lib', 'record' ],
99 syslog("LOG_DEBUG", "OILS: Item '%s' : not found", $item_id);
103 my $circ = $e->search_action_circulation([
105 target_copy => $copy->id,
106 stop_fines_time => undef,
107 checkin_time => undef
120 my $user = $circ->usr;
121 my $bc = ($user->card) ? $user->card->barcode : '';
122 $self->{patron} = $bc;
123 $self->{patron_object} = $user;
125 syslog('LOG_DEBUG', "OILS: Open circulation exists on $item_id : user = $bc");
128 $self->{id} = $item_id;
129 $self->{copy} = $copy;
130 $self->{volume} = $copy->call_number;
131 $self->{record} = $copy->call_number->record;
132 $self->{call_number} = $copy->call_number->label;
133 $self->{mods} = $U->record_to_mvr($self->{record}) if $self->{record}->marc;
134 $self->{transit} = $self->fetch_transit;
135 $self->{hold} = $self->fetch_hold;
138 # use the non-translated version of the copy location as the
139 # collection code, since it may be used for additional routing
140 # purposes by the SIP client. Config option?
141 $self->{collection_code} =
142 $e->retrieve_asset_copy_location([
143 $copy->location, {no_i18n => 1}])->name;
146 if($self->{transit}) {
147 $self->{destination_loc} = $self->{transit}->dest->shortname;
149 } elsif($self->{hold}) {
150 $self->{destination_loc} = $self->{hold}->pickup_lib->shortname;
153 syslog("LOG_DEBUG", "OILS: Item('$item_id'): found with title '%s'", $self->title_id);
155 my $config = OpenILS::SIP->config(); # FIXME : will not always match!
156 my $legacy = $config->{implementation_config}->{legacy_script_support} || undef;
158 if( defined $legacy ) {
159 $self->{legacy_script_support} = ($legacy =~ /t(rue)?/io) ? 1 : 0;
160 syslog("LOG_DEBUG", "legacy_script_support is set in SIP config: " . $self->{legacy_script_support});
163 my $lss = OpenSRF::Utils::SettingsClient->new->config_value(
164 apps => 'open-ils.circ',
165 app_settings => 'legacy_script_support'
167 $self->{legacy_script_support} = ($lss =~ /t(rue)?/io) ? 1 : 0;
168 syslog("LOG_DEBUG", "legacy_script_support is set in SRF config: " . $self->{legacy_script_support});
177 my $copy = $self->{copy} or return;
178 my $e = OpenILS::SIP->editor();
180 if ($copy->status->id == OILS_COPY_STATUS_IN_TRANSIT) {
181 my $transit = $e->search_action_transit_copy([
183 target_copy => $copy->id, # NOT barcode ($self->id)
184 dest_recv_time => undef
194 syslog('LOG_WARNING', "OILS: Item(".$copy->barcode.
195 ") status is In Transit, but no action.transit_copy found!") unless $transit;
203 # fetch captured hold.
204 # Assume transit has already beeen fetched
207 my $copy = $self->{copy} or return;
208 my $e = OpenILS::SIP->editor();
210 if( ($copy->status->id == OILS_COPY_STATUS_ON_HOLDS_SHELF) ||
211 ($self->{transit} and $self->{transit}->copy_status == OILS_COPY_STATUS_ON_HOLDS_SHELF) ) {
212 # item has been captured for a hold
214 my $hold = $e->search_action_hold_request([
216 current_copy => $copy->id,
217 capture_time => {'!=' => undef},
218 cancel_time => undef,
219 fulfillment_time => undef
225 ahr => ['pickup_lib']
230 syslog('LOG_WARNING', "OILS: Item(".$copy->barcode.
231 ") is captured for a hold, but there is no matching hold request") unless $hold;
239 sub run_attr_script {
241 return 1 if $self->{ran_script};
242 $self->{ran_script} = 1;
244 if($self->{legacy_script_support}){
246 syslog('LOG_DEBUG', "Legacy script support is ON");
247 my $config = OpenILS::SIP->config();
248 my $path = $config->{implementation_config}->{scripts}->{path};
249 my $item_config_script = $config->{implementation_config}->{scripts}->{item_config};
251 $path = ref($path) eq 'ARRAY' ? $path : [$path];
252 my $path_str = join(", ", @$path);
254 syslog('LOG_DEBUG', "OILS: Script path = [$path_str], Item config script = $item_config_script");
256 my $runner = OpenILS::Application::Circ::ScriptBuilder->build({
257 copy => $self->{copy},
258 editor => OpenILS::SIP->editor(),
261 $runner->add_path($_) for @$path;
262 $runner->load($item_config_script);
264 unless( $self->{item_config_result} = $runner->run ) { # assignment, not comparison
266 warn "Item config script [$path_str : $item_config_script] failed to run: $@\n";
267 syslog('LOG_ERR', "OILS: Item config script [$path_str : $item_config_script] failed to run: $@");
275 # use the in-db circ modifier configuration
276 my $config = {magneticMedia => 'f', SIPMediaType => '001'}; # defaults
277 my $mod = $self->{copy}->circ_modifier;
280 my $mod_obj = OpenILS::SIP->editor()->retrieve_config_circ_modifier($mod);
282 $config->{magneticMedia} = $mod_obj->magnetic_media;
283 $config->{SIPMediaType} = $mod_obj->sip2_media_type;
287 $self->{item_config_result} = { item_config => $config };
299 return 0 unless $self->run_attr_script;
300 my $mag = $self->{item_config_result}->{item_config}->{magneticMedia} || '';
301 syslog('LOG_DEBUG', "OILS: magnetic = $mag");
302 return ($mag and $mag =~ /t(rue)?/io) ? 1 : 0;
307 return 0 unless $self->run_attr_script;
308 my $media = $self->{item_config_result}->{item_config}->{SIPMediaType} || '';
309 syslog('LOG_DEBUG', "OILS: media type = $media");
310 return ($media) ? $media : '001';
315 my $t = ($self->{mods}) ? $self->{mods}->title : $self->{copy}->dummy_title;
316 return OpenILS::SIP::clean_text($t);
319 sub permanent_location {
321 return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
324 sub current_location {
326 return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
335 # 05 Charged; not to be recalled until earliest recall date
338 # 08 Waiting on hold shelf
339 # 09 Waiting to be re-shelved
340 # 10 In transit between library locations
341 # 11 Claimed returned
344 sub sip_circulation_status {
346 my $stat = $self->{copy}->status->id;
348 return '02' if $stat == OILS_COPY_STATUS_ON_ORDER;
349 return '03' if $stat == OILS_COPY_STATUS_AVAILABLE;
350 return '04' if $stat == OILS_COPY_STATUS_CHECKED_OUT;
351 return '06' if $stat == OILS_COPY_STATUS_IN_PROCESS;
352 return '08' if $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF;
353 return '09' if $stat == OILS_COPY_STATUS_RESHELVING;
354 return '10' if $stat == OILS_COPY_STATUS_IN_TRANSIT;
355 return '12' if $stat == OILS_COPY_STATUS_LOST;
356 return '13' if $stat == OILS_COPY_STATUS_MISSING;
361 sub sip_security_marker {
362 return '02'; # FIXME? 00-other; 01-None; 02-Tattle-Tape Security Strip (3M); 03-Whisper Tape (3M)
366 return '01'; # FIXME? 01-09 enumerated in spec. We just use O1-other/unknown.
377 return OpenILS::SIP->config()->{implementation_config}->{currency};
382 return OpenILS::SIP::clean_text($self->{copy}->circ_lib->shortname);
390 sub hold_queue_position { # TODO
391 my ($self, $patron_id) = @_;
398 # this should force correct circ fetching
399 require OpenILS::Utils::CStoreEditor;
400 my $e = OpenILS::Utils::CStoreEditor->new(xact => 1);
401 #my $e = OpenILS::SIP->editor();
403 my $circ = $e->search_action_circulation(
404 { target_copy => $self->{copy}->id, checkin_time => undef } )->[0];
409 syslog('LOG_INFO', "OILS: No open circ found for copy");
413 my $due = OpenILS::SIP->format_date($circ->due_date, 'due');
414 syslog('LOG_DEBUG', "OILS: Found item due date = $due");
418 sub recall_date { # TODO
424 # Note: If the held item is in transit, this will be an approximation of shelf
425 # expire time, since the time is not set until the item is checked in at the pickup location
426 my %shelf_expire_setting_cache;
427 sub hold_pickup_date {
429 my $copy = $self->{copy};
430 my $hold = $self->{hold} or return 0;
432 my $date = $hold->shelf_expire_time;
435 # hold has not hit the shelf. create a best guess.
437 my $interval = $shelf_expire_setting_cache{$hold->pickup_lib->id} ||
438 $U->ou_ancestor_setting_value(
439 $hold->pickup_lib->id,
440 'circ.holds.default_shelf_expire_interval');
442 $shelf_expire_setting_cache{$hold->pickup_lib->id} = $interval;
445 my $seconds = OpenSRF::Utils->interval_to_seconds($interval);
446 $date = DateTime->now->add(seconds => $seconds);
447 $date = $date->strftime('%FT%T%z') if $date;
451 return OpenILS::SIP->format_date($date) if $date;
456 # message to display on console
459 return OpenILS::SIP::clean_text($self->{screen_msg}) || '';
466 return OpenILS::SIP::clean_text($self->{print_line}) || '';
470 # An item is available for a patron if
471 # 1) It's not checked out and (there's no hold queue OR patron
472 # is at the front of the queue)
474 # 2) It's checked out to the patron and there's no hold queue
476 my ($self, $for_patron) = @_;
478 my $stat = $self->{copy}->status->id;
480 $stat == OILS_COPY_STATUS_AVAILABLE or
481 $stat == OILS_COPY_STATUS_RESHELVING;
492 OpenILS::SIP::Item - SIP abstraction layer for OpenILS Items.
496 =head2 owning_lib vs. circ_lib
498 In Evergreen, owning_lib is the org unit that purchased the item, the place to which the item
499 should return after it's done rotating/floating to other branches (via staff intervention),
500 or some combination of those. The owning_lib, however, is not necessarily where the item
501 should be going "right now" or where it should return to by default. That would be the copy
502 circ_lib or the transit destination. (In fact, the item may B<never> go to the owning_lib for
503 its entire existence). In the context of SIP, the circ_lib more accurately describes the item's
504 permanent location, i.e. where it needs to be sent if it's not en route to somewhere else.
506 This confusion extends also to the SIP extension field of "owner". It means that the SIP owner does not
507 correspond to EG's asset.volume.owning_lib, mainly because owning_lib is effectively the "ultimate
508 owner" but not necessarily the "current owner". Because we populate SIP fields with circ_lib, the
509 owning_lib is unused by SIP.