1 package OpenILS::Application::Circ::ScriptBuilder;
2 use strict; use warnings;
3 use OpenILS::Utils::ScriptRunner;
4 use OpenILS::Utils::CStoreEditor qw/:funcs/;
5 use OpenILS::Application::AppUtils;
6 use OpenILS::Application::Actor;
7 use OpenSRF::Utils::Logger qw/$logger/;
8 use OpenILS::Application::Circ::Holds;
9 use DateTime::Format::ISO8601;
10 use OpenSRF::Utils qw/:datetime/;
11 use Scalar::Util qw/weaken/;
12 my $U = "OpenILS::Application::AppUtils";
15 my $holdcode = "OpenILS::Application::Circ::Holds";
17 my $evt = "environment";
25 # -----------------------------------------------------------------------
35 # fetch_patron_circ_info - load info on items out, overdues, and fines.
37 # _direct - this is a hash of key/value pairs to shove directly into the
38 # script runner. Use this to cover data not covered by this module
39 # -----------------------------------------------------------------------
41 my( $class, $args ) = @_;
46 my $editor = $$args{editor} || new_editor();
48 $args->{_direct} = {} unless $args->{_direct};
50 $evt = fetch_bib_data($editor, $args);
51 push(@evts, $evt) if $evt;
52 $evt = fetch_user_data($editor, $args);
53 push(@evts, $evt) if $evt;
57 push( @e, $_->{textcode} ) for @evts;
58 $logger->info("script_builder: some events occurred: @e");
59 $logger->debug("script_builder: some events occurred: " . Dumper(\@evts));
60 $args->{_events} = \@evts;
63 return build_runner($editor, $args);
71 my $runner = OpenILS::Utils::ScriptRunner->new;
74 $runner->insert( "$evt.groupTree", $gt, 1);
77 $runner->insert( "$evt.patron", $ctx->{patron}, 1);
78 $runner->insert( "$evt.copy", $ctx->{copy}, 1);
79 $runner->insert( "$evt.volume", $ctx->{volume}, 1);
80 $runner->insert( "$evt.title", $ctx->{title}, 1);
82 if( ref $ctx->{requestor} ) {
83 $runner->insert( "$evt.requestor", $ctx->{requestor}, 1);
84 if($ctx->{requestor}->ws_ou) {
85 $runner->insert( "$evt.location",
86 $editor->retrieve_actor_org_unit($ctx->{requestor}->ws_ou), 1);
90 $runner->insert( "$evt.patronItemsOut", $ctx->{patronItemsOut}, 1 );
91 $runner->insert( "$evt.patronOverdueCount", $ctx->{patronOverdue}, 1 );
92 $runner->insert( "$evt.patronFines", $ctx->{patronFines}, 1 );
94 $runner->insert("$evt.$_", $ctx->{_direct}->{$_}, 1) for keys %{$ctx->{_direct}};
96 insert_org_methods( $editor, $runner );
97 insert_copy_methods( $editor, $ctx, $runner );
108 my $flesh = { flesh => 1, flesh_fields => { acp => [ 'location', 'status', 'circ_lib' ] } };
110 if($ctx->{copy_id}) {
111 $ctx->{copy} = $e->retrieve_asset_copy(
112 [$ctx->{copy_id}, $flesh ]) or return $e->event;
114 } elsif( $ctx->{copy_barcode} ) {
116 $ctx->{copy} = $e->search_asset_copy(
117 [{barcode => $ctx->{copy_barcode}, deleted => 'f'}, $flesh ])->[0]
122 return undef unless my $copy = $ctx->{copy};
124 $copy->location($e->retrieve_asset_copy_location($copy->location))
125 unless( ref $copy->location );
127 $copy->status($e->retrieve_config_copy_status($copy->status))
128 unless( ref $copy->status );
131 $e->retrieve_actor_org_unit($copy->circ_lib))
132 unless ref $copy->circ_lib;
134 $ctx->{volume} = $e->retrieve_asset_call_number(
135 $ctx->{copy}->call_number) or return $e->event;
137 $ctx->{title} = $e->retrieve_biblio_record_entry(
138 $ctx->{volume}->record) or return $e->event;
141 $e->retrieve_config_rules_age_hold_protect($copy->age_protect))
142 if $ctx->{flesh_age_protect} and $copy->age_protect;
149 sub fetch_user_data {
152 if(!$ctx->{patron}) {
154 if( $ctx->{patron_id} ) {
155 $ctx->{patron} = $e->retrieve_actor_user($ctx->{patron_id});
157 } elsif( $ctx->{patron_barcode} ) {
159 my $card = $e->search_actor_card(
160 { barcode => $ctx->{patron_barcode} } )->[0] or return $e->event;
162 $ctx->{patron} = $e->search_actor_user(
163 { card => $card->id })->[0] or return $e->event;
165 } elsif( $ctx->{fetch_patron_by_circ_copy} ) {
167 if( my $copy = $ctx->{copy} ) {
168 my $circs = $e->search_action_circulation(
169 { target_copy => $copy->id, checkin_time => undef });
171 if( my $circ = $circs->[0] ) {
172 $ctx->{patron} = $e->retrieve_actor_user($circ->usr)
179 return undef unless my $patron = $ctx->{patron};
181 unless( $ctx->{ignore_user_status} ) {
182 return OpenILS::Event->new('PATRON_INACTIVE')
183 unless $U->is_true($patron->active);
185 $patron->card($e->retrieve_actor_card($patron->card))
186 unless ref $patron->card;
188 return OpenILS::Event->new('PATRON_CARD_INACTIVE')
189 unless $U->is_true($patron->card->active);
191 my $expire = DateTime::Format::ISO8601->new->parse_datetime(
192 clense_ISO8601($patron->expire_date));
194 return OpenILS::Event->new('PATRON_ACCOUNT_EXPIRED')
195 if( CORE::time > $expire->epoch ) ;
199 $e->retrieve_actor_org_unit($patron->home_ou) )
200 unless ref $patron->home_ou;
202 $patron->home_ou->ou_type(
203 $patron->home_ou->ou_type->id)
204 if ref $patron->home_ou->ou_type;
207 $GROUP_TREE = $e->search_permission_grp_tree(
212 flesh_fields => { pgt => ['children'] }
217 flatten_groups($GROUP_TREE);
220 $patron->profile( $GROUP_SET{$patron->profile} )
221 unless ref $patron->profile;
224 $ctx->{requestor} = $ctx->{requestor} || $e->requestor;
226 # this could alter the requestor object within the editor..
227 #if( my $req = $ctx->{requestor} ) {
228 # $req->home_ou( $e->retrieve_actor_org_unit($requestor->home_ou) );
229 # $req->ws_ou( $e->retrieve_actor_org_unit($requestor->ws_ou) );
232 if( $ctx->{fetch_patron_circ_info} ) {
234 OpenILS::Application::Actor::_checked_out(1, $e, $patron->id);
236 $ctx->{patronOverdue} = $circ_counts->{overdue} || 0;
237 $ctx->{patronItemsOut} = $ctx->{patronOverdue} + $circ_counts->{out};
238 $logger->debug("script_builder: patron overdue count is " . $ctx->{patronOverdue});
241 if( $ctx->{fetch_patron_money_info} ) {
243 # my $fxacts = $e->search_money_billable_transaction_summary(
244 # { usr => $patron->id, balance_owed => { "!=" => 0 }, xact_finish => undef });
247 # $fines += $_->balance_owed for @$fxacts;
248 # $ctx->{patronFines} = $fines;
249 $ctx->{patronFines} = $U->patron_money_owed($patron->id);
250 $logger->debug("script_builder: patron fines determined to be ".$ctx->{patronFines});
259 return undef unless $tree;
260 $GROUP_SET{$tree->id} = $tree;
261 if( $tree->children ) {
262 flatten_groups($_) for @{$tree->children};
266 sub flatten_org_tree {
268 return undef unless $tree;
269 push( @ORG_LIST, $tree );
270 if( $tree->children ) {
271 flatten_org_tree($_) for @{$tree->children};
277 sub insert_org_methods {
278 my ( $editor, $runner ) = @_;
281 $ORG_TREE = $editor->search_actor_org_unit(
283 {"parent_ou" => undef },
286 flesh_fields => { aou => ['children'] },
287 order_by => { aou => 'name'}
291 flatten_org_tree($ORG_TREE);
297 $r->insert(__OILS_FUNC_isOrgDescendent =>
299 my( $write_key, $sname, $id ) = @_;
300 my ($parent) = grep { $_->shortname eq $sname } @ORG_LIST;
301 my ($child) = grep { $_->id == $id } @ORG_LIST;
302 my $val = is_org_descendent( $parent, $child );
303 $logger->debug("script_builder: is_org_desc returned val $val, writing to $write_key");
304 $r->insert($write_key, $val, 1) if $val;
309 $r->insert(__OILS_FUNC_hasCommonAncestor =>
311 my( $write_key, $orgid1, $orgid2, $depth ) = @_;
312 my $val = has_common_ancestor( $orgid1, $orgid2, $depth );
313 $logger->debug("script_builder: has_common_ancestor resturned $val");
314 $r->insert($write_key, $val, 1) if $val;
321 sub is_org_descendent {
322 my( $parent, $child ) = @_;
323 return 0 unless $parent and $child;
324 $logger->debug("script_builder: is_org_desc checking parent=".$parent->id.", child=".$child->id);
326 return 0 unless defined $child->parent_ou;
327 return 1 if $parent->id == $child->id;
328 } while( ($child) = grep { $_->id == $child->parent_ou } @ORG_LIST );
332 sub has_common_ancestor {
333 my( $org1, $org2, $depth ) = @_;
334 return 0 unless $org1 and $org2;
335 $logger->debug("script_builder: has_common_ancestor checking orgs $org1 : $org2");
337 return 1 if $org1 == $org2;
338 ($org1) = grep { $_->id == $org1 } @ORG_LIST;
339 ($org2) = grep { $_->id == $org2 } @ORG_LIST;
341 my $p1 = find_parent_at_depth($org1, $depth);
342 my $p2 = find_parent_at_depth($org2, $depth);
344 return 1 if $p1->id == $p2->id;
349 sub find_parent_at_depth {
354 my ($t) = grep { $_->id == $org->ou_type } @OU_TYPES;
355 return $org if $t->depth == $depth;
356 } while( ($org) = grep { $_->id == $org->parent_ou } @ORG_LIST );
363 @OU_TYPES = @{new_editor()->retrieve_all_actor_org_unit_type()};
366 sub insert_copy_methods {
367 my( $e, $ctx, $runner ) = @_;
368 if( my $copy = $ctx->{copy} ) {
369 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_best_hold', sub {
371 $logger->debug("script_builder: searching for permitted hold for copy ".$copy->barcode);
372 my ($hold) = $holdcode->find_nearest_permitted_hold(
373 OpenSRF::AppSession->create('open-ils.storage'), $copy, $e->requestor );
374 $runner->insert( $key, $hold, 1 );