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 OpenSRF::Utils::Logger qw/$logger/;
7 use OpenILS::Application::Circ::Holds;
8 use DateTime::Format::ISO8601;
9 use OpenSRF::Utils qw/:datetime/;
10 use Scalar::Util qw/weaken/;
11 my $U = "OpenILS::Application::AppUtils";
14 my $holdcode = "OpenILS::Application::Circ::Holds";
16 my $evt = "environment";
24 # -----------------------------------------------------------------------
34 # fetch_patron_circ_info - load info on items out, overdues, and fines.
36 # _direct - this is a hash of key/value pairs to shove directly into the
37 # script runner. Use this to cover data not covered by this module
38 # -----------------------------------------------------------------------
40 my( $class, $args ) = @_;
46 my $editor = $$args{editor};
49 $editor = new_editor(xact => 1);
53 $args->{_direct} = {} unless $args->{_direct};
54 #$args->{editor} = $editor;
56 $evt = fetch_bib_data($editor, $args);
57 push(@evts, $evt) if $evt;
58 $evt = fetch_user_data($editor, $args);
59 push(@evts, $evt) if $evt;
63 push( @e, $_->{textcode} ) for @evts;
64 $logger->info("script_builder: some events occurred: @e");
65 $logger->debug("script_builder: some events occurred: " . Dumper(\@evts));
66 $args->{_events} = \@evts;
69 my $r = build_runner($editor, $args);
70 $editor->rollback if $rollback;
79 my $runner = OpenILS::Utils::ScriptRunner->new;
82 $runner->insert( "$evt.groupTree", $gt, 1);
85 $runner->insert( "$evt.patron", $ctx->{patron}, 1);
86 $runner->insert( "$evt.copy", $ctx->{copy}, 1);
87 $runner->insert( "$evt.volume", $ctx->{volume}, 1);
88 $runner->insert( "$evt.title", $ctx->{title}, 1);
90 if( ref $ctx->{requestor} ) {
91 $runner->insert( "$evt.requestor", $ctx->{requestor}, 1);
92 if($ctx->{requestor}->ws_ou) {
93 $runner->insert( "$evt.location",
94 $editor->retrieve_actor_org_unit($ctx->{requestor}->ws_ou), 1);
98 $runner->insert( "$evt.patronItemsOut", $ctx->{patronItemsOut}, 1 );
99 $runner->insert( "$evt.patronOverdueCount", $ctx->{patronOverdue}, 1 );
100 $runner->insert( "$evt.patronFines", $ctx->{patronFines}, 1 );
102 $runner->insert("$evt.$_", $ctx->{_direct}->{$_}, 1) for keys %{$ctx->{_direct}};
104 insert_org_methods( $editor, $runner );
105 insert_copy_methods( $editor, $ctx, $runner );
106 insert_user_funcs( $editor, $ctx, $runner );
118 acp => [ 'location', 'status', 'circ_lib', 'age_protect', 'call_number' ],
124 $ctx->{copy_id} = $ctx->{copy}->id
125 unless $ctx->{copy_id} or $ctx->{copy_barcode};
130 if($ctx->{copy_id}) {
131 $copy = $e->retrieve_asset_copy(
132 [$ctx->{copy_id}, $flesh ]) or return $e->event;
134 } elsif( $ctx->{copy_barcode} ) {
136 $copy = $e->search_asset_copy(
137 [{barcode => $ctx->{copy_barcode}, deleted => 'f'}, $flesh ])->[0]
141 return undef unless $copy;
143 my $vol = $copy->call_number;
144 my $rec = $vol->record;
145 $ctx->{copy} = $copy;
146 $ctx->{volume} = $vol;
147 $copy->call_number($vol->id);
148 $ctx->{title} = $rec;
149 $vol->record($rec->id);
156 sub fetch_user_data {
162 au => [ qw/ profile home_ou card / ],
163 aou => [ 'ou_type' ],
167 if( $ctx->{patron} ) {
168 $ctx->{patron_id} = $ctx->{patron}->id unless $ctx->{patron_id};
173 if( $ctx->{patron_id} ) {
174 $patron = $e->retrieve_actor_user([$ctx->{patron_id}, $flesh]);
176 } elsif( $ctx->{patron_barcode} ) {
178 my $card = $e->search_actor_card(
179 { barcode => $ctx->{patron_barcode} } )->[0] or return $e->event;
181 $patron = $e->search_actor_user(
182 [{ card => $card->id }, $flesh ]
183 )->[0] or return $e->event;
185 } elsif( $ctx->{fetch_patron_by_circ_copy} ) {
187 if( my $copy = $ctx->{copy} ) {
188 my $circs = $e->search_action_circulation(
189 { target_copy => $copy->id, checkin_time => undef });
191 if( my $circ = $circs->[0] ) {
192 $patron = $e->retrieve_actor_user([$circ->usr, $flesh])
198 return undef unless $ctx->{patron} = $patron;
202 $ctx->{requestor} = $ctx->{requestor} || $e->requestor;
204 if( $ctx->{fetch_patron_circ_info} ) {
205 my $circ_counts = $U->storagereq('open-ils.storage.actor.user.checked_out.count', $patron->id);
207 $ctx->{patronOverdue} = $circ_counts->{overdue} + $circ_counts->{long_overdue};
208 my $out = $ctx->{patronOverdue} + $circ_counts->{out};
210 $ctx->{patronItemsOut} = $out
211 unless( $ctx->{patronItemsOut} and $ctx->{patronItemsOut} > $out );
213 $logger->debug("script_builder: patron overdue count is " . $ctx->{patronOverdue});
216 if( $ctx->{fetch_patron_money_info} ) {
217 $ctx->{patronFines} = $U->patron_money_owed($patron->id);
218 $logger->debug("script_builder: patron fines determined to be ".$ctx->{patronFines});
221 unless( $ctx->{ignore_user_status} ) {
222 return OpenILS::Event->new('PATRON_INACTIVE')
223 unless $U->is_true($patron->active);
225 return OpenILS::Event->new('PATRON_CARD_INACTIVE')
226 unless $U->is_true($patron->card->active);
228 my $expire = DateTime::Format::ISO8601->new->parse_datetime(
229 cleanse_ISO8601($patron->expire_date));
231 return OpenILS::Event->new('PATRON_ACCOUNT_EXPIRED')
232 if( CORE::time > $expire->epoch ) ;
244 $GROUP_TREE = $e->search_permission_grp_tree(
249 flesh_fields => { pgt => ['children'] }
256 return undef unless $tree;
257 $GROUP_SET{$tree->id} = $tree;
258 if( $tree->children ) {
259 flatten_groups($e, $_) for @{$tree->children};
263 sub flatten_org_tree {
265 return undef unless $tree;
266 push( @ORG_LIST, $tree );
267 if( $tree->children ) {
268 flatten_org_tree($_) for @{$tree->children};
274 sub insert_org_methods {
275 my ( $editor, $runner ) = @_;
278 $ORG_TREE = $editor->search_actor_org_unit(
280 {"parent_ou" => undef },
283 flesh_fields => { aou => ['children'] },
284 order_by => { aou => 'name'}
288 flatten_org_tree($ORG_TREE);
294 $r->insert(__OILS_FUNC_isOrgDescendent =>
296 my( $write_key, $sname, $id ) = @_;
297 my ($parent) = grep { $_->shortname eq $sname } @ORG_LIST;
298 my ($child) = grep { $_->id == $id } @ORG_LIST;
299 my $val = is_org_descendent( $parent, $child );
300 $logger->debug("script_builder: is_org_desc $sname:$id returned val $val, writing to $write_key");
301 $r->insert($write_key, $val, 1) if $val;
306 $r->insert(__OILS_FUNC_hasCommonAncestor =>
308 my( $write_key, $orgid1, $orgid2, $depth ) = @_;
309 my $val = has_common_ancestor( $orgid1, $orgid2, $depth );
310 $logger->debug("script_builder: has_common_ancestor resturned $val");
311 $r->insert($write_key, $val, 1) if $val;
318 sub is_org_descendent {
319 my( $parent, $child ) = @_;
320 return 0 unless $parent and $child;
321 $logger->debug("script_builder: is_org_desc checking parent=".$parent->id.", child=".$child->id);
323 return 0 unless defined $child->parent_ou;
324 return 1 if $parent->id == $child->id;
325 } while( ($child) = grep { $_->id == $child->parent_ou } @ORG_LIST );
329 sub has_common_ancestor {
330 my( $org1, $org2, $depth ) = @_;
331 return 0 unless $org1 and $org2;
332 $logger->debug("script_builder: has_common_ancestor checking orgs $org1 : $org2");
334 return 1 if $org1 == $org2;
335 ($org1) = grep { $_->id == $org1 } @ORG_LIST;
336 ($org2) = grep { $_->id == $org2 } @ORG_LIST;
338 my $p1 = find_parent_at_depth($org1, $depth);
339 my $p2 = find_parent_at_depth($org2, $depth);
341 return 1 if $p1->id == $p2->id;
346 sub find_parent_at_depth {
349 return undef unless $org and $depth;
352 my ($t) = grep { $_->id == $org->ou_type } @OU_TYPES;
353 return $org if $t->depth == $depth;
354 } while( ($org) = grep { $_->id == $org->parent_ou } @ORG_LIST );
361 @OU_TYPES = @{new_editor()->retrieve_all_actor_org_unit_type()};
364 sub insert_copy_methods {
365 my( $e, $ctx, $runner ) = @_;
366 my $reqr = $ctx->{requestor} || $e->requestor;
367 if( my $copy = $ctx->{copy} ) {
368 $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_best_hold', sub {
370 $logger->debug("script_builder: searching for permitted hold for copy ".$copy->barcode);
371 my ($hold) = $holdcode->find_nearest_permitted_hold( $e, $copy, $reqr, 1 ); # do we need a new editor here since the xact may be dead??
372 $runner->insert( $key, $hold, 1 );
378 sub insert_user_funcs {
379 my( $e, $ctx, $runner ) = @_;
381 # tells how many holds a user has
382 $runner->insert(__OILS_FUNC_userHoldCount =>
384 my( $write_key, $userid ) = @_;
385 my $val = $holdcode->__user_hold_count(new_editor(), $userid);
386 $logger->info("script_runner: user hold count is $val");
387 $runner->insert($write_key, $val, 1) if $val;
392 $runner->insert(__OILS_FUNC_userCircsByCircmod =>
394 my( $write_key, $userid ) = @_;
395 use OpenSRF::Utils::JSON;
397 # this bug ugly thing generates a count of checkouts by circ_modifier
400 "acp" => ["circ_modifier"],
402 "aggregate"=> OpenSRF::Utils::JSON->true,
403 "transform"=>"count",
408 "from"=>{"acp"=>{"circ"=>{"field"=>"target_copy","fkey"=>"id"}}},
411 "checkin_time"=>undef,
414 {"stop_fines"=>["MAXFINES","LONGOVERDUE"]},
415 {"stop_fines"=>undef}
421 my $mods = $e->json_query($query);
423 $breakdown->{$_->{circ_modifier}} = $_->{count} for @$mods;
424 $logger->info("script_runner: Loaded checkouts by circ_modifier breakdown:".
425 OpenSRF::Utils::JSON->perl2JSON($breakdown));
426 $runner->insert($write_key, $breakdown, 1) if (keys %$breakdown);